@prisma-next/cli 0.5.0-dev.1 → 0.5.0-dev.11

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 (138) hide show
  1. package/README.md +17 -18
  2. package/dist/agent-skill-mongo.md +63 -31
  3. package/dist/agent-skill-postgres.md +1 -1
  4. package/dist/cli-errors-By1iVE3z.mjs +34 -0
  5. package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
  6. package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-D2NPMaxW.d.mts} +1 -0
  7. package/dist/cli.mjs +126 -13
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/{client-TG7rbCWT.mjs → client-faKQqcix.mjs} +19 -4
  10. package/dist/client-faKQqcix.mjs.map +1 -0
  11. package/dist/commands/contract-emit.mjs +7 -2
  12. package/dist/commands/contract-infer.mjs +8 -2
  13. package/dist/commands/db-init.mjs +8 -7
  14. package/dist/commands/db-init.mjs.map +1 -1
  15. package/dist/commands/db-schema.mjs +8 -5
  16. package/dist/commands/db-schema.mjs.map +1 -1
  17. package/dist/commands/db-sign.mjs +8 -7
  18. package/dist/commands/db-sign.mjs.map +1 -1
  19. package/dist/commands/db-update.mjs +8 -7
  20. package/dist/commands/db-update.mjs.map +1 -1
  21. package/dist/commands/db-verify.mjs +8 -7
  22. package/dist/commands/db-verify.mjs.map +1 -1
  23. package/dist/commands/migration-apply.d.mts +1 -1
  24. package/dist/commands/migration-apply.d.mts.map +1 -1
  25. package/dist/commands/migration-apply.mjs +15 -38
  26. package/dist/commands/migration-apply.mjs.map +1 -1
  27. package/dist/commands/migration-new.d.mts.map +1 -1
  28. package/dist/commands/migration-new.mjs +21 -26
  29. package/dist/commands/migration-new.mjs.map +1 -1
  30. package/dist/commands/migration-plan.d.mts +6 -3
  31. package/dist/commands/migration-plan.d.mts.map +1 -1
  32. package/dist/commands/migration-plan.mjs +31 -36
  33. package/dist/commands/migration-plan.mjs.map +1 -1
  34. package/dist/commands/migration-ref.d.mts +6 -4
  35. package/dist/commands/migration-ref.d.mts.map +1 -1
  36. package/dist/commands/migration-ref.mjs +31 -40
  37. package/dist/commands/migration-ref.mjs.map +1 -1
  38. package/dist/commands/migration-show.d.mts +4 -4
  39. package/dist/commands/migration-show.d.mts.map +1 -1
  40. package/dist/commands/migration-show.mjs +19 -26
  41. package/dist/commands/migration-show.mjs.map +1 -1
  42. package/dist/commands/migration-status.d.mts +5 -4
  43. package/dist/commands/migration-status.d.mts.map +1 -1
  44. package/dist/commands/migration-status.mjs +7 -2
  45. package/dist/{config-loader-_W4T21X1.mjs → config-loader-C25b63rJ.mjs} +1 -1
  46. package/dist/{config-loader-_W4T21X1.mjs.map → config-loader-C25b63rJ.mjs.map} +1 -1
  47. package/dist/config-loader.mjs +1 -1
  48. package/dist/contract-emit-B9wkchud.mjs +6 -0
  49. package/dist/{contract-emit-CNYyzJwF.mjs → contract-emit-Cf3fjDL6.mjs} +8 -8
  50. package/dist/{contract-emit-CNYyzJwF.mjs.map → contract-emit-Cf3fjDL6.mjs.map} +1 -1
  51. package/dist/{contract-emit-CQfj7xJn.mjs → contract-emit-PeB96eHy.mjs} +6 -6
  52. package/dist/{contract-emit-CQfj7xJn.mjs.map → contract-emit-PeB96eHy.mjs.map} +1 -1
  53. package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-CAOELa-H.mjs} +1 -1
  54. package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-CAOELa-H.mjs.map} +1 -1
  55. package/dist/{contract-infer-BP3DrGgz.mjs → contract-infer-BgnsSkGp.mjs} +4 -4
  56. package/dist/{contract-infer-BP3DrGgz.mjs.map → contract-infer-BgnsSkGp.mjs.map} +1 -1
  57. package/dist/exports/control-api.mjs +6 -4
  58. package/dist/exports/index.mjs +7 -2
  59. package/dist/exports/index.mjs.map +1 -1
  60. package/dist/exports/init-output.d.mts +39 -0
  61. package/dist/exports/init-output.d.mts.map +1 -0
  62. package/dist/exports/init-output.mjs +3 -0
  63. package/dist/{extract-operation-statements-DZUJNmL3.mjs → extract-operation-statements-DsFfxXVZ.mjs} +2 -2
  64. package/dist/{extract-operation-statements-DZUJNmL3.mjs.map → extract-operation-statements-DsFfxXVZ.mjs.map} +1 -1
  65. package/dist/{extract-sql-ddl-DDMX-9mz.mjs → extract-sql-ddl-D9UbZDyz.mjs} +1 -1
  66. package/dist/{extract-sql-ddl-DDMX-9mz.mjs.map → extract-sql-ddl-D9UbZDyz.mjs.map} +1 -1
  67. package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-C6el-5x_.mjs} +2 -2
  68. package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-C6el-5x_.mjs.map} +1 -1
  69. package/dist/init-jf33mNQ6.mjs +2062 -0
  70. package/dist/init-jf33mNQ6.mjs.map +1 -0
  71. package/dist/{inspect-live-schema-DWzf4Q_m.mjs → inspect-live-schema-yCu0JT0I.mjs} +6 -6
  72. package/dist/{inspect-live-schema-DWzf4Q_m.mjs.map → inspect-live-schema-yCu0JT0I.mjs.map} +1 -1
  73. package/dist/migration-cli.mjs +14 -7
  74. package/dist/migration-cli.mjs.map +1 -1
  75. package/dist/{migration-command-scaffold-CLMD302g.mjs → migration-command-scaffold-B8HAEGhQ.mjs} +6 -6
  76. package/dist/{migration-command-scaffold-CLMD302g.mjs.map → migration-command-scaffold-B8HAEGhQ.mjs.map} +1 -1
  77. package/dist/{migration-status-B0HLF7So.mjs → migration-status-8QUxCJHE.mjs} +19 -33
  78. package/dist/migration-status-8QUxCJHE.mjs.map +1 -0
  79. package/dist/{migrations-B0dOQlk0.mjs → migrations-CKRMAKka.mjs} +3 -3
  80. package/dist/migrations-CKRMAKka.mjs.map +1 -0
  81. package/dist/output-BpcQrnnq.mjs +103 -0
  82. package/dist/output-BpcQrnnq.mjs.map +1 -0
  83. package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DvQWB1nK.mjs} +1 -1
  84. package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DvQWB1nK.mjs.map} +1 -1
  85. package/dist/quick-reference-mongo.md +34 -13
  86. package/dist/quick-reference-postgres.md +11 -9
  87. package/dist/{result-handler-CIyu0Pdt.mjs → result-handler-CuhZ3kNu.mjs} +10 -91
  88. package/dist/result-handler-CuhZ3kNu.mjs.map +1 -0
  89. package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-C3ZLwQxK.mjs} +76 -2
  90. package/dist/terminal-ui-C3ZLwQxK.mjs.map +1 -0
  91. package/dist/{validate-contract-deps-esa-VQ0h.mjs → validate-contract-deps-B_Cs29TL.mjs} +1 -1
  92. package/dist/{validate-contract-deps-esa-VQ0h.mjs.map → validate-contract-deps-B_Cs29TL.mjs.map} +1 -1
  93. package/dist/{verify-BxiVp50b.mjs → verify-Bkycc-Tf.mjs} +2 -2
  94. package/dist/{verify-BxiVp50b.mjs.map → verify-Bkycc-Tf.mjs.map} +1 -1
  95. package/package.json +20 -15
  96. package/src/commands/init/detect-pnpm-catalog.ts +141 -0
  97. package/src/commands/init/errors.ts +254 -0
  98. package/src/commands/init/exit-codes.ts +62 -0
  99. package/src/commands/init/hygiene-gitattributes.ts +97 -0
  100. package/src/commands/init/hygiene-gitignore.ts +48 -0
  101. package/src/commands/init/hygiene-package-scripts.ts +91 -0
  102. package/src/commands/init/index.ts +112 -7
  103. package/src/commands/init/init.ts +766 -144
  104. package/src/commands/init/inputs.ts +421 -0
  105. package/src/commands/init/output.ts +147 -0
  106. package/src/commands/init/probe-db.ts +308 -0
  107. package/src/commands/init/reinit-cleanup.ts +83 -0
  108. package/src/commands/init/templates/agent-skill-mongo.md +63 -31
  109. package/src/commands/init/templates/agent-skill-postgres.md +1 -1
  110. package/src/commands/init/templates/agent-skill.ts +25 -3
  111. package/src/commands/init/templates/code-templates.ts +125 -32
  112. package/src/commands/init/templates/env.ts +80 -0
  113. package/src/commands/init/templates/quick-reference-mongo.md +34 -13
  114. package/src/commands/init/templates/quick-reference-postgres.md +11 -9
  115. package/src/commands/init/templates/quick-reference.ts +42 -3
  116. package/src/commands/init/templates/tsconfig.ts +167 -5
  117. package/src/commands/migration-apply.ts +15 -50
  118. package/src/commands/migration-new.ts +23 -28
  119. package/src/commands/migration-plan.ts +53 -42
  120. package/src/commands/migration-ref.ts +40 -54
  121. package/src/commands/migration-show.ts +27 -28
  122. package/src/commands/migration-status.ts +33 -50
  123. package/src/control-api/operations/migration-apply.ts +15 -0
  124. package/src/exports/init-output.ts +10 -0
  125. package/src/migration-cli.ts +16 -9
  126. package/src/utils/cli-errors.ts +45 -1
  127. package/src/utils/command-helpers.ts +13 -26
  128. package/src/utils/formatters/graph-migration-mapper.ts +2 -2
  129. package/src/utils/formatters/migrations.ts +2 -2
  130. package/dist/cli-errors-DHq6GQGu.mjs +0 -5
  131. package/dist/client-TG7rbCWT.mjs.map +0 -1
  132. package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
  133. package/dist/init-CQfo_4Ro.mjs +0 -430
  134. package/dist/init-CQfo_4Ro.mjs.map +0 -1
  135. package/dist/migration-status-B0HLF7So.mjs.map +0 -1
  136. package/dist/migrations-B0dOQlk0.mjs.map +0 -1
  137. package/dist/result-handler-CIyu0Pdt.mjs.map +0 -1
  138. package/dist/terminal-ui-C5k88MmW.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',
