@prisma-next/cli 0.5.0-dev.9 → 0.5.1
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 +61 -26
- package/dist/cli-errors-B9OBbled.d.mts +3 -0
- package/dist/cli-errors-D3_sMh2K.mjs +33 -0
- package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
- package/dist/cli.mjs +16 -78
- package/dist/cli.mjs.map +1 -1
- package/dist/client-BCnP7cHo.mjs +1485 -0
- package/dist/client-BCnP7cHo.mjs.map +1 -0
- package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
- package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -4
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -4
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +16 -13
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +6 -7
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +9 -9
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +15 -13
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -321
- package/dist/commands/migration-apply.d.mts +28 -13
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +55 -151
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts +0 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +34 -40
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +33 -6
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +2 -348
- 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 +8 -12
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +64 -10
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +166 -60
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +126 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -4
- package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
- package/dist/config-loader.d.mts +0 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +2 -3
- package/dist/contract-emit-9DBda5Ou.mjs +150 -0
- package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
- package/dist/contract-emit-B77TsJqf.mjs +327 -0
- package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
- package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
- package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
- package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-ByxhPjpW.mjs} +13 -22
- package/dist/contract-infer-ByxhPjpW.mjs.map +1 -0
- package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs +160 -0
- package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs.map +1 -0
- package/dist/db-verify-Czm5T-J4.mjs +404 -0
- package/dist/db-verify-Czm5T-J4.mjs.map +1 -0
- package/dist/exports/config-types.mjs +1 -2
- package/dist/exports/control-api.d.mts +101 -586
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +4 -6
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +28 -30
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +2 -4
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +2 -3
- package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
- package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
- package/dist/global-flags-Icqpxk23.d.mts +12 -0
- package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
- package/dist/helpers-eqdN8tH6.mjs +25 -0
- package/dist/helpers-eqdN8tH6.mjs.map +1 -0
- package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
- package/dist/init-DETSgw3h.mjs.map +1 -0
- package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-DxdBd4Er.mjs} +10 -11
- package/dist/inspect-live-schema-DxdBd4Er.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -12
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +309 -86
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-BdV8JYXV.mjs} +8 -9
- package/dist/migration-command-scaffold-BdV8JYXV.mjs.map +1 -0
- package/dist/migration-plan-mRu5K81L.mjs +494 -0
- package/dist/migration-plan-mRu5K81L.mjs.map +1 -0
- package/dist/{migration-status-DUMiH8_G.mjs → migration-status-By9G5p2H.mjs} +270 -65
- package/dist/migration-status-By9G5p2H.mjs.map +1 -0
- package/dist/migrations-CTsyBXCA.mjs +229 -0
- package/dist/migrations-CTsyBXCA.mjs.map +1 -0
- package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
- package/dist/output-B16Kefzx.mjs.map +1 -0
- package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
- package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
- package/dist/result-handler-rmPVKIP2.mjs +25 -0
- package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
- package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
- package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
- package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
- package/dist/types-LItU7E4l.d.mts +856 -0
- package/dist/types-LItU7E4l.d.mts.map +1 -0
- package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
- package/dist/verify-CiwNWM9N.mjs.map +1 -0
- package/package.json +28 -26
- 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 +15 -3
- package/src/commands/db-update.ts +9 -4
- package/src/commands/db-verify.ts +47 -15
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +2 -2
- package/src/commands/init/templates/code-templates.ts +26 -18
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +114 -212
- package/src/commands/migration-new.ts +42 -45
- package/src/commands/migration-plan.ts +213 -75
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +274 -70
- package/src/commands/migration-status.ts +491 -64
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +85 -5
- package/src/control-api/contract-enrichment.ts +6 -4
- package/src/control-api/operations/apply-aggregate.ts +290 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-apply-aggregate.ts +399 -0
- package/src/control-api/operations/db-init.ts +51 -253
- package/src/control-api/operations/db-update.ts +66 -183
- package/src/control-api/operations/db-verify.ts +342 -0
- package/src/control-api/operations/migration-apply.ts +430 -131
- package/src/control-api/types.ts +278 -29
- package/src/exports/control-api.ts +15 -3
- package/src/load-ts-contract.ts +28 -26
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/combine-schema-results.ts +84 -0
- package/src/utils/command-helpers.ts +69 -25
- package/src/utils/contract-space-aggregate-loader.ts +177 -0
- package/src/utils/contract-space-seed-phase.ts +201 -0
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/extension-pack-inputs.ts +162 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +255 -77
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
- package/dist/cli-errors-Cd79vmTH.mjs +0 -5
- package/dist/client-CrsnY58k.mjs +0 -997
- package/dist/client-CrsnY58k.mjs.map +0 -1
- package/dist/commands/db-verify.mjs.map +0 -1
- package/dist/commands/migration-plan.mjs.map +0 -1
- package/dist/config-loader-C25b63rJ.mjs.map +0 -1
- package/dist/contract-emit--feXyNd7.mjs +0 -4
- package/dist/contract-emit-NJ01hiiv.mjs +0 -195
- package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
- package/dist/contract-emit-V5SSitUT.mjs +0 -122
- package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
- package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
- package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
- package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
- package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
- package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
- package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
- package/dist/init-C5220SY9.mjs.map +0 -1
- package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
- package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
- package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
- package/dist/migrations-Bo5WtTla.mjs +0 -153
- package/dist/migrations-Bo5WtTla.mjs.map +0 -1
- package/dist/output-BpcQrnnq.mjs.map +0 -1
- package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
- package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
- package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
- package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
- package/dist/verify-Bkycc-Tf.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
|
@@ -1,19 +1,40 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
3
|
+
import {
|
|
4
|
+
createControlStack,
|
|
5
|
+
type MigrationPlanOperation,
|
|
6
|
+
type OperationPreview,
|
|
7
|
+
} from '@prisma-next/framework-components/control';
|
|
8
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
3
9
|
import { readMigrationPackage, readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
4
|
-
import
|
|
5
|
-
|
|
10
|
+
import {
|
|
11
|
+
findLatestMigration,
|
|
12
|
+
reconstructGraph,
|
|
13
|
+
} from '@prisma-next/migration-tools/migration-graph';
|
|
14
|
+
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
15
|
+
import { spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';
|
|
16
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
6
17
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
7
18
|
import { Command } from 'commander';
|
|
8
|
-
import { relative, resolve } from 'pathe';
|
|
19
|
+
import { isAbsolute, relative, resolve } from 'pathe';
|
|
9
20
|
import { loadConfig } from '../config-loader';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
21
|
+
import { createControlClient } from '../control-api/client';
|
|
22
|
+
import {
|
|
23
|
+
type CliStructuredError,
|
|
24
|
+
errorContractValidationFailed,
|
|
25
|
+
errorFileNotFound,
|
|
26
|
+
errorRuntime,
|
|
27
|
+
errorUnexpected,
|
|
28
|
+
mapMigrationToolsError,
|
|
29
|
+
} from '../utils/cli-errors';
|
|
12
30
|
import {
|
|
13
31
|
addGlobalOptions,
|
|
32
|
+
resolveContractPath,
|
|
33
|
+
resolveMigrationPaths,
|
|
14
34
|
setCommandDescriptions,
|
|
15
35
|
setCommandExamples,
|
|
16
36
|
} from '../utils/command-helpers';
|
|
37
|
+
import { buildContractSpaceAggregate } from '../utils/contract-space-aggregate-loader';
|
|
17
38
|
import { formatMigrationShowOutput } from '../utils/formatters/migrations';
|
|
18
39
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
19
40
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
@@ -25,34 +46,102 @@ interface MigrationShowOptions extends CommonCommandOptions {
|
|
|
25
46
|
readonly config?: string;
|
|
26
47
|
}
|
|
27
48
|
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Details of one space's latest (or targeted) migration package.
|
|
51
|
+
*/
|
|
52
|
+
export interface MigrationShowSpacePresent {
|
|
53
|
+
readonly kind: 'present';
|
|
54
|
+
readonly spaceId: string;
|
|
30
55
|
readonly dirName: string;
|
|
31
56
|
readonly dirPath: string;
|
|
32
|
-
readonly from: string;
|
|
57
|
+
readonly from: string | null;
|
|
33
58
|
readonly to: string;
|
|
34
|
-
readonly
|
|
35
|
-
readonly kind: string;
|
|
59
|
+
readonly migrationHash: string;
|
|
36
60
|
readonly createdAt: string;
|
|
37
61
|
readonly operations: readonly {
|
|
38
62
|
readonly id: string;
|
|
39
63
|
readonly label: string;
|
|
40
64
|
readonly operationClass: string;
|
|
41
65
|
}[];
|
|
42
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Family-agnostic textual preview of the migration's operations. Always
|
|
68
|
+
* defined; statements is empty for a no-op migration or a family that does
|
|
69
|
+
* not implement the `OperationPreviewCapable` capability.
|
|
70
|
+
*/
|
|
71
|
+
readonly preview: OperationPreview;
|
|
43
72
|
readonly summary: string;
|
|
44
73
|
}
|
|
45
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Placeholder for a loaded contract space that has no on-disk migration
|
|
77
|
+
* package — the extension descriptor declared the space but no migrations
|
|
78
|
+
* directory has been materialised for it yet. Surfaces the space in the
|
|
79
|
+
* response so JSON consumers see every loaded extension instead of having
|
|
80
|
+
* silently-skipped entries.
|
|
81
|
+
*/
|
|
82
|
+
export interface MigrationShowSpaceMissing {
|
|
83
|
+
readonly kind: 'missing';
|
|
84
|
+
readonly spaceId: string;
|
|
85
|
+
readonly summary: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type MigrationShowSpaceResult = MigrationShowSpacePresent | MigrationShowSpaceMissing;
|
|
89
|
+
|
|
90
|
+
export interface MigrationShowResult {
|
|
91
|
+
readonly ok: true;
|
|
92
|
+
/**
|
|
93
|
+
* Per-space results, ordered: app first, then extensions alphabetically
|
|
94
|
+
* (matching the aggregate's canonical ordering).
|
|
95
|
+
*/
|
|
96
|
+
readonly spaces: readonly MigrationShowSpaceResult[];
|
|
97
|
+
}
|
|
98
|
+
|
|
46
99
|
function looksLikePath(target: string): boolean {
|
|
47
100
|
return target.includes('/') || target.includes('\\');
|
|
48
101
|
}
|
|
49
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Validate that a path-like `migration show` target resolves inside the app
|
|
105
|
+
* migrations directory. The returned result is always emitted under
|
|
106
|
+
* `aggregate.app.spaceId`, so accepting an extension-space (or otherwise
|
|
107
|
+
* external) path here would silently mislabel the result. Returns the
|
|
108
|
+
* resolved absolute path on success.
|
|
109
|
+
*
|
|
110
|
+
* `pathe.relative` can return an absolute path when the target cannot be
|
|
111
|
+
* expressed relative to the base (e.g. on Windows when `target` is on a
|
|
112
|
+
* different drive than `appMigrationsDir`). That case does not start with
|
|
113
|
+
* `..`, so the absolute-check below is required to reject cross-drive
|
|
114
|
+
* targets rather than mislabeling them as app-space.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveAppTargetPath(
|
|
117
|
+
target: string,
|
|
118
|
+
appMigrationsDir: string,
|
|
119
|
+
appMigrationsRelative: string,
|
|
120
|
+
): Result<string, CliStructuredError> {
|
|
121
|
+
const targetPath = resolve(target);
|
|
122
|
+
const relativeToApp = relative(appMigrationsDir, targetPath);
|
|
123
|
+
const isOutsideAppDir =
|
|
124
|
+
relativeToApp === '' ||
|
|
125
|
+
relativeToApp === '.' ||
|
|
126
|
+
relativeToApp.startsWith('..') ||
|
|
127
|
+
isAbsolute(relativeToApp);
|
|
128
|
+
if (isOutsideAppDir) {
|
|
129
|
+
return notOk(
|
|
130
|
+
errorRuntime('Target must point to an app-space migration', {
|
|
131
|
+
why: `Expected a path under ${appMigrationsRelative}, got ${target}`,
|
|
132
|
+
fix: 'Pass an app-space migration directory or use a hash prefix.',
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return ok(targetPath);
|
|
137
|
+
}
|
|
138
|
+
|
|
50
139
|
export function resolveByHashPrefix(
|
|
51
|
-
packages: readonly
|
|
140
|
+
packages: readonly OnDiskMigrationPackage[],
|
|
52
141
|
prefix: string,
|
|
53
|
-
): Result<
|
|
142
|
+
): Result<OnDiskMigrationPackage, CliStructuredError> {
|
|
54
143
|
const normalizedPrefix = prefix.startsWith('sha256:') ? prefix : `sha256:${prefix}`;
|
|
55
|
-
const matches = packages.filter((p) => p.
|
|
144
|
+
const matches = packages.filter((p) => p.metadata.migrationHash.startsWith(normalizedPrefix));
|
|
56
145
|
|
|
57
146
|
if (matches.length === 1) {
|
|
58
147
|
return ok(matches[0]!);
|
|
@@ -61,13 +150,13 @@ export function resolveByHashPrefix(
|
|
|
61
150
|
if (matches.length === 0) {
|
|
62
151
|
return notOk(
|
|
63
152
|
errorRuntime('No migration found matching prefix', {
|
|
64
|
-
why: `No migration has a
|
|
153
|
+
why: `No migration has a migrationHash starting with "${normalizedPrefix}"`,
|
|
65
154
|
fix: 'Run `prisma-next migration show` (no argument) to see the latest migration, or check the migrations directory for available packages.',
|
|
66
155
|
}),
|
|
67
156
|
);
|
|
68
157
|
}
|
|
69
158
|
|
|
70
|
-
const candidates = matches.map((p) => ` ${p.dirName} ${p.
|
|
159
|
+
const candidates = matches.map((p) => ` ${p.dirName} ${p.metadata.migrationHash}`).join('\n');
|
|
71
160
|
return notOk(
|
|
72
161
|
errorRuntime('Ambiguous hash prefix', {
|
|
73
162
|
why: `Multiple migrations match prefix "${normalizedPrefix}":\n${candidates}`,
|
|
@@ -76,6 +165,71 @@ export function resolveByHashPrefix(
|
|
|
76
165
|
);
|
|
77
166
|
}
|
|
78
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the latest migration from a space directory.
|
|
170
|
+
*
|
|
171
|
+
* Returns `ok(null)` only when the directory is empty or absent (ENOENT is
|
|
172
|
+
* absorbed by `readMigrationsDir`). If `readMigrationsDir` returned packages
|
|
173
|
+
* but `findLatestMigration` cannot pick a leaf, the on-disk history is
|
|
174
|
+
* corrupt — return a runtime error rather than collapsing it to a `missing`
|
|
175
|
+
* placeholder, which would hide the corruption from the caller.
|
|
176
|
+
*/
|
|
177
|
+
export async function resolveLatestFromDir(
|
|
178
|
+
spaceDir: string,
|
|
179
|
+
): Promise<Result<OnDiskMigrationPackage | null, CliStructuredError>> {
|
|
180
|
+
try {
|
|
181
|
+
const allPackages = await readMigrationsDir(spaceDir);
|
|
182
|
+
if (allPackages.length === 0) return ok(null);
|
|
183
|
+
const graph = reconstructGraph(allPackages);
|
|
184
|
+
const latestMigration = findLatestMigration(graph);
|
|
185
|
+
if (!latestMigration) {
|
|
186
|
+
return notOk(
|
|
187
|
+
errorRuntime('Could not resolve latest migration', {
|
|
188
|
+
why: `No latest migration found in ${relative(process.cwd(), spaceDir)}`,
|
|
189
|
+
fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const leafPkg = allPackages.find(
|
|
194
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
195
|
+
);
|
|
196
|
+
return ok(leafPkg ?? null);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
|
|
199
|
+
return notOk(
|
|
200
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
201
|
+
why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function pkgToSpaceResult(
|
|
208
|
+
spaceId: string,
|
|
209
|
+
pkg: OnDiskMigrationPackage,
|
|
210
|
+
client: ReturnType<typeof createControlClient>,
|
|
211
|
+
): MigrationShowSpacePresent {
|
|
212
|
+
const ops = pkg.ops as readonly MigrationPlanOperation[];
|
|
213
|
+
const preview: OperationPreview = client.toOperationPreview(ops) ?? { statements: [] };
|
|
214
|
+
return {
|
|
215
|
+
kind: 'present',
|
|
216
|
+
spaceId,
|
|
217
|
+
dirName: pkg.dirName,
|
|
218
|
+
dirPath: relative(process.cwd(), pkg.dirPath),
|
|
219
|
+
from: pkg.metadata.from,
|
|
220
|
+
to: pkg.metadata.to,
|
|
221
|
+
migrationHash: pkg.metadata.migrationHash,
|
|
222
|
+
createdAt: pkg.metadata.createdAt,
|
|
223
|
+
operations: ops.map((op) => ({
|
|
224
|
+
id: op.id,
|
|
225
|
+
label: op.label,
|
|
226
|
+
operationClass: op.operationClass,
|
|
227
|
+
})),
|
|
228
|
+
preview,
|
|
229
|
+
summary: `${ops.length} operation(s)`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
79
233
|
async function executeMigrationShowCommand(
|
|
80
234
|
target: string | undefined,
|
|
81
235
|
options: MigrationShowOptions,
|
|
@@ -83,20 +237,17 @@ async function executeMigrationShowCommand(
|
|
|
83
237
|
ui: TerminalUI,
|
|
84
238
|
): Promise<Result<MigrationShowResult, CliStructuredError>> {
|
|
85
239
|
const config = await loadConfig(options.config);
|
|
86
|
-
const configPath =
|
|
87
|
-
|
|
88
|
-
: 'prisma-next.config.ts';
|
|
240
|
+
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
|
|
241
|
+
resolveMigrationPaths(options.config, config);
|
|
89
242
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
config.migrations?.dir ?? 'migrations',
|
|
93
|
-
);
|
|
94
|
-
const migrationsRelative = relative(process.cwd(), migrationsDir);
|
|
243
|
+
const contractPathAbsolute = resolveContractPath(config);
|
|
244
|
+
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
95
245
|
|
|
96
246
|
if (!flags.json && !flags.quiet) {
|
|
97
247
|
const details: Array<{ label: string; value: string }> = [
|
|
98
248
|
{ label: 'config', value: configPath },
|
|
99
|
-
{ label: '
|
|
249
|
+
{ label: 'contract', value: contractPath },
|
|
250
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
100
251
|
];
|
|
101
252
|
if (target) {
|
|
102
253
|
details.push({ label: 'target', value: target });
|
|
@@ -110,26 +261,87 @@ async function executeMigrationShowCommand(
|
|
|
110
261
|
ui.stderr(header);
|
|
111
262
|
}
|
|
112
263
|
|
|
113
|
-
|
|
264
|
+
// Load the app contract so the aggregate loader can validate it.
|
|
265
|
+
let contractJsonContent: string;
|
|
266
|
+
try {
|
|
267
|
+
contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
270
|
+
return notOk(
|
|
271
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
272
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
273
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}`,
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return notOk(
|
|
278
|
+
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
279
|
+
why: 'Failed to read contract file',
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let appContract: Contract;
|
|
285
|
+
try {
|
|
286
|
+
appContract = JSON.parse(contractJsonContent) as Contract;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return notOk(
|
|
289
|
+
errorContractValidationFailed(
|
|
290
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
291
|
+
{ where: { path: contractPathAbsolute } },
|
|
292
|
+
),
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Build the aggregate against current disk state to enumerate all spaces.
|
|
297
|
+
const stack = createControlStack(config);
|
|
298
|
+
const familyInstance = config.family.create(stack);
|
|
299
|
+
const aggregateResult = await buildContractSpaceAggregate({
|
|
300
|
+
targetId: config.target.targetId,
|
|
301
|
+
migrationsDir,
|
|
302
|
+
appContract,
|
|
303
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
304
|
+
validateContract: (json: unknown) => familyInstance.validateContract(json),
|
|
305
|
+
});
|
|
306
|
+
if (!aggregateResult.ok) {
|
|
307
|
+
return notOk(aggregateResult.failure);
|
|
308
|
+
}
|
|
309
|
+
const aggregate = aggregateResult.value;
|
|
310
|
+
|
|
311
|
+
// `migration show` is an offline command; the control client is constructed
|
|
312
|
+
// purely to dispatch the family-specific `toOperationPreview` capability and
|
|
313
|
+
// is not connected to a database.
|
|
314
|
+
const client = createControlClient({
|
|
315
|
+
family: config.family,
|
|
316
|
+
target: config.target,
|
|
317
|
+
adapter: config.adapter,
|
|
318
|
+
...ifDefined('driver', config.driver),
|
|
319
|
+
extensionPacks: config.extensionPacks ?? [],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const spaces: MigrationShowSpaceResult[] = [];
|
|
114
323
|
|
|
324
|
+
// App space: honour the `target` argument (path or hash prefix) when provided.
|
|
115
325
|
try {
|
|
326
|
+
let appPkg: OnDiskMigrationPackage;
|
|
116
327
|
if (target && looksLikePath(target)) {
|
|
117
|
-
|
|
328
|
+
const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
|
|
329
|
+
if (!resolved.ok) return resolved;
|
|
330
|
+
appPkg = await readMigrationPackage(resolved.value);
|
|
118
331
|
} else {
|
|
119
|
-
const allPackages = await readMigrationsDir(
|
|
332
|
+
const allPackages = await readMigrationsDir(appMigrationsDir);
|
|
120
333
|
if (allPackages.length === 0) {
|
|
121
334
|
return notOk(
|
|
122
335
|
errorRuntime('No migrations found', {
|
|
123
|
-
why: `No migration packages found in ${
|
|
336
|
+
why: `No migration packages found in ${appMigrationsRelative}`,
|
|
124
337
|
fix: 'Run `prisma-next migration plan` to create a migration first.',
|
|
125
338
|
}),
|
|
126
339
|
);
|
|
127
340
|
}
|
|
128
|
-
|
|
129
341
|
if (target) {
|
|
130
342
|
const resolved = resolveByHashPrefix(allPackages, target);
|
|
131
343
|
if (!resolved.ok) return resolved;
|
|
132
|
-
|
|
344
|
+
appPkg = resolved.value;
|
|
133
345
|
} else {
|
|
134
346
|
const graph = reconstructGraph(allPackages);
|
|
135
347
|
const latestMigration = findLatestMigration(graph);
|
|
@@ -142,7 +354,7 @@ async function executeMigrationShowCommand(
|
|
|
142
354
|
);
|
|
143
355
|
}
|
|
144
356
|
const leafPkg = allPackages.find(
|
|
145
|
-
(p) => p.
|
|
357
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
146
358
|
);
|
|
147
359
|
if (!leafPkg) {
|
|
148
360
|
return notOk(
|
|
@@ -152,47 +364,42 @@ async function executeMigrationShowCommand(
|
|
|
152
364
|
}),
|
|
153
365
|
);
|
|
154
366
|
}
|
|
155
|
-
|
|
367
|
+
appPkg = leafPkg;
|
|
156
368
|
}
|
|
157
369
|
}
|
|
370
|
+
spaces.push(pkgToSpaceResult(aggregate.app.spaceId, appPkg, client));
|
|
158
371
|
} catch (error) {
|
|
159
372
|
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
|
-
);
|
|
373
|
+
return notOk(mapMigrationToolsError(error));
|
|
167
374
|
}
|
|
168
375
|
return notOk(
|
|
169
376
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
170
|
-
why: `Failed to read migration: ${error instanceof Error ? error.message : String(error)}`,
|
|
377
|
+
why: `Failed to read app-space migration: ${error instanceof Error ? error.message : String(error)}`,
|
|
171
378
|
}),
|
|
172
379
|
);
|
|
173
380
|
}
|
|
174
381
|
|
|
175
|
-
|
|
176
|
-
|
|
382
|
+
// Extension spaces: always emit one entry per loaded extension so the
|
|
383
|
+
// response enumerates every space the aggregate knows about. Spaces
|
|
384
|
+
// with no on-disk migration package yet (e.g. an extension was declared
|
|
385
|
+
// but never `migrate`d) become `kind: 'missing'` placeholders instead
|
|
386
|
+
// of being silently skipped.
|
|
387
|
+
for (const ext of aggregate.extensions) {
|
|
388
|
+
const extSpaceDir = spaceMigrationDirectory(migrationsDir, ext.spaceId);
|
|
389
|
+
const extPkgResult = await resolveLatestFromDir(extSpaceDir);
|
|
390
|
+
if (!extPkgResult.ok) return extPkgResult;
|
|
391
|
+
if (extPkgResult.value !== null) {
|
|
392
|
+
spaces.push(pkgToSpaceResult(ext.spaceId, extPkgResult.value, client));
|
|
393
|
+
} else {
|
|
394
|
+
spaces.push({
|
|
395
|
+
kind: 'missing',
|
|
396
|
+
spaceId: ext.spaceId,
|
|
397
|
+
summary: 'No on-disk migration package for this space',
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
177
401
|
|
|
178
|
-
|
|
179
|
-
ok: true,
|
|
180
|
-
dirName: pkg.dirName,
|
|
181
|
-
dirPath: relative(process.cwd(), pkg.dirPath),
|
|
182
|
-
from: pkg.manifest.from,
|
|
183
|
-
to: pkg.manifest.to,
|
|
184
|
-
migrationId: pkg.manifest.migrationId,
|
|
185
|
-
kind: pkg.manifest.kind,
|
|
186
|
-
createdAt: pkg.manifest.createdAt,
|
|
187
|
-
operations: ops.map((op) => ({
|
|
188
|
-
id: op.id,
|
|
189
|
-
label: op.label,
|
|
190
|
-
operationClass: op.operationClass,
|
|
191
|
-
})),
|
|
192
|
-
sql,
|
|
193
|
-
summary: `${ops.length} operation(s)`,
|
|
194
|
-
};
|
|
195
|
-
return ok(result);
|
|
402
|
+
return ok({ ok: true, spaces });
|
|
196
403
|
}
|
|
197
404
|
|
|
198
405
|
export function createMigrationShowCommand(): Command {
|
|
@@ -200,19 +407,16 @@ export function createMigrationShowCommand(): Command {
|
|
|
200
407
|
setCommandDescriptions(
|
|
201
408
|
command,
|
|
202
409
|
'Display migration package contents',
|
|
203
|
-
'Shows the operations,
|
|
204
|
-
'Accepts a directory path
|
|
205
|
-
'latest
|
|
410
|
+
'Shows the operations, statement preview, and metadata for every loaded contract\n' +
|
|
411
|
+
'space (app + extensions). Accepts a directory path or hash prefix to target a\n' +
|
|
412
|
+
'specific app-space migration; defaults to the latest per space.',
|
|
206
413
|
);
|
|
207
414
|
setCommandExamples(command, [
|
|
208
415
|
'prisma-next migration show',
|
|
209
416
|
'prisma-next migration show sha256:a1b2c3',
|
|
210
417
|
]);
|
|
211
418
|
addGlobalOptions(command)
|
|
212
|
-
.argument(
|
|
213
|
-
'[target]',
|
|
214
|
-
'Migration directory path or migrationId hash prefix (defaults to latest)',
|
|
215
|
-
)
|
|
419
|
+
.argument('[target]', 'App-space migration path or migrationHash prefix (defaults to latest)')
|
|
216
420
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
217
421
|
.action(async (target: string | undefined, options: MigrationShowOptions) => {
|
|
218
422
|
const flags = parseGlobalFlags(options);
|