@prisma-next/cli 0.5.0-dev.4 → 0.5.0-dev.41
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 -21
- 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-C0JhVj0c.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
- package/dist/cli.mjs +123 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-TG7rbCWT.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 +2 -2
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -2
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +10 -9
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.mjs +7 -7
- package/dist/commands/db-update.mjs +9 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +9 -9
- 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 +55 -56
- 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 +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +6 -10
- 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 +27 -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 +3 -3
- package/dist/{config-loader-_W4T21X1.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-LjzCoicC.mjs +4 -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-CGW6mm-E.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
- package/dist/{contract-infer-BS4kIX9c.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 +5 -5
- package/dist/exports/index.mjs +3 -3
- 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-DfZKQBQ2.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
- package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
- package/dist/init-C7dE9KOJ.mjs +2062 -0
- package/dist/init-C7dE9KOJ.mjs.map +1 -0
- package/dist/{inspect-live-schema-BsoFVoS1.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
- package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -11
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +308 -84
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-DOXnheFa.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
- package/dist/{migration-command-scaffold-DOXnheFa.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
- package/dist/{migration-status-Ry3TnEya.mjs → migration-status-DoPrFIOQ.mjs} +114 -57
- package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
- package/dist/{migrations-fU0xoKjS.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-B-YvmcDu.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.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-BJwA7ufw.mjs → result-handler-Ch6hVnOo.mjs} +35 -93
- package/dist/result-handler-Ch6hVnOo.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
- package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
- package/dist/{verify-bl__PkXk.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-bl__PkXk.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +22 -16
- 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 +84 -63
- package/src/commands/migration-new.ts +28 -34
- package/src/commands/migration-plan.ts +80 -56
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +194 -58
- 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 +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/command-helpers.ts +45 -23
- 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-DHq6GQGu.mjs +0 -5
- package/dist/client-TG7rbCWT.mjs.map +0 -1
- package/dist/config-loader-_W4T21X1.mjs.map +0 -1
- package/dist/contract-emit-CQfj7xJn.mjs +0 -122
- package/dist/contract-emit-CQfj7xJn.mjs.map +0 -1
- package/dist/contract-emit-DpPjuFy-.mjs +0 -195
- package/dist/contract-emit-DpPjuFy-.mjs.map +0 -1
- package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
- package/dist/contract-infer-BS4kIX9c.mjs.map +0 -1
- package/dist/extract-operation-statements-DZUJNmL3.mjs +0 -13
- package/dist/extract-operation-statements-DZUJNmL3.mjs.map +0 -1
- package/dist/extract-sql-ddl-DDMX-9mz.mjs +0 -26
- package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +0 -1
- package/dist/init-CQfo_4Ro.mjs +0 -430
- package/dist/init-CQfo_4Ro.mjs.map +0 -1
- package/dist/inspect-live-schema-BsoFVoS1.mjs.map +0 -1
- package/dist/migration-status-Ry3TnEya.mjs.map +0 -1
- package/dist/migrations-fU0xoKjS.mjs.map +0 -1
- package/dist/result-handler-BJwA7ufw.mjs.map +0 -1
- package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
- package/dist/validate-contract-deps-esa-VQ0h.mjs +0 -37
- package/dist/validate-contract-deps-esa-VQ0h.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,3 +1,4 @@
|
|
|
1
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
1
2
|
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
2
3
|
import {
|
|
3
4
|
deleteRef,
|
|
@@ -7,11 +8,15 @@ import {
|
|
|
7
8
|
validateRefValue,
|
|
8
9
|
writeRef,
|
|
9
10
|
} from '@prisma-next/migration-tools/refs';
|
|
10
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
11
11
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
12
12
|
import { Command } from 'commander';
|
|
13
13
|
import { loadConfig } from '../config-loader';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
CliStructuredError,
|
|
16
|
+
errorRuntime,
|
|
17
|
+
errorUnexpected,
|
|
18
|
+
mapMigrationToolsError,
|
|
19
|
+
} from '../utils/cli-errors';
|
|
15
20
|
import {
|
|
16
21
|
addGlobalOptions,
|
|
17
22
|
resolveMigrationPaths,
|
|
@@ -49,11 +54,7 @@ interface RefListResult {
|
|
|
49
54
|
|
|
50
55
|
function mapError(error: unknown): CliStructuredError {
|
|
51
56
|
if (MigrationToolsError.is(error)) {
|
|
52
|
-
return
|
|
53
|
-
why: error.why,
|
|
54
|
-
fix: error.fix,
|
|
55
|
-
meta: { code: error.code },
|
|
56
|
-
});
|
|
57
|
+
return mapMigrationToolsError(error);
|
|
57
58
|
}
|
|
58
59
|
return errorUnexpected(error instanceof Error ? error.message : String(error));
|
|
59
60
|
}
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
MigrationPlanOperation,
|
|
3
|
+
OperationPreview,
|
|
4
|
+
} from '@prisma-next/framework-components/control';
|
|
5
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
3
6
|
import { readMigrationPackage, readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
4
|
-
import
|
|
5
|
-
|
|
7
|
+
import {
|
|
8
|
+
findLatestMigration,
|
|
9
|
+
reconstructGraph,
|
|
10
|
+
} from '@prisma-next/migration-tools/migration-graph';
|
|
11
|
+
import type { MigrationPackage } from '@prisma-next/migration-tools/package';
|
|
6
12
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
7
13
|
import { Command } from 'commander';
|
|
8
14
|
import { relative, resolve } from 'pathe';
|
|
9
15
|
import { loadConfig } from '../config-loader';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
16
|
+
import { createControlClient } from '../control-api/client';
|
|
17
|
+
import {
|
|
18
|
+
type CliStructuredError,
|
|
19
|
+
errorRuntime,
|
|
20
|
+
errorUnexpected,
|
|
21
|
+
mapMigrationToolsError,
|
|
22
|
+
} from '../utils/cli-errors';
|
|
12
23
|
import {
|
|
13
24
|
addGlobalOptions,
|
|
14
25
|
setCommandDescriptions,
|
|
@@ -29,17 +40,22 @@ export interface MigrationShowResult {
|
|
|
29
40
|
readonly ok: true;
|
|
30
41
|
readonly dirName: string;
|
|
31
42
|
readonly dirPath: string;
|
|
32
|
-
readonly from: string;
|
|
43
|
+
readonly from: string | null;
|
|
33
44
|
readonly to: string;
|
|
34
|
-
readonly
|
|
35
|
-
readonly kind: string;
|
|
45
|
+
readonly migrationHash: string;
|
|
36
46
|
readonly createdAt: string;
|
|
37
47
|
readonly operations: readonly {
|
|
38
48
|
readonly id: string;
|
|
39
49
|
readonly label: string;
|
|
40
50
|
readonly operationClass: string;
|
|
41
51
|
}[];
|
|
42
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Family-agnostic textual preview of the migration's operations. Replaces
|
|
54
|
+
* the previous string-array DDL field. Always defined; statements is empty
|
|
55
|
+
* for a no-op migration or a family that does not implement the
|
|
56
|
+
* `OperationPreviewCapable` capability.
|
|
57
|
+
*/
|
|
58
|
+
readonly preview: OperationPreview;
|
|
43
59
|
readonly summary: string;
|
|
44
60
|
}
|
|
45
61
|
|
|
@@ -48,11 +64,11 @@ function looksLikePath(target: string): boolean {
|
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
export function resolveByHashPrefix(
|
|
51
|
-
packages: readonly
|
|
67
|
+
packages: readonly MigrationPackage[],
|
|
52
68
|
prefix: string,
|
|
53
|
-
): Result<
|
|
69
|
+
): Result<MigrationPackage, CliStructuredError> {
|
|
54
70
|
const normalizedPrefix = prefix.startsWith('sha256:') ? prefix : `sha256:${prefix}`;
|
|
55
|
-
const matches = packages.filter((p) => p.
|
|
71
|
+
const matches = packages.filter((p) => p.metadata.migrationHash.startsWith(normalizedPrefix));
|
|
56
72
|
|
|
57
73
|
if (matches.length === 1) {
|
|
58
74
|
return ok(matches[0]!);
|
|
@@ -61,13 +77,13 @@ export function resolveByHashPrefix(
|
|
|
61
77
|
if (matches.length === 0) {
|
|
62
78
|
return notOk(
|
|
63
79
|
errorRuntime('No migration found matching prefix', {
|
|
64
|
-
why: `No migration has a
|
|
80
|
+
why: `No migration has a migrationHash starting with "${normalizedPrefix}"`,
|
|
65
81
|
fix: 'Run `prisma-next migration show` (no argument) to see the latest migration, or check the migrations directory for available packages.',
|
|
66
82
|
}),
|
|
67
83
|
);
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
const candidates = matches.map((p) => ` ${p.dirName} ${p.
|
|
86
|
+
const candidates = matches.map((p) => ` ${p.dirName} ${p.metadata.migrationHash}`).join('\n');
|
|
71
87
|
return notOk(
|
|
72
88
|
errorRuntime('Ambiguous hash prefix', {
|
|
73
89
|
why: `Multiple migrations match prefix "${normalizedPrefix}":\n${candidates}`,
|
|
@@ -110,7 +126,7 @@ async function executeMigrationShowCommand(
|
|
|
110
126
|
ui.stderr(header);
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
let pkg:
|
|
129
|
+
let pkg: MigrationPackage;
|
|
114
130
|
|
|
115
131
|
try {
|
|
116
132
|
if (target && looksLikePath(target)) {
|
|
@@ -142,7 +158,7 @@ async function executeMigrationShowCommand(
|
|
|
142
158
|
);
|
|
143
159
|
}
|
|
144
160
|
const leafPkg = allPackages.find(
|
|
145
|
-
(p) => p.
|
|
161
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
146
162
|
);
|
|
147
163
|
if (!leafPkg) {
|
|
148
164
|
return notOk(
|
|
@@ -157,13 +173,7 @@ async function executeMigrationShowCommand(
|
|
|
157
173
|
}
|
|
158
174
|
} catch (error) {
|
|
159
175
|
if (MigrationToolsError.is(error)) {
|
|
160
|
-
return notOk(
|
|
161
|
-
errorRuntime(error.message, {
|
|
162
|
-
why: error.why,
|
|
163
|
-
fix: error.fix,
|
|
164
|
-
meta: { code: error.code, ...(error.details ?? {}) },
|
|
165
|
-
}),
|
|
166
|
-
);
|
|
176
|
+
return notOk(mapMigrationToolsError(error));
|
|
167
177
|
}
|
|
168
178
|
return notOk(
|
|
169
179
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
@@ -173,23 +183,33 @@ async function executeMigrationShowCommand(
|
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
const ops = pkg.ops as readonly MigrationPlanOperation[];
|
|
176
|
-
|
|
186
|
+
|
|
187
|
+
// `migration show` is an offline command; the control client is constructed
|
|
188
|
+
// purely to dispatch the family-specific `toOperationPreview` capability and
|
|
189
|
+
// is not connected to a database.
|
|
190
|
+
const client = createControlClient({
|
|
191
|
+
family: config.family,
|
|
192
|
+
target: config.target,
|
|
193
|
+
adapter: config.adapter,
|
|
194
|
+
...(config.driver ? { driver: config.driver } : {}),
|
|
195
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
196
|
+
});
|
|
197
|
+
const preview: OperationPreview = client.toOperationPreview(ops) ?? { statements: [] };
|
|
177
198
|
|
|
178
199
|
const result: MigrationShowResult = {
|
|
179
200
|
ok: true,
|
|
180
201
|
dirName: pkg.dirName,
|
|
181
202
|
dirPath: relative(process.cwd(), pkg.dirPath),
|
|
182
|
-
from: pkg.
|
|
183
|
-
to: pkg.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
createdAt: pkg.manifest.createdAt,
|
|
203
|
+
from: pkg.metadata.from,
|
|
204
|
+
to: pkg.metadata.to,
|
|
205
|
+
migrationHash: pkg.metadata.migrationHash,
|
|
206
|
+
createdAt: pkg.metadata.createdAt,
|
|
187
207
|
operations: ops.map((op) => ({
|
|
188
208
|
id: op.id,
|
|
189
209
|
label: op.label,
|
|
190
210
|
operationClass: op.operationClass,
|
|
191
211
|
})),
|
|
192
|
-
|
|
212
|
+
preview,
|
|
193
213
|
summary: `${ops.length} operation(s)`,
|
|
194
214
|
};
|
|
195
215
|
return ok(result);
|
|
@@ -200,7 +220,7 @@ export function createMigrationShowCommand(): Command {
|
|
|
200
220
|
setCommandDescriptions(
|
|
201
221
|
command,
|
|
202
222
|
'Display migration package contents',
|
|
203
|
-
'Shows the operations,
|
|
223
|
+
'Shows the operations, statement preview, and metadata for a migration package.\n' +
|
|
204
224
|
'Accepts a directory path, a hash prefix (git-style), or defaults to the\n' +
|
|
205
225
|
'latest migration.',
|
|
206
226
|
);
|
|
@@ -209,10 +229,7 @@ export function createMigrationShowCommand(): Command {
|
|
|
209
229
|
'prisma-next migration show sha256:a1b2c3',
|
|
210
230
|
]);
|
|
211
231
|
addGlobalOptions(command)
|
|
212
|
-
.argument(
|
|
213
|
-
'[target]',
|
|
214
|
-
'Migration directory path or migrationId hash prefix (defaults to latest)',
|
|
215
|
-
)
|
|
232
|
+
.argument('[target]', 'Migration directory path or migrationHash prefix (defaults to latest)')
|
|
216
233
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
217
234
|
.action(async (target: string | undefined, options: MigrationShowOptions) => {
|
|
218
235
|
const flags = parseGlobalFlags(options);
|