Skip to content

[CLNP-8355] [ci]: add automated release workflow and update CI to Node 24#1414

Merged
sf-tyler-jeong merged 42 commits into
mainfrom
feature/CLNP-8355
May 13, 2026
Merged

[CLNP-8355] [ci]: add automated release workflow and update CI to Node 24#1414
sf-tyler-jeong merged 42 commits into
mainfrom
feature/CLNP-8355

Conversation

@sf-tyler-jeong
Copy link
Copy Markdown
Contributor

@sf-tyler-jeong sf-tyler-jeong commented Apr 2, 2026

Add Jira-integrated automated release workflow and modernize CI infrastructure.

Release Automation (release-workflow.yml - new)

  • Automated release pipeline triggered via Jira ticket approval
  • Steps: build → test → npm publish (OIDC provenance) → merge to main → git tag → GitHub Release → Slack notification → Jira transition
  • On failure: automatic Jira ticket rollback to CONDITIONAL_RELEASE_APPROVED
  • Uses CHANGELOG_DRAFT.md for GitHub Release body to avoid duplicate titles

CI Updates

  • Update actions/checkout v4 → v6, actions/setup-node v4 → v6 across all workflows
  • Update build-and-test.yml Node version from 16.19.1 → 24 (LTS Krypton)
  • Migrate NPM auth from token-based to OIDC trusted publishing in release workflow
  • Normalize secret name npm_tokenNPM_TOKEN in package-publish.yml

New Files

  • CHANGELOG_DRAFT.md: template for per-release changelog (used by release workflow and Jira ticket)

Fixes CLNP-8355

Changelogs

  • Added automated release workflow with Jira integration
  • Added test step in release workflow before npm publish
  • Updated all CI workflows to Node 24 and latest GitHub Actions
  • Migrated npm publish to OIDC trusted publishing with provenance
  • Added CHANGELOG_DRAFT.md template for release notes

Checklist

  • All tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • Public components / utils / props are appropriately exported
  • I have added necessary documentation (if appropriate)

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 2, 2026

Deploy Preview for sendbird-uikit-react ready!

Name Link
🔨 Latest commit ecce64d
🔍 Latest deploy log https://app.netlify.com/projects/sendbird-uikit-react/deploys/6a0419755e9ab7000991cb89
😎 Deploy Preview https://deploy-preview-1414--sendbird-uikit-react.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread .github/workflows/package-publish.yml
Comment thread .github/workflows/package-publish.yml
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0fd80e71ef

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .github/workflows/release-workflow.yml
sf-tyler-jeong and others added 11 commits April 2, 2026 12:18
…flow shell commands

Prevent potential shell injection by using env variables for github.ref_name
and github.event.repository.name. Improve rollback step to check existence
before reinstalling dependencies instead of silently swallowing errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n release workflow

Use python3 import check instead of command -v for requests package detection.
Change contents permission from read to write for git tag push.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@danney-chun
Copy link
Copy Markdown
Contributor

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bfdf72b909

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +62 to +69
git fetch origin main
git switch main
git pull origin main

- name: Publish to npm
run: |
cd ./dist
npm publish --access=public --provenance
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebuild dist after pulling main before npm publish

The workflow builds dist before merging/pulling main and then publishes without rebuilding, so the published tarball can come from the pre-merge release-branch contents rather than the commit that gets tagged/released on main. This mismatch is possible whenever main has moved or merge resolution changes files, and it can ship artifacts that do not match the Git tag/source of record.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge 후 yarn install --immutable && yarn build로 rebuild 스텝을 추가했습니다. 이제 publish되는 tarball이 main merge 후 코드와 일치합니다. (dbf4b11e)

Comment thread jest.config.js
Comment on lines +107 to +110
['jest-junit', {
outputDirectory: './test-results',
outputName: 'junit-report.xml',
}],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Configure jest-junit to emit file paths for retry logic

scripts/failed-test-retry.js only retries failures when each failed <testsuite> includes a file attribute, but the new reporter config does not enable that output. As a result, on failed test runs the retry step often finds zero failed files and exits, so the flaky-test retry path never actually works even though CI now depends on this report.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addFileAttribute: 'true' 추가하여 JUnit 리포트에 file 속성이 출력되도록 수정했습니다. 이제 failed-test-retry.js가 실패 테스트 파일을 정상적으로 파싱합니다. (dbf4b11e)

