Repository navigation
fix(credentials): correct keyless setup instructions and enforce the … #285
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
| name: publish-sim-cli | |
| on: | |
| push: | |
| branches: [main, staging, dev] | |
| paths: | |
| - 'packages/sim-cli/**' | |
| # The build bundles its workspace dependencies, so a change there | |
| # changes what ships. | |
| - 'packages/utils/**' | |
| # A change to how the package is built or published has to republish it. | |
| - '.github/workflows/publish-sim-cli.yml' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: publish-sim-cli-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| 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 | |
| # 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: '22' | |
| - name: Auto-bump package version | |
| run: bun run bump:npm-packages sim-cli | |
| - name: Run tests | |
| working-directory: packages/sim-cli | |
| run: bun run test | |
| - name: Type-check package | |
| working-directory: packages/sim-cli | |
| run: bun run type-check | |
| # The usage-reporting destination is baked into the bundle at build time | |
| # (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: | |
| SIM_CLI_TELEMETRY_KEY: ${{ vars.SIM_CLI_TELEMETRY_KEY }} | |
| SIM_CLI_TELEMETRY_HOST: ${{ vars.SIM_CLI_TELEMETRY_HOST }} | |
| run: bun run build | |
| - name: Resolve release channel | |
| id: release | |
| working-directory: packages/sim-cli | |
| env: | |
| BRANCH: ${{ github.ref_name }} | |
| run: | | |
| BASE_VERSION="$(bun -p "require('./package.json').version")" | |
| if [[ ! "$BASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "Package version must be a stable X.Y.Z base, got '$BASE_VERSION'." >&2 | |
| exit 1 | |
| fi | |
| case "$BRANCH" in | |
| dev) | |
| VERSION="${BASE_VERSION}-dev.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}" | |
| TAG="dev" | |
| ;; | |
| staging) | |
| VERSION="${BASE_VERSION}-preview.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}" | |
| TAG="staging" | |
| ;; | |
| main) | |
| VERSION="$BASE_VERSION" | |
| TAG="latest" | |
| ;; | |
| *) | |
| echo "Unsupported release branch '$BRANCH'." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| bun pm pkg set "version=$VERSION" | |
| RESOLVED_VERSION="$(bun -p "require('./package.json').version")" | |
| if [ "$RESOLVED_VERSION" != "$VERSION" ]; then | |
| echo "Version injection mismatch: wanted '$VERSION', got '$RESOLVED_VERSION'." >&2 | |
| exit 1 | |
| fi | |
| { | |
| echo "version=$VERSION" | |
| echo "tag=$TAG" | |
| } >> "$GITHUB_OUTPUT" | |
| # `./embed` and the `#sim-cli/*` imports point at TypeScript source, which | |
| # only the monorepo's own Next.js app consumes (it transpiles the package). | |
| # The tarball ships `dist` alone, so published they would be dead entry | |
| # points; the smoke test below fails on any export that does not exist. | |
| - name: Drop source-only entry points | |
| working-directory: packages/sim-cli | |
| run: | | |
| jq 'del(.imports, .exports["./embed"])' package.json > "$RUNNER_TEMP/package.json" | |
| mv "$RUNNER_TEMP/package.json" package.json | |
| # Bun packs because it rewrites `workspace:` ranges to real versions, | |
| # 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 | |
| working-directory: packages/sim-cli | |
| 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 | |
| with: | |
| node-version: '20' | |
| - name: Smoke-test packed Node bundle | |
| env: | |
| VERSION: ${{ steps.release.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| SMOKE_DIR="$(mktemp -d "$RUNNER_TEMP/sim-cli-smoke.XXXXXX")" | |
| tar -xzf "$RUNNER_TEMP/package.tgz" -C "$SMOKE_DIR" | |
| test "$("$SMOKE_DIR/package/dist/index.js" --version)" = "$VERSION" | |
| node -e ' | |
| const fs = require("node:fs") | |
| const path = require("node:path") | |
| const root = path.join(process.argv[1], "package") | |
| const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) | |
| const targets = Object.values(pkg.exports ?? {}).flatMap((entry) => | |
| typeof entry === "string" ? [entry] : Object.values(entry) | |
| ) | |
| const missing = targets.filter((target) => !fs.existsSync(path.join(root, target))) | |
| if (missing.length > 0) { | |
| console.error("Exports point at files the tarball does not contain: " + missing.join(", ")) | |
| process.exit(1) | |
| } | |
| ' "$SMOKE_DIR" | |
| - name: Verify version is unpublished | |
| env: | |
| VERSION: ${{ steps.release.outputs.version }} | |
| run: | | |
| if bun pm view "sim@$VERSION" version > /dev/null 2>&1; then | |
| echo "sim@$VERSION is already published. The automatic version bump did not produce a unique release." >&2 | |
| exit 1 | |
| fi | |
| - 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: | |
| NPM_TAG: ${{ needs.build.outputs.tag }} | |
| run: npm publish "$RUNNER_TEMP/package.tgz" --access public --tag "$NPM_TAG" | |
| - name: Summarize release | |
| env: | |
| VERSION: ${{ needs.build.outputs.version }} | |
| NPM_TAG: ${{ needs.build.outputs.tag }} | |
| run: echo "Published sim@$VERSION with the '$NPM_TAG' tag." |