@prisma-next/cli 0.8.0 → 0.9.0-dev.2
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 +8 -9
- package/dist/{cli-errors-D3_sMh2K.mjs → cli-errors-CF60g2cG.mjs} +40 -2
- package/dist/cli-errors-CF60g2cG.mjs.map +1 -0
- package/dist/cli.mjs +67 -19
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-BCnP7cHo.mjs → client-Brv4qlfB.mjs} +28 -30
- package/dist/client-Brv4qlfB.mjs.map +1 -0
- package/dist/{command-helpers-BeZHkxV8.mjs → command-helpers-D3vL5yi8.mjs} +29 -6
- package/dist/command-helpers-D3vL5yi8.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 +7 -7
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +67 -25
- 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 +37 -9
- 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 +28 -0
- package/dist/commands/migrate.d.mts.map +1 -0
- package/dist/commands/{migration-apply.mjs → migrate.mjs} +65 -39
- package/dist/commands/migrate.mjs.map +1 -0
- package/dist/commands/migration-check.d.mts +18 -0
- package/dist/commands/migration-check.d.mts.map +1 -0
- package/dist/commands/migration-check.mjs +284 -0
- package/dist/commands/migration-check.mjs.map +1 -0
- package/dist/commands/migration-graph.d.mts +16 -0
- package/dist/commands/migration-graph.d.mts.map +1 -0
- package/dist/commands/migration-graph.mjs +141 -0
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +20 -0
- package/dist/commands/migration-list.d.mts.map +1 -0
- package/dist/commands/migration-list.mjs +107 -0
- package/dist/commands/migration-list.mjs.map +1 -0
- package/dist/commands/migration-log.d.mts +21 -0
- package/dist/commands/migration-log.d.mts.map +1 -0
- package/dist/commands/migration-log.mjs +146 -0
- package/dist/commands/migration-log.mjs.map +1 -0
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +30 -29
- 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 +1 -1
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +90 -52
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +5 -17
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +732 -1
- package/dist/commands/migration-status.mjs.map +1 -0
- package/dist/commands/ref.d.mts +34 -0
- package/dist/commands/ref.d.mts.map +1 -0
- package/dist/commands/{migration-ref.mjs → ref.mjs} +28 -57
- package/dist/commands/ref.mjs.map +1 -0
- package/dist/{contract-emit-9DBda5Ou.mjs → contract-emit-C3STUIBg.mjs} +6 -6
- package/dist/{contract-emit-9DBda5Ou.mjs.map → contract-emit-C3STUIBg.mjs.map} +1 -1
- package/dist/{contract-emit-B77TsJqf.mjs → contract-emit-iynA3BCA.mjs} +9 -5
- package/dist/contract-emit-iynA3BCA.mjs.map +1 -0
- package/dist/{contract-infer-ByxhPjpW.mjs → contract-infer-Cnj8G1E2.mjs} +5 -5
- package/dist/{contract-infer-ByxhPjpW.mjs.map → contract-infer-Cnj8G1E2.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs → contract-space-aggregate-loader-pAc8CDfY.mjs} +4 -4
- package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs.map → contract-space-aggregate-loader-pAc8CDfY.mjs.map} +1 -1
- package/dist/{db-verify-Czm5T-J4.mjs → db-verify-D7cyH_zz.mjs} +12 -9
- package/dist/db-verify-D7cyH_zz.mjs.map +1 -0
- package/dist/errors-Cw6kyTyV.mjs +56 -0
- package/dist/errors-Cw6kyTyV.mjs.map +1 -0
- 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/index.mjs.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-ChqVUxR-.mjs → framework-components-xFLFpZUO.mjs} +2 -2
- package/dist/{framework-components-ChqVUxR-.mjs.map → framework-components-xFLFpZUO.mjs.map} +1 -1
- package/dist/{global-flags-Icqpxk23.d.mts → global-flags-DGmw6Kqg.d.mts} +1 -1
- package/dist/{global-flags-Icqpxk23.d.mts.map → global-flags-DGmw6Kqg.d.mts.map} +1 -1
- package/dist/{migration-status-By9G5p2H.mjs → graph-render-eJDcLWny.mjs} +3 -692
- package/dist/graph-render-eJDcLWny.mjs.map +1 -0
- package/dist/{init-B-k3a1Qw.mjs → init-DEe-IimY.mjs} +133 -61
- package/dist/init-DEe-IimY.mjs.map +1 -0
- package/dist/{inspect-live-schema-DxdBd4Er.mjs → inspect-live-schema-CWLK_lgs.mjs} +4 -4
- package/dist/{inspect-live-schema-DxdBd4Er.mjs.map → inspect-live-schema-CWLK_lgs.mjs.map} +1 -1
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs → migration-command-scaffold-CmXXC1UZ.mjs} +4 -4
- package/dist/{migration-command-scaffold-BdV8JYXV.mjs.map → migration-command-scaffold-CmXXC1UZ.mjs.map} +1 -1
- package/dist/{migration-plan-mRu5K81L.mjs → migration-plan-CHyUlBV0.mjs} +76 -37
- package/dist/migration-plan-CHyUlBV0.mjs.map +1 -0
- package/dist/migration-types-D2FW63pr.d.mts +15 -0
- package/dist/migration-types-D2FW63pr.d.mts.map +1 -0
- package/dist/{migrations-CTsyBXCA.mjs → migrations-DyUf5lTt.mjs} +2 -2
- package/dist/migrations-DyUf5lTt.mjs.map +1 -0
- package/dist/{output-BVj6a971.mjs → output-B60Gw5fu.mjs} +12 -11
- package/dist/{output-BVj6a971.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
- package/dist/{result-handler-rmPVKIP2.mjs → result-handler-Bm_6dDYg.mjs} +2 -2
- package/dist/{result-handler-rmPVKIP2.mjs.map → result-handler-Bm_6dDYg.mjs.map} +1 -1
- package/dist/{terminal-ui-C_hFNbAn.mjs → terminal-ui-XtOQsqe9.mjs} +2 -54
- package/dist/terminal-ui-XtOQsqe9.mjs.map +1 -0
- package/dist/{types-LItU7E4l.d.mts → types-0aS865QN.d.mts} +14 -8
- package/dist/types-0aS865QN.d.mts.map +1 -0
- package/dist/{verify-CiwNWM9N.mjs → verify-D7ypCCe6.mjs} +1 -1
- package/dist/{verify-CiwNWM9N.mjs.map → verify-D7ypCCe6.mjs.map} +1 -1
- package/package.json +39 -23
- package/src/cli.ts +78 -15
- package/src/commands/db-sign.ts +102 -32
- package/src/commands/db-update.ts +56 -4
- package/src/commands/db-verify.ts +19 -3
- package/src/commands/init/agent-skill-install.ts +145 -43
- package/src/commands/init/errors.ts +2 -2
- package/src/commands/init/exit-codes.ts +2 -2
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +15 -6
- package/src/commands/init/inputs.ts +1 -1
- package/src/commands/init/output.ts +22 -17
- package/src/commands/{migration-apply.ts → migrate.ts} +77 -73
- package/src/commands/migration-check/exit-codes.ts +3 -0
- package/src/commands/migration-check.ts +369 -0
- package/src/commands/migration-graph.ts +184 -0
- package/src/commands/migration-list.ts +155 -0
- package/src/commands/migration-log.ts +218 -0
- package/src/commands/migration-new.ts +30 -22
- package/src/commands/migration-plan.ts +104 -35
- package/src/commands/migration-show.ts +141 -65
- package/src/commands/migration-status.ts +82 -69
- package/src/commands/{migration-ref.ts → ref.ts} +32 -86
- package/src/control-api/client.ts +30 -21
- package/src/control-api/operations/apply-aggregate.ts +4 -4
- package/src/control-api/operations/contract-emit.ts +26 -3
- package/src/control-api/operations/db-apply-aggregate.ts +4 -3
- package/src/control-api/operations/db-verify.ts +2 -2
- package/src/control-api/operations/migration-apply.ts +5 -4
- package/src/control-api/types.ts +12 -7
- package/src/load-ts-contract.ts +9 -1
- package/src/migration-cli.ts +1 -1
- package/src/utils/cli-errors.ts +37 -0
- package/src/utils/command-helpers.ts +28 -3
- package/src/utils/contract-space-aggregate-loader.ts +4 -4
- package/src/utils/contract-space-seed-phase.ts +2 -2
- package/src/utils/formatters/help.ts +12 -2
- package/src/utils/formatters/migrations.ts +2 -2
- package/dist/cli-errors-D3_sMh2K.mjs.map +0 -1
- package/dist/client-BCnP7cHo.mjs.map +0 -1
- package/dist/command-helpers-BeZHkxV8.mjs.map +0 -1
- package/dist/commands/migration-apply.d.mts +0 -51
- package/dist/commands/migration-apply.d.mts.map +0 -1
- package/dist/commands/migration-apply.mjs.map +0 -1
- package/dist/commands/migration-ref.d.mts +0 -45
- package/dist/commands/migration-ref.d.mts.map +0 -1
- package/dist/commands/migration-ref.mjs.map +0 -1
- package/dist/contract-emit-B77TsJqf.mjs.map +0 -1
- package/dist/db-verify-Czm5T-J4.mjs.map +0 -1
- package/dist/init-B-k3a1Qw.mjs.map +0 -1
- package/dist/migration-plan-mRu5K81L.mjs.map +0 -1
- package/dist/migration-status-By9G5p2H.mjs.map +0 -1
- package/dist/migrations-CTsyBXCA.mjs.map +0 -1
- package/dist/terminal-ui-C_hFNbAn.mjs.map +0 -1
- package/dist/types-LItU7E4l.d.mts.map +0 -1
- /package/dist/{cli-errors-B9OBbled.d.mts → cli-errors-DdcjVLJV.d.mts} +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import type { Contract } from '@prisma-next/contract/types';
|
|
3
|
+
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
3
4
|
import { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
5
|
+
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
4
6
|
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
5
|
-
import { readRefs
|
|
7
|
+
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
6
8
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
7
9
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
8
10
|
import { Command } from 'commander';
|
|
@@ -25,6 +27,7 @@ import {
|
|
|
25
27
|
errorTargetMigrationNotSupported,
|
|
26
28
|
errorUnexpected,
|
|
27
29
|
mapMigrationToolsError,
|
|
30
|
+
mapRefResolutionError,
|
|
28
31
|
} from '../utils/cli-errors';
|
|
29
32
|
import {
|
|
30
33
|
addGlobalOptions,
|
|
@@ -44,29 +47,16 @@ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
|
|
|
44
47
|
import { handleResult } from '../utils/result-handler';
|
|
45
48
|
import { TerminalUI } from '../utils/terminal-ui';
|
|
46
49
|
|
|
47
|
-
interface
|
|
50
|
+
interface MigrateCommandOptions extends CommonCommandOptions {
|
|
48
51
|
readonly db?: string;
|
|
49
52
|
readonly config?: string;
|
|
50
|
-
readonly
|
|
53
|
+
readonly to?: string;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
* Per-space breakdown of an apply run. The CLI command surfaces these
|
|
55
|
-
* for both the JSON shape (`appliedSpaces[]`) and the human-readable
|
|
56
|
-
* formatter (per-space block — same shape `db init` / `db update`
|
|
57
|
-
* use, M6 sub-spec § Output shape contract).
|
|
58
|
-
*/
|
|
59
|
-
export interface MigrationApplyResult {
|
|
56
|
+
export interface MigrateResult {
|
|
60
57
|
readonly ok: boolean;
|
|
61
|
-
/** Number of contract spaces that had non-zero pending operations applied. */
|
|
62
58
|
readonly migrationsApplied: number;
|
|
63
|
-
/** Total contract spaces visible in the aggregate (pending + already-up-to-date). */
|
|
64
59
|
readonly migrationsTotal: number;
|
|
65
|
-
/**
|
|
66
|
-
* Marker hash for the **app member** post-apply. Surfaced for
|
|
67
|
-
* back-compat with single-space callers; per-space markers live on
|
|
68
|
-
* `perSpace[].marker.storageHash`.
|
|
69
|
-
*/
|
|
70
60
|
readonly markerHash: string;
|
|
71
61
|
readonly applied: readonly {
|
|
72
62
|
readonly spaceId: string;
|
|
@@ -77,17 +67,7 @@ export interface MigrationApplyResult {
|
|
|
77
67
|
readonly operationsExecuted: number;
|
|
78
68
|
}[];
|
|
79
69
|
readonly summary: string;
|
|
80
|
-
/**
|
|
81
|
-
* Per-space breakdown in canonical schedule order (extensions
|
|
82
|
-
* alphabetically, then app). Always present for the aggregate-walking
|
|
83
|
-
* apply path.
|
|
84
|
-
*/
|
|
85
70
|
readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
|
|
86
|
-
/**
|
|
87
|
-
* Path-decision data for the app member. Surfaced for back-compat
|
|
88
|
-
* with single-space callers (cli-journeys invariant tests).
|
|
89
|
-
* Absent for no-op applies where the app had nothing to do.
|
|
90
|
-
*/
|
|
91
71
|
readonly pathDecision?: MigrationApplyPathDecision;
|
|
92
72
|
readonly timings: {
|
|
93
73
|
readonly total: number;
|
|
@@ -97,17 +77,17 @@ export interface MigrationApplyResult {
|
|
|
97
77
|
function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
|
|
98
78
|
return errorRuntime(failure.summary, {
|
|
99
79
|
why: failure.why ?? 'Migration runner failed',
|
|
100
|
-
fix: 'Fix the issue and re-run `prisma-next
|
|
80
|
+
fix: 'Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved.',
|
|
101
81
|
meta: failure.meta ?? {},
|
|
102
82
|
});
|
|
103
83
|
}
|
|
104
84
|
|
|
105
|
-
async function
|
|
106
|
-
options:
|
|
85
|
+
async function executeMigrateCommand(
|
|
86
|
+
options: MigrateCommandOptions,
|
|
107
87
|
flags: GlobalFlags,
|
|
108
88
|
ui: TerminalUI,
|
|
109
89
|
startTime: number,
|
|
110
|
-
): Promise<Result<
|
|
90
|
+
): Promise<Result<MigrateResult, CliStructuredErrorType>> {
|
|
111
91
|
const config = await loadConfig(options.config);
|
|
112
92
|
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
|
|
113
93
|
resolveMigrationPaths(options.config, config);
|
|
@@ -116,8 +96,8 @@ async function executeMigrationApplyCommand(
|
|
|
116
96
|
if (!dbConnection) {
|
|
117
97
|
return notOk(
|
|
118
98
|
errorDatabaseConnectionRequired({
|
|
119
|
-
why: `Database connection is required for
|
|
120
|
-
commandName: '
|
|
99
|
+
why: `Database connection is required for migrate (set db.connection in ${configPath}, or pass --db <url>)`,
|
|
100
|
+
commandName: 'migrate',
|
|
121
101
|
}),
|
|
122
102
|
);
|
|
123
103
|
}
|
|
@@ -125,7 +105,7 @@ async function executeMigrationApplyCommand(
|
|
|
125
105
|
if (!config.driver) {
|
|
126
106
|
return notOk(
|
|
127
107
|
errorDriverRequired({
|
|
128
|
-
why: 'Config.driver is required for
|
|
108
|
+
why: 'Config.driver is required for migrate',
|
|
129
109
|
}),
|
|
130
110
|
);
|
|
131
111
|
}
|
|
@@ -139,12 +119,22 @@ async function executeMigrationApplyCommand(
|
|
|
139
119
|
}
|
|
140
120
|
|
|
141
121
|
let refEntry: RefEntry | undefined;
|
|
142
|
-
const
|
|
122
|
+
const toArg = options.to;
|
|
143
123
|
|
|
144
|
-
if (
|
|
124
|
+
if (toArg) {
|
|
145
125
|
try {
|
|
146
126
|
const refs = await readRefs(refsDir);
|
|
147
|
-
|
|
127
|
+
const { graph } = await loadMigrationPackages(appMigrationsDir);
|
|
128
|
+
const refResult = parseContractRef(toArg, { graph, refs });
|
|
129
|
+
if (!refResult.ok) {
|
|
130
|
+
return notOk(mapRefResolutionError(refResult.failure));
|
|
131
|
+
}
|
|
132
|
+
if (refResult.value.provenance.kind === 'ref') {
|
|
133
|
+
const resolved = refs[refResult.value.provenance.refName];
|
|
134
|
+
if (resolved) refEntry = resolved;
|
|
135
|
+
} else {
|
|
136
|
+
refEntry = { hash: refResult.value.hash, invariants: [] };
|
|
137
|
+
}
|
|
148
138
|
} catch (error) {
|
|
149
139
|
if (MigrationToolsError.is(error)) {
|
|
150
140
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -153,25 +143,42 @@ async function executeMigrationApplyCommand(
|
|
|
153
143
|
}
|
|
154
144
|
}
|
|
155
145
|
|
|
156
|
-
//
|
|
157
|
-
//
|
|
146
|
+
// Construct the family instance up-front so the on-disk contract read
|
|
147
|
+
// crosses the serializer seam (`familyInstance.deserializeContract`) at
|
|
148
|
+
// the read site. The downstream `client.migrationApply({ contract })`
|
|
149
|
+
// re-validates internally (no harm — validation is idempotent), but
|
|
150
|
+
// closing the gap at the read site is what makes the cast-pattern
|
|
151
|
+
// lint enforceable and matches the other CLI commands. See TML-2536.
|
|
152
|
+
const stack = createControlStack(config);
|
|
153
|
+
const familyInstance = config.family.create(stack);
|
|
154
|
+
|
|
158
155
|
const contractPathAbsolute = resolveContractPath(config);
|
|
159
156
|
let contractRaw: Contract;
|
|
157
|
+
let contractContent: string;
|
|
160
158
|
try {
|
|
161
|
-
|
|
162
|
-
contractRaw = JSON.parse(contractContent) as Contract;
|
|
159
|
+
contractContent = await readFile(contractPathAbsolute, 'utf-8');
|
|
163
160
|
} catch (error) {
|
|
164
161
|
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
165
162
|
return notOk(
|
|
166
163
|
errorFileNotFound(contractPathAbsolute, {
|
|
167
164
|
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
168
|
-
fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry
|
|
165
|
+
fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry.',
|
|
169
166
|
}),
|
|
170
167
|
);
|
|
171
168
|
}
|
|
172
169
|
return notOk(
|
|
173
170
|
errorContractValidationFailed(
|
|
174
|
-
`
|
|
171
|
+
`Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
|
|
172
|
+
{ where: { path: contractPathAbsolute } },
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
contractRaw = familyInstance.deserializeContract(JSON.parse(contractContent) as unknown);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return notOk(
|
|
180
|
+
errorContractValidationFailed(
|
|
181
|
+
`Contract at ${contractPathAbsolute} failed to deserialize: ${error instanceof Error ? error.message : String(error)}`,
|
|
175
182
|
{ where: { path: contractPathAbsolute } },
|
|
176
183
|
),
|
|
177
184
|
);
|
|
@@ -188,21 +195,19 @@ async function executeMigrationApplyCommand(
|
|
|
188
195
|
value: maskConnectionUrl(dbConnection),
|
|
189
196
|
});
|
|
190
197
|
}
|
|
191
|
-
if (
|
|
192
|
-
details.push({ label: '
|
|
198
|
+
if (toArg) {
|
|
199
|
+
details.push({ label: 'to', value: toArg });
|
|
193
200
|
}
|
|
194
201
|
const header = formatStyledHeader({
|
|
195
|
-
command: '
|
|
196
|
-
description: 'Apply planned migrations to the database',
|
|
197
|
-
url: 'https://pris.ly/
|
|
202
|
+
command: 'migrate',
|
|
203
|
+
description: 'Apply planned migrations to advance the database',
|
|
204
|
+
url: 'https://pris.ly/migrate',
|
|
198
205
|
details,
|
|
199
206
|
flags,
|
|
200
207
|
});
|
|
201
208
|
ui.stderr(header);
|
|
202
209
|
}
|
|
203
210
|
|
|
204
|
-
// Load app-space migration packages — the aggregate operation
|
|
205
|
-
// needs them to hydrate the app member's graph for graph-walk.
|
|
206
211
|
let appPackages: Awaited<ReturnType<typeof loadMigrationPackages>>;
|
|
207
212
|
try {
|
|
208
213
|
appPackages = await loadMigrationPackages(appMigrationsDir);
|
|
@@ -224,13 +229,6 @@ async function executeMigrationApplyCommand(
|
|
|
224
229
|
try {
|
|
225
230
|
await client.connect(dbConnection);
|
|
226
231
|
|
|
227
|
-
// Pre-check unknown invariants against `(declared by app graph) ∪
|
|
228
|
-
// (already on the app marker)`. The marker side of the union
|
|
229
|
-
// catches the case where the ref carries an invariant whose
|
|
230
|
-
// declaring migration was retired (history rewritten) but whose
|
|
231
|
-
// id is recorded on the marker — surfacing UNKNOWN_INVARIANT
|
|
232
|
-
// there would be misleading because the database has already
|
|
233
|
-
// satisfied the requirement.
|
|
234
232
|
if (refEntry && refEntry.invariants.length > 0) {
|
|
235
233
|
const allMarkers = await client.readAllMarkers();
|
|
236
234
|
const appMarker = allMarkers.get('app') ?? null;
|
|
@@ -242,7 +240,7 @@ async function executeMigrationApplyCommand(
|
|
|
242
240
|
return notOk(
|
|
243
241
|
mapMigrationToolsError(
|
|
244
242
|
errorUnknownInvariant({
|
|
245
|
-
...ifDefined('refName',
|
|
243
|
+
...ifDefined('refName', toArg),
|
|
246
244
|
unknown,
|
|
247
245
|
declared: [...declared].sort(),
|
|
248
246
|
}),
|
|
@@ -261,7 +259,7 @@ async function executeMigrationApplyCommand(
|
|
|
261
259
|
appMigrationPackages: appPackages.bundles,
|
|
262
260
|
...ifDefined('refHash', refEntry?.hash),
|
|
263
261
|
...(refEntry?.invariants ? { refInvariants: refEntry.invariants } : {}),
|
|
264
|
-
...(refEntry !== undefined ? ifDefined('refName',
|
|
262
|
+
...(refEntry !== undefined ? ifDefined('refName', toArg) : {}),
|
|
265
263
|
});
|
|
266
264
|
|
|
267
265
|
if (!applyResult.ok) {
|
|
@@ -290,7 +288,7 @@ async function executeMigrationApplyCommand(
|
|
|
290
288
|
}
|
|
291
289
|
return notOk(
|
|
292
290
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
293
|
-
why: `Unexpected error during
|
|
291
|
+
why: `Unexpected error during migrate: ${error instanceof Error ? error.message : String(error)}`,
|
|
294
292
|
}),
|
|
295
293
|
);
|
|
296
294
|
} finally {
|
|
@@ -298,23 +296,29 @@ async function executeMigrationApplyCommand(
|
|
|
298
296
|
}
|
|
299
297
|
}
|
|
300
298
|
|
|
301
|
-
export function
|
|
302
|
-
const command = new Command('
|
|
299
|
+
export function createMigrateCommand(): Command {
|
|
300
|
+
const command = new Command('migrate');
|
|
303
301
|
setCommandDescriptions(
|
|
304
302
|
command,
|
|
305
|
-
'Apply planned migrations to the database',
|
|
303
|
+
'Apply planned migrations to advance the database',
|
|
306
304
|
'Walks every contract space (app + extensions) and applies pending\n' +
|
|
307
305
|
'on-disk migrations in canonical order (extensions alphabetically,\n' +
|
|
308
|
-
'then app). Graph-walks the on-disk migration graph for every space
|
|
309
|
-
|
|
310
|
-
"transaction; per-space failure rolls back every space's writes.",
|
|
306
|
+
'then app). Graph-walks the on-disk migration graph for every space.\n' +
|
|
307
|
+
'Use --to to target a specific contract (hash, ref name, migration dir).',
|
|
311
308
|
);
|
|
312
|
-
setCommandExamples(command, [
|
|
309
|
+
setCommandExamples(command, [
|
|
310
|
+
'prisma-next migrate --db $DATABASE_URL',
|
|
311
|
+
'prisma-next migrate --to production --db $DATABASE_URL',
|
|
312
|
+
'prisma-next migrate --to sha256:abc123 --db $DATABASE_URL',
|
|
313
|
+
]);
|
|
313
314
|
addGlobalOptions(command)
|
|
314
315
|
.option('--db <url>', 'Database connection string')
|
|
315
316
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
316
|
-
.option(
|
|
317
|
-
|
|
317
|
+
.option(
|
|
318
|
+
'--to <contract>',
|
|
319
|
+
'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
|
|
320
|
+
)
|
|
321
|
+
.action(async (options: MigrateCommandOptions) => {
|
|
318
322
|
const flags = parseGlobalFlags(options);
|
|
319
323
|
const startTime = Date.now();
|
|
320
324
|
|
|
@@ -323,13 +327,13 @@ export function createMigrationApplyCommand(): Command {
|
|
|
323
327
|
interactive: flags.interactive,
|
|
324
328
|
});
|
|
325
329
|
|
|
326
|
-
const result = await
|
|
330
|
+
const result = await executeMigrateCommand(options, flags, ui, startTime);
|
|
327
331
|
|
|
328
|
-
const exitCode = handleResult(result, flags, ui, (
|
|
332
|
+
const exitCode = handleResult(result, flags, ui, (migrateResult) => {
|
|
329
333
|
if (flags.json) {
|
|
330
|
-
ui.output(JSON.stringify(
|
|
334
|
+
ui.output(JSON.stringify(migrateResult, null, 2));
|
|
331
335
|
} else if (!flags.quiet) {
|
|
332
|
-
ui.log(formatMigrationApplyCommandOutput(
|
|
336
|
+
ui.log(formatMigrationApplyCommandOutput(migrateResult, flags));
|
|
333
337
|
}
|
|
334
338
|
});
|
|
335
339
|
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
3
|
+
import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
4
|
+
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
5
|
+
import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
6
|
+
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import { join, relative } from 'pathe';
|
|
9
|
+
import { loadConfig } from '../config-loader';
|
|
10
|
+
import {
|
|
11
|
+
addGlobalOptions,
|
|
12
|
+
loadMigrationPackages,
|
|
13
|
+
resolveMigrationPaths,
|
|
14
|
+
setCommandDescriptions,
|
|
15
|
+
setCommandExamples,
|
|
16
|
+
setCommandSeeAlso,
|
|
17
|
+
} from '../utils/command-helpers';
|
|
18
|
+
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
19
|
+
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
20
|
+
import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
|
|
21
|
+
import { TerminalUI } from '../utils/terminal-ui';
|
|
22
|
+
import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
|
|
23
|
+
|
|
24
|
+
interface MigrationCheckOptions extends CommonCommandOptions {
|
|
25
|
+
readonly config?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CheckFailure {
|
|
29
|
+
readonly pnCode: string;
|
|
30
|
+
readonly where: string;
|
|
31
|
+
readonly why: string;
|
|
32
|
+
readonly fix: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface MigrationCheckResult {
|
|
36
|
+
readonly ok: boolean;
|
|
37
|
+
readonly failures: readonly CheckFailure[];
|
|
38
|
+
readonly summary: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
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
|
+
function migrationPathRelative(dirPath: string): string {
|
|
47
|
+
return relative(process.cwd(), dirPath);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function migrationFileRelative(dirPath: string, fileName: string): string {
|
|
51
|
+
return join(migrationPathRelative(dirPath), fileName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function checkFileExists(dirPath: string, dirName: string, fileName: string): CheckFailure | null {
|
|
55
|
+
if (!existsSync(join(dirPath, fileName))) {
|
|
56
|
+
return {
|
|
57
|
+
pnCode: 'PN-MIG-CHECK-002',
|
|
58
|
+
where: migrationFileRelative(dirPath, fileName),
|
|
59
|
+
why: `${fileName} is missing from ${dirName}`,
|
|
60
|
+
fix: 'Re-emit the migration package or restore from version control.',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
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
|
+
function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | null {
|
|
82
|
+
const endContractPath = join(pkg.dirPath, 'end-contract.json');
|
|
83
|
+
if (!existsSync(endContractPath)) return null;
|
|
84
|
+
try {
|
|
85
|
+
const raw = JSON.parse(readFileSync(endContractPath, 'utf-8')) as Record<string, unknown>;
|
|
86
|
+
const storage = raw['storage'] as Record<string, unknown> | undefined;
|
|
87
|
+
const snapshotHash = storage?.['storageHash'];
|
|
88
|
+
if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {
|
|
89
|
+
return {
|
|
90
|
+
pnCode: 'PN-MIG-CHECK-005',
|
|
91
|
+
where: migrationPathRelative(pkg.dirPath),
|
|
92
|
+
why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but end-contract.json has storageHash=${snapshotHash}`,
|
|
93
|
+
fix: 'Re-emit the migration package so migration.json and end-contract.json agree.',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
return {
|
|
98
|
+
pnCode: 'PN-MIG-CHECK-006',
|
|
99
|
+
where: migrationPathRelative(pkg.dirPath),
|
|
100
|
+
why: `Migration "${pkg.dirName}" has an unparseable end-contract.json.`,
|
|
101
|
+
fix: 'Re-emit the migration package to repair the snapshot file.',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function executeMigrationCheckCommand(
|
|
108
|
+
target: string | undefined,
|
|
109
|
+
options: MigrationCheckOptions,
|
|
110
|
+
flags: GlobalFlags,
|
|
111
|
+
ui: TerminalUI,
|
|
112
|
+
): Promise<{ result: MigrationCheckResult; exitCode: number }> {
|
|
113
|
+
const config = await loadConfig(options.config);
|
|
114
|
+
const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
|
|
115
|
+
options.config,
|
|
116
|
+
config,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!flags.json && !flags.quiet) {
|
|
120
|
+
const details: Array<{ label: string; value: string }> = [
|
|
121
|
+
{ label: 'config', value: configPath },
|
|
122
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
123
|
+
];
|
|
124
|
+
if (target) {
|
|
125
|
+
details.push({ label: 'target', value: target });
|
|
126
|
+
}
|
|
127
|
+
const header = formatStyledHeader({
|
|
128
|
+
command: 'migration check',
|
|
129
|
+
description: 'Verify artifact and graph integrity',
|
|
130
|
+
details,
|
|
131
|
+
flags,
|
|
132
|
+
});
|
|
133
|
+
ui.stderr(header);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const failures: CheckFailure[] = [];
|
|
137
|
+
|
|
138
|
+
let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
|
|
139
|
+
let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
|
|
140
|
+
try {
|
|
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
|
+
}
|
|
169
|
+
|
|
170
|
+
if (existsSync(appMigrationsDir)) {
|
|
171
|
+
const loadedDirNames = new Set(bundles.map((p) => p.dirName));
|
|
172
|
+
try {
|
|
173
|
+
const entries = readdirSync(appMigrationsDir);
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
if (entry.startsWith('.') || entry.startsWith('_') || entry === 'refs') continue;
|
|
176
|
+
const entryPath = join(appMigrationsDir, entry);
|
|
177
|
+
try {
|
|
178
|
+
if (!statSync(entryPath).isDirectory()) continue;
|
|
179
|
+
} catch {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (!loadedDirNames.has(entry)) {
|
|
183
|
+
for (const f of ['migration.json', 'ops.json']) {
|
|
184
|
+
const fail = checkFileExists(entryPath, entry, f);
|
|
185
|
+
if (fail) failures.push(fail);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// migrations dir unreadable — skip
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (target) {
|
|
195
|
+
const refs = await readRefs(refsDir);
|
|
196
|
+
const migResult = parseMigrationRef(target, { graph, refs });
|
|
197
|
+
if (!migResult.ok) {
|
|
198
|
+
const msg =
|
|
199
|
+
migResult.failure.kind === 'not-found'
|
|
200
|
+
? `Migration "${target}" does not exist`
|
|
201
|
+
: migResult.failure.kind === 'wrong-grammar'
|
|
202
|
+
? migResult.failure.message
|
|
203
|
+
: `Invalid migration reference: "${target}"`;
|
|
204
|
+
return {
|
|
205
|
+
result: { ok: false, failures: [], summary: msg },
|
|
206
|
+
exitCode: PRECONDITION,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const matchedPkg = bundles.find(
|
|
211
|
+
(p) => p.metadata.migrationHash === migResult.value.migrationHash,
|
|
212
|
+
);
|
|
213
|
+
if (!matchedPkg) {
|
|
214
|
+
return {
|
|
215
|
+
result: {
|
|
216
|
+
ok: false,
|
|
217
|
+
failures: [],
|
|
218
|
+
summary: `Migration package for "${target}" not found on disk`,
|
|
219
|
+
},
|
|
220
|
+
exitCode: PRECONDITION,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const f of ['migration.json', 'ops.json']) {
|
|
225
|
+
const fail = checkFileExists(matchedPkg.dirPath, matchedPkg.dirName, f);
|
|
226
|
+
if (fail) failures.push(fail);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const verification = verifyMigrationHash(matchedPkg);
|
|
230
|
+
if (!verification.ok) {
|
|
231
|
+
failures.push({
|
|
232
|
+
pnCode: 'PN-MIG-CHECK-001',
|
|
233
|
+
where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),
|
|
234
|
+
why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
|
|
235
|
+
fix: 'Re-emit the migration package or restore from version control.',
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
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
|
+
const snapshotFailure = checkSnapshotConsistency(matchedPkg);
|
|
243
|
+
if (snapshotFailure) failures.push(snapshotFailure);
|
|
244
|
+
} 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
|
+
for (const pkg of bundles) {
|
|
263
|
+
const snapshotFailure = checkSnapshotConsistency(pkg);
|
|
264
|
+
if (snapshotFailure) failures.push(snapshotFailure);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const allToHashes = new Set(bundles.map((p) => p.metadata.to));
|
|
268
|
+
for (const pkg of bundles) {
|
|
269
|
+
const isReachable =
|
|
270
|
+
pkg.metadata.from === null ||
|
|
271
|
+
allToHashes.has(pkg.metadata.from) ||
|
|
272
|
+
pkg.metadata.from === 'sha256:empty';
|
|
273
|
+
if (!isReachable) {
|
|
274
|
+
failures.push({
|
|
275
|
+
pnCode: 'PN-MIG-CHECK-003',
|
|
276
|
+
where: migrationPathRelative(pkg.dirPath),
|
|
277
|
+
why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
|
|
278
|
+
fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const refs = await readRefs(refsDir);
|
|
285
|
+
for (const [name, entry] of Object.entries(refs)) {
|
|
286
|
+
if (!graph.nodes.has(entry.hash)) {
|
|
287
|
+
failures.push({
|
|
288
|
+
pnCode: 'PN-MIG-CHECK-004',
|
|
289
|
+
where: relative(process.cwd(), join(refsDir, `${name}.json`)),
|
|
290
|
+
why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
|
|
291
|
+
fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
// Refs unreadable — skip ref checks
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (failures.length === 0) {
|
|
301
|
+
return {
|
|
302
|
+
result: { ok: true, failures: [], summary: 'All checks passed' },
|
|
303
|
+
exitCode: OK,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
|
|
309
|
+
exitCode: INTEGRITY_FAILED,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function createMigrationCheckCommand(): Command {
|
|
314
|
+
const command = new Command('check');
|
|
315
|
+
setCommandDescriptions(
|
|
316
|
+
command,
|
|
317
|
+
'Verify artifact and graph integrity',
|
|
318
|
+
'Validates that on-disk migration packages are internally consistent\n' +
|
|
319
|
+
'(hashes match, manifests are complete) and that the graph is well-formed\n' +
|
|
320
|
+
'(edges connect, refs point at valid nodes). Offline — does not consult\n' +
|
|
321
|
+
'the database.',
|
|
322
|
+
);
|
|
323
|
+
setCommandExamples(command, [
|
|
324
|
+
'prisma-next migration check',
|
|
325
|
+
'prisma-next migration check 20260101-add-users',
|
|
326
|
+
'prisma-next migration check --json',
|
|
327
|
+
]);
|
|
328
|
+
setCommandSeeAlso(command, [
|
|
329
|
+
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
330
|
+
{ verb: 'migration list', oneLiner: 'List on-disk migrations' },
|
|
331
|
+
{ verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
|
|
332
|
+
]);
|
|
333
|
+
command.exitOverride();
|
|
334
|
+
addGlobalOptions(command)
|
|
335
|
+
.argument('[migration]', 'Migration reference (directory name or hash) to check')
|
|
336
|
+
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
337
|
+
.action(async (target: string | undefined, options: MigrationCheckOptions) => {
|
|
338
|
+
const flags = parseGlobalFlags(options);
|
|
339
|
+
const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
|
|
340
|
+
|
|
341
|
+
let result: MigrationCheckResult;
|
|
342
|
+
let exitCode: number;
|
|
343
|
+
try {
|
|
344
|
+
({ result, exitCode } = await executeMigrationCheckCommand(target, options, flags, ui));
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
347
|
+
result = { ok: false, failures: [], summary: msg };
|
|
348
|
+
exitCode = PRECONDITION;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (flags.json) {
|
|
352
|
+
ui.output(JSON.stringify(result, null, 2));
|
|
353
|
+
} else if (!flags.quiet) {
|
|
354
|
+
if (result.ok) {
|
|
355
|
+
ui.log(`✔ ${result.summary}`);
|
|
356
|
+
} else {
|
|
357
|
+
for (const f of result.failures) {
|
|
358
|
+
ui.log(`✗ [${f.pnCode}] ${f.where}: ${f.why}`);
|
|
359
|
+
ui.log(` fix: ${f.fix}`);
|
|
360
|
+
}
|
|
361
|
+
ui.log(`\n${result.summary}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
process.exit(exitCode);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return command;
|
|
369
|
+
}
|