@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
@@ -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
  }
@@ -209,39 +201,23 @@ async function executeMigrationNewCommand(
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
  );
@@ -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(
@@ -142,7 +122,7 @@ async function runPlannerLeg(
142
122
  );
143
123
  }
144
124
  } catch (e) {
145
- if (CliStructuredError.is(e) && e.domain === 'MIG' && e.code === '2001') {
125
+ if (CliStructuredError.is(e) && e.code === 'MIGRATION.UNFILLED_PLACEHOLDER') {
146
126
  hasPlaceholders = true;
147
127
  } else {
148
128
  throw e;
@@ -309,14 +289,18 @@ 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({
@@ -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,11 +371,11 @@ 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.
378
+ // app-space change) still re-pins extension artifacts on disk.
394
379
  const canonicalExtensionInputs = toExtensionInputs(config.extensionPacks ?? []);
395
380
  const seedResult = await runContractSpaceSeedPhase({
396
381
  migrationsDir,
@@ -463,24 +448,26 @@ async function executeMigrationPlanCommand(
463
448
  [config.target, config.adapter, ...(config.extensionPacks ?? [])],
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) {
@@ -1,16 +1,19 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import { loadConfig } from '@prisma-next/config-loader';
3
2
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
3
+ import {
4
+ contractSnapshotDir,
5
+ readContractSnapshotJson,
6
+ } from '@prisma-next/migration-tools/contract-snapshot-store';
4
7
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
5
8
  import { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';
6
9
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
7
10
  import type { RefEntry } from '@prisma-next/migration-tools/refs';
8
11
  import {
9
- deleteRefPaired,
12
+ deleteRef,
10
13
  readRefs,
11
14
  validateRefName,
12
15
  validateRefValue,
13
- writeRefPaired,
16
+ writeRef,
14
17
  } from '@prisma-next/migration-tools/refs';
15
18
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
16
19
  import { Command } from 'commander';
@@ -34,7 +37,6 @@ import {
34
37
  import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
35
38
  import { formatCommandHelp } from '../utils/formatters/help';
36
39
  import { parseGlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
37
- import { readContractIR } from '../utils/ref-advancement';
38
40
  import { handleResult } from '../utils/result-handler';
39
41
  import { createTerminalUI } from '../utils/terminal-ui';
40
42
 
@@ -114,26 +116,29 @@ export async function executeRefSetCommand(
114
116
  return notOk(errorRefSetBundleNotFound(resolvedHash));
115
117
  }
116
118
 
117
- const contractJsonPath = join(matchingBundle.dirPath, 'end-contract.json');
118
- let contractJson: Record<string, unknown>;
119
+ const contractJsonPath = join(
120
+ contractSnapshotDir(migrationsDir, resolvedHash),
121
+ 'contract.json',
122
+ );
119
123
  try {
120
- const raw = await readFile(contractJsonPath, 'utf-8');
121
- contractJson = JSON.parse(raw) as Record<string, unknown>;
124
+ await readContractSnapshotJson(migrationsDir, resolvedHash);
122
125
  } catch (readError) {
123
- if (readError instanceof Error && (readError as NodeJS.ErrnoException).code === 'ENOENT') {
126
+ if (
127
+ MigrationToolsError.is(readError) &&
128
+ readError.code === 'MIGRATION.CONTRACT_SNAPSHOT_MISSING'
129
+ ) {
124
130
  return notOk(
125
131
  errorFileNotFound(contractJsonPath, {
126
- why: `Migration bundle for hash ${resolvedHash} is missing its end-contract snapshot at ${contractJsonPath}`,
127
- fix: 'Run `pnpm fixtures:check`, or re-emit the migration so its end-contract.json is restored.',
132
+ why: `Migration bundle for hash ${resolvedHash} is missing its contract snapshot at ${contractJsonPath}`,
133
+ fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',
128
134
  }),
129
135
  );
130
136
  }
131
137
  throw readError;
132
138
  }
133
139
 
134
- const contractIR = await readContractIR(contractJson, contractJsonPath);
135
140
  const entry: RefEntry = { hash: resolvedHash, invariants: [] };
136
- await writeRefPaired(refsDir, name, entry, contractIR);
141
+ await writeRef(refsDir, name, entry);
137
142
  return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });
138
143
  } catch (error) {
139
144
  if (error instanceof CliStructuredError) return notOk(error);
@@ -148,7 +153,7 @@ export async function executeRefDeleteCommand(
148
153
  try {
149
154
  const config = await loadConfig(options.config);
150
155
  const { refsDir } = resolveMigrationPaths(options.config, config);
151
- await deleteRefPaired(refsDir, name);
156
+ await deleteRef(refsDir, name);
152
157
  return ok({ ok: true as const, ref: name, deleted: true as const });
153
158
  } catch (error) {
154
159
  if (error instanceof CliStructuredError) return notOk(error);
@@ -286,8 +286,9 @@ function aggregatePlannerWarnings(
286
286
  * an extension that was removed from `extensionPacks` without first
287
287
  * cleaning up its on-disk migrations / database tables.
288
288
  *
289
- * Returns a {@link CliStructuredError} envelope (code `5002`,
290
- * `kind: 'orphanMarker'`) for the first orphan it finds, or `null`
289
+ * Returns a {@link CliStructuredError} envelope (code
290
+ * `MIGRATION.CONTRACT_SPACE_VIOLATION`, `kind: 'orphanMarker'`) for the
291
+ * first orphan it finds, or `null`
291
292
  * when every marker row maps to a declared contract space. Mirrors the M2
292
293
  * `runContractSpaceVerifierMarkerCheck` envelope so downstream
293
294
  * tooling (integration tests, JSON consumers) keeps asserting on the
@@ -313,8 +314,7 @@ function detectOrphanMarkers(
313
314
  orphans.length === 1
314
315
  ? `Orphan contract-space marker detected for "${orphans[0]}"`
315
316
  : `Orphan contract-space markers detected for ${orphans.length} spaces`;
316
- return new CliStructuredError('5002', summary, {
317
- domain: 'MIG',
317
+ return new CliStructuredError('MIGRATION.CONTRACT_SPACE_VIOLATION', summary, {
318
318
  why: `The database has \`_prisma_marker\` rows for spaces (${orphans
319
319
  .map((s) => `"${s}"`)
320
320
  .join(
@@ -254,12 +254,15 @@ function finaliseVerifyResult(args: {
254
254
  if (!verifyResult.ok) {
255
255
  emitVerifySpan(onProgress, 'spanEndError');
256
256
  return notOk(
257
- new CliStructuredError('5002', 'Aggregate verifier introspection failed', {
258
- domain: 'MIG',
259
- why: verifyResult.failure.detail,
260
- fix: 'Check database connectivity and the introspection tooling.',
261
- docsUrl: 'https://pris.ly/contract-spaces',
262
- }),
257
+ new CliStructuredError(
258
+ 'MIGRATION.CONTRACT_SPACE_VIOLATION',
259
+ 'Aggregate verifier introspection failed',
260
+ {
261
+ why: verifyResult.failure.detail,
262
+ fix: 'Check database connectivity and the introspection tooling.',
263
+ docsUrl: 'https://pris.ly/contract-spaces',
264
+ },
265
+ ),
263
266
  );
264
267
  }
265
268
  const markerError = skipMarker
@@ -300,7 +303,8 @@ function buildSkippedSchemaResult(space: AggregateContractSpace): VerifyDatabase
300
303
  /**
301
304
  * Translate per-space marker check failures and orphan markers into a
302
305
  * single CLI structured error envelope. Preserves the legacy code
303
- * `5002` (was emitted by `runContractSpaceVerifierMarkerCheck`).
306
+ * `MIGRATION.CONTRACT_SPACE_VIOLATION` (was emitted by
307
+ * `runContractSpaceVerifierMarkerCheck`).
304
308
  */
305
309
  function mapMarkerCheckFailures(
306
310
  appSpaceId: string,
@@ -354,8 +358,7 @@ function mapMarkerCheckFailures(
354
358
  violations.length === 1
355
359
  ? 'Contract-space verifier found a violation'
356
360
  : `Contract-space verifier found violations (${violations.length})`;
357
- return new CliStructuredError('5002', summary, {
358
- domain: 'MIG',
361
+ return new CliStructuredError('MIGRATION.CONTRACT_SPACE_VIOLATION', summary, {
359
362
  why: `The on-disk \`migrations/\` directory, the \`extensionPacks\` declaration, and the live database marker rows are not in agreement.\n${lines.join('\n')}`,
360
363
  fix: violations[0]?.remediation ?? 'Review and reconcile the violations listed above.',
361
364
  docsUrl: 'https://pris.ly/contract-spaces',
@@ -1,8 +1,9 @@
1
1
  import { readFile, writeFile } from 'node:fs/promises';
2
2
  import { EOL } from 'node:os';
3
3
  import { loadConfig } from '@prisma-next/config-loader';
4
- import { type FormatOptions, format, PslFormatError } from '@prisma-next/psl-parser/format';
4
+ import { type FormatOptions, format } from '@prisma-next/psl-parser/format';
5
5
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
6
+ import { isStructuredError } from '@prisma-next/utils/structured-error';
6
7
  import { CliStructuredError, errorRuntime, errorUnexpected } from '../../utils/cli-errors';
7
8
 
8
9
  export interface FormatOperationOptions {
@@ -71,12 +72,12 @@ export async function executeFormat(
71
72
  try {
72
73
  formatted = format(contents, formatOptions);
73
74
  } catch (error) {
74
- if (error instanceof PslFormatError) {
75
+ if (isStructuredError(error) && error.code === 'PSL.PARSE_FAILED') {
75
76
  return notOk(
76
77
  errorRuntime('Cannot format PSL with parse errors', {
77
78
  why: error.message,
78
79
  fix: 'Fix the parse errors in your schema and try again.',
79
- meta: { diagnostics: error.diagnostics },
80
+ meta: { diagnostics: error.meta?.['diagnostics'] },
80
81
  }),
81
82
  );
82
83
  }