@prisma-next/cli 0.4.0-dev.9 → 0.4.2

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 (157) hide show
  1. package/README.md +26 -18
  2. package/dist/agent-skill-mongo.md +63 -31
  3. package/dist/agent-skill-postgres.md +1 -1
  4. package/dist/cli-errors-BFYgBH3L.d.mts +4 -0
  5. package/dist/cli-errors-Cd79vmTH.mjs +5 -0
  6. package/dist/cli.mjs +127 -25
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/{client-CJxHfhze.mjs → client-CrsnY58k.mjs} +9 -8
  9. package/dist/{client-CJxHfhze.mjs.map → client-CrsnY58k.mjs.map} +1 -1
  10. package/dist/commands/contract-emit.d.mts.map +1 -1
  11. package/dist/commands/contract-emit.mjs +7 -7
  12. package/dist/commands/contract-infer.mjs +8 -8
  13. package/dist/commands/db-init.mjs +8 -8
  14. package/dist/commands/db-schema.mjs +8 -8
  15. package/dist/commands/db-sign.mjs +8 -8
  16. package/dist/commands/db-update.mjs +8 -8
  17. package/dist/commands/db-verify.mjs +9 -9
  18. package/dist/commands/migration-apply.d.mts +1 -1
  19. package/dist/commands/migration-apply.d.mts.map +1 -1
  20. package/dist/commands/migration-apply.mjs +37 -28
  21. package/dist/commands/migration-apply.mjs.map +1 -1
  22. package/dist/commands/migration-new.d.mts.map +1 -1
  23. package/dist/commands/migration-new.mjs +48 -23
  24. package/dist/commands/migration-new.mjs.map +1 -1
  25. package/dist/commands/migration-plan.d.mts +6 -1
  26. package/dist/commands/migration-plan.d.mts.map +1 -1
  27. package/dist/commands/migration-plan.mjs +94 -71
  28. package/dist/commands/migration-plan.mjs.map +1 -1
  29. package/dist/commands/migration-ref.d.mts +6 -4
  30. package/dist/commands/migration-ref.d.mts.map +1 -1
  31. package/dist/commands/migration-ref.mjs +29 -34
  32. package/dist/commands/migration-ref.mjs.map +1 -1
  33. package/dist/commands/migration-show.d.mts +2 -2
  34. package/dist/commands/migration-show.d.mts.map +1 -1
  35. package/dist/commands/migration-show.mjs +11 -16
  36. package/dist/commands/migration-show.mjs.map +1 -1
  37. package/dist/commands/migration-status.d.mts +4 -5
  38. package/dist/commands/migration-status.d.mts.map +1 -1
  39. package/dist/commands/migration-status.mjs +7 -7
  40. package/dist/config-loader-C25b63rJ.mjs +90 -0
  41. package/dist/config-loader-C25b63rJ.mjs.map +1 -0
  42. package/dist/config-loader.d.mts.map +1 -1
  43. package/dist/config-loader.mjs +1 -1
  44. package/dist/contract-emit-DxgyXrqV.mjs +6 -0
  45. package/dist/{contract-emit-gpJNLGs7.mjs → contract-emit-NJ01hiiv.mjs} +20 -16
  46. package/dist/contract-emit-NJ01hiiv.mjs.map +1 -0
  47. package/dist/{contract-emit-CKig_Lra.mjs → contract-emit-V5SSitUT.mjs} +25 -21
  48. package/dist/contract-emit-V5SSitUT.mjs.map +1 -0
  49. package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-CAOELa-H.mjs} +1 -1
  50. package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-CAOELa-H.mjs.map} +1 -1
  51. package/dist/{contract-infer-BDJgg7Xb.mjs → contract-infer-D9cC3rJm.mjs} +4 -4
  52. package/dist/{contract-infer-BDJgg7Xb.mjs.map → contract-infer-D9cC3rJm.mjs.map} +1 -1
  53. package/dist/exports/control-api.d.mts +2 -2
  54. package/dist/exports/control-api.d.mts.map +1 -1
  55. package/dist/exports/control-api.mjs +6 -6
  56. package/dist/exports/index.mjs +7 -7
  57. package/dist/exports/init-output.d.mts +39 -0
  58. package/dist/exports/init-output.d.mts.map +1 -0
  59. package/dist/exports/init-output.mjs +3 -0
  60. package/dist/{extract-operation-statements-DZUJNmL3.mjs → extract-operation-statements-DsFfxXVZ.mjs} +2 -2
  61. package/dist/{extract-operation-statements-DZUJNmL3.mjs.map → extract-operation-statements-DsFfxXVZ.mjs.map} +1 -1
  62. package/dist/{extract-sql-ddl-DDMX-9mz.mjs → extract-sql-ddl-D9UbZDyz.mjs} +1 -1
  63. package/dist/{extract-sql-ddl-DDMX-9mz.mjs.map → extract-sql-ddl-D9UbZDyz.mjs.map} +1 -1
  64. package/dist/{framework-components-Bsr1GaIj.mjs → framework-components-Cr--XBKy.mjs} +2 -2
  65. package/dist/{framework-components-Bsr1GaIj.mjs.map → framework-components-Cr--XBKy.mjs.map} +1 -1
  66. package/dist/init-m8x0UoPY.mjs +2062 -0
  67. package/dist/init-m8x0UoPY.mjs.map +1 -0
  68. package/dist/{inspect-live-schema-ChqrALmw.mjs → inspect-live-schema-yrHAvG71.mjs} +6 -6
  69. package/dist/{inspect-live-schema-ChqrALmw.mjs.map → inspect-live-schema-yrHAvG71.mjs.map} +1 -1
  70. package/dist/migration-cli.d.mts +50 -0
  71. package/dist/migration-cli.d.mts.map +1 -0
  72. package/dist/migration-cli.mjs +184 -0
  73. package/dist/migration-cli.mjs.map +1 -0
  74. package/dist/{migration-command-scaffold-B0oH_hyB.mjs → migration-command-scaffold-B3B09et6.mjs} +7 -7
  75. package/dist/{migration-command-scaffold-B0oH_hyB.mjs.map → migration-command-scaffold-B3B09et6.mjs.map} +1 -1
  76. package/dist/{migration-status-CPamfEPj.mjs → migration-status-DUMiH8_G.mjs} +25 -43
  77. package/dist/migration-status-DUMiH8_G.mjs.map +1 -0
  78. package/dist/{migrations-BIsjFjSV.mjs → migrations-Bo5WtTla.mjs} +4 -15
  79. package/dist/migrations-Bo5WtTla.mjs.map +1 -0
  80. package/dist/output-BpcQrnnq.mjs +103 -0
  81. package/dist/output-BpcQrnnq.mjs.map +1 -0
  82. package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DvQWB1nK.mjs} +1 -1
  83. package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DvQWB1nK.mjs.map} +1 -1
  84. package/dist/quick-reference-mongo.md +34 -13
  85. package/dist/quick-reference-postgres.md +11 -9
  86. package/dist/{result-handler-AFK4hxyX.mjs → result-handler-Ba3zWQsI.mjs} +26 -88
  87. package/dist/result-handler-Ba3zWQsI.mjs.map +1 -0
  88. package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-C3ZLwQxK.mjs} +76 -2
  89. package/dist/terminal-ui-C3ZLwQxK.mjs.map +1 -0
  90. package/dist/{validate-contract-deps-DBH6iTAD.mjs → validate-contract-deps-B_Cs29TL.mjs} +1 -1
  91. package/dist/{validate-contract-deps-DBH6iTAD.mjs.map → validate-contract-deps-B_Cs29TL.mjs.map} +1 -1
  92. package/dist/{verify-C56CuQc7.mjs → verify-Bkycc-Tf.mjs} +2 -2
  93. package/dist/{verify-C56CuQc7.mjs.map → verify-Bkycc-Tf.mjs.map} +1 -1
  94. package/package.json +24 -19
  95. package/src/cli.ts +1 -5
  96. package/src/commands/contract-emit.ts +9 -10
  97. package/src/commands/init/detect-pnpm-catalog.ts +141 -0
  98. package/src/commands/init/errors.ts +254 -0
  99. package/src/commands/init/exit-codes.ts +62 -0
  100. package/src/commands/init/hygiene-gitattributes.ts +97 -0
  101. package/src/commands/init/hygiene-gitignore.ts +48 -0
  102. package/src/commands/init/hygiene-package-scripts.ts +91 -0
  103. package/src/commands/init/index.ts +112 -7
  104. package/src/commands/init/init.ts +766 -144
  105. package/src/commands/init/inputs.ts +421 -0
  106. package/src/commands/init/output.ts +147 -0
  107. package/src/commands/init/probe-db.ts +308 -0
  108. package/src/commands/init/reinit-cleanup.ts +83 -0
  109. package/src/commands/init/templates/agent-skill-mongo.md +63 -31
  110. package/src/commands/init/templates/agent-skill-postgres.md +1 -1
  111. package/src/commands/init/templates/agent-skill.ts +25 -3
  112. package/src/commands/init/templates/code-templates.ts +125 -32
  113. package/src/commands/init/templates/env.ts +80 -0
  114. package/src/commands/init/templates/quick-reference-mongo.md +34 -13
  115. package/src/commands/init/templates/quick-reference-postgres.md +11 -9
  116. package/src/commands/init/templates/quick-reference.ts +42 -3
  117. package/src/commands/init/templates/tsconfig.ts +167 -5
  118. package/src/commands/migration-apply.ts +37 -26
  119. package/src/commands/migration-new.ts +39 -17
  120. package/src/commands/migration-plan.ts +119 -104
  121. package/src/commands/migration-ref.ts +32 -47
  122. package/src/commands/migration-show.ts +6 -16
  123. package/src/commands/migration-status.ts +30 -55
  124. package/src/config-loader.ts +35 -29
  125. package/src/config-path-validation.ts +75 -0
  126. package/src/control-api/client.ts +2 -1
  127. package/src/control-api/operations/contract-emit.ts +24 -23
  128. package/src/control-api/types.ts +1 -1
  129. package/src/exports/init-output.ts +10 -0
  130. package/src/migration-cli.ts +254 -0
  131. package/src/utils/cli-errors.ts +1 -0
  132. package/src/utils/command-helpers.ts +18 -22
  133. package/src/utils/formatters/graph-migration-mapper.ts +5 -14
  134. package/src/utils/formatters/help.ts +0 -1
  135. package/src/utils/formatters/migrations.ts +2 -29
  136. package/dist/cli-errors-BUuJr6py.mjs +0 -5
  137. package/dist/cli-errors-Dic2eADK.d.mts +0 -4
  138. package/dist/commands/migration-emit.d.mts +0 -38
  139. package/dist/commands/migration-emit.d.mts.map +0 -1
  140. package/dist/commands/migration-emit.mjs +0 -81
  141. package/dist/commands/migration-emit.mjs.map +0 -1
  142. package/dist/config-loader-C4VXKl8f.mjs +0 -43
  143. package/dist/config-loader-C4VXKl8f.mjs.map +0 -1
  144. package/dist/contract-emit-CKig_Lra.mjs.map +0 -1
  145. package/dist/contract-emit-CU-SYNe4.mjs +0 -6
  146. package/dist/contract-emit-gpJNLGs7.mjs.map +0 -1
  147. package/dist/init-DZWvhEP0.mjs +0 -430
  148. package/dist/init-DZWvhEP0.mjs.map +0 -1
  149. package/dist/migration-emit-Du4DBMqz.mjs +0 -125
  150. package/dist/migration-emit-Du4DBMqz.mjs.map +0 -1
  151. package/dist/migration-status-CPamfEPj.mjs.map +0 -1
  152. package/dist/migrations-BIsjFjSV.mjs.map +0 -1
  153. package/dist/result-handler-AFK4hxyX.mjs.map +0 -1
  154. package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
  155. package/src/commands/migration-emit.ts +0 -134
  156. package/src/lib/migration-emit.ts +0 -125
  157. package/src/lib/migration-strategy.ts +0 -49
