RC tag release #22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Release workflow for @loop-engine/* OSS packages | |
| # | |
| # Normal publish trigger: | |
| # git tag v0.2.0 && git push --tags | |
| # → workflow fires automatically, no manual dispatch needed | |
| # | |
| # Pipeline test (dry run): | |
| # GitHub → Actions → RC tag release → Run workflow | |
| # → leave dry_run CHECKED → run | |
| # → exercises install, build, validate:publish, and pnpm publish --dry-run | |
| # | |
| # Manual dispatch with dry_run UNCHECKED: | |
| # Only allowed from main or a version tag — workflow will hard-fail otherwise. | |
| # Use only as an emergency escape hatch, not as a routine publish path. | |
| # | |
| # Tag pushes: inputs.dry_run is undefined; guard step uses if: ${{ !inputs.dry_run }} so it still | |
| # runs, and startsWith(github.ref, 'refs/tags/') passes immediately — do not change that behavior. | |
| # | |
| # Trusted publishing (OIDC): do NOT set NODE_AUTH_TOKEN / NPM_TOKEN on the publish step — that forces token auth and | |
| # yields EOTP in CI. npm + pnpm use the GitHub OIDC token when id-token: write is set and npm CLI is new enough. | |
| # Requirements (npm docs): Node >= 22.14.0, npm CLI >= 11.5.1. Use Node 24 on the runner — it bundles npm 11.x. Do not run | |
| # `npm install -g npm@...` here; it can break the toolcache install (e.g. MODULE_NOT_FOUND promise-retry). | |
| # Each published package needs Trusted Publisher on npmjs (same repo + workflow filename rc-tag-release.yml). | |
| # | |
| # First-publish bootstrap (token_bootstrap input): npm trusted publishing CANNOT create a package | |
| # that does not yet exist on the registry (npm/cli#8544) — the TP config UI requires an existing | |
| # package record. For brand-new names, run this workflow manually with token_bootstrap CHECKED and | |
| # dry_run UNCHECKED: the bootstrap step publishes ONLY the names listed in BOOTSTRAP_PACKAGES via a | |
| # granular automation token (secrets.NPM_TOKEN — new-package create rights on @loop-engine, | |
| # bypass-2FA), with provenance (id-token: write makes attestation work under token auth too). | |
| # Then bind Trusted Publishers for the new names on npmjs and publish everything else via the tag. | |
| # IMPORTANT: an empty NODE_AUTH_TOKEN is still a value — npm only falls back to OIDC when the var | |
| # is completely unset. That is why token auth lives on a separate step, not a conditional env value. | |
| # | |
| # Optional: NPM_READ_TOKEN + NODE_AUTH_TOKEN only if you add private @scope deps during install (public monorepo: omit). | |
| name: RC tag release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Dry run only — does not publish to npm. Uncheck only from main with a version tag pushed." | |
| type: boolean | |
| default: true | |
| required: true | |
| token_bootstrap: | |
| description: "First-publish bootstrap: token-auth publish of ONLY the BOOTSTRAP_PACKAGES list (names trusted publishing can't create yet). Requires secrets.NPM_TOKEN." | |
| type: boolean | |
| default: false | |
| required: false | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| publish: | |
| name: Publish to npm | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # required to create GitHub Releases | |
| id-token: write # npm provenance (links tarball to this workflow run) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 9.0.0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "24" | |
| cache: pnpm | |
| - name: Show Node and npm versions (OIDC needs npm >= 11.5.1) | |
| run: node --version && npm --version | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build all packages | |
| run: pnpm build | |
| - name: Validate no workspace:* in publish targets | |
| run: pnpm validate:publish | |
| - name: Enforce main or tag for real publish | |
| if: ${{ !inputs.dry_run }} | |
| run: | | |
| IS_TAG=${{ startsWith(github.ref, 'refs/tags/') }} | |
| IS_MAIN=${{ github.ref_name == 'main' }} | |
| if [ "$IS_TAG" != "true" ] && [ "$IS_MAIN" != "true" ]; then | |
| echo "❌ Real publish is only allowed from main or a version tag." | |
| echo " Current ref: ${{ github.ref }}" | |
| echo " To test the pipeline safely, re-run with dry_run checked." | |
| exit 1 | |
| fi | |
| echo "✅ Ref check passed: ${{ github.ref }}" | |
| - name: Bootstrap first publishes (token auth — names TP cannot create) | |
| if: ${{ inputs.token_bootstrap }} | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_CONFIG_PROVENANCE: "true" | |
| run: | | |
| if [ -z "$NODE_AUTH_TOKEN" ]; then | |
| echo "❌ token_bootstrap requires secrets.NPM_TOKEN (granular automation token with new-package create rights)." | |
| exit 1 | |
| fi | |
| # Names that do not exist on npmjs yet — trusted publishing cannot create them (npm/cli#8544). | |
| # After this run succeeds: bind Trusted Publishers for each, then DELETE entries from this list. | |
| BOOTSTRAP_PACKAGES=( | |
| "@loop-engine/auth-iface" | |
| "@loop-engine/canonical-json" | |
| "@loop-engine/entitlements-iface" | |
| "@loop-engine/loop-status-client" | |
| "@loop-engine/runtime-core" | |
| "@loop-engine/runtime-db" | |
| "@loop-engine/runtime-routes" | |
| "@loop-engine/studio-client" | |
| "@loop-engine/studio-ui" | |
| ) | |
| FLAGS="--access public --no-git-checks" | |
| if [ "${{ inputs.dry_run }}" = "true" ]; then | |
| FLAGS="$FLAGS --dry-run" | |
| echo "ℹ️ Dry run mode — no packages will be published" | |
| fi | |
| FILTERS="" | |
| for pkg in "${BOOTSTRAP_PACKAGES[@]}"; do | |
| FILTERS="$FILTERS --filter $pkg" | |
| done | |
| pnpm publish -r $FILTERS $FLAGS | |
| - name: Publish to npm | |
| if: ${{ !inputs.token_bootstrap }} | |
| run: | | |
| FLAGS="--access public --no-git-checks" | |
| if [ "${{ inputs.dry_run }}" = "true" ]; then | |
| FLAGS="$FLAGS --dry-run" | |
| echo "ℹ️ Dry run mode — no packages will be published" | |
| fi | |
| # Provenance is automatic with trusted publishing; do not pass NODE_AUTH_TOKEN (token auth → EOTP in CI). | |
| pnpm publish -r $FLAGS | |
| # Skip only on workflow_dispatch with dry_run (inputs undefined on tag push — do not use !inputs.dry_run alone). | |
| # Also skip on bootstrap runs — they publish a subset from a branch ref, not a release tag. | |
| - name: Create GitHub Release | |
| if: ${{ github.event_name != 'workflow_dispatch' || (!inputs.dry_run && !inputs.token_bootstrap) }} | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: ${{ github.ref_name }} | |
| body: | | |
| ## @loop-engine ${{ github.ref_name }} | |
| Published packages: | |
| - See [npm @loop-engine org](https://www.npmjs.com/org/loop-engine) for full package list and changelogs. | |
| ### Install | |
| ```bash | |
| npm install @loop-engine/sdk@${{ github.ref_name }} | |
| ``` | |
| ### What changed | |
| See [CHANGELOG.md](https://github.com/loopengine/loop-engine/blob/main/CHANGELOG.md) | |
| for detailed changes, or view the [npm release history](https://www.npmjs.com/package/@loop-engine/sdk?activeTab=versions). | |
| draft: false | |
| prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }} | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Verify clean install | |
| if: github.event_name == 'push' | |
| run: | | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| mkdir -p /tmp/install-test && cd /tmp/install-test | |
| npm init -y | |
| npm install "@loop-engine/sdk@${VERSION}" | |
| node -e "const { createLoopSystem } = require('@loop-engine/sdk'); console.log('✅ @loop-engine/sdk installs cleanly')" |