@prisma-next/cli 0.15.0 → 0.16.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +3 -4
  2. package/dist/cli.d.mts +1 -1
  3. package/dist/cli.mjs +13 -13
  4. package/dist/{client-DigVI8zX.mjs → client-2kwLMBSf.mjs} +67 -36
  5. package/dist/client-2kwLMBSf.mjs.map +1 -0
  6. package/dist/{command-helpers-CUoKYTlG.mjs → command-helpers-CVn3U9Uz.mjs} +32 -8
  7. package/dist/command-helpers-CVn3U9Uz.mjs.map +1 -0
  8. package/dist/commands/contract-emit.d.mts +0 -1
  9. package/dist/commands/contract-emit.d.mts.map +1 -1
  10. package/dist/commands/contract-emit.mjs +1 -1
  11. package/dist/commands/contract-infer.d.mts +0 -1
  12. package/dist/commands/contract-infer.d.mts.map +1 -1
  13. package/dist/commands/contract-infer.mjs +1 -1
  14. package/dist/commands/db-init.d.mts +0 -1
  15. package/dist/commands/db-init.d.mts.map +1 -1
  16. package/dist/commands/db-init.mjs +8 -7
  17. package/dist/commands/db-init.mjs.map +1 -1
  18. package/dist/commands/db-schema.d.mts +0 -1
  19. package/dist/commands/db-schema.d.mts.map +1 -1
  20. package/dist/commands/db-schema.mjs +3 -3
  21. package/dist/commands/db-sign.d.mts +0 -1
  22. package/dist/commands/db-sign.d.mts.map +1 -1
  23. package/dist/commands/db-sign.mjs +8 -10
  24. package/dist/commands/db-sign.mjs.map +1 -1
  25. package/dist/commands/db-update.d.mts +0 -1
  26. package/dist/commands/db-update.d.mts.map +1 -1
  27. package/dist/commands/db-update.mjs +12 -14
  28. package/dist/commands/db-update.mjs.map +1 -1
  29. package/dist/commands/db-verify.d.mts +0 -1
  30. package/dist/commands/db-verify.d.mts.map +1 -1
  31. package/dist/commands/db-verify.mjs +1 -1
  32. package/dist/commands/migrate.d.mts +1 -2
  33. package/dist/commands/migrate.d.mts.map +1 -1
  34. package/dist/commands/migrate.mjs +7 -7
  35. package/dist/commands/migrate.mjs.map +1 -1
  36. package/dist/commands/migration-check.d.mts +4 -3
  37. package/dist/commands/migration-check.d.mts.map +1 -1
  38. package/dist/commands/migration-check.mjs +1 -1
  39. package/dist/commands/migration-graph.d.mts +2 -3
  40. package/dist/commands/migration-graph.d.mts.map +1 -1
  41. package/dist/commands/migration-graph.mjs +3 -3
  42. package/dist/commands/migration-list.d.mts +2 -3
  43. package/dist/commands/migration-list.d.mts.map +1 -1
  44. package/dist/commands/migration-list.mjs +1 -1
  45. package/dist/commands/migration-log.d.mts +2 -3
  46. package/dist/commands/migration-log.d.mts.map +1 -1
  47. package/dist/commands/migration-log.mjs +1 -1
  48. package/dist/commands/migration-new.d.mts +0 -1
  49. package/dist/commands/migration-new.d.mts.map +1 -1
  50. package/dist/commands/migration-new.mjs +14 -38
  51. package/dist/commands/migration-new.mjs.map +1 -1
  52. package/dist/commands/migration-plan.d.mts +0 -1
  53. package/dist/commands/migration-plan.d.mts.map +1 -1
  54. package/dist/commands/migration-plan.mjs +1 -1
  55. package/dist/commands/migration-show.d.mts +0 -1
  56. package/dist/commands/migration-show.d.mts.map +1 -1
  57. package/dist/commands/migration-show.mjs +3 -3
  58. package/dist/commands/migration-status.d.mts +2 -3
  59. package/dist/commands/migration-status.d.mts.map +1 -1
  60. package/dist/commands/migration-status.mjs +1 -1
  61. package/dist/commands/ref.d.mts +1 -2
  62. package/dist/commands/ref.d.mts.map +1 -1
  63. package/dist/commands/ref.mjs +13 -18
  64. package/dist/commands/ref.mjs.map +1 -1
  65. package/dist/commands/telemetry/index.d.mts +0 -1
  66. package/dist/commands/telemetry/index.d.mts.map +1 -1
  67. package/dist/commands/telemetry/index.mjs +1 -1
  68. package/dist/contract-at-errors-C-l4-JEY.mjs +36 -0
  69. package/dist/contract-at-errors-C-l4-JEY.mjs.map +1 -0
  70. package/dist/{contract-emit-wgqrOXUI.mjs → contract-emit-DzOkTs11.mjs} +3 -3
  71. package/dist/{contract-emit-wgqrOXUI.mjs.map → contract-emit-DzOkTs11.mjs.map} +1 -1
  72. package/dist/{contract-emit-sYrqzsdN.mjs → contract-emit-iSMJVs26.mjs} +3 -3
  73. package/dist/{contract-emit-sYrqzsdN.mjs.map → contract-emit-iSMJVs26.mjs.map} +1 -1
  74. package/dist/{contract-infer-6SQnnVj7.mjs → contract-infer-BVqworBB.mjs} +3 -3
  75. package/dist/{contract-infer-6SQnnVj7.mjs.map → contract-infer-BVqworBB.mjs.map} +1 -1
  76. package/dist/{contract-space-aggregate-loader-DpB4x97a.mjs → contract-space-aggregate-loader-hPVymNLw.mjs} +14 -15
  77. package/dist/contract-space-aggregate-loader-hPVymNLw.mjs.map +1 -0
  78. package/dist/{db-verify-CEsG5Gek.mjs → db-verify-BpwCMyWJ.mjs} +6 -7
  79. package/dist/db-verify-BpwCMyWJ.mjs.map +1 -0
  80. package/dist/exports/control-api.d.mts +1 -2
  81. package/dist/exports/control-api.d.mts.map +1 -1
  82. package/dist/exports/control-api.mjs +2 -2
  83. package/dist/exports/index.d.mts +0 -1
  84. package/dist/exports/index.d.mts.map +1 -1
  85. package/dist/exports/index.mjs +2 -2
  86. package/dist/exports/init-output.d.mts +1 -0
  87. package/dist/exports/init-output.d.mts.map +1 -1
  88. package/dist/exports/init-output.mjs +1 -1
  89. package/dist/{format-CjongNb1.mjs → format-CFLZv_zU.mjs} +6 -5
  90. package/dist/format-CFLZv_zU.mjs.map +1 -0
  91. package/dist/{framework-components-BbnM1RYm.mjs → framework-components-VMQJbAwl.mjs} +2 -2
  92. package/dist/{framework-components-BbnM1RYm.mjs.map → framework-components-VMQJbAwl.mjs.map} +1 -1
  93. package/dist/global-flags-BpoOYtNZ.d.mts.map +1 -1
  94. package/dist/{init-D8YFfaD6.mjs → init-BSgPxVXA.mjs} +85 -90
  95. package/dist/init-BSgPxVXA.mjs.map +1 -0
  96. package/dist/{inspect-live-schema-CEt_ztBJ.mjs → inspect-live-schema-DxsD_a4d.mjs} +3 -3
  97. package/dist/{inspect-live-schema-CEt_ztBJ.mjs.map → inspect-live-schema-DxsD_a4d.mjs.map} +1 -1
  98. package/dist/{migration-check-ByeQHOhK.mjs → migration-check-DMxPzbHl.mjs} +70 -44
  99. package/dist/migration-check-DMxPzbHl.mjs.map +1 -0
  100. package/dist/migration-cli.d.mts +0 -1
  101. package/dist/migration-cli.d.mts.map +1 -1
  102. package/dist/migration-cli.mjs +3 -3
  103. package/dist/migration-cli.mjs.map +1 -1
  104. package/dist/{migration-command-scaffold-CJO3tjiR.mjs → migration-command-scaffold-DB_i9nBC.mjs} +3 -3
  105. package/dist/{migration-command-scaffold-CJO3tjiR.mjs.map → migration-command-scaffold-DB_i9nBC.mjs.map} +1 -1
  106. package/dist/{migration-list-DTYM8xvm.mjs → migration-list-cnSKilYH.mjs} +5 -5
  107. package/dist/migration-list-cnSKilYH.mjs.map +1 -0
  108. package/dist/{migration-log-qZ8LpQIl.mjs → migration-log-CEqFiez9.mjs} +3 -3
  109. package/dist/{migration-log-qZ8LpQIl.mjs.map → migration-log-CEqFiez9.mjs.map} +1 -1
  110. package/dist/{migration-path-target-DmNwsDgs.mjs → migration-path-target-C0TQuV5n.mjs} +2 -2
  111. package/dist/{migration-path-target-DmNwsDgs.mjs.map → migration-path-target-C0TQuV5n.mjs.map} +1 -1
  112. package/dist/{migration-plan-CfPrB80t.mjs → migration-plan-BJHulFZC.mjs} +54 -62
  113. package/dist/migration-plan-BJHulFZC.mjs.map +1 -0
  114. package/dist/{migration-status-DRuS-rP5.mjs → migration-status-ugWwGk1V.mjs} +5 -5
  115. package/dist/{migration-status-DRuS-rP5.mjs.map → migration-status-ugWwGk1V.mjs.map} +1 -1
  116. package/dist/{output-mEQ74_nd.mjs → output-DWNYqZGc.mjs} +5 -6
  117. package/dist/output-DWNYqZGc.mjs.map +1 -0
  118. package/dist/{ref-advancement-BkXlikCA.mjs → ref-advancement-BCz7X7qJ.mjs} +13 -6
  119. package/dist/ref-advancement-BCz7X7qJ.mjs.map +1 -0
  120. package/dist/schemas-CeGMYFYX.d.mts.map +1 -1
  121. package/dist/{telemetry-DHBBq5wT.mjs → telemetry-BEyNMfco.mjs} +2 -2
  122. package/dist/{telemetry-DHBBq5wT.mjs.map → telemetry-BEyNMfco.mjs.map} +1 -1
  123. package/dist/{terminal-ui-DGRNFWna.d.mts → terminal-ui-BN4QOsm5.d.mts} +1 -2
  124. package/dist/terminal-ui-BN4QOsm5.d.mts.map +1 -0
  125. package/dist/{types-DFoqNIcQ.d.mts → types-D4r_HI9-.d.mts} +8 -4
  126. package/dist/types-D4r_HI9-.d.mts.map +1 -0
  127. package/dist/{verify-DDCb0R2r.mjs → verify-C4RazDE1.mjs} +9 -8
  128. package/dist/verify-C4RazDE1.mjs.map +1 -0
  129. package/package.json +25 -25
  130. package/src/commands/db-init.ts +4 -3
  131. package/src/commands/db-sign.ts +6 -4
  132. package/src/commands/db-update.ts +15 -7
  133. package/src/commands/db-verify.ts +2 -3
  134. package/src/commands/init/errors.ts +62 -58
  135. package/src/commands/init/hygiene-gitattributes.ts +37 -21
  136. package/src/commands/init/init.ts +35 -32
  137. package/src/commands/init/output.ts +4 -5
  138. package/src/commands/init/reinit-cleanup.ts +16 -17
  139. package/src/commands/migrate.ts +1 -0
  140. package/src/commands/migration-check.ts +75 -38
  141. package/src/commands/migration-list.ts +1 -5
  142. package/src/commands/migration-new.ts +15 -39
  143. package/src/commands/migration-plan.ts +56 -78
  144. package/src/commands/ref.ts +19 -14
  145. package/src/control-api/operations/db-run.ts +4 -4
  146. package/src/control-api/operations/db-verify.ts +12 -9
  147. package/src/control-api/operations/format.ts +4 -3
  148. package/src/control-api/operations/migrate.ts +70 -26
  149. package/src/control-api/types.ts +4 -1
  150. package/src/migration-cli.ts +5 -5
  151. package/src/utils/cli-errors.ts +28 -7
  152. package/src/utils/combine-verify-results.ts +1 -1
  153. package/src/utils/contract-at-errors.ts +13 -22
  154. package/src/utils/contract-space-aggregate-loader.ts +12 -13
  155. package/src/utils/contract-space-seed-phase.ts +8 -6
  156. package/src/utils/formatters/errors.ts +24 -3
  157. package/src/utils/formatters/verify.ts +7 -6
  158. package/src/utils/integrity-violation-to-check-failure.ts +14 -14
  159. package/src/utils/plan-resolution.ts +6 -9
  160. package/src/utils/ref-advancement.ts +22 -3
  161. package/src/utils/result-handler.ts +4 -2
  162. package/dist/client-DigVI8zX.mjs.map +0 -1
  163. package/dist/command-helpers-CUoKYTlG.mjs.map +0 -1
  164. package/dist/contract-at-errors-D8CNDD7g.mjs +0 -42
  165. package/dist/contract-at-errors-D8CNDD7g.mjs.map +0 -1
  166. package/dist/contract-space-aggregate-loader-DpB4x97a.mjs.map +0 -1
  167. package/dist/db-verify-CEsG5Gek.mjs.map +0 -1
  168. package/dist/format-CjongNb1.mjs.map +0 -1
  169. package/dist/init-D8YFfaD6.mjs.map +0 -1
  170. package/dist/migration-check-ByeQHOhK.mjs.map +0 -1
  171. package/dist/migration-list-DTYM8xvm.mjs.map +0 -1
  172. package/dist/migration-plan-CfPrB80t.mjs.map +0 -1
  173. package/dist/output-mEQ74_nd.mjs.map +0 -1
  174. package/dist/ref-advancement-BkXlikCA.mjs.map +0 -1
  175. package/dist/terminal-ui-DGRNFWna.d.mts.map +0 -1
  176. package/dist/types-DFoqNIcQ.d.mts.map +0 -1
  177. package/dist/verify-DDCb0R2r.mjs.map +0 -1
