@prisma-next/cli 0.4.0-dev.9 → 0.4.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 +26 -18
- package/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli-errors-BFYgBH3L.d.mts +4 -0
- package/dist/cli-errors-Cd79vmTH.mjs +5 -0
- package/dist/cli.mjs +127 -25
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-CJxHfhze.mjs → client-CrsnY58k.mjs} +9 -8
- package/dist/{client-CJxHfhze.mjs.map → client-CrsnY58k.mjs.map} +1 -1
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +7 -7
- package/dist/commands/contract-infer.mjs +8 -8
- package/dist/commands/db-init.mjs +8 -8
- package/dist/commands/db-schema.mjs +8 -8
- package/dist/commands/db-sign.mjs +8 -8
- package/dist/commands/db-update.mjs +8 -8
- package/dist/commands/db-verify.mjs +9 -9
- package/dist/commands/migration-apply.d.mts +1 -1
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +37 -28
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +48 -23
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +6 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +94 -71
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +6 -4
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +29 -34
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +2 -2
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +11 -16
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +4 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +7 -7
- package/dist/config-loader-C25b63rJ.mjs +90 -0
- package/dist/config-loader-C25b63rJ.mjs.map +1 -0
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-DxgyXrqV.mjs +6 -0
- package/dist/{contract-emit-gpJNLGs7.mjs → contract-emit-NJ01hiiv.mjs} +20 -16
- package/dist/contract-emit-NJ01hiiv.mjs.map +1 -0
- package/dist/{contract-emit-CKig_Lra.mjs → contract-emit-V5SSitUT.mjs} +25 -21
- package/dist/contract-emit-V5SSitUT.mjs.map +1 -0
- package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-CAOELa-H.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-CAOELa-H.mjs.map} +1 -1
- package/dist/{contract-infer-BDJgg7Xb.mjs → contract-infer-D9cC3rJm.mjs} +4 -4
- package/dist/{contract-infer-BDJgg7Xb.mjs.map → contract-infer-D9cC3rJm.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +2 -2
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +6 -6
- package/dist/exports/index.mjs +7 -7
- package/dist/exports/init-output.d.mts +39 -0
- package/dist/exports/init-output.d.mts.map +1 -0
- package/dist/exports/init-output.mjs +3 -0
- package/dist/{extract-operation-statements-DZUJNmL3.mjs → extract-operation-statements-DsFfxXVZ.mjs} +2 -2
- package/dist/{extract-operation-statements-DZUJNmL3.mjs.map → extract-operation-statements-DsFfxXVZ.mjs.map} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs → extract-sql-ddl-D9UbZDyz.mjs} +1 -1
- package/dist/{extract-sql-ddl-DDMX-9mz.mjs.map → extract-sql-ddl-D9UbZDyz.mjs.map} +1 -1
- package/dist/{framework-components-Bsr1GaIj.mjs → framework-components-Cr--XBKy.mjs} +2 -2
- package/dist/{framework-components-Bsr1GaIj.mjs.map → framework-components-Cr--XBKy.mjs.map} +1 -1
- package/dist/init-m8x0UoPY.mjs +2062 -0
- package/dist/init-m8x0UoPY.mjs.map +1 -0
- package/dist/{inspect-live-schema-ChqrALmw.mjs → inspect-live-schema-yrHAvG71.mjs} +6 -6
- package/dist/{inspect-live-schema-ChqrALmw.mjs.map → inspect-live-schema-yrHAvG71.mjs.map} +1 -1
- package/dist/migration-cli.d.mts +50 -0
- package/dist/migration-cli.d.mts.map +1 -0
- package/dist/migration-cli.mjs +184 -0
- package/dist/migration-cli.mjs.map +1 -0
- package/dist/{migration-command-scaffold-B0oH_hyB.mjs → migration-command-scaffold-B3B09et6.mjs} +7 -7
- package/dist/{migration-command-scaffold-B0oH_hyB.mjs.map → migration-command-scaffold-B3B09et6.mjs.map} +1 -1
- package/dist/{migration-status-CPamfEPj.mjs → migration-status-DUMiH8_G.mjs} +25 -43
- package/dist/migration-status-DUMiH8_G.mjs.map +1 -0
- package/dist/{migrations-BIsjFjSV.mjs → migrations-Bo5WtTla.mjs} +4 -15
- package/dist/migrations-Bo5WtTla.mjs.map +1 -0
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DvQWB1nK.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DvQWB1nK.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-AFK4hxyX.mjs → result-handler-Ba3zWQsI.mjs} +26 -88
- package/dist/result-handler-Ba3zWQsI.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-C3ZLwQxK.mjs} +76 -2
- package/dist/terminal-ui-C3ZLwQxK.mjs.map +1 -0
- package/dist/{validate-contract-deps-DBH6iTAD.mjs → validate-contract-deps-B_Cs29TL.mjs} +1 -1
- package/dist/{validate-contract-deps-DBH6iTAD.mjs.map → validate-contract-deps-B_Cs29TL.mjs.map} +1 -1
- package/dist/{verify-C56CuQc7.mjs → verify-Bkycc-Tf.mjs} +2 -2
- package/dist/{verify-C56CuQc7.mjs.map → verify-Bkycc-Tf.mjs.map} +1 -1
- package/package.json +24 -19
- package/src/cli.ts +1 -5
- package/src/commands/contract-emit.ts +9 -10
- package/src/commands/init/detect-pnpm-catalog.ts +141 -0
- package/src/commands/init/errors.ts +254 -0
- package/src/commands/init/exit-codes.ts +62 -0
- package/src/commands/init/hygiene-gitattributes.ts +97 -0
- package/src/commands/init/hygiene-gitignore.ts +48 -0
- package/src/commands/init/hygiene-package-scripts.ts +91 -0
- package/src/commands/init/index.ts +112 -7
- package/src/commands/init/init.ts +766 -144
- package/src/commands/init/inputs.ts +421 -0
- package/src/commands/init/output.ts +147 -0
- package/src/commands/init/probe-db.ts +308 -0
- package/src/commands/init/reinit-cleanup.ts +83 -0
- package/src/commands/init/templates/agent-skill-mongo.md +63 -31
- package/src/commands/init/templates/agent-skill-postgres.md +1 -1
- package/src/commands/init/templates/agent-skill.ts +25 -3
- package/src/commands/init/templates/code-templates.ts +125 -32
- package/src/commands/init/templates/env.ts +80 -0
- package/src/commands/init/templates/quick-reference-mongo.md +34 -13
- package/src/commands/init/templates/quick-reference-postgres.md +11 -9
- package/src/commands/init/templates/quick-reference.ts +42 -3
- package/src/commands/init/templates/tsconfig.ts +167 -5
- package/src/commands/migration-apply.ts +37 -26
- package/src/commands/migration-new.ts +39 -17
- package/src/commands/migration-plan.ts +119 -104
- package/src/commands/migration-ref.ts +32 -47
- package/src/commands/migration-show.ts +6 -16
- package/src/commands/migration-status.ts +30 -55
- package/src/config-loader.ts +35 -29
- package/src/config-path-validation.ts +75 -0
- package/src/control-api/client.ts +2 -1
- package/src/control-api/operations/contract-emit.ts +24 -23
- package/src/control-api/types.ts +1 -1
- package/src/exports/init-output.ts +10 -0
- package/src/migration-cli.ts +254 -0
- package/src/utils/cli-errors.ts +1 -0
- package/src/utils/command-helpers.ts +18 -22
- package/src/utils/formatters/graph-migration-mapper.ts +5 -14
- package/src/utils/formatters/help.ts +0 -1
- package/src/utils/formatters/migrations.ts +2 -29
- package/dist/cli-errors-BUuJr6py.mjs +0 -5
- package/dist/cli-errors-Dic2eADK.d.mts +0 -4
- package/dist/commands/migration-emit.d.mts +0 -38
- package/dist/commands/migration-emit.d.mts.map +0 -1
- package/dist/commands/migration-emit.mjs +0 -81
- package/dist/commands/migration-emit.mjs.map +0 -1
- package/dist/config-loader-C4VXKl8f.mjs +0 -43
- package/dist/config-loader-C4VXKl8f.mjs.map +0 -1
- package/dist/contract-emit-CKig_Lra.mjs.map +0 -1
- package/dist/contract-emit-CU-SYNe4.mjs +0 -6
- package/dist/contract-emit-gpJNLGs7.mjs.map +0 -1
- package/dist/init-DZWvhEP0.mjs +0 -430
- package/dist/init-DZWvhEP0.mjs.map +0 -1
- package/dist/migration-emit-Du4DBMqz.mjs +0 -125
- package/dist/migration-emit-Du4DBMqz.mjs.map +0 -1
- package/dist/migration-status-CPamfEPj.mjs.map +0 -1
- package/dist/migrations-BIsjFjSV.mjs.map +0 -1
- package/dist/result-handler-AFK4hxyX.mjs.map +0 -1
- package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
- package/src/commands/migration-emit.ts +0 -134
- package/src/lib/migration-emit.ts +0 -125
- package/src/lib/migration-strategy.ts +0 -49
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import { t as loadConfig } from "../config-loader-
|
|
2
|
-
import { a as errorContractValidationFailed,
|
|
3
|
-
import { t as assertFrameworkComponentsCompatible } from "../framework-components-
|
|
4
|
-
import { t as extractSqlDdl } from "../extract-sql-ddl-
|
|
5
|
-
import {
|
|
6
|
-
import { t as
|
|
7
|
-
import { n as migrationStrategy, t as emitMigration } from "../migration-emit-Du4DBMqz.mjs";
|
|
1
|
+
import { t as loadConfig } from "../config-loader-C25b63rJ.mjs";
|
|
2
|
+
import { _ as errorUnexpected, a as errorContractValidationFailed, f as errorMigrationPlanningFailed, h as errorTargetMigrationNotSupported, l as errorFileNotFound, m as errorRuntime, t as CliStructuredError } from "../cli-errors-Cd79vmTH.mjs";
|
|
3
|
+
import { t as assertFrameworkComponentsCompatible } from "../framework-components-Cr--XBKy.mjs";
|
|
4
|
+
import { t as extractSqlDdl } from "../extract-sql-ddl-D9UbZDyz.mjs";
|
|
5
|
+
import { t as TerminalUI } from "../terminal-ui-C3ZLwQxK.mjs";
|
|
6
|
+
import { _ as formatStyledHeader, c as resolveMigrationPaths, d as setCommandExamples, i as loadAllBundles, m as parseGlobalFlags, n as addGlobalOptions, r as getTargetMigrations, s as resolveContractPath, t as handleResult, u as setCommandDescriptions } from "../result-handler-Ba3zWQsI.mjs";
|
|
8
7
|
import { Command } from "commander";
|
|
8
|
+
import { getEmittedArtifactPaths } from "@prisma-next/emitter";
|
|
9
9
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
10
10
|
import { join, relative } from "pathe";
|
|
11
11
|
import { createControlStack } from "@prisma-next/framework-components/control";
|
|
12
12
|
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
14
|
import { findLatestMigration } from "@prisma-next/migration-tools/dag";
|
|
15
|
-
import {
|
|
15
|
+
import { copyFilesWithRename, formatMigrationDirName, writeMigrationPackage } from "@prisma-next/migration-tools/io";
|
|
16
|
+
import { computeMigrationId } from "@prisma-next/migration-tools/attestation";
|
|
16
17
|
import { MigrationToolsError } from "@prisma-next/migration-tools/types";
|
|
17
18
|
import { writeMigrationTs } from "@prisma-next/migration-tools/migration-ts";
|
|
18
19
|
|
|
@@ -87,13 +88,9 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
|
|
|
87
88
|
const toStorageHash = rawStorageHash;
|
|
88
89
|
let fromContract = null;
|
|
89
90
|
let fromHash = EMPTY_CONTRACT_HASH;
|
|
91
|
+
let fromContractSourceDir = null;
|
|
90
92
|
try {
|
|
91
|
-
const {
|
|
92
|
-
const existingDraft = drafts.find((d) => d.manifest.to === toStorageHash);
|
|
93
|
-
if (existingDraft) return notOk(errorRuntime("A draft migration to this contract already exists", {
|
|
94
|
-
why: `Draft migration at "${existingDraft.dirName}" already targets ${toStorageHash}`,
|
|
95
|
-
fix: `Run 'prisma-next migration emit --dir ${migrationsRelative}/${existingDraft.dirName}' to attest it, or delete it and re-plan.`
|
|
96
|
-
}));
|
|
93
|
+
const { bundles, graph } = await loadAllBundles(migrationsDir);
|
|
97
94
|
if (options.from) {
|
|
98
95
|
const resolved = resolveBundleByPrefix(bundles, options.from);
|
|
99
96
|
if (!resolved.ok) {
|
|
@@ -108,12 +105,16 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
|
|
|
108
105
|
}
|
|
109
106
|
fromHash = resolved.value.manifest.to;
|
|
110
107
|
fromContract = resolved.value.manifest.toContract;
|
|
108
|
+
fromContractSourceDir = resolved.value.dirPath;
|
|
111
109
|
} else {
|
|
112
110
|
const latestMigration = findLatestMigration(graph);
|
|
113
111
|
if (latestMigration) {
|
|
114
112
|
fromHash = latestMigration.to;
|
|
115
113
|
const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);
|
|
116
|
-
if (leafPkg)
|
|
114
|
+
if (leafPkg) {
|
|
115
|
+
fromContract = leafPkg.manifest.toContract;
|
|
116
|
+
fromContractSourceDir = leafPkg.dirPath;
|
|
117
|
+
}
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
} catch (error) {
|
|
@@ -136,93 +137,103 @@ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
|
|
|
136
137
|
config.adapter,
|
|
137
138
|
...config.extensionPacks ?? []
|
|
138
139
|
]);
|
|
139
|
-
const strategy = migrationStrategy(migrations, config.target.targetId);
|
|
140
140
|
const timestamp = /* @__PURE__ */ new Date();
|
|
141
141
|
const packageDir = join(migrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
|
|
142
|
-
const
|
|
142
|
+
const baseManifest = {
|
|
143
143
|
from: fromHash,
|
|
144
144
|
to: toStorageHash,
|
|
145
|
-
migrationId: null,
|
|
146
145
|
kind: "regular",
|
|
147
146
|
fromContract,
|
|
148
147
|
toContract: toContractJson,
|
|
149
148
|
hints: {
|
|
150
149
|
used: [],
|
|
151
150
|
applied: [],
|
|
152
|
-
plannerVersion: "2.0.0"
|
|
153
|
-
planningStrategy: strategy === "descriptor" ? "descriptors" : "class-based"
|
|
151
|
+
plannerVersion: "2.0.0"
|
|
154
152
|
},
|
|
155
153
|
labels: [],
|
|
156
154
|
createdAt: timestamp.toISOString()
|
|
157
155
|
};
|
|
158
|
-
const scaffoldContext = {
|
|
159
|
-
packageDir,
|
|
160
|
-
contractJsonPath: contractPathAbsolute,
|
|
161
|
-
fromHash,
|
|
162
|
-
toHash: toStorageHash
|
|
163
|
-
};
|
|
164
156
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"additive",
|
|
189
|
-
"widening",
|
|
190
|
-
"destructive",
|
|
191
|
-
"data"
|
|
192
|
-
] },
|
|
193
|
-
fromHash,
|
|
194
|
-
frameworkComponents
|
|
195
|
-
});
|
|
196
|
-
if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
|
|
197
|
-
if (plannerResult.plan.operations.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
|
|
157
|
+
const stack = createControlStack(config);
|
|
158
|
+
const familyInstance = config.family.create(stack);
|
|
159
|
+
const planner = migrations.createPlanner(familyInstance);
|
|
160
|
+
const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
|
|
161
|
+
const plannerResult = planner.plan({
|
|
162
|
+
contract: toContractJson,
|
|
163
|
+
schema: fromSchema,
|
|
164
|
+
policy: { allowedOperationClasses: [
|
|
165
|
+
"additive",
|
|
166
|
+
"widening",
|
|
167
|
+
"destructive",
|
|
168
|
+
"data"
|
|
169
|
+
] },
|
|
170
|
+
fromHash,
|
|
171
|
+
fromContract,
|
|
172
|
+
frameworkComponents
|
|
173
|
+
});
|
|
174
|
+
if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
|
|
175
|
+
let plannedOps = [];
|
|
176
|
+
let hasPlaceholders = false;
|
|
177
|
+
try {
|
|
178
|
+
plannedOps = plannerResult.plan.operations;
|
|
179
|
+
if (plannedOps.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
|
|
198
180
|
kind: "unsupportedChange",
|
|
199
181
|
summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes."
|
|
200
182
|
}] }));
|
|
201
|
-
|
|
183
|
+
} catch (e) {
|
|
184
|
+
if (CliStructuredError.is(e) && e.domain === "MIG" && e.code === "2001") hasPlaceholders = true;
|
|
185
|
+
else throw e;
|
|
186
|
+
}
|
|
187
|
+
const migrationTsContent = plannerResult.plan.renderTypeScript();
|
|
188
|
+
const opsForWrite = hasPlaceholders ? [] : plannedOps;
|
|
189
|
+
await writeMigrationPackage(packageDir, {
|
|
190
|
+
...baseManifest,
|
|
191
|
+
migrationId: computeMigrationId(baseManifest, opsForWrite)
|
|
192
|
+
}, opsForWrite);
|
|
193
|
+
const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
|
|
194
|
+
await copyFilesWithRename(packageDir, [{
|
|
195
|
+
sourcePath: destinationArtifacts.jsonPath,
|
|
196
|
+
destName: "end-contract.json"
|
|
197
|
+
}, {
|
|
198
|
+
sourcePath: destinationArtifacts.dtsPath,
|
|
199
|
+
destName: "end-contract.d.ts"
|
|
200
|
+
}]);
|
|
201
|
+
if (fromContractSourceDir !== null) {
|
|
202
|
+
const sourceArtifacts = getEmittedArtifactPaths(join(fromContractSourceDir, "end-contract.json"));
|
|
203
|
+
await copyFilesWithRename(packageDir, [{
|
|
204
|
+
sourcePath: sourceArtifacts.jsonPath,
|
|
205
|
+
destName: "start-contract.json"
|
|
206
|
+
}, {
|
|
207
|
+
sourcePath: sourceArtifacts.dtsPath,
|
|
208
|
+
destName: "start-contract.d.ts"
|
|
209
|
+
}]);
|
|
202
210
|
}
|
|
203
|
-
await writeMigrationPackage(packageDir, manifest, []);
|
|
204
|
-
await copyContractToMigrationDir(packageDir, contractPathAbsolute);
|
|
205
211
|
await writeMigrationTs(packageDir, migrationTsContent);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
if (hasPlaceholders) return ok({
|
|
213
|
+
ok: true,
|
|
214
|
+
noOp: false,
|
|
215
|
+
from: fromHash,
|
|
216
|
+
to: toStorageHash,
|
|
217
|
+
dir: relative(process.cwd(), packageDir),
|
|
218
|
+
operations: [],
|
|
219
|
+
pendingPlaceholders: true,
|
|
220
|
+
summary: "Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
|
|
221
|
+
timings: { total: Date.now() - startTime }
|
|
210
222
|
});
|
|
211
|
-
const sql = extractSqlDdl(
|
|
223
|
+
const sql = extractSqlDdl(plannedOps);
|
|
212
224
|
return ok({
|
|
213
225
|
ok: true,
|
|
214
226
|
noOp: false,
|
|
215
227
|
from: fromHash,
|
|
216
228
|
to: toStorageHash,
|
|
217
|
-
migrationId,
|
|
218
229
|
dir: relative(process.cwd(), packageDir),
|
|
219
|
-
operations:
|
|
230
|
+
operations: plannedOps.map((op) => ({
|
|
220
231
|
id: op.id,
|
|
221
232
|
label: op.label,
|
|
222
233
|
operationClass: op.operationClass
|
|
223
234
|
})),
|
|
224
235
|
sql,
|
|
225
|
-
summary: `Planned ${
|
|
236
|
+
summary: `Planned ${plannedOps.length} operation(s)`,
|
|
226
237
|
timings: { total: Date.now() - startTime }
|
|
227
238
|
});
|
|
228
239
|
} catch (error) {
|
|
@@ -260,6 +271,17 @@ function formatMigrationPlanOutput(result, flags) {
|
|
|
260
271
|
lines.push(dim_(` to: ${result.to}`));
|
|
261
272
|
return lines.join("\n");
|
|
262
273
|
}
|
|
274
|
+
if (result.pendingPlaceholders) {
|
|
275
|
+
lines.push(`${yellow_("⚠")} ${result.summary}`);
|
|
276
|
+
lines.push("");
|
|
277
|
+
lines.push(dim_(`from: ${result.from}`));
|
|
278
|
+
lines.push(dim_(`to: ${result.to}`));
|
|
279
|
+
if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
|
|
280
|
+
lines.push("");
|
|
281
|
+
lines.push("Open migration.ts and replace each `placeholder(...)` call with your actual query.");
|
|
282
|
+
lines.push(`Then run: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)}`);
|
|
283
|
+
return lines.join("\n");
|
|
284
|
+
}
|
|
263
285
|
lines.push(`${green_("✔")} ${result.summary}`);
|
|
264
286
|
lines.push("");
|
|
265
287
|
if (result.operations.length > 0) {
|
|
@@ -278,8 +300,9 @@ function formatMigrationPlanOutput(result, flags) {
|
|
|
278
300
|
}
|
|
279
301
|
lines.push(dim_(`from: ${result.from}`));
|
|
280
302
|
lines.push(dim_(`to: ${result.to}`));
|
|
281
|
-
if (result.migrationId) lines.push(dim_(`migrationId: ${result.migrationId}`));
|
|
282
303
|
if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
|
|
304
|
+
lines.push("");
|
|
305
|
+
lines.push(`Next: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)} to emit ops.json and attest migrationId before running ${green_("prisma-next migration apply")}.`);
|
|
283
306
|
if (result.sql && result.sql.length > 0) {
|
|
284
307
|
lines.push("");
|
|
285
308
|
lines.push(dim_("DDL preview"));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration-plan.mjs","names":["details: Array<{ label: string; value: string }>","contractJsonContent: string","toContractJson: Contract","fromContract: Contract | null","fromHash: string","manifest: MigrationManifest","migrationTsContent: string","lines: string[]"],"sources":["../../src/commands/migration-plan.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { createControlStack } from '@prisma-next/framework-components/control';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { findLatestMigration } from '@prisma-next/migration-tools/dag';\nimport {\n copyContractToMigrationDir,\n formatMigrationDirName,\n writeMigrationPackage,\n} from '@prisma-next/migration-tools/io';\nimport { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';\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 } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport { extractSqlDdl } from '../control-api/operations/extract-sql-ddl';\nimport { emitMigration } from '../lib/migration-emit';\nimport { migrationStrategy } from '../lib/migration-strategy';\nimport {\n type CliErrorConflict,\n CliStructuredError,\n errorContractValidationFailed,\n errorFileNotFound,\n errorMigrationPlanningFailed,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n getTargetMigrations,\n loadAllBundles,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationPlanOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly name?: string;\n readonly from?: string;\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 (CliStructuredError.is(error)) {\n return error;\n }\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 ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationPlanResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n if (!flags.json && !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 ui.stderr(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: Contract;\n try {\n toContractJson = JSON.parse(contractJsonContent) as Contract;\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 rawStorageHash = toContractJson.storage?.storageHash;\n if (typeof rawStorageHash !== 'string') {\n return notOk(\n errorContractValidationFailed('Contract is missing storageHash', {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n const toStorageHash = rawStorageHash;\n\n // Read existing migrations and determine \"from\" contract\n let fromContract: Contract | null = null;\n let fromHash: string = EMPTY_CONTRACT_HASH;\n\n try {\n const { attested: bundles, drafts, graph } = await loadAllBundles(migrationsDir);\n\n // Check if a draft migration already targets this contract\n const existingDraft = drafts.find((d) => d.manifest.to === toStorageHash);\n if (existingDraft) {\n return notOk(\n errorRuntime('A draft migration to this contract already exists', {\n why: `Draft migration at \"${existingDraft.dirName}\" already targets ${toStorageHash}`,\n fix: `Run 'prisma-next migration emit --dir ${migrationsRelative}/${existingDraft.dirName}' to attest it, or delete it and re-plan.`,\n }),\n );\n }\n\n if (options.from) {\n const resolved = resolveBundleByPrefix(bundles, options.from);\n if (!resolved.ok) {\n const f = resolved.failure;\n return notOk(\n f.reason === 'ambiguous'\n ? errorRuntime('Multiple matching migrations found', {\n why: `Prefix \"${options.from}\" matches ${f.count} migrations in ${migrationsRelative}`,\n fix: 'Provide a longer prefix to disambiguate, or omit --from to use the latest migration target.',\n })\n : errorRuntime('Starting contract not found', {\n why: `No migration with to hash matching \"${options.from}\" exists in ${migrationsRelative}`,\n fix: 'Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target.',\n }),\n );\n }\n fromHash = resolved.value.manifest.to;\n fromContract = resolved.value.manifest.toContract;\n } else {\n const latestMigration = findLatestMigration(graph);\n if (latestMigration) {\n fromHash = latestMigration.to;\n const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);\n if (leafPkg) {\n fromContract = leafPkg.manifest.toContract;\n }\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 migrations = getTargetMigrations(config.target);\n if (!migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n [config.target, config.adapter, ...(config.extensionPacks ?? [])],\n );\n\n const strategy = migrationStrategy(migrations, config.target.targetId);\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 kind: 'regular',\n fromContract,\n toContract: toContractJson,\n hints: {\n used: [],\n applied: [],\n plannerVersion: '2.0.0',\n planningStrategy: strategy === 'descriptor' ? 'descriptors' : 'class-based',\n },\n labels: [],\n createdAt: timestamp.toISOString(),\n };\n\n const scaffoldContext = {\n packageDir,\n contractJsonPath: contractPathAbsolute,\n fromHash,\n toHash: toStorageHash,\n };\n\n try {\n let migrationTsContent: string;\n\n if (strategy === 'descriptor') {\n if (!migrations.planWithDescriptors || !migrations.renderDescriptorTypeScript) {\n throw errorTargetMigrationNotSupported({\n why: `Target \"${config.target.targetId}\" advertises descriptor flow but is missing required hooks`,\n });\n }\n const descriptorResult = migrations.planWithDescriptors({\n fromContract,\n toContract: toContractJson,\n frameworkComponents,\n });\n if (!descriptorResult.ok) {\n return notOk(\n errorMigrationPlanningFailed({\n conflicts: descriptorResult.conflicts as readonly CliErrorConflict[],\n }),\n );\n }\n if (descriptorResult.descriptors.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.',\n },\n ],\n }),\n );\n }\n migrationTsContent = migrations.renderDescriptorTypeScript(\n descriptorResult.descriptors,\n scaffoldContext,\n );\n } else {\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n const planner = migrations.createPlanner(familyInstance);\n const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);\n const plannerResult = planner.plan({\n contract: toContractJson,\n schema: fromSchema,\n policy: { allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] },\n fromHash,\n frameworkComponents,\n });\n if (plannerResult.kind === 'failure') {\n return notOk(\n errorMigrationPlanningFailed({\n conflicts: plannerResult.conflicts as readonly CliErrorConflict[],\n }),\n );\n }\n if (plannerResult.plan.operations.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.',\n },\n ],\n }),\n );\n }\n migrationTsContent = plannerResult.plan.renderTypeScript();\n }\n\n await writeMigrationPackage(packageDir, manifest, []);\n await copyContractToMigrationDir(packageDir, contractPathAbsolute);\n await writeMigrationTs(packageDir, migrationTsContent);\n\n // Always run emit inline. If migration.ts contains unfilled\n // placeholders (e.g. user must hand-author a dataTransform body),\n // emitMigration throws errorUnfilledPlaceholder (PN-MIG-2001) and\n // we propagate that structured error to the user.\n const { operations, migrationId } = await emitMigration(packageDir, {\n targetId: config.target.targetId,\n migrations,\n frameworkComponents,\n });\n\n const sql = extractSqlDdl(operations);\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: operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n sql,\n summary: `Planned ${operations.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 setCommandExamples(command, [\n 'prisma-next migration plan',\n 'prisma-next migration plan --name add-users-table',\n ]);\n addGlobalOptions(command)\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 latest migration target)')\n .action(async (options: MigrationPlanOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });\n const result = await executeMigrationPlanCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (planResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(planResult, null, 2));\n } else if (!flags.quiet) {\n ui.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\nexport type PrefixResolutionFailure =\n | { reason: 'ambiguous'; count: number }\n | { reason: 'not-found' };\n\n/**\n * Resolve a migration bundle by exact hash or prefix match.\n *\n * Tries exact match first, then prefix match (auto-prepending `sha256:` when\n * the needle omits the scheme). Returns the matched bundle on success, or a\n * discriminated failure indicating whether the prefix was ambiguous or simply\n * not found.\n *\n * @internal Exported for testing only.\n */\nexport function resolveBundleByPrefix<T extends { manifest: { to: string } }>(\n bundles: readonly T[],\n needle: string,\n): Result<T, PrefixResolutionFailure> {\n const exact = bundles.find((p) => p.manifest.to === needle);\n if (exact) return ok(exact);\n\n const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;\n const candidates = bundles.filter((p) => p.manifest.to.startsWith(prefixWithScheme));\n\n if (candidates.length === 1) return ok(candidates[0]!);\n if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });\n return notOk({ reason: 'not-found' });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"migration-plan.mjs","names":["details: Array<{ label: string; value: string }>","contractJsonContent: string","toContractJson: Contract","fromContract: Contract | null","fromHash: string","fromContractSourceDir: string | null","baseManifest: Omit<MigrationManifest, 'migrationId'>","plannedOps: readonly MigrationPlanOperation[]","lines: string[]"],"sources":["../../src/commands/migration-plan.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport type { Contract } from '@prisma-next/contract/types';\nimport { getEmittedArtifactPaths } from '@prisma-next/emitter';\nimport {\n createControlStack,\n type MigrationPlanOperation,\n} from '@prisma-next/framework-components/control';\nimport { computeMigrationId } from '@prisma-next/migration-tools/attestation';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { findLatestMigration } from '@prisma-next/migration-tools/dag';\nimport {\n copyFilesWithRename,\n formatMigrationDirName,\n writeMigrationPackage,\n} from '@prisma-next/migration-tools/io';\nimport { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';\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 } from 'pathe';\nimport { loadConfig } from '../config-loader';\nimport { extractSqlDdl } from '../control-api/operations/extract-sql-ddl';\nimport {\n type CliErrorConflict,\n CliStructuredError,\n errorContractValidationFailed,\n errorFileNotFound,\n errorMigrationPlanningFailed,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n getTargetMigrations,\n loadAllBundles,\n resolveContractPath,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n} from '../utils/command-helpers';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationPlanOptions extends CommonCommandOptions {\n readonly config?: string;\n readonly name?: string;\n readonly from?: string;\n}\n\nexport interface MigrationPlanResult {\n readonly ok: boolean;\n readonly noOp: boolean;\n readonly from: string;\n readonly to: 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 /**\n * When true, `migration.ts` was written but contains unfilled\n * `placeholder(...)` calls. The user must edit the file and then run\n * `node migration.ts` to self-emit `ops.json` / `migration.json`.\n */\n readonly pendingPlaceholders?: boolean;\n readonly timings: {\n readonly total: number;\n };\n}\n\nfunction mapMigrationToolsError(error: unknown): CliStructuredError {\n if (CliStructuredError.is(error)) {\n return error;\n }\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 ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationPlanResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const contractPathAbsolute = resolveContractPath(config);\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n if (!flags.json && !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 ui.stderr(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: Contract;\n try {\n toContractJson = JSON.parse(contractJsonContent) as Contract;\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 rawStorageHash = toContractJson.storage?.storageHash;\n if (typeof rawStorageHash !== 'string') {\n return notOk(\n errorContractValidationFailed('Contract is missing storageHash', {\n where: { path: contractPathAbsolute },\n }),\n );\n }\n const toStorageHash = rawStorageHash;\n\n // Read existing migrations and determine \"from\" contract\n let fromContract: Contract | null = null;\n let fromHash: string = EMPTY_CONTRACT_HASH;\n let fromContractSourceDir: string | null = null;\n\n try {\n const { bundles, graph } = await loadAllBundles(migrationsDir);\n\n if (options.from) {\n const resolved = resolveBundleByPrefix(bundles, options.from);\n if (!resolved.ok) {\n const f = resolved.failure;\n return notOk(\n f.reason === 'ambiguous'\n ? errorRuntime('Multiple matching migrations found', {\n why: `Prefix \"${options.from}\" matches ${f.count} migrations in ${migrationsRelative}`,\n fix: 'Provide a longer prefix to disambiguate, or omit --from to use the latest migration target.',\n })\n : errorRuntime('Starting contract not found', {\n why: `No migration with to hash matching \"${options.from}\" exists in ${migrationsRelative}`,\n fix: 'Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target.',\n }),\n );\n }\n fromHash = resolved.value.manifest.to;\n fromContract = resolved.value.manifest.toContract;\n fromContractSourceDir = resolved.value.dirPath;\n } else {\n const latestMigration = findLatestMigration(graph);\n if (latestMigration) {\n fromHash = latestMigration.to;\n const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);\n if (leafPkg) {\n fromContract = leafPkg.manifest.toContract;\n fromContractSourceDir = leafPkg.dirPath;\n }\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 migrations = getTargetMigrations(config.target);\n if (!migrations) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n [config.target, config.adapter, ...(config.extensionPacks ?? [])],\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 baseManifest: Omit<MigrationManifest, 'migrationId'> = {\n from: fromHash,\n to: toStorageHash,\n kind: 'regular',\n fromContract,\n toContract: toContractJson,\n hints: {\n used: [],\n applied: [],\n plannerVersion: '2.0.0',\n },\n labels: [],\n createdAt: timestamp.toISOString(),\n };\n\n try {\n const stack = createControlStack(config);\n const familyInstance = config.family.create(stack);\n const planner = migrations.createPlanner(familyInstance);\n const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);\n const plannerResult = planner.plan({\n contract: toContractJson,\n schema: fromSchema,\n policy: { allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] },\n fromHash,\n fromContract,\n frameworkComponents,\n });\n if (plannerResult.kind === 'failure') {\n return notOk(\n errorMigrationPlanningFailed({\n conflicts: plannerResult.conflicts as readonly CliErrorConflict[],\n }),\n );\n }\n\n // Accessing .operations triggers toOp() on each call. If any call\n // is a DataTransformCall with an unfilled placeholder stub, toOp()\n // throws PN-MIG-2001. We catch that here so the migration can still\n // be scaffolded with `ops: []`; the user fills the placeholder, then\n // re-runs `node migration.ts` to attest with the real ops.\n let plannedOps: readonly MigrationPlanOperation[] = [];\n let hasPlaceholders = false;\n try {\n plannedOps = plannerResult.plan.operations;\n if (plannedOps.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.',\n },\n ],\n }),\n );\n }\n } catch (e) {\n if (CliStructuredError.is(e) && e.domain === 'MIG' && e.code === '2001') {\n hasPlaceholders = true;\n } else {\n throw e;\n }\n }\n\n const migrationTsContent = plannerResult.plan.renderTypeScript();\n\n // Always-attest: compute migrationId over (manifest, ops). When\n // placeholders blocked lowering, ops is `[]` and the id hashes over\n // the empty list — re-emitting after the user fills the placeholder\n // produces a different id (over the real ops). This is intentional;\n // there is no on-disk \"draft\" state.\n const opsForWrite = hasPlaceholders ? [] : plannedOps;\n const manifest: MigrationManifest = {\n ...baseManifest,\n migrationId: computeMigrationId(baseManifest, opsForWrite),\n };\n\n await writeMigrationPackage(packageDir, manifest, opsForWrite);\n const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);\n await copyFilesWithRename(packageDir, [\n { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },\n { sourcePath: destinationArtifacts.dtsPath, destName: 'end-contract.d.ts' },\n ]);\n if (fromContractSourceDir !== null) {\n const sourceArtifacts = getEmittedArtifactPaths(\n join(fromContractSourceDir, 'end-contract.json'),\n );\n await copyFilesWithRename(packageDir, [\n { sourcePath: sourceArtifacts.jsonPath, destName: 'start-contract.json' },\n { sourcePath: sourceArtifacts.dtsPath, destName: 'start-contract.d.ts' },\n ]);\n }\n await writeMigrationTs(packageDir, migrationTsContent);\n\n if (hasPlaceholders) {\n const result: MigrationPlanResult = {\n ok: true,\n noOp: false,\n from: fromHash,\n to: toStorageHash,\n dir: relative(process.cwd(), packageDir),\n operations: [],\n pendingPlaceholders: true,\n summary:\n 'Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit',\n timings: { total: Date.now() - startTime },\n };\n return ok(result);\n }\n\n const sql = extractSqlDdl(plannedOps);\n const result: MigrationPlanResult = {\n ok: true,\n noOp: false,\n from: fromHash,\n to: toStorageHash,\n dir: relative(process.cwd(), packageDir),\n operations: plannedOps.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n sql,\n summary: `Planned ${plannedOps.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 setCommandExamples(command, [\n 'prisma-next migration plan',\n 'prisma-next migration plan --name add-users-table',\n ]);\n addGlobalOptions(command)\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 latest migration target)')\n .action(async (options: MigrationPlanOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });\n const result = await executeMigrationPlanCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (planResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(planResult, null, 2));\n } else if (!flags.quiet) {\n ui.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 if (result.pendingPlaceholders) {\n lines.push(`${yellow_('⚠')} ${result.summary}`);\n lines.push('');\n lines.push(dim_(`from: ${result.from}`));\n lines.push(dim_(`to: ${result.to}`));\n if (result.dir) {\n lines.push(dim_(`dir: ${result.dir}`));\n }\n lines.push('');\n lines.push(\n 'Open migration.ts and replace each `placeholder(...)` call with your actual query.',\n );\n lines.push(`Then run: ${green_(`node ${result.dir ?? '<dir>'}/migration.ts`)}`);\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.dir) {\n lines.push(dim_(`dir: ${result.dir}`));\n }\n\n lines.push('');\n lines.push(\n `Next: ${green_(`node ${result.dir ?? '<dir>'}/migration.ts`)} to emit ops.json and attest migrationId before running ${green_('prisma-next migration apply')}.`,\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\nexport type PrefixResolutionFailure =\n | { reason: 'ambiguous'; count: number }\n | { reason: 'not-found' };\n\n/**\n * Resolve a migration bundle by exact hash or prefix match.\n *\n * Tries exact match first, then prefix match (auto-prepending `sha256:` when\n * the needle omits the scheme). Returns the matched bundle on success, or a\n * discriminated failure indicating whether the prefix was ambiguous or simply\n * not found.\n *\n * @internal Exported for testing only.\n */\nexport function resolveBundleByPrefix<T extends { manifest: { to: string } }>(\n bundles: readonly T[],\n needle: string,\n): Result<T, PrefixResolutionFailure> {\n const exact = bundles.find((p) => p.manifest.to === needle);\n if (exact) return ok(exact);\n\n const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;\n const candidates = bundles.filter((p) => p.manifest.to.startsWith(prefixWithScheme));\n\n if (candidates.length === 1) return ok(candidates[0]!);\n if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });\n return notOk({ reason: 'not-found' });\n}\n"],"mappings":"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"}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { t as CliStructuredError } from "../cli-errors-
|
|
1
|
+
import { t as CliStructuredError } from "../cli-errors-BFYgBH3L.mjs";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { Result } from "@prisma-next/utils/result";
|
|
4
|
+
import { RefEntry } from "@prisma-next/migration-tools/refs";
|
|
4
5
|
|
|
5
6
|
//#region src/commands/migration-ref.d.ts
|
|
6
7
|
interface RefSetResult {
|
|
7
8
|
readonly ok: true;
|
|
8
9
|
readonly ref: string;
|
|
9
10
|
readonly hash: string;
|
|
11
|
+
readonly invariants: readonly string[];
|
|
10
12
|
}
|
|
11
13
|
interface RefGetResult {
|
|
12
14
|
readonly ok: true;
|
|
13
15
|
readonly ref: string;
|
|
14
16
|
readonly hash: string;
|
|
17
|
+
readonly invariants: readonly string[];
|
|
15
18
|
}
|
|
16
19
|
interface RefDeleteResult {
|
|
17
20
|
readonly ok: true;
|
|
@@ -20,11 +23,10 @@ interface RefDeleteResult {
|
|
|
20
23
|
}
|
|
21
24
|
interface RefListResult {
|
|
22
25
|
readonly ok: true;
|
|
23
|
-
readonly refs: Record<string,
|
|
26
|
+
readonly refs: Record<string, RefEntry>;
|
|
24
27
|
}
|
|
25
28
|
declare function cliErrorInvalidRefName(name: string): CliStructuredError;
|
|
26
29
|
declare function cliErrorInvalidRefValue(hash: string): CliStructuredError;
|
|
27
|
-
declare function errorRefNotFound(name: string): CliStructuredError;
|
|
28
30
|
declare function executeRefSetCommand(name: string, hash: string, options: {
|
|
29
31
|
config?: string;
|
|
30
32
|
}): Promise<Result<RefSetResult, CliStructuredError>>;
|
|
@@ -39,5 +41,5 @@ declare function executeRefListCommand(options: {
|
|
|
39
41
|
}): Promise<Result<RefListResult, CliStructuredError>>;
|
|
40
42
|
declare function createMigrationRefCommand(): Command;
|
|
41
43
|
//#endregion
|
|
42
|
-
export { cliErrorInvalidRefName, cliErrorInvalidRefValue, createMigrationRefCommand,
|
|
44
|
+
export { cliErrorInvalidRefName, cliErrorInvalidRefValue, createMigrationRefCommand, executeRefDeleteCommand, executeRefGetCommand, executeRefListCommand, executeRefSetCommand };
|
|
43
45
|
//# sourceMappingURL=migration-ref.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration-ref.d.mts","names":[],"sources":["../../src/commands/migration-ref.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migration-ref.d.mts","names":[],"sources":["../../src/commands/migration-ref.ts"],"sourcesContent":[],"mappings":";;;;;;UAwBU,YAAA;;EAAA,SAAA,GAAA,EAAA,MAAY;EAOZ,SAAA,IAAA,EAAY,MAAA;EAOZ,SAAA,UAAe,EAAA,SAAA,MAAA,EAAA;AAAA;AAQF,UAfb,YAAA,CA6BD;EAOA,SAAA,EAAA,EAAA,IAAA;EAOM,SAAA,GAAA,EAAA,MAAA;EAIG,SAAA,IAAA,EAAA,MAAA;EAAc,SAAA,UAAA,EAAA,SAAA,MAAA,EAAA;;UAxCtB,eAAA,CAwCP;EAAO,SAAA,EAAA,EAAA,IAAA;EAoBK,SAAA,GAAA,EAAA,MAAA;EAGG,SAAA,OAAA,EAAA,IAAA;;UAzDR,aAAA,CAyDC;EAAR,SAAA,EAAA,EAAA,IAAA;EAAO,SAAA,IAAA,EAvDO,MAuDP,CAAA,MAAA,EAvDsB,QAuDtB,CAAA;AAAA;iBAzCD,sBAAA,CAwDS,IAAA,EAAA,MAAA,CAAA,EAxD6B,kBAwD7B;iBAjDT,uBAAA,CAiD0B,IAAA,EAAA,MAAA,CAAA,EAjDa,kBAiDb;iBA1CpB,oBAAA,CA0CJ,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA;EAAR,MAAA,CAAA,EAAA,MAAA;CAAO,CAAA,EAtCP,OAsCO,CAtCC,MAsCD,CAtCQ,YAsCR,EAtCsB,kBAsCtB,CAAA,CAAA;AAAA,iBAlBK,oBAAA,CA8BqB,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA;EAEjB,MAAA,CAAA,EAAA,MAAA;CAAe,CAAA,EA7B/B,OA6B+B,CA7BvB,MA6BuB,CA7BhB,YA6BgB,EA7BF,kBA6BE,CAAA,CAAA;iBAjBnB,uBAAA,CAiBH,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA;EAAR,MAAA,CAAA,EAAA,MAAA;CAAO,CAAA,EAdR,OAcQ,CAdA,MAcA,CAdO,eAcP,EAdwB,kBAcxB,CAAA,CAAA;AA8IX,iBAhJe,qBAAA,CAgJ0B,OAAI,EAAA;;IA9IzC,QAAQ,OAAO,eAAe;iBA8IlB,yBAAA,CAAA,GAA6B"}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { t as loadConfig } from "../config-loader-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
1
|
+
import { t as loadConfig } from "../config-loader-C25b63rJ.mjs";
|
|
2
|
+
import { _ as errorUnexpected, m as errorRuntime, t as CliStructuredError } from "../cli-errors-Cd79vmTH.mjs";
|
|
3
|
+
import { t as TerminalUI } from "../terminal-ui-C3ZLwQxK.mjs";
|
|
4
|
+
import { c as resolveMigrationPaths, h as formatCommandHelp, m as parseGlobalFlags, n as addGlobalOptions, t as handleResult, u as setCommandDescriptions } from "../result-handler-Ba3zWQsI.mjs";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
7
|
-
import {
|
|
7
|
+
import { deleteRef, readRef, readRefs, validateRefName, validateRefValue, writeRef } from "@prisma-next/migration-tools/refs";
|
|
8
8
|
import { MigrationToolsError } from "@prisma-next/migration-tools/types";
|
|
9
|
-
import { readRefs, resolveRef, validateRefName, validateRefValue, writeRefs } from "@prisma-next/migration-tools/refs";
|
|
10
9
|
|
|
11
10
|
//#region src/commands/migration-ref.ts
|
|
12
|
-
function resolveRefsPath(configPath, config) {
|
|
13
|
-
return resolve(configPath ? resolve(configPath, "..") : process.cwd(), config?.migrations?.dir ?? "migrations", "refs.json");
|
|
14
|
-
}
|
|
15
11
|
function mapError(error) {
|
|
16
12
|
if (MigrationToolsError.is(error)) return errorRuntime(error.message, {
|
|
17
13
|
why: error.why,
|
|
@@ -32,26 +28,21 @@ function cliErrorInvalidRefValue(hash) {
|
|
|
32
28
|
fix: "Contract hashes must match the format \"sha256:<64 hex chars>\". Copy the hash from `prisma-next contract emit` or `migration status --json`."
|
|
33
29
|
});
|
|
34
30
|
}
|
|
35
|
-
function errorRefNotFound(name) {
|
|
36
|
-
return errorRuntime(`Ref "${name}" does not exist`, {
|
|
37
|
-
why: `No ref named "${name}" found in refs.json`,
|
|
38
|
-
fix: `Run \`prisma-next migration ref list\` to see available refs, or \`prisma-next migration ref set ${name} <hash>\` to create it`
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
31
|
async function executeRefSetCommand(name, hash, options) {
|
|
42
32
|
if (!validateRefName(name)) return notOk(cliErrorInvalidRefName(name));
|
|
43
33
|
if (!validateRefValue(hash)) return notOk(cliErrorInvalidRefValue(hash));
|
|
44
34
|
try {
|
|
45
35
|
const config = await loadConfig(options.config);
|
|
46
|
-
const
|
|
47
|
-
await
|
|
48
|
-
|
|
49
|
-
[
|
|
36
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
37
|
+
await writeRef(refsDir, name, {
|
|
38
|
+
hash,
|
|
39
|
+
invariants: []
|
|
50
40
|
});
|
|
51
41
|
return ok({
|
|
52
42
|
ok: true,
|
|
53
43
|
ref: name,
|
|
54
|
-
hash
|
|
44
|
+
hash,
|
|
45
|
+
invariants: []
|
|
55
46
|
});
|
|
56
47
|
} catch (error) {
|
|
57
48
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -61,10 +52,13 @@ async function executeRefSetCommand(name, hash, options) {
|
|
|
61
52
|
async function executeRefGetCommand(name, options) {
|
|
62
53
|
try {
|
|
63
54
|
const config = await loadConfig(options.config);
|
|
55
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
56
|
+
const entry = await readRef(refsDir, name);
|
|
64
57
|
return ok({
|
|
65
58
|
ok: true,
|
|
66
59
|
ref: name,
|
|
67
|
-
hash:
|
|
60
|
+
hash: entry.hash,
|
|
61
|
+
invariants: entry.invariants
|
|
68
62
|
});
|
|
69
63
|
} catch (error) {
|
|
70
64
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -74,11 +68,8 @@ async function executeRefGetCommand(name, options) {
|
|
|
74
68
|
async function executeRefDeleteCommand(name, options) {
|
|
75
69
|
try {
|
|
76
70
|
const config = await loadConfig(options.config);
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
if (!Object.hasOwn(refs, name)) return notOk(errorRefNotFound(name));
|
|
80
|
-
const { [name]: _, ...remaining } = refs;
|
|
81
|
-
await writeRefs(refsPath, remaining);
|
|
71
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
72
|
+
await deleteRef(refsDir, name);
|
|
82
73
|
return ok({
|
|
83
74
|
ok: true,
|
|
84
75
|
ref: name,
|
|
@@ -92,9 +83,10 @@ async function executeRefDeleteCommand(name, options) {
|
|
|
92
83
|
async function executeRefListCommand(options) {
|
|
93
84
|
try {
|
|
94
85
|
const config = await loadConfig(options.config);
|
|
86
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
95
87
|
return ok({
|
|
96
88
|
ok: true,
|
|
97
|
-
refs: await readRefs(
|
|
89
|
+
refs: await readRefs(refsDir)
|
|
98
90
|
});
|
|
99
91
|
} catch (error) {
|
|
100
92
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -103,7 +95,7 @@ async function executeRefListCommand(options) {
|
|
|
103
95
|
}
|
|
104
96
|
function createRefSetCommand() {
|
|
105
97
|
const command = new Command("set");
|
|
106
|
-
setCommandDescriptions(command, "Set a ref to a contract hash", "Sets a named ref to point to a contract hash in migrations/refs
|
|
98
|
+
setCommandDescriptions(command, "Set a ref to a contract hash", "Sets a named ref to point to a contract hash in migrations/refs/.");
|
|
107
99
|
addGlobalOptions(command).argument("<name>", "Ref name (e.g., staging, production)").argument("<hash>", "Contract hash to point to").option("--config <path>", "Path to prisma-next.config.ts").action(async (name, hash, options) => {
|
|
108
100
|
const flags = parseGlobalFlags(options);
|
|
109
101
|
const ui = new TerminalUI({
|
|
@@ -120,7 +112,7 @@ function createRefSetCommand() {
|
|
|
120
112
|
}
|
|
121
113
|
function createRefGetCommand() {
|
|
122
114
|
const command = new Command("get");
|
|
123
|
-
setCommandDescriptions(command, "Get the hash for a ref", "Reads a named ref from migrations/refs
|
|
115
|
+
setCommandDescriptions(command, "Get the hash for a ref", "Reads a named ref from migrations/refs/ and prints its contract hash.");
|
|
124
116
|
addGlobalOptions(command).argument("<name>", "Ref name to look up").option("--config <path>", "Path to prisma-next.config.ts").action(async (name, options) => {
|
|
125
117
|
const flags = parseGlobalFlags(options);
|
|
126
118
|
const ui = new TerminalUI({
|
|
@@ -137,7 +129,7 @@ function createRefGetCommand() {
|
|
|
137
129
|
}
|
|
138
130
|
function createRefDeleteCommand() {
|
|
139
131
|
const command = new Command("delete");
|
|
140
|
-
setCommandDescriptions(command, "Delete a ref", "Removes a named ref from migrations/refs
|
|
132
|
+
setCommandDescriptions(command, "Delete a ref", "Removes a named ref from migrations/refs/.");
|
|
141
133
|
addGlobalOptions(command).argument("<name>", "Ref name to delete").option("--config <path>", "Path to prisma-next.config.ts").action(async (name, options) => {
|
|
142
134
|
const flags = parseGlobalFlags(options);
|
|
143
135
|
const ui = new TerminalUI({
|
|
@@ -154,7 +146,7 @@ function createRefDeleteCommand() {
|
|
|
154
146
|
}
|
|
155
147
|
function createRefListCommand() {
|
|
156
148
|
const command = new Command("list");
|
|
157
|
-
setCommandDescriptions(command, "List all refs", "Lists all named refs from migrations/refs
|
|
149
|
+
setCommandDescriptions(command, "List all refs", "Lists all named refs from migrations/refs/.");
|
|
158
150
|
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").action(async (options) => {
|
|
159
151
|
const flags = parseGlobalFlags(options);
|
|
160
152
|
const ui = new TerminalUI({
|
|
@@ -166,7 +158,10 @@ function createRefListCommand() {
|
|
|
166
158
|
else if (!flags.quiet) {
|
|
167
159
|
const entries = Object.entries(value.refs);
|
|
168
160
|
if (entries.length === 0) ui.output("No refs defined");
|
|
169
|
-
else for (const [refName,
|
|
161
|
+
else for (const [refName, entry] of entries) {
|
|
162
|
+
const invariantsSuffix = entry.invariants.length > 0 ? ` [invariants: ${entry.invariants.join(", ")}]` : "";
|
|
163
|
+
ui.output(`${refName} → ${entry.hash}${invariantsSuffix}`);
|
|
164
|
+
}
|
|
170
165
|
}
|
|
171
166
|
});
|
|
172
167
|
process.exit(exitCode);
|
|
@@ -175,7 +170,7 @@ function createRefListCommand() {
|
|
|
175
170
|
}
|
|
176
171
|
function createMigrationRefCommand() {
|
|
177
172
|
const command = new Command("ref");
|
|
178
|
-
setCommandDescriptions(command, "Manage migration refs", "Manage named refs in migrations/refs
|
|
173
|
+
setCommandDescriptions(command, "Manage migration refs", "Manage named refs in migrations/refs/. Refs map logical environment\nnames (e.g., staging, production) to contract hashes.");
|
|
179
174
|
addGlobalOptions(command).configureHelp({
|
|
180
175
|
formatHelp: (cmd) => formatCommandHelp({
|
|
181
176
|
command: cmd,
|
|
@@ -191,5 +186,5 @@ function createMigrationRefCommand() {
|
|
|
191
186
|
}
|
|
192
187
|
|
|
193
188
|
//#endregion
|
|
194
|
-
export { cliErrorInvalidRefName, cliErrorInvalidRefValue, createMigrationRefCommand,
|
|
189
|
+
export { cliErrorInvalidRefName, cliErrorInvalidRefValue, createMigrationRefCommand, executeRefDeleteCommand, executeRefGetCommand, executeRefListCommand, executeRefSetCommand };
|
|
195
190
|
//# sourceMappingURL=migration-ref.mjs.map
|