diff --git a/.github/actions/prerelease-publish/action.yml b/.github/actions/prerelease-publish/action.yml new file mode 100644 index 00000000000..ac33186c08f --- /dev/null +++ b/.github/actions/prerelease-publish/action.yml @@ -0,0 +1,127 @@ +name: PR prerelease npm publish +description: Checkout, setup, build publish targets, gamut-release, and sticky PR comment + +inputs: + node-auth-token: + description: NPM token value (pass secrets.NODE_AUTH_TOKEN) + required: true + gh-token: + description: GitHub token for release tooling (pass secrets.ACTIONS_GITHUB_TOKEN) + required: true + ignore-commit-message: + description: Commit message prefix to skip (chore release commits) + required: true + manifest-path: + description: JSON manifest path written by gamut-release + required: true + comment-path: + description: Markdown file path for the PR comment body + required: true + comment-title: + description: Title line after the emoji (e.g. Published Alpha Packages) + required: true + sticky-header: + description: Sticky comment header key + required: true + publish-run: + description: Bash to set PREID and run gamut-release:pre-release + required: true + +runs: + using: composite + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + filter: tree:0 + ref: ${{ github.event.pull_request.head.ref }} + + - name: Setup and Build + id: setup + uses: ./.github/actions/yarn + + - name: Set git user + uses: ./.github/actions/set-git-user + + - name: Set npm token + uses: ./.github/actions/set-npm-token + with: + token-secret: ${{ inputs.node-auth-token }} + + - name: Ensure workflow is associated with a pull request + uses: ./.github/actions/validate-pr-context + + - name: Skip build from automated commit + uses: ./.github/actions/skip-automated-commits + with: + ignore-commit-message: ${{ inputs.ignore-commit-message }} + + - run: npx nx run-many --target=publish-build --parallel=3 + shell: bash + + - name: Publish prerelease packages + id: publish-prerelease + shell: bash + env: + GH_TOKEN: ${{ inputs.gh-token }} + NODE_AUTH_TOKEN: ${{ inputs.node-auth-token }} + run: | + ${{ inputs.publish-run }} + + - name: Build prerelease packages comment + id: build-comment + continue-on-error: true + uses: actions/github-script@v7 + env: + MANIFEST_PATH: ${{ inputs.manifest-path }} + OUTPUT_PATH: ${{ inputs.comment-path }} + COMMENT_TITLE: ${{ inputs.comment-title }} + with: + script: | + const fs = require('fs'); + const core = require('@actions/core'); + const manifestPath = process.env.MANIFEST_PATH; + const outputPath = process.env.OUTPUT_PATH; + const commentTitle = process.env.COMMENT_TITLE; + if (!fs.existsSync(manifestPath)) { + fs.writeFileSync(outputPath, ''); + core.setOutput('has_comment', 'false'); + return; + } + + const entries = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + if (!Array.isArray(entries) || entries.length === 0) { + fs.writeFileSync(outputPath, ''); + core.setOutput('has_comment', 'false'); + return; + } + + const tableLines = [ + '| Package | Version | npm | Diff |', + '| --- | --- | --- | --- |', + ...entries.map((entry) => { + const pkgUrl = `https://www.npmjs.com/package/${entry.name}`; + const versionUrl = `${pkgUrl}/v/${entry.version}`; + const baseVersion = entry.previousVersion || null; + const diffUrl = baseVersion + ? `https://npmdiff.dev/${encodeURIComponent( + entry.name + )}/${baseVersion}/${entry.version}/` + : null; + const npmLink = `[npm](${versionUrl})`; + const diffLink = diffUrl ? `[diff](${diffUrl})` : 'N/A'; + + return `| \`${entry.name}\` | \`${entry.version}\` | ${npmLink} | ${diffLink} |`; + }), + ]; + const comment = `šŸ“¬ ${commentTitle}\n\n${tableLines.join('\n')}\n`; + fs.writeFileSync(outputPath, comment); + core.setOutput('has_comment', 'true'); + + - name: Comment with published packages + uses: ./.github/actions/sticky-comment + if: ${{ !cancelled() && steps.build-comment.outputs.has_comment == 'true' }} + with: + github-token: ${{ inputs.gh-token }} + header: ${{ inputs.sticky-header }} + message-path: ${{ inputs.comment-path }} diff --git a/.github/workflows/publish-alpha.yml b/.github/workflows/publish-alpha.yml index b1d926c845c..b968dd17558 100644 --- a/.github/workflows/publish-alpha.yml +++ b/.github/workflows/publish-alpha.yml @@ -17,94 +17,26 @@ permissions: issues: write actions: write +concurrency: + group: npm-prerelease-${{ github.event.pull_request.number }} + cancel-in-progress: false + jobs: publish-alpha: + if: ${{ !contains(github.event.pull_request.labels.*.name, 'beta') }} runs-on: ubuntu-22.04 + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref }} - - - name: Setup and Build - id: setup - uses: ./.github/actions/yarn - - - name: Set git user - uses: ./.github/actions/set-git-user - - - name: Set npm token - uses: ./.github/actions/set-npm-token - with: - token-secret: ${{ secrets.NODE_AUTH_TOKEN }} - - - name: Ensure workflow is associated with a pull request - uses: ./.github/actions/validate-pr-context - - - name: Skip build from automated commit - uses: ./.github/actions/skip-automated-commits + - uses: ./.github/actions/prerelease-publish with: + node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }} + gh-token: ${{ secrets.ACTIONS_GITHUB_TOKEN }} ignore-commit-message: ${{ env.IGNORE_COMMIT_MESSAGE }} - - - run: npx nx run-many --target=publish-build --parallel=3 - - - name: Publish alpha packages - id: publish-alpha - run: | - SHORT_SHA=${GITHUB_SHA:0:6} - PREID="alpha.${SHORT_SHA}" - npx nx run gamut-release:alpha --preid="${PREID}" --manifest - env: - GH_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} - - - name: List alpha packages versions - id: published - continue-on-error: true - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const manifestPath = 'alpha-publish-manifest.json'; - const outputPath = 'alpha-publish-comment.md'; - if (!fs.existsSync(manifestPath)) { - fs.writeFileSync(outputPath, ''); - return; - } - - const entries = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); - if (!Array.isArray(entries) || entries.length === 0) { - fs.writeFileSync(outputPath, ''); - return; - } - - const tableLines = [ - '| Package | Version | npm | Diff |', - '| --- | --- | --- | --- |', - ...entries.map((entry) => { - const pkgUrl = `https://www.npmjs.com/package/${entry.name}`; - const versionUrl = `${pkgUrl}/v/${entry.version}`; - const baseVersion = entry.previousVersion || null; - const diffUrl = baseVersion - ? `https://npmdiff.dev/${encodeURIComponent( - entry.name - )}/${baseVersion}/${entry.version}/` - : null; - const npmLink = `[npm](${versionUrl})`; - const diffLink = diffUrl ? `[diff](${diffUrl})` : 'N/A'; - - return `| \`${entry.name}\` | \`${entry.version}\` | ${npmLink} | ${diffLink} |`; - }), - ]; - const comment = `šŸ“¬ Published Alpha Packages:\n\n${tableLines.join( - '\n' - )}\n`; - fs.writeFileSync(outputPath, comment); - - - name: Comment with published alpha packages - uses: ./.github/actions/sticky-comment - if: ${{ !cancelled() && hashFiles('alpha-publish-comment.md') != '' }} - with: - github-token: ${{ secrets.ACTIONS_GITHUB_TOKEN }} - header: Alpha Packages - message-path: alpha-publish-comment.md + manifest-path: alpha-publish-manifest.json + comment-path: alpha-publish-comment.md + comment-title: Published Alpha Packages + sticky-header: Alpha Packages + publish-run: | + SHORT_SHA=${GITHUB_SHA:0:6} + PREID="alpha.${SHORT_SHA}" + npx nx run gamut-release:pre-release --preid="${PREID}" --manifest diff --git a/.github/workflows/publish-beta.yml b/.github/workflows/publish-beta.yml new file mode 100644 index 00000000000..2578cd6249f --- /dev/null +++ b/.github/workflows/publish-beta.yml @@ -0,0 +1,43 @@ +name: Publish Beta + +on: + pull_request: + types: [labeled] + +env: + NODE_VERSION: '22.13.1' + NODE_OPTIONS: '--max_old_space_size=8196' + NX_CLOUD: false + IGNORE_COMMIT_MESSAGE: 'chore(release): publish' + +permissions: + id-token: write + contents: read + pull-requests: write + issues: write + actions: write + +concurrency: + group: npm-prerelease-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + publish-beta: + if: github.event.label.name == 'beta' && github.event.pull_request.head.ref == 'cass-gmt-1607-publish' + runs-on: ubuntu-22.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/prerelease-publish + with: + node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }} + gh-token: ${{ secrets.ACTIONS_GITHUB_TOKEN }} + ignore-commit-message: ${{ env.IGNORE_COMMIT_MESSAGE }} + manifest-path: beta-publish-manifest.json + comment-path: beta-publish-comment.md + comment-title: 'Published Beta Packages (install with `@beta`):' + sticky-header: Beta Packages + publish-run: | + PREID="beta.pr${{ github.event.pull_request.number }}.${{ github.run_id }}" + npx nx run gamut-release:pre-release --preid="${PREID}" --publish-tag=beta --manifest=beta-publish-manifest.json diff --git a/README.md b/README.md index 84f0e396661..0d3957b39ad 100644 --- a/README.md +++ b/README.md @@ -108,17 +108,21 @@ Add new Button variant and fix spacing issues - It will create git tags and GitHub releases 1. You can find the new version number on npmjs.com/package/, or find it in that package's `package.json` on the `main` branch -### Publishing an alpha version of a module +### Publishing an pre-release version of a module -Every PR that changes files in a package publishes alpha releases that you can use to test your changes across applications. +You can consume prerelease packages from npm before your PR merges. Two flows are available; **alpha** and **beta** are mutually exclusive on a PR. + +**Alpha (default):** On each push to the PR branch, CI publishes alpha releases (per-commit prerelease versions on npm). This does **not** run while the PR has the **`beta`** label. + +**Beta:** Add the **`beta`** label to the PR (create the label in the repo if it does not exist). That triggers a one-time publish to npm under the **`beta`** dist-tag (install with e.g. `yarn add @codecademy/gamut@beta`). Pushing new commits does **not** republish beta automatically; remove the **`beta`** label and add it again after your changes to trigger another publish. > NOTE: in case an alpha build is not published upon opening of the PR or Draft PR, re-run the `build-test` check and that will re-run the alpha build publishing flows 1. Create a PR or Draft PR. - - This will kickoff a Github Action workflow which will publish an alpha build. (This will appear in Github as the "Deploy") -1. After the alpha build is published, the `codecademydev` bot should comment on your PR with the names of the published alpha packages.
+ - Without the **`beta`** label, each push kicks off the workflow that publishes an alpha build. (This will appear in Github as the "Deploy") +1. After packages are published, the `codecademydev` bot should comment on your PR with the names of the published packages (separate comments for alpha vs beta).
-1. Install this version of the package in your application you wish to test your changes on. +1. Install that version in the application where you want to test your changes (alpha by exact version from the table, or beta via `@beta` when using the beta flow). ### Working with pre-published changes diff --git a/tools/gamut-release/README.md b/tools/gamut-release/README.md index 4c5fd9dd87e..a5f622aa5b4 100644 --- a/tools/gamut-release/README.md +++ b/tools/gamut-release/README.md @@ -1,25 +1,31 @@ # Gamut Release -A CLI tool for publishing alpha versions of packages in the gamut repo using Nx release. +A CLI tool for publishing prerelease versions of packages in the gamut repo using Nx release. Mainly meant to be run in CI. ## Overview -This tool automates the process of publishing alpha versions of packages in the gamut repo. This is mainly meant to be run in CI for pull requests, but can be run manually using the same Nx target. +This tool automates publishing alpha or beta prereleases of packages. It is run in CI on pull requests via the Nx target `gamut-release:pre-release`, which injects the required Node experimental flags. -CI is responsible for validating the presence of a version plan before running alpha publishes. +The Release Plan workflow (`release-plan-check.yml`) enforces version plans on PRs separately from these prerelease publishes. ## Usage -### Alpha Release +### Prerelease publish ```bash -npx nx run gamut-release:alpha --preid=alpha.abc123 +npx nx run gamut-release:pre-release --preid=alpha.abc123 +``` + +By default, the **npm dist-tag** is the same as `--preid`. To publish under a standard tag such as `beta` while using a unique semver prerelease id, pass **`--publish-tag`**: + +```bash +npx nx run gamut-release:pre-release --preid=beta.pr123.456789 --publish-tag=beta ``` ### Manifest output (optional) ```bash -npx nx run gamut-release:alpha --preid=alpha.abc123 --manifest[=path] +npx nx run gamut-release:pre-release --preid=alpha.abc123 --manifest[=path] ``` diff --git a/tools/gamut-release/project.json b/tools/gamut-release/project.json index a3e21b04633..c79e61d831f 100644 --- a/tools/gamut-release/project.json +++ b/tools/gamut-release/project.json @@ -5,7 +5,7 @@ "projectType": "library", "tags": ["scope:nx"], "targets": { - "alpha": { + "pre-release": { "executor": "nx:run-commands", "options": { "commands": ["node tools/gamut-release/src/main.ts"], diff --git a/tools/gamut-release/src/main.ts b/tools/gamut-release/src/main.ts index 2655c033a81..177c02e284c 100644 --- a/tools/gamut-release/src/main.ts +++ b/tools/gamut-release/src/main.ts @@ -6,10 +6,10 @@ * * This script uses the Nx Release programmatic API to publish alpha versions * of packages. It is designed to run in CI for pull requests via the Nx target - * `gamut-release:alpha`, which injects the required Node experimental flags. + * `gamut-release:pre-release`, which injects the required Node experimental flags. * * Usage: - * npx nx run gamut-release:alpha --preid=alpha.abc123 [--manifest[=path]] + * npx nx run gamut-release:pre-release --preid=alpha.abc123 [--publish-tag=beta] [--manifest[=path]] */ import { writeFile } from 'node:fs/promises'; @@ -25,6 +25,7 @@ import { releasePublish, releaseVersion } from 'nx/release/index.js'; type AlphaReleaseOptions = { preid: string; + publishTag?: string; dryRun?: boolean; verbose?: boolean; manifest?: string | boolean; @@ -111,6 +112,10 @@ const program = new Command() .name('gamut-release-alpha') .description('Publish alpha versions of packages using Nx Release.') .requiredOption('--preid ', 'Prerelease identifier, e.g. alpha.abc123') + .option( + '--publish-tag ', + 'npm dist-tag for publish (defaults to the same value as --preid)' + ) .option('-d, --dry-run', 'Run without publishing') .option('--verbose', 'Enable verbose logging') .option( @@ -123,6 +128,7 @@ program.parse(process.argv); const options = program.opts(); const preidArg = options.preid; +const publishTagArg = options.publishTag?.trim() || preidArg; const dryRun = options.dryRun ?? false; const verbose = options.verbose ?? false; const manifestPath = resolveManifestPath(options.manifest); @@ -163,10 +169,12 @@ async function releaseAlpha(): Promise { }); } - // Step 2: Publish packages with alpha tag - console.log(`\nšŸ“¤ Publishing packages with tag: ${preidArg}...`); + // Step 2: Publish packages + console.log( + `\nšŸ“¤ Publishing packages with npm dist-tag: ${publishTagArg}...` + ); const publishStatus = await releasePublish({ - tag: preidArg, + tag: publishTagArg, dryRun, verbose, });