@@ -1,47 +1,60 @@
1
1
  import type { TargetId } from './templates/code-templates';
2
2
 
3
3
  /**
4
- * The schema-relative `.gitattributes` entries written for a freshly
5
- * initialised project (FR3.4). Mirrors the relevant subset of the
6
- * repo-root [`.gitattributes`](../../../../../../../../.gitattributes):
4
+ * The `.gitattributes` entries written for a freshly initialised project
5
+ * (FR3.4). Mirrors the relevant subset of the repo-root
6
+ * [`.gitattributes`](../../../../../../../../.gitattributes):
7
7
  *
8
8
  * - **Today**: `contract.json`, `contract.d.ts` are emitted on every
9
9
  * `prisma-next contract emit`. Marking them `linguist-generated`
10
10
  * keeps GitHub's diff stats honest and collapses the file in code
11
11
  * review by default.
12
- * - **Forward-looking**: `end-contract.*`, `start-contract.*`, `ops.json`,
13
- * `migration.json` are not yet emitted by `init` flows but will be
14
- * produced by adjacent commands (lower / migration tooling). Adding
15
- * them now matches Decision 5 (forward-looking subset) so the file
16
- * does not need to be amended every time a new artefact lands.
12
+ * - **Forward-looking**: `ops.json`, `migration.json` are not yet emitted
13
+ * by `init` flows but will be produced by adjacent commands (lower /
14
+ * migration tooling). Adding them now matches Decision 5
15
+ * (forward-looking subset) so the file does not need to be amended
16
+ * every time a new artifact lands.
17
17
  *
18
- * Patterns are written relative to the schema directory so a user
19
- * who runs `init --schema-path db/contract.prisma` gets
20
- * `db/contract.json linguist-generated` — not the workspace-glob form
21
- * `<glob>/contract.json` (which would over-match any unrelated
18
+ * `ARTIFACT_FILENAMES` entries are written relative to the schema
19
+ * directory so a user who runs `init --schema-path db/contract.prisma`
20
+ * gets `db/contract.json linguist-generated` — not the workspace-glob
21
+ * form `<glob>/contract.json` (which would over-match any unrelated
22
22
  * `contract.json` the user has elsewhere) and not the absolute
23
- * `DEFAULT_CONTRACT_SOURCE_DIR/contract.json` (which would silently break for a non-default
24
- * schema path).
23
+ * `DEFAULT_CONTRACT_SOURCE_DIR/contract.json` (which would silently
24
+ * break for a non-default schema path).
25
+ *
26
+ * The migration contract snapshot store (`migrations/snapshots/<hex>/`)
27
+ * is anchored to the migrations root instead, not the schema directory:
28
+ * migration package depth under `migrations/` varies (`app/<pkg>`,
29
+ * `<space>/<pkg>`, or a bare `<pkg>` in extension source repos), so no
30
+ * single schema-dir-relative pattern can reach every snapshot. See
31
+ * `STORE_GITATTRIBUTES_LINES` below.
25
32
  */
