@prisma-next/cli 0.16.0-dev.3 → 0.16.0-dev.31

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 (173) hide show
  1. package/README.md +44 -45
  2. package/dist/cli.mjs +13 -13
  3. package/dist/{client-KuBQftxz.mjs → client-B1UjQc_x.mjs} +50 -58
  4. package/dist/client-B1UjQc_x.mjs.map +1 -0
  5. package/dist/{command-helpers-Cpq44aVa.mjs → command-helpers-D-jBl0Qt.mjs} +16 -8
  6. package/dist/command-helpers-D-jBl0Qt.mjs.map +1 -0
  7. package/dist/commands/contract-emit.mjs +1 -1
  8. package/dist/commands/contract-infer.mjs +1 -1
  9. package/dist/commands/db-init.d.mts.map +1 -1
  10. package/dist/commands/db-init.mjs +8 -6
  11. package/dist/commands/db-init.mjs.map +1 -1
  12. package/dist/commands/db-schema.mjs +3 -3
  13. package/dist/commands/db-sign.d.mts.map +1 -1
  14. package/dist/commands/db-sign.mjs +11 -12
  15. package/dist/commands/db-sign.mjs.map +1 -1
  16. package/dist/commands/db-update.d.mts.map +1 -1
  17. package/dist/commands/db-update.mjs +14 -15
  18. package/dist/commands/db-update.mjs.map +1 -1
  19. package/dist/commands/db-verify.mjs +1 -1
  20. package/dist/commands/migrate.d.mts +1 -1
  21. package/dist/commands/migrate.d.mts.map +1 -1
  22. package/dist/commands/migrate.mjs +13 -13
  23. package/dist/commands/migrate.mjs.map +1 -1
  24. package/dist/commands/migration-check.d.mts +4 -2
  25. package/dist/commands/migration-check.d.mts.map +1 -1
  26. package/dist/commands/migration-check.mjs +1 -1
  27. package/dist/commands/migration-graph.d.mts +1 -1
  28. package/dist/commands/migration-graph.mjs +4 -4
  29. package/dist/commands/migration-list.d.mts +1 -1
  30. package/dist/commands/migration-list.d.mts.map +1 -1
  31. package/dist/commands/migration-list.mjs +1 -1
  32. package/dist/commands/migration-log.d.mts +1 -1
  33. package/dist/commands/migration-log.mjs +1 -1
  34. package/dist/commands/migration-new.d.mts.map +1 -1
  35. package/dist/commands/migration-new.mjs +16 -40
  36. package/dist/commands/migration-new.mjs.map +1 -1
  37. package/dist/commands/migration-plan.d.mts +3 -4
  38. package/dist/commands/migration-plan.d.mts.map +1 -1
  39. package/dist/commands/migration-plan.mjs +1 -1
  40. package/dist/commands/migration-show.mjs +5 -5
  41. package/dist/commands/migration-show.mjs.map +1 -1
  42. package/dist/commands/migration-status.d.mts +1 -1
  43. package/dist/commands/migration-status.d.mts.map +1 -1
  44. package/dist/commands/migration-status.mjs +1 -1
  45. package/dist/commands/ref.d.mts +1 -1
  46. package/dist/commands/ref.d.mts.map +1 -1
  47. package/dist/commands/ref.mjs +13 -18
  48. package/dist/commands/ref.mjs.map +1 -1
  49. package/dist/commands/telemetry/index.mjs +1 -1
  50. package/dist/contract-at-errors-DvZ_DzJK.mjs +36 -0
  51. package/dist/contract-at-errors-DvZ_DzJK.mjs.map +1 -0
  52. package/dist/{contract-emit-CgnXSANN.mjs → contract-emit-BT46Mu8Q.mjs} +3 -3
  53. package/dist/{contract-emit-CgnXSANN.mjs.map → contract-emit-BT46Mu8Q.mjs.map} +1 -1
  54. package/dist/{contract-emit-cJQBlvGb.mjs → contract-emit-hVdlaF2U.mjs} +6 -7
  55. package/dist/{contract-emit-cJQBlvGb.mjs.map → contract-emit-hVdlaF2U.mjs.map} +1 -1
  56. package/dist/{contract-enrichment-gn9sWbPw.mjs → contract-enrichment-JUhLv-BX.mjs} +8 -8
  57. package/dist/{contract-enrichment-gn9sWbPw.mjs.map → contract-enrichment-JUhLv-BX.mjs.map} +1 -1
  58. package/dist/{contract-infer-Cg8evbeg.mjs → contract-infer-DpqTQECL.mjs} +3 -3
  59. package/dist/{contract-infer-Cg8evbeg.mjs.map → contract-infer-DpqTQECL.mjs.map} +1 -1
  60. package/dist/{contract-space-aggregate-loader-D_3VeHVb.mjs → contract-space-aggregate-loader-CNd-ZB6C.mjs} +14 -14
  61. package/dist/contract-space-aggregate-loader-CNd-ZB6C.mjs.map +1 -0
  62. package/dist/{db-verify-BEclUzki.mjs → db-verify-FEN6k1m7.mjs} +10 -8
  63. package/dist/db-verify-FEN6k1m7.mjs.map +1 -0
  64. package/dist/exports/control-api.d.mts +4 -4
  65. package/dist/exports/control-api.d.mts.map +1 -1
  66. package/dist/exports/control-api.mjs +3 -3
  67. package/dist/exports/index.d.mts +2 -1
  68. package/dist/exports/index.d.mts.map +1 -1
  69. package/dist/exports/index.mjs +61 -21
  70. package/dist/exports/index.mjs.map +1 -1
  71. package/dist/exports/init-output.d.mts.map +1 -1
  72. package/dist/exports/init-output.mjs +1 -1
  73. package/dist/{format-DAUAZqPt.mjs → format-DI5qVr22.mjs} +8 -7
  74. package/dist/format-DI5qVr22.mjs.map +1 -0
  75. package/dist/{framework-components-CnbUbiJw.mjs → framework-components-C4XACYNB.mjs} +3 -3
  76. package/dist/framework-components-C4XACYNB.mjs.map +1 -0
  77. package/dist/{init-C3JhkHg-.mjs → init-ClrRkbwd.mjs} +77 -67
  78. package/dist/init-ClrRkbwd.mjs.map +1 -0
  79. package/dist/{inspect-live-schema-CX7EA5C3.mjs → inspect-live-schema-C0Rgdtw_.mjs} +4 -4
  80. package/dist/{inspect-live-schema-CX7EA5C3.mjs.map → inspect-live-schema-C0Rgdtw_.mjs.map} +1 -1
  81. package/dist/{migration-check-MHT3w7_v.mjs → migration-check-zEkHpPDT.mjs} +58 -30
  82. package/dist/migration-check-zEkHpPDT.mjs.map +1 -0
  83. package/dist/{migration-command-scaffold-CRyYrHZ9.mjs → migration-command-scaffold-CkNsp9g1.mjs} +4 -4
  84. package/dist/{migration-command-scaffold-CRyYrHZ9.mjs.map → migration-command-scaffold-CkNsp9g1.mjs.map} +1 -1
  85. package/dist/{migration-graph-command-render-B83xrnNy.mjs → migration-graph-command-render-DEwFpx-w.mjs} +5 -4
  86. package/dist/migration-graph-command-render-DEwFpx-w.mjs.map +1 -0
  87. package/dist/{migration-list-BAKujUjh.mjs → migration-list-BkafFVXS.mjs} +6 -6
  88. package/dist/migration-list-BkafFVXS.mjs.map +1 -0
  89. package/dist/{migration-log-C_xam2HZ.mjs → migration-log-BxhAtbhA.mjs} +5 -5
  90. package/dist/{migration-log-C_xam2HZ.mjs.map → migration-log-BxhAtbhA.mjs.map} +1 -1
  91. package/dist/{migration-path-target-DpPTsRCg.mjs → migration-path-target-B5HkjSTO.mjs} +2 -2
  92. package/dist/{migration-path-target-DpPTsRCg.mjs.map → migration-path-target-B5HkjSTO.mjs.map} +1 -1
  93. package/dist/{migration-plan-CreSVtWZ.mjs → migration-plan-Bk2pbPQH.mjs} +64 -74
  94. package/dist/migration-plan-Bk2pbPQH.mjs.map +1 -0
  95. package/dist/{migration-status-YgEvXf-u.mjs → migration-status-BzlARseq.mjs} +13 -12
  96. package/dist/migration-status-BzlARseq.mjs.map +1 -0
  97. package/dist/{output-mEQ74_nd.mjs → output-DWNYqZGc.mjs} +5 -6
  98. package/dist/output-DWNYqZGc.mjs.map +1 -0
  99. package/dist/{ref-advancement-BkXlikCA.mjs → ref-advancement-BCz7X7qJ.mjs} +13 -6
  100. package/dist/ref-advancement-BCz7X7qJ.mjs.map +1 -0
  101. package/dist/{telemetry-DVr6RFnq.mjs → telemetry-BbniBUbP.mjs} +2 -2
  102. package/dist/{telemetry-DVr6RFnq.mjs.map → telemetry-BbniBUbP.mjs.map} +1 -1
  103. package/dist/{types-D4r_HI9-.d.mts → types-DgXprE3w.d.mts} +3 -3
  104. package/dist/types-DgXprE3w.d.mts.map +1 -0
  105. package/dist/{verify-CgDVZjhc.mjs → verify-BSaNeaSL.mjs} +2 -2
  106. package/dist/{verify-CgDVZjhc.mjs.map → verify-BSaNeaSL.mjs.map} +1 -1
  107. package/package.json +21 -21
  108. package/src/commands/db-init.ts +5 -3
  109. package/src/commands/db-sign.ts +9 -7
  110. package/src/commands/db-update.ts +18 -9
  111. package/src/commands/db-verify.ts +4 -4
  112. package/src/commands/format.ts +1 -1
  113. package/src/commands/init/hygiene-gitattributes.ts +37 -21
  114. package/src/commands/init/init.ts +6 -6
  115. package/src/commands/init/output.ts +4 -5
  116. package/src/commands/init/probe-db.ts +9 -2
  117. package/src/commands/init/reinit-cleanup.ts +16 -17
  118. package/src/commands/init/templates/render.ts +2 -1
  119. package/src/commands/inspect-live-schema.ts +1 -1
  120. package/src/commands/migrate.ts +6 -5
  121. package/src/commands/migration-check.ts +72 -34
  122. package/src/commands/migration-list.ts +1 -5
  123. package/src/commands/migration-log.ts +1 -1
  124. package/src/commands/migration-new.ts +17 -41
  125. package/src/commands/migration-plan.ts +64 -88
  126. package/src/commands/migration-show.ts +2 -2
  127. package/src/commands/migration-status.ts +6 -7
  128. package/src/commands/ref.ts +19 -14
  129. package/src/control-api/client.ts +43 -25
  130. package/src/control-api/contract-enrichment.ts +7 -7
  131. package/src/control-api/operations/contract-emit.ts +2 -3
  132. package/src/control-api/operations/db-init.ts +3 -3
  133. package/src/control-api/operations/db-run.ts +8 -7
  134. package/src/control-api/operations/db-update.ts +2 -2
  135. package/src/control-api/operations/db-verify.ts +4 -4
  136. package/src/control-api/operations/format.ts +5 -4
  137. package/src/control-api/operations/migrate.ts +6 -5
  138. package/src/control-api/operations/run-migration.ts +2 -1
  139. package/src/control-api/types.ts +1 -1
  140. package/src/load-ts-contract.ts +81 -16
  141. package/src/utils/cli-errors.ts +15 -7
  142. package/src/utils/combine-verify-results.ts +2 -1
  143. package/src/utils/command-helpers.ts +15 -5
  144. package/src/utils/contract-at-errors.ts +13 -22
  145. package/src/utils/contract-space-aggregate-loader.ts +8 -8
  146. package/src/utils/contract-space-seed-phase.ts +11 -9
  147. package/src/utils/extension-pack-inputs.ts +8 -8
  148. package/src/utils/formatters/migration-graph-grid-layout.ts +2 -1
  149. package/src/utils/formatters/migration-graph-labels.ts +1 -2
  150. package/src/utils/formatters/migration-list-data-column.ts +1 -2
  151. package/src/utils/framework-components.ts +9 -9
  152. package/src/utils/integrity-violation-to-check-failure.ts +3 -3
  153. package/src/utils/migration-command-scaffold.ts +1 -1
  154. package/src/utils/plan-resolution.ts +7 -10
  155. package/src/utils/ref-advancement.ts +22 -3
  156. package/dist/client-KuBQftxz.mjs.map +0 -1
  157. package/dist/command-helpers-Cpq44aVa.mjs.map +0 -1
  158. package/dist/contract-at-errors-D9gEjYtg.mjs +0 -42
  159. package/dist/contract-at-errors-D9gEjYtg.mjs.map +0 -1
  160. package/dist/contract-space-aggregate-loader-D_3VeHVb.mjs.map +0 -1
  161. package/dist/db-verify-BEclUzki.mjs.map +0 -1
  162. package/dist/format-DAUAZqPt.mjs.map +0 -1
  163. package/dist/framework-components-CnbUbiJw.mjs.map +0 -1
  164. package/dist/init-C3JhkHg-.mjs.map +0 -1
  165. package/dist/migration-check-MHT3w7_v.mjs.map +0 -1
  166. package/dist/migration-graph-command-render-B83xrnNy.mjs.map +0 -1
  167. package/dist/migration-list-BAKujUjh.mjs.map +0 -1
  168. package/dist/migration-plan-CreSVtWZ.mjs.map +0 -1
  169. package/dist/migration-status-YgEvXf-u.mjs.map +0 -1
  170. package/dist/output-mEQ74_nd.mjs.map +0 -1
  171. package/dist/ref-advancement-BkXlikCA.mjs.map +0 -1
  172. package/dist/types-D4r_HI9-.d.mts.map +0 -1
  173. package/src/control-api/errors.ts +0 -9
@@ -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);
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { InternalError } from '@prisma-next/utils/internal-error';
2
3
  import { join } from 'pathe';
3
4
 
4
5
  export function renderTemplate(
@@ -12,7 +13,7 @@ export function renderTemplate(
12
13
  for (const key of variableNames) {
13
14
  const value = vars[key];
14
15
  if (value === undefined) {
15
- throw new Error(`Template variable '${key}' is not defined`);
16
+ throw new InternalError(`Template variable '${key}' is not defined`);
16
17
  }
17
18
  result = result.replaceAll(`{{${key}}}`, value);
18
19
  }
@@ -131,7 +131,7 @@ export async function inspectLiveSchema(
131
131
  target: config.target,
132
132
  adapter: config.adapter,
133
133
  driver: config.driver,
134
- extensionPacks: config.extensionPacks ?? [],
134
+ extensions: config.extensions ?? [],
135
135
  });
136
136
  const onProgress = createProgressAdapter({ ui, flags });
137
137
 
@@ -330,7 +330,7 @@ async function executeMigrateShowCommand(
330
330
  target: config.target,
331
331
  adapter: config.adapter,
332
332
  driver: config.driver!,
333
- extensionPacks: config.extensionPacks ?? [],
333
+ extensions: config.extensions ?? [],
334
334
  });
