@prisma-next/cli 0.12.0-dev.4 → 0.12.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 +2 -2
- package/dist/cli.mjs +180 -163
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-KgJorIvG.mjs → client-Dk-zRFuT.mjs} +83 -58
- package/dist/client-Dk-zRFuT.mjs.map +1 -0
- package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-xvg9oq4T.mjs} +301 -23
- package/dist/command-helpers-xvg9oq4T.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +4 -5
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +10 -7
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +2 -2
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +6 -8
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +55 -1
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +2 -2
- package/dist/commands/migration-graph.d.mts +25 -7
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +170 -2
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +24 -26
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +2 -190
- package/dist/commands/migration-log.d.mts +20 -15
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +1 -137
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -4
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +13 -25
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +41 -141
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -759
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +3 -3
- package/dist/commands/telemetry/index.d.mts +7 -0
- package/dist/commands/telemetry/index.d.mts.map +1 -0
- package/dist/commands/telemetry/index.mjs +2 -0
- package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-Wj3u4Xco.mjs} +2 -2
- package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-Wj3u4Xco.mjs.map} +1 -1
- package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-CZU0UO6M.mjs} +3 -3
- package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-CZU0UO6M.mjs.map} +1 -1
- package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-FetLZ3jn.mjs} +5 -5
- package/dist/{contract-emit-D-4jrNve.mjs.map → contract-emit-FetLZ3jn.mjs.map} +1 -1
- package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-3wtOsS_H.mjs} +3 -3
- package/dist/{contract-infer-D8uEbJuu.mjs.map → contract-infer-3wtOsS_H.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-BdRPfM3Q.mjs} +63 -5
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-BdRPfM3Q.mjs.map} +1 -1
- package/dist/{db-verify-v_vUKXTU.mjs → db-verify-BisylXFZ.mjs} +4 -4
- package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-BisylXFZ.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +2 -2
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-fYXjz_in.mjs → framework-components-Be4inY3I.mjs} +2 -2
- package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-Be4inY3I.mjs.map} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
- package/dist/{init-Cv9UzWL5.mjs → init-BTE2U7lG.mjs} +5 -58
- package/dist/init-BTE2U7lG.mjs.map +1 -0
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-Cy9Y4wsL.mjs} +3 -3
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs.map → inspect-live-schema-Cy9Y4wsL.mjs.map} +1 -1
- package/dist/{migration-check-BiBJoYYW.mjs → migration-check-CUavU7U9.mjs} +236 -88
- package/dist/migration-check-CUavU7U9.mjs.map +1 -0
- package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-BxOxtyJ6.mjs} +3 -3
- package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-BxOxtyJ6.mjs.map} +1 -1
- package/dist/migration-graph-space-render-ByJ83gxp.mjs +1966 -0
- package/dist/migration-graph-space-render-ByJ83gxp.mjs.map +1 -0
- package/dist/migration-list-jK6QeczE.mjs +228 -0
- package/dist/migration-list-jK6QeczE.mjs.map +1 -0
- package/dist/migration-list-types-DS63IdFd.d.mts +23 -0
- package/dist/migration-list-types-DS63IdFd.d.mts.map +1 -0
- package/dist/migration-log-DWI6dZyi.mjs +215 -0
- package/dist/migration-log-DWI6dZyi.mjs.map +1 -0
- package/dist/migration-path-target-DqcrbOis.mjs +24 -0
- package/dist/migration-path-target-DqcrbOis.mjs.map +1 -0
- package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-NHdlUwPG.mjs} +5 -6
- package/dist/{migration-plan-9DJ7q7_z.mjs.map → migration-plan-NHdlUwPG.mjs.map} +1 -1
- package/dist/migration-status-DI6Ldjbo.mjs +439 -0
- package/dist/migration-status-DI6Ldjbo.mjs.map +1 -0
- package/dist/{output-B60Gw5fu.mjs → output-CF_hqzI-.mjs} +1 -1
- package/dist/{output-B60Gw5fu.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-CJY9zOv7.mjs} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-CJY9zOv7.mjs.map} +1 -1
- package/dist/telemetry-DQP0BvKv.mjs +122 -0
- package/dist/telemetry-DQP0BvKv.mjs.map +1 -0
- package/dist/{types-Dt_SfqFm.d.mts → types-Cculk5KV.d.mts} +44 -31
- package/dist/types-Cculk5KV.d.mts.map +1 -0
- package/dist/{verify-DCA9Sldu.mjs → verify-tvHRBBVP.mjs} +2 -2
- package/dist/{verify-DCA9Sldu.mjs.map → verify-tvHRBBVP.mjs.map} +1 -1
- package/package.json +22 -19
- package/src/cli.ts +5 -0
- package/src/commands/db-update.ts +7 -1
- package/src/commands/init/index.ts +6 -35
- package/src/commands/init/init.ts +1 -14
- package/src/commands/init/inputs.ts +0 -75
- package/src/commands/migrate.ts +6 -6
- package/src/commands/migration-check.ts +340 -117
- package/src/commands/migration-graph.ts +163 -90
- package/src/commands/migration-list.ts +55 -25
- package/src/commands/migration-log.ts +49 -98
- package/src/commands/migration-show.ts +10 -38
- package/src/commands/migration-status-overlay.ts +61 -0
- package/src/commands/migration-status.ts +440 -1056
- package/src/commands/telemetry/index.ts +107 -0
- package/src/commands/telemetry/status.ts +67 -0
- package/src/control-api/client.ts +20 -9
- package/src/control-api/operations/contract-emit.ts +2 -2
- package/src/control-api/operations/db-init.ts +3 -3
- package/src/control-api/operations/{db-apply.ts → db-run.ts} +37 -10
- package/src/control-api/operations/db-update.ts +4 -4
- package/src/control-api/operations/{migration-apply.ts → migrate.ts} +32 -24
- package/src/control-api/operations/{apply.ts → run-migration.ts} +33 -27
- package/src/control-api/types.ts +46 -29
- package/src/utils/cli-errors.ts +47 -2
- package/src/utils/formatters/errors.ts +11 -0
- package/src/utils/formatters/migration-graph-lane-colors.ts +194 -0
- package/src/utils/formatters/migration-graph-layout.ts +51 -7
- package/src/utils/formatters/migration-graph-rows.ts +128 -15
- package/src/utils/formatters/migration-graph-space-render.ts +138 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +405 -77
- package/src/utils/formatters/migration-list-data-column.ts +4 -91
- package/src/utils/formatters/migration-list-graph-topology.ts +68 -90
- package/src/utils/formatters/migration-list-render.ts +122 -70
- package/src/utils/formatters/migration-list-styler.ts +48 -5
- package/src/utils/formatters/migration-log-table.ts +200 -0
- package/src/utils/formatters/migrations.ts +25 -1
- package/src/utils/global-flags.ts +35 -0
- package/src/utils/legend.ts +38 -0
- package/src/utils/migration-path-target.ts +39 -0
- package/src/utils/telemetry.ts +68 -32
- package/dist/client-KgJorIvG.mjs.map +0 -1
- package/dist/command-helpers-Bbw1GbwL.mjs.map +0 -1
- package/dist/commands/migration-list.mjs.map +0 -1
- package/dist/commands/migration-log.mjs.map +0 -1
- package/dist/commands/migration-status.mjs.map +0 -1
- package/dist/extension-pack-inputs-IDvjRCi3.mjs +0 -62
- package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +0 -1
- package/dist/graph-render-rFAqZujX.mjs +0 -1081
- package/dist/graph-render-rFAqZujX.mjs.map +0 -1
- package/dist/init-Cv9UzWL5.mjs.map +0 -1
- package/dist/migration-check-BiBJoYYW.mjs.map +0 -1
- package/dist/migration-graph-D7DVUElV.mjs +0 -1232
- package/dist/migration-graph-D7DVUElV.mjs.map +0 -1
- package/dist/migration-list-styler-BRwF4-gy.mjs +0 -399
- package/dist/migration-list-styler-BRwF4-gy.mjs.map +0 -1
- package/dist/migration-types-D2FW63pr.d.mts +0 -15
- package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
- package/dist/migrations-Cv2jxNNK.mjs +0 -228
- package/dist/migrations-Cv2jxNNK.mjs.map +0 -1
- package/dist/types-Dt_SfqFm.d.mts.map +0 -1
- package/src/utils/formatters/graph-migration-mapper.ts +0 -235
- package/src/utils/formatters/graph-render.ts +0 -1323
- package/src/utils/formatters/graph-types.ts +0 -120
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ContractSpaceAggregate,
|
|
6
|
+
IntegrityViolation,
|
|
7
|
+
} from '@prisma-next/migration-tools/aggregate';
|
|
5
8
|
import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
|
|
9
|
+
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
6
10
|
import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
7
11
|
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
8
12
|
import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
|
|
9
13
|
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
10
14
|
import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
15
|
+
import type { Refs } from '@prisma-next/migration-tools/refs';
|
|
11
16
|
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
17
|
+
import {
|
|
18
|
+
isValidSpaceId,
|
|
19
|
+
listContractSpaceDirectories,
|
|
20
|
+
RESERVED_SPACE_SUBDIR_NAMES,
|
|
21
|
+
spaceMigrationDirectory,
|
|
22
|
+
spaceRefsDirectory,
|
|
23
|
+
} from '@prisma-next/migration-tools/spaces';
|
|
24
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
12
25
|
import { Command } from 'commander';
|
|
13
26
|
import { join, relative } from 'pathe';
|
|
14
27
|
import { loadConfig } from '../config-loader';
|
|
28
|
+
import {
|
|
29
|
+
type CliStructuredError,
|
|
30
|
+
errorInvalidSpaceId,
|
|
31
|
+
errorSpaceNotFound,
|
|
32
|
+
mapRefResolutionError,
|
|
33
|
+
} from '../utils/cli-errors';
|
|
15
34
|
import {
|
|
16
35
|
addGlobalOptions,
|
|
17
36
|
resolveContractPath,
|
|
@@ -20,7 +39,9 @@ import {
|
|
|
20
39
|
setCommandExamples,
|
|
21
40
|
setCommandSeeAlso,
|
|
22
41
|
} from '../utils/command-helpers';
|
|
42
|
+
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
23
43
|
import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
|
|
44
|
+
import { formatErrorJson, formatErrorOutput } from '../utils/formatters/errors';
|
|
24
45
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
25
46
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
26
47
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
@@ -28,11 +49,17 @@ import {
|
|
|
28
49
|
type CheckFailure,
|
|
29
50
|
integrityViolationToCheckFailure,
|
|
30
51
|
} from '../utils/integrity-violation-to-check-failure';
|
|
52
|
+
import {
|
|
53
|
+
findPackageByDirPath,
|
|
54
|
+
looksLikePath,
|
|
55
|
+
resolveAppTargetPath,
|
|
56
|
+
} from '../utils/migration-path-target';
|
|
31
57
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
32
58
|
import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
|
|
33
59
|
|
|
34
60
|
interface MigrationCheckOptions extends CommonCommandOptions {
|
|
35
61
|
readonly config?: string;
|
|
62
|
+
readonly space?: string;
|
|
36
63
|
}
|
|
37
64
|
|
|
38
65
|
export type { CheckFailure } from '../utils/integrity-violation-to-check-failure';
|
|
@@ -89,6 +116,173 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
|
|
|
89
116
|
return null;
|
|
90
117
|
}
|
|
91
118
|
|
|
119
|
+
/**
|
|
120
|
+
* One contract space's on-disk state, resolved for the explicit graph
|
|
121
|
+
* checks `runMigrationCheck` runs per space: the space's migration
|
|
122
|
+
* packages, its user-authored refs, its induced graph, and the absolute
|
|
123
|
+
* `migrations/<space>/` + `migrations/<space>/refs/` directories the
|
|
124
|
+
* file-existence and dangling-ref `where` paths are derived from.
|
|
125
|
+
*/
|
|
126
|
+
export interface CheckSpace {
|
|
127
|
+
readonly spaceId: string;
|
|
128
|
+
readonly packages: readonly OnDiskMigrationPackage[];
|
|
129
|
+
readonly refs: Refs;
|
|
130
|
+
readonly graph: MigrationGraph;
|
|
131
|
+
readonly migrationsDir: string;
|
|
132
|
+
readonly refsDir: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Project the loaded {@link ContractSpaceAggregate} into the
|
|
137
|
+
* {@link CheckSpace} rows the multi-space check iterates — one per on-disk
|
|
138
|
+
* contract-space directory, in the aggregate's `app`-first ordering. Mirrors
|
|
139
|
+
* `migration list`'s `migrationSpaceListEntriesFromAggregate`: space
|
|
140
|
+
* membership matches the on-disk directories, package / ref / graph data come
|
|
141
|
+
* from `aggregate.space(id)`.
|
|
142
|
+
*/
|
|
143
|
+
export async function enumerateCheckSpaces(
|
|
144
|
+
aggregate: ContractSpaceAggregate,
|
|
145
|
+
projectMigrationsDir: string,
|
|
146
|
+
): Promise<readonly CheckSpace[]> {
|
|
147
|
+
const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
|
|
148
|
+
const onDiskSpaceIds = new Set(
|
|
149
|
+
candidateDirs.filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name)).filter(isValidSpaceId),
|
|
150
|
+
);
|
|
151
|
+
const spaces: CheckSpace[] = [];
|
|
152
|
+
for (const member of aggregate.spaces()) {
|
|
153
|
+
const spaceId = member.spaceId;
|
|
154
|
+
if (!isValidSpaceId(spaceId)) continue;
|
|
155
|
+
if (!onDiskSpaceIds.has(spaceId)) continue;
|
|
156
|
+
const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);
|
|
157
|
+
spaces.push({
|
|
158
|
+
spaceId,
|
|
159
|
+
packages: member.packages,
|
|
160
|
+
refs: member.refs,
|
|
161
|
+
graph: member.graph(),
|
|
162
|
+
migrationsDir,
|
|
163
|
+
refsDir: spaceRefsDirectory(migrationsDir),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return spaces;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function checkManifestFilesPresent(space: CheckSpace): readonly CheckFailure[] {
|
|
170
|
+
if (!existsSync(space.migrationsDir)) return [];
|
|
171
|
+
const loadedDirNames = new Set(space.packages.map((p) => p.dirName));
|
|
172
|
+
const failures: CheckFailure[] = [];
|
|
173
|
+
let entries: string[];
|
|
174
|
+
try {
|
|
175
|
+
entries = readdirSync(space.migrationsDir);
|
|
176
|
+
} catch {
|
|
177
|
+
return failures;
|
|
178
|
+
}
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
if (entry.startsWith('.') || entry.startsWith('_') || entry === 'refs') continue;
|
|
181
|
+
const entryPath = join(space.migrationsDir, entry);
|
|
182
|
+
try {
|
|
183
|
+
if (!statSync(entryPath).isDirectory()) continue;
|
|
184
|
+
} catch {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (!loadedDirNames.has(entry)) {
|
|
188
|
+
for (const f of ['migration.json', 'ops.json']) {
|
|
189
|
+
const fail = checkFileExists(entryPath, entry, f);
|
|
190
|
+
if (fail) failures.push(fail);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return failures;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function checkReachability(space: CheckSpace): readonly CheckFailure[] {
|
|
198
|
+
const allToHashes = new Set(space.packages.map((p) => p.metadata.to));
|
|
199
|
+
const failures: CheckFailure[] = [];
|
|
200
|
+
for (const pkg of space.packages) {
|
|
201
|
+
const isReachable =
|
|
202
|
+
pkg.metadata.from === null ||
|
|
203
|
+
allToHashes.has(pkg.metadata.from) ||
|
|
204
|
+
pkg.metadata.from === 'sha256:empty';
|
|
205
|
+
if (!isReachable) {
|
|
206
|
+
failures.push({
|
|
207
|
+
pnCode: 'PN-MIG-CHECK-003',
|
|
208
|
+
where: migrationPathRelative(pkg.dirPath),
|
|
209
|
+
why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
|
|
210
|
+
fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return failures;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {
|
|
218
|
+
const failures: CheckFailure[] = [];
|
|
219
|
+
for (const [name, entry] of Object.entries(space.refs)) {
|
|
220
|
+
if (!space.graph.nodes.has(entry.hash)) {
|
|
221
|
+
failures.push({
|
|
222
|
+
pnCode: 'PN-MIG-CHECK-004',
|
|
223
|
+
where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),
|
|
224
|
+
why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
|
|
225
|
+
fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return failures;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function checkSpace(space: CheckSpace): readonly CheckFailure[] {
|
|
233
|
+
return [
|
|
234
|
+
...checkManifestFilesPresent(space),
|
|
235
|
+
...space.packages.map(checkSnapshotConsistency).filter((f): f is CheckFailure => f !== null),
|
|
236
|
+
...checkReachability(space),
|
|
237
|
+
...checkDanglingRefs(space),
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Inputs for {@link runMigrationCheck} — the multi-space policy core of
|
|
243
|
+
* the holistic (no-arg) `migration check`. Enumeration is supplied by the
|
|
244
|
+
* caller (the CLI shell builds it from {@link enumerateCheckSpaces}); the
|
|
245
|
+
* core does not touch config, flags, or streams.
|
|
246
|
+
*/
|
|
247
|
+
export interface RunMigrationCheckInputs {
|
|
248
|
+
readonly spaces: readonly CheckSpace[];
|
|
249
|
+
readonly spaceFilter?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Policy core of the holistic `migration check`: validates `--space`,
|
|
254
|
+
* narrows the pre-enumerated spaces, and runs the per-space explicit graph
|
|
255
|
+
* checks (file-existence, snapshot consistency, reachability, dangling
|
|
256
|
+
* refs), aggregating every failure into one {@link MigrationCheckResult}.
|
|
257
|
+
*
|
|
258
|
+
* `--space` validation mirrors `migration list`: an invalid id →
|
|
259
|
+
* {@link errorInvalidSpaceId}; an id with no on-disk space →
|
|
260
|
+
* {@link errorSpaceNotFound}. Both map to exit `PRECONDITION` at the shell.
|
|
261
|
+
* Aggregate-integrity violations (which already span every space) are folded
|
|
262
|
+
* in by the caller, not here.
|
|
263
|
+
*/
|
|
264
|
+
export function runMigrationCheck(
|
|
265
|
+
inputs: RunMigrationCheckInputs,
|
|
266
|
+
): Result<MigrationCheckResult, CliStructuredError> {
|
|
267
|
+
const { spaces, spaceFilter } = inputs;
|
|
268
|
+
|
|
269
|
+
if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
|
|
270
|
+
return notOk(errorInvalidSpaceId(spaceFilter));
|
|
271
|
+
}
|
|
272
|
+
if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
|
|
273
|
+
return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const scopedSpaces =
|
|
277
|
+
spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
|
|
278
|
+
|
|
279
|
+
const failures = scopedSpaces.flatMap(checkSpace);
|
|
280
|
+
if (failures.length === 0) {
|
|
281
|
+
return ok({ ok: true, failures: [], summary: 'All checks passed' });
|
|
282
|
+
}
|
|
283
|
+
return ok({ ok: false, failures, summary: `${failures.length} integrity failure(s)` });
|
|
284
|
+
}
|
|
285
|
+
|
|
92
286
|
async function loadAggregateIntegrityViolations(
|
|
93
287
|
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
94
288
|
migrationsDir: string,
|
|
@@ -110,12 +304,18 @@ async function loadAggregateIntegrityViolations(
|
|
|
110
304
|
}
|
|
111
305
|
}
|
|
112
306
|
|
|
307
|
+
interface MigrationCheckOutcome {
|
|
308
|
+
readonly result?: MigrationCheckResult;
|
|
309
|
+
readonly error?: CliStructuredError;
|
|
310
|
+
readonly exitCode: number;
|
|
311
|
+
}
|
|
312
|
+
|
|
113
313
|
async function executeMigrationCheckCommand(
|
|
114
314
|
target: string | undefined,
|
|
115
315
|
options: MigrationCheckOptions,
|
|
116
316
|
flags: GlobalFlags,
|
|
117
317
|
ui: TerminalUI,
|
|
118
|
-
): Promise<
|
|
318
|
+
): Promise<MigrationCheckOutcome> {
|
|
119
319
|
const config = await loadConfig(options.config);
|
|
120
320
|
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
|
|
121
321
|
resolveMigrationPaths(options.config, config);
|
|
@@ -137,133 +337,132 @@ async function executeMigrationCheckCommand(
|
|
|
137
337
|
ui.stderr(header);
|
|
138
338
|
}
|
|
139
339
|
|
|
140
|
-
|
|
340
|
+
if (target) {
|
|
341
|
+
return await checkSingleTarget(target, {
|
|
342
|
+
appMigrationsDir,
|
|
343
|
+
appMigrationsRelative,
|
|
344
|
+
refsDir,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
141
347
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
348
|
+
const loadedAggregate = await buildReadAggregate(config, { migrationsDir });
|
|
349
|
+
if (!loadedAggregate.ok) {
|
|
350
|
+
return { error: loadedAggregate.failure, exitCode: PRECONDITION };
|
|
351
|
+
}
|
|
145
352
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
if (!statSync(entryPath).isDirectory()) continue;
|
|
155
|
-
} catch {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
if (!loadedDirNames.has(entry)) {
|
|
159
|
-
for (const f of ['migration.json', 'ops.json']) {
|
|
160
|
-
const fail = checkFileExists(entryPath, entry, f);
|
|
161
|
-
if (fail) failures.push(fail);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
} catch {
|
|
166
|
-
// migrations dir unreadable — skip
|
|
167
|
-
}
|
|
353
|
+
const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);
|
|
354
|
+
const checkResult = runMigrationCheck({
|
|
355
|
+
spaces,
|
|
356
|
+
...(options.space !== undefined ? { spaceFilter: options.space } : {}),
|
|
357
|
+
});
|
|
358
|
+
if (!checkResult.ok) {
|
|
359
|
+
return { error: checkResult.failure, exitCode: PRECONDITION };
|
|
168
360
|
}
|
|
169
361
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
: `Invalid migration reference: "${target}"`;
|
|
180
|
-
return {
|
|
181
|
-
result: { ok: false, failures: [], summary: msg },
|
|
182
|
-
exitCode: PRECONDITION,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
362
|
+
const failures: CheckFailure[] = [...checkResult.value.failures];
|
|
363
|
+
const allViolations = await loadAggregateIntegrityViolations(config, migrationsDir);
|
|
364
|
+
const scopedViolations =
|
|
365
|
+
options.space === undefined
|
|
366
|
+
? allViolations
|
|
367
|
+
: allViolations.filter((v) => v.kind !== 'disjointness' && v.spaceId === options.space);
|
|
368
|
+
for (const violation of scopedViolations) {
|
|
369
|
+
failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
|
|
370
|
+
}
|
|
185
371
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
ok: false,
|
|
193
|
-
failures: [],
|
|
194
|
-
summary: `Migration package for "${target}" not found on disk`,
|
|
195
|
-
},
|
|
196
|
-
exitCode: PRECONDITION,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
372
|
+
if (failures.length === 0) {
|
|
373
|
+
return {
|
|
374
|
+
result: { ok: true, failures: [], summary: 'All checks passed' },
|
|
375
|
+
exitCode: OK,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
199
378
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
379
|
+
return {
|
|
380
|
+
result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
|
|
381
|
+
exitCode: INTEGRITY_FAILED,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
204
384
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
385
|
+
interface SingleTargetPaths {
|
|
386
|
+
readonly appMigrationsDir: string;
|
|
387
|
+
readonly appMigrationsRelative: string;
|
|
388
|
+
readonly refsDir: string;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Single-target (`check <ref/path>`) mode — app-space only by design (the
|
|
393
|
+
* migration's space is pinned by the reference; multi-space single-target
|
|
394
|
+
* resolution is a deliberate follow-up, see the slice spec § Out of scope).
|
|
395
|
+
* Resolves the one referenced package and verifies its hash / manifest /
|
|
396
|
+
* snapshot, plus the app-space orphan-manifest check the prior behaviour ran.
|
|
397
|
+
*/
|
|
398
|
+
async function checkSingleTarget(
|
|
399
|
+
target: string,
|
|
400
|
+
paths: SingleTargetPaths,
|
|
401
|
+
): Promise<MigrationCheckOutcome> {
|
|
402
|
+
const { appMigrationsDir, appMigrationsRelative, refsDir } = paths;
|
|
403
|
+
const loaded = await readMigrationsDir(appMigrationsDir);
|
|
404
|
+
const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
|
|
405
|
+
const appSpace: CheckSpace = {
|
|
406
|
+
spaceId: 'app',
|
|
407
|
+
packages: bundles,
|
|
408
|
+
refs: await readRefs(refsDir),
|
|
409
|
+
graph: reconstructGraph(bundles),
|
|
410
|
+
migrationsDir: appMigrationsDir,
|
|
411
|
+
refsDir,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const failures: CheckFailure[] = [...checkManifestFilesPresent(appSpace)];
|
|
214
415
|
|
|
215
|
-
|
|
216
|
-
|
|
416
|
+
let matchedPkg: OnDiskMigrationPackage | undefined;
|
|
417
|
+
if (looksLikePath(target)) {
|
|
418
|
+
const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
|
|
419
|
+
if (!resolved.ok) {
|
|
420
|
+
return { error: resolved.failure, exitCode: PRECONDITION };
|
|
421
|
+
}
|
|
422
|
+
matchedPkg = findPackageByDirPath(bundles, resolved.value);
|
|
217
423
|
} else {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
424
|
+
const migResult = parseMigrationRef(target, { graph: appSpace.graph, refs: appSpace.refs });
|
|
425
|
+
if (!migResult.ok) {
|
|
426
|
+
return { error: mapRefResolutionError(migResult.failure), exitCode: PRECONDITION };
|
|
221
427
|
}
|
|
428
|
+
matchedPkg = bundles.find((p) => p.metadata.migrationHash === migResult.value.migrationHash);
|
|
429
|
+
}
|
|
222
430
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
|
|
234
|
-
fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
}
|
|
431
|
+
if (!matchedPkg) {
|
|
432
|
+
return {
|
|
433
|
+
result: {
|
|
434
|
+
ok: false,
|
|
435
|
+
failures: [],
|
|
436
|
+
summary: `Migration package for "${target}" not found on disk`,
|
|
437
|
+
},
|
|
438
|
+
exitCode: PRECONDITION,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
238
441
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
failures.push({
|
|
244
|
-
pnCode: 'PN-MIG-CHECK-004',
|
|
245
|
-
where: relative(process.cwd(), join(refsDir, `${name}.json`)),
|
|
246
|
-
why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
|
|
247
|
-
fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} catch {
|
|
252
|
-
// Refs unreadable — skip ref checks
|
|
253
|
-
}
|
|
442
|
+
for (const f of ['migration.json', 'ops.json']) {
|
|
443
|
+
const fail = checkFileExists(matchedPkg.dirPath, matchedPkg.dirName, f);
|
|
444
|
+
if (fail) failures.push(fail);
|
|
445
|
+
}
|
|
254
446
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
447
|
+
const verification = verifyMigrationHash(matchedPkg);
|
|
448
|
+
if (!verification.ok) {
|
|
449
|
+
failures.push({
|
|
450
|
+
pnCode: 'PN-MIG-CHECK-001',
|
|
451
|
+
where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),
|
|
452
|
+
why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
|
|
453
|
+
fix: 'Re-emit the migration package or restore from version control.',
|
|
454
|
+
});
|
|
258
455
|
}
|
|
259
456
|
|
|
457
|
+
const snapshotFailure = checkSnapshotConsistency(matchedPkg);
|
|
458
|
+
if (snapshotFailure) failures.push(snapshotFailure);
|
|
459
|
+
|
|
260
460
|
if (failures.length === 0) {
|
|
261
461
|
return {
|
|
262
462
|
result: { ok: true, failures: [], summary: 'All checks passed' },
|
|
263
463
|
exitCode: OK,
|
|
264
464
|
};
|
|
265
465
|
}
|
|
266
|
-
|
|
267
466
|
return {
|
|
268
467
|
result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
|
|
269
468
|
exitCode: INTEGRITY_FAILED,
|
|
@@ -277,11 +476,16 @@ export function createMigrationCheckCommand(): Command {
|
|
|
277
476
|
'Verify artifact and graph integrity',
|
|
278
477
|
'Validates that on-disk migration packages are internally consistent\n' +
|
|
279
478
|
'(hashes match, manifests are complete) and that the graph is well-formed\n' +
|
|
280
|
-
'(edges connect, refs point at valid nodes).
|
|
281
|
-
'
|
|
479
|
+
'(edges connect, refs point at valid nodes). The whole-graph check spans\n' +
|
|
480
|
+
'every contract space by default; pass --space <id> to narrow to one, or\n' +
|
|
481
|
+
'a migration reference to check a single app-space package.\n' +
|
|
482
|
+
'Offline — does not consult the database.\n' +
|
|
483
|
+
'Exit codes: 0 = all checks passed, 2 = precondition failed\n' +
|
|
484
|
+
'(unresolved target or unknown --space), 4 = integrity failure(s) found.',
|
|
282
485
|
);
|
|
283
486
|
setCommandExamples(command, [
|
|
284
487
|
'prisma-next migration check',
|
|
488
|
+
'prisma-next migration check --space app',
|
|
285
489
|
'prisma-next migration check 20260101-add-users',
|
|
286
490
|
'prisma-next migration check --json',
|
|
287
491
|
]);
|
|
@@ -289,25 +493,44 @@ export function createMigrationCheckCommand(): Command {
|
|
|
289
493
|
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
290
494
|
{ verb: 'migration list', oneLiner: 'List on-disk migrations' },
|
|
291
495
|
{ verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
|
|
496
|
+
{ verb: 'migration show', oneLiner: 'Display migration package contents' },
|
|
292
497
|
]);
|
|
293
498
|
command.exitOverride();
|
|
294
499
|
addGlobalOptions(command)
|
|
295
|
-
.argument('[
|
|
500
|
+
.argument('[target]', 'Migration reference: directory name, hash/prefix, ref, or path')
|
|
296
501
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
502
|
+
.option('--space <id>', 'Narrow output to a single contract space')
|
|
297
503
|
.action(async (target: string | undefined, options: MigrationCheckOptions) => {
|
|
298
504
|
const flags = parseGlobalFlagsOrExit(options);
|
|
299
505
|
const ui = createTerminalUI(flags);
|
|
300
506
|
|
|
301
|
-
let
|
|
302
|
-
let exitCode: number;
|
|
507
|
+
let outcome: MigrationCheckOutcome;
|
|
303
508
|
try {
|
|
304
|
-
|
|
509
|
+
outcome = await executeMigrationCheckCommand(target, options, flags, ui);
|
|
305
510
|
} catch (error) {
|
|
306
511
|
const msg = error instanceof Error ? error.message : String(error);
|
|
307
|
-
|
|
308
|
-
|
|
512
|
+
outcome = {
|
|
513
|
+
result: { ok: false, failures: [], summary: msg },
|
|
514
|
+
exitCode: PRECONDITION,
|
|
515
|
+
};
|
|
309
516
|
}
|
|
310
517
|
|
|
518
|
+
if (outcome.error) {
|
|
519
|
+
const envelope = outcome.error.toEnvelope();
|
|
520
|
+
if (flags.json) {
|
|
521
|
+
ui.output(formatErrorJson(envelope));
|
|
522
|
+
} else if (!flags.quiet) {
|
|
523
|
+
ui.error(formatErrorOutput(envelope, flags));
|
|
524
|
+
}
|
|
525
|
+
process.exit(outcome.exitCode);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const result = outcome.result ?? {
|
|
529
|
+
ok: false,
|
|
530
|
+
failures: [],
|
|
531
|
+
summary: 'No check result produced',
|
|
532
|
+
};
|
|
533
|
+
|
|
311
534
|
if (flags.json) {
|
|
312
535
|
ui.output(JSON.stringify(result, null, 2));
|
|
313
536
|
} else if (!flags.quiet) {
|
|
@@ -322,7 +545,7 @@ export function createMigrationCheckCommand(): Command {
|
|
|
322
545
|
}
|
|
323
546
|
}
|
|
324
547
|
|
|
325
|
-
process.exit(exitCode);
|
|
548
|
+
process.exit(outcome.exitCode);
|
|
326
549
|
});
|
|
327
550
|
|
|
328
551
|
return command;
|