@prisma-next/cli 0.10.0 → 0.11.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-errors-Bw2GlweY.mjs +175 -0
  3. package/dist/cli-errors-Bw2GlweY.mjs.map +1 -0
  4. package/dist/cli.mjs +400 -13
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-Brv4qlfB.mjs → client-UnIveZxZ.mjs} +6 -5
  7. package/dist/client-UnIveZxZ.mjs.map +1 -0
  8. package/dist/{command-helpers-D3vL5yi8.mjs → command-helpers-CRfjbZRz.mjs} +109 -12
  9. package/dist/command-helpers-CRfjbZRz.mjs.map +1 -0
  10. package/dist/commands/contract-emit.d.mts.map +1 -1
  11. package/dist/commands/contract-emit.mjs +1 -1
  12. package/dist/commands/contract-infer.mjs +1 -1
  13. package/dist/commands/db-init.d.mts.map +1 -1
  14. package/dist/commands/db-init.mjs +47 -21
  15. package/dist/commands/db-init.mjs.map +1 -1
  16. package/dist/commands/db-schema.mjs +6 -10
  17. package/dist/commands/db-schema.mjs.map +1 -1
  18. package/dist/commands/db-sign.mjs +8 -12
  19. package/dist/commands/db-sign.mjs.map +1 -1
  20. package/dist/commands/db-update.d.mts.map +1 -1
  21. package/dist/commands/db-update.mjs +47 -19
  22. package/dist/commands/db-update.mjs.map +1 -1
  23. package/dist/commands/db-verify.mjs +1 -1
  24. package/dist/commands/migrate.d.mts +5 -1
  25. package/dist/commands/migrate.d.mts.map +1 -1
  26. package/dist/commands/migrate.mjs +47 -15
  27. package/dist/commands/migrate.mjs.map +1 -1
  28. package/dist/commands/migration-check.mjs +5 -8
  29. package/dist/commands/migration-check.mjs.map +1 -1
  30. package/dist/commands/migration-graph.d.mts +1 -1
  31. package/dist/commands/migration-graph.mjs +6 -10
  32. package/dist/commands/migration-graph.mjs.map +1 -1
  33. package/dist/commands/migration-list.mjs +5 -9
  34. package/dist/commands/migration-list.mjs.map +1 -1
  35. package/dist/commands/migration-log.d.mts.map +1 -1
  36. package/dist/commands/migration-log.mjs +7 -10
  37. package/dist/commands/migration-log.mjs.map +1 -1
  38. package/dist/commands/migration-new.mjs +6 -10
  39. package/dist/commands/migration-new.mjs.map +1 -1
  40. package/dist/commands/migration-plan.d.mts +2 -1
  41. package/dist/commands/migration-plan.d.mts.map +1 -1
  42. package/dist/commands/migration-plan.mjs +1 -1
  43. package/dist/commands/migration-show.d.mts +1 -1
  44. package/dist/commands/migration-show.mjs +9 -13
  45. package/dist/commands/migration-show.mjs.map +1 -1
  46. package/dist/commands/migration-status.d.mts +1 -1
  47. package/dist/commands/migration-status.d.mts.map +1 -1
  48. package/dist/commands/migration-status.mjs +37 -15
  49. package/dist/commands/migration-status.mjs.map +1 -1
  50. package/dist/commands/ref.d.mts +1 -1
  51. package/dist/commands/ref.d.mts.map +1 -1
  52. package/dist/commands/ref.mjs +41 -25
  53. package/dist/commands/ref.mjs.map +1 -1
  54. package/dist/{contract-emit-iynA3BCA.mjs → contract-emit-C6rlsljO.mjs} +7 -6
  55. package/dist/contract-emit-C6rlsljO.mjs.map +1 -0
  56. package/dist/{contract-emit-C3STUIBg.mjs → contract-emit-mqXmapxB.mjs} +22 -20
  57. package/dist/contract-emit-mqXmapxB.mjs.map +1 -0
  58. package/dist/{contract-infer-Cnj8G1E2.mjs → contract-infer-C4jxc1aZ.mjs} +9 -14
  59. package/dist/contract-infer-C4jxc1aZ.mjs.map +1 -0
  60. package/dist/{contract-space-aggregate-loader-pAc8CDfY.mjs → contract-space-aggregate-loader-CGakRlKM.mjs} +2 -2
  61. package/dist/{contract-space-aggregate-loader-pAc8CDfY.mjs.map → contract-space-aggregate-loader-CGakRlKM.mjs.map} +1 -1
  62. package/dist/{db-verify-D7cyH_zz.mjs → db-verify-1d8tDoFN.mjs} +9 -13
  63. package/dist/db-verify-1d8tDoFN.mjs.map +1 -0
  64. package/dist/exports/control-api.d.mts +1 -1
  65. package/dist/exports/control-api.mjs +2 -2
  66. package/dist/exports/index.mjs +2 -2
  67. package/dist/exports/init-output.mjs +1 -1
  68. package/dist/{framework-components-xFLFpZUO.mjs → framework-components-Bexd0f4E.mjs} +2 -2
  69. package/dist/{framework-components-xFLFpZUO.mjs.map → framework-components-Bexd0f4E.mjs.map} +1 -1
  70. package/dist/{global-flags-DGmw6Kqg.d.mts → global-flags-CdE7M0d9.d.mts} +4 -1
  71. package/dist/global-flags-CdE7M0d9.d.mts.map +1 -0
  72. package/dist/{graph-render-eJDcLWny.mjs → graph-render-BE8vmJ_7.mjs} +1 -1
  73. package/dist/{graph-render-eJDcLWny.mjs.map → graph-render-BE8vmJ_7.mjs.map} +1 -1
  74. package/dist/{init-eh2z5Tl6.mjs → init-ByoeQphC.mjs} +528 -627
  75. package/dist/init-ByoeQphC.mjs.map +1 -0
  76. package/dist/{inspect-live-schema-CWLK_lgs.mjs → inspect-live-schema-B1Q49RF0.mjs} +4 -4
  77. package/dist/{inspect-live-schema-CWLK_lgs.mjs.map → inspect-live-schema-B1Q49RF0.mjs.map} +1 -1
  78. package/dist/{migration-command-scaffold-CmXXC1UZ.mjs → migration-command-scaffold-oY4P1Qto.mjs} +4 -4
  79. package/dist/{migration-command-scaffold-CmXXC1UZ.mjs.map → migration-command-scaffold-oY4P1Qto.mjs.map} +1 -1
  80. package/dist/{migration-plan-CHyUlBV0.mjs → migration-plan-jdAHg_gK.mjs} +349 -94
  81. package/dist/migration-plan-jdAHg_gK.mjs.map +1 -0
  82. package/dist/{migration-types-D2FW63pr.d.mts → migration-types-BXWvz12q.d.mts} +1 -1
  83. package/dist/{migration-types-D2FW63pr.d.mts.map → migration-types-BXWvz12q.d.mts.map} +1 -1
  84. package/dist/{migrations-DyUf5lTt.mjs → migrations-B7n518mT.mjs} +11 -2
  85. package/dist/migrations-B7n518mT.mjs.map +1 -0
  86. package/dist/{output-B60Gw5fu.mjs → output-CUIdfYo5.mjs} +1 -1
  87. package/dist/{output-B60Gw5fu.mjs.map → output-CUIdfYo5.mjs.map} +1 -1
  88. package/dist/quick-reference-mongo.md +1 -1
  89. package/dist/quick-reference-postgres.md +1 -1
  90. package/dist/readme-mongo.md +35 -0
  91. package/dist/readme-postgres.md +34 -0
  92. package/dist/ref-advancement-DRh5Nquq.mjs +50 -0
  93. package/dist/ref-advancement-DRh5Nquq.mjs.map +1 -0
  94. package/dist/{terminal-ui-XtOQsqe9.mjs → terminal-ui-BiB_8KNo.mjs} +131 -24
  95. package/dist/terminal-ui-BiB_8KNo.mjs.map +1 -0
  96. package/dist/{types-0aS865QN.d.mts → types-UWB2-rrw.d.mts} +12 -4
  97. package/dist/types-UWB2-rrw.d.mts.map +1 -0
  98. package/dist/{verify-D7ypCCe6.mjs → verify-C5UvbrF1.mjs} +2 -2
  99. package/dist/{verify-D7ypCCe6.mjs.map → verify-C5UvbrF1.mjs.map} +1 -1
  100. package/package.json +20 -18
  101. package/src/cli.ts +42 -0
  102. package/src/commands/contract-emit.ts +23 -11
  103. package/src/commands/contract-infer.ts +7 -7
  104. package/src/commands/db-init.ts +61 -7
  105. package/src/commands/db-schema.ts +4 -4
  106. package/src/commands/db-sign.ts +4 -4
  107. package/src/commands/db-update.ts +58 -5
  108. package/src/commands/db-verify.ts +5 -5
  109. package/src/commands/init/detect-package-manager.ts +15 -0
  110. package/src/commands/init/errors.ts +33 -2
  111. package/src/commands/init/hygiene-gitattributes.ts +2 -2
  112. package/src/commands/init/index.ts +15 -6
  113. package/src/commands/init/init.ts +61 -32
  114. package/src/commands/init/inputs.ts +82 -5
  115. package/src/commands/init/output.ts +1 -1
  116. package/src/commands/init/{agent-skill-install.ts → skill-install.ts} +42 -31
  117. package/src/commands/init/templates/code-templates.ts +26 -24
  118. package/src/commands/init/templates/env.ts +8 -1
  119. package/src/commands/init/templates/quick-reference-mongo.md +1 -1
  120. package/src/commands/init/templates/quick-reference-postgres.md +1 -1
  121. package/src/commands/init/templates/readme-mongo.md +35 -0
  122. package/src/commands/init/templates/readme-postgres.md +34 -0
  123. package/src/commands/init/templates/readme.ts +62 -0
  124. package/src/commands/migrate.ts +77 -10
  125. package/src/commands/migration-check.ts +4 -4
  126. package/src/commands/migration-graph.ts +4 -4
  127. package/src/commands/migration-list.ts +4 -4
  128. package/src/commands/migration-log.ts +6 -5
  129. package/src/commands/migration-new.ts +4 -4
  130. package/src/commands/migration-plan.ts +369 -132
  131. package/src/commands/migration-show.ts +4 -4
  132. package/src/commands/migration-status.ts +49 -6
  133. package/src/commands/ref.ts +54 -14
  134. package/src/control-api/operations/apply-aggregate.ts +1 -0
  135. package/src/control-api/operations/contract-emit.ts +7 -4
  136. package/src/control-api/types.ts +7 -0
  137. package/src/utils/cli-errors.ts +177 -0
  138. package/src/utils/command-helpers.ts +14 -6
  139. package/src/utils/formatters/migrations.ts +25 -0
  140. package/src/utils/global-flags.ts +105 -16
  141. package/src/utils/is-ci.ts +18 -0
  142. package/src/utils/plan-resolution.ts +257 -0
  143. package/src/utils/ref-advancement.ts +68 -0
  144. package/src/utils/telemetry.ts +141 -0
  145. package/src/utils/terminal-ui.ts +44 -23
  146. package/dist/cli-errors-CF60g2cG.mjs +0 -71
  147. package/dist/cli-errors-CF60g2cG.mjs.map +0 -1
  148. package/dist/cli-errors-DdcjVLJV.d.mts +0 -3
  149. package/dist/client-Brv4qlfB.mjs.map +0 -1
  150. package/dist/command-helpers-D3vL5yi8.mjs.map +0 -1
  151. package/dist/contract-emit-C3STUIBg.mjs.map +0 -1
  152. package/dist/contract-emit-iynA3BCA.mjs.map +0 -1
  153. package/dist/contract-infer-Cnj8G1E2.mjs.map +0 -1
  154. package/dist/db-verify-D7cyH_zz.mjs.map +0 -1
  155. package/dist/errors-Cw6kyTyV.mjs +0 -56
  156. package/dist/errors-Cw6kyTyV.mjs.map +0 -1
  157. package/dist/global-flags-DGmw6Kqg.d.mts.map +0 -1
  158. package/dist/helpers-eqdN8tH6.mjs +0 -25
  159. package/dist/helpers-eqdN8tH6.mjs.map +0 -1
  160. package/dist/init-eh2z5Tl6.mjs.map +0 -1
  161. package/dist/migration-plan-CHyUlBV0.mjs.map +0 -1
  162. package/dist/migrations-DyUf5lTt.mjs.map +0 -1
  163. package/dist/result-handler-Bm_6dDYg.mjs +0 -25
  164. package/dist/result-handler-Bm_6dDYg.mjs.map +0 -1
  165. package/dist/terminal-ui-XtOQsqe9.mjs.map +0 -1
  166. package/dist/types-0aS865QN.d.mts.map +0 -1
