@prisma-next/cli 0.11.0-dev.9 → 0.12.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/dist/cli.mjs +9 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-UnIveZxZ.mjs → client-KgJorIvG.mjs} +72 -60
- package/dist/client-KgJorIvG.mjs.map +1 -0
- package/dist/{command-helpers-CRfjbZRz.mjs → command-helpers-Bbw1GbwL.mjs} +642 -45
- 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 +5 -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 +12 -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 +2 -2
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +68 -69
- 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 +2 -2
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +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 +84 -80
- 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 +10 -8
- 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-C6rlsljO.mjs → contract-emit-D-4jrNve.mjs} +21 -7
- package/dist/{contract-emit-C6rlsljO.mjs.map → contract-emit-D-4jrNve.mjs.map} +1 -1
- package/dist/{contract-emit-mqXmapxB.mjs → contract-emit-DxcGl4Uq.mjs} +4 -6
- package/dist/{contract-emit-mqXmapxB.mjs.map → contract-emit-DxcGl4Uq.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-C4jxc1aZ.mjs → contract-infer-D8uEbJuu.mjs} +3 -4
- package/dist/{contract-infer-C4jxc1aZ.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-1d8tDoFN.mjs → db-verify-v_vUKXTU.mjs} +5 -7
- package/dist/{db-verify-1d8tDoFN.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-Bexd0f4E.mjs → framework-components-fYXjz_in.mjs} +2 -2
- package/dist/{framework-components-Bexd0f4E.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-BE8vmJ_7.mjs → graph-render-rFAqZujX.mjs} +2 -2
- package/dist/{graph-render-BE8vmJ_7.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
- package/dist/{init-ByoeQphC.mjs → init-Cv9UzWL5.mjs} +17 -19
- package/dist/init-Cv9UzWL5.mjs.map +1 -0
- package/dist/{inspect-live-schema-B1Q49RF0.mjs → inspect-live-schema-C6ohV_oQ.mjs} +4 -5
- package/dist/{inspect-live-schema-B1Q49RF0.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-oY4P1Qto.mjs → migration-command-scaffold-CjvwO6at.mjs} +4 -5
- package/dist/{migration-command-scaffold-oY4P1Qto.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-jdAHg_gK.mjs → migration-plan-9DJ7q7_z.mjs} +169 -189
- 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-B7n518mT.mjs → migrations-Cv2jxNNK.mjs} +3 -13
- package/dist/migrations-Cv2jxNNK.mjs.map +1 -0
- package/dist/{output-CUIdfYo5.mjs → output-B60Gw5fu.mjs} +1 -1
- package/dist/{output-CUIdfYo5.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
- package/dist/{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-DRh5Nquq.mjs → ref-advancement-DUZqsue6.mjs} +1 -1
- package/dist/{ref-advancement-DRh5Nquq.mjs.map → ref-advancement-DUZqsue6.mjs.map} +1 -1
- package/dist/terminal-ui-5Y6mrg93.d.mts +133 -0
- package/dist/terminal-ui-5Y6mrg93.d.mts.map +1 -0
- package/dist/{types-UWB2-rrw.d.mts → types-Dt_SfqFm.d.mts} +18 -26
- package/dist/types-Dt_SfqFm.d.mts.map +1 -0
- package/dist/{verify-C5UvbrF1.mjs → verify-DCA9Sldu.mjs} +2 -2
- package/dist/{verify-C5UvbrF1.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
- package/package.json +34 -23
- package/src/commands/db-sign.ts +9 -5
- package/src/commands/db-update.ts +9 -8
- package/src/commands/init/templates/env.ts +13 -14
- package/src/commands/migrate.ts +87 -76
- 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 +107 -129
- package/src/commands/migration-show.ts +65 -284
- package/src/commands/migration-status.ts +127 -124
- package/src/commands/ref.ts +9 -4
- 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 +7 -2
- 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 +19 -27
- package/src/migration-cli.ts +4 -4
- package/src/utils/cli-errors.ts +73 -12
- 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-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 +12 -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 +134 -133
- package/src/utils/terminal-ui.ts +42 -1
- package/dist/cli-errors-Bw2GlweY.mjs +0 -175
- package/dist/cli-errors-Bw2GlweY.mjs.map +0 -1
- package/dist/client-UnIveZxZ.mjs.map +0 -1
- package/dist/command-helpers-CRfjbZRz.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-enrichment-Dani0mMW.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-CGakRlKM.mjs +0 -160
- package/dist/contract-space-aggregate-loader-CGakRlKM.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-ByoeQphC.mjs.map +0 -1
- package/dist/migration-plan-jdAHg_gK.mjs.map +0 -1
- package/dist/migrations-B7n518mT.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-UWB2-rrw.d.mts.map +0 -1
package/src/commands/migrate.ts
CHANGED
|
@@ -5,17 +5,15 @@ import { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migrati
|
|
|
5
5
|
import { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';
|
|
6
6
|
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
7
7
|
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
8
|
-
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
9
8
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
10
9
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
11
10
|
import { Command } from 'commander';
|
|
12
|
-
import { join } from 'pathe';
|
|
13
11
|
import { loadConfig } from '../config-loader';
|
|
14
12
|
import { createControlClient } from '../control-api/client';
|
|
15
13
|
import type {
|
|
16
|
-
AggregatePerSpaceExecutionEntry,
|
|
17
14
|
MigrationApplyFailure,
|
|
18
15
|
MigrationApplyPathDecision,
|
|
16
|
+
PerSpaceExecutionEntry,
|
|
19
17
|
} from '../control-api/types';
|
|
20
18
|
import {
|
|
21
19
|
CliStructuredError,
|
|
@@ -35,7 +33,6 @@ import {
|
|
|
35
33
|
import {
|
|
36
34
|
addGlobalOptions,
|
|
37
35
|
collectDeclaredInvariants,
|
|
38
|
-
loadMigrationPackages,
|
|
39
36
|
maskConnectionUrl,
|
|
40
37
|
resolveContractPath,
|
|
41
38
|
resolveMigrationPaths,
|
|
@@ -43,6 +40,12 @@ import {
|
|
|
43
40
|
setCommandExamples,
|
|
44
41
|
targetSupportsMigrations,
|
|
45
42
|
} from '../utils/command-helpers';
|
|
43
|
+
import { mapContractAtError } from '../utils/contract-at-errors';
|
|
44
|
+
import {
|
|
45
|
+
loadContractSpaceAggregateForCli,
|
|
46
|
+
refuseContractSpaceIntegrity,
|
|
47
|
+
} from '../utils/contract-space-aggregate-loader';
|
|
48
|
+
import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
|
|
46
49
|
import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
|
|
47
50
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
48
51
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
@@ -72,7 +75,7 @@ export interface MigrateResult {
|
|
|
72
75
|
readonly operationsExecuted: number;
|
|
73
76
|
}[];
|
|
74
77
|
readonly summary: string;
|
|
75
|
-
readonly perSpace: readonly
|
|
78
|
+
readonly perSpace: readonly PerSpaceExecutionEntry[];
|
|
76
79
|
readonly pathDecision?: MigrationApplyPathDecision;
|
|
77
80
|
readonly timings: {
|
|
78
81
|
readonly total: number;
|
|
@@ -98,8 +101,10 @@ async function executeMigrateCommand(
|
|
|
98
101
|
startTime: number,
|
|
99
102
|
): Promise<Result<MigrateResult, CliStructuredErrorType>> {
|
|
100
103
|
const config = await loadConfig(options.config);
|
|
101
|
-
const { configPath, migrationsDir,
|
|
102
|
-
|
|
104
|
+
const { configPath, migrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
|
|
105
|
+
options.config,
|
|
106
|
+
config,
|
|
107
|
+
);
|
|
103
108
|
|
|
104
109
|
const dbConnection = options.db ?? config.db?.connection;
|
|
105
110
|
if (!dbConnection) {
|
|
@@ -127,31 +132,8 @@ async function executeMigrateCommand(
|
|
|
127
132
|
);
|
|
128
133
|
}
|
|
129
134
|
|
|
130
|
-
let refEntry: RefEntry | undefined;
|
|
131
135
|
const toArg = options.to;
|
|
132
136
|
|
|
133
|
-
if (toArg) {
|
|
134
|
-
try {
|
|
135
|
-
const refs = await readRefs(refsDir);
|
|
136
|
-
const { graph } = await loadMigrationPackages(appMigrationsDir);
|
|
137
|
-
const refResult = parseContractRef(toArg, { graph, refs });
|
|
138
|
-
if (!refResult.ok) {
|
|
139
|
-
return notOk(mapRefResolutionError(refResult.failure));
|
|
140
|
-
}
|
|
141
|
-
if (refResult.value.provenance.kind === 'ref') {
|
|
142
|
-
const resolved = refs[refResult.value.provenance.refName];
|
|
143
|
-
if (resolved) refEntry = resolved;
|
|
144
|
-
} else {
|
|
145
|
-
refEntry = { hash: refResult.value.hash, invariants: [] };
|
|
146
|
-
}
|
|
147
|
-
} catch (error) {
|
|
148
|
-
if (MigrationToolsError.is(error)) {
|
|
149
|
-
return notOk(mapMigrationToolsError(error));
|
|
150
|
-
}
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
137
|
// Construct the family instance up-front so the on-disk contract read
|
|
156
138
|
// crosses the serializer seam (`familyInstance.deserializeContract`) at
|
|
157
139
|
// the read site. The downstream `client.migrationApply({ contract })`
|
|
@@ -193,6 +175,44 @@ async function executeMigrateCommand(
|
|
|
193
175
|
);
|
|
194
176
|
}
|
|
195
177
|
|
|
178
|
+
const loadedAggregate = await loadContractSpaceAggregateForCli({
|
|
179
|
+
targetId: config.target.targetId,
|
|
180
|
+
migrationsDir,
|
|
181
|
+
appContract: contractRaw,
|
|
182
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
183
|
+
deserializeContract: (json) => familyInstance.deserializeContract(json),
|
|
184
|
+
});
|
|
185
|
+
if (!loadedAggregate.ok) {
|
|
186
|
+
return notOk(loadedAggregate.failure);
|
|
187
|
+
}
|
|
188
|
+
const aggregate = loadedAggregate.value;
|
|
189
|
+
const integrityFailure = refuseContractSpaceIntegrity(aggregate, {
|
|
190
|
+
declaredExtensions: toDeclaredExtensionsFromRaw(
|
|
191
|
+
(config.extensionPacks ?? []) as ReadonlyArray<unknown>,
|
|
192
|
+
),
|
|
193
|
+
checkContracts: true,
|
|
194
|
+
});
|
|
195
|
+
if (integrityFailure) {
|
|
196
|
+
return notOk(integrityFailure);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let refEntry: RefEntry | undefined;
|
|
200
|
+
let refName: string | undefined;
|
|
201
|
+
if (toArg) {
|
|
202
|
+
const refs = aggregate.app.refs;
|
|
203
|
+
const refResult = parseContractRef(toArg, { graph: aggregate.app.graph(), refs });
|
|
204
|
+
if (!refResult.ok) {
|
|
205
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
206
|
+
}
|
|
207
|
+
if (refResult.value.provenance.kind === 'ref') {
|
|
208
|
+
refName = refResult.value.provenance.refName;
|
|
209
|
+
const resolved = refs[refName];
|
|
210
|
+
if (resolved) refEntry = resolved;
|
|
211
|
+
} else {
|
|
212
|
+
refEntry = { hash: refResult.value.hash, invariants: [] };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
if (!flags.json && !flags.quiet) {
|
|
197
217
|
const details: Array<{ label: string; value: string }> = [
|
|
198
218
|
{ label: 'config', value: configPath },
|
|
@@ -217,15 +237,7 @@ async function executeMigrateCommand(
|
|
|
217
237
|
ui.stderr(header);
|
|
218
238
|
}
|
|
219
239
|
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
appPackages = await loadMigrationPackages(appMigrationsDir);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
if (MigrationToolsError.is(error)) {
|
|
225
|
-
return notOk(mapMigrationToolsError(error));
|
|
226
|
-
}
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
240
|
+
const appGraph = aggregate.app.graph();
|
|
229
241
|
|
|
230
242
|
const client = createControlClient({
|
|
231
243
|
family: config.family,
|
|
@@ -240,20 +252,19 @@ async function executeMigrateCommand(
|
|
|
240
252
|
|
|
241
253
|
const allMarkers = await client.readAllMarkers();
|
|
242
254
|
const appMarker = allMarkers.get('app') ?? null;
|
|
243
|
-
const { graph } = appPackages;
|
|
244
255
|
|
|
245
|
-
if (appMarker !== null && !isGraphNode(appMarker.storageHash,
|
|
256
|
+
if (appMarker !== null && !isGraphNode(appMarker.storageHash, appGraph)) {
|
|
246
257
|
return notOk(
|
|
247
258
|
errorMarkerMismatch(
|
|
248
259
|
appMarker.storageHash,
|
|
249
|
-
[...
|
|
250
|
-
findLatestMigration(
|
|
260
|
+
[...appGraph.nodes].sort(),
|
|
261
|
+
findLatestMigration(appGraph)?.to ?? null,
|
|
251
262
|
),
|
|
252
263
|
);
|
|
253
264
|
}
|
|
254
265
|
|
|
255
266
|
if (refEntry && refEntry.invariants.length > 0) {
|
|
256
|
-
const declared = collectDeclaredInvariants(
|
|
267
|
+
const declared = collectDeclaredInvariants(appGraph);
|
|
257
268
|
const known = new Set<string>(declared);
|
|
258
269
|
for (const id of appMarker?.invariants ?? []) known.add(id);
|
|
259
270
|
const unknown = refEntry.invariants.filter((id) => !known.has(id));
|
|
@@ -274,10 +285,36 @@ async function executeMigrateCommand(
|
|
|
274
285
|
ui.step('Loading contract spaces…');
|
|
275
286
|
}
|
|
276
287
|
|
|
288
|
+
// When `--to` resolves to an on-disk graph node with a matching bundle,
|
|
289
|
+
// verify and apply against THAT bundle's destination contract via
|
|
290
|
+
// `contractAt` — not the emitted `contract.json`. With `--to` omitted,
|
|
291
|
+
// or a target with no matching bundle, the emitted contract stays the
|
|
292
|
+
// apply contract (the only migrate-specific default). The same
|
|
293
|
+
// `contractAt` artifacts feed the optional ref-advancement snapshot.
|
|
294
|
+
let applyContract: Contract = contractRaw;
|
|
295
|
+
let snapshotContractJson: Record<string, unknown> = JSON.parse(contractContent);
|
|
296
|
+
let snapshotContractDts: string | undefined;
|
|
297
|
+
if (toArg && refEntry) {
|
|
298
|
+
const targetHash = refEntry.hash;
|
|
299
|
+
const matchingBundle = aggregate.app.packages.find((p) => p.metadata.to === targetHash);
|
|
300
|
+
if (matchingBundle) {
|
|
301
|
+
try {
|
|
302
|
+
const at = await aggregate.app.contractAt(
|
|
303
|
+
targetHash,
|
|
304
|
+
refName !== undefined ? { refName } : undefined,
|
|
305
|
+
);
|
|
306
|
+
applyContract = at.contract;
|
|
307
|
+
snapshotContractJson = at.contractJson as Record<string, unknown>;
|
|
308
|
+
snapshotContractDts = at.contractDts;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return mapContractAtError(error, { artifactRole: 'to' });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
277
315
|
const applyResult = await client.migrationApply({
|
|
278
|
-
contract:
|
|
316
|
+
contract: applyContract,
|
|
279
317
|
migrationsDir,
|
|
280
|
-
appMigrationPackages: appPackages.bundles,
|
|
281
318
|
...ifDefined('refHash', refEntry?.hash),
|
|
282
319
|
...(refEntry?.invariants ? { refInvariants: refEntry.invariants } : {}),
|
|
283
320
|
...(refEntry !== undefined ? ifDefined('refName', toArg) : {}),
|
|
@@ -291,37 +328,11 @@ async function executeMigrateCommand(
|
|
|
291
328
|
|
|
292
329
|
let advancedRef: { name: string; hash: string } | null = null;
|
|
293
330
|
if (options.advanceRef !== undefined) {
|
|
294
|
-
let contractJsonPathForSnapshot = contractPathAbsolute;
|
|
295
|
-
let contractJsonForSnapshot: Record<string, unknown> = JSON.parse(contractContent) as Record<
|
|
296
|
-
string,
|
|
297
|
-
unknown
|
|
298
|
-
>;
|
|
299
|
-
if (toArg && refEntry) {
|
|
300
|
-
const matchingBundle = appPackages.bundles.find((p) => p.metadata.to === refEntry.hash);
|
|
301
|
-
if (matchingBundle) {
|
|
302
|
-
const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
|
|
303
|
-
contractJsonPathForSnapshot = endContractPath;
|
|
304
|
-
try {
|
|
305
|
-
const raw = await readFile(endContractPath, 'utf-8');
|
|
306
|
-
contractJsonForSnapshot = JSON.parse(raw) as Record<string, unknown>;
|
|
307
|
-
} catch (error) {
|
|
308
|
-
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
309
|
-
return notOk(
|
|
310
|
-
errorFileNotFound(endContractPath, {
|
|
311
|
-
why: `Bundle end-contract not found at ${endContractPath}`,
|
|
312
|
-
fix: 'Re-emit the migration bundle or pick a different --to target.',
|
|
313
|
-
}),
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
throw error;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
331
|
try {
|
|
321
|
-
const contractIR =
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
332
|
+
const contractIR =
|
|
333
|
+
snapshotContractDts !== undefined
|
|
334
|
+
? { contract: snapshotContractJson, contractDts: snapshotContractDts }
|
|
335
|
+
: await readContractIR(snapshotContractJson, contractPathAbsolute);
|
|
325
336
|
advancedRef = await executeRefAdvancement(
|
|
326
337
|
refsDir,
|
|
327
338
|
options.advanceRef,
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
4
|
+
import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';
|
|
5
|
+
import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
|
|
3
6
|
import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
7
|
+
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
8
|
+
import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
|
|
4
9
|
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
5
10
|
import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
6
11
|
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
@@ -9,15 +14,20 @@ import { join, relative } from 'pathe';
|
|
|
9
14
|
import { loadConfig } from '../config-loader';
|
|
10
15
|
import {
|
|
11
16
|
addGlobalOptions,
|
|
12
|
-
|
|
17
|
+
resolveContractPath,
|
|
13
18
|
resolveMigrationPaths,
|
|
14
19
|
setCommandDescriptions,
|
|
15
20
|
setCommandExamples,
|
|
16
21
|
setCommandSeeAlso,
|
|
17
22
|
} from '../utils/command-helpers';
|
|
23
|
+
import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
|
|
18
24
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
19
25
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
20
26
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
27
|
+
import {
|
|
28
|
+
type CheckFailure,
|
|
29
|
+
integrityViolationToCheckFailure,
|
|
30
|
+
} from '../utils/integrity-violation-to-check-failure';
|
|
21
31
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
22
32
|
import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
|
|
23
33
|
|
|
@@ -25,12 +35,7 @@ interface MigrationCheckOptions extends CommonCommandOptions {
|
|
|
25
35
|
readonly config?: string;
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
export
|
|
29
|
-
readonly pnCode: string;
|
|
30
|
-
readonly where: string;
|
|
31
|
-
readonly why: string;
|
|
32
|
-
readonly fix: string;
|
|
33
|
-
}
|
|
38
|
+
export type { CheckFailure } from '../utils/integrity-violation-to-check-failure';
|
|
34
39
|
|
|
35
40
|
export interface MigrationCheckResult {
|
|
36
41
|
readonly ok: boolean;
|
|
@@ -38,11 +43,6 @@ export interface MigrationCheckResult {
|
|
|
38
43
|
readonly summary: string;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
/**
|
|
42
|
-
* Canonical user-facing locator for a check failure: the cwd-relative path
|
|
43
|
-
* to the migration package directory. Surfacing the same shape across every
|
|
44
|
-
* PN code means `--json` consumers can branch uniformly on `where`.
|
|
45
|
-
*/
|
|
46
46
|
function migrationPathRelative(dirPath: string): string {
|
|
47
47
|
return relative(process.cwd(), dirPath);
|
|
48
48
|
}
|
|
@@ -63,21 +63,6 @@ function checkFileExists(dirPath: string, dirName: string, fileName: string): Ch
|
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* Within-migration snapshot-consistency check (PN-MIG-CHECK-005).
|
|
68
|
-
*
|
|
69
|
-
* Compares the migration's stored `metadata.to` against the `storageHash`
|
|
70
|
-
* recorded in its on-disk `end-contract.json` snapshot. The two values are
|
|
71
|
-
* independent on-disk records of the same fact (the migration's destination
|
|
72
|
-
* contract); drift between them indicates the package is internally
|
|
73
|
-
* corrupt. Cross-migration consistency (one migration's end-contract.json
|
|
74
|
-
* agreeing with the next migration's start-contract.json) is a separate
|
|
75
|
-
* check that requires shadow execution and is deferred to
|
|
76
|
-
* `migration preflight`.
|
|
77
|
-
*
|
|
78
|
-
* Shared between the graph-wide and per-migration code paths so both report
|
|
79
|
-
* the same failure for the same on-disk state.
|
|
80
|
-
*/
|
|
81
66
|
function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | null {
|
|
82
67
|
const endContractPath = join(pkg.dirPath, 'end-contract.json');
|
|
83
68
|
if (!existsSync(endContractPath)) return null;
|
|
@@ -104,6 +89,27 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
|
|
|
104
89
|
return null;
|
|
105
90
|
}
|
|
106
91
|
|
|
92
|
+
async function loadAggregateIntegrityViolations(
|
|
93
|
+
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
94
|
+
migrationsDir: string,
|
|
95
|
+
): Promise<readonly IntegrityViolation[]> {
|
|
96
|
+
try {
|
|
97
|
+
const contractJsonContent = await readFile(resolveContractPath(config), 'utf-8');
|
|
98
|
+
const familyInstance = config.family.create(createControlStack(config));
|
|
99
|
+
const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);
|
|
100
|
+
|
|
101
|
+
const parsedAppContract: unknown = JSON.parse(contractJsonContent);
|
|
102
|
+
const aggregate = await loadContractSpaceAggregate({
|
|
103
|
+
migrationsDir,
|
|
104
|
+
deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
|
|
105
|
+
appContract: familyInstance.deserializeContract(parsedAppContract),
|
|
106
|
+
});
|
|
107
|
+
return aggregate.checkIntegrity({ declaredExtensions, checkContracts: true });
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
107
113
|
async function executeMigrationCheckCommand(
|
|
108
114
|
target: string | undefined,
|
|
109
115
|
options: MigrationCheckOptions,
|
|
@@ -111,10 +117,8 @@ async function executeMigrationCheckCommand(
|
|
|
111
117
|
ui: TerminalUI,
|
|
112
118
|
): Promise<{ result: MigrationCheckResult; exitCode: number }> {
|
|
113
119
|
const config = await loadConfig(options.config);
|
|
114
|
-
const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } =
|
|
115
|
-
options.config,
|
|
116
|
-
config,
|
|
117
|
-
);
|
|
120
|
+
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
|
|
121
|
+
resolveMigrationPaths(options.config, config);
|
|
118
122
|
|
|
119
123
|
if (!flags.json && !flags.quiet) {
|
|
120
124
|
const details: Array<{ label: string; value: string }> = [
|
|
@@ -135,37 +139,9 @@ async function executeMigrationCheckCommand(
|
|
|
135
139
|
|
|
136
140
|
const failures: CheckFailure[] = [];
|
|
137
141
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const loaded = await loadMigrationPackages(appMigrationsDir);
|
|
142
|
-
bundles = loaded.bundles;
|
|
143
|
-
graph = loaded.graph;
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (MigrationToolsError.is(error)) {
|
|
146
|
-
const pnCode =
|
|
147
|
-
error.code === 'MIGRATION.HASH_MISMATCH' ? 'PN-MIG-CHECK-001' : 'PN-MIG-CHECK-002';
|
|
148
|
-
// Normalise to a cwd-relative path. `error.details.dir` is absolute
|
|
149
|
-
// (the migration-tools layer doesn't know the caller's cwd); the
|
|
150
|
-
// `filePath` fallback is also absolute. Surfacing the relative form
|
|
151
|
-
// matches the rest of the command's `where` shape and keeps `--json`
|
|
152
|
-
// consumers from having to special-case the bootstrap-failure path.
|
|
153
|
-
const rawWhere =
|
|
154
|
-
(error.details?.['dir'] as string) ?? (error.details?.['filePath'] as string) ?? null;
|
|
155
|
-
const where = rawWhere ? relative(process.cwd(), rawWhere) : 'unknown';
|
|
156
|
-
failures.push({
|
|
157
|
-
pnCode,
|
|
158
|
-
where,
|
|
159
|
-
why: error.why,
|
|
160
|
-
fix: error.fix,
|
|
161
|
-
});
|
|
162
|
-
return {
|
|
163
|
-
result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
|
|
164
|
-
exitCode: INTEGRITY_FAILED,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
142
|
+
const loaded = await readMigrationsDir(appMigrationsDir);
|
|
143
|
+
const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
|
|
144
|
+
const graph = reconstructGraph(bundles);
|
|
169
145
|
|
|
170
146
|
if (existsSync(appMigrationsDir)) {
|
|
171
147
|
const loadedDirNames = new Set(bundles.map((p) => p.dirName));
|
|
@@ -236,29 +212,9 @@ async function executeMigrationCheckCommand(
|
|
|
236
212
|
});
|
|
237
213
|
}
|
|
238
214
|
|
|
239
|
-
// PN-MIG-CHECK-005 must fire per-migration as well as graph-wide; both
|
|
240
|
-
// call sites delegate to the shared helper so the same on-disk drift
|
|
241
|
-
// produces the same failure regardless of how the user invoked check.
|
|
242
215
|
const snapshotFailure = checkSnapshotConsistency(matchedPkg);
|
|
243
216
|
if (snapshotFailure) failures.push(snapshotFailure);
|
|
244
217
|
} else {
|
|
245
|
-
for (const pkg of bundles) {
|
|
246
|
-
for (const f of ['migration.json', 'ops.json']) {
|
|
247
|
-
const fail = checkFileExists(pkg.dirPath, pkg.dirName, f);
|
|
248
|
-
if (fail) failures.push(fail);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const verification = verifyMigrationHash(pkg);
|
|
252
|
-
if (!verification.ok) {
|
|
253
|
-
failures.push({
|
|
254
|
-
pnCode: 'PN-MIG-CHECK-001',
|
|
255
|
-
where: migrationFileRelative(pkg.dirPath, 'migration.json'),
|
|
256
|
-
why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
|
|
257
|
-
fix: 'Re-emit the migration package or restore from version control.',
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
218
|
for (const pkg of bundles) {
|
|
263
219
|
const snapshotFailure = checkSnapshotConsistency(pkg);
|
|
264
220
|
if (snapshotFailure) failures.push(snapshotFailure);
|
|
@@ -295,6 +251,10 @@ async function executeMigrationCheckCommand(
|
|
|
295
251
|
} catch {
|
|
296
252
|
// Refs unreadable — skip ref checks
|
|
297
253
|
}
|
|
254
|
+
|
|
255
|
+
for (const violation of await loadAggregateIntegrityViolations(config, migrationsDir)) {
|
|
256
|
+
failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
|
|
257
|
+
}
|
|
298
258
|
}
|
|
299
259
|
|
|
300
260
|
if (failures.length === 0) {
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
2
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
3
2
|
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
4
|
-
import {
|
|
5
|
-
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
3
|
+
import { ok, type Result } from '@prisma-next/utils/result';
|
|
6
4
|
import { Command } from 'commander';
|
|
7
5
|
import { loadConfig } from '../config-loader';
|
|
8
|
-
import {
|
|
9
|
-
type CliStructuredError,
|
|
10
|
-
errorUnexpected,
|
|
11
|
-
mapMigrationToolsError,
|
|
12
|
-
} from '../utils/cli-errors';
|
|
6
|
+
import type { CliStructuredError } from '../utils/cli-errors';
|
|
13
7
|
import {
|
|
14
8
|
addGlobalOptions,
|
|
15
|
-
loadMigrationPackages,
|
|
16
|
-
readContractEnvelope,
|
|
17
9
|
resolveMigrationPaths,
|
|
18
10
|
setCommandDescriptions,
|
|
19
11
|
setCommandExamples,
|
|
20
12
|
setCommandSeeAlso,
|
|
21
13
|
} from '../utils/command-helpers';
|
|
14
|
+
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
22
15
|
import { migrationGraphToRenderInput } from '../utils/formatters/graph-migration-mapper';
|
|
23
16
|
import { graphRenderer } from '../utils/formatters/graph-render';
|
|
17
|
+
import { buildMigrationGraphLayout } from '../utils/formatters/migration-graph-layout';
|
|
18
|
+
import { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';
|
|
19
|
+
import { renderMigrationGraphTree } from '../utils/formatters/migration-graph-tree-render';
|
|
24
20
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
25
21
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
26
22
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
@@ -31,6 +27,8 @@ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
|
31
27
|
interface MigrationGraphOptions extends CommonCommandOptions {
|
|
32
28
|
readonly config?: string;
|
|
33
29
|
readonly dot?: boolean;
|
|
30
|
+
readonly tree?: boolean;
|
|
31
|
+
readonly ascii?: boolean;
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
export interface MigrationGraphResult {
|
|
@@ -41,13 +39,13 @@ export interface MigrationGraphResult {
|
|
|
41
39
|
readonly summary: string;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
async function executeMigrationGraphCommand(
|
|
42
|
+
export async function executeMigrationGraphCommand(
|
|
45
43
|
options: MigrationGraphOptions,
|
|
46
44
|
flags: GlobalFlags,
|
|
47
45
|
ui: TerminalUI,
|
|
48
46
|
): Promise<Result<MigrationGraphResult, CliStructuredError>> {
|
|
49
47
|
const config = await loadConfig(options.config);
|
|
50
|
-
const { configPath,
|
|
48
|
+
const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(
|
|
51
49
|
options.config,
|
|
52
50
|
config,
|
|
53
51
|
);
|
|
@@ -65,37 +63,18 @@ async function executeMigrationGraphCommand(
|
|
|
65
63
|
ui.stderr(header);
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} catch (error) {
|
|
72
|
-
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
73
|
-
return notOk(
|
|
74
|
-
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
75
|
-
why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
|
|
76
|
-
}),
|
|
77
|
-
);
|
|
66
|
+
const loaded = await buildReadAggregate(config, { migrationsDir });
|
|
67
|
+
if (!loaded.ok) {
|
|
68
|
+
return loaded;
|
|
78
69
|
}
|
|
79
70
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let refs: readonly StatusRef[] = [];
|
|
89
|
-
try {
|
|
90
|
-
const allRefs = await readRefs(refsDir);
|
|
91
|
-
refs = Object.entries(allRefs).map(([name, entry]) => ({
|
|
92
|
-
name,
|
|
93
|
-
hash: entry.hash,
|
|
94
|
-
active: false,
|
|
95
|
-
}));
|
|
96
|
-
} catch {
|
|
97
|
-
// Refs unreadable — render graph without ref markers
|
|
98
|
-
}
|
|
71
|
+
const { aggregate, contractHash } = loaded.value;
|
|
72
|
+
const graph = aggregate.app.graph();
|
|
73
|
+
const refs: readonly StatusRef[] = Object.entries(aggregate.app.refs).map(([name, entry]) => ({
|
|
74
|
+
name,
|
|
75
|
+
hash: entry.hash,
|
|
76
|
+
active: false,
|
|
77
|
+
}));
|
|
99
78
|
|
|
100
79
|
return ok({
|
|
101
80
|
ok: true,
|
|
@@ -111,14 +90,18 @@ export function createMigrationGraphCommand(): Command {
|
|
|
111
90
|
setCommandDescriptions(
|
|
112
91
|
command,
|
|
113
92
|
'Show the migration graph topology',
|
|
114
|
-
'Renders the migration graph
|
|
115
|
-
'
|
|
116
|
-
'--
|
|
93
|
+
'Renders the migration graph topology. Offline — does not consult\n' +
|
|
94
|
+
'the database. Use --tree for the condensed annotated tree\n' +
|
|
95
|
+
'(--ascii swaps box-drawing for pipe-friendly ASCII glyphs),\n' +
|
|
96
|
+
'--json for machine-readable output, or --dot for Graphviz DOT\n' +
|
|
97
|
+
'format.',
|
|
117
98
|
);
|
|
118
99
|
setCommandExamples(command, [
|
|
119
100
|
'prisma-next migration graph',
|
|
120
101
|
'prisma-next migration graph --json',
|
|
121
102
|
'prisma-next migration graph --dot',
|
|
103
|
+
'prisma-next migration graph --tree',
|
|
104
|
+
'prisma-next migration graph --tree --ascii',
|
|
122
105
|
]);
|
|
123
106
|
setCommandSeeAlso(command, [
|
|
124
107
|
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
@@ -129,6 +112,8 @@ export function createMigrationGraphCommand(): Command {
|
|
|
129
112
|
addGlobalOptions(command)
|
|
130
113
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
131
114
|
.option('--dot', 'Output in Graphviz DOT format')
|
|
115
|
+
.option('--tree', 'Experimental condensed annotated tree renderer')
|
|
116
|
+
.option('--ascii', 'Use ASCII glyphs for --tree (pipe-friendly)')
|
|
132
117
|
.action(async (options: MigrationGraphOptions) => {
|
|
133
118
|
const flags = parseGlobalFlagsOrExit(options);
|
|
134
119
|
const ui = createTerminalUI(flags);
|
|
@@ -160,22 +145,52 @@ export function createMigrationGraphCommand(): Command {
|
|
|
160
145
|
JSON.stringify({ ok: true, nodes, edges, summary: graphResult.summary }, null, 2),
|
|
161
146
|
);
|
|
162
147
|
} else if (!flags.quiet) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
148
|
+
if (options.tree) {
|
|
149
|
+
const refsByHash = new Map<string, string[]>();
|
|
150
|
+
for (const ref of graphResult.refs) {
|
|
151
|
+
const existing = refsByHash.get(ref.hash);
|
|
152
|
+
refsByHash.set(ref.hash, existing ? [...existing, ref.name] : [ref.name]);
|
|
153
|
+
}
|
|
154
|
+
const rowModel = buildMigrationGraphRows(graphResult.graph, {
|
|
155
|
+
...(graphResult.contractHash !== null
|
|
156
|
+
? { contractHash: graphResult.contractHash }
|
|
157
|
+
: {}),
|
|
158
|
+
});
|
|
159
|
+
const layout = buildMigrationGraphLayout(rowModel);
|
|
160
|
+
const activeRef = graphResult.refs.find((ref) => ref.active);
|
|
161
|
+
const treeOutput = renderMigrationGraphTree(layout, {
|
|
162
|
+
refsByHash,
|
|
163
|
+
...(graphResult.contractHash !== null
|
|
164
|
+
? { contractHash: graphResult.contractHash }
|
|
165
|
+
: {}),
|
|
166
|
+
...(activeRef !== undefined ? { activeRefName: activeRef.name } : {}),
|
|
167
|
+
colorize: flags.color !== false,
|
|
168
|
+
glyphMode: ui.resolveGlyphMode(options.ascii === true),
|
|
169
|
+
});
|
|
170
|
+
// Emit the rendered tree to stdout (same stream as flat `migration list`),
|
|
171
|
+
// not through clack's `log.message` rail: the graph is the command's
|
|
172
|
+
// result (and its own box-drawing is the only vertical structure it
|
|
173
|
+
// should carry), not a status line that needs the prompt gutter.
|
|
174
|
+
ui.output(treeOutput);
|
|
175
|
+
ui.output(`\n${graphResult.summary}`);
|
|
176
|
+
} else {
|
|
177
|
+
const renderInput = migrationGraphToRenderInput({
|
|
178
|
+
graph: graphResult.graph,
|
|
179
|
+
mode: 'offline',
|
|
180
|
+
markerHash: undefined,
|
|
181
|
+
contractHash: graphResult.contractHash ?? EMPTY_CONTRACT_HASH,
|
|
182
|
+
refs: graphResult.refs,
|
|
183
|
+
activeRefHash: undefined,
|
|
184
|
+
activeRefName: undefined,
|
|
185
|
+
edgeStatuses: [],
|
|
186
|
+
});
|
|
187
|
+
const graphOutput = graphRenderer.render(renderInput.graph, {
|
|
188
|
+
...renderInput.options,
|
|
189
|
+
colorize: flags.color !== false,
|
|
190
|
+
});
|
|
191
|
+
ui.log(graphOutput);
|
|
192
|
+
ui.log(`\n${graphResult.summary}`);
|
|
193
|
+
}
|
|
179
194
|
}
|
|
180
195
|
});
|
|
181
196
|
process.exit(exitCode);
|