diff --git a/.github/workflows/openclaw-npm-release.yml b/.github/workflows/openclaw-npm-release.yml index 240420c37b9..9fde17cc361 100644 --- a/.github/workflows/openclaw-npm-release.yml +++ b/.github/workflows/openclaw-npm-release.yml @@ -24,19 +24,9 @@ on: options: - beta - latest - promote_beta_to_latest: - description: Skip publish and promote the stable version already on npm beta to latest - required: true - default: false - type: boolean - sync_stable_dist_tags: - description: Skip publish and point both latest and beta at an already-published stable version - required: true - default: false - type: boolean concurrency: - group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}-{2}-{3}', inputs.tag, inputs.npm_dist_tag, inputs.promote_beta_to_latest, inputs.sync_stable_dist_tags) || github.ref }} + group: openclaw-npm-release-${{ github.event_name == 'workflow_dispatch' && format('{0}-{1}', inputs.tag, inputs.npm_dist_tag) || github.ref }} cancel-in-progress: false env: @@ -48,8 +38,11 @@ jobs: # PLEASE DON'T ADD LONG-RUNNING OR FLAKY CHECKS TO THE npm RELEASE PATH. # KEEP THIS WORKFLOW SHORT AND DETERMINISTIC OR IT CAN GET STUCK AND JEOPARDIZE THE RELEASE. # RELEASE-TIME LIVE OR END-TO-END VALIDATION BELONGS IN openclaw-release-checks.yml. + # SECURITY NOTE: TOKEN-BASED npm dist-tag mutation moved to + # openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml + # so this public workflow can stay focused on OIDC publish only. preflight_openclaw_npm: - if: ${{ inputs.preflight_only && !inputs.promote_beta_to_latest && !inputs.sync_stable_dist_tags }} + if: ${{ inputs.preflight_only }} runs-on: blacksmith-32vcpu-ubuntu-2404 permissions: contents: read @@ -246,7 +239,7 @@ jobs: if-no-files-found: error validate_publish_request: - if: ${{ !inputs.preflight_only && !inputs.promote_beta_to_latest && !inputs.sync_stable_dist_tags }} + if: ${{ !inputs.preflight_only }} runs-on: blacksmith-32vcpu-ubuntu-2404 permissions: contents: read @@ -275,7 +268,7 @@ jobs: # KEEP THE REAL RELEASE/PUBLISH PATH ON A GITHUB-HOSTED RUNNER. # npm trusted publishing + provenance requires this to stay on ubuntu-latest. needs: [validate_publish_request] - if: ${{ !inputs.preflight_only && !inputs.promote_beta_to_latest && !inputs.sync_stable_dist_tags }} + if: ${{ !inputs.preflight_only }} runs-on: ubuntu-latest environment: npm-release permissions: @@ -411,205 +404,3 @@ jobs: publish_target="./${publish_target}" fi bash scripts/openclaw-npm-publish.sh --publish "${publish_target}" - - promote_beta_to_latest: - # KEEP THE MUTATING RELEASE PATH ON A GITHUB-HOSTED RUNNER TOO. - # This job changes the public npm dist-tags, so we keep it aligned with the - # real release path instead of moving it onto the larger Blacksmith runners. - if: ${{ inputs.promote_beta_to_latest && !inputs.sync_stable_dist_tags }} - runs-on: ubuntu-latest - environment: npm-release - permissions: - contents: read - steps: - - name: Require main workflow ref for promotion - env: - WORKFLOW_REF: ${{ github.ref }} - run: | - set -euo pipefail - if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then - echo "Promotion runs must be dispatched from main." - exit 1 - fi - - - name: Validate promotion inputs - env: - PREFLIGHT_ONLY: ${{ inputs.preflight_only }} - PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }} - RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} - run: | - set -euo pipefail - if [[ "${PREFLIGHT_ONLY}" == "true" ]]; then - echo "Promotion mode cannot run with preflight_only=true." - exit 1 - fi - if [[ -n "${PREFLIGHT_RUN_ID}" ]]; then - echo "Promotion mode does not use preflight_run_id." - exit 1 - fi - if [[ "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then - echo "Promotion mode expects npm_dist_tag=beta because it moves beta to latest without publishing." - exit 1 - fi - - - name: Validate stable tag input format - env: - RELEASE_TAG: ${{ inputs.tag }} - run: | - set -euo pipefail - if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]; then - echo "Invalid stable release tag format: ${RELEASE_TAG}" >&2 - exit 1 - fi - echo "RELEASE_VERSION=${RELEASE_TAG#v}" >> "$GITHUB_ENV" - - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Node environment - uses: ./.github/actions/setup-node-env - with: - node-version: ${{ env.NODE_VERSION }} - pnpm-version: ${{ env.PNPM_VERSION }} - install-bun: "false" - use-sticky-disk: "false" - install-deps: "false" - - - name: Validate npm dist-tags - env: - RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - run: | - set -euo pipefail - beta_version="$(npm view openclaw dist-tags.beta)" - latest_version="$(npm view openclaw dist-tags.latest)" - - echo "Current beta dist-tag: ${beta_version}" - echo "Current latest dist-tag: ${latest_version}" - - if [[ "${beta_version}" != "${RELEASE_VERSION}" ]]; then - echo "npm beta points at ${beta_version}, expected ${RELEASE_VERSION}." >&2 - exit 1 - fi - - if ! npm view "openclaw@${RELEASE_VERSION}" version >/dev/null 2>&1; then - echo "openclaw@${RELEASE_VERSION} is not published on npm." >&2 - exit 1 - fi - - - name: Promote beta to latest - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - run: | - set -euo pipefail - npm_userconfig="$(mktemp)" - trap 'rm -f "${npm_userconfig}"' EXIT - chmod 0600 "${npm_userconfig}" - printf '//registry.npmjs.org/:_authToken=%s\n' "${NODE_AUTH_TOKEN}" > "${npm_userconfig}" - NPM_CONFIG_USERCONFIG="${npm_userconfig}" npm dist-tag add "openclaw@${RELEASE_VERSION}" latest - promoted_latest="$(npm view openclaw dist-tags.latest)" - if [[ "${promoted_latest}" != "${RELEASE_VERSION}" ]]; then - echo "npm latest points at ${promoted_latest}, expected ${RELEASE_VERSION} after promotion." >&2 - exit 1 - fi - echo "Promoted openclaw@${RELEASE_VERSION} from beta to latest." - - sync_stable_dist_tags: - # This mode is for direct stable publishes where latest is correct but beta - # should also point at the same stable build after release. - if: ${{ inputs.sync_stable_dist_tags && !inputs.promote_beta_to_latest }} - runs-on: ubuntu-latest - environment: npm-release - permissions: - contents: read - steps: - - name: Require main workflow ref for dist-tag sync - env: - WORKFLOW_REF: ${{ github.ref }} - run: | - set -euo pipefail - if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then - echo "Dist-tag sync runs must be dispatched from main." - exit 1 - fi - - - name: Validate sync inputs - env: - PREFLIGHT_ONLY: ${{ inputs.preflight_only }} - PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }} - RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} - run: | - set -euo pipefail - if [[ "${PREFLIGHT_ONLY}" == "true" ]]; then - echo "Dist-tag sync mode cannot run with preflight_only=true." - exit 1 - fi - if [[ -n "${PREFLIGHT_RUN_ID}" ]]; then - echo "Dist-tag sync mode does not use preflight_run_id." - exit 1 - fi - if [[ "${RELEASE_NPM_DIST_TAG}" != "latest" ]]; then - echo "Dist-tag sync mode expects npm_dist_tag=latest because it points latest and beta at the stable version." - exit 1 - fi - - - name: Validate stable tag input format - env: - RELEASE_TAG: ${{ inputs.tag }} - run: | - set -euo pipefail - if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]; then - echo "Invalid stable release tag format: ${RELEASE_TAG}" >&2 - exit 1 - fi - echo "RELEASE_VERSION=${RELEASE_TAG#v}" >> "$GITHUB_ENV" - - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup Node environment - uses: ./.github/actions/setup-node-env - with: - node-version: ${{ env.NODE_VERSION }} - pnpm-version: ${{ env.PNPM_VERSION }} - install-bun: "false" - use-sticky-disk: "false" - install-deps: "false" - - - name: Validate published stable version - env: - RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - run: | - set -euo pipefail - if ! npm view "openclaw@${RELEASE_VERSION}" version >/dev/null 2>&1; then - echo "openclaw@${RELEASE_VERSION} is not published on npm." >&2 - exit 1 - fi - - echo "Current latest dist-tag: $(npm view openclaw dist-tags.latest)" - echo "Current beta dist-tag: $(npm view openclaw dist-tags.beta)" - - - name: Sync stable dist-tags - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - RELEASE_VERSION: ${{ env.RELEASE_VERSION }} - run: | - set -euo pipefail - npm_userconfig="$(mktemp)" - trap 'rm -f "${npm_userconfig}"' EXIT - chmod 0600 "${npm_userconfig}" - printf '//registry.npmjs.org/:_authToken=%s\n' "${NODE_AUTH_TOKEN}" > "${npm_userconfig}" - NPM_CONFIG_USERCONFIG="${npm_userconfig}" npm dist-tag add "openclaw@${RELEASE_VERSION}" latest - NPM_CONFIG_USERCONFIG="${npm_userconfig}" npm dist-tag add "openclaw@${RELEASE_VERSION}" beta - - synced_latest="$(npm view openclaw dist-tags.latest)" - synced_beta="$(npm view openclaw dist-tags.beta)" - if [[ "${synced_latest}" != "${RELEASE_VERSION}" ]]; then - echo "npm latest points at ${synced_latest}, expected ${RELEASE_VERSION} after sync." >&2 - exit 1 - fi - if [[ "${synced_beta}" != "${RELEASE_VERSION}" ]]; then - echo "npm beta points at ${synced_beta}, expected ${RELEASE_VERSION} after sync." >&2 - exit 1 - fi - echo "Synced openclaw@${RELEASE_VERSION} to npm latest and beta." diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 42cc7cae500..777d63e2a74 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -74,10 +74,10 @@ OpenClaw has three public release lanes: - real npm publish must pass a successful npm `preflight_run_id` - stable npm releases default to `beta` - stable npm publish can target `latest` explicitly via workflow input - - stable npm promotion from `beta` to `latest` is still available as an explicit manual mode on the trusted `OpenClaw NPM Release` workflow - - direct stable publishes can also run an explicit dist-tag sync mode that - points both `latest` and `beta` at the already-published stable version - - those dist-tag modes still need a valid `NPM_TOKEN` in the `npm-release` environment because npm `dist-tag` management is separate from trusted publishing + - token-based npm dist-tag mutation now lives in + `openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml` + for security, because `npm dist-tag add` still needs `NPM_TOKEN` while the + public repo keeps OIDC-only publish - public `macOS Release` is validation-only - real private mac publish must pass successful private mac `preflight_run_id` and `validate_run_id` @@ -116,10 +116,6 @@ OpenClaw has three public release lanes: - `preflight_run_id`: required on the real publish path so the workflow reuses the prepared tarball from the successful preflight run - `npm_dist_tag`: npm target tag for the publish path; defaults to `beta` -- `promote_beta_to_latest`: `true` to skip publish and move an already-published - stable `beta` build onto `latest` -- `sync_stable_dist_tags`: `true` to skip publish and point both `latest` and - `beta` at an already-published stable version `OpenClaw Release Checks` accepts these operator-controlled inputs: @@ -134,14 +130,6 @@ Rules: - Release checks commit-SHA mode also requires the current `origin/main` HEAD - The real publish path must use the same `npm_dist_tag` used during preflight; the workflow verifies that metadata before publish continues -- Promotion mode must use a stable or correction tag, `preflight_only=false`, - an empty `preflight_run_id`, and `npm_dist_tag=beta` -- Dist-tag sync mode must use a stable or correction tag, - `preflight_only=false`, an empty `preflight_run_id`, `npm_dist_tag=latest`, - and `promote_beta_to_latest=false` -- Promotion and dist-tag sync modes also require a valid `NPM_TOKEN` because - `npm dist-tag add` still needs regular npm auth; trusted publishing covers - the package publish path only ## Stable npm release sequence @@ -159,17 +147,16 @@ When cutting a stable npm release: 4. Save the successful `preflight_run_id` 5. Run `OpenClaw NPM Release` again with `preflight_only=false`, the same `tag`, the same `npm_dist_tag`, and the saved `preflight_run_id` -6. If the release landed on `beta`, run `OpenClaw NPM Release` later with the - same stable `tag`, `promote_beta_to_latest=true`, `preflight_only=false`, - `preflight_run_id` empty, and `npm_dist_tag=beta` when you want to move that - published build to `latest` +6. If the release landed on `beta`, use the private + `openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml` + workflow to promote that stable version from `beta` to `latest` 7. If the release intentionally published directly to `latest` and `beta` - should follow the same stable build, run `OpenClaw NPM Release` with the same - stable `tag`, `sync_stable_dist_tags=true`, `promote_beta_to_latest=false`, - `preflight_only=false`, `preflight_run_id` empty, and `npm_dist_tag=latest` + should follow the same stable build immediately, use that same private + workflow to point both dist-tags at the stable version, or let its scheduled + self-healing sync move `beta` later -The promotion and dist-tag sync modes still require the `npm-release` -environment approval and a valid `NPM_TOKEN` accessible to that workflow run. +The dist-tag mutation lives in the private repo for security because it still +requires `NPM_TOKEN`, while the public repo keeps OIDC-only publish. That keeps the direct publish path and the beta-first promotion path both documented and operator-visible.