@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,10 +1,10 @@
1
1
  import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import type { Contract } from '@prisma-next/contract/types';
3
- import { emit } from '@prisma-next/emitter';
3
+ import { emit, getEmittedArtifactPaths } from '@prisma-next/emitter';
4
4
  import { createControlStack } from '@prisma-next/framework-components/control';
5
5
  import { abortable } from '@prisma-next/utils/abortable';
6
6
  import { ifDefined } from '@prisma-next/utils/defined';
7
- import { dirname, isAbsolute, join, resolve } from 'pathe';
7
+ import { dirname } from 'pathe';
8
8
  import { loadConfig } from '../../config-loader';
9
9
  import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
10
10
  import { assertFrameworkComponentsCompatible } from '../../utils/framework-components';
@@ -72,27 +72,23 @@ export async function executeContractEmit(
72
72
  why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
73
73
  });
74
74
  }
75
- if (!contractConfig.output.endsWith('.json')) {
75
+
76
+ // Validate source exists and is callable
77
+ if (typeof contractConfig.source?.load !== 'function') {
76
78
  throw errorContractConfigMissing({
77
- why: 'Contract config output path must end with .json (e.g., "src/prisma/contract.json")',
79
+ why: 'Contract config must include a valid source provider object',
78
80
  });
79
81
  }
80
82
 
81
- // Validate source exists and is callable
82
- if (typeof contractConfig.source !== 'function') {
83
+ let outputPaths: ReturnType<typeof getEmittedArtifactPaths>;
84
+ try {
85
+ outputPaths = getEmittedArtifactPaths(contractConfig.output);
86
+ } catch (error) {
83
87
  throw errorContractConfigMissing({
84
- why: 'Contract config must include a valid source provider function',
88
+ why: error instanceof Error ? error.message : String(error),
85
89
  });
86
90
  }
87
-
88
- // Normalize configPath and resolve artifact paths relative to config file directory
89
- const normalizedConfigPath = resolve(configPath);
90
- const configDir = dirname(normalizedConfigPath);
91
- const outputJsonPath = isAbsolute(contractConfig.output)
92
- ? contractConfig.output
93
- : join(configDir, contractConfig.output);
94
- // Colocate .d.ts with .json (contract.json → contract.d.ts)
95
- const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;
91
+ const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;
96
92
 
97
93
  const stack = createControlStack(config);
98
94
 
@@ -102,39 +98,40 @@ export async function executeContractEmit(
102
98
  authoringContributions: stack.authoringContributions,
103
99
  codecLookup: stack.codecLookup,
104
100
  controlMutationDefaults: stack.controlMutationDefaults,
101
+ resolvedInputs: contractConfig.source.inputs ?? [],
105
102
  };
106
103
 
107
- let providerResult: Awaited<ReturnType<typeof contractConfig.source>>;
104
+ let providerResult: Awaited<ReturnType<typeof contractConfig.source.load>>;
108
105
  try {
109
- providerResult = await unlessAborted(contractConfig.source(sourceContext));
106
+ providerResult = await unlessAborted(contractConfig.source.load(sourceContext));
110
107
  } catch (error) {
111
108
  if (signal.aborted || isAbortError(error)) {
112
109
  throw error;
113
110
  }
114
111
  throw errorRuntime('Failed to resolve contract source', {
115
112
  why: error instanceof Error ? error.message : String(error),
116
- fix: 'Ensure contract.source resolves to ok(Contract) or returns structured diagnostics.',
113
+ fix: 'Ensure contract.source.load resolves to ok(Contract) or returns structured diagnostics.',
117
114
  });
118
115
  }
119
116
 
120
117
  if (!isRecord(providerResult) || typeof providerResult.ok !== 'boolean') {
121
118
  throw errorRuntime('Failed to resolve contract source', {
122
119
  why: 'Contract source provider returned malformed result shape.',
123
- fix: 'Ensure contract.source resolves to ok(Contract) or notOk({ summary, diagnostics }).',
120
+ fix: 'Ensure contract.source.load resolves to ok(Contract) or notOk({ summary, diagnostics }).',
124
121
  });
125
122
  }
126
123
 
127
124
  if (providerResult.ok && !('value' in providerResult)) {
128
125
  throw errorRuntime('Failed to resolve contract source', {
129
126
  why: 'Contract source provider returned malformed success result: missing value.',
130
- fix: 'Ensure contract.source success payload is ok(Contract).',
127
+ fix: 'Ensure contract.source.load success payload is ok(Contract).',
131
128
  });
132
129
  }
133
130
 
134
131
  if (!providerResult.ok && !isProviderFailureLike(providerResult.failure)) {
135
132
  throw errorRuntime('Failed to resolve contract source', {
136
133
  why: 'Contract source provider returned malformed failure result: expected summary and diagnostics.',
137
- fix: 'Ensure contract.source failure payload is notOk({ summary, diagnostics, meta? }).',
134
+ fix: 'Ensure contract.source.load failure payload is notOk({ summary, diagnostics, meta? }).',
138
135
  });
139
136
  }
140
137
 
@@ -160,7 +157,11 @@ export async function executeContractEmit(
160
157
  const enrichedIR = enrichContract(providerResult.value as Contract, frameworkComponents);
161
158
 
162
159
  familyInstance.validateContract(enrichedIR);
163
- const emitResult = await unlessAborted(emit(enrichedIR, stack, config.family.emission));
160
+ const emitResult = await unlessAborted(
161
+ emit(enrichedIR, stack, config.family.emission, {
162
+ outputJsonPath: outputJsonPath,
163
+ }),
164
+ );
164
165
 
165
166
  // Create directory if needed and write files (both colocated)
166
167
  await unlessAborted(mkdir(dirname(outputJsonPath), { recursive: true }));
@@ -248,7 +248,7 @@ export interface EmitContractConfig {
248
248
  /**
249
249
  * Contract source provider.
250
250
  */
251
- readonly sourceProvider: ContractSourceProvider;
251
+ readonly source: ContractSourceProvider;
252
252
  /**
253
253
  * Output path for contract.json.
254
254
  * The .d.ts types file will be colocated (e.g., contract.json → contract.d.ts).
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public re-export of the `init --json` success-document schema (FR1.5).
3
+ *
4
+ * Imported as `@prisma-next/cli/init-output`. The shared error envelope is
5
+ * exported separately from `@prisma-next/errors`; consumers should branch
6
+ * on the `ok` discriminator (success documents carry `ok: true`, error
7
+ * envelopes carry `ok: false`) per the
8
+ * [Style Guide § JSON Semantics](../../../../../../../docs/CLI%20Style%20Guide.md#json-semantics).
9
+ */
10
+ export { type InitOutput, InitOutputSchema } from '../commands/init/output';
@@ -0,0 +1,254 @@
1
+ /**
2
+ * The migration-file CLI interface: the actor invoked when the author runs
3
+ * `node migration.ts` directly.
4
+ *
5
+ * Naming: this is *not* a "migration runner" in the apply-time sense. The
6
+ * apply-time runner is the thing `prisma-next migration apply` uses to
7
+ * execute migration JSON ops against a database. `MigrationCLI` is the
8
+ * tiny CLI surface owned by an authored `migration.ts` file: parse the
9
+ * file's argv, load the project's `prisma-next.config.ts`, assemble a
10
+ * `ControlStack`, instantiate the migration class, and serialize.
11
+ *
12
+ * The user authors a migration class, then calls
13
+ * `MigrationCLI.run(import.meta.url, MigrationClass)` at module scope
14
+ * after the class definition. When the file is invoked as a node
15
+ * entrypoint (`node migration.ts`), the CLI:
16
+ *
17
+ * 1. Detects whether the file is the direct entrypoint (no-op when imported).
18
+ * 2. Parses CLI args (`--help`, `--dry-run`, `--config <path>`).
19
+ * 3. Loads the project's `prisma-next.config.ts` via the same `loadConfig`
20
+ * the CLI commands use, walking up from the migration file's directory.
21
+ * 4. Probe-instantiates the migration class without a stack so it can read
22
+ * `targetId` and verify it matches `config.target.targetId`
23
+ * (`PN-MIG-2006` on mismatch) before any stack-driven adapter
24
+ * construction runs.
25
+ * 5. Assembles a `ControlStack` from the loaded config descriptors and
26
+ * constructs the migration with that stack.
27
+ * 6. Reads any previously-scaffolded `migration.json`, then calls
28
+ * `buildMigrationArtifacts` from `@prisma-next/migration-tools` to
29
+ * produce in-memory `ops.json` + `migration.json` content. Persists
30
+ * the result to disk (or prints in dry-run mode).
31
+ *
32
+ * File I/O lives here, in `@prisma-next/cli`: this is the only place
33
+ * that legitimately combines config loading, stack assembly, and
34
+ * on-disk persistence. `@prisma-next/migration-tools` owns the pure
35
+ * conversion from a `Migration` instance to artifact strings; `Migration`
36
+ * stays a pure abstract class.
37
+ */
38
+
39
+ import { readFileSync, writeFileSync } from 'node:fs';
40
+ import { fileURLToPath } from 'node:url';
41
+ import { CliStructuredError, errorMigrationCliInvalidConfigArg } from '@prisma-next/errors/control';
42
+ import { errorMigrationTargetMismatch } from '@prisma-next/errors/migration';
43
+ import { createControlStack } from '@prisma-next/framework-components/control';
44
+ import {
45
+ buildMigrationArtifacts,
46
+ isDirectEntrypoint,
47
+ type Migration,
48
+ printMigrationHelp,
49
+ } from '@prisma-next/migration-tools/migration';
50
+ import type { MigrationManifest } from '@prisma-next/migration-tools/types';
51
+ import { dirname, join } from 'pathe';
52
+ import { loadConfig } from './config-loader';
53
+
54
+ /**
55
+ * Constructor shape accepted by `MigrationCLI.run`. `Migration` subclasses
56
+ * accept an optional `ControlStack` in their constructor (each subclass
57
+ * narrows the stack to its own family/target generics); the CLI always
58
+ * passes one assembled from the loaded config. We use a rest-args `any[]`
59
+ * constructor signature so that subclass constructors with narrower
60
+ * parameter types remain assignable - constructor type compatibility in
61
+ * TS is contravariant in the parameter, and a wider `unknown` parameter
62
+ * on the alias side would reject any narrower subclass signature.
63
+ *
64
+ * The CLI only ever passes one argument (`new MigrationClass(stack)`);
65
+ * the rest-arity is purely a type-compatibility concession for subclass
66
+ * constructors that declare narrower parameter types, not an extension
67
+ * point for additional construction arguments.
68
+ */
69
+ // biome-ignore lint/suspicious/noExplicitAny: see JSDoc - rest args with any are the idiomatic TS pattern for accepting arbitrary subclass constructor signatures
70
+ export type MigrationConstructor = new (...args: any[]) => Migration;
71
+
72
+ interface ParsedArgs {
73
+ readonly help: boolean;
74
+ readonly dryRun: boolean;
75
+ readonly configPath: string | undefined;
76
+ }
77
+
78
+ /**
79
+ * Parse the subset of `process.argv` that `MigrationCLI.run` cares about.
80
+ * Recognised flags: `--help`, `--dry-run`, `--config <path>` /
81
+ * `--config=<path>`. Unknown flags are ignored to keep the surface
82
+ * forgiving for ad-hoc tooling that wraps a migration file.
83
+ *
84
+ * Throws `errorMigrationCliInvalidConfigArg` (`PN-CLI-4012`) when
85
+ * `--config` is missing its path argument or is followed by another flag
86
+ * (e.g. `--config --dry-run`); silently consuming the next flag would
87
+ * either drop dry-run handling or serialize against the wrong project.
88
+ *
89
+ * NOTE: this hand-rolled parser is a known wart, tracked separately by
90
+ * TML-2318 ("Migration CLI: replace handrolled arg parser with shared
91
+ * CLI library"). Until that lands the surface is intentionally tiny.
92
+ */
93
+ function parseArgs(argv: readonly string[]): ParsedArgs {
94
+ let help = false;
95
+ let dryRun = false;
96
+ let configPath: string | undefined;
97
+
98
+ for (let i = 0; i < argv.length; i++) {
99
+ const arg = argv[i]!;
100
+ if (arg === '--help' || arg === '-h') {
101
+ help = true;
102
+ } else if (arg === '--dry-run') {
103
+ dryRun = true;
104
+ } else if (arg === '--config') {
105
+ const next = argv[i + 1];
106
+ if (next === undefined) {
107
+ throw errorMigrationCliInvalidConfigArg();
108
+ }
109
+ if (next.startsWith('-')) {
110
+ throw errorMigrationCliInvalidConfigArg({ nextToken: next });
111
+ }
112
+ configPath = next;
113
+ i++;
114
+ } else if (arg.startsWith('--config=')) {
115
+ configPath = arg.slice('--config='.length);
116
+ }
117
+ }
118
+
119
+ return { help, dryRun, configPath };
120
+ }
121
+
122
+ /**
123
+ * The CLI surface invoked by an authored `migration.ts` file. Exposed as
124
+ * a class with a static `run` method (rather than a free function) to
125
+ * give the concept a stable identity in the ubiquitous language: this is
126
+ * the "migration-file CLI", distinct from the apply-time runner that
127
+ * executes migration JSON ops.
128
+ *
129
+ * Currently a single static method. Future surface (e.g. a programmatic
130
+ * `MigrationCLI.serializeOnly(...)` for tests, or extra subcommands) can
131
+ * land here without changing the import shape used by every authored
132
+ * migration.
133
+ */
134
+ // biome-ignore lint/complexity/noStaticOnlyClass: see JSDoc - intentional class facade for the migration-file CLI surface; future methods will share state derived from argv/config.
135
+ export class MigrationCLI {
136
+ /**
137
+ * Orchestrates a class-flow `migration.ts` script run. Awaitable:
138
+ * callers may `await MigrationCLI.run(...)` to surface async failures
139
+ * from config loading, but the typical usage pattern (top-level call
140
+ * after the class definition) does not require awaiting because
141
+ * node's module evaluation keeps the promise alive until completion.
142
+ *
143
+ * Any throwable inside this function must surface through the internal
144
+ * try/catch — script callers do not await, so an unhandled rejection
145
+ * would silently exit 0. Treat the try/catch as load-bearing for the
146
+ * no-await usage pattern.
147
+ */
148
+ static async run(importMetaUrl: string, MigrationClass: MigrationConstructor): Promise<void> {
149
+ if (!importMetaUrl) return;
150
+ if (!isDirectEntrypoint(importMetaUrl)) return;
151
+
152
+ try {
153
+ const args = parseArgs(process.argv.slice(2));
154
+
155
+ if (args.help) {
156
+ printMigrationHelp();
157
+ return;
158
+ }
159
+
160
+ const migrationFile = fileURLToPath(importMetaUrl);
161
+ const migrationDir = dirname(migrationFile);
162
+
163
+ const config = await loadConfig(args.configPath);
164
+
165
+ // Probe-instantiate without a stack so we can read `targetId` before
166
+ // any target-specific constructor side effects (e.g.
167
+ // `PostgresMigration`'s `stack.adapter.create(stack)`) run. Concrete
168
+ // subclasses are required to accept the no-arg form; the abstract
169
+ // `Migration` constructor declares `stack?` and target subclasses
170
+ // (Postgres, Mongo) propagate that optionality. This makes the
171
+ // target-mismatch guard fail fast with `PN-MIG-2006` before any
172
+ // stack-driven adapter construction begins, even if the wrong-target
173
+ // adapter's `create` would otherwise succeed and silently misshapen
174
+ // the stored adapter cast.
175
+ const probe = new MigrationClass();
176
+
177
+ if (probe.targetId !== config.target.targetId) {
178
+ throw errorMigrationTargetMismatch({
179
+ migrationTargetId: probe.targetId,
180
+ configTargetId: config.target.targetId,
181
+ });
182
+ }
183
+
184
+ const stack = createControlStack(config);
185
+ const instance = new MigrationClass(stack);
186
+
187
+ serializeMigrationToDisk(instance, migrationDir, args.dryRun);
188
+ } catch (err) {
189
+ if (CliStructuredError.is(err)) {
190
+ process.stderr.write(`${err.message}: ${err.why}\n`);
191
+ } else {
192
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
193
+ }
194
+ process.exitCode = 1;
195
+ }
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Read a previously-scaffolded `migration.json` from disk, returning
201
+ * `null` when the file is missing or unparseable. The CLI feeds this into
202
+ * `buildMigrationArtifacts` so the pure builder can preserve fields owned
203
+ * by `migration plan` (contract bookends, hints, labels, `createdAt`)
204
+ * across re-emits.
205
+ */
206
+ function readExistingManifest(manifestPath: string): Partial<MigrationManifest> | null {
207
+ let raw: string;
208
+ try {
209
+ raw = readFileSync(manifestPath, 'utf-8');
210
+ } catch {
211
+ return null;
212
+ }
213
+ try {
214
+ return JSON.parse(raw) as Partial<MigrationManifest>;
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Persist a migration instance's artifacts to `migrationDir`. In
222
+ * `dryRun` mode the artifacts are printed to stdout (with the same
223
+ * `--- migration.json --- / --- ops.json ---` framing the legacy
224
+ * `serializeMigration` helper used) and no files are written. Otherwise
225
+ * `ops.json` and `migration.json` are written next to `migration.ts` and
226
+ * a confirmation line is printed.
227
+ *
228
+ * File I/O lives in the CLI rather than `@prisma-next/migration-tools`
229
+ * so the migration-tools package stays focused on the pure
230
+ * `Migration` → in-memory artifact conversion. The CLI is the only
231
+ * legitimate site for combining config loading, stack assembly, and
232
+ * filesystem persistence.
233
+ */
234
+ function serializeMigrationToDisk(
235
+ instance: Migration,
236
+ migrationDir: string,
237
+ dryRun: boolean,
238
+ ): void {
239
+ const manifestPath = join(migrationDir, 'migration.json');
240
+ const existing = readExistingManifest(manifestPath);
241
+ const { opsJson, manifestJson } = buildMigrationArtifacts(instance, existing);
242
+
243
+ if (dryRun) {
244
+ process.stdout.write(`--- migration.json ---\n${manifestJson}\n`);
245
+ process.stdout.write('--- ops.json ---\n');
246
+ process.stdout.write(`${opsJson}\n`);
247
+ return;
248
+ }
249
+
250
+ writeFileSync(join(migrationDir, 'ops.json'), opsJson);
251
+ writeFileSync(manifestPath, manifestJson);
252
+
253
+ process.stdout.write(`Wrote ops.json + migration.json to ${migrationDir}\n`);
254
+ }
@@ -14,6 +14,7 @@ export {
14
14
  errorDriverRequired,
15
15
  errorFamilyReadMarkerSqlRequired,
16
16
  errorFileNotFound,
17
+ errorMigrationCliInvalidConfigArg,
17
18
  errorMigrationPlanningFailed,
18
19
  errorQueryRunnerFactoryRequired,
19
20
  errorTargetMigrationNotSupported,
@@ -4,12 +4,7 @@ import { hasMigrations } from '@prisma-next/framework-components/control';
4
4
  import type { PathDecision } from '@prisma-next/migration-tools/dag';
5
5
  import { reconstructGraph } from '@prisma-next/migration-tools/dag';
6
6
  import { readMigrationsDir } from '@prisma-next/migration-tools/io';
7
- import type {
8
- AttestedMigrationBundle,
9
- DraftMigrationBundle,
10
- MigrationGraph,
11
- } from '@prisma-next/migration-tools/types';
12
- import { isAttested, isDraft } from '@prisma-next/migration-tools/types';
7
+ import type { MigrationBundle, MigrationGraph } from '@prisma-next/migration-tools/types';
13
8
  import { ifDefined } from '@prisma-next/utils/defined';
14
9
  import type { Command } from 'commander';
15
10
  import { relative, resolve } from 'pathe';
@@ -90,7 +85,7 @@ export function resolveMigrationPaths(
90
85
  configPath: string;
91
86
  migrationsDir: string;
92
87
  migrationsRelative: string;
93
- refsPath: string;
88
+ refsDir: string;
94
89
  } {
95
90
  const configPath = configOption
96
91
  ? relative(process.cwd(), resolve(configOption))
@@ -100,8 +95,8 @@ export function resolveMigrationPaths(
100
95
  config.migrations?.dir ?? 'migrations',
101
96
  );
102
97
  const migrationsRelative = relative(process.cwd(), migrationsDir);
103
- const refsPath = resolve(migrationsDir, 'refs.json');
104
- return { configPath, migrationsDir, migrationsRelative, refsPath };
98
+ const refsDir = resolve(migrationsDir, 'refs');
99
+ return { configPath, migrationsDir, migrationsRelative, refsDir };
105
100
  }
106
101
 
107
102
  /**
@@ -150,32 +145,33 @@ export function getTargetMigrations(target: ControlTargetDescriptor<string, stri
150
145
  }
151
146
 
152
147
  /**
153
- * Reads the migrations directory, filters to attested bundles, and builds
154
- * the migration graph. Throws on I/O or graph errors — callers handle
155
- * error mapping.
148
+ * Reads the migrations directory and builds the migration graph from all
149
+ * bundles. Throws on I/O or graph errors — callers handle error mapping.
150
+ *
151
+ * Every on-disk bundle is content-addressed (`migrationId` is always a
152
+ * string); there is no draft state to filter out.
156
153
  */
157
154
  export async function loadMigrationBundles(migrationsDir: string): Promise<{
158
- bundles: readonly AttestedMigrationBundle[];
155
+ bundles: readonly MigrationBundle[];
159
156
  graph: MigrationGraph;
160
157
  }> {
161
- const allBundles = await readMigrationsDir(migrationsDir);
162
- const bundles = allBundles.filter(isAttested);
158
+ const bundles = await readMigrationsDir(migrationsDir);
163
159
  const graph = reconstructGraph(bundles);
164
160
  return { bundles, graph };
165
161
  }
166
162
 
167
163
  export interface MigrationBundleSet {
168
- readonly attested: readonly AttestedMigrationBundle[];
169
- readonly drafts: readonly DraftMigrationBundle[];
164
+ readonly bundles: readonly MigrationBundle[];
170
165
  readonly graph: MigrationGraph;
171
166
  }
172
167
 
168
+ /**
169
+ * Alias of `loadMigrationBundles` retained for naming-clarity in commands
170
+ * that previously needed both attested and draft splits. With the
171
+ * collapse of the draft state, both helpers do the same thing.
172
+ */
173
173
  export async function loadAllBundles(migrationsDir: string): Promise<MigrationBundleSet> {
174
- const all = await readMigrationsDir(migrationsDir);
175
- const attested = all.filter(isAttested);
176
- const drafts = all.filter(isDraft);
177
- const graph = reconstructGraph(attested);
178
- return { attested, drafts, graph };
174
+ return loadMigrationBundles(migrationsDir);
179
175
  }
180
176
 
181
177
  /**
@@ -39,12 +39,6 @@ export interface EdgeStatus {
39
39
  readonly status: EdgeStatusKind;
40
40
  }
41
41
 
42
- export interface DraftEdge {
43
- readonly from: string;
44
- readonly to: string;
45
- readonly dirName: string;
46
- }
47
-
48
42
  export interface MigrationGraphInput {
49
43
  readonly graph: MigrationGraph;
50
44
  readonly mode: 'online' | 'offline';
@@ -58,8 +52,6 @@ export interface MigrationGraphInput {
58
52
  * icons (✓/⧗) are baked into edge labels. Undefined in offline mode.
59
53
  */
60
54
  readonly edgeStatuses?: readonly EdgeStatus[] | undefined;
61
- /** Draft migrations to render as dashed edges. */
62
- readonly draftEdges?: readonly DraftEdge[] | undefined;
63
55
  }
64
56
 
65
57
  export interface MigrationRenderInput {
@@ -204,7 +196,9 @@ export function migrationGraphToRenderInput(input: MigrationGraphInput): Migrati
204
196
  spineTargetHash = lastEdge?.to ?? EMPTY_CONTRACT_HASH;
205
197
  }
206
198
 
207
- // Contract not in attested graph — connect from spine target with a dashed edge
199
+ // Contract not in the migration graph — connect from spine target with a
200
+ // dashed edge so the user can see the gap (contract has changed but no
201
+ // migration has been planned yet).
208
202
  if (contractHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(contractHash)) {
209
203
  const contractMarkers: NodeMarker[] = [];
210
204
  if (mode === 'online' && markerHash === contractHash) {
@@ -216,13 +210,10 @@ export function migrationGraphToRenderInput(input: MigrationGraphInput): Migrati
216
210
  markers: contractMarkers,
217
211
  });
218
212
 
219
- const matchingDraft = input.draftEdges?.find((d) => d.to === contractHash);
220
- const fromHash = matchingDraft?.from ?? spineTargetHash;
221
- if (graph.nodes.has(fromHash) || fromHash === spineTargetHash) {
213
+ if (graph.nodes.has(spineTargetHash) || spineTargetHash === EMPTY_CONTRACT_HASH) {
222
214
  edgeList.push({
223
- from: toShortId(fromHash),
215
+ from: toShortId(spineTargetHash),
224
216
  to: shortHash(contractHash),
225
- ...ifDefined('label', matchingDraft ? `${matchingDraft.dirName} [draft]` : undefined),
226
217
  style: 'dashed',
227
218
  });
228
219
  }
@@ -137,7 +137,6 @@ function getCommandDocsUrl(commandPath: string): string | undefined {
137
137
  'migration apply': 'https://pris.ly/migration-apply',
138
138
  'migration show': 'https://pris.ly/migration-show',
139
139
  'migration status': 'https://pris.ly/migration-status',
140
- 'migration emit': 'https://pris.ly/migration-emit',
141
140
  };
142
141
  return docsMap[commandPath];
143
142
  }
@@ -136,10 +136,6 @@ export interface MigrationApplyCommandOutputResult {
136
136
  };
137
137
  }
138
138
 
139
- export interface MigrationEmitCommandOutputResult {
140
- readonly migrationId: string;
141
- }
142
-
143
139
  export function formatMigrationApplyCommandOutput(
144
140
  result: MigrationApplyCommandOutputResult,
145
141
  flags: GlobalFlags,
@@ -182,31 +178,12 @@ export function formatMigrationApplyCommandOutput(
182
178
  return lines.join('\n');
183
179
  }
184
180
 
185
- export function formatMigrationEmitCommandOutput(
186
- result: MigrationEmitCommandOutputResult,
187
- flags: GlobalFlags,
188
- ): string {
189
- if (flags.quiet) {
190
- return '';
191
- }
192
-
193
- const lines: string[] = [];
194
- const useColor = flags.color !== false;
195
- const formatGreen = createColorFormatter(useColor, green);
196
- const formatDimText = (text: string) => formatDim(useColor, text);
197
-
198
- lines.push(`${formatGreen('✔')} Emitted ops.json and attested migration`);
199
- lines.push(formatDimText(` migrationId: ${result.migrationId}`));
200
-
201
- return lines.join('\n');
202
- }
203
-
204
181
  interface MigrationShowResult {
205
182
  readonly dirName: string;
206
183
  readonly dirPath: string;
207
184
  readonly from: string;
208
185
  readonly to: string;
209
- readonly migrationId: string | null;
186
+ readonly migrationId: string;
210
187
  readonly kind: string;
211
188
  readonly createdAt: string;
212
189
  readonly operations: readonly {
@@ -234,11 +211,7 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
234
211
  lines.push(`${formatDimText(` kind: ${result.kind}`)}`);
235
212
  lines.push(`${formatDimText(` from: ${result.from}`)}`);
236
213
  lines.push(`${formatDimText(` to: ${result.to}`)}`);
237
- if (result.migrationId) {
238
- lines.push(`${formatDimText(` migrationId: ${result.migrationId}`)}`);
239
- } else {
240
- lines.push(`${formatYellow(' migrationId: (draft — not yet attested)')}`);
241
- }
214
+ lines.push(`${formatDimText(` migrationId: ${result.migrationId}`)}`);
242
215
  lines.push(`${formatDimText(` created: ${result.createdAt}`)}`);
243
216
 
244
217
  lines.push('');
@@ -1,5 +0,0 @@
1
- import { CliStructuredError, errorConfigValidation as errorConfigValidation$1, errorContractConfigMissing as errorContractConfigMissing$1, errorContractValidationFailed, errorDatabaseConnectionRequired, errorDriverRequired, errorFileNotFound, errorMigrationPlanningFailed, errorTargetMigrationNotSupported, errorUnexpected as errorUnexpected$1 } from "@prisma-next/errors/control";
2
- import { ERROR_CODE_DESTRUCTIVE_CHANGES, errorDestructiveChanges, errorHashMismatch, errorMarkerMissing, errorRunnerFailed, errorRuntime as errorRuntime$1, errorTargetMismatch } from "@prisma-next/errors/execution";
3
- import { errorMigrationFileMissing } from "@prisma-next/errors/migration";
4
-
5
- export { errorTargetMismatch as _, errorContractValidationFailed as a, errorDriverRequired as c, errorMarkerMissing as d, errorMigrationFileMissing as f, errorTargetMigrationNotSupported as g, errorRuntime$1 as h, errorContractConfigMissing$1 as i, errorFileNotFound as l, errorRunnerFailed as m, ERROR_CODE_DESTRUCTIVE_CHANGES as n, errorDatabaseConnectionRequired as o, errorMigrationPlanningFailed as p, errorConfigValidation$1 as r, errorDestructiveChanges as s, CliStructuredError as t, errorHashMismatch as u, errorUnexpected$1 as v };
@@ -1,4 +0,0 @@
1
- import { CliStructuredError } from "@prisma-next/errors/control";
2
- import "@prisma-next/errors/execution";
3
- import "@prisma-next/errors/migration";
4
- export { CliStructuredError as t };
@@ -1,38 +0,0 @@
1
- import { Command } from "commander";
2
-
3
- //#region src/utils/global-flags.d.ts
4
-
5
- /**
6
- * Common options parsed by Commander.js for every command.
7
- * Extend this for command-specific options instead of duplicating these fields.
8
- */
9
- interface CommonCommandOptions {
10
- readonly json?: string | boolean;
11
- readonly quiet?: boolean;
12
- readonly q?: boolean;
13
- readonly verbose?: boolean;
14
- readonly v?: boolean;
15
- readonly trace?: boolean;
16
- readonly color?: boolean;
17
- readonly 'no-color'?: boolean;
18
- readonly interactive?: boolean;
19
- readonly 'no-interactive'?: boolean;
20
- readonly yes?: boolean;
21
- readonly y?: boolean;
22
- }
23
- //#endregion
24
- //#region src/commands/migration-emit.d.ts
25
- interface MigrationEmitOptions extends CommonCommandOptions {
26
- readonly dir: string;
27
- readonly config?: string;
28
- }
29
- interface MigrationEmitResult {
30
- readonly ok: boolean;
31
- readonly dir: string;
32
- readonly migrationId: string;
33
- readonly summary: string;
34
- }
35
- declare function createMigrationEmitCommand(): Command;
36
- //#endregion
37
- export { MigrationEmitOptions, MigrationEmitResult, createMigrationEmitCommand };
38
- //# sourceMappingURL=migration-emit.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"migration-emit.d.mts","names":[],"sources":["../../src/utils/global-flags.ts","../../src/commands/migration-emit.ts"],"sourcesContent":[],"mappings":";;;;AC8BA;AAuEA;;;UDxFiB,oBAAA;;;;;;;;;;;;;;;;UCYA,oBAAA,SAA6B;EDZ7B,SAAA,GAAA,EAAA,MAAA;;;UCiBA,mBAAA;EALA,SAAA,EAAA,EAAA,OAAA;EAKA,SAAA,GAAA,EAAA,MAAA;EAuED,SAAA,WAAA,EAAA,MAAA;;;iBAAA,0BAAA,CAAA,GAA8B"}