Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 107 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Publish to npm

# Every run publishes a dev build. A run also publishes a release when
# Every normal run publishes a dev build. A run also publishes a release when
# the push changed the root `version`, or a dispatch asked for one. The
# two are not alternatives: when they were, the release commit never
# reached the dev channel and `dev` sat on an older version than the
Expand All @@ -25,18 +25,123 @@ on:
required: false
default: true
type: boolean
engine-only:
description: "Test and publish only the engine, before its Composer and ORM consumers can update."
required: false
default: false
type: boolean

concurrency:
group: npm-publish
cancel-in-progress: false

jobs:
publish-engine:
name: Publish engine only
if: ${{ github.event_name == 'workflow_dispatch' && inputs.engine-only }}
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
pull-requests: read
id-token: write

steps:
- name: Verify the release source
id: source
env:
GH_TOKEN: ${{ github.token }}
DIST_TAG: ${{ inputs.dist-tag }}
run: |
if [ -n "$DIST_TAG" ] && [ "$DIST_TAG" != latest ]; then
echo "Engine releases use the latest dist-tag."
exit 1
fi
if [ "$GITHUB_REF" != refs/heads/main ]; then
pr=$(gh pr list --repo "$GITHUB_REPOSITORY" --state open --head "$GITHUB_REF_NAME" \
--json number,headRefOid,baseRefName,isCrossRepository,reviewDecision,reviews \
| jq -er --arg sha "$GITHUB_SHA" '.[] | select(.headRefOid == $sha and .baseRefName == "main" and .isCrossRepository == false and .reviewDecision == "APPROVED" and any(.reviews[]; .state == "APPROVED" and .commit.oid == $sha)) | .number')
echo "pr=$pr" >> "$GITHUB_OUTPUT"
fi

- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
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

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Verify the engine version
run: node scripts/check-engine-version.mjs origin/main

- name: Test the engine
run: pnpm --filter @prisma/cli-engine test

- name: Verify the packed engine
id: engine
run: |
version=$(node -p "require('./packages/cli-engine/package.json').version")
pnpm --filter @prisma/cli-engine pack --pack-destination "$RUNNER_TEMP/engine"
tarball="$RUNNER_TEMP/engine/prisma-cli-engine-$version.tgz"
sandbox=$(mktemp -d)
cd "$sandbox"
npm init -y
npm install "$tarball" --ignore-scripts --no-audit --no-fund
node --input-type=module -e 'await import("@prisma/cli-engine"); await import("@prisma/cli-engine/protocol"); await import("@prisma/cli-engine/testing");'
npm publish "$tarball" --dry-run --ignore-scripts --access public --tag latest
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tarball=$tarball" >> "$GITHUB_OUTPUT"

- name: Upload the verified engine
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: engine-tarball
path: ${{ steps.engine.outputs.tarball }}
if-no-files-found: error

- name: Verify the source has not changed
if: ${{ !inputs.dry-run }}
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ steps.source.outputs.pr }}
run: |
if [ -n "$PR_NUMBER" ]; then
gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
--json state,headRefOid,baseRefName,isCrossRepository,reviewDecision,reviews \
| jq -e --arg sha "$GITHUB_SHA" '.state == "OPEN" and .headRefOid == $sha and .baseRefName == "main" and .isCrossRepository == false and .reviewDecision == "APPROVED" and any(.reviews[]; .state == "APPROVED" and .commit.oid == $sha)'
else
test "$(gh api "repos/$GITHUB_REPOSITORY/branches/main" --jq .commit.sha)" = "$GITHUB_SHA"
fi

- name: Publish the verified engine
if: ${{ !inputs.dry-run }}
env:
TARBALL: ${{ steps.engine.outputs.tarball }}
NPM_CONFIG_PROVENANCE: "true"
run: bash scripts/publish-packages.sh latest "$TARBALL"

- name: Verify the published engine resolves
if: ${{ !inputs.dry-run }}
env:
ENGINE_VERSION: ${{ steps.engine.outputs.version }}
run: node scripts/verify-published.mjs "@prisma/cli-engine@$ENGINE_VERSION"

publish:
name: Publish packages to npm
runs-on: ubuntu-latest
# Only `main` publishes; a dry-run dispatch may validate the pipeline
# from any branch.
if: ${{ github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && github.event.inputs.dry-run == 'true') }}
if: ${{ !inputs.engine-only && (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
Expand Down
12 changes: 11 additions & 1 deletion docs/oss/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Publishing requires:

- **Push access to `main`** — pushing to `main` or merging a release PR is restricted to maintainers.
- **A green run of the [`Publish to npm`](../../.github/workflows/publish.yml) workflow.** The workflow uses npm OIDC trusted publishing — no long-lived `NPM_TOKEN` exists in repository secrets, so a leaked secret cannot be used to publish out-of-band. Each published tarball carries an [npm provenance attestation](https://docs.npmjs.com/generating-provenance-statements) tying it to this repository and the workflow run that produced it.
- The workflow only publishes from `main`. Dry-runs are permitted from any branch (see "validate publish changes" below); every step that would mutate external state is independently guarded.
- CLI packages publish only from `main`. An engine-only dispatch can also publish from the current head of an approved, same-repository pull request targeting `main`. This lets the engine publish before Composer and ORM can update their exact peers. Every publishing step is guarded; dry-runs never publish.

## Mechanism: how we deliver the contract

Expand Down Expand Up @@ -85,6 +85,16 @@ The release cadence is one PR per release (on the RC line: one PR per `rc.N`). A

If the publish needs to be re-run (transient registry failure, etc.), a maintainer can dispatch the [`Publish to npm`](../../.github/workflows/publish.yml) workflow from `main` with `dist-tag: latest` and `dry-run=false`; the workflow re-publishes the version currently committed at HEAD, and because the chosen tag matches the canonical one it also re-creates the GitHub Release if it is missing. This is the same path used to cut a hand-rolled `beta` (`dist-tag=beta`, no Release).

## Procedure: release the engine before its consumers

Dispatch `publish.yml` from the approved engine PR's branch with `engine-only=true` and `dry-run=true`. The workflow runs the engine's build, typecheck and tests, packs it with pnpm, installs the tarball into a clean npm project, and imports each public entrypoint. It does not stamp versions or publish a CLI.

Once that passes, dispatch the same branch with `engine-only=true` and `dry-run=false`. Both source checks require an approval for the exact dispatched commit, not an approval carried over from an older PR head. The workflow publishes the verified engine tarball through trusted publishing. It keeps the existing `publish.yml` identity and records the dispatched commit in npm provenance. An already-published version is a no-op, so a rerun can still reach registry verification.

When the PR is checked again, the engine version guard compares its source with the commit recorded in that artifact's npm provenance. The published version is accepted only while the engine remains unchanged; further changes still require a new version.

Release Composer and ORM against that engine version, update both CLI consumers, and run full CLI conformance before merging the engine PR or cutting the CLI release. No conformance exceptions are needed.

## Procedure: validate publish changes

The publish workflow's `dry-run` mode (the input default) can be invoked from any branch to validate that the publish pipeline still works after touching `publish.yml`, `set-version.ts`, `determine-version.ts`, or the build scripts. A dry-run exercises `pnpm publish --dry-run` against both CLI packages and skips the registry publish + GitHub Release.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lint:fix": "biome check . --write",
"bump-version": "node scripts/bump-version.ts",
"test": "turbo run test",
"test:scripts": "node --test scripts/determine-version-utils.test.ts scripts/set-version-utils.test.ts scripts/bump-cli-engine-version-utils.test.ts scripts/resolve-package-version.test.mjs scripts/update-product-versions.test.mjs scripts/verify-published.test.mjs",
"test:scripts": "node --test scripts/determine-version-utils.test.ts scripts/set-version-utils.test.ts scripts/bump-cli-engine-version-utils.test.ts scripts/resolve-package-version.test.mjs scripts/update-product-versions.test.mjs scripts/verify-published.test.mjs scripts/check-engine-version.test.mjs",
"typecheck": "turbo run typecheck",
"prisma-cli": "tsx packages/cli/src/bin.ts",
"prisma": "tsx packages/cli/src/bin.ts",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli-engine/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@prisma/cli-engine",
"version": "0.6.2",
"version": "0.6.3",
Comment thread
AmanVarshney01 marked this conversation as resolved.
"description": "The execution engine of the unified Prisma CLI.",
"type": "module",
"exports": {
Expand Down Expand Up @@ -48,7 +48,7 @@
"dependencies": {
"@clack/prompts": "1.5.0",
"@stricli/core": "1.3.0",
"arktype": "2.2.3",
"arktype": "^2.2.7",
"c12": "3.3.4",
"colorette": "^2.0.20",
"package-manager-detector": "1.8.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"dependencies": {
"@manypkg/tools": "^2.1.2",
"@prisma/cli-engine": "workspace:0.6.2",
"@prisma/cli-engine": "workspace:0.6.3",
"@prisma/composer-cli": "0.26.0",
"@prisma/compute-sdk": "0.43.0",
"@prisma/management-api-sdk": "1.80.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/prisma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
},
"dependencies": {
"@manypkg/tools": "^2.1.2",
"@prisma/cli-engine": "workspace:0.6.2",
"@prisma/cli-engine": "workspace:0.6.3",
"@prisma/composer-cli": "0.26.0",
"@prisma/compute-sdk": "0.43.0",
"@prisma/management-api-sdk": "1.80.0",
Expand Down
36 changes: 32 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading