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
91 changes: 0 additions & 91 deletions .github/workflows/auto-repin.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ name: Publish to npm
# 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
# 2026-08-13: automated product-version updates 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>`
Expand Down
114 changes: 114 additions & 0 deletions .github/workflows/update-product-versions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Update product CLI versions

# When composer or prisma/prisma publish a family package, this repo
# updates its pinned version automatically, and the merged update ships as a dev version of
# the CLI (operator ruling 2026-08-13; docs/oss/versioning.md). Only a
# real CLI release needs a human.
#
# Triggers:
# - repository_dispatch `family-published`: sent by the product
# repos' publish workflows the moment they ship.
# - schedule (daily): the backstop for a missed dispatch — the same
# comparison against the registry, so a lost event delays an update
# by at most a day.
# - workflow_dispatch: run it by hand.
#
# The PR is opened with DEPLOY_GITHUB_TOKEN — the deploy automation's
# own account, not a person's — rather than GITHUB_TOKEN, for two reasons:
# pull requests created with the workflow token never trigger CI, and
# without CI the required checks never report, so auto-merge would hang
# forever. The PAT is the wmadden-electric bot's.

on:
repository_dispatch:
types: [family-published]
schedule:
- cron: "17 6 * * *"
workflow_dispatch:

concurrency:
group: update-product-versions
cancel-in-progress: false

jobs:
update:
name: Update pinned versions against the registry
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- 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: Compare pins against the registry
id: compare
run: node scripts/update-product-versions.mjs

# `pnpm install --no-frozen-lockfile` scoped to the lockfile
# refresh: the version update edits one manifest, and the PR must carry the
# matching lockfile or every CI job fails on --frozen-lockfile.
- name: Refresh the lockfile
if: ${{ steps.compare.outputs.changed == 'true' }}
run: |
corepack enable
pnpm install --lockfile-only --no-frozen-lockfile