package/src/cli.ts CHANGED
@@ -27,6 +27,7 @@ import { setCommandDescriptions } from './utils/command-helpers';
27
27
  import { formatCommandHelp, formatRootHelp } from './utils/formatters/help';
28
28
  import { parseGlobalFlags } from './utils/global-flags';
29
29
  import { suggestCommands } from './utils/suggest-command';
30
+ import { fireTelemetryFromPreAction } from './utils/telemetry';
30
31
 
31
32
  /**
32
33
  * Lookup table mapping removed subcommands to their replacement verbs.
@@ -68,6 +69,21 @@ const program = new Command();
68
69
 
69
70
  program.name('prisma-next').description('Prisma Next CLI').version(packageJson.version);
70
71
 
72
+ // Telemetry hook — fires at command start, before the action body
73
+ // runs. Synchronous by construction: `fireTelemetryFromPreAction`
74
+ // resolves gates (cheap), then `fork()`s the detached sender. The
75
+ // fork is enqueued before the action body runs at all, so the child
76
+ // survives even when the action throws synchronously. The try/catch
77
+ // is defence-in-depth — `runTelemetry` already swallows every failure
78
+ // mode internally and returns an outcome instead of throwing.
79
+ program.hook('preAction', (_thisCommand, actionCommand) => {
80
+ try {
81
+ fireTelemetryFromPreAction(actionCommand);
82
+ } catch {
83
+ // defence-in-depth — runTelemetry already swallows internally.
84
+ }
85
+ });
86
+
71
87
  // Override version option description to match capitalization style
72
88
  const versionOption = program.options.find((opt) => opt.flags.includes('--version'));
73
89
  if (versionOption) {
@@ -302,6 +318,32 @@ program.addCommand(dbCommand);
302
318
  program.addCommand(migrationCommand);
303
319
  program.addCommand(refCommand);
304
320
 
321
+ // Test-only hidden command used by `cli-telemetry`'s `cli-e2e.test.ts`
322
+ // to verify that telemetry still lands when a CLI command crashes
323
+ // mid-execution. The preAction hook is synchronous and `fork()`s the
324
+ // detached sender before this action body runs; the small sleep
325
+ // gives the IPC `child.send()` a tick to flush before the throw
326
+ // triggers commander's `exitOverride` and `process.exit(1)`. Hidden
327
+ // from help; underscore prefix marks it as internal. Doesn't depend
328
+ // on any project state, so it runs in any tempdir.
329
+ //
330
+ // Gated behind `PRISMA_NEXT_ENABLE_TEST_COMMANDS=1` so the command is
331
+ // not even registered (and therefore not invocable) in shipped
332
+ // binaries. `hidden: true` only filters the help output; without this
333
+ // env gate the command would still be callable from production. The
334
+ // e2e suite sets the env var when it spawns the CLI.
335
+ const TELEMETRY_CRASH_TEST_SLEEP_MS = 200;
336
+ if (process.env['PRISMA_NEXT_ENABLE_TEST_COMMANDS'] === '1') {
337
+ const telemetryCrashTestCommand = new Command('__telemetry-crash-test')
338
+ .description('Internal: deliberately throw for the telemetry e2e suite.')
339
+ .action(async () => {
340
+ await new Promise((settle) => setTimeout(settle, TELEMETRY_CRASH_TEST_SLEEP_MS));
341
+ throw new Error('__telemetry-crash-test: intentional crash for e2e coverage');
342
+ });
343
+ telemetryCrashTestCommand.configureHelp({ visibleCommands: () => [] });
344
+ program.addCommand(telemetryCrashTestCommand, { hidden: true });
345
+ }
346
+
305
347
  // Create help command
306
348
  const helpCommand = new Command('help')
307
349
  .description('Show usage instructions')
@@ -3,7 +3,7 @@ import { errorContractConfigMissing } from '@prisma-next/errors/control';
3
3
  import { ifDefined } from '@prisma-next/utils/defined';
4
4
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
5
5
  import { Command } from 'commander';
6
- import { dirname, relative, resolve } from 'pathe';
6
+ import { dirname, join, relative, resolve } from 'pathe';
7
7
  import { loadConfig } from '../config-loader';
8
8
  import { executeContractEmit } from '../control-api/operations/contract-emit';
9
9
  import type { ContractEmitResult } from '../control-api/types';
@@ -20,13 +20,14 @@ import {
20
20
  } from '../utils/formatters/emit';
21
21
  import { formatStyledHeader, formatSuccessMessage } from '../utils/formatters/styled';
22
22
  import type { CommonCommandOptions } from '../utils/global-flags';
23
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
23
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
24
24
  import { createProgressAdapter } from '../utils/progress-adapter';
25
25
  import { handleResult } from '../utils/result-handler';
26
- import { TerminalUI } from '../utils/terminal-ui';
26
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
27
27
 
28
28
  interface ContractEmitOptions extends CommonCommandOptions {
29
29
  readonly config?: string;
30
+ readonly outputPath?: string;
30
31
  }
31
32
 
32
33
  interface HeaderPaths {
@@ -43,6 +44,7 @@ interface HeaderPaths {
43
44
  */
44
45
  async function resolveHeaderPaths(
45
46
  configOption: string | undefined,
47
+ outputPath: string | undefined,
46
48
  ): Promise<Result<HeaderPaths, CliStructuredError>> {
47
49
  const displayConfigPath = configOption
48
50
  ? relative(process.cwd(), resolve(configOption))
@@ -62,7 +64,10 @@ async function resolveHeaderPaths(
62
64
  );
63
65
  }
64
66
 
65
- if (!config.contract?.output) {
67
+ const effectiveJsonPath =
68
+ outputPath !== undefined ? join(outputPath, 'contract.json') : config.contract?.output;
69
+
70
+ if (!effectiveJsonPath) {
66
71
  return notOk(
67
72
  errorContractConfigMissing({
68
73
  why: 'Config.contract.output is required for emit. Define it in your config: contract: { source: ..., output: ... }',
@@ -71,9 +76,8 @@ async function resolveHeaderPaths(
71
76
  }
72
77
 
73
78
  try {
74
- const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = getEmittedArtifactPaths(
75
- config.contract.output,
76
- );
79
+ const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } =
80
+ getEmittedArtifactPaths(effectiveJsonPath);
77
81
  return ok({ displayConfigPath, outputJsonPath, outputDtsPath });
78
82
  } catch (error) {
79
83
  return notOk(
@@ -90,7 +94,9 @@ async function executeContractEmitCommand(
90
94
  ui: TerminalUI,
91
95
  startTime: number,
92
96
  ): Promise<Result<EmitContractResult, CliStructuredError>> {
93
- const headerPathsResult = await resolveHeaderPaths(options.config);
97
+ const outputPath = options.outputPath !== undefined ? resolve(options.outputPath) : undefined;
98
+
99
+ const headerPathsResult = await resolveHeaderPaths(options.config, outputPath);
94
100
  if (!headerPathsResult.ok) {
95
101
  return headerPathsResult;
96
102
  }
@@ -117,7 +123,11 @@ async function executeContractEmitCommand(
117
123
 
118
124
  let result: ContractEmitResult;
119
125
  try {
120
- result = await executeContractEmit({ configPath, onProgress });
126
+ result = await executeContractEmit({
127
+ configPath,
128
+ onProgress,
129
+ ...ifDefined('outputPath', outputPath),
130
+ });
121
131
  } catch (error) {
122
132
  if (CliStructuredError.is(error)) {
123
133
  return notOk(error);
@@ -155,12 +165,14 @@ export function createContractEmitCommand(): Command {
155
165
  setCommandExamples(command, [
156
166
  'prisma-next contract emit',
157
167
  'prisma-next contract emit --config ./custom-config.ts',
168
+ 'prisma-next contract emit --output-path ./generated',
158
169
  ]);
159
170
  addGlobalOptions(command)
160
171
  .option('--config <path>', 'Path to prisma-next.config.ts')
172
+ .option('--output-path <dir>', 'Directory to write contract.json and contract.d.ts into')
161
173
  .action(async (options: ContractEmitOptions) => {
162
- const flags = parseGlobalFlags(options);
163
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
174
+ const flags = parseGlobalFlagsOrExit(options);
175
+ const ui = createTerminalUI(flags);
164
176
  const startTime = Date.now();
165
177
 
166
178
  const result = await executeContractEmitCommand(options, flags, ui, startTime);
@@ -10,9 +10,9 @@ import {
10
10
  setCommandDescriptions,
11
11
  setCommandExamples,
12
12
  } from '../utils/command-helpers';
13
- import { parseGlobalFlags } from '../utils/global-flags';
13
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
14
14
  import { handleResult } from '../utils/result-handler';
15
- import { TerminalUI } from '../utils/terminal-ui';
15
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
16
16
  import { resolveContractInferOutputPath } from './contract-infer-paths';
17
17
  import {
18
18
  type InspectLiveSchemaOptions,
@@ -39,10 +39,10 @@ interface ContractInferSuccessResult {
39
39
 
40
40
  async function executeContractInferCommand(
41
41
  options: ContractInferOptions,
42
+ flags: GlobalFlags,
42
43
  ui: TerminalUI,
43
44
  startTime: number,
44
45
  ): Promise<Result<ContractInferSuccessResult, CliStructuredError>> {
45
- const flags = parseGlobalFlags(options);
46
46
  const inspectResult = await inspectLiveSchema(options, flags, ui, startTime, {
47
47
  commandName: 'contract infer',
48
48
  description: 'Infer a PSL contract from the live database schema',
@@ -104,7 +104,7 @@ export function createContractInferCommand(): Command {
104
104
  );
105
105
  setCommandExamples(command, [
106
106
  'prisma-next contract infer --db $DATABASE_URL',
107
- 'prisma-next contract infer --db $DATABASE_URL --output ./prisma/contract.prisma',
107
+ 'prisma-next contract infer --db $DATABASE_URL --output ./src/prisma/contract.prisma',
108
108
  'prisma-next contract infer --db $DATABASE_URL --json',
109
109
  ]);
110
110
  addGlobalOptions(command)
@@ -112,11 +112,11 @@ export function createContractInferCommand(): Command {
112
112
  .option('--config <path>', 'Path to prisma-next.config.ts')
113
113
  .option('--output <path>', 'Write the inferred PSL contract to the specified path')
114
114
  .action(async (options: ContractInferOptions) => {
115
- const flags = parseGlobalFlags(options);
116
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
115
+ const flags = parseGlobalFlagsOrExit(options);
116
+ const ui = createTerminalUI(flags);
117
117
  const startTime = Date.now();
118
118
 
119
- const result = await executeContractInferCommand(options, ui, startTime);
119
+ const result = await executeContractInferCommand(options, flags, ui, startTime);
120
120
  const exitCode = handleResult(result, flags, ui, (value) => {
121
121
  if (flags.json) {
122
122
  ui.output(JSON.stringify(value, null, 2));
@@ -1,3 +1,4 @@
1
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
1
2
  import { ifDefined } from '@prisma-next/utils/defined';
2
3
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
3
4
  import { Command } from 'commander';
@@ -10,6 +11,7 @@ import {
10
11
  errorRunnerFailed,
11
12
  errorRuntime,
12
13
  errorUnexpected,
14
+ mapMigrationToolsError,
13
15
  } from '../utils/cli-errors';
14
16
  import type { MigrationCommandOptions } from '../utils/command-helpers';
15
17
  import {
@@ -24,15 +26,23 @@ import {
24
26
  formatMigrationPlanOutput,
25
27
  type MigrationCommandResult,
26
28
  } from '../utils/formatters/migrations';
27
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
29
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
28
30
  import {
29
31
  addMigrationCommandOptions,
30
32
  prepareMigrationContext,
31
33
  } from '../utils/migration-command-scaffold';
34
+ import {
35
+ buildRefAdvancementFields,
36
+ computeRefAdvancementName,
37
+ type RefAdvancementFields,
38
+ readContractIR,
39
+ } from '../utils/ref-advancement';
32
40
  import { handleResult } from '../utils/result-handler';
33
- import { TerminalUI } from '../utils/terminal-ui';
41
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
34
42
 
35
- type DbInitOptions = MigrationCommandOptions;
43
+ interface DbInitOptions extends MigrationCommandOptions {
44
+ readonly advanceRef?: string;
45
+ }
36
46
 
37
47
  /**
38
48
  * Maps a DbInitFailure to a CliStructuredError for consistent error handling.
@@ -80,9 +90,17 @@ function mapDbInitFailure(failure: DbInitFailure): CliStructuredError {
80
90
  }
81
91
 
82
92
  if (failure.code === 'RUNNER_FAILED') {
93
+ const runnerCode =
94
+ typeof failure.meta?.['runnerErrorCode'] === 'string'
95
+ ? failure.meta['runnerErrorCode']
96
+ : undefined;
97
+ const fix =
98
+ runnerCode === 'LEGACY_MARKER_SHAPE'
99
+ ? 'Legacy marker-table shape detected. Drop `prisma_contract.marker` (Postgres) or `_prisma_marker` (SQLite) and re-run `prisma-next db init` to recreate it with the current per-space schema.'
100
+ : 'Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`';
83
101
  return errorRunnerFailed(failure.summary, {
84
102
  why: failure.why ?? 'Migration runner failed',
85
- fix: 'Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`',
103
+ fix,
86
104
  ...(failure.meta
87
105
  ? { meta: { code: 'RUNNER_FAILED', ...failure.meta } }
88
106
  : { meta: { code: 'RUNNER_FAILED' } }),
@@ -120,7 +138,7 @@ async function executeDbInitCommand(
120
138
  // per-space precheck + marker-check helpers are no longer needed at
121
139
  // this surface. Marker-vs-on-disk drift surfaces through the planner's
122
140
  // graph-walk strategy.
123
- const { migrationsDir } = resolveMigrationPaths(options.config, config);
141
+ const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
124
142
 
125
143
  try {
126
144
  await client.connect(dbConnection);
@@ -137,6 +155,39 @@ async function executeDbInitCommand(
137
155
  return notOk(mapDbInitFailure(result.failure));
138
156
  }
139
157
 
158
+ const advancementHash =
159
+ result.value.mode === 'apply'
160
+ ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)
161
+ : result.value.destination.storageHash;
162
+
163
+ let refAdvancementFields: RefAdvancementFields = {
164
+ advancedRef: null,
165
+ plannedAdvanceRef: null,
166
+ };
167
+ if (
168
+ computeRefAdvancementName({
169
+ ...ifDefined('advanceRef', options.advanceRef),
170
+ ...ifDefined('db', options.db),
171
+ }) !== null
172
+ ) {
173
+ try {
174
+ const contractIR = await readContractIR(contractJson, contractPathAbsolute);
175
+ refAdvancementFields = await buildRefAdvancementFields({
176
+ ...ifDefined('advanceRef', options.advanceRef),
177
+ ...ifDefined('db', options.db),
178
+ refsDir,
179
+ contractIR,
180
+ mode: result.value.mode,
181
+ hash: advancementHash,
182
+ });
183
+ } catch (error) {
184
+ if (MigrationToolsError.is(error)) {
185
+ return notOk(mapMigrationToolsError(error));
186
+ }
187
+ throw error;
188
+ }
189
+ }
190
+
140
191
  // Convert success result to CLI output format
141
192
  const dbInitResult: MigrationCommandResult = {
142
193
  ok: true,
@@ -171,6 +222,8 @@ async function executeDbInitCommand(
171
222
  }
172
223
  : {}),
173
224
  ...ifDefined('perSpace', result.value.perSpace),
225
+ advancedRef: refAdvancementFields.advancedRef,
226
+ plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,
174
227
  summary: result.value.summary,
175
228
  timings: { total: Date.now() - startTime },
176
229
  };
@@ -221,11 +274,12 @@ export function createDbInitCommand(): Command {
221
274
  'prisma-next db init --db $DATABASE_URL --dry-run',
222
275
  ]);
223
276
  addMigrationCommandOptions(command);
277
+ command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');
224
278
  command.action(async (options: DbInitOptions) => {
225
- const flags = parseGlobalFlags(options);
279
+ const flags = parseGlobalFlagsOrExit(options);
226
280
  const startTime = Date.now();
227
281
 
228
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
282
+ const ui = createTerminalUI(flags);
229
283
 
230
284
  const result = await executeDbInitCommand(options, flags, ui, startTime);
231
285
 
@@ -6,9 +6,9 @@ import {
6
6
  setCommandExamples,
7
7
  } from '../utils/command-helpers';
8
8
  import { formatIntrospectJson, formatIntrospectOutput } from '../utils/formatters/verify';
9
- import { parseGlobalFlags } from '../utils/global-flags';
9
+ import { parseGlobalFlagsOrExit } from '../utils/global-flags';
10
10
  import { handleResult } from '../utils/result-handler';
11
- import { TerminalUI } from '../utils/terminal-ui';
11
+ import { createTerminalUI } from '../utils/terminal-ui';
12
12
  import {
13
13
  type InspectLiveSchemaOptions,
14
14
  type InspectLiveSchemaResult,
@@ -46,8 +46,8 @@ export function createDbSchemaCommand(): Command {
46
46
  .option('--db <url>', 'Database connection string')
47
47
  .option('--config <path>', 'Path to prisma-next.config.ts')
48
48
  .action(async (options: InspectLiveSchemaOptions) => {
49
- const flags = parseGlobalFlags(options);
50
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
49
+ const flags = parseGlobalFlagsOrExit(options);
50
+ const ui = createTerminalUI(flags);
51
51
  const startTime = Date.now();
52
52
 
53
53
  const result = await inspectLiveSchema(options, flags, ui, startTime, {
@@ -40,10 +40,10 @@ import {
40
40
  formatSignOutput,
41
41
  } from '../utils/formatters/verify';
42
42
  import type { CommonCommandOptions } from '../utils/global-flags';
43
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
43
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
44
44
  import { createProgressAdapter } from '../utils/progress-adapter';
45
45
  import { handleResult } from '../utils/result-handler';
46
- import { TerminalUI } from '../utils/terminal-ui';
46
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
47
47
 
48
48
  interface DbSignOptions extends CommonCommandOptions {
49
49
  readonly db?: string;
@@ -270,7 +270,7 @@ export function createDbSignCommand(): Command {
270
270
  'Contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
271
271
  )
272
272
  .action(async (positionalContract: string | undefined, options: DbSignOptions) => {
273
- const flags = parseGlobalFlags(options);
273
+ const flags = parseGlobalFlagsOrExit(options);
274
274
 
275
275
  if (positionalContract && options.contract) {
276
276
  process.stderr.write(
@@ -280,7 +280,7 @@ export function createDbSignCommand(): Command {
280
280
  return;
281
281
  }
282
282
 
283
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
283
+ const ui = createTerminalUI(flags);
284
284
 
285
285
  const result = await executeDbSignCommand(positionalContract, options, flags, ui);
286
286
 
@@ -33,16 +33,23 @@ import {
33
33
  formatMigrationPlanOutput,
34
34
  type MigrationCommandResult,
35
35
  } from '../utils/formatters/migrations';
36
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
36
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
37
37
  import {
38
38
  addMigrationCommandOptions,
39
39
  prepareMigrationContext,
40
40
  } from '../utils/migration-command-scaffold';
41
+ import {
42
+ buildRefAdvancementFields,
43
+ computeRefAdvancementName,
44
+ type RefAdvancementFields,
45
+ readContractIR,
46
+ } from '../utils/ref-advancement';
41
47
  import { handleResult } from '../utils/result-handler';
42
- import { TerminalUI } from '../utils/terminal-ui';
48
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
43
49
 
44
50
  interface DbUpdateOptions extends MigrationCommandOptions {
45
51
  readonly to?: string;
52
+ readonly advanceRef?: string;
46
53
  }
47
54
 
48
55
  /**
@@ -54,9 +61,17 @@ function mapDbUpdateFailure(failure: DbUpdateFailure): CliStructuredError {
54
61
  }
55
62
 
56
63
  if (failure.code === 'RUNNER_FAILED') {
64
+ const runnerCode =
65
+ typeof failure.meta?.['runnerErrorCode'] === 'string'
66
+ ? failure.meta['runnerErrorCode']
67
+ : undefined;
68
+ const fix =
69
+ runnerCode === 'LEGACY_MARKER_SHAPE'
70
+ ? 'Legacy marker-table shape detected. Drop `prisma_contract.marker` (Postgres) or `_prisma_marker` (SQLite) and re-run `prisma-next db init` to recreate it with the current per-space schema.'
71
+ : 'Inspect the reported conflict, reconcile schema drift if needed, then re-run `prisma-next db update`';
57
72
  return errorRunnerFailed(failure.summary, {
58
73
  why: failure.why ?? 'Migration runner failed',
59
- fix: 'Inspect the reported conflict, reconcile schema drift if needed, then re-run `prisma-next db update`',
74
+ fix,
60
75
  ...ifDefined('meta', failure.meta),
61
76
  });
62
77
  }
@@ -93,6 +108,7 @@ async function executeDbUpdateCommand(
93
108
  }
94
109
  const { client, config, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;
95
110
  let { contractJson } = ctxResult.value;
111
+ let contractJsonPathForSnapshot = contractPathAbsolute;
96
112
  const { migrationsDir, appMigrationsDir, refsDir } = resolveMigrationPaths(
97
113
  options.config,
98
114
  config,
@@ -122,6 +138,7 @@ async function executeDbUpdateCommand(
122
138
  const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
123
139
  const raw = await readFile(endContractPath, 'utf-8');
124
140
  contractJson = JSON.parse(raw) as Record<string, unknown>;
141
+ contractJsonPathForSnapshot = endContractPath;
125
142
  } catch (error) {
126
143
  if (MigrationToolsError.is(error)) {
127
144
  return notOk(mapMigrationToolsError(error));
@@ -149,6 +166,39 @@ async function executeDbUpdateCommand(
149
166
  return notOk(mapDbUpdateFailure(result.failure));
150
167
  }
151
168
 
169
+ const advancementHash =
170
+ result.value.mode === 'apply'
171
+ ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)
172
+ : result.value.destination.storageHash;
173
+
174
+ let refAdvancementFields: RefAdvancementFields = {
175
+ advancedRef: null,
176
+ plannedAdvanceRef: null,
177
+ };
178
+ if (
179
+ computeRefAdvancementName({
180
+ ...ifDefined('advanceRef', options.advanceRef),
181
+ ...ifDefined('db', options.db),
182
+ }) !== null
183
+ ) {
184
+ try {
185
+ const contractIR = await readContractIR(contractJson, contractJsonPathForSnapshot);
186
+ refAdvancementFields = await buildRefAdvancementFields({
187
+ ...ifDefined('advanceRef', options.advanceRef),
188
+ ...ifDefined('db', options.db),
189
+ refsDir,
190
+ contractIR,
191
+ mode: result.value.mode,
192
+ hash: advancementHash,
193
+ });
194
+ } catch (error) {
195
+ if (MigrationToolsError.is(error)) {
196
+ return notOk(mapMigrationToolsError(error));
197
+ }
198
+ throw error;
199
+ }
200
+ }
201
+
152
202
  // Convert success result to CLI output format
153
203
  const dbUpdateResult: MigrationCommandResult = {
154
204
  ok: true,
@@ -185,6 +235,8 @@ async function executeDbUpdateCommand(
185
235
  : undefined,
186
236
  ),
187
237
  ...ifDefined('perSpace', result.value.perSpace),
238
+ advancedRef: refAdvancementFields.advancedRef,
239
+ plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,
188
240
  summary: result.value.summary,
189
241
  timings: { total: Date.now() - startTime },
190
242
  };
@@ -237,11 +289,12 @@ export function createDbUpdateCommand(): Command {
237
289
  '--to <contract>',
238
290
  'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
239
291
  );
292
+ command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');
240
293
  command.action(async (options: DbUpdateOptions) => {
241
- const flags = parseGlobalFlags(options);
294
+ const flags = parseGlobalFlagsOrExit(options);
242
295
  const startTime = Date.now();
243
296
 
244
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
297
+ const ui = createTerminalUI(flags);
245
298
 
246
299
  let result = await executeDbUpdateCommand(options, flags, ui, startTime);
247
300
 
@@ -47,10 +47,10 @@ import {
47
47
  formatVerifyOutput,
48
48
  } from '../utils/formatters/verify';
49
49
  import type { CommonCommandOptions } from '../utils/global-flags';
50
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
50
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
51
51
  import { createProgressAdapter } from '../utils/progress-adapter';
52
52
  import { handleResult } from '../utils/result-handler';
53
- import { TerminalUI } from '../utils/terminal-ui';
53
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
54
54
 
55
55
  interface DbVerifyOptions extends CommonCommandOptions {
56
56
  readonly db?: string;
@@ -331,7 +331,7 @@ function wrapVerifyError(
331
331
  contractPathAbsolute: string,
332
332
  modeLabel: string,
333
333
  ): Result<never, CliStructuredError> {
334
- if (error instanceof CliStructuredError) {
334
+ if (CliStructuredError.is(error)) {
335
335
  return notOk(error);
336
336
  }
337
337
  if (error instanceof ContractValidationError) {
@@ -529,8 +529,8 @@ export function createDbVerifyCommand(): Command {
529
529
  false,
530
530
  )
531
531
  .action(async (options: DbVerifyOptions) => {
532
- const flags = parseGlobalFlags(options);
533
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
532
+ const flags = parseGlobalFlagsOrExit(options);
533
+ const ui = createTerminalUI(flags);
534
534
 
535
535
  const modeResult = resolveDbVerifyMode(options);
536
536
  if (!modeResult.ok) {
@@ -56,6 +56,21 @@ export function formatRunCommand(pm: PackageManager, bin: string, args: string):
56
56
  return `${pm} ${bin} ${args}`;
57
57
  }
58
58
 
59
+ export function formatRunScriptCommand(pm: PackageManager, scriptName: string): string {
60
+ switch (pm) {
61
+ case 'deno':
62
+ return `deno task ${scriptName}`;
63
+ case 'bun':
64
+ return `bun run ${scriptName}`;
65
+ case 'pnpm':
66
+ return `pnpm run ${scriptName}`;
67
+ case 'yarn':
68
+ return `yarn run ${scriptName}`;
69
+ default:
70
+ return `npm run ${scriptName}`;
71
+ }
72
+ }
73
+
59
74
  export function formatAddArgs(pm: PackageManager, packages: string[]): string[] {
60
75
  if (pm === 'deno') {
61
76
  return ['add', ...packages.map((p) => `npm:${p}`)];
@@ -69,6 +69,37 @@ export function errorInitInvalidFlagValue(options: {
69
69
  });
70
70
  }
71
71
 
72
+ /**
73
+ * `--authoring` and `--schema-path` disagree on file extension (e.g. PSL
74
+ * authoring with a `.ts` path). Surfaces before any scaffold files are
75
+ * written so the project tree stays untouched.
76
+ */
77
+ export function errorInitAuthoringSchemaPathMismatch(options: {
78
+ readonly authoring: 'psl' | 'typescript';
79
+ readonly schemaPath: string;
80
+ readonly actualExtension: string;
81
+ readonly expectedExtension: string;
82
+ }): CliStructuredError {
83
+ const expectedAuthoring = options.expectedExtension === '.ts' ? 'typescript' : 'psl';
84
+ return new CliStructuredError('5014', 'Authoring and schema path do not match', {
85
+ domain: 'CLI',
86
+ why:
87
+ `\`--authoring ${options.authoring}\` requires a schema file ending in ${options.expectedExtension}, ` +
88
+ `but \`--schema-path ${options.schemaPath}\` ends in ${options.actualExtension}.`,
89
+ fix:
90
+ `Use a matching pair, for example \`--authoring ${expectedAuthoring} --schema-path <path>${options.expectedExtension}\`, ` +
91
+ 'or change `--authoring` to match the path you supplied. ' +
92
+ 'You can also omit `--schema-path` to use the default for the chosen authoring.',
93
+ docsUrl: 'https://prisma-next.dev/docs/cli/init',
94
+ meta: {
95
+ authoring: options.authoring,
96
+ schemaPath: options.schemaPath,
97
+ actualExtension: options.actualExtension,
98
+ expectedExtension: options.expectedExtension,
99
+ },
100
+ });
101
+ }
102
+
72
103
  /**
73
104
  * The user cancelled an interactive prompt (Ctrl-C, escape, declined a
74
105
  * selection). Distinct from `errorInitReinitNeedsForce` because that path
@@ -238,7 +269,7 @@ export function errorInitEmitFailed(options: {
238
269
  }
239
270
 
240
271
  /**
241
- * The project-level agent-skill install (`npx skills add
272
+ * The project-level skills install (`npx skills add
242
273
  * prisma/prisma-next#v<version>`) failed after a successful dependency
243
274
  * install + emit. The project's scaffold remains on disk; the user
244
275
  * can either fix the underlying issue (network, registry, PATH) and
@@ -260,7 +291,7 @@ export function errorInitSkillInstallFailed(options: {
260
291
  'Either:\n' +
261
292
  ` - Re-run \`prisma-next init --no-skill${options.filesWritten.length > 0 ? ' --force' : ''}\` to skip the skill install for this run, or\n` +
262
293
  ` - Fix the underlying issue (network, npm registry, \`npx skills\` on PATH) and install manually:\n ${options.skillInstallCommand}`,
263
- docsUrl: 'https://prisma-next.dev/docs/cli/init#agent-skill',
294
+ docsUrl: 'https://prisma-next.dev/docs/cli/init#skills',
264
295
  meta: {
265
296
  filesWritten: options.filesWritten,
266
297
  skillInstallCommand: options.skillInstallCommand,
@@ -20,7 +20,7 @@ import type { TargetId } from './templates/code-templates';
20
20
  * `db/contract.json linguist-generated` — not the workspace-glob form
21
21
  * `<glob>/contract.json` (which would over-match any unrelated
22
22
  * `contract.json` the user has elsewhere) and not the absolute
23
- * `prisma/contract.json` (which would silently break for a non-default
23
+ * `DEFAULT_CONTRACT_SOURCE_DIR/contract.json` (which would silently break for a non-default
24
24
  * schema path).
25
25
  */
26
26
  const ARTEFACT_FILENAMES: readonly string[] = [
@@ -59,7 +59,7 @@ export function requiredGitattributesLines(
59
59
  *
60
60
  * Equivalence is exact-line: a user-customised line like
61
61
  * `prisma/*.json linguist-generated` is *not* recognised as covering
62
- * `prisma/contract.json linguist-generated`. We accept that
62
+ * `DEFAULT_CONTRACT_SOURCE_DIR/contract.json linguist-generated`. We accept that
63
63
  * over-specification — preserving the user's broad pattern *and*
64
64
  * appending the narrow one — because the narrow lines are what the
65
65
  * acceptance criteria pin (FR3.4 AC).