Rebuild dist after merging to main so the published tarball matches the
tagged commit. Add addFileAttribute to jest-junit config so the retry
script can locate failed test files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread CHANGELOG_DRAFT.md
@@ -0,0 +1,5 @@
### Features
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github에서 release tag 생성에 필요해서 추가 하신거 같은데, 최종 chagelog.md로 update는 언제 하게 되나요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prepare-changelog 워크플로우를 추가해서 자동화했습니다. release 브랜치 push 시 CHANGELOG_DRAFT.md 내용이 CHANGELOG.md에 자동으로 prepend되고, 실제 릴리즈 시점에 release-workflow가 날짜를 확정합니다. 개발자는 CHANGELOG_DRAFT.md만 작성하면 됩니다.
감사합니다!

sf-tyler-jeong and others added 3 commits April 28, 2026 18:21
- Add prepare-changelog.yml: prepends/replaces section in CHANGELOG.md
  when CHANGELOG_DRAFT.md or package.json changes on release/v* branches.
  Skips on bot's own commits (author email check) and empty draft templates.
- Add scripts/update-changelog.js: idempotent prepend/replace logic that
  handles multi-major-version eras (e.g., # Changelog - v3 / v4) and
  preserves the blank line after the H1 header.
- Update release-workflow.yml: add "Finalize CHANGELOG.md date" step on
  release branch before merge so the date in CHANGELOG.md matches the
  actual release day, not the last manual edit.
- CHANGELOG.md: add blank line after header for readability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CHANGELOG_DRAFT.md is no longer reset to template after release, so the
exact-template comparison would never trigger after the first release.
PR review remains the safety net for stale draft content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- release-workflow: remove continue-on-error from "Finalize CHANGELOG.md
  date" step. Silent failure would let npm publish/tag/release proceed
  with a stale date, defeating the purpose of the step. Fail-fast keeps
  CHANGELOG.md and release artifacts in sync.
- package-publish: move workflow_dispatch inputs (version, npm_tag,
  tag_suffix) to job-level env and add semver/charset validation. Replace
  remaining ${{ github.event.inputs.* }} interpolations in run blocks
  with quoted "$VAR" references to prevent shell injection. Also move
  NPM_TOKEN secret into env to match the same pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sf-tyler-jeong and others added 18 commits April 28, 2026 18:44
Pre-release gate that runs after Extract release version and before
Finalize CHANGELOG.md date. Fails the release if:

- CHANGELOG.md does not have v$VERSION as its most recent entry
  (means prepare-changelog never ran on the release branch).
- CHANGELOG_DRAFT.md is byte-identical to the previous release's section
  body in CHANGELOG.md (means the developer forgot to update the draft
  and the previous release notes would be re-published).

This catches stale-draft scenarios that would otherwise rely solely on
PR review to notice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a third failure case to check-changelog-draft.js: if the draft is
byte-identical to the placeholder template
(### Features / - Added ... / ### Fixes / - Fixed a bug where ...),
fail the release before publishing. Without this gate, an accidentally
reset/empty draft would have published placeholder copy to npm, the
GitHub Release page, and Jira.

Also drops a redundant `.replace(/\s+$/, '').trim()` chain in favor of
just `.trim()`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two safety gates added to release-workflow:

- Validate branch is a release branch: reject dispatch from non-release
  branches (main, feature/*, etc.) or non-semver release branches.
  workflow_dispatch lets admins pick any ref, and downstream steps
  assume release/vX.Y.Z without an internal guard. Now we fail fast
  with a clear error instead of relying on extract_version.py behavior.

- Wait for required checks on release branch: after Finalize pushes a
  new commit, build-and-test re-runs and the commit's check is pending.
  Without waiting, the immediate Merge step could either fail (strict
  branch protection) or merge an unverified commit (if the bot bypasses
  protection). Use `gh pr checks --watch --required` so we explicitly
  wait for required checks to pass; 30min timeout caps stuck states.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the Finalize step pushes a commit, GitHub takes a few seconds to
register the build-and-test workflow run. Without this delay,
`gh pr checks --watch --required` could observe no checks on the latest
commit and exit 0 immediately, defeating the wait. A 15s sleep covers
the registration window; if Finalize made no commit, the existing check
status is already terminal and watch returns quickly anyway.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopts the approach from #1413 directly so
that both release-workflow.yml (already OIDC) and package-publish.yml
authenticate the same way:

- Add permissions: id-token: write, contents: read at job level
- Drop NPM_TOKEN env and the .npmrc auth setup
- Add --provenance to both npm publish commands so the published
  tarballs carry GitHub Actions provenance attestations
- Add an "Update npm to latest" step because OIDC trusted publishing
  requires npm 11+ and Node 18 ships with an older bundled npm

The hardening already in this branch (input validation step, env-based
version/npm_tag/tag_suffix references, `set -x` in branch existence
check) is preserved. PR 1413 can be closed since this commit supersedes
its package-publish.yml changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match release-workflow.yml's `contents: write` so both workflows declare
the same permission set. Functionally either works since git operations
use the bot token, but consistency makes future audits simpler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two prerequisites for npm Trusted Publishing flagged in code review:

1. Bump package-publish.yml Node version 18.x → 24

   npm Trusted Publishing requires npm CLI 11.5.1+ and Node 22.14.0+.
   Node 18 with `npm install -g npm@latest` is still unsupported because
   the Node version itself is too old. Aligning with release-workflow.yml
   which already uses Node 24.

2. Correct repository.url in package template

   scripts/post_build.js generates dist/package.json from
   scripts/package.template.json, so the template's repository.url is
   what npm registry sees. Both the template and the root package.json
   pointed at sendbird-uikit-react-sources.git while origin is
   sendbird-uikit-react.git. npm Trusted Publishing verifies that
   repository.url matches the publishing GitHub repo, so the mismatch
   would fail OIDC validation. Updated both files to the correct repo
   and switched to https URL form per npm convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
npm OIDC Trusted Publishing supports only one publisher configuration
per package. The default is release-workflow.yml (automated production
releases). Running package-publish.yml requires temporarily switching
the npm Trusted Publisher's workflow filename, then restoring it.

Add an explicit comment at the top of package-publish.yml describing
this operational requirement so the dependency is visible to anyone
about to dispatch the workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new automation introduced in this PR (release-workflow.yml +
prepare-changelog.yml + check-changelog-draft.js) supersedes the manual
process described in RELEASE_GUIDE.md. The remaining internal release
narrative (Jira ticket flow, Slack channel, bot account, pr-comment-bot)
is operational detail that does not belong in a public OSS repository.

Removed files:
- RELEASE_GUIDE.md (no other file references it)
- screenshots/workflow-guide.png (only referenced by RELEASE_GUIDE.md)

Internal release procedure now lives only in the workspace-level
HOW_TO_RELEASE.md kept alongside other Sendbird internal docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
workflow_dispatch lets the operator pick any ref. Previously, dispatching
from main with version=3.18.0 would still pass the existing branch
existence check (release/v3.18.0 exists on origin) and then build dist
from main and publish it as v3.18.0. Now we fail fast if github.ref_name
does not equal release/v$VERSION, so the published artifact always comes
from the matching release branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match build-and-test.yml and release-workflow.yml which already pin
deps with --immutable. Plain `yarn install` would silently update the
lockfile during a manual publish, so the published artifact could
diverge from what was committed and reviewed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two input combos previously slipped through validation:

1. version=X.Y.Z-pre with no npm_tag → publishes to the default 'latest'
   dist-tag, so a prerelease becomes what `npm install pkg` resolves to.
2. tag_suffix without npm_tag → "Update version in package.json" step is
   gated on npm_tag, so tag_suffix is silently ignored and the publish
   proceeds as a stable release.

Add a combo check after the per-input regex validation: when npm_tag is
empty, version must not contain '-' and tag_suffix must be empty.
Operators who really want a prerelease publish must explicitly provide
npm_tag (beta, rc, etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tag=latest

Two further input combos that bypassed the previous combo check:

- version=X.Y.Z-pre with npm_tag=anything would still publish, but the
  resulting npm version (X.Y.Z-pre-NPM_TAG) is nonsensical. Since this
  workflow is supposed to build prerelease versions by combining a
  stable version + npm_tag (+ optional tag_suffix), VERSION should
  always be stable X.Y.Z.
- npm_tag=latest with a prerelease version was not blocked by the
  earlier "npm_tag empty" combo check, so a prerelease could still land
  on the latest dist-tag.

Tighten the rules:
- VERSION must match ^X.Y.Z$ (no prerelease suffix).
- npm_tag is optional but cannot be 'latest'; leave it empty for a
  stable publish to default latest.
- tag_suffix still requires npm_tag.

The earlier "npm_tag empty + version contains -" guard becomes
unreachable under the stricter VERSION regex and is removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Semver 2.0 prerelease and build identifiers allow only [0-9A-Za-z.-].
The previous regex permitted underscore (_), so an input like
npm_tag=beta_0 would produce npm version 3.18.0-beta_0, which is invalid
semver and fails at npm publish. Drop _ from the allowed character set
so we fail at validation with a clear error instead of partway through
the build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous regex (^[0-9A-Za-z.-]+$) only checked the character set,
so beta., .beta, and beta..0 all passed. When combined into a version
suffix (e.g., 3.18.0-beta.) the result is invalid semver and npm
publish would fail mid-flight.

Switch to ^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ which mirrors the semver 2.0
prerelease identifier rule: each dot-separated identifier must be
non-empty and use only [0-9A-Za-z-].

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three fixes from code review:

1. package-publish.yml — combined semver validation
   - Previous regex only checked the prerelease character set.
     beta.01 passed but 3.18.0-beta.01 is invalid semver (semver 2.0
     forbids leading zeros in numeric identifiers).
   - 1.0 / v1.4 also passed but npm rejects dist-tags that parse as
     semver/range.
   - Add a check that NPM_TAG does not match ^v?N(\.N)+$, then build
     the candidate npm version and validate against the full semver 2.0
     regex. Fails fast with the constructed string in the error message.

2. scripts/check-changelog-draft.js — empty-draft guard
   - An empty CHANGELOG_DRAFT.md was distinct from both the placeholder
     template and the previous release body, so it slipped through and
     the GitHub Release would have been created with an empty body.
     Add `if (!draft) exit 1` right after reading the file.

3. scripts/failed-test-retry.js — parse <testcase> file attribute too
   - jest-junit's addFileAttribute behavior varies by version. The
     existing parser only looked at <testsuite>; if the file attribute
     ends up on <testcase> the retry script reports zero failed files
     and the retry path silently does nothing. Add a fallback regex
     that picks up <testcase file="..."> entries containing a <failure>
     or <error> child.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous semver-range check only caught strict numeric forms like
1.0 or v1.4. npm dist-tags also reject other semver-parseable values
(1.x, 1.2.x, v1.x, 1.2.3-beta, etc.), and the manual publish would
fail late at npm publish --tag.

The simplest way to cover all parseable forms is to forbid npm_tag from
starting with a digit or v/V. Conventional dist-tags (beta, rc, next,
canary) all start with a letter and pass; numeric-led inputs are
rejected up front with a clear error.

Reference: https://docs.npmjs.com/cli/v8/commands/npm-dist-tag#caveats

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous import:
    import pkg from "./package.json" with {type: "json"};

uses JSON import attributes, which Node only supports from 18.20.0 /
20.10.0 onward. package.json declares engines.node: ">=18", so any
Node 18.0.0–18.19.x environment running `yarn build` would fail at
config parse time.

Switch to createRequire(import.meta.url) which is supported across
the whole Node 18 line, keeping the engines.node range honest. Library
consumers (who only consume dist/) are unaffected — this only matters
for environments that build the package.

Reference: https://nodejs.org/api/esm.html#import-attributes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@danney-chun danney-chun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm~

sf-tyler-jeong and others added 2 commits May 13, 2026 15:22
Document the fix for invisible zero-width spaces inserted during paste
in MessageInput that could be included in sent or updated messages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sf-tyler-jeong sf-tyler-jeong merged commit 835ac89 into main May 13, 2026
7 checks passed
@sf-tyler-jeong sf-tyler-jeong deleted the feature/CLNP-8355 branch May 13, 2026 06:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants