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

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 (162) hide show
  1. package/README.md +44 -45
  2. package/dist/cli.mjs +13 -13
  3. package/dist/{client-KuBQftxz.mjs → client-CMusjGV5.mjs} +24 -25
  4. package/dist/client-CMusjGV5.mjs.map +1 -0
  5. package/dist/{command-helpers-Cpq44aVa.mjs → command-helpers-CVn3U9Uz.mjs} +12 -4
  6. package/dist/command-helpers-CVn3U9Uz.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 +5 -4
  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 +9 -11
  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 +11 -13
  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-C-l4-JEY.mjs +36 -0
  51. package/dist/contract-at-errors-C-l4-JEY.mjs.map +1 -0
  52. package/dist/{contract-emit-cJQBlvGb.mjs → contract-emit-CfIbTnsR.mjs} +6 -7
  53. package/dist/{contract-emit-cJQBlvGb.mjs.map → contract-emit-CfIbTnsR.mjs.map} +1 -1
  54. package/dist/{contract-emit-CgnXSANN.mjs → contract-emit-PaY3qRdN.mjs} +3 -3
  55. package/dist/{contract-emit-CgnXSANN.mjs.map → contract-emit-PaY3qRdN.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-DCju2fTk.mjs} +3 -3
  59. package/dist/{contract-infer-Cg8evbeg.mjs.map → contract-infer-DCju2fTk.mjs.map} +1 -1
  60. package/dist/{contract-space-aggregate-loader-D_3VeHVb.mjs → contract-space-aggregate-loader-B6E9LGa6.mjs} +14 -14
  61. package/dist/contract-space-aggregate-loader-B6E9LGa6.mjs.map +1 -0
  62. package/dist/{db-verify-BEclUzki.mjs → db-verify-B_dKu6Yp.mjs} +5 -5
  63. package/dist/db-verify-B_dKu6Yp.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.mjs +2 -2
  68. package/dist/exports/init-output.d.mts.map +1 -1
  69. package/dist/exports/init-output.mjs +1 -1
  70. package/dist/{format-DAUAZqPt.mjs → format-cCyDfmRE.mjs} +8 -7
  71. package/dist/format-cCyDfmRE.mjs.map +1 -0
  72. package/dist/{framework-components-CnbUbiJw.mjs → framework-components-DUN-TYDg.mjs} +3 -3
  73. package/dist/framework-components-DUN-TYDg.mjs.map +1 -0
  74. package/dist/{init-C3JhkHg-.mjs → init-C9U3ffHH.mjs} +55 -47
  75. package/dist/init-C9U3ffHH.mjs.map +1 -0
  76. package/dist/{inspect-live-schema-CX7EA5C3.mjs → inspect-live-schema-DHXE7skX.mjs} +4 -4
  77. package/dist/{inspect-live-schema-CX7EA5C3.mjs.map → inspect-live-schema-DHXE7skX.mjs.map} +1 -1
  78. package/dist/{migration-check-MHT3w7_v.mjs → migration-check-W5XEx4d-.mjs} +58 -30
  79. package/dist/migration-check-W5XEx4d-.mjs.map +1 -0
  80. package/dist/{migration-command-scaffold-CRyYrHZ9.mjs → migration-command-scaffold-hI5cnoGe.mjs} +4 -4
  81. package/dist/{migration-command-scaffold-CRyYrHZ9.mjs.map → migration-command-scaffold-hI5cnoGe.mjs.map} +1 -1
  82. package/dist/{migration-graph-command-render-B83xrnNy.mjs → migration-graph-command-render-DgQg5App.mjs} +3 -3
  83. package/dist/migration-graph-command-render-DgQg5App.mjs.map +1 -0
  84. package/dist/{migration-list-BAKujUjh.mjs → migration-list-ClkEaoL8.mjs} +6 -6
  85. package/dist/migration-list-ClkEaoL8.mjs.map +1 -0
  86. package/dist/{migration-log-C_xam2HZ.mjs → migration-log-CjafbZ2Z.mjs} +5 -5
  87. package/dist/{migration-log-C_xam2HZ.mjs.map → migration-log-CjafbZ2Z.mjs.map} +1 -1
  88. package/dist/{migration-path-target-DpPTsRCg.mjs → migration-path-target-C0TQuV5n.mjs} +2 -2
  89. package/dist/{migration-path-target-DpPTsRCg.mjs.map → migration-path-target-C0TQuV5n.mjs.map} +1 -1
  90. package/dist/{migration-plan-CreSVtWZ.mjs → migration-plan-Pc4hCF1H.mjs} +64 -74
  91. package/dist/migration-plan-Pc4hCF1H.mjs.map +1 -0
  92. package/dist/{migration-status-YgEvXf-u.mjs → migration-status-CqAdRzs8.mjs} +13 -12
  93. package/dist/migration-status-CqAdRzs8.mjs.map +1 -0
  94. package/dist/{output-mEQ74_nd.mjs → output-DWNYqZGc.mjs} +5 -6
  95. package/dist/output-DWNYqZGc.mjs.map +1 -0
  96. package/dist/{ref-advancement-BkXlikCA.mjs → ref-advancement-BCz7X7qJ.mjs} +13 -6
  97. package/dist/ref-advancement-BCz7X7qJ.mjs.map +1 -0
  98. package/dist/{telemetry-DVr6RFnq.mjs → telemetry-BEyNMfco.mjs} +2 -2
  99. package/dist/{telemetry-DVr6RFnq.mjs.map → telemetry-BEyNMfco.mjs.map} +1 -1
  100. package/dist/{types-D4r_HI9-.d.mts → types-DgXprE3w.d.mts} +3 -3
  101. package/dist/types-DgXprE3w.d.mts.map +1 -0
  102. package/dist/{verify-CgDVZjhc.mjs → verify-C4RazDE1.mjs} +2 -2
  103. package/dist/{verify-CgDVZjhc.mjs.map → verify-C4RazDE1.mjs.map} +1 -1
  104. package/package.json +21 -21
  105. package/src/commands/db-init.ts +1 -0
  106. package/src/commands/db-sign.ts +7 -5
  107. package/src/commands/db-update.ts +14 -6
  108. package/src/commands/db-verify.ts +1 -1
  109. package/src/commands/format.ts +1 -1
  110. package/src/commands/init/hygiene-gitattributes.ts +37 -21
  111. package/src/commands/init/init.ts +6 -6
  112. package/src/commands/init/output.ts +4 -5
  113. package/src/commands/init/reinit-cleanup.ts +16 -17
  114. package/src/commands/inspect-live-schema.ts +1 -1
  115. package/src/commands/migrate.ts +6 -5
  116. package/src/commands/migration-check.ts +72 -34
  117. package/src/commands/migration-list.ts +1 -5
  118. package/src/commands/migration-log.ts +1 -1
  119. package/src/commands/migration-new.ts +17 -41
  120. package/src/commands/migration-plan.ts +64 -88
  121. package/src/commands/migration-show.ts +2 -2
  122. package/src/commands/migration-status.ts +6 -7
  123. package/src/commands/ref.ts +19 -14
  124. package/src/control-api/client.ts +7 -8
  125. package/src/control-api/contract-enrichment.ts +7 -7
  126. package/src/control-api/operations/contract-emit.ts +2 -3
  127. package/src/control-api/operations/db-init.ts +3 -3
  128. package/src/control-api/operations/db-run.ts +6 -6
  129. package/src/control-api/operations/db-update.ts +2 -2
  130. package/src/control-api/operations/db-verify.ts +4 -4
  131. package/src/control-api/operations/format.ts +5 -4
  132. package/src/control-api/operations/migrate.ts +3 -3
  133. package/src/control-api/types.ts +1 -1
  134. package/src/utils/cli-errors.ts +15 -7
  135. package/src/utils/contract-at-errors.ts +13 -22
  136. package/src/utils/contract-space-aggregate-loader.ts +8 -8
  137. package/src/utils/contract-space-seed-phase.ts +11 -9
  138. package/src/utils/extension-pack-inputs.ts +8 -8
  139. package/src/utils/formatters/migration-graph-labels.ts +1 -2
  140. package/src/utils/formatters/migration-list-data-column.ts +1 -2
  141. package/src/utils/framework-components.ts +9 -9
  142. package/src/utils/integrity-violation-to-check-failure.ts +3 -3
  143. package/src/utils/migration-command-scaffold.ts +1 -1
  144. package/src/utils/plan-resolution.ts +7 -10
  145. package/src/utils/ref-advancement.ts +22 -3
  146. package/dist/client-KuBQftxz.mjs.map +0 -1
  147. package/dist/command-helpers-Cpq44aVa.mjs.map +0 -1
  148. package/dist/contract-at-errors-D9gEjYtg.mjs +0 -42
  149. package/dist/contract-at-errors-D9gEjYtg.mjs.map +0 -1
  150. package/dist/contract-space-aggregate-loader-D_3VeHVb.mjs.map +0 -1
  151. package/dist/db-verify-BEclUzki.mjs.map +0 -1
  152. package/dist/format-DAUAZqPt.mjs.map +0 -1
  153. package/dist/framework-components-CnbUbiJw.mjs.map +0 -1
  154. package/dist/init-C3JhkHg-.mjs.map +0 -1
  155. package/dist/migration-check-MHT3w7_v.mjs.map +0 -1
  156. package/dist/migration-graph-command-render-B83xrnNy.mjs.map +0 -1
  157. package/dist/migration-list-BAKujUjh.mjs.map +0 -1
  158. package/dist/migration-plan-CreSVtWZ.mjs.map +0 -1
  159. package/dist/migration-status-YgEvXf-u.mjs.map +0 -1
  160. package/dist/output-mEQ74_nd.mjs.map +0 -1
  161. package/dist/ref-advancement-BkXlikCA.mjs.map +0 -1
  162. package/dist/types-D4r_HI9-.d.mts.map +0 -1
@@ -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)')
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
1
+ import { readFile } from 'node:fs/promises';
2
2
  import { loadConfig } from '@prisma-next/config-loader';
3
3
  import type { Contract } from '@prisma-next/contract/types';
4
4
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
@@ -9,15 +9,14 @@ import {
9
9
  type OperationPreview,
10
10
  type SchemaOwnership,
11
11
  } from '@prisma-next/framework-components/control';
12
- import { canonicalizeJson } from '@prisma-next/framework-components/utils';
12
+ import {
13
+ snapshotsImportPathFrom,
14
+ writeContractSnapshot,
15
+ } from '@prisma-next/migration-tools/contract-snapshot-store';
13
16
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
14
17
  import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
15
18
  import { deriveProvidedInvariants } from '@prisma-next/migration-tools/invariants';
16
- import {
17
- copyFilesWithRename,
18
- formatMigrationDirName,
19
- writeMigrationPackage,
20
- } from '@prisma-next/migration-tools/io';
19
+ import { formatMigrationDirName, writeMigrationPackage } from '@prisma-next/migration-tools/io';
21
20
  import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
22
21
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
23
22
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
@@ -62,27 +61,6 @@ interface MigrationPlanOptions extends CommonCommandOptions {
62
61
  readonly to?: string;
63
62
  }
