@prisma-next/cli 0.14.0-dev.39 → 0.14.0-dev.40
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/dist/cli.mjs +8 -8
- package/dist/{client-VA66WgBx.mjs → client-CWTNqJdj.mjs} +68 -67
- package/dist/client-CWTNqJdj.mjs.map +1 -0
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +2 -2
- package/dist/commands/db-schema.mjs +2 -2
- package/dist/commands/db-sign.mjs +2 -2
- package/dist/commands/db-update.mjs +2 -2
- 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 +31 -31
- 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 +5 -5
- 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-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +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/{contract-infer-Bmq_szvX.mjs → contract-infer-B__56PGb.mjs} +2 -2
- package/dist/{contract-infer-Bmq_szvX.mjs.map → contract-infer-B__56PGb.mjs.map} +1 -1
- package/dist/{db-verify-r157atLp.mjs → db-verify-DlqHvkkA.mjs} +65 -48
- package/dist/db-verify-DlqHvkkA.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +3 -3
- package/dist/exports/control-api.mjs +1 -1
- package/dist/extension-pack-inputs-1ySHqxKG.mjs.map +1 -1
- package/dist/{inspect-live-schema-DBF3Rx17.mjs → inspect-live-schema-Dd1Ft0dw.mjs} +2 -2
- package/dist/{inspect-live-schema-DBF3Rx17.mjs.map → inspect-live-schema-Dd1Ft0dw.mjs.map} +1 -1
- package/dist/{migration-check-CTcJfCC4.mjs → migration-check-UoHMvsP3.mjs} +6 -6
- package/dist/migration-check-UoHMvsP3.mjs.map +1 -0
- package/dist/{migration-command-scaffold-bxooJ8Jl.mjs → migration-command-scaffold-DW2BpjZW.mjs} +2 -2
- package/dist/{migration-command-scaffold-bxooJ8Jl.mjs.map → migration-command-scaffold-DW2BpjZW.mjs.map} +1 -1
- package/dist/{migration-list-DVHVmMrf.mjs → migration-list-BOzQzJK4.mjs} +12 -12
- package/dist/migration-list-BOzQzJK4.mjs.map +1 -0
- package/dist/{migration-log-CBYftWK1.mjs → migration-log-q50rqMiU.mjs} +2 -2
- package/dist/{migration-log-CBYftWK1.mjs.map → migration-log-q50rqMiU.mjs.map} +1 -1
- package/dist/{migration-plan-o2aqjnfd.mjs → migration-plan-BwJLPkwV.mjs} +19 -19
- package/dist/migration-plan-BwJLPkwV.mjs.map +1 -0
- package/dist/{migration-status-BLa0SheI.mjs → migration-status-27NzO2SC.mjs} +10 -10
- package/dist/migration-status-27NzO2SC.mjs.map +1 -0
- package/dist/{types-qETCCNbe.d.mts → types-CKgJmH02.d.mts} +25 -19
- package/dist/types-CKgJmH02.d.mts.map +1 -0
- package/dist/{verify-CLw3YRbl.mjs → verify-DC14_nPu.mjs} +23 -5
- package/dist/verify-DC14_nPu.mjs.map +1 -0
- package/package.json +21 -21
- package/src/commands/db-verify.ts +31 -19
- package/src/commands/migrate.ts +44 -41
- package/src/commands/migration-check.ts +5 -5
- package/src/commands/migration-graph.ts +4 -4
- package/src/commands/migration-list.ts +12 -12
- package/src/commands/migration-plan.ts +3 -3
- package/src/commands/migration-status.ts +9 -9
- package/src/control-api/operations/db-init.ts +2 -2
- package/src/control-api/operations/db-run.ts +7 -7
- package/src/control-api/operations/db-verify.ts +37 -33
- package/src/control-api/operations/migrate.ts +66 -66
- package/src/control-api/operations/run-migration.ts +2 -2
- package/src/control-api/types.ts +7 -7
- package/src/utils/combine-verify-results.ts +113 -0
- package/src/utils/extension-pack-inputs.ts +1 -1
- package/src/utils/formatters/verify.ts +36 -3
- package/src/utils/plan-resolution.ts +18 -18
- package/dist/client-VA66WgBx.mjs.map +0 -1
- package/dist/db-verify-r157atLp.mjs.map +0 -1
- package/dist/migration-check-CTcJfCC4.mjs.map +0 -1
- package/dist/migration-list-DVHVmMrf.mjs.map +0 -1
- package/dist/migration-plan-o2aqjnfd.mjs.map +0 -1
- package/dist/migration-status-BLa0SheI.mjs.map +0 -1
- package/dist/types-qETCCNbe.d.mts.map +0 -1
- package/dist/verify-CLw3YRbl.mjs.map +0 -1
- package/src/utils/combine-schema-results.ts +0 -87
package/src/commands/migrate.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { loadConfig } from '@prisma-next/config-loader';
|
|
3
3
|
import type { Contract } from '@prisma-next/contract/types';
|
|
4
4
|
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type AggregateContractSpace,
|
|
7
|
+
requireHeadRef,
|
|
8
|
+
} from '@prisma-next/migration-tools/aggregate';
|
|
6
9
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
7
10
|
import { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
8
11
|
import { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';
|
|
@@ -13,7 +16,7 @@ import { ifDefined } from '@prisma-next/utils/defined';
|
|
|
13
16
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
14
17
|
import { Command } from 'commander';
|
|
15
18
|
import { createControlClient } from '../control-api/client';
|
|
16
|
-
import {
|
|
19
|
+
import { planSpacePath } from '../control-api/operations/migrate';
|
|
17
20
|
import type {
|
|
18
21
|
MigrateFailure,
|
|
19
22
|
MigratePathDecision,
|
|
@@ -149,7 +152,7 @@ export interface MigrateResult {
|
|
|
149
152
|
* Computes the path through the SAME seam as `executeMigrate`:
|
|
150
153
|
* - `readAllMarkers()` for the from-state (when no `--from` is given), preserving
|
|
151
154
|
* the full marker including `invariants` (not just `storageHash`).
|
|
152
|
-
* - `
|
|
155
|
+
* - `planSpacePath()` (shared with `executeMigrate`) for per-space path selection,
|
|
153
156
|
* which feeds `graphWalkStrategy()` with the same target hash, target invariants,
|
|
154
157
|
* and current marker as the real apply path uses.
|
|
155
158
|
*
|
|
@@ -203,7 +206,7 @@ async function executeMigrateShowCommand(
|
|
|
203
206
|
const appGraph = aggregate.app.graph();
|
|
204
207
|
|
|
205
208
|
// Resolve the --to target (defaults to the on-disk contract, same as migrate).
|
|
206
|
-
// Also capture the ref's invariants so
|
|
209
|
+
// Also capture the ref's invariants so planSpacePath feeds graphWalkStrategy the
|
|
207
210
|
// same target invariants that real migrate would use (refInvariants ?? headRef.invariants).
|
|
208
211
|
let targetHash: string = contractHash;
|
|
209
212
|
let refInvariants: readonly string[] | undefined;
|
|
@@ -258,13 +261,13 @@ async function executeMigrateShowCommand(
|
|
|
258
261
|
// - Explicit --from: parse it offline (no connection).
|
|
259
262
|
// - Omitted: read the live DB marker via readAllMarkers() — the same source migrate uses.
|
|
260
263
|
//
|
|
261
|
-
// Full marker records (storageHash + invariants) are preserved so
|
|
264
|
+
// Full marker records (storageHash + invariants) are preserved so planSpacePath
|
|
262
265
|
// can feed graphWalkStrategy the complete currentMarker — exactly as executeMigrate
|
|
263
266
|
// does via familyInstance.readAllMarkers(). A stripped { storageHash, invariants: [] }
|
|
264
267
|
// marker would produce a different `required` set and a different (incorrect) path.
|
|
265
268
|
type LiveMarker = { readonly storageHash: string; readonly invariants: readonly string[] };
|
|
266
269
|
const markerBySpace = new Map<string, LiveMarker | null>();
|
|
267
|
-
const
|
|
270
|
+
const allSpaces: ReadonlyArray<AggregateContractSpace> = [aggregate.app, ...aggregate.extensions];
|
|
268
271
|
|
|
269
272
|
if (hasExplicitFrom) {
|
|
270
273
|
// @db with explicit --from requires a connection
|
|
@@ -301,7 +304,7 @@ async function executeMigrateShowCommand(
|
|
|
301
304
|
} else {
|
|
302
305
|
// Offline hypothetical: the --from ref only carries a hash (no live invariants).
|
|
303
306
|
// Apply the from-hash marker to the APP space only. Extension spaces are left
|
|
304
|
-
// absent from markerBySpace (treated as null / greenfield by
|
|
307
|
+
// absent from markerBySpace (treated as null / greenfield by planSpacePath),
|
|
305
308
|
// so they plan from their own marker → own head — exactly as executeMigrate does.
|
|
306
309
|
const fromHash = fromResult.value.hash;
|
|
307
310
|
const offlineMarker: LiveMarker | null =
|
|
@@ -334,9 +337,9 @@ async function executeMigrateShowCommand(
|
|
|
334
337
|
const allMarkers = await client.readAllMarkers();
|
|
335
338
|
// Store the full marker record (storageHash + invariants) per space.
|
|
336
339
|
// This is the same data executeMigrate uses via familyInstance.readAllMarkers().
|
|
337
|
-
for (const
|
|
338
|
-
const marker = allMarkers.get(
|
|
339
|
-
markerBySpace.set(
|
|
340
|
+
for (const space of allSpaces) {
|
|
341
|
+
const marker = allMarkers.get(space.spaceId);
|
|
342
|
+
markerBySpace.set(space.spaceId, marker ?? null);
|
|
340
343
|
}
|
|
341
344
|
} catch (error) {
|
|
342
345
|
if (CliStructuredError.is(error)) {
|
|
@@ -355,35 +358,35 @@ async function executeMigrateShowCommand(
|
|
|
355
358
|
}
|
|
356
359
|
}
|
|
357
360
|
|
|
358
|
-
// Walk the path via
|
|
359
|
-
//
|
|
361
|
+
// Walk the path via planSpacePath — the same helper executeMigrate uses.
|
|
362
|
+
// planSpacePath feeds graphWalkStrategy identical inputs (targetHash, targetInvariants,
|
|
360
363
|
// currentMarker with full invariants), so the preview path is always the path migrate runs.
|
|
361
364
|
//
|
|
362
365
|
// Canonical schedule order: extensions alphabetically first, then app — mirroring the
|
|
363
366
|
// runner's `applyOrder` in operations/migrate.ts so the "Will run, in order:" list
|
|
364
367
|
// reflects the actual execution sequence (extensions install first, app last).
|
|
365
|
-
const
|
|
368
|
+
const canonicalOrderSpaces: ReadonlyArray<AggregateContractSpace> = [
|
|
366
369
|
...aggregate.extensions,
|
|
367
370
|
aggregate.app,
|
|
368
371
|
];
|
|
369
372
|
const orderedMigrations: MigrateShowMigration[] = [];
|
|
370
|
-
for (const
|
|
371
|
-
const
|
|
372
|
-
const headRef = requireHeadRef(
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
const liveMarker = markerBySpace.get(
|
|
376
|
-
|
|
377
|
-
const outcome =
|
|
378
|
-
|
|
373
|
+
for (const space of canonicalOrderSpaces) {
|
|
374
|
+
const isAppSpace = space.spaceId === aggregate.app.spaceId;
|
|
375
|
+
const headRef = requireHeadRef(space);
|
|
376
|
+
const spaceTargetHash = isAppSpace ? targetHash : headRef.hash;
|
|
377
|
+
const spaceRefInvariants = isAppSpace ? refInvariants : undefined;
|
|
378
|
+
const liveMarker = markerBySpace.get(space.spaceId) ?? null;
|
|
379
|
+
|
|
380
|
+
const outcome = planSpacePath({
|
|
381
|
+
space,
|
|
379
382
|
aggregate,
|
|
380
|
-
targetHash:
|
|
381
|
-
refInvariants:
|
|
383
|
+
targetHash: spaceTargetHash,
|
|
384
|
+
refInvariants: spaceRefInvariants,
|
|
382
385
|
liveMarker,
|
|
383
386
|
});
|
|
384
387
|
|
|
385
388
|
if (outcome.kind === 'at-head') {
|
|
386
|
-
// Empty-graph
|
|
389
|
+
// Empty-graph space already at target — nothing to run for this space.
|
|
387
390
|
continue;
|
|
388
391
|
}
|
|
389
392
|
if (outcome.kind === 'never-planned') {
|
|
@@ -417,7 +420,7 @@ async function executeMigrateShowCommand(
|
|
|
417
420
|
|
|
418
421
|
for (const edge of outcome.plan.migrationEdges) {
|
|
419
422
|
orderedMigrations.push({
|
|
420
|
-
spaceId:
|
|
423
|
+
spaceId: space.spaceId,
|
|
421
424
|
dirName: edge.dirName,
|
|
422
425
|
migrationHash: edge.migrationHash,
|
|
423
426
|
from: edge.from,
|
|
@@ -445,12 +448,12 @@ async function executeMigrateShowCommand(
|
|
|
445
448
|
// before rendering. This ensures the name column, hash column, and ops column
|
|
446
449
|
// start at the same horizontal offset across every space section AND the
|
|
447
450
|
// "Will run, in order:" list below.
|
|
448
|
-
const
|
|
449
|
-
const isApp =
|
|
450
|
-
const
|
|
451
|
-
const rowModel = buildMigrationGraphRows(
|
|
451
|
+
const spaceLayouts = allSpaces.map((space) => {
|
|
452
|
+
const isApp = space.spaceId === aggregate.app.spaceId;
|
|
453
|
+
const spaceGraph = space.graph();
|
|
454
|
+
const rowModel = buildMigrationGraphRows(spaceGraph, isApp ? { contractHash } : {});
|
|
452
455
|
const edgeAnnotations = new Map<string, MigrationEdgeAnnotation>();
|
|
453
|
-
for (const edge of
|
|
456
|
+
for (const edge of spaceGraph.migrationByHash.values()) {
|
|
454
457
|
edgeAnnotations.set(edge.migrationHash, {
|
|
455
458
|
pathHighlight: onPathHashes.has(edge.migrationHash) ? 'on-path' : 'off-path',
|
|
456
459
|
});
|
|
@@ -459,17 +462,17 @@ async function executeMigrateShowCommand(
|
|
|
459
462
|
// green/continuous; off-path lanes dim. Rows, gutter, and labels all come
|
|
460
463
|
// from this one grid.
|
|
461
464
|
const grid = buildGrid(rowModel, {}, highlightFromEdgeAnnotations(edgeAnnotations));
|
|
462
|
-
return {
|
|
465
|
+
return { space, isApp, spaceGraph, rowModel, grid, edgeAnnotations };
|
|
463
466
|
});
|
|
464
467
|
|
|
465
468
|
// Global max across all space grids so every section's labels share columns.
|
|
466
469
|
const globalLabelColumn =
|
|
467
|
-
|
|
468
|
-
? Math.max(...
|
|
470
|
+
spaceLayouts.length > 1
|
|
471
|
+
? Math.max(...spaceLayouts.map(({ grid }) => computeLabelColumn(grid, 'unicode')))
|
|
469
472
|
: undefined;
|
|
470
473
|
const globalMaxDirNameWidthFromLayouts =
|
|
471
|
-
|
|
472
|
-
? Math.max(...
|
|
474
|
+
spaceLayouts.length > 1
|
|
475
|
+
? Math.max(...spaceLayouts.map(({ rowModel }) => computeMaxDirNameWidth(rowModel)))
|
|
473
476
|
: undefined;
|
|
474
477
|
// The run-list name column width must be at least as wide as the global tree dirName
|
|
475
478
|
// width so that tree sections and the list align at the hash column.
|
|
@@ -485,10 +488,10 @@ async function executeMigrateShowCommand(
|
|
|
485
488
|
runListLeftPad = globalLabelColumn;
|
|
486
489
|
|
|
487
490
|
// Render each space section with globally computed widths.
|
|
488
|
-
const showSpaceHeadings =
|
|
491
|
+
const showSpaceHeadings = allSpaces.length > 1;
|
|
489
492
|
const sections: string[] = [];
|
|
490
|
-
for (const {
|
|
491
|
-
const liveMarker = markerBySpace.get(
|
|
493
|
+
for (const { space, isApp, rowModel, grid, edgeAnnotations } of spaceLayouts) {
|
|
494
|
+
const liveMarker = markerBySpace.get(space.spaceId) ?? null;
|
|
492
495
|
const liveMarkerHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
|
|
493
496
|
const tree = renderMigrationGraphCommand({
|
|
494
497
|
grid,
|
|
@@ -496,7 +499,7 @@ async function executeMigrateShowCommand(
|
|
|
496
499
|
contractHash,
|
|
497
500
|
isAppSpace: isApp,
|
|
498
501
|
...(needsLiveMarker ? { dbHash: liveMarkerHash } : {}),
|
|
499
|
-
refsByHash: listRefsByContractHash(
|
|
502
|
+
refsByHash: listRefsByContractHash(space),
|
|
500
503
|
edgeAnnotationsByHash: edgeAnnotations,
|
|
501
504
|
colorize,
|
|
502
505
|
glyphMode: 'unicode',
|
|
@@ -505,7 +508,7 @@ async function executeMigrateShowCommand(
|
|
|
505
508
|
});
|
|
506
509
|
if (tree.length === 0) continue;
|
|
507
510
|
if (showSpaceHeadings) {
|
|
508
|
-
sections.push(`${
|
|
511
|
+
sections.push(`${space.spaceId}:\n${indentMigrationGraphTreeBlock(tree, ' ')}`);
|
|
509
512
|
} else {
|
|
510
513
|
sections.push(tree);
|
|
511
514
|
}
|
|
@@ -156,16 +156,16 @@ export async function enumerateCheckSpaces(
|
|
|
156
156
|
candidateDirs.filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name)).filter(isValidSpaceId),
|
|
157
157
|
);
|
|
158
158
|
const spaces: CheckSpace[] = [];
|
|
159
|
-
for (const
|
|
160
|
-
const spaceId =
|
|
159
|
+
for (const space of aggregate.spaces()) {
|
|
160
|
+
const spaceId = space.spaceId;
|
|
161
161
|
if (!isValidSpaceId(spaceId)) continue;
|
|
162
162
|
if (!onDiskSpaceIds.has(spaceId)) continue;
|
|
163
163
|
const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);
|
|
164
164
|
spaces.push({
|
|
165
165
|
spaceId,
|
|
166
|
-
packages:
|
|
167
|
-
refs:
|
|
168
|
-
graph:
|
|
166
|
+
packages: space.packages,
|
|
167
|
+
refs: space.refs,
|
|
168
|
+
graph: space.graph(),
|
|
169
169
|
migrationsDir,
|
|
170
170
|
refsDir: spaceRefsDirectory(migrationsDir),
|
|
171
171
|
});
|
|
@@ -154,13 +154,13 @@ export async function executeMigrationGraphCommand(
|
|
|
154
154
|
const treeSections: MigrationGraphTreeSection[] = [];
|
|
155
155
|
const spaces: MigrationSpaceGraphEntry[] = [];
|
|
156
156
|
for (const spaceEntry of scopedSpaces) {
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
157
|
+
const space = aggregate.space(spaceEntry.space);
|
|
158
|
+
if (space === undefined) {
|
|
159
159
|
continue;
|
|
160
160
|
}
|
|
161
|
-
const graph =
|
|
161
|
+
const graph = space.graph();
|
|
162
162
|
const isAppSpace = spaceEntry.space === aggregate.app.spaceId;
|
|
163
|
-
const refsByHash = listRefsByContractHash(
|
|
163
|
+
const refsByHash = listRefsByContractHash(space);
|
|
164
164
|
const tree =
|
|
165
165
|
spaceEntry.migrations.length === 0
|
|
166
166
|
? ''
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { loadConfig } from '@prisma-next/config-loader';
|
|
2
2
|
import type {
|
|
3
|
+
AggregateContractSpace,
|
|
3
4
|
ContractSpaceAggregate,
|
|
4
|
-
ContractSpaceMember,
|
|
5
5
|
} from '@prisma-next/migration-tools/aggregate';
|
|
6
6
|
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
7
7
|
import { HEAD_REF_NAME, refsByContractHash } from '@prisma-next/migration-tools/refs';
|
|
@@ -59,18 +59,18 @@ function compareDirNamesDescending(a: MigrationListEntry, b: MigrationListEntry)
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Ref names decorating a space's destination contract hashes. The
|
|
62
|
-
* tolerant `
|
|
62
|
+
* tolerant `space.refs` deliberately omits the structural `head.json`;
|
|
63
63
|
* for extension spaces the old enumerator surfaced it as a `head`
|
|
64
|
-
* decoration on the tip migration, so fold `
|
|
64
|
+
* decoration on the tip migration, so fold `space.headRef` back in to
|
|
65
65
|
* keep that output. The app space synthesises its head, so it carries
|
|
66
66
|
* no on-disk `head` ref to restore.
|
|
67
67
|
*/
|
|
68
68
|
export function listRefsByContractHash(
|
|
69
|
-
|
|
69
|
+
space: AggregateContractSpace,
|
|
70
70
|
): ReadonlyMap<string, readonly string[]> {
|
|
71
|
-
const byHash = new Map(refsByContractHash(
|
|
72
|
-
if (
|
|
73
|
-
const hash =
|
|
71
|
+
const byHash = new Map(refsByContractHash(space.refs));
|
|
72
|
+
if (space.spaceId !== APP_SPACE_ID && space.headRef !== null) {
|
|
73
|
+
const hash = space.headRef.hash;
|
|
74
74
|
const bucket = byHash.get(hash) ?? [];
|
|
75
75
|
if (!bucket.includes(HEAD_REF_NAME)) {
|
|
76
76
|
byHash.set(hash, [...bucket, HEAD_REF_NAME].sort());
|
|
@@ -92,7 +92,7 @@ async function orderedOnDiskSpaceIds(projectMigrationsDir: string): Promise<read
|
|
|
92
92
|
* {@link MigrationSpaceListEntry} rows `migration list` displays.
|
|
93
93
|
*
|
|
94
94
|
* Space membership matches the on-disk contract-space directories (not the
|
|
95
|
-
* aggregate's always-present synthesized app
|
|
95
|
+
* aggregate's always-present synthesized app space when `migrations/app/`
|
|
96
96
|
* is absent); package and ref data come from `aggregate.space(id)`.
|
|
97
97
|
*/
|
|
98
98
|
export async function migrationSpaceListEntriesFromAggregate(
|
|
@@ -103,12 +103,12 @@ export async function migrationSpaceListEntriesFromAggregate(
|
|
|
103
103
|
const spaces: MigrationSpaceListEntry[] = [];
|
|
104
104
|
|
|
105
105
|
for (const spaceId of spaceIds) {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
106
|
+
const space = aggregate.space(spaceId);
|
|
107
|
+
if (space === undefined) {
|
|
108
108
|
continue;
|
|
109
109
|
}
|
|
110
|
-
const refsByHash = listRefsByContractHash(
|
|
111
|
-
const migrations: MigrationListEntry[] =
|
|
110
|
+
const refsByHash = listRefsByContractHash(space);
|
|
111
|
+
const migrations: MigrationListEntry[] = space.packages
|
|
112
112
|
.map((pkg) => ({
|
|
113
113
|
name: pkg.dirName,
|
|
114
114
|
hash: pkg.metadata.migrationHash,
|
|
@@ -321,11 +321,11 @@ async function executeMigrationPlanCommand(
|
|
|
321
321
|
if (!tolerantAggregateResult.ok) {
|
|
322
322
|
return notOk(tolerantAggregateResult.failure);
|
|
323
323
|
}
|
|
324
|
-
const
|
|
324
|
+
const resolutionSpace = tolerantAggregateResult.value.app;
|
|
325
325
|
|
|
326
326
|
const resolutionResult = await resolveFromForPlan({
|
|
327
327
|
optionsFrom: options.from,
|
|
328
|
-
|
|
328
|
+
space: resolutionSpace,
|
|
329
329
|
});
|
|
330
330
|
|
|
331
331
|
if (!resolutionResult.ok) {
|
|
@@ -365,7 +365,7 @@ async function executeMigrationPlanCommand(
|
|
|
365
365
|
// change.
|
|
366
366
|
if (options.to !== undefined) {
|
|
367
367
|
const toResolution = await resolveToForPlan(options.to, {
|
|
368
|
-
|
|
368
|
+
space: resolutionSpace,
|
|
369
369
|
});
|
|
370
370
|
if (!toResolution.ok) {
|
|
371
371
|
return notOk(toResolution.failure);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { loadConfig } from '@prisma-next/config-loader';
|
|
2
2
|
import type { LedgerEntryRecord } from '@prisma-next/contract/types';
|
|
3
3
|
import type {
|
|
4
|
+
AggregateContractSpace,
|
|
4
5
|
ContractMarkerRecordLike,
|
|
5
|
-
ContractSpaceMember,
|
|
6
6
|
} from '@prisma-next/migration-tools/aggregate';
|
|
7
7
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
8
8
|
import {
|
|
@@ -130,7 +130,7 @@ function buildStatusMigrations(
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
function renderSpaceTree(args: {
|
|
133
|
-
readonly
|
|
133
|
+
readonly space: AggregateContractSpace;
|
|
134
134
|
readonly liveContractHash: string;
|
|
135
135
|
readonly migrations: readonly MigrationListEntry[];
|
|
136
136
|
readonly markerHash: string | undefined;
|
|
@@ -142,7 +142,7 @@ function renderSpaceTree(args: {
|
|
|
142
142
|
readonly globalMaxEdgeTreePrefixWidth?: number;
|
|
143
143
|
readonly globalMaxDirNameWidth?: number;
|
|
144
144
|
}): string {
|
|
145
|
-
const graph = args.
|
|
145
|
+
const graph = args.space.graph();
|
|
146
146
|
if (graph.nodes.size === 0) {
|
|
147
147
|
return '';
|
|
148
148
|
}
|
|
@@ -150,7 +150,7 @@ function renderSpaceTree(args: {
|
|
|
150
150
|
graph,
|
|
151
151
|
migrations: args.migrations,
|
|
152
152
|
liveContractHash: args.liveContractHash,
|
|
153
|
-
refsByHash: listRefsByContractHash(args.
|
|
153
|
+
refsByHash: listRefsByContractHash(args.space),
|
|
154
154
|
statusOverlayByHash: args.statusOverlay,
|
|
155
155
|
colorize: args.colorize,
|
|
156
156
|
glyphMode: args.glyphMode,
|
|
@@ -493,12 +493,12 @@ export async function executeMigrationStatusCommand(
|
|
|
493
493
|
globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : undefined;
|
|
494
494
|
|
|
495
495
|
for (const spaceEntry of scopedSpaces) {
|
|
496
|
-
const
|
|
497
|
-
if (
|
|
496
|
+
const space = aggregate.space(spaceEntry.space);
|
|
497
|
+
if (space === undefined) {
|
|
498
498
|
continue;
|
|
499
499
|
}
|
|
500
|
-
const graph =
|
|
501
|
-
const spaceContractHash =
|
|
500
|
+
const graph = space.graph();
|
|
501
|
+
const spaceContractHash = space.contract().storage.storageHash;
|
|
502
502
|
const targetHash = resolveTarget(spaceContractHash, activeRefHash);
|
|
503
503
|
if (spaceEntry.space === aggregate.app.spaceId) {
|
|
504
504
|
headlineTargetHash = targetHash;
|
|
@@ -547,7 +547,7 @@ export async function executeMigrationStatusCommand(
|
|
|
547
547
|
});
|
|
548
548
|
const isAppSpace = spaceEntry.space === aggregate.app.spaceId;
|
|
549
549
|
const tree = renderSpaceTree({
|
|
550
|
-
|
|
550
|
+
space,
|
|
551
551
|
liveContractHash: contractHash,
|
|
552
552
|
migrations: spaceEntry.migrations,
|
|
553
553
|
markerHash,
|
|
@@ -20,7 +20,7 @@ import { executeRun } from './db-run';
|
|
|
20
20
|
* {@link import('@prisma-next/migration-tools/aggregate').loadContractSpaceAggregate}
|
|
21
21
|
* from the supplied descriptor set + on-disk on-disk artefacts.
|
|
22
22
|
* 2. The aggregate planner runs with `callerPolicy.ignoreGraphFor`
|
|
23
|
-
* locked to the app
|
|
23
|
+
* locked to the app space — synth strategy for the app, graph-walk
|
|
24
24
|
* for every extension.
|
|
25
25
|
* 3. The runner's `execute` applies the per-space plans
|
|
26
26
|
* inside one outer transaction.
|
|
@@ -53,7 +53,7 @@ export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extend
|
|
|
53
53
|
/**
|
|
54
54
|
* Declared extension descriptors. Defaults to an empty list, which
|
|
55
55
|
* routes through the same loader → planner → runner pipeline with no
|
|
56
|
-
* extension
|
|
56
|
+
* extension spaces in the aggregate.
|
|
57
57
|
*/
|
|
58
58
|
readonly extensionPacks?: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
59
59
|
/** Optional progress callback for observing operation progress */
|
|
@@ -98,9 +98,9 @@ export interface ExecuteRunOptions<TFamilyId extends string, TTargetId extends s
|
|
|
98
98
|
* 2. **Read DB state**: marker rows (`familyInstance.readAllMarkers`)
|
|
99
99
|
* + introspected schema (`familyInstance.introspect`).
|
|
100
100
|
* 3. **Plan**: {@link planMigration} chooses graph-walk vs synth per
|
|
101
|
-
*
|
|
101
|
+
* space according to `callerPolicy.ignoreGraphFor`. The app space
|
|
102
102
|
* is forced through synth (today's daily-driver behaviour); every
|
|
103
|
-
* extension
|
|
103
|
+
* extension space walks its on-disk graph.
|
|
104
104
|
* 4. **Apply** (when `mode === 'apply'`): every per-space `MigrationPlan`
|
|
105
105
|
* feeds into the runner's `execute` — one outer
|
|
106
106
|
* transaction across every space; failure on any space rolls back
|
|
@@ -200,7 +200,7 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
|
|
|
200
200
|
const appResolution = orderedResolutions.find((r) => r.spaceId === aggregate.app.spaceId);
|
|
201
201
|
if (!appResolution) {
|
|
202
202
|
throw new Error(
|
|
203
|
-
'Aggregate planner returned no plan for the app
|
|
203
|
+
'Aggregate planner returned no plan for the app space — the planner is supposed to always emit one.',
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
206
|
const appPlan = appResolution.entry.plan;
|
|
@@ -280,14 +280,14 @@ function aggregatePlannerWarnings(
|
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
282
|
* Compare the live `_prisma_marker` rows against the aggregate's
|
|
283
|
-
* declared
|
|
283
|
+
* declared contract spaces. Any marker row whose `space` is not a space of
|
|
284
284
|
* the aggregate is an "orphan" — typically a marker left behind by
|
|
285
285
|
* an extension that was removed from `extensionPacks` without first
|
|
286
286
|
* cleaning up its on-disk migrations / database tables.
|
|
287
287
|
*
|
|
288
288
|
* Returns a {@link CliStructuredError} envelope (code `5002`,
|
|
289
289
|
* `kind: 'orphanMarker'`) for the first orphan it finds, or `null`
|
|
290
|
-
* when every marker row maps to a declared
|
|
290
|
+
* when every marker row maps to a declared contract space. Mirrors the M2
|
|
291
291
|
* `runContractSpaceVerifierMarkerCheck` envelope so downstream
|
|
292
292
|
* tooling (integration tests, JSON consumers) keeps asserting on the
|
|
293
293
|
* same shape.
|
|
@@ -296,13 +296,13 @@ function detectOrphanMarkers(
|
|
|
296
296
|
aggregate: ContractSpaceAggregate,
|
|
297
297
|
markerRows: ReadonlyMap<string, unknown>,
|
|
298
298
|
): CliStructuredError | null {
|
|
299
|
-
const
|
|
299
|
+
const aggregateSpaceIds = new Set<string>([
|
|
300
300
|
aggregate.app.spaceId,
|
|
301
301
|
...aggregate.extensions.map((m) => m.spaceId),
|
|
302
302
|
]);
|
|
303
303
|
const orphans: string[] = [];
|
|
304
304
|
for (const [spaceId, row] of markerRows) {
|
|
305
|
-
if (row !== null && row !== undefined && !
|
|
305
|
+
if (row !== null && row !== undefined && !aggregateSpaceIds.has(spaceId)) {
|
|
306
306
|
orphans.push(spaceId);
|
|
307
307
|
}
|
|
308
308
|
}
|
|
@@ -7,13 +7,13 @@ import type {
|
|
|
7
7
|
VerifyDatabaseSchemaResult,
|
|
8
8
|
} from '@prisma-next/framework-components/control';
|
|
9
9
|
import {
|
|
10
|
-
type
|
|
10
|
+
type AggregateContractSpace,
|
|
11
11
|
collectAggregateNamespaces,
|
|
12
12
|
requireHeadRef,
|
|
13
13
|
type VerifierOutput,
|
|
14
14
|
verifyMigration,
|
|
15
15
|
} from '@prisma-next/migration-tools/aggregate';
|
|
16
|
-
import {
|
|
16
|
+
import { castAs } from '@prisma-next/utils/casts';
|
|
17
17
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
18
18
|
import { CliStructuredError } from '../../utils/cli-errors';
|
|
19
19
|
import {
|
|
@@ -37,9 +37,9 @@ const SPAN_IDS = {
|
|
|
37
37
|
*
|
|
38
38
|
* Loader → verifier pipeline. The loader (sole descriptor-import
|
|
39
39
|
* boundary) builds a {@link import('@prisma-next/migration-tools/aggregate').ContractSpaceAggregate};
|
|
40
|
-
* the aggregate verifier bundles `markerCheck` + per-space
|
|
41
|
-
*
|
|
42
|
-
* facing `--strict` flag.
|
|
40
|
+
* the aggregate verifier bundles `markerCheck` + per-space `schemaCheck`
|
|
41
|
+
* (each contract space verified against the full schema; extras stripped to its own view).
|
|
42
|
+
* `mode: 'strict' | 'lenient'` maps directly to the user facing `--strict` flag.
|
|
43
43
|
*/
|
|
44
44
|
export interface ExecuteDbVerifyOptions<TFamilyId extends string, TTargetId extends string> {
|
|
45
45
|
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
@@ -63,13 +63,19 @@ export interface ExecuteDbVerifyOptions<TFamilyId extends string, TTargetId exte
|
|
|
63
63
|
* emitted, so downstream tooling and integration tests assert on the
|
|
64
64
|
* same shape).
|
|
65
65
|
*
|
|
66
|
-
* On success, the per-space
|
|
66
|
+
* On success, the per-space verify results are returned for the CLI to
|
|
67
67
|
* render. When `skipSchema` is true (`--marker-only`), the schema map
|
|
68
68
|
* is empty.
|
|
69
69
|
*/
|
|
70
70
|
export interface ExecuteDbVerifySuccess {
|
|
71
71
|
readonly schemaResults: ReadonlyMap<string, VerifyDatabaseSchemaResult>;
|
|
72
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Live element names no contract space declares, deduplicated and reported
|
|
74
|
+
* once for the whole database (never per space). Strict mode fails on a
|
|
75
|
+
* non-empty list; lenient mode surfaces it informationally.
|
|
76
|
+
*/
|
|
77
|
+
readonly unclaimed: readonly string[];
|
|
78
|
+
readonly spaceOrder: readonly string[];
|
|
73
79
|
readonly appSpaceId: string;
|
|
74
80
|
}
|
|
75
81
|
|
|
@@ -85,11 +91,11 @@ export type ExecuteDbVerifyResult = Result<ExecuteDbVerifySuccess, CliStructured
|
|
|
85
91
|
* structured CLI error.
|
|
86
92
|
* 2. **Read DB state**: marker rows + (when `skipSchema` is `false`)
|
|
87
93
|
* schema introspection.
|
|
88
|
-
* 3. **Verify**: {@link verifyMigration} returns per-space
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* callers (CLI command) can render
|
|
92
|
-
* returned to the caller verbatim.
|
|
94
|
+
* 3. **Verify**: {@link verifyMigration} returns per-space `markerCheck` +
|
|
95
|
+
* per-space `schemaCheck` (each contract space verified against the full schema,
|
|
96
|
+
* then scoped to its own contract space). Marker mismatches map to
|
|
97
|
+
* `CliStructuredError` (code `5002`) so callers (CLI command) can render
|
|
98
|
+
* and exit. Verify results are returned to the caller verbatim.
|
|
93
99
|
*/
|
|
94
100
|
export async function executeDbVerify<TFamilyId extends string, TTargetId extends string>(
|
|
95
101
|
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
@@ -115,7 +121,7 @@ export async function executeDbVerify<TFamilyId extends string, TTargetId extend
|
|
|
115
121
|
markersBySpaceId,
|
|
116
122
|
schemaIntrospection,
|
|
117
123
|
mode: options.mode,
|
|
118
|
-
|
|
124
|
+
verifySchemaForSpace: createPerSpaceVerifier(options),
|
|
119
125
|
});
|
|
120
126
|
return finaliseVerifyResult({ verifyResult, aggregate, skipMarker, onProgress });
|
|
121
127
|
}
|
|
@@ -166,30 +172,27 @@ async function runIntrospection<TFamilyId extends string, TTargetId extends stri
|
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
/**
|
|
169
|
-
* Build the per-
|
|
175
|
+
* Build the per-space schema callback handed to the aggregate verifier.
|
|
170
176
|
* When `skipSchema` is true the callback short-circuits with a synthetic
|
|
171
177
|
* `ok` result so the verifier still runs the (cheap) schemaCheck loop
|
|
172
178
|
* without invoking the family's verification path.
|
|
173
179
|
*/
|
|
174
|
-
export function
|
|
180
|
+
export function createPerSpaceVerifier<TFamilyId extends string, TTargetId extends string>(
|
|
175
181
|
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
176
182
|
): (
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
schema: unknown,
|
|
184
|
+
space: AggregateContractSpace,
|
|
179
185
|
verifyMode: 'strict' | 'lenient',
|
|
180
186
|
) => VerifyDatabaseSchemaResult {
|
|
181
187
|
const { skipSchema, familyInstance, frameworkComponents } = options;
|
|
182
|
-
return (
|
|
183
|
-
if (skipSchema) return buildSkippedSchemaResult(
|
|
188
|
+
return (schema, space, verifyMode) => {
|
|
189
|
+
if (skipSchema) return buildSkippedSchemaResult(space);
|
|
184
190
|
return familyInstance.verifySchema({
|
|
185
|
-
contract:
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// family
|
|
189
|
-
schema
|
|
190
|
-
never,
|
|
191
|
-
'family TSchemaIR is opaque to migration-tools; projectedSchema is passed straight through'
|
|
192
|
-
>(projectedSchema),
|
|
191
|
+
contract: space.contract(),
|
|
192
|
+
// `familyInstance` is `ControlFamilyInstance<_, unknown>`, so `verifySchema`
|
|
193
|
+
// takes its `TSchemaIR` as `unknown` — the introspected schema passes
|
|
194
|
+
// straight through; the family narrows to its own IR node internally.
|
|
195
|
+
schema,
|
|
193
196
|
strict: verifyMode === 'strict',
|
|
194
197
|
frameworkComponents,
|
|
195
198
|
});
|
|
@@ -223,7 +226,7 @@ function emitVerifySpan(
|
|
|
223
226
|
* by the CLI's `--schema-only` mode.
|
|
224
227
|
*/
|
|
225
228
|
function finaliseVerifyResult(args: {
|
|
226
|
-
verifyResult: VerifierOutput
|
|
229
|
+
verifyResult: VerifierOutput;
|
|
227
230
|
aggregate: {
|
|
228
231
|
readonly app: { readonly spaceId: string };
|
|
229
232
|
readonly extensions: ReadonlyArray<{ readonly spaceId: string }>;
|
|
@@ -253,14 +256,15 @@ function finaliseVerifyResult(args: {
|
|
|
253
256
|
emitVerifySpan(onProgress, 'spanEndOk');
|
|
254
257
|
return ok({
|
|
255
258
|
schemaResults: verifyResult.value.schemaCheck.perSpace,
|
|
256
|
-
|
|
259
|
+
unclaimed: verifyResult.value.schemaCheck.unclaimed,
|
|
260
|
+
spaceOrder: [aggregate.app.spaceId, ...aggregate.extensions.map((e) => e.spaceId)],
|
|
257
261
|
appSpaceId: aggregate.app.spaceId,
|
|
258
262
|
});
|
|
259
263
|
}
|
|
260
264
|
|
|
261
|
-
function buildSkippedSchemaResult(
|
|
262
|
-
const contract =
|
|
263
|
-
const headRef = requireHeadRef(
|
|
265
|
+
function buildSkippedSchemaResult(space: AggregateContractSpace): VerifyDatabaseSchemaResult {
|
|
266
|
+
const contract = space.contract();
|
|
267
|
+
const headRef = requireHeadRef(space);
|
|
264
268
|
const profileHash = castAs<{ profileHash?: string }>(contract).profileHash;
|
|
265
269
|
return {
|
|
266
270
|
ok: true,
|
|
@@ -276,7 +280,7 @@ function buildSkippedSchemaResult(member: ContractSpaceMember): VerifyDatabaseSc
|
|
|
276
280
|
root: {
|
|
277
281
|
status: 'pass',
|
|
278
282
|
kind: 'skipped',
|
|
279
|
-
name:
|
|
283
|
+
name: space.spaceId,
|
|
280
284
|
contractPath: '',
|
|
281
285
|
code: 'SKIPPED',
|
|
282
286
|
message: 'Schema verification skipped',
|