@prisma-next/cli 0.5.0-dev.2 → 0.5.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 (153) hide show
  1. package/README.md +54 -21
  2. package/dist/agent-skill-mongo.md +63 -31
  3. package/dist/agent-skill-postgres.md +1 -1
  4. package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-BJLUczXT.d.mts} +1 -0
  5. package/dist/cli-errors-By1iVE3z.mjs +34 -0
  6. package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
  7. package/dist/cli.mjs +127 -13
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/{client-TG7rbCWT.mjs → client-enZIahga.mjs} +20 -5
  10. package/dist/client-enZIahga.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -1
  12. package/dist/commands/contract-emit.mjs +7 -2
  13. package/dist/commands/contract-infer.mjs +8 -2
  14. package/dist/commands/db-init.mjs +9 -8
  15. package/dist/commands/db-init.mjs.map +1 -1
  16. package/dist/commands/db-schema.mjs +8 -5
  17. package/dist/commands/db-schema.mjs.map +1 -1
  18. package/dist/commands/db-sign.mjs +8 -7
  19. package/dist/commands/db-sign.mjs.map +1 -1
  20. package/dist/commands/db-update.mjs +9 -8
  21. package/dist/commands/db-update.mjs.map +1 -1
  22. package/dist/commands/db-verify.mjs +10 -9
  23. package/dist/commands/db-verify.mjs.map +1 -1
  24. package/dist/commands/migration-apply.d.mts +1 -1
  25. package/dist/commands/migration-apply.d.mts.map +1 -1
  26. package/dist/commands/migration-apply.mjs +15 -38
  27. package/dist/commands/migration-apply.mjs.map +1 -1
  28. package/dist/commands/migration-new.d.mts.map +1 -1
  29. package/dist/commands/migration-new.mjs +24 -28
  30. package/dist/commands/migration-new.mjs.map +1 -1
  31. package/dist/commands/migration-plan.d.mts +6 -3
  32. package/dist/commands/migration-plan.d.mts.map +1 -1
  33. package/dist/commands/migration-plan.mjs +38 -38
  34. package/dist/commands/migration-plan.mjs.map +1 -1
  35. package/dist/commands/migration-ref.d.mts +6 -4
  36. package/dist/commands/migration-ref.d.mts.map +1 -1
  37. package/dist/commands/migration-ref.mjs +31 -40
  38. package/dist/commands/migration-ref.mjs.map +1 -1
  39. package/dist/commands/migration-show.d.mts +4 -4
  40. package/dist/commands/migration-show.d.mts.map +1 -1
  41. package/dist/commands/migration-show.mjs +19 -26
  42. package/dist/commands/migration-show.mjs.map +1 -1
  43. package/dist/commands/migration-status.d.mts +5 -4
  44. package/dist/commands/migration-status.d.mts.map +1 -1
  45. package/dist/commands/migration-status.mjs +7 -2
  46. package/dist/{config-loader-_W4T21X1.mjs → config-loader-ih8ViDb_.mjs} +2 -2
  47. package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
  48. package/dist/config-loader.mjs +1 -1
  49. package/dist/contract-emit-DS5NzZh2.mjs +6 -0
  50. package/dist/contract-emit-DWtGQYCD.mjs +150 -0
  51. package/dist/contract-emit-DWtGQYCD.mjs.map +1 -0
  52. package/dist/contract-emit-RZBWzkop.mjs +329 -0
  53. package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
  54. package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
  55. package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
  56. package/dist/{contract-infer-BP3DrGgz.mjs → contract-infer-BjzkcwQt.mjs} +5 -5
  57. package/dist/{contract-infer-BP3DrGgz.mjs.map → contract-infer-BjzkcwQt.mjs.map} +1 -1
  58. package/dist/exports/control-api.d.mts +41 -16
  59. package/dist/exports/control-api.d.mts.map +1 -1
  60. package/dist/exports/control-api.mjs +7 -5
  61. package/dist/exports/index.mjs +8 -3
  62. package/dist/exports/index.mjs.map +1 -1
  63. package/dist/exports/init-output.d.mts +39 -0
  64. package/dist/exports/init-output.d.mts.map +1 -0
  65. package/dist/exports/init-output.mjs +3 -0
  66. package/dist/{extract-operation-statements-DZUJNmL3.mjs → extract-operation-statements-CU-Pp4-N.mjs} +2 -2
  67. package/dist/{extract-operation-statements-DZUJNmL3.mjs.map → extract-operation-statements-CU-Pp4-N.mjs.map} +1 -1
  68. package/dist/{extract-sql-ddl-DDMX-9mz.mjs → extract-sql-ddl-Bm0Mm0IT.mjs} +1 -1
  69. package/dist/{extract-sql-ddl-DDMX-9mz.mjs.map → extract-sql-ddl-Bm0Mm0IT.mjs.map} +1 -1
  70. package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
  71. package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
  72. package/dist/init-C-H-if1m.mjs +2062 -0
  73. package/dist/init-C-H-if1m.mjs.map +1 -0
  74. package/dist/{inspect-live-schema-DWzf4Q_m.mjs → inspect-live-schema-QklSDLt_.mjs} +6 -6
  75. package/dist/{inspect-live-schema-DWzf4Q_m.mjs.map → inspect-live-schema-QklSDLt_.mjs.map} +1 -1
  76. package/dist/migration-cli.mjs +15 -8
  77. package/dist/migration-cli.mjs.map +1 -1
  78. package/dist/{migration-command-scaffold-CLMD302g.mjs → migration-command-scaffold-BfloSWPZ.mjs} +7 -7
  79. package/dist/{migration-command-scaffold-CLMD302g.mjs.map → migration-command-scaffold-BfloSWPZ.mjs.map} +1 -1
  80. package/dist/{migration-status-B0HLF7So.mjs → migration-status-C5VYA5r9.mjs} +21 -35
  81. package/dist/migration-status-C5VYA5r9.mjs.map +1 -0
  82. package/dist/{migrations-B0dOQlk0.mjs → migrations-CSaDHNpB.mjs} +3 -3
  83. package/dist/migrations-CSaDHNpB.mjs.map +1 -0
  84. package/dist/output-BiO7kt87.mjs +103 -0
  85. package/dist/output-BiO7kt87.mjs.map +1 -0
  86. package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
  87. package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
  88. package/dist/quick-reference-mongo.md +34 -13
  89. package/dist/quick-reference-postgres.md +11 -9
  90. package/dist/{result-handler-CIyu0Pdt.mjs → result-handler-BmVh8AeV.mjs} +12 -93
  91. package/dist/result-handler-BmVh8AeV.mjs.map +1 -0
  92. package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
  93. package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
  94. package/dist/{verify-BxiVp50b.mjs → verify-BumcH6Ry.mjs} +2 -2
  95. package/dist/{verify-BxiVp50b.mjs.map → verify-BumcH6Ry.mjs.map} +1 -1
  96. package/package.json +20 -15
  97. package/src/commands/contract-emit.ts +67 -163
  98. package/src/commands/init/detect-pnpm-catalog.ts +141 -0
  99. package/src/commands/init/errors.ts +254 -0
  100. package/src/commands/init/exit-codes.ts +62 -0
  101. package/src/commands/init/hygiene-gitattributes.ts +97 -0
  102. package/src/commands/init/hygiene-gitignore.ts +48 -0
  103. package/src/commands/init/hygiene-package-scripts.ts +91 -0
  104. package/src/commands/init/index.ts +112 -7
  105. package/src/commands/init/init.ts +766 -144
  106. package/src/commands/init/inputs.ts +421 -0
  107. package/src/commands/init/output.ts +147 -0
  108. package/src/commands/init/probe-db.ts +308 -0
  109. package/src/commands/init/reinit-cleanup.ts +83 -0
  110. package/src/commands/init/templates/agent-skill-mongo.md +63 -31
  111. package/src/commands/init/templates/agent-skill-postgres.md +1 -1
  112. package/src/commands/init/templates/agent-skill.ts +25 -3
  113. package/src/commands/init/templates/code-templates.ts +125 -32
  114. package/src/commands/init/templates/env.ts +80 -0
  115. package/src/commands/init/templates/quick-reference-mongo.md +34 -13
  116. package/src/commands/init/templates/quick-reference-postgres.md +11 -9
  117. package/src/commands/init/templates/quick-reference.ts +42 -3
  118. package/src/commands/init/templates/tsconfig.ts +167 -5
  119. package/src/commands/migration-apply.ts +15 -50
  120. package/src/commands/migration-new.ts +24 -28
  121. package/src/commands/migration-plan.ts +58 -42
  122. package/src/commands/migration-ref.ts +40 -54
  123. package/src/commands/migration-show.ts +27 -28
  124. package/src/commands/migration-status.ts +33 -50
  125. package/src/config-path-validation.ts +0 -1
  126. package/src/control-api/operations/contract-emit.ts +198 -115
  127. package/src/control-api/operations/migration-apply.ts +15 -0
  128. package/src/control-api/types.ts +22 -3
  129. package/src/exports/control-api.ts +2 -1
  130. package/src/exports/init-output.ts +10 -0
  131. package/src/migration-cli.ts +16 -9
  132. package/src/utils/cli-errors.ts +45 -1
  133. package/src/utils/command-helpers.ts +13 -26
  134. package/src/utils/emit-queue.ts +26 -0
  135. package/src/utils/formatters/graph-migration-mapper.ts +2 -2
  136. package/src/utils/formatters/migrations.ts +2 -2
  137. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  138. package/dist/cli-errors-DHq6GQGu.mjs +0 -5
  139. package/dist/client-TG7rbCWT.mjs.map +0 -1
  140. package/dist/config-loader-_W4T21X1.mjs.map +0 -1
  141. package/dist/contract-emit-CNYyzJwF.mjs +0 -195
  142. package/dist/contract-emit-CNYyzJwF.mjs.map +0 -1
  143. package/dist/contract-emit-CQfj7xJn.mjs +0 -122
  144. package/dist/contract-emit-CQfj7xJn.mjs.map +0 -1
  145. package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
  146. package/dist/init-CQfo_4Ro.mjs +0 -430
  147. package/dist/init-CQfo_4Ro.mjs.map +0 -1
  148. package/dist/migration-status-B0HLF7So.mjs.map +0 -1
  149. package/dist/migrations-B0dOQlk0.mjs.map +0 -1
  150. package/dist/result-handler-CIyu0Pdt.mjs.map +0 -1
  151. package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
  152. package/dist/validate-contract-deps-esa-VQ0h.mjs +0 -37
  153. package/dist/validate-contract-deps-esa-VQ0h.mjs.map +0 -1
