From 890fc18bd646f44565c64c9b6a39b691e4d9e4eb Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 7 Nov 2026 13:52:06 -0800 Subject: [PATCH 1/2] ci(npm): publish every npm package through trusted publishing The four publish workflows authenticate to npm with the runner's OIDC token instead of the long-lived NPM_TOKEN secret, so no npm credential is stored anywhere and every release carries a provenance attestation. - Publish jobs request id-token: write and run Node 24, whose npm 11 is required for trusted publishing. - sim and sim-setup pack once with bun, which rewrites workspace: ranges, smoke-test that tarball, then npm publish the same tarball, so what was tested is what ships. The smoke tests now also assert the bumped version. - sim-setup republishes when its own workflow changes, as sim already does. - simstudio gets the repository field provenance requires. - The setup action drops its registry-url input, which existed only to feed NODE_AUTH_TOKEN. --- .github/actions/setup/action.yml | 5 --- .github/workflows/publish-cli.yml | 9 ++-- .github/workflows/publish-sim-cli.yml | 60 +++++++++++++++---------- .github/workflows/publish-sim-setup.yml | 53 ++++++++++++++-------- .github/workflows/publish-ts-sdk.yml | 10 +++-- packages/cli/package.json | 5 +++ 6 files changed, 87 insertions(+), 55 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 16babab1c9a..58c96a86e5b 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -14,10 +14,6 @@ inputs: description: Node version to install. Empty keeps the runner's preinstalled Node, for jobs that only ever ran Bun. required: false default: '24' - registry-url: - description: npm registry to write an .npmrc for, so `npm publish` reads NODE_AUTH_TOKEN. Empty writes none. - required: false - default: '' # Sticky disk keys are scoped by event name, and fork PRs get their own namespace on # top: untrusted fork runs must never share a cache with push runs (whose caches @@ -43,7 +39,6 @@ runs: uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 with: node-version: ${{ inputs.node-version }} - registry-url: ${{ inputs.registry-url }} - name: Mount Bun cache if: inputs.provider == '' || inputs.provider == 'blacksmith' diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index a6696c27926..0bdf7b54f92 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -6,8 +6,11 @@ on: paths: - 'packages/cli/**' +# npm trusted publishing: the publish step trades the runner's OIDC token for a +# short-lived npm credential, so no npm token is stored anywhere. permissions: contents: read + id-token: write # A newer push waits for the running publish rather than racing its version check. concurrency: @@ -25,12 +28,12 @@ jobs: with: persist-credentials: false + # Node 24 bundles npm 11.5.1+, which trusted publishing requires. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '22' - registry-url: https://registry.npmjs.org/ + node-version: '24' - name: Build package working-directory: packages/cli @@ -56,8 +59,6 @@ jobs: if: steps.version_check.outputs.exists == 'false' working-directory: packages/cli run: npm publish --access=public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Log skipped publish if: steps.version_check.outputs.exists == 'true' diff --git a/.github/workflows/publish-sim-cli.yml b/.github/workflows/publish-sim-cli.yml index 591748b1606..6635e5e81ae 100644 --- a/.github/workflows/publish-sim-cli.yml +++ b/.github/workflows/publish-sim-cli.yml @@ -11,8 +11,11 @@ on: # A change to how the package is built or published has to republish it. - '.github/workflows/publish-sim-cli.yml' +# npm trusted publishing: the publish step trades the runner's OIDC token for a +# short-lived npm credential, so no npm token is stored anywhere. permissions: contents: read + id-token: write concurrency: group: publish-sim-cli-${{ github.ref }} @@ -29,18 +32,14 @@ jobs: with: persist-credentials: false - # Vitest 5 requires Node 22.12+; the published bundle is still - # smoke-tested on Node 20 below, its minimum supported runtime. + # Node 24 bundles npm 11.5.1+, which trusted publishing requires; the + # published bundle is still smoke-tested on Node 20 below, its minimum + # supported runtime. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '22' - - - name: Verify npm authentication - env: - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} - run: bun pm whoami + node-version: '24' - name: Auto-bump package version run: bun run bump:npm-packages sim-cli @@ -54,9 +53,10 @@ jobs: run: bun run type-check # The usage-reporting destination is baked into the bundle at build time - # (docs/cli/usage-data), so only the two steps that build it get the - # token. Every other step, the tests above in particular, runs without - # it: a test that runs a real command must never report to production. + # (docs/cli/usage-data), so only this step gets the token, and the + # tarball packed from its output is what ships. Every other step, the + # tests above in particular, runs without it: a test that runs a real + # command must never report to production. - name: Build package working-directory: packages/sim-cli env: @@ -107,23 +107,38 @@ jobs: echo "tag=$TAG" } >> "$GITHUB_OUTPUT" + # Bun packs because it rewrites `workspace:` ranges to real versions, + # which `npm pack` would publish verbatim. The smoke test and the publish + # below both use this one tarball, so what was tested is what ships. + - name: Pack package + id: pack + working-directory: packages/sim-cli + run: | + TARBALL="$RUNNER_TEMP/sim-cli.tgz" + bun pm pack --ignore-scripts --filename "$TARBALL" --quiet + echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + - name: Setup Node 20 for the bundle smoke test uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 with: node-version: '20' - name: Smoke-test packed Node bundle - working-directory: packages/sim-cli + env: + TARBALL: ${{ steps.pack.outputs.tarball }} + VERSION: ${{ steps.release.outputs.version }} run: | set -euo pipefail SMOKE_DIR="$(mktemp -d "$RUNNER_TEMP/sim-cli-smoke.XXXXXX")" - PACKAGE_PATH="$SMOKE_DIR/sim-cli.tgz" - bun pm pack --ignore-scripts --filename "$PACKAGE_PATH" --quiet - tar -xzf "$PACKAGE_PATH" -C "$SMOKE_DIR" - "$SMOKE_DIR/package/dist/index.js" --version + tar -xzf "$TARBALL" -C "$SMOKE_DIR" + test "$("$SMOKE_DIR/package/dist/index.js" --version)" = "$VERSION" + + - name: Setup Node 24 for trusted publishing + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 + with: + node-version: '24' - name: Verify version is unpublished - working-directory: packages/sim-cli env: VERSION: ${{ steps.release.outputs.version }} run: | @@ -132,16 +147,13 @@ jobs: exit 1 fi - # `bun publish` runs `prepublishOnly`, which rebuilds `dist`, and that - # rebuild is what ships, so it needs the token as well. + # npm publishes a tarball without running lifecycle scripts, so nothing + # rebuilds `dist` here. Provenance is attached automatically. - name: Publish to npm - working-directory: packages/sim-cli env: - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} + TARBALL: ${{ steps.pack.outputs.tarball }} NPM_TAG: ${{ steps.release.outputs.tag }} - SIM_CLI_TELEMETRY_KEY: ${{ vars.SIM_CLI_TELEMETRY_KEY }} - SIM_CLI_TELEMETRY_HOST: ${{ vars.SIM_CLI_TELEMETRY_HOST }} - run: bun publish --access public --tag "$NPM_TAG" --no-save + run: npm publish "$TARBALL" --access public --tag "$NPM_TAG" - name: Summarize release env: diff --git a/.github/workflows/publish-sim-setup.yml b/.github/workflows/publish-sim-setup.yml index 2bcedc4b5db..725341b945e 100644 --- a/.github/workflows/publish-sim-setup.yml +++ b/.github/workflows/publish-sim-setup.yml @@ -10,9 +10,14 @@ on: - 'packages/utils/**' - 'docker-compose.prod.yml' - 'bun.lock' + # A change to how the package is built or published has to republish it. + - '.github/workflows/publish-sim-setup.yml' +# npm trusted publishing: the publish step trades the runner's OIDC token for a +# short-lived npm credential, so no npm token is stored anywhere. permissions: contents: read + id-token: write concurrency: group: publish-sim-setup-${{ github.ref }} @@ -29,18 +34,14 @@ jobs: with: persist-credentials: false - # Vitest 5 requires Node 22.12+; the published bundle is still - # smoke-tested on Node 20 below, its minimum supported runtime. + # Node 24 bundles npm 11.5.1+, which trusted publishing requires; the + # published bundle is still smoke-tested on Node 20 below, its minimum + # supported runtime. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '22' - - - name: Verify npm authentication - env: - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} - run: bun pm whoami + node-version: '24' - name: Auto-bump package version run: bun run bump:npm-packages sim-setup @@ -104,21 +105,32 @@ jobs: echo "tag=$TAG" } >> "$GITHUB_OUTPUT" + # Bun packs because it rewrites `workspace:` ranges to real versions, + # which `npm pack` would publish verbatim. The smoke test and the publish + # below both use this one tarball, so what was tested is what ships. + - name: Pack package + id: pack + working-directory: packages/sim-setup + run: | + TARBALL="$RUNNER_TEMP/sim-setup.tgz" + bun pm pack --ignore-scripts --filename "$TARBALL" --quiet + echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + - name: Setup Node 20 for the bundle smoke test uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 with: node-version: '20' - name: Smoke-test packed Node bundle - working-directory: packages/sim-setup + env: + TARBALL: ${{ steps.pack.outputs.tarball }} + VERSION: ${{ steps.release.outputs.version }} run: | set -euo pipefail SMOKE_DIR="$(mktemp -d "$RUNNER_TEMP/sim-setup-smoke.XXXXXX")" - PACKAGE_PATH="$SMOKE_DIR/sim-setup.tgz" - bun pm pack --ignore-scripts --filename "$PACKAGE_PATH" --quiet - tar -xzf "$PACKAGE_PATH" -C "$SMOKE_DIR" + tar -xzf "$TARBALL" -C "$SMOKE_DIR" - if tar -tzf "$PACKAGE_PATH" | grep -Eq '^package/(apps|docker|helm|packages|scripts)/'; then + if tar -tzf "$TARBALL" | grep -Eq '^package/(apps|docker|helm|packages|scripts)/'; then echo 'Packed setup package contains repository source directories.' >&2 exit 1 fi @@ -130,7 +142,7 @@ jobs: fi cd "$SMOKE_DIR" - node package/dist/index.js --version + test "$(node package/dist/index.js --version)" = "$VERSION" node package/dist/index.js --help for command in config add doctor start stop restart update status logs down reset; do node package/dist/index.js "$command" --help > /dev/null @@ -145,8 +157,12 @@ jobs: exit 1 fi + - name: Setup Node 24 for trusted publishing + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 + with: + node-version: '24' + - name: Verify version is unpublished - working-directory: packages/sim-setup env: VERSION: ${{ steps.release.outputs.version }} run: | @@ -155,12 +171,13 @@ jobs: exit 1 fi + # npm publishes a tarball without running lifecycle scripts, so nothing + # rebuilds `dist` here. Provenance is attached automatically. - name: Publish to npm - working-directory: packages/sim-setup env: - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} + TARBALL: ${{ steps.pack.outputs.tarball }} NPM_TAG: ${{ steps.release.outputs.tag }} - run: bun publish --access public --tag "$NPM_TAG" --no-save + run: npm publish "$TARBALL" --access public --tag "$NPM_TAG" - name: Summarize release env: diff --git a/.github/workflows/publish-ts-sdk.yml b/.github/workflows/publish-ts-sdk.yml index 1c5e7e35e40..5378aca9768 100644 --- a/.github/workflows/publish-ts-sdk.yml +++ b/.github/workflows/publish-ts-sdk.yml @@ -6,8 +6,12 @@ on: paths: - 'packages/ts-sdk/**' +# `contents: write` creates the GitHub release. `id-token: write` is npm trusted +# publishing: the publish step trades the runner's OIDC token for a short-lived +# npm credential, so no npm token is stored anywhere. permissions: contents: write + id-token: write # A newer push waits for the running publish rather than racing its version check. concurrency: @@ -25,12 +29,12 @@ jobs: with: persist-credentials: false + # Node 24 bundles npm 11.5.1+, which trusted publishing requires. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '22' - registry-url: https://registry.npmjs.org/ + node-version: '24' - name: Run tests working-directory: packages/ts-sdk @@ -60,8 +64,6 @@ jobs: if: steps.version_check.outputs.exists == 'false' working-directory: packages/ts-sdk run: npm publish --access=public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Log skipped publish if: steps.version_check.outputs.exists == 'true' diff --git a/packages/cli/package.json b/packages/cli/package.json index 34e245b7b1d..3975d34a98c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,6 +35,11 @@ ], "author": "Sim", "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/simstudioai/sim.git", + "directory": "packages/cli" + }, "engines": { "node": ">=18" }, From 583e8cb97b93a6238c33cd77be487ecaccab7974 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 7 Nov 2026 14:03:04 -0800 Subject: [PATCH 2/2] ci(npm): publish from a GitHub-hosted job that alone can mint the OIDC token npm trusted publishing accepts OIDC tokens only from GitHub-hosted runners, and the publish jobs ran on Blacksmith by default. Each workflow now builds, tests, packs and smoke-tests on its usual runner with read-only permissions, then hands the tarball to a GitHub-hosted publish job, the only one granted id-token: write. That job runs no repository code, so no test or dependency can mint an npm credential. The TypeScript SDK's GitHub release moves to its own job with contents: write. --- .github/workflows/publish-cli.yml | 66 +++++++++++++---- .github/workflows/publish-sim-cli.yml | 80 +++++++++++++-------- .github/workflows/publish-sim-setup.yml | 82 +++++++++++++-------- .github/workflows/publish-ts-sdk.yml | 94 ++++++++++++++++++------- 4 files changed, 224 insertions(+), 98 deletions(-) diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 0bdf7b54f92..5eef3cc7e1a 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -6,11 +6,8 @@ on: paths: - 'packages/cli/**' -# npm trusted publishing: the publish step trades the runner's OIDC token for a -# short-lived npm credential, so no npm token is stored anywhere. permissions: contents: read - id-token: write # A newer push waits for the running publish rather than racing its version check. concurrency: @@ -18,22 +15,24 @@ concurrency: cancel-in-progress: false jobs: - publish: - name: publish + build: + name: build runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 + outputs: + version: ${{ steps.package_version.outputs.version }} + exists: ${{ steps.version_check.outputs.exists }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 with: persist-credentials: false - # Node 24 bundles npm 11.5.1+, which trusted publishing requires. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '24' + node-version: '22' - name: Build package working-directory: packages/cli @@ -42,7 +41,7 @@ jobs: - name: Get package version id: package_version working-directory: packages/cli - run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" - name: Check if version already exists id: version_check @@ -50,18 +49,59 @@ jobs: VERSION: ${{ steps.package_version.outputs.version }} run: | if npm view "simstudio@$VERSION" version &> /dev/null; then - echo "exists=true" >> $GITHUB_OUTPUT + echo "exists=true" >> "$GITHUB_OUTPUT" else - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >> "$GITHUB_OUTPUT" fi - - name: Publish to npm + # Bun packs because it rewrites `workspace:` ranges to real versions, + # which `npm pack` would publish verbatim. + - name: Pack package if: steps.version_check.outputs.exists == 'false' working-directory: packages/cli - run: npm publish --access=public + run: bun pm pack --ignore-scripts --filename "$RUNNER_TEMP/package.tgz" --quiet + + - name: Upload package + if: steps.version_check.outputs.exists == 'false' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: package + path: ${{ runner.temp }}/package.tgz + if-no-files-found: error + retention-days: 1 - name: Log skipped publish if: steps.version_check.outputs.exists == 'true' env: VERSION: ${{ steps.package_version.outputs.version }} - run: echo "Skipped publishing because version $VERSION already exists on npm" \ No newline at end of file + run: echo "Skipped publishing because version $VERSION already exists on npm" + + # npm trusted publishing trades this job's OIDC token for a short-lived npm + # credential, so no npm token is stored anywhere. npm accepts the token only + # from GitHub-hosted runners, and only this job can mint one: it runs no + # repository code, it publishes the tarball the build job packed. npm runs no + # lifecycle scripts for a tarball, so nothing is rebuilt here, and provenance + # is attached automatically. + publish: + name: publish + needs: build + if: needs.build.outputs.exists == 'false' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + id-token: write + steps: + # Node 24 bundles npm 11.5.1+, which trusted publishing requires. + - name: Setup Node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 + with: + node-version: '24' + + - name: Download package + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: package + path: ${{ runner.temp }} + + - name: Publish to npm + run: npm publish "$RUNNER_TEMP/package.tgz" --access public diff --git a/.github/workflows/publish-sim-cli.yml b/.github/workflows/publish-sim-cli.yml index 6635e5e81ae..2d87aa6bd92 100644 --- a/.github/workflows/publish-sim-cli.yml +++ b/.github/workflows/publish-sim-cli.yml @@ -11,35 +11,34 @@ on: # A change to how the package is built or published has to republish it. - '.github/workflows/publish-sim-cli.yml' -# npm trusted publishing: the publish step trades the runner's OIDC token for a -# short-lived npm credential, so no npm token is stored anywhere. permissions: contents: read - id-token: write concurrency: group: publish-sim-cli-${{ github.ref }} cancel-in-progress: false jobs: - publish: - name: publish + build: + name: build runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 + outputs: + version: ${{ steps.release.outputs.version }} + tag: ${{ steps.release.outputs.tag }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 with: persist-credentials: false - # Node 24 bundles npm 11.5.1+, which trusted publishing requires; the - # published bundle is still smoke-tested on Node 20 below, its minimum - # supported runtime. + # Vitest 5 requires Node 22.12+; the published bundle is still + # smoke-tested on Node 20 below, its minimum supported runtime. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '24' + node-version: '22' - name: Auto-bump package version run: bun run bump:npm-packages sim-cli @@ -108,15 +107,11 @@ jobs: } >> "$GITHUB_OUTPUT" # Bun packs because it rewrites `workspace:` ranges to real versions, - # which `npm pack` would publish verbatim. The smoke test and the publish - # below both use this one tarball, so what was tested is what ships. + # which `npm pack` would publish verbatim. The smoke test below and the + # publish job both use this one tarball, so what was tested is what ships. - name: Pack package - id: pack working-directory: packages/sim-cli - run: | - TARBALL="$RUNNER_TEMP/sim-cli.tgz" - bun pm pack --ignore-scripts --filename "$TARBALL" --quiet - echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + run: bun pm pack --ignore-scripts --filename "$RUNNER_TEMP/package.tgz" --quiet - name: Setup Node 20 for the bundle smoke test uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 @@ -125,19 +120,13 @@ jobs: - name: Smoke-test packed Node bundle env: - TARBALL: ${{ steps.pack.outputs.tarball }} VERSION: ${{ steps.release.outputs.version }} run: | set -euo pipefail SMOKE_DIR="$(mktemp -d "$RUNNER_TEMP/sim-cli-smoke.XXXXXX")" - tar -xzf "$TARBALL" -C "$SMOKE_DIR" + tar -xzf "$RUNNER_TEMP/package.tgz" -C "$SMOKE_DIR" test "$("$SMOKE_DIR/package/dist/index.js" --version)" = "$VERSION" - - name: Setup Node 24 for trusted publishing - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 - with: - node-version: '24' - - name: Verify version is unpublished env: VERSION: ${{ steps.release.outputs.version }} @@ -147,16 +136,47 @@ jobs: exit 1 fi - # npm publishes a tarball without running lifecycle scripts, so nothing - # rebuilds `dist` here. Provenance is attached automatically. + - name: Upload package + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: package + path: ${{ runner.temp }}/package.tgz + if-no-files-found: error + retention-days: 1 + + # npm trusted publishing trades this job's OIDC token for a short-lived npm + # credential, so no npm token is stored anywhere. npm accepts the token only + # from GitHub-hosted runners, and only this job can mint one: it runs no + # repository code, it publishes the tarball the build job tested. npm runs no + # lifecycle scripts for a tarball, so nothing is rebuilt here, and provenance + # is attached automatically. + publish: + name: publish + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + id-token: write + steps: + # Node 24 bundles npm 11.5.1+, which trusted publishing requires. + - name: Setup Node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 + with: + node-version: '24' + + - name: Download package + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: package + path: ${{ runner.temp }} + - name: Publish to npm env: - TARBALL: ${{ steps.pack.outputs.tarball }} - NPM_TAG: ${{ steps.release.outputs.tag }} - run: npm publish "$TARBALL" --access public --tag "$NPM_TAG" + NPM_TAG: ${{ needs.build.outputs.tag }} + run: npm publish "$RUNNER_TEMP/package.tgz" --access public --tag "$NPM_TAG" - name: Summarize release env: - VERSION: ${{ steps.release.outputs.version }} - NPM_TAG: ${{ steps.release.outputs.tag }} + VERSION: ${{ needs.build.outputs.version }} + NPM_TAG: ${{ needs.build.outputs.tag }} run: echo "Published sim@$VERSION with the '$NPM_TAG' tag." diff --git a/.github/workflows/publish-sim-setup.yml b/.github/workflows/publish-sim-setup.yml index 725341b945e..3356f40ed9f 100644 --- a/.github/workflows/publish-sim-setup.yml +++ b/.github/workflows/publish-sim-setup.yml @@ -13,35 +13,34 @@ on: # A change to how the package is built or published has to republish it. - '.github/workflows/publish-sim-setup.yml' -# npm trusted publishing: the publish step trades the runner's OIDC token for a -# short-lived npm credential, so no npm token is stored anywhere. permissions: contents: read - id-token: write concurrency: group: publish-sim-setup-${{ github.ref }} cancel-in-progress: false jobs: - publish: - name: publish + build: + name: build runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 + outputs: + version: ${{ steps.release.outputs.version }} + tag: ${{ steps.release.outputs.tag }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 with: persist-credentials: false - # Node 24 bundles npm 11.5.1+, which trusted publishing requires; the - # published bundle is still smoke-tested on Node 20 below, its minimum - # supported runtime. + # Vitest 5 requires Node 22.12+; the published bundle is still + # smoke-tested on Node 20 below, its minimum supported runtime. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '24' + node-version: '22' - name: Auto-bump package version run: bun run bump:npm-packages sim-setup @@ -106,15 +105,11 @@ jobs: } >> "$GITHUB_OUTPUT" # Bun packs because it rewrites `workspace:` ranges to real versions, - # which `npm pack` would publish verbatim. The smoke test and the publish - # below both use this one tarball, so what was tested is what ships. + # which `npm pack` would publish verbatim. The smoke test below and the + # publish job both use this one tarball, so what was tested is what ships. - name: Pack package - id: pack working-directory: packages/sim-setup - run: | - TARBALL="$RUNNER_TEMP/sim-setup.tgz" - bun pm pack --ignore-scripts --filename "$TARBALL" --quiet - echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + run: bun pm pack --ignore-scripts --filename "$RUNNER_TEMP/package.tgz" --quiet - name: Setup Node 20 for the bundle smoke test uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 @@ -123,14 +118,13 @@ jobs: - name: Smoke-test packed Node bundle env: - TARBALL: ${{ steps.pack.outputs.tarball }} VERSION: ${{ steps.release.outputs.version }} run: | set -euo pipefail SMOKE_DIR="$(mktemp -d "$RUNNER_TEMP/sim-setup-smoke.XXXXXX")" - tar -xzf "$TARBALL" -C "$SMOKE_DIR" + tar -xzf "$RUNNER_TEMP/package.tgz" -C "$SMOKE_DIR" - if tar -tzf "$TARBALL" | grep -Eq '^package/(apps|docker|helm|packages|scripts)/'; then + if tar -tzf "$RUNNER_TEMP/package.tgz" | grep -Eq '^package/(apps|docker|helm|packages|scripts)/'; then echo 'Packed setup package contains repository source directories.' >&2 exit 1 fi @@ -157,11 +151,6 @@ jobs: exit 1 fi - - name: Setup Node 24 for trusted publishing - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 - with: - node-version: '24' - - name: Verify version is unpublished env: VERSION: ${{ steps.release.outputs.version }} @@ -171,16 +160,47 @@ jobs: exit 1 fi - # npm publishes a tarball without running lifecycle scripts, so nothing - # rebuilds `dist` here. Provenance is attached automatically. + - name: Upload package + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: package + path: ${{ runner.temp }}/package.tgz + if-no-files-found: error + retention-days: 1 + + # npm trusted publishing trades this job's OIDC token for a short-lived npm + # credential, so no npm token is stored anywhere. npm accepts the token only + # from GitHub-hosted runners, and only this job can mint one: it runs no + # repository code, it publishes the tarball the build job tested. npm runs no + # lifecycle scripts for a tarball, so nothing is rebuilt here, and provenance + # is attached automatically. + publish: + name: publish + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + id-token: write + steps: + # Node 24 bundles npm 11.5.1+, which trusted publishing requires. + - name: Setup Node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 + with: + node-version: '24' + + - name: Download package + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: package + path: ${{ runner.temp }} + - name: Publish to npm env: - TARBALL: ${{ steps.pack.outputs.tarball }} - NPM_TAG: ${{ steps.release.outputs.tag }} - run: npm publish "$TARBALL" --access public --tag "$NPM_TAG" + NPM_TAG: ${{ needs.build.outputs.tag }} + run: npm publish "$RUNNER_TEMP/package.tgz" --access public --tag "$NPM_TAG" - name: Summarize release env: - VERSION: ${{ steps.release.outputs.version }} - NPM_TAG: ${{ steps.release.outputs.tag }} + VERSION: ${{ needs.build.outputs.version }} + NPM_TAG: ${{ needs.build.outputs.tag }} run: echo "Published sim-setup@$VERSION with the '$NPM_TAG' tag." diff --git a/.github/workflows/publish-ts-sdk.yml b/.github/workflows/publish-ts-sdk.yml index 5378aca9768..8a3c9c15b5e 100644 --- a/.github/workflows/publish-ts-sdk.yml +++ b/.github/workflows/publish-ts-sdk.yml @@ -6,12 +6,8 @@ on: paths: - 'packages/ts-sdk/**' -# `contents: write` creates the GitHub release. `id-token: write` is npm trusted -# publishing: the publish step trades the runner's OIDC token for a short-lived -# npm credential, so no npm token is stored anywhere. permissions: - contents: write - id-token: write + contents: read # A newer push waits for the running publish rather than racing its version check. concurrency: @@ -19,22 +15,24 @@ concurrency: cancel-in-progress: false jobs: - publish: - name: publish + build: + name: build runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 + outputs: + version: ${{ steps.package_version.outputs.version }} + exists: ${{ steps.version_check.outputs.exists }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 with: persist-credentials: false - # Node 24 bundles npm 11.5.1+, which trusted publishing requires. - name: Setup workspace uses: ./.github/actions/setup with: provider: ${{ vars.CI_PROVIDER }} - node-version: '24' + node-version: '22' - name: Run tests working-directory: packages/ts-sdk @@ -47,7 +45,7 @@ jobs: - name: Get package version id: package_version working-directory: packages/ts-sdk - run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" - name: Check if version already exists id: version_check @@ -55,15 +53,26 @@ jobs: VERSION: ${{ steps.package_version.outputs.version }} run: | if npm view "simstudio-ts-sdk@$VERSION" version &> /dev/null; then - echo "exists=true" >> $GITHUB_OUTPUT + echo "exists=true" >> "$GITHUB_OUTPUT" else - echo "exists=false" >> $GITHUB_OUTPUT + echo "exists=false" >> "$GITHUB_OUTPUT" fi - - name: Publish to npm + # Bun packs because it rewrites `workspace:` ranges to real versions, + # which `npm pack` would publish verbatim. + - name: Pack package if: steps.version_check.outputs.exists == 'false' working-directory: packages/ts-sdk - run: npm publish --access=public + run: bun pm pack --ignore-scripts --filename "$RUNNER_TEMP/package.tgz" --quiet + + - name: Upload package + if: steps.version_check.outputs.exists == 'false' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: package + path: ${{ runner.temp }}/package.tgz + if-no-files-found: error + retention-days: 1 - name: Log skipped publish if: steps.version_check.outputs.exists == 'true' @@ -71,25 +80,62 @@ jobs: VERSION: ${{ steps.package_version.outputs.version }} run: echo "Skipped publishing because version $VERSION already exists on npm" + # npm trusted publishing trades this job's OIDC token for a short-lived npm + # credential, so no npm token is stored anywhere. npm accepts the token only + # from GitHub-hosted runners, and only this job can mint one: it runs no + # repository code, it publishes the tarball the build job tested. npm runs no + # lifecycle scripts for a tarball, so nothing is rebuilt here, and provenance + # is attached automatically. + publish: + name: publish + needs: build + if: needs.build.outputs.exists == 'false' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + id-token: write + steps: + # Node 24 bundles npm 11.5.1+, which trusted publishing requires. + - name: Setup Node + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 + with: + node-version: '24' + + - name: Download package + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: package + path: ${{ runner.temp }} + + - name: Publish to npm + run: npm publish "$RUNNER_TEMP/package.tgz" --access public + + release: + name: release + needs: [build, publish] + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + steps: - name: Create GitHub Release - if: steps.version_check.outputs.exists == 'false' uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: typescript-sdk-v${{ steps.package_version.outputs.version }} - name: TypeScript SDK v${{ steps.package_version.outputs.version }} + tag_name: typescript-sdk-v${{ needs.build.outputs.version }} + name: TypeScript SDK v${{ needs.build.outputs.version }} body: | - ## TypeScript SDK v${{ steps.package_version.outputs.version }} - - Published simstudio-ts-sdk@${{ steps.package_version.outputs.version }} to npm. - + ## TypeScript SDK v${{ needs.build.outputs.version }} + + Published simstudio-ts-sdk@${{ needs.build.outputs.version }} to npm. + ### Installation ```bash - npm install simstudio-ts-sdk@${{ steps.package_version.outputs.version }} + npm install simstudio-ts-sdk@${{ needs.build.outputs.version }} ``` - + ### Documentation See the [README](https://github.com/simstudioai/sim/tree/main/packages/ts-sdk) or the [docs](https://docs.sim.ai/sdks/typescript) for more information. draft: false - prerelease: false \ No newline at end of file + prerelease: false