64
63
 
65
- async function writeSnapshotContractArtifacts(
66
- packageDir: string,
67
- contractJson: unknown,
68
- contractDts: string,
69
- artifactBasename: 'start-contract' | 'end-contract',
70
- ): Promise<void> {
71
- await mkdir(packageDir, { recursive: true });
72
- const jsonContent = `${canonicalizeJson(contractJson)}\n`;
73
- const dtsContent = contractDts.endsWith('\n') ? contractDts : `${contractDts}\n`;
74
- await writeFile(join(packageDir, `${artifactBasename}.json`), jsonContent);
75
- await writeFile(join(packageDir, `${artifactBasename}.d.ts`), dtsContent);
76
- }
77
-
78
- async function writeSnapshotStartContract(
79
- packageDir: string,
80
- contractJson: unknown,
81
- contractDts: string,
82
- ): Promise<void> {
83
- await writeSnapshotContractArtifacts(packageDir, contractJson, contractDts, 'start-contract');
84
- }
85
-
86
64
  type PlannerSuccess = {
87
65
  readonly plannedOps: readonly MigrationPlanOperation[];
88
66
  readonly migrationTsContent: string;
@@ -99,6 +77,7 @@ async function runPlannerLeg(
99
77
  fromContract: Contract | null,
100
78
  spaceId: string,
101
79
  ownership: SchemaOwnership,
80
+ snapshotsImportPath: string,
102
81
  ): Promise<Result<PlannerSuccess, CliStructuredError>> {
103
82
  const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
104
83
  const plannerResult = planner.plan({
@@ -114,6 +93,7 @@ async function runPlannerLeg(
114
93
  // space in the aggregate, so it stays a genuine drop; a table another
115
94
  // space owns is never dropped.
116
95
  ownership,
96
+ snapshotsImportPath,
117
97
  });
118
98
  if (plannerResult.kind === 'failure') {
119
99
  return notOk(
@@ -309,21 +289,25 @@ async function executeMigrationPlanCommand(
309
289
  let toStorageHash: string = rawStorageHash;
310
290
 
311
291
  // When `--to <ref>` resolves a non-default destination, these carry its raw
312
- // artifacts so the planned package's `end-contract.*` is written from the
313
- // resolved target rather than copied from the emitted `contract.json`.
292
+ // artifacts so the planned package's destination snapshot store entry is
293
+ // written from the resolved target rather than copied from the emitted
294
+ // `contract.json`.
314
295
  let toArtifacts: { contractJson: unknown; contractDts: string } | null = null;
315
296
 
316
297
  let fromContract: Contract | null = null;
317
298
  let fromHash: string | null = null;
318
- let fromContractSourceDir: string | null = null;
319
- let snapshotStartContract: { contractJson: unknown; contractDts: string } | null = null;
299
+ let snapshotStartContract: {
300
+ readonly fromHash: string;
301
+ readonly contractJson: unknown;
302
+ readonly contractDts: string;
303
+ } | null = null;
320
304
  let isAutoBaseline = false;
321
305
 
322
306
  const tolerantAggregateResult = await loadContractSpaceAggregateForCli({
323
307
  targetId: config.target.targetId,
324
308
  migrationsDir,
325
309
  appContract: toContract,
326
- extensionPacks: config.extensionPacks ?? [],
310
+ extensions: config.extensions ?? [],
327
311
  deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
328
312
  });
329
313
  if (!tolerantAggregateResult.ok) {
@@ -346,12 +330,12 @@ async function executeMigrationPlanCommand(
346
330
  case 'graph-node':
347
331
  fromHash = resolutionResult.value.fromHash;
348
332
  fromContract = resolutionResult.value.fromContract;
349
- fromContractSourceDir = resolutionResult.value.sourceDir;
350
333
  break;
351
- case 'snapshot':
334
+ case 'ref':
352
335
  fromHash = resolutionResult.value.fromHash;
353
336
  fromContract = resolutionResult.value.fromContract;
354
337
  snapshotStartContract = {
338
+ fromHash: resolutionResult.value.fromHash,
355
339
  contractJson: resolutionResult.value.contractJson,
356
340
  contractDts: resolutionResult.value.contractDts,
357
341
  };
@@ -360,6 +344,7 @@ async function executeMigrationPlanCommand(
360
344
  fromHash = resolutionResult.value.fromHash;
361
345
  fromContract = resolutionResult.value.fromContract;
362
346
  snapshotStartContract = {
347
+ fromHash: resolutionResult.value.fromHash,
363
348
  contractJson: resolutionResult.value.contractJson,
364
349
  contractDts: resolutionResult.value.contractDts,
365
350
  };
@@ -369,7 +354,7 @@ async function executeMigrationPlanCommand(
369
354
 
370
355
  // `--to <ref>` swaps the planner destination to an arbitrary resolved
371
356
  // contract (e.g. an ancestor / rollback target). The from-side resolution
372
- // above is untouched; only the destination + its emitted `end-contract.*`
357
+ // above is untouched; only the destination + its snapshot store entry
373
358
  // change.
374
359
  if (options.to !== undefined) {
375
360
  const toResolution = await resolveToForPlan(options.to, {
@@ -386,15 +371,15 @@ async function executeMigrationPlanCommand(
386
371
  };
387
372
  }
388
373
 
389
- // Phase 1 — seed: unconditionally re-emit per-space pinned artefacts
374
+ // Phase 1 — seed: unconditionally re-emit per-space pinned artifacts
390
375
  // (contract.json / contract.d.ts / refs/head.json) and materialise any
391
376
  // descriptor-shipped migration packages not yet on disk. Runs before
392
377
  // the no-op check so that an extension bump alone (with no structural
393
- // app-space change) still re-pins extension artefacts on disk.
394
- const canonicalExtensionInputs = toExtensionInputs(config.extensionPacks ?? []);
378
+ // app-space change) still re-pins extension artifacts on disk.
379
+ const canonicalExtensionInputs = toExtensionInputs(config.extensions ?? []);
395
380
  const seedResult = await runContractSpaceSeedPhase({
396
381
  migrationsDir,
397
- extensionPacks: canonicalExtensionInputs,
382
+ extensions: canonicalExtensionInputs,
398
383
  });
399
384
  if (!flags.json && !flags.quiet) {
400
385
  for (const record of seedResult.seeded) {
@@ -449,7 +434,7 @@ async function executeMigrationPlanCommand(
449
434
  targetId: config.target.targetId,
450
435
  migrationsDir,
451
436
  appContract: toContract,
452
- extensionPacks: config.extensionPacks ?? [],
437
+ extensions: config.extensions ?? [],
453
438
  deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
454
439
  });
455
440
  if (!aggregateResult.ok) {
@@ -460,27 +445,29 @@ async function executeMigrationPlanCommand(
460
445
  const frameworkComponents = assertFrameworkComponentsCompatible(
461
446
  config.family.familyId,
462
447
  config.target.targetId,
463
- [config.target, config.adapter, ...(config.extensionPacks ?? [])],
448
+ [config.target, config.adapter, ...(config.extensions ?? [])],
464
449
  );
465
450
 
466
- // Write the planned package's destination `end-contract.*`. With `--to`, the
467
- // resolved target's raw artifacts are written; otherwise the emitted
468
- // `contract.json` / `contract.d.ts` are copied verbatim (today's behaviour).
469
- async function writeDestinationEndContract(packageDir: string): Promise<void> {
451
+ // Write the planned package's destination contract into the snapshot store.
452
+ // With `--to`, the resolved target's raw artifacts are written; otherwise
453
+ // the emitted `contract.json` / `contract.d.ts` are read from disk.
454
+ async function writeDestinationSnapshot(destHash: string): Promise<void> {
470
455
  if (toArtifacts !== null) {
471
- await writeSnapshotContractArtifacts(
472
- packageDir,
473
- toArtifacts.contractJson,
474
- toArtifacts.contractDts,
475
- 'end-contract',
476
- );
456
+ await writeContractSnapshot(migrationsDir, destHash, {
457
+ contractJson: toArtifacts.contractJson,
458
+ contractDts: toArtifacts.contractDts,
459
+ });
477
460
  return;
478
461
  }
479
462
  const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
480
- await copyFilesWithRename(packageDir, [
481
- { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
482
- { sourcePath: destinationArtifacts.dtsPath, destName: 'end-contract.d.ts' },
463
+ const [contractJsonRaw, contractDts] = await Promise.all([
464
+ readFile(destinationArtifacts.jsonPath, 'utf-8'),
465
+ readFile(destinationArtifacts.dtsPath, 'utf-8'),
483
466
  ]);
467
+ await writeContractSnapshot(migrationsDir, destHash, {
468
+ contractJson: JSON.parse(contractJsonRaw) as unknown,
469
+ contractDts,
470
+ });
484
471
  }
485
472
 
486
473
  try {
@@ -507,6 +494,7 @@ async function executeMigrationPlanCommand(
507
494
  null,
508
495
  aggregate.app.spaceId,
509
496
  aggregate,
497
+ snapshotsImportPathFrom(baselinePackageDir, migrationsDir),
510
498
  );
511
499
  if (!baselineLeg.ok) {
512
500
  return notOk(baselineLeg.failure);
@@ -519,12 +507,10 @@ async function executeMigrationPlanCommand(
519
507
  baselineTimestamp,
520
508
  baselineLeg.value,
521
509
  );
522
- await writeSnapshotContractArtifacts(
523
- baselinePackageDir,
524
- snapshotStartContract.contractJson,
525
- snapshotStartContract.contractDts,
526
- 'end-contract',
527
- );
510
+ await writeContractSnapshot(migrationsDir, fromHash, {
511
+ contractJson: snapshotStartContract.contractJson,
512
+ contractDts: snapshotStartContract.contractDts,
513
+ });
528
514
 
529
515
  if (fromHash === toStorageHash) {
530
516
  const baselineOps = baselineLeg.value.hasPlaceholders ? [] : baselineLeg.value.plannedOps;
@@ -577,6 +563,7 @@ async function executeMigrationPlanCommand(
577
563
  fromContract,
578
564
  aggregate.app.spaceId,
579
565
  aggregate,
566
+ snapshotsImportPathFrom(deltaPackageDir, migrationsDir),
580
567
  );
581
568
  if (!deltaLeg.ok) {
582
569
  return notOk(deltaLeg.failure);
@@ -589,12 +576,11 @@ async function executeMigrationPlanCommand(
589
576
  deltaTimestamp,
590
577
  deltaLeg.value,
591
578
  );
592
- await writeDestinationEndContract(deltaPackageDir);
593
- await writeSnapshotStartContract(
594
- deltaPackageDir,
595
- snapshotStartContract.contractJson,
596
- snapshotStartContract.contractDts,
597
- );
579
+ await writeDestinationSnapshot(toStorageHash);
580
+ await writeContractSnapshot(migrationsDir, fromHash, {
581
+ contractJson: snapshotStartContract.contractJson,
582
+ contractDts: snapshotStartContract.contractDts,
583
+ });
598
584
 
599
585
  const deltaOps = deltaLeg.value.hasPlaceholders ? [] : deltaLeg.value.plannedOps;
600
586
  if (deltaLeg.value.hasPlaceholders) {
@@ -651,6 +637,7 @@ async function executeMigrationPlanCommand(
651
637
  fromContract,
652
638
  aggregate.app.spaceId,
653
639
  aggregate,
640
+ snapshotsImportPathFrom(packageDir, migrationsDir),
654
641
  );
655
642
  if (!deltaLeg.ok) {
656
643
  return notOk(deltaLeg.failure);
@@ -663,21 +650,12 @@ async function executeMigrationPlanCommand(
663
650
  timestamp,
664
651
  deltaLeg.value,
665
652
  );
666
- await writeDestinationEndContract(packageDir);
667
- if (fromContractSourceDir !== null) {
668
- const sourceArtifacts = getEmittedArtifactPaths(
669
- join(fromContractSourceDir, 'end-contract.json'),
670
- );
671
- await copyFilesWithRename(packageDir, [
672
- { sourcePath: sourceArtifacts.jsonPath, destName: 'start-contract.json' },
673
- { sourcePath: sourceArtifacts.dtsPath, destName: 'start-contract.d.ts' },
674
- ]);
675
- } else if (snapshotStartContract !== null) {
676
- await writeSnapshotStartContract(
677
- packageDir,
678
- snapshotStartContract.contractJson,
679
- snapshotStartContract.contractDts,
680
- );
653
+ await writeDestinationSnapshot(toStorageHash);
654
+ if (snapshotStartContract !== null) {
655
+ await writeContractSnapshot(migrationsDir, snapshotStartContract.fromHash, {
656
+ contractJson: snapshotStartContract.contractJson,
657
+ contractDts: snapshotStartContract.contractDts,
658
+ });
681
659
  }
682
660
 
683
661
  if (deltaLeg.value.hasPlaceholders) {
@@ -953,10 +931,9 @@ export type PrefixResolutionFailure =
953
931
  *
954
932
  * Note: matches `metadata.to` (the contract hash this migration produces),
955
933
  * not `metadata.migrationHash` (the package's content-addressed identity).
956
- * Tries exact match first, then prefix match (auto-prepending `sha256:` when
957
- * the needle omits the scheme). Returns the matched package on success, or a
958
- * discriminated failure indicating whether the prefix was ambiguous or simply
959
- * not found.
934
+ * Tries exact match first, then prefix match. Returns the matched package on
935
+ * success, or a discriminated failure indicating whether the prefix was
936
+ * ambiguous or simply not found.
960
937
  *
961
938
  * @internal Exported for testing only.
962
939
  */
@@ -967,8 +944,7 @@ export function resolveBundleByPrefix<T extends { metadata: { to: string } }>(
967
944
  const exact = bundles.find((p) => p.metadata.to === needle);
968
945
  if (exact) return ok(exact);
969
946
 
970
- const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;
971
- const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
947
+ const candidates = bundles.filter((p) => p.metadata.to.startsWith(needle));
972
948
 
973
949
  if (candidates.length === 1) return ok(candidates[0]!);
974
950
  if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });