@prisma-next/cli 0.4.2 → 0.5.0-dev.10
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 +17 -18
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/{cli-errors-BFYgBH3L.d.mts → cli-errors-D2NPMaxW.d.mts} +1 -0
- package/dist/cli.mjs +11 -11
- package/dist/{client-CrsnY58k.mjs → client-faKQqcix.mjs} +17 -2
- package/dist/client-faKQqcix.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +5 -5
- package/dist/commands/contract-infer.mjs +6 -6
- package/dist/commands/db-init.mjs +6 -6
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.mjs +6 -6
- package/dist/commands/db-verify.mjs +4 -4
- 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 +10 -34
- 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 +18 -23
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +6 -3
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +27 -32
- 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 +4 -8
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +4 -4
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +15 -22
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +5 -4
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +5 -5
- package/dist/{contract-emit-NJ01hiiv.mjs → contract-emit-B5wnhTuF.mjs} +4 -4
- package/dist/{contract-emit-NJ01hiiv.mjs.map → contract-emit-B5wnhTuF.mjs.map} +1 -1
- package/dist/contract-emit-B9wkchud.mjs +6 -0
- package/dist/{contract-emit-V5SSitUT.mjs → contract-emit-PeB96eHy.mjs} +3 -3
- package/dist/{contract-emit-V5SSitUT.mjs.map → contract-emit-PeB96eHy.mjs.map} +1 -1
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-DnY9fUw0.mjs} +3 -3
- package/dist/{contract-infer-D9cC3rJm.mjs.map → contract-infer-DnY9fUw0.mjs.map} +1 -1
- package/dist/exports/control-api.mjs +4 -4
- package/dist/exports/index.mjs +5 -5
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-C6el-5x_.mjs} +2 -2
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-C6el-5x_.mjs.map} +1 -1
- package/dist/{init-m8x0UoPY.mjs → init-jf33mNQ6.mjs} +3 -3
- package/dist/{init-m8x0UoPY.mjs.map → init-jf33mNQ6.mjs.map} +1 -1
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-CqoZhKC1.mjs} +4 -4
- package/dist/{inspect-live-schema-yrHAvG71.mjs.map → inspect-live-schema-CqoZhKC1.mjs.map} +1 -1
- package/dist/migration-cli.mjs +13 -6
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-B40VaF-m.mjs} +4 -4
- package/dist/{migration-command-scaffold-B3B09et6.mjs.map → migration-command-scaffold-B40VaF-m.mjs.map} +1 -1
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CDgFxhAo.mjs} +10 -22
- package/dist/migration-status-CDgFxhAo.mjs.map +1 -0
- package/dist/{migrations-Bo5WtTla.mjs → migrations-CKRMAKka.mjs} +2 -2
- package/dist/migrations-CKRMAKka.mjs.map +1 -0
- package/dist/{result-handler-Ba3zWQsI.mjs → result-handler-DcV0QoTr.mjs} +6 -14
- package/dist/{result-handler-Ba3zWQsI.mjs.map → result-handler-DcV0QoTr.mjs.map} +1 -1
- package/package.json +14 -14
- package/src/commands/migration-apply.ts +11 -46
- package/src/commands/migration-new.ts +19 -27
- package/src/commands/migration-plan.ts +52 -41
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +23 -27
- package/src/commands/migration-status.ts +20 -32
- package/src/control-api/operations/migration-apply.ts +15 -0
- package/src/migration-cli.ts +16 -9
- package/src/utils/cli-errors.ts +45 -1
- package/src/utils/command-helpers.ts +8 -21
- package/src/utils/formatters/graph-migration-mapper.ts +1 -1
- package/src/utils/formatters/migrations.ts +2 -2
- package/dist/cli-errors-Cd79vmTH.mjs +0 -5
- package/dist/client-CrsnY58k.mjs.map +0 -1
- package/dist/contract-emit-DxgyXrqV.mjs +0 -6
- package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
- package/dist/migrations-Bo5WtTla.mjs.map +0 -1
|
@@ -5,16 +5,17 @@ import {
|
|
|
5
5
|
createControlStack,
|
|
6
6
|
type MigrationPlanOperation,
|
|
7
7
|
} from '@prisma-next/framework-components/control';
|
|
8
|
-
import { computeMigrationId } from '@prisma-next/migration-tools/attestation';
|
|
9
8
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
10
9
|
import { findLatestMigration } from '@prisma-next/migration-tools/dag';
|
|
10
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
11
|
+
import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
11
12
|
import {
|
|
12
13
|
copyFilesWithRename,
|
|
13
14
|
formatMigrationDirName,
|
|
14
15
|
writeMigrationPackage,
|
|
15
16
|
} from '@prisma-next/migration-tools/io';
|
|
17
|
+
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
16
18
|
import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
|
|
17
|
-
import { type MigrationManifest, MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
18
19
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
19
20
|
import { Command } from 'commander';
|
|
20
21
|
import { join, relative } from 'pathe';
|
|
@@ -29,11 +30,12 @@ import {
|
|
|
29
30
|
errorRuntime,
|
|
30
31
|
errorTargetMigrationNotSupported,
|
|
31
32
|
errorUnexpected,
|
|
33
|
+
mapMigrationToolsError,
|
|
32
34
|
} from '../utils/cli-errors';
|
|
33
35
|
import {
|
|
34
36
|
addGlobalOptions,
|
|
35
37
|
getTargetMigrations,
|
|
36
|
-
|
|
38
|
+
loadMigrationPackages,
|
|
37
39
|
resolveContractPath,
|
|
38
40
|
resolveMigrationPaths,
|
|
39
41
|
setCommandDescriptions,
|
|
@@ -76,22 +78,6 @@ export interface MigrationPlanResult {
|
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
|
|
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
81
|
async function executeMigrationPlanCommand(
|
|
96
82
|
options: MigrationPlanOptions,
|
|
97
83
|
flags: GlobalFlags,
|
|
@@ -177,7 +163,7 @@ async function executeMigrationPlanCommand(
|
|
|
177
163
|
let fromContractSourceDir: string | null = null;
|
|
178
164
|
|
|
179
165
|
try {
|
|
180
|
-
const { bundles, graph } = await
|
|
166
|
+
const { bundles, graph } = await loadMigrationPackages(migrationsDir);
|
|
181
167
|
|
|
182
168
|
if (options.from) {
|
|
183
169
|
const resolved = resolveBundleByPrefix(bundles, options.from);
|
|
@@ -195,16 +181,18 @@ async function executeMigrationPlanCommand(
|
|
|
195
181
|
}),
|
|
196
182
|
);
|
|
197
183
|
}
|
|
198
|
-
fromHash = resolved.value.
|
|
199
|
-
fromContract = resolved.value.
|
|
184
|
+
fromHash = resolved.value.metadata.to;
|
|
185
|
+
fromContract = resolved.value.metadata.toContract;
|
|
200
186
|
fromContractSourceDir = resolved.value.dirPath;
|
|
201
187
|
} else {
|
|
202
188
|
const latestMigration = findLatestMigration(graph);
|
|
203
189
|
if (latestMigration) {
|
|
204
190
|
fromHash = latestMigration.to;
|
|
205
|
-
const leafPkg = bundles.find(
|
|
191
|
+
const leafPkg = bundles.find(
|
|
192
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
193
|
+
);
|
|
206
194
|
if (leafPkg) {
|
|
207
|
-
fromContract = leafPkg.
|
|
195
|
+
fromContract = leafPkg.metadata.toContract;
|
|
208
196
|
fromContractSourceDir = leafPkg.dirPath;
|
|
209
197
|
}
|
|
210
198
|
}
|
|
@@ -213,7 +201,16 @@ async function executeMigrationPlanCommand(
|
|
|
213
201
|
if (MigrationToolsError.is(error)) {
|
|
214
202
|
return notOk(mapMigrationToolsError(error));
|
|
215
203
|
}
|
|
216
|
-
|
|
204
|
+
// Wrap unexpected (non-MigrationToolsError) failures from the migration
|
|
205
|
+
// load phase in a structured CLI envelope. Letting them throw would
|
|
206
|
+
// bypass `handleResult()` and crash the command — see CLI structured-
|
|
207
|
+
// errors guideline (CliStructuredError + Result pattern).
|
|
208
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
209
|
+
return notOk(
|
|
210
|
+
errorUnexpected(message, {
|
|
211
|
+
why: `Unexpected error while loading migrations: ${message}`,
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
217
214
|
}
|
|
218
215
|
|
|
219
216
|
// Check for no-op (same hash means no changes)
|
|
@@ -251,7 +248,7 @@ async function executeMigrationPlanCommand(
|
|
|
251
248
|
const dirName = formatMigrationDirName(timestamp, slug);
|
|
252
249
|
const packageDir = join(migrationsDir, dirName);
|
|
253
250
|
|
|
254
|
-
const
|
|
251
|
+
const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {
|
|
255
252
|
from: fromHash,
|
|
256
253
|
to: toStorageHash,
|
|
257
254
|
kind: 'regular',
|
|
@@ -320,18 +317,18 @@ async function executeMigrationPlanCommand(
|
|
|
320
317
|
|
|
321
318
|
const migrationTsContent = plannerResult.plan.renderTypeScript();
|
|
322
319
|
|
|
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
|
|
320
|
+
// Always-attest: compute migrationHash over (metadata, ops). When
|
|
321
|
+
// placeholders blocked lowering, ops is `[]` and the hash is computed
|
|
322
|
+
// over the empty list — re-emitting after the user fills the placeholder
|
|
323
|
+
// produces a different hash (over the real ops). This is intentional;
|
|
327
324
|
// there is no on-disk "draft" state.
|
|
328
325
|
const opsForWrite = hasPlaceholders ? [] : plannedOps;
|
|
329
|
-
const
|
|
330
|
-
...
|
|
331
|
-
|
|
326
|
+
const metadata: MigrationMetadata = {
|
|
327
|
+
...baseMetadata,
|
|
328
|
+
migrationHash: computeMigrationHash(baseMetadata, opsForWrite),
|
|
332
329
|
};
|
|
333
330
|
|
|
334
|
-
await writeMigrationPackage(packageDir,
|
|
331
|
+
await writeMigrationPackage(packageDir, metadata, opsForWrite);
|
|
335
332
|
const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
|
|
336
333
|
await copyFilesWithRename(packageDir, [
|
|
337
334
|
{ sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
|
|
@@ -382,7 +379,18 @@ async function executeMigrationPlanCommand(
|
|
|
382
379
|
};
|
|
383
380
|
return ok(result);
|
|
384
381
|
} catch (error) {
|
|
385
|
-
|
|
382
|
+
if (CliStructuredError.is(error)) {
|
|
383
|
+
return notOk(error);
|
|
384
|
+
}
|
|
385
|
+
if (MigrationToolsError.is(error)) {
|
|
386
|
+
return notOk(mapMigrationToolsError(error));
|
|
387
|
+
}
|
|
388
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
389
|
+
return notOk(
|
|
390
|
+
errorUnexpected(message, {
|
|
391
|
+
why: `Unexpected error during migration plan: ${message}`,
|
|
392
|
+
}),
|
|
393
|
+
);
|
|
386
394
|
}
|
|
387
395
|
}
|
|
388
396
|
|
|
@@ -489,7 +497,7 @@ function formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFla
|
|
|
489
497
|
|
|
490
498
|
lines.push('');
|
|
491
499
|
lines.push(
|
|
492
|
-
`Next: ${green_(
|
|
500
|
+
`Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,
|
|
493
501
|
);
|
|
494
502
|
|
|
495
503
|
if (result.sql && result.sql.length > 0) {
|
|
@@ -517,24 +525,27 @@ export type PrefixResolutionFailure =
|
|
|
517
525
|
| { reason: 'not-found' };
|
|
518
526
|
|
|
519
527
|
/**
|
|
520
|
-
* Resolve a migration
|
|
528
|
+
* Resolve a migration package by **target contract hash** (`metadata.to`)
|
|
529
|
+
* using exact match or prefix match.
|
|
521
530
|
*
|
|
531
|
+
* Note: matches `metadata.to` (the contract hash this migration produces),
|
|
532
|
+
* not `metadata.migrationHash` (the package's content-addressed identity).
|
|
522
533
|
* Tries exact match first, then prefix match (auto-prepending `sha256:` when
|
|
523
|
-
* the needle omits the scheme). Returns the matched
|
|
534
|
+
* the needle omits the scheme). Returns the matched package on success, or a
|
|
524
535
|
* discriminated failure indicating whether the prefix was ambiguous or simply
|
|
525
536
|
* not found.
|
|
526
537
|
*
|
|
527
538
|
* @internal Exported for testing only.
|
|
528
539
|
*/
|
|
529
|
-
export function resolveBundleByPrefix<T extends {
|
|
540
|
+
export function resolveBundleByPrefix<T extends { metadata: { to: string } }>(
|
|
530
541
|
bundles: readonly T[],
|
|
531
542
|
needle: string,
|
|
532
543
|
): Result<T, PrefixResolutionFailure> {
|
|
533
|
-
const exact = bundles.find((p) => p.
|
|
544
|
+
const exact = bundles.find((p) => p.metadata.to === needle);
|
|
534
545
|
if (exact) return ok(exact);
|
|
535
546
|
|
|
536
547
|
const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;
|
|
537
|
-
const candidates = bundles.filter((p) => p.
|
|
548
|
+
const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
|
|
538
549
|
|
|
539
550
|
if (candidates.length === 1) return ok(candidates[0]!);
|
|
540
551
|
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,19 @@
|
|
|
1
1
|
import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
|
|
2
2
|
import { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';
|
|
3
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
3
4
|
import { readMigrationPackage, readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
4
|
-
import type {
|
|
5
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
5
|
+
import type { MigrationPackage } from '@prisma-next/migration-tools/package';
|
|
6
6
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import { relative, resolve } from 'pathe';
|
|
9
9
|
import { loadConfig } from '../config-loader';
|
|
10
10
|
import { extractOperationStatements } from '../control-api/operations/extract-operation-statements';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type CliStructuredError,
|
|
13
|
+
errorRuntime,
|
|
14
|
+
errorUnexpected,
|
|
15
|
+
mapMigrationToolsError,
|
|
16
|
+
} from '../utils/cli-errors';
|
|
12
17
|
import {
|
|
13
18
|
addGlobalOptions,
|
|
14
19
|
setCommandDescriptions,
|
|
@@ -31,7 +36,7 @@ export interface MigrationShowResult {
|
|
|
31
36
|
readonly dirPath: string;
|
|
32
37
|
readonly from: string;
|
|
33
38
|
readonly to: string;
|
|
34
|
-
readonly
|
|
39
|
+
readonly migrationHash: string;
|
|
35
40
|
readonly kind: string;
|
|
36
41
|
readonly createdAt: string;
|
|
37
42
|
readonly operations: readonly {
|
|
@@ -48,11 +53,11 @@ function looksLikePath(target: string): boolean {
|
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
export function resolveByHashPrefix(
|
|
51
|
-
packages: readonly
|
|
56
|
+
packages: readonly MigrationPackage[],
|
|
52
57
|
prefix: string,
|
|
53
|
-
): Result<
|
|
58
|
+
): Result<MigrationPackage, CliStructuredError> {
|
|
54
59
|
const normalizedPrefix = prefix.startsWith('sha256:') ? prefix : `sha256:${prefix}`;
|
|
55
|
-
const matches = packages.filter((p) => p.
|
|
60
|
+
const matches = packages.filter((p) => p.metadata.migrationHash.startsWith(normalizedPrefix));
|
|
56
61
|
|
|
57
62
|
if (matches.length === 1) {
|
|
58
63
|
return ok(matches[0]!);
|
|
@@ -61,13 +66,13 @@ export function resolveByHashPrefix(
|
|
|
61
66
|
if (matches.length === 0) {
|
|
62
67
|
return notOk(
|
|
63
68
|
errorRuntime('No migration found matching prefix', {
|
|
64
|
-
why: `No migration has a
|
|
69
|
+
why: `No migration has a migrationHash starting with "${normalizedPrefix}"`,
|
|
65
70
|
fix: 'Run `prisma-next migration show` (no argument) to see the latest migration, or check the migrations directory for available packages.',
|
|
66
71
|
}),
|
|
67
72
|
);
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
const candidates = matches.map((p) => ` ${p.dirName} ${p.
|
|
75
|
+
const candidates = matches.map((p) => ` ${p.dirName} ${p.metadata.migrationHash}`).join('\n');
|
|
71
76
|
return notOk(
|
|
72
77
|
errorRuntime('Ambiguous hash prefix', {
|
|
73
78
|
why: `Multiple migrations match prefix "${normalizedPrefix}":\n${candidates}`,
|
|
@@ -110,7 +115,7 @@ async function executeMigrationShowCommand(
|
|
|
110
115
|
ui.stderr(header);
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
let pkg:
|
|
118
|
+
let pkg: MigrationPackage;
|
|
114
119
|
|
|
115
120
|
try {
|
|
116
121
|
if (target && looksLikePath(target)) {
|
|
@@ -142,7 +147,7 @@ async function executeMigrationShowCommand(
|
|
|
142
147
|
);
|
|
143
148
|
}
|
|
144
149
|
const leafPkg = allPackages.find(
|
|
145
|
-
(p) => p.
|
|
150
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
146
151
|
);
|
|
147
152
|
if (!leafPkg) {
|
|
148
153
|
return notOk(
|
|
@@ -157,13 +162,7 @@ async function executeMigrationShowCommand(
|
|
|
157
162
|
}
|
|
158
163
|
} catch (error) {
|
|
159
164
|
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
|
-
);
|
|
165
|
+
return notOk(mapMigrationToolsError(error));
|
|
167
166
|
}
|
|
168
167
|
return notOk(
|
|
169
168
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
@@ -179,11 +178,11 @@ async function executeMigrationShowCommand(
|
|
|
179
178
|
ok: true,
|
|
180
179
|
dirName: pkg.dirName,
|
|
181
180
|
dirPath: relative(process.cwd(), pkg.dirPath),
|
|
182
|
-
from: pkg.
|
|
183
|
-
to: pkg.
|
|
184
|
-
|
|
185
|
-
kind: pkg.
|
|
186
|
-
createdAt: pkg.
|
|
181
|
+
from: pkg.metadata.from,
|
|
182
|
+
to: pkg.metadata.to,
|
|
183
|
+
migrationHash: pkg.metadata.migrationHash,
|
|
184
|
+
kind: pkg.metadata.kind,
|
|
185
|
+
createdAt: pkg.metadata.createdAt,
|
|
187
186
|
operations: ops.map((op) => ({
|
|
188
187
|
id: op.id,
|
|
189
188
|
label: op.label,
|
|
@@ -209,10 +208,7 @@ export function createMigrationShowCommand(): Command {
|
|
|
209
208
|
'prisma-next migration show sha256:a1b2c3',
|
|
210
209
|
]);
|
|
211
210
|
addGlobalOptions(command)
|
|
212
|
-
.argument(
|
|
213
|
-
'[target]',
|
|
214
|
-
'Migration directory path or migrationId hash prefix (defaults to latest)',
|
|
215
|
-
)
|
|
211
|
+
.argument('[target]', 'Migration directory path or migrationHash prefix (defaults to latest)')
|
|
216
212
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
217
213
|
.action(async (target: string | undefined, options: MigrationShowOptions) => {
|
|
218
214
|
const flags = parseGlobalFlags(options);
|
|
@@ -5,14 +5,11 @@ import {
|
|
|
5
5
|
findPathWithDecision,
|
|
6
6
|
findReachableLeaves,
|
|
7
7
|
} from '@prisma-next/migration-tools/dag';
|
|
8
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
9
|
+
import type { MigrationChainEntry, MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
10
|
+
import type { MigrationPackage } from '@prisma-next/migration-tools/package';
|
|
8
11
|
import type { Refs } from '@prisma-next/migration-tools/refs';
|
|
9
12
|
import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
|
|
10
|
-
import type {
|
|
11
|
-
MigrationBundle,
|
|
12
|
-
MigrationChainEntry,
|
|
13
|
-
MigrationGraph,
|
|
14
|
-
} from '@prisma-next/migration-tools/types';
|
|
15
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
16
13
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
17
14
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
18
15
|
import { cyan, dim, magenta, yellow } from 'colorette';
|
|
@@ -20,10 +17,15 @@ import { Command } from 'commander';
|
|
|
20
17
|
|
|
21
18
|
import { loadConfig } from '../config-loader';
|
|
22
19
|
import { createControlClient } from '../control-api/client';
|
|
23
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
type CliStructuredError,
|
|
22
|
+
errorRuntime,
|
|
23
|
+
errorUnexpected,
|
|
24
|
+
mapMigrationToolsError,
|
|
25
|
+
} from '../utils/cli-errors';
|
|
24
26
|
import {
|
|
25
27
|
addGlobalOptions,
|
|
26
|
-
|
|
28
|
+
loadMigrationPackages,
|
|
27
29
|
maskConnectionUrl,
|
|
28
30
|
readContractEnvelope,
|
|
29
31
|
resolveMigrationPaths,
|
|
@@ -61,7 +63,7 @@ export interface MigrationStatusEntry {
|
|
|
61
63
|
readonly dirName: string;
|
|
62
64
|
readonly from: string;
|
|
63
65
|
readonly to: string;
|
|
64
|
-
readonly
|
|
66
|
+
readonly migrationHash: string;
|
|
65
67
|
readonly operationCount: number;
|
|
66
68
|
readonly operationSummary: string;
|
|
67
69
|
readonly hasDestructive: boolean;
|
|
@@ -86,7 +88,7 @@ export interface MigrationStatusResult {
|
|
|
86
88
|
readonly refName?: string;
|
|
87
89
|
readonly selectedPath: readonly {
|
|
88
90
|
readonly dirName: string;
|
|
89
|
-
readonly
|
|
91
|
+
readonly migrationHash: string;
|
|
90
92
|
readonly from: string;
|
|
91
93
|
readonly to: string;
|
|
92
94
|
}[];
|
|
@@ -94,7 +96,7 @@ export interface MigrationStatusResult {
|
|
|
94
96
|
readonly summary: string;
|
|
95
97
|
readonly diagnostics: readonly StatusDiagnostic[];
|
|
96
98
|
readonly graph?: MigrationGraph;
|
|
97
|
-
readonly bundles?: readonly
|
|
99
|
+
readonly bundles?: readonly MigrationPackage[];
|
|
98
100
|
readonly edgeStatuses?: readonly EdgeStatus[];
|
|
99
101
|
readonly activeRefHash?: string;
|
|
100
102
|
readonly activeRefName?: string;
|
|
@@ -225,7 +227,7 @@ export function deriveEdgeStatuses(
|
|
|
225
227
|
*/
|
|
226
228
|
function buildMigrationEntries(
|
|
227
229
|
chain: readonly MigrationChainEntry[],
|
|
228
|
-
packages: readonly
|
|
230
|
+
packages: readonly MigrationPackage[],
|
|
229
231
|
mode: 'online' | 'offline',
|
|
230
232
|
markerHash: string | undefined,
|
|
231
233
|
edgeStatuses?: readonly EdgeStatus[],
|
|
@@ -261,7 +263,7 @@ function buildMigrationEntries(
|
|
|
261
263
|
dirName: migration.dirName,
|
|
262
264
|
from: migration.from,
|
|
263
265
|
to: migration.to,
|
|
264
|
-
|
|
266
|
+
migrationHash: migration.migrationHash,
|
|
265
267
|
operationCount: ops.length,
|
|
266
268
|
operationSummary: summary,
|
|
267
269
|
hasDestructive,
|
|
@@ -360,13 +362,7 @@ async function executeMigrationStatusCommand(
|
|
|
360
362
|
allRefs = await readRefs(refsDir);
|
|
361
363
|
} catch (error) {
|
|
362
364
|
if (MigrationToolsError.is(error)) {
|
|
363
|
-
return notOk(
|
|
364
|
-
errorRuntime(error.message, {
|
|
365
|
-
why: error.why,
|
|
366
|
-
fix: error.fix,
|
|
367
|
-
meta: { code: error.code },
|
|
368
|
-
}),
|
|
369
|
-
);
|
|
365
|
+
return notOk(mapMigrationToolsError(error));
|
|
370
366
|
}
|
|
371
367
|
throw error;
|
|
372
368
|
}
|
|
@@ -377,13 +373,7 @@ async function executeMigrationStatusCommand(
|
|
|
377
373
|
activeRefHash = resolveRef(allRefs, activeRefName).hash;
|
|
378
374
|
} catch (error) {
|
|
379
375
|
if (MigrationToolsError.is(error)) {
|
|
380
|
-
return notOk(
|
|
381
|
-
errorRuntime(error.message, {
|
|
382
|
-
why: error.why,
|
|
383
|
-
fix: error.fix,
|
|
384
|
-
meta: { code: error.code },
|
|
385
|
-
}),
|
|
386
|
-
);
|
|
376
|
+
return notOk(mapMigrationToolsError(error));
|
|
387
377
|
}
|
|
388
378
|
throw error;
|
|
389
379
|
}
|
|
@@ -429,15 +419,13 @@ async function executeMigrationStatusCommand(
|
|
|
429
419
|
});
|
|
430
420
|
}
|
|
431
421
|
|
|
432
|
-
let bundles: readonly
|
|
422
|
+
let bundles: readonly MigrationPackage[];
|
|
433
423
|
let graph: MigrationGraph;
|
|
434
424
|
try {
|
|
435
|
-
({ bundles, graph } = await
|
|
425
|
+
({ bundles, graph } = await loadMigrationPackages(migrationsDir));
|
|
436
426
|
} catch (error) {
|
|
437
427
|
if (MigrationToolsError.is(error)) {
|
|
438
|
-
return notOk(
|
|
439
|
-
errorRuntime(error.message, { why: error.why, fix: error.fix, meta: { code: error.code } }),
|
|
440
|
-
);
|
|
428
|
+
return notOk(mapMigrationToolsError(error));
|
|
441
429
|
}
|
|
442
430
|
return notOk(
|
|
443
431
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
@@ -30,6 +30,21 @@ export interface ExecuteMigrationApplyOptions<TFamilyId extends string, TTargetI
|
|
|
30
30
|
readonly onProgress?: OnControlProgress;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Apply a sequence of migration packages against the configured driver.
|
|
35
|
+
*
|
|
36
|
+
* Validates the path's continuity (origin → ... → destination, no gaps),
|
|
37
|
+
* then drives the family/target's migration runner over each package's
|
|
38
|
+
* operations in order, surfacing per-migration progress through `onProgress`.
|
|
39
|
+
*
|
|
40
|
+
* The `pendingMigrations` parameter is trusted input. Callers are responsible
|
|
41
|
+
* for upstream verification of the originating migration packages — typically
|
|
42
|
+
* by loading them via `readMigrationPackage` from
|
|
43
|
+
* `@prisma-next/migration-tools/io`, which performs hash-integrity checks at
|
|
44
|
+
* the load boundary. This operation does not re-verify the packages and
|
|
45
|
+
* assumes the `(metadata, ops)` pairs on disk have not been tampered with
|
|
46
|
+
* since emit.
|
|
47
|
+
*/
|
|
33
48
|
export async function executeMigrationApply<TFamilyId extends string, TTargetId extends string>(
|
|
34
49
|
options: ExecuteMigrationApplyOptions<TFamilyId, TTargetId>,
|
|
35
50
|
): Promise<MigrationApplyResult> {
|
package/src/migration-cli.ts
CHANGED
|
@@ -41,13 +41,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
41
41
|
import { CliStructuredError, errorMigrationCliInvalidConfigArg } from '@prisma-next/errors/control';
|
|
42
42
|
import { errorMigrationTargetMismatch } from '@prisma-next/errors/migration';
|
|
43
43
|
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
44
|
+
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
44
45
|
import {
|
|
45
46
|
buildMigrationArtifacts,
|
|
46
47
|
isDirectEntrypoint,
|
|
47
48
|
type Migration,
|
|
48
49
|
printMigrationHelp,
|
|
49
50
|
} from '@prisma-next/migration-tools/migration';
|
|
50
|
-
import type { MigrationManifest } from '@prisma-next/migration-tools/types';
|
|
51
51
|
import { dirname, join } from 'pathe';
|
|
52
52
|
import { loadConfig } from './config-loader';
|
|
53
53
|
|
|
@@ -202,16 +202,23 @@ export class MigrationCLI {
|
|
|
202
202
|
* `buildMigrationArtifacts` so the pure builder can preserve fields owned
|
|
203
203
|
* by `migration plan` (contract bookends, hints, labels, `createdAt`)
|
|
204
204
|
* across re-emits.
|
|
205
|
+
*
|
|
206
|
+
* Author-time path: this loader is non-verifying by design. Hash mismatch
|
|
207
|
+
* is the *expected* outcome of a re-author (the developer's source
|
|
208
|
+
* changes invalidate the prior hash by construction), and verification
|
|
209
|
+
* here would block legitimate regenerations. Apply-time consumers always
|
|
210
|
+
* route through the verifying `readMigrationPackage` in
|
|
211
|
+
* `@prisma-next/migration-tools/io` instead.
|
|
205
212
|
*/
|
|
206
|
-
function
|
|
213
|
+
function readExistingMetadata(metadataPath: string): Partial<MigrationMetadata> | null {
|
|
207
214
|
let raw: string;
|
|
208
215
|
try {
|
|
209
|
-
raw = readFileSync(
|
|
216
|
+
raw = readFileSync(metadataPath, 'utf-8');
|
|
210
217
|
} catch {
|
|
211
218
|
return null;
|
|
212
219
|
}
|
|
213
220
|
try {
|
|
214
|
-
return JSON.parse(raw) as Partial<
|
|
221
|
+
return JSON.parse(raw) as Partial<MigrationMetadata>;
|
|
215
222
|
} catch {
|
|
216
223
|
return null;
|
|
217
224
|
}
|
|
@@ -236,19 +243,19 @@ function serializeMigrationToDisk(
|
|
|
236
243
|
migrationDir: string,
|
|
237
244
|
dryRun: boolean,
|
|
238
245
|
): void {
|
|
239
|
-
const
|
|
240
|
-
const existing =
|
|
241
|
-
const { opsJson,
|
|
246
|
+
const metadataPath = join(migrationDir, 'migration.json');
|
|
247
|
+
const existing = readExistingMetadata(metadataPath);
|
|
248
|
+
const { opsJson, metadataJson } = buildMigrationArtifacts(instance, existing);
|
|
242
249
|
|
|
243
250
|
if (dryRun) {
|
|
244
|
-
process.stdout.write(`--- migration.json ---\n${
|
|
251
|
+
process.stdout.write(`--- migration.json ---\n${metadataJson}\n`);
|
|
245
252
|
process.stdout.write('--- ops.json ---\n');
|
|
246
253
|
process.stdout.write(`${opsJson}\n`);
|
|
247
254
|
return;
|
|
248
255
|
}
|
|
249
256
|
|
|
250
257
|
writeFileSync(join(migrationDir, 'ops.json'), opsJson);
|
|
251
|
-
writeFileSync(
|
|
258
|
+
writeFileSync(metadataPath, metadataJson);
|
|
252
259
|
|
|
253
260
|
process.stdout.write(`Wrote ops.json + migration.json to ${migrationDir}\n`);
|
|
254
261
|
}
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* CLI-specific errors (e.g., Commander.js argument validation) can be added here if needed.
|
|
4
4
|
*/
|
|
5
5
|
export type { CliErrorConflict, CliErrorEnvelope } from '@prisma-next/errors/control';
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
import {
|
|
7
8
|
CliStructuredError,
|
|
8
9
|
errorConfigFileNotFound,
|
|
9
10
|
errorConfigValidation,
|
|
@@ -20,6 +21,26 @@ export {
|
|
|
20
21
|
errorTargetMigrationNotSupported,
|
|
21
22
|
errorUnexpected,
|
|
22
23
|
} from '@prisma-next/errors/control';
|
|
24
|
+
import { errorRuntime } from '@prisma-next/errors/execution';
|
|
25
|
+
import type { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
CliStructuredError,
|
|
29
|
+
errorConfigFileNotFound,
|
|
30
|
+
errorConfigValidation,
|
|
31
|
+
errorContractConfigMissing,
|
|
32
|
+
errorContractMissingExtensionPacks,
|
|
33
|
+
errorContractValidationFailed,
|
|
34
|
+
errorDatabaseConnectionRequired,
|
|
35
|
+
errorDriverRequired,
|
|
36
|
+
errorFamilyReadMarkerSqlRequired,
|
|
37
|
+
errorFileNotFound,
|
|
38
|
+
errorMigrationCliInvalidConfigArg,
|
|
39
|
+
errorMigrationPlanningFailed,
|
|
40
|
+
errorQueryRunnerFactoryRequired,
|
|
41
|
+
errorTargetMigrationNotSupported,
|
|
42
|
+
errorUnexpected,
|
|
43
|
+
};
|
|
23
44
|
export {
|
|
24
45
|
ERROR_CODE_DESTRUCTIVE_CHANGES,
|
|
25
46
|
errorDestructiveChanges,
|
|
@@ -38,3 +59,26 @@ export {
|
|
|
38
59
|
errorUnfilledPlaceholder,
|
|
39
60
|
placeholder,
|
|
40
61
|
} from '@prisma-next/errors/migration';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Maps a `MigrationToolsError` raised by the migration-tools loader/graph
|
|
65
|
+
* surface (`readMigrationPackage`, `readMigrationsDir`, `readRefs`,
|
|
66
|
+
* `resolveRef`, `reconstructGraph`, ...) into a CLI `errorRuntime` envelope.
|
|
67
|
+
*
|
|
68
|
+
* The full `error.details` payload is forwarded into `meta` so machine
|
|
69
|
+
* consumers (`--json`) see structural fields like `dir`, `storedHash`,
|
|
70
|
+
* `computedHash` (for `MIGRATION.HASH_MISMATCH`) alongside the stable
|
|
71
|
+
* `code`. The user-visible `summary`/`why`/`fix` text is unchanged.
|
|
72
|
+
*
|
|
73
|
+
* Callers are expected to gate on `MigrationToolsError.is(error)` first
|
|
74
|
+
* (mirroring the original inline pattern); non-`MigrationToolsError`
|
|
75
|
+
* values are caller-classified (rethrow, wrap with command-specific
|
|
76
|
+
* `errorUnexpected`, etc.).
|
|
77
|
+
*/
|
|
78
|
+
export function mapMigrationToolsError(error: MigrationToolsError): CliStructuredError {
|
|
79
|
+
return errorRuntime(error.message, {
|
|
80
|
+
why: error.why,
|
|
81
|
+
fix: error.fix,
|
|
82
|
+
meta: { code: error.code, ...(error.details ?? {}) },
|
|
83
|
+
});
|
|
84
|
+
}
|