@@ -1,9 +1,8 @@
1
- import { verifyMigrationBundle } from '@prisma-next/migration-tools/attestation';
2
1
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
3
- import { findPathWithDecision } from '@prisma-next/migration-tools/dag';
2
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
+ import { findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';
4
+ import type { MigrationPackage } from '@prisma-next/migration-tools/package';
4
5
  import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
5
- import type { MigrationBundle } from '@prisma-next/migration-tools/types';
6
- import { MigrationToolsError } from '@prisma-next/migration-tools/types';
7
6
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
8
7
  import { Command } from 'commander';
9
8
 
@@ -18,11 +17,11 @@ import {
18
17
  errorRuntime,
19
18
  errorTargetMigrationNotSupported,
20
19
  errorUnexpected,
20
+ mapMigrationToolsError,
21
21
  } from '../utils/cli-errors';
22
22
  import {
23
23
  addGlobalOptions,
24
- loadAllBundles,
25
- type MigrationBundleSet,
24
+ loadMigrationPackages,
26
25
  maskConnectionUrl,
27
26
  readContractEnvelope,
28
27
  resolveMigrationPaths,
@@ -64,7 +63,7 @@ export interface MigrationApplyResult {
64
63
  readonly refName?: string;
65
64
  readonly selectedPath: readonly {
66
65
  readonly dirName: string;
67
- readonly migrationId: string;
66
+ readonly migrationHash: string;
68
67
  readonly from: string;
69
68
  readonly to: string;
70
69
  }[];
@@ -74,19 +73,6 @@ export interface MigrationApplyResult {
74
73
  };
75
74
  }
76
75
 
77
- function mapMigrationToolsError(error: unknown): CliStructuredErrorType {
78
- if (MigrationToolsError.is(error)) {
79
- return errorRuntime(error.message, {
80
- why: error.why,
81
- fix: error.fix,
82
- meta: { code: error.code, ...(error.details ?? {}) },
83
- });
84
- }
85
- return errorUnexpected(error instanceof Error ? error.message : String(error), {
86
- why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,
87
- });
88
- }
89
-
90
76
  function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
91
77
  return errorRuntime(failure.summary, {
92
78
  why: failure.why ?? 'Migration runner failed',
@@ -95,12 +81,12 @@ function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType
95
81
  });
96
82
  }
97
83
 
98
- function packageToStep(pkg: MigrationBundle): MigrationApplyStep {
84
+ function packageToStep(pkg: MigrationPackage): MigrationApplyStep {
99
85
  return {
100
86
  dirName: pkg.dirName,
101
- from: pkg.manifest.from,
102
- to: pkg.manifest.to,
103
- toContract: pkg.manifest.toContract,
87
+ from: pkg.metadata.from,
88
+ to: pkg.metadata.to,
89
+ toContract: pkg.metadata.toContract,
104
90
  operations: pkg.ops,
105
91
  };
106
92
  }
@@ -112,7 +98,7 @@ async function executeMigrationApplyCommand(
112
98
  startTime: number,
113
99
  ): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {
114
100
  const config = await loadConfig(options.config);
115
- const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(
101
+ const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(
116
102
  options.config,
117
103
  config,
118
104
  );
@@ -149,8 +135,8 @@ async function executeMigrationApplyCommand(
149
135
  if (options.ref) {
150
136
  refName = options.ref;
151
137
  try {
152
- const refs = await readRefs(refsPath);
153
- destinationHash = resolveRef(refs, refName);
138
+ const refs = await readRefs(refsDir);
139
+ destinationHash = resolveRef(refs, refName).hash;
154
140
  } catch (error) {
155
141
  if (MigrationToolsError.is(error)) {
156
142
  return notOk(mapMigrationToolsError(error));
@@ -196,9 +182,9 @@ async function executeMigrationApplyCommand(
196
182
  }
197
183
 
198
184
  // Read migrations and build migration chain model (offline — no DB needed)
199
- let migrations: MigrationBundleSet;
185
+ let migrations: Awaited<ReturnType<typeof loadMigrationPackages>>;
200
186
  try {
201
- migrations = await loadAllBundles(migrationsDir);
187
+ migrations = await loadMigrationPackages(migrationsDir);
202
188
  } catch (error) {
203
189
  if (MigrationToolsError.is(error)) {
204
190
  return notOk(mapMigrationToolsError(error));
@@ -206,27 +192,6 @@ async function executeMigrationApplyCommand(
206
192
  throw error;
207
193
  }
208
194
 
209
- // Defense in depth: re-hash every bundle and confirm the recorded
210
- // `migrationId` matches the on-disk `(manifest, ops)`. Catches FS
211
- // corruption, partial writes, and post-emit hand edits before we
212
- // start touching the database.
213
- for (const bundle of migrations.bundles) {
214
- const verified = verifyMigrationBundle(bundle);
215
- if (!verified.ok) {
216
- return notOk(
217
- errorRuntime(`Migration package is corrupt: ${bundle.dirName}`, {
218
- why: `Stored migrationId "${verified.storedMigrationId}" does not match the recomputed hash "${verified.computedMigrationId}" for ${migrationsRelative}/${bundle.dirName}. The migration.json or ops.json has been edited or partially written since emit.`,
219
- fix: `Re-emit the package by running \`node "${migrationsRelative}/${bundle.dirName}/migration.ts"\`, or restore the directory from version control.`,
220
- meta: {
221
- dirName: bundle.dirName,
222
- storedMigrationId: verified.storedMigrationId,
223
- computedMigrationId: verified.computedMigrationId,
224
- },
225
- }),
226
- );
227
- }
228
- }
229
-
230
195
  const client = createControlClient({
231
196
  family: config.family,
232
197
  target: config.target,
@@ -12,31 +12,36 @@ import { readFileSync } from 'node:fs';
12
12
  import type { Contract } from '@prisma-next/contract/types';
13
13
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
14
14
  import { createControlStack } from '@prisma-next/framework-components/control';
15
- import { computeMigrationId } from '@prisma-next/migration-tools/attestation';
16
15
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
17
- import { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';
16
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
17
+ import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
18
18
  import {
19
19
  copyFilesWithRename,
20
20
  formatMigrationDirName,
21
21
  readMigrationsDir,
22
22
  writeMigrationPackage,
23
23
  } from '@prisma-next/migration-tools/io';
24
+ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
25
+ import {
26
+ findLatestMigration,
27
+ reconstructGraph,
28
+ } from '@prisma-next/migration-tools/migration-graph';
24
29
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
25
- import type { MigrationManifest } from '@prisma-next/migration-tools/types';
26
- import { MigrationToolsError } from '@prisma-next/migration-tools/types';
27
30
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
28
31
  import { Command } from 'commander';
29
- import { join, relative, resolve } from 'pathe';
32
+ import { join, relative } from 'pathe';
30
33
  import { loadConfig } from '../config-loader';
31
34
  import {
32
35
  CliStructuredError,
33
36
  errorRuntime,
34
37
  errorTargetMigrationNotSupported,
35
38
  errorUnexpected,
39
+ mapMigrationToolsError,
36
40
  } from '../utils/cli-errors';
37
41
  import {
38
42
  addGlobalOptions,
39
43
  getTargetMigrations,
44
+ resolveContractPath,
40
45
  resolveMigrationPaths,
41
46
  setCommandDescriptions,
42
47
  setCommandExamples,
@@ -68,11 +73,7 @@ async function executeMigrationNewCommand(
68
73
  const config = await loadConfig(options.config);
69
74
  const { migrationsDir, migrationsRelative } = resolveMigrationPaths(options.config, config);
70
75
 
71
- const contractPath = config.contract?.output ?? 'contract.json';
72
- const contractPathAbsolute = resolve(
73
- options.config ? resolve(options.config, '..') : process.cwd(),
74
- contractPath,
75
- );
76
+ const contractPathAbsolute = resolveContractPath(config);
76
77
 
77
78
  let contractJsonContent: string;
78
79
  try {
@@ -126,7 +127,7 @@ async function executeMigrationNewCommand(
126
127
  const graph = reconstructGraph(packages);
127
128
 
128
129
  if (options.from) {
129
- const match = packages.find((p) => p.manifest.to.startsWith(options.from!));
130
+ const match = packages.find((p) => p.metadata.to.startsWith(options.from!));
130
131
  if (!match) {
131
132
  return notOk(
132
133
  errorRuntime('Starting contract not found', {
@@ -135,18 +136,18 @@ async function executeMigrationNewCommand(
135
136
  }),
136
137
  );
137
138
  }
138
- fromHash = match.manifest.to;
139
- fromContract = match.manifest.toContract;
139
+ fromHash = match.metadata.to;
140
+ fromContract = match.metadata.toContract;
140
141
  fromContractSourceDir = match.dirPath;
141
142
  } else {
142
143
  const latestMigration = findLatestMigration(graph);
143
144
  if (latestMigration) {
144
145
  fromHash = latestMigration.to;
145
146
  const leafPkg = packages.find(
146
- (p) => p.manifest.migrationId === latestMigration.migrationId,
147
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
147
148
  );
148
149
  if (leafPkg) {
149
- fromContract = leafPkg.manifest.toContract;
150
+ fromContract = leafPkg.metadata.toContract;
150
151
  fromContractSourceDir = leafPkg.dirPath;
151
152
  }
152
153
  }
@@ -154,13 +155,7 @@ async function executeMigrationNewCommand(
154
155
  }
155
156
  } catch (error) {
156
157
  if (MigrationToolsError.is(error)) {
157
- return notOk(
158
- errorRuntime(error.message, {
159
- why: error.why,
160
- fix: error.fix,
161
- meta: { code: error.code },
162
- }),
163
- );
158
+ return notOk(mapMigrationToolsError(error));
164
159
  }
165
160
  throw error;
166
161
  }
@@ -181,9 +176,9 @@ async function executeMigrationNewCommand(
181
176
 
182
177
  // `migration new` scaffolds an empty `migration.ts` for the user to
183
178
  // fill, so we attest over `ops: []`. Re-running self-emit after the
184
- // user adds operations will produce a different `migrationId` (over
179
+ // user adds operations will produce a different `migrationHash` (over
185
180
  // the real ops). This is intentional — there is no on-disk draft.
186
- const baseManifest: Omit<MigrationManifest, 'migrationId'> = {
181
+ const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {
187
182
  from: fromHash,
188
183
  to: toStorageHash,
189
184
  kind: 'regular',
@@ -195,11 +190,12 @@ async function executeMigrationNewCommand(
195
190
  plannerVersion: '1.0.0',
196
191
  },
197
192
  labels: [],
193
+ providedInvariants: [],
198
194
  createdAt: timestamp.toISOString(),
199
195
  };
200
- const manifest: MigrationManifest = {
201
- ...baseManifest,
202
- migrationId: computeMigrationId(baseManifest, []),
196
+ const metadata: MigrationMetadata = {
197
+ ...baseMetadata,
198
+ migrationHash: computeMigrationHash(baseMetadata, []),
203
199
  };
204
200
 
205
201
  const migrations = getTargetMigrations(config.target);
@@ -218,7 +214,7 @@ async function executeMigrationNewCommand(
218
214
  ...(config.extensionPacks ?? []),
219
215
  ]);
220
216
 
221
- await writeMigrationPackage(packageDir, manifest, []);
217
+ await writeMigrationPackage(packageDir, metadata, []);
222
218
  const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
223
219
  await copyFilesWithRename(packageDir, [
224
220
  { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
@@ -5,16 +5,18 @@ import {
5
5
  createControlStack,
6
6
  type MigrationPlanOperation,
7
7
  } from '@prisma-next/framework-components/control';
8
- import { computeMigrationId } from '@prisma-next/migration-tools/attestation';
9
8
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
10
- import { findLatestMigration } from '@prisma-next/migration-tools/dag';
9
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
10
+ import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
11
+ import { deriveProvidedInvariants } from '@prisma-next/migration-tools/invariants';
11
12
  import {
12
13
  copyFilesWithRename,
13
14
  formatMigrationDirName,
14
15
  writeMigrationPackage,
15
16
  } from '@prisma-next/migration-tools/io';
17
+ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
18
+ import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
16
19
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
17
- import { type MigrationManifest, MigrationToolsError } from '@prisma-next/migration-tools/types';
18
20
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
19
21
  import { Command } from 'commander';
20
22
  import { join, relative } from 'pathe';
@@ -29,11 +31,12 @@ import {
29
31
  errorRuntime,
30
32
  errorTargetMigrationNotSupported,
31
33
  errorUnexpected,
34
+ mapMigrationToolsError,
32
35
  } from '../utils/cli-errors';
33
36
  import {
34
37
  addGlobalOptions,
35
38
  getTargetMigrations,
36
- loadAllBundles,
39
+ loadMigrationPackages,
37
40
  resolveContractPath,
38
41
  resolveMigrationPaths,
39
42
  setCommandDescriptions,
@@ -76,22 +79,6 @@ export interface MigrationPlanResult {
76
79
  };
77
80
  }
78
81
 
79
- function mapMigrationToolsError(error: unknown): CliStructuredError {
80
- if (CliStructuredError.is(error)) {
81
- return error;
82
- }
83
- if (MigrationToolsError.is(error)) {
84
- return errorRuntime(error.message, {
85
- why: error.why,
86
- fix: error.fix,
87
- meta: { code: error.code, ...(error.details ?? {}) },
88
- });
89
- }
90
- return errorUnexpected(error instanceof Error ? error.message : String(error), {
91
- why: `Unexpected error during migration plan: ${error instanceof Error ? error.message : String(error)}`,
92
- });
93
- }
94
-
95
82
  async function executeMigrationPlanCommand(
96
83
  options: MigrationPlanOptions,
97
84
  flags: GlobalFlags,
@@ -177,7 +164,7 @@ async function executeMigrationPlanCommand(
177
164
  let fromContractSourceDir: string | null = null;
178
165
 
179
166
  try {
180
- const { bundles, graph } = await loadAllBundles(migrationsDir);
167
+ const { bundles, graph } = await loadMigrationPackages(migrationsDir);
181
168
 
182
169
  if (options.from) {
183
170
  const resolved = resolveBundleByPrefix(bundles, options.from);
@@ -195,16 +182,18 @@ async function executeMigrationPlanCommand(
195
182
  }),
196
183
  );
197
184
  }
198
- fromHash = resolved.value.manifest.to;
199
- fromContract = resolved.value.manifest.toContract;
185
+ fromHash = resolved.value.metadata.to;
186
+ fromContract = resolved.value.metadata.toContract;
200
187
  fromContractSourceDir = resolved.value.dirPath;
201
188
  } else {
202
189
  const latestMigration = findLatestMigration(graph);
203
190
  if (latestMigration) {
204
191
  fromHash = latestMigration.to;
205
- const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);
192
+ const leafPkg = bundles.find(
193
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
194
+ );
206
195
  if (leafPkg) {
207
- fromContract = leafPkg.manifest.toContract;
196
+ fromContract = leafPkg.metadata.toContract;
208
197
  fromContractSourceDir = leafPkg.dirPath;
209
198
  }
210
199
  }
@@ -213,7 +202,16 @@ async function executeMigrationPlanCommand(
213
202
  if (MigrationToolsError.is(error)) {
214
203
  return notOk(mapMigrationToolsError(error));
215
204
  }
216
- throw error;
205
+ // Wrap unexpected (non-MigrationToolsError) failures from the migration
206
+ // load phase in a structured CLI envelope. Letting them throw would
207
+ // bypass `handleResult()` and crash the command — see CLI structured-
208
+ // errors guideline (CliStructuredError + Result pattern).
209
+ const message = error instanceof Error ? error.message : String(error);
210
+ return notOk(
211
+ errorUnexpected(message, {
212
+ why: `Unexpected error while loading migrations: ${message}`,
213
+ }),
214
+ );
217
215
  }
218
216
 
219
217
  // Check for no-op (same hash means no changes)
@@ -251,7 +249,7 @@ async function executeMigrationPlanCommand(
251
249
  const dirName = formatMigrationDirName(timestamp, slug);
252
250
  const packageDir = join(migrationsDir, dirName);
253
251
 
254
- const baseManifest: Omit<MigrationManifest, 'migrationId'> = {
252
+ const baseMetadata: Omit<MigrationMetadata, 'migrationHash' | 'providedInvariants'> = {
255
253
  from: fromHash,
256
254
  to: toStorageHash,
257
255
  kind: 'regular',
@@ -320,18 +318,22 @@ async function executeMigrationPlanCommand(
320
318
 
321
319
  const migrationTsContent = plannerResult.plan.renderTypeScript();
322
320
 
323
- // Always-attest: compute migrationId over (manifest, ops). When
324
- // placeholders blocked lowering, ops is `[]` and the id hashes over
325
- // the empty list — re-emitting after the user fills the placeholder
326
- // produces a different id (over the real ops). This is intentional;
321
+ // Always-attest: compute migrationHash over (metadata, ops). When
322
+ // placeholders blocked lowering, ops is `[]` and the hash is computed
323
+ // over the empty list — re-emitting after the user fills the placeholder
324
+ // produces a different hash (over the real ops). This is intentional;
327
325
  // there is no on-disk "draft" state.
328
326
  const opsForWrite = hasPlaceholders ? [] : plannedOps;
329
- const manifest: MigrationManifest = {
330
- ...baseManifest,
331
- migrationId: computeMigrationId(baseManifest, opsForWrite),
327
+ const metadataWithInvariants: Omit<MigrationMetadata, 'migrationHash'> = {
328
+ ...baseMetadata,
329
+ providedInvariants: deriveProvidedInvariants(opsForWrite),
330
+ };
331
+ const metadata: MigrationMetadata = {
332
+ ...metadataWithInvariants,
333
+ migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite),
332
334
  };
333
335
 
334
- await writeMigrationPackage(packageDir, manifest, opsForWrite);
336
+ await writeMigrationPackage(packageDir, metadata, opsForWrite);
335
337
  const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
336
338
  await copyFilesWithRename(packageDir, [
337
339
  { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
@@ -382,7 +384,18 @@ async function executeMigrationPlanCommand(
382
384
  };
383
385
  return ok(result);
384
386
  } catch (error) {
385
- return notOk(mapMigrationToolsError(error));
387
+ if (CliStructuredError.is(error)) {
388
+ return notOk(error);
389
+ }
390
+ if (MigrationToolsError.is(error)) {
391
+ return notOk(mapMigrationToolsError(error));
392
+ }
393
+ const message = error instanceof Error ? error.message : String(error);
394
+ return notOk(
395
+ errorUnexpected(message, {
396
+ why: `Unexpected error during migration plan: ${message}`,
397
+ }),
398
+ );
386
399
  }
387
400
  }
388
401
 
@@ -489,7 +502,7 @@ function formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFla
489
502
 
490
503
  lines.push('');
491
504
  lines.push(
492
- `Next: ${green_(`node ${result.dir ?? '<dir>'}/migration.ts`)} to emit ops.json and attest migrationId before running ${green_('prisma-next migration apply')}.`,
505
+ `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,
493
506
  );
494
507
 
495
508
  if (result.sql && result.sql.length > 0) {
@@ -517,24 +530,27 @@ export type PrefixResolutionFailure =
517
530
  | { reason: 'not-found' };
518
531
 
519
532
  /**
520
- * Resolve a migration bundle by exact hash or prefix match.
533
+ * Resolve a migration package by **target contract hash** (`metadata.to`)
534
+ * using exact match or prefix match.
521
535
  *
536
+ * Note: matches `metadata.to` (the contract hash this migration produces),
537
+ * not `metadata.migrationHash` (the package's content-addressed identity).
522
538
  * Tries exact match first, then prefix match (auto-prepending `sha256:` when
523
- * the needle omits the scheme). Returns the matched bundle on success, or a
539
+ * the needle omits the scheme). Returns the matched package on success, or a
524
540
  * discriminated failure indicating whether the prefix was ambiguous or simply
525
541
  * not found.
526
542
  *
527
543
  * @internal Exported for testing only.
528
544
  */
529
- export function resolveBundleByPrefix<T extends { manifest: { to: string } }>(
545
+ export function resolveBundleByPrefix<T extends { metadata: { to: string } }>(
530
546
  bundles: readonly T[],
531
547
  needle: string,
532
548
  ): Result<T, PrefixResolutionFailure> {
533
- const exact = bundles.find((p) => p.manifest.to === needle);
549
+ const exact = bundles.find((p) => p.metadata.to === needle);
534
550
  if (exact) return ok(exact);
535
551
 
536
552
  const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;
537
- const candidates = bundles.filter((p) => p.manifest.to.startsWith(prefixWithScheme));
553
+ const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
538
554
 
539
555
  if (candidates.length === 1) return ok(candidates[0]!);
540
556
  if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });