@prisma-next/cli 0.8.0 → 0.9.0-dev.1
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 +8 -9
- package/dist/{cli-errors-D3_sMh2K.mjs → cli-errors-CF60g2cG.mjs} +40 -2
- package/dist/cli-errors-CF60g2cG.mjs.map +1 -0
- package/dist/cli.mjs +67 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-BCnP7cHo.mjs → client-Brv4qlfB.mjs} +28 -30
- package/dist/client-Brv4qlfB.mjs.map +1 -0
- package/dist/{command-helpers-BeZHkxV8.mjs → command-helpers-D3vL5yi8.mjs} +29 -6
- package/dist/command-helpers-D3vL5yi8.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 +7 -7
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +67 -25
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +37 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +28 -0
- package/dist/commands/migrate.d.mts.map +1 -0
- package/dist/commands/{migration-apply.mjs → migrate.mjs} +65 -39
- package/dist/commands/migrate.mjs.map +1 -0
- package/dist/commands/migration-check.d.mts +18 -0
- package/dist/commands/migration-check.d.mts.map +1 -0
- package/dist/commands/migration-check.mjs +284 -0
- package/dist/commands/migration-check.mjs.map +1 -0
- package/dist/commands/migration-graph.d.mts +16 -0
- package/dist/commands/migration-graph.d.mts.map +1 -0
- package/dist/commands/migration-graph.mjs +141 -0
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +20 -0
- package/dist/commands/migration-list.d.mts.map +1 -0
- package/dist/commands/migration-list.mjs +107 -0
- package/dist/commands/migration-list.mjs.map +1 -0
- package/dist/commands/migration-log.d.mts +21 -0
- package/dist/commands/migration-log.d.mts.map +1 -0
- package/dist/commands/migration-log.mjs +146 -0
- package/dist/commands/migration-log.mjs.map +1 -0
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +30 -29
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +2 -2
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +90 -52
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +5 -17
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +732 -1
- package/dist/commands/migration-status.mjs.map +1 -0
- package/dist/commands/ref.d.mts +34 -0
- package/dist/commands/ref.d.mts.map +1 -0
- package/dist/commands/{migration-ref.mjs → ref.mjs} +28 -57
- package/dist/commands/ref.mjs.map +1 -0
- package/dist/{contract-emit-9DBda5Ou.mjs → contract-emit-C3STUIBg.mjs} +6 -6
- package/dist/{contract-emit-9DBda5Ou.mjs.map → contract-emit-C3STUIBg.mjs.map} +1 -1
- package/dist/{contract-emit-B77TsJqf.mjs → contract-emit-iynA3BCA.mjs} +9 -5
- package/dist/contract-emit-iynA3BCA.mjs.map +1 -0
- package/dist/{contract-infer-ByxhPjpW.mjs → contract-infer-Cnj8G1E2.mjs} +5 -5
- package/dist/{contract-infer-ByxhPjpW.mjs.map → contract-infer-Cnj8G1E2.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs → contract-space-aggregate-loader-pAc8CDfY.mjs} +4 -4
- package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs.map → contract-space-aggregate-loader-pAc8CDfY.mjs.map} +1 -1
- package/dist/{db-verify-Czm5T-J4.mjs → db-verify-D7cyH_zz.mjs} +12 -9
- package/dist/db-verify-D7cyH_zz.mjs.map +1 -0
- package/dist/errors-Cw6kyTyV.mjs +56 -0
- package/dist/errors-Cw6kyTyV.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-ChqVUxR-.mjs → framework-components-xFLFpZUO.mjs} +2 -2
- package/dist/{framework-components-ChqVUxR-.mjs.map → framework-components-xFLFpZUO.mjs.map} +1 -1
- package/dist/{global-flags-Icqpxk23.d.mts → global-flags-DGmw6Kqg.d.mts} +1 -1
- package/dist/{global-flags-Icqpxk23.d.mts.map → global-flags-DGmw6Kqg.d.mts.map} +1 -1
- package/dist/{migration-status-By9G5p2H.mjs → graph-render-eJDcLWny.mjs} +3 -692
- package/dist/graph-render-eJDcLWny.mjs.map +1 -0
- package/dist/{init-B-k3a1Qw.mjs → init-Bqg5JWg7.mjs} +133 -61
- package/dist/init-Bqg5JWg7.mjs.map +1 -0
- package/dist/{inspect-live-schema-DxdBd4Er.mjs → inspect-live-schema-CWLK_lgs.mjs} +4 -4
- package/dist/{inspect-live-schema-DxdBd4Er.mjs.map → inspect-live-schema-CWLK_lgs.mjs.map} +1 -1
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs → migration-command-scaffold-CmXXC1UZ.mjs} +4 -4
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs.map → migration-command-scaffold-CmXXC1UZ.mjs.map} +1 -1
- package/dist/{migration-plan-mRu5K81L.mjs → migration-plan-CHyUlBV0.mjs} +76 -37
- package/dist/migration-plan-CHyUlBV0.mjs.map +1 -0
- package/dist/migration-types-D2FW63pr.d.mts +15 -0
- package/dist/migration-types-D2FW63pr.d.mts.map +1 -0
- package/dist/{migrations-CTsyBXCA.mjs → migrations-DyUf5lTt.mjs} +2 -2
- package/dist/migrations-DyUf5lTt.mjs.map +1 -0
- package/dist/{output-BVj6a971.mjs → output-B60Gw5fu.mjs} +12 -11
- package/dist/{output-BVj6a971.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
- package/dist/{result-handler-rmPVKIP2.mjs → result-handler-Bm_6dDYg.mjs} +2 -2
- package/dist/{result-handler-rmPVKIP2.mjs.map → result-handler-Bm_6dDYg.mjs.map} +1 -1
- package/dist/{terminal-ui-C_hFNbAn.mjs → terminal-ui-XtOQsqe9.mjs} +2 -54
- package/dist/terminal-ui-XtOQsqe9.mjs.map +1 -0
- package/dist/{types-LItU7E4l.d.mts → types-0aS865QN.d.mts} +14 -8
- package/dist/types-0aS865QN.d.mts.map +1 -0
- package/dist/{verify-CiwNWM9N.mjs → verify-D7ypCCe6.mjs} +1 -1
- package/dist/{verify-CiwNWM9N.mjs.map → verify-D7ypCCe6.mjs.map} +1 -1
- package/package.json +39 -23
- package/src/cli.ts +78 -15
- package/src/commands/db-sign.ts +102 -32
- package/src/commands/db-update.ts +56 -4
- package/src/commands/db-verify.ts +19 -3
- package/src/commands/init/agent-skill-install.ts +145 -43
- package/src/commands/init/errors.ts +2 -2
- package/src/commands/init/exit-codes.ts +2 -2
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +15 -6
- package/src/commands/init/inputs.ts +1 -1
- package/src/commands/init/output.ts +22 -17
- package/src/commands/{migration-apply.ts → migrate.ts} +77 -73
- package/src/commands/migration-check/exit-codes.ts +3 -0
- package/src/commands/migration-check.ts +369 -0
- package/src/commands/migration-graph.ts +184 -0
- package/src/commands/migration-list.ts +155 -0
- package/src/commands/migration-log.ts +218 -0
- package/src/commands/migration-new.ts +30 -22
- package/src/commands/migration-plan.ts +104 -35
- package/src/commands/migration-show.ts +141 -65
- package/src/commands/migration-status.ts +82 -69
- package/src/commands/{migration-ref.ts → ref.ts} +32 -86
- package/src/control-api/client.ts +30 -21
- package/src/control-api/operations/apply-aggregate.ts +4 -4
- package/src/control-api/operations/contract-emit.ts +26 -3
- package/src/control-api/operations/db-apply-aggregate.ts +4 -3
- package/src/control-api/operations/db-verify.ts +2 -2
- package/src/control-api/operations/migration-apply.ts +5 -4
- package/src/control-api/types.ts +12 -7
- package/src/load-ts-contract.ts +9 -1
- package/src/migration-cli.ts +1 -1
- package/src/utils/cli-errors.ts +37 -0
- package/src/utils/command-helpers.ts +28 -3
- package/src/utils/contract-space-aggregate-loader.ts +4 -4
- package/src/utils/contract-space-seed-phase.ts +2 -2
- package/src/utils/formatters/help.ts +12 -2
- package/src/utils/formatters/migrations.ts +2 -2
- package/dist/cli-errors-D3_sMh2K.mjs.map +0 -1
- package/dist/client-BCnP7cHo.mjs.map +0 -1
- package/dist/command-helpers-BeZHkxV8.mjs.map +0 -1
- package/dist/commands/migration-apply.d.mts +0 -51
- package/dist/commands/migration-apply.d.mts.map +0 -1
- package/dist/commands/migration-apply.mjs.map +0 -1
- package/dist/commands/migration-ref.d.mts +0 -45
- package/dist/commands/migration-ref.d.mts.map +0 -1
- package/dist/commands/migration-ref.mjs.map +0 -1
- package/dist/contract-emit-B77TsJqf.mjs.map +0 -1
- package/dist/db-verify-Czm5T-J4.mjs.map +0 -1
- package/dist/init-B-k3a1Qw.mjs.map +0 -1
- package/dist/migration-plan-mRu5K81L.mjs.map +0 -1
- package/dist/migration-status-By9G5p2H.mjs.map +0 -1
- package/dist/migrations-CTsyBXCA.mjs.map +0 -1
- package/dist/terminal-ui-C_hFNbAn.mjs.map +0 -1
- package/dist/types-LItU7E4l.d.mts.map +0 -1
- /package/dist/{cli-errors-B9OBbled.d.mts → cli-errors-DdcjVLJV.d.mts} +0 -0
|
@@ -19,8 +19,9 @@ import {
|
|
|
19
19
|
findReachableLeaves,
|
|
20
20
|
} from '@prisma-next/migration-tools/migration-graph';
|
|
21
21
|
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
22
|
+
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
22
23
|
import type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';
|
|
23
|
-
import { readRefs
|
|
24
|
+
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
24
25
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
25
26
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
26
27
|
import { cyan, dim, magenta, yellow } from 'colorette';
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
errorRuntime,
|
|
34
35
|
errorUnexpected,
|
|
35
36
|
mapMigrationToolsError,
|
|
37
|
+
mapRefResolutionError,
|
|
36
38
|
} from '../utils/cli-errors';
|
|
37
39
|
import {
|
|
38
40
|
addGlobalOptions,
|
|
@@ -43,6 +45,7 @@ import {
|
|
|
43
45
|
resolveMigrationPaths,
|
|
44
46
|
setCommandDescriptions,
|
|
45
47
|
setCommandExamples,
|
|
48
|
+
setCommandSeeAlso,
|
|
46
49
|
toPathDecisionResult,
|
|
47
50
|
toStructuralEdge,
|
|
48
51
|
} from '../utils/command-helpers';
|
|
@@ -70,10 +73,8 @@ import { TerminalUI } from '../utils/terminal-ui';
|
|
|
70
73
|
interface MigrationStatusOptions extends CommonCommandOptions {
|
|
71
74
|
readonly db?: string;
|
|
72
75
|
readonly config?: string;
|
|
73
|
-
readonly
|
|
74
|
-
readonly
|
|
75
|
-
readonly limit?: string;
|
|
76
|
-
readonly all?: boolean;
|
|
76
|
+
readonly to?: string;
|
|
77
|
+
readonly from?: string;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export interface MigrationStatusEntry {
|
|
@@ -94,7 +95,7 @@ export interface MigrationStatusEntry {
|
|
|
94
95
|
*
|
|
95
96
|
* - `headHash`: the on-disk head ref's hash (where the space is going).
|
|
96
97
|
* - `markerHash`: the live marker hash for the space, or null if no
|
|
97
|
-
* marker has been written yet (greenfield, or pre-`
|
|
98
|
+
* marker has been written yet (greenfield, or pre-`migrate`).
|
|
98
99
|
* - `pendingCount`: number of migration edges between marker and head.
|
|
99
100
|
* Computed via {@link graphWalkStrategy}; 0 means the space is
|
|
100
101
|
* already at head.
|
|
@@ -420,23 +421,6 @@ function resolveDisplayChain(
|
|
|
420
421
|
return toTarget;
|
|
421
422
|
}
|
|
422
423
|
|
|
423
|
-
const DEFAULT_LIMIT = 10;
|
|
424
|
-
|
|
425
|
-
function determineLimit(opts: MigrationStatusOptions) {
|
|
426
|
-
if (opts.all) {
|
|
427
|
-
// No limit
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (!opts.limit) {
|
|
431
|
-
return DEFAULT_LIMIT;
|
|
432
|
-
}
|
|
433
|
-
const parsed = Number.parseInt(opts.limit, 10);
|
|
434
|
-
if (Number.isNaN(parsed)) {
|
|
435
|
-
return DEFAULT_LIMIT;
|
|
436
|
-
}
|
|
437
|
-
return parsed;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
424
|
/**
|
|
441
425
|
* Build the aggregate enumeration of contract spaces for the status
|
|
442
426
|
* output. Loads the aggregate from disk (lossy on failure — extension
|
|
@@ -453,15 +437,15 @@ export async function loadAggregateStatusSpaces(args: {
|
|
|
453
437
|
readonly migrationsDir: string;
|
|
454
438
|
readonly appContractRaw: unknown;
|
|
455
439
|
readonly extensionPacks: BuildAggregateInputs<string, string>['extensionPacks'];
|
|
456
|
-
readonly
|
|
440
|
+
readonly deserializeContract: BuildAggregateInputs<string, string>['deserializeContract'];
|
|
457
441
|
readonly markersBySpace: ReadonlyMap<string, ContractMarkerRecordLike> | null;
|
|
458
442
|
}): Promise<readonly MigrationStatusSpaceEntry[]> {
|
|
459
443
|
const loadInputs: BuildAggregateInputs<string, string> = {
|
|
460
444
|
targetId: args.targetId,
|
|
461
445
|
migrationsDir: args.migrationsDir,
|
|
462
|
-
appContract: args.
|
|
446
|
+
appContract: args.deserializeContract(args.appContractRaw),
|
|
463
447
|
extensionPacks: args.extensionPacks,
|
|
464
|
-
|
|
448
|
+
deserializeContract: args.deserializeContract,
|
|
465
449
|
};
|
|
466
450
|
|
|
467
451
|
const loaded = await buildContractSpaceAggregate(loadInputs);
|
|
@@ -581,11 +565,32 @@ async function executeMigrationStatusCommand(
|
|
|
581
565
|
throw error;
|
|
582
566
|
}
|
|
583
567
|
|
|
584
|
-
|
|
585
|
-
|
|
568
|
+
let fromOverrideHash: string | undefined;
|
|
569
|
+
|
|
570
|
+
if (options.to || options.from) {
|
|
586
571
|
try {
|
|
587
|
-
|
|
588
|
-
|
|
572
|
+
const { graph: earlyGraph } = await loadMigrationPackages(appMigrationsDir);
|
|
573
|
+
|
|
574
|
+
if (options.to) {
|
|
575
|
+
const refResult = parseContractRef(options.to, { graph: earlyGraph, refs: allRefs });
|
|
576
|
+
if (!refResult.ok) {
|
|
577
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
578
|
+
}
|
|
579
|
+
activeRefHash = refResult.value.hash;
|
|
580
|
+
if (refResult.value.provenance.kind === 'ref') {
|
|
581
|
+
const resolvedRefName = refResult.value.provenance.refName;
|
|
582
|
+
activeRefName = resolvedRefName;
|
|
583
|
+
activeRefEntry = allRefs[resolvedRefName];
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (options.from) {
|
|
588
|
+
const fromResult = parseContractRef(options.from, { graph: earlyGraph, refs: allRefs });
|
|
589
|
+
if (!fromResult.ok) {
|
|
590
|
+
return notOk(mapRefResolutionError(fromResult.failure));
|
|
591
|
+
}
|
|
592
|
+
fromOverrideHash = fromResult.value.hash;
|
|
593
|
+
}
|
|
589
594
|
} catch (error) {
|
|
590
595
|
if (MigrationToolsError.is(error)) {
|
|
591
596
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -613,6 +618,9 @@ async function executeMigrationStatusCommand(
|
|
|
613
618
|
if (activeRefName) {
|
|
614
619
|
details.push({ label: 'ref', value: activeRefName });
|
|
615
620
|
}
|
|
621
|
+
if (options.from) {
|
|
622
|
+
details.push({ label: 'from', value: options.from });
|
|
623
|
+
}
|
|
616
624
|
if (activeRefEntry && activeRefEntry.invariants.length > 0) {
|
|
617
625
|
details.push({
|
|
618
626
|
label: 'required',
|
|
@@ -696,8 +704,8 @@ async function executeMigrationStatusCommand(
|
|
|
696
704
|
severity: 'warn',
|
|
697
705
|
message: 'There are multiple valid migration paths — you must select a target',
|
|
698
706
|
hints: [
|
|
699
|
-
"Use '--
|
|
700
|
-
"Or 'prisma-next
|
|
707
|
+
"Use '--to <contract>' to select a target",
|
|
708
|
+
"Or 'prisma-next ref set <name> <hash>' to create one",
|
|
701
709
|
],
|
|
702
710
|
});
|
|
703
711
|
}
|
|
@@ -751,6 +759,12 @@ async function executeMigrationStatusCommand(
|
|
|
751
759
|
}
|
|
752
760
|
}
|
|
753
761
|
|
|
762
|
+
if (fromOverrideHash !== undefined) {
|
|
763
|
+
markerHash = fromOverrideHash;
|
|
764
|
+
mode = 'offline';
|
|
765
|
+
allMarkers = null;
|
|
766
|
+
}
|
|
767
|
+
|
|
754
768
|
// Build the aggregate enumeration of contract spaces. Lossy on
|
|
755
769
|
// failure (extensions are simply omitted) so the existing
|
|
756
770
|
// single-space app pipeline below still runs even if extensions
|
|
@@ -760,7 +774,7 @@ async function executeMigrationStatusCommand(
|
|
|
760
774
|
let aggregateSpaces: readonly MigrationStatusSpaceEntry[] = [];
|
|
761
775
|
if (contractRawForAggregate !== null) {
|
|
762
776
|
// The aggregate loader needs a typed-Contract producer. Build a
|
|
763
|
-
// real control stack so `
|
|
777
|
+
// real control stack so `deserializeContract` runs against a fully
|
|
764
778
|
// composed family instance — descriptors that read stack members
|
|
765
779
|
// during construction (e.g. codec lookups) get a consistent view.
|
|
766
780
|
const stack = createControlStack(config);
|
|
@@ -771,7 +785,7 @@ async function executeMigrationStatusCommand(
|
|
|
771
785
|
migrationsDir,
|
|
772
786
|
appContractRaw: contractRawForAggregate,
|
|
773
787
|
extensionPacks: config.extensionPacks ?? [],
|
|
774
|
-
|
|
788
|
+
deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
|
|
775
789
|
markersBySpace: allMarkers,
|
|
776
790
|
});
|
|
777
791
|
} catch {
|
|
@@ -863,7 +877,7 @@ async function executeMigrationStatusCommand(
|
|
|
863
877
|
code: 'MIGRATION.NO_MARKER',
|
|
864
878
|
severity: 'warn',
|
|
865
879
|
message: 'Database has not been initialized — no migration marker found',
|
|
866
|
-
hints: ["Run 'prisma-next
|
|
880
|
+
hints: ["Run 'prisma-next migrate' to apply pending migrations"],
|
|
867
881
|
});
|
|
868
882
|
}
|
|
869
883
|
|
|
@@ -923,7 +937,7 @@ async function executeMigrationStatusCommand(
|
|
|
923
937
|
let missingInvariants: readonly string[] | undefined;
|
|
924
938
|
let effectiveRequired = new Set<string>();
|
|
925
939
|
if (mode === 'online') {
|
|
926
|
-
// Mirrors `
|
|
940
|
+
// Mirrors `migrate.ts`: compute `effectiveRequired = required −
|
|
927
941
|
// marker.invariants` directly, then derive the display fields from it.
|
|
928
942
|
// `appliedInvariants` is the intersection (`required ∩ marker`), which
|
|
929
943
|
// is what JSON consumers see for the active ref; the unfiltered set
|
|
@@ -945,17 +959,17 @@ async function executeMigrationStatusCommand(
|
|
|
945
959
|
if (mode === 'online') {
|
|
946
960
|
if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
|
|
947
961
|
summary = `${bundles.length} migration(s) on disk`;
|
|
948
|
-
} else if (activeRefHash && markerHash !== undefined) {
|
|
949
|
-
const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName
|
|
962
|
+
} else if (activeRefHash && activeRefName && markerHash !== undefined) {
|
|
963
|
+
const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName);
|
|
950
964
|
summary = hasInvariantWork ? `${distance} — missing invariant(s): ${missingList}` : distance;
|
|
951
965
|
} else if (pendingCount === 0 && !hasInvariantWork) {
|
|
952
966
|
summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? 's' : ''} applied)`;
|
|
953
967
|
} else if (pendingCount === 0 && hasInvariantWork) {
|
|
954
|
-
summary = `Missing invariant(s): ${missingList} — run 'prisma-next
|
|
968
|
+
summary = `Missing invariant(s): ${missingList} — run 'prisma-next migrate --to ${activeRefName ?? '<ref>'}' to apply`;
|
|
955
969
|
} else if (markerHash === undefined) {
|
|
956
970
|
summary = `${pendingCount} pending migration(s) — database has no marker`;
|
|
957
971
|
} else {
|
|
958
|
-
summary = `${pendingCount} pending migration(s) — run 'prisma-next
|
|
972
|
+
summary = `${pendingCount} pending migration(s) — run 'prisma-next migrate' to apply`;
|
|
959
973
|
}
|
|
960
974
|
} else {
|
|
961
975
|
summary = `${entries.length} migration(s) on disk`;
|
|
@@ -997,8 +1011,7 @@ async function executeMigrationStatusCommand(
|
|
|
997
1011
|
diagnostics.push({
|
|
998
1012
|
code: 'MIGRATION.MARKER_NOT_IN_HISTORY',
|
|
999
1013
|
severity: 'warn',
|
|
1000
|
-
message:
|
|
1001
|
-
'Database matches the current contract but was updated directly (not via migration apply)',
|
|
1014
|
+
message: 'Database matches the current contract but was updated directly (not via migrate)',
|
|
1002
1015
|
hints: ["Run 'prisma-next migration plan' to plan a migration to your current contract"],
|
|
1003
1016
|
});
|
|
1004
1017
|
} else if (pendingCount > 0) {
|
|
@@ -1006,7 +1019,7 @@ async function executeMigrationStatusCommand(
|
|
|
1006
1019
|
code: 'MIGRATION.DATABASE_BEHIND',
|
|
1007
1020
|
severity: 'info',
|
|
1008
1021
|
message: `${pendingCount} migration(s) pending`,
|
|
1009
|
-
hints: ["Run 'prisma-next
|
|
1022
|
+
hints: ["Run 'prisma-next migrate' to apply pending migrations"],
|
|
1010
1023
|
});
|
|
1011
1024
|
} else if (hasInvariantWork) {
|
|
1012
1025
|
diagnostics.push({
|
|
@@ -1014,7 +1027,7 @@ async function executeMigrationStatusCommand(
|
|
|
1014
1027
|
severity: 'info',
|
|
1015
1028
|
message: `Missing required invariant(s): ${missingList}`,
|
|
1016
1029
|
hints: [
|
|
1017
|
-
`Run 'prisma-next
|
|
1030
|
+
`Run 'prisma-next migrate --to ${activeRefName ?? '<ref>'}' to apply a path that covers the required invariants`,
|
|
1018
1031
|
],
|
|
1019
1032
|
});
|
|
1020
1033
|
} else if (!routingUnreachable) {
|
|
@@ -1056,37 +1069,41 @@ export function createMigrationStatusCommand(): Command {
|
|
|
1056
1069
|
const command = new Command('status');
|
|
1057
1070
|
setCommandDescriptions(
|
|
1058
1071
|
command,
|
|
1059
|
-
'Show migration
|
|
1060
|
-
'
|
|
1061
|
-
'
|
|
1062
|
-
'
|
|
1072
|
+
'Show migration path and pending status',
|
|
1073
|
+
'Shows which migrations are pending between the database marker and\n' +
|
|
1074
|
+
'the target contract. Requires a database connection for live status.\n' +
|
|
1075
|
+
'Use `migration graph` for topology, `migration log` for history,\n' +
|
|
1076
|
+
'and `migration list` for on-disk enumeration.',
|
|
1063
1077
|
);
|
|
1064
1078
|
setCommandExamples(command, [
|
|
1065
|
-
'prisma-next migration status',
|
|
1066
1079
|
'prisma-next migration status --db $DATABASE_URL',
|
|
1080
|
+
'prisma-next migration status --to production --db $DATABASE_URL',
|
|
1081
|
+
]);
|
|
1082
|
+
setCommandSeeAlso(command, [
|
|
1083
|
+
{ verb: 'migration log', oneLiner: 'Show executed migration history' },
|
|
1084
|
+
{ verb: 'migration list', oneLiner: 'List on-disk migrations' },
|
|
1085
|
+
{ verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
|
|
1086
|
+
{ verb: 'migration show', oneLiner: 'Display migration package contents' },
|
|
1067
1087
|
]);
|
|
1068
1088
|
addGlobalOptions(command)
|
|
1069
1089
|
.option('--db <url>', 'Database connection string')
|
|
1070
1090
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
1071
|
-
.option(
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1091
|
+
.option(
|
|
1092
|
+
'--to <contract>',
|
|
1093
|
+
'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
|
|
1094
|
+
)
|
|
1095
|
+
.option(
|
|
1096
|
+
'--from <contract>',
|
|
1097
|
+
'Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.',
|
|
1098
|
+
)
|
|
1075
1099
|
.action(async (options: MigrationStatusOptions) => {
|
|
1076
1100
|
const flags = parseGlobalFlags(options);
|
|
1077
|
-
|
|
1078
1101
|
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
1079
1102
|
|
|
1080
1103
|
const result = await executeMigrationStatusCommand(options, flags, ui);
|
|
1081
1104
|
|
|
1082
1105
|
const exitCode = handleResult(result, flags, ui, (statusResult) => {
|
|
1083
1106
|
if (flags.json) {
|
|
1084
|
-
// Strip non-JSON-shape fields before emitting. These belong to
|
|
1085
|
-
// the in-memory result so the human renderer can avoid
|
|
1086
|
-
// recomputing them, but they would either bloat the wire format
|
|
1087
|
-
// (graph, bundles, edgeStatuses) or expose internals
|
|
1088
|
-
// (activeRefHash, activeRefName, diverged) that consumers should
|
|
1089
|
-
// read off `pathDecision` / `refs` instead.
|
|
1090
1107
|
const {
|
|
1091
1108
|
graph: _graph,
|
|
1092
1109
|
bundles: _bundles,
|
|
@@ -1101,7 +1118,6 @@ export function createMigrationStatusCommand(): Command {
|
|
|
1101
1118
|
const colorize = flags.color !== false;
|
|
1102
1119
|
|
|
1103
1120
|
if (statusResult.graph) {
|
|
1104
|
-
const limit = determineLimit(options);
|
|
1105
1121
|
const renderInput = migrationGraphToRenderInput({
|
|
1106
1122
|
graph: statusResult.graph,
|
|
1107
1123
|
mode: statusResult.mode,
|
|
@@ -1113,16 +1129,13 @@ export function createMigrationStatusCommand(): Command {
|
|
|
1113
1129
|
edgeStatuses: statusResult.edgeStatuses,
|
|
1114
1130
|
});
|
|
1115
1131
|
|
|
1116
|
-
const graphToRender =
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const dagreOptions =
|
|
1121
|
-
!options.graph && isLinearGraph(graphToRender) ? { ranksep: 1 } : undefined;
|
|
1132
|
+
const graphToRender = statusResult.diverged
|
|
1133
|
+
? renderInput.graph
|
|
1134
|
+
: extractRelevantSubgraph(renderInput.graph, renderInput.relevantPaths);
|
|
1135
|
+
const dagreOptions = isLinearGraph(graphToRender) ? { ranksep: 1 } : undefined;
|
|
1122
1136
|
const renderOptions = {
|
|
1123
1137
|
...renderInput.options,
|
|
1124
1138
|
colorize,
|
|
1125
|
-
...ifDefined('limit', limit),
|
|
1126
1139
|
...ifDefined('dagreOptions', dagreOptions),
|
|
1127
1140
|
};
|
|
1128
1141
|
const graphOutput = graphRenderer.render(graphToRender, renderOptions);
|
|
@@ -1209,7 +1222,7 @@ export function formatStatusSummary(result: MigrationStatusResult, colorize: boo
|
|
|
1209
1222
|
if (total > 0) {
|
|
1210
1223
|
lines.push('');
|
|
1211
1224
|
lines.push(
|
|
1212
|
-
`${c(yellow, '⧗')} ${total} pending migration(s) across ${result.spaces.length} space(s) — run 'prisma-next
|
|
1225
|
+
`${c(yellow, '⧗')} ${total} pending migration(s) across ${result.spaces.length} space(s) — run 'prisma-next migrate' to apply`,
|
|
1213
1226
|
);
|
|
1214
1227
|
}
|
|
1215
1228
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
2
|
+
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
2
3
|
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
3
4
|
import {
|
|
4
5
|
deleteRef,
|
|
5
|
-
readRef,
|
|
6
6
|
readRefs,
|
|
7
7
|
validateRefName,
|
|
8
8
|
validateRefValue,
|
|
@@ -16,9 +16,11 @@ import {
|
|
|
16
16
|
errorRuntime,
|
|
17
17
|
errorUnexpected,
|
|
18
18
|
mapMigrationToolsError,
|
|
19
|
+
mapRefResolutionError,
|
|
19
20
|
} from '../utils/cli-errors';
|
|
20
21
|
import {
|
|
21
22
|
addGlobalOptions,
|
|
23
|
+
loadMigrationPackages,
|
|
22
24
|
resolveMigrationPaths,
|
|
23
25
|
setCommandDescriptions,
|
|
24
26
|
} from '../utils/command-helpers';
|
|
@@ -34,13 +36,6 @@ interface RefSetResult {
|
|
|
34
36
|
readonly invariants: readonly string[];
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
interface RefGetResult {
|
|
38
|
-
readonly ok: true;
|
|
39
|
-
readonly ref: string;
|
|
40
|
-
readonly hash: string;
|
|
41
|
-
readonly invariants: readonly string[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
39
|
interface RefDeleteResult {
|
|
45
40
|
readonly ok: true;
|
|
46
41
|
readonly ref: string;
|
|
@@ -66,53 +61,42 @@ function cliErrorInvalidRefName(name: string): CliStructuredError {
|
|
|
66
61
|
});
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
function
|
|
70
|
-
return errorRuntime(`Invalid contract hash "${hash}"`, {
|
|
71
|
-
why: `"${hash}" is not a valid contract hash`,
|
|
72
|
-
fix: 'Contract hashes must match the format "sha256:<64 hex chars>". Copy the hash from `prisma-next contract emit` or `migration status --json`.',
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function executeRefSetCommand(
|
|
64
|
+
export async function executeRefSetCommand(
|
|
77
65
|
name: string,
|
|
78
|
-
|
|
66
|
+
contractInput: string,
|
|
79
67
|
options: { config?: string },
|
|
80
68
|
): Promise<Result<RefSetResult, CliStructuredError>> {
|
|
81
69
|
if (!validateRefName(name)) {
|
|
82
70
|
return notOk(cliErrorInvalidRefName(name));
|
|
83
71
|
}
|
|
84
|
-
if (!validateRefValue(hash)) {
|
|
85
|
-
return notOk(cliErrorInvalidRefValue(hash));
|
|
86
|
-
}
|
|
87
72
|
|
|
88
73
|
try {
|
|
89
74
|
const config = await loadConfig(options.config);
|
|
90
|
-
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
91
|
-
|
|
75
|
+
const { appMigrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
|
|
76
|
+
|
|
77
|
+
let resolvedHash: string;
|
|
78
|
+
if (validateRefValue(contractInput)) {
|
|
79
|
+
resolvedHash = contractInput;
|
|
80
|
+
} else {
|
|
81
|
+
const { graph } = await loadMigrationPackages(appMigrationsDir);
|
|
82
|
+
const refs = await readRefs(refsDir);
|
|
83
|
+
const refResult = parseContractRef(contractInput, { graph, refs });
|
|
84
|
+
if (!refResult.ok) {
|
|
85
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
86
|
+
}
|
|
87
|
+
resolvedHash = refResult.value.hash;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const entry: RefEntry = { hash: resolvedHash, invariants: [] };
|
|
92
91
|
await writeRef(refsDir, name, entry);
|
|
93
|
-
return ok({ ok: true as const, ref: name, hash, invariants: [] });
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (error instanceof CliStructuredError) return notOk(error);
|
|
96
|
-
return notOk(mapError(error));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async function executeRefGetCommand(
|
|
101
|
-
name: string,
|
|
102
|
-
options: { config?: string },
|
|
103
|
-
): Promise<Result<RefGetResult, CliStructuredError>> {
|
|
104
|
-
try {
|
|
105
|
-
const config = await loadConfig(options.config);
|
|
106
|
-
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
107
|
-
const entry = await readRef(refsDir, name);
|
|
108
|
-
return ok({ ok: true as const, ref: name, hash: entry.hash, invariants: entry.invariants });
|
|
92
|
+
return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });
|
|
109
93
|
} catch (error) {
|
|
110
94
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
111
95
|
return notOk(mapError(error));
|
|
112
96
|
}
|
|
113
97
|
}
|
|
114
98
|
|
|
115
|
-
async function executeRefDeleteCommand(
|
|
99
|
+
export async function executeRefDeleteCommand(
|
|
116
100
|
name: string,
|
|
117
101
|
options: { config?: string },
|
|
118
102
|
): Promise<Result<RefDeleteResult, CliStructuredError>> {
|
|
@@ -127,7 +111,7 @@ async function executeRefDeleteCommand(
|
|
|
127
111
|
}
|
|
128
112
|
}
|
|
129
113
|
|
|
130
|
-
async function executeRefListCommand(options: {
|
|
114
|
+
export async function executeRefListCommand(options: {
|
|
131
115
|
config?: string;
|
|
132
116
|
}): Promise<Result<RefListResult, CliStructuredError>> {
|
|
133
117
|
try {
|
|
@@ -145,12 +129,15 @@ function createRefSetCommand(): Command {
|
|
|
145
129
|
const command = new Command('set');
|
|
146
130
|
setCommandDescriptions(
|
|
147
131
|
command,
|
|
148
|
-
'Set a ref to a contract
|
|
149
|
-
'Sets a named ref to point to a contract hash in migrations/refs/.',
|
|
132
|
+
'Set a ref to a contract reference',
|
|
133
|
+
'Sets a named ref to point to a resolved contract reference (hash, alias, or path) in migrations/refs/.',
|
|
150
134
|
);
|
|
151
135
|
addGlobalOptions(command)
|
|
152
136
|
.argument('<name>', 'Ref name (e.g., staging, production)')
|
|
153
|
-
.argument(
|
|
137
|
+
.argument(
|
|
138
|
+
'<contract>',
|
|
139
|
+
'Contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
|
|
140
|
+
)
|
|
154
141
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
155
142
|
.action(
|
|
156
143
|
async (
|
|
@@ -174,37 +161,6 @@ function createRefSetCommand(): Command {
|
|
|
174
161
|
return command;
|
|
175
162
|
}
|
|
176
163
|
|
|
177
|
-
function createRefGetCommand(): Command {
|
|
178
|
-
const command = new Command('get');
|
|
179
|
-
setCommandDescriptions(
|
|
180
|
-
command,
|
|
181
|
-
'Get the hash for a ref',
|
|
182
|
-
'Reads a named ref from migrations/refs/ and prints its contract hash.',
|
|
183
|
-
);
|
|
184
|
-
addGlobalOptions(command)
|
|
185
|
-
.argument('<name>', 'Ref name to look up')
|
|
186
|
-
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
187
|
-
.action(
|
|
188
|
-
async (
|
|
189
|
-
name: string,
|
|
190
|
-
options: { config?: string; json?: string | boolean; quiet?: boolean },
|
|
191
|
-
) => {
|
|
192
|
-
const flags = parseGlobalFlags(options);
|
|
193
|
-
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
194
|
-
const result = await executeRefGetCommand(name, options);
|
|
195
|
-
const exitCode = handleResult(result, flags, ui, (value) => {
|
|
196
|
-
if (flags.json) {
|
|
197
|
-
ui.output(JSON.stringify(value));
|
|
198
|
-
} else {
|
|
199
|
-
ui.output(value.hash);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
process.exit(exitCode);
|
|
203
|
-
},
|
|
204
|
-
);
|
|
205
|
-
return command;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
164
|
function createRefDeleteCommand(): Command {
|
|
209
165
|
const command = new Command('delete');
|
|
210
166
|
setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');
|
|
@@ -262,20 +218,11 @@ function createRefListCommand(): Command {
|
|
|
262
218
|
return command;
|
|
263
219
|
}
|
|
264
220
|
|
|
265
|
-
export {
|
|
266
|
-
cliErrorInvalidRefName,
|
|
267
|
-
cliErrorInvalidRefValue,
|
|
268
|
-
executeRefDeleteCommand,
|
|
269
|
-
executeRefGetCommand,
|
|
270
|
-
executeRefListCommand,
|
|
271
|
-
executeRefSetCommand,
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
export function createMigrationRefCommand(): Command {
|
|
221
|
+
export function createRefCommand(): Command {
|
|
275
222
|
const command = new Command('ref');
|
|
276
223
|
setCommandDescriptions(
|
|
277
224
|
command,
|
|
278
|
-
'Manage
|
|
225
|
+
'Manage contract refs',
|
|
279
226
|
'Manage named refs in migrations/refs/. Refs map logical environment\n' +
|
|
280
227
|
'names (e.g., staging, production) to contract hashes.',
|
|
281
228
|
);
|
|
@@ -284,7 +231,6 @@ export function createMigrationRefCommand(): Command {
|
|
|
284
231
|
subcommandDescription: () => '',
|
|
285
232
|
});
|
|
286
233
|
command.addCommand(createRefSetCommand());
|
|
287
|
-
command.addCommand(createRefGetCommand());
|
|
288
234
|
command.addCommand(createRefDeleteCommand());
|
|
289
235
|
command.addCommand(createRefListCommand());
|
|
290
236
|
return command;
|
|
@@ -201,7 +201,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
201
201
|
// Validate contract using family instance
|
|
202
202
|
let contract: Contract;
|
|
203
203
|
try {
|
|
204
|
-
contract = familyInstance.
|
|
204
|
+
contract = familyInstance.deserializeContract(options.contract);
|
|
205
205
|
} catch (error) {
|
|
206
206
|
const message = error instanceof Error ? error.message : String(error);
|
|
207
207
|
throw new ContractValidationError(message, error);
|
|
@@ -254,7 +254,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
254
254
|
// Validate contract using family instance
|
|
255
255
|
let contract: Contract;
|
|
256
256
|
try {
|
|
257
|
-
contract = familyInstance.
|
|
257
|
+
contract = familyInstance.deserializeContract(options.contract);
|
|
258
258
|
} catch (error) {
|
|
259
259
|
const message = error instanceof Error ? error.message : String(error);
|
|
260
260
|
throw new ContractValidationError(message, error);
|
|
@@ -269,12 +269,15 @@ class ControlClientImpl implements ControlClient {
|
|
|
269
269
|
});
|
|
270
270
|
|
|
271
271
|
try {
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
// Introspect the live schema, then verify the contract against
|
|
273
|
+
// it. Composing the two primitives here keeps the family
|
|
274
|
+
// interface a single synchronous verifier and gives callers
|
|
275
|
+
// (and tests) explicit control over the introspected schema.
|
|
276
|
+
const schema = await familyInstance.introspect({ driver, contract });
|
|
277
|
+
const result = familyInstance.verifySchema({
|
|
275
278
|
contract,
|
|
279
|
+
schema,
|
|
276
280
|
strict: options.strict ?? false,
|
|
277
|
-
contractPath: '',
|
|
278
281
|
frameworkComponents,
|
|
279
282
|
});
|
|
280
283
|
|
|
@@ -305,7 +308,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
305
308
|
// Validate contract using family instance
|
|
306
309
|
let contract: Contract;
|
|
307
310
|
try {
|
|
308
|
-
contract = familyInstance.
|
|
311
|
+
contract = familyInstance.deserializeContract(options.contract);
|
|
309
312
|
} catch (error) {
|
|
310
313
|
const message = error instanceof Error ? error.message : String(error);
|
|
311
314
|
throw new ContractValidationError(message, error);
|
|
@@ -358,7 +361,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
358
361
|
|
|
359
362
|
let contract: Contract;
|
|
360
363
|
try {
|
|
361
|
-
contract = familyInstance.
|
|
364
|
+
contract = familyInstance.deserializeContract(options.contract);
|
|
362
365
|
} catch (error) {
|
|
363
366
|
const message = error instanceof Error ? error.message : String(error);
|
|
364
367
|
throw new ContractValidationError(message, error);
|
|
@@ -389,7 +392,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
389
392
|
|
|
390
393
|
let contract: Contract;
|
|
391
394
|
try {
|
|
392
|
-
contract = familyInstance.
|
|
395
|
+
contract = familyInstance.deserializeContract(options.contract);
|
|
393
396
|
} catch (error) {
|
|
394
397
|
const message = error instanceof Error ? error.message : String(error);
|
|
395
398
|
throw new ContractValidationError(message, error);
|
|
@@ -415,18 +418,10 @@ class ControlClientImpl implements ControlClient {
|
|
|
415
418
|
await this.connectWithProgress(options.connection, 'dbVerify', onProgress);
|
|
416
419
|
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
417
420
|
|
|
418
|
-
let contract: Contract;
|
|
419
|
-
try {
|
|
420
|
-
contract = familyInstance.validateContract(options.contract);
|
|
421
|
-
} catch (error) {
|
|
422
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
423
|
-
throw new ContractValidationError(message, error);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
421
|
return executeDbVerify({
|
|
427
422
|
driver,
|
|
428
423
|
familyInstance,
|
|
429
|
-
contract,
|
|
424
|
+
contract: options.contract,
|
|
430
425
|
migrationsDir: options.migrationsDir,
|
|
431
426
|
targetId: this.options.target.targetId,
|
|
432
427
|
extensionPacks: this.options.extensionPacks ?? [],
|
|
@@ -463,7 +458,7 @@ class ControlClientImpl implements ControlClient {
|
|
|
463
458
|
|
|
464
459
|
let contract: Contract;
|
|
465
460
|
try {
|
|
466
|
-
contract = familyInstance.
|
|
461
|
+
contract = familyInstance.deserializeContract(options.contract);
|
|
467
462
|
} catch (error) {
|
|
468
463
|
const message = error instanceof Error ? error.message : String(error);
|
|
469
464
|
throw new ContractValidationError(message, error);
|
|
@@ -638,10 +633,20 @@ class ControlClientImpl implements ControlClient {
|
|
|
638
633
|
});
|
|
639
634
|
|
|
640
635
|
try {
|
|
641
|
-
|
|
636
|
+
// Blind cast: `contractRaw` is the unverified provider
|
|
637
|
+
// payload — `enrichContract` only adds capability + extension
|
|
638
|
+
// metadata onto whatever shape it receives. The structural
|
|
639
|
+
// check happens immediately afterwards via
|
|
640
|
+
// `familyInstance.deserializeContract(enrichedIR)`, which is
|
|
641
|
+
// the seam-of-record and the only thing that may surface
|
|
642
|
+
// structural errors to the caller.
|
|
643
|
+
const enrichedIR = enrichContract(
|
|
644
|
+
contractRaw as unknown as Contract,
|
|
645
|
+
this.frameworkComponents ?? [],
|
|
646
|
+
);
|
|
642
647
|
|
|
643
648
|
try {
|
|
644
|
-
this.familyInstance.
|
|
649
|
+
this.familyInstance.deserializeContract(enrichedIR);
|
|
645
650
|
} catch (error) {
|
|
646
651
|
onProgress?.({
|
|
647
652
|
action: 'emit',
|
|
@@ -662,6 +667,10 @@ class ControlClientImpl implements ControlClient {
|
|
|
662
667
|
enrichedIR,
|
|
663
668
|
this.stack!,
|
|
664
669
|
this.options.family.emission,
|
|
670
|
+
{
|
|
671
|
+
serializeContract: (contract) =>
|
|
672
|
+
this.options.target.contractSerializer.serializeContract(contract),
|
|
673
|
+
},
|
|
665
674
|
);
|
|
666
675
|
|
|
667
676
|
onProgress?.({
|