Deploy a real service in e2e, and cover the eight commands that neede… #53
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 to npm | |
| # Source-of-truth model (ported from prisma/prisma; see | |
| # docs/oss/versioning.md): | |
| # The version comes from the root `package.json` `version` field. | |
| # Maintainers advance it via `pnpm bump-version`, which writes it | |
| # across the workspace and is committed. This workflow can never | |
| # publish a version other than what is committed at HEAD, and never | |
| # rewrites a manifest to get there. | |
| # | |
| # Trigger model: | |
| # - push to `main` with the root `version` unchanged → publish | |
| # `<base>-dev.<run>` under the `dev` dist-tag (operator ruling | |
| # 2026-08-13: automated family repins deploy automatically; only a | |
| # real release needs a human). The dev suffix is stamped | |
| # ephemerally below and never committed. | |
| # - push to `main` with the root `version` changed → publish `<base>` | |
| # under its canonical dist-tag — `next` on the RC line, `latest` for | |
| # stable — and create a GitHub Release (marked pre-release on the RC | |
| # line). `latest` keeps serving the pre-8 CLI until the operator | |
| # deliberately moves it (operator ruling 2026-08-12). This is how a | |
| # merged `chore(release): ...` PR auto-ships. | |
| # - workflow_dispatch → publish `<base>` | |
| # under the chosen dist-tag (default `latest`); also the dry-run path. | |
| # | |
| # Scope: publishes `@prisma/cli-engine`, then `@prisma/cli`, then | |
| # `prisma` — the unscoped name is the same shell under the `prisma` bin, | |
| # and it goes last because it carries the whole tree (dependents after | |
| # dependencies). The engine versions | |
| # INDEPENDENTLY of the lockstep (ADR 0004, operator 2026-08-13): it | |
| # publishes at whatever version its own manifest carries, and because | |
| # an already-published version is treated as done (see publish_one), an | |
| # unbumped engine is a no-op while a bumped one ships in the same run. | |
| # Its dist-tag rides the run's tag; every consumer pins it exactly, so | |
| # the tag is cosmetic for the engine. `@prisma/compute` is excluded | |
| # from the lockstep by operator ruling (2026-08-10) and keeps its own | |
| # workflow (`publish-compute.yml`). | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["!**"] | |
| workflow_dispatch: | |
| inputs: | |
| dist-tag: | |
| description: "npm dist-tag. Empty = the version's canonical tag (next on the RC line, latest for stable). Pass latest explicitly to move latest onto an RC — that is the deliberate cutover act." | |
| required: false | |
| default: "" | |
| type: string | |
| dry-run: | |
| description: "Dry-run only (build + pack, no npm publish, no GitHub Release)." | |
| required: false | |
| default: true | |
| type: boolean | |
| concurrency: | |
| group: npm-publish | |
| cancel-in-progress: false | |
| jobs: | |
| publish: | |
| name: Publish packages to npm | |
| runs-on: ubuntu-latest | |
| # Only `main` may produce a real publish. A dry-run dispatch is permitted | |
| # from any branch so maintainers can validate the pipeline before merging | |
| # changes that touch publishing. The dry-run path performs no registry | |
| # writes and skips the GitHub Release step, so non-main runs cannot | |
| # affect production state. | |
| if: ${{ github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'true') }} | |
| permissions: | |
| contents: write # Required to create the GitHub Release + tag for latest publishes | |
| id-token: write # Required for npm OIDC Trusted Publishing | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| # Need history reaching `github.event.before` so `determine-version.ts` | |
| # can compare the root `package.json` version at that ref to HEAD and | |
| # decide whether this push is a release bump (publish `latest`) or a | |
| # routine commit (publish `dev`). A multi-commit push can place | |
| # `before` arbitrarily far back, so fetch the full history. | |
| fetch-depth: 0 | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version-file: .node-version | |
| cache: pnpm | |
| - name: Configure npm | |
| run: pnpm config set registry https://registry.npmjs.org | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Determine version | |
| id: version | |
| env: | |
| GITHUB_EVENT_NAME: ${{ github.event_name }} | |
| INPUT_DIST_TAG: ${{ github.event.inputs.dist-tag }} | |
| # `before` is the ref `main` pointed at before this push. | |
| # `determine-version.ts` reads the root `package.json` at that ref | |
| # to detect release bumps. Empty for `workflow_dispatch`, which | |
| # the script also handles. | |
| PUSH_BEFORE_SHA: ${{ github.event.before }} | |
| run: node scripts/determine-version.ts | |
| # Dev publishes only: stamp `<base>-dev.<run>` across the lockstep | |
| # manifests for this run. Ephemeral — nothing is committed; release | |
| # versions remain exactly what the commit says. The engine is | |
| # excluded from the sweep and ships at its own committed version. | |
| - name: Stamp dev version | |
| if: ${{ steps.version.outputs.publish == 'true' && steps.version.outputs.tag == 'dev' }} | |
| # The lockfile refresh is part of the stamp: pnpm verifies | |
| # manifests against the lockfile before running any script, so a | |
| # stamped workspace with an unstamped lockfile fails the next | |
| # pnpm invocation (it did, publish run 48). Same pairing | |
| # bump-version does for committed bumps; still ephemeral. | |
| run: | | |
| node scripts/set-version.ts "${{ steps.version.outputs.version }}" | |
| pnpm install --lockfile-only --no-frozen-lockfile | |
| - name: Build packages | |
| if: ${{ steps.version.outputs.publish == 'true' }} | |
| run: pnpm build | |
| # The assembled command tree, checked before anything reaches the | |
| # registry: every family command mounted, every mounted command | |
| # owned by a family, every path spelled as expected. A publish | |
| # that has lost a command fails here instead of shipping. | |
| - name: Check grammar completeness | |
| if: ${{ steps.version.outputs.publish == 'true' }} | |
| run: pnpm check:grammar | |
| - name: Run script tests | |
| if: ${{ steps.version.outputs.publish == 'true' }} | |
| run: pnpm test:scripts | |
| # The three conformance checks against what is about to ship: | |
| # built output imports only declared dependencies, every mounted | |
| # config-section validator survives hostile input, and the packed | |
| # tarballs survive a registry consumer's install — clean sandbox, | |
| # npm with --ignore-scripts, unpublished workspace siblings via | |
| # computed file: overrides, every declared bin started on plain | |
| # Node at exit 0, engine pins agreeing everywhere. Runs before | |
| # BOTH publish paths so the dry run is covered too. The tarballs | |
| # it packs land in artifacts/tarballs and are the ones uploaded | |
| # below: what was verified is what ships. (This absorbed the | |
| # interim scripts/tarball-smoke.mjs, which was written to this | |
| # check's design.) | |
| - name: Run conformance checks | |
| if: ${{ steps.version.outputs.publish == 'true' }} | |
| run: pnpm check:conformance | |
| # The verified tarballs, retrievable per run. On a real release the | |
| # same files are attached to the GitHub Release below. | |
| - name: Upload tarball artifacts | |
| if: ${{ steps.version.outputs.publish == 'true' }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: npm-tarballs | |
| path: artifacts/tarballs/*.tgz | |
| if-no-files-found: error | |
| # NODE_AUTH_TOKEN is intentionally NOT set. npm detects the OIDC | |
| # environment (id-token: write) and authenticates via Trusted | |
| # Publishing automatically. Setting NODE_AUTH_TOKEN to any value -- | |
| # even empty string -- would block OIDC. | |
| # | |
| # `pnpm publish` (not `npm publish`) so `workspace:<version>` | |
| # specifiers are rewritten to exact versions in the published | |
| # manifest. `--no-git-checks` because the packing step touches the | |
| # tree; the version itself is whatever the commit says. | |
| # | |
| # Publish order: the engine first, then the cli that depends on it. | |
| # Dry-run path: exercises the full publish pipeline (pack, validate | |
| # tarball contents, dependency rewriting) without touching the npm | |
| # registry. Use from any branch via `workflow_dispatch` to validate | |
| # changes that affect publishing before merging. | |
| - name: Publish packages (dry-run) | |
| if: ${{ steps.version.outputs.publish == 'true' && github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'true' }} | |
| run: | | |
| pnpm --filter @prisma/cli-engine publish --tag "${{ steps.version.outputs.tag }}" --access public --no-git-checks --dry-run | |
| pnpm --filter @prisma/cli publish --tag "${{ steps.version.outputs.tag }}" --access public --no-git-checks --dry-run | |
| pnpm --filter prisma publish --tag "${{ steps.version.outputs.tag }}" --access public --no-git-checks --dry-run | |
| # A rerun (or a re-publish dispatch) meets versions that are | |
| # already on the registry. npm refuses to publish over them — | |
| # correctly — but that refusal must not stop the run before the | |
| # Release step gets to repair a missing Release or its assets. An | |
| # already-published version is treated as done; every other | |
| # publish failure still fails the run. | |
| - name: Publish packages | |
| if: ${{ steps.version.outputs.publish == 'true' && (github.event_name != 'workflow_dispatch' || github.event.inputs.dry-run != 'true') }} | |
| env: | |
| NPM_CONFIG_PROVENANCE: "true" | |
| DIST_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| publish_one() { | |
| local out | |
| if out=$(pnpm --filter "$1" publish --tag "$DIST_TAG" --access public --no-git-checks 2>&1); then | |
| printf '%s\n' "$out" | |
| else | |
| printf '%s\n' "$out" | |
| if grep -qiE 'E409|EPUBLISHCONFLICT|cannot publish over|previously published' <<<"$out"; then | |
| echo "$1: this version is already on the registry — continuing so the Release step can run." | |
| else | |
| return 1 | |
| fi | |
| fi | |
| } | |
| publish_one @prisma/cli-engine | |
| publish_one @prisma/cli | |
| publish_one prisma | |
| # Emit a GitHub Release for releases only — runs whose dist-tag is | |
| # the canonical one for their version (`next` on the RC line, | |
| # `latest` for stable; `release` from determine-version.ts). Marked | |
| # pre-release on the RC line. Beta / preview cuts publish to npm | |
| # but do not produce a Release — those would drown out the | |
| # changelog signal. The Release is created at $GITHUB_SHA so the | |
| # tag points at the same commit the publish ran from. | |
| # | |
| # This repo's releases are immutable: once published, neither the | |
| # assets nor the tag can change (uploading to a published release | |
| # answers HTTP 422). So the Release is created as a DRAFT with the | |
| # smoked tarballs already attached, then published — assets first, | |
| # publish second. On a rerun that finds the Release already | |
| # published there is nothing left to repair; the step says so and | |
| # succeeds. | |
| - name: Create GitHub Release | |
| if: ${{ steps.version.outputs.publish == 'true' && steps.version.outputs.release == 'true' && (github.event_name != 'workflow_dispatch' || github.event.inputs.dry-run != 'true') }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| VERSION: ${{ steps.version.outputs.version }} | |
| run: | | |
| if gh release view "v$VERSION" >/dev/null 2>&1; then | |
| echo "Release v$VERSION already exists and releases are immutable — nothing to repair." | |
| exit 0 | |
| fi | |
| PRERELEASE_FLAG="" | |
| case "$VERSION" in | |
| *-rc.*) PRERELEASE_FLAG="--prerelease" ;; | |
| esac | |
| gh release create "v$VERSION" \ | |
| --draft \ | |
| --target "$GITHUB_SHA" \ | |
| --title "v$VERSION" \ | |
| --generate-notes \ | |
| $PRERELEASE_FLAG \ | |
| artifacts/tarballs/*.tgz | |
| # A draft's tag does not exist yet, so `gh release edit | |
| # <tag> --draft=false` cannot address it; publish through the | |
| # API by the draft's id. | |
| release_id=$(gh api "repos/$GITHUB_REPOSITORY/releases" \ | |
| --jq ".[] | select(.draft and .tag_name == \"v$VERSION\") | .id" | head -1) | |
| if [ -z "$release_id" ]; then | |
| echo "Could not find the draft release for v$VERSION" >&2 | |
| exit 1 | |
| fi | |
| gh api -X PATCH "repos/$GITHUB_REPOSITORY/releases/$release_id" \ | |
| -F draft=false >/dev/null | |
| echo "Published release v$VERSION with $(ls artifacts/tarballs/*.tgz | wc -l | tr -d ' ') asset(s)." |