26
- const ARTEFACT_FILENAMES: readonly string[] = [
33
+ const ARTIFACT_FILENAMES: readonly string[] = [
27
34
  'contract.json',
28
35
  'contract.d.ts',
29
- 'end-contract.json',
30
- 'end-contract.d.ts',
31
- 'start-contract.json',
32
- 'start-contract.d.ts',
33
36
  'ops.json',
34
37
  'migration.json',
35
38
  ];
36
39
 
37
40
  const ATTRIBUTE = 'linguist-generated';
38
41
 
42
+ /**
43
+ * Full `.gitattributes` lines for the migration contract snapshot store,
44
+ * already anchored to the migrations root — unlike `ARTIFACT_FILENAMES`,
45
+ * these are not combined with the schema-relative prefix.
46
+ */
47
+ const STORE_GITATTRIBUTES_LINES: readonly string[] = [
48
+ `migrations/snapshots/**/contract.json ${ATTRIBUTE}`,
49
+ `migrations/snapshots/**/contract.d.ts ${ATTRIBUTE}`,
50
+ ];
51
+
39
52
  /**
40
53
  * Computes the `.gitattributes` lines this scaffold expects to own. Each
41
54
  * line has the shape `<path> linguist-generated`. The `target` parameter
42
55
  * is currently unused but accepted for symmetry with the other hygiene
43
56
  * helpers and to leave room for target-specific entries (e.g. a future
44
- * Mongo-only artefact) without a signature break.
57
+ * family-specific artifact) without a signature break.
45
58
  */
46
59
  export function requiredGitattributesLines(
47
60
  schemaDir: string,
@@ -49,7 +62,10 @@ export function requiredGitattributesLines(
49
62
  ): readonly string[] {
50
63
  const dir = schemaDir === '.' ? '' : schemaDir.replace(/\/+$/, '');
51
64
  const prefix = dir === '' ? '' : `${dir}/`;
52
- return ARTEFACT_FILENAMES.map((file) => `${prefix}${file} ${ATTRIBUTE}`);
65
+ return [
66
+ ...ARTIFACT_FILENAMES.map((file) => `${prefix}${file} ${ATTRIBUTE}`),
67
+ ...STORE_GITATTRIBUTES_LINES,
68
+ ];
53
69
  }
54
70
 
55
71
  /**
@@ -48,7 +48,7 @@ import {
48
48
  renderInitOutro,
49
49
  } from './output';
50
50
  import { type ProbeOutcome, type ProbeOverrides, probeServerVersion } from './probe-db';
51
- import { findStaleArtefacts, removeDependency } from './reinit-cleanup';
51
+ import { findStaleArtifacts, removeDependency } from './reinit-cleanup';
52
52
  import {
53
53
  DEFAULT_SKILL_SOURCES,
54
54
  formatSkillInstallCommand,
@@ -175,14 +175,14 @@ export async function runInit(
175
175
  { path: '.env.example', content: envExampleContent(inputs.target) },
176
176
  ];
177
177
 
178
- // FR9.1 — on re-init, queue the previously-emitted contract artefacts
178
+ // FR9.1 — on re-init, queue the previously-emitted contract artifacts
179
179
  // for deletion so a target switch (or schema-shape change) does not
180
180
  // leave a stale `contract.json` / `contract.d.ts` next to the new
181
181
  // schema source. Detection is filesystem-only (no parsing of the
182
182
  // previous config) so the cleanup is safe to run before the write
183
183
  // phase: each path is checked for existence in the precondition,
184
184
  // and missing-on-disk-at-write-time is tolerated.
185
- const filesToDelete: string[] = inputs.reinit ? [...findStaleArtefacts(baseDir, schemaDir)] : [];
185
+ const filesToDelete: string[] = inputs.reinit ? [...findStaleArtifacts(baseDir, schemaDir)] : [];
186
186
 
187
187
  // `init` delegates the skill to `npx skills add prisma/prisma-next#v<version>`,
188
188
  // so a hand-rolled `.agents/skills/prisma-next/SKILL.md` in the project
@@ -245,7 +245,7 @@ export async function runInit(
245
245
  }
246
246
 
247
247
  // FR3.4: idempotent .gitattributes — linguist-generated entries for
248
- // the emitted artefacts so GitHub diff stats / code review collapse
248
+ // the emitted artifacts so GitHub diff stats / code review collapse
249
249
  // them by default.
250
250
  const gitattributesPath = join(baseDir, '.gitattributes');
251
251
  const existingGitattributes = existsSync(gitattributesPath)
@@ -373,7 +373,7 @@ export async function runInit(
373
373
  }
374
374
  }
375
375
 
376
- // FR9.1 — delete stale artefacts after the new templates are written.
376
+ // FR9.1 — delete stale artifacts after the new templates are written.
377
377
  // Order is intentional: the names do not collide with `filesToWrite`
378
378
  // (we never write `contract.json` from this command — that's `contract
379
379
  // emit`'s job), so deletion *after* the writes guarantees we never
@@ -534,18 +534,21 @@ export async function runInit(
534
534
  if (validated instanceof Error || (validated as { problems?: unknown }).problems !== undefined) {
535
535
  // Route through `emitError` rather than throwing: the bare throw
536
536
  // bypassed `--json` envelope formatting and `exitCodeForError`, so a
537
- // 5009 regression would surface as an uncaught exception in
538
- // commander instead of the documented `INTERNAL_ERROR` envelope on
539
- // the right channel.
537
+ // CLI.INIT_INVALID_OUTPUT_DOCUMENT regression would surface as an
538
+ // uncaught exception in commander instead of the documented
539
+ // `INTERNAL_ERROR` envelope on the right channel.
540
540
  return emitError(
541
541
  ui,
542
542
  flags,
543
- new CliStructuredError('5009', 'Init produced an invalid output document', {
544
- domain: 'CLI',
545
- why: `The success document failed schema validation: ${String(validated)}`,
546
- fix: 'This is a bug in prisma-next. Please report it with the full `-v` output.',
547
- docsUrl: 'https://prisma-next.dev/docs/cli/init',
548
- }),
543
+ new CliStructuredError(
544
+ 'CLI.INIT_INVALID_OUTPUT_DOCUMENT',
545
+ 'Init produced an invalid output document',
546
+ {
547
+ why: `The success document failed schema validation: ${String(validated)}`,
548
+ fix: 'This is a bug in prisma-next. Please report it with the full `-v` output.',
549
+ docsUrl: 'https://prisma-next.dev/docs/cli/init',
550
+ },
551
+ ),
549
552
  );
550
553
  }
551
554
 
@@ -582,10 +585,10 @@ function emitError(ui: TerminalUI, flags: GlobalFlags, error: CliStructuredError
582
585
  * Maps a structured init error to its documented exit code. Centralised so
583
586
  * the error → exit-code contract lives next to the codes themselves.
584
587
  *
585
- * `5009` (and the unknown-code default branch) routes to
586
- * `INIT_EXIT_INTERNAL_ERROR` because those represent prisma-next bugs the
587
- * user did not cause — surfacing them as `PRECONDITION` would mislead
588
- * automation into thinking the caller mis-invoked the CLI.
588
+ * `CLI.INIT_INVALID_OUTPUT_DOCUMENT` (and the unknown-code default branch)
589
+ * routes to `INIT_EXIT_INTERNAL_ERROR` because those represent prisma-next
590
+ * bugs the user did not cause — surfacing them as `PRECONDITION` would
591
+ * mislead automation into thinking the caller mis-invoked the CLI.
589
592
  *
590
593
  * See [exit-codes.ts](./exit-codes.ts) for the canonical list and
591
594
  * [Style Guide § Exit Codes](../../../../../../../docs/CLI%20Style%20Guide.md#exit-codes)
@@ -596,24 +599,24 @@ function emitError(ui: TerminalUI, flags: GlobalFlags, error: CliStructuredError
596
599
  */
597
600
  export function exitCodeForError(error: { readonly code: string }): number {
598
601
  switch (error.code) {
599
- case '5002': // re-init needs --force — precondition
600
- case '5003': // missing flags — precondition
601
- case '5004': // invalid flag value — precondition
602
- case '5005': // --strict-probe without --probe-db — precondition
603
- case '5010': // invalid manifest (malformed package.json) — precondition
604
- case '5011': // invalid tsconfig (unparseable JSONC) — precondition
605
- case '5012': // probe failed under --strict-probe — precondition
606
- case '5014': // --authoring / --schema-path extension mismatch — precondition
602
+ case 'CLI.INIT_REINIT_NEEDS_FORCE': // re-init needs --force — precondition
603
+ case 'CLI.INIT_MISSING_FLAGS': // missing flags — precondition
604
+ case 'CLI.INIT_INVALID_FLAG_VALUE': // invalid flag value — precondition
605
+ case 'CLI.INIT_STRICT_PROBE_WITHOUT_PROBE': // --strict-probe without --probe-db — precondition
606
+ case 'CLI.INIT_INVALID_MANIFEST': // invalid manifest (malformed package.json) — precondition
607
+ case 'CLI.INIT_INVALID_TSCONFIG': // invalid tsconfig (unparseable JSONC) — precondition
608
+ case 'CLI.INIT_PROBE_FAILED': // probe failed under --strict-probe — precondition
609
+ case 'CLI.INIT_AUTHORING_SCHEMA_PATH_MISMATCH': // --authoring / --schema-path extension mismatch — precondition
607
610
  return INIT_EXIT_PRECONDITION;
608
- case '5006': // user aborted interactive prompt
611
+ case 'CLI.INIT_USER_ABORTED': // user aborted interactive prompt
609
612
  return INIT_EXIT_USER_ABORTED;
610
- case '5007': // install failed
613
+ case 'CLI.INIT_INSTALL_FAILED': // install failed
611
614
  return INIT_EXIT_INSTALL_FAILED;
612
- case '5008': // emit failed
615
+ case 'CLI.INIT_EMIT_FAILED': // emit failed
613
616
  return INIT_EXIT_EMIT_FAILED;
614
- case '5009': // invalid output document — internal bug in prisma-next
617
+ case 'CLI.INIT_INVALID_OUTPUT_DOCUMENT': // invalid output document — internal bug in prisma-next
615
618
  return INIT_EXIT_INTERNAL_ERROR;
616
- case '5013': // skill install failed
619
+ case 'CLI.INIT_SKILL_INSTALL_FAILED': // skill install failed
617
620
  return INIT_EXIT_SKILL_INSTALL_FAILED;
618
621
  default:
619
622
  // Any unexpected code is treated as an internal bug rather than
@@ -844,7 +847,7 @@ function formatCatalogWarning(
844
847
  /**
845
848
  * Recognised pnpm error signatures that justify a fallback to npm.
846
849
  *
847
- * These patterns indicate the published artefact itself is at fault
850
+ * These patterns indicate the published artifact itself is at fault
848
851
  * (a leaked `workspace:*` or `catalog:` specifier), not the user's
849
852
  * environment — pnpm is faithfully reporting "I cannot resolve this
850
853
  * registry version", and npm is willing to install it because npm
@@ -24,10 +24,9 @@ export const InitOutputSchema = type({
24
24
  filesWritten: 'string[]',
25
25
  /**
26
26
  * FR9.1 — files removed from disk during this run. Populated only on
27
- * re-init when previously-emitted contract artefacts (`contract.json`,
28
- * `contract.d.ts`, `start-/end-contract.*`, `ops.json`,
29
- * `migration.json`) were left behind by an earlier run. Empty on a
30
- * green-field init.
27
+ * re-init when previously-emitted contract artifacts (`contract.json`,
28
+ * `contract.d.ts`, `ops.json`, `migration.json`) were left behind by an
29
+ * earlier run. Empty on a green-field init.
31
30
  */
32
31
  filesDeleted: 'string[]',
33
32
  packagesInstalled: {
@@ -80,7 +79,7 @@ export function renderInitOutro(ui: TerminalUI, output: InitOutput, flags: Globa
80
79
 
81
80
  if (output.filesDeleted.length > 0) {
82
81
  lines.push('');
83
- lines.push('Files deleted (stale contract artefacts):');
82
+ lines.push('Files deleted (stale contract artifacts):');
84
83
  for (const file of output.filesDeleted) {
85
84
  lines.push(` • ${file}`);
86
85
  }
@@ -4,41 +4,40 @@ import { join } from 'pathe';
4
4
  /**
5
5
  * Filenames the contract pipeline emits next to the user's schema source
6
6
  * (`<schemaDir>/contract.json`, `<schemaDir>/contract.d.ts`, …). Mirrors
7
- * `ARTEFACT_FILENAMES` in `hygiene-gitattributes.ts`; kept as a separate
8
- * constant here because the cleanup contract is target-agnostic and we
9
- * deliberately do not want a stale `start-contract.json` from a previous
10
- * target lingering after a re-init.
7
+ * the schema-dir-relative `ARTIFACT_FILENAMES` in `hygiene-gitattributes.ts`
8
+ * (not that file's migrations-root-anchored store lines, which are outside
9
+ * this command's scope reinit only ever touches `schemaDir`); kept as a
10
+ * separate constant here because the cleanup contract is target-agnostic
11
+ * and we deliberately do not want a stale artifact from a previous target
12
+ * lingering after a re-init.
11
13
  *
12
- * If a future emit pipeline produces an additional artefact, add it here
13
- * **and** to the gitattributes list — the two stay in lockstep so the
14
- * file `init` advertises as `linguist-generated` is exactly the file
15
- * `init` is willing to delete on re-init.
14
+ * If a future emit pipeline produces an additional schema-dir artifact,
15
+ * add it here **and** to `ARTIFACT_FILENAMES` in `hygiene-gitattributes.ts`
16
+ * the two stay in lockstep so the file `init` advertises as
17
+ * `linguist-generated` is exactly the file `init` is willing to delete on
18
+ * re-init.
16
19
  */
17
- const ARTEFACT_FILENAMES: readonly string[] = [
20
+ const ARTIFACT_FILENAMES: readonly string[] = [
18
21
  'contract.json',
19
22
  'contract.d.ts',
20
- 'end-contract.json',
21
- 'end-contract.d.ts',
22
- 'start-contract.json',
23
- 'start-contract.d.ts',
24
23
  'ops.json',
25
24
  'migration.json',
26
25
  ];
27
26
 
28
27
  /**
29
- * Returns the schema-relative paths of stale contract artefacts the
28
+ * Returns the schema-relative paths of stale contract artifacts the
30
29
  * previous `init` run (or a `contract emit`) left behind in `schemaDir`.
31
30
  * Paths are returned relative to `baseDir` so the caller can plumb them
32
31
  * into `filesWritten`-style logging without re-deriving the path.
33
32
  *
34
33
  * Pure function: no filesystem mutation. Used by `runInit`'s precondition
35
34
  * phase (FR6.2 / NFR3 atomicity) so a downstream parse failure leaves
36
- * the artefacts on disk and the project byte-identical to its pre-init
35
+ * the artifacts on disk and the project byte-identical to its pre-init
37
36
  * state.
38
37
  */
39
- export function findStaleArtefacts(baseDir: string, schemaDir: string): readonly string[] {
38
+ export function findStaleArtifacts(baseDir: string, schemaDir: string): readonly string[] {
40
39
  const result: string[] = [];
41
- for (const filename of ARTEFACT_FILENAMES) {
40
+ for (const filename of ARTIFACT_FILENAMES) {
42
41
  const rel = join(schemaDir, filename);
43
42
  if (existsSync(join(baseDir, rel))) {
44
43
  result.push(rel);
@@ -831,6 +831,7 @@ async function executeMigrateCommand(
831
831
  : await readContractIR(snapshotContractJson, contractPathAbsolute);
832
832
  advancedRef = await executeRefAdvancement(
833
833
  refsDir,
834
+ migrationsDir,
834
835
  options.advanceRef,
835
836
  value.markerHash,
836
837
  contractIR,
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { loadConfig } from '@prisma-next/config-loader';
4
4
  import { createControlStack } from '@prisma-next/framework-components/control';
@@ -7,6 +7,11 @@ import type {
7
7
  IntegrityViolation,
8
8
  } from '@prisma-next/migration-tools/aggregate';
9
9
  import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
10
+ import {
11
+ contractSnapshotDir,
12
+ readContractSnapshotJson,
13
+ } from '@prisma-next/migration-tools/contract-snapshot-store';
14
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
10
15
  import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
11
16
  import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
12
17
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
@@ -18,7 +23,6 @@ import type { Refs } from '@prisma-next/migration-tools/refs';
18
23
  import {
19
24
  isValidSpaceId,
20
25
  listContractSpaceDirectories,
21
- RESERVED_SPACE_SUBDIR_NAMES,
22
26
  spaceMigrationDirectory,
23
27
  spaceRefsDirectory,
24
28
  } from '@prisma-next/migration-tools/spaces';
@@ -83,7 +87,7 @@ function checkFileExists(
83
87
  if (!existsSync(join(dirPath, fileName))) {
84
88
  return {
85
89
  space: spaceId,
86
- code: 'PN-MIG-CHECK-002',
90
+ code: 'MIGRATION.CHECK_FILE_MISSING',
87
91
  where: migrationFileRelative(dirPath, fileName),
88
92
  why: `${fileName} is missing from ${dirName}`,
89
93
  fix: 'Re-emit the migration package or restore from version control.',
@@ -92,32 +96,57 @@ function checkFileExists(
92
96
  return null;
93
97
  }
94
98
 
95
- function checkSnapshotConsistency(
99
+ /**
100
+ * Snapshot-store consistency check for one migration package (D6.5):
101
+ * absent store entry is not an issue (runner independence, ADR 199 — a
102
+ * package dir with only `migration.json` + `ops.json` is legitimate); a
103
+ * present entry whose inner `storage.storageHash` disagrees with
104
+ * `pkg.metadata.to` is `MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH`; an unparseable store
105
+ * entry (or a malformed `to`) is `MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE`.
106
+ */
107
+ async function checkSnapshotConsistency(
96
108
  spaceId: string,
97
109
  pkg: OnDiskMigrationPackage,
98
- ): CheckFailure | null {
99
- const endContractPath = join(pkg.dirPath, 'end-contract.json');
100
- if (!existsSync(endContractPath)) return null;
110
+ migrationsDir: string,
111
+ ): Promise<CheckFailure | null> {
112
+ let snapshotDir: string;
101
113
  try {
102
- const raw = JSON.parse(readFileSync(endContractPath, 'utf-8')) as Record<string, unknown>;
103
- const storage = raw['storage'] as Record<string, unknown> | undefined;
104
- const snapshotHash = storage?.['storageHash'];
105
- if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {
106
- return {
107
- space: spaceId,
108
- code: 'PN-MIG-CHECK-005',
109
- where: migrationPathRelative(pkg.dirPath),
110
- why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but end-contract.json has storageHash=${snapshotHash}`,
111
- fix: 'Re-emit the migration package so migration.json and end-contract.json agree.',
112
- };
113
- }
114
+ snapshotDir = contractSnapshotDir(migrationsDir, pkg.metadata.to);
114
115
  } catch {
115
116
  return {
116
117
  space: spaceId,
117
- code: 'PN-MIG-CHECK-006',
118
+ code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',
118
119
  where: migrationPathRelative(pkg.dirPath),
119
- why: `Migration "${pkg.dirName}" has an unparseable end-contract.json.`,
120
- fix: 'Re-emit the migration package to repair the snapshot file.',
120
+ why: `Migration "${pkg.dirName}" declares to="${pkg.metadata.to}", which is not a well-formed contract snapshot hash.`,
121
+ fix: 'Re-emit the migration package so migration.json declares a valid `sha256:<64hex>` to-hash.',
122
+ };
123
+ }
124
+
125
+ let raw: unknown;
126
+ try {
127
+ raw = await readContractSnapshotJson(migrationsDir, pkg.metadata.to);
128
+ } catch (error) {
129
+ if (MigrationToolsError.is(error) && error.code === 'MIGRATION.CONTRACT_SNAPSHOT_MISSING') {
130
+ return null;
131
+ }
132
+ return {
133
+ space: spaceId,
134
+ code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',
135
+ where: migrationPathRelative(pkg.dirPath),
136
+ why: `Migration "${pkg.dirName}" has an unparseable contract snapshot at ${snapshotDir}/contract.json.`,
137
+ fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',
138
+ };
139
+ }
140
+ const record = raw !== null && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
141
+ const storage = record['storage'] as Record<string, unknown> | undefined;
142
+ const snapshotHash = storage?.['storageHash'];
143
+ if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {
144
+ return {
145
+ space: spaceId,
146
+ code: 'MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH',
147
+ where: migrationPathRelative(pkg.dirPath),
148
+ why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but its contract snapshot has storageHash=${snapshotHash}`,
149
+ fix: 'Re-emit the migration package so migration.json and its contract snapshot agree.',
121
150
  };
122
151
  }
123
152
  return null;
@@ -137,6 +166,8 @@ export interface CheckSpace {
137
166
  readonly graph: MigrationGraph;
138
167
  readonly migrationsDir: string;
139
168
  readonly refsDir: string;
169
+ /** Migrations root the contract-snapshot store lives under — shared by every space. */
170
+ readonly projectMigrationsDir: string;
140
171
  }
141
172
 
142
173
  /**
@@ -152,9 +183,7 @@ export async function enumerateCheckSpaces(
152
183
  projectMigrationsDir: string,
153
184
  ): Promise<readonly CheckSpace[]> {
154
185
  const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
155
- const onDiskSpaceIds = new Set(
156
- candidateDirs.filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name)).filter(isValidSpaceId),
157
- );
186
+ const onDiskSpaceIds = new Set(candidateDirs.filter(isValidSpaceId));
158
187
  const spaces: CheckSpace[] = [];
159
188
  for (const space of aggregate.spaces()) {
160
189
  const spaceId = space.spaceId;
@@ -168,6 +197,7 @@ export async function enumerateCheckSpaces(
168
197
  graph: space.graph(),
169
198
  migrationsDir,
170
199
  refsDir: spaceRefsDirectory(migrationsDir),
200
+ projectMigrationsDir,
171
201
  });
172
202
  }
173
203
  return spaces;
@@ -212,7 +242,7 @@ function checkReachability(space: CheckSpace): readonly CheckFailure[] {
212
242
  if (!isReachable) {
213
243
  failures.push({
214
244
  space: space.spaceId,
215
- code: 'PN-MIG-CHECK-003',
245
+ code: 'MIGRATION.CHECK_UNREACHABLE_MIGRATION',
216
246
  where: migrationPathRelative(pkg.dirPath),
217
247
  why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
218
248
  fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',
@@ -228,7 +258,7 @@ function checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {
228
258
  if (!space.graph.nodes.has(entry.hash)) {
229
259
  failures.push({
230
260
  space: space.spaceId,
231
- code: 'PN-MIG-CHECK-004',
261
+ code: 'MIGRATION.CHECK_DANGLING_REF',
232
262
  where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),
233
263
  why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
234
264
  fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`,
@@ -238,12 +268,15 @@ function checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {
238
268
  return failures;
239
269
  }
240
270
 
241
- function checkSpace(space: CheckSpace): readonly CheckFailure[] {
271
+ async function checkSpace(space: CheckSpace): Promise<readonly CheckFailure[]> {
272
+ const snapshotFailures = await Promise.all(
273
+ space.packages.map((pkg) =>
274
+ checkSnapshotConsistency(space.spaceId, pkg, space.projectMigrationsDir),
275
+ ),
276
+ );
242
277
  return [
243
278
  ...checkManifestFilesPresent(space),
244
- ...space.packages
245
- .map((pkg) => checkSnapshotConsistency(space.spaceId, pkg))
246
- .filter((f): f is CheckFailure => f !== null),
279
+ ...snapshotFailures.filter((f): f is CheckFailure => f !== null),
247
280
  ...checkReachability(space),
248
281
  ...checkDanglingRefs(space),
249
282
  ];
@@ -272,9 +305,9 @@ export interface RunMigrationCheckInputs {
272
305
  * Aggregate-integrity violations (which already span every space) are folded
273
306
  * in by the caller, not here.
274
307
  */
275
- export function runMigrationCheck(
308
+ export async function runMigrationCheck(
276
309
  inputs: RunMigrationCheckInputs,
277
- ): Result<MigrationCheckResult, CliStructuredError> {
310
+ ): Promise<Result<MigrationCheckResult, CliStructuredError>> {
278
311
  const { spaces, spaceFilter } = inputs;
279
312
 
280
313
  if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
@@ -287,7 +320,7 @@ export function runMigrationCheck(
287
320
  const scopedSpaces =
288
321
  spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
289
322
 
290
- const failures = scopedSpaces.flatMap(checkSpace);
323
+ const failures = (await Promise.all(scopedSpaces.map(checkSpace))).flat();
291
324
  if (failures.length === 0) {
292
325
  return ok({ ok: true, failures: [], summary: 'All checks passed' });
293
326
  }
@@ -365,7 +398,7 @@ async function executeMigrationCheckCommand(
365
398
  });
366
399
  }
367
400
 
368
- const checkResult = runMigrationCheck({
401
+ const checkResult = await runMigrationCheck({
369
402
  spaces,
370
403
  ...(options.space !== undefined ? { spaceFilter: options.space } : {}),
371
404
  });
@@ -522,7 +555,7 @@ async function checkSingleTarget(
522
555
  matchedPkg = hits[0]!.pkg;
523
556
  } else if (bestParseFailure !== undefined) {
524
557
  // The ref didn't resolve in any in-scope space — surface the most informative
525
- // parse failure through the shared ref-resolution envelope (PN-RUN-3000) the
558
+ // parse failure through the shared ref-resolution envelope (CONTRACT.VERIFY_FAILED) the
526
559
  // earlier work established, rather than a bespoke string. (Ref-resolved-but-
527
560
  // no-package falls through to the "not found on disk" result below.)
528
561
  return { error: mapRefResolutionError(bestParseFailure), exitCode: PRECONDITION };
@@ -551,14 +584,18 @@ async function checkSingleTarget(
551
584
  if (!verification.ok) {
552
585
  failures.push({
553
586
  space: matchedSpace.spaceId,
554
- code: 'PN-MIG-CHECK-001',
587
+ code: 'MIGRATION.CHECK_HASH_MISMATCH',
555
588
  where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),
556
589
  why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
557
590
  fix: 'Re-emit the migration package or restore from version control.',
558
591
  });
559
592
  }
560
593
 
561
- const snapshotFailure = checkSnapshotConsistency(matchedSpace.spaceId, matchedPkg);
594
+ const snapshotFailure = await checkSnapshotConsistency(
595
+ matchedSpace.spaceId,
596
+ matchedPkg,
597
+ matchedSpace.projectMigrationsDir,
598
+ );
562
599
  if (snapshotFailure) failures.push(snapshotFailure);
563
600
 
564
601
  const resolvedSpaceId = matchedSpace.spaceId !== 'app' ? matchedSpace.spaceId : undefined;
@@ -9,7 +9,6 @@ import {
9
9
  APP_SPACE_ID,
10
10
  isValidSpaceId,
11
11
  listContractSpaceDirectories,
12
- RESERVED_SPACE_SUBDIR_NAMES,
13
12
  } from '@prisma-next/migration-tools/spaces';
14
13
  import { ifDefined } from '@prisma-next/utils/defined';
15
14
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
@@ -81,10 +80,7 @@ export function listRefsByContractHash(
81
80
 
82
81
  async function orderedOnDiskSpaceIds(projectMigrationsDir: string): Promise<readonly string[]> {
83
82
  const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
84
- return candidateDirs
85
- .filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name))
86
- .filter(isValidSpaceId)
87
- .sort(compareSpaceIds);
83
+ return candidateDirs.filter(isValidSpaceId).sort(compareSpaceIds);
88
84
  }
89
85
 
90
86
  /**