@prisma-next/cli 0.12.0-dev.3 → 0.12.0-dev.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +180 -163
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-KgJorIvG.mjs → client-xeWpMlq1.mjs} +39 -15
- package/dist/client-xeWpMlq1.mjs.map +1 -0
- package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-DK_5ItoJ.mjs} +284 -24
- package/dist/command-helpers-DK_5ItoJ.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +4 -5
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +10 -7
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.mjs +5 -6
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +13 -7
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +171 -2
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +24 -26
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +2 -190
- package/dist/commands/migration-log.d.mts +6 -18
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +1 -137
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.mjs +3 -4
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +41 -141
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -759
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +3 -3
- package/dist/commands/telemetry/index.d.mts +7 -0
- package/dist/commands/telemetry/index.d.mts.map +1 -0
- package/dist/commands/telemetry/index.mjs +2 -0
- package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-DG3kjgoz.mjs} +2 -2
- package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-DG3kjgoz.mjs.map} +1 -1
- package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-BO0l6fnT.mjs} +3 -3
- package/dist/{contract-emit-D-4jrNve.mjs.map → contract-emit-BO0l6fnT.mjs.map} +1 -1
- package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-C0Bs0VRj.mjs} +3 -3
- package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-C0Bs0VRj.mjs.map} +1 -1
- package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-2wtPflGH.mjs} +3 -3
- package/dist/{contract-infer-D8uEbJuu.mjs.map → contract-infer-2wtPflGH.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-Dbr3-jHF.mjs} +4 -4
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-Dbr3-jHF.mjs.map} +1 -1
- package/dist/{db-verify-v_vUKXTU.mjs → db-verify-CxHiSiTG.mjs} +4 -4
- package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-CxHiSiTG.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 +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-fYXjz_in.mjs → framework-components-CxOVKAAh.mjs} +2 -2
- package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-CxOVKAAh.mjs.map} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
- package/dist/{init-Cv9UzWL5.mjs → init-R272pxux.mjs} +5 -58
- package/dist/init-R272pxux.mjs.map +1 -0
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-RekOwfi5.mjs} +3 -3
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs.map → inspect-live-schema-RekOwfi5.mjs.map} +1 -1
- package/dist/{migration-check-BiBJoYYW.mjs → migration-check-Dc0cOhKH.mjs} +2 -2
- package/dist/{migration-check-BiBJoYYW.mjs.map → migration-check-Dc0cOhKH.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-ApB3NxWY.mjs} +3 -3
- package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-ApB3NxWY.mjs.map} +1 -1
- package/dist/migration-graph-space-render-dmLLWift.mjs +1966 -0
- package/dist/migration-graph-space-render-dmLLWift.mjs.map +1 -0
- package/dist/migration-list-C5sXrl0U.mjs +228 -0
- package/dist/migration-list-C5sXrl0U.mjs.map +1 -0
- package/dist/migration-list-types-DS63IdFd.d.mts +23 -0
- package/dist/migration-list-types-DS63IdFd.d.mts.map +1 -0
- package/dist/migration-log-DD_vCbYW.mjs +203 -0
- package/dist/migration-log-DD_vCbYW.mjs.map +1 -0
- package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-CeTjQOIG.mjs} +5 -5
- package/dist/{migration-plan-9DJ7q7_z.mjs.map → migration-plan-CeTjQOIG.mjs.map} +1 -1
- package/dist/migration-status-qV8ctwPy.mjs +432 -0
- package/dist/migration-status-qV8ctwPy.mjs.map +1 -0
- package/dist/{output-B60Gw5fu.mjs → output-CF_hqzI-.mjs} +1 -1
- package/dist/{output-B60Gw5fu.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-V1o-9LVK.mjs} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-V1o-9LVK.mjs.map} +1 -1
- package/dist/telemetry-S-NGi9U6.mjs +122 -0
- package/dist/telemetry-S-NGi9U6.mjs.map +1 -0
- package/dist/{types-Dt_SfqFm.d.mts → types-Mh7mdPHM.d.mts} +13 -2
- package/dist/types-Mh7mdPHM.d.mts.map +1 -0
- package/dist/{verify-DCA9Sldu.mjs → verify-BdI-BgYi.mjs} +2 -2
- package/dist/{verify-DCA9Sldu.mjs.map → verify-BdI-BgYi.mjs.map} +1 -1
- package/package.json +22 -19
- package/src/cli.ts +5 -0
- package/src/commands/db-update.ts +7 -1
- package/src/commands/init/index.ts +6 -35
- package/src/commands/init/init.ts +1 -14
- package/src/commands/init/inputs.ts +0 -75
- package/src/commands/migration-graph.ts +143 -82
- package/src/commands/migration-list.ts +55 -25
- package/src/commands/migration-log.ts +23 -89
- package/src/commands/migration-status-overlay.ts +61 -0
- package/src/commands/migration-status.ts +431 -1055
- package/src/commands/telemetry/index.ts +107 -0
- package/src/commands/telemetry/status.ts +67 -0
- package/src/control-api/client.ts +11 -1
- package/src/control-api/operations/apply.ts +1 -0
- package/src/control-api/operations/db-apply.ts +24 -1
- package/src/control-api/operations/migration-apply.ts +10 -3
- package/src/control-api/types.ts +16 -1
- package/src/utils/cli-errors.ts +17 -0
- package/src/utils/formatters/errors.ts +11 -0
- package/src/utils/formatters/migration-graph-lane-colors.ts +194 -0
- package/src/utils/formatters/migration-graph-layout.ts +51 -7
- package/src/utils/formatters/migration-graph-rows.ts +128 -15
- package/src/utils/formatters/migration-graph-space-render.ts +138 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +405 -77
- package/src/utils/formatters/migration-list-data-column.ts +4 -91
- package/src/utils/formatters/migration-list-graph-topology.ts +68 -90
- package/src/utils/formatters/migration-list-render.ts +122 -70
- package/src/utils/formatters/migration-list-styler.ts +48 -5
- package/src/utils/formatters/migration-log-table.ts +190 -0
- package/src/utils/formatters/migrations.ts +25 -1
- package/src/utils/global-flags.ts +35 -0
- package/src/utils/legend.ts +38 -0
- package/src/utils/telemetry.ts +68 -32
- package/dist/client-KgJorIvG.mjs.map +0 -1
- package/dist/command-helpers-Bbw1GbwL.mjs.map +0 -1
- package/dist/commands/migration-list.mjs.map +0 -1
- package/dist/commands/migration-log.mjs.map +0 -1
- package/dist/commands/migration-status.mjs.map +0 -1
- package/dist/graph-render-rFAqZujX.mjs +0 -1081
- package/dist/graph-render-rFAqZujX.mjs.map +0 -1
- package/dist/init-Cv9UzWL5.mjs.map +0 -1
- package/dist/migration-graph-D7DVUElV.mjs +0 -1232
- package/dist/migration-graph-D7DVUElV.mjs.map +0 -1
- package/dist/migration-list-styler-BRwF4-gy.mjs +0 -399
- package/dist/migration-list-styler-BRwF4-gy.mjs.map +0 -1
- package/dist/migration-types-D2FW63pr.d.mts +0 -15
- package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
- package/dist/migrations-Cv2jxNNK.mjs +0 -228
- package/dist/migrations-Cv2jxNNK.mjs.map +0 -1
- package/dist/types-Dt_SfqFm.d.mts.map +0 -1
- package/src/utils/formatters/graph-migration-mapper.ts +0 -235
- package/src/utils/formatters/graph-render.ts +0 -1323
- package/src/utils/formatters/graph-types.ts +0 -120
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import * as clack from '@clack/prompts';
|
|
3
|
-
import { readUserConfig, resolveGating, writeUserConfig } from '@prisma-next/cli-telemetry';
|
|
4
3
|
import { extname, join, normalize } from 'pathe';
|
|
5
4
|
import type { GlobalFlags } from '../../utils/global-flags';
|
|
6
|
-
import { isCI } from '../../utils/is-ci';
|
|
7
5
|
import {
|
|
8
6
|
errorInitAuthoringSchemaPathMismatch,
|
|
9
7
|
errorInitInvalidFlagValue,
|
|
@@ -71,18 +69,6 @@ export interface ResolvedInitInputs {
|
|
|
71
69
|
* is added separately via the install step.
|
|
72
70
|
*/
|
|
73
71
|
readonly removePreviousFacade: string | null;
|
|
74
|
-
/**
|
|
75
|
-
* Telemetry consent answer recorded during this `init` run, or `null`
|
|
76
|
-
* when no prompt was shown. The prompt fires only on the
|
|
77
|
-
* canPrompt + !autoAcceptPrompts + no env/CI opt-out +
|
|
78
|
-
* `enableTelemetry === undefined` intersection; outside that window
|
|
79
|
-
* the field is `null` and the stored preference (if any) stays
|
|
80
|
-
* unchanged. The answer has already been persisted to
|
|
81
|
-
* `$XDG_CONFIG_HOME/prisma-next/config.json` by
|
|
82
|
-
* the time `runInit` sees this value — it's surfaced here purely so
|
|
83
|
-
* the post-init summary can mention what the user chose.
|
|
84
|
-
*/
|
|
85
|
-
readonly enableTelemetry: boolean | null;
|
|
86
72
|
/**
|
|
87
73
|
* Whether to run `npx skills add prisma/prisma-next#v<version>` at the
|
|
88
74
|
* project level after install + emit. True by default; `--no-skill`
|
|
@@ -106,12 +92,6 @@ const AUTHORING_VALUES: ReadonlyMap<string, AuthoringId> = new Map([
|
|
|
106
92
|
['ts', 'typescript'],
|
|
107
93
|
]);
|
|
108
94
|
|
|
109
|
-
export const TELEMETRY_CONSENT_MESSAGE = [
|
|
110
|
-
'Help us prioritize features by sharing anonymous CLI usage data?',
|
|
111
|
-
'The telemetry implementation is open source and fully transparent.',
|
|
112
|
-
'(packages/1-framework/3-tooling/cli-telemetry and apps/telemetry-backend).',
|
|
113
|
-
].join(' ');
|
|
114
|
-
|
|
115
95
|
/**
|
|
116
96
|
* Resolves every required input for `runInit`. In interactive mode, missing
|
|
117
97
|
* inputs are prompted via clack; in non-interactive mode, missing required
|
|
@@ -197,11 +177,6 @@ export async function resolveInitInputs(ctx: {
|
|
|
197
177
|
autoAcceptPrompts,
|
|
198
178
|
});
|
|
199
179
|
|
|
200
|
-
const enableTelemetry = await resolveTelemetryConsent({
|
|
201
|
-
canPrompt,
|
|
202
|
-
autoAcceptPrompts,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
180
|
// Skill-install gating. `--no-skill` (commander parses
|
|
206
181
|
// `options.skill === false`) is the only escape hatch; otherwise
|
|
207
182
|
// project-level install is unconditional. The skill is always
|
|
@@ -219,60 +194,10 @@ export async function resolveInitInputs(ctx: {
|
|
|
219
194
|
strictProbe: Boolean(options.strictProbe),
|
|
220
195
|
reinit,
|
|
221
196
|
removePreviousFacade,
|
|
222
|
-
enableTelemetry,
|
|
223
197
|
installProjectSkill,
|
|
224
198
|
};
|
|
225
199
|
}
|
|
226
200
|
|
|
227
|
-
/**
|
|
228
|
-
* The interactive telemetry consent prompt. Shown as the last
|
|
229
|
-
* question of the `init` sequence iff:
|
|
230
|
-
* 1. `canPrompt === true` (interactive stdin / stdout combo),
|
|
231
|
-
* 2. `autoAcceptPrompts === false` (the user did not pass `--yes`),
|
|
232
|
-
* 3. neither telemetry env opt-out is active,
|
|
233
|
-
* 4. the process is not running in CI,
|
|
234
|
-
* 5. the stored `enableTelemetry` value is `undefined` (the user
|
|
235
|
-
* has never been asked, or skipped the prompt previously).
|
|
236
|
-
*
|
|
237
|
-
* Outside that intersection the function returns `null` and leaves the
|
|
238
|
-
* stored preference untouched. Inside it, the user's answer is
|
|
239
|
-
* persisted via `writeUserConfig({ enableTelemetry })` before this
|
|
240
|
-
* function returns; on an affirmative answer `writeUserConfig`
|
|
241
|
-
* generates and stores the v4 `installationId` in the same write.
|
|
242
|
-
*
|
|
243
|
-
* The wording names CLI usage data and points to the open-source
|
|
244
|
-
* client/backend paths. Default value is `true` to match precedent
|
|
245
|
-
* and to make the consent answer a single keystroke once it's been
|
|
246
|
-
* disclosed.
|
|
247
|
-
*/
|
|
248
|
-
async function resolveTelemetryConsent(opts: {
|
|
249
|
-
readonly canPrompt: boolean;
|
|
250
|
-
readonly autoAcceptPrompts: boolean;
|
|
251
|
-
}): Promise<boolean | null> {
|
|
252
|
-
if (!opts.canPrompt || opts.autoAcceptPrompts || isCI()) {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
const config = readUserConfig();
|
|
256
|
-
const gating = resolveGating({ env: process.env, config });
|
|
257
|
-
if (!gating.enabled && gating.reason === 'env-override') {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
const stored = config.enableTelemetry;
|
|
261
|
-
if (stored !== undefined) {
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
const result = await clack.confirm({
|
|
265
|
-
message: TELEMETRY_CONSENT_MESSAGE,
|
|
266
|
-
initialValue: true,
|
|
267
|
-
output: process.stderr,
|
|
268
|
-
});
|
|
269
|
-
if (clack.isCancel(result)) {
|
|
270
|
-
throw errorInitUserAborted();
|
|
271
|
-
}
|
|
272
|
-
writeUserConfig({ enableTelemetry: Boolean(result) });
|
|
273
|
-
return Boolean(result);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
201
|
async function resolveWriteEnv(opts: {
|
|
277
202
|
readonly flag: boolean | undefined;
|
|
278
203
|
readonly canPrompt: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
2
1
|
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
2
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
3
|
import { ok, type Result } from '@prisma-next/utils/result';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { loadConfig } from '../config-loader';
|
|
@@ -12,40 +12,75 @@ import {
|
|
|
12
12
|
setCommandSeeAlso,
|
|
13
13
|
} from '../utils/command-helpers';
|
|
14
14
|
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
import {
|
|
16
|
+
computeGlobalMaxDirNameWidth,
|
|
17
|
+
computeGlobalMaxEdgeTreePrefixWidth,
|
|
18
|
+
indentMigrationGraphTreeBlock,
|
|
19
|
+
renderMigrationGraphSpaceTree,
|
|
20
|
+
} from '../utils/formatters/migration-graph-space-render';
|
|
21
|
+
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-tree-render';
|
|
20
22
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
21
23
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
22
24
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
23
|
-
import
|
|
25
|
+
import { shouldShowLegend, validateLegendOptions } from '../utils/legend';
|
|
24
26
|
import { handleResult } from '../utils/result-handler';
|
|
25
27
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
28
|
+
import {
|
|
29
|
+
listRefsByContractHash,
|
|
30
|
+
migrationSpaceListEntriesFromAggregate,
|
|
31
|
+
runMigrationList,
|
|
32
|
+
} from './migration-list';
|
|
26
33
|
|
|
27
34
|
interface MigrationGraphOptions extends CommonCommandOptions {
|
|
28
35
|
readonly config?: string;
|
|
29
36
|
readonly dot?: boolean;
|
|
30
|
-
readonly
|
|
37
|
+
readonly space?: string;
|
|
31
38
|
readonly ascii?: boolean;
|
|
39
|
+
readonly legend?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MigrationGraphTreeSection {
|
|
43
|
+
readonly spaceId: string;
|
|
44
|
+
readonly tree: string;
|
|
45
|
+
readonly showHeading: boolean;
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
export interface MigrationGraphResult {
|
|
35
49
|
readonly ok: true;
|
|
50
|
+
/** App-space graph for `--json` / `--dot` (unchanged machine output). */
|
|
36
51
|
readonly graph: MigrationGraph;
|
|
37
|
-
readonly
|
|
38
|
-
readonly refs: readonly StatusRef[];
|
|
52
|
+
readonly treeSections: readonly MigrationGraphTreeSection[];
|
|
39
53
|
readonly summary: string;
|
|
40
54
|
}
|
|
41
55
|
|
|
56
|
+
function computeGraphSummary(graph: MigrationGraph): string {
|
|
57
|
+
return `${graph.nodes.size} node(s), ${graph.migrationByHash.size} edge(s)`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatMigrationGraphHumanOutput(result: MigrationGraphResult): string {
|
|
61
|
+
const sections: string[] = [];
|
|
62
|
+
for (const section of result.treeSections) {
|
|
63
|
+
if (section.showHeading) {
|
|
64
|
+
sections.push(`${section.spaceId}:`);
|
|
65
|
+
}
|
|
66
|
+
if (section.tree.length > 0) {
|
|
67
|
+
sections.push(section.tree);
|
|
68
|
+
} else {
|
|
69
|
+
sections.push('(no migrations)');
|
|
70
|
+
}
|
|
71
|
+
sections.push('');
|
|
72
|
+
}
|
|
73
|
+
sections.push(result.summary);
|
|
74
|
+
return sections.join('\n').trimEnd();
|
|
75
|
+
}
|
|
76
|
+
|
|
42
77
|
export async function executeMigrationGraphCommand(
|
|
43
78
|
options: MigrationGraphOptions,
|
|
44
79
|
flags: GlobalFlags,
|
|
45
80
|
ui: TerminalUI,
|
|
46
81
|
): Promise<Result<MigrationGraphResult, CliStructuredError>> {
|
|
47
82
|
const config = await loadConfig(options.config);
|
|
48
|
-
const { configPath,
|
|
83
|
+
const { configPath, migrationsRelative, migrationsDir } = resolveMigrationPaths(
|
|
49
84
|
options.config,
|
|
50
85
|
config,
|
|
51
86
|
);
|
|
@@ -56,11 +91,21 @@ export async function executeMigrationGraphCommand(
|
|
|
56
91
|
description: 'Show the migration graph topology',
|
|
57
92
|
details: [
|
|
58
93
|
{ label: 'config', value: configPath },
|
|
59
|
-
{ label: 'migrations', value:
|
|
94
|
+
{ label: 'migrations', value: migrationsRelative },
|
|
95
|
+
...(options.space !== undefined ? [{ label: 'space', value: options.space }] : []),
|
|
60
96
|
],
|
|
61
97
|
flags,
|
|
62
98
|
});
|
|
63
99
|
ui.stderr(header);
|
|
100
|
+
if (shouldShowLegend(options, flags)) {
|
|
101
|
+
ui.stderr(
|
|
102
|
+
renderMigrationGraphLegend({
|
|
103
|
+
colorize: flags.color !== false,
|
|
104
|
+
glyphMode: ui.resolveGlyphMode(options.ascii === true),
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
ui.stderr('');
|
|
108
|
+
}
|
|
64
109
|
}
|
|
65
110
|
|
|
66
111
|
const loaded = await buildReadAggregate(config, { migrationsDir });
|
|
@@ -68,20 +113,72 @@ export async function executeMigrationGraphCommand(
|
|
|
68
113
|
return loaded;
|
|
69
114
|
}
|
|
70
115
|
|
|
71
|
-
const { aggregate, contractHash } = loaded.value;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
116
|
+
const { aggregate, contractHash: liveContractHash } = loaded.value;
|
|
117
|
+
const appGraph = aggregate.app.graph();
|
|
118
|
+
|
|
119
|
+
const listSpaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);
|
|
120
|
+
const listResult = runMigrationList({
|
|
121
|
+
spaces: listSpaces,
|
|
122
|
+
...ifDefined('spaceFilter', options.space),
|
|
123
|
+
});
|
|
124
|
+
if (!listResult.ok) {
|
|
125
|
+
return listResult;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const scopedSpaces = listResult.value.spaces;
|
|
129
|
+
const showSpaceHeadings = scopedSpaces.length > 1;
|
|
130
|
+
const glyphMode = ui.resolveGlyphMode(options.ascii === true);
|
|
131
|
+
const colorize = flags.color !== false;
|
|
132
|
+
|
|
133
|
+
const globalLayoutInputs = showSpaceHeadings
|
|
134
|
+
? scopedSpaces
|
|
135
|
+
.filter((spaceEntry) => spaceEntry.migrations.length > 0)
|
|
136
|
+
.map((spaceEntry) => ({
|
|
137
|
+
graph: aggregate.space(spaceEntry.spaceId)!.graph(),
|
|
138
|
+
liveContractHash,
|
|
139
|
+
}))
|
|
140
|
+
: [];
|
|
141
|
+
const globalMaxEdgeTreePrefixWidth =
|
|
142
|
+
globalLayoutInputs.length > 0
|
|
143
|
+
? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs)
|
|
144
|
+
: undefined;
|
|
145
|
+
const globalMaxDirNameWidth =
|
|
146
|
+
globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : undefined;
|
|
147
|
+
|
|
148
|
+
const treeSections: MigrationGraphTreeSection[] = [];
|
|
149
|
+
for (const spaceEntry of scopedSpaces) {
|
|
150
|
+
const member = aggregate.space(spaceEntry.spaceId);
|
|
151
|
+
if (member === undefined) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const graph = member.graph();
|
|
155
|
+
const tree =
|
|
156
|
+
spaceEntry.migrations.length === 0
|
|
157
|
+
? ''
|
|
158
|
+
: renderMigrationGraphSpaceTree({
|
|
159
|
+
graph,
|
|
160
|
+
migrations: spaceEntry.migrations,
|
|
161
|
+
liveContractHash,
|
|
162
|
+
glyphMode,
|
|
163
|
+
colorize,
|
|
164
|
+
refsByHash: listRefsByContractHash(member),
|
|
165
|
+
...(globalMaxEdgeTreePrefixWidth !== undefined ? { globalMaxEdgeTreePrefixWidth } : {}),
|
|
166
|
+
...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),
|
|
167
|
+
});
|
|
168
|
+
const displayTree =
|
|
169
|
+
showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, ' ') : tree;
|
|
170
|
+
treeSections.push({
|
|
171
|
+
spaceId: spaceEntry.spaceId,
|
|
172
|
+
tree: displayTree,
|
|
173
|
+
showHeading: showSpaceHeadings,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
78
176
|
|
|
79
177
|
return ok({
|
|
80
178
|
ok: true,
|
|
81
|
-
graph,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
summary: `${graph.nodes.size} node(s), ${graph.migrationByHash.size} edge(s)`,
|
|
179
|
+
graph: appGraph,
|
|
180
|
+
treeSections,
|
|
181
|
+
summary: computeGraphSummary(appGraph),
|
|
85
182
|
});
|
|
86
183
|
}
|
|
87
184
|
|
|
@@ -91,17 +188,17 @@ export function createMigrationGraphCommand(): Command {
|
|
|
91
188
|
command,
|
|
92
189
|
'Show the migration graph topology',
|
|
93
190
|
'Renders the migration graph topology. Offline — does not consult\n' +
|
|
94
|
-
'the database.
|
|
95
|
-
'
|
|
96
|
-
'--json for machine-readable output, or --dot for Graphviz DOT\n' +
|
|
191
|
+
'the database. --ascii swaps box-drawing for pipe-friendly ASCII glyphs.\n' +
|
|
192
|
+
'Use --json for machine-readable output, or --dot for Graphviz DOT\n' +
|
|
97
193
|
'format.',
|
|
98
194
|
);
|
|
99
195
|
setCommandExamples(command, [
|
|
100
196
|
'prisma-next migration graph',
|
|
101
197
|
'prisma-next migration graph --json',
|
|
102
198
|
'prisma-next migration graph --dot',
|
|
103
|
-
'prisma-next migration graph --
|
|
104
|
-
'prisma-next migration graph --
|
|
199
|
+
'prisma-next migration graph --ascii',
|
|
200
|
+
'prisma-next migration graph --legend',
|
|
201
|
+
'prisma-next migration graph --space app',
|
|
105
202
|
]);
|
|
106
203
|
setCommandSeeAlso(command, [
|
|
107
204
|
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
@@ -111,19 +208,19 @@ export function createMigrationGraphCommand(): Command {
|
|
|
111
208
|
]);
|
|
112
209
|
addGlobalOptions(command)
|
|
113
210
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
211
|
+
.option('--space <id>', 'Narrow output to a single contract space')
|
|
114
212
|
.option('--dot', 'Output in Graphviz DOT format')
|
|
115
|
-
.option('--
|
|
116
|
-
.option('--
|
|
213
|
+
.option('--ascii', 'Use ASCII glyphs (pipe-friendly)')
|
|
214
|
+
.option('--legend', 'Print a key for the tree glyphs and lane colors')
|
|
117
215
|
.action(async (options: MigrationGraphOptions) => {
|
|
118
216
|
const flags = parseGlobalFlagsOrExit(options);
|
|
119
217
|
const ui = createTerminalUI(flags);
|
|
218
|
+
const legendValidation = validateLegendOptions(options, flags);
|
|
219
|
+
if (!legendValidation.ok) {
|
|
220
|
+
process.exit(handleResult(legendValidation, flags, ui));
|
|
221
|
+
}
|
|
120
222
|
const result = await executeMigrationGraphCommand(options, flags, ui);
|
|
121
223
|
const exitCode = handleResult(result, flags, ui, (graphResult) => {
|
|
122
|
-
// Explicit format flags win over the auto-JSON default. `flags.json`
|
|
123
|
-
// is auto-enabled when stdout is non-TTY (per CLI Style Guide §
|
|
124
|
-
// JSON Semantics); without this ordering, `migration graph --dot |
|
|
125
|
-
// dot -Tsvg` pipes JSON into the GraphViz binary, which then
|
|
126
|
-
// errors. `--dot` is the more specific instruction; honour it.
|
|
127
224
|
if (options.dot) {
|
|
128
225
|
const lines = ['digraph migrations {'];
|
|
129
226
|
for (const edge of graphResult.graph.migrationByHash.values()) {
|
|
@@ -142,55 +239,19 @@ export function createMigrationGraphCommand(): Command {
|
|
|
142
239
|
migrationHash: e.migrationHash,
|
|
143
240
|
}));
|
|
144
241
|
ui.output(
|
|
145
|
-
JSON.stringify(
|
|
242
|
+
JSON.stringify(
|
|
243
|
+
{
|
|
244
|
+
ok: true,
|
|
245
|
+
nodes,
|
|
246
|
+
edges,
|
|
247
|
+
summary: `${graphResult.graph.nodes.size} node(s), ${graphResult.graph.migrationByHash.size} edge(s)`,
|
|
248
|
+
},
|
|
249
|
+
null,
|
|
250
|
+
2,
|
|
251
|
+
),
|
|
146
252
|
);
|
|
147
253
|
} else if (!flags.quiet) {
|
|
148
|
-
|
|
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
|
-
}
|
|
254
|
+
ui.output(formatMigrationGraphHumanOutput(graphResult));
|
|
194
255
|
}
|
|
195
256
|
});
|
|
196
257
|
process.exit(exitCode);
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ContractSpaceAggregate,
|
|
3
3
|
ContractSpaceMember,
|
|
4
4
|
} from '@prisma-next/migration-tools/aggregate';
|
|
5
|
+
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
5
6
|
import { HEAD_REF_NAME, refsByContractHash } from '@prisma-next/migration-tools/refs';
|
|
6
7
|
import {
|
|
7
8
|
APP_SPACE_ID,
|
|
@@ -26,10 +27,8 @@ import {
|
|
|
26
27
|
setCommandSeeAlso,
|
|
27
28
|
} from '../utils/command-helpers';
|
|
28
29
|
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
renderMigrationListWithStyle,
|
|
32
|
-
} from '../utils/formatters/migration-list-render';
|
|
30
|
+
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-tree-render';
|
|
31
|
+
import { renderMigrationListWithStyle } from '../utils/formatters/migration-list-render';
|
|
33
32
|
import { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';
|
|
34
33
|
import type {
|
|
35
34
|
MigrationListEntry,
|
|
@@ -40,6 +39,7 @@ import { formatStyledHeader } from '../utils/formatters/styled';
|
|
|
40
39
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
41
40
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
42
41
|
import type { GlyphMode } from '../utils/glyph-mode';
|
|
42
|
+
import { shouldShowLegend, validateLegendOptions } from '../utils/legend';
|
|
43
43
|
import { handleResult } from '../utils/result-handler';
|
|
44
44
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
45
45
|
|
|
@@ -65,7 +65,7 @@ function compareDirNamesDescending(a: MigrationListEntry, b: MigrationListEntry)
|
|
|
65
65
|
* keep that output. The app space synthesises its head, so it carries
|
|
66
66
|
* no on-disk `head` ref to restore.
|
|
67
67
|
*/
|
|
68
|
-
function listRefsByContractHash(
|
|
68
|
+
export function listRefsByContractHash(
|
|
69
69
|
member: ContractSpaceMember,
|
|
70
70
|
): ReadonlyMap<string, readonly string[]> {
|
|
71
71
|
const byHash = new Map(refsByContractHash(member.refs));
|
|
@@ -131,11 +131,20 @@ interface MigrationListOptions extends CommonCommandOptions {
|
|
|
131
131
|
readonly config?: string;
|
|
132
132
|
readonly space?: string;
|
|
133
133
|
readonly ascii?: boolean;
|
|
134
|
+
readonly legend?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface MigrationListExecuteResult {
|
|
138
|
+
readonly list: MigrationListResult;
|
|
139
|
+
readonly liveContractHash: string;
|
|
140
|
+
readonly aggregate: ContractSpaceAggregate;
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
export interface MigrationListHumanRenderOptions {
|
|
137
144
|
readonly glyphMode: GlyphMode;
|
|
138
145
|
readonly useColor: boolean;
|
|
146
|
+
readonly liveContractHash: string;
|
|
147
|
+
readonly graphForSpace: (spaceId: string) => MigrationGraph | undefined;
|
|
139
148
|
}
|
|
140
149
|
|
|
141
150
|
export function renderMigrationListHumanOutput(
|
|
@@ -143,8 +152,11 @@ export function renderMigrationListHumanOutput(
|
|
|
143
152
|
options: MigrationListHumanRenderOptions,
|
|
144
153
|
): string {
|
|
145
154
|
const styler = createAnsiMigrationListStyler({ useColor: options.useColor });
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
return renderMigrationListWithStyle(result, styler, options.glyphMode, {
|
|
156
|
+
colorize: options.useColor,
|
|
157
|
+
liveContractHash: options.liveContractHash,
|
|
158
|
+
graphForSpace: options.graphForSpace,
|
|
159
|
+
});
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
/**
|
|
@@ -212,7 +224,7 @@ export async function executeMigrationListCommand(
|
|
|
212
224
|
options: MigrationListOptions,
|
|
213
225
|
flags: GlobalFlags,
|
|
214
226
|
ui: TerminalUI,
|
|
215
|
-
): Promise<Result<
|
|
227
|
+
): Promise<Result<MigrationListExecuteResult, CliStructuredError>> {
|
|
216
228
|
const config = await loadConfig(options.config);
|
|
217
229
|
const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(
|
|
218
230
|
options.config,
|
|
@@ -222,7 +234,7 @@ export async function executeMigrationListCommand(
|
|
|
222
234
|
if (!flags.json && !flags.quiet) {
|
|
223
235
|
const header = formatStyledHeader({
|
|
224
236
|
command: 'migration list',
|
|
225
|
-
description: 'List on-disk migrations
|
|
237
|
+
description: 'List on-disk migrations per contract space',
|
|
226
238
|
details: [
|
|
227
239
|
{ label: 'config', value: configPath },
|
|
228
240
|
{ label: 'migrations', value: migrationsRelative },
|
|
@@ -231,6 +243,15 @@ export async function executeMigrationListCommand(
|
|
|
231
243
|
flags,
|
|
232
244
|
});
|
|
233
245
|
ui.stderr(header);
|
|
246
|
+
if (shouldShowLegend(options, flags)) {
|
|
247
|
+
ui.stderr(
|
|
248
|
+
renderMigrationGraphLegend({
|
|
249
|
+
colorize: flags.color !== false,
|
|
250
|
+
glyphMode: ui.resolveGlyphMode(options.ascii === true),
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
ui.stderr('');
|
|
254
|
+
}
|
|
234
255
|
}
|
|
235
256
|
|
|
236
257
|
const loaded = await buildReadAggregate(config, { migrationsDir });
|
|
@@ -238,35 +259,37 @@ export async function executeMigrationListCommand(
|
|
|
238
259
|
return notOk(loaded.failure);
|
|
239
260
|
}
|
|
240
261
|
|
|
241
|
-
const
|
|
242
|
-
loaded.value.aggregate,
|
|
243
|
-
migrationsDir,
|
|
244
|
-
);
|
|
262
|
+
const { aggregate, contractHash: liveContractHash } = loaded.value;
|
|
245
263
|
|
|
246
|
-
|
|
264
|
+
const spaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);
|
|
265
|
+
|
|
266
|
+
const listResult = runMigrationList({
|
|
247
267
|
spaces,
|
|
248
268
|
...ifDefined('spaceFilter', options.space),
|
|
249
269
|
});
|
|
270
|
+
if (!listResult.ok) {
|
|
271
|
+
return listResult;
|
|
272
|
+
}
|
|
273
|
+
return ok({ list: listResult.value, liveContractHash, aggregate });
|
|
250
274
|
}
|
|
251
275
|
|
|
252
276
|
export function createMigrationListCommand(): Command {
|
|
253
277
|
const command = new Command('list');
|
|
254
278
|
setCommandDescriptions(
|
|
255
279
|
command,
|
|
256
|
-
'List on-disk migrations
|
|
280
|
+
'List on-disk migrations per contract space',
|
|
257
281
|
'Enumerates every on-disk migration under migrations/<space>/ for every\n' +
|
|
258
|
-
'contract space found on disk
|
|
259
|
-
'
|
|
260
|
-
'
|
|
261
|
-
'
|
|
262
|
-
'
|
|
263
|
-
'to narrow to one contract space. --ascii forces ASCII kind glyphs\n' +
|
|
264
|
-
'(orthogonal to --no-color).',
|
|
282
|
+
'contract space found on disk. Offline — does not consult the database.\n' +
|
|
283
|
+
'Human output draws the shared migration graph tree with operation counts,\n' +
|
|
284
|
+
'invariants on each migration row, and refs on destination contract nodes.\n' +
|
|
285
|
+
'Pass --space <id> to narrow to one contract space. --ascii forces ASCII\n' +
|
|
286
|
+
'tree glyphs (orthogonal to --no-color).',
|
|
265
287
|
);
|
|
266
288
|
setCommandExamples(command, [
|
|
267
289
|
'prisma-next migration list',
|
|
268
290
|
'prisma-next migration list --space app',
|
|
269
291
|
'prisma-next migration list --ascii',
|
|
292
|
+
'prisma-next migration list --legend',
|
|
270
293
|
'prisma-next migration list --json',
|
|
271
294
|
]);
|
|
272
295
|
setCommandSeeAlso(command, [
|
|
@@ -279,18 +302,25 @@ export function createMigrationListCommand(): Command {
|
|
|
279
302
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
280
303
|
.option('--space <id>', 'Narrow output to a single contract space')
|
|
281
304
|
.option('--ascii', 'Use ASCII kind glyphs (pipe-friendly)')
|
|
305
|
+
.option('--legend', 'Print a key for the tree glyphs and lane colors')
|
|
282
306
|
.action(async (options: MigrationListOptions) => {
|
|
283
307
|
const flags = parseGlobalFlagsOrExit(options);
|
|
284
308
|
const ui = createTerminalUI(flags);
|
|
309
|
+
const legendValidation = validateLegendOptions(options, flags);
|
|
310
|
+
if (!legendValidation.ok) {
|
|
311
|
+
process.exit(handleResult(legendValidation, flags, ui));
|
|
312
|
+
}
|
|
285
313
|
const result = await executeMigrationListCommand(options, flags, ui);
|
|
286
|
-
const exitCode = handleResult(result, flags, ui, (
|
|
314
|
+
const exitCode = handleResult(result, flags, ui, ({ list, liveContractHash, aggregate }) => {
|
|
287
315
|
if (flags.json) {
|
|
288
|
-
ui.output(JSON.stringify(
|
|
316
|
+
ui.output(JSON.stringify(list, null, 2));
|
|
289
317
|
} else if (!flags.quiet) {
|
|
290
318
|
ui.output(
|
|
291
|
-
renderMigrationListHumanOutput(
|
|
319
|
+
renderMigrationListHumanOutput(list, {
|
|
292
320
|
glyphMode: ui.resolveGlyphMode(options.ascii === true),
|
|
293
321
|
useColor: ui.useColor,
|
|
322
|
+
liveContractHash,
|
|
323
|
+
graphForSpace: (spaceId) => aggregate.space(spaceId)?.graph(),
|
|
294
324
|
}),
|
|
295
325
|
);
|
|
296
326
|
}
|