@@ -197,9 +192,9 @@ async function executeMigrationNewCommand(
197
192
  labels: [],
198
193
  createdAt: timestamp.toISOString(),
199
194
  };
200
- const manifest: MigrationManifest = {
201
- ...baseManifest,
202
- migrationId: computeMigrationId(baseManifest, []),
195
+ const metadata: MigrationMetadata = {
196
+ ...baseMetadata,
197
+ migrationHash: computeMigrationHash(baseMetadata, []),
203
198
  };
204
199
 
205
200
  const migrations = getTargetMigrations(config.target);
@@ -218,7 +213,7 @@ async function executeMigrationNewCommand(
218
213
  ...(config.extensionPacks ?? []),
219
214
  ]);
220
215
 
221
- await writeMigrationPackage(packageDir, manifest, []);
216
+ await writeMigrationPackage(packageDir, metadata, []);
222
217
  const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
223
218
  await copyFilesWithRename(packageDir, [
224
219
  { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
@@ -5,16 +5,17 @@ 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
11
  import {
12
12
  copyFilesWithRename,
13
13
  formatMigrationDirName,
14
14
  writeMigrationPackage,
15
15
  } from '@prisma-next/migration-tools/io';
16
+ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
17
+ import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
16
18
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
17
- import { type MigrationManifest, MigrationToolsError } from '@prisma-next/migration-tools/types';
18
19
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
19
20
  import { Command } from 'commander';
20
21
  import { join, relative } from 'pathe';
@@ -29,11 +30,12 @@ import {
29
30
  errorRuntime,
30
31
  errorTargetMigrationNotSupported,
31
32
  errorUnexpected,
33
+ mapMigrationToolsError,
32
34
  } from '../utils/cli-errors';
33
35
  import {
34
36
  addGlobalOptions,
35
37
  getTargetMigrations,
36
- loadAllBundles,
38
+ loadMigrationPackages,
37
39
  resolveContractPath,
38
40
  resolveMigrationPaths,
39
41
  setCommandDescriptions,
@@ -76,22 +78,6 @@ export interface MigrationPlanResult {
76
78
  };
77
79
  }
78
80
 
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
81
  async function executeMigrationPlanCommand(
96
82
  options: MigrationPlanOptions,
97
83
  flags: GlobalFlags,
@@ -177,7 +163,7 @@ async function executeMigrationPlanCommand(
177
163
  let fromContractSourceDir: string | null = null;
178
164
 
179
165
  try {
180
- const { bundles, graph } = await loadAllBundles(migrationsDir);
166
+ const { bundles, graph } = await loadMigrationPackages(migrationsDir);
181
167
 
182
168
  if (options.from) {
183
169
  const resolved = resolveBundleByPrefix(bundles, options.from);
@@ -195,16 +181,18 @@ async function executeMigrationPlanCommand(
195
181
  }),
196
182
  );
197
183
  }
198
- fromHash = resolved.value.manifest.to;
199
- fromContract = resolved.value.manifest.toContract;
184
+ fromHash = resolved.value.metadata.to;
185
+ fromContract = resolved.value.metadata.toContract;
200
186
  fromContractSourceDir = resolved.value.dirPath;
201
187
  } else {
202
188
  const latestMigration = findLatestMigration(graph);
203
189
  if (latestMigration) {
204
190
  fromHash = latestMigration.to;
205
- const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);
191
+ const leafPkg = bundles.find(
192
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
193
+ );
206
194
  if (leafPkg) {
207
- fromContract = leafPkg.manifest.toContract;
195
+ fromContract = leafPkg.metadata.toContract;
208
196
  fromContractSourceDir = leafPkg.dirPath;
209
197
  }
210
198
  }
@@ -213,7 +201,16 @@ async function executeMigrationPlanCommand(
213
201
  if (MigrationToolsError.is(error)) {
214
202
  return notOk(mapMigrationToolsError(error));
215
203
  }
216
- throw error;
204
+ // Wrap unexpected (non-MigrationToolsError) failures from the migration
205
+ // load phase in a structured CLI envelope. Letting them throw would
206
+ // bypass `handleResult()` and crash the command — see CLI structured-
207
+ // errors guideline (CliStructuredError + Result pattern).
208
+ const message = error instanceof Error ? error.message : String(error);
209
+ return notOk(
210
+ errorUnexpected(message, {
211
+ why: `Unexpected error while loading migrations: ${message}`,
212
+ }),
213
+ );
217
214
  }
218
215
 
219
216
  // Check for no-op (same hash means no changes)
@@ -251,7 +248,7 @@ async function executeMigrationPlanCommand(
251
248
  const dirName = formatMigrationDirName(timestamp, slug);
252
249
  const packageDir = join(migrationsDir, dirName);
253
250
 
254
- const baseManifest: Omit<MigrationManifest, 'migrationId'> = {
251
+ const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {
255
252
  from: fromHash,
256
253
  to: toStorageHash,
257
254
  kind: 'regular',
@@ -320,18 +317,18 @@ async function executeMigrationPlanCommand(
320
317
 
321
318
  const migrationTsContent = plannerResult.plan.renderTypeScript();
322
319
 
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;
320
+ // Always-attest: compute migrationHash over (metadata, ops). When
321
+ // placeholders blocked lowering, ops is `[]` and the hash is computed
322
+ // over the empty list — re-emitting after the user fills the placeholder
323
+ // produces a different hash (over the real ops). This is intentional;
327
324
  // there is no on-disk "draft" state.
328
325
  const opsForWrite = hasPlaceholders ? [] : plannedOps;
329
- const manifest: MigrationManifest = {
330
- ...baseManifest,
331
- migrationId: computeMigrationId(baseManifest, opsForWrite),
326
+ const metadata: MigrationMetadata = {
327
+ ...baseMetadata,
328
+ migrationHash: computeMigrationHash(baseMetadata, opsForWrite),
332
329
  };
333
330
 
334
- await writeMigrationPackage(packageDir, manifest, opsForWrite);
331
+ await writeMigrationPackage(packageDir, metadata, opsForWrite);
335
332
  const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
336
333
  await copyFilesWithRename(packageDir, [
337
334
  { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
@@ -382,7 +379,18 @@ async function executeMigrationPlanCommand(
382
379
  };
383
380
  return ok(result);
384
381
  } catch (error) {
385
- return notOk(mapMigrationToolsError(error));
382
+ if (CliStructuredError.is(error)) {
383
+ return notOk(error);
384
+ }
385
+ if (MigrationToolsError.is(error)) {
386
+ return notOk(mapMigrationToolsError(error));
387
+ }
388
+ const message = error instanceof Error ? error.message : String(error);
389
+ return notOk(
390
+ errorUnexpected(message, {
391
+ why: `Unexpected error during migration plan: ${message}`,
392
+ }),
393
+ );
386
394
  }
387
395
  }
388
396
 
@@ -489,7 +497,7 @@ function formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFla
489
497
 
490
498
  lines.push('');
491
499
  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')}.`,
500
+ `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,
493
501
  );
494
502
 
495
503
  if (result.sql && result.sql.length > 0) {
@@ -517,24 +525,27 @@ export type PrefixResolutionFailure =
517
525
  | { reason: 'not-found' };
518
526
 
519
527
  /**
520
- * Resolve a migration bundle by exact hash or prefix match.
528
+ * Resolve a migration package by **target contract hash** (`metadata.to`)
529
+ * using exact match or prefix match.
521
530
  *
531
+ * Note: matches `metadata.to` (the contract hash this migration produces),
532
+ * not `metadata.migrationHash` (the package's content-addressed identity).
522
533
  * 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
534
+ * the needle omits the scheme). Returns the matched package on success, or a
524
535
  * discriminated failure indicating whether the prefix was ambiguous or simply
525
536
  * not found.
526
537
  *
527
538
  * @internal Exported for testing only.
528
539
  */
529
- export function resolveBundleByPrefix<T extends { manifest: { to: string } }>(
540
+ export function resolveBundleByPrefix<T extends { metadata: { to: string } }>(
530
541
  bundles: readonly T[],
531
542
  needle: string,
532
543
  ): Result<T, PrefixResolutionFailure> {
533
- const exact = bundles.find((p) => p.manifest.to === needle);
544
+ const exact = bundles.find((p) => p.metadata.to === needle);
534
545
  if (exact) return ok(exact);
535
546
 
536
547
  const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;
537
- const candidates = bundles.filter((p) => p.manifest.to.startsWith(prefixWithScheme));
548
+ const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
538
549
 
539
550
  if (candidates.length === 1) return ok(candidates[0]!);
540
551
  if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });
@@ -1,17 +1,27 @@
1
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
2
+ import type { RefEntry } from '@prisma-next/migration-tools/refs';
1
3
  import {
4
+ deleteRef,
5
+ readRef,
2
6
  readRefs,
3
- resolveRef,
4
7
  validateRefName,
5
8
  validateRefValue,
6
- writeRefs,
9
+ writeRef,
7
10
  } from '@prisma-next/migration-tools/refs';
8
- import { MigrationToolsError } from '@prisma-next/migration-tools/types';
9
11
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
10
12
  import { Command } from 'commander';
11
- import { resolve } from 'pathe';
12
13
  import { loadConfig } from '../config-loader';
13
- import { CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';
14
- import { addGlobalOptions, setCommandDescriptions } from '../utils/command-helpers';
14
+ import {
15
+ CliStructuredError,
16
+ errorRuntime,
17
+ errorUnexpected,
18
+ mapMigrationToolsError,
19
+ } from '../utils/cli-errors';
20
+ import {
21
+ addGlobalOptions,
22
+ resolveMigrationPaths,
23
+ setCommandDescriptions,
24
+ } from '../utils/command-helpers';
15
25
  import { formatCommandHelp } from '../utils/formatters/help';
16
26
  import { parseGlobalFlags } from '../utils/global-flags';
17
27
  import { handleResult } from '../utils/result-handler';
@@ -21,12 +31,14 @@ interface RefSetResult {
21
31
  readonly ok: true;
22
32
  readonly ref: string;
23
33
  readonly hash: string;
34
+ readonly invariants: readonly string[];
24
35
  }
25
36
 
26
37
  interface RefGetResult {
27
38
  readonly ok: true;
28
39
  readonly ref: string;
29
40
  readonly hash: string;
41
+ readonly invariants: readonly string[];
30
42
  }
31
43
 
32
44
  interface RefDeleteResult {
@@ -37,21 +49,12 @@ interface RefDeleteResult {
37
49
 
38
50
  interface RefListResult {
39
51
  readonly ok: true;
40
- readonly refs: Record<string, string>;
41
- }
42
-
43
- function resolveRefsPath(configPath?: string, config?: { migrations?: { dir?: string } }): string {
44
- const base = configPath ? resolve(configPath, '..') : process.cwd();
45
- return resolve(base, config?.migrations?.dir ?? 'migrations', 'refs.json');
52
+ readonly refs: Record<string, RefEntry>;
46
53
  }
47
54
 
48
55
  function mapError(error: unknown): CliStructuredError {
49
56
  if (MigrationToolsError.is(error)) {
50
- return errorRuntime(error.message, {
51
- why: error.why,
52
- fix: error.fix,
53
- meta: { code: error.code },
54
- });
57
+ return mapMigrationToolsError(error);
55
58
  }
56
59
  return errorUnexpected(error instanceof Error ? error.message : String(error));
57
60
  }
@@ -70,13 +73,6 @@ function cliErrorInvalidRefValue(hash: string): CliStructuredError {
70
73
  });
71
74
  }
72
75
 
73
- function errorRefNotFound(name: string): CliStructuredError {
74
- return errorRuntime(`Ref "${name}" does not exist`, {
75
- why: `No ref named "${name}" found in refs.json`,
76
- fix: `Run \`prisma-next migration ref list\` to see available refs, or \`prisma-next migration ref set ${name} <hash>\` to create it`,
77
- });
78
- }
79
-
80
76
  async function executeRefSetCommand(
81
77
  name: string,
82
78
  hash: string,
@@ -91,11 +87,10 @@ async function executeRefSetCommand(
91
87
 
92
88
  try {
93
89
  const config = await loadConfig(options.config);
94
- const refsPath = resolveRefsPath(options.config, config);
95
- const refs = await readRefs(refsPath);
96
- const updated = { ...refs, [name]: hash };
97
- await writeRefs(refsPath, updated);
98
- return ok({ ok: true as const, ref: name, hash });
90
+ const { refsDir } = resolveMigrationPaths(options.config, config);
91
+ const entry: RefEntry = { hash, invariants: [] };
92
+ await writeRef(refsDir, name, entry);
93
+ return ok({ ok: true as const, ref: name, hash, invariants: [] });
99
94
  } catch (error) {
100
95
  if (error instanceof CliStructuredError) return notOk(error);
101
96
  return notOk(mapError(error));
@@ -108,10 +103,9 @@ async function executeRefGetCommand(
108
103
  ): Promise<Result<RefGetResult, CliStructuredError>> {
109
104
  try {
110
105
  const config = await loadConfig(options.config);
111
- const refsPath = resolveRefsPath(options.config, config);
112
- const refs = await readRefs(refsPath);
113
- const hash = resolveRef(refs, name);
114
- return ok({ ok: true as const, ref: name, hash });
106
+ const { refsDir } = resolveMigrationPaths(options.config, config);
107
+ const entry = await readRef(refsDir, name);
108
+ return ok({ ok: true as const, ref: name, hash: entry.hash, invariants: entry.invariants });
115
109
  } catch (error) {
116
110
  if (error instanceof CliStructuredError) return notOk(error);
117
111
  return notOk(mapError(error));
@@ -124,13 +118,8 @@ async function executeRefDeleteCommand(
124
118
  ): Promise<Result<RefDeleteResult, CliStructuredError>> {
125
119
  try {
126
120
  const config = await loadConfig(options.config);
127
- const refsPath = resolveRefsPath(options.config, config);
128
- const refs = await readRefs(refsPath);
129
- if (!Object.hasOwn(refs, name)) {
130
- return notOk(errorRefNotFound(name));
131
- }
132
- const { [name]: _, ...remaining } = refs;
133
- await writeRefs(refsPath, remaining);
121
+ const { refsDir } = resolveMigrationPaths(options.config, config);
122
+ await deleteRef(refsDir, name);
134
123
  return ok({ ok: true as const, ref: name, deleted: true as const });
135
124
  } catch (error) {
136
125
  if (error instanceof CliStructuredError) return notOk(error);
@@ -143,8 +132,8 @@ async function executeRefListCommand(options: {
143
132
  }): Promise<Result<RefListResult, CliStructuredError>> {
144
133
  try {
145
134
  const config = await loadConfig(options.config);
146
- const refsPath = resolveRefsPath(options.config, config);
147
- const refs = await readRefs(refsPath);
135
+ const { refsDir } = resolveMigrationPaths(options.config, config);
136
+ const refs = await readRefs(refsDir);
148
137
  return ok({ ok: true as const, refs });
149
138
  } catch (error) {
150
139
  if (error instanceof CliStructuredError) return notOk(error);
@@ -157,7 +146,7 @@ function createRefSetCommand(): Command {
157
146
  setCommandDescriptions(
158
147
  command,
159
148
  'Set a ref to a contract hash',
160
- 'Sets a named ref to point to a contract hash in migrations/refs.json.',
149
+ 'Sets a named ref to point to a contract hash in migrations/refs/.',
161
150
  );
162
151
  addGlobalOptions(command)
163
152
  .argument('<name>', 'Ref name (e.g., staging, production)')
@@ -190,7 +179,7 @@ function createRefGetCommand(): Command {
190
179
  setCommandDescriptions(
191
180
  command,
192
181
  'Get the hash for a ref',
193
- 'Reads a named ref from migrations/refs.json and prints its contract hash.',
182
+ 'Reads a named ref from migrations/refs/ and prints its contract hash.',
194
183
  );
195
184
  addGlobalOptions(command)
196
185
  .argument('<name>', 'Ref name to look up')
@@ -218,7 +207,7 @@ function createRefGetCommand(): Command {
218
207
 
219
208
  function createRefDeleteCommand(): Command {
220
209
  const command = new Command('delete');
221
- setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs.json.');
210
+ setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');
222
211
  addGlobalOptions(command)
223
212
  .argument('<name>', 'Ref name to delete')
224
213
  .option('--config <path>', 'Path to prisma-next.config.ts')
@@ -245,11 +234,7 @@ function createRefDeleteCommand(): Command {
245
234
 
246
235
  function createRefListCommand(): Command {
247
236
  const command = new Command('list');
248
- setCommandDescriptions(
249
- command,
250
- 'List all refs',
251
- 'Lists all named refs from migrations/refs.json.',
252
- );
237
+ setCommandDescriptions(command, 'List all refs', 'Lists all named refs from migrations/refs/.');
253
238
  addGlobalOptions(command)
254
239
  .option('--config <path>', 'Path to prisma-next.config.ts')
255
240
  .action(async (options: { config?: string; json?: string | boolean; quiet?: boolean }) => {
@@ -264,8 +249,10 @@ function createRefListCommand(): Command {
264
249
  if (entries.length === 0) {
265
250
  ui.output('No refs defined');
266
251
  } else {
267
- for (const [refName, hash] of entries) {
268
- ui.output(`${refName} ${hash}`);
252
+ for (const [refName, entry] of entries) {
253
+ const invariantsSuffix =
254
+ entry.invariants.length > 0 ? ` [invariants: ${entry.invariants.join(', ')}]` : '';
255
+ ui.output(`${refName} → ${entry.hash}${invariantsSuffix}`);
269
256
  }
270
257
  }
271
258
  }
@@ -282,7 +269,6 @@ export {
282
269
  executeRefListCommand,
283
270
  cliErrorInvalidRefName,
284
271
  cliErrorInvalidRefValue,
285
- errorRefNotFound,
286
272
  };
287
273
 
288
274
  export function createMigrationRefCommand(): Command {
@@ -290,7 +276,7 @@ export function createMigrationRefCommand(): Command {
290
276
  setCommandDescriptions(
291
277
  command,
292
278
  'Manage migration refs',
293
- 'Manage named refs in migrations/refs.json. Refs map logical environment\n' +
279
+ 'Manage named refs in migrations/refs/. Refs map logical environment\n' +
294
280
  'names (e.g., staging, production) to contract hashes.',
295
281
  );
296
282
  addGlobalOptions(command).configureHelp({