@prisma-next/cli 0.11.0 → 0.12.0

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 (196) hide show
  1. package/README.md +13 -9
  2. package/dist/cli.mjs +259 -12
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-oXO2WCPD.mjs → client-KgJorIvG.mjs} +72 -60
  5. package/dist/client-KgJorIvG.mjs.map +1 -0
  6. package/dist/{command-helpers-BSb0tRC8.mjs → command-helpers-Bbw1GbwL.mjs} +646 -46
  7. package/dist/command-helpers-Bbw1GbwL.mjs.map +1 -0
  8. package/dist/commands/contract-emit.d.mts.map +1 -1
  9. package/dist/commands/contract-emit.mjs +1 -1
  10. package/dist/commands/contract-infer.d.mts.map +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.d.mts.map +1 -1
  13. package/dist/commands/db-init.mjs +32 -7
  14. package/dist/commands/db-init.mjs.map +1 -1
  15. package/dist/commands/db-schema.d.mts.map +1 -1
  16. package/dist/commands/db-schema.mjs +3 -4
  17. package/dist/commands/db-schema.mjs.map +1 -1
  18. package/dist/commands/db-sign.d.mts.map +1 -1
  19. package/dist/commands/db-sign.mjs +12 -10
  20. package/dist/commands/db-sign.mjs.map +1 -1
  21. package/dist/commands/db-update.d.mts.map +1 -1
  22. package/dist/commands/db-update.mjs +41 -11
  23. package/dist/commands/db-update.mjs.map +1 -1
  24. package/dist/commands/db-verify.d.mts.map +1 -1
  25. package/dist/commands/db-verify.mjs +1 -1
  26. package/dist/commands/migrate.d.mts +6 -2
  27. package/dist/commands/migrate.d.mts.map +1 -1
  28. package/dist/commands/migrate.mjs +75 -40
  29. package/dist/commands/migrate.mjs.map +1 -1
  30. package/dist/commands/migration-check.d.mts +4 -3
  31. package/dist/commands/migration-check.d.mts.map +1 -1
  32. package/dist/commands/migration-check.mjs +1 -280
  33. package/dist/commands/migration-graph.d.mts +13 -2
  34. package/dist/commands/migration-graph.d.mts.map +1 -1
  35. package/dist/commands/migration-graph.mjs +2 -137
  36. package/dist/commands/migration-list.d.mts +64 -4
  37. package/dist/commands/migration-list.d.mts.map +1 -1
  38. package/dist/commands/migration-list.mjs +143 -56
  39. package/dist/commands/migration-list.mjs.map +1 -1
  40. package/dist/commands/migration-log.d.mts +10 -1
  41. package/dist/commands/migration-log.d.mts.map +1 -1
  42. package/dist/commands/migration-log.mjs +10 -15
  43. package/dist/commands/migration-log.mjs.map +1 -1
  44. package/dist/commands/migration-new.d.mts.map +1 -1
  45. package/dist/commands/migration-new.mjs +32 -38
  46. package/dist/commands/migration-new.mjs.map +1 -1
  47. package/dist/commands/migration-plan.d.mts +3 -2
  48. package/dist/commands/migration-plan.d.mts.map +1 -1
  49. package/dist/commands/migration-plan.mjs +1 -1
  50. package/dist/commands/migration-show.d.mts +4 -55
  51. package/dist/commands/migration-show.d.mts.map +1 -1
  52. package/dist/commands/migration-show.mjs +61 -153
  53. package/dist/commands/migration-show.mjs.map +1 -1
  54. package/dist/commands/migration-status.d.mts +12 -49
  55. package/dist/commands/migration-status.d.mts.map +1 -1
  56. package/dist/commands/migration-status.mjs +85 -81
  57. package/dist/commands/migration-status.mjs.map +1 -1
  58. package/dist/commands/ref.d.mts +1 -1
  59. package/dist/commands/ref.d.mts.map +1 -1
  60. package/dist/commands/ref.mjs +38 -10
  61. package/dist/commands/ref.mjs.map +1 -1
  62. package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
  63. package/dist/config-loader.d.mts.map +1 -1
  64. package/dist/contract-at-errors-BxP-TOMl.mjs +42 -0
  65. package/dist/contract-at-errors-BxP-TOMl.mjs.map +1 -0
  66. package/dist/{contract-emit-bcrpT-wD.mjs → contract-emit-D-4jrNve.mjs} +25 -10
  67. package/dist/contract-emit-D-4jrNve.mjs.map +1 -0
  68. package/dist/{contract-emit-r4y8Zhf1.mjs → contract-emit-DxcGl4Uq.mjs} +19 -14
  69. package/dist/contract-emit-DxcGl4Uq.mjs.map +1 -0
  70. package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-a0V5Y_mL.mjs} +4 -25
  71. package/dist/contract-enrichment-a0V5Y_mL.mjs.map +1 -0
  72. package/dist/{contract-infer-BmySmqVT.mjs → contract-infer-D8uEbJuu.mjs} +4 -5
  73. package/dist/{contract-infer-BmySmqVT.mjs.map → contract-infer-D8uEbJuu.mjs.map} +1 -1
  74. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs +247 -0
  75. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs.map +1 -0
  76. package/dist/{db-verify-BClPs3ph.mjs → db-verify-v_vUKXTU.mjs} +5 -7
  77. package/dist/{db-verify-BClPs3ph.mjs.map → db-verify-v_vUKXTU.mjs.map} +1 -1
  78. package/dist/exports/control-api.d.mts +3 -3
  79. package/dist/exports/control-api.d.mts.map +1 -1
  80. package/dist/exports/control-api.mjs +3 -3
  81. package/dist/exports/index.d.mts.map +1 -1
  82. package/dist/exports/index.mjs +1 -1
  83. package/dist/exports/index.mjs.map +1 -1
  84. package/dist/exports/init-output.d.mts.map +1 -1
  85. package/dist/exports/init-output.mjs +1 -1
  86. package/dist/extension-pack-inputs-IDvjRCi3.mjs +62 -0
  87. package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +1 -0
  88. package/dist/{framework-components-65gOHkHB.mjs → framework-components-fYXjz_in.mjs} +2 -2
  89. package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-fYXjz_in.mjs.map} +1 -1
  90. package/dist/global-flags-DEHjV8_s.d.mts +34 -0
  91. package/dist/global-flags-DEHjV8_s.d.mts.map +1 -0
  92. package/dist/{graph-render-DJVv0_uf.mjs → graph-render-rFAqZujX.mjs} +2 -2
  93. package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
  94. package/dist/{init-BCJZPWE1.mjs → init-Cv9UzWL5.mjs} +20 -269
  95. package/dist/init-Cv9UzWL5.mjs.map +1 -0
  96. package/dist/{inspect-live-schema-DSRbFoOL.mjs → inspect-live-schema-C6ohV_oQ.mjs} +4 -5
  97. package/dist/{inspect-live-schema-DSRbFoOL.mjs.map → inspect-live-schema-C6ohV_oQ.mjs.map} +1 -1
  98. package/dist/migration-check-BiBJoYYW.mjs +341 -0
  99. package/dist/migration-check-BiBJoYYW.mjs.map +1 -0
  100. package/dist/migration-cli.d.mts.map +1 -1
  101. package/dist/migration-cli.mjs +4 -4
  102. package/dist/migration-cli.mjs.map +1 -1
  103. package/dist/{migration-command-scaffold-Bzd9La5c.mjs → migration-command-scaffold-CjvwO6at.mjs} +4 -5
  104. package/dist/{migration-command-scaffold-Bzd9La5c.mjs.map → migration-command-scaffold-CjvwO6at.mjs.map} +1 -1
  105. package/dist/migration-graph-D7DVUElV.mjs +1232 -0
  106. package/dist/migration-graph-D7DVUElV.mjs.map +1 -0
  107. package/dist/migration-list-styler-BRwF4-gy.mjs +399 -0
  108. package/dist/migration-list-styler-BRwF4-gy.mjs.map +1 -0
  109. package/dist/{migration-plan-CFwqw3Gk.mjs → migration-plan-9DJ7q7_z.mjs} +372 -133
  110. package/dist/migration-plan-9DJ7q7_z.mjs.map +1 -0
  111. package/dist/{migration-types-BXWvz12q.d.mts → migration-types-D2FW63pr.d.mts} +1 -1
  112. package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-D2FW63pr.d.mts.map} +1 -1
  113. package/dist/{migrations-CwZMa1Ck.mjs → migrations-Cv2jxNNK.mjs} +12 -13
  114. package/dist/migrations-Cv2jxNNK.mjs.map +1 -0
  115. package/dist/{output-BlsrGMEF.mjs → output-B60Gw5fu.mjs} +1 -1
  116. package/dist/{output-BlsrGMEF.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  117. package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-C644QK8l.mjs} +1 -1
  118. package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-C644QK8l.mjs.map} +1 -1
  119. package/dist/ref-advancement-DUZqsue6.mjs +50 -0
  120. package/dist/ref-advancement-DUZqsue6.mjs.map +1 -0
  121. package/dist/terminal-ui-5Y6mrg93.d.mts +133 -0
  122. package/dist/terminal-ui-5Y6mrg93.d.mts.map +1 -0
  123. package/dist/{types--CqjMdk0.d.mts → types-Dt_SfqFm.d.mts} +28 -28
  124. package/dist/types-Dt_SfqFm.d.mts.map +1 -0
  125. package/dist/{verify-Bom75OYI.mjs → verify-DCA9Sldu.mjs} +2 -2
  126. package/dist/{verify-Bom75OYI.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
  127. package/package.json +35 -24
  128. package/src/commands/contract-emit.ts +19 -7
  129. package/src/commands/contract-infer.ts +1 -1
  130. package/src/commands/db-init.ts +48 -2
  131. package/src/commands/db-sign.ts +9 -5
  132. package/src/commands/db-update.ts +54 -8
  133. package/src/commands/init/hygiene-gitattributes.ts +2 -2
  134. package/src/commands/init/index.ts +2 -1
  135. package/src/commands/init/templates/code-templates.ts +4 -2
  136. package/src/commands/init/templates/env.ts +13 -14
  137. package/src/commands/migrate.ts +125 -44
  138. package/src/commands/migration-check.ts +43 -83
  139. package/src/commands/migration-graph.ts +75 -60
  140. package/src/commands/migration-list.ts +220 -74
  141. package/src/commands/migration-log.ts +8 -14
  142. package/src/commands/migration-new.ts +44 -48
  143. package/src/commands/migration-plan.ts +412 -197
  144. package/src/commands/migration-show.ts +65 -284
  145. package/src/commands/migration-status.ts +127 -124
  146. package/src/commands/ref.ts +53 -8
  147. package/src/control-api/client.ts +0 -1
  148. package/src/control-api/contract-enrichment.ts +6 -42
  149. package/src/control-api/operations/{apply-aggregate.ts → apply.ts} +44 -75
  150. package/src/control-api/operations/contract-emit.ts +14 -6
  151. package/src/control-api/operations/{db-apply-aggregate.ts → db-apply.ts} +19 -19
  152. package/src/control-api/operations/db-init.ts +4 -4
  153. package/src/control-api/operations/db-update.ts +4 -4
  154. package/src/control-api/operations/db-verify.ts +15 -11
  155. package/src/control-api/operations/migration-apply.ts +56 -47
  156. package/src/control-api/types.ts +26 -27
  157. package/src/migration-cli.ts +4 -4
  158. package/src/utils/cli-errors.ts +234 -0
  159. package/src/utils/command-helpers.ts +9 -24
  160. package/src/utils/contract-at-errors.ts +96 -0
  161. package/src/utils/contract-space-aggregate-loader.ts +336 -117
  162. package/src/utils/formatters/migration-graph-layout.ts +1119 -0
  163. package/src/utils/formatters/migration-graph-rows.ts +336 -0
  164. package/src/utils/formatters/migration-graph-tree-render.ts +459 -0
  165. package/src/utils/formatters/migration-list-data-column.ts +115 -0
  166. package/src/utils/formatters/migration-list-graph-topology.ts +368 -0
  167. package/src/utils/formatters/migration-list-render.ts +191 -0
  168. package/src/utils/formatters/migration-list-styler.ts +63 -0
  169. package/src/utils/formatters/migration-list-types.ts +21 -0
  170. package/src/utils/formatters/migrations.ts +37 -46
  171. package/src/utils/glyph-mode.ts +22 -0
  172. package/src/utils/integrity-violation-to-check-failure.ts +130 -0
  173. package/src/utils/plan-resolution.ts +258 -0
  174. package/src/utils/ref-advancement.ts +68 -0
  175. package/src/utils/terminal-ui.ts +42 -1
  176. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  177. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  178. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  179. package/dist/client-oXO2WCPD.mjs.map +0 -1
  180. package/dist/command-helpers-BSb0tRC8.mjs.map +0 -1
  181. package/dist/commands/migration-check.mjs.map +0 -1
  182. package/dist/commands/migration-graph.mjs.map +0 -1
  183. package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
  184. package/dist/contract-emit-r4y8Zhf1.mjs.map +0 -1
  185. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  186. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
  187. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
  188. package/dist/global-flags-CdE7M0d9.d.mts +0 -15
  189. package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
  190. package/dist/init-BCJZPWE1.mjs.map +0 -1
  191. package/dist/migration-plan-CFwqw3Gk.mjs.map +0 -1
  192. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  193. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  194. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  195. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  196. package/dist/types--CqjMdk0.d.mts.map +0 -1
@@ -5,7 +5,6 @@ import type {
5
5
  } from '@prisma-next/framework-components/control';
6
6
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
7
7
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
8
- import { readRefs } from '@prisma-next/migration-tools/refs';
9
8
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
10
9
  import { Command } from 'commander';
11
10
  import { join, relative, resolve } from 'pathe';
@@ -25,13 +24,13 @@ import {
25
24
  } from '../utils/cli-errors';
26
25
  import {
27
26
  addGlobalOptions,
28
- loadMigrationPackages,
29
27
  maskConnectionUrl,
30
28
  resolveContractPath,
31
29
  resolveMigrationPaths,
32
30
  setCommandDescriptions,
33
31
  setCommandExamples,
34
32
  } from '../utils/command-helpers';
33
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
35
34
  import { formatStyledHeader } from '../utils/formatters/styled';
36
35
  import {
37
36
  formatSchemaVerifyJson,
@@ -99,9 +98,14 @@ async function executeDbSignCommand(
99
98
 
100
99
  if (effectiveContractArg) {
101
100
  try {
102
- const { appMigrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
103
- const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
104
- const refs = await readRefs(refsDir);
101
+ const { migrationsDir } = resolveMigrationPaths(options.config, config);
102
+ const loaded = await buildReadAggregate(config, { migrationsDir });
103
+ if (!loaded.ok) {
104
+ return notOk(loaded.failure);
105
+ }
106
+ const graph = loaded.value.aggregate.app.graph();
107
+ const bundles = loaded.value.aggregate.app.packages;
108
+ const refs = loaded.value.aggregate.app.refs;
105
109
  const refResult = parseContractRef(effectiveContractArg, { graph, refs });
106
110
  if (!refResult.ok) {
107
111
  return notOk(mapRefResolutionError(refResult.failure));
@@ -1,7 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
3
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
4
- import { readRefs } from '@prisma-next/migration-tools/refs';
5
4
  import { ifDefined } from '@prisma-next/utils/defined';
6
5
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
7
6
  import { Command } from 'commander';
@@ -21,12 +20,12 @@ import {
21
20
  } from '../utils/cli-errors';
22
21
  import type { MigrationCommandOptions } from '../utils/command-helpers';
23
22
  import {
24
- loadMigrationPackages,
25
23
  resolveMigrationPaths,
26
24
  sanitizeErrorMessage,
27
25
  setCommandDescriptions,
28
26
  setCommandExamples,
29
27
  } from '../utils/command-helpers';
28
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
30
29
  import {
31
30
  formatMigrationApplyOutput,
32
31
  formatMigrationJson,
@@ -38,11 +37,18 @@ import {
38
37
  addMigrationCommandOptions,
39
38
  prepareMigrationContext,
40
39
  } from '../utils/migration-command-scaffold';
40
+ import {
41
+ buildRefAdvancementFields,
42
+ computeRefAdvancementName,
43
+ type RefAdvancementFields,
44
+ readContractIR,
45
+ } from '../utils/ref-advancement';
41
46
  import { handleResult } from '../utils/result-handler';
42
47
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
43
48
 
44
49
  interface DbUpdateOptions extends MigrationCommandOptions {
45
50
  readonly to?: string;
51
+ readonly advanceRef?: string;
46
52
  }
47
53
 
48
54
  /**
@@ -101,15 +107,18 @@ async function executeDbUpdateCommand(
101
107
  }
102
108
  const { client, config, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;
103
109
  let { contractJson } = ctxResult.value;
104
- const { migrationsDir, appMigrationsDir, refsDir } = resolveMigrationPaths(
105
- options.config,
106
- config,
107
- );
110
+ let contractJsonPathForSnapshot = contractPathAbsolute;
111
+ const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
108
112
 
109
113
  if (options.to) {
110
114
  try {
111
- const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
112
- const refs = await readRefs(refsDir);
115
+ const loaded = await buildReadAggregate(config, { migrationsDir });
116
+ if (!loaded.ok) {
117
+ return notOk(loaded.failure);
118
+ }
119
+ const graph = loaded.value.aggregate.app.graph();
120
+ const bundles = loaded.value.aggregate.app.packages;
121
+ const refs = loaded.value.aggregate.app.refs;
113
122
  const refResult = parseContractRef(options.to, { graph, refs });
114
123
  if (!refResult.ok) {
115
124
  return notOk(mapRefResolutionError(refResult.failure));
@@ -130,6 +139,7 @@ async function executeDbUpdateCommand(
130
139
  const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
131
140
  const raw = await readFile(endContractPath, 'utf-8');
132
141
  contractJson = JSON.parse(raw) as Record<string, unknown>;
142
+ contractJsonPathForSnapshot = endContractPath;
133
143
  } catch (error) {
134
144
  if (MigrationToolsError.is(error)) {
135
145
  return notOk(mapMigrationToolsError(error));
@@ -157,6 +167,39 @@ async function executeDbUpdateCommand(
157
167
  return notOk(mapDbUpdateFailure(result.failure));
158
168
  }
159
169
 
170
+ const advancementHash =
171
+ result.value.mode === 'apply'
172
+ ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)
173
+ : result.value.destination.storageHash;
174
+
175
+ let refAdvancementFields: RefAdvancementFields = {
176
+ advancedRef: null,
177
+ plannedAdvanceRef: null,
178
+ };
179
+ if (
180
+ computeRefAdvancementName({
181
+ ...ifDefined('advanceRef', options.advanceRef),
182
+ ...ifDefined('db', options.db),
183
+ }) !== null
184
+ ) {
185
+ try {
186
+ const contractIR = await readContractIR(contractJson, contractJsonPathForSnapshot);
187
+ refAdvancementFields = await buildRefAdvancementFields({
188
+ ...ifDefined('advanceRef', options.advanceRef),
189
+ ...ifDefined('db', options.db),
190
+ refsDir,
191
+ contractIR,
192
+ mode: result.value.mode,
193
+ hash: advancementHash,
194
+ });
195
+ } catch (error) {
196
+ if (MigrationToolsError.is(error)) {
197
+ return notOk(mapMigrationToolsError(error));
198
+ }
199
+ throw error;
200
+ }
201
+ }
202
+
160
203
  // Convert success result to CLI output format
161
204
  const dbUpdateResult: MigrationCommandResult = {
162
205
  ok: true,
@@ -193,6 +236,8 @@ async function executeDbUpdateCommand(
193
236
  : undefined,
194
237
  ),
195
238
  ...ifDefined('perSpace', result.value.perSpace),
239
+ advancedRef: refAdvancementFields.advancedRef,
240
+ plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,
196
241
  summary: result.value.summary,
197
242
  timings: { total: Date.now() - startTime },
198
243
  };
@@ -245,6 +290,7 @@ export function createDbUpdateCommand(): Command {
245
290
  '--to <contract>',
246
291
  'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
247
292
  );
293
+ command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');
248
294
  command.action(async (options: DbUpdateOptions) => {
249
295
  const flags = parseGlobalFlagsOrExit(options);
250
296
  const startTime = Date.now();
@@ -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).
@@ -15,6 +15,7 @@ import {
15
15
  INIT_EXIT_SKILL_INSTALL_FAILED,
16
16
  INIT_EXIT_USER_ABORTED,
17
17
  } from './exit-codes';
18
+ import { defaultSchemaPath } from './templates/code-templates';
18
19
 
19
20
  /**
20
21
  * Commander.js parsed options for `init`. The init-specific options live
@@ -72,7 +73,7 @@ export function createInitCommand(): Command {
72
73
  .option('--authoring <style>', 'Schema authoring style: psl or typescript')
73
74
  .option(
74
75
  '--schema-path <path>',
75
- 'Where to write the starter schema (default: prisma/contract.prisma)',
76
+ `Where to write the starter schema (default: ${defaultSchemaPath('psl')})`,
76
77
  )
77
78
  .option('--force', 'Overwrite an existing scaffold without prompting')
78
79
  .option(
@@ -1,3 +1,5 @@
1
+ import { DEFAULT_CONTRACT_SOURCE_DIR } from '@prisma-next/config/config-types';
2
+
1
3
  export type TargetId = 'postgres' | 'mongo';
2
4
  export type AuthoringId = 'psl' | 'typescript';
3
5
 
@@ -11,9 +13,9 @@ export function targetLabel(target: TargetId): string {
11
13
 
12
14
  export function defaultSchemaPath(authoring: AuthoringId): string {
13
15
  if (authoring === 'typescript') {
14
- return 'prisma/contract.ts';
16
+ return `${DEFAULT_CONTRACT_SOURCE_DIR}/contract.ts`;
15
17
  }
16
- return 'prisma/contract.prisma';
18
+ return `${DEFAULT_CONTRACT_SOURCE_DIR}/contract.prisma`;
17
19
  }
18
20
 
19
21
  export function starterSchema(target: TargetId, authoring: AuthoringId): string {
@@ -1,7 +1,7 @@
1
1
  import type { TargetId } from './code-templates';
2
2
 
3
3
  /**
4
- * The minimum supported server version for each target (FR8.1). The
4
+ * The minimum supported server version for each target. The
5
5
  * authoritative source of truth is each target package's
6
6
  * `package.json#prismaNext.minServerVersion` field — this module
7
7
  * mirrors those values and a workspace-level test asserts the two
@@ -9,13 +9,13 @@ import type { TargetId } from './code-templates';
9
9
  *
10
10
  * Bumping a value here in isolation is **not** safe: edit the
11
11
  * corresponding target package's `package.json` first, then mirror
12
- * here. The scaffold's `.env.example` (FR3.1, FR8.2) and the
13
- * "Requirements" section of `prisma-next.md` both read from this
14
- * constant, so a stale value lies to every freshly initialised user.
12
+ * here. The scaffold's `.env.example` and the "Requirements" section
13
+ * of `prisma-next.md` both read from this constant, so a stale value
14
+ * lies to every freshly initialised user.
15
15
  */
16
16
  export const MIN_SERVER_VERSION: Record<TargetId, string> = {
17
- postgres: '14',
18
- mongo: '6.0',
17
+ postgres: '17',
18
+ mongo: '8.0',
19
19
  };
20
20
 
21
21
  export const TARGET_LABEL: Record<TargetId, string> = {
@@ -41,7 +41,7 @@ function envPlaceholderBody(target: TargetId): string {
41
41
  lines.push('DATABASE_URL="postgresql://user:password@localhost:5432/mydb"');
42
42
  } else {
43
43
  lines.push(
44
- '# Standalone local mongod / `docker run mongo:7` — no replica set required for first-run queries.',
44
+ '# Standalone local mongod / `docker run mongo:8` — no replica set required for first-run queries.',
45
45
  );
46
46
  lines.push(
47
47
  '# Transactions and change streams need a replica set; add ?replicaSet=... only after initiating one.',
@@ -54,7 +54,7 @@ function envPlaceholderBody(target: TargetId): string {
54
54
  }
55
55
 
56
56
  /**
57
- * Renders the `.env.example` content for a given target (FR3.1):
57
+ * Renders the `.env.example` content for a given target:
58
58
  *
59
59
  * - Carries a "Copy this file to `.env`…" intro that only makes sense
60
60
  * for the example file (the real `.env` is the destination of that
@@ -63,8 +63,7 @@ function envPlaceholderBody(target: TargetId): string {
63
63
  * shape (Postgres: standard `postgresql://`, Mongo: `mongodb://` plus
64
64
  * a `mydb` database segment so the lazy facade has a `dbName`).
65
65
  * - Carries a `# Requires <db> >= <version>` comment so a fresh user
66
- * knows the minimum supported server before they first try to
67
- * connect (FR8.2).
66
+ * knows the minimum supported server before they first try to connect.
68
67
  */
69
68
  export function envExampleContent(target: TargetId): string {
70
69
  const lines: string[] = [];
@@ -77,10 +76,10 @@ export function envExampleContent(target: TargetId): string {
77
76
 
78
77
  /**
79
78
  * Renders the initial `.env` content for `--write-env` / interactive
80
- * opt-in (FR3.2). Same placeholder body as `.env.example`, **without**
81
- * the example file's "Copy this file to `.env`…" intro: the real `.env`
82
- * is the destination of that copy, so the line would lie. Writing this
83
- * file is gitignored (FR3.3 ensures `.env` lands in `.gitignore`).
79
+ * opt-in. Same placeholder body as `.env.example`, **without** the
80
+ * example file's "Copy this file to `.env`…" intro: the real `.env` is
81
+ * the destination of that copy, so the line would lie. Writing this
82
+ * file is gitignored (`.env` lands in `.gitignore` during init).
84
83
  */
85
84
  export function envFileContent(target: TargetId): string {
86
85
  return envPlaceholderBody(target);
@@ -2,19 +2,18 @@ import { readFile } from 'node:fs/promises';
2
2
  import type { Contract } from '@prisma-next/contract/types';
3
3
  import { createControlStack } from '@prisma-next/framework-components/control';
4
4
  import { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migration-tools/errors';
5
+ import { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';
5
6
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
6
7
  import type { RefEntry } from '@prisma-next/migration-tools/refs';
7
- import { readRefs } from '@prisma-next/migration-tools/refs';
8
8
  import { ifDefined } from '@prisma-next/utils/defined';
9
9
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
10
10
  import { Command } from 'commander';
11
-
12
11
  import { loadConfig } from '../config-loader';
13
12
  import { createControlClient } from '../control-api/client';
14
13
  import type {
15
- AggregatePerSpaceExecutionEntry,
16
14
  MigrationApplyFailure,
17
15
  MigrationApplyPathDecision,
16
+ PerSpaceExecutionEntry,
18
17
  } from '../control-api/types';
19
18
  import {
20
19
  CliStructuredError,
@@ -23,6 +22,8 @@ import {
23
22
  errorDatabaseConnectionRequired,
24
23
  errorDriverRequired,
25
24
  errorFileNotFound,
25
+ errorMarkerMismatch,
26
+ errorPathUnreachable,
26
27
  errorRuntime,
27
28
  errorTargetMigrationNotSupported,
28
29
  errorUnexpected,
@@ -32,7 +33,6 @@ import {
32
33
  import {
33
34
  addGlobalOptions,
34
35
  collectDeclaredInvariants,
35
- loadMigrationPackages,
36
36
  maskConnectionUrl,
37
37
  resolveContractPath,
38
38
  resolveMigrationPaths,
@@ -40,10 +40,17 @@ import {
40
40
  setCommandExamples,
41
41
  targetSupportsMigrations,
42
42
  } from '../utils/command-helpers';
43
+ import { mapContractAtError } from '../utils/contract-at-errors';
44
+ import {
45
+ loadContractSpaceAggregateForCli,
46
+ refuseContractSpaceIntegrity,
47
+ } from '../utils/contract-space-aggregate-loader';
48
+ import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
43
49
  import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
44
50
  import { formatStyledHeader } from '../utils/formatters/styled';
45
51
  import type { CommonCommandOptions } from '../utils/global-flags';
46
52
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
53
+ import { executeRefAdvancement, readContractIR } from '../utils/ref-advancement';
47
54
  import { handleResult } from '../utils/result-handler';
48
55
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
49
56
 
@@ -51,6 +58,7 @@ interface MigrateCommandOptions extends CommonCommandOptions {
51
58
  readonly db?: string;
52
59
  readonly config?: string;
53
60
  readonly to?: string;
61
+ readonly advanceRef?: string;
54
62
  }
55
63
 
56
64
  export interface MigrateResult {
@@ -67,14 +75,18 @@ export interface MigrateResult {
67
75
  readonly operationsExecuted: number;
68
76
  }[];
69
77
  readonly summary: string;
70
- readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
78
+ readonly perSpace: readonly PerSpaceExecutionEntry[];
71
79
  readonly pathDecision?: MigrationApplyPathDecision;
72
80
  readonly timings: {
73
81
  readonly total: number;
74
82
  };
83
+ readonly advancedRef?: { readonly name: string; readonly hash: string } | null;
75
84
  }
76
85
 
77
86
  function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
87
+ if (failure.code === 'MIGRATION_PATH_NOT_FOUND') {
88
+ return errorPathUnreachable(failure);
89
+ }
78
90
  return errorRuntime(failure.summary, {
79
91
  why: failure.why ?? 'Migration runner failed',
80
92
  fix: 'Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved.',
@@ -89,8 +101,10 @@ async function executeMigrateCommand(
89
101
  startTime: number,
90
102
  ): Promise<Result<MigrateResult, CliStructuredErrorType>> {
91
103
  const config = await loadConfig(options.config);
92
- const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
93
- resolveMigrationPaths(options.config, config);
104
+ const { configPath, migrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
105
+ options.config,
106
+ config,
107
+ );
94
108
 
95
109
  const dbConnection = options.db ?? config.db?.connection;
96
110
  if (!dbConnection) {
@@ -118,31 +132,8 @@ async function executeMigrateCommand(
118
132
  );
119
133
  }
120
134
 
121
- let refEntry: RefEntry | undefined;
122
135
  const toArg = options.to;
123
136
 
124
- if (toArg) {
125
- try {
126
- const refs = await readRefs(refsDir);
127
- const { graph } = await loadMigrationPackages(appMigrationsDir);
128
- const refResult = parseContractRef(toArg, { graph, refs });
129
- if (!refResult.ok) {
130
- return notOk(mapRefResolutionError(refResult.failure));
131
- }
132
- if (refResult.value.provenance.kind === 'ref') {
133
- const resolved = refs[refResult.value.provenance.refName];
134
- if (resolved) refEntry = resolved;
135
- } else {
136
- refEntry = { hash: refResult.value.hash, invariants: [] };
137
- }
138
- } catch (error) {
139
- if (MigrationToolsError.is(error)) {
140
- return notOk(mapMigrationToolsError(error));
141
- }
142
- throw error;
143
- }
144
- }
145
-
146
137
  // Construct the family instance up-front so the on-disk contract read
147
138
  // crosses the serializer seam (`familyInstance.deserializeContract`) at
148
139
  // the read site. The downstream `client.migrationApply({ contract })`
@@ -184,6 +175,44 @@ async function executeMigrateCommand(
184
175
  );
185
176
  }
186
177
 
178
+ const loadedAggregate = await loadContractSpaceAggregateForCli({
179
+ targetId: config.target.targetId,
180
+ migrationsDir,
181
+ appContract: contractRaw,
182
+ extensionPacks: config.extensionPacks ?? [],
183
+ deserializeContract: (json) => familyInstance.deserializeContract(json),
184
+ });
185
+ if (!loadedAggregate.ok) {
186
+ return notOk(loadedAggregate.failure);
187
+ }
188
+ const aggregate = loadedAggregate.value;
189
+ const integrityFailure = refuseContractSpaceIntegrity(aggregate, {
190
+ declaredExtensions: toDeclaredExtensionsFromRaw(
191
+ (config.extensionPacks ?? []) as ReadonlyArray<unknown>,
192
+ ),
193
+ checkContracts: true,
194
+ });
195
+ if (integrityFailure) {
196
+ return notOk(integrityFailure);
197
+ }
198
+
199
+ let refEntry: RefEntry | undefined;
200
+ let refName: string | undefined;
201
+ if (toArg) {
202
+ const refs = aggregate.app.refs;
203
+ const refResult = parseContractRef(toArg, { graph: aggregate.app.graph(), refs });
204
+ if (!refResult.ok) {
205
+ return notOk(mapRefResolutionError(refResult.failure));
206
+ }
207
+ if (refResult.value.provenance.kind === 'ref') {
208
+ refName = refResult.value.provenance.refName;
209
+ const resolved = refs[refName];
210
+ if (resolved) refEntry = resolved;
211
+ } else {
212
+ refEntry = { hash: refResult.value.hash, invariants: [] };
213
+ }
214
+ }
215
+
187
216
  if (!flags.json && !flags.quiet) {
188
217
  const details: Array<{ label: string; value: string }> = [
189
218
  { label: 'config', value: configPath },
@@ -208,15 +237,7 @@ async function executeMigrateCommand(
208
237
  ui.stderr(header);
209
238
  }
210
239
 
211
- let appPackages: Awaited<ReturnType<typeof loadMigrationPackages>>;
212
- try {
213
- appPackages = await loadMigrationPackages(appMigrationsDir);
214
- } catch (error) {
215
- if (MigrationToolsError.is(error)) {
216
- return notOk(mapMigrationToolsError(error));
217
- }
218
- throw error;
219
- }
240
+ const appGraph = aggregate.app.graph();
220
241
 
221
242
  const client = createControlClient({
222
243
  family: config.family,
@@ -229,10 +250,21 @@ async function executeMigrateCommand(
229
250
  try {
230
251
  await client.connect(dbConnection);
231
252
 
253
+ const allMarkers = await client.readAllMarkers();
254
+ const appMarker = allMarkers.get('app') ?? null;
255
+
256
+ if (appMarker !== null && !isGraphNode(appMarker.storageHash, appGraph)) {
257
+ return notOk(
258
+ errorMarkerMismatch(
259
+ appMarker.storageHash,
260
+ [...appGraph.nodes].sort(),
261
+ findLatestMigration(appGraph)?.to ?? null,
262
+ ),
263
+ );
264
+ }
265
+
232
266
  if (refEntry && refEntry.invariants.length > 0) {
233
- const allMarkers = await client.readAllMarkers();
234
- const appMarker = allMarkers.get('app') ?? null;
235
- const declared = collectDeclaredInvariants(appPackages.graph);
267
+ const declared = collectDeclaredInvariants(appGraph);
236
268
  const known = new Set<string>(declared);
237
269
  for (const id of appMarker?.invariants ?? []) known.add(id);
238
270
  const unknown = refEntry.invariants.filter((id) => !known.has(id));
@@ -253,10 +285,36 @@ async function executeMigrateCommand(
253
285
  ui.step('Loading contract spaces…');
254
286
  }
255
287
 
288
+ // When `--to` resolves to an on-disk graph node with a matching bundle,
289
+ // verify and apply against THAT bundle's destination contract via
290
+ // `contractAt` — not the emitted `contract.json`. With `--to` omitted,
291
+ // or a target with no matching bundle, the emitted contract stays the
292
+ // apply contract (the only migrate-specific default). The same
293
+ // `contractAt` artifacts feed the optional ref-advancement snapshot.
294
+ let applyContract: Contract = contractRaw;
295
+ let snapshotContractJson: Record<string, unknown> = JSON.parse(contractContent);
296
+ let snapshotContractDts: string | undefined;
297
+ if (toArg && refEntry) {
298
+ const targetHash = refEntry.hash;
299
+ const matchingBundle = aggregate.app.packages.find((p) => p.metadata.to === targetHash);
300
+ if (matchingBundle) {
301
+ try {
302
+ const at = await aggregate.app.contractAt(
303
+ targetHash,
304
+ refName !== undefined ? { refName } : undefined,
305
+ );
306
+ applyContract = at.contract;
307
+ snapshotContractJson = at.contractJson as Record<string, unknown>;
308
+ snapshotContractDts = at.contractDts;
309
+ } catch (error) {
310
+ return mapContractAtError(error, { artifactRole: 'to' });
311
+ }
312
+ }
313
+ }
314
+
256
315
  const applyResult = await client.migrationApply({
257
- contract: contractRaw,
316
+ contract: applyContract,
258
317
  migrationsDir,
259
- appMigrationPackages: appPackages.bundles,
260
318
  ...ifDefined('refHash', refEntry?.hash),
261
319
  ...(refEntry?.invariants ? { refInvariants: refEntry.invariants } : {}),
262
320
  ...(refEntry !== undefined ? ifDefined('refName', toArg) : {}),
@@ -268,6 +326,27 @@ async function executeMigrateCommand(
268
326
 
269
327
  const { value } = applyResult;
270
328
 
329
+ let advancedRef: { name: string; hash: string } | null = null;
330
+ if (options.advanceRef !== undefined) {
331
+ try {
332
+ const contractIR =
333
+ snapshotContractDts !== undefined
334
+ ? { contract: snapshotContractJson, contractDts: snapshotContractDts }
335
+ : await readContractIR(snapshotContractJson, contractPathAbsolute);
336
+ advancedRef = await executeRefAdvancement(
337
+ refsDir,
338
+ options.advanceRef,
339
+ value.markerHash,
340
+ contractIR,
341
+ );
342
+ } catch (error) {
343
+ if (MigrationToolsError.is(error)) {
344
+ return notOk(mapMigrationToolsError(error));
345
+ }
346
+ throw error;
347
+ }
348
+ }
349
+
271
350
  return ok({
272
351
  ok: true,
273
352
  migrationsApplied: value.migrationsApplied,
@@ -278,6 +357,7 @@ async function executeMigrateCommand(
278
357
  perSpace: value.perSpace,
279
358
  ...ifDefined('pathDecision', value.pathDecision),
280
359
  timings: { total: Date.now() - startTime },
360
+ advancedRef,
281
361
  });
282
362
  } catch (error) {
283
363
  if (CliStructuredError.is(error)) {
@@ -318,6 +398,7 @@ export function createMigrateCommand(): Command {
318
398
  '--to <contract>',
319
399
  'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
320
400
  )
401
+ .option('--advance-ref <name>', 'Advance the named ref to the post-apply marker after success')
321
402
  .action(async (options: MigrateCommandOptions) => {
322
403
  const flags = parseGlobalFlagsOrExit(options);
323
404
  const startTime = Date.now();