@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,55 +1,220 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ContractSpaceAggregate,
|
|
3
|
+
ContractSpaceMember,
|
|
4
|
+
} from '@prisma-next/migration-tools/aggregate';
|
|
5
|
+
import { HEAD_REF_NAME, refsByContractHash } from '@prisma-next/migration-tools/refs';
|
|
6
|
+
import {
|
|
7
|
+
APP_SPACE_ID,
|
|
8
|
+
isValidSpaceId,
|
|
9
|
+
listContractSpaceDirectories,
|
|
10
|
+
RESERVED_SPACE_SUBDIR_NAMES,
|
|
11
|
+
} from '@prisma-next/migration-tools/spaces';
|
|
12
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
5
13
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
6
14
|
import { Command } from 'commander';
|
|
7
15
|
import { loadConfig } from '../config-loader';
|
|
8
16
|
import {
|
|
9
17
|
type CliStructuredError,
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
errorInvalidSpaceId,
|
|
19
|
+
errorSpaceNotFound,
|
|
12
20
|
} from '../utils/cli-errors';
|
|
13
21
|
import {
|
|
14
22
|
addGlobalOptions,
|
|
15
|
-
loadMigrationPackages,
|
|
16
23
|
resolveMigrationPaths,
|
|
17
24
|
setCommandDescriptions,
|
|
18
25
|
setCommandExamples,
|
|
19
26
|
setCommandSeeAlso,
|
|
20
27
|
} from '../utils/command-helpers';
|
|
28
|
+
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
29
|
+
import {
|
|
30
|
+
buildMigrationListTopologyBySpace,
|
|
31
|
+
renderMigrationListWithStyle,
|
|
32
|
+
} from '../utils/formatters/migration-list-render';
|
|
33
|
+
import { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';
|
|
34
|
+
import type {
|
|
35
|
+
MigrationListEntry,
|
|
36
|
+
MigrationListResult,
|
|
37
|
+
MigrationSpaceListEntry,
|
|
38
|
+
} from '../utils/formatters/migration-list-types';
|
|
21
39
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
22
40
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
23
41
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
42
|
+
import type { GlyphMode } from '../utils/glyph-mode';
|
|
24
43
|
import { handleResult } from '../utils/result-handler';
|
|
25
44
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
26
45
|
|
|
46
|
+
function compareSpaceIds(a: string, b: string): number {
|
|
47
|
+
if (a === APP_SPACE_ID) return b === APP_SPACE_ID ? 0 : -1;
|
|
48
|
+
if (b === APP_SPACE_ID) return 1;
|
|
49
|
+
if (a < b) return -1;
|
|
50
|
+
if (a > b) return 1;
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function compareDirNamesDescending(a: MigrationListEntry, b: MigrationListEntry): number {
|
|
55
|
+
if (a.dirName < b.dirName) return 1;
|
|
56
|
+
if (a.dirName > b.dirName) return -1;
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Ref names decorating a space's destination contract hashes. The
|
|
62
|
+
* tolerant `member.refs` deliberately omits the structural `head.json`;
|
|
63
|
+
* for extension spaces the old enumerator surfaced it as a `head`
|
|
64
|
+
* decoration on the tip migration, so fold `member.headRef` back in to
|
|
65
|
+
* keep that output. The app space synthesises its head, so it carries
|
|
66
|
+
* no on-disk `head` ref to restore.
|
|
67
|
+
*/
|
|
68
|
+
function listRefsByContractHash(
|
|
69
|
+
member: ContractSpaceMember,
|
|
70
|
+
): ReadonlyMap<string, readonly string[]> {
|
|
71
|
+
const byHash = new Map(refsByContractHash(member.refs));
|
|
72
|
+
if (member.spaceId !== APP_SPACE_ID && member.headRef !== null) {
|
|
73
|
+
const hash = member.headRef.hash;
|
|
74
|
+
const bucket = byHash.get(hash) ?? [];
|
|
75
|
+
if (!bucket.includes(HEAD_REF_NAME)) {
|
|
76
|
+
byHash.set(hash, [...bucket, HEAD_REF_NAME].sort());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return byHash;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function orderedOnDiskSpaceIds(projectMigrationsDir: string): Promise<readonly string[]> {
|
|
83
|
+
const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
|
|
84
|
+
return candidateDirs
|
|
85
|
+
.filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name))
|
|
86
|
+
.filter(isValidSpaceId)
|
|
87
|
+
.sort(compareSpaceIds);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Project the loaded {@link ContractSpaceAggregate} into the render-ready
|
|
92
|
+
* {@link MigrationSpaceListEntry} rows `migration list` displays.
|
|
93
|
+
*
|
|
94
|
+
* Space membership matches the on-disk contract-space directories (not the
|
|
95
|
+
* aggregate's always-present synthesized app member when `migrations/app/`
|
|
96
|
+
* is absent); package and ref data come from `aggregate.space(id)`.
|
|
97
|
+
*/
|
|
98
|
+
export async function migrationSpaceListEntriesFromAggregate(
|
|
99
|
+
aggregate: ContractSpaceAggregate,
|
|
100
|
+
projectMigrationsDir: string,
|
|
101
|
+
): Promise<readonly MigrationSpaceListEntry[]> {
|
|
102
|
+
const spaceIds = await orderedOnDiskSpaceIds(projectMigrationsDir);
|
|
103
|
+
const spaces: MigrationSpaceListEntry[] = [];
|
|
104
|
+
|
|
105
|
+
for (const spaceId of spaceIds) {
|
|
106
|
+
const member = aggregate.space(spaceId);
|
|
107
|
+
if (member === undefined) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const refsByHash = listRefsByContractHash(member);
|
|
111
|
+
const migrations: MigrationListEntry[] = member.packages
|
|
112
|
+
.map((pkg) => ({
|
|
113
|
+
dirName: pkg.dirName,
|
|
114
|
+
from: pkg.metadata.from,
|
|
115
|
+
to: pkg.metadata.to,
|
|
116
|
+
migrationHash: pkg.metadata.migrationHash,
|
|
117
|
+
operationCount: pkg.ops.length,
|
|
118
|
+
createdAt: pkg.metadata.createdAt,
|
|
119
|
+
refs: refsByHash.get(pkg.metadata.to) ?? [],
|
|
120
|
+
providedInvariants: pkg.metadata.providedInvariants,
|
|
121
|
+
}))
|
|
122
|
+
.sort(compareDirNamesDescending);
|
|
123
|
+
|
|
124
|
+
spaces.push({ spaceId, migrations });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return spaces;
|
|
128
|
+
}
|
|
129
|
+
|
|
27
130
|
interface MigrationListOptions extends CommonCommandOptions {
|
|
28
131
|
readonly config?: string;
|
|
132
|
+
readonly space?: string;
|
|
133
|
+
readonly ascii?: boolean;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface MigrationListHumanRenderOptions {
|
|
137
|
+
readonly glyphMode: GlyphMode;
|
|
138
|
+
readonly useColor: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function renderMigrationListHumanOutput(
|
|
142
|
+
result: MigrationListResult,
|
|
143
|
+
options: MigrationListHumanRenderOptions,
|
|
144
|
+
): string {
|
|
145
|
+
const styler = createAnsiMigrationListStyler({ useColor: options.useColor });
|
|
146
|
+
const topologyBySpaceId = buildMigrationListTopologyBySpace(result);
|
|
147
|
+
return renderMigrationListWithStyle(result, styler, options.glyphMode, topologyBySpaceId);
|
|
29
148
|
}
|
|
30
149
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Inputs for {@link runMigrationList} — the policy core of `migration list`
|
|
152
|
+
* that tests exercise directly.
|
|
153
|
+
*
|
|
154
|
+
* The core does not call `loadConfig`, parse CLI flags, render a styled
|
|
155
|
+
* header, or write to any stream. Enumeration is supplied by the caller
|
|
156
|
+
* (the CLI shell builds it from {@link migrationSpaceListEntriesFromAggregate}).
|
|
157
|
+
*/
|
|
158
|
+
export interface RunMigrationListInputs {
|
|
159
|
+
readonly spaces: readonly MigrationSpaceListEntry[];
|
|
160
|
+
readonly spaceFilter?: string;
|
|
38
161
|
}
|
|
39
162
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
163
|
+
function computeSummary(spaces: readonly MigrationSpaceListEntry[]): string {
|
|
164
|
+
const totalMigrations = spaces.reduce((count, space) => count + space.migrations.length, 0);
|
|
165
|
+
if (spaces.length <= 1) {
|
|
166
|
+
return `${totalMigrations} migration(s) on disk`;
|
|
167
|
+
}
|
|
168
|
+
return `${totalMigrations} migration(s) across ${spaces.length} contract space(s)`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Policy core of `migration list`: validates `--space`, narrows the
|
|
173
|
+
* pre-enumerated spaces, and assembles a {@link MigrationListResult}.
|
|
174
|
+
*
|
|
175
|
+
* - `migrations/` missing or contains no valid space directories →
|
|
176
|
+
* caller passes `spaces: []`; this synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]`.
|
|
177
|
+
* - `--space <id>` on an existing-but-empty space → `{ spaceId, migrations: [] }` in the input.
|
|
178
|
+
* - `--space <id>` on a non-existent (or reserved) space → `SPACE_NOT_FOUND`.
|
|
179
|
+
*/
|
|
180
|
+
export function runMigrationList(
|
|
181
|
+
inputs: RunMigrationListInputs,
|
|
182
|
+
): Result<MigrationListResult, CliStructuredError> {
|
|
183
|
+
const { spaces, spaceFilter } = inputs;
|
|
184
|
+
|
|
185
|
+
if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
|
|
186
|
+
return notOk(errorInvalidSpaceId(spaceFilter));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
|
|
190
|
+
return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const scopedSpaces =
|
|
194
|
+
spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
|
|
195
|
+
|
|
196
|
+
const resultSpaces: readonly MigrationSpaceListEntry[] =
|
|
197
|
+
scopedSpaces.length === 0 ? [{ spaceId: APP_SPACE_ID, migrations: [] }] : scopedSpaces;
|
|
198
|
+
|
|
199
|
+
return ok({
|
|
200
|
+
ok: true,
|
|
201
|
+
spaces: resultSpaces,
|
|
202
|
+
summary: computeSummary(resultSpaces),
|
|
203
|
+
});
|
|
44
204
|
}
|
|
45
205
|
|
|
46
|
-
|
|
206
|
+
/**
|
|
207
|
+
* CLI shell: loads config, resolves paths, prints the styled header on
|
|
208
|
+
* stderr (interactive mode only), and delegates to {@link runMigrationList}.
|
|
209
|
+
* Kept intentionally thin so the unit-testable surface lives in the core.
|
|
210
|
+
*/
|
|
211
|
+
export async function executeMigrationListCommand(
|
|
47
212
|
options: MigrationListOptions,
|
|
48
213
|
flags: GlobalFlags,
|
|
49
214
|
ui: TerminalUI,
|
|
50
215
|
): Promise<Result<MigrationListResult, CliStructuredError>> {
|
|
51
216
|
const config = await loadConfig(options.config);
|
|
52
|
-
const { configPath,
|
|
217
|
+
const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(
|
|
53
218
|
options.config,
|
|
54
219
|
config,
|
|
55
220
|
);
|
|
@@ -57,58 +222,30 @@ async function executeMigrationListCommand(
|
|
|
57
222
|
if (!flags.json && !flags.quiet) {
|
|
58
223
|
const header = formatStyledHeader({
|
|
59
224
|
command: 'migration list',
|
|
60
|
-
description: 'List on-disk migrations
|
|
225
|
+
description: 'List on-disk migrations, latest first, per contract space',
|
|
61
226
|
details: [
|
|
62
227
|
{ label: 'config', value: configPath },
|
|
63
|
-
{ label: 'migrations', value:
|
|
228
|
+
{ label: 'migrations', value: migrationsRelative },
|
|
229
|
+
...(options.space !== undefined ? [{ label: 'space', value: options.space }] : []),
|
|
64
230
|
],
|
|
65
231
|
flags,
|
|
66
232
|
});
|
|
67
233
|
ui.stderr(header);
|
|
68
234
|
}
|
|
69
235
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
|
|
74
|
-
} catch (error) {
|
|
75
|
-
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
76
|
-
return notOk(
|
|
77
|
-
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
78
|
-
why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
|
|
79
|
-
}),
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (bundles.length === 0) {
|
|
84
|
-
return ok({ ok: true, migrations: [], summary: 'No migrations found' });
|
|
236
|
+
const loaded = await buildReadAggregate(config, { migrationsDir });
|
|
237
|
+
if (!loaded.ok) {
|
|
238
|
+
return notOk(loaded.failure);
|
|
85
239
|
}
|
|
86
240
|
|
|
87
|
-
const
|
|
88
|
-
|
|
241
|
+
const spaces = await migrationSpaceListEntriesFromAggregate(
|
|
242
|
+
loaded.value.aggregate,
|
|
243
|
+
migrationsDir,
|
|
89
244
|
);
|
|
90
|
-
const targetHash =
|
|
91
|
-
leaves.length === 1 ? leaves[0]! : ([...graph.nodes].values().next().value as string);
|
|
92
|
-
const chain = findPath(graph, EMPTY_CONTRACT_HASH, targetHash) ?? [];
|
|
93
|
-
|
|
94
|
-
const pkgByDirName = new Map(bundles.map((p) => [p.dirName, p]));
|
|
95
|
-
const entries: MigrationListEntry[] = chain.map((edge) => {
|
|
96
|
-
const pkg = pkgByDirName.get(edge.dirName);
|
|
97
|
-
const ops = (pkg?.ops ?? []) as readonly MigrationPlanOperation[];
|
|
98
|
-
return {
|
|
99
|
-
dirName: edge.dirName,
|
|
100
|
-
from: edge.from,
|
|
101
|
-
to: edge.to,
|
|
102
|
-
migrationHash: edge.migrationHash,
|
|
103
|
-
operationCount: ops.length,
|
|
104
|
-
createdAt: edge.createdAt,
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
245
|
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
summary: `${entries.length} migration(s) on disk`,
|
|
246
|
+
return runMigrationList({
|
|
247
|
+
spaces,
|
|
248
|
+
...ifDefined('spaceFilter', options.space),
|
|
112
249
|
});
|
|
113
250
|
}
|
|
114
251
|
|
|
@@ -116,11 +253,22 @@ export function createMigrationListCommand(): Command {
|
|
|
116
253
|
const command = new Command('list');
|
|
117
254
|
setCommandDescriptions(
|
|
118
255
|
command,
|
|
119
|
-
'List on-disk migrations
|
|
120
|
-
'Enumerates
|
|
121
|
-
'
|
|
256
|
+
'List on-disk migrations, latest first, per contract space',
|
|
257
|
+
'Enumerates every on-disk migration under migrations/<space>/ for every\n' +
|
|
258
|
+
'contract space found on disk, latest first. Offline — does not consult\n' +
|
|
259
|
+
'the database. Each row leads with a kind glyph (* forward, ↩ rollback,\n' +
|
|
260
|
+
'⟲ self), then dirName, then source → destination contract hashes\n' +
|
|
261
|
+
'(7-char git-style). Self-edges show a single hash. Invariants render as\n' +
|
|
262
|
+
'{...}; refs on the destination as (production, db). Pass --space <id>\n' +
|
|
263
|
+
'to narrow to one contract space. --ascii forces ASCII kind glyphs\n' +
|
|
264
|
+
'(orthogonal to --no-color).',
|
|
122
265
|
);
|
|
123
|
-
setCommandExamples(command, [
|
|
266
|
+
setCommandExamples(command, [
|
|
267
|
+
'prisma-next migration list',
|
|
268
|
+
'prisma-next migration list --space app',
|
|
269
|
+
'prisma-next migration list --ascii',
|
|
270
|
+
'prisma-next migration list --json',
|
|
271
|
+
]);
|
|
124
272
|
setCommandSeeAlso(command, [
|
|
125
273
|
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
126
274
|
{ verb: 'migration log', oneLiner: 'Show executed migration history' },
|
|
@@ -129,6 +277,8 @@ export function createMigrationListCommand(): Command {
|
|
|
129
277
|
]);
|
|
130
278
|
addGlobalOptions(command)
|
|
131
279
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
280
|
+
.option('--space <id>', 'Narrow output to a single contract space')
|
|
281
|
+
.option('--ascii', 'Use ASCII kind glyphs (pipe-friendly)')
|
|
132
282
|
.action(async (options: MigrationListOptions) => {
|
|
133
283
|
const flags = parseGlobalFlagsOrExit(options);
|
|
134
284
|
const ui = createTerminalUI(flags);
|
|
@@ -137,16 +287,12 @@ export function createMigrationListCommand(): Command {
|
|
|
137
287
|
if (flags.json) {
|
|
138
288
|
ui.output(JSON.stringify(listResult, null, 2));
|
|
139
289
|
} else if (!flags.quiet) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
ui.log(`\n${listResult.summary}`);
|
|
149
|
-
}
|
|
290
|
+
ui.output(
|
|
291
|
+
renderMigrationListHumanOutput(listResult, {
|
|
292
|
+
glyphMode: ui.resolveGlyphMode(options.ascii === true),
|
|
293
|
+
useColor: ui.useColor,
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
150
296
|
}
|
|
151
297
|
});
|
|
152
298
|
process.exit(exitCode);
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
} from '../utils/cli-errors';
|
|
17
17
|
import {
|
|
18
18
|
addGlobalOptions,
|
|
19
|
-
loadMigrationPackages,
|
|
20
19
|
maskConnectionUrl,
|
|
21
20
|
resolveMigrationPaths,
|
|
22
21
|
setCommandDescriptions,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
setCommandSeeAlso,
|
|
25
24
|
targetSupportsMigrations,
|
|
26
25
|
} from '../utils/command-helpers';
|
|
26
|
+
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
27
27
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
28
28
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
29
29
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
@@ -51,13 +51,13 @@ export interface MigrationLogResult {
|
|
|
51
51
|
readonly summary: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
async function executeMigrationLogCommand(
|
|
54
|
+
export async function executeMigrationLogCommand(
|
|
55
55
|
options: MigrationLogOptions,
|
|
56
56
|
flags: GlobalFlags,
|
|
57
57
|
ui: TerminalUI,
|
|
58
58
|
): Promise<Result<MigrationLogResult, CliStructuredError>> {
|
|
59
59
|
const config = await loadConfig(options.config);
|
|
60
|
-
const { configPath,
|
|
60
|
+
const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(
|
|
61
61
|
options.config,
|
|
62
62
|
config,
|
|
63
63
|
);
|
|
@@ -94,18 +94,12 @@ async function executeMigrationLogCommand(
|
|
|
94
94
|
ui.stderr(header);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
|
|
101
|
-
} catch (error) {
|
|
102
|
-
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
103
|
-
return notOk(
|
|
104
|
-
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
105
|
-
why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
|
|
106
|
-
}),
|
|
107
|
-
);
|
|
97
|
+
const loaded = await buildReadAggregate(config, { migrationsDir });
|
|
98
|
+
if (!loaded.ok) {
|
|
99
|
+
return loaded;
|
|
108
100
|
}
|
|
101
|
+
const graph = loaded.value.aggregate.app.graph();
|
|
102
|
+
const bundles = loaded.value.aggregate.app.packages;
|
|
109
103
|
|
|
110
104
|
const client = createControlClient({
|
|
111
105
|
family: config.family,
|
|
@@ -12,19 +12,15 @@ import { readFile } from 'node:fs/promises';
|
|
|
12
12
|
import type { Contract } from '@prisma-next/contract/types';
|
|
13
13
|
import { getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
14
14
|
import { APP_SPACE_ID, createControlStack } from '@prisma-next/framework-components/control';
|
|
15
|
-
import {
|
|
15
|
+
import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
|
|
16
16
|
import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
17
17
|
import {
|
|
18
18
|
copyFilesWithRename,
|
|
19
19
|
formatMigrationDirName,
|
|
20
|
-
readMigrationsDir,
|
|
21
20
|
writeMigrationPackage,
|
|
22
21
|
} from '@prisma-next/migration-tools/io';
|
|
23
22
|
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
24
|
-
import {
|
|
25
|
-
findLatestMigration,
|
|
26
|
-
reconstructGraph,
|
|
27
|
-
} from '@prisma-next/migration-tools/migration-graph';
|
|
23
|
+
import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
|
|
28
24
|
import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
|
|
29
25
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
30
26
|
import { Command } from 'commander';
|
|
@@ -36,7 +32,6 @@ import {
|
|
|
36
32
|
errorRuntime,
|
|
37
33
|
errorTargetMigrationNotSupported,
|
|
38
34
|
errorUnexpected,
|
|
39
|
-
mapMigrationToolsError,
|
|
40
35
|
} from '../utils/cli-errors';
|
|
41
36
|
import {
|
|
42
37
|
addGlobalOptions,
|
|
@@ -46,6 +41,7 @@ import {
|
|
|
46
41
|
setCommandDescriptions,
|
|
47
42
|
setCommandExamples,
|
|
48
43
|
} from '../utils/command-helpers';
|
|
44
|
+
import { refusePackageCorruptionOnAggregate } from '../utils/contract-space-aggregate-loader';
|
|
49
45
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
50
46
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
51
47
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
@@ -71,7 +67,10 @@ async function executeMigrationNewCommand(
|
|
|
71
67
|
options: MigrationNewOptions,
|
|
72
68
|
): Promise<Result<MigrationNewResult, CliStructuredError>> {
|
|
73
69
|
const config = await loadConfig(options.config);
|
|
74
|
-
const { appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(
|
|
70
|
+
const { migrationsDir, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(
|
|
71
|
+
options.config,
|
|
72
|
+
config,
|
|
73
|
+
);
|
|
75
74
|
|
|
76
75
|
// Construct the family instance up-front so the on-disk contract read
|
|
77
76
|
// below crosses the serializer seam (`familyInstance.deserializeContract`)
|
|
@@ -98,7 +97,8 @@ async function executeMigrationNewCommand(
|
|
|
98
97
|
|
|
99
98
|
let toContract: Contract;
|
|
100
99
|
try {
|
|
101
|
-
|
|
100
|
+
const parsedContract: unknown = JSON.parse(contractJsonContent);
|
|
101
|
+
toContract = familyInstance.deserializeContract(parsedContract);
|
|
102
102
|
} catch (error) {
|
|
103
103
|
return notOk(
|
|
104
104
|
errorRuntime('Contract JSON is invalid', {
|
|
@@ -118,45 +118,47 @@ async function executeMigrationNewCommand(
|
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const aggregate = await loadContractSpaceAggregate({
|
|
122
|
+
migrationsDir,
|
|
123
|
+
deserializeContract: (json) => familyInstance.deserializeContract(json),
|
|
124
|
+
appContract: toContract,
|
|
125
|
+
});
|
|
126
|
+
const packageCorruptionFailure = refusePackageCorruptionOnAggregate(aggregate);
|
|
127
|
+
if (packageCorruptionFailure) {
|
|
128
|
+
return notOk(packageCorruptionFailure);
|
|
129
|
+
}
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
const packages = aggregate.app.packages;
|
|
132
|
+
const graph = aggregate.app.graph();
|
|
126
133
|
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
let fromHash: string | null = null;
|
|
135
|
+
let fromContractSourceDir: string | null = null;
|
|
129
136
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
if (packages.length > 0) {
|
|
138
|
+
if (options.from) {
|
|
139
|
+
const match = packages.find((p) => p.metadata.to.startsWith(options.from!));
|
|
140
|
+
if (!match) {
|
|
141
|
+
return notOk(
|
|
142
|
+
errorRuntime('Starting contract not found', {
|
|
143
|
+
why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
|
|
144
|
+
fix: 'Check that the --from hash matches a known migration target hash.',
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
fromHash = match.metadata.to;
|
|
149
|
+
fromContractSourceDir = match.dirPath;
|
|
150
|
+
} else {
|
|
151
|
+
const latestMigration = findLatestMigration(graph);
|
|
152
|
+
if (latestMigration) {
|
|
153
|
+
fromHash = latestMigration.to;
|
|
154
|
+
const leafPkg = packages.find(
|
|
155
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
156
|
+
);
|
|
157
|
+
if (leafPkg) {
|
|
158
|
+
fromContractSourceDir = leafPkg.dirPath;
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
|
-
} catch (error) {
|
|
156
|
-
if (MigrationToolsError.is(error)) {
|
|
157
|
-
return notOk(mapMigrationToolsError(error));
|
|
158
|
-
}
|
|
159
|
-
throw error;
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
if (fromHash === toStorageHash && !options.from) {
|
|
@@ -180,12 +182,6 @@ async function executeMigrationNewCommand(
|
|
|
180
182
|
const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {
|
|
181
183
|
from: fromHash,
|
|
182
184
|
to: toStorageHash,
|
|
183
|
-
hints: {
|
|
184
|
-
used: [],
|
|
185
|
-
applied: [],
|
|
186
|
-
plannerVersion: '1.0.0',
|
|
187
|
-
},
|
|
188
|
-
labels: [],
|
|
189
185
|
providedInvariants: [],
|
|
190
186
|
createdAt: timestamp.toISOString(),
|
|
191
187
|
};
|