335
335
  try {
336
336
  await client.connect(dbConnection);
@@ -675,7 +675,7 @@ async function executeMigrateCommand(
675
675
  targetId: config.target.targetId,
676
676
  migrationsDir,
677
677
  appContract: contractRaw,
678
- extensionPacks: config.extensionPacks ?? [],
678
+ extensions: config.extensions ?? [],
679
679
  deserializeContract: (json) => familyInstance.deserializeContract(json),
680
680
  });
681
681
  if (!loadedAggregate.ok) {
@@ -684,7 +684,7 @@ async function executeMigrateCommand(
684
684
  const aggregate = loadedAggregate.value;
685
685
  const integrityFailure = refuseContractSpaceIntegrity(aggregate, {
686
686
  declaredExtensions: toDeclaredExtensionsFromRaw(
687
- (config.extensionPacks ?? []) as ReadonlyArray<unknown>,
687
+ (config.extensions ?? []) as ReadonlyArray<unknown>,
688
688
  ),
689
689
  checkContracts: true,
690
690
  });
@@ -740,7 +740,7 @@ async function executeMigrateCommand(
740
740
  target: config.target,
741
741
  adapter: config.adapter,
742
742
  driver: config.driver,
743
- extensionPacks: config.extensionPacks ?? [],
743
+ extensions: config.extensions ?? [],
744
744
  });
745
745
 
746
746
  try {
@@ -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,
@@ -886,7 +887,7 @@ export function createMigrateCommand(): Command {
886
887
  setCommandExamples(command, [
887
888
  'prisma-next migrate --db $DATABASE_URL',
888
889
  'prisma-next migrate --to production --db $DATABASE_URL',
889
- 'prisma-next migrate --to sha256:abc123 --db $DATABASE_URL',
890
+ 'prisma-next migrate --to abc123 --db $DATABASE_URL',
890
891
  'prisma-next migrate --show --db $DATABASE_URL',
891
892
  'prisma-next migrate --show --from @contract --to production',
892
893
  ]);
@@ -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,12 @@ 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 { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
11
+ import {
12
+ contractSnapshotDir,
13
+ readContractSnapshotJson,
14
+ } from '@prisma-next/migration-tools/contract-snapshot-store';
15
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
10
16
  import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
11
17
  import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
12
18
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
@@ -18,7 +24,6 @@ import type { Refs } from '@prisma-next/migration-tools/refs';
18
24
  import {
19
25
  isValidSpaceId,
20
26
  listContractSpaceDirectories,
21
- RESERVED_SPACE_SUBDIR_NAMES,
22
27
  spaceMigrationDirectory,
23
28
  spaceRefsDirectory,
24
29
  } from '@prisma-next/migration-tools/spaces';
@@ -92,32 +97,57 @@ function checkFileExists(
92
97
  return null;
93
98
  }
94
99
 
95
- function checkSnapshotConsistency(
100
+ /**
101
+ * Snapshot-store consistency check for one migration package (D6.5):
102
+ * absent store entry is not an issue (runner independence, ADR 199 — a
103
+ * package dir with only `migration.json` + `ops.json` is legitimate); a
104
+ * present entry whose inner `storage.storageHash` disagrees with
105
+ * `pkg.metadata.to` is `MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH`; an unparseable store
106
+ * entry (or a malformed `to`) is `MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE`.
107
+ */
108
+ async function checkSnapshotConsistency(
96
109
  spaceId: string,
97
110
  pkg: OnDiskMigrationPackage,
98
- ): CheckFailure | null {
99
- const endContractPath = join(pkg.dirPath, 'end-contract.json');
100
- if (!existsSync(endContractPath)) return null;
111
+ migrationsDir: string,
112
+ ): Promise<CheckFailure | null> {
113
+ let snapshotDir: string;
101
114
  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: 'MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH',
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
- }
115
+ snapshotDir = contractSnapshotDir(migrationsDir, pkg.metadata.to);
114
116
  } catch {
115
117
  return {
116
118
  space: spaceId,
117
119
  code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',
118
120
  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.',
121
+ why: `Migration "${pkg.dirName}" declares to="${pkg.metadata.to}", which is not a well-formed contract snapshot hash.`,
122
+ fix: 'Re-emit the migration package so migration.json declares a valid 64-hex to-hash.',
123
+ };
124
+ }
125
+
126
+ let raw: unknown;
127
+ try {
128
+ raw = await readContractSnapshotJson(migrationsDir, pkg.metadata.to);
129
+ } catch (error) {
130
+ if (MigrationToolsError.is(error) && error.code === 'MIGRATION.CONTRACT_SNAPSHOT_MISSING') {
131
+ return null;
132
+ }
133
+ return {
134
+ space: spaceId,
135
+ code: 'MIGRATION.CHECK_SNAPSHOT_UNPARSEABLE',
136
+ where: migrationPathRelative(pkg.dirPath),
137
+ why: `Migration "${pkg.dirName}" has an unparseable contract snapshot at ${snapshotDir}/contract.json.`,
138
+ fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',
139
+ };
140
+ }
141
+ const record = raw !== null && typeof raw === 'object' ? (raw as Record<string, unknown>) : {};
142
+ const storage = record['storage'] as Record<string, unknown> | undefined;
143
+ const snapshotHash = storage?.['storageHash'];
144
+ if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {
145
+ return {
146
+ space: spaceId,
147
+ code: 'MIGRATION.CHECK_SNAPSHOT_HASH_MISMATCH',
148
+ where: migrationPathRelative(pkg.dirPath),
149
+ why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but its contract snapshot has storageHash=${snapshotHash}`,
150
+ fix: 'Re-emit the migration package so migration.json and its contract snapshot agree.',
121
151
  };
122
152
  }
123
153
  return null;
@@ -137,6 +167,8 @@ export interface CheckSpace {
137
167
  readonly graph: MigrationGraph;
138
168
  readonly migrationsDir: string;
139
169
  readonly refsDir: string;
170
+ /** Migrations root the contract-snapshot store lives under — shared by every space. */
171
+ readonly projectMigrationsDir: string;
140
172
  }
141
173
 
142
174
  /**
@@ -152,9 +184,7 @@ export async function enumerateCheckSpaces(
152
184
  projectMigrationsDir: string,
153
185
  ): Promise<readonly CheckSpace[]> {
154
186
  const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
155
- const onDiskSpaceIds = new Set(
156
- candidateDirs.filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name)).filter(isValidSpaceId),
157
- );
187
+ const onDiskSpaceIds = new Set(candidateDirs.filter(isValidSpaceId));
158
188
  const spaces: CheckSpace[] = [];
159
189
  for (const space of aggregate.spaces()) {
160
190
  const spaceId = space.spaceId;
@@ -168,6 +198,7 @@ export async function enumerateCheckSpaces(
168
198
  graph: space.graph(),
169
199
  migrationsDir,
170
200
  refsDir: spaceRefsDirectory(migrationsDir),
201
+ projectMigrationsDir,
171
202
  });
172
203
  }
173
204
  return spaces;
@@ -208,7 +239,7 @@ function checkReachability(space: CheckSpace): readonly CheckFailure[] {
208
239
  const isReachable =
209
240
  pkg.metadata.from === null ||
210
241
  allToHashes.has(pkg.metadata.from) ||
211
- pkg.metadata.from === 'sha256:empty';
242
+ pkg.metadata.from === EMPTY_CONTRACT_HASH;
212
243
  if (!isReachable) {
213
244
  failures.push({
214
245
  space: space.spaceId,
@@ -238,12 +269,15 @@ function checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {
238
269
  return failures;
239
270
  }
240
271
 
241
- function checkSpace(space: CheckSpace): readonly CheckFailure[] {
272
+ async function checkSpace(space: CheckSpace): Promise<readonly CheckFailure[]> {
273
+ const snapshotFailures = await Promise.all(
274
+ space.packages.map((pkg) =>
275
+ checkSnapshotConsistency(space.spaceId, pkg, space.projectMigrationsDir),
276
+ ),
277
+ );
242
278
  return [
243
279
  ...checkManifestFilesPresent(space),
244
- ...space.packages
245
- .map((pkg) => checkSnapshotConsistency(space.spaceId, pkg))
246
- .filter((f): f is CheckFailure => f !== null),
280
+ ...snapshotFailures.filter((f): f is CheckFailure => f !== null),
247
281
  ...checkReachability(space),
248
282
  ...checkDanglingRefs(space),
249
283
  ];
@@ -272,9 +306,9 @@ export interface RunMigrationCheckInputs {
272
306
  * Aggregate-integrity violations (which already span every space) are folded
273
307
  * in by the caller, not here.
274
308
  */
275
- export function runMigrationCheck(
309
+ export async function runMigrationCheck(
276
310
  inputs: RunMigrationCheckInputs,
277
- ): Result<MigrationCheckResult, CliStructuredError> {
311
+ ): Promise<Result<MigrationCheckResult, CliStructuredError>> {
278
312
  const { spaces, spaceFilter } = inputs;
279
313
 
280
314
  if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
@@ -287,7 +321,7 @@ export function runMigrationCheck(
287
321
  const scopedSpaces =
288
322
  spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
289
323
 
290
- const failures = scopedSpaces.flatMap(checkSpace);
324
+ const failures = (await Promise.all(scopedSpaces.map(checkSpace))).flat();
291
325
  if (failures.length === 0) {
292
326
  return ok({ ok: true, failures: [], summary: 'All checks passed' });
293
327
  }
@@ -301,7 +335,7 @@ async function loadAggregateIntegrityViolations(
301
335
  try {
302
336
  const contractJsonContent = await readFile(resolveContractPath(config), 'utf-8');
303
337
  const familyInstance = config.family.create(createControlStack(config));
304
- const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);
338
+ const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensions ?? []);
305
339
 
306
340
  const parsedAppContract: unknown = JSON.parse(contractJsonContent);
307
341
  const aggregate = await loadContractSpaceAggregate({
@@ -365,7 +399,7 @@ async function executeMigrationCheckCommand(
365
399
  });
366
400
  }
367
401
 
368
- const checkResult = runMigrationCheck({
402
+ const checkResult = await runMigrationCheck({
369
403
  spaces,
370
404
  ...(options.space !== undefined ? { spaceFilter: options.space } : {}),
371
405
  });
@@ -558,7 +592,11 @@ async function checkSingleTarget(
558
592
  });
559
593
  }
560
594
 
561
- const snapshotFailure = checkSnapshotConsistency(matchedSpace.spaceId, matchedPkg);
595
+ const snapshotFailure = await checkSnapshotConsistency(
596
+ matchedSpace.spaceId,
597
+ matchedPkg,
598
+ matchedSpace.projectMigrationsDir,
599
+ );
562
600
  if (snapshotFailure) failures.push(snapshotFailure);
563
601
 
564
602
  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
  /**
@@ -84,7 +84,7 @@ export async function executeMigrationLogCommand(
84
84
  target: config.target,
85
85
  adapter: config.adapter,
86
86
  ...ifDefined('driver', config.driver),
87
- extensionPacks: config.extensionPacks ?? [],
87
+ extensions: config.extensions ?? [],
88
88
  });
89
89
 
90
90
  try {
@@ -14,12 +14,13 @@ import type { Contract } from '@prisma-next/contract/types';
14
14
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
15
15
  import { APP_SPACE_ID, createControlStack } from '@prisma-next/framework-components/control';
16
16
  import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
17
- import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
18
17
  import {
19
- copyFilesWithRename,
20
- formatMigrationDirName,
21
- writeMigrationPackage,
22
- } from '@prisma-next/migration-tools/io';
18
+ contractSnapshotDir,
19
+ snapshotsImportPathFrom,
20
+ writeContractSnapshot,
21
+ } from '@prisma-next/migration-tools/contract-snapshot-store';
22
+ import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
23
+ import { formatMigrationDirName, writeMigrationPackage } from '@prisma-next/migration-tools/io';
23
24
  import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
24
25
  import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
25
26
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
@@ -28,7 +29,6 @@ import { Command } from 'commander';
28
29
  import { join, relative } from 'pathe';
29
30
  import {
30
31
  CliStructuredError,
31
- errorFileNotFound,
32
32
  errorRuntime,
33
33
  errorTargetMigrationNotSupported,
34
34
  errorUnexpected,
@@ -133,7 +133,6 @@ async function executeMigrationNewCommand(
133
133
  const graph = aggregate.app.graph();
134
134
 
135
135
  let fromHash: string | null = null;
136
- let fromContractSourceDir: string | null = null;
137
136
 
138
137
  if (packages.length > 0) {
139
138
  if (options.from) {
@@ -147,17 +146,10 @@ async function executeMigrationNewCommand(
147
146
  );
148
147
  }
149
148
  fromHash = match.metadata.to;
150
- fromContractSourceDir = match.dirPath;
151
149
  } else {
152
150
  const latestMigration = findLatestMigration(graph);
153
151
  if (latestMigration) {
154
152
  fromHash = latestMigration.to;
155
- const leafPkg = packages.find(
156
- (p) => p.metadata.migrationHash === latestMigration.migrationHash,
157
- );
158
- if (leafPkg) {
159
- fromContractSourceDir = leafPkg.dirPath;
160
- }
161
153
  }
162
154
  }
163
155
  }
@@ -204,44 +196,28 @@ async function executeMigrationNewCommand(
204
196
  assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [
205
197
  config.target,
206
198
  config.adapter,
207
- ...(config.extensionPacks ?? []),
199
+ ...(config.extensions ?? []),
208
200
  ]);
209
201
 
210
202
  await writeMigrationPackage(packageDir, metadata, []);
211
203
  const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
212
- await copyFilesWithRename(packageDir, [
213
- { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
214
- { sourcePath: destinationArtifacts.dtsPath, destName: 'end-contract.d.ts' },
204
+ const [contractJsonRaw, contractDts] = await Promise.all([
205
+ readFile(destinationArtifacts.jsonPath, 'utf-8'),
206
+ readFile(destinationArtifacts.dtsPath, 'utf-8'),
215
207
  ]);
216
- if (fromContractSourceDir !== null) {
217
- const sourceArtifacts = getEmittedArtifactPaths(
218
- join(fromContractSourceDir, 'end-contract.json'),
219
- );
220
- try {
221
- await copyFilesWithRename(packageDir, [
222
- { sourcePath: sourceArtifacts.jsonPath, destName: 'start-contract.json' },
223
- { sourcePath: sourceArtifacts.dtsPath, destName: 'start-contract.d.ts' },
224
- ]);
225
- } catch (error) {
226
- if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
227
- return notOk(
228
- errorFileNotFound(sourceArtifacts.jsonPath, {
229
- why: `Predecessor migration is missing its destination contract snapshot at ${sourceArtifacts.jsonPath}`,
230
- fix: 'Re-emit the predecessor migration (`prisma-next migration plan` from its source) so its sibling `end-contract.json` is restored, then re-run this command.',
231
- }),
232
- );
233
- }
234
- throw error;
235
- }
236
- }
208
+ await writeContractSnapshot(migrationsDir, toStorageHash, {
209
+ contractJson: JSON.parse(contractJsonRaw) as unknown,
210
+ contractDts,
211
+ });
237
212
 
238
213
  const planner = migrations.createPlanner(controlAdapter);
239
214
  const emptyPlan = planner.emptyMigration(
240
215
  {
241
216
  packageDir,
242
- contractJsonPath: join(packageDir, 'end-contract.json'),
217
+ contractJsonPath: join(contractSnapshotDir(migrationsDir, toStorageHash), 'contract.json'),
243
218
  fromHash,
244
219
  toHash: toStorageHash,
220
+ snapshotsImportPath: snapshotsImportPathFrom(packageDir, migrationsDir),
245
221
  },
246
222
  APP_SPACE_ID,
247
223
  );
@@ -277,7 +253,7 @@ export function createMigrationNewCommand(): Command {
277
253
  );
278
254
  setCommandExamples(command, [
279
255
  'prisma-next migration new --name split-name',
280
- 'prisma-next migration new --name custom-fk --from sha256:abc...',
256
+ 'prisma-next migration new --name custom-fk --from abc123...',
281
257
  ]);
282
258
  addGlobalOptions(command)
283
259
  .option('--name <slug>', 'Migration name (used in directory name)')