feat(app): add --no-promote for promotionless deploys#105
Conversation
Add a `--no-promote` flag to `prisma-cli app deploy` that builds a new
deployment without flipping the live pointer. The previous deployment
keeps serving live; the new one is reachable only at its own candidate
URL until a later `app promote <deployment-id>` makes it live.
This is the CI build-then-verify path: build the candidate, health-check
its URL, then promote. Because a promotionless deploy never replaces the
live deployment, it bypasses the production-confirmation gate — so
`app deploy --no-promote` on the production branch works without `--prod`.
Wiring:
- ComputeClient.deploy already supports `skipPromote`; the provider now
forwards it and maps the not-promoted return (candidate URL, live
pointer stays on the previous deployment, `promoted: false`).
- Controller threads `noPromote`, skips the gate when set, surfaces
`promoted` + `deployment.live`, and leads nextSteps with `app promote`.
- Human output renders a built-not-live headline with a promote hint;
`--json` carries `promoted` and the candidate `deployment.{url,live}`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 17 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughThis PR adds a ChangesCohort: --no-promote flag implementation
Sequence Diagram(s)Included within the hidden review stack artifact for the provider and controller layers. Compact Metadata Suggested Labels: cli, enhancement, deploy Suggested Reviewers: (based on ownership of Poem 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/cli/src/controllers/app.ts (1)
807-830: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winDon’t cache a promotionless candidate as the known live deployment.
With
skipPromote: noPromote,deployResult.deployment.idis a candidate whendeployResult.promotedis false, but Lines 826-830 still persist it as the known live deployment. Store the actual live id instead, or skip this write when no live deployment exists.Proposed fix
await context.stateStore.setSelectedApp(projectId, { id: deployResult.app.id, name: deployResult.app.name, }); - await context.stateStore.setKnownLiveDeployment( - projectId, - deployResult.app.id, - deployResult.deployment.id, - ); + const knownLiveDeploymentId = deployResult.promoted + ? deployResult.deployment.id + : deployResult.app.liveDeploymentId; + if (knownLiveDeploymentId) { + await context.stateStore.setKnownLiveDeployment( + projectId, + deployResult.app.id, + knownLiveDeploymentId, + ); + }🤖 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/src/controllers/app.ts` around lines 807 - 830, The deploy flow in app.ts is persisting the wrong deployment as live when `skipPromote`/`noPromote` leaves `deployResult.promoted` false. Update the logic around the `deployResult` handling to use the actual live deployment id only when a promotion succeeded, and avoid calling `setKnownLiveDeployment` with `deployResult.deployment.id` for a promotionless candidate; reference `deployResult.promoted`, `deployResult.app`, and `setKnownLiveDeployment` to place the fix.
🤖 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.
Outside diff comments:
In `@packages/cli/src/controllers/app.ts`:
- Around line 807-830: The deploy flow in app.ts is persisting the wrong
deployment as live when `skipPromote`/`noPromote` leaves `deployResult.promoted`
false. Update the logic around the `deployResult` handling to use the actual
live deployment id only when a promotion succeeded, and avoid calling
`setKnownLiveDeployment` with `deployResult.deployment.id` for a promotionless
candidate; reference `deployResult.promoted`, `deployResult.app`, and
`setKnownLiveDeployment` to place the fix.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 54ca020b-b4ca-4e88-8b8f-33c7b542bf5a
📒 Files selected for processing (10)
docs/product/command-spec.mdpackages/cli/src/commands/app/index.tspackages/cli/src/controllers/app.tspackages/cli/src/lib/app/app-provider.tspackages/cli/src/presenters/app.tspackages/cli/src/types/app.tspackages/cli/tests/app-controller.test.tspackages/cli/tests/app-env-vars.test.tspackages/cli/tests/app-presenter.test.tspackages/cli/tests/app-provider.test.ts
setKnownLiveDeployment cached the un-promoted candidate as the known-live deployment; with --no-promote that id is not live. Cache the actually-live id instead (the previous deployment that keeps serving), and skip the write when the app has no live deployment yet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Good catch — fixed in 2417940. With Added a controller test asserting the candidate is not cached as live (the store returns the previous live id after a |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
|
@CodeRabbit approve |
✅ Action performedComments resolved and changes approved. |
What
Adds a
--no-promoteflag toprisma-cli app deploy— a promotionless deployment that builds a new deployment without flipping the live pointer. Deploy stays promote-by-default; this is strictly opt-in.When
--no-promoteis set:running, but not promoted.prisma-cli app promote <deployment-id>makes it live (existing command, unchanged).promote/rollback/list-deploys/show-deploy/domainand deploy's default behavior are untouched.Why — the CI build-then-verify path
CI wants to build a candidate, health-check it against a stable URL, and only then flip production. Promote-on-build gives CI no window to verify before customers see the new code.
--no-promotesplits the two steps:Because a promotionless deploy never replaces the live deployment, it bypasses the production-confirmation gate (
enforceProductionDeployGate→PROD_DEPLOY_REQUIRES_FLAG):app deploy --no-promoteon the production branch builds a candidate without--prodand without a confirmation prompt.Grounding — SDK already supports it (no SDK / management-api change)
@prisma/compute-sdkComputeClient.deploy(options)acceptsskipPromote?: boolean(DeployOptionsincompute-client.d.ts). WithskipPromote: true,deploy()builds + starts + polls torunningand returns:deploymentEndpointDomainis the candidate's own URL (what CI health-checks); on the promoted path the SDK instead returnsappEndpointDomain: <live url>, promoted: true. This PR only wires the CLI onto that existing SDK behavior.Changes
lib/app/app-provider.ts—deployAppacceptsskipPromote, forwards it tosdk.deploy(...), and maps the return bydeployed.promoted. When not promoted:app.liveDeploymentId = previousDeploymentId(the still-live old deployment),deployment.live = false,promoted = false.DeployRecordgainspromotedanddeployment.live. (TheliveUrl/deployment.urlexpressions are unchanged from before —appEndpointDomainis null on the not-promoted path, so they already resolve to the candidate endpoint.)controllers/app.ts— threadsnoPromoteintodeployApp({ skipPromote }); skipsenforceProductionDeployGatewhennoPromote(treatingfirstProductionDeployas false); surfacespromoted+deployment.live;nextStepslead withapp promote <id>when not promoted.commands/app/index.ts— adds the--no-promoteCommander option (same--no-*negatable idiom as the existing--no-db; exposesoptions.promote === false) and threadsnoPromote.presenters/app.ts— human output gains a "built, not live" branch:Built <id> in <duration> (not promoted), the candidate URL, a note that the live deployment is unchanged, and aPromotehint. The promoted path is unchanged.types/app.ts—AppDeployResultgainspromotedanddeployment.live.--json(serializeAppDeploy) surfacesresult.promotedand the candidateresult.deployment.{id,url,live}(field nameurlkept, so existing consumers reading.result.deployment.urlget the candidate URL under--no-promote).docs/product/command-spec.md— documents the flag (signature, behavior, gate bypass, output, deploy-all propagation, example), per this package's doc-driven-development rule.Tests
Extended the existing suites (562 passing, +7 new):
deployApp({ skipPromote: true })forwards it tosdk.deployand maps the promotionless return (candidate URL,deployment.live: false,promoted: false, live pointer stays onpreviousDeploymentId); plus the promoted mapping.--no-promotecallsdeployAppwithskipPromote: true, returnspromoted: false+ candidate URL +deployment.live: false, andnextStepsincludeapp promote <id>; and--no-promoteon the production branch does not calllistDeployments(gate bypassed) and deploys without--prod.--no-promotethreadsnoPromote: trueintorunAppDeployand--jsonsurfacespromoted: false+ the candidatedeployment.url.pnpm --filter @prisma/cli test(562 pass),tsc --noEmit, andbiome checkall pass; CLI builds;node dist/cli.js app deploy --helpshows--no-promote.🤖 Generated with Claude Code