@prisma-next/cli 0.11.0 → 0.12.0
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 +259 -12
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-oXO2WCPD.mjs → client-KgJorIvG.mjs} +72 -60
- package/dist/client-KgJorIvG.mjs.map +1 -0
- package/dist/{command-helpers-BSb0tRC8.mjs → command-helpers-Bbw1GbwL.mjs} +646 -46
- package/dist/command-helpers-Bbw1GbwL.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 +6 -2
- 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 +13 -2
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +2 -137
- package/dist/commands/migration-list.d.mts +64 -4
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +143 -56
- package/dist/commands/migration-list.mjs.map +1 -1
- 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 +3 -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 +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 +12 -49
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +85 -81
- 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-BxP-TOMl.mjs +42 -0
- package/dist/contract-at-errors-BxP-TOMl.mjs.map +1 -0
- package/dist/{contract-emit-bcrpT-wD.mjs → contract-emit-D-4jrNve.mjs} +25 -10
- package/dist/contract-emit-D-4jrNve.mjs.map +1 -0
- package/dist/{contract-emit-r4y8Zhf1.mjs → contract-emit-DxcGl4Uq.mjs} +19 -14
- package/dist/contract-emit-DxcGl4Uq.mjs.map +1 -0
- 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-BmySmqVT.mjs → contract-infer-D8uEbJuu.mjs} +4 -5
- package/dist/{contract-infer-BmySmqVT.mjs.map → contract-infer-D8uEbJuu.mjs.map} +1 -1
- package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs +247 -0
- package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs.map +1 -0
- package/dist/{db-verify-BClPs3ph.mjs → db-verify-v_vUKXTU.mjs} +5 -7
- package/dist/{db-verify-BClPs3ph.mjs.map → db-verify-v_vUKXTU.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +3 -3
- 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-fYXjz_in.mjs} +2 -2
- package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-fYXjz_in.mjs.map} +1 -1
- package/dist/global-flags-DEHjV8_s.d.mts +34 -0
- package/dist/global-flags-DEHjV8_s.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-BCJZPWE1.mjs → init-Cv9UzWL5.mjs} +20 -269
- package/dist/init-Cv9UzWL5.mjs.map +1 -0
- package/dist/{inspect-live-schema-DSRbFoOL.mjs → inspect-live-schema-C6ohV_oQ.mjs} +4 -5
- package/dist/{inspect-live-schema-DSRbFoOL.mjs.map → inspect-live-schema-C6ohV_oQ.mjs.map} +1 -1
- package/dist/migration-check-BiBJoYYW.mjs +341 -0
- package/dist/migration-check-BiBJoYYW.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-Bzd9La5c.mjs → migration-command-scaffold-CjvwO6at.mjs} +4 -5
- package/dist/{migration-command-scaffold-Bzd9La5c.mjs.map → migration-command-scaffold-CjvwO6at.mjs.map} +1 -1
- package/dist/migration-graph-D7DVUElV.mjs +1232 -0
- package/dist/migration-graph-D7DVUElV.mjs.map +1 -0
- package/dist/migration-list-styler-BRwF4-gy.mjs +399 -0
- package/dist/migration-list-styler-BRwF4-gy.mjs.map +1 -0
- package/dist/{migration-plan-CFwqw3Gk.mjs → migration-plan-9DJ7q7_z.mjs} +372 -133
- package/dist/migration-plan-9DJ7q7_z.mjs.map +1 -0
- package/dist/{migration-types-BXWvz12q.d.mts → migration-types-D2FW63pr.d.mts} +1 -1
- package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-D2FW63pr.d.mts.map} +1 -1
- package/dist/{migrations-CwZMa1Ck.mjs → migrations-Cv2jxNNK.mjs} +12 -13
- package/dist/migrations-Cv2jxNNK.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-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-5Y6mrg93.d.mts +133 -0
- package/dist/terminal-ui-5Y6mrg93.d.mts.map +1 -0
- package/dist/{types--CqjMdk0.d.mts → types-Dt_SfqFm.d.mts} +28 -28
- package/dist/types-Dt_SfqFm.d.mts.map +1 -0
- package/dist/{verify-Bom75OYI.mjs → verify-DCA9Sldu.mjs} +2 -2
- package/dist/{verify-Bom75OYI.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
- package/package.json +35 -24
- package/src/commands/contract-emit.ts +19 -7
- package/src/commands/contract-infer.ts +1 -1
- 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/init/hygiene-gitattributes.ts +2 -2
- package/src/commands/init/index.ts +2 -1
- package/src/commands/init/templates/code-templates.ts +4 -2
- package/src/commands/init/templates/env.ts +13 -14
- package/src/commands/migrate.ts +125 -44
- package/src/commands/migration-check.ts +43 -83
- package/src/commands/migration-graph.ts +75 -60
- package/src/commands/migration-list.ts +220 -74
- package/src/commands/migration-log.ts +8 -14
- package/src/commands/migration-new.ts +44 -48
- package/src/commands/migration-plan.ts +412 -197
- package/src/commands/migration-show.ts +65 -284
- package/src/commands/migration-status.ts +127 -124
- 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/{apply-aggregate.ts → apply.ts} +44 -75
- package/src/control-api/operations/contract-emit.ts +14 -6
- package/src/control-api/operations/{db-apply-aggregate.ts → db-apply.ts} +19 -19
- package/src/control-api/operations/db-init.ts +4 -4
- package/src/control-api/operations/db-update.ts +4 -4
- package/src/control-api/operations/db-verify.ts +15 -11
- package/src/control-api/operations/migration-apply.ts +56 -47
- package/src/control-api/types.ts +26 -27
- package/src/migration-cli.ts +4 -4
- package/src/utils/cli-errors.ts +234 -0
- package/src/utils/command-helpers.ts +9 -24
- package/src/utils/contract-at-errors.ts +96 -0
- package/src/utils/contract-space-aggregate-loader.ts +336 -117
- package/src/utils/formatters/migration-graph-layout.ts +1119 -0
- package/src/utils/formatters/migration-graph-rows.ts +336 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +459 -0
- package/src/utils/formatters/migration-list-data-column.ts +115 -0
- package/src/utils/formatters/migration-list-graph-topology.ts +368 -0
- package/src/utils/formatters/migration-list-render.ts +191 -0
- package/src/utils/formatters/migration-list-styler.ts +63 -0
- package/src/utils/formatters/migration-list-types.ts +21 -0
- package/src/utils/formatters/migrations.ts +37 -46
- 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-BSb0tRC8.mjs.map +0 -1
- package/dist/commands/migration-check.mjs.map +0 -1
- package/dist/commands/migration-graph.mjs.map +0 -1
- package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
- package/dist/contract-emit-r4y8Zhf1.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/init-BCJZPWE1.mjs.map +0 -1
- package/dist/migration-plan-CFwqw3Gk.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--CqjMdk0.d.mts.map +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import type { Contract } from '@prisma-next/contract/types';
|
|
3
3
|
import { getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
4
4
|
import {
|
|
5
|
-
type ControlFamilyInstance,
|
|
6
5
|
createControlStack,
|
|
7
6
|
hasOperationPreview,
|
|
8
7
|
type MigrationPlanOperation,
|
|
9
8
|
type OperationPreview,
|
|
10
9
|
} from '@prisma-next/framework-components/control';
|
|
10
|
+
import { canonicalizeJson } from '@prisma-next/framework-components/utils';
|
|
11
11
|
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
12
12
|
import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
13
13
|
import { deriveProvidedInvariants } from '@prisma-next/migration-tools/invariants';
|
|
@@ -17,10 +17,7 @@ import {
|
|
|
17
17
|
writeMigrationPackage,
|
|
18
18
|
} from '@prisma-next/migration-tools/io';
|
|
19
19
|
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
20
|
-
import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
|
|
21
20
|
import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
|
|
22
|
-
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
23
|
-
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
24
21
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
25
22
|
import { Command } from 'commander';
|
|
26
23
|
import { join, relative } from 'pathe';
|
|
@@ -34,24 +31,26 @@ import {
|
|
|
34
31
|
errorTargetMigrationNotSupported,
|
|
35
32
|
errorUnexpected,
|
|
36
33
|
mapMigrationToolsError,
|
|
37
|
-
mapRefResolutionError,
|
|
38
34
|
} from '../utils/cli-errors';
|
|
39
35
|
import {
|
|
40
36
|
addGlobalOptions,
|
|
41
37
|
getTargetMigrations,
|
|
42
|
-
loadMigrationPackages,
|
|
43
38
|
resolveContractPath,
|
|
44
39
|
resolveMigrationPaths,
|
|
45
40
|
setCommandDescriptions,
|
|
46
41
|
setCommandExamples,
|
|
47
42
|
} from '../utils/command-helpers';
|
|
48
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
buildContractSpaceAggregate,
|
|
45
|
+
loadContractSpaceAggregateForCli,
|
|
46
|
+
} from '../utils/contract-space-aggregate-loader';
|
|
49
47
|
import { runContractSpaceSeedPhase } from '../utils/contract-space-seed-phase';
|
|
50
48
|
import { toExtensionInputs } from '../utils/extension-pack-inputs';
|
|
51
49
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
52
50
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
53
51
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
54
52
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
53
|
+
import { resolveFromForPlan, resolveToForPlan } from '../utils/plan-resolution';
|
|
55
54
|
import { handleResult } from '../utils/result-handler';
|
|
56
55
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
57
56
|
|
|
@@ -59,55 +58,116 @@ interface MigrationPlanOptions extends CommonCommandOptions {
|
|
|
59
58
|
readonly config?: string;
|
|
60
59
|
readonly name?: string;
|
|
61
60
|
readonly from?: string;
|
|
61
|
+
readonly to?: string;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
64
|
+
async function writeSnapshotContractArtifacts(
|
|
65
|
+
packageDir: string,
|
|
66
|
+
contractJson: unknown,
|
|
67
|
+
contractDts: string,
|
|
68
|
+
artifactBasename: 'start-contract' | 'end-contract',
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
await mkdir(packageDir, { recursive: true });
|
|
71
|
+
const jsonContent = `${canonicalizeJson(contractJson)}\n`;
|
|
72
|
+
const dtsContent = contractDts.endsWith('\n') ? contractDts : `${contractDts}\n`;
|
|
73
|
+
await writeFile(join(packageDir, `${artifactBasename}.json`), jsonContent);
|
|
74
|
+
await writeFile(join(packageDir, `${artifactBasename}.d.ts`), dtsContent);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function writeSnapshotStartContract(
|
|
78
|
+
packageDir: string,
|
|
79
|
+
contractJson: unknown,
|
|
80
|
+
contractDts: string,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
await writeSnapshotContractArtifacts(packageDir, contractJson, contractDts, 'start-contract');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type PlannerSuccess = {
|
|
86
|
+
readonly plannedOps: readonly MigrationPlanOperation[];
|
|
87
|
+
readonly migrationTsContent: string;
|
|
88
|
+
readonly hasPlaceholders: boolean;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type TargetMigrationsApi = NonNullable<ReturnType<typeof getTargetMigrations>>;
|
|
92
|
+
|
|
93
|
+
async function runPlannerLeg(
|
|
94
|
+
planner: ReturnType<TargetMigrationsApi['createPlanner']>,
|
|
95
|
+
migrations: TargetMigrationsApi,
|
|
96
|
+
frameworkComponents: ReturnType<typeof assertFrameworkComponentsCompatible>,
|
|
97
|
+
contract: Contract,
|
|
98
|
+
fromContract: Contract | null,
|
|
99
|
+
spaceId: string,
|
|
100
|
+
): Promise<Result<PlannerSuccess, CliStructuredError>> {
|
|
101
|
+
const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
|
|
102
|
+
const plannerResult = planner.plan({
|
|
103
|
+
contract,
|
|
104
|
+
schema: fromSchema,
|
|
105
|
+
policy: { allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] },
|
|
106
|
+
fromContract,
|
|
107
|
+
frameworkComponents,
|
|
108
|
+
spaceId,
|
|
109
|
+
});
|
|
110
|
+
if (plannerResult.kind === 'failure') {
|
|
111
|
+
return notOk(
|
|
112
|
+
errorMigrationPlanningFailed({
|
|
113
|
+
conflicts: plannerResult.conflicts as readonly CliErrorConflict[],
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
99
116
|
}
|
|
117
|
+
|
|
118
|
+
let plannedOps: readonly MigrationPlanOperation[] = [];
|
|
119
|
+
let hasPlaceholders = false;
|
|
100
120
|
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
plannedOps = plannerResult.plan.operations;
|
|
122
|
+
if (plannedOps.length === 0) {
|
|
123
|
+
return notOk(
|
|
124
|
+
errorMigrationPlanningFailed({
|
|
125
|
+
conflicts: [
|
|
126
|
+
{
|
|
127
|
+
kind: 'unsupportedChange',
|
|
128
|
+
summary:
|
|
129
|
+
'Contract changed but planner produced no operations. ' +
|
|
130
|
+
'This indicates unsupported or ignored changes.',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
} catch (e) {
|
|
137
|
+
if (CliStructuredError.is(e) && e.domain === 'MIG' && e.code === '2001') {
|
|
138
|
+
hasPlaceholders = true;
|
|
139
|
+
} else {
|
|
140
|
+
throw e;
|
|
105
141
|
}
|
|
106
|
-
throw errorContractValidationFailed(
|
|
107
|
-
`Predecessor contract at ${path} failed to deserialize: ${error instanceof Error ? error.message : String(error)}`,
|
|
108
|
-
{ where: { path } },
|
|
109
|
-
);
|
|
110
142
|
}
|
|
143
|
+
|
|
144
|
+
return ok({
|
|
145
|
+
plannedOps,
|
|
146
|
+
migrationTsContent: plannerResult.plan.renderTypeScript(),
|
|
147
|
+
hasPlaceholders,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function writePlannedMigrationPackage(
|
|
152
|
+
packageDir: string,
|
|
153
|
+
fromHash: string | null,
|
|
154
|
+
toHash: string,
|
|
155
|
+
createdAt: Date,
|
|
156
|
+
leg: PlannerSuccess,
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
const opsForWrite = leg.hasPlaceholders ? [] : leg.plannedOps;
|
|
159
|
+
const metadataWithInvariants: Omit<MigrationMetadata, 'migrationHash'> = {
|
|
160
|
+
from: fromHash,
|
|
161
|
+
to: toHash,
|
|
162
|
+
providedInvariants: deriveProvidedInvariants(opsForWrite),
|
|
163
|
+
createdAt: createdAt.toISOString(),
|
|
164
|
+
};
|
|
165
|
+
const metadata: MigrationMetadata = {
|
|
166
|
+
...metadataWithInvariants,
|
|
167
|
+
migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite),
|
|
168
|
+
};
|
|
169
|
+
await writeMigrationPackage(packageDir, metadata, opsForWrite);
|
|
170
|
+
await writeMigrationTs(packageDir, leg.migrationTsContent);
|
|
111
171
|
}
|
|
112
172
|
|
|
113
173
|
export interface MigrationPlanResult {
|
|
@@ -116,6 +176,7 @@ export interface MigrationPlanResult {
|
|
|
116
176
|
readonly from: string | null;
|
|
117
177
|
readonly to: string;
|
|
118
178
|
readonly dir?: string;
|
|
179
|
+
readonly baselineDir?: string;
|
|
119
180
|
/**
|
|
120
181
|
* Extension-space migration packages materialised onto disk during this
|
|
121
182
|
* `plan` run. Each entry names a `migrations/<spaceId>/<dirName>/`
|
|
@@ -125,7 +186,7 @@ export interface MigrationPlanResult {
|
|
|
125
186
|
*
|
|
126
187
|
* Surfacing these in the result (rather than only via `ui.step` log
|
|
127
188
|
* lines) makes the cross-space side effect explicit to JSON consumers
|
|
128
|
-
* and the success-summary renderer — the same
|
|
189
|
+
* and the success-summary renderer — the same cross-space side effect
|
|
129
190
|
* that `migrate` will replay.
|
|
130
191
|
*/
|
|
131
192
|
readonly emittedExtensionDirs: readonly { readonly spaceId: string; readonly dirName: string }[];
|
|
@@ -174,6 +235,9 @@ async function executeMigrationPlanCommand(
|
|
|
174
235
|
if (options.from) {
|
|
175
236
|
details.push({ label: 'from', value: options.from });
|
|
176
237
|
}
|
|
238
|
+
if (options.to) {
|
|
239
|
+
details.push({ label: 'to', value: options.to });
|
|
240
|
+
}
|
|
177
241
|
if (options.name) {
|
|
178
242
|
details.push({ label: 'name', value: options.name });
|
|
179
243
|
}
|
|
@@ -207,8 +271,7 @@ async function executeMigrationPlanCommand(
|
|
|
207
271
|
);
|
|
208
272
|
}
|
|
209
273
|
|
|
210
|
-
// Construct the family instance up-front so on-disk reads
|
|
211
|
-
// contract here + every `readPredecessorEndContract` below) cross the
|
|
274
|
+
// Construct the family instance up-front so on-disk contract reads cross the
|
|
212
275
|
// serializer seam at the read site, not after the planner has already
|
|
213
276
|
// started dispatching on raw shapes. See TML-2536.
|
|
214
277
|
const stack = createControlStack(config);
|
|
@@ -234,70 +297,84 @@ async function executeMigrationPlanCommand(
|
|
|
234
297
|
}),
|
|
235
298
|
);
|
|
236
299
|
}
|
|
237
|
-
|
|
300
|
+
let toStorageHash: string = rawStorageHash;
|
|
301
|
+
|
|
302
|
+
// When `--to <ref>` resolves a non-default destination, these carry its raw
|
|
303
|
+
// artifacts so the planned package's `end-contract.*` is written from the
|
|
304
|
+
// resolved target rather than copied from the emitted `contract.json`.
|
|
305
|
+
let toArtifacts: { contractJson: unknown; contractDts: string } | null = null;
|
|
238
306
|
|
|
239
|
-
// Read existing migrations and determine "from" contract
|
|
240
307
|
let fromContract: Contract | null = null;
|
|
241
308
|
let fromHash: string | null = null;
|
|
242
309
|
let fromContractSourceDir: string | null = null;
|
|
310
|
+
let snapshotStartContract: { contractJson: unknown; contractDts: string } | null = null;
|
|
311
|
+
let isAutoBaseline = false;
|
|
243
312
|
|
|
244
|
-
|
|
245
|
-
|
|
313
|
+
const tolerantAggregateResult = await loadContractSpaceAggregateForCli({
|
|
314
|
+
targetId: config.target.targetId,
|
|
315
|
+
migrationsDir,
|
|
316
|
+
appContract: toContract,
|
|
317
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
318
|
+
deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
|
|
319
|
+
});
|
|
320
|
+
if (!tolerantAggregateResult.ok) {
|
|
321
|
+
return notOk(tolerantAggregateResult.failure);
|
|
322
|
+
}
|
|
323
|
+
const resolutionMember = tolerantAggregateResult.value.app;
|
|
246
324
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
325
|
+
const resolutionResult = await resolveFromForPlan({
|
|
326
|
+
optionsFrom: options.from,
|
|
327
|
+
member: resolutionMember,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
if (!resolutionResult.ok) {
|
|
331
|
+
return notOk(resolutionResult.failure);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
switch (resolutionResult.value.kind) {
|
|
335
|
+
case 'greenfield':
|
|
336
|
+
break;
|
|
337
|
+
case 'graph-node':
|
|
338
|
+
fromHash = resolutionResult.value.fromHash;
|
|
339
|
+
fromContract = resolutionResult.value.fromContract;
|
|
340
|
+
fromContractSourceDir = resolutionResult.value.sourceDir;
|
|
341
|
+
break;
|
|
342
|
+
case 'snapshot':
|
|
343
|
+
fromHash = resolutionResult.value.fromHash;
|
|
344
|
+
fromContract = resolutionResult.value.fromContract;
|
|
345
|
+
snapshotStartContract = {
|
|
346
|
+
contractJson: resolutionResult.value.contractJson,
|
|
347
|
+
contractDts: resolutionResult.value.contractDts,
|
|
348
|
+
};
|
|
349
|
+
break;
|
|
350
|
+
case 'auto-baseline':
|
|
351
|
+
fromHash = resolutionResult.value.fromHash;
|
|
352
|
+
fromContract = resolutionResult.value.fromContract;
|
|
353
|
+
snapshotStartContract = {
|
|
354
|
+
contractJson: resolutionResult.value.contractJson,
|
|
355
|
+
contractDts: resolutionResult.value.contractDts,
|
|
356
|
+
};
|
|
357
|
+
isAutoBaseline = true;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// `--to <ref>` swaps the planner destination to an arbitrary resolved
|
|
362
|
+
// contract (e.g. an ancestor / rollback target). The from-side resolution
|
|
363
|
+
// above is untouched; only the destination + its emitted `end-contract.*`
|
|
364
|
+
// change.
|
|
365
|
+
if (options.to !== undefined) {
|
|
366
|
+
const toResolution = await resolveToForPlan(options.to, {
|
|
367
|
+
member: resolutionMember,
|
|
368
|
+
});
|
|
369
|
+
if (!toResolution.ok) {
|
|
370
|
+
return notOk(toResolution.failure);
|
|
290
371
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
errorUnexpected(message, {
|
|
298
|
-
why: `Unexpected error while loading migrations: ${message}`,
|
|
299
|
-
}),
|
|
300
|
-
);
|
|
372
|
+
toContract = toResolution.value.contract;
|
|
373
|
+
toStorageHash = toResolution.value.hash;
|
|
374
|
+
toArtifacts = {
|
|
375
|
+
contractJson: toResolution.value.contractJson,
|
|
376
|
+
contractDts: toResolution.value.contractDts,
|
|
377
|
+
};
|
|
301
378
|
}
|
|
302
379
|
|
|
303
380
|
// Phase 1 — seed: unconditionally re-emit per-space pinned artefacts
|
|
@@ -325,8 +402,10 @@ async function executeMigrationPlanCommand(
|
|
|
325
402
|
r.newMigrationDirs.map((dirName) => ({ spaceId: r.spaceId, dirName })),
|
|
326
403
|
);
|
|
327
404
|
|
|
328
|
-
// Check for no-op (same hash means no changes)
|
|
329
|
-
|
|
405
|
+
// Check for no-op (same hash means no changes). Auto-baseline is exempt:
|
|
406
|
+
// an empty graph with db ref at the current contract still needs a
|
|
407
|
+
// null → fromHash baseline bundle so migrate can anchor the marker.
|
|
408
|
+
if (fromHash === toStorageHash && !isAutoBaseline) {
|
|
330
409
|
const result: MigrationPlanResult = {
|
|
331
410
|
ok: true,
|
|
332
411
|
noOp: true,
|
|
@@ -375,97 +454,204 @@ async function executeMigrationPlanCommand(
|
|
|
375
454
|
[config.target, config.adapter, ...(config.extensionPacks ?? [])],
|
|
376
455
|
);
|
|
377
456
|
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
457
|
+
// Write the planned package's destination `end-contract.*`. With `--to`, the
|
|
458
|
+
// resolved target's raw artifacts are written; otherwise the emitted
|
|
459
|
+
// `contract.json` / `contract.d.ts` are copied verbatim (today's behaviour).
|
|
460
|
+
async function writeDestinationEndContract(packageDir: string): Promise<void> {
|
|
461
|
+
if (toArtifacts !== null) {
|
|
462
|
+
await writeSnapshotContractArtifacts(
|
|
463
|
+
packageDir,
|
|
464
|
+
toArtifacts.contractJson,
|
|
465
|
+
toArtifacts.contractDts,
|
|
466
|
+
'end-contract',
|
|
467
|
+
);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
|
|
471
|
+
await copyFilesWithRename(packageDir, [
|
|
472
|
+
{ sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
|
|
473
|
+
{ sourcePath: destinationArtifacts.dtsPath, destName: 'end-contract.d.ts' },
|
|
474
|
+
]);
|
|
475
|
+
}
|
|
395
476
|
|
|
396
477
|
try {
|
|
397
478
|
const planner = migrations.createPlanner(familyInstance);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
479
|
+
|
|
480
|
+
if (
|
|
481
|
+
isAutoBaseline &&
|
|
482
|
+
fromHash !== null &&
|
|
483
|
+
fromContract !== null &&
|
|
484
|
+
snapshotStartContract !== null
|
|
485
|
+
) {
|
|
486
|
+
const baselineTimestamp = new Date();
|
|
487
|
+
const deltaTimestamp = new Date(baselineTimestamp.getTime() + 60_000);
|
|
488
|
+
const baselineDirName = formatMigrationDirName(baselineTimestamp, 'baseline');
|
|
489
|
+
const deltaDirName = formatMigrationDirName(deltaTimestamp, options.name ?? 'migration');
|
|
490
|
+
const baselinePackageDir = join(appMigrationsDir, baselineDirName);
|
|
491
|
+
const deltaPackageDir = join(appMigrationsDir, deltaDirName);
|
|
492
|
+
|
|
493
|
+
const baselineLeg = await runPlannerLeg(
|
|
494
|
+
planner,
|
|
495
|
+
migrations,
|
|
496
|
+
frameworkComponents,
|
|
497
|
+
fromContract,
|
|
498
|
+
null,
|
|
499
|
+
aggregate.app.spaceId,
|
|
412
500
|
);
|
|
413
|
-
|
|
501
|
+
if (!baselineLeg.ok) {
|
|
502
|
+
return notOk(baselineLeg.failure);
|
|
503
|
+
}
|
|
414
504
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
505
|
+
await writePlannedMigrationPackage(
|
|
506
|
+
baselinePackageDir,
|
|
507
|
+
null,
|
|
508
|
+
fromHash,
|
|
509
|
+
baselineTimestamp,
|
|
510
|
+
baselineLeg.value,
|
|
511
|
+
);
|
|
512
|
+
await writeSnapshotContractArtifacts(
|
|
513
|
+
baselinePackageDir,
|
|
514
|
+
snapshotStartContract.contractJson,
|
|
515
|
+
snapshotStartContract.contractDts,
|
|
516
|
+
'end-contract',
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
if (fromHash === toStorageHash) {
|
|
520
|
+
const baselineOps = baselineLeg.value.hasPlaceholders ? [] : baselineLeg.value.plannedOps;
|
|
521
|
+
if (baselineLeg.value.hasPlaceholders) {
|
|
522
|
+
const baselineDir = relative(process.cwd(), baselinePackageDir);
|
|
523
|
+
const result: MigrationPlanResult = {
|
|
524
|
+
ok: true,
|
|
525
|
+
noOp: false,
|
|
526
|
+
from: fromHash,
|
|
527
|
+
to: toStorageHash,
|
|
528
|
+
dir: baselineDir,
|
|
529
|
+
baselineDir,
|
|
530
|
+
operations: [],
|
|
531
|
+
emittedExtensionDirs,
|
|
532
|
+
pendingPlaceholders: true,
|
|
533
|
+
summary:
|
|
534
|
+
'Planned baseline with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit',
|
|
535
|
+
timings: { total: Date.now() - startTime },
|
|
536
|
+
};
|
|
537
|
+
return ok(result);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const preview = hasOperationPreview(familyInstance)
|
|
541
|
+
? familyInstance.toOperationPreview(baselineOps)
|
|
542
|
+
: undefined;
|
|
543
|
+
const result: MigrationPlanResult = {
|
|
544
|
+
ok: true,
|
|
545
|
+
noOp: false,
|
|
546
|
+
from: fromHash,
|
|
547
|
+
to: toStorageHash,
|
|
548
|
+
baselineDir: relative(process.cwd(), baselinePackageDir),
|
|
549
|
+
operations: baselineOps.map((op) => ({
|
|
550
|
+
id: op.id,
|
|
551
|
+
label: op.label,
|
|
552
|
+
operationClass: op.operationClass,
|
|
553
|
+
})),
|
|
554
|
+
emittedExtensionDirs,
|
|
555
|
+
...(preview !== undefined ? { preview } : {}),
|
|
556
|
+
summary: buildAutoBaselinePlanSummary(0, emittedExtensionDirs.length),
|
|
557
|
+
timings: { total: Date.now() - startTime },
|
|
558
|
+
};
|
|
559
|
+
return ok(result);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const deltaLeg = await runPlannerLeg(
|
|
563
|
+
planner,
|
|
564
|
+
migrations,
|
|
565
|
+
frameworkComponents,
|
|
566
|
+
aggregate.app.contract(),
|
|
567
|
+
fromContract,
|
|
568
|
+
aggregate.app.spaceId,
|
|
569
|
+
);
|
|
570
|
+
if (!deltaLeg.ok) {
|
|
571
|
+
return notOk(deltaLeg.failure);
|
|
437
572
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
573
|
+
|
|
574
|
+
await writePlannedMigrationPackage(
|
|
575
|
+
deltaPackageDir,
|
|
576
|
+
fromHash,
|
|
577
|
+
toStorageHash,
|
|
578
|
+
deltaTimestamp,
|
|
579
|
+
deltaLeg.value,
|
|
580
|
+
);
|
|
581
|
+
await writeDestinationEndContract(deltaPackageDir);
|
|
582
|
+
await writeSnapshotStartContract(
|
|
583
|
+
deltaPackageDir,
|
|
584
|
+
snapshotStartContract.contractJson,
|
|
585
|
+
snapshotStartContract.contractDts,
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const deltaOps = deltaLeg.value.hasPlaceholders ? [] : deltaLeg.value.plannedOps;
|
|
589
|
+
if (deltaLeg.value.hasPlaceholders) {
|
|
590
|
+
const result: MigrationPlanResult = {
|
|
591
|
+
ok: true,
|
|
592
|
+
noOp: false,
|
|
593
|
+
from: fromHash,
|
|
594
|
+
to: toStorageHash,
|
|
595
|
+
dir: relative(process.cwd(), deltaPackageDir),
|
|
596
|
+
baselineDir: relative(process.cwd(), baselinePackageDir),
|
|
597
|
+
operations: [],
|
|
598
|
+
emittedExtensionDirs,
|
|
599
|
+
pendingPlaceholders: true,
|
|
600
|
+
summary:
|
|
601
|
+
'Planned baseline + migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit',
|
|
602
|
+
timings: { total: Date.now() - startTime },
|
|
603
|
+
};
|
|
604
|
+
return ok(result);
|
|
443
605
|
}
|
|
606
|
+
|
|
607
|
+
const preview = hasOperationPreview(familyInstance)
|
|
608
|
+
? familyInstance.toOperationPreview(deltaOps)
|
|
609
|
+
: undefined;
|
|
610
|
+
const result: MigrationPlanResult = {
|
|
611
|
+
ok: true,
|
|
612
|
+
noOp: false,
|
|
613
|
+
from: fromHash,
|
|
614
|
+
to: toStorageHash,
|
|
615
|
+
dir: relative(process.cwd(), deltaPackageDir),
|
|
616
|
+
baselineDir: relative(process.cwd(), baselinePackageDir),
|
|
617
|
+
operations: deltaOps.map((op) => ({
|
|
618
|
+
id: op.id,
|
|
619
|
+
label: op.label,
|
|
620
|
+
operationClass: op.operationClass,
|
|
621
|
+
})),
|
|
622
|
+
emittedExtensionDirs,
|
|
623
|
+
...(preview !== undefined ? { preview } : {}),
|
|
624
|
+
summary: buildAutoBaselinePlanSummary(deltaOps.length, emittedExtensionDirs.length),
|
|
625
|
+
timings: { total: Date.now() - startTime },
|
|
626
|
+
};
|
|
627
|
+
return ok(result);
|
|
444
628
|
}
|
|
445
629
|
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
// over the empty list — re-emitting after the user fills the placeholder
|
|
451
|
-
// produces a different hash (over the real ops). This is intentional;
|
|
452
|
-
// there is no on-disk "draft" state.
|
|
453
|
-
const opsForWrite = hasPlaceholders ? [] : plannedOps;
|
|
454
|
-
const metadataWithInvariants: Omit<MigrationMetadata, 'migrationHash'> = {
|
|
455
|
-
...baseMetadata,
|
|
456
|
-
providedInvariants: deriveProvidedInvariants(opsForWrite),
|
|
457
|
-
};
|
|
458
|
-
const metadata: MigrationMetadata = {
|
|
459
|
-
...metadataWithInvariants,
|
|
460
|
-
migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite),
|
|
461
|
-
};
|
|
630
|
+
const timestamp = new Date();
|
|
631
|
+
const slug = options.name ?? 'migration';
|
|
632
|
+
const dirName = formatMigrationDirName(timestamp, slug);
|
|
633
|
+
const packageDir = join(appMigrationsDir, dirName);
|
|
462
634
|
|
|
463
|
-
await
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
635
|
+
const deltaLeg = await runPlannerLeg(
|
|
636
|
+
planner,
|
|
637
|
+
migrations,
|
|
638
|
+
frameworkComponents,
|
|
639
|
+
aggregate.app.contract(),
|
|
640
|
+
fromContract,
|
|
641
|
+
aggregate.app.spaceId,
|
|
642
|
+
);
|
|
643
|
+
if (!deltaLeg.ok) {
|
|
644
|
+
return notOk(deltaLeg.failure);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
await writePlannedMigrationPackage(
|
|
648
|
+
packageDir,
|
|
649
|
+
fromHash,
|
|
650
|
+
toStorageHash,
|
|
651
|
+
timestamp,
|
|
652
|
+
deltaLeg.value,
|
|
653
|
+
);
|
|
654
|
+
await writeDestinationEndContract(packageDir);
|
|
469
655
|
if (fromContractSourceDir !== null) {
|
|
470
656
|
const sourceArtifacts = getEmittedArtifactPaths(
|
|
471
657
|
join(fromContractSourceDir, 'end-contract.json'),
|
|
@@ -474,10 +660,15 @@ async function executeMigrationPlanCommand(
|
|
|
474
660
|
{ sourcePath: sourceArtifacts.jsonPath, destName: 'start-contract.json' },
|
|
475
661
|
{ sourcePath: sourceArtifacts.dtsPath, destName: 'start-contract.d.ts' },
|
|
476
662
|
]);
|
|
663
|
+
} else if (snapshotStartContract !== null) {
|
|
664
|
+
await writeSnapshotStartContract(
|
|
665
|
+
packageDir,
|
|
666
|
+
snapshotStartContract.contractJson,
|
|
667
|
+
snapshotStartContract.contractDts,
|
|
668
|
+
);
|
|
477
669
|
}
|
|
478
|
-
await writeMigrationTs(packageDir, migrationTsContent);
|
|
479
670
|
|
|
480
|
-
if (hasPlaceholders) {
|
|
671
|
+
if (deltaLeg.value.hasPlaceholders) {
|
|
481
672
|
const result: MigrationPlanResult = {
|
|
482
673
|
ok: true,
|
|
483
674
|
noOp: false,
|
|
@@ -494,6 +685,7 @@ async function executeMigrationPlanCommand(
|
|
|
494
685
|
return ok(result);
|
|
495
686
|
}
|
|
496
687
|
|
|
688
|
+
const plannedOps = deltaLeg.value.plannedOps;
|
|
497
689
|
const preview = hasOperationPreview(familyInstance)
|
|
498
690
|
? familyInstance.toOperationPreview(plannedOps)
|
|
499
691
|
: undefined;
|
|
@@ -542,6 +734,7 @@ export function createMigrationPlanCommand(): Command {
|
|
|
542
734
|
setCommandExamples(command, [
|
|
543
735
|
'prisma-next migration plan',
|
|
544
736
|
'prisma-next migration plan --name add-users-table',
|
|
737
|
+
'prisma-next migration plan --to <migration-dir>^ --name rollback',
|
|
545
738
|
]);
|
|
546
739
|
addGlobalOptions(command)
|
|
547
740
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
@@ -550,6 +743,10 @@ export function createMigrationPlanCommand(): Command {
|
|
|
550
743
|
'--from <contract>',
|
|
551
744
|
'Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
|
|
552
745
|
)
|
|
746
|
+
.option(
|
|
747
|
+
'--to <contract>',
|
|
748
|
+
'Destination contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path); defaults to the emitted contract',
|
|
749
|
+
)
|
|
553
750
|
.action(async (options: MigrationPlanOptions) => {
|
|
554
751
|
const flags = parseGlobalFlagsOrExit(options);
|
|
555
752
|
const startTime = Date.now();
|
|
@@ -593,6 +790,17 @@ function buildPlanSummary(plannedOpsCount: number, emittedExtensionDirsCount: nu
|
|
|
593
790
|
return `${base}; materialised ${emittedExtensionDirsCount} ${noun}`;
|
|
594
791
|
}
|
|
595
792
|
|
|
793
|
+
function buildAutoBaselinePlanSummary(
|
|
794
|
+
deltaOpsCount: number,
|
|
795
|
+
emittedExtensionDirsCount: number,
|
|
796
|
+
): string {
|
|
797
|
+
const base = `Planned baseline + ${deltaOpsCount} operation(s)`;
|
|
798
|
+
if (emittedExtensionDirsCount === 0) return base;
|
|
799
|
+
const noun =
|
|
800
|
+
emittedExtensionDirsCount === 1 ? 'extension-space migration' : 'extension-space migrations';
|
|
801
|
+
return `${base}; materialised ${emittedExtensionDirsCount} ${noun}`;
|
|
802
|
+
}
|
|
803
|
+
|
|
596
804
|
export function formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFlags): string {
|
|
597
805
|
const lines: string[] = [];
|
|
598
806
|
const useColor = flags.color !== false;
|
|
@@ -672,6 +880,9 @@ export function formatMigrationPlanOutput(result: MigrationPlanResult, flags: Gl
|
|
|
672
880
|
|
|
673
881
|
lines.push(dim_(`from: ${result.from}`));
|
|
674
882
|
lines.push(dim_(`to: ${result.to}`));
|
|
883
|
+
if (result.baselineDir) {
|
|
884
|
+
lines.push(dim_(`Baseline → ${result.baselineDir}`));
|
|
885
|
+
}
|
|
675
886
|
if (result.dir) {
|
|
676
887
|
lines.push(dim_(`App space → ${result.dir}`));
|
|
677
888
|
}
|
|
@@ -689,8 +900,12 @@ export function formatMigrationPlanOutput(result: MigrationPlanResult, flags: Gl
|
|
|
689
900
|
// (`prisma-next migrate`) regardless of how many spaces were
|
|
690
901
|
// materialised — `db update` is a dev-time convenience, not the
|
|
691
902
|
// canonical replay step.
|
|
903
|
+
const reviewTarget =
|
|
904
|
+
result.baselineDir !== undefined && result.dir !== undefined
|
|
905
|
+
? `${result.baselineDir} and ${result.dir}`
|
|
906
|
+
: (result.baselineDir ?? result.dir ?? '<dir>');
|
|
692
907
|
lines.push(
|
|
693
|
-
`Next: review ${green_(
|
|
908
|
+
`Next: review ${green_(reviewTarget)} if needed, then run ${green_('prisma-next migrate')}.`,
|
|
694
909
|
);
|
|
695
910
|
|
|
696
911
|
if (result.preview && result.preview.statements.length > 0) {
|