@prisma-next/cli 0.14.0 → 0.15.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.
- package/README.md +2 -2
- package/dist/cli.mjs +33 -14
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-CJzuo5wX.mjs → client-DwOyZz0a.mjs} +141 -123
- package/dist/client-DwOyZz0a.mjs.map +1 -0
- package/dist/{command-helpers-DGMvGBeX.mjs → command-helpers-CNwURRf9.mjs} +30 -11
- package/dist/command-helpers-CNwURRf9.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +3 -3
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +5 -5
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +37 -38
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.mjs +9 -9
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +6 -6
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.d.mts +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +4 -4
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +4 -4
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +3 -3
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-CFXsstzm.mjs → contract-at-errors-BHbY04pl.mjs} +2 -2
- package/dist/{contract-at-errors-CFXsstzm.mjs.map → contract-at-errors-BHbY04pl.mjs.map} +1 -1
- package/dist/{contract-emit-C8HmtboH.mjs → contract-emit-BeidkyUC.mjs} +9 -11
- package/dist/contract-emit-BeidkyUC.mjs.map +1 -0
- package/dist/{contract-emit-B_qriF8B.mjs → contract-emit-IQBYv351.mjs} +6 -6
- package/dist/contract-emit-IQBYv351.mjs.map +1 -0
- package/dist/{contract-infer-BYT_ra_U.mjs → contract-infer-B3Wq2cu1.mjs} +3 -3
- package/dist/{contract-infer-BYT_ra_U.mjs.map → contract-infer-B3Wq2cu1.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-ClI1KN6d.mjs → contract-space-aggregate-loader-BirzMmW1.mjs} +63 -5
- package/dist/contract-space-aggregate-loader-BirzMmW1.mjs.map +1 -0
- package/dist/{db-verify-C24FKhb7.mjs → db-verify-DpRrecs7.mjs} +57 -61
- package/dist/db-verify-DpRrecs7.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +3 -3
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.d.mts +5 -1
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +4 -3
- package/dist/exports/index.mjs.map +1 -1
- package/dist/format-CaCF63ld.mjs +102 -0
- package/dist/format-CaCF63ld.mjs.map +1 -0
- package/dist/{framework-components-YVQHhPH7.mjs → framework-components-CnBcJDmF.mjs} +2 -2
- package/dist/{framework-components-YVQHhPH7.mjs.map → framework-components-CnBcJDmF.mjs.map} +1 -1
- package/dist/{init-0HwB-Vh8.mjs → init-Boa0LzeJ.mjs} +27 -19
- package/dist/init-Boa0LzeJ.mjs.map +1 -0
- package/dist/{inspect-live-schema-DF6IwcDl.mjs → inspect-live-schema-CldSQjVI.mjs} +4 -4
- package/dist/inspect-live-schema-CldSQjVI.mjs.map +1 -0
- package/dist/{migration-check-VwM8xCZV.mjs → migration-check-CGtYnR9k.mjs} +10 -11
- package/dist/migration-check-CGtYnR9k.mjs.map +1 -0
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-DA-Lhx6o.mjs → migration-command-scaffold-B3m-MMAx.mjs} +4 -4
- package/dist/migration-command-scaffold-B3m-MMAx.mjs.map +1 -0
- package/dist/{migration-graph-command-render-CEez7YUK.mjs → migration-graph-command-render-B83xrnNy.mjs} +24 -24
- package/dist/{migration-graph-command-render-CEez7YUK.mjs.map → migration-graph-command-render-B83xrnNy.mjs.map} +1 -1
- package/dist/{migration-list-DlJJ_38Z.mjs → migration-list-DBU6Pneg.mjs} +16 -16
- package/dist/migration-list-DBU6Pneg.mjs.map +1 -0
- package/dist/{migration-log-CG0qQAFm.mjs → migration-log-BYngyvXa.mjs} +5 -5
- package/dist/migration-log-BYngyvXa.mjs.map +1 -0
- package/dist/{migration-path-target-Ce6OZImp.mjs → migration-path-target-DQa4Caoc.mjs} +2 -2
- package/dist/{migration-path-target-Ce6OZImp.mjs.map → migration-path-target-DQa4Caoc.mjs.map} +1 -1
- package/dist/{migration-plan-W_E8FQOk.mjs → migration-plan-D6bHpCzE.mjs} +30 -30
- package/dist/migration-plan-D6bHpCzE.mjs.map +1 -0
- package/dist/{migration-status-CD-LC2Ip.mjs → migration-status-CpQ5UhcU.mjs} +14 -14
- package/dist/migration-status-CpQ5UhcU.mjs.map +1 -0
- package/dist/{telemetry-BIM4beEO.mjs → telemetry-BQJ_XHa9.mjs} +2 -2
- package/dist/{telemetry-BIM4beEO.mjs.map → telemetry-BQJ_XHa9.mjs.map} +1 -1
- package/dist/{types-C_tYiJYx.d.mts → types-DFoqNIcQ.d.mts} +27 -21
- package/dist/types-DFoqNIcQ.d.mts.map +1 -0
- package/dist/{verify-DcOYZ1tH.mjs → verify-CInGyt4U.mjs} +58 -159
- package/dist/verify-CInGyt4U.mjs.map +1 -0
- package/package.json +28 -30
- package/src/cli.ts +7 -0
- package/src/commands/contract-emit.ts +1 -1
- package/src/commands/db-sign.ts +1 -1
- package/src/commands/db-verify.ts +34 -20
- package/src/commands/format.ts +75 -0
- package/src/commands/init/detect-package-manager.ts +8 -4
- package/src/commands/init/hygiene-package-scripts.ts +16 -3
- package/src/commands/inspect-live-schema.ts +1 -1
- package/src/commands/lsp.ts +32 -0
- package/src/commands/migrate.ts +47 -44
- package/src/commands/migration-check.ts +6 -6
- package/src/commands/migration-graph.ts +5 -5
- package/src/commands/migration-list.ts +13 -13
- package/src/commands/migration-log.ts +1 -1
- package/src/commands/migration-new.ts +1 -1
- package/src/commands/migration-plan.ts +15 -4
- package/src/commands/migration-show.ts +1 -1
- package/src/commands/migration-status.ts +10 -10
- package/src/commands/ref.ts +1 -1
- package/src/control-api/client.ts +8 -23
- package/src/control-api/operations/contract-emit.ts +9 -27
- package/src/control-api/operations/db-init.ts +2 -2
- package/src/control-api/operations/db-run.ts +18 -17
- package/src/control-api/operations/db-verify.ts +52 -44
- package/src/control-api/operations/format.ts +98 -0
- package/src/control-api/operations/migrate.ts +139 -95
- package/src/control-api/operations/run-migration.ts +6 -5
- package/src/control-api/types.ts +8 -8
- package/src/exports/index.ts +1 -0
- package/src/migration-cli.ts +1 -1
- package/src/utils/cli-errors.ts +14 -1
- package/src/utils/combine-verify-results.ts +94 -0
- package/src/utils/extension-pack-inputs.ts +1 -1
- package/src/utils/formatters/errors.ts +34 -5
- package/src/utils/formatters/verify.ts +89 -227
- package/src/utils/migration-command-scaffold.ts +1 -1
- package/src/utils/plan-resolution.ts +18 -18
- package/dist/client-CJzuo5wX.mjs.map +0 -1
- package/dist/command-helpers-DGMvGBeX.mjs.map +0 -1
- package/dist/config-loader-p9JMrekQ.mjs +0 -88
- package/dist/config-loader-p9JMrekQ.mjs.map +0 -1
- package/dist/config-loader.d.mts +0 -16
- package/dist/config-loader.d.mts.map +0 -1
- package/dist/config-loader.mjs +0 -2
- package/dist/contract-emit-B_qriF8B.mjs.map +0 -1
- package/dist/contract-emit-C8HmtboH.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-ClI1KN6d.mjs.map +0 -1
- package/dist/db-verify-C24FKhb7.mjs.map +0 -1
- package/dist/extension-pack-inputs-1ySHqxKG.mjs +0 -62
- package/dist/extension-pack-inputs-1ySHqxKG.mjs.map +0 -1
- package/dist/init-0HwB-Vh8.mjs.map +0 -1
- package/dist/inspect-live-schema-DF6IwcDl.mjs.map +0 -1
- package/dist/migration-check-VwM8xCZV.mjs.map +0 -1
- package/dist/migration-command-scaffold-DA-Lhx6o.mjs.map +0 -1
- package/dist/migration-list-DlJJ_38Z.mjs.map +0 -1
- package/dist/migration-log-CG0qQAFm.mjs.map +0 -1
- package/dist/migration-plan-W_E8FQOk.mjs.map +0 -1
- package/dist/migration-status-CD-LC2Ip.mjs.map +0 -1
- package/dist/types-C_tYiJYx.d.mts.map +0 -1
- package/dist/verify-DcOYZ1tH.mjs.map +0 -1
- package/src/config-loader.ts +0 -92
- package/src/config-path-validation.ts +0 -74
- package/src/utils/combine-schema-results.ts +0 -84
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Backs the `migrate` command.
|
|
2
|
+
* Backs the `migrate` command. Resolves the recorded path for every space, replay-only (no introspect/diff/planner).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { Contract } from '@prisma-next/contract/types';
|
|
@@ -11,13 +11,14 @@ import type {
|
|
|
11
11
|
TargetMigrationsCapability,
|
|
12
12
|
} from '@prisma-next/framework-components/control';
|
|
13
13
|
import {
|
|
14
|
-
|
|
14
|
+
type AggregateContractSpace,
|
|
15
|
+
allStorageElementsExternal,
|
|
16
|
+
buildFabricatedMigrationEdge,
|
|
15
17
|
type ContractMarkerRecordLike,
|
|
16
18
|
type ContractSpaceAggregate,
|
|
17
|
-
type ContractSpaceMember,
|
|
18
|
-
graphWalkStrategy,
|
|
19
19
|
type PerSpacePlan,
|
|
20
20
|
requireHeadRef,
|
|
21
|
+
resolveRecordedPath,
|
|
21
22
|
} from '@prisma-next/migration-tools/aggregate';
|
|
22
23
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
23
24
|
import { errorNoInvariantPath } from '@prisma-next/migration-tools/errors';
|
|
@@ -62,8 +63,8 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
|
|
|
62
63
|
readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
63
64
|
readonly targetId: TTargetId;
|
|
64
65
|
/**
|
|
65
|
-
* Optional app-space ref override. When provided, the app
|
|
66
|
-
* graph-walk targets this hash instead of `
|
|
66
|
+
* Optional app-space ref override. When provided, the app space's
|
|
67
|
+
* graph-walk targets this hash instead of `space.headRef.hash`.
|
|
67
68
|
* Extensions are unaffected — they always walk to their own head.
|
|
68
69
|
*/
|
|
69
70
|
readonly refHash?: string;
|
|
@@ -72,7 +73,7 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
|
|
|
72
73
|
* Threaded into the graph-walk's `required` calculation so the
|
|
73
74
|
* planner picks an invariant-bearing path and surfaces the
|
|
74
75
|
* required/satisfied set on the success envelope. When `refHash`
|
|
75
|
-
* is absent the file's `
|
|
76
|
+
* is absent the file's `space.headRef.invariants` are used.
|
|
76
77
|
*/
|
|
77
78
|
readonly refInvariants?: readonly string[];
|
|
78
79
|
/**
|
|
@@ -89,22 +90,26 @@ export interface ExecuteMigrateOptions<TFamilyId extends string, TTargetId exten
|
|
|
89
90
|
/**
|
|
90
91
|
* Apply pending migrations across every contract space (app +
|
|
91
92
|
* extensions). Replay-only: graph-walk against the on-disk graph for
|
|
92
|
-
* every
|
|
93
|
+
* every contract space; no synth, no introspection.
|
|
93
94
|
*
|
|
94
95
|
* Pipeline:
|
|
95
96
|
*
|
|
96
97
|
* 1. Load aggregate from disk (loader hydrates extension graphs;
|
|
97
98
|
* caller provides app-space packages).
|
|
98
99
|
* 2. Read live marker rows per space (`familyInstance.readAllMarkers`).
|
|
99
|
-
* 3. Per
|
|
100
|
-
* marker to `
|
|
101
|
-
*
|
|
102
|
-
*
|
|
100
|
+
* 3. Per space: `resolveRecordedPath` plots the path from the live
|
|
101
|
+
* marker to `space.headRef.hash` (or `refHash` for the app
|
|
102
|
+
* space when provided). An empty-graph space whose elements are ALL
|
|
103
|
+
* externally managed resolves declaratively (marker to head, zero
|
|
104
|
+
* ops), mirroring the db-init aggregate planner: such a space ships
|
|
105
|
+
* no DDL and has nothing to author. Every other empty-graph space
|
|
106
|
+
* fails loudly — "never planned" is a user-error condition for
|
|
107
|
+
* replay.
|
|
103
108
|
* 4. Hand off to {@link runMigration} (the runner-driving tail
|
|
104
109
|
* shared with `db init` / `db update`). Marker advancement is
|
|
105
110
|
* inside the per-space transaction.
|
|
106
111
|
*
|
|
107
|
-
* Encodes the replay-only contract:
|
|
112
|
+
* Encodes the replay-only contract: the app contract space must have an
|
|
108
113
|
* authored migration graph on disk before this operation can advance it.
|
|
109
114
|
*/
|
|
110
115
|
export async function executeMigrate<TFamilyId extends string, TTargetId extends string>(
|
|
@@ -140,41 +145,41 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
140
145
|
|
|
141
146
|
const markerRows = await familyInstance.readAllMarkers({ driver });
|
|
142
147
|
|
|
143
|
-
// Plan every
|
|
148
|
+
// Plan every space via graph-walk. App space targets `refHash`
|
|
144
149
|
// when provided, otherwise its own head; extensions always walk
|
|
145
150
|
// to their own head ref.
|
|
146
|
-
const
|
|
151
|
+
const allSpaces: ReadonlyArray<AggregateContractSpace> = [aggregate.app, ...aggregate.extensions];
|
|
147
152
|
const perSpacePlans = new Map<string, PerSpacePlan>();
|
|
148
|
-
// Already-at-head empty-graph
|
|
153
|
+
// Already-at-head empty-graph spaces (typically extensions whose
|
|
149
154
|
// head ref is the empty sentinel, or whose live marker already
|
|
150
155
|
// matches the target). Kept out of the runner schedule so we don't
|
|
151
156
|
// write spurious markers for greenfield extensions, but merged back
|
|
152
|
-
// into the success envelope so every loaded
|
|
157
|
+
// into the success envelope so every loaded space is represented.
|
|
153
158
|
const atHeadResolutions = new Map<string, PerSpacePlan>();
|
|
154
|
-
for (const
|
|
155
|
-
const
|
|
156
|
-
// The aggregate passed the integrity gate, so every
|
|
159
|
+
for (const space of allSpaces) {
|
|
160
|
+
const isAppSpace = space.spaceId === aggregate.app.spaceId;
|
|
161
|
+
// The aggregate passed the integrity gate, so every space's head ref
|
|
157
162
|
// is resolved (the app's is synthesised from the live contract).
|
|
158
|
-
const headRef = requireHeadRef(
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
const liveMarker = markerRows.get(
|
|
163
|
+
const headRef = requireHeadRef(space);
|
|
164
|
+
const spaceTargetHash = isAppSpace && refHash !== undefined ? refHash : headRef.hash;
|
|
165
|
+
const spaceRefInvariants = isAppSpace && refHash !== undefined ? refInvariants : undefined;
|
|
166
|
+
const liveMarker = markerRows.get(space.spaceId) ?? null;
|
|
162
167
|
|
|
163
|
-
const outcome =
|
|
164
|
-
|
|
168
|
+
const outcome = planSpacePath({
|
|
169
|
+
space,
|
|
165
170
|
aggregate,
|
|
166
|
-
targetHash:
|
|
167
|
-
refInvariants:
|
|
171
|
+
targetHash: spaceTargetHash,
|
|
172
|
+
refInvariants: spaceRefInvariants,
|
|
168
173
|
liveMarker,
|
|
169
|
-
...(
|
|
174
|
+
...(isAppSpace ? { refName } : {}),
|
|
170
175
|
});
|
|
171
176
|
|
|
172
177
|
if (outcome.kind === 'at-head') {
|
|
173
|
-
// Empty-graph
|
|
178
|
+
// Empty-graph space whose live marker already matches the target.
|
|
174
179
|
// Kept out of the runner schedule so we don't write spurious markers
|
|
175
180
|
// for greenfield extensions, but merged back into the success envelope
|
|
176
|
-
// so every loaded
|
|
177
|
-
atHeadResolutions.set(
|
|
181
|
+
// so every loaded space is represented.
|
|
182
|
+
atHeadResolutions.set(space.spaceId, outcome.plan);
|
|
178
183
|
continue;
|
|
179
184
|
}
|
|
180
185
|
if (outcome.kind === 'never-planned') {
|
|
@@ -197,9 +202,9 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
197
202
|
// is never a graph node, producing an empty `structuralPath` and
|
|
198
203
|
// a less actionable diagnostic.
|
|
199
204
|
const structural = findPathWithDecision(
|
|
200
|
-
outcome.
|
|
205
|
+
outcome.targetSpace.graph(),
|
|
201
206
|
outcome.liveHash,
|
|
202
|
-
|
|
207
|
+
spaceTargetHash,
|
|
203
208
|
{ required: new Set<string>() },
|
|
204
209
|
);
|
|
205
210
|
const structuralPath =
|
|
@@ -220,18 +225,23 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
220
225
|
});
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
perSpacePlans.set(
|
|
228
|
+
perSpacePlans.set(space.spaceId, outcome.plan);
|
|
224
229
|
}
|
|
225
230
|
|
|
226
231
|
const canonicalOrder = [...aggregate.extensions.map((m) => m.spaceId), aggregate.app.spaceId];
|
|
227
232
|
const applyOrder = canonicalOrder.filter((spaceId) => perSpacePlans.has(spaceId));
|
|
228
233
|
|
|
229
234
|
// Short-circuit: nothing pending across any space (no runner-bound
|
|
230
|
-
// plans). Surfaces every loaded
|
|
235
|
+
// plans). Surfaces every loaded space — including at-head empty-
|
|
231
236
|
// graph extensions — in `perSpace[]` so the result reflects the
|
|
232
237
|
// full aggregate, not just the spaces the runner would have touched.
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
// A zero-op plan still counts as pending when it advances a marker
|
|
239
|
+
// (declared-state resolution for an all-external extension space).
|
|
240
|
+
const hasPendingWork = applyOrder.some((spaceId) => {
|
|
241
|
+
const entry = perSpacePlans.get(spaceId);
|
|
242
|
+
return entry !== undefined && planRequiresExecution(entry);
|
|
243
|
+
});
|
|
244
|
+
if (!hasPendingWork) {
|
|
235
245
|
const ordered = canonicalOrder
|
|
236
246
|
.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId))
|
|
237
247
|
.map((spaceId) => {
|
|
@@ -285,7 +295,7 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
285
295
|
}
|
|
286
296
|
|
|
287
297
|
// Merge at-head zero-op resolutions back into the canonical order
|
|
288
|
-
// so the success envelope surfaces every loaded
|
|
298
|
+
// so the success envelope surfaces every loaded space, not just
|
|
289
299
|
// those the runner executed.
|
|
290
300
|
const orderedAll = canonicalOrder
|
|
291
301
|
.filter((spaceId) => perSpacePlans.has(spaceId) || atHeadResolutions.has(spaceId))
|
|
@@ -321,16 +331,16 @@ export async function executeMigrate<TFamilyId extends string, TTargetId extends
|
|
|
321
331
|
}
|
|
322
332
|
|
|
323
333
|
/**
|
|
324
|
-
* Outcome variants for one
|
|
334
|
+
* Outcome variants for one space's path computation.
|
|
325
335
|
*
|
|
326
336
|
* Callers switch on `kind` and map to their own error representation:
|
|
327
337
|
* `executeMigrate` throws / returns `notOk`; `executeMigrateShowCommand`
|
|
328
338
|
* returns a CLI structured error. The shared discriminant guarantees both
|
|
329
|
-
* paths feed `
|
|
339
|
+
* paths feed `resolveRecordedPath` the same inputs.
|
|
330
340
|
*
|
|
331
341
|
* @internal Exported for `executeMigrateShowCommand` to call.
|
|
332
342
|
*/
|
|
333
|
-
export type
|
|
343
|
+
export type SpacePathOutcome =
|
|
334
344
|
| { readonly kind: 'ok'; readonly plan: PerSpacePlan }
|
|
335
345
|
| { readonly kind: 'at-head'; readonly plan: PerSpacePlan }
|
|
336
346
|
| { readonly kind: 'never-planned'; readonly spaceId: string; readonly targetHash: string }
|
|
@@ -343,16 +353,16 @@ export type MemberPathOutcome =
|
|
|
343
353
|
| {
|
|
344
354
|
readonly kind: 'unsatisfiable';
|
|
345
355
|
readonly spaceId: string;
|
|
346
|
-
readonly
|
|
356
|
+
readonly isAppSpace: boolean;
|
|
347
357
|
readonly missing: readonly string[];
|
|
348
358
|
readonly targetInvariants: readonly string[];
|
|
349
|
-
readonly
|
|
359
|
+
readonly targetSpace: AggregateContractSpace;
|
|
350
360
|
readonly liveHash: string;
|
|
351
361
|
readonly refName: string | undefined;
|
|
352
362
|
};
|
|
353
363
|
|
|
354
364
|
/**
|
|
355
|
-
* Compute the graph-walk path for one contract
|
|
365
|
+
* Compute the graph-walk path for one contract space.
|
|
356
366
|
*
|
|
357
367
|
* Encapsulates the invariant-correct input assembly that both
|
|
358
368
|
* `executeMigrate` and `executeMigrateShowCommand` must use:
|
|
@@ -361,71 +371,104 @@ export type MemberPathOutcome =
|
|
|
361
371
|
* - `targetInvariants` uses the caller-supplied `refInvariants` when a
|
|
362
372
|
* `--to` ref was resolved (not always the file head ref's invariants).
|
|
363
373
|
*
|
|
364
|
-
* Both callers map the returned `
|
|
374
|
+
* Both callers map the returned `SpacePathOutcome` to their own error
|
|
365
375
|
* representation; the path-compute logic is shared and identical.
|
|
366
376
|
*
|
|
367
377
|
* @internal Exported for `executeMigrateShowCommand`.
|
|
368
378
|
*/
|
|
369
|
-
export function
|
|
370
|
-
|
|
379
|
+
export function planSpacePath({
|
|
380
|
+
space,
|
|
371
381
|
aggregate,
|
|
372
382
|
targetHash,
|
|
373
383
|
refInvariants,
|
|
374
384
|
liveMarker,
|
|
375
385
|
refName,
|
|
376
386
|
}: {
|
|
377
|
-
readonly
|
|
387
|
+
readonly space: AggregateContractSpace;
|
|
378
388
|
readonly aggregate: Pick<ContractSpaceAggregate, 'targetId' | 'app'>;
|
|
379
389
|
readonly targetHash: string;
|
|
380
390
|
readonly refInvariants: readonly string[] | undefined;
|
|
381
391
|
readonly liveMarker: ContractMarkerRecordLike | null;
|
|
382
392
|
readonly refName?: string;
|
|
383
|
-
}):
|
|
384
|
-
const
|
|
385
|
-
const headRef = requireHeadRef(
|
|
393
|
+
}): SpacePathOutcome {
|
|
394
|
+
const isAppSpace = space.spaceId === aggregate.app.spaceId;
|
|
395
|
+
const headRef = requireHeadRef(space);
|
|
386
396
|
|
|
387
|
-
if (
|
|
397
|
+
if (space.graph().nodes.size === 0) {
|
|
388
398
|
const liveHash = liveMarker?.storageHash;
|
|
389
399
|
if (targetHash === liveHash || (liveHash === undefined && targetHash === EMPTY_CONTRACT_HASH)) {
|
|
390
400
|
return {
|
|
391
401
|
kind: 'at-head',
|
|
392
402
|
plan: buildAtHeadResolution({
|
|
393
403
|
aggregateTargetId: aggregate.targetId,
|
|
394
|
-
|
|
404
|
+
space,
|
|
395
405
|
targetHash,
|
|
396
406
|
liveMarker,
|
|
397
407
|
}),
|
|
398
408
|
};
|
|
399
409
|
}
|
|
400
|
-
|
|
410
|
+
// Empty-graph extension space not yet at head: the space ships no
|
|
411
|
+
// migration packages at all. Advancing the marker without migrations
|
|
412
|
+
// (the db-init aggregate planner's declared-state strategy, mirrored
|
|
413
|
+
// here) is valid exclusively when every element the space declares is
|
|
414
|
+
// externally managed — nothing Prisma Next owns exists in such a space
|
|
415
|
+
// (e.g. Supabase's auth/storage), so its declared state needs no
|
|
416
|
+
// migration to be true, and there is no command that could author an
|
|
417
|
+
// edge for it. A space that declares a managed element but ships no
|
|
418
|
+
// migration graph is an authoring bug: it falls through to the
|
|
419
|
+
// never-planned failure, like an app space with no authored graph.
|
|
420
|
+
if (!isAppSpace && allStorageElementsExternal(space.contract())) {
|
|
421
|
+
if (headRef.invariants.length > 0) {
|
|
422
|
+
return {
|
|
423
|
+
kind: 'unsatisfiable',
|
|
424
|
+
spaceId: space.spaceId,
|
|
425
|
+
isAppSpace,
|
|
426
|
+
missing: [...headRef.invariants].sort(),
|
|
427
|
+
targetInvariants: headRef.invariants,
|
|
428
|
+
targetSpace: space,
|
|
429
|
+
liveHash: liveHash ?? EMPTY_CONTRACT_HASH,
|
|
430
|
+
refName: undefined,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
kind: 'ok',
|
|
435
|
+
plan: buildAtHeadResolution({
|
|
436
|
+
aggregateTargetId: aggregate.targetId,
|
|
437
|
+
space,
|
|
438
|
+
targetHash,
|
|
439
|
+
liveMarker,
|
|
440
|
+
}),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
return { kind: 'never-planned', spaceId: space.spaceId, targetHash };
|
|
401
444
|
}
|
|
402
445
|
|
|
403
446
|
const targetInvariants =
|
|
404
|
-
|
|
405
|
-
const
|
|
447
|
+
isAppSpace && refInvariants !== undefined ? refInvariants : headRef.invariants;
|
|
448
|
+
const targetSpace: AggregateContractSpace =
|
|
406
449
|
targetHash === headRef.hash && targetInvariants === headRef.invariants
|
|
407
|
-
?
|
|
408
|
-
: { ...
|
|
450
|
+
? space
|
|
451
|
+
: { ...space, headRef: { hash: targetHash, invariants: targetInvariants } };
|
|
409
452
|
|
|
410
|
-
const walked =
|
|
453
|
+
const walked = resolveRecordedPath({
|
|
411
454
|
aggregateTargetId: aggregate.targetId,
|
|
412
|
-
|
|
455
|
+
space: targetSpace,
|
|
413
456
|
currentMarker: liveMarker,
|
|
414
|
-
...(
|
|
457
|
+
...(isAppSpace && refName !== undefined ? { refName } : {}),
|
|
415
458
|
});
|
|
416
459
|
|
|
417
460
|
if (walked.kind === 'unreachable') {
|
|
418
|
-
return { kind: 'unreachable', spaceId:
|
|
461
|
+
return { kind: 'unreachable', spaceId: space.spaceId, liveMarker, targetHash };
|
|
419
462
|
}
|
|
420
463
|
if (walked.kind === 'unsatisfiable') {
|
|
421
464
|
const liveHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
|
|
422
465
|
return {
|
|
423
466
|
kind: 'unsatisfiable',
|
|
424
|
-
spaceId:
|
|
425
|
-
|
|
467
|
+
spaceId: space.spaceId,
|
|
468
|
+
isAppSpace,
|
|
426
469
|
missing: walked.missing,
|
|
427
470
|
targetInvariants,
|
|
428
|
-
|
|
471
|
+
targetSpace,
|
|
429
472
|
liveHash,
|
|
430
473
|
refName,
|
|
431
474
|
};
|
|
@@ -434,33 +477,35 @@ export function planMemberPath({
|
|
|
434
477
|
}
|
|
435
478
|
|
|
436
479
|
/**
|
|
437
|
-
* Build a zero-op {@link PerSpacePlan} for an empty-graph
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
480
|
+
* Build a zero-op {@link PerSpacePlan} for an empty-graph space —
|
|
481
|
+
* either one whose live marker already matches the target (at-head), or
|
|
482
|
+
* an all-external extension space whose marker must advance to the head
|
|
483
|
+
* ref with no DDL (declared-state). Lets the apply pipeline thread the
|
|
484
|
+
* space through `perSpacePlans` -> `applyOrder` -> the success
|
|
485
|
+
* envelope's `perSpace[]` block so the result reflects every loaded
|
|
486
|
+
* space, even when there is nothing to execute.
|
|
442
487
|
*/
|
|
443
488
|
function buildAtHeadResolution(args: {
|
|
444
489
|
readonly aggregateTargetId: string;
|
|
445
|
-
readonly
|
|
490
|
+
readonly space: AggregateContractSpace;
|
|
446
491
|
readonly targetHash: string;
|
|
447
492
|
readonly liveMarker: ContractMarkerRecordLike | null;
|
|
448
493
|
}): PerSpacePlan {
|
|
449
|
-
const { aggregateTargetId,
|
|
494
|
+
const { aggregateTargetId, space, targetHash, liveMarker } = args;
|
|
450
495
|
return {
|
|
451
496
|
plan: {
|
|
452
497
|
targetId: aggregateTargetId,
|
|
453
|
-
spaceId:
|
|
498
|
+
spaceId: space.spaceId,
|
|
454
499
|
origin: liveMarker === null ? null : { storageHash: liveMarker.storageHash },
|
|
455
500
|
destination: { storageHash: targetHash },
|
|
456
501
|
operations: [],
|
|
457
502
|
providedInvariants: [],
|
|
458
503
|
},
|
|
459
504
|
displayOps: [],
|
|
460
|
-
destinationContract:
|
|
461
|
-
strategy: '
|
|
505
|
+
destinationContract: space.contract(),
|
|
506
|
+
strategy: 'declared-state',
|
|
462
507
|
migrationEdges: [
|
|
463
|
-
|
|
508
|
+
buildFabricatedMigrationEdge({
|
|
464
509
|
currentMarkerStorageHash: liveMarker?.storageHash,
|
|
465
510
|
destinationStorageHash: targetHash,
|
|
466
511
|
operationCount: 0,
|
|
@@ -469,17 +514,14 @@ function buildAtHeadResolution(args: {
|
|
|
469
514
|
};
|
|
470
515
|
}
|
|
471
516
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
total += entry.plan.operations.length;
|
|
481
|
-
}
|
|
482
|
-
return total;
|
|
517
|
+
/**
|
|
518
|
+
* A plan needs the runner when it executes operations or advances the
|
|
519
|
+
* space's marker (a declared-state resolution has zero operations but a
|
|
520
|
+
* destination hash the live marker doesn't carry yet).
|
|
521
|
+
*/
|
|
522
|
+
function planRequiresExecution(entry: PerSpacePlan): boolean {
|
|
523
|
+
if (entry.plan.operations.length > 0) return true;
|
|
524
|
+
return entry.plan.origin?.storageHash !== entry.plan.destination.storageHash;
|
|
483
525
|
}
|
|
484
526
|
|
|
485
527
|
interface BuildSuccessArgs {
|
|
@@ -494,7 +536,7 @@ interface BuildSuccessArgs {
|
|
|
494
536
|
}
|
|
495
537
|
|
|
496
538
|
function buildSuccess(args: BuildSuccessArgs): MigrateSuccess {
|
|
497
|
-
// The marker hash surfaced at the top level is the **app
|
|
539
|
+
// The marker hash surfaced at the top level is the **app space's**
|
|
498
540
|
// post-migrate marker (the top-level `markerHash` field).
|
|
499
541
|
// Per-space markers live on `perSpace[].marker.storageHash`.
|
|
500
542
|
const appResolution = args.orderedResolutions.find(
|
|
@@ -552,10 +594,12 @@ function buildSuccess(args: BuildSuccessArgs): MigrateSuccess {
|
|
|
552
594
|
}
|
|
553
595
|
|
|
554
596
|
/**
|
|
555
|
-
* Build the `neverPlanned` failure raised when a contract space
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
597
|
+
* Build the `neverPlanned` failure raised when a contract space that
|
|
598
|
+
* declares managed storage elements has no on-disk migration graph but
|
|
599
|
+
* migrate was asked to reach a target hash. All-external spaces never reach
|
|
600
|
+
* this: they resolve declaratively (marker advances to the head ref with
|
|
601
|
+
* zero operations). The `why` states only the condition; the recovery
|
|
602
|
+
* sequence is composed by `errorPathUnreachable`'s `fix`.
|
|
559
603
|
*
|
|
560
604
|
* @internal Exported for testing only.
|
|
561
605
|
*/
|
|
@@ -563,7 +607,7 @@ export function buildNeverPlannedFailure(spaceId: string, targetHash: string): M
|
|
|
563
607
|
return {
|
|
564
608
|
code: 'MIGRATION_PATH_NOT_FOUND',
|
|
565
609
|
summary: `No on-disk migrations for contract space "${spaceId}"`,
|
|
566
|
-
why: `migrate is replay-only:
|
|
610
|
+
why: `migrate is replay-only: a contract space that declares managed storage elements must have an authored migration graph on disk. Space "${spaceId}" has no migrations under \`migrations/${spaceId}/\` but its head ref targets "${targetHash}".`,
|
|
567
611
|
meta: { spaceId, target: targetHash, kind: 'neverPlanned' },
|
|
568
612
|
};
|
|
569
613
|
}
|
|
@@ -586,7 +630,7 @@ export function buildPathNotFoundFailure(
|
|
|
586
630
|
const fromHash = marker?.storageHash ?? '<empty>';
|
|
587
631
|
// The app-case phrasing names the user-visible condition (a
|
|
588
632
|
// contract has been emitted that no on-disk migration reaches) so
|
|
589
|
-
// the error reads naturally for the app
|
|
633
|
+
// the error reads naturally for the app space. Extension spaces
|
|
590
634
|
// see the same condition expressed against the offending space.
|
|
591
635
|
const summary =
|
|
592
636
|
spaceId === 'app'
|
|
@@ -45,10 +45,11 @@ export interface RunMigrationInputs<TFamilyId extends string, TTargetId extends
|
|
|
45
45
|
readonly aggregate: ContractSpaceAggregate;
|
|
46
46
|
/**
|
|
47
47
|
* Per-space plans, keyed by `spaceId`. Produced by either the full
|
|
48
|
-
* {@link planMigration} pipeline (`db init` / `db update` —
|
|
49
|
-
* for the app,
|
|
50
|
-
* {@link
|
|
51
|
-
* for every
|
|
48
|
+
* {@link planMigration} pipeline (`db init` / `db update` —
|
|
49
|
+
* `planFromDiff` for the app, `resolveRecordedPath` for extensions) or
|
|
50
|
+
* by direct {@link resolveRecordedPath} calls (`migrate` — resolves
|
|
51
|
+
* the recorded path for every contract space). Either way, the runner
|
|
52
|
+
* consumes the same shape.
|
|
52
53
|
*/
|
|
53
54
|
readonly perSpacePlans: ReadonlyMap<string, PerSpacePlan>;
|
|
54
55
|
/**
|
|
@@ -229,7 +230,7 @@ export function buildPerSpaceBreakdown(
|
|
|
229
230
|
|
|
230
231
|
/**
|
|
231
232
|
* Materialise the `applyOrder` ordering into resolved per-space
|
|
232
|
-
* entries. Throws if the planner output is missing a
|
|
233
|
+
* entries. Throws if the planner output is missing a contract space listed
|
|
233
234
|
* in `applyOrder` — a wiring bug that should never reach runtime.
|
|
234
235
|
*
|
|
235
236
|
* Exported so callers building their own success envelopes after a
|
package/src/control-api/types.ts
CHANGED
|
@@ -545,7 +545,7 @@ export type EmitResult = Result<EmitSuccess, EmitFailure>;
|
|
|
545
545
|
*
|
|
546
546
|
* The control-api operation is responsible for: loading the
|
|
547
547
|
* contract-space aggregate, reading per-space marker rows from the
|
|
548
|
-
* live database, plotting per-space paths via `
|
|
548
|
+
* live database, plotting per-space paths via `resolveRecordedPath`
|
|
549
549
|
* (replay-only — no synth, no introspection), and dispatching
|
|
550
550
|
* through the shared `runMigration` primitive. The CLI command
|
|
551
551
|
* just resolves the descriptor surface (config, refs, contract
|
|
@@ -557,9 +557,9 @@ export interface MigrateOptions {
|
|
|
557
557
|
/** Migrations root directory (`migrations/` under the project). */
|
|
558
558
|
readonly migrationsDir: string;
|
|
559
559
|
/**
|
|
560
|
-
* Optional app-space ref override. When provided, the app
|
|
560
|
+
* Optional app-space ref override. When provided, the app space's
|
|
561
561
|
* graph-walk targets this hash instead of `contract.storage.storageHash`.
|
|
562
|
-
* Extension
|
|
562
|
+
* Extension spaces always walk to their own `headRef.hash`.
|
|
563
563
|
*/
|
|
564
564
|
readonly refHash?: string;
|
|
565
565
|
/**
|
|
@@ -631,17 +631,17 @@ export interface MigrateRanEntry {
|
|
|
631
631
|
|
|
632
632
|
/**
|
|
633
633
|
* Successful migrate result. Carries both the top-level fields
|
|
634
|
-
* (`markerHash` is the **app
|
|
634
|
+
* (`markerHash` is the **app space's** post-migrate marker) and the
|
|
635
635
|
* per-space breakdown (`perSpace` — markers / operations in canonical
|
|
636
636
|
* schedule order).
|
|
637
637
|
*/
|
|
638
638
|
/**
|
|
639
|
-
* Path-decision summary for the **app
|
|
639
|
+
* Path-decision summary for the **app space** post-migrate. Surfaced
|
|
640
640
|
* at the top level (and consumed by the cli-journeys suite, which
|
|
641
641
|
* inspects `requiredInvariants`/`satisfiedInvariants`/
|
|
642
642
|
* `selectedPath` to validate invariant routing).
|
|
643
643
|
*
|
|
644
|
-
* Per-space path decisions for extension
|
|
644
|
+
* Per-space path decisions for extension spaces are not surfaced —
|
|
645
645
|
* extensions own their own ref/invariant control.
|
|
646
646
|
*/
|
|
647
647
|
export interface MigratePathDecision {
|
|
@@ -673,7 +673,7 @@ export interface MigrateSuccess {
|
|
|
673
673
|
*/
|
|
674
674
|
readonly perSpace: ReadonlyArray<PerSpaceExecutionEntry>;
|
|
675
675
|
/**
|
|
676
|
-
* Path-decision data for the app
|
|
676
|
+
* Path-decision data for the app space. Present whenever the
|
|
677
677
|
* graph-walk strategy ran for the app (i.e. always for the
|
|
678
678
|
* aggregate-walking migrate path). Absent only for the no-op
|
|
679
679
|
* "Already up to date" early return when the app has no plan.
|
|
@@ -864,7 +864,7 @@ export interface ControlClient {
|
|
|
864
864
|
* markers, and (unless `skipSchema` is true) per-space schema
|
|
865
865
|
* verification with pre-projection (closes F23).
|
|
866
866
|
*
|
|
867
|
-
* @returns Result pattern: per-space
|
|
867
|
+
* @returns Result pattern: per-space verify results on success;
|
|
868
868
|
* structured CLI error on marker / loader failure.
|
|
869
869
|
* @throws If not connected or infrastructure failure
|
|
870
870
|
*/
|
package/src/exports/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// CLI-specific exports
|
|
2
2
|
export { createContractEmitCommand } from '../commands/contract-emit';
|
|
3
|
+
export { createFormatCommand } from '../commands/format';
|
|
3
4
|
export type { LoadTsContractOptions } from '../load-ts-contract';
|
|
4
5
|
export { loadContractFromTs } from '../load-ts-contract';
|
package/src/migration-cli.ts
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
import { readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
46
46
|
import type { Writable } from 'node:stream';
|
|
47
47
|
import { fileURLToPath } from 'node:url';
|
|
48
|
+
import { loadConfig } from '@prisma-next/config-loader';
|
|
48
49
|
import {
|
|
49
50
|
CliStructuredError,
|
|
50
51
|
errorMigrationCliInvalidConfigArg,
|
|
@@ -57,7 +58,6 @@ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
|
57
58
|
import { buildMigrationArtifacts, type Migration } from '@prisma-next/migration-tools/migration';
|
|
58
59
|
import { Cli, Command, Option, UsageError } from 'clipanion';
|
|
59
60
|
import { dirname, join } from 'pathe';
|
|
60
|
-
import { loadConfig } from './config-loader';
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Constructor shape accepted by `MigrationCLI.run`. `Migration` subclasses
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -287,7 +287,18 @@ export function errorPathUnreachable(failure: MigrateFailure): CliStructuredErro
|
|
|
287
287
|
// Plan-then-apply recovery. The planner destination is the missing edge's
|
|
288
288
|
// target; `migration plan --to` (built for arbitrary targets) makes this a
|
|
289
289
|
// real command, so the diagnostic that sends you here is now honest.
|
|
290
|
+
//
|
|
291
|
+
// Never-planned spaces have an EMPTY migration graph, and contract-ref
|
|
292
|
+
// resolution only resolves full hashes against graph nodes — a
|
|
293
|
+
// `--to <hash>` remediation would reject its own input. `migration plan`
|
|
294
|
+
// without `--to` targets the working contract (the same contract the app
|
|
295
|
+
// space's synthesized head ref carries), so the bare form is the one that
|
|
296
|
+
// runs verbatim.
|
|
297
|
+
const neverPlanned = meta['kind'] === 'neverPlanned';
|
|
290
298
|
const planCommand = (() => {
|
|
299
|
+
if (neverPlanned) {
|
|
300
|
+
return 'prisma-next migration plan --name <slug>';
|
|
301
|
+
}
|
|
291
302
|
if (planFromHash !== null && targetHash !== null) {
|
|
292
303
|
return `prisma-next migration plan --from ${planFromHash} --to ${targetHash} --name <slug>`;
|
|
293
304
|
}
|
|
@@ -300,7 +311,9 @@ export function errorPathUnreachable(failure: MigrateFailure): CliStructuredErro
|
|
|
300
311
|
return 'prisma-next migration plan';
|
|
301
312
|
})();
|
|
302
313
|
const applyCommand =
|
|
303
|
-
targetHash !== null
|
|
314
|
+
targetHash !== null && !neverPlanned
|
|
315
|
+
? `prisma-next migrate --to ${targetHash}`
|
|
316
|
+
: 'prisma-next migrate';
|
|
304
317
|
return errorRuntime(failure.summary, {
|
|
305
318
|
why:
|
|
306
319
|
failure.why ??
|