@@ -1,17 +1,22 @@
1
+ import type { RefEntry } from '@prisma-next/migration-tools/refs';
1
2
  import {
3
+ deleteRef,
4
+ readRef,
2
5
  readRefs,
3
- resolveRef,
4
6
  validateRefName,
5
7
  validateRefValue,
6
- writeRefs,
8
+ writeRef,
7
9
  } from '@prisma-next/migration-tools/refs';
8
10
  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
14
  import { CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';
14
- import { addGlobalOptions, setCommandDescriptions } from '../utils/command-helpers';
15
+ import {
16
+ addGlobalOptions,
17
+ resolveMigrationPaths,
18
+ setCommandDescriptions,
19
+ } from '../utils/command-helpers';
15
20
  import { formatCommandHelp } from '../utils/formatters/help';
16
21
  import { parseGlobalFlags } from '../utils/global-flags';
17
22
  import { handleResult } from '../utils/result-handler';
@@ -21,12 +26,14 @@ interface RefSetResult {
21
26
  readonly ok: true;
22
27
  readonly ref: string;
23
28
  readonly hash: string;
29
+ readonly invariants: readonly string[];
24
30
  }
25
31
 
26
32
  interface RefGetResult {
27
33
  readonly ok: true;
28
34
  readonly ref: string;
29
35
  readonly hash: string;
36
+ readonly invariants: readonly string[];
30
37
  }
31
38
 
32
39
  interface RefDeleteResult {
@@ -37,12 +44,7 @@ interface RefDeleteResult {
37
44
 
38
45
  interface RefListResult {
39
46
  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');
47
+ readonly refs: Record<string, RefEntry>;
46
48
  }
47
49
 
48
50
  function mapError(error: unknown): CliStructuredError {
@@ -70,13 +72,6 @@ function cliErrorInvalidRefValue(hash: string): CliStructuredError {
70
72
  });
71
73
  }
72
74
 
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
75
  async function executeRefSetCommand(
81
76
  name: string,
82
77
  hash: string,
@@ -91,11 +86,10 @@ async function executeRefSetCommand(
91
86
 
92
87
  try {
93
88
  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 });
89
+ const { refsDir } = resolveMigrationPaths(options.config, config);
90
+ const entry: RefEntry = { hash, invariants: [] };
91
+ await writeRef(refsDir, name, entry);
92
+ return ok({ ok: true as const, ref: name, hash, invariants: [] });
99
93
  } catch (error) {
100
94
  if (error instanceof CliStructuredError) return notOk(error);
101
95
  return notOk(mapError(error));
@@ -108,10 +102,9 @@ async function executeRefGetCommand(
108
102
  ): Promise<Result<RefGetResult, CliStructuredError>> {
109
103
  try {
110
104
  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 });
105
+ const { refsDir } = resolveMigrationPaths(options.config, config);
106
+ const entry = await readRef(refsDir, name);
107
+ return ok({ ok: true as const, ref: name, hash: entry.hash, invariants: entry.invariants });
115
108
  } catch (error) {
116
109
  if (error instanceof CliStructuredError) return notOk(error);
117
110
  return notOk(mapError(error));
@@ -124,13 +117,8 @@ async function executeRefDeleteCommand(
124
117
  ): Promise<Result<RefDeleteResult, CliStructuredError>> {
125
118
  try {
126
119
  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);
120
+ const { refsDir } = resolveMigrationPaths(options.config, config);
121
+ await deleteRef(refsDir, name);
134
122
  return ok({ ok: true as const, ref: name, deleted: true as const });
135
123
  } catch (error) {
136
124
  if (error instanceof CliStructuredError) return notOk(error);
@@ -143,8 +131,8 @@ async function executeRefListCommand(options: {
143
131
  }): Promise<Result<RefListResult, CliStructuredError>> {
144
132
  try {
145
133
  const config = await loadConfig(options.config);
146
- const refsPath = resolveRefsPath(options.config, config);
147
- const refs = await readRefs(refsPath);
134
+ const { refsDir } = resolveMigrationPaths(options.config, config);
135
+ const refs = await readRefs(refsDir);
148
136
  return ok({ ok: true as const, refs });
149
137
  } catch (error) {
150
138
  if (error instanceof CliStructuredError) return notOk(error);
@@ -157,7 +145,7 @@ function createRefSetCommand(): Command {
157
145
  setCommandDescriptions(
158
146
  command,
159
147
  'Set a ref to a contract hash',
160
- 'Sets a named ref to point to a contract hash in migrations/refs.json.',
148
+ 'Sets a named ref to point to a contract hash in migrations/refs/.',
161
149
  );
162
150
  addGlobalOptions(command)
163
151
  .argument('<name>', 'Ref name (e.g., staging, production)')
@@ -190,7 +178,7 @@ function createRefGetCommand(): Command {
190
178
  setCommandDescriptions(
191
179
  command,
192
180
  'Get the hash for a ref',
193
- 'Reads a named ref from migrations/refs.json and prints its contract hash.',
181
+ 'Reads a named ref from migrations/refs/ and prints its contract hash.',
194
182
  );
195
183
  addGlobalOptions(command)
196
184
  .argument('<name>', 'Ref name to look up')
@@ -218,7 +206,7 @@ function createRefGetCommand(): Command {
218
206
 
219
207
  function createRefDeleteCommand(): Command {
220
208
  const command = new Command('delete');
221
- setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs.json.');
209
+ setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');
222
210
  addGlobalOptions(command)
223
211
  .argument('<name>', 'Ref name to delete')
224
212
  .option('--config <path>', 'Path to prisma-next.config.ts')
@@ -245,11 +233,7 @@ function createRefDeleteCommand(): Command {
245
233
 
246
234
  function createRefListCommand(): Command {
247
235
  const command = new Command('list');
248
- setCommandDescriptions(
249
- command,
250
- 'List all refs',
251
- 'Lists all named refs from migrations/refs.json.',
252
- );
236
+ setCommandDescriptions(command, 'List all refs', 'Lists all named refs from migrations/refs/.');
253
237
  addGlobalOptions(command)
254
238
  .option('--config <path>', 'Path to prisma-next.config.ts')
255
239
  .action(async (options: { config?: string; json?: string | boolean; quiet?: boolean }) => {
@@ -264,8 +248,10 @@ function createRefListCommand(): Command {
264
248
  if (entries.length === 0) {
265
249
  ui.output('No refs defined');
266
250
  } else {
267
- for (const [refName, hash] of entries) {
268
- ui.output(`${refName} ${hash}`);
251
+ for (const [refName, entry] of entries) {
252
+ const invariantsSuffix =
253
+ entry.invariants.length > 0 ? ` [invariants: ${entry.invariants.join(', ')}]` : '';
254
+ ui.output(`${refName} → ${entry.hash}${invariantsSuffix}`);
269
255
  }
270
256
  }
271
257
  }
@@ -282,7 +268,6 @@ export {
282
268
  executeRefListCommand,
283
269
  cliErrorInvalidRefName,
284
270
  cliErrorInvalidRefValue,
285
- errorRefNotFound,
286
271
  };
287
272
 
288
273
  export function createMigrationRefCommand(): Command {
@@ -290,7 +275,7 @@ export function createMigrationRefCommand(): Command {
290
275
  setCommandDescriptions(
291
276
  command,
292
277
  'Manage migration refs',
293
- 'Manage named refs in migrations/refs.json. Refs map logical environment\n' +
278
+ 'Manage named refs in migrations/refs/. Refs map logical environment\n' +
294
279
  'names (e.g., staging, production) to contract hashes.',
295
280
  );
296
281
  addGlobalOptions(command).configureHelp({
@@ -2,7 +2,7 @@ import type { MigrationPlanOperation } from '@prisma-next/framework-components/c
2
2
  import { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';
3
3
  import { readMigrationPackage, readMigrationsDir } from '@prisma-next/migration-tools/io';
4
4
  import type { MigrationBundle } from '@prisma-next/migration-tools/types';
5
- import { isAttested, MigrationToolsError } from '@prisma-next/migration-tools/types';
5
+ import { MigrationToolsError } from '@prisma-next/migration-tools/types';
6
6
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
7
7
  import { Command } from 'commander';
8
8
  import { relative, resolve } from 'pathe';
@@ -31,7 +31,7 @@ export interface MigrationShowResult {
31
31
  readonly dirPath: string;
32
32
  readonly from: string;
33
33
  readonly to: string;
34
- readonly migrationId: string | null;
34
+ readonly migrationId: string;
35
35
  readonly kind: string;
36
36
  readonly createdAt: string;
37
37
  readonly operations: readonly {
@@ -52,8 +52,7 @@ export function resolveByHashPrefix(
52
52
  prefix: string,
53
53
  ): Result<MigrationBundle, CliStructuredError> {
54
54
  const normalizedPrefix = prefix.startsWith('sha256:') ? prefix : `sha256:${prefix}`;
55
- const attested = packages.filter((p) => typeof p.manifest.migrationId === 'string');
56
- const matches = attested.filter((p) => p.manifest.migrationId!.startsWith(normalizedPrefix));
55
+ const matches = packages.filter((p) => p.manifest.migrationId.startsWith(normalizedPrefix));
57
56
 
58
57
  if (matches.length === 1) {
59
58
  return ok(matches[0]!);
@@ -62,7 +61,7 @@ export function resolveByHashPrefix(
62
61
  if (matches.length === 0) {
63
62
  return notOk(
64
63
  errorRuntime('No migration found matching prefix', {
65
- why: `No attested migration has a migrationId starting with "${normalizedPrefix}"`,
64
+ why: `No migration has a migrationId starting with "${normalizedPrefix}"`,
66
65
  fix: 'Run `prisma-next migration show` (no argument) to see the latest migration, or check the migrations directory for available packages.',
67
66
  }),
68
67
  );
@@ -132,16 +131,7 @@ async function executeMigrationShowCommand(
132
131
  if (!resolved.ok) return resolved;
133
132
  pkg = resolved.value;
134
133
  } else {
135
- const attested = allPackages.filter(isAttested);
136
- if (attested.length === 0) {
137
- return notOk(
138
- errorRuntime('No attested migrations found', {
139
- why: `All migrations in ${migrationsRelative} are drafts (migrationId: null)`,
140
- fix: 'Run `prisma-next migration emit --dir <path>` to attest a draft migration.',
141
- }),
142
- );
143
- }
144
- const graph = reconstructGraph(attested);
134
+ const graph = reconstructGraph(allPackages);
145
135
  const latestMigration = findLatestMigration(graph);
146
136
  if (!latestMigration) {
147
137
  return notOk(
@@ -151,7 +141,7 @@ async function executeMigrationShowCommand(
151
141
  }),
152
142
  );
153
143
  }
154
- const leafPkg = attested.find(
144
+ const leafPkg = allPackages.find(
155
145
  (p) => p.manifest.migrationId === latestMigration.migrationId,
156
146
  );
157
147
  if (!leafPkg) {
@@ -8,8 +8,7 @@ import {
8
8
  import type { Refs } from '@prisma-next/migration-tools/refs';
9
9
  import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
10
10
  import type {
11
- AttestedMigrationBundle,
12
- DraftMigrationBundle,
11
+ MigrationBundle,
13
12
  MigrationChainEntry,
14
13
  MigrationGraph,
15
14
  } from '@prisma-next/migration-tools/types';
@@ -62,7 +61,7 @@ export interface MigrationStatusEntry {
62
61
  readonly dirName: string;
63
62
  readonly from: string;
64
63
  readonly to: string;
65
- readonly migrationId: string | null;
64
+ readonly migrationId: string;
66
65
  readonly operationCount: number;
67
66
  readonly operationSummary: string;
68
67
  readonly hasDestructive: boolean;
@@ -87,7 +86,7 @@ export interface MigrationStatusResult {
87
86
  readonly refName?: string;
88
87
  readonly selectedPath: readonly {
89
88
  readonly dirName: string;
90
- readonly migrationId: string | null;
89
+ readonly migrationId: string;
91
90
  readonly from: string;
92
91
  readonly to: string;
93
92
  }[];
@@ -95,8 +94,7 @@ export interface MigrationStatusResult {
95
94
  readonly summary: string;
96
95
  readonly diagnostics: readonly StatusDiagnostic[];
97
96
  readonly graph?: MigrationGraph;
98
- readonly bundles?: readonly AttestedMigrationBundle[];
99
- readonly drafts?: readonly DraftMigrationBundle[];
97
+ readonly bundles?: readonly MigrationBundle[];
100
98
  readonly edgeStatuses?: readonly EdgeStatus[];
101
99
  readonly activeRefHash?: string;
102
100
  readonly activeRefName?: string;
@@ -227,7 +225,7 @@ export function deriveEdgeStatuses(
227
225
  */
228
226
  function buildMigrationEntries(
229
227
  chain: readonly MigrationChainEntry[],
230
- packages: readonly AttestedMigrationBundle[],
228
+ packages: readonly MigrationBundle[],
231
229
  mode: 'online' | 'offline',
232
230
  markerHash: string | undefined,
233
231
  edgeStatuses?: readonly EdgeStatus[],
@@ -347,7 +345,7 @@ async function executeMigrationStatusCommand(
347
345
  ui: TerminalUI,
348
346
  ): Promise<Result<MigrationStatusResult, CliStructuredError>> {
349
347
  const config = await loadConfig(options.config);
350
- const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(
348
+ const { configPath, migrationsDir, migrationsRelative, refsDir } = resolveMigrationPaths(
351
349
  options.config,
352
350
  config,
353
351
  );
@@ -359,7 +357,7 @@ async function executeMigrationStatusCommand(
359
357
  let activeRefHash: string | undefined;
360
358
  let allRefs: Refs = {};
361
359
  try {
362
- allRefs = await readRefs(refsPath);
360
+ allRefs = await readRefs(refsDir);
363
361
  } catch (error) {
364
362
  if (MigrationToolsError.is(error)) {
365
363
  return notOk(
@@ -375,30 +373,25 @@ async function executeMigrationStatusCommand(
375
373
 
376
374
  if (options.ref) {
377
375
  activeRefName = options.ref;
378
- const refHash = allRefs[activeRefName];
379
- if (refHash) {
380
- activeRefHash = refHash;
381
- } else {
382
- try {
383
- activeRefHash = resolveRef(allRefs, activeRefName);
384
- } catch (error) {
385
- if (MigrationToolsError.is(error)) {
386
- return notOk(
387
- errorRuntime(error.message, {
388
- why: error.why,
389
- fix: error.fix,
390
- meta: { code: error.code },
391
- }),
392
- );
393
- }
394
- throw error;
376
+ try {
377
+ activeRefHash = resolveRef(allRefs, activeRefName).hash;
378
+ } catch (error) {
379
+ if (MigrationToolsError.is(error)) {
380
+ return notOk(
381
+ errorRuntime(error.message, {
382
+ why: error.why,
383
+ fix: error.fix,
384
+ meta: { code: error.code },
385
+ }),
386
+ );
395
387
  }
388
+ throw error;
396
389
  }
397
390
  }
398
391
 
399
- const statusRefs: StatusRef[] = Object.entries(allRefs).map(([name, hash]) => ({
392
+ const statusRefs: StatusRef[] = Object.entries(allRefs).map(([name, entry]) => ({
400
393
  name,
401
- hash,
394
+ hash: entry.hash,
402
395
  active: name === activeRefName,
403
396
  }));
404
397
 
@@ -436,11 +429,10 @@ async function executeMigrationStatusCommand(
436
429
  });
437
430
  }
438
431
 
439
- let attested: readonly AttestedMigrationBundle[];
440
- let drafts: readonly DraftMigrationBundle[];
432
+ let bundles: readonly MigrationBundle[];
441
433
  let graph: MigrationGraph;
442
434
  try {
443
- ({ attested, drafts, graph } = await loadAllBundles(migrationsDir));
435
+ ({ bundles, graph } = await loadAllBundles(migrationsDir));
444
436
  } catch (error) {
445
437
  if (MigrationToolsError.is(error)) {
446
438
  return notOk(
@@ -454,18 +446,7 @@ async function executeMigrationStatusCommand(
454
446
  );
455
447
  }
456
448
 
457
- if (drafts.length > 0) {
458
- diagnostics.push({
459
- code: 'MIGRATION.DRAFTS',
460
- severity: 'warn',
461
- message: `${drafts.length} draft migration(s) found: ${drafts.map((d) => d.dirName).join(', ')}`,
462
- hints: [
463
- "Run 'prisma-next migration emit --dir <path>' to attest draft migrations before applying",
464
- ],
465
- });
466
- }
467
-
468
- if (attested.length === 0) {
449
+ if (bundles.length === 0) {
469
450
  if (contractHash !== EMPTY_CONTRACT_HASH) {
470
451
  diagnostics.push({
471
452
  code: 'CONTRACT.AHEAD',
@@ -576,7 +557,7 @@ async function executeMigrationStatusCommand(
576
557
  migrations: [],
577
558
  targetHash: EMPTY_CONTRACT_HASH,
578
559
  contractHash,
579
- summary: `${attested.length} migration(s) on disk`,
560
+ summary: `${bundles.length} migration(s) on disk`,
580
561
  diagnostics,
581
562
  markerHash,
582
563
  ...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
@@ -616,12 +597,12 @@ async function executeMigrationStatusCommand(
616
597
  migrations: [],
617
598
  targetHash: EMPTY_CONTRACT_HASH,
618
599
  contractHash,
619
- summary: `${attested.length} migration(s) on disk`,
600
+ summary: `${bundles.length} migration(s) on disk`,
620
601
  diagnostics,
621
602
  ...ifDefined('markerHash', markerHash),
622
603
  ...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
623
604
  graph,
624
- bundles: attested,
605
+ bundles,
625
606
  diverged: true,
626
607
  });
627
608
  }
@@ -638,7 +619,7 @@ async function executeMigrationStatusCommand(
638
619
  }
639
620
 
640
621
  const edgeStatuses = deriveEdgeStatuses(graph, targetHash, contractHash, markerHash, mode);
641
- const entries = buildMigrationEntries(chain, attested, mode, markerHash, edgeStatuses);
622
+ const entries = buildMigrationEntries(chain, bundles, mode, markerHash, edgeStatuses);
642
623
 
643
624
  const pendingCount = edgeStatuses.filter((e) => e.status === 'pending').length;
644
625
  const appliedCount = edgeStatuses.filter((e) => e.status === 'applied').length;
@@ -646,7 +627,7 @@ async function executeMigrationStatusCommand(
646
627
  let summary: string;
647
628
  if (mode === 'online') {
648
629
  if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
649
- summary = `${attested.length} migration(s) on disk`;
630
+ summary = `${bundles.length} migration(s) on disk`;
650
631
  } else if (activeRefHash && markerHash !== undefined) {
651
632
  summary = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName!);
652
633
  } else if (pendingCount === 0) {
@@ -706,8 +687,7 @@ async function executeMigrationStatusCommand(
706
687
  ...(statusRefs.length > 0 ? { refs: statusRefs } : {}),
707
688
  ...ifDefined('pathDecision', pathDecision),
708
689
  graph,
709
- bundles: attested,
710
- ...(drafts.length > 0 ? { drafts } : {}),
690
+ bundles,
711
691
  edgeStatuses,
712
692
  ...ifDefined('activeRefHash', activeRefHash),
713
693
  ...ifDefined('activeRefName', activeRefName),
@@ -768,11 +748,6 @@ export function createMigrationStatusCommand(): Command {
768
748
  activeRefHash: statusResult.activeRefHash,
769
749
  activeRefName: statusResult.activeRefName,
770
750
  edgeStatuses: statusResult.edgeStatuses,
771
- draftEdges: statusResult.drafts?.map((d) => ({
772
- from: d.manifest.from,
773
- to: d.manifest.to,
774
- dirName: d.dirName,
775
- })),
776
751
  });
777
752
 
778
753
  const graphToRender =
@@ -1,4 +1,3 @@
1
- import { dirname, resolve } from 'node:path';
2
1
  import type { PrismaNextConfig } from '@prisma-next/config/config-types';
3
2
  import { ConfigValidationError, validateConfig } from '@prisma-next/config/config-validation';
4
3
  import {
@@ -6,7 +5,41 @@ import {
6
5
  errorConfigValidation,
7
6
  errorUnexpected,
8
7
  } from '@prisma-next/errors/control';
8
+ import { ifDefined } from '@prisma-next/utils/defined';
9
9
  import { loadConfig as loadConfigC12 } from 'c12';
10
+ import { dirname, resolve } from 'pathe';
11
+ import { finalizeConfig } from './config-path-validation';
12
+
13
+ async function loadValidatedConfig(configPath?: string): Promise<PrismaNextConfig> {
14
+ const cwd = process.cwd();
15
+ const resolvedConfigPath = configPath ? resolve(cwd, configPath) : undefined;
16
+ const configCwd = resolvedConfigPath ? dirname(resolvedConfigPath) : cwd;
17
+
18
+ const result = await loadConfigC12<PrismaNextConfig>({
19
+ name: 'prisma-next',
20
+ ...ifDefined('configFile', resolvedConfigPath),
21
+ cwd: configCwd,
22
+ });
23
+
24
+ // When a specific config file was requested, verify it was actually loaded
25
+ // (c12 falls back to searching by name if the specified file doesn't exist)
26
+ if (resolvedConfigPath && result.configFile !== resolvedConfigPath) {
27
+ throw errorConfigFileNotFound(resolvedConfigPath);
28
+ }
29
+
30
+ // Check if config is missing or empty (c12 may return empty object when file doesn't exist)
31
+ if (!result.config || Object.keys(result.config).length === 0) {
32
+ // Use c12's configFile if available, otherwise use explicit configPath, otherwise omit path
33
+ const displayPath = result.configFile || resolvedConfigPath || configPath;
34
+ throw errorConfigFileNotFound(displayPath);
35
+ }
36
+
37
+ // Validate config structure
38
+ validateConfig(result.config);
39
+
40
+ const loadedConfigDir = result.configFile ? dirname(result.configFile) : configCwd;
41
+ return finalizeConfig(result.config, loadedConfigDir);
42
+ }
10
43
 
11
44
  /**
12
45
  * Loads the Prisma Next config from a TypeScript file.
@@ -19,34 +52,7 @@ import { loadConfig as loadConfigC12 } from 'c12';
19
52
  */
20
53
  export async function loadConfig(configPath?: string): Promise<PrismaNextConfig> {
21
54
  try {
22
- const cwd = process.cwd();
23
- // Resolve config path to absolute path and set cwd to config directory when path is provided
24
- const resolvedConfigPath = configPath ? resolve(cwd, configPath) : undefined;
25
- const configCwd = resolvedConfigPath ? dirname(resolvedConfigPath) : cwd;
26
-
27
- const result = await loadConfigC12<PrismaNextConfig>({
28
- name: 'prisma-next',
29
- ...(resolvedConfigPath ? { configFile: resolvedConfigPath } : {}),
30
- cwd: configCwd,
31
- });
32
-
33
- // When a specific config file was requested, verify it was actually loaded
34
- // (c12 falls back to searching by name if the specified file doesn't exist)
35
- if (resolvedConfigPath && result.configFile !== resolvedConfigPath) {
36
- throw errorConfigFileNotFound(resolvedConfigPath);
37
- }
38
-
39
- // Check if config is missing or empty (c12 may return empty object when file doesn't exist)
40
- if (!result.config || Object.keys(result.config).length === 0) {
41
- // Use c12's configFile if available, otherwise use explicit configPath, otherwise omit path
42
- const displayPath = result.configFile || resolvedConfigPath || configPath;
43
- throw errorConfigFileNotFound(displayPath);
44
- }
45
-
46
- // Validate config structure
47
- validateConfig(result.config);
48
-
49
- return result.config;
55
+ return await loadValidatedConfig(configPath);
50
56
  } catch (error) {
51
57
  if (error instanceof ConfigValidationError) {
52
58
  throw errorConfigValidation(error.field, {
@@ -0,0 +1,75 @@
1
+ import {
2
+ type ContractSourceProvider,
3
+ normalizeContractConfig,
4
+ type PrismaNextConfig,
5
+ } from '@prisma-next/config/config-types';
6
+ import { ConfigValidationError } from '@prisma-next/config/config-validation';
7
+ import { getEmittedArtifactPaths } from '@prisma-next/emitter';
8
+ import { resolve } from 'pathe';
9
+
10
+ function throwValidation(field: string, why: string): never {
11
+ throw new ConfigValidationError(field, why);
12
+ }
13
+
14
+ function finalizeContractSource(
15
+ source: ContractSourceProvider,
16
+ configDir: string,
17
+ ): ContractSourceProvider {
18
+ const resolvedInputs = source.inputs?.map((input) => resolve(configDir, input));
19
+ if (resolvedInputs === undefined) {
20
+ return source;
21
+ }
22
+
23
+ return {
24
+ ...source,
25
+ inputs: resolvedInputs,
26
+ };
27
+ }
28
+
29
+ function validateNoOutputsAreInputs(
30
+ inputs: readonly string[] | undefined,
31
+ output: string | undefined,
32
+ ): void {
33
+ if (inputs === undefined || output === undefined) {
34
+ return;
35
+ }
36
+
37
+ let emittedArtifactPaths: ReturnType<typeof getEmittedArtifactPaths>;
38
+ try {
39
+ emittedArtifactPaths = getEmittedArtifactPaths(output);
40
+ } catch (error) {
41
+ throwValidation('contract.output', error instanceof Error ? error.message : String(error));
42
+ }
43
+
44
+ const emittedPaths = new Set([emittedArtifactPaths.jsonPath, emittedArtifactPaths.dtsPath]);
45
+
46
+ for (const input of inputs) {
47
+ if (emittedPaths.has(input)) {
48
+ throwValidation(
49
+ 'contract.source.inputs[]',
50
+ 'Config.contract.source.inputs must not include emitted artifact paths derived from contract.output',
51
+ );
52
+ }
53
+ }
54
+ }
55
+
56
+ export function finalizeConfig(config: PrismaNextConfig, configDir: string): PrismaNextConfig {
57
+ if (!config.contract) {
58
+ return config;
59
+ }
60
+
61
+ const contract = normalizeContractConfig(config.contract);
62
+ const source = finalizeContractSource(contract.source, configDir);
63
+ const output = resolve(configDir, contract.output);
64
+
65
+ validateNoOutputsAreInputs(source.inputs, output);
66
+
67
+ return {
68
+ ...config,
69
+ contract: {
70
+ ...contract,
71
+ source,
72
+ output,
73
+ },
74
+ };
75
+ }
@@ -496,8 +496,9 @@ class ControlClientImpl implements ControlClient {
496
496
  authoringContributions: stack.authoringContributions,
497
497
  codecLookup: stack.codecLookup,
498
498
  controlMutationDefaults: stack.controlMutationDefaults,
499
+ resolvedInputs: contractConfig.source.inputs ?? [],
499
500
  };
500
- const providerResult = await contractConfig.sourceProvider(sourceContext);
501
+ const providerResult = await contractConfig.source.load(sourceContext);
501
502
  if (!providerResult.ok) {
502
503
  onProgress?.({
503
504
  action: 'emit',