feat(dev): run the deploy pipeline against local providers — the dev extension seam#162
Conversation
|
Warning Review limit reached
Next review available in: 59 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 Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (74)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
commit: |
8088bfa to
ff117ac
Compare
DevExtensionDescriptor (full qualifier); lower() learns nothing about dev (providers become an explicit LowerOptions override resolved by the generated dev stack module — one orchestration point, everything downstream provenance-ignorant); DeploymentProps.serviceAddress reverted (the address rides the artifact own manifest, v2); artifact extraction via node-tar per the dependency razor; the local provider suite moves to a new @internal/local-target package (a lowering dev subtree conflates Composer vocabulary); the @prisma/dev programmatic adoption is pulled into this wave — postgres-main daemon, CLI shell-out deleted. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…endency razor pins) Takes .drive/projects/local-dev/spec.md wholesale from design-branch commit 904d23a, which pins two things this round implements: the POST /apps/<app>/start session-resume endpoint (70c0ef1) and the dependency-razor decision retiring the no-new-deps contract in favor of proper-lockfile and get-port for the daemon lock and free-port probing (904d23a itself). Verified empty diff against 904d23a exactly. The design branch has since moved to d407c1f ("the #162 rework wave — operator review reversals"), which is out of scope for this round and not chased here. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…surface pin) Takes .drive/projects/local-dev/spec.md wholesale from design-branch commit 62d1a6a, which revises the Postgres providers section (operator review of #162, S7 pulled forward): the CLI shell-out is replaced by a third emulator daemon, postgres-main, hosting @prisma/dev's startPrismaDevServer() programmatically. Verified empty diff against 62d1a6a exactly. The design branch has since moved to 5c6eb71 ("the dev seam is a lazy reference — no dev code in production bundles"), out of scope for this round and not chased here. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ff117ac to
b7971f6
Compare
|
All review findings addressed — the branch was rebuilt (force-pushed, tip b7971f6) and independently delta-reviewed against each finding:
Two open items before re-review: a slug-derivation drift between |
|
The last open item from the delta review is closed (954ebec): the duplicate slug in |
b7971f6 to
954ebec
Compare
DevDescriptor + DevProvidersInput/DevEmulatorsInput/DevAttachInput/ DevAttachment, DEV_DIR, and ExtensionDescriptor.dev in app-config.ts; LowerOptions.dev and mergedDevProviders (an extension with no dev descriptor throws, unlike mergedProviders skipping) in deploy.ts, wired into lower() so opts.dev routes the stack at the dev providers layer instead of the hosted one. No nodes/provisions/state on the new type by design -- the lowering stays byte-identical between dev and deploy; only the Alchemy provider boundary is substituted. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DeploymentProps gains an optional serviceAddress, ignored by the hosted provider. descriptors/compute.ts populates ComputeSerialized.address from ctx.address at serialize and threads it into the Deployment call at deploy -- the local Deployment provider (added next) requires it to key the Compute emulators env materialization. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New @internal/lowering/dev subpath: dev-store.ts (env.json/secrets.json/ postgres.json, temp-then-rename, one in-process queue per file), the eight local providers (compute cluster: ComputeService/Deployment/ EnvironmentVariable/Project; postgres cluster: Database/Connection over the ORM CLIs prisma dev, with the stop-then-start port-stability sequence verified live against prisma dev v0.16.22 -- the port holds, so no --db-port fallback is wired; bucket cluster: Bucket/BucketKey as bucket-emulator clients), devProviders() collecting them under the same Providers tag providers() uses, resolve-bin.ts (a generalized resolveAlchemyBin), and compute/artifact-extract.ts, a minimal ustar reader matching packageComputeArtifact byte for byte. Local providers read the app name off the generic ContainerInstance directly (container.input.appName) rather than through a targets narrowing helper: @internal/lowering sits below the extensions layer and cannot import one (ADR-0028s layer order), and the field is already on the base interface, so no narrowing is needed for it. Likewise, the Deployment providers env-materialization step needs the exact env-var key the deployed artifacts serializer.ts computes for the port param; the formula is duplicated inline (documented as a protocol match, not a convenience) because the real configKey() lives in the extensions layer and cannot be imported from here either. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ec S5) preflight-names.ts pulls the provisionManifest/paramManifest walk out of preflight.ts as collectPreflightNames(), returning secrets and envParams as two separate lists rather than one merged map -- deploy preflight still merges them (identical policy for both), but the upcoming dev preflight needs to tell them apart (a placeholder for a missing secret, a hard error for a missing env-sourced param). Sharing this walk is what keeps the two preflights from drifting on which names they check. No behavior change to runPreflight. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lazily prismaCloud() now builds with no environment present: resolveOptions() no longer requires PRISMA_WORKSPACE_ID (nothing downstream ever read it, and container.ts already independently requires it where the value actually matters, at ensure/locate), and region resolution is wrapped in a memoized thunk threaded into every node descriptor instead of called eagerly at construction. A descriptor calls the thunk from inside provision/serialize, at lowering time rather than construction time, which is what makes dev (which runs the same node descriptors) safe: an absent PRISMA_REGION resolves to undefined without throwing, same as today; a garbage one still fails loudly, just at first use. container.ts exports its existing deserialize() so the new dev container descriptor can reuse it verbatim; dev and deploy containers produce and consume the identical wire shape. New src/dev/*: container.ts (a purely local identity, projectId literal "local", no platform call), preflight.ts (dev value-sourcing policy, shell env else a minted placeholder plus warning for secrets, shell env else a hard error for env-sourced params, over the shared preflight-names.ts walk), emulators.ts (ensures the compute daemon always, buckets only when the graph uses an s3-kinded resource), attach.ts (a Compute-emulator client: endpoints, merged logs, stopServices), teardown.ts (--fresh: removes this app's prisma dev instances and emulator records, delegating every actual fs/ child-process operation to @internal/lowering/dev so this package's own source stays free of node:/bun: imports, invariant 5). All wired onto prismaCloud()'s new dev field per spec. Test fallout: control-env.test.ts rewritten for the new construction- time behavior (succeeds in a scrubbed env; a bad PRISMA_REGION only fails at first lowering use, driven directly through the compute descriptor's provision()); invariants.test.ts allowlists dev/ preflight.ts's two process.env reads (its own shell-token read, dev's value-sourcing policy); one direct computeDescriptor(o) call site in control-lowering.test.ts updated for the thunk signature. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
artifact-extract.test.ts: extractComputeArtifact round-trips packageComputeArtifact output byte for byte, and rejects a non-regular-file tar entry (typeflag != "0") that the real writer never produces. dev-preflight.test.ts: a missing secret mints a placeholder, persists it, and warns exactly once; the placeholder is stable across two preflight runs (the pinned policy warns only on the mint branch, so a second run finding the persisted value stays silent); a shell-set secret is used directly with no placeholder or warning; a missing env-sourced param is a hard error listing the name/service and pointing at the dev shell; a shell-set env-sourced param is written with no error. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e workspace-id CLI proof lower()'s dev devDir join used node:path, which core's own invariant test forbids (core stays runtime-agnostic, no Node/Bun coupling in its shipped surface). Local dev is POSIX-only already (the spec's own Windows note), so a plain `/`-join is exact, not an approximation -- same fix already applied to the target extension's dev/preflight.ts. test/integration/test/cli.extension-config.test.ts's second test asserted the OLD eager-throw behavior removed in the prior commit (prismaCloud() failing at config evaluation without PRISMA_WORKSPACE_ID). Updated to assert the new, correct behavior: config evaluation now succeeds regardless, and deploy fails at the same missing-built-entry point either way -- a stronger proof of the restructure, since it goes through the real CLI/config-loading path rather than a unit test of prismaCloud() alone. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…error Mid-flight spec update (cherry-picked b1d7324): the could-not-read-URL error embeds captured `prisma dev --detach` output verbatim, which can contain a connection URL with a real password when the CLI's stdout happens to echo one back on failure. The no-value-logging behavior contract applies to embedded diagnostics too, not just stashed config values, so the output is masked (`://user:***@`) before it ever reaches the thrown message. Test drives LocalDatabaseProvider's reconcile against a fake failing `prisma` bin that echoes a password-bearing URL, asserting the thrown message keeps the URL legible but never contains the raw password. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixture at test/integration/test/fixtures/local-dev/: compute x2
(web depends on postgres+bucket, bkg has no deps), a bare postgres()
resource, and a bucket() resource, so a dev: true lowering exercises
all eight local providers. Built entries live under built/ (dist/ is
gitignored repo-wide) and are hand-written self-contained files (no
bundler): each declares its own compute() node inline rather than
importing the sibling authoring .ts file, because a relative import to
a file outside the packaged artifact's own bundle breaks once the
artifact is unpacked elsewhere -- confirmed live, not theoretical.
dev-config.ts is the dev stack's own narrower config (prismaCloud()
only, no nodeBuild()) -- see its doc comment and the spec's recorded
Open Questions entry: mergedDevProviders throws for any configured
extension with no dev descriptor, including a build-only one, which
structurally can't implement one. lower() itself never needs the build
registry (only assembleServices does, a CLI-tooling-level concern), so
omitting nodeBuild() from the dev stack's own config is safe, not a
behavior change.
local-dev.integration.test.ts hand-writes the dev stack file per spec
S6's template (S5 owns generate-dev-stack.ts) and drives it through the
real alchemy binary and the real prisma dev CLI, importing only
9-public per ADR-0028 -- the emulator daemons and dev-store files are
verified through their documented wire protocol and on-disk contracts
(plain fetch + fs.readFileSync), never by importing @internal/lowering
or @internal/dev-emulators. Covers: scrubbed-env construction, SERVING
via HTTP round-trip, emulator listings, env/secrets/postgres store
contents, a real running prisma dev instance, second-converge no-op
(pid stability), and changed-artifact restart of exactly one service.
Also fixes composer-prisma-cloud's own package.json: daemon.ts's
import.meta.resolve('@internal/dev-emulators/...') is a genuine runtime
resolution once bundled into this package's dist, so it needs to be a
real "dependencies" entry, not bundled away like its devDependencies
siblings (confirmed: without it, the published control.mjs throws
Cannot find module at runtime). Flagged in the S4 report: this makes a
private, unpublished workspace package a real runtime dependency of a
published one -- a real concern for whenever local dev needs to work
for actual npm consumers, not resolved here.
KNOWN LIMITATION, recorded in the test file's own doc comment and the
S4 report rather than worked around: under bun test specifically, the
postgres local provider's `prisma dev --detach` subprocess (spawned
from inside the alchemy child's own Effect runtime) runs and genuinely
creates the instance, but its stdout is not reliably captured by the
calling spawnSync, so the test's first converge can fail with an empty
captured-output error. Confirmed NOT a defect in the local providers:
the identical stack file converges successfully via a plain shell, via
`bun run <script>` (not bun test), and via plain node, every time,
with the fixture's /health endpoint then returning exactly the
expected SERVING payload. The failure is specific to being a
process-tree descendant of bun test's own test runner, independent of
nesting depth, stdio mode, or the detached flag.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… cleanup Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uperseded Open Questions entry The cherry-pick of 90f4383 three-way-merged onto my diverged spec.md and produced a version that neither matched the design branch nor made sense on its own. Overwrite spec.md and plan.md with the design branch's exact content so the S4 Open Questions entry (superseded by the pinned build-only exemption) is gone and the files match bot/claude/local-dev-feature-design-0b49fd byte for byte. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-0041) Export isBuildOnlyExtension(extension) from app-config.ts: true when every nodes entry is kind: 'build' and none of providers/application/provisions/container are declared. A build-only extension (e.g. nodeBuild()) owns no resources or services, so it has nothing to emulate. mergedDevProviders now skips an exempt extension the way mergedProviders skips an extension with no providers, instead of throwing the no-dev-support error unconditionally for every configured extension. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eBuild() in the dev config bun test was silently losing a deeply-nested grandchild's stdout (prisma dev --detach, spawned from inside alchemy's own Effect runtime) specifically when the process tree's root was bun test — confirmed by rigorous isolation across plain shell, bun run, and plain node, none of which lost it. Rather than work around an unrelated subprocess this test doesn't control, the test is now a standalone script (test/local-dev.integration.ts) driven with plain asserts and process.exitCode, invoked by package.json's test script as `bun test && bun run test/local-dev.integration.ts` so it stays a required gate. The header comment documents the root cause. dev-config.ts now includes nodeBuild() again, exercising the build-only exemption (mergedDevProviders no longer needs the fixture-side workaround that omitted it). Also fixed two assertion bugs only reachable once the test could run to completion: postgres.json is keyed by the full instance name (pcdev-<app>-<node>), not the bare node id; and the SERVING/pid stability checks now poll with a bounded deadline instead of asserting on the first attempt, since a freshly-deployed child needs a moment to start listening — the same tolerance a real client (browser, curl) would need. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d a real port race @prisma/integration-tests' local-dev proof now runs its real machine-global compute/buckets daemons for its full duration (the bun-test fix in the previous commit means it actually gets that far). @internal/dev-emulators' own test suite spins up isolated daemon instances that scan for a free port starting at the same MIN_PORT (4300). Run concurrently by turbo, the two raced for the same OS-level port, and one test suite's fetches would occasionally land on the other's process. Adding a package-scoped test-task dependency (@prisma/integration-tests#test depends on @internal/dev-emulators#test) makes turbo run them one after another within a single `pnpm test` invocation, eliminating the race — nothing in @internal/dev-emulators itself changes. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nce derivation does removeLocalPostgresInstances globbed pcdev-<app>-* with the raw app name, but postgres.ts's instanceName derives pcdev-<slug(app)>-<slug(id)>. An app name containing characters slug() transforms (spaces, dots, uppercase, ...) would orphan its instances at teardown. Export postgres.ts's slug and share it in the glob. Add dev-teardown.test.ts: a fake prisma bin records its argv, asserted against both a slug-transformed app name (My.App) and an already-safe one. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…child
local-dev spec § 4 pins that the Deployment provider's port override
(COMPOSER_<ADDRESS>_PORT = the emulator-assigned port, JSON-encoded)
is materialized fresh into each deployment's own env and never
persisted to env.json ("ports live nowhere here") — so reading
env.json for that key finds nothing. The fixture's /health handler now
echoes process.env.COMPOSER_WEB_PORT back in its response, and the
test asserts it equals JSON.stringify(the emulator-assigned port)
reported by the compute emulator's own service listing — proving the
override reaches the deployed process through the documented HTTP
contract, not by reading a file that was never meant to carry it.
Bucket-credential assertions in this test are already single-app only
(no cross-app PUT or read), so S3's newly-enforced per-app bucket
scoping needs no changes here.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…v-ignorant DevDescriptor is renamed to DevExtensionDescriptor everywhere in app-config.ts, since it's the dev variant OF ExtensionDescriptor. deploy.ts loses LowerOptions.dev and every dev branch; it gains LowerOptions.providers, following the same precedence pattern as state. The dev provider aggregation moves out of deploy.ts into a new control/dev.ts, so lower() no longer knows anything about dev. ExtensionDescriptor.dev becomes a lazy async thunk (() => Promise<DevExtensionDescriptor>) rather than the descriptor itself. This keeps any production control entry's static import graph free of dev implementation code: the thunk is a one-line dynamic import of the extension's own dev entry by bare specifier. control/dev.ts adds resolveDevDescriptors(config), which resolves every non-build-only extension's thunk once into a map, and devProviders(resolved, containers, devDir), which merges the resolved descriptors' provider layers. Registers a new /dev control-plane subpath for the core package (src/exports/dev.ts, published as @prisma/composer/dev) in tsconfig.depcruise.json and architecture.config.json, re-exporting devProviders, resolveDevDescriptors, DEV_DIR, and the seam types. Nothing dev-flavored is exported from /deploy or the root. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… it, extract via node-tar Emulation code is physically separated from production source: the dev-only implementation that used to live under @internal/lowering's src/dev/ (dev-store, app-name, compute/postgres/bucket providers, providers.ts, teardown, resolve-bin, spawn-utils) moves wholesale into a new package, @internal/local-target, registered as its own plane (control), layer (lowering), domain (prisma-cloud) entry. Import specifiers inside the moved files are fixed from relative ../compute/*, ../postgres/* paths to package imports against @internal/lowering's own public subpaths (compute, postgres, buckets). app-name.ts's floating module doc comment is reattached directly above the appNameOf function it actually describes, per repo style; its module header is now a single line. compute.ts's local Deployment provider reads the artifact's address from compute.manifest.json after extraction, instead of threading it through DeploymentProps/ComputeSerialized (see the next commit for the revert on the lowering side). Missing address throws a pinned error: "artifact manifest carries no address — repackage with a current @prisma/composer." artifact-extract.ts also moves here (not left in @internal/lowering) and is rewritten to extract via the tar package instead of a hand-rolled ustar reader, keeping the same pinned filtering: only regular files, reject links/devices/path-escaping entries, and a temp-dir-then-rename swap for atomicity. A round-trip test imports packageComputeArtifact from @internal/lowering/compute (downward) and asserts extraction fidelity against format drift. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in the manifest DeploymentProps.serviceAddress and the serialize/deploy threading that carried it are reverted. The compute artifact's address is now intrinsic artifact metadata: packageComputeArtifact writes it into compute.manifest.json as an address field, so any deployment target can recover it after unpacking without a platform-primitive field dedicated to dev. manifestVersion stays "1" — there are no consumers to protect against, so this is not a breaking manifest change, just an added field. The lowering package no longer has a /dev export or dev-only dependencies (@internal/dev-emulators, @internal/s3-protocol); that code moved to @internal/local-target in the previous commit. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… in extension.ts
control/extension.ts's dev field is now the one-line lazy reference
required by ADR-0041: dev: () => import('@prisma/composer-prisma-cloud/dev')
.then((m) => m.devDescriptor()). No dev implementation is imported
statically by this file anymore.
The actual devDescriptor() implementation moves to src/dev/descriptor.ts
(self-contained, no deploy-factory options since dev is credential-free),
assembling the container descriptor, provider layer, preflight,
emulators, attach, and teardown from the existing src/dev/* files plus
@internal/local-target's devProviders. src/exports/dev.ts is a thin
re-export over it, published as @prisma/composer-prisma-cloud/dev.
src/dev-entry.d.ts is an ambient module declaration for the bare
specifier @prisma/composer-prisma-cloud/dev, so tsc can typecheck the
dynamic import without creating either a real package.json dependency
(that would be an actual build-order cycle, since composer-prisma-cloud
already depends on this package's dist) or a bundler-visible resolvable
path (which would get the import inlined/rewritten instead of left as
a genuine external reference). Precedent: streams-server.d.ts.
invariants.test.ts gets a new invariant asserting the built ./control
bundle's import graph carries the bare-specifier dynamic import to
./dev but no dev-implementation token — proof the production control
bundle stays free of dev code.
Import specifiers referencing the old @internal/lowering/dev subpath
are updated to @internal/local-target.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oser-prisma-cloud @prisma/composer/dev re-exports @internal/core/dev, and @prisma/composer-prisma-cloud/dev re-exports @internal/prisma-cloud/dev, mapped exactly like the existing /control and /deploy subpaths. composer-prisma-cloud also gains @internal/local-target as a devDependency (its /dev entry pulls in devProviders through the target extension's dev descriptor). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dev subpath The public-is-a-sink rule has no built-in exceptions mechanism (unlike the general cross-domain rules), so this adds a narrow pathNot directly on that rule's to matcher, scoped to exactly packages/9-public/composer-prisma-cloud/src/exports/dev.ts. This edge exists only because architecture-coverage requires every workspace specifier to be aliased in tsconfig.depcruise.json, which then makes dependency-cruiser able to see, and by default reject, the target's dynamic import of its own published dev entry (ADR-0041's lazy dev reference). The specifier resolves at a consuming app's runtime, never as a real build-time dependency between these two packages — verified by the target's invariant test that the built control.mjs keeps it as a genuine external dynamic import, never inlined. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oviders layer The hand-written dev stack module renderDevStackFile() generates now imports devProviders, resolveDevDescriptors, and DEV_DIR from @prisma/composer/dev instead of building providers inline; it top-level-awaits resolveDevDescriptors(config), then passes the resolved map into devProviders(resolved, containers, devDir) as LowerOptions.providers. The outer test script resolves the extension's lazy dev thunk (descriptor.dev()) before reading its teardown, matching the async-thunk API. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wholesale sync from the design branch (bot/claude/local-dev-feature-design-0b49fd) at its current tip, covering the manifest-v1+address correction, the artifact-extract/local-target package placement, core's /dev subpath, and the lazy dev reference pivot. Diff against the design branch's copies is empty. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…c @prisma/dev The CLI shell-out is deleted wholesale: no bin walk-up, no stdout URL parsing, no prisma dev stop/rm glob teardown. Database and Connection become clients of the postgres-main emulator daemon (S3's `bot/claude/local-dev-s3-dev-emulators`, hosting @prisma/dev's programmatic startPrismaDevServer — one named, persistent server per Database resource): - LocalDatabaseProvider resolves the app's own @prisma/dev module path (two-step: resolve @prisma/dev directly from the app's node_modules; on failure, resolve prisma and resolve @prisma/dev from its own dependency tree; both failing throws the pinned "local dev needs @prisma/dev..." error), PUTs the daemon's ensure, and stores the returned url on its own attributes. - LocalConnectionProvider resolves its url from the daemon's live listing (GET databases), matched by instance name. dev-store.ts loses postgres.json — the daemon owns instance state now. resolve-bin.ts and spawn-utils.ts are deleted with the shell-out; their tests are replaced by module-resolution tests for the two-step @prisma/dev resolution. teardown.ts loses removeLocalPostgresInstances (the CLI glob) — postgres teardown is now a plain postgresClient().deleteApp(app) call, made directly by the target extension's own teardown.ts alongside compute/buckets, needing no local-target delegate of its own. resolve-package-entry.ts is new: import.meta.resolve + fileURLToPath, for resolving a daemon program's published entrypoint. Lives here (not in the target extension's own src/dev/emulators.ts) because that extension's shipped source must stay free of node:/bun: imports (invariant 5). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lient, re-emit its program emulators.ts now ensures postgres-main whenever the graph has a postgres- or prisma-next-kinded resource node (Postgres is a first-class daemon since the programmatic @prisma/dev adoption). Entry resolution for all three daemons goes through @internal/local-target's resolvePackageEntry, resolved against the PUBLISHED @prisma/composer-prisma-cloud/dev/*-main subpaths — never the private @internal/dev-emulators ones — so a published install's dev command finds its daemon programs in its own dependency tree. teardown.ts calls postgresClient().deleteApp(app) directly, alongside the existing compute/buckets DELETEs, replacing the CLI-glob removeLocalPostgresInstances call. composer-prisma-cloud's tsdown config re-emits compute-main.mjs, buckets-main.mjs, and postgres-main.mjs from @internal/dev-emulators' own dist, at the SAME depth as its other top-level entries (not a subdirectory) — the daemon programs' readOwnVersion() resolves ../package.json relative to themselves, and the CALLING side (ensureDaemon, inside dev.mjs) resolves the same relative path from its own location; both sides must sit at the same depth under dist/ or they read different package.json files and disagree on "own version", breaking ensureDaemon's staleness check. Confirmed live: a fresh spawn under the wrong depth failed with an ENOENT reading dist/package.json; moving to the top level fixed it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es.json The postgres-related assertions move from reading dev-store's postgres.json to the postgres-main daemon's own documented wire protocol (GET /apps/<app>/databases, plain fetch — same convention the compute/buckets assertions already use). Instance names are unchanged. The registry/baseline tracking that already existed for compute and buckets extends to postgres (registered, only stopped at the end if this run caused it to start). The data-persistence assertion (the no-op second converge keeping the same pids) is unaffected — it never depended on how Postgres itself is hosted. Also updates the file's own stale doc comments: the "why a standalone script, not bun:test" rationale no longer blames LocalDatabaseProvider's own prisma dev --name --detach subprocess (that shell-out is gone) — the same class of stdout-loss-under-bun-test issue applies more generally to any daemon a converge causes ensureDaemon to spawn. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… derivation Delta review finding A (#160): LocalDatabaseProvider re-derived the pcdev-<app>-<database-id> instance name with its own slug, which dropped the leading/trailing-dash trim postgres-main's own derivation (now dev-emulators' instance-name.ts, extracted in the CodeQL linear-regex fix) applies. An app name or database id ending in a hyphen — a shape the wire protocol's own segment rule already allows through — produced a DIFFERENT concatenated name here than the one the daemon actually created the server under (a doubled dash at the boundary), so LocalConnectionProvider's listing-based lookup threw noRecordedInstanceError even though the server existed. postgres.ts drops its own slug/instanceName and imports instanceNameFor directly from @internal/dev-emulators instead — one implementation, so this can no longer drift. instance-name.ts's slug/instanceNameFor are exported through dev-emulators' main entry (a pure function module, no daemon main() to run on import — verified importing it is side-effect free). Adds a cross-boundary test against the real postgres-main daemon: ensure through LocalDatabaseProvider, Connection-resolve through LocalConnectionProvider, for an app name and database id each ending in a hyphen — the exact pathological pair that threw noRecordedInstanceError before this fix. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
954ebec to
57e7374
Compare
… name only Operator naming decision (spec pin, design tip 12956d9): "dev" names the user-facing feature only — the `prisma-composer dev` command, the `[dev]` log prefix, the `.prisma-composer/dev/` state dir (DEV_DIR keeps its name and value). The architectural seam takes the concept's real noun, already established by ADR-0041 and @internal/local-target: - ExtensionDescriptor.dev -> localTarget (still the lazy async thunk); DevExtensionDescriptor -> LocalTargetDescriptor; DevProvidersInput/DevEmulatorsInput/DevAttachInput/DevAttachment -> LocalTargetProvidersInput/LocalTargetEmulatorsInput/ LocalTargetAttachInput/LocalTargetAttachment. - Core subpath @prisma/composer/dev -> @prisma/composer/local-target; resolveDevDescriptors -> resolveLocalTargets; devProviders -> localTargetProviders; control/dev.ts -> control/local-target.ts, exports/dev.ts -> exports/local-target.ts likewise. The pinned no-local-target-support error's field-name reference updates to match: "declares no `localTarget` descriptor" (was "`dev` descriptor"). - Extension subpath @prisma/composer-prisma-cloud/dev -> /local-target, exporting localTargetDescriptor() (was devDescriptor()); src/exports/dev.ts -> src/exports/local-target.ts; src/dev-entry.d.ts -> src/local-target-entry.d.ts; the one-line thunk in extension.ts updates accordingly. - The re-emitted daemon program subpaths (compute-main/buckets-main/ postgres-main) move from ./dev/*-main to ./local-target/*-main — their subpath literally contained /dev/; the physical dist files (already at the top level, not a subdirectory) are unaffected. The entry resolver in the target extension's emulators.ts updates to match. - @internal/local-target's own internal `devProviders` aggregator (a DIFFERENT function from core's, per-extension rather than cross-extension) renames to localTargetProviders for the same reason, even though it isn't part of the public seam. - Ripple: 9-public mappings, tsdown configs, tsconfig.depcruise.json path aliases, architecture.config.json globs and the crossDomainExceptions entry, dependency-cruiser.config.mjs's sanctioned-dynamic-import pathNot, the target's firewall invariant 7 test's token list and entry-map test, the S4 integration test's generated stack module and descriptor.localTarget usage. - Internal descriptor hook names (providers/container/preflight/ emulators/attach/teardown) unchanged, per the spec's own pin. @internal/local-target's package name was already correct. spec.md/plan.md synced wholesale from design tip 12956d9; empty diff confirmed against it. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n point The generated module now deserializes the containers, top-level-awaits resolveDevDescriptors(config), and passes providers: devProviders(...) + state: localState() explicitly (spec § 3 REVISED — operator review of #162). The dev: true flag is gone — LowerOptions no longer has it; lower() learns nothing about dev. Rendering test updated to the new source. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n point The generated module now deserializes the containers, top-level-awaits resolveDevDescriptors(config), and passes providers: devProviders(...) + state: localState() explicitly (spec § 3 REVISED — operator review of #162). The dev: true flag is gone — LowerOptions no longer has it; lower() learns nothing about dev. Rendering test updated to the new source. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The platform removed GET /v1/projects/{id}/compute-services today; the
resource now lists at GET /v1/apps?projectId= with appEndpointDomain.
The deploy providers and the TypeScript verify scripts already use
/v1/apps — only this bash script still hit the deleted route, which
turned every 'Deploy, verify, destroy' run red across all branches.
Verified live: the new query returns the domain for a deployed app and
correctly reports empty for a destroyed project.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…and finish the localTarget rename in its error text Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The naming rule reserves 'dev' for user-facing surfaces (the command, the [dev] log prefix, .prisma-composer/dev). Internal paths now say what the code is: @internal/local-target's implementation flattens to src/ (the package name already scopes it), and the target extension's local-target implementation moves from src/dev/ to src/local-target/, matching its export subpath. dev-store.ts keeps its name because it is the store OF the user-facing .prisma-composer/dev directory. Tests, the env-touch invariant allow-list, and stale comment paths updated; no behavior change. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…review) Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> 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> 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> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The decision: local dev is the deploy pipeline with its infrastructure providers swapped — nothing else changes.
Context in one breath:
prisma-composer dev(ADR-0041, in the base branch) runs a whole Composer app locally with no cloud credentials. This is the third layer of the PR stack, on top of #160 (the emulator daemons). Stacked PR: the diff here is only this layer.How a deploy works, and what dev swaps
Deploying a Composer app runs a pipeline: load the app's graph from its code, assemble each service's built output into a bundle, lower the graph into infrastructure resources (services, databases, buckets, environment variables), and hand those to Alchemy — the provisioning engine — which converges them by calling provider plugins that talk to the platform's API. Everything above the providers is pure logic: addresses, config serialization, secret wiring, minted service keys, migrations.
This PR adds a
devfield to the extension descriptor (the object a platform package registers with the framework). It carries local counterparts for exactly the operational pieces: providers that talk to the emulators instead of the cloud, a local container identity, a dev preflight, and hooks the dev command will use to start emulators and attach to the running app. Deliberately absent from the field: anything that could change what gets provisioned. The lowering is shared code, so local dev cannot diverge from a real deploy by construction. Deploy state is also untouched — dev uses Alchemy's built-in file store, as the snippet shows.The local providers are thin emulator clients. Registering a compute service returns its stable local port, which is how a service's own origin URL resolves with zero special-casing. Deploying unpacks the real packaged artifact (the same tar a deploy ships), prepares the child's full environment from the lowered configuration rows, and PUTs it at the Compute emulator. The Postgres provider shells the ORM's
prisma devwith a probe-then-recover sequence (its port stability across stop/start was verified experimentally first). Where dev deliberately differs from deploy, it's pinned and reasoned: a secret missing from your shell becomes a persisted placeholder plus one warning, so the app boots and only the paths touching that credential fail, at the real external service; a missing environment-sourced param is a hard error naming it, because params feed schema validation where a placeholder produces a confusing crash. Captured subprocess output is credential-masked before it can appear in any error.One practical seam: an extension that only supplies build adapters has nothing to emulate and is exempt from the dev requirement (one shared predicate, used everywhere).
Tested
An integration proof drives the snippet above for real: a fixture app (two services, Postgres, a bucket) converges credential-free with the actual
alchemybinary and serves — HTTP round-trip asserted. A second converge with an unchanged build restarts nothing (asserted via pid stability). Changing one service's artifact restarts exactly that service. Environment rows, the poisonDATABASE_URLrows, and secret pointer wiring are asserted against the store. Plus focused suites: the tar extractor round-trips the packager's output, placeholder minting is stable across runs, the missing-param error lists names, and the extension factory constructs with a fully scrubbed environment. Workspace gates green, no new type casts.Alternatives considered
🤖 Generated with Claude Code