@prisma-next/cli 0.11.0-dev.46 → 0.11.0-dev.48
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 +9 -9
- package/dist/{client-a5NJce0-.mjs → client-5uvDppD8.mjs} +20 -18
- package/dist/client-5uvDppD8.mjs.map +1 -0
- package/dist/{command-helpers-yLuA78TP.mjs → command-helpers-CI8P5Xyd.mjs} +4 -4
- package/dist/{command-helpers-yLuA78TP.mjs.map → command-helpers-CI8P5Xyd.mjs.map} +1 -1
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +5 -5
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +3 -3
- package/dist/commands/db-update.mjs +5 -5
- 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 +42 -37
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +4 -3
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +1 -279
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.mjs +2 -2
- package/dist/commands/migration-list.d.mts +2 -2
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.mjs +2 -2
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +32 -30
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +4 -55
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +62 -152
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +5 -40
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +91 -64
- package/dist/commands/migration-status.mjs.map +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +2 -2
- package/dist/{contract-emit-FtDVFs2Q.mjs → contract-emit-DmBG2Nnc.mjs} +2 -2
- package/dist/{contract-emit-FtDVFs2Q.mjs.map → contract-emit-DmBG2Nnc.mjs.map} +1 -1
- package/dist/{contract-infer-CVMuoJKk.mjs → contract-infer-BSWFKgI1.mjs} +3 -3
- package/dist/{contract-infer-CVMuoJKk.mjs.map → contract-infer-BSWFKgI1.mjs.map} +1 -1
- package/dist/contract-space-aggregate-loader-CVHGuA35.mjs +170 -0
- package/dist/contract-space-aggregate-loader-CVHGuA35.mjs.map +1 -0
- package/dist/{db-verify-B00o3LuC.mjs → db-verify-BzpwFyLg.mjs} +4 -4
- package/dist/{db-verify-B00o3LuC.mjs.map → db-verify-BzpwFyLg.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/control-api.mjs +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/extension-pack-inputs-BiY86HbQ.mjs +62 -0
- package/dist/extension-pack-inputs-BiY86HbQ.mjs.map +1 -0
- package/dist/{global-flags-Dvibm2yu.d.mts → global-flags-DWsQ6SSI.d.mts} +1 -1
- package/dist/{global-flags-Dvibm2yu.d.mts.map → global-flags-DWsQ6SSI.d.mts.map} +1 -1
- package/dist/{graph-render-DJVv0_uf.mjs → graph-render-D2FnLpuK.mjs} +1 -1
- package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-D2FnLpuK.mjs.map} +1 -1
- package/dist/{init-BKgB6EKw.mjs → init-DEssiJ8j.mjs} +3 -3
- package/dist/{init-BKgB6EKw.mjs.map → init-DEssiJ8j.mjs.map} +1 -1
- package/dist/{inspect-live-schema-BXUd6RfS.mjs → inspect-live-schema-DlBM84nh.mjs} +3 -3
- package/dist/{inspect-live-schema-BXUd6RfS.mjs.map → inspect-live-schema-DlBM84nh.mjs.map} +1 -1
- package/dist/migration-check-CzLbAqIQ.mjs +341 -0
- package/dist/migration-check-CzLbAqIQ.mjs.map +1 -0
- package/dist/{migration-command-scaffold-3l3EdmSD.mjs → migration-command-scaffold-Bp8UHnvJ.mjs} +3 -3
- package/dist/{migration-command-scaffold-3l3EdmSD.mjs.map → migration-command-scaffold-Bp8UHnvJ.mjs.map} +1 -1
- package/dist/{migration-list-DopkAG7L.mjs → migration-list-C2xnaYsT.mjs} +2 -2
- package/dist/{migration-list-DopkAG7L.mjs.map → migration-list-C2xnaYsT.mjs.map} +1 -1
- package/dist/{migration-list-graph-render-C-daUZLU.d.mts → migration-list-graph-render-DKw1AT-e.d.mts} +1 -1
- package/dist/migration-list-graph-render-DKw1AT-e.d.mts.map +1 -0
- package/dist/{migration-plan-BHoeET4O.mjs → migration-plan-BLvOmNCu.mjs} +6 -5
- package/dist/{migration-plan-BHoeET4O.mjs.map → migration-plan-BLvOmNCu.mjs.map} +1 -1
- package/dist/{migration-types-BXWvz12q.d.mts → migration-types-q64xAI_J.d.mts} +1 -1
- package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-q64xAI_J.d.mts.map} +1 -1
- package/dist/{migrations-D-UCOGtk.mjs → migrations-vzQt9LI2.mjs} +3 -13
- package/dist/migrations-vzQt9LI2.mjs.map +1 -0
- package/dist/{output-CUIdfYo5.mjs → output-B60Gw5fu.mjs} +1 -1
- package/dist/{output-CUIdfYo5.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
- package/dist/{ref-advancement-CHJ_8HxQ.mjs → ref-advancement-DRh5Nquq.mjs} +1 -1
- package/dist/{ref-advancement-CHJ_8HxQ.mjs.map → ref-advancement-DRh5Nquq.mjs.map} +1 -1
- package/dist/{types-UWB2-rrw.d.mts → types-CEtm6v6a.d.mts} +1 -8
- package/dist/types-CEtm6v6a.d.mts.map +1 -0
- package/dist/{verify-9gDJz6cm.mjs → verify-ktSRQvIS.mjs} +2 -2
- package/dist/{verify-9gDJz6cm.mjs.map → verify-ktSRQvIS.mjs.map} +1 -1
- package/package.json +18 -18
- package/src/commands/migrate.ts +52 -42
- package/src/commands/migration-check.ts +43 -83
- package/src/commands/migration-new.ts +44 -42
- package/src/commands/migration-plan.ts +2 -2
- package/src/commands/migration-show.ts +65 -284
- package/src/commands/migration-status.ts +131 -99
- package/src/control-api/client.ts +0 -1
- package/src/control-api/operations/db-verify.ts +9 -5
- package/src/control-api/operations/migration-apply.ts +11 -19
- package/src/control-api/types.ts +0 -7
- package/src/utils/command-helpers.ts +8 -3
- package/src/utils/contract-space-aggregate-loader.ts +221 -117
- package/src/utils/formatters/migrations.ts +4 -38
- package/src/utils/integrity-violation-to-check-failure.ts +130 -0
- package/dist/client-a5NJce0-.mjs.map +0 -1
- package/dist/commands/migration-check.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-EVU3n9YE.mjs +0 -160
- package/dist/contract-space-aggregate-loader-EVU3n9YE.mjs.map +0 -1
- package/dist/migration-list-graph-render-C-daUZLU.d.mts.map +0 -1
- package/dist/migrations-D-UCOGtk.mjs.map +0 -1
- package/dist/types-UWB2-rrw.d.mts.map +0 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
1
2
|
import {
|
|
2
3
|
createControlStack,
|
|
3
4
|
type MigrationPlanOperation,
|
|
4
5
|
} from '@prisma-next/framework-components/control';
|
|
5
6
|
import {
|
|
6
7
|
type ContractMarkerRecordLike,
|
|
8
|
+
type ContractSpaceAggregate,
|
|
7
9
|
graphWalkStrategy,
|
|
10
|
+
loadContractSpaceAggregate,
|
|
11
|
+
requireHeadRef,
|
|
8
12
|
} from '@prisma-next/migration-tools/aggregate';
|
|
9
13
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
10
14
|
import {
|
|
@@ -22,6 +26,7 @@ import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/packag
|
|
|
22
26
|
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
23
27
|
import type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';
|
|
24
28
|
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
29
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
25
30
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
26
31
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
27
32
|
import { cyan, dim, magenta, yellow } from 'colorette';
|
|
@@ -39,7 +44,6 @@ import {
|
|
|
39
44
|
import {
|
|
40
45
|
addGlobalOptions,
|
|
41
46
|
collectDeclaredInvariants,
|
|
42
|
-
loadMigrationPackages,
|
|
43
47
|
maskConnectionUrl,
|
|
44
48
|
readContractEnvelope,
|
|
45
49
|
resolveMigrationPaths,
|
|
@@ -50,9 +54,10 @@ import {
|
|
|
50
54
|
toStructuralEdge,
|
|
51
55
|
} from '../utils/command-helpers';
|
|
52
56
|
import {
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
refuseContractSpaceIntegrity,
|
|
58
|
+
refusePackageCorruptionOnAggregate,
|
|
55
59
|
} from '../utils/contract-space-aggregate-loader';
|
|
60
|
+
import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
|
|
56
61
|
import {
|
|
57
62
|
type EdgeStatus,
|
|
58
63
|
type EdgeStatusKind,
|
|
@@ -433,46 +438,43 @@ function resolveDisplayChain(
|
|
|
433
438
|
* cross-space totals.
|
|
434
439
|
*/
|
|
435
440
|
export async function loadAggregateStatusSpaces(args: {
|
|
436
|
-
readonly
|
|
437
|
-
readonly
|
|
438
|
-
readonly appContractRaw: unknown;
|
|
439
|
-
readonly extensionPacks: BuildAggregateInputs<string, string>['extensionPacks'];
|
|
440
|
-
readonly deserializeContract: BuildAggregateInputs<string, string>['deserializeContract'];
|
|
441
|
+
readonly aggregate: ContractSpaceAggregate;
|
|
442
|
+
readonly extensionPacks: ReadonlyArray<unknown>;
|
|
441
443
|
readonly markersBySpace: ReadonlyMap<string, ContractMarkerRecordLike> | null;
|
|
442
444
|
}): Promise<readonly MigrationStatusSpaceEntry[]> {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
if (!loaded.ok) {
|
|
453
|
-
// Loader failure (drift, layout violation, etc.) — surfacing it
|
|
454
|
-
// as a status diagnostic would duplicate `migration plan`'s job.
|
|
445
|
+
const declaredExtensions = toDeclaredExtensionsFromRaw(args.extensionPacks);
|
|
446
|
+
if (
|
|
447
|
+
refuseContractSpaceIntegrity(args.aggregate, {
|
|
448
|
+
declaredExtensions,
|
|
449
|
+
checkContracts: true,
|
|
450
|
+
})
|
|
451
|
+
) {
|
|
452
|
+
// Full integrity refusal (drift, layout violation, etc.) — surfacing
|
|
453
|
+
// it as a status diagnostic would duplicate `migration plan`'s job.
|
|
455
454
|
// The single-space app pipeline still runs; extensions are simply
|
|
456
455
|
// not enumerated.
|
|
457
456
|
return [];
|
|
458
457
|
}
|
|
459
|
-
const aggregate =
|
|
458
|
+
const aggregate = args.aggregate;
|
|
460
459
|
|
|
461
460
|
const orderedMembers = [...aggregate.extensions, aggregate.app];
|
|
462
461
|
const rows: MigrationStatusSpaceEntry[] = [];
|
|
463
462
|
for (const member of orderedMembers) {
|
|
464
463
|
const liveMarker = args.markersBySpace?.get(member.spaceId) ?? null;
|
|
465
464
|
const isApp = member.spaceId === aggregate.app.spaceId;
|
|
465
|
+
// The aggregate passed the integrity gate above, so every member has
|
|
466
|
+
// a resolved head ref (a missing one would have refused the load).
|
|
467
|
+
const headRef = requireHeadRef(member);
|
|
466
468
|
|
|
467
|
-
if (member.
|
|
469
|
+
if (member.graph().nodes.size === 0) {
|
|
468
470
|
rows.push({
|
|
469
471
|
spaceId: member.spaceId,
|
|
470
472
|
kind: isApp ? 'app' : 'extension',
|
|
471
|
-
headHash:
|
|
473
|
+
headHash: headRef.hash,
|
|
472
474
|
...(args.markersBySpace !== null
|
|
473
475
|
? {
|
|
474
476
|
markerHash: liveMarker?.storageHash ?? null,
|
|
475
|
-
status:
|
|
477
|
+
status: headRef.hash === EMPTY_CONTRACT_HASH ? 'up-to-date' : 'never-planned',
|
|
476
478
|
pendingCount: 0,
|
|
477
479
|
}
|
|
478
480
|
: {}),
|
|
@@ -484,7 +486,7 @@ export async function loadAggregateStatusSpaces(args: {
|
|
|
484
486
|
rows.push({
|
|
485
487
|
spaceId: member.spaceId,
|
|
486
488
|
kind: isApp ? 'app' : 'extension',
|
|
487
|
-
headHash:
|
|
489
|
+
headHash: headRef.hash,
|
|
488
490
|
});
|
|
489
491
|
continue;
|
|
490
492
|
}
|
|
@@ -513,7 +515,7 @@ export async function loadAggregateStatusSpaces(args: {
|
|
|
513
515
|
rows.push({
|
|
514
516
|
spaceId: member.spaceId,
|
|
515
517
|
kind: isApp ? 'app' : 'extension',
|
|
516
|
-
headHash:
|
|
518
|
+
headHash: headRef.hash,
|
|
517
519
|
markerHash: liveMarker?.storageHash ?? null,
|
|
518
520
|
pendingCount,
|
|
519
521
|
...(status ? { status } : {}),
|
|
@@ -528,6 +530,22 @@ export async function loadAggregateStatusSpaces(args: {
|
|
|
528
530
|
* the existing `readContractEnvelope` path will report the same
|
|
529
531
|
* problem via a status diagnostic, no need to double-surface.
|
|
530
532
|
*/
|
|
533
|
+
|
|
534
|
+
function appContractShellForAggregateLoad(args: {
|
|
535
|
+
readonly contractHash: string;
|
|
536
|
+
readonly targetId: string;
|
|
537
|
+
readonly targetFamily: string;
|
|
538
|
+
}): Contract {
|
|
539
|
+
return blindCast<Contract, 'status aggregate load without contract.json'>({
|
|
540
|
+
storage: { storageHash: args.contractHash },
|
|
541
|
+
schemaVersion: '0.0.0',
|
|
542
|
+
target: args.targetId,
|
|
543
|
+
targetFamily: args.targetFamily,
|
|
544
|
+
models: {},
|
|
545
|
+
profileHash: EMPTY_CONTRACT_HASH,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
531
549
|
async function loadContractRawSafely(config: {
|
|
532
550
|
contract?: { output?: string };
|
|
533
551
|
}): Promise<unknown | null> {
|
|
@@ -580,8 +598,10 @@ async function executeMigrationStatusCommand(
|
|
|
580
598
|
ui: TerminalUI,
|
|
581
599
|
): Promise<Result<MigrationStatusResult, CliStructuredError>> {
|
|
582
600
|
const config = await loadConfig(options.config);
|
|
583
|
-
const { configPath,
|
|
584
|
-
|
|
601
|
+
const { configPath, appMigrationsRelative, migrationsDir, refsDir } = resolveMigrationPaths(
|
|
602
|
+
options.config,
|
|
603
|
+
config,
|
|
604
|
+
);
|
|
585
605
|
|
|
586
606
|
const dbConnection = options.db ?? config.db?.connection;
|
|
587
607
|
const hasDriver = !!config.driver;
|
|
@@ -599,37 +619,92 @@ async function executeMigrationStatusCommand(
|
|
|
599
619
|
throw error;
|
|
600
620
|
}
|
|
601
621
|
|
|
602
|
-
|
|
622
|
+
const diagnostics: StatusDiagnostic[] = [];
|
|
623
|
+
let contractHash: string = EMPTY_CONTRACT_HASH;
|
|
624
|
+
try {
|
|
625
|
+
const envelope = await readContractEnvelope(config);
|
|
626
|
+
contractHash = envelope.storageHash;
|
|
627
|
+
} catch (error) {
|
|
628
|
+
diagnostics.push({
|
|
629
|
+
code: 'CONTRACT.UNREADABLE',
|
|
630
|
+
severity: 'warn',
|
|
631
|
+
message: `Could not read contract: ${error instanceof Error ? error.message : 'unknown error'}`,
|
|
632
|
+
hints: ["Run 'prisma-next contract emit' to generate a valid contract"],
|
|
633
|
+
});
|
|
634
|
+
}
|
|
603
635
|
|
|
604
|
-
|
|
636
|
+
const contractRawForAggregate = await loadContractRawSafely(config);
|
|
637
|
+
const stack = createControlStack(config);
|
|
638
|
+
const familyInstance = config.family.create(stack);
|
|
639
|
+
const deserializeContract = (json: unknown): Contract => familyInstance.deserializeContract(json);
|
|
640
|
+
const appContractShell = appContractShellForAggregateLoad({
|
|
641
|
+
contractHash,
|
|
642
|
+
targetId: config.target.id,
|
|
643
|
+
targetFamily: config.target.familyId,
|
|
644
|
+
});
|
|
645
|
+
let appContractForLoad: Contract = appContractShell;
|
|
646
|
+
if (contractRawForAggregate !== null) {
|
|
605
647
|
try {
|
|
606
|
-
|
|
648
|
+
appContractForLoad = deserializeContract(contractRawForAggregate);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
diagnostics.push({
|
|
651
|
+
code: 'CONTRACT.UNREADABLE',
|
|
652
|
+
severity: 'warn',
|
|
653
|
+
message: `Could not deserialize contract: ${error instanceof Error ? error.message : 'unknown error'}`,
|
|
654
|
+
hints: ["Run 'prisma-next contract emit' to generate a valid contract"],
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
607
658
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
659
|
+
let aggregate: ContractSpaceAggregate;
|
|
660
|
+
try {
|
|
661
|
+
aggregate = await loadContractSpaceAggregate({
|
|
662
|
+
migrationsDir,
|
|
663
|
+
deserializeContract,
|
|
664
|
+
appContract: appContractForLoad,
|
|
665
|
+
});
|
|
666
|
+
} catch (error) {
|
|
667
|
+
if (MigrationToolsError.is(error)) {
|
|
668
|
+
return notOk(mapMigrationToolsError(error));
|
|
669
|
+
}
|
|
670
|
+
return notOk(
|
|
671
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
672
|
+
why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
673
|
+
}),
|
|
674
|
+
);
|
|
675
|
+
}
|
|
620
676
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
677
|
+
if (contractRawForAggregate !== null) {
|
|
678
|
+
const corruptionFailure = refusePackageCorruptionOnAggregate(aggregate);
|
|
679
|
+
if (corruptionFailure) {
|
|
680
|
+
return notOk(corruptionFailure);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const appGraph = aggregate.app.graph();
|
|
685
|
+
|
|
686
|
+
let fromOverrideHash: string | undefined;
|
|
687
|
+
|
|
688
|
+
if (options.to || options.from) {
|
|
689
|
+
if (options.to) {
|
|
690
|
+
const refResult = parseContractRef(options.to, { graph: appGraph, refs: allRefs });
|
|
691
|
+
if (!refResult.ok) {
|
|
692
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
627
693
|
}
|
|
628
|
-
|
|
629
|
-
if (
|
|
630
|
-
|
|
694
|
+
activeRefHash = refResult.value.hash;
|
|
695
|
+
if (refResult.value.provenance.kind === 'ref') {
|
|
696
|
+
const resolvedRefName = refResult.value.provenance.refName;
|
|
697
|
+
activeRefName = resolvedRefName;
|
|
698
|
+
activeRefEntry = allRefs[resolvedRefName];
|
|
631
699
|
}
|
|
632
|
-
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (options.from) {
|
|
703
|
+
const fromResult = parseContractRef(options.from, { graph: appGraph, refs: allRefs });
|
|
704
|
+
if (!fromResult.ok) {
|
|
705
|
+
return notOk(mapRefResolutionError(fromResult.failure));
|
|
706
|
+
}
|
|
707
|
+
fromOverrideHash = fromResult.value.hash;
|
|
633
708
|
}
|
|
634
709
|
}
|
|
635
710
|
|
|
@@ -670,34 +745,8 @@ async function executeMigrationStatusCommand(
|
|
|
670
745
|
ui.stderr(header);
|
|
671
746
|
}
|
|
672
747
|
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
try {
|
|
676
|
-
const envelope = await readContractEnvelope(config);
|
|
677
|
-
contractHash = envelope.storageHash;
|
|
678
|
-
} catch (error) {
|
|
679
|
-
diagnostics.push({
|
|
680
|
-
code: 'CONTRACT.UNREADABLE',
|
|
681
|
-
severity: 'warn',
|
|
682
|
-
message: `Could not read contract: ${error instanceof Error ? error.message : 'unknown error'}`,
|
|
683
|
-
hints: ["Run 'prisma-next contract emit' to generate a valid contract"],
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
let bundles: readonly OnDiskMigrationPackage[];
|
|
688
|
-
let graph: MigrationGraph;
|
|
689
|
-
try {
|
|
690
|
-
({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
|
|
691
|
-
} catch (error) {
|
|
692
|
-
if (MigrationToolsError.is(error)) {
|
|
693
|
-
return notOk(mapMigrationToolsError(error));
|
|
694
|
-
}
|
|
695
|
-
return notOk(
|
|
696
|
-
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
697
|
-
why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
698
|
-
}),
|
|
699
|
-
);
|
|
700
|
-
}
|
|
748
|
+
const bundles = aggregate.app.packages;
|
|
749
|
+
const graph = appGraph;
|
|
701
750
|
|
|
702
751
|
if (bundles.length === 0) {
|
|
703
752
|
if (dbConnection && hasDriver) {
|
|
@@ -808,32 +857,15 @@ async function executeMigrationStatusCommand(
|
|
|
808
857
|
allMarkers = null;
|
|
809
858
|
}
|
|
810
859
|
|
|
811
|
-
// Build the aggregate enumeration of contract spaces. Lossy on
|
|
812
|
-
// failure (extensions are simply omitted) so the existing
|
|
813
|
-
// single-space app pipeline below still runs even if extensions
|
|
814
|
-
// can't be loaded — a strict failure here would degrade the
|
|
815
|
-
// load-bearing app-space output for unrelated reasons.
|
|
816
|
-
const contractRawForAggregate = await loadContractRawSafely(config);
|
|
817
860
|
let aggregateSpaces: readonly MigrationStatusSpaceEntry[] = [];
|
|
818
861
|
if (contractRawForAggregate !== null) {
|
|
819
|
-
// The aggregate loader needs a typed-Contract producer. Build a
|
|
820
|
-
// real control stack so `deserializeContract` runs against a fully
|
|
821
|
-
// composed family instance — descriptors that read stack members
|
|
822
|
-
// during construction (e.g. codec lookups) get a consistent view.
|
|
823
|
-
const stack = createControlStack(config);
|
|
824
|
-
const familyInstance = config.family.create(stack);
|
|
825
862
|
try {
|
|
826
863
|
aggregateSpaces = await loadAggregateStatusSpaces({
|
|
827
|
-
|
|
828
|
-
migrationsDir,
|
|
829
|
-
appContractRaw: contractRawForAggregate,
|
|
864
|
+
aggregate,
|
|
830
865
|
extensionPacks: config.extensionPacks ?? [],
|
|
831
|
-
deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
|
|
832
866
|
markersBySpace: allMarkers,
|
|
833
867
|
});
|
|
834
868
|
} catch {
|
|
835
|
-
// Loader failure short-circuits silently — the existing
|
|
836
|
-
// single-space app pipeline below still runs.
|
|
837
869
|
aggregateSpaces = [];
|
|
838
870
|
}
|
|
839
871
|
}
|
|
@@ -473,7 +473,6 @@ class ControlClientImpl implements ControlClient {
|
|
|
473
473
|
migrationsDir: options.migrationsDir,
|
|
474
474
|
extensionPacks: this.options.extensionPacks ?? [],
|
|
475
475
|
targetId: this.options.target.targetId,
|
|
476
|
-
appMigrationPackages: options.appMigrationPackages,
|
|
477
476
|
...ifDefined('refHash', options.refHash),
|
|
478
477
|
...ifDefined('refInvariants', options.refInvariants),
|
|
479
478
|
...ifDefined('refName', options.refName),
|
|
@@ -9,8 +9,10 @@ import type {
|
|
|
9
9
|
import {
|
|
10
10
|
type AggregateVerifierOutput,
|
|
11
11
|
type ContractSpaceMember,
|
|
12
|
+
requireHeadRef,
|
|
12
13
|
verifyAggregate,
|
|
13
14
|
} from '@prisma-next/migration-tools/aggregate';
|
|
15
|
+
import { castAs } from '@prisma-next/utils/casts';
|
|
14
16
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
15
17
|
import { CliStructuredError } from '../../utils/cli-errors';
|
|
16
18
|
import {
|
|
@@ -162,7 +164,7 @@ async function runIntrospection<TFamilyId extends string, TTargetId extends stri
|
|
|
162
164
|
* `ok` result so the verifier still runs the (cheap) schemaCheck loop
|
|
163
165
|
* without invoking the family's verification path.
|
|
164
166
|
*/
|
|
165
|
-
function createPerMemberVerifier<TFamilyId extends string, TTargetId extends string>(
|
|
167
|
+
export function createPerMemberVerifier<TFamilyId extends string, TTargetId extends string>(
|
|
166
168
|
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
167
169
|
): (
|
|
168
170
|
projectedSchema: unknown,
|
|
@@ -173,7 +175,7 @@ function createPerMemberVerifier<TFamilyId extends string, TTargetId extends str
|
|
|
173
175
|
return (projectedSchema, member, verifyMode) => {
|
|
174
176
|
if (skipSchema) return buildSkippedSchemaResult(member);
|
|
175
177
|
return familyInstance.verifySchema({
|
|
176
|
-
contract: member.contract,
|
|
178
|
+
contract: member.contract(),
|
|
177
179
|
// The family's `TSchemaIR` is opaque to migration-tools; the
|
|
178
180
|
// aggregate verifier passes through whatever we hand it. The
|
|
179
181
|
// family expects its own IR shape on the way back.
|
|
@@ -247,15 +249,17 @@ function finaliseVerifyResult(args: {
|
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
function buildSkippedSchemaResult(member: ContractSpaceMember): VerifyDatabaseSchemaResult {
|
|
250
|
-
const
|
|
252
|
+
const contract = member.contract();
|
|
253
|
+
const headRef = requireHeadRef(member);
|
|
254
|
+
const profileHash = castAs<{ profileHash?: string }>(contract).profileHash;
|
|
251
255
|
return {
|
|
252
256
|
ok: true,
|
|
253
257
|
summary: 'Schema verification skipped',
|
|
254
258
|
contract: {
|
|
255
|
-
storageHash:
|
|
259
|
+
storageHash: headRef.hash,
|
|
256
260
|
...(profileHash ? { profileHash } : {}),
|
|
257
261
|
},
|
|
258
|
-
target: { expected:
|
|
262
|
+
target: { expected: contract.target },
|
|
259
263
|
schema: {
|
|
260
264
|
issues: [],
|
|
261
265
|
root: {
|
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
type ContractSpaceAggregate,
|
|
13
13
|
type ContractSpaceMember,
|
|
14
14
|
graphWalkStrategy,
|
|
15
|
+
requireHeadRef,
|
|
15
16
|
} from '@prisma-next/migration-tools/aggregate';
|
|
16
17
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
17
18
|
import { errorNoInvariantPath } from '@prisma-next/migration-tools/errors';
|
|
18
19
|
import { findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';
|
|
19
|
-
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
20
20
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
21
21
|
import { notOk, ok } from '@prisma-next/utils/result';
|
|
22
22
|
import {
|
|
@@ -56,15 +56,6 @@ export interface ExecuteMigrationApplyOptions<TFamilyId extends string, TTargetI
|
|
|
56
56
|
readonly migrationsDir: string;
|
|
57
57
|
readonly extensionPacks: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
58
58
|
readonly targetId: TTargetId;
|
|
59
|
-
/**
|
|
60
|
-
* Already-loaded app-space migration packages. The CLI command
|
|
61
|
-
* loads these via `loadMigrationPackages(appMigrationsDir)`; the
|
|
62
|
-
* operation hydrates the app member's graph with them. Required
|
|
63
|
-
* because the framework-neutral aggregate loader doesn't know how
|
|
64
|
-
* to read the user's `migrations/` directory layout (it's family-
|
|
65
|
-
* aware: ops.json shape, manifest keys, etc.).
|
|
66
|
-
*/
|
|
67
|
-
readonly appMigrationPackages: ReadonlyArray<OnDiskMigrationPackage>;
|
|
68
59
|
/**
|
|
69
60
|
* Optional app-space ref override. When provided, the app member's
|
|
70
61
|
* graph-walk targets this hash instead of `member.headRef.hash`.
|
|
@@ -125,7 +116,6 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
125
116
|
migrationsDir,
|
|
126
117
|
extensionPacks,
|
|
127
118
|
targetId,
|
|
128
|
-
appMigrationPackages,
|
|
129
119
|
refHash,
|
|
130
120
|
refInvariants,
|
|
131
121
|
refName,
|
|
@@ -138,7 +128,6 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
138
128
|
appContract: contract,
|
|
139
129
|
extensionPacks,
|
|
140
130
|
deserializeContract: (json) => familyInstance.deserializeContract(json),
|
|
141
|
-
appMigrationPackages,
|
|
142
131
|
};
|
|
143
132
|
const loaded = await buildContractSpaceAggregate(loadInputs);
|
|
144
133
|
if (!loaded.ok) {
|
|
@@ -161,12 +150,15 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
161
150
|
const atHeadResolutions = new Map<string, AggregatePerSpacePlan>();
|
|
162
151
|
for (const member of allMembers) {
|
|
163
152
|
const isAppMember = member.spaceId === aggregate.app.spaceId;
|
|
164
|
-
|
|
153
|
+
// The aggregate passed the integrity gate, so every member's head ref
|
|
154
|
+
// is resolved (the app's is synthesised from the live contract).
|
|
155
|
+
const headRef = requireHeadRef(member);
|
|
156
|
+
const targetHash = isAppMember && refHash !== undefined ? refHash : headRef.hash;
|
|
165
157
|
const liveMarker = markerRows.get(member.spaceId) ?? null;
|
|
166
158
|
|
|
167
159
|
// Empty-graph members fail loudly: replay needs an on-disk path
|
|
168
160
|
// and an empty graph means the user has never planned this space.
|
|
169
|
-
if (member.
|
|
161
|
+
if (member.graph().nodes.size === 0) {
|
|
170
162
|
// Edge case: target == EMPTY (greenfield, nothing to do) or
|
|
171
163
|
// the live marker already matches the target. Loader integrity
|
|
172
164
|
// allows this for extensions whose head ref is the empty
|
|
@@ -197,9 +189,9 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
197
189
|
const targetInvariants =
|
|
198
190
|
isAppMember && refHash !== undefined && refInvariants !== undefined
|
|
199
191
|
? refInvariants
|
|
200
|
-
:
|
|
192
|
+
: headRef.invariants;
|
|
201
193
|
const targetMember: ContractSpaceMember =
|
|
202
|
-
targetHash ===
|
|
194
|
+
targetHash === headRef.hash && targetInvariants === headRef.invariants
|
|
203
195
|
? member
|
|
204
196
|
: { ...member, headRef: { hash: targetHash, invariants: targetInvariants } };
|
|
205
197
|
|
|
@@ -224,7 +216,7 @@ export async function executeMigrationApply<TFamilyId extends string, TTargetId
|
|
|
224
216
|
// is never a graph node, producing an empty `structuralPath` and
|
|
225
217
|
// a less actionable diagnostic.
|
|
226
218
|
const fromHash = liveMarker?.storageHash ?? EMPTY_CONTRACT_HASH;
|
|
227
|
-
const structural = findPathWithDecision(targetMember.
|
|
219
|
+
const structural = findPathWithDecision(targetMember.graph(), fromHash, targetHash, {
|
|
228
220
|
required: new Set<string>(),
|
|
229
221
|
});
|
|
230
222
|
const structuralPath =
|
|
@@ -369,7 +361,7 @@ function buildAtHeadResolution(args: {
|
|
|
369
361
|
providedInvariants: [],
|
|
370
362
|
},
|
|
371
363
|
displayOps: [],
|
|
372
|
-
destinationContract: member.contract,
|
|
364
|
+
destinationContract: member.contract(),
|
|
373
365
|
strategy: 'graph-walk',
|
|
374
366
|
migrationEdges: [],
|
|
375
367
|
};
|
|
@@ -407,7 +399,7 @@ function buildSuccess(args: BuildSuccessArgs): MigrationApplySuccess {
|
|
|
407
399
|
(r) => r.spaceId === args.aggregate.app.spaceId,
|
|
408
400
|
);
|
|
409
401
|
const appMarkerHash =
|
|
410
|
-
appResolution?.entry.plan.destination.storageHash ?? args.aggregate.app.
|
|
402
|
+
appResolution?.entry.plan.destination.storageHash ?? requireHeadRef(args.aggregate.app).hash;
|
|
411
403
|
|
|
412
404
|
// Per-migration entries (one per authored edge) preserve the
|
|
413
405
|
// single-space `migrationsApplied` count semantics for back-compat
|
package/src/control-api/types.ts
CHANGED
|
@@ -18,7 +18,6 @@ import type {
|
|
|
18
18
|
VerifyDatabaseSchemaResult,
|
|
19
19
|
} from '@prisma-next/framework-components/control';
|
|
20
20
|
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
21
|
-
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
22
21
|
import type { Result } from '@prisma-next/utils/result';
|
|
23
22
|
import type { ExecuteDbVerifyResult } from './operations/db-verify';
|
|
24
23
|
|
|
@@ -548,12 +547,6 @@ export interface MigrationApplyOptions {
|
|
|
548
547
|
readonly contract: unknown;
|
|
549
548
|
/** Migrations root directory (`migrations/` under the project). */
|
|
550
549
|
readonly migrationsDir: string;
|
|
551
|
-
/**
|
|
552
|
-
* Already-loaded app-space migration packages. The CLI loads these
|
|
553
|
-
* via `loadMigrationPackages(appMigrationsDir)` before invoking
|
|
554
|
-
* `migrationApply`.
|
|
555
|
-
*/
|
|
556
|
-
readonly appMigrationPackages: ReadonlyArray<OnDiskMigrationPackage>;
|
|
557
550
|
/**
|
|
558
551
|
* Optional app-space ref override. When provided, the app member's
|
|
559
552
|
* graph-walk targets this hash instead of `contract.storage.storageHash`.
|
|
@@ -242,9 +242,14 @@ export async function loadMigrationPackages(migrationsDir: string): Promise<{
|
|
|
242
242
|
bundles: readonly OnDiskMigrationPackage[];
|
|
243
243
|
graph: MigrationGraph;
|
|
244
244
|
}> {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
// `readMigrationsDir` is tolerant: it retains hash-/invariant-mismatched
|
|
246
|
+
// packages and omits unparseable ones, reporting both via `problems`.
|
|
247
|
+
// This helper preserves its historical `{ bundles, graph }` contract by
|
|
248
|
+
// exposing the retained packages; callers that need to surface load
|
|
249
|
+
// problems read them from the tolerant primitive directly.
|
|
250
|
+
const { packages } = await readMigrationsDir(migrationsDir);
|
|
251
|
+
const graph = reconstructGraph(packages);
|
|
252
|
+
return { bundles: packages, graph };
|
|
248
253
|
}
|
|
249
254
|
|
|
250
255
|
/**
|