@prisma-next/cli 0.12.0-dev.9 → 0.13.0-dev.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 +2 -2
- package/dist/cli.mjs +180 -163
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-KgJorIvG.mjs → client-CJzuo5wX.mjs} +222 -107
- package/dist/client-CJzuo5wX.mjs.map +1 -0
- package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-DGMvGBeX.mjs} +318 -25
- package/dist/command-helpers-DGMvGBeX.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +4 -5
- 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 +3 -3
- 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 +6 -6
- 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 +10 -7
- 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 -1
- package/dist/commands/migrate.d.mts +37 -3
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +298 -12
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +55 -13
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +3 -2
- package/dist/commands/migration-graph.d.mts +16 -25
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +185 -2
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +26 -27
- 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 +9 -19
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +1 -137
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +6 -5
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +17 -21
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +24 -36
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +42 -144
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +3 -759
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.d.mts.map +1 -1
- package/dist/commands/ref.mjs +4 -4
- package/dist/commands/ref.mjs.map +1 -1
- 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/{config-loader-B6sJjXTv.mjs → config-loader-p9JMrekQ.mjs} +1 -1
- package/dist/{config-loader-B6sJjXTv.mjs.map → config-loader-p9JMrekQ.mjs.map} +1 -1
- package/dist/config-loader.mjs +1 -1
- package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-CFXsstzm.mjs} +2 -2
- package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-CFXsstzm.mjs.map} +1 -1
- package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-B_qriF8B.mjs} +5 -5
- package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-B_qriF8B.mjs.map} +1 -1
- package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-C8HmtboH.mjs} +12 -7
- package/dist/contract-emit-C8HmtboH.mjs.map +1 -0
- package/dist/{contract-enrichment-a0V5Y_mL.mjs → contract-enrichment-gn9sWbPw.mjs} +1 -1
- package/dist/{contract-enrichment-a0V5Y_mL.mjs.map → contract-enrichment-gn9sWbPw.mjs.map} +1 -1
- package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-BYT_ra_U.mjs} +5 -5
- package/dist/contract-infer-BYT_ra_U.mjs.map +1 -0
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-ClI1KN6d.mjs} +5 -5
- package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-ClI1KN6d.mjs.map} +1 -1
- package/dist/{db-verify-v_vUKXTU.mjs → db-verify-C24FKhb7.mjs} +6 -6
- package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-C24FKhb7.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +5 -3
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +3 -3
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +1 -3
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{extension-pack-inputs-IDvjRCi3.mjs → extension-pack-inputs-1ySHqxKG.mjs} +1 -1
- package/dist/{extension-pack-inputs-IDvjRCi3.mjs.map → extension-pack-inputs-1ySHqxKG.mjs.map} +1 -1
- package/dist/{framework-components-fYXjz_in.mjs → framework-components-YVQHhPH7.mjs} +2 -2
- package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-YVQHhPH7.mjs.map} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-BpoOYtNZ.d.mts} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-BpoOYtNZ.d.mts.map} +1 -1
- package/dist/{init-Cv9UzWL5.mjs → init-0HwB-Vh8.mjs} +5 -58
- package/dist/init-0HwB-Vh8.mjs.map +1 -0
- package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-DF6IwcDl.mjs} +7 -5
- package/dist/inspect-live-schema-DF6IwcDl.mjs.map +1 -0
- package/dist/migration-check-soB5uZEQ.mjs +573 -0
- package/dist/migration-check-soB5uZEQ.mjs.map +1 -0
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-DA-Lhx6o.mjs} +5 -5
- package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-DA-Lhx6o.mjs.map} +1 -1
- package/dist/migration-graph-command-render-CEez7YUK.mjs +1960 -0
- package/dist/migration-graph-command-render-CEez7YUK.mjs.map +1 -0
- package/dist/migration-list-DlJJ_38Z.mjs +230 -0
- package/dist/migration-list-DlJJ_38Z.mjs.map +1 -0
- package/dist/migration-log-CG0qQAFm.mjs +222 -0
- package/dist/migration-log-CG0qQAFm.mjs.map +1 -0
- package/dist/migration-path-target-Ce6OZImp.mjs +38 -0
- package/dist/migration-path-target-Ce6OZImp.mjs.map +1 -0
- package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-z5Ing-TD.mjs} +9 -8
- package/dist/migration-plan-z5Ing-TD.mjs.map +1 -0
- package/dist/migration-status-CgWSoI_g.mjs +446 -0
- package/dist/migration-status-CgWSoI_g.mjs.map +1 -0
- package/dist/{output-B60Gw5fu.mjs → output-mEQ74_nd.mjs} +1 -1
- package/dist/{output-B60Gw5fu.mjs.map → output-mEQ74_nd.mjs.map} +1 -1
- package/dist/{progress-adapter-C644QK8l.mjs → progress-adapter-CjAeTxY_.mjs} +1 -1
- package/dist/{progress-adapter-C644QK8l.mjs.map → progress-adapter-CjAeTxY_.mjs.map} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-BkXlikCA.mjs} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-BkXlikCA.mjs.map} +1 -1
- package/dist/schemas-CeGMYFYX.d.mts +191 -0
- package/dist/schemas-CeGMYFYX.d.mts.map +1 -0
- package/dist/schemas-KhXMzNA_.mjs +112 -0
- package/dist/schemas-KhXMzNA_.mjs.map +1 -0
- package/dist/telemetry-BIM4beEO.mjs +122 -0
- package/dist/telemetry-BIM4beEO.mjs.map +1 -0
- package/dist/{terminal-ui-5Y6mrg93.d.mts → terminal-ui-DGRNFWna.d.mts} +1 -1
- package/dist/terminal-ui-DGRNFWna.d.mts.map +1 -0
- package/dist/{types-Dt_SfqFm.d.mts → types-C_tYiJYx.d.mts} +53 -31
- package/dist/types-C_tYiJYx.d.mts.map +1 -0
- package/dist/{verify-DCA9Sldu.mjs → verify-DcOYZ1tH.mjs} +2 -2
- package/dist/{verify-DCA9Sldu.mjs.map → verify-DcOYZ1tH.mjs.map} +1 -1
- package/package.json +26 -22
- package/src/cli.ts +5 -0
- package/src/commands/contract-infer.ts +2 -2
- 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/inspect-live-schema.ts +10 -0
- package/src/commands/json/schemas.ts +195 -0
- package/src/commands/migrate.ts +527 -8
- package/src/commands/migration-check.ts +469 -134
- package/src/commands/migration-graph.ts +151 -119
- package/src/commands/migration-list.ts +72 -39
- package/src/commands/migration-log.ts +52 -102
- package/src/commands/migration-new.ts +2 -1
- package/src/commands/migration-plan.ts +2 -1
- package/src/commands/migration-show.ts +31 -66
- package/src/commands/migration-status-overlay.ts +61 -0
- package/src/commands/migration-status.ts +458 -1066
- package/src/commands/telemetry/index.ts +107 -0
- package/src/commands/telemetry/status.ts +67 -0
- package/src/control-api/client.ts +70 -9
- package/src/control-api/operations/contract-emit.ts +22 -2
- package/src/control-api/operations/db-init.ts +6 -3
- package/src/control-api/operations/{db-apply.ts → db-run.ts} +55 -14
- package/src/control-api/operations/db-update.ts +7 -4
- package/src/control-api/operations/db-verify.ts +15 -5
- package/src/control-api/operations/{migration-apply.ts → migrate.ts} +181 -80
- package/src/control-api/operations/{apply.ts → run-migration.ts} +33 -27
- package/src/control-api/types.ts +56 -29
- package/src/utils/cli-errors.ts +70 -2
- package/src/utils/formatters/errors.ts +11 -0
- package/src/utils/formatters/migration-graph-command-render.ts +239 -0
- package/src/utils/formatters/migration-graph-grid-layout.ts +1134 -0
- package/src/utils/formatters/migration-graph-labels.ts +408 -0
- package/src/utils/formatters/migration-graph-model.ts +103 -0
- package/src/utils/formatters/migration-graph-occlusion-render.ts +258 -0
- package/src/utils/formatters/migration-graph-rows.ts +128 -15
- package/src/utils/formatters/migration-graph-space-render.ts +188 -0
- package/src/utils/formatters/migration-list-data-column.ts +4 -91
- package/src/utils/formatters/migration-list-graph-topology.ts +72 -94
- package/src/utils/formatters/migration-list-render.ts +135 -71
- package/src/utils/formatters/migration-list-styler.ts +46 -5
- package/src/utils/formatters/migration-list-types.ts +5 -21
- package/src/utils/formatters/migration-log-table.ts +205 -0
- package/src/utils/formatters/migrations.ts +33 -11
- package/src/utils/global-flags.ts +35 -0
- package/src/utils/integrity-violation-to-check-failure.ts +28 -19
- package/src/utils/legend.ts +38 -0
- package/src/utils/migration-path-target.ts +60 -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/contract-emit-D-4jrNve.mjs.map +0 -1
- package/dist/contract-infer-D8uEbJuu.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/inspect-live-schema-C6ohV_oQ.mjs.map +0 -1
- package/dist/migration-check-BiBJoYYW.mjs +0 -341
- package/dist/migration-check-BiBJoYYW.mjs.map +0 -1
- package/dist/migration-graph-C9WC-7eO.mjs +0 -1478
- package/dist/migration-graph-C9WC-7eO.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-plan-9DJ7q7_z.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/terminal-ui-5Y6mrg93.d.mts.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
- package/src/utils/formatters/migration-graph-lane-colors.ts +0 -31
- package/src/utils/formatters/migration-graph-layout.ts +0 -1141
- package/src/utils/formatters/migration-graph-tree-render.ts +0 -768
|
@@ -1,17 +1,38 @@
|
|
|
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
|
-
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
8
|
-
import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
|
|
9
11
|
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
+
import {
|
|
13
|
+
parseMigrationRef,
|
|
14
|
+
type RefResolutionError,
|
|
15
|
+
} from '@prisma-next/migration-tools/ref-resolution';
|
|
16
|
+
import type { Refs } 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 { ifDefined } from '@prisma-next/utils/defined';
|
|
25
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
12
26
|
import { Command } from 'commander';
|
|
13
27
|
import { join, relative } from 'pathe';
|
|
14
28
|
import { loadConfig } from '../config-loader';
|
|
29
|
+
import {
|
|
30
|
+
type CliStructuredError,
|
|
31
|
+
errorAmbiguousMigrationRef,
|
|
32
|
+
errorInvalidSpaceId,
|
|
33
|
+
errorSpaceNotFound,
|
|
34
|
+
mapRefResolutionError,
|
|
35
|
+
} from '../utils/cli-errors';
|
|
15
36
|
import {
|
|
16
37
|
addGlobalOptions,
|
|
17
38
|
resolveContractPath,
|
|
@@ -20,28 +41,30 @@ import {
|
|
|
20
41
|
setCommandExamples,
|
|
21
42
|
setCommandSeeAlso,
|
|
22
43
|
} from '../utils/command-helpers';
|
|
44
|
+
import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
|
|
23
45
|
import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
|
|
46
|
+
import { formatErrorJson, formatErrorOutput } from '../utils/formatters/errors';
|
|
24
47
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
25
48
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
26
49
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
50
|
+
import { integrityViolationToCheckFailure } from '../utils/integrity-violation-to-check-failure';
|
|
27
51
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
findPackageByDirPath,
|
|
53
|
+
looksLikePath,
|
|
54
|
+
resolveAppTargetPath,
|
|
55
|
+
resolveTargetPathAcrossSpaces,
|
|
56
|
+
} from '../utils/migration-path-target';
|
|
31
57
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
58
|
+
import type { CheckFailure, MigrationCheckResult } from './json/schemas';
|
|
32
59
|
import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
|
|
33
60
|
|
|
34
61
|
interface MigrationCheckOptions extends CommonCommandOptions {
|
|
35
62
|
readonly config?: string;
|
|
63
|
+
readonly space?: string;
|
|
36
64
|
}
|
|
37
65
|
|
|
38
|
-
export type { CheckFailure } from '
|
|
39
|
-
|
|
40
|
-
export interface MigrationCheckResult {
|
|
41
|
-
readonly ok: boolean;
|
|
42
|
-
readonly failures: readonly CheckFailure[];
|
|
43
|
-
readonly summary: string;
|
|
44
|
-
}
|
|
66
|
+
export type { CheckFailure, MigrationCheckResult } from './json/schemas';
|
|
67
|
+
export { migrationCheckResultSchema } from './json/schemas';
|
|
45
68
|
|
|
46
69
|
function migrationPathRelative(dirPath: string): string {
|
|
47
70
|
return relative(process.cwd(), dirPath);
|
|
@@ -51,10 +74,16 @@ function migrationFileRelative(dirPath: string, fileName: string): string {
|
|
|
51
74
|
return join(migrationPathRelative(dirPath), fileName);
|
|
52
75
|
}
|
|
53
76
|
|
|
54
|
-
function checkFileExists(
|
|
77
|
+
function checkFileExists(
|
|
78
|
+
spaceId: string,
|
|
79
|
+
dirPath: string,
|
|
80
|
+
dirName: string,
|
|
81
|
+
fileName: string,
|
|
82
|
+
): CheckFailure | null {
|
|
55
83
|
if (!existsSync(join(dirPath, fileName))) {
|
|
56
84
|
return {
|
|
57
|
-
|
|
85
|
+
space: spaceId,
|
|
86
|
+
code: 'PN-MIG-CHECK-002',
|
|
58
87
|
where: migrationFileRelative(dirPath, fileName),
|
|
59
88
|
why: `${fileName} is missing from ${dirName}`,
|
|
60
89
|
fix: 'Re-emit the migration package or restore from version control.',
|
|
@@ -63,7 +92,10 @@ function checkFileExists(dirPath: string, dirName: string, fileName: string): Ch
|
|
|
63
92
|
return null;
|
|
64
93
|
}
|
|
65
94
|
|
|
66
|
-
function checkSnapshotConsistency(
|
|
95
|
+
function checkSnapshotConsistency(
|
|
96
|
+
spaceId: string,
|
|
97
|
+
pkg: OnDiskMigrationPackage,
|
|
98
|
+
): CheckFailure | null {
|
|
67
99
|
const endContractPath = join(pkg.dirPath, 'end-contract.json');
|
|
68
100
|
if (!existsSync(endContractPath)) return null;
|
|
69
101
|
try {
|
|
@@ -72,7 +104,8 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
|
|
|
72
104
|
const snapshotHash = storage?.['storageHash'];
|
|
73
105
|
if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {
|
|
74
106
|
return {
|
|
75
|
-
|
|
107
|
+
space: spaceId,
|
|
108
|
+
code: 'PN-MIG-CHECK-005',
|
|
76
109
|
where: migrationPathRelative(pkg.dirPath),
|
|
77
110
|
why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but end-contract.json has storageHash=${snapshotHash}`,
|
|
78
111
|
fix: 'Re-emit the migration package so migration.json and end-contract.json agree.',
|
|
@@ -80,7 +113,8 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
|
|
|
80
113
|
}
|
|
81
114
|
} catch {
|
|
82
115
|
return {
|
|
83
|
-
|
|
116
|
+
space: spaceId,
|
|
117
|
+
code: 'PN-MIG-CHECK-006',
|
|
84
118
|
where: migrationPathRelative(pkg.dirPath),
|
|
85
119
|
why: `Migration "${pkg.dirName}" has an unparseable end-contract.json.`,
|
|
86
120
|
fix: 'Re-emit the migration package to repair the snapshot file.',
|
|
@@ -89,6 +123,177 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
|
|
|
89
123
|
return null;
|
|
90
124
|
}
|
|
91
125
|
|
|
126
|
+
/**
|
|
127
|
+
* One contract space's on-disk state, resolved for the explicit graph
|
|
128
|
+
* checks `runMigrationCheck` runs per space: the space's migration
|
|
129
|
+
* packages, its user-authored refs, its induced graph, and the absolute
|
|
130
|
+
* `migrations/<space>/` + `migrations/<space>/refs/` directories the
|
|
131
|
+
* file-existence and dangling-ref `where` paths are derived from.
|
|
132
|
+
*/
|
|
133
|
+
export interface CheckSpace {
|
|
134
|
+
readonly spaceId: string;
|
|
135
|
+
readonly packages: readonly OnDiskMigrationPackage[];
|
|
136
|
+
readonly refs: Refs;
|
|
137
|
+
readonly graph: MigrationGraph;
|
|
138
|
+
readonly migrationsDir: string;
|
|
139
|
+
readonly refsDir: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Project the loaded {@link ContractSpaceAggregate} into the
|
|
144
|
+
* {@link CheckSpace} rows the multi-space check iterates — one per on-disk
|
|
145
|
+
* contract-space directory, in the aggregate's `app`-first ordering. Mirrors
|
|
146
|
+
* `migration list`'s `migrationSpaceListEntriesFromAggregate`: space
|
|
147
|
+
* membership matches the on-disk directories, package / ref / graph data come
|
|
148
|
+
* from `aggregate.space(id)`.
|
|
149
|
+
*/
|
|
150
|
+
export async function enumerateCheckSpaces(
|
|
151
|
+
aggregate: ContractSpaceAggregate,
|
|
152
|
+
projectMigrationsDir: string,
|
|
153
|
+
): Promise<readonly CheckSpace[]> {
|
|
154
|
+
const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
|
|
155
|
+
const onDiskSpaceIds = new Set(
|
|
156
|
+
candidateDirs.filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name)).filter(isValidSpaceId),
|
|
157
|
+
);
|
|
158
|
+
const spaces: CheckSpace[] = [];
|
|
159
|
+
for (const member of aggregate.spaces()) {
|
|
160
|
+
const spaceId = member.spaceId;
|
|
161
|
+
if (!isValidSpaceId(spaceId)) continue;
|
|
162
|
+
if (!onDiskSpaceIds.has(spaceId)) continue;
|
|
163
|
+
const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);
|
|
164
|
+
spaces.push({
|
|
165
|
+
spaceId,
|
|
166
|
+
packages: member.packages,
|
|
167
|
+
refs: member.refs,
|
|
168
|
+
graph: member.graph(),
|
|
169
|
+
migrationsDir,
|
|
170
|
+
refsDir: spaceRefsDirectory(migrationsDir),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return spaces;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function checkManifestFilesPresent(space: CheckSpace): readonly CheckFailure[] {
|
|
177
|
+
if (!existsSync(space.migrationsDir)) return [];
|
|
178
|
+
const loadedDirNames = new Set(space.packages.map((p) => p.dirName));
|
|
179
|
+
const failures: CheckFailure[] = [];
|
|
180
|
+
let entries: string[];
|
|
181
|
+
try {
|
|
182
|
+
entries = readdirSync(space.migrationsDir);
|
|
183
|
+
} catch {
|
|
184
|
+
return failures;
|
|
185
|
+
}
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
if (entry.startsWith('.') || entry.startsWith('_') || entry === 'refs') continue;
|
|
188
|
+
const entryPath = join(space.migrationsDir, entry);
|
|
189
|
+
try {
|
|
190
|
+
if (!statSync(entryPath).isDirectory()) continue;
|
|
191
|
+
} catch {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (!loadedDirNames.has(entry)) {
|
|
195
|
+
for (const f of ['migration.json', 'ops.json']) {
|
|
196
|
+
const fail = checkFileExists(space.spaceId, entryPath, entry, f);
|
|
197
|
+
if (fail) failures.push(fail);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return failures;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function checkReachability(space: CheckSpace): readonly CheckFailure[] {
|
|
205
|
+
const allToHashes = new Set(space.packages.map((p) => p.metadata.to));
|
|
206
|
+
const failures: CheckFailure[] = [];
|
|
207
|
+
for (const pkg of space.packages) {
|
|
208
|
+
const isReachable =
|
|
209
|
+
pkg.metadata.from === null ||
|
|
210
|
+
allToHashes.has(pkg.metadata.from) ||
|
|
211
|
+
pkg.metadata.from === 'sha256:empty';
|
|
212
|
+
if (!isReachable) {
|
|
213
|
+
failures.push({
|
|
214
|
+
space: space.spaceId,
|
|
215
|
+
code: 'PN-MIG-CHECK-003',
|
|
216
|
+
where: migrationPathRelative(pkg.dirPath),
|
|
217
|
+
why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
|
|
218
|
+
fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return failures;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {
|
|
226
|
+
const failures: CheckFailure[] = [];
|
|
227
|
+
for (const [name, entry] of Object.entries(space.refs)) {
|
|
228
|
+
if (!space.graph.nodes.has(entry.hash)) {
|
|
229
|
+
failures.push({
|
|
230
|
+
space: space.spaceId,
|
|
231
|
+
code: 'PN-MIG-CHECK-004',
|
|
232
|
+
where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),
|
|
233
|
+
why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
|
|
234
|
+
fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return failures;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function checkSpace(space: CheckSpace): readonly CheckFailure[] {
|
|
242
|
+
return [
|
|
243
|
+
...checkManifestFilesPresent(space),
|
|
244
|
+
...space.packages
|
|
245
|
+
.map((pkg) => checkSnapshotConsistency(space.spaceId, pkg))
|
|
246
|
+
.filter((f): f is CheckFailure => f !== null),
|
|
247
|
+
...checkReachability(space),
|
|
248
|
+
...checkDanglingRefs(space),
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Inputs for {@link runMigrationCheck} — the multi-space policy core of
|
|
254
|
+
* the holistic (no-arg) `migration check`. Enumeration is supplied by the
|
|
255
|
+
* caller (the CLI shell builds it from {@link enumerateCheckSpaces}); the
|
|
256
|
+
* core does not touch config, flags, or streams.
|
|
257
|
+
*/
|
|
258
|
+
export interface RunMigrationCheckInputs {
|
|
259
|
+
readonly spaces: readonly CheckSpace[];
|
|
260
|
+
readonly spaceFilter?: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Policy core of the holistic `migration check`: validates `--space`,
|
|
265
|
+
* narrows the pre-enumerated spaces, and runs the per-space explicit graph
|
|
266
|
+
* checks (file-existence, snapshot consistency, reachability, dangling
|
|
267
|
+
* refs), aggregating every failure into one {@link MigrationCheckResult}.
|
|
268
|
+
*
|
|
269
|
+
* `--space` validation mirrors `migration list`: an invalid id →
|
|
270
|
+
* {@link errorInvalidSpaceId}; an id with no on-disk space →
|
|
271
|
+
* {@link errorSpaceNotFound}. Both map to exit `PRECONDITION` at the shell.
|
|
272
|
+
* Aggregate-integrity violations (which already span every space) are folded
|
|
273
|
+
* in by the caller, not here.
|
|
274
|
+
*/
|
|
275
|
+
export function runMigrationCheck(
|
|
276
|
+
inputs: RunMigrationCheckInputs,
|
|
277
|
+
): Result<MigrationCheckResult, CliStructuredError> {
|
|
278
|
+
const { spaces, spaceFilter } = inputs;
|
|
279
|
+
|
|
280
|
+
if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
|
|
281
|
+
return notOk(errorInvalidSpaceId(spaceFilter));
|
|
282
|
+
}
|
|
283
|
+
if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
|
|
284
|
+
return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const scopedSpaces =
|
|
288
|
+
spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
|
|
289
|
+
|
|
290
|
+
const failures = scopedSpaces.flatMap(checkSpace);
|
|
291
|
+
if (failures.length === 0) {
|
|
292
|
+
return ok({ ok: true, failures: [], summary: 'All checks passed' });
|
|
293
|
+
}
|
|
294
|
+
return ok({ ok: false, failures, summary: `${failures.length} integrity failure(s)` });
|
|
295
|
+
}
|
|
296
|
+
|
|
92
297
|
async function loadAggregateIntegrityViolations(
|
|
93
298
|
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
94
299
|
migrationsDir: string,
|
|
@@ -110,14 +315,21 @@ async function loadAggregateIntegrityViolations(
|
|
|
110
315
|
}
|
|
111
316
|
}
|
|
112
317
|
|
|
318
|
+
interface MigrationCheckOutcome {
|
|
319
|
+
readonly result?: MigrationCheckResult;
|
|
320
|
+
readonly error?: CliStructuredError;
|
|
321
|
+
readonly exitCode: number;
|
|
322
|
+
readonly resolvedSpaceId?: string;
|
|
323
|
+
}
|
|
324
|
+
|
|
113
325
|
async function executeMigrationCheckCommand(
|
|
114
326
|
target: string | undefined,
|
|
115
327
|
options: MigrationCheckOptions,
|
|
116
328
|
flags: GlobalFlags,
|
|
117
329
|
ui: TerminalUI,
|
|
118
|
-
): Promise<
|
|
330
|
+
): Promise<MigrationCheckOutcome> {
|
|
119
331
|
const config = await loadConfig(options.config);
|
|
120
|
-
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative
|
|
332
|
+
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
|
|
121
333
|
resolveMigrationPaths(options.config, config);
|
|
122
334
|
|
|
123
335
|
if (!flags.json && !flags.quiet) {
|
|
@@ -137,136 +349,231 @@ async function executeMigrationCheckCommand(
|
|
|
137
349
|
ui.stderr(header);
|
|
138
350
|
}
|
|
139
351
|
|
|
140
|
-
const
|
|
352
|
+
const loadedAggregate = await buildReadAggregate(config, { migrationsDir });
|
|
353
|
+
if (!loadedAggregate.ok) {
|
|
354
|
+
return { error: loadedAggregate.failure, exitCode: PRECONDITION };
|
|
355
|
+
}
|
|
141
356
|
|
|
142
|
-
const
|
|
143
|
-
const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
|
|
144
|
-
const graph = reconstructGraph(bundles);
|
|
357
|
+
const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);
|
|
145
358
|
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
359
|
+
if (target) {
|
|
360
|
+
return await checkSingleTarget(target, {
|
|
361
|
+
spaces,
|
|
362
|
+
...(options.space !== undefined ? { spaceFilter: options.space } : {}),
|
|
363
|
+
appMigrationsDir,
|
|
364
|
+
appMigrationsRelative,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const checkResult = runMigrationCheck({
|
|
369
|
+
spaces,
|
|
370
|
+
...(options.space !== undefined ? { spaceFilter: options.space } : {}),
|
|
371
|
+
});
|
|
372
|
+
if (!checkResult.ok) {
|
|
373
|
+
return { error: checkResult.failure, exitCode: PRECONDITION };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const failures: CheckFailure[] = [...checkResult.value.failures];
|
|
377
|
+
const allViolations = await loadAggregateIntegrityViolations(config, migrationsDir);
|
|
378
|
+
const scopedViolations =
|
|
379
|
+
options.space === undefined
|
|
380
|
+
? allViolations
|
|
381
|
+
: allViolations.filter((v) => v.kind !== 'disjointness' && v.spaceId === options.space);
|
|
382
|
+
for (const violation of scopedViolations) {
|
|
383
|
+
failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (failures.length === 0) {
|
|
387
|
+
return {
|
|
388
|
+
result: { ok: true, failures: [], summary: 'All checks passed' },
|
|
389
|
+
exitCode: OK,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
|
|
395
|
+
exitCode: INTEGRITY_FAILED,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
interface SingleTargetInputs {
|
|
400
|
+
readonly spaces: readonly CheckSpace[];
|
|
401
|
+
readonly spaceFilter?: string;
|
|
402
|
+
readonly appMigrationsDir: string;
|
|
403
|
+
readonly appMigrationsRelative: string;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Ranks ref-resolution failure kinds by how informative they are, so a
|
|
408
|
+
* single-target check surfaces the most useful failure across spaces instead of
|
|
409
|
+
* whichever space failed first. `not-found` (the input matched nothing here)
|
|
410
|
+
* says the least; a malformed input, a wrong grammar, or an in-space ambiguity
|
|
411
|
+
* all say more.
|
|
412
|
+
*/
|
|
413
|
+
function refFailureSpecificity(error: RefResolutionError): number {
|
|
414
|
+
switch (error.kind) {
|
|
415
|
+
case 'wrong-grammar':
|
|
416
|
+
return 3;
|
|
417
|
+
case 'ambiguous':
|
|
418
|
+
return 2;
|
|
419
|
+
case 'invalid-format':
|
|
420
|
+
return 1;
|
|
421
|
+
case 'not-found':
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Single-target (`check <ref/path>`) mode — resolves a migration reference
|
|
428
|
+
* across all contract spaces (or the one space narrowed by `--space <id>`).
|
|
429
|
+
*
|
|
430
|
+
* Resolution:
|
|
431
|
+
* - filesystem path → find the owning space by checking which space's
|
|
432
|
+
* `migrationsDir` contains the resolved path; falls back to app-relative
|
|
433
|
+
* validation when the path is outside every space dir.
|
|
434
|
+
* - ref → `parseMigrationRef` against each in-scope space; collect every
|
|
435
|
+
* (space, package) hit; 0 hits = not-found, 1 = check it, >1 = ambiguity
|
|
436
|
+
* error (qualify with `--space`).
|
|
437
|
+
*
|
|
438
|
+
* `--space <id>` is validated the same way the holistic path does it:
|
|
439
|
+
* invalid id → `errorInvalidSpaceId`; no on-disk space → `errorSpaceNotFound`.
|
|
440
|
+
*/
|
|
441
|
+
async function checkSingleTarget(
|
|
442
|
+
target: string,
|
|
443
|
+
inputs: SingleTargetInputs,
|
|
444
|
+
): Promise<MigrationCheckOutcome> {
|
|
445
|
+
const { spaces, spaceFilter, appMigrationsDir, appMigrationsRelative } = inputs;
|
|
446
|
+
|
|
447
|
+
if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
|
|
448
|
+
return { error: errorInvalidSpaceId(spaceFilter), exitCode: PRECONDITION };
|
|
449
|
+
}
|
|
450
|
+
if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
|
|
451
|
+
return {
|
|
452
|
+
error: errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()),
|
|
453
|
+
exitCode: PRECONDITION,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const scopedSpaces =
|
|
458
|
+
spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
|
|
459
|
+
|
|
460
|
+
let matchedSpace: CheckSpace | undefined;
|
|
461
|
+
let matchedPkg: OnDiskMigrationPackage | undefined;
|
|
462
|
+
|
|
463
|
+
if (looksLikePath(target)) {
|
|
464
|
+
const resolvedPath = resolveTargetPathAcrossSpaces(target, scopedSpaces);
|
|
465
|
+
if (resolvedPath !== null) {
|
|
466
|
+
for (const space of scopedSpaces) {
|
|
467
|
+
const found = findPackageByDirPath(space.packages, resolvedPath);
|
|
468
|
+
if (found) {
|
|
469
|
+
matchedSpace = space;
|
|
470
|
+
matchedPkg = found;
|
|
471
|
+
break;
|
|
157
472
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
// Path outside every space dir — fall back to app-relative validation
|
|
476
|
+
const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
|
|
477
|
+
if (!resolved.ok) {
|
|
478
|
+
return { error: resolved.failure, exitCode: PRECONDITION };
|
|
479
|
+
}
|
|
480
|
+
const appSpace = scopedSpaces.find((s) => s.spaceId === 'app');
|
|
481
|
+
if (appSpace) {
|
|
482
|
+
matchedSpace = appSpace;
|
|
483
|
+
matchedPkg = findPackageByDirPath(appSpace.packages, resolved.value);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
// Ref resolution: try each in-scope space, collect all hits.
|
|
488
|
+
const hits: Array<{ space: CheckSpace; pkg: OnDiskMigrationPackage }> = [];
|
|
489
|
+
let bestParseFailure: RefResolutionError | undefined;
|
|
490
|
+
for (const space of scopedSpaces) {
|
|
491
|
+
const migResult = parseMigrationRef(target, { graph: space.graph, refs: space.refs });
|
|
492
|
+
if (!migResult.ok) {
|
|
493
|
+
// Keep scanning — a later space may hold a hit that must not be discarded.
|
|
494
|
+
// When no space yields a hit, keep the most informative failure rather than
|
|
495
|
+
// whichever space failed first (the kind is space-dependent).
|
|
496
|
+
if (
|
|
497
|
+
bestParseFailure === undefined ||
|
|
498
|
+
refFailureSpecificity(migResult.failure) > refFailureSpecificity(bestParseFailure)
|
|
499
|
+
) {
|
|
500
|
+
bestParseFailure = migResult.failure;
|
|
163
501
|
}
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
const pkg = space.packages.find(
|
|
505
|
+
(p) => p.metadata.migrationHash === migResult.value.migrationHash,
|
|
506
|
+
);
|
|
507
|
+
if (pkg) {
|
|
508
|
+
hits.push({ space, pkg });
|
|
164
509
|
}
|
|
165
|
-
} catch {
|
|
166
|
-
// migrations dir unreadable — skip
|
|
167
510
|
}
|
|
168
|
-
}
|
|
169
511
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const migResult = parseMigrationRef(target, { graph, refs });
|
|
173
|
-
if (!migResult.ok) {
|
|
174
|
-
const msg =
|
|
175
|
-
migResult.failure.kind === 'not-found'
|
|
176
|
-
? `Migration "${target}" does not exist`
|
|
177
|
-
: migResult.failure.kind === 'wrong-grammar'
|
|
178
|
-
? migResult.failure.message
|
|
179
|
-
: `Invalid migration reference: "${target}"`;
|
|
512
|
+
if (hits.length > 1) {
|
|
513
|
+
const spaceIds = hits.map((h) => h.space.spaceId);
|
|
180
514
|
return {
|
|
181
|
-
|
|
515
|
+
error: errorAmbiguousMigrationRef(target, spaceIds),
|
|
182
516
|
exitCode: PRECONDITION,
|
|
183
517
|
};
|
|
184
518
|
}
|
|
185
519
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
},
|
|
196
|
-
exitCode: PRECONDITION,
|
|
197
|
-
};
|
|
520
|
+
if (hits.length === 1) {
|
|
521
|
+
matchedSpace = hits[0]!.space;
|
|
522
|
+
matchedPkg = hits[0]!.pkg;
|
|
523
|
+
} else if (bestParseFailure !== undefined) {
|
|
524
|
+
// The ref didn't resolve in any in-scope space — surface the most informative
|
|
525
|
+
// parse failure through the shared ref-resolution envelope (PN-RUN-3000) the
|
|
526
|
+
// earlier work established, rather than a bespoke string. (Ref-resolved-but-
|
|
527
|
+
// no-package falls through to the "not found on disk" result below.)
|
|
528
|
+
return { error: mapRefResolutionError(bestParseFailure), exitCode: PRECONDITION };
|
|
198
529
|
}
|
|
530
|
+
}
|
|
199
531
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
532
|
+
if (!matchedPkg || !matchedSpace) {
|
|
533
|
+
return {
|
|
534
|
+
result: {
|
|
535
|
+
ok: false,
|
|
536
|
+
failures: [],
|
|
537
|
+
summary: `Migration package for "${target}" not found on disk`,
|
|
538
|
+
},
|
|
539
|
+
exitCode: PRECONDITION,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
204
542
|
|
|
205
|
-
|
|
206
|
-
if (!verification.ok) {
|
|
207
|
-
failures.push({
|
|
208
|
-
pnCode: 'PN-MIG-CHECK-001',
|
|
209
|
-
where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),
|
|
210
|
-
why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
|
|
211
|
-
fix: 'Re-emit the migration package or restore from version control.',
|
|
212
|
-
});
|
|
213
|
-
}
|
|
543
|
+
const failures: CheckFailure[] = [...checkManifestFilesPresent(matchedSpace)];
|
|
214
544
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const snapshotFailure = checkSnapshotConsistency(pkg);
|
|
220
|
-
if (snapshotFailure) failures.push(snapshotFailure);
|
|
221
|
-
}
|
|
545
|
+
for (const f of ['migration.json', 'ops.json']) {
|
|
546
|
+
const fail = checkFileExists(matchedSpace.spaceId, matchedPkg.dirPath, matchedPkg.dirName, f);
|
|
547
|
+
if (fail) failures.push(fail);
|
|
548
|
+
}
|
|
222
549
|
|
|
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
|
-
}
|
|
550
|
+
const verification = verifyMigrationHash(matchedPkg);
|
|
551
|
+
if (!verification.ok) {
|
|
552
|
+
failures.push({
|
|
553
|
+
space: matchedSpace.spaceId,
|
|
554
|
+
code: 'PN-MIG-CHECK-001',
|
|
555
|
+
where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),
|
|
556
|
+
why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
|
|
557
|
+
fix: 'Re-emit the migration package or restore from version control.',
|
|
558
|
+
});
|
|
559
|
+
}
|
|
238
560
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
for (const [name, entry] of Object.entries(refs)) {
|
|
242
|
-
if (!graph.nodes.has(entry.hash)) {
|
|
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
|
-
}
|
|
561
|
+
const snapshotFailure = checkSnapshotConsistency(matchedSpace.spaceId, matchedPkg);
|
|
562
|
+
if (snapshotFailure) failures.push(snapshotFailure);
|
|
254
563
|
|
|
255
|
-
|
|
256
|
-
failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
|
|
257
|
-
}
|
|
258
|
-
}
|
|
564
|
+
const resolvedSpaceId = matchedSpace.spaceId !== 'app' ? matchedSpace.spaceId : undefined;
|
|
259
565
|
|
|
260
566
|
if (failures.length === 0) {
|
|
261
567
|
return {
|
|
262
568
|
result: { ok: true, failures: [], summary: 'All checks passed' },
|
|
263
569
|
exitCode: OK,
|
|
570
|
+
...ifDefined('resolvedSpaceId', resolvedSpaceId),
|
|
264
571
|
};
|
|
265
572
|
}
|
|
266
|
-
|
|
267
573
|
return {
|
|
268
574
|
result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
|
|
269
575
|
exitCode: INTEGRITY_FAILED,
|
|
576
|
+
...ifDefined('resolvedSpaceId', resolvedSpaceId),
|
|
270
577
|
};
|
|
271
578
|
}
|
|
272
579
|
|
|
@@ -277,52 +584,80 @@ export function createMigrationCheckCommand(): Command {
|
|
|
277
584
|
'Verify artifact and graph integrity',
|
|
278
585
|
'Validates that on-disk migration packages are internally consistent\n' +
|
|
279
586
|
'(hashes match, manifests are complete) and that the graph is well-formed\n' +
|
|
280
|
-
'(edges connect, refs point at valid nodes).
|
|
281
|
-
'
|
|
587
|
+
'(edges connect, refs point at valid nodes). The whole-graph check spans\n' +
|
|
588
|
+
'every contract space by default; pass --space <id> to narrow to one. A\n' +
|
|
589
|
+
'migration reference checks a single package, resolved across all contract\n' +
|
|
590
|
+
'spaces (narrow with --space; an ambiguous reference is a precondition failure).\n' +
|
|
591
|
+
'Offline — does not consult the database.\n' +
|
|
592
|
+
'Exit codes: 0 = all checks passed, 2 = precondition failed\n' +
|
|
593
|
+
'(unresolved target or unknown --space), 4 = integrity failure(s) found.',
|
|
282
594
|
);
|
|
283
595
|
setCommandExamples(command, [
|
|
284
596
|
'prisma-next migration check',
|
|
597
|
+
'prisma-next migration check --space app',
|
|
285
598
|
'prisma-next migration check 20260101-add-users',
|
|
599
|
+
'prisma-next migration check 20260101-add-users --space app',
|
|
286
600
|
'prisma-next migration check --json',
|
|
287
601
|
]);
|
|
288
602
|
setCommandSeeAlso(command, [
|
|
289
603
|
{ verb: 'migration status', oneLiner: 'Show migration path and pending status' },
|
|
290
604
|
{ verb: 'migration list', oneLiner: 'List on-disk migrations' },
|
|
291
605
|
{ verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
|
|
606
|
+
{ verb: 'migration show', oneLiner: 'Display migration package contents' },
|
|
292
607
|
]);
|
|
293
608
|
command.exitOverride();
|
|
294
609
|
addGlobalOptions(command)
|
|
295
|
-
.argument('[
|
|
610
|
+
.argument('[target]', 'Migration reference: directory name, hash/prefix, ref, or path')
|
|
296
611
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
612
|
+
.option('--space <id>', 'Narrow output to a single contract space')
|
|
297
613
|
.action(async (target: string | undefined, options: MigrationCheckOptions) => {
|
|
298
614
|
const flags = parseGlobalFlagsOrExit(options);
|
|
299
615
|
const ui = createTerminalUI(flags);
|
|
300
616
|
|
|
301
|
-
let
|
|
302
|
-
let exitCode: number;
|
|
617
|
+
let outcome: MigrationCheckOutcome;
|
|
303
618
|
try {
|
|
304
|
-
|
|
619
|
+
outcome = await executeMigrationCheckCommand(target, options, flags, ui);
|
|
305
620
|
} catch (error) {
|
|
306
621
|
const msg = error instanceof Error ? error.message : String(error);
|
|
307
|
-
|
|
308
|
-
|
|
622
|
+
outcome = {
|
|
623
|
+
result: { ok: false, failures: [], summary: msg },
|
|
624
|
+
exitCode: PRECONDITION,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (outcome.error) {
|
|
629
|
+
const envelope = outcome.error.toEnvelope();
|
|
630
|
+
if (flags.json) {
|
|
631
|
+
ui.output(formatErrorJson(envelope));
|
|
632
|
+
} else if (!flags.quiet) {
|
|
633
|
+
ui.error(formatErrorOutput(envelope, flags));
|
|
634
|
+
}
|
|
635
|
+
process.exit(outcome.exitCode);
|
|
309
636
|
}
|
|
310
637
|
|
|
638
|
+
const result = outcome.result ?? {
|
|
639
|
+
ok: false,
|
|
640
|
+
failures: [],
|
|
641
|
+
summary: 'No check result produced',
|
|
642
|
+
};
|
|
643
|
+
|
|
311
644
|
if (flags.json) {
|
|
312
645
|
ui.output(JSON.stringify(result, null, 2));
|
|
313
646
|
} else if (!flags.quiet) {
|
|
314
647
|
if (result.ok) {
|
|
315
|
-
|
|
648
|
+
const spaceSuffix =
|
|
649
|
+
outcome.resolvedSpaceId !== undefined ? ` (space: ${outcome.resolvedSpaceId})` : '';
|
|
650
|
+
ui.log(`✔ ${result.summary}${spaceSuffix}`);
|
|
316
651
|
} else {
|
|
317
652
|
for (const f of result.failures) {
|
|
318
|
-
ui.log(`✗ [${f.
|
|
653
|
+
ui.log(`✗ [${f.code}] ${f.where}: ${f.why}`);
|
|
319
654
|
ui.log(` fix: ${f.fix}`);
|
|
320
655
|
}
|
|
321
656
|
ui.log(`\n${result.summary}`);
|
|
322
657
|
}
|
|
323
658
|
}
|
|
324
659
|
|
|
325
|
-
process.exit(exitCode);
|
|
660
|
+
process.exit(outcome.exitCode);
|
|
326
661
|
});
|
|
327
662
|
|
|
328
663
|
return command;
|