chore: new release processes - #899
joaodordio wants to merge 4 commits into
Conversation
Replaces the existing ad-hoc release workflows with a two-step process: - Prepare Release: bumps version, updates changelog, opens PR, creates GitHub draft release - Publish Release: promotes draft, tags main, publishes to npm, posts Slack Ref: SDK release process team agreement 2026-09-11
|
Coverage Impact This PR will not change total coverage. 🚦 See full report on Qlty Cloud »🛟 Help
|
|
This PR vs Changelog Check is failing. What the code does What the spec/rule says Why it conflicts Suggested action Satisfy Changelog Check before merge. |
… grep
- Add ref: master/main to actions/checkout in all prepare/publish workflows
so workflows always operate on the default branch regardless of dispatch ref
- Replace shell-injection-prone ${{ steps...outputs.notes }} pattern with
--notes-file using $RUNNER_TEMP/release-notes.md (safe from backticks/quotes
in changelog content)
- Treat empty [Unreleased] section as a hard error in prepare-release
- Fix CHANGELOG verification grep: grep -qE "^## \[VERSION\]" (anchored,
prevents substring matches and prefix collisions like 3.1.0 vs 3.1.0-rc1)
Replace SDK_RELEASE_TOKEN (iOS) and GITHUB_TOKEN (all repos) with a short-lived installation token from the iterable-sdk-release GitHub App, generated via actions/create-github-app-token@v1. Benefits: - App token triggers CI on PRs it creates (GITHUB_TOKEN cannot) - 1h TTL vs long-lived PAT - Workflow-scoped permissions so we can push .github/workflows/ files Required credentials (repo variable + secret, or set at org level): vars.ITERABLE_SDK_RELEASE_APP_ID secrets.ITERABLE_SDK_RELEASE_APP_PRIVATE_KEY
| - name: Verify release is ready | ||
| run: | | ||
| if ! grep -qE "^## $VERSION\b" CHANGELOG.md; then | ||
| echo "::error::CHANGELOG.md has no entry for $VERSION. Merge the prepare-release PR to master before running this workflow." | ||
| exit 1 |
There was a problem hiding this comment.
Plus line 56/57. Publish does not verify that master HEAD is the version being published
What the code does: “Verify release is ready” is only grep -qE "^## $VERSION\b" CHANGELOG.md. Then npm publish --provenance publishes whatever package.json on master currently is. There is no check that package.json / src/itblBuildInfo.ts equal $VERSION. ## 3.1.0 is already on master today, so Publish 3.1.0 would pass the grep while npm publish would ship 3.1.0 or a newer prepared version depending on what is in package.json. gh release edit "$VERSION" --latest then operates on the GitHub release named by the input, not by the npm package version.
What the spec says: “Verifies the prepare-release PR has been merged (checks CHANGELOG on master)” and “Publishes to npm (@iterable/react-native-sdk)”. The role of that gate is to prove this version was prepared and merged, then publish that version.
Why it conflicts: A historical ## X.Y.Z header always remains in CHANGELOG. The grep cannot tell “3.2.0 was just merged” from “3.1.0 shipped last quarter.” npm and GitHub can then diverge (wrong GitHub release marked Latest, or npm publishing a different version than the input). [caused by change]
Suggested action: The author should make the ready-gate prove identity at master HEAD: changelog header and package.json / itblBuildInfo.ts equal the input version, and refuse when they don’t. Historical headers must not count as “prepare PR merged.”
| - name: Create Pull Request | ||
| uses: peter-evans/create-pull-request@4e1beaa7521e8b457b572c090b25bd3db56bf1c5 # v5 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| title: "SDK-${{ github.event.inputs.ticket }}: Prepare for Release ${{ github.event.inputs.version }}" | ||
| body: | | ||
| # Prepare for Release ${{ github.event.inputs.version }} | ||
|
|
||
| ## SDK Release Checklist | ||
| - [ ] CHANGELOG.md updated with release notes under the new version header | ||
| - [ ] Version bumped in package.json | ||
| - [ ] src/itblBuildInfo.ts regenerated | ||
| - [ ] README.md reviewed (if needed) | ||
| - [ ] All tests passing | ||
| - [ ] Documentation updated (if needed) | ||
| branch: "release/SDK-${{ github.event.inputs.ticket }}-${{ github.event.inputs.version }}" | ||
| commit-message: "[SDK-${{ github.event.inputs.ticket }}]: Prepare for release ${{ github.event.inputs.version }}" | ||
| labels: release | ||
| delete-branch: true |
There was a problem hiding this comment.
vs .github/workflows/validate-release.yml ~L34–37: Generated release PRs cannot pass existing release validation
What the code does: Prepare applies labels: release and opens a PR whose body is only the checklist. validate-release.yml runs on that label and fails unless the body contains github.com/Iterable/iterable-docs/pull/<n>.
What the spec says: “Opens a release PR with a checklist.” Project rule: release-labeled PRs must include a docs PR link (validate-release.yml; PR template “Docs PR if applicable”).
Why it conflicts: The two-step flow’s first PR is red on an existing check as soon as it is opened. The checklist says “Documentation updated (if needed)” but the validator always requires a docs URL. [caused by change]
Suggested action: The author should make the generated PR satisfy (or explicitly skip, if that is the new policy) Validate Release PR, typically a docs-PR input or a body slot the validator already accepts, before this lands.
| env: | ||
| VERSION: ${{ github.event.inputs.version }} |
There was a problem hiding this comment.
And then ~L34–38: Publish does not validate the version input
What the code does: $VERSION is taken from workflow_dispatch with no format check (Prepare has one). It is interpolated into grep -E "^## $VERSION\b".
What the spec says: Publish input is “Version to publish (e.g., 3.6.0).” Prepare already defines the allowed format.
Why it conflicts: An unvalidated value is an ERE on CHANGELOG, so the ready-gate can match the wrong header. [caused by change]
Suggested action: The author should apply the same version-format gate on Publish before grep or gh/npm.
| - name: Publish to npm | ||
| run: npm publish --provenance | ||
|
|
||
| - name: Publish draft GitHub release and tag main | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| run: | | ||
| gh release edit "$VERSION" \ | ||
| --draft=false \ | ||
| --target "$(git rev-parse HEAD)" \ | ||
| --latest |
There was a problem hiding this comment.
npm publish is not retry-safe relative to the GitHub release step
What the code does: npm publish --provenance runs, then gh release edit publishes the draft and tags HEAD. Spec lists draft/tag then npm. If npm succeeds and gh release edit fails (no draft, auth, etc.), a rerun dies on “cannot publish over previously published versions” and never reaches the GitHub step.
What the spec says: “Publishes the draft release and creates the tag on master at this moment” then “Publishes to npm.”
Why it conflicts: The happy path can work, but a partial failure leaves the package on npm with no tag/release and no way to finish via this workflow. Android PR #1090 uses the same distro-then-GitHub order, so this may be the team’s real sequence — the retry hole remains. [caused by change]
Suggested action: The author should make Publish finish (or skip) each step idempotently so a retry can complete the GitHub release after npm already succeeded, and align the PR body with the actual order.

Summary
Replaces the existing ad-hoc release workflows with a standardized two-step process.
No customer facing changes
Workflow 1: Prepare Release (
workflow_dispatch)Inputs:
version,ticketpackage.json+ regeneratessrc/itblBuildInfo.tsviascripts/autoCreatePackageInfo.jsWorkflow 2: Publish Release (
workflow_dispatch)Input:
version@iterable/react-native-sdk) via OIDC trusted publishing#eng-sdk-teamon SlackFiles changed
.github/workflows/prepare-release.yml.github/workflows/publish-release.ymlSecrets to add
SLACK_WEBHOOKmust be configured in repo Settings > Secrets > Actions before the first Publish Release run.No customer facing changes