@prisma-next/cli 0.16.0-dev.2 → 0.16.0-dev.20

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 (129) hide show
  1. package/README.md +31 -32
  2. package/dist/cli.mjs +13 -13
  3. package/dist/{client-KuBQftxz.mjs → client-DeJvLD2h.mjs} +4 -5
  4. package/dist/{client-KuBQftxz.mjs.map → client-DeJvLD2h.mjs.map} +1 -1
  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 +8 -10
  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.map +1 -1
  21. package/dist/commands/migrate.mjs +9 -9
  22. package/dist/commands/migrate.mjs.map +1 -1
  23. package/dist/commands/migration-check.d.mts +3 -1
  24. package/dist/commands/migration-check.d.mts.map +1 -1
  25. package/dist/commands/migration-check.mjs +1 -1
  26. package/dist/commands/migration-graph.mjs +4 -4
  27. package/dist/commands/migration-list.d.mts.map +1 -1
  28. package/dist/commands/migration-list.mjs +1 -1
  29. package/dist/commands/migration-log.mjs +1 -1
  30. package/dist/commands/migration-new.d.mts.map +1 -1
  31. package/dist/commands/migration-new.mjs +15 -39
  32. package/dist/commands/migration-new.mjs.map +1 -1
  33. package/dist/commands/migration-plan.d.mts +3 -4
  34. package/dist/commands/migration-plan.d.mts.map +1 -1
  35. package/dist/commands/migration-plan.mjs +1 -1
  36. package/dist/commands/migration-show.mjs +4 -4
  37. package/dist/commands/migration-show.mjs.map +1 -1
  38. package/dist/commands/migration-status.d.mts.map +1 -1
  39. package/dist/commands/migration-status.mjs +1 -1
  40. package/dist/commands/ref.d.mts.map +1 -1
  41. package/dist/commands/ref.mjs +13 -18
  42. package/dist/commands/ref.mjs.map +1 -1
  43. package/dist/commands/telemetry/index.mjs +1 -1
  44. package/dist/contract-at-errors-C-l4-JEY.mjs +36 -0
  45. package/dist/contract-at-errors-C-l4-JEY.mjs.map +1 -0
  46. package/dist/{contract-emit-CgnXSANN.mjs → contract-emit-BMJiefhy.mjs} +3 -3
  47. package/dist/{contract-emit-CgnXSANN.mjs.map → contract-emit-BMJiefhy.mjs.map} +1 -1
  48. package/dist/{contract-emit-cJQBlvGb.mjs → contract-emit-DJ8SRI7L.mjs} +3 -4
  49. package/dist/{contract-emit-cJQBlvGb.mjs.map → contract-emit-DJ8SRI7L.mjs.map} +1 -1
  50. package/dist/{contract-infer-Cg8evbeg.mjs → contract-infer-_tstfy3k.mjs} +3 -3
  51. package/dist/{contract-infer-Cg8evbeg.mjs.map → contract-infer-_tstfy3k.mjs.map} +1 -1
  52. package/dist/{contract-space-aggregate-loader-D_3VeHVb.mjs → contract-space-aggregate-loader-hPVymNLw.mjs} +2 -2
  53. package/dist/{contract-space-aggregate-loader-D_3VeHVb.mjs.map → contract-space-aggregate-loader-hPVymNLw.mjs.map} +1 -1
  54. package/dist/{db-verify-BEclUzki.mjs → db-verify-3WM8y323.mjs} +4 -4
  55. package/dist/{db-verify-BEclUzki.mjs.map → db-verify-3WM8y323.mjs.map} +1 -1
  56. package/dist/exports/control-api.mjs +2 -2
  57. package/dist/exports/index.mjs +2 -2
  58. package/dist/exports/init-output.d.mts.map +1 -1
  59. package/dist/exports/init-output.mjs +1 -1
  60. package/dist/{format-DAUAZqPt.mjs → format-CFLZv_zU.mjs} +6 -5
  61. package/dist/format-CFLZv_zU.mjs.map +1 -0
  62. package/dist/{framework-components-CnbUbiJw.mjs → framework-components-VMQJbAwl.mjs} +2 -2
  63. package/dist/{framework-components-CnbUbiJw.mjs.map → framework-components-VMQJbAwl.mjs.map} +1 -1
  64. package/dist/{init-C3JhkHg-.mjs → init-XycYREvA.mjs} +55 -47
  65. package/dist/init-XycYREvA.mjs.map +1 -0
  66. package/dist/{inspect-live-schema-CX7EA5C3.mjs → inspect-live-schema-xS6VqjHE.mjs} +3 -3
  67. package/dist/{inspect-live-schema-CX7EA5C3.mjs.map → inspect-live-schema-xS6VqjHE.mjs.map} +1 -1
  68. package/dist/{migration-check-VqN-iTcx.mjs → migration-check-C_4gozCn.mjs} +53 -26
  69. package/dist/migration-check-C_4gozCn.mjs.map +1 -0
  70. package/dist/{migration-command-scaffold-CRyYrHZ9.mjs → migration-command-scaffold-D9JkLq_M.mjs} +3 -3
  71. package/dist/{migration-command-scaffold-CRyYrHZ9.mjs.map → migration-command-scaffold-D9JkLq_M.mjs.map} +1 -1
  72. package/dist/{migration-graph-command-render-B83xrnNy.mjs → migration-graph-command-render-DgQg5App.mjs} +3 -3
  73. package/dist/migration-graph-command-render-DgQg5App.mjs.map +1 -0
  74. package/dist/{migration-list-BAKujUjh.mjs → migration-list-BwEwRv25.mjs} +6 -6
  75. package/dist/migration-list-BwEwRv25.mjs.map +1 -0
  76. package/dist/{migration-log-C_xam2HZ.mjs → migration-log-C3RMIsUI.mjs} +4 -4
  77. package/dist/{migration-log-C_xam2HZ.mjs.map → migration-log-C3RMIsUI.mjs.map} +1 -1
  78. package/dist/{migration-path-target-DpPTsRCg.mjs → migration-path-target-C0TQuV5n.mjs} +2 -2
  79. package/dist/{migration-path-target-DpPTsRCg.mjs.map → migration-path-target-C0TQuV5n.mjs.map} +1 -1
  80. package/dist/{migration-plan-CreSVtWZ.mjs → migration-plan-BzxmK_do.mjs} +58 -68
  81. package/dist/migration-plan-BzxmK_do.mjs.map +1 -0
  82. package/dist/{migration-status-DYErXpyF.mjs → migration-status-ChQKA6Jf.mjs} +11 -11
  83. package/dist/migration-status-ChQKA6Jf.mjs.map +1 -0
  84. package/dist/{output-mEQ74_nd.mjs → output-DWNYqZGc.mjs} +5 -6
  85. package/dist/output-DWNYqZGc.mjs.map +1 -0
  86. package/dist/{ref-advancement-BkXlikCA.mjs → ref-advancement-BCz7X7qJ.mjs} +13 -6
  87. package/dist/ref-advancement-BCz7X7qJ.mjs.map +1 -0
  88. package/dist/{telemetry-DVr6RFnq.mjs → telemetry-BEyNMfco.mjs} +2 -2
  89. package/dist/{telemetry-DVr6RFnq.mjs.map → telemetry-BEyNMfco.mjs.map} +1 -1
  90. package/dist/{verify-CgDVZjhc.mjs → verify-C4RazDE1.mjs} +2 -2
  91. package/dist/{verify-CgDVZjhc.mjs.map → verify-C4RazDE1.mjs.map} +1 -1
  92. package/package.json +21 -21
  93. package/src/commands/db-init.ts +1 -0
  94. package/src/commands/db-sign.ts +6 -4
  95. package/src/commands/db-update.ts +14 -6
  96. package/src/commands/init/hygiene-gitattributes.ts +37 -21
  97. package/src/commands/init/init.ts +6 -6
  98. package/src/commands/init/output.ts +4 -5
  99. package/src/commands/init/reinit-cleanup.ts +16 -17
  100. package/src/commands/migrate.ts +2 -1
  101. package/src/commands/migration-check.ts +71 -33
  102. package/src/commands/migration-list.ts +1 -5
  103. package/src/commands/migration-new.ts +16 -40
  104. package/src/commands/migration-plan.ts +59 -83
  105. package/src/commands/migration-show.ts +1 -1
  106. package/src/commands/migration-status.ts +5 -6
  107. package/src/commands/ref.ts +19 -14
  108. package/src/control-api/client.ts +0 -1
  109. package/src/control-api/operations/contract-emit.ts +0 -1
  110. package/src/control-api/operations/format.ts +4 -3
  111. package/src/utils/cli-errors.ts +13 -5
  112. package/src/utils/contract-at-errors.ts +13 -22
  113. package/src/utils/contract-space-seed-phase.ts +8 -6
  114. package/src/utils/formatters/migration-graph-labels.ts +1 -2
  115. package/src/utils/formatters/migration-list-data-column.ts +1 -2
  116. package/src/utils/plan-resolution.ts +7 -10
  117. package/src/utils/ref-advancement.ts +22 -3
  118. package/dist/command-helpers-Cpq44aVa.mjs.map +0 -1
  119. package/dist/contract-at-errors-D9gEjYtg.mjs +0 -42
  120. package/dist/contract-at-errors-D9gEjYtg.mjs.map +0 -1
  121. package/dist/format-DAUAZqPt.mjs.map +0 -1
  122. package/dist/init-C3JhkHg-.mjs.map +0 -1
  123. package/dist/migration-check-VqN-iTcx.mjs.map +0 -1
  124. package/dist/migration-graph-command-render-B83xrnNy.mjs.map +0 -1
  125. package/dist/migration-list-BAKujUjh.mjs.map +0 -1
  126. package/dist/migration-plan-CreSVtWZ.mjs.map +0 -1
  127. package/dist/migration-status-DYErXpyF.mjs.map +0 -1
  128. package/dist/output-mEQ74_nd.mjs.map +0 -1
  129. package/dist/ref-advancement-BkXlikCA.mjs.map +0 -1
