@prisma-next/cli 0.11.0-dev.6 → 0.11.0-dev.61
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 +13 -9
- package/dist/cli.mjs +9 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-oXO2WCPD.mjs → client-CD3om3R0.mjs} +44 -24
- package/dist/client-CD3om3R0.mjs.map +1 -0
- package/dist/{command-helpers-DtavI0wJ.mjs → command-helpers-CoceqqMl.mjs} +642 -45
- package/dist/command-helpers-CoceqqMl.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 +32 -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 +12 -10
- 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 +41 -11
- 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 +75 -40
- 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 +11 -2
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +15 -30
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +66 -4
- 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 +10 -1
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +10 -15
- 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 +32 -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 +61 -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 +81 -76
- 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 +38 -10
- 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-at-errors-Bhf2jnkp.mjs +42 -0
- package/dist/contract-at-errors-Bhf2jnkp.mjs.map +1 -0
- package/dist/{contract-emit-CmsklifJ.mjs → contract-emit-C47r1loe.mjs} +4 -6
- package/dist/{contract-emit-CmsklifJ.mjs.map → contract-emit-C47r1loe.mjs.map} +1 -1
- package/dist/{contract-emit-o-8VmdQX.mjs → contract-emit-DxEfEc-M.mjs} +21 -7
- package/dist/{contract-emit-o-8VmdQX.mjs.map → contract-emit-DxEfEc-M.mjs.map} +1 -1
- package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-a0V5Y_mL.mjs} +4 -25
- package/dist/contract-enrichment-a0V5Y_mL.mjs.map +1 -0
- package/dist/{contract-infer-pKkiCt7C.mjs → contract-infer-B5EhTqdR.mjs} +3 -4
- package/dist/{contract-infer-pKkiCt7C.mjs.map → contract-infer-B5EhTqdR.mjs.map} +1 -1
- package/dist/contract-space-aggregate-loader-lafgkTwG.mjs +247 -0
- package/dist/contract-space-aggregate-loader-lafgkTwG.mjs.map +1 -0
- package/dist/{db-verify-AoIUriL4.mjs → db-verify-B7fbkGnp.mjs} +5 -7
- package/dist/{db-verify-AoIUriL4.mjs.map → db-verify-B7fbkGnp.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-IDvjRCi3.mjs +62 -0
- package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +1 -0
- package/dist/{framework-components-65gOHkHB.mjs → framework-components-R_O3y5IW.mjs} +2 -2
- package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-R_O3y5IW.mjs.map} +1 -1
- package/dist/global-flags-2SPgqWma.d.mts +34 -0
- package/dist/global-flags-2SPgqWma.d.mts.map +1 -0
- package/dist/{graph-render-DJVv0_uf.mjs → graph-render-rFAqZujX.mjs} +2 -2
- package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
- package/dist/{init-Db5Itt5r.mjs → init-BQNpPozW.mjs} +4 -5
- package/dist/{init-Db5Itt5r.mjs.map → init-BQNpPozW.mjs.map} +1 -1
- package/dist/{inspect-live-schema-LeWvkZVz.mjs → inspect-live-schema-CQJnuPgD.mjs} +4 -5
- package/dist/{inspect-live-schema-LeWvkZVz.mjs.map → inspect-live-schema-CQJnuPgD.mjs.map} +1 -1
- package/dist/migration-check-CKfQlAWR.mjs +341 -0
- package/dist/migration-check-CKfQlAWR.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-CE931d1-.mjs} +4 -5
- package/dist/{migration-command-scaffold-BtkunvFQ.mjs.map → migration-command-scaffold-CE931d1-.mjs.map} +1 -1
- package/dist/migration-list-A3bJ4j5e.mjs +780 -0
- package/dist/migration-list-A3bJ4j5e.mjs.map +1 -0
- package/dist/{migration-plan-C2jeH1J5.mjs → migration-plan-ZZm8C0s-.mjs} +372 -133
- package/dist/migration-plan-ZZm8C0s-.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-CjO1DsYe.mjs} +12 -13
- package/dist/migrations-CjO1DsYe.mjs.map +1 -0
- package/dist/{output-BlsrGMEF.mjs → output-DEg3SSnJ.mjs} +1 -1
- package/dist/{output-BlsrGMEF.mjs.map → output-DEg3SSnJ.mjs.map} +1 -1
- package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-C644QK8l.mjs} +1 -1
- package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-C644QK8l.mjs.map} +1 -1
- package/dist/ref-advancement-DUZqsue6.mjs +50 -0
- package/dist/ref-advancement-DUZqsue6.mjs.map +1 -0
- package/dist/terminal-ui-sLZt2cxc.d.mts +133 -0
- package/dist/terminal-ui-sLZt2cxc.d.mts.map +1 -0
- package/dist/{types-C9FfXb1l.d.mts → types-DK-ge7eR.d.mts} +5 -11
- package/dist/types-DK-ge7eR.d.mts.map +1 -0
- package/dist/{verify-Bom75OYI.mjs → verify-vl983Ed-.mjs} +2 -2
- package/dist/{verify-Bom75OYI.mjs.map → verify-vl983Ed-.mjs.map} +1 -1
- package/package.json +19 -19
- package/src/commands/db-init.ts +48 -2
- package/src/commands/db-sign.ts +9 -5
- package/src/commands/db-update.ts +54 -8
- package/src/commands/migrate.ts +123 -42
- package/src/commands/migration-check.ts +43 -83
- package/src/commands/migration-graph.ts +15 -41
- package/src/commands/migration-list.ts +231 -74
- package/src/commands/migration-log.ts +8 -14
- package/src/commands/migration-new.ts +44 -48
- package/src/commands/migration-plan.ts +411 -196
- package/src/commands/migration-show.ts +65 -284
- package/src/commands/migration-status.ts +116 -110
- package/src/commands/ref.ts +53 -8
- 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 +36 -23
- package/src/control-api/types.ts +3 -10
- package/src/migration-cli.ts +4 -4
- package/src/utils/cli-errors.ts +234 -0
- package/src/utils/command-helpers.ts +1 -20
- package/src/utils/contract-at-errors.ts +96 -0
- package/src/utils/contract-space-aggregate-loader.ts +336 -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 +311 -0
- package/src/utils/formatters/migration-list-graph-topology.ts +158 -0
- package/src/utils/formatters/migration-list-render.ts +191 -0
- package/src/utils/formatters/migration-list-styler.ts +61 -0
- package/src/utils/formatters/migration-list-types.ts +21 -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 +258 -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 +0 -15
- 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/rolldown-runtime-twds-ZHy.mjs +0 -14
- 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 {
|
|
@@ -39,7 +43,6 @@ import {
|
|
|
39
43
|
import {
|
|
40
44
|
addGlobalOptions,
|
|
41
45
|
collectDeclaredInvariants,
|
|
42
|
-
loadMigrationPackages,
|
|
43
46
|
maskConnectionUrl,
|
|
44
47
|
readContractEnvelope,
|
|
45
48
|
resolveMigrationPaths,
|
|
@@ -50,9 +53,12 @@ import {
|
|
|
50
53
|
toStructuralEdge,
|
|
51
54
|
} from '../utils/command-helpers';
|
|
52
55
|
import {
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
appContractStandInFromIdentity,
|
|
57
|
+
loadContractRawSafely,
|
|
58
|
+
refuseContractSpaceIntegrity,
|
|
59
|
+
refusePackageCorruptionOnAggregate,
|
|
55
60
|
} from '../utils/contract-space-aggregate-loader';
|
|
61
|
+
import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
|
|
56
62
|
import {
|
|
57
63
|
type EdgeStatus,
|
|
58
64
|
type EdgeStatusKind,
|
|
@@ -433,46 +439,43 @@ function resolveDisplayChain(
|
|
|
433
439
|
* cross-space totals.
|
|
434
440
|
*/
|
|
435
441
|
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'];
|
|
442
|
+
readonly aggregate: ContractSpaceAggregate;
|
|
443
|
+
readonly extensionPacks: ReadonlyArray<unknown>;
|
|
441
444
|
readonly markersBySpace: ReadonlyMap<string, ContractMarkerRecordLike> | null;
|
|
442
445
|
}): 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.
|
|
446
|
+
const declaredExtensions = toDeclaredExtensionsFromRaw(args.extensionPacks);
|
|
447
|
+
if (
|
|
448
|
+
refuseContractSpaceIntegrity(args.aggregate, {
|
|
449
|
+
declaredExtensions,
|
|
450
|
+
checkContracts: true,
|
|
451
|
+
})
|
|
452
|
+
) {
|
|
453
|
+
// Full integrity refusal (drift, layout violation, etc.) — surfacing
|
|
454
|
+
// it as a status diagnostic would duplicate `migration plan`'s job.
|
|
455
455
|
// The single-space app pipeline still runs; extensions are simply
|
|
456
456
|
// not enumerated.
|
|
457
457
|
return [];
|
|
458
458
|
}
|
|
459
|
-
const aggregate =
|
|
459
|
+
const aggregate = args.aggregate;
|
|
460
460
|
|
|
461
461
|
const orderedMembers = [...aggregate.extensions, aggregate.app];
|
|
462
462
|
const rows: MigrationStatusSpaceEntry[] = [];
|
|
463
463
|
for (const member of orderedMembers) {
|
|
464
464
|
const liveMarker = args.markersBySpace?.get(member.spaceId) ?? null;
|
|
465
465
|
const isApp = member.spaceId === aggregate.app.spaceId;
|
|
466
|
+
// The aggregate passed the integrity gate above, so every member has
|
|
467
|
+
// a resolved head ref (a missing one would have refused the load).
|
|
468
|
+
const headRef = requireHeadRef(member);
|
|
466
469
|
|
|
467
|
-
if (member.
|
|
470
|
+
if (member.graph().nodes.size === 0) {
|
|
468
471
|
rows.push({
|
|
469
472
|
spaceId: member.spaceId,
|
|
470
473
|
kind: isApp ? 'app' : 'extension',
|
|
471
|
-
headHash:
|
|
474
|
+
headHash: headRef.hash,
|
|
472
475
|
...(args.markersBySpace !== null
|
|
473
476
|
? {
|
|
474
477
|
markerHash: liveMarker?.storageHash ?? null,
|
|
475
|
-
status:
|
|
478
|
+
status: headRef.hash === EMPTY_CONTRACT_HASH ? 'up-to-date' : 'never-planned',
|
|
476
479
|
pendingCount: 0,
|
|
477
480
|
}
|
|
478
481
|
: {}),
|
|
@@ -484,7 +487,7 @@ export async function loadAggregateStatusSpaces(args: {
|
|
|
484
487
|
rows.push({
|
|
485
488
|
spaceId: member.spaceId,
|
|
486
489
|
kind: isApp ? 'app' : 'extension',
|
|
487
|
-
headHash:
|
|
490
|
+
headHash: headRef.hash,
|
|
488
491
|
});
|
|
489
492
|
continue;
|
|
490
493
|
}
|
|
@@ -513,7 +516,7 @@ export async function loadAggregateStatusSpaces(args: {
|
|
|
513
516
|
rows.push({
|
|
514
517
|
spaceId: member.spaceId,
|
|
515
518
|
kind: isApp ? 'app' : 'extension',
|
|
516
|
-
headHash:
|
|
519
|
+
headHash: headRef.hash,
|
|
517
520
|
markerHash: liveMarker?.storageHash ?? null,
|
|
518
521
|
pendingCount,
|
|
519
522
|
...(status ? { status } : {}),
|
|
@@ -528,17 +531,6 @@ export async function loadAggregateStatusSpaces(args: {
|
|
|
528
531
|
* the existing `readContractEnvelope` path will report the same
|
|
529
532
|
* problem via a status diagnostic, no need to double-surface.
|
|
530
533
|
*/
|
|
531
|
-
async function loadContractRawSafely(config: {
|
|
532
|
-
contract?: { output?: string };
|
|
533
|
-
}): Promise<unknown | null> {
|
|
534
|
-
try {
|
|
535
|
-
const path = (await import('../utils/command-helpers')).resolveContractPath(config);
|
|
536
|
-
const raw = await (await import('node:fs/promises')).readFile(path, 'utf-8');
|
|
537
|
-
return JSON.parse(raw) as unknown;
|
|
538
|
-
} catch {
|
|
539
|
-
return null;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
534
|
|
|
543
535
|
async function validateOnlineMarkerRead(
|
|
544
536
|
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
@@ -580,8 +572,10 @@ async function executeMigrationStatusCommand(
|
|
|
580
572
|
ui: TerminalUI,
|
|
581
573
|
): Promise<Result<MigrationStatusResult, CliStructuredError>> {
|
|
582
574
|
const config = await loadConfig(options.config);
|
|
583
|
-
const { configPath,
|
|
584
|
-
|
|
575
|
+
const { configPath, appMigrationsRelative, migrationsDir, refsDir } = resolveMigrationPaths(
|
|
576
|
+
options.config,
|
|
577
|
+
config,
|
|
578
|
+
);
|
|
585
579
|
|
|
586
580
|
const dbConnection = options.db ?? config.db?.connection;
|
|
587
581
|
const hasDriver = !!config.driver;
|
|
@@ -599,37 +593,92 @@ async function executeMigrationStatusCommand(
|
|
|
599
593
|
throw error;
|
|
600
594
|
}
|
|
601
595
|
|
|
602
|
-
|
|
596
|
+
const diagnostics: StatusDiagnostic[] = [];
|
|
597
|
+
let contractHash: string = EMPTY_CONTRACT_HASH;
|
|
598
|
+
try {
|
|
599
|
+
const envelope = await readContractEnvelope(config);
|
|
600
|
+
contractHash = envelope.storageHash;
|
|
601
|
+
} catch (error) {
|
|
602
|
+
diagnostics.push({
|
|
603
|
+
code: 'CONTRACT.UNREADABLE',
|
|
604
|
+
severity: 'warn',
|
|
605
|
+
message: `Could not read contract: ${error instanceof Error ? error.message : 'unknown error'}`,
|
|
606
|
+
hints: ["Run 'prisma-next contract emit' to generate a valid contract"],
|
|
607
|
+
});
|
|
608
|
+
}
|
|
603
609
|
|
|
604
|
-
|
|
610
|
+
const contractRawForAggregate = await loadContractRawSafely(config);
|
|
611
|
+
const stack = createControlStack(config);
|
|
612
|
+
const familyInstance = config.family.create(stack);
|
|
613
|
+
const deserializeContract = (json: unknown): Contract => familyInstance.deserializeContract(json);
|
|
614
|
+
const appContractStandIn = appContractStandInFromIdentity({
|
|
615
|
+
contractHash,
|
|
616
|
+
targetId: config.target.id,
|
|
617
|
+
targetFamily: config.target.familyId,
|
|
618
|
+
});
|
|
619
|
+
let appContractForLoad: Contract = appContractStandIn;
|
|
620
|
+
if (contractRawForAggregate !== null) {
|
|
605
621
|
try {
|
|
606
|
-
|
|
622
|
+
appContractForLoad = deserializeContract(contractRawForAggregate);
|
|
623
|
+
} catch (error) {
|
|
624
|
+
diagnostics.push({
|
|
625
|
+
code: 'CONTRACT.UNREADABLE',
|
|
626
|
+
severity: 'warn',
|
|
627
|
+
message: `Could not deserialize contract: ${error instanceof Error ? error.message : 'unknown error'}`,
|
|
628
|
+
hints: ["Run 'prisma-next contract emit' to generate a valid contract"],
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
607
632
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
633
|
+
let aggregate: ContractSpaceAggregate;
|
|
634
|
+
try {
|
|
635
|
+
aggregate = await loadContractSpaceAggregate({
|
|
636
|
+
migrationsDir,
|
|
637
|
+
deserializeContract,
|
|
638
|
+
appContract: appContractForLoad,
|
|
639
|
+
});
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (MigrationToolsError.is(error)) {
|
|
642
|
+
return notOk(mapMigrationToolsError(error));
|
|
643
|
+
}
|
|
644
|
+
return notOk(
|
|
645
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
646
|
+
why: `Failed to read migrations directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
647
|
+
}),
|
|
648
|
+
);
|
|
649
|
+
}
|
|
620
650
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
651
|
+
if (contractRawForAggregate !== null) {
|
|
652
|
+
const corruptionFailure = refusePackageCorruptionOnAggregate(aggregate);
|
|
653
|
+
if (corruptionFailure) {
|
|
654
|
+
return notOk(corruptionFailure);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const appGraph = aggregate.app.graph();
|
|
659
|
+
|
|
660
|
+
let fromOverrideHash: string | undefined;
|
|
661
|
+
|
|
662
|
+
if (options.to || options.from) {
|
|
663
|
+
if (options.to) {
|
|
664
|
+
const refResult = parseContractRef(options.to, { graph: appGraph, refs: allRefs });
|
|
665
|
+
if (!refResult.ok) {
|
|
666
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
627
667
|
}
|
|
628
|
-
|
|
629
|
-
if (
|
|
630
|
-
|
|
668
|
+
activeRefHash = refResult.value.hash;
|
|
669
|
+
if (refResult.value.provenance.kind === 'ref') {
|
|
670
|
+
const resolvedRefName = refResult.value.provenance.refName;
|
|
671
|
+
activeRefName = resolvedRefName;
|
|
672
|
+
activeRefEntry = allRefs[resolvedRefName];
|
|
631
673
|
}
|
|
632
|
-
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (options.from) {
|
|
677
|
+
const fromResult = parseContractRef(options.from, { graph: appGraph, refs: allRefs });
|
|
678
|
+
if (!fromResult.ok) {
|
|
679
|
+
return notOk(mapRefResolutionError(fromResult.failure));
|
|
680
|
+
}
|
|
681
|
+
fromOverrideHash = fromResult.value.hash;
|
|
633
682
|
}
|
|
634
683
|
}
|
|
635
684
|
|
|
@@ -670,34 +719,8 @@ async function executeMigrationStatusCommand(
|
|
|
670
719
|
ui.stderr(header);
|
|
671
720
|
}
|
|
672
721
|
|
|
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
|
-
}
|
|
722
|
+
const bundles = aggregate.app.packages;
|
|
723
|
+
const graph = appGraph;
|
|
701
724
|
|
|
702
725
|
if (bundles.length === 0) {
|
|
703
726
|
if (dbConnection && hasDriver) {
|
|
@@ -808,32 +831,15 @@ async function executeMigrationStatusCommand(
|
|
|
808
831
|
allMarkers = null;
|
|
809
832
|
}
|
|
810
833
|
|
|
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
834
|
let aggregateSpaces: readonly MigrationStatusSpaceEntry[] = [];
|
|
818
835
|
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
836
|
try {
|
|
826
837
|
aggregateSpaces = await loadAggregateStatusSpaces({
|
|
827
|
-
|
|
828
|
-
migrationsDir,
|
|
829
|
-
appContractRaw: contractRawForAggregate,
|
|
838
|
+
aggregate,
|
|
830
839
|
extensionPacks: config.extensionPacks ?? [],
|
|
831
|
-
deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
|
|
832
840
|
markersBySpace: allMarkers,
|
|
833
841
|
});
|
|
834
842
|
} catch {
|
|
835
|
-
// Loader failure short-circuits silently — the existing
|
|
836
|
-
// single-space app pipeline below still runs.
|
|
837
843
|
aggregateSpaces = [];
|
|
838
844
|
}
|
|
839
845
|
}
|
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,
|
|
@@ -20,12 +28,13 @@ import {
|
|
|
20
28
|
} from '../utils/cli-errors';
|
|
21
29
|
import {
|
|
22
30
|
addGlobalOptions,
|
|
23
|
-
loadMigrationPackages,
|
|
24
31
|
resolveMigrationPaths,
|
|
25
32
|
setCommandDescriptions,
|
|
26
33
|
} from '../utils/command-helpers';
|
|
34
|
+
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
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
|
|
|
@@ -72,14 +81,19 @@ export async function executeRefSetCommand(
|
|
|
72
81
|
|
|
73
82
|
try {
|
|
74
83
|
const config = await loadConfig(options.config);
|
|
75
|
-
const {
|
|
84
|
+
const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
|
|
85
|
+
const loaded = await buildReadAggregate(config, { migrationsDir });
|
|
86
|
+
if (!loaded.ok) {
|
|
87
|
+
return notOk(loaded.failure);
|
|
88
|
+
}
|
|
89
|
+
const graph = loaded.value.aggregate.app.graph();
|
|
90
|
+
const bundles = loaded.value.aggregate.app.packages;
|
|
91
|
+
const refs = loaded.value.aggregate.app.refs;
|
|
76
92
|
|
|
77
93
|
let resolvedHash: string;
|
|
78
94
|
if (validateRefValue(contractInput)) {
|
|
79
95
|
resolvedHash = contractInput;
|
|
80
96
|
} else {
|
|
81
|
-
const { graph } = await loadMigrationPackages(appMigrationsDir);
|
|
82
|
-
const refs = await readRefs(refsDir);
|
|
83
97
|
const refResult = parseContractRef(contractInput, { graph, refs });
|
|
84
98
|
if (!refResult.ok) {
|
|
85
99
|
return notOk(mapRefResolutionError(refResult.failure));
|
|
@@ -87,8 +101,39 @@ export async function executeRefSetCommand(
|
|
|
87
101
|
resolvedHash = refResult.value.hash;
|
|
88
102
|
}
|
|
89
103
|
|
|
104
|
+
if (resolvedHash === EMPTY_CONTRACT_HASH) {
|
|
105
|
+
return notOk(errorRefSetEmptySentinel(resolvedHash));
|
|
106
|
+
}
|
|
107
|
+
if (!isGraphNode(resolvedHash, graph)) {
|
|
108
|
+
const graphTip = findLatestMigration(graph)?.to ?? null;
|
|
109
|
+
return notOk(errorRefSetHashNotInGraph(resolvedHash, [...graph.nodes].sort(), graphTip));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const matchingBundle = bundles.find((bundle) => bundle.metadata.to === resolvedHash);
|
|
113
|
+
if (!matchingBundle) {
|
|
114
|
+
return notOk(errorRefSetBundleNotFound(resolvedHash));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const contractJsonPath = join(matchingBundle.dirPath, 'end-contract.json');
|
|
118
|
+
let contractJson: Record<string, unknown>;
|
|
119
|
+
try {
|
|
120
|
+
const raw = await readFile(contractJsonPath, 'utf-8');
|
|
121
|
+
contractJson = JSON.parse(raw) as Record<string, unknown>;
|
|
122
|
+
} catch (readError) {
|
|
123
|
+
if (readError instanceof Error && (readError as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
124
|
+
return notOk(
|
|
125
|
+
errorFileNotFound(contractJsonPath, {
|
|
126
|
+
why: `Migration bundle for hash ${resolvedHash} is missing its end-contract snapshot at ${contractJsonPath}`,
|
|
127
|
+
fix: 'Run `pnpm fixtures:check`, or re-emit the migration so its end-contract.json is restored.',
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
throw readError;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const contractIR = await readContractIR(contractJson, contractJsonPath);
|
|
90
135
|
const entry: RefEntry = { hash: resolvedHash, invariants: [] };
|
|
91
|
-
await
|
|
136
|
+
await writeRefPaired(refsDir, name, entry, contractIR);
|
|
92
137
|
return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });
|
|
93
138
|
} catch (error) {
|
|
94
139
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -103,7 +148,7 @@ export async function executeRefDeleteCommand(
|
|
|
103
148
|
try {
|
|
104
149
|
const config = await loadConfig(options.config);
|
|
105
150
|
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
106
|
-
await
|
|
151
|
+
await deleteRefPaired(refsDir, name);
|
|
107
152
|
return ok({ ok: true as const, ref: name, deleted: true as const });
|
|
108
153
|
} catch (error) {
|
|
109
154
|
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: {
|