# A second publish arriving before the first update PR merges must
# not open a duplicate: the dispatch's 204 only says GitHub accepted
# the event, not that anything was processed. An open PR from this
# workflow already carries whatever the registry now says (the
# comparison is against the registry, not against the event), so the
# right move is to leave it alone and let its own merge pick up both
# publishes.
- name: Skip if an update PR is already open
id: existing
if: ${{ steps.compare.outputs.changed == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
OPEN=$(gh pr list --state open --json headRefName \
--jq '[.[] | select(.headRefName | startswith("auto/update-product-versions-"))] | length')
if [ "$OPEN" != "0" ]; then
echo "An update PR is already open; leaving it to pick up this publish too."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Open the version-update PR with auto-merge
if: ${{ steps.compare.outputs.changed == 'true' && steps.existing.outputs.skip == 'false' }}
env:
GH_TOKEN: ${{ secrets.DEPLOY_GITHUB_TOKEN }}
SUMMARY: ${{ steps.compare.outputs.summary }}
run: |
if [ -z "$GH_TOKEN" ]; then
echo "::error::DEPLOY_GITHUB_TOKEN is not configured; cannot open a CI-triggering PR. Add the deploy automation's GitHub token as the DEPLOY_GITHUB_TOKEN repository secret (see docs/oss/release-automation.md)."
exit 1
fi
BRANCH="auto/update-product-versions-$(date -u +%Y%m%d%H%M%S)"
git config user.name "willbot"
git config user.email "w.a.madden+machine@gmail.com"
git checkout -b "$BRANCH"
git add packages/cli/package.json pnpm-lock.yaml
git commit -s --trailer "Signed-off-by: Will Madden <madden@prisma.io>" \
-m "Update product CLI packages to their latest published versions: $SUMMARY" \
-m "Opened automatically (update-product-versions.yml); merging ships a dev version of the CLI. A real release stays a human act."
git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$BRANCH"
PR_URL=$(gh pr create \
--title "Update product CLI packages: $SUMMARY" \
--body "Automated version update from the registry ($SUMMARY). Merging publishes a \`dev\` version of the CLI automatically; the conformance checks on this PR are what stand between a family publish and that dev build. Opened by update-product-versions.yml." \
--base main --head "$BRANCH")
gh pr merge --auto --squash "$PR_URL"
echo "Opened $PR_URL with auto-merge armed."
2 changes: 1 addition & 1 deletion docs/architecture/adrs/0004-engine-version-pinning.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Per edge:

**Exact now, range later.** During the rc line the engine breaks its consumers deliberately, slice by slice, so a version range would be fiction. Post-GA, the recorded destination is widening the peers to a range under a written engine compatibility contract, so a non-breaking engine release ships in the next shell release with zero family republishes. Widening is a deliberate future decision against that contract, not a drift.

**Engine versioning — RULED (operator, 2026-08-13): the engine versions independently.** Under the shell lockstep it previously shared, every CLI release manufactured a new engine version — and with it a family-repin obligation — whether or not the engine changed. Decoupled, an engine version means "the engine changed", the exact peers stay valid between real engine changes, and the repin train runs only when there is something to repin for. Mechanically: the engine is excluded from `set-version.ts`'s lockstep (alongside `@prisma/compute`), follows honest pre-1.0 semver (breaking bumps the minor), and publishes at its own manifest version, with an already-published version treated as a no-op by the publish workflow.
**Engine versioning — RULED (operator, 2026-08-13): the engine versions independently.** Under the shell lockstep it previously shared, every CLI release manufactured a new engine version — and with it an obligation for every family to update its pin — whether or not the engine changed. Decoupled, an engine version means "the engine changed", the exact peers stay valid between real engine changes, and the cross-repo update train runs only when there is something to update for. Mechanically: the engine is excluded from `set-version.ts`'s lockstep (alongside `@prisma/compute`), follows honest pre-1.0 semver (breaking bumps the minor), and publishes at its own manifest version, with an already-published version treated as a no-op by the publish workflow.

## Enforcement

Expand Down
4 changes: 2 additions & 2 deletions docs/oss/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The transition onto the RC line is a one-time bump from the pre-8 base to `8.0.0

Every lockstep workspace package — publishable, private, and the workspace root — carries the same `version`. One read of root [`package.json`](../../package.json) answers "what version is this code?" for the repository.

**Exceptions:** `@prisma/compute` versions independently, pending extraction to another repository (operator ruling 2026-08-10), and keeps its own publish workflow ([`publish-compute.yml`](../../.github/workflows/publish-compute.yml)). `@prisma/cli-engine` also versions independently ([ADR 0004](../architecture/adrs/0004-engine-version-pinning.md), operator ruling 2026-08-13): an engine version means "the engine changed", not "the CLI released", which is what keeps the exact peer pins the product CLI packages hold on it cheap — they repin only when the engine actually moves. The engine follows honest pre-1.0 semver (a breaking change bumps the minor); bumping it is one command — `pnpm bump-cli-engine-version <patch|minor|X.Y.Z>` — which edits `packages/cli-engine/package.json`, the shell's `workspace:<engine version>` pin, and the lockfile together, landed as a reviewed commit like any other version change (run it in the PR that changes the engine). Both packages are hard-excluded in [`scripts/set-version.ts`](../../scripts/set-version.ts), which still sweeps their `workspace:` pins on lockstep siblings so those never go stale. At publish time the engine ships at its own manifest version; an already-published engine version is a no-op. The engine's own line continues from `0.1.0` (after the published `0.0.x` series); the `8.0.0-rc.N` engine versions that shipped while it was still in lockstep are burned values — they exist on the registry, nothing pins them, and version numbers are never reused.
**Exceptions:** `@prisma/compute` versions independently, pending extraction to another repository (operator ruling 2026-08-10), and keeps its own publish workflow ([`publish-compute.yml`](../../.github/workflows/publish-compute.yml)). `@prisma/cli-engine` also versions independently ([ADR 0004](../architecture/adrs/0004-engine-version-pinning.md), operator ruling 2026-08-13): an engine version means "the engine changed", not "the CLI released", which is what keeps the exact peer pins the product CLI packages hold on it cheap — they update that pin only when the engine actually moves. The engine follows honest pre-1.0 semver (a breaking change bumps the minor); bumping it is one command — `pnpm bump-cli-engine-version <patch|minor|X.Y.Z>` — which edits `packages/cli-engine/package.json`, the shell's `workspace:<engine version>` pin, and the lockfile together, landed as a reviewed commit like any other version change (run it in the PR that changes the engine). Both packages are hard-excluded in [`scripts/set-version.ts`](../../scripts/set-version.ts), which still sweeps their `workspace:` pins on lockstep siblings so those never go stale. At publish time the engine ships at its own manifest version; an already-published engine version is a no-op. The engine's own line continues from `0.1.0` (after the published `0.0.x` series); the `8.0.0-rc.N` engine versions that shipped while it was still in lockstep are burned values — they exist on the registry, nothing pins them, and version numbers are never reused.

The lockstep set is: the workspace root, `packages/cli`, `packages/cli-telemetry`, `packages/cli-conformance`, and `packages/tsconfig`. Private packages are never published (`pnpm publish` skips them), but they still version in lockstep so a contributor cloning the repo at any commit sees one consistent answer to "what version is this code?". Workspace-internal dependencies are pinned as `workspace:<version>` (e.g. `workspace:8.0.0-rc.1`); pnpm resolves them locally during development and rewrites them to the exact version at publish time, so every published package carries an exact-version pin on its siblings.

Expand All @@ -30,7 +30,7 @@ The npm registry exposes the CLI packages under these dist-tags:
- **`next`** — the Prisma 8 RC line (`8.0.0-rc.N`). A merged release PR on the RC line publishes here automatically.
- **`beta`** — reserved for hand-cut previews ahead of significant changes, published by dispatching the workflow with that dist-tag. Routine releases do not use this tag.

- **`dev`** — every routine push to `main` publishes `<base>-dev.<run>` here automatically (operator ruling 2026-08-13, superseding the earlier "no dev channel" ruling). The suffix derives from the workflow run number and is stamped ephemerally in CI, never committed, so release versions remain exactly what a commit says. The channel exists so automated family repins ([`auto-repin.yml`](../../.github/workflows/auto-repin.yml)) deploy without a human: composer's and prisma/prisma's publish workflows dispatch to this repo, an auto-merge repin PR runs the full quality and conformance checks, and its merge ships the dev build. Only a real release — an `rc.N` bump under `next`, or moving `latest` — is a human act.
- **`dev`** — every routine push to `main` publishes `<base>-dev.<run>` here automatically (operator ruling 2026-08-13, superseding the earlier "no dev channel" ruling). The suffix derives from the workflow run number and is stamped ephemerally in CI, never committed, so release versions remain exactly what a commit says. The channel exists so automated product-version updates ([`update-product-versions.yml`](../../.github/workflows/update-product-versions.yml)) deploy without a human: composer's and prisma/prisma's publish workflows dispatch to this repo, an auto-merge version-update PR runs the full quality and conformance checks, and its merge ships the dev build. Only a real release — an `rc.N` bump under `next`, or moving `latest` — is a human act.

PR previews go through [`pkg.pr.new`](https://pkg.pr.new) ([`preview-cli-package.yml`](../../.github/workflows/preview-cli-package.yml)); they carry the committed base version and install via per-commit URLs, not dist-tags.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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/auto-repin.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",
"typecheck": "turbo run typecheck",
"prisma-cli": "tsx packages/cli/src/bin.ts",
"prisma": "tsx packages/cli/src/bin.ts",
Expand Down
2 changes: 1 addition & 1 deletion scripts/determine-version-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface VersionResult {

/**
* The version a routine main push publishes under the `dev` dist-tag
* (operator ruling 2026-08-13: automatic repins deploy automatic dev
* (operator ruling 2026-08-13: automatic product-version updates deploy dev
* versions; only a real release needs a human). The run number makes
* the suffix monotonic per workflow run without reading the registry.
*/
Expand Down
6 changes: 3 additions & 3 deletions scripts/determine-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* operator deliberately moves it.
* Otherwise `<base>-dev.<run>` under the `dev`
* dist-tag: every routine main push — an
* automated family repin above all — ships an
* automated product-version update above all — ships an
* installable dev build automatically
* (operator ruling 2026-08-13).
* - `workflow_dispatch` → `<base>` (no suffix), dist-tag from
Expand Down Expand Up @@ -163,8 +163,8 @@ switch (eventName) {
result = { version: baseVersion, tag: releaseDistTag(baseVersion) };
} else if (previous.available) {
// Routine push: publish `<base>-dev.<run>` under `dev` (operator
// ruling 2026-08-13 — automatic repins from the product repos
// deploy automatically; only a real release needs a human). The
// ruling 2026-08-13 — automatic product-version updates deploy
// automatically; only a real release needs a human). The
// suffix is derived here and stamped ephemerally in CI; it is
// never committed, so releases remain committed-at-HEAD.
const runNumber = process.env.GITHUB_RUN_NUMBER ?? "";
Expand Down
Loading
Loading