@@ -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,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) {
@@ -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 });
@@ -228,7 +228,7 @@ export function createMigrationShowCommand(): Command {
228
228
  );
229
229
  setCommandExamples(command, [
230
230
  'prisma-next migration show 20260101_100000_add_user',
231
- 'prisma-next migration show sha256:a1b2c3',
231
+ 'prisma-next migration show a1b2c3',
232
232
  'prisma-next migration show 20260101_100000_add_user --json',
233
233
  ]);
234
234
  setCommandSeeAlso(command, [
@@ -111,8 +111,7 @@ interface MigrationStatusCommandResult {
111
111
  }
112
112
 
113
113
  function shortDisplayHash(hash: string): string {
114
- const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;
115
- return stripped.slice(0, 12);
114
+ return hash.slice(0, 12);
116
115
  }
117
116
 
118
117
  function resolveTarget(contractHash: string, activeRefHash: string | undefined): string {
@@ -660,10 +659,10 @@ export function createMigrationStatusCommand(): Command {
660
659
  setCommandExamples(command, [
661
660
  'prisma-next migration status --db $DATABASE_URL',
662
661
  'prisma-next migration status --to production --db $DATABASE_URL',
663
- 'prisma-next migration status --from sha256:abc --to production',
664
- 'prisma-next migration status --from sha256:abc --to production --json',
665
- 'prisma-next migration status --ascii --from sha256:abc --to production',
666
- 'prisma-next migration status --legend --from sha256:abc --to production',
662
+ 'prisma-next migration status --from abc123 --to production',
663
+ 'prisma-next migration status --from abc123 --to production --json',
664
+ 'prisma-next migration status --ascii --from abc123 --to production',
665
+ 'prisma-next migration status --legend --from abc123 --to production',
667
666
  ]);
668
667
  setCommandSeeAlso(command, [
669
668
  { verb: 'migration log', oneLiner: 'Show executed migration history' },
@@ -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);
@@ -609,7 +609,6 @@ class ControlClientImpl implements ControlClient {
609
609
  const sourceContext = {
610
610
  composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
611
611
  composedExtensionContracts: stack.extensionContracts,
612
- scalarTypeDescriptors: stack.scalarTypeDescriptors,
613
612
  authoringContributions: stack.authoringContributions,
614
613
  codecLookup: stack.codecLookup,
615
614
  controlMutationDefaults: stack.controlMutationDefaults,
@@ -197,7 +197,6 @@ export async function executeContractEmit(
197
197
  const sourceContext = {
198
198
  composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
199
199
  composedExtensionContracts: stack.extensionContracts,
200
- scalarTypeDescriptors: stack.scalarTypeDescriptors,
201
200
  authoringContributions: stack.authoringContributions,
202
201
  codecLookup: stack.codecLookup,
203
202
  controlMutationDefaults: stack.controlMutationDefaults,
@@ -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
  }
@@ -215,22 +215,30 @@ export function errorPlanForgotTheFlag(
215
215
  });
216
216
  }
217
217
 
218
+ /**
219
+ * `viaRef: true` (the default) mirrors migration-tools' `errorRefNotResolvable`:
220
+ * a ref name with no pointer file, where the fallback hash isn't a graph
221
+ * node either — there's nothing to materialize a contract from.
222
+ * `viaRef: false` is a distinct, ref-independent case: an explicit `--from
223
+ * <hash>` that doesn't name a ref, on an empty migration graph, so there is
224
+ * no graph node and no ref to resolve a contract through.
225
+ */
218
226
  export function errorSnapshotMissing(
219
227
  identifier: string,
220
228
  options?: { readonly viaRef?: boolean },
221
229
  ): CliStructuredError {
222
230
  const viaRef = options?.viaRef !== false;
223
231
  const fix = viaRef
224
- ? `Run "prisma-next db update --advance-ref ${identifier}" to repopulate the snapshot, or "prisma-next ref delete ${identifier}" to clear the orphan pointer.`
225
- : `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name that has a paired snapshot, or run db update first.`;
232
+ ? `Create the ref with "prisma-next ref set ${identifier} <hash>" (or advance it via "prisma-next db update --advance-ref ${identifier}"), or pass a hash that is a node in the migration graph.`
233
+ : `No contract source exists for hash "${identifier}" on an empty migration graph. Use --from with a ref name (its contract resolves through the snapshot store), or run db update first.`;
226
234
  return errorRuntime(
227
235
  viaRef
228
- ? `Ref "${identifier}" has no paired contract snapshot`
236
+ ? `Ref "${identifier}" is not resolvable`
229
237
  : `No contract source for from-hash "${identifier}"`,
230
238
  {
231
239
  why: viaRef
232
- ? `Ref "${identifier}" exists but its paired snapshot files are missing.`
233
- : `Hash "${identifier}" is not a graph node and no paired ref snapshot supplies a contract.`,
240
+ ? `Ref "${identifier}" has no pointer file, and the hash being resolved is not a node in the migration graph either.`
241
+ : `Hash "${identifier}" is not a node in the migration graph (the graph is empty), and it does not name a ref either.`,
234
242
  fix,
235
243
  meta: {
236
244
  code: 'MIGRATION.SNAPSHOT_MISSING',
@@ -1,6 +1,5 @@
1
1
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
2
2
  import { notOk, type Result } from '@prisma-next/utils/result';
3
- import { join } from 'pathe';
4
3
  import {
5
4
  CliStructuredError,
6
5
  errorContractValidationFailed,
@@ -16,7 +15,7 @@ export function mapContractAtError(
16
15
  ): Result<never, CliStructuredError> {
17
16
  if (MigrationToolsError.is(error)) {
18
17
  switch (error.code) {
19
- case 'MIGRATION.SNAPSHOT_MISSING': {
18
+ case 'MIGRATION.REF_NOT_RESOLVABLE': {
20
19
  const refName =
21
20
  typeof error.details?.['refName'] === 'string'
22
21
  ? error.details['refName']
@@ -27,18 +26,13 @@ export function mapContractAtError(
27
26
  }
28
27
  case 'MIGRATION.CONTRACT_DESERIALIZATION_FAILED': {
29
28
  const filePath =
30
- typeof error.details?.['filePath'] === 'string'
31
- ? error.details['filePath']
32
- : 'ref-snapshot';
29
+ typeof error.details?.['filePath'] === 'string' ? error.details['filePath'] : 'unknown';
33
30
  const message =
34
31
  typeof error.details?.['message'] === 'string' ? error.details['message'] : error.message;
35
- const isRefSnapshot = filePath.endsWith('.contract.json');
36
32
  return notOk(
37
33
  errorContractValidationFailed(
38
- isRefSnapshot
39
- ? `Ref snapshot contract failed to deserialize: ${message}`
40
- : `Predecessor contract at ${filePath} failed to deserialize: ${message}`,
41
- { where: { path: isRefSnapshot ? 'ref-snapshot' : filePath } },
34
+ `Predecessor contract at ${filePath} failed to deserialize: ${message}`,
35
+ { where: { path: filePath } },
42
36
  ),
43
37
  );
44
38
  }
@@ -66,22 +60,19 @@ export function mapContractAtError(
66
60
  fix: error.fix,
67
61
  }),
68
62
  );
69
- case 'MIGRATION.FILE_MISSING': {
70
- const file =
71
- typeof error.details?.['file'] === 'string' ? error.details['file'] : 'end-contract.json';
72
- const dir = typeof error.details?.['dir'] === 'string' ? error.details['dir'] : '';
73
- const jsonPath = dir ? join(dir, 'end-contract.json') : file;
63
+ case 'MIGRATION.CONTRACT_SNAPSHOT_MISSING': {
64
+ const expectedPath =
65
+ typeof error.details?.['expectedPath'] === 'string'
66
+ ? error.details['expectedPath']
67
+ : 'migrations/snapshots/';
74
68
  const role = options?.artifactRole ?? 'from';
75
69
  return notOk(
76
- errorFileNotFound(jsonPath, {
70
+ errorFileNotFound(expectedPath, {
77
71
  why:
78
72
  role === 'to'
79
- ? `Target migration is missing its destination contract snapshot at ${jsonPath}`
80
- : `Predecessor migration is missing its destination contract snapshot at ${jsonPath}`,
81
- fix:
82
- role === 'to'
83
- ? 'Re-emit the target migration so its sibling `end-contract.json` / `end-contract.d.ts` are restored, then re-run this command.'
84
- : '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.',
73
+ ? `Target migration is missing its contract snapshot at ${expectedPath}`
74
+ : `Predecessor migration is missing its contract snapshot at ${expectedPath}`,
75
+ fix: 'Restore migrations/snapshots/ from version control, or re-run the command that produced this migration to regenerate its snapshot.',
85
76
  }),
86
77
  );
87
78
  }
@@ -2,7 +2,7 @@ import { materialiseExtensionMigrationPackageIfMissing } from '@prisma-next/migr
2
2
  import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
3
3
  import type { MigrationOps } from '@prisma-next/migration-tools/package';
4
4
  import {
5
- emitContractSpaceArtefacts,
5
+ emitContractSpaceArtifacts,
6
6
  planAllSpaces,
7
7
  readContractSpaceHeadRef,
8
8
  type SpacePlanOutput,
@@ -53,7 +53,7 @@ export interface ContractSpaceSeedPhaseInputs {
53
53
  * - `action: 'unchanged'` — the on-disk head already matched the
54
54
  * descriptor and no new migration packages needed to be written.
55
55
  *
56
- * Either way, the artefacts (`contract.json`, `contract.d.ts`,
56
+ * Either way, the artifacts (`contract.json`, `contract.d.ts`,
57
57
  * `refs/head.json`) are re-emitted: the framework owns those files and
58
58
  * makes the re-emit observably idempotent at the byte level.
59
59
  */
@@ -75,9 +75,11 @@ export interface ContractSpaceSeedPhaseResult {
75
75
  * For every extension that exposes a `contractSpace`:
76
76
  *
77
77
  * 1. Read the on-disk head ref (returns `null` on first emit).
78
- * 2. Re-emit `contract.json` / `contract.d.ts` / `refs/head.json`
79
- * unconditionally via {@link emitContractSpaceArtefacts}. The
80
- * framework owns these files; re-emit is the contract.
78
+ * 2. Write the head contract into the migrations-root snapshot store
79
+ * (write-if-absent, keyed by hash) and unconditionally re-emit
80
+ * `refs/head.json`, via {@link emitContractSpaceArtifacts}. The
81
+ * framework owns `refs/head.json`; re-emit is the contract for that
82
+ * file. The snapshot itself is only written once per distinct hash.
81
83
  * 3. Materialise any descriptor-shipped migration packages not yet on
82
84
  * disk via {@link materialiseExtensionMigrationPackageIfMissing}.
83
85
  * Existing packages are left untouched (by-existence skip).
@@ -144,7 +146,7 @@ export async function runContractSpaceSeedPhase(
144
146
  const onDiskHeadRef = await readContractSpaceHeadRef(inputs.migrationsDir, space.spaceId);
145
147
  const priorHash = onDiskHeadRef?.hash ?? null;
146
148
 
147
- await emitContractSpaceArtefacts(inputs.migrationsDir, space.spaceId, {
149
+ await emitContractSpaceArtifacts(inputs.migrationsDir, space.spaceId, {
148
150
  contract: descriptor.contractJson,
149
151
  contractDts: buildPlaceholderContractDts(space.spaceId),
150
152
  headRef: { hash: descriptor.headRef.hash, invariants: descriptor.headRef.invariants },
@@ -104,8 +104,7 @@ function abbreviateHash(hash: string, hashLength: number, emptySource: string):
104
104
  if (hash === EMPTY_CONTRACT_HASH) {
105
105
  return emptySource;
106
106
  }
107
- const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;
108
- return stripped.slice(0, hashLength);
107
+ return hash.slice(0, hashLength);
109
108
  }
110
109
 
111
110
  interface ContractOverlayNames {
@@ -18,8 +18,7 @@ export function migrationListEmptySource(glyphMode: GlyphMode): string {
18
18
  }
19
19
 
20
20
  export function abbreviateContractHash(hash: string): string {
21
- const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;
22
- return stripped.slice(0, MIGRATION_LIST_HASH_WIDTH);
21
+ return hash.slice(0, MIGRATION_LIST_HASH_WIDTH);
23
22
  }
24
23
 
25
24
  export function padFromHashColumn(text: string, width: number): string {