Skip to content

Attach the tarballs before the Release publishes, because published means frozen - #165

Closed
wmadden-electric wants to merge 14 commits into
mainfrom
claude/s7-release-pipeline-rc1-92c89d
Closed

Attach the tarballs before the Release publishes, because published means frozen#165
wmadden-electric wants to merge 14 commits into
mainfrom
claude/s7-release-pipeline-rc1-92c89d

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

The grounding example

The first real next publish (run 31618278670) shipped both packages to npm and then failed its final step:

https://github.com/prisma/prisma-cli/releases/tag/v8.0.0-rc.1
HTTP 422: Cannot upload assets to an immutable release.
##[error]Process completed with exit code 1

v8.0.0-rc.1 exists, marked pre-release — with no tarballs attached, and none can ever be attached now.

The decision

This repo's releases are immutable: publishing freezes assets and tag. So the Release step now creates the Release as a draft with the smoked tarballs already attached, then publishes it through the API by the draft's id — assets first, publish second. A draft's tag does not exist yet, so gh release edit <tag> cannot address it; the id lookup handles that. A rerun that finds the Release already published logs that there is nothing to repair and succeeds, instead of failing a run whose npm publish completed.

v8.0.0-rc.1 itself stays assetless — immutability cuts both ways. Its tarballs remain retrievable as the run's workflow artifacts and from npm itself. The next release gets its assets.

Alternatives considered

  • Upload then publish via gh release create without --draft — what the code did; gh creates the release published and uploads after, which immutability rejects.
  • Disable immutable releases — the setting is a supply-chain protection worth keeping; the pipeline should conform to it, not the reverse.
  • Delete and recreate v8.0.0-rc.1 with assets — immutable releases cannot be deleted; and even if they could, replacing a published release quietly would defeat the immutability guarantee.

🤖 Generated with Claude Code

wmadden-electric and others added 13 commits August 12, 2026 14:27
…e + rc1, STOPs open

The slice contract for S7: mount the ORM family, make the grammar
completeness check fail the build, create the prisma package, commit
all product pins, and extend publish.yml to emit and verify the rc1
artifacts. Eight operator questions are open (trigger shape, the one
action, package shape, exception-list ratification, S6 wiring, S8
ordering, pin convergence, publish credentials); nothing dispatches
until they are ruled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…is one binary

Applies the operator's two rulings (2026-08-12): rc1 ships as
@prisma/cli under the existing prisma-cli bin, with the bare prisma
name a follow-up; the slice's goal is combining all available
commands into one binary, with exception-list reconciliation also
deferred. Closes STOP-2/3/4/8; records working defaults so D1/D2
proceed while STOP-1/5/6/7 stay open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… of a prisma package

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`prisma migration list`, `prisma db verify`, `prisma init` and the rest
of the ORM family existed in no binary this repo shipped. They were
published in @prisma/orm-toolchain, mounted nowhere. This mounts the
family in the v8 shell, so one binary answers platform, composer and
ORM commands.

The family keys its commands by the path they mount at, so the mount is
its own map spread in whole; its `orm` config section, docs base and
redirect table ride the family object, with nothing wired per command.
Four group briefs (contract, db, migration, ref) join cliGroups.

The dependency is exact and committed at 8.0.0-rc.1-dev.40, the interim
version the contract's working default names. It pins
@prisma/cli-engine 0.0.9 while this workspace ships 8.0.0-rc.1, the same
second engine copy composer's pin already installs; both close when the
rc lines converge.

Tests: the mount-coverage check gains the family and its 22 paths, and
a new v8-orm-mount suite runs `migration list` end to end against a
fixture project (exit 0, real result), proves the config section is
validated before a command runs, proves `migration apply` settles as the
family's typed redirect, and proves the root help names the new groups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The mount-coverage check ran in the test suite only, and the publish
path runs `pnpm build` plus `pnpm test:scripts` — so a tree missing a
command could be packed and published without that check ever seeing
it.

It is now `pnpm check:grammar`: the same vitest file, run as a turbo
task that builds its dependencies first and is never cached, wired into
pr-quality.yml as its own required job and into publish.yml before the
first publish step, under the same publish == 'true' condition its
neighbours carry. Nothing is reimplemented; the check that guards the
tree in tests is the check that guards the release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
An 8.x publish whose declared bin was the retiring commander shell
would misdescribe itself: `prisma-cli` resolved dist/cli.js, which
answers no composer and no ORM command. The bin map now points at
dist/v8/cli.js — the tree the grammar check guards. The legacy entry
keeps building and shipping inside the tarball; deleting it is S2d.

The new e2e test starts from the manifest rather than a hard-coded
path: it reads the declared bin, runs it with --version on plain Node
in a bare environment, and asserts the lockstep version comes back in
the result envelope at exit 0. Piped stdout is not a TTY, so the
shell answers in its JSON stream, and the test asserts the frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Operator review of the ORM mount: spreading the family's own command
map made the family, not the shell, the source of truth for where its
commands mount — against R12. The 22 mounts are now written out per
path, like every other family's.

Writing them out also put the ORM paths in front of the e2e-coverage
scan for the first time (it reads the source text, and a spread has no
keys to read). They are excluded with the reason the convention asks
for: ORM commands never call the management API this suite covers —
their real e2e lives in prisma/prisma per R7, and the shell proves
composition in v8-orm-mount.test.ts per R8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The publish workflow built, checked the grammar, and published. Nothing
between those steps ever installed the tarball the way a user would, so
a packed artifact that could not start outside the workspace would have
shipped without any check noticing.

The pipeline now packs both publishable tarballs and installs the CLI's
in a sandbox outside the repo: npm with --ignore-scripts (the runner
holds id-token: write; third-party postinstalls do not run at the
pipeline's most privileged moment), the unpublished engine resolved
through a computed npm override with an absolute file: path and a
version-qualified key, and every declared bin started on plain Node at
exit 0 — which since the bin flip means the v8 tree, so the start
crosses the composer and ORM import boundaries. The verified tarballs
upload as workflow artifacts and attach to the GitHub Release, so what
was verified is what is retrievable. The dry-run dispatch exercises all
of it without registry writes.

The override computation is a pure function with its own tests, run by
test:scripts. The mechanics follow S6's check-3b design (ruled
2026-08-12: S7 carries the check itself while S6 lands in parallel), so
the conformance slice absorbs this as a move, not a rewrite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ipeline

Operator rulings 2026-08-12: the existing publish mechanisms stand
(STOP-1); S7 carries the install smoke while S6 lands in parallel
(STOP-5); pin convergence waits until 8.0.0-rc.1 publishes (STOP-7).
STOP-6 closed by events - #162 merged first and this branch carries
the merge. versioning.md documents the verification stage between the
version decision and the registry; plan.md and deferred.md record
where S7 stands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Operator ruling 2026-08-12: merging an RC-line bump PR must not move
latest - the pre-v8 CLI keeps serving bare installs until the line is
ready. Each release now publishes under its version's canonical
dist-tag, decided by one pure function: next for 8.0.0-rc.N, latest
for stable. The GitHub Release (with the smoked tarballs attached)
follows releases rather than the latest tag, so RC releases get their
prerelease Release too.

The dispatch input's default changes from latest to empty, which
resolves to the canonical tag - so a routine re-publish dispatch of an
RC can never move latest by accident. Passing latest explicitly for an
RC version is the deliberate cutover act, and the input says so.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…the rulings

Review findings (CodeRabbit). The substantive one: a workflow rerun or
re-publish dispatch met npm's refusal to publish over an existing
version and died before the Release step could repair a missing
Release or its assets. The publish step now treats an
already-published version as done and still fails on every other
error.

The rest are record consistency: the rollout table's @prisma/cli row
follows the next-tag ruling; the spec and dispatch plan are marked
revision 3 with the final STOP dispositions instead of the working
defaults they were drafted against; two markdownlint complaints
(heading levels, a line starting with #162) fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…eans frozen

The first real next publish reached the Release step and failed there:
this repo's releases are immutable, so gh release create published
v8.0.0-rc.1 and the follow-up asset upload was refused with HTTP 422.
The step now creates the Release as a draft with the smoked tarballs
already attached, then publishes it through the API by the draft's id
(a draft's tag does not exist yet, so gh cannot address it by tag). A
rerun that finds the Release already published reports there is
nothing to repair and succeeds, instead of failing the run after a
complete npm publish.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@wmadden-electric, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 53d04dc0-ac29-4ddb-bf00-d36548e9fc5f

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae8d4f and 4bd400a.

📒 Files selected for processing (1)
  • .drive/projects/prisma-cli-v8/deferred.md

Summary by CodeRabbit

  • New Features

    • Added ORM command groups for contracts, databases, migrations, and references to the v8 CLI.
    • Updated the shipped CLI entry point to use the v8 command set.
    • Added tarball installation and command-startup validation for packaged releases.
  • Release Improvements

    • RC releases now use the next channel, while stable releases use latest.
    • Added automated grammar, packaging, smoke-test, and artifact checks before publishing.
    • Documented the v8 RC release process, versioning rules, and command coverage.

Walkthrough

The v8 CLI now mounts the ORM command family and validates command completeness. The shipped prisma-cli binary points to dist/v8/cli.js. Tarball smoke tests validate installation and execution of declared binaries. Release scripts and workflows use next for RC versions and latest for stable versions. Publishing now runs grammar and packaging checks, uploads verified artifacts, and manages canonical GitHub Releases. Documentation records the release contract and deferred work.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: attaching tarballs before publishing immutable GitHub Releases.
Description check ✅ Passed The description directly explains the immutable Release issue and the draft-first asset publication workflow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch claude/s7-release-pipeline-rc1-92c89d
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/s7-release-pipeline-rc1-92c89d
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/s7-release-pipeline-rc1-92c89d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…r path

Operator ruling 2026-08-12: cosmetic, not immediate. npm is healthy;
only the GitHub Release page is missing its tarballs, permanently,
because it published before the upload and releases are immutable.
The repair steps (merge #165, attempt deletion, re-dispatch) are in
the entry for whoever picks it up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Superseded by #166 — same two commits on a branch cut from current main. This PR's diff showed the entire already-merged S7 slice because #164 was squash-merged, which orphaned this branch's original commits.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.drive/projects/prisma-cli-v8/plan.md:
- Line 126: Update the release-status entry in plan.md to record that the
immutable v8.0.0-rc.1 Release has no assets and cannot be modified or recreated.
Clarify that future Releases will have tarball assets attached while still in
draft, while preserving the existing shipped-work summary.

In @.drive/projects/prisma-cli-v8/specs/s7-release.md:
- Around line 15-16: Update the stale STOP status across the three cited sites:
in .drive/projects/prisma-cli-v8/specs/s7-release.md lines 15-16, replace the
present-tense open-STOP statement with the final closed/deferred status; in
.drive/projects/prisma-cli-v8/plans/s7-release.md lines 16-19, mark the ordering
statement as historical or revise it for the closed STOPs; and in lines 78-80,
remove the obsolete STOP-1 blocker statement.
- Around line 174-188: The D5 release contract and plan must document the
immutable GitHub Release lifecycle: update
.drive/projects/prisma-cli-v8/specs/s7-release.md lines 174-188 and
.drive/projects/prisma-cli-v8/plans/s7-release.md lines 78-96 to require
creating a draft Release, attaching tarballs, and publishing npm packages by
Release ID; update docs/oss/versioning.md lines 58-69 to state that reruns may
create a missing Release but must not modify a published Release, while
retaining the existing deferred.md record for the assetless v8.0.0-rc.1 Release.

In @.github/workflows/publish.yml:
- Around line 210-235: Update the release handling around gh release view and
release_id so an existing matching release is queried for its draft status
rather than treated as complete unconditionally. Exit only when the release is
published; when a draft exists, reuse its ID, publish it, and reconcile assets
only if necessary instead of creating another release.

In `@packages/cli/tests/e2e-coverage.test.ts`:
- Around line 33-51: Update mountedCommands() to recognize both quoted and
unquoted object keys so format, init, lsp, migrate, and feedback are included in
the coverage scan. Then add e2e coverage for any newly detected ORM commands or
add justified entries to EXCLUSIONS for those that remain intentionally
excluded.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4860a1b1-ab73-4463-8572-b9a7d3cb95bd

📥 Commits

Reviewing files that changed from the base of the PR and between c5fe09d and 5ae8d4f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (26)
  • .drive/projects/prisma-cli-v8/assets/rollout-plan.md
  • .drive/projects/prisma-cli-v8/assets/s2/parity-divergences-s7.md
  • .drive/projects/prisma-cli-v8/deferred.md
  • .drive/projects/prisma-cli-v8/plan.md
  • .drive/projects/prisma-cli-v8/plans/s7-release.md
  • .drive/projects/prisma-cli-v8/specs/s7-release.md
  • .github/workflows/pr-quality.yml
  • .github/workflows/publish.yml
  • .gitignore
  • docs/oss/versioning.md
  • package.json
  • packages/cli/e2e/declared-bin.e2e.ts
  • packages/cli/package.json
  • packages/cli/src/v8/cli.ts
  • packages/cli/tests/e2e-coverage.test.ts
  • packages/cli/tests/fixtures/v8-orm-project/migrations/.gitkeep
  • packages/cli/tests/v8-mount-coverage.test.ts
  • packages/cli/tests/v8-orm-mount.test.ts
  • pnpm-workspace.yaml
  • scripts/determine-version-utils.test.ts
  • scripts/determine-version-utils.ts
  • scripts/determine-version.ts
  • scripts/tarball-smoke-utils.mjs
  • scripts/tarball-smoke-utils.test.mjs
  • scripts/tarball-smoke.mjs
  • turbo.json

release automation, pinned product versions, and the pipeline emitting
a publishable `prisma@8.0.0-rc1` artifact from a tagged commit. Ends
when the operator can publish with one action (project DoD).
Repo: prisma-cli. **In flight (PR #164).** Ruled 2026-08-12: rc1 publishes under the existing names (`@prisma/cli`, bin `prisma-cli`); the bare-`prisma` cutover follows once `prisma7` frees the name. Shipped so far: the ORM family mounted (one binary answers platform, composer and ORM), the grammar check promoted to `pnpm check:grammar` running in `pr-quality.yml` and before every publish, the declared bin flipped to the v8 tree, and the tarball install smoke in the publish path (packed tarballs verified out-of-workspace on plain Node, uploaded as artifacts, attached to the Release). The operator's one action is merging the bump PR, per `docs/oss/versioning.md`. Engine-pin convergence is deferred until `8.0.0-rc.1` publishes (contract STOP-7).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Record the immutable v8.0.0-rc.1 Release exception.

Line 126 says tarballs are attached to the Release. The existing v8.0.0-rc.1 Release has no assets and cannot be modified or recreated. State that future Releases attach assets while draft, and record this exception.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.drive/projects/prisma-cli-v8/plan.md at line 126, Update the release-status
entry in plan.md to record that the immutable v8.0.0-rc.1 Release has no assets
and cannot be modified or recreated. Clarify that future Releases will have
tarball assets attached while still in draft, while preserving the existing
shipped-work summary.

Comment on lines +15 to +16
implementation. STOP-1 and STOP-5…7 remain open for the release-side
deliverables; D1/D2 proceed on the recorded working defaults.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove or qualify stale STOP status.

These statements present STOPs as currently open even though the same records state that all STOPs are closed or deferred. This can cause operators to follow superseded release instructions.

  • .drive/projects/prisma-cli-v8/specs/s7-release.md#L15-L16: replace the present-tense open-STOP statement with the final status.
  • .drive/projects/prisma-cli-v8/plans/s7-release.md#L16-L19: mark the ordering statement as historical, or update it for the closed STOPs.
  • .drive/projects/prisma-cli-v8/plans/s7-release.md#L78-L80: remove the obsolete STOP-1 blocker statement.
📍 Affects 2 files
  • .drive/projects/prisma-cli-v8/specs/s7-release.md#L15-L16 (this comment)
  • .drive/projects/prisma-cli-v8/plans/s7-release.md#L16-L19
  • .drive/projects/prisma-cli-v8/plans/s7-release.md#L78-L80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.drive/projects/prisma-cli-v8/specs/s7-release.md around lines 15 - 16,
Update the stale STOP status across the three cited sites: in
.drive/projects/prisma-cli-v8/specs/s7-release.md lines 15-16, replace the
present-tense open-STOP statement with the final closed/deferred status; in
.drive/projects/prisma-cli-v8/plans/s7-release.md lines 16-19, mark the ordering
statement as historical or revise it for the closed STOPs; and in lines 78-80,
remove the obsolete STOP-1 blocker statement.

Comment on lines +174 to +188
**D5 — The pipeline: release commit → verified artifact → one action.**
Per STOP-1's ruling on trigger shape. Written against the recommendation
(STOP-1a): `publish.yml` gains an artifact-emission stage — after `pnpm
build` and the grammar check, it packs the engine and cli tarballs with
`pnpm pack`, runs the tarball smoke (S6 check 3b mechanics:
out-of-workspace install with computed absolute `file:` overrides,
`--ignore-scripts`, every declared bin starts on plain Node, exit 0,
under a timeout — which after D3 means the smoke exercises the v8 tree
and the composer/ORM family boundary), uploads the tarballs as workflow
artifacts, and attaches them to the GitHub Release it already creates.
The operator's one action for rc1 stays what versioning.md already
rules: merge the `chore(release)` bump PR. Everything after that push —
build, grammar check, smoke, npm publishes, Release + tag, artifact
upload — is the pipeline. Dry-run dispatch exercises all of it minus
registry writes.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- tracked candidate files ---'
git ls-files \
  '.drive/projects/prisma-cli-v8/specs/s7-release.md' \
  '.drive/projects/prisma-cli-v8/plans/s7-release.md' \
  'docs/oss/versioning.md' \
  '.github/workflows/publish.yml' \
  'scripts' | sed -n '1,120p'

printf '%s\n' '--- relevant workflow and release references ---'
rg -n -i -C 4 \
  'release|upload|artifact|dry.?run|publish-npm|determine-version|check:grammar|tarball-smoke|pnpm pack' \
  .github/workflows scripts docs/oss .drive/projects/prisma-cli-v8/specs/s7-release.md .drive/projects/prisma-cli-v8/plans/s7-release.md \
  2>/dev/null | sed -n '1,260p'

printf '%s\n' '--- target sections ---'
sed -n '1,115p' .drive/projects/prisma-cli-v8/plans/s7-release.md
sed -n '1,215p' .drive/projects/prisma-cli-v8/specs/s7-release.md
sed -n '45,85p' docs/oss/versioning.md

Repository: prisma/prisma-cli

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- publish.yml outline ---'
ast-grep outline .github/workflows/publish.yml

printf '%s\n' '--- publish.yml release and artifact logic ---'
rg -n -i -C 8 \
  'release|upload|artifact|dry.?run|publish|determine-version|version' \
  .github/workflows/publish.yml | sed -n '1,320p'

printf '%s\n' '--- exact contradictory status and D5 text ---'
nl -ba .drive/projects/prisma-cli-v8/specs/s7-release.md | sed -n '1,22p;174,190p;198,235p;276,305p'
nl -ba .drive/projects/prisma-cli-v8/plans/s7-release.md | sed -n '10,20p;78,97p'
nl -ba docs/oss/versioning.md | sed -n '55,85p'

printf '%s\n' '--- release-related action references ---'
rg -n -i -C 5 \
  'softprops/action-gh-release|gh release|create.*release|upload.*release|release.*asset|draft|immutable|existing Release|re-creates' \
  .github .drive docs scripts 2>/dev/null | sed -n '1,320p'

Repository: prisma/prisma-cli

Length of output: 46196


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

workflow = Path(".github/workflows/publish.yml").read_text()
spec = Path(".drive/projects/prisma-cli-v8/specs/s7-release.md").read_text()
plan = Path(".drive/projects/prisma-cli-v8/plans/s7-release.md").read_text()
docs = Path("docs/oss/versioning.md").read_text()
deferred = Path(".drive/projects/prisma-cli-v8/deferred.md").read_text()

def section(text, start, end=None):
    i = text.index(start)
    j = text.index(end, i) if end else len(text)
    return text[i:j]

d5 = section(spec, "**D5 —", "**D6 —")
plan_d5 = section(plan, "## D5 —", "## D6 —")
acceptance = section(spec, "## 5. Acceptance", "## 6. Out of scope")

checks = {
    "workflow creates draft before assets and publishes by release id":
        workflow.index("gh release create") < workflow.index("release_id=$(gh api") < workflow.index("gh api -X PATCH"),
    "workflow passes tarballs to draft creation":
        "artifacts/tarballs/*.tgz" in workflow[workflow.index("gh release create"):workflow.index("release_id=$(gh api")],
    "workflow handles existing published release as success":
        "already exists and releases are immutable" in workflow,
    "spec D5 states draft":
        bool(re.search(r"\bdraft\b", d5, re.I)),
    "spec acceptance states draft or publish-by-id":
        bool(re.search(r"\bdraft\b|publish(?:ed|ing)? by|release id", acceptance, re.I)),
    "plan D5 states draft":
        bool(re.search(r"\bdraft\b", plan_d5, re.I)),
    "docs states published release cannot be modified":
        bool(re.search(r"cannot|can't|immutable|cannot modify|cannot repair", docs, re.I)),
    "docs states missing-release rerun behavior":
        bool(re.search(r"re-creates.*Release|missing Release|missing.*release", docs, re.I)),
    "deferred records rc1 assetless and immutable":
        bool(re.search(r"v8\.0\.0-rc\.1.*no tarballs", deferred, re.I | re.S))
        and bool(re.search(r"immutable", deferred, re.I)),
}

for name, result in checks.items():
    print(f"{'PASS' if result else 'FAIL'}: {name}")

print("\nRelevant contract excerpts:")
for label, text in [("spec D5", d5), ("plan D5", plan_d5)]:
    print(f"\n--- {label} ---\n{text.strip()}")

print("\n--- docs rerun paragraph ---")
print(docs[docs.index("If the publish needs to be re-run"):docs.index("\n\n", docs.index("If the publish needs to be re-run"))])
PY

Repository: prisma/prisma-cli

Length of output: 3343


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path

docs = Path("docs/oss/versioning.md").read_text().splitlines()
print("--- versioning.md lines containing release immutability terms ---")
for number, line in enumerate(docs, 1):
    low = line.lower()
    if any(term in low for term in ("immutable", "cannot", "can't", "repair", "missing release", "re-create")):
        print(f"{number}: {line}")

print("\n--- deferred.md rc1 record ---")
deferred = Path(".drive/projects/prisma-cli-v8/deferred.md").read_text().splitlines()
for number, line in enumerate(deferred, 1):
    if 7 <= number <= 25:
        print(f"{number}: {line}")

print("\n--- exact documentation gap check ---")
paragraph = "\n".join(docs[67:70])
required_phrases = (
    "draft",
    "attach",
    "publish",
    "Release ID",
    "already-published",
    "cannot modify",
)
for phrase in required_phrases:
    print(f"{'PASS' if phrase.lower() in paragraph.lower() else 'FAIL'}: docs rerun paragraph contains {phrase!r}")
PY

Repository: prisma/prisma-cli

Length of output: 3226


Document the immutable GitHub Release lifecycle.

Add the existing workflow order—draft Release, attach tarballs, publish by Release ID—to the D5 contract and plan. State in docs/oss/versioning.md that reruns can create a missing Release but cannot modify a published Release. The existing deferred.md entry already records the assetless v8.0.0-rc.1 Release.

📍 Affects 3 files
  • .drive/projects/prisma-cli-v8/specs/s7-release.md#L174-L188 (this comment)
  • .drive/projects/prisma-cli-v8/plans/s7-release.md#L78-L96
  • docs/oss/versioning.md#L58-L69
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.drive/projects/prisma-cli-v8/specs/s7-release.md around lines 174 - 188,
The D5 release contract and plan must document the immutable GitHub Release
lifecycle: update .drive/projects/prisma-cli-v8/specs/s7-release.md lines
174-188 and .drive/projects/prisma-cli-v8/plans/s7-release.md lines 78-96 to
require creating a draft Release, attaching tarballs, and publishing npm
packages by Release ID; update docs/oss/versioning.md lines 58-69 to state that
reruns may create a missing Release but must not modify a published Release,
while retaining the existing deferred.md record for the assetless v8.0.0-rc.1
Release.

Comment on lines +210 to +235
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
if gh release view "v$VERSION" >/dev/null 2>&1; then
gh release edit "v$VERSION" \
--target "$GITHUB_SHA" \
--title "v$VERSION"
else
gh release create "v$VERSION" \
--target "$GITHUB_SHA" \
--title "v$VERSION" \
--generate-notes \
$PRERELEASE_FLAG
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

: "${VERSION:?Set VERSION to the release version without the v prefix}"

echo "Release records for v$VERSION:"
gh api --paginate "repos/$GITHUB_REPOSITORY/releases?per_page=100" \
  --jq ".[] | select(.tag_name == \"v$VERSION\") | {id, draft, published_at, assets: [.assets[].name]}"

Repository: prisma/prisma-cli

Length of output: 234


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/publish.yml"
sed -n '180,245p' "$file"
printf '\nRelease-related references:\n'
rg -n -C 3 'gh release|releases|release_id|artifacts/tarballs|draft' "$file"

Repository: prisma/prisma-cli

Length of output: 5656


🌐 Web query:

GitHub CLI gh release view draft release by tag behavior REST API GET release by tag draft

💡 Result:

The GitHub REST API endpoint GET /repos/{owner}/{repo}/releases/tags/{tag} is designed to retrieve published releases only [1][2]. It does not support retrieving draft releases, and attempting to use this endpoint for a draft release will typically result in a 404 Not Found error [3]. The GitHub CLI (gh) handles this limitation by using a fallback mechanism when you run gh release view <tag>. Because the standard REST API lookup fails for drafts, the CLI performs the following internal sequence [3]: 1. It initially attempts to fetch the release using the standard REST API endpoint (GET /repos/{owner}/{repo}/releases/tags/{tag}) [3]. 2. If that request returns a 404, the CLI falls back to using a GraphQL query to search for the release by tag within the repository [3]. 3. Once the release (including a draft) is located via GraphQL, the CLI retrieves the full release details using a subsequent REST API call to the individual release endpoint (GET /repos/{owner}/{repo}/releases/{id}) [3]. In summary, while the underlying REST API used to look up a release by its tag does not support drafts, the gh release view command is engineered to work around this by utilizing GraphQL and the release ID to successfully display draft release information, provided the user has sufficient permissions to see the draft [3]. Accessing draft releases requires appropriate repository permissions (typically write access) [4][1].

Citations:


🌐 Web query:

GitHub CLI gh release create existing tag draft release duplicate behavior

💡 Result:

When using the GitHub CLI (gh) to create a release, the behavior regarding existing tags and draft releases depends on how the command is configured and the state of your repository [1][2]. Key behaviors include: Automatic Tag Creation: If you run gh release create and the specified git tag does not already exist in the remote repository, the command will automatically create the tag based on the latest state of the default branch [1][3]. You can use the --verify-tag flag to force the command to abort if the tag does not exist instead of creating it [1][3]. Draft Releases and Tag Association: When you create a release as a draft (using --draft), GitHub may associate it with an "untagged" state or a temporary hash until it is published [4]. This is standard behavior for draft releases, which do not necessarily need to be permanently bound to a git tag while still in draft mode [4]. If you later publish the draft, it will typically associate with the intended tag [4]. Duplicate/Existing Release Issues: Race Conditions: There are known reports of race conditions when users attempt to delete an existing release and tag (e.g., gh release delete --cleanup-tag) and immediately follow it with gh release create [5]. If the delete operation has not fully propagated on the GitHub platform, the subsequent create command may incorrectly create a draft release or fail to associate properly [5]. Inserting a brief delay between the delete and create commands is a common workaround [5]. Local vs. Remote Tags: If a tag exists locally but has not been pushed to the remote repository, gh release create will not automatically push your local tag [6]. Instead, the API may create a new tag on the remote side, which might lack specific annotations present in your local version [6]. It is recommended to push tags manually before running the release command to ensure consistency [6]. To avoid issues with duplicate or unintentional draft behavior, ensure that: 1. Tags are pushed to the remote repository before running the release command if you want to use existing local tags [6]. 2. You provide sufficient time for deletion operations to process on the server side if automating a "delete and recreate" workflow [5]. 3. You use --verify-tag if you want to strictly prevent the creation of new tags [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/view/view.go -o "$tmp/view.go"
curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/create/create.go -o "$tmp/create.go"

printf '%s\n' '--- gh release view draft-related logic ---'
rg -n -C 8 'draft|GetReleaseByTag|GraphQL|release.*id|HTTPNotFound' "$tmp/view.go" || true

printf '%s\n' '--- gh release create draft/upload/publish-related logic ---'
rg -n -C 8 'Draft|draft|Upload|asset|CreateRelease|Publish' "$tmp/create.go" || true

Repository: prisma/prisma-cli

Length of output: 10004


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/view/view.go -o "$tmp/view.go"
curl -fsSL https://raw.githubusercontent.com/cli/cli/trunk/pkg/cmd/release/shared/http.go -o "$tmp/http.go" || true

printf '%s\n' '--- view command implementation ---'
sed -n '1,180p' "$tmp/view.go"

printf '%s\n' '--- release lookup helpers in CLI source ---'
curl -fsSL https://api.github.com/repos/cli/cli/git/trees/trunk?recursive=1 |
  jq -r '.tree[].path' |
  rg 'pkg/cmd/release/.+\.go$' |
  while read -r path; do
    url="https://raw.githubusercontent.com/cli/cli/trunk/$path"
    body="$(curl -fsSL "$url")"
    if printf '%s' "$body" | rg -q 'GetReleaseByTag|release.*tag|IsDraft|draft'; then
      printf '\n--- %s ---\n' "$path"
      printf '%s' "$body" | rg -n -C 5 'GetReleaseByTag|publishedReleaseExists|IsDraft|draft'
    fi
  done

Repository: prisma/prisma-cli

Length of output: 4577


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

for path in \
  pkg/cmd/release/shared/shared.go \
  pkg/cmd/release/shared/release.go \
  pkg/cmd/release/shared/fetch.go \
  pkg/cmd/release/shared/http.go \
  pkg/cmd/release/shared/api.go
do
  url="https://raw.githubusercontent.com/cli/cli/trunk/$path"
  if curl -fsSL "$url" -o "$tmp/$(basename "$path")"; then
    printf '\n--- %s ---\n' "$path"
    rg -n -C 8 'FetchRelease|GetRelease|releases/tags|graphql|IsDraft|draft' "$tmp/$(basename "$path")" || true
  fi
done

printf '\n--- local state-machine probe ---\n'
python3 - <<'PY'
states = {
    "published": {"view": 0, "create": 0, "publish": 0},
    "draft": {"view": 0, "create": 1, "publish": 0},
    "missing": {"view": 1, "create": 0, "publish": 1},
}
for name, s in states.items():
    if s["view"] == 0:
        outcome = "early success; draft remains unpublished" if name == "draft" else "early success"
    elif s["create"] == 0:
        outcome = "create then publish"
    else:
        outcome = "create failure"
    print(f"{name}: {outcome}")
PY

Repository: prisma/prisma-cli

Length of output: 5103


Resume an existing draft Release before creating a new one.

If gh release create --draft succeeds and the publish call fails, gh release view "v$VERSION" also finds the draft and exits successfully. The draft remains unpublished.

Query the matching Release and inspect draft. Exit only when the Release is published. If a matching draft exists, use its ID to publish it and reconcile assets only when required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml around lines 210 - 235, Update the release
handling around gh release view and release_id so an existing matching release
is queried for its draft status rather than treated as complete unconditionally.
Exit only when the release is published; when a draft exists, reuse its ID,
publish it, and reconcile assets only if necessary instead of creating another
release.

Comment on lines 33 to +51
const EXCLUSIONS: Readonly<Record<string, string>> = {
"contract emit": ORM_FAMILY_REASON,
"contract infer": ORM_FAMILY_REASON,
"db init": ORM_FAMILY_REASON,
"db schema": ORM_FAMILY_REASON,
"db sign": ORM_FAMILY_REASON,
"db update": ORM_FAMILY_REASON,
"db verify": ORM_FAMILY_REASON,
"migration check": ORM_FAMILY_REASON,
"migration graph": ORM_FAMILY_REASON,
"migration list": ORM_FAMILY_REASON,
"migration log": ORM_FAMILY_REASON,
"migration new": ORM_FAMILY_REASON,
"migration plan": ORM_FAMILY_REASON,
"migration show": ORM_FAMILY_REASON,
"migration status": ORM_FAMILY_REASON,
"ref delete": ORM_FAMILY_REASON,
"ref list": ORM_FAMILY_REASON,
"ref set": ORM_FAMILY_REASON,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline packages/cli/tests/e2e-coverage.test.ts --items all
rg -n -C 4 'EXCLUSIONS|format|init|lsp|migrate|mountedCommands' \
  packages/cli/tests/e2e-coverage.test.ts \
  packages/cli/src/v8/cli.ts

Repository: prisma/prisma-cli

Length of output: 10547


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat -n packages/cli/tests/e2e-coverage.test.ts | sed -n '25,185p'
printf '\n--- command descriptions in e2e suites ---\n'
rg -n -C 2 'describeCommand\(|["'\''](format|init|lsp|migrate)["'\'']' packages/cli/e2e packages/cli/tests
printf '\n--- ORM mount tests and command definitions ---\n'
rg -n -C 3 'format|init|lsp|migrate|describeCommand' packages/cli/e2e packages/cli/src | head -n 300

Repository: prisma/prisma-cli

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

cli = Path("packages/cli/src/v8/cli.ts").read_text()
coverage = Path("packages/cli/tests/e2e-coverage.test.ts").read_text()

marker = "mountedCommands: Readonly<Record<string, AnyCommand>> = {"
start = cli.index(marker)
end = cli.index("\n};", start)
block = cli[start + len(marker):end]

quoted = re.findall(r'^\s*"([^"]+)":', block, re.M)
all_keys = re.findall(r'^\s*(?:"([^"]+)"|([A-Za-z_$][\w$]*))\s*:', block, re.M)
all_keys = [a or b for a, b in all_keys]
print("quoted-key parser:", quoted)
print("all direct keys:", all_keys)
print("root commands omitted by coverage parser:",
      [key for key in ("format", "init", "lsp", "migrate") if key not in quoted])

e2e = set(re.findall(r'describeCommand\(\s*"([^"]+)"', "\n".join(
    p.read_text() for p in Path("packages/cli/e2e").glob("*.e2e.ts")
)))
print("root commands with describeCommand:", sorted(e2e & {"format", "init", "lsp", "migrate"}))
print("root commands in EXCLUSIONS:",
      sorted(set(re.findall(r'^\s*"([^"]+)":', coverage, re.M))
             & {"format", "init", "lsp", "migrate"}))
PY

Repository: prisma/prisma-cli

Length of output: 3475


Include unquoted keys in the coverage scan.

mountedCommands() only matches quoted keys, so it omits format, init, lsp, migrate, and feedback. Update the parser, then add e2e coverage or justified EXCLUSIONS entries for the omitted ORM commands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/tests/e2e-coverage.test.ts` around lines 33 - 51, Update
mountedCommands() to recognize both quoted and unquoted object keys so format,
init, lsp, migrate, and feedback are included in the coverage scan. Then add e2e
coverage for any newly detected ORM commands or add justified entries to
EXCLUSIONS for those that remain intentionally excluded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants