@prisma-next/cli 0.3.0-dev.55 → 0.3.0-dev.63
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/LICENSE +201 -0
- package/README.md +145 -6
- package/dist/cli-errors-JlPTsazx.mjs +3 -0
- package/dist/cli.mjs +29 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-B7f4PZZ1.mjs → client-PimzSD1f.mjs} +155 -82
- package/dist/client-PimzSD1f.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +5 -3
- package/dist/commands/contract-emit.mjs.map +1 -1
- package/dist/commands/db-init.mjs +5 -4
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-introspect.mjs +5 -3
- package/dist/commands/db-introspect.mjs.map +1 -1
- package/dist/commands/db-schema-verify.d.mts.map +1 -1
- package/dist/commands/db-schema-verify.mjs +6 -4
- package/dist/commands/db-schema-verify.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +6 -4
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +5 -4
- 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 +6 -4
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/commands/migration-apply.d.mts +23 -0
- package/dist/commands/migration-apply.d.mts.map +1 -0
- package/dist/commands/migration-apply.mjs +249 -0
- package/dist/commands/migration-apply.mjs.map +1 -0
- package/dist/commands/migration-plan.d.mts +25 -0
- package/dist/commands/migration-plan.d.mts.map +1 -0
- package/dist/commands/migration-plan.mjs +266 -0
- package/dist/commands/migration-plan.mjs.map +1 -0
- package/dist/commands/migration-show.d.mts +28 -0
- package/dist/commands/migration-show.d.mts.map +1 -0
- package/dist/commands/migration-show.mjs +138 -0
- package/dist/commands/migration-show.mjs.map +1 -0
- package/dist/commands/migration-status.d.mts +35 -0
- package/dist/commands/migration-status.d.mts.map +1 -0
- package/dist/commands/migration-status.mjs +259 -0
- package/dist/commands/migration-status.mjs.map +1 -0
- package/dist/commands/migration-verify.d.mts +16 -0
- package/dist/commands/migration-verify.d.mts.map +1 -0
- package/dist/commands/migration-verify.mjs +86 -0
- package/dist/commands/migration-verify.mjs.map +1 -0
- package/dist/{config-loader-DqKf1qSa.mjs → config-loader-PPf4CtDj.mjs} +4 -3
- package/dist/config-loader-PPf4CtDj.mjs.map +1 -0
- package/dist/config-loader.d.mts +1 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/exports/config-types.d.mts +1 -1
- package/dist/exports/config-types.mjs +1 -1
- package/dist/exports/control-api.d.mts +100 -2
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +3 -2
- package/dist/exports/control-api.mjs.map +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/extract-sql-ddl-BmlKvk4o.mjs +26 -0
- package/dist/extract-sql-ddl-BmlKvk4o.mjs.map +1 -0
- package/dist/framework-components-CjV_jD8f.mjs +59 -0
- package/dist/framework-components-CjV_jD8f.mjs.map +1 -0
- package/dist/{migration-command-scaffold-BELw_do2.mjs → migration-command-scaffold-DfY_F3ev.mjs} +7 -5
- package/dist/migration-command-scaffold-DfY_F3ev.mjs.map +1 -0
- package/dist/progress-adapter-DENrzF6I.mjs +49 -0
- package/dist/progress-adapter-DENrzF6I.mjs.map +1 -0
- package/dist/{result-handler-BhmrXIvT.mjs → result-handler-iA9JtUC7.mjs} +158 -51
- package/dist/result-handler-iA9JtUC7.mjs.map +1 -0
- package/package.json +34 -12
- package/src/cli.ts +38 -0
- package/src/commands/db-schema-verify.ts +6 -4
- package/src/commands/db-sign.ts +6 -4
- package/src/commands/db-verify.ts +6 -4
- package/src/commands/migration-apply.ts +431 -0
- package/src/commands/migration-plan.ts +446 -0
- package/src/commands/migration-show.ts +255 -0
- package/src/commands/migration-status.ts +436 -0
- package/src/commands/migration-verify.ts +151 -0
- package/src/config-loader.ts +13 -3
- package/src/control-api/client.ts +31 -0
- package/src/control-api/operations/migration-apply.ts +195 -0
- package/src/control-api/types.ts +113 -1
- package/src/exports/config-types.ts +3 -3
- package/src/utils/command-helpers.ts +11 -0
- package/src/utils/migration-command-scaffold.ts +7 -6
- package/src/utils/output.ts +305 -3
- package/dist/client-B7f4PZZ1.mjs.map +0 -1
- package/dist/config-loader-DqKf1qSa.mjs.map +0 -1
- package/dist/migration-command-scaffold-BELw_do2.mjs.map +0 -1
- package/dist/result-handler-BhmrXIvT.mjs.map +0 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { t as loadConfig } from "../config-loader-PPf4CtDj.mjs";
|
|
2
|
+
import { _ as errorUnexpected, a as errorDatabaseConnectionRequired, h as errorTargetMigrationNotSupported, m as errorRuntime, s as errorDriverRequired, t as CliStructuredError } from "../cli-errors-JlPTsazx.mjs";
|
|
3
|
+
import { t as createControlClient } from "../client-PimzSD1f.mjs";
|
|
4
|
+
import { C as parseGlobalFlags, D as setCommandDescriptions, T as resolveContractPath, n as formatCommandHelp, s as formatMigrationApplyCommandOutput, t as handleResult, w as maskConnectionUrl, y as formatStyledHeader } from "../result-handler-iA9JtUC7.mjs";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
7
|
+
import { relative, resolve } from "pathe";
|
|
8
|
+
import { EMPTY_CONTRACT_HASH } from "@prisma-next/core-control-plane/constants";
|
|
9
|
+
import { readFile } from "node:fs/promises";
|
|
10
|
+
import { findPath, reconstructGraph } from "@prisma-next/migration-tools/dag";
|
|
11
|
+
import { readMigrationsDir } from "@prisma-next/migration-tools/io";
|
|
12
|
+
import { MigrationToolsError } from "@prisma-next/migration-tools/types";
|
|
13
|
+
|
|
14
|
+
//#region src/commands/migration-apply.ts
|
|
15
|
+
function mapMigrationToolsError(error) {
|
|
16
|
+
if (MigrationToolsError.is(error)) return errorRuntime(error.message, {
|
|
17
|
+
why: error.why,
|
|
18
|
+
fix: error.fix,
|
|
19
|
+
meta: {
|
|
20
|
+
code: error.code,
|
|
21
|
+
...error.details ?? {}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}` });
|
|
25
|
+
}
|
|
26
|
+
function mapApplyFailure(failure) {
|
|
27
|
+
return errorRuntime(failure.summary, {
|
|
28
|
+
why: failure.why ?? "Migration runner failed",
|
|
29
|
+
fix: "Fix the issue and re-run `prisma-next migration apply` — previously applied migrations are preserved.",
|
|
30
|
+
meta: failure.meta ?? {}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function packageToStep(pkg) {
|
|
34
|
+
return {
|
|
35
|
+
dirName: pkg.dirName,
|
|
36
|
+
from: pkg.manifest.from,
|
|
37
|
+
to: pkg.manifest.to,
|
|
38
|
+
toContract: pkg.manifest.toContract,
|
|
39
|
+
operations: pkg.ops
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function executeMigrationApplyCommand(options, flags, startTime) {
|
|
43
|
+
const config = await loadConfig(options.config);
|
|
44
|
+
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
|
|
45
|
+
const migrationsDir = resolve(options.config ? resolve(options.config, "..") : process.cwd(), config.migrations?.dir ?? "migrations");
|
|
46
|
+
const migrationsRelative = relative(process.cwd(), migrationsDir);
|
|
47
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
48
|
+
if (!dbConnection) return notOk(errorDatabaseConnectionRequired({ why: `Database connection is required for migration apply (set db.connection in ${configPath}, or pass --db <url>)` }));
|
|
49
|
+
if (!config.driver) return notOk(errorDriverRequired({ why: "Config.driver is required for migration apply" }));
|
|
50
|
+
if (!config.target.migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
|
|
51
|
+
let destinationHash;
|
|
52
|
+
try {
|
|
53
|
+
const contractPathAbsolute = resolveContractPath(config);
|
|
54
|
+
const contractHash = JSON.parse(await readFile(contractPathAbsolute, "utf-8"))["storageHash"];
|
|
55
|
+
if (typeof contractHash !== "string") return notOk(errorRuntime("Current contract is missing storage hash", {
|
|
56
|
+
why: `The contract at ${relative(process.cwd(), contractPathAbsolute)} does not contain a valid storageHash`,
|
|
57
|
+
fix: "Run `prisma-next contract emit` and re-run `prisma-next migration apply`."
|
|
58
|
+
}));
|
|
59
|
+
destinationHash = contractHash;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return notOk(errorRuntime("Current contract is unavailable", {
|
|
62
|
+
why: `Failed to read contract hash before apply: ${error instanceof Error ? error.message : String(error)}`,
|
|
63
|
+
fix: "Run `prisma-next contract emit` to generate a valid contract.json, then retry apply."
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
67
|
+
const details = [{
|
|
68
|
+
label: "config",
|
|
69
|
+
value: configPath
|
|
70
|
+
}, {
|
|
71
|
+
label: "migrations",
|
|
72
|
+
value: migrationsRelative
|
|
73
|
+
}];
|
|
74
|
+
if (typeof dbConnection === "string") details.push({
|
|
75
|
+
label: "database",
|
|
76
|
+
value: maskConnectionUrl(dbConnection)
|
|
77
|
+
});
|
|
78
|
+
const header = formatStyledHeader({
|
|
79
|
+
command: "migration apply",
|
|
80
|
+
description: "Apply planned migrations to the database",
|
|
81
|
+
url: "https://pris.ly/migration-apply",
|
|
82
|
+
details,
|
|
83
|
+
flags
|
|
84
|
+
});
|
|
85
|
+
console.log(header);
|
|
86
|
+
}
|
|
87
|
+
let packages;
|
|
88
|
+
try {
|
|
89
|
+
packages = (await readMigrationsDir(migrationsDir)).filter((p) => typeof p.manifest.migrationId === "string");
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
if (packages.length === 0) {
|
|
95
|
+
const client$1 = createControlClient({
|
|
96
|
+
family: config.family,
|
|
97
|
+
target: config.target,
|
|
98
|
+
adapter: config.adapter,
|
|
99
|
+
driver: config.driver,
|
|
100
|
+
extensionPacks: config.extensionPacks ?? []
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
await client$1.connect(dbConnection);
|
|
104
|
+
const markerHash = (await client$1.readMarker())?.storageHash ?? EMPTY_CONTRACT_HASH;
|
|
105
|
+
if (markerHash !== EMPTY_CONTRACT_HASH) return notOk(errorRuntime("Database has state but no migrations exist", {
|
|
106
|
+
why: `The database marker hash "${markerHash}" exists but no attested migrations were found in ${migrationsRelative}`,
|
|
107
|
+
fix: "Ensure the migrations directory is correct, or reset the database with `prisma-next db init`.",
|
|
108
|
+
meta: {
|
|
109
|
+
markerHash,
|
|
110
|
+
migrationsDir: migrationsRelative
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
if (destinationHash !== EMPTY_CONTRACT_HASH) return notOk(errorRuntime("Current contract has no planned migrations", {
|
|
114
|
+
why: `No attested migrations were found in ${migrationsRelative}, but current contract hash is "${destinationHash}"`,
|
|
115
|
+
fix: "Run `prisma-next migration plan` to create an attested migration for the current contract.",
|
|
116
|
+
meta: {
|
|
117
|
+
destinationHash,
|
|
118
|
+
migrationsDir: migrationsRelative
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (CliStructuredError.is(error)) return notOk(error);
|
|
123
|
+
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}` }));
|
|
124
|
+
} finally {
|
|
125
|
+
await client$1.close();
|
|
126
|
+
}
|
|
127
|
+
return ok({
|
|
128
|
+
ok: true,
|
|
129
|
+
migrationsApplied: 0,
|
|
130
|
+
migrationsTotal: 0,
|
|
131
|
+
markerHash: EMPTY_CONTRACT_HASH,
|
|
132
|
+
applied: [],
|
|
133
|
+
summary: "No attested migrations found",
|
|
134
|
+
timings: { total: Date.now() - startTime }
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
let graph;
|
|
138
|
+
try {
|
|
139
|
+
graph = reconstructGraph(packages);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
const client = createControlClient({
|
|
145
|
+
family: config.family,
|
|
146
|
+
target: config.target,
|
|
147
|
+
adapter: config.adapter,
|
|
148
|
+
driver: config.driver,
|
|
149
|
+
extensionPacks: config.extensionPacks ?? []
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
await client.connect(dbConnection);
|
|
153
|
+
const marker = await client.readMarker();
|
|
154
|
+
if (marker?.storageHash === EMPTY_CONTRACT_HASH) return notOk(errorRuntime("Database marker contains the empty sentinel hash", {
|
|
155
|
+
why: `The marker row exists but contains the empty sentinel value "${EMPTY_CONTRACT_HASH}". This should never happen — the marker should contain the hash of the last applied contract.`,
|
|
156
|
+
fix: "The marker is corrupted. Reset the database with `prisma-next db init`, or manually update the marker to the correct contract hash.",
|
|
157
|
+
meta: { markerHash: EMPTY_CONTRACT_HASH }
|
|
158
|
+
}));
|
|
159
|
+
const markerHash = marker?.storageHash ?? EMPTY_CONTRACT_HASH;
|
|
160
|
+
if (markerHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(markerHash)) return notOk(errorRuntime("Database marker does not match any known migration", {
|
|
161
|
+
why: `The database marker hash "${markerHash}" is not found in the migration history at ${migrationsRelative}`,
|
|
162
|
+
fix: "Ensure the migrations directory matches this database, or reset the database with `prisma-next db init`.",
|
|
163
|
+
meta: {
|
|
164
|
+
markerHash,
|
|
165
|
+
knownNodes: [...graph.nodes]
|
|
166
|
+
}
|
|
167
|
+
}));
|
|
168
|
+
if (!graph.nodes.has(destinationHash)) return notOk(errorRuntime("Current contract has no planned migration path", {
|
|
169
|
+
why: `Current contract hash "${destinationHash}" is not present in the migration history at ${migrationsRelative}`,
|
|
170
|
+
fix: "Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.",
|
|
171
|
+
meta: {
|
|
172
|
+
destinationHash,
|
|
173
|
+
knownNodes: [...graph.nodes]
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
const pendingPath = findPath(graph, markerHash, destinationHash);
|
|
177
|
+
if (!pendingPath) return notOk(errorRuntime("No migration path from current state to target", {
|
|
178
|
+
why: `Cannot find a path from marker hash "${markerHash}" to target "${destinationHash}"`,
|
|
179
|
+
fix: "Check the migration history for gaps or inconsistencies.",
|
|
180
|
+
meta: {
|
|
181
|
+
markerHash,
|
|
182
|
+
destinationHash
|
|
183
|
+
}
|
|
184
|
+
}));
|
|
185
|
+
if (pendingPath.length === 0) return ok({
|
|
186
|
+
ok: true,
|
|
187
|
+
migrationsApplied: 0,
|
|
188
|
+
migrationsTotal: 0,
|
|
189
|
+
markerHash,
|
|
190
|
+
applied: [],
|
|
191
|
+
summary: "Already up to date",
|
|
192
|
+
timings: { total: Date.now() - startTime }
|
|
193
|
+
});
|
|
194
|
+
const packageByDir = new Map(packages.map((pkg) => [pkg.dirName, pkg]));
|
|
195
|
+
const pendingMigrations = [];
|
|
196
|
+
for (const migration of pendingPath) {
|
|
197
|
+
const pkg = packageByDir.get(migration.dirName);
|
|
198
|
+
if (!pkg) return notOk(errorRuntime(`Migration package not found: ${migration.dirName}`, {
|
|
199
|
+
why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,
|
|
200
|
+
fix: "Ensure all migration directories are present and intact."
|
|
201
|
+
}));
|
|
202
|
+
pendingMigrations.push(packageToStep(pkg));
|
|
203
|
+
}
|
|
204
|
+
if (!flags.quiet && flags.json !== "object") for (const migration of pendingMigrations) console.log(` Applying ${migration.dirName}...`);
|
|
205
|
+
const applyResult = await client.migrationApply({
|
|
206
|
+
originHash: markerHash,
|
|
207
|
+
destinationHash,
|
|
208
|
+
pendingMigrations
|
|
209
|
+
});
|
|
210
|
+
if (!applyResult.ok) return notOk(mapApplyFailure(applyResult.failure));
|
|
211
|
+
const { value } = applyResult;
|
|
212
|
+
return ok({
|
|
213
|
+
ok: true,
|
|
214
|
+
migrationsApplied: value.migrationsApplied,
|
|
215
|
+
migrationsTotal: pendingPath.length,
|
|
216
|
+
markerHash: value.markerHash,
|
|
217
|
+
applied: value.applied,
|
|
218
|
+
summary: value.summary,
|
|
219
|
+
timings: { total: Date.now() - startTime }
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (CliStructuredError.is(error)) return notOk(error);
|
|
223
|
+
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}` }));
|
|
224
|
+
} finally {
|
|
225
|
+
await client.close();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function createMigrationApplyCommand() {
|
|
229
|
+
const command = new Command("apply");
|
|
230
|
+
setCommandDescriptions(command, "Apply planned migrations to the database", "Applies previously planned migrations (created by `migration plan`) to a live database.\nCompares the database marker against the migration chain to determine which\nmigrations are pending, then executes them sequentially. Each migration runs\nin its own transaction. Does not plan new migrations — run `migration plan` first.");
|
|
231
|
+
command.configureHelp({ formatHelp: (cmd) => {
|
|
232
|
+
return formatCommandHelp({
|
|
233
|
+
command: cmd,
|
|
234
|
+
flags: parseGlobalFlags({})
|
|
235
|
+
});
|
|
236
|
+
} }).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object)", false).option("-q, --quiet", "Quiet mode: errors only").option("-v, --verbose", "Verbose output: debug info, timings").option("-vv, --trace", "Trace output: deep internals, stack traces").option("--timestamps", "Add timestamps to output").option("--color", "Force color output").option("--no-color", "Disable color output").action(async (options) => {
|
|
237
|
+
const flags = parseGlobalFlags(options);
|
|
238
|
+
const exitCode = handleResult(await executeMigrationApplyCommand(options, flags, Date.now()), flags, (applyResult) => {
|
|
239
|
+
if (flags.json === "object") console.log(JSON.stringify(applyResult, null, 2));
|
|
240
|
+
else if (!flags.quiet) console.log(formatMigrationApplyCommandOutput(applyResult, flags));
|
|
241
|
+
});
|
|
242
|
+
process.exit(exitCode);
|
|
243
|
+
});
|
|
244
|
+
return command;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//#endregion
|
|
248
|
+
export { createMigrationApplyCommand };
|
|
249
|
+
//# sourceMappingURL=migration-apply.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-apply.mjs","names":["destinationHash: string","details: Array<{ label: string; value: string }>","packages: readonly MigrationPackage[]","client","graph: MigrationGraph","pendingMigrations: MigrationApplyStep[]"],"sources":["../../src/commands/migration-apply.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/core-control-plane/constants';\nimport { findPath, reconstructGraph } from '@prisma-next/migration-tools/dag';\nimport { readMigrationsDir } from '@prisma-next/migration-tools/io';\nimport type { MigrationGraph, MigrationPackage } from '@prisma-next/migration-tools/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { relative, resolve } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport type { MigrationApplyFailure, MigrationApplyStep } from '../control-api/types';\nimport {\n CliStructuredError,\n type CliStructuredError as CliStructuredErrorType,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n maskConnectionUrl,\n resolveContractPath,\n setCommandDescriptions,\n} from '../utils/command-helpers';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport {\n formatCommandHelp,\n formatMigrationApplyCommandOutput,\n formatStyledHeader,\n} from '../utils/output';\nimport { handleResult } from '../utils/result-handler';\n\ninterface MigrationApplyCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport interface MigrationApplyResult {\n readonly ok: boolean;\n readonly migrationsApplied: number;\n readonly migrationsTotal: number;\n readonly markerHash: string;\n readonly applied: readonly {\n readonly dirName: string;\n readonly from: string;\n readonly to: string;\n readonly operationsExecuted: number;\n }[];\n readonly summary: string;\n readonly timings: {\n readonly total: number;\n };\n}\n\nfunction mapMigrationToolsError(error: unknown): CliStructuredErrorType {\n if (MigrationToolsError.is(error)) {\n return errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code, ...(error.details ?? {}) },\n });\n }\n return errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n });\n}\n\nfunction mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {\n return errorRuntime(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix: 'Fix the issue and re-run `prisma-next migration apply` — previously applied migrations are preserved.',\n meta: failure.meta ?? {},\n });\n}\n\nfunction packageToStep(pkg: MigrationPackage): MigrationApplyStep {\n return {\n dirName: pkg.dirName,\n from: pkg.manifest.from,\n to: pkg.manifest.to,\n toContract: pkg.manifest.toContract,\n operations: pkg.ops as MigrationApplyStep['operations'],\n };\n}\n\nasync function executeMigrationApplyCommand(\n options: MigrationApplyCommandOptions,\n flags: GlobalFlags,\n startTime: number,\n): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n\n const migrationsDir = resolve(\n options.config ? resolve(options.config, '..') : process.cwd(),\n config.migrations?.dir ?? 'migrations',\n );\n const migrationsRelative = relative(process.cwd(), migrationsDir);\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for migration apply (set db.connection in ${configPath}, or pass --db <url>)`,\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(errorDriverRequired({ why: 'Config.driver is required for migration apply' }));\n }\n\n const targetWithMigrations = config.target as typeof config.target & {\n readonly migrations?: unknown;\n };\n if (!targetWithMigrations.migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n let destinationHash: string;\n try {\n const contractPathAbsolute = resolveContractPath(config);\n const contractRaw = JSON.parse(await readFile(contractPathAbsolute, 'utf-8')) as Record<\n string,\n unknown\n >;\n const contractHash = contractRaw['storageHash'];\n if (typeof contractHash !== 'string') {\n return notOk(\n errorRuntime('Current contract is missing storage hash', {\n why: `The contract at ${relative(process.cwd(), contractPathAbsolute)} does not contain a valid storageHash`,\n fix: 'Run `prisma-next contract emit` and re-run `prisma-next migration apply`.',\n }),\n );\n }\n destinationHash = contractHash;\n } catch (error) {\n return notOk(\n errorRuntime('Current contract is unavailable', {\n why: `Failed to read contract hash before apply: ${error instanceof Error ? error.message : String(error)}`,\n fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry apply.',\n }),\n );\n }\n\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (typeof dbConnection === 'string') {\n details.push({ label: 'database', value: maskConnectionUrl(dbConnection) });\n }\n const header = formatStyledHeader({\n command: 'migration apply',\n description: 'Apply planned migrations to the database',\n url: 'https://pris.ly/migration-apply',\n details,\n flags,\n });\n console.log(header);\n }\n\n // Read migrations and build migration chain model (offline — no DB needed)\n let packages: readonly MigrationPackage[];\n try {\n const allPackages = await readMigrationsDir(migrationsDir);\n packages = allPackages.filter((p) => typeof p.manifest.migrationId === 'string');\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n if (packages.length === 0) {\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n try {\n await client.connect(dbConnection);\n const marker = await client.readMarker();\n const markerHash = marker?.storageHash ?? EMPTY_CONTRACT_HASH;\n if (markerHash !== EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Database has state but no migrations exist', {\n why: `The database marker hash \"${markerHash}\" exists but no attested migrations were found in ${migrationsRelative}`,\n fix: 'Ensure the migrations directory is correct, or reset the database with `prisma-next db init`.',\n meta: { markerHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n if (destinationHash !== EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Current contract has no planned migrations', {\n why: `No attested migrations were found in ${migrationsRelative}, but current contract hash is \"${destinationHash}\"`,\n fix: 'Run `prisma-next migration plan` to create an attested migration for the current contract.',\n meta: { destinationHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash: EMPTY_CONTRACT_HASH,\n applied: [],\n summary: 'No attested migrations found',\n timings: { total: Date.now() - startTime },\n });\n }\n\n let graph: MigrationGraph;\n try {\n graph = reconstructGraph(packages);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n // Create control client for all DB operations\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n\n try {\n await client.connect(dbConnection);\n\n const marker = await client.readMarker();\n\n // Distinguish \"no marker row\" (null) from \"marker row exists with the\n // empty sentinel\". The sentinel should never appear in a real marker row —\n // if it does, the marker was corrupted and replaying all migrations would\n // be dangerous (the DB likely already has tables).\n if (marker?.storageHash === EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Database marker contains the empty sentinel hash', {\n why: `The marker row exists but contains the empty sentinel value \"${EMPTY_CONTRACT_HASH}\". This should never happen — the marker should contain the hash of the last applied contract.`,\n fix: 'The marker is corrupted. Reset the database with `prisma-next db init`, or manually update the marker to the correct contract hash.',\n meta: { markerHash: EMPTY_CONTRACT_HASH },\n }),\n );\n }\n\n const markerHash = marker?.storageHash ?? EMPTY_CONTRACT_HASH;\n\n if (markerHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(markerHash)) {\n return notOk(\n errorRuntime('Database marker does not match any known migration', {\n why: `The database marker hash \"${markerHash}\" is not found in the migration history at ${migrationsRelative}`,\n fix: 'Ensure the migrations directory matches this database, or reset the database with `prisma-next db init`.',\n meta: { markerHash, knownNodes: [...graph.nodes] },\n }),\n );\n }\n\n if (!graph.nodes.has(destinationHash)) {\n return notOk(\n errorRuntime('Current contract has no planned migration path', {\n why: `Current contract hash \"${destinationHash}\" is not present in the migration history at ${migrationsRelative}`,\n fix: 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',\n meta: { destinationHash, knownNodes: [...graph.nodes] },\n }),\n );\n }\n\n const pendingPath = findPath(graph, markerHash, destinationHash);\n if (!pendingPath) {\n return notOk(\n errorRuntime('No migration path from current state to target', {\n why: `Cannot find a path from marker hash \"${markerHash}\" to target \"${destinationHash}\"`,\n fix: 'Check the migration history for gaps or inconsistencies.',\n meta: { markerHash, destinationHash },\n }),\n );\n }\n\n if (pendingPath.length === 0) {\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash,\n applied: [],\n summary: 'Already up to date',\n timings: { total: Date.now() - startTime },\n });\n }\n\n // Resolve graph edges to full apply-ready edges\n const packageByDir = new Map(packages.map((pkg) => [pkg.dirName, pkg]));\n const pendingMigrations: MigrationApplyStep[] = [];\n for (const migration of pendingPath) {\n const pkg = packageByDir.get(migration.dirName);\n if (!pkg) {\n return notOk(\n errorRuntime(`Migration package not found: ${migration.dirName}`, {\n why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,\n fix: 'Ensure all migration directories are present and intact.',\n }),\n );\n }\n pendingMigrations.push(packageToStep(pkg));\n }\n\n if (!flags.quiet && flags.json !== 'object') {\n for (const migration of pendingMigrations) {\n console.log(` Applying ${migration.dirName}...`);\n }\n }\n\n const applyResult = await client.migrationApply({\n originHash: markerHash,\n destinationHash,\n pendingMigrations,\n });\n\n if (!applyResult.ok) {\n return notOk(mapApplyFailure(applyResult.failure));\n }\n\n const { value } = applyResult;\n\n return ok({\n ok: true,\n migrationsApplied: value.migrationsApplied,\n migrationsTotal: pendingPath.length,\n markerHash: value.markerHash,\n applied: value.applied,\n summary: value.summary,\n timings: { total: Date.now() - startTime },\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrationApplyCommand(): Command {\n const command = new Command('apply');\n setCommandDescriptions(\n command,\n 'Apply planned migrations to the database',\n 'Applies previously planned migrations (created by `migration plan`) to a live database.\\n' +\n 'Compares the database marker against the migration chain to determine which\\n' +\n 'migrations are pending, then executes them sequentially. Each migration runs\\n' +\n 'in its own transaction. Does not plan new migrations — run `migration plan` first.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const defaultFlags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags: defaultFlags });\n },\n })\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--json [format]', 'Output as JSON (object)', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: MigrationApplyCommandOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const result = await executeMigrationApplyCommand(options, flags, startTime);\n\n const exitCode = handleResult(result, flags, (applyResult) => {\n if (flags.json === 'object') {\n console.log(JSON.stringify(applyResult, null, 2));\n } else if (!flags.quiet) {\n console.log(formatMigrationApplyCommandOutput(applyResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
//#region src/commands/migration-plan.d.ts
|
|
4
|
+
interface MigrationPlanResult {
|
|
5
|
+
readonly ok: boolean;
|
|
6
|
+
readonly noOp: boolean;
|
|
7
|
+
readonly from: string;
|
|
8
|
+
readonly to: string;
|
|
9
|
+
readonly migrationId?: string;
|
|
10
|
+
readonly dir?: string;
|
|
11
|
+
readonly operations: readonly {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly label: string;
|
|
14
|
+
readonly operationClass: string;
|
|
15
|
+
}[];
|
|
16
|
+
readonly sql?: readonly string[];
|
|
17
|
+
readonly summary: string;
|
|
18
|
+
readonly timings: {
|
|
19
|
+
readonly total: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
declare function createMigrationPlanCommand(): Command;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { MigrationPlanResult, createMigrationPlanCommand };
|
|
25
|
+
//# sourceMappingURL=migration-plan.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-plan.d.mts","names":[],"sources":["../../src/commands/migration-plan.ts"],"sourcesContent":[],"mappings":";;;UAqDiB,mBAAA;;EAAA,SAAA,IAAA,EAAA,OAAmB;EAsRpB,SAAA,IAAA,EAAA,MAAA;;;;;;;;;;;;;;;iBAAA,0BAAA,CAAA,GAA8B"}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { t as loadConfig } from "../config-loader-PPf4CtDj.mjs";
|
|
2
|
+
import { _ as errorUnexpected, c as errorFileNotFound, f as errorMigrationPlanningFailed, h as errorTargetMigrationNotSupported, i as errorContractValidationFailed, m as errorRuntime } from "../cli-errors-JlPTsazx.mjs";
|
|
3
|
+
import { t as assertFrameworkComponentsCompatible } from "../framework-components-CjV_jD8f.mjs";
|
|
4
|
+
import { t as extractSqlDdl } from "../extract-sql-ddl-BmlKvk4o.mjs";
|
|
5
|
+
import { C as parseGlobalFlags, D as setCommandDescriptions, T as resolveContractPath, n as formatCommandHelp, t as handleResult, y as formatStyledHeader } from "../result-handler-iA9JtUC7.mjs";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
8
|
+
import { join, relative, resolve } from "pathe";
|
|
9
|
+
import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
|
|
10
|
+
import { EMPTY_CONTRACT_HASH } from "@prisma-next/core-control-plane/constants";
|
|
11
|
+
import { readFile } from "node:fs/promises";
|
|
12
|
+
import { findLatestMigration, reconstructGraph } from "@prisma-next/migration-tools/dag";
|
|
13
|
+
import { formatMigrationDirName, readMigrationsDir, writeMigrationPackage } from "@prisma-next/migration-tools/io";
|
|
14
|
+
import { MigrationToolsError } from "@prisma-next/migration-tools/types";
|
|
15
|
+
import { attestMigration } from "@prisma-next/migration-tools/attestation";
|
|
16
|
+
|
|
17
|
+
//#region src/commands/migration-plan.ts
|
|
18
|
+
function mapMigrationToolsError(error) {
|
|
19
|
+
if (MigrationToolsError.is(error)) return errorRuntime(error.message, {
|
|
20
|
+
why: error.why,
|
|
21
|
+
fix: error.fix,
|
|
22
|
+
meta: {
|
|
23
|
+
code: error.code,
|
|
24
|
+
...error.details ?? {}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during migration plan: ${error instanceof Error ? error.message : String(error)}` });
|
|
28
|
+
}
|
|
29
|
+
async function executeMigrationPlanCommand(options, flags, startTime) {
|
|
30
|
+
const config = await loadConfig(options.config);
|
|
31
|
+
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
|
|
32
|
+
const migrationsDir = resolve(options.config ? resolve(options.config, "..") : process.cwd(), config.migrations?.dir ?? "migrations");
|
|
33
|
+
const migrationsRelative = relative(process.cwd(), migrationsDir);
|
|
34
|
+
const contractPathAbsolute = resolveContractPath(config);
|
|
35
|
+
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
36
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
37
|
+
const details = [
|
|
38
|
+
{
|
|
39
|
+
label: "config",
|
|
40
|
+
value: configPath
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: "contract",
|
|
44
|
+
value: contractPath
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: "migrations",
|
|
48
|
+
value: migrationsRelative
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
if (options.from) details.push({
|
|
52
|
+
label: "from",
|
|
53
|
+
value: options.from
|
|
54
|
+
});
|
|
55
|
+
if (options.name) details.push({
|
|
56
|
+
label: "name",
|
|
57
|
+
value: options.name
|
|
58
|
+
});
|
|
59
|
+
const header = formatStyledHeader({
|
|
60
|
+
command: "migration plan",
|
|
61
|
+
description: "Plan a migration from contract changes",
|
|
62
|
+
url: "https://pris.ly/migration-plan",
|
|
63
|
+
details,
|
|
64
|
+
flags
|
|
65
|
+
});
|
|
66
|
+
console.log(header);
|
|
67
|
+
}
|
|
68
|
+
let contractJsonContent;
|
|
69
|
+
try {
|
|
70
|
+
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
|
|
73
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
74
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
75
|
+
}));
|
|
76
|
+
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
|
|
77
|
+
}
|
|
78
|
+
let toContractJson;
|
|
79
|
+
try {
|
|
80
|
+
toContractJson = JSON.parse(contractJsonContent);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
|
|
83
|
+
}
|
|
84
|
+
const toStorageHash = toContractJson["storageHash"];
|
|
85
|
+
if (!toStorageHash) return notOk(errorContractValidationFailed("Contract is missing storageHash", { where: { path: contractPathAbsolute } }));
|
|
86
|
+
let fromContract = null;
|
|
87
|
+
let fromHash = EMPTY_CONTRACT_HASH;
|
|
88
|
+
let parentMigrationId = null;
|
|
89
|
+
try {
|
|
90
|
+
const packages = (await readMigrationsDir(migrationsDir)).filter((p) => typeof p.manifest.migrationId === "string");
|
|
91
|
+
const latestMigration = findLatestMigration(reconstructGraph(packages));
|
|
92
|
+
const leafHash = latestMigration ? latestMigration.to : EMPTY_CONTRACT_HASH;
|
|
93
|
+
if (options.from) {
|
|
94
|
+
fromHash = options.from;
|
|
95
|
+
const sourcePkg = packages.find((p) => p.manifest.to === fromHash);
|
|
96
|
+
if (!sourcePkg) return notOk(errorRuntime("Starting contract not found", {
|
|
97
|
+
why: `No migration with to="${fromHash}" exists in ${migrationsRelative}`,
|
|
98
|
+
fix: "Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration leaf."
|
|
99
|
+
}));
|
|
100
|
+
fromContract = sourcePkg.manifest.toContract;
|
|
101
|
+
parentMigrationId = sourcePkg.manifest.migrationId;
|
|
102
|
+
} else if (leafHash !== EMPTY_CONTRACT_HASH && latestMigration) {
|
|
103
|
+
fromHash = leafHash;
|
|
104
|
+
parentMigrationId = latestMigration.migrationId;
|
|
105
|
+
const leafPkg = packages.find((p) => p.manifest.migrationId === latestMigration.migrationId);
|
|
106
|
+
if (leafPkg) fromContract = leafPkg.manifest.toContract;
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
if (fromHash === toStorageHash) return ok({
|
|
113
|
+
ok: true,
|
|
114
|
+
noOp: true,
|
|
115
|
+
from: fromHash,
|
|
116
|
+
to: toStorageHash,
|
|
117
|
+
operations: [],
|
|
118
|
+
summary: "No changes detected between contracts",
|
|
119
|
+
timings: { total: Date.now() - startTime }
|
|
120
|
+
});
|
|
121
|
+
const targetWithMigrations = config.target;
|
|
122
|
+
if (!targetWithMigrations.migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
|
|
123
|
+
const { migrations } = targetWithMigrations;
|
|
124
|
+
const stack = createControlPlaneStack({
|
|
125
|
+
target: config.target,
|
|
126
|
+
adapter: config.adapter,
|
|
127
|
+
extensionPacks: config.extensionPacks ?? []
|
|
128
|
+
});
|
|
129
|
+
const familyInstance = config.family.create(stack);
|
|
130
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [
|
|
131
|
+
config.target,
|
|
132
|
+
config.adapter,
|
|
133
|
+
...config.extensionPacks ?? []
|
|
134
|
+
]);
|
|
135
|
+
const planner = migrations.createPlanner(familyInstance);
|
|
136
|
+
const fromSchemaIR = migrations.contractToSchema(fromContract);
|
|
137
|
+
const plannerResult = planner.plan({
|
|
138
|
+
contract: toContractJson,
|
|
139
|
+
schema: fromSchemaIR,
|
|
140
|
+
policy: { allowedOperationClasses: [
|
|
141
|
+
"additive",
|
|
142
|
+
"widening",
|
|
143
|
+
"destructive"
|
|
144
|
+
] },
|
|
145
|
+
frameworkComponents
|
|
146
|
+
});
|
|
147
|
+
if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
|
|
148
|
+
const ops = plannerResult.plan.operations;
|
|
149
|
+
if (ops.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
|
|
150
|
+
kind: "unsupportedChange",
|
|
151
|
+
summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes (e.g. removals, type changes, or a planner/contract mismatch)."
|
|
152
|
+
}] }));
|
|
153
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
154
|
+
const packageDir = join(migrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
|
|
155
|
+
const manifest = {
|
|
156
|
+
from: fromHash,
|
|
157
|
+
to: toStorageHash,
|
|
158
|
+
migrationId: null,
|
|
159
|
+
parentMigrationId,
|
|
160
|
+
kind: "regular",
|
|
161
|
+
fromContract,
|
|
162
|
+
toContract: toContractJson,
|
|
163
|
+
hints: {
|
|
164
|
+
used: [],
|
|
165
|
+
applied: [],
|
|
166
|
+
plannerVersion: "1.0.0",
|
|
167
|
+
planningStrategy: "diff"
|
|
168
|
+
},
|
|
169
|
+
labels: [],
|
|
170
|
+
createdAt: timestamp.toISOString()
|
|
171
|
+
};
|
|
172
|
+
try {
|
|
173
|
+
await writeMigrationPackage(packageDir, manifest, ops);
|
|
174
|
+
const migrationId = await attestMigration(packageDir);
|
|
175
|
+
const sql = extractSqlDdl(ops);
|
|
176
|
+
return ok({
|
|
177
|
+
ok: true,
|
|
178
|
+
noOp: false,
|
|
179
|
+
from: fromHash,
|
|
180
|
+
to: toStorageHash,
|
|
181
|
+
migrationId,
|
|
182
|
+
dir: relative(process.cwd(), packageDir),
|
|
183
|
+
operations: ops.map((op) => ({
|
|
184
|
+
id: op.id,
|
|
185
|
+
label: op.label,
|
|
186
|
+
operationClass: op.operationClass
|
|
187
|
+
})),
|
|
188
|
+
sql,
|
|
189
|
+
summary: `Planned ${ops.length} operation(s)`,
|
|
190
|
+
timings: { total: Date.now() - startTime }
|
|
191
|
+
});
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return notOk(mapMigrationToolsError(error));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function createMigrationPlanCommand() {
|
|
197
|
+
const command = new Command("plan");
|
|
198
|
+
setCommandDescriptions(command, "Plan a migration from contract changes", "Compares the emitted contract against the latest on-disk migration state and\nproduces a new migration package with the required operations. No database\nconnection is needed — this is a fully offline operation.");
|
|
199
|
+
command.configureHelp({ formatHelp: (cmd) => {
|
|
200
|
+
return formatCommandHelp({
|
|
201
|
+
command: cmd,
|
|
202
|
+
flags: parseGlobalFlags({})
|
|
203
|
+
});
|
|
204
|
+
} }).option("--config <path>", "Path to prisma-next.config.ts").option("--name <slug>", "Name slug for the migration directory", "migration").option("--from <hash>", "Explicit starting contract hash (overrides migration chain leaf)").option("--json [format]", "Output as JSON (object)", false).option("-q, --quiet", "Quiet mode: errors only").option("-v, --verbose", "Verbose output: debug info, timings").option("-vv, --trace", "Trace output: deep internals, stack traces").option("--timestamps", "Add timestamps to output").option("--color", "Force color output").option("--no-color", "Disable color output").action(async (options) => {
|
|
205
|
+
const flags = parseGlobalFlags(options);
|
|
206
|
+
const exitCode = handleResult(await executeMigrationPlanCommand(options, flags, Date.now()), flags, (planResult) => {
|
|
207
|
+
if (flags.json === "object") console.log(JSON.stringify(planResult, null, 2));
|
|
208
|
+
else if (!flags.quiet) console.log(formatMigrationPlanOutput(planResult, flags));
|
|
209
|
+
});
|
|
210
|
+
process.exit(exitCode);
|
|
211
|
+
});
|
|
212
|
+
return command;
|
|
213
|
+
}
|
|
214
|
+
function formatMigrationPlanOutput(result, flags) {
|
|
215
|
+
const lines = [];
|
|
216
|
+
const useColor = flags.color !== false;
|
|
217
|
+
const green_ = useColor ? (s) => `\x1b[32m${s}\x1b[0m` : (s) => s;
|
|
218
|
+
const yellow_ = useColor ? (s) => `\x1b[33m${s}\x1b[0m` : (s) => s;
|
|
219
|
+
const dim_ = useColor ? (s) => `\x1b[2m${s}\x1b[0m` : (s) => s;
|
|
220
|
+
if (result.noOp) {
|
|
221
|
+
lines.push(`${green_("✔")} No changes detected`);
|
|
222
|
+
lines.push(dim_(` from: ${result.from}`));
|
|
223
|
+
lines.push(dim_(` to: ${result.to}`));
|
|
224
|
+
return lines.join("\n");
|
|
225
|
+
}
|
|
226
|
+
lines.push(`${green_("✔")} ${result.summary}`);
|
|
227
|
+
lines.push("");
|
|
228
|
+
if (result.operations.length > 0) {
|
|
229
|
+
lines.push(dim_("│"));
|
|
230
|
+
for (let i = 0; i < result.operations.length; i++) {
|
|
231
|
+
const op = result.operations[i];
|
|
232
|
+
const treeChar = i === result.operations.length - 1 ? "└" : "├";
|
|
233
|
+
const opClassLabel = op.operationClass === "destructive" ? yellow_(`[${op.operationClass}]`) : dim_(`[${op.operationClass}]`);
|
|
234
|
+
lines.push(`${dim_(treeChar)}─ ${op.label} ${opClassLabel}`);
|
|
235
|
+
}
|
|
236
|
+
if (result.operations.some((op) => op.operationClass === "destructive")) {
|
|
237
|
+
lines.push("");
|
|
238
|
+
lines.push(`${yellow_("⚠")} This migration contains destructive operations that may cause data loss.`);
|
|
239
|
+
}
|
|
240
|
+
lines.push("");
|
|
241
|
+
}
|
|
242
|
+
lines.push(dim_(`from: ${result.from}`));
|
|
243
|
+
lines.push(dim_(`to: ${result.to}`));
|
|
244
|
+
if (result.migrationId) lines.push(dim_(`migrationId: ${result.migrationId}`));
|
|
245
|
+
if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
|
|
246
|
+
if (result.sql && result.sql.length > 0) {
|
|
247
|
+
lines.push("");
|
|
248
|
+
lines.push(dim_("DDL preview"));
|
|
249
|
+
lines.push("");
|
|
250
|
+
for (const statement of result.sql) {
|
|
251
|
+
const trimmed = statement.trim();
|
|
252
|
+
if (!trimmed) continue;
|
|
253
|
+
const line = trimmed.endsWith(";") ? trimmed : `${trimmed};`;
|
|
254
|
+
lines.push(line);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (flags.verbose && result.timings) {
|
|
258
|
+
lines.push("");
|
|
259
|
+
lines.push(dim_(`Total time: ${result.timings.total}ms`));
|
|
260
|
+
}
|
|
261
|
+
return lines.join("\n");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
//#endregion
|
|
265
|
+
export { createMigrationPlanCommand };
|
|
266
|
+
//# sourceMappingURL=migration-plan.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-plan.mjs","names":["details: Array<{ label: string; value: string }>","contractJsonContent: string","toContractJson: ContractIR","fromContract: ContractIR | null","fromHash: string","parentMigrationId: string | null","ops: readonly MigrationPlanOperation[]","manifest: MigrationManifest","lines: string[]"],"sources":["../../src/commands/migration-plan.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/core-control-plane/constants';\nimport { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';\nimport type {\n ControlTargetDescriptor,\n MigrationPlanOperation,\n} from '@prisma-next/core-control-plane/types';\nimport { attestMigration } from '@prisma-next/migration-tools/attestation';\nimport { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';\nimport {\n formatMigrationDirName,\n readMigrationsDir,\n writeMigrationPackage,\n} from '@prisma-next/migration-tools/io';\nimport { type MigrationManifest, MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\nimport { join, relative, resolve } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport { extractSqlDdl } from '../control-api/operations/extract-sql-ddl';\nimport {\n type CliErrorConflict,\n type CliStructuredError,\n errorContractValidationFailed,\n errorFileNotFound,\n errorMigrationPlanningFailed,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport { resolveContractPath, setCommandDescriptions } from '../utils/command-helpers';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport { formatCommandHelp, formatStyledHeader } from '../utils/output';\nimport { handleResult } from '../utils/result-handler';\n\ninterface MigrationPlanOptions {\n readonly config?: string;\n readonly name?: string;\n readonly from?: string;\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport interface MigrationPlanResult {\n readonly ok: boolean;\n readonly noOp: boolean;\n readonly from: string;\n readonly to: string;\n readonly migrationId?: string;\n readonly dir?: string;\n readonly operations: readonly {\n readonly id: string;\n readonly label: string;\n readonly operationClass: string;\n }[];\n readonly sql?: readonly string[];\n readonly summary: string;\n readonly timings: {\n readonly total: number;\n };\n}\n\nfunction mapMigrationToolsError(error: unknown): CliStructuredError {\n if (MigrationToolsError.is(error)) {\n return errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code, ...(error.details ?? {}) },\n });\n }\n return errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration plan: ${error instanceof Error ? error.message : String(error)}`,\n });\n}\n\nasync function executeMigrationPlanCommand(\n options: MigrationPlanOptions,\n flags: GlobalFlags,\n startTime: number,\n): Promise<Result<MigrationPlanResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n\n const migrationsDir = resolve(\n options.config ? resolve(options.config, '..') : process.cwd(),\n config.migrations?.dir ?? 'migrations',\n );\n const migrationsRelative = relative(process.cwd(), migrationsDir);\n\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (options.from) {\n details.push({ label: 'from', value: options.from });\n }\n if (options.name) {\n details.push({ label: 'name', value: options.name });\n }\n const header = formatStyledHeader({\n command: 'migration plan',\n description: 'Plan a migration from contract changes',\n url: 'https://pris.ly/migration-plan',\n details,\n flags,\n });\n console.log(header);\n }\n\n // Load contract file (the \"to\" contract)\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n return notOk(\n errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n fix: `Run \\`prisma-next contract emit\\` to generate ${contractPath}, or update \\`config.contract.output\\` in ${configPath}`,\n }),\n );\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n }\n\n let toContractJson: ContractIR;\n try {\n toContractJson = JSON.parse(contractJsonContent) as ContractIR;\n } catch (error) {\n return notOk(\n errorContractValidationFailed(\n `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,\n { where: { path: contractPathAbsolute } },\n ),\n );\n }\n\n const toStorageHash = (toContractJson as unknown as Record<string, unknown>)['storageHash'] as\n | string\n | undefined;\n if (!toStorageHash) {\n return notOk(\n errorContractValidationFailed('Contract is missing storageHash', {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n\n // Read existing migrations and determine \"from\" contract\n let fromContract: ContractIR | null = null;\n let fromHash: string = EMPTY_CONTRACT_HASH;\n let parentMigrationId: string | null = null;\n\n try {\n const allPackages = await readMigrationsDir(migrationsDir);\n const packages = allPackages.filter((p) => typeof p.manifest.migrationId === 'string');\n const graph = reconstructGraph(packages);\n const latestMigration = findLatestMigration(graph);\n const leafHash = latestMigration ? latestMigration.to : EMPTY_CONTRACT_HASH;\n\n if (options.from) {\n fromHash = options.from;\n const sourcePkg = packages.find((p) => p.manifest.to === fromHash);\n if (!sourcePkg) {\n return notOk(\n errorRuntime('Starting contract not found', {\n why: `No migration with to=\"${fromHash}\" exists in ${migrationsRelative}`,\n fix: 'Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration leaf.',\n }),\n );\n }\n fromContract = sourcePkg.manifest.toContract;\n parentMigrationId = sourcePkg.manifest.migrationId;\n } else if (leafHash !== EMPTY_CONTRACT_HASH && latestMigration) {\n fromHash = leafHash;\n parentMigrationId = latestMigration.migrationId;\n const leafPkg = packages.find((p) => p.manifest.migrationId === latestMigration.migrationId);\n if (leafPkg) {\n fromContract = leafPkg.manifest.toContract;\n }\n }\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n // Check for no-op (same hash means no changes)\n if (fromHash === toStorageHash) {\n const result: MigrationPlanResult = {\n ok: true,\n noOp: true,\n from: fromHash,\n to: toStorageHash,\n operations: [],\n summary: 'No changes detected between contracts',\n timings: { total: Date.now() - startTime },\n };\n return ok(result);\n }\n\n // Check target supports migrations\n const targetWithMigrations = config.target as ControlTargetDescriptor<string, string>;\n if (!targetWithMigrations.migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n // Plan migration using the same planner as db init\n const { migrations } = targetWithMigrations;\n const stack = createControlPlaneStack({\n target: config.target,\n adapter: config.adapter,\n extensionPacks: config.extensionPacks ?? [],\n });\n const familyInstance = config.family.create(stack);\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n [config.target, config.adapter, ...(config.extensionPacks ?? [])],\n );\n const planner = migrations.createPlanner(familyInstance);\n const fromSchemaIR = migrations.contractToSchema(fromContract);\n const plannerResult = planner.plan({\n contract: toContractJson,\n schema: fromSchemaIR,\n policy: { allowedOperationClasses: ['additive', 'widening', 'destructive'] },\n frameworkComponents,\n });\n\n if (plannerResult.kind === 'failure') {\n return notOk(\n errorMigrationPlanningFailed({\n conflicts: plannerResult.conflicts as readonly CliErrorConflict[],\n }),\n );\n }\n\n const ops: readonly MigrationPlanOperation[] = plannerResult.plan.operations;\n\n if (ops.length === 0) {\n return notOk(\n errorMigrationPlanningFailed({\n conflicts: [\n {\n kind: 'unsupportedChange',\n summary:\n 'Contract changed but planner produced no operations. ' +\n 'This indicates unsupported or ignored changes (e.g. removals, type changes, or a planner/contract mismatch).',\n },\n ],\n }),\n );\n }\n\n // Build manifest and write migration package\n const timestamp = new Date();\n const slug = options.name ?? 'migration';\n const dirName = formatMigrationDirName(timestamp, slug);\n const packageDir = join(migrationsDir, dirName);\n\n const manifest: MigrationManifest = {\n from: fromHash,\n to: toStorageHash,\n migrationId: null,\n parentMigrationId,\n kind: 'regular',\n fromContract,\n toContract: toContractJson,\n hints: {\n used: [],\n applied: [],\n plannerVersion: '1.0.0',\n planningStrategy: 'diff',\n },\n labels: [],\n createdAt: timestamp.toISOString(),\n };\n\n try {\n await writeMigrationPackage(packageDir, manifest, ops);\n const migrationId = await attestMigration(packageDir);\n\n const sql = extractSqlDdl(ops);\n const result: MigrationPlanResult = {\n ok: true,\n noOp: false,\n from: fromHash,\n to: toStorageHash,\n migrationId,\n dir: relative(process.cwd(), packageDir),\n operations: ops.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n sql,\n summary: `Planned ${ops.length} operation(s)`,\n timings: { total: Date.now() - startTime },\n };\n return ok(result);\n } catch (error) {\n return notOk(mapMigrationToolsError(error));\n }\n}\n\nexport function createMigrationPlanCommand(): Command {\n const command = new Command('plan');\n setCommandDescriptions(\n command,\n 'Plan a migration from contract changes',\n 'Compares the emitted contract against the latest on-disk migration state and\\n' +\n 'produces a new migration package with the required operations. No database\\n' +\n 'connection is needed — this is a fully offline operation.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--name <slug>', 'Name slug for the migration directory', 'migration')\n .option('--from <hash>', 'Explicit starting contract hash (overrides migration chain leaf)')\n .option('--json [format]', 'Output as JSON (object)', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: MigrationPlanOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const result = await executeMigrationPlanCommand(options, flags, startTime);\n\n const exitCode = handleResult(result, flags, (planResult) => {\n if (flags.json === 'object') {\n console.log(JSON.stringify(planResult, null, 2));\n } else if (!flags.quiet) {\n console.log(formatMigrationPlanOutput(planResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n\nfunction formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFlags): string {\n const lines: string[] = [];\n const useColor = flags.color !== false;\n\n const green_ = useColor ? (s: string) => `\\x1b[32m${s}\\x1b[0m` : (s: string) => s;\n const yellow_ = useColor ? (s: string) => `\\x1b[33m${s}\\x1b[0m` : (s: string) => s;\n const dim_ = useColor ? (s: string) => `\\x1b[2m${s}\\x1b[0m` : (s: string) => s;\n\n if (result.noOp) {\n lines.push(`${green_('✔')} No changes detected`);\n lines.push(dim_(` from: ${result.from}`));\n lines.push(dim_(` to: ${result.to}`));\n return lines.join('\\n');\n }\n\n lines.push(`${green_('✔')} ${result.summary}`);\n lines.push('');\n\n if (result.operations.length > 0) {\n lines.push(dim_('│'));\n for (let i = 0; i < result.operations.length; i++) {\n const op = result.operations[i]!;\n const isLast = i === result.operations.length - 1;\n const treeChar = isLast ? '└' : '├';\n const opClassLabel =\n op.operationClass === 'destructive'\n ? yellow_(`[${op.operationClass}]`)\n : dim_(`[${op.operationClass}]`);\n lines.push(`${dim_(treeChar)}─ ${op.label} ${opClassLabel}`);\n }\n\n const hasDestructive = result.operations.some((op) => op.operationClass === 'destructive');\n if (hasDestructive) {\n lines.push('');\n lines.push(\n `${yellow_('⚠')} This migration contains destructive operations that may cause data loss.`,\n );\n }\n lines.push('');\n }\n\n lines.push(dim_(`from: ${result.from}`));\n lines.push(dim_(`to: ${result.to}`));\n if (result.migrationId) {\n lines.push(dim_(`migrationId: ${result.migrationId}`));\n }\n if (result.dir) {\n lines.push(dim_(`dir: ${result.dir}`));\n }\n\n if (result.sql && result.sql.length > 0) {\n lines.push('');\n lines.push(dim_('DDL preview'));\n lines.push('');\n for (const statement of result.sql) {\n const trimmed = statement.trim();\n if (!trimmed) continue;\n const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;\n lines.push(line);\n }\n }\n\n if (flags.verbose && result.timings) {\n lines.push('');\n lines.push(dim_(`Total time: ${result.timings.total}ms`));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|