@prisma-next/cli 0.4.1 → 0.4.3
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 +56 -26
- package/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/cli-errors-DDeVsP2Y.d.mts +5 -0
- package/dist/cli.mjs +131 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-DGKrciLM.mjs → client-1JqqkiC7.mjs} +45 -20
- package/dist/client-1JqqkiC7.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +7 -2
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +8 -2
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +11 -9
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +8 -5
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.mjs +8 -7
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +10 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +10 -9
- package/dist/commands/db-verify.mjs.map +1 -1
- package/dist/commands/migration-apply.d.mts +5 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +57 -57
- 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 +26 -32
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +45 -48
- 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 +31 -40
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +28 -29
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +23 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +8 -3
- package/dist/{config-loader-_xQZsw0i.mjs → config-loader-ih8ViDb_.mjs} +2 -2
- package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-DS5NzZh2.mjs +6 -0
- package/dist/contract-emit-RZBWzkop.mjs +329 -0
- package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
- package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
- package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
- package/dist/{contract-enrichment-BV4KpbNW.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
- package/dist/{contract-enrichment-BV4KpbNW.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
- package/dist/{contract-infer-CUbiWGX0.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
- package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +86 -21
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +7 -5
- package/dist/exports/index.mjs +8 -3
- package/dist/exports/index.mjs.map +1 -1
- 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/{framework-components-B__p--vT.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
- package/dist/{framework-components-B__p--vT.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
- package/dist/init-DAbQMxIR.mjs +2062 -0
- package/dist/init-DAbQMxIR.mjs.map +1 -0
- package/dist/{inspect-live-schema-wIYBTdL3.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
- package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
- package/dist/migration-cli.d.mts +80 -0
- package/dist/migration-cli.d.mts.map +1 -0
- package/dist/migration-cli.mjs +408 -0
- package/dist/migration-cli.mjs.map +1 -0
- package/dist/{migration-command-scaffold-BC73xQSo.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
- package/dist/{migration-command-scaffold-BC73xQSo.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
- package/dist/{migration-status-CXBbScH5.mjs → migration-status-DoPrFIOQ.mjs} +119 -64
- package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
- package/dist/{migrations-DYRAjiVh.mjs → migrations-MEoKMiV5.mjs} +42 -21
- package/dist/migrations-MEoKMiV5.mjs.map +1 -0
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-Bwouy73-.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
- package/dist/{progress-adapter-Bwouy73-.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-CGohaH1o.mjs → result-handler-Ch6hVnOo.mjs} +36 -94
- package/dist/result-handler-Ch6hVnOo.mjs.map +1 -0
- package/dist/{terminal-ui-BuPXVRFY.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
- package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
- package/dist/{verify-Cm2UFuZA.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-Cm2UFuZA.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +27 -17
- package/src/cli.ts +32 -6
- package/src/commands/contract-emit.ts +67 -163
- package/src/commands/contract-infer.ts +7 -20
- package/src/commands/db-init.ts +1 -0
- package/src/commands/db-update.ts +1 -1
- 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/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +86 -65
- package/src/commands/migration-new.ts +28 -34
- package/src/commands/migration-plan.ts +80 -56
- package/src/commands/migration-ref.ts +40 -54
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +202 -71
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +21 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-init.ts +10 -6
- package/src/control-api/operations/db-update.ts +10 -6
- package/src/control-api/operations/migration-apply.ts +30 -9
- package/src/control-api/types.ts +69 -7
- package/src/exports/control-api.ts +2 -1
- package/src/exports/init-output.ts +10 -0
- package/src/migration-cli.ts +577 -0
- package/src/utils/cli-errors.ts +50 -2
- package/src/utils/command-helpers.ts +48 -26
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-CznZA5-d.mjs +0 -5
- package/dist/cli-errors-z37sV3eR.d.mts +0 -4
- package/dist/client-DGKrciLM.mjs.map +0 -1
- package/dist/config-loader-_xQZsw0i.mjs.map +0 -1
- package/dist/contract-emit-304WZtZJ.mjs +0 -4
- package/dist/contract-emit-DgeWdonT.mjs +0 -122
- package/dist/contract-emit-DgeWdonT.mjs.map +0 -1
- package/dist/contract-emit-mU1_B_m9.mjs +0 -195
- package/dist/contract-emit-mU1_B_m9.mjs.map +0 -1
- package/dist/contract-infer-CUbiWGX0.mjs.map +0 -1
- package/dist/extract-operation-statements-DWWFz1PK.mjs +0 -13
- package/dist/extract-operation-statements-DWWFz1PK.mjs.map +0 -1
- package/dist/extract-sql-ddl-7zn_AFS8.mjs +0 -26
- package/dist/extract-sql-ddl-7zn_AFS8.mjs.map +0 -1
- package/dist/init-DRquYpPa.mjs +0 -430
- package/dist/init-DRquYpPa.mjs.map +0 -1
- package/dist/inspect-live-schema-wIYBTdL3.mjs.map +0 -1
- package/dist/migration-status-CXBbScH5.mjs.map +0 -1
- package/dist/migrations-DYRAjiVh.mjs.map +0 -1
- package/dist/result-handler-CGohaH1o.mjs.map +0 -1
- package/dist/terminal-ui-BuPXVRFY.mjs.map +0 -1
- package/dist/validate-contract-deps-DZqv9m7H.mjs +0 -37
- package/dist/validate-contract-deps-DZqv9m7H.mjs.map +0 -1
- package/src/control-api/operations/extract-operation-statements.ts +0 -14
- package/src/control-api/operations/extract-sql-ddl.ts +0 -47
|
@@ -3,23 +3,25 @@ import type { Contract } from '@prisma-next/contract/types';
|
|
|
3
3
|
import { getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
4
4
|
import {
|
|
5
5
|
createControlStack,
|
|
6
|
+
hasOperationPreview,
|
|
6
7
|
type MigrationPlanOperation,
|
|
8
|
+
type OperationPreview,
|
|
7
9
|
} from '@prisma-next/framework-components/control';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
10
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
11
|
+
import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
12
|
+
import { deriveProvidedInvariants } from '@prisma-next/migration-tools/invariants';
|
|
11
13
|
import {
|
|
12
14
|
copyFilesWithRename,
|
|
13
15
|
formatMigrationDirName,
|
|
14
16
|
writeMigrationPackage,
|
|
15
17
|
} from '@prisma-next/migration-tools/io';
|
|
18
|
+
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
19
|
+
import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
|
|
16
20
|
import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
|
|
17
|
-
import { type MigrationManifest, MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
18
21
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
19
22
|
import { Command } from 'commander';
|
|
20
23
|
import { join, relative } from 'pathe';
|
|
21
24
|
import { loadConfig } from '../config-loader';
|
|
22
|
-
import { extractSqlDdl } from '../control-api/operations/extract-sql-ddl';
|
|
23
25
|
import {
|
|
24
26
|
type CliErrorConflict,
|
|
25
27
|
CliStructuredError,
|
|
@@ -29,11 +31,12 @@ import {
|
|
|
29
31
|
errorRuntime,
|
|
30
32
|
errorTargetMigrationNotSupported,
|
|
31
33
|
errorUnexpected,
|
|
34
|
+
mapMigrationToolsError,
|
|
32
35
|
} from '../utils/cli-errors';
|
|
33
36
|
import {
|
|
34
37
|
addGlobalOptions,
|
|
35
38
|
getTargetMigrations,
|
|
36
|
-
|
|
39
|
+
loadMigrationPackages,
|
|
37
40
|
resolveContractPath,
|
|
38
41
|
resolveMigrationPaths,
|
|
39
42
|
setCommandDescriptions,
|
|
@@ -55,7 +58,7 @@ interface MigrationPlanOptions extends CommonCommandOptions {
|
|
|
55
58
|
export interface MigrationPlanResult {
|
|
56
59
|
readonly ok: boolean;
|
|
57
60
|
readonly noOp: boolean;
|
|
58
|
-
readonly from: string;
|
|
61
|
+
readonly from: string | null;
|
|
59
62
|
readonly to: string;
|
|
60
63
|
readonly dir?: string;
|
|
61
64
|
readonly operations: readonly {
|
|
@@ -63,7 +66,12 @@ export interface MigrationPlanResult {
|
|
|
63
66
|
readonly label: string;
|
|
64
67
|
readonly operationClass: string;
|
|
65
68
|
}[];
|
|
66
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Family-agnostic textual preview of the migration plan operations.
|
|
71
|
+
* Replaces the previous `sql?: readonly string[]` field; consumers should
|
|
72
|
+
* read `result.preview?.statements`.
|
|
73
|
+
*/
|
|
74
|
+
readonly preview?: OperationPreview;
|
|
67
75
|
readonly summary: string;
|
|
68
76
|
/**
|
|
69
77
|
* When true, `migration.ts` was written but contains unfilled
|
|
@@ -76,22 +84,6 @@ export interface MigrationPlanResult {
|
|
|
76
84
|
};
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
function mapMigrationToolsError(error: unknown): CliStructuredError {
|
|
80
|
-
if (CliStructuredError.is(error)) {
|
|
81
|
-
return error;
|
|
82
|
-
}
|
|
83
|
-
if (MigrationToolsError.is(error)) {
|
|
84
|
-
return errorRuntime(error.message, {
|
|
85
|
-
why: error.why,
|
|
86
|
-
fix: error.fix,
|
|
87
|
-
meta: { code: error.code, ...(error.details ?? {}) },
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
91
|
-
why: `Unexpected error during migration plan: ${error instanceof Error ? error.message : String(error)}`,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
87
|
async function executeMigrationPlanCommand(
|
|
96
88
|
options: MigrationPlanOptions,
|
|
97
89
|
flags: GlobalFlags,
|
|
@@ -173,11 +165,11 @@ async function executeMigrationPlanCommand(
|
|
|
173
165
|
|
|
174
166
|
// Read existing migrations and determine "from" contract
|
|
175
167
|
let fromContract: Contract | null = null;
|
|
176
|
-
let fromHash: string =
|
|
168
|
+
let fromHash: string | null = null;
|
|
177
169
|
let fromContractSourceDir: string | null = null;
|
|
178
170
|
|
|
179
171
|
try {
|
|
180
|
-
const { bundles, graph } = await
|
|
172
|
+
const { bundles, graph } = await loadMigrationPackages(migrationsDir);
|
|
181
173
|
|
|
182
174
|
if (options.from) {
|
|
183
175
|
const resolved = resolveBundleByPrefix(bundles, options.from);
|
|
@@ -195,16 +187,18 @@ async function executeMigrationPlanCommand(
|
|
|
195
187
|
}),
|
|
196
188
|
);
|
|
197
189
|
}
|
|
198
|
-
fromHash = resolved.value.
|
|
199
|
-
fromContract = resolved.value.
|
|
190
|
+
fromHash = resolved.value.metadata.to;
|
|
191
|
+
fromContract = resolved.value.metadata.toContract;
|
|
200
192
|
fromContractSourceDir = resolved.value.dirPath;
|
|
201
193
|
} else {
|
|
202
194
|
const latestMigration = findLatestMigration(graph);
|
|
203
195
|
if (latestMigration) {
|
|
204
196
|
fromHash = latestMigration.to;
|
|
205
|
-
const leafPkg = bundles.find(
|
|
197
|
+
const leafPkg = bundles.find(
|
|
198
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
199
|
+
);
|
|
206
200
|
if (leafPkg) {
|
|
207
|
-
fromContract = leafPkg.
|
|
201
|
+
fromContract = leafPkg.metadata.toContract;
|
|
208
202
|
fromContractSourceDir = leafPkg.dirPath;
|
|
209
203
|
}
|
|
210
204
|
}
|
|
@@ -213,7 +207,16 @@ async function executeMigrationPlanCommand(
|
|
|
213
207
|
if (MigrationToolsError.is(error)) {
|
|
214
208
|
return notOk(mapMigrationToolsError(error));
|
|
215
209
|
}
|
|
216
|
-
|
|
210
|
+
// Wrap unexpected (non-MigrationToolsError) failures from the migration
|
|
211
|
+
// load phase in a structured CLI envelope. Letting them throw would
|
|
212
|
+
// bypass `handleResult()` and crash the command — see CLI structured-
|
|
213
|
+
// errors guideline (CliStructuredError + Result pattern).
|
|
214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
215
|
+
return notOk(
|
|
216
|
+
errorUnexpected(message, {
|
|
217
|
+
why: `Unexpected error while loading migrations: ${message}`,
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
// Check for no-op (same hash means no changes)
|
|
@@ -251,10 +254,9 @@ async function executeMigrationPlanCommand(
|
|
|
251
254
|
const dirName = formatMigrationDirName(timestamp, slug);
|
|
252
255
|
const packageDir = join(migrationsDir, dirName);
|
|
253
256
|
|
|
254
|
-
const
|
|
257
|
+
const baseMetadata: Omit<MigrationMetadata, 'migrationHash' | 'providedInvariants'> = {
|
|
255
258
|
from: fromHash,
|
|
256
259
|
to: toStorageHash,
|
|
257
|
-
kind: 'regular',
|
|
258
260
|
fromContract,
|
|
259
261
|
toContract: toContractJson,
|
|
260
262
|
hints: {
|
|
@@ -275,7 +277,6 @@ async function executeMigrationPlanCommand(
|
|
|
275
277
|
contract: toContractJson,
|
|
276
278
|
schema: fromSchema,
|
|
277
279
|
policy: { allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] },
|
|
278
|
-
fromHash,
|
|
279
280
|
fromContract,
|
|
280
281
|
frameworkComponents,
|
|
281
282
|
});
|
|
@@ -320,18 +321,22 @@ async function executeMigrationPlanCommand(
|
|
|
320
321
|
|
|
321
322
|
const migrationTsContent = plannerResult.plan.renderTypeScript();
|
|
322
323
|
|
|
323
|
-
// Always-attest: compute
|
|
324
|
-
// placeholders blocked lowering, ops is `[]` and the
|
|
325
|
-
// the empty list — re-emitting after the user fills the placeholder
|
|
326
|
-
// produces a different
|
|
324
|
+
// Always-attest: compute migrationHash over (metadata, ops). When
|
|
325
|
+
// placeholders blocked lowering, ops is `[]` and the hash is computed
|
|
326
|
+
// over the empty list — re-emitting after the user fills the placeholder
|
|
327
|
+
// produces a different hash (over the real ops). This is intentional;
|
|
327
328
|
// there is no on-disk "draft" state.
|
|
328
329
|
const opsForWrite = hasPlaceholders ? [] : plannedOps;
|
|
329
|
-
const
|
|
330
|
-
...
|
|
331
|
-
|
|
330
|
+
const metadataWithInvariants: Omit<MigrationMetadata, 'migrationHash'> = {
|
|
331
|
+
...baseMetadata,
|
|
332
|
+
providedInvariants: deriveProvidedInvariants(opsForWrite),
|
|
333
|
+
};
|
|
334
|
+
const metadata: MigrationMetadata = {
|
|
335
|
+
...metadataWithInvariants,
|
|
336
|
+
migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite),
|
|
332
337
|
};
|
|
333
338
|
|
|
334
|
-
await writeMigrationPackage(packageDir,
|
|
339
|
+
await writeMigrationPackage(packageDir, metadata, opsForWrite);
|
|
335
340
|
const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
|
|
336
341
|
await copyFilesWithRename(packageDir, [
|
|
337
342
|
{ sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
|
|
@@ -364,7 +369,9 @@ async function executeMigrationPlanCommand(
|
|
|
364
369
|
return ok(result);
|
|
365
370
|
}
|
|
366
371
|
|
|
367
|
-
const
|
|
372
|
+
const preview = hasOperationPreview(familyInstance)
|
|
373
|
+
? familyInstance.toOperationPreview(plannedOps)
|
|
374
|
+
: undefined;
|
|
368
375
|
const result: MigrationPlanResult = {
|
|
369
376
|
ok: true,
|
|
370
377
|
noOp: false,
|
|
@@ -376,13 +383,24 @@ async function executeMigrationPlanCommand(
|
|
|
376
383
|
label: op.label,
|
|
377
384
|
operationClass: op.operationClass,
|
|
378
385
|
})),
|
|
379
|
-
|
|
386
|
+
...(preview !== undefined ? { preview } : {}),
|
|
380
387
|
summary: `Planned ${plannedOps.length} operation(s)`,
|
|
381
388
|
timings: { total: Date.now() - startTime },
|
|
382
389
|
};
|
|
383
390
|
return ok(result);
|
|
384
391
|
} catch (error) {
|
|
385
|
-
|
|
392
|
+
if (CliStructuredError.is(error)) {
|
|
393
|
+
return notOk(error);
|
|
394
|
+
}
|
|
395
|
+
if (MigrationToolsError.is(error)) {
|
|
396
|
+
return notOk(mapMigrationToolsError(error));
|
|
397
|
+
}
|
|
398
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
+
return notOk(
|
|
400
|
+
errorUnexpected(message, {
|
|
401
|
+
why: `Unexpected error during migration plan: ${message}`,
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
386
404
|
}
|
|
387
405
|
}
|
|
388
406
|
|
|
@@ -489,17 +507,20 @@ function formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFla
|
|
|
489
507
|
|
|
490
508
|
lines.push('');
|
|
491
509
|
lines.push(
|
|
492
|
-
`Next: ${green_(
|
|
510
|
+
`Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,
|
|
493
511
|
);
|
|
494
512
|
|
|
495
|
-
if (result.
|
|
513
|
+
if (result.preview && result.preview.statements.length > 0) {
|
|
514
|
+
// The non-empty length is already guaranteed by the surrounding check, so
|
|
515
|
+
// a plain `every` here is equivalent to the helper in formatters/migrations.ts.
|
|
516
|
+
const allSql = result.preview.statements.every((s) => s.language === 'sql');
|
|
496
517
|
lines.push('');
|
|
497
|
-
lines.push(dim_('DDL preview'));
|
|
518
|
+
lines.push(dim_(allSql ? 'DDL preview' : 'Operation preview'));
|
|
498
519
|
lines.push('');
|
|
499
|
-
for (const statement of result.
|
|
500
|
-
const trimmed = statement.trim();
|
|
520
|
+
for (const statement of result.preview.statements) {
|
|
521
|
+
const trimmed = statement.text.trim();
|
|
501
522
|
if (!trimmed) continue;
|
|
502
|
-
const line = trimmed.endsWith(';') ? trimmed :
|
|
523
|
+
const line = statement.language === 'sql' && !trimmed.endsWith(';') ? `${trimmed};` : trimmed;
|
|
503
524
|
lines.push(line);
|
|
504
525
|
}
|
|
505
526
|
}
|
|
@@ -517,24 +538,27 @@ export type PrefixResolutionFailure =
|
|
|
517
538
|
| { reason: 'not-found' };
|
|
518
539
|
|
|
519
540
|
/**
|
|
520
|
-
* Resolve a migration
|
|
541
|
+
* Resolve a migration package by **target contract hash** (`metadata.to`)
|
|
542
|
+
* using exact match or prefix match.
|
|
521
543
|
*
|
|
544
|
+
* Note: matches `metadata.to` (the contract hash this migration produces),
|
|
545
|
+
* not `metadata.migrationHash` (the package's content-addressed identity).
|
|
522
546
|
* Tries exact match first, then prefix match (auto-prepending `sha256:` when
|
|
523
|
-
* the needle omits the scheme). Returns the matched
|
|
547
|
+
* the needle omits the scheme). Returns the matched package on success, or a
|
|
524
548
|
* discriminated failure indicating whether the prefix was ambiguous or simply
|
|
525
549
|
* not found.
|
|
526
550
|
*
|
|
527
551
|
* @internal Exported for testing only.
|
|
528
552
|
*/
|
|
529
|
-
export function resolveBundleByPrefix<T extends {
|
|
553
|
+
export function resolveBundleByPrefix<T extends { metadata: { to: string } }>(
|
|
530
554
|
bundles: readonly T[],
|
|
531
555
|
needle: string,
|
|
532
556
|
): Result<T, PrefixResolutionFailure> {
|
|
533
|
-
const exact = bundles.find((p) => p.
|
|
557
|
+
const exact = bundles.find((p) => p.metadata.to === needle);
|
|
534
558
|
if (exact) return ok(exact);
|
|
535
559
|
|
|
536
560
|
const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;
|
|
537
|
-
const candidates = bundles.filter((p) => p.
|
|
561
|
+
const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
|
|
538
562
|
|
|
539
563
|
if (candidates.length === 1) return ok(candidates[0]!);
|
|
540
564
|
if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
2
|
+
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
1
3
|
import {
|
|
4
|
+
deleteRef,
|
|
5
|
+
readRef,
|
|
2
6
|
readRefs,
|
|
3
|
-
resolveRef,
|
|
4
7
|
validateRefName,
|
|
5
8
|
validateRefValue,
|
|
6
|
-
|
|
9
|
+
writeRef,
|
|
7
10
|
} from '@prisma-next/migration-tools/refs';
|
|
8
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
9
11
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
10
12
|
import { Command } from 'commander';
|
|
11
|
-
import { resolve } from 'pathe';
|
|
12
13
|
import { loadConfig } from '../config-loader';
|
|
13
|
-
import {
|
|
14
|
-
|
|
14
|
+
import {
|
|
15
|
+
CliStructuredError,
|
|
16
|
+
errorRuntime,
|
|
17
|
+
errorUnexpected,
|
|
18
|
+
mapMigrationToolsError,
|
|
19
|
+
} from '../utils/cli-errors';
|
|
20
|
+
import {
|
|
21
|
+
addGlobalOptions,
|
|
22
|
+
resolveMigrationPaths,
|
|
23
|
+
setCommandDescriptions,
|
|
24
|
+
} from '../utils/command-helpers';
|
|
15
25
|
import { formatCommandHelp } from '../utils/formatters/help';
|
|
16
26
|
import { parseGlobalFlags } from '../utils/global-flags';
|
|
17
27
|
import { handleResult } from '../utils/result-handler';
|
|
@@ -21,12 +31,14 @@ interface RefSetResult {
|
|
|
21
31
|
readonly ok: true;
|
|
22
32
|
readonly ref: string;
|
|
23
33
|
readonly hash: string;
|
|
34
|
+
readonly invariants: readonly string[];
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
interface RefGetResult {
|
|
27
38
|
readonly ok: true;
|
|
28
39
|
readonly ref: string;
|
|
29
40
|
readonly hash: string;
|
|
41
|
+
readonly invariants: readonly string[];
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
interface RefDeleteResult {
|
|
@@ -37,21 +49,12 @@ interface RefDeleteResult {
|
|
|
37
49
|
|
|
38
50
|
interface RefListResult {
|
|
39
51
|
readonly ok: true;
|
|
40
|
-
readonly refs: Record<string,
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function resolveRefsPath(configPath?: string, config?: { migrations?: { dir?: string } }): string {
|
|
44
|
-
const base = configPath ? resolve(configPath, '..') : process.cwd();
|
|
45
|
-
return resolve(base, config?.migrations?.dir ?? 'migrations', 'refs.json');
|
|
52
|
+
readonly refs: Record<string, RefEntry>;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
function mapError(error: unknown): CliStructuredError {
|
|
49
56
|
if (MigrationToolsError.is(error)) {
|
|
50
|
-
return
|
|
51
|
-
why: error.why,
|
|
52
|
-
fix: error.fix,
|
|
53
|
-
meta: { code: error.code },
|
|
54
|
-
});
|
|
57
|
+
return mapMigrationToolsError(error);
|
|
55
58
|
}
|
|
56
59
|
return errorUnexpected(error instanceof Error ? error.message : String(error));
|
|
57
60
|
}
|
|
@@ -70,13 +73,6 @@ function cliErrorInvalidRefValue(hash: string): CliStructuredError {
|
|
|
70
73
|
});
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
function errorRefNotFound(name: string): CliStructuredError {
|
|
74
|
-
return errorRuntime(`Ref "${name}" does not exist`, {
|
|
75
|
-
why: `No ref named "${name}" found in refs.json`,
|
|
76
|
-
fix: `Run \`prisma-next migration ref list\` to see available refs, or \`prisma-next migration ref set ${name} <hash>\` to create it`,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
76
|
async function executeRefSetCommand(
|
|
81
77
|
name: string,
|
|
82
78
|
hash: string,
|
|
@@ -91,11 +87,10 @@ async function executeRefSetCommand(
|
|
|
91
87
|
|
|
92
88
|
try {
|
|
93
89
|
const config = await loadConfig(options.config);
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return ok({ ok: true as const, ref: name, hash });
|
|
90
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
91
|
+
const entry: RefEntry = { hash, invariants: [] };
|
|
92
|
+
await writeRef(refsDir, name, entry);
|
|
93
|
+
return ok({ ok: true as const, ref: name, hash, invariants: [] });
|
|
99
94
|
} catch (error) {
|
|
100
95
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
101
96
|
return notOk(mapError(error));
|
|
@@ -108,10 +103,9 @@ async function executeRefGetCommand(
|
|
|
108
103
|
): Promise<Result<RefGetResult, CliStructuredError>> {
|
|
109
104
|
try {
|
|
110
105
|
const config = await loadConfig(options.config);
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const hash
|
|
114
|
-
return ok({ ok: true as const, ref: name, hash });
|
|
106
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
107
|
+
const entry = await readRef(refsDir, name);
|
|
108
|
+
return ok({ ok: true as const, ref: name, hash: entry.hash, invariants: entry.invariants });
|
|
115
109
|
} catch (error) {
|
|
116
110
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
117
111
|
return notOk(mapError(error));
|
|
@@ -124,13 +118,8 @@ async function executeRefDeleteCommand(
|
|
|
124
118
|
): Promise<Result<RefDeleteResult, CliStructuredError>> {
|
|
125
119
|
try {
|
|
126
120
|
const config = await loadConfig(options.config);
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
if (!Object.hasOwn(refs, name)) {
|
|
130
|
-
return notOk(errorRefNotFound(name));
|
|
131
|
-
}
|
|
132
|
-
const { [name]: _, ...remaining } = refs;
|
|
133
|
-
await writeRefs(refsPath, remaining);
|
|
121
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
122
|
+
await deleteRef(refsDir, name);
|
|
134
123
|
return ok({ ok: true as const, ref: name, deleted: true as const });
|
|
135
124
|
} catch (error) {
|
|
136
125
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -143,8 +132,8 @@ async function executeRefListCommand(options: {
|
|
|
143
132
|
}): Promise<Result<RefListResult, CliStructuredError>> {
|
|
144
133
|
try {
|
|
145
134
|
const config = await loadConfig(options.config);
|
|
146
|
-
const
|
|
147
|
-
const refs = await readRefs(
|
|
135
|
+
const { refsDir } = resolveMigrationPaths(options.config, config);
|
|
136
|
+
const refs = await readRefs(refsDir);
|
|
148
137
|
return ok({ ok: true as const, refs });
|
|
149
138
|
} catch (error) {
|
|
150
139
|
if (error instanceof CliStructuredError) return notOk(error);
|
|
@@ -157,7 +146,7 @@ function createRefSetCommand(): Command {
|
|
|
157
146
|
setCommandDescriptions(
|
|
158
147
|
command,
|
|
159
148
|
'Set a ref to a contract hash',
|
|
160
|
-
'Sets a named ref to point to a contract hash in migrations/refs
|
|
149
|
+
'Sets a named ref to point to a contract hash in migrations/refs/.',
|
|
161
150
|
);
|
|
162
151
|
addGlobalOptions(command)
|
|
163
152
|
.argument('<name>', 'Ref name (e.g., staging, production)')
|
|
@@ -190,7 +179,7 @@ function createRefGetCommand(): Command {
|
|
|
190
179
|
setCommandDescriptions(
|
|
191
180
|
command,
|
|
192
181
|
'Get the hash for a ref',
|
|
193
|
-
'Reads a named ref from migrations/refs
|
|
182
|
+
'Reads a named ref from migrations/refs/ and prints its contract hash.',
|
|
194
183
|
);
|
|
195
184
|
addGlobalOptions(command)
|
|
196
185
|
.argument('<name>', 'Ref name to look up')
|
|
@@ -218,7 +207,7 @@ function createRefGetCommand(): Command {
|
|
|
218
207
|
|
|
219
208
|
function createRefDeleteCommand(): Command {
|
|
220
209
|
const command = new Command('delete');
|
|
221
|
-
setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs
|
|
210
|
+
setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');
|
|
222
211
|
addGlobalOptions(command)
|
|
223
212
|
.argument('<name>', 'Ref name to delete')
|
|
224
213
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
@@ -245,11 +234,7 @@ function createRefDeleteCommand(): Command {
|
|
|
245
234
|
|
|
246
235
|
function createRefListCommand(): Command {
|
|
247
236
|
const command = new Command('list');
|
|
248
|
-
setCommandDescriptions(
|
|
249
|
-
command,
|
|
250
|
-
'List all refs',
|
|
251
|
-
'Lists all named refs from migrations/refs.json.',
|
|
252
|
-
);
|
|
237
|
+
setCommandDescriptions(command, 'List all refs', 'Lists all named refs from migrations/refs/.');
|
|
253
238
|
addGlobalOptions(command)
|
|
254
239
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
255
240
|
.action(async (options: { config?: string; json?: string | boolean; quiet?: boolean }) => {
|
|
@@ -264,8 +249,10 @@ function createRefListCommand(): Command {
|
|
|
264
249
|
if (entries.length === 0) {
|
|
265
250
|
ui.output('No refs defined');
|
|
266
251
|
} else {
|
|
267
|
-
for (const [refName,
|
|
268
|
-
|
|
252
|
+
for (const [refName, entry] of entries) {
|
|
253
|
+
const invariantsSuffix =
|
|
254
|
+
entry.invariants.length > 0 ? ` [invariants: ${entry.invariants.join(', ')}]` : '';
|
|
255
|
+
ui.output(`${refName} → ${entry.hash}${invariantsSuffix}`);
|
|
269
256
|
}
|
|
270
257
|
}
|
|
271
258
|
}
|
|
@@ -282,7 +269,6 @@ export {
|
|
|
282
269
|
executeRefListCommand,
|
|
283
270
|
cliErrorInvalidRefName,
|
|
284
271
|
cliErrorInvalidRefValue,
|
|
285
|
-
errorRefNotFound,
|
|
286
272
|
};
|
|
287
273
|
|
|
288
274
|
export function createMigrationRefCommand(): Command {
|
|
@@ -290,7 +276,7 @@ export function createMigrationRefCommand(): Command {
|
|
|
290
276
|
setCommandDescriptions(
|
|
291
277
|
command,
|
|
292
278
|
'Manage migration refs',
|
|
293
|
-
'Manage named refs in migrations/refs
|
|
279
|
+
'Manage named refs in migrations/refs/. Refs map logical environment\n' +
|
|
294
280
|
'names (e.g., staging, production) to contract hashes.',
|
|
295
281
|
);
|
|
296
282
|
addGlobalOptions(command).configureHelp({
|