-
Notifications
You must be signed in to change notification settings - Fork 3
ci(release): add canary npm publishing #557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,11 +4,13 @@ on: | |
| push: | ||
| branches: | ||
| - main | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: ${{ github.workflow }}-${{ github.ref }} | ||
|
|
||
| jobs: | ||
| release: | ||
| if: github.event_name == 'push' | ||
| name: Release | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
|
|
@@ -83,3 +85,119 @@ jobs: | |
| pnpm install --frozen-lockfile | ||
| pnpm exec turbo --filter=@stakekit/widget build | ||
| npm publish "./$PACKAGE_DIR" | ||
|
|
||
| canary: | ||
| if: github.event_name == 'workflow_dispatch' | ||
| name: Publish canary | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
| environment: production | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P0 — publish authority relies on environment approval that is not configured.
Since Please configure required reviewers on the |
||
| concurrency: | ||
| group: npm-canary-publish | ||
| cancel-in-progress: false | ||
| env: | ||
| PACKAGE_DIR: packages/widget | ||
|
|
||
| steps: | ||
| # actions/checkout@v6.0.2 | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | ||
| with: | ||
| ref: ${{ github.sha }} | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Install mise | ||
| # jdx/mise-action@v4.2.3 | ||
| uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 | ||
| with: | ||
| version: 2026.5.6 | ||
|
|
||
| - name: Install dependencies | ||
| run: pnpm install --frozen-lockfile | ||
|
|
||
| - name: Install Playwright browsers | ||
| run: pnpm --filter @stakekit/widget exec playwright install | ||
|
|
||
| - name: Read npm release state | ||
| id: registry | ||
| shell: bash | ||
| run: | | ||
| package_name=$(node -p "require('./${PACKAGE_DIR}/package.json').name") | ||
| latest_version=$(npm view "${package_name}@latest" version) | ||
|
|
||
| { | ||
| echo "package_name=${package_name}" | ||
| echo "latest_version=${latest_version}" | ||
| } >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Prepare canary version | ||
| id: canary_version | ||
| env: | ||
| LATEST_VERSION: ${{ steps.registry.outputs.latest_version }} | ||
| run: pnpm --filter @stakekit/widget exec tsx scripts/prepare-canary-release.ts | ||
|
|
||
| - name: Build widget | ||
| run: pnpm exec turbo --filter=@stakekit/widget build | ||
|
|
||
| - name: Test widget | ||
| run: | | ||
| pnpm --filter @stakekit/widget exec vitest -c vite/vitest.config.scripts.ts run | ||
| pnpm --filter @stakekit/widget test | ||
|
petar-omni marked this conversation as resolved.
|
||
|
|
||
| - name: Check canary publish status | ||
| id: publish | ||
| shell: bash | ||
| env: | ||
| PACKAGE_NAME: ${{ steps.registry.outputs.package_name }} | ||
| VERSION: ${{ steps.canary_version.outputs.version }} | ||
| run: | | ||
| if npm view "${PACKAGE_NAME}@${VERSION}" version >/dev/null 2>&1; then | ||
| echo "${PACKAGE_NAME}@${VERSION} is already published; skipping." | ||
| echo "should_publish=false" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "should_publish=true" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Inspect package contents | ||
| if: steps.publish.outputs.should_publish == 'true' | ||
| run: npm pack "./$PACKAGE_DIR" --dry-run | ||
|
|
||
| - name: Publish canary to npm | ||
| if: steps.publish.outputs.should_publish == 'true' | ||
| run: npm publish "./$PACKAGE_DIR" --access public --tag canary | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1 — re-running an older failed run can move the
The Suggest moving the |
||
|
|
||
| - name: Write release summary | ||
| env: | ||
| PACKAGE_NAME: ${{ steps.registry.outputs.package_name }} | ||
| VERSION: ${{ steps.canary_version.outputs.version }} | ||
| SHOULD_PUBLISH: ${{ steps.publish.outputs.should_publish }} | ||
| run: | | ||
| if [[ "$SHOULD_PUBLISH" == "true" ]]; then | ||
| release_status="Published" | ||
| else | ||
| release_status="Already published" | ||
| fi | ||
|
|
||
| { | ||
| echo "## Widget canary release" | ||
| echo | ||
| echo "| Field | Value |" | ||
| echo "| --- | --- |" | ||
| echo "| Status | ${release_status} |" | ||
| echo "| Package | ${PACKAGE_NAME} |" | ||
| echo "| Version | ${VERSION} |" | ||
| echo "| npm tag | canary |" | ||
| echo "| Branch | ${{ github.ref_name }} |" | ||
| echo "| Commit | ${{ github.sha }} |" | ||
| echo | ||
| echo "Install the moving canary channel:" | ||
| echo | ||
| echo " pnpm add ${PACKAGE_NAME}@canary" | ||
| echo | ||
| echo "Install this exact build:" | ||
| echo | ||
| echo " pnpm add ${PACKAGE_NAME}@${VERSION}" | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # Releases | ||
|
|
||
| `@stakekit/widget` has two npm release paths: | ||
|
|
||
| - Stable releases are produced from `main` through Release Please and use the | ||
| npm `latest` tag. | ||
| - Canary releases are manually requested from a selected branch for internal | ||
| StakeKit QA and use the npm `canary` tag. | ||
|
|
||
| Canaries are not a public support channel and are never promoted into stable | ||
| releases. | ||
|
|
||
| ## Publishing authority | ||
|
|
||
| Repository writers may request a canary workflow run. Both canary and stable | ||
| npm publication use the `production` GitHub environment, which must require | ||
| approval from a release maintainer. | ||
|
|
||
| Both release paths publish from the existing | ||
| `.github/workflows/release.yml` workflow so npm trusted publishing remains | ||
| bound to one workflow identity and the `production` environment claim. | ||
|
|
||
| ## Stable releases | ||
|
|
||
| A push to `main` runs Release Please. Release Please maintains the release pull | ||
| request and creates the package Git tag and GitHub release when that pull | ||
| request is merged. | ||
|
|
||
| The `production` environment approval gates the stable release job. | ||
|
|
||
| The workflow publishes only when all of these conditions hold: | ||
|
|
||
| 1. A GitHub release exists for `@stakekit/widget@<version>`. | ||
| 2. That exact package version is not already present on npm. | ||
| 3. The tagged source builds successfully. | ||
|
|
||
| Stable releases publish without an alternate dist-tag and therefore update | ||
| `latest`. | ||
|
|
||
| ## Canary releases | ||
|
|
||
| To request a canary: | ||
|
|
||
| 1. Open **Actions** in GitHub. | ||
| 2. Select the **Release** workflow. | ||
| 3. Choose **Run workflow**. | ||
| 4. Select the branch to test. | ||
| 5. Start the workflow. | ||
| 6. Obtain release-maintainer approval for the `production` environment. | ||
|
|
||
| The selected branch must contain the manual release workflow and its | ||
| `packages/widget/package.json` version must equal the version currently | ||
| published under npm `latest`. Rebase or merge the current stable baseline | ||
| before requesting a canary from a stale branch. | ||
|
|
||
| For a branch based on `0.0.282`, workflow run `418` publishes: | ||
|
|
||
| ```text | ||
| @stakekit/widget@0.0.283-canary.418 | ||
| ``` | ||
|
|
||
| The workflow: | ||
|
|
||
| 1. Checks out the exact selected commit. | ||
| 2. Reads the current npm `latest` version. | ||
| 3. Derives `next patch + canary + GITHUB_RUN_NUMBER`. | ||
| 4. Updates `packages/widget/package.json` only in the runner. | ||
| 5. Builds the widget and runs its script and widget test suites. | ||
| 6. Inspects the package contents. | ||
| 7. Publishes with the npm `canary` dist-tag. | ||
| 8. Records the branch, commit, exact version, and install commands in the | ||
| workflow summary. | ||
|
|
||
| It does not commit the generated version, create a Git tag, or create a GitHub | ||
| release. | ||
|
|
||
| ## QA usage | ||
|
|
||
| Use the moving channel only to obtain the newest published canary: | ||
|
|
||
| ```sh | ||
| pnpm add @stakekit/widget@canary | ||
| ``` | ||
|
|
||
| Record and pin the exact version in bug reports, test environments, and QA | ||
| sign-off notes: | ||
|
|
||
| ```sh | ||
| pnpm add @stakekit/widget@0.0.283-canary.418 | ||
| ``` | ||
|
|
||
| Each successful canary publication moves the `canary` dist-tag. The moving tag | ||
| is convenient but is not a reproducible test reference. | ||
|
|
||
| ## Retries and failures | ||
|
|
||
| - Rerun the same GitHub workflow run when its commit has not changed. The run | ||
| retains its canary version, and the npm existence check makes a | ||
| post-publication rerun safe. | ||
| - Start a new workflow run after changing the branch or selecting a different | ||
| commit. The new run number intentionally creates a new canary version. | ||
| - If the branch version is behind npm `latest`, rebase or merge the current | ||
| stable baseline and start a new run. | ||
| - If build or tests fail, fix the selected branch and start a new run. | ||
|
|
||
| ## Stable release after QA | ||
|
|
||
| Canary QA sign-off supplies evidence for a later stable release; it does not | ||
| promote or retag the canary artifact. Merge the intended changes into `main` | ||
| and let Release Please produce and publish the independent stable version. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; | ||
| import { tmpdir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { describe, expect, it } from "vitest"; | ||
| import { | ||
| deriveCanaryVersion, | ||
| prepareCanaryRelease, | ||
| } from "./prepare-canary-release"; | ||
|
|
||
| describe("deriveCanaryVersion", () => { | ||
| it("derives a patch canary from the current stable release", () => { | ||
| expect( | ||
| deriveCanaryVersion({ | ||
| currentVersion: "0.0.282", | ||
| latestVersion: "0.0.282", | ||
| runNumber: "418", | ||
| }) | ||
| ).toBe("0.0.283-canary.418"); | ||
| }); | ||
|
|
||
| it("rejects a stale selected branch", () => { | ||
| expect(() => | ||
| deriveCanaryVersion({ | ||
| currentVersion: "0.0.281", | ||
| latestVersion: "0.0.282", | ||
| runNumber: "420", | ||
| }) | ||
| ).toThrow(/Rebase or update the branch/); | ||
| }); | ||
|
|
||
| it("rejects a prerelease branch version", () => { | ||
| expect(() => | ||
| deriveCanaryVersion({ | ||
| currentVersion: "0.0.283-canary.1", | ||
| latestVersion: "0.0.282", | ||
| runNumber: "421", | ||
| }) | ||
| ).toThrow(/must be a stable semantic version/); | ||
| }); | ||
|
|
||
| it("rejects invalid run numbers", () => { | ||
| expect(() => | ||
| deriveCanaryVersion({ | ||
| currentVersion: "0.0.282", | ||
| latestVersion: "0.0.282", | ||
| runNumber: "0", | ||
| }) | ||
| ).toThrow(/must be a positive integer/); | ||
| }); | ||
| }); | ||
|
|
||
| describe("prepareCanaryRelease", () => { | ||
| it("updates only the runner package version and writes action outputs", () => { | ||
| const directory = mkdtempSync(join(tmpdir(), "stakekit-canary-")); | ||
| const packageJsonPath = join(directory, "package.json"); | ||
| const githubOutput = join(directory, "github-output"); | ||
|
|
||
| writeFileSync( | ||
| packageJsonPath, | ||
| `${JSON.stringify({ name: "@stakekit/widget", version: "0.0.282" })}\n` | ||
| ); | ||
| writeFileSync(githubOutput, ""); | ||
|
|
||
| expect( | ||
| prepareCanaryRelease({ | ||
| packageDir: directory, | ||
| latestVersion: "0.0.282", | ||
| runNumber: "422", | ||
| githubOutput, | ||
| }) | ||
| ).toBe("0.0.283-canary.422"); | ||
| const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as { | ||
| readonly version: string; | ||
| }; | ||
| expect(packageJson.version).toBe("0.0.283-canary.422"); | ||
| expect(readFileSync(githubOutput, "utf8")).toBe( | ||
| "base_version=0.0.282\nversion=0.0.283-canary.422\n" | ||
| ); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.