@prisma-next/cli 0.11.0-dev.5 → 0.11.0-dev.51
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-errors-DFF1LlfU.mjs +215 -0
- package/dist/cli-errors-DFF1LlfU.mjs.map +1 -0
- package/dist/cli.mjs +9 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-oXO2WCPD.mjs → client-5uvDppD8.mjs} +23 -21
- package/dist/client-5uvDppD8.mjs.map +1 -0
- package/dist/{command-helpers-DtavI0wJ.mjs → command-helpers-4UNsRRc4.mjs} +427 -9
- package/dist/command-helpers-4UNsRRc4.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +33 -7
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +3 -4
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +6 -7
- 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 +36 -8
- 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 +5 -1
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +79 -39
- 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 -280
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +3 -4
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +63 -12
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +2 -103
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +3 -4
- package/dist/commands/migration-log.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +33 -38
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +2 -1
- 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 +4 -55
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +62 -153
- 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 +93 -67
- package/dist/commands/migration-status.mjs.map +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.d.mts.map +1 -1
- package/dist/commands/ref.mjs +34 -9
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/{contract-emit-o-8VmdQX.mjs → contract-emit-C-CFGZsI.mjs} +9 -6
- package/dist/{contract-emit-o-8VmdQX.mjs.map → contract-emit-C-CFGZsI.mjs.map} +1 -1
- package/dist/{contract-emit-CmsklifJ.mjs → contract-emit-CuUzzM46.mjs} +5 -6
- package/dist/{contract-emit-CmsklifJ.mjs.map → contract-emit-CuUzzM46.mjs.map} +1 -1
- package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-XmUPhmsS.mjs} +4 -25
- package/dist/contract-enrichment-XmUPhmsS.mjs.map +1 -0
- package/dist/{contract-infer-pKkiCt7C.mjs → contract-infer-C98ZaRhp.mjs} +3 -4
- package/dist/{contract-infer-pKkiCt7C.mjs.map → contract-infer-C98ZaRhp.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-AoIUriL4.mjs → db-verify-BWl1Yxi-.mjs} +6 -7
- package/dist/{db-verify-AoIUriL4.mjs.map → db-verify-BWl1Yxi-.mjs.map} +1 -1
- 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 +3 -3
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts.map +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/{framework-components-65gOHkHB.mjs → framework-components-DTcjouhS.mjs} +2 -2
- package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-DTcjouhS.mjs.map} +1 -1
- package/dist/{global-flags-CdE7M0d9.d.mts → global-flags-DWsQ6SSI.d.mts} +1 -1
- package/dist/global-flags-DWsQ6SSI.d.mts.map +1 -0
- package/dist/glyph-mode-CBB4emzO.d.mts +5 -0
- package/dist/glyph-mode-CBB4emzO.d.mts.map +1 -0
- 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-Db5Itt5r.mjs → init-C7PvN163.mjs} +5 -5
- package/dist/{init-Db5Itt5r.mjs.map → init-C7PvN163.mjs.map} +1 -1
- package/dist/{inspect-live-schema-LeWvkZVz.mjs → inspect-live-schema-BRCWQ-Sr.mjs} +5 -5
- package/dist/{inspect-live-schema-LeWvkZVz.mjs.map → inspect-live-schema-BRCWQ-Sr.mjs.map} +1 -1
- package/dist/migration-check-DoskM1nB.mjs +341 -0
- package/dist/migration-check-DoskM1nB.mjs.map +1 -0
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +4 -4
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-BtkunvFQ.mjs → migration-command-scaffold-CXLkoIJx.mjs} +5 -5
- package/dist/{migration-command-scaffold-BtkunvFQ.mjs.map → migration-command-scaffold-CXLkoIJx.mjs.map} +1 -1
- package/dist/migration-list-B2-iQ5Jd.mjs +646 -0
- package/dist/migration-list-B2-iQ5Jd.mjs.map +1 -0
- package/dist/{migration-plan-C2jeH1J5.mjs → migration-plan-BqmIKQpZ.mjs} +341 -88
- package/dist/migration-plan-BqmIKQpZ.mjs.map +1 -0
- 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-CwZMa1Ck.mjs → migrations-BcVTutso.mjs} +12 -13
- package/dist/migrations-BcVTutso.mjs.map +1 -0
- package/dist/{output-BlsrGMEF.mjs → output-B60Gw5fu.mjs} +1 -1
- package/dist/{output-BlsrGMEF.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
- package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-xASh41wr.mjs} +1 -1
- package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-xASh41wr.mjs.map} +1 -1
- package/dist/ref-advancement-DRh5Nquq.mjs +50 -0
- package/dist/ref-advancement-DRh5Nquq.mjs.map +1 -0
- package/dist/{types-C9FfXb1l.d.mts → types-CEtm6v6a.d.mts} +5 -11
- package/dist/types-CEtm6v6a.d.mts.map +1 -0
- package/dist/{verify-Bom75OYI.mjs → verify-DOHbbrub.mjs} +2 -2
- package/dist/{verify-Bom75OYI.mjs.map → verify-DOHbbrub.mjs.map} +1 -1
- package/package.json +20 -20
- package/src/commands/db-init.ts +48 -2
- package/src/commands/db-update.ts +45 -0
- package/src/commands/migrate.ts +120 -40
- package/src/commands/migration-check.ts +43 -83
- package/src/commands/migration-list.ts +173 -74
- package/src/commands/migration-new.ts +44 -48
- package/src/commands/migration-plan.ts +359 -128
- package/src/commands/migration-show.ts +65 -284
- package/src/commands/migration-status.ts +131 -99
- package/src/commands/ref.ts +46 -6
- package/src/control-api/client.ts +0 -1
- package/src/control-api/contract-enrichment.ts +6 -42
- package/src/control-api/operations/contract-emit.ts +7 -2
- 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/migration-cli.ts +4 -4
- package/src/utils/cli-errors.ts +224 -0
- package/src/utils/command-helpers.ts +9 -4
- package/src/utils/contract-space-aggregate-loader.ts +221 -117
- package/src/utils/formatters/migration-list-data-column.ts +115 -0
- package/src/utils/formatters/migration-list-graph-layout.ts +268 -0
- package/src/utils/formatters/migration-list-graph-render.ts +314 -0
- package/src/utils/formatters/migration-list-render.ts +194 -0
- package/src/utils/formatters/migration-list-styler.ts +61 -0
- package/src/utils/formatters/migrations.ts +29 -38
- package/src/utils/glyph-mode.ts +22 -0
- package/src/utils/integrity-violation-to-check-failure.ts +130 -0
- package/src/utils/plan-resolution.ts +257 -0
- package/src/utils/ref-advancement.ts +68 -0
- package/src/utils/terminal-ui.ts +42 -1
- package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
- package/dist/cli-errors-Djtz98Vm.mjs +0 -71
- package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
- package/dist/client-oXO2WCPD.mjs.map +0 -1
- package/dist/command-helpers-DtavI0wJ.mjs.map +0 -1
- package/dist/commands/migration-check.mjs.map +0 -1
- package/dist/commands/migration-list.mjs.map +0 -1
- package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
- package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
- package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
- package/dist/migration-plan-C2jeH1J5.mjs.map +0 -1
- package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
- package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
- package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
- package/dist/types-C9FfXb1l.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
|
}
|
package/src/commands/ref.ts
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
1
3
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
4
|
+
import { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';
|
|
2
5
|
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
3
6
|
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
4
7
|
import {
|
|
5
|
-
|
|
8
|
+
deleteRefPaired,
|
|
6
9
|
readRefs,
|
|
7
10
|
validateRefName,
|
|
8
11
|
validateRefValue,
|
|
9
|
-
|
|
12
|
+
writeRefPaired,
|
|
10
13
|
} from '@prisma-next/migration-tools/refs';
|
|
11
14
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
12
15
|
import { Command } from 'commander';
|
|
16
|
+
import { join } from 'pathe';
|
|
13
17
|
import { loadConfig } from '../config-loader';
|
|
14
18
|
import {
|
|
15
19
|
CliStructuredError,
|
|
20
|
+
errorFileNotFound,
|
|
21
|
+
errorRefSetBundleNotFound,
|
|
22
|
+
errorRefSetEmptySentinel,
|
|
23
|
+
errorRefSetHashNotInGraph,
|
|
16
24
|
errorRuntime,
|
|
17
25
|
errorUnexpected,
|
|
18
26
|
mapMigrationToolsError,
|
|
@@ -26,6 +34,7 @@ import {
|
|
|
26
34
|
} from '../utils/command-helpers';
|
|
27
35
|
import { formatCommandHelp } from '../utils/formatters/help';
|
|
28
36
|
import { parseGlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
37
|
+
import { readContractIR } from '../utils/ref-advancement';
|
|
29
38
|
import { handleResult } from '../utils/result-handler';
|
|
30
39
|
import { createTerminalUI } from '../utils/terminal-ui';
|
|
31
40
|
|
|
@@ -73,13 +82,13 @@ export async function executeRefSetCommand(
|
|
|
73
82
|
try {
|
|
74
83
|
const config = await loadConfig(options.config);
|
|
75
84
|
const { appMigrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
|
|
85
|
+
const { graph, bundles } = await loadMigrationPackages(appMigrationsDir);
|
|
86
|
+
const refs = await readRefs(refsDir);
|
|
76
87
|
|
|
77
88
|
let resolvedHash: string;
|
|
78
89
|
if (validateRefValue(contractInput)) {
|
|
79
90
|
resolvedHash = contractInput;
|
|
80
91
|
} else {
|
|
81
|
-
const { graph } = await loadMigrationPackages(appMigrationsDir);
|
|
82
|
-
const refs = await readRefs(refsDir);
|
|
83
92
|
const refResult = parseContractRef(contractInput, { graph, refs });
|
|
84
93
|
if (!refResult.ok) {
|
|
85
94
|
return notOk(mapRefResolutionError(refResult.failure));
|
|
@@ -87,8 +96,39 @@ export async function executeRefSetCommand(
|
|
|
87
96
|
resolvedHash = refResult.value.hash;
|
|
88
97
|
}
|
|
89
98
|
|
|
99
|
+
if (resolvedHash === EMPTY_CONTRACT_HASH) {
|
|
100
|
+
return notOk(errorRefSetEmptySentinel(resolvedHash));
|
|
101
|
+
}
|
|
102
|
+
if (!isGraphNode(resolvedHash, graph)) {
|
|
103
|
+
const graphTip = findLatestMigration(graph)?.to ?? null;
|
|
104
|
+
return notOk(errorRefSetHashNotInGraph(resolvedHash, [...graph.nodes].sort(), graphTip));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const matchingBundle = bundles.find((bundle) => bundle.metadata.to === resolvedHash);
|
|
108
|
+
if (!matchingBundle) {
|
|
109
|
+
return notOk(errorRefSetBundleNotFound(resolvedHash));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const contractJsonPath = join(matchingBundle.dirPath, 'end-contract.json');
|
|
113
|
+
let contractJson: Record<string, unknown>;
|
|
114
|
+
try {
|
|
115
|
+
const raw = await readFile(contractJsonPath, 'utf-8');
|
|
116
|
+
contractJson = JSON.parse(raw) as Record<string, unknown>;
|
|
117
|
+
} catch (readError) {
|
|
118
|
+
if (readError instanceof Error && (readError as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
119
|
+
return notOk(
|
|
120
|
+
errorFileNotFound(contractJsonPath, {
|
|
121
|
+
why: `Migration bundle for hash ${resolvedHash} is missing its end-contract snapshot at ${contractJsonPath}`,
|
|
122
|
+
fix: 'Run `pnpm fixtures:check`, or re-emit the migration so its end-contract.json is restored.',
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
throw readError;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const contractIR = await readContractIR(contractJson, contractJsonPath);
|
|
90
130
|
const entry: RefEntry = { hash: resolvedHash, invariants: [] };
|
|
91
|
-
await
|
|
131
|
+
await writeRefPaired(refsDir, name, entry, contractIR);
|
|
92
132
|
return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });
|
|
93
133
|
} catch (error) {
|
|
94
134
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -103,7 +143,7 @@ export async function executeRefDeleteCommand(
|
|
|
103
143
|
try {
|
|
104
144
|
const config = await loadConfig(options.config);
|
|
105
145
|
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
106
|
-
await
|
|
146
|
+
await deleteRefPaired(refsDir, name);
|
|
107
147
|
return ok({ ok: true as const, ref: name, deleted: true as const });
|
|
108
148
|
} catch (error) {
|
|
109
149
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -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),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
type
|
|
2
|
+
import {
|
|
3
|
+
mergeCapabilityMatrices,
|
|
4
|
+
type TargetBoundComponentDescriptor,
|
|
5
|
+
} from '@prisma-next/framework-components/components';
|
|
5
6
|
|
|
6
7
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
7
8
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -26,37 +27,6 @@ function sortDeepTyped<T>(value: T): T {
|
|
|
26
27
|
return sortDeep(value) as T;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
function extractCapabilityMatrix(value: unknown): CapabilityMatrix {
|
|
30
|
-
if (!isPlainObject(value)) return {};
|
|
31
|
-
|
|
32
|
-
const out: CapabilityMatrix = {};
|
|
33
|
-
for (const [namespace, maybeCaps] of Object.entries(value)) {
|
|
34
|
-
if (!isPlainObject(maybeCaps)) continue;
|
|
35
|
-
const caps: Record<string, boolean> = {};
|
|
36
|
-
for (const [key, flag] of Object.entries(maybeCaps)) {
|
|
37
|
-
if (typeof flag === 'boolean') {
|
|
38
|
-
caps[key] = flag;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (Object.keys(caps).length > 0) {
|
|
42
|
-
out[namespace] = caps;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function mergeCapabilities(left: CapabilityMatrix, right: CapabilityMatrix): CapabilityMatrix {
|
|
50
|
-
const next: CapabilityMatrix = { ...left };
|
|
51
|
-
for (const [namespace, capabilities] of Object.entries(right)) {
|
|
52
|
-
next[namespace] = {
|
|
53
|
-
...(left[namespace] ?? {}),
|
|
54
|
-
...capabilities,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return next;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
30
|
function extractExtensionPackMeta(
|
|
61
31
|
component: TargetBoundComponentDescriptor<string, string>,
|
|
62
32
|
): Record<string, unknown> {
|
|
@@ -93,16 +63,10 @@ export function enrichContract(
|
|
|
93
63
|
ir: Contract,
|
|
94
64
|
components: ReadonlyArray<TargetBoundComponentDescriptor<string, string>>,
|
|
95
65
|
): Contract {
|
|
96
|
-
|
|
97
|
-
const extensionPacksMeta: Record<string, unknown> = {};
|
|
66
|
+
const mergedCapabilities = mergeCapabilityMatrices(ir.capabilities, components);
|
|
98
67
|
|
|
68
|
+
const extensionPacksMeta: Record<string, unknown> = {};
|
|
99
69
|
for (const component of components) {
|
|
100
|
-
if (component.capabilities) {
|
|
101
|
-
mergedCapabilities = mergeCapabilities(
|
|
102
|
-
mergedCapabilities,
|
|
103
|
-
extractCapabilityMatrix(component.capabilities),
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
70
|
if (component.kind === 'extension') {
|
|
107
71
|
extensionPacksMeta[component.id] = extractExtensionPackMeta(component);
|
|
108
72
|
}
|
|
@@ -253,13 +253,18 @@ export async function executeContractEmit(
|
|
|
253
253
|
// on-disk JSON envelope is constructed by target-owned code
|
|
254
254
|
// rather than by walking the in-memory contract with
|
|
255
255
|
// `Object.entries` (which would leak runtime-only class API
|
|
256
|
-
// fields into the persisted shape).
|
|
256
|
+
// fields into the persisted shape). The optional `shouldPreserveEmpty`
|
|
257
|
+
// and `sortStorage` hooks let the family contribute storage-specific
|
|
258
|
+
// canonicalization rules without the framework importing family code.
|
|
259
|
+
const { contractSerializer } = config.target;
|
|
257
260
|
const serializeContract = (c: Contract): JsonObject =>
|
|
258
|
-
|
|
261
|
+
contractSerializer.serializeContract(c);
|
|
259
262
|
emitResult = await unlessAborted(
|
|
260
263
|
emit(enrichedIR, stack, config.family.emission, {
|
|
261
264
|
outputJsonPath,
|
|
262
265
|
serializeContract,
|
|
266
|
+
...ifDefined('shouldPreserveEmpty', contractSerializer.shouldPreserveEmpty),
|
|
267
|
+
...ifDefined('sortStorage', contractSerializer.sortStorage),
|
|
263
268
|
}),
|
|
264
269
|
);
|
|
265
270
|
} catch (error) {
|
|
@@ -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: {
|