@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,35 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
3
|
+
import { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
4
|
+
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
4
5
|
import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
|
|
5
|
-
import
|
|
6
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
6
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
7
7
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
8
8
|
import { Command } from 'commander';
|
|
9
9
|
|
|
10
10
|
import { loadConfig } from '../config-loader';
|
|
11
11
|
import { createControlClient } from '../control-api/client';
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
AggregatePerSpaceExecutionEntry,
|
|
14
|
+
MigrationApplyFailure,
|
|
15
|
+
MigrationApplyPathDecision,
|
|
16
|
+
} from '../control-api/types';
|
|
13
17
|
import {
|
|
14
18
|
CliStructuredError,
|
|
15
19
|
type CliStructuredError as CliStructuredErrorType,
|
|
20
|
+
errorContractValidationFailed,
|
|
16
21
|
errorDatabaseConnectionRequired,
|
|
17
22
|
errorDriverRequired,
|
|
23
|
+
errorFileNotFound,
|
|
18
24
|
errorRuntime,
|
|
19
25
|
errorTargetMigrationNotSupported,
|
|
20
26
|
errorUnexpected,
|
|
27
|
+
mapMigrationToolsError,
|
|
21
28
|
} from '../utils/cli-errors';
|
|
22
29
|
import {
|
|
23
30
|
addGlobalOptions,
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
collectDeclaredInvariants,
|
|
32
|
+
loadMigrationPackages,
|
|
26
33
|
maskConnectionUrl,
|
|
27
|
-
|
|
34
|
+
resolveContractPath,
|
|
28
35
|
resolveMigrationPaths,
|
|
29
36
|
setCommandDescriptions,
|
|
30
37
|
setCommandExamples,
|
|
31
38
|
targetSupportsMigrations,
|
|
32
|
-
toPathDecisionResult,
|
|
33
39
|
} from '../utils/command-helpers';
|
|
34
40
|
import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
|
|
35
41
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
@@ -44,49 +50,50 @@ interface MigrationApplyCommandOptions extends CommonCommandOptions {
|
|
|
44
50
|
readonly ref?: string;
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Per-space breakdown of an apply run. The CLI command surfaces these
|
|
55
|
+
* for both the JSON shape (`appliedSpaces[]`) and the human-readable
|
|
56
|
+
* formatter (per-space block — same shape `db init` / `db update`
|
|
57
|
+
* use, M6 sub-spec § Output shape contract).
|
|
58
|
+
*/
|
|
47
59
|
export interface MigrationApplyResult {
|
|
48
60
|
readonly ok: boolean;
|
|
61
|
+
/** Number of contract spaces that had non-zero pending operations applied. */
|
|
49
62
|
readonly migrationsApplied: number;
|
|
63
|
+
/** Total contract spaces visible in the aggregate (pending + already-up-to-date). */
|
|
50
64
|
readonly migrationsTotal: number;
|
|
65
|
+
/**
|
|
66
|
+
* Marker hash for the **app member** post-apply. Surfaced for
|
|
67
|
+
* back-compat with single-space callers; per-space markers live on
|
|
68
|
+
* `perSpace[].marker.storageHash`.
|
|
69
|
+
*/
|
|
51
70
|
readonly markerHash: string;
|
|
52
71
|
readonly applied: readonly {
|
|
72
|
+
readonly spaceId: string;
|
|
53
73
|
readonly dirName: string;
|
|
74
|
+
readonly migrationHash: string;
|
|
54
75
|
readonly from: string;
|
|
55
76
|
readonly to: string;
|
|
56
77
|
readonly operationsExecuted: number;
|
|
57
78
|
}[];
|
|
58
79
|
readonly summary: string;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
80
|
+
/**
|
|
81
|
+
* Per-space breakdown in canonical schedule order (extensions
|
|
82
|
+
* alphabetically, then app). Always present for the aggregate-walking
|
|
83
|
+
* apply path.
|
|
84
|
+
*/
|
|
85
|
+
readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
|
|
86
|
+
/**
|
|
87
|
+
* Path-decision data for the app member. Surfaced for back-compat
|
|
88
|
+
* with single-space callers (cli-journeys invariant tests).
|
|
89
|
+
* Absent for no-op applies where the app had nothing to do.
|
|
90
|
+
*/
|
|
91
|
+
readonly pathDecision?: MigrationApplyPathDecision;
|
|
72
92
|
readonly timings: {
|
|
73
93
|
readonly total: number;
|
|
74
94
|
};
|
|
75
95
|
}
|
|
76
96
|
|
|
77
|
-
function mapMigrationToolsError(error: unknown): CliStructuredErrorType {
|
|
78
|
-
if (MigrationToolsError.is(error)) {
|
|
79
|
-
return errorRuntime(error.message, {
|
|
80
|
-
why: error.why,
|
|
81
|
-
fix: error.fix,
|
|
82
|
-
meta: { code: error.code, ...(error.details ?? {}) },
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
return errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
86
|
-
why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
97
|
function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
|
|
91
98
|
return errorRuntime(failure.summary, {
|
|
92
99
|
why: failure.why ?? 'Migration runner failed',
|
|
@@ -95,16 +102,6 @@ function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType
|
|
|
95
102
|
});
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
function packageToStep(pkg: MigrationBundle): MigrationApplyStep {
|
|
99
|
-
return {
|
|
100
|
-
dirName: pkg.dirName,
|
|
101
|
-
from: pkg.manifest.from,
|
|
102
|
-
to: pkg.manifest.to,
|
|
103
|
-
toContract: pkg.manifest.toContract,
|
|
104
|
-
operations: pkg.ops,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
105
|
async function executeMigrationApplyCommand(
|
|
109
106
|
options: MigrationApplyCommandOptions,
|
|
110
107
|
flags: GlobalFlags,
|
|
@@ -112,10 +109,8 @@ async function executeMigrationApplyCommand(
|
|
|
112
109
|
startTime: number,
|
|
113
110
|
): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {
|
|
114
111
|
const config = await loadConfig(options.config);
|
|
115
|
-
const { configPath, migrationsDir,
|
|
116
|
-
options.config,
|
|
117
|
-
config,
|
|
118
|
-
);
|
|
112
|
+
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
|
|
113
|
+
resolveMigrationPaths(options.config, config);
|
|
119
114
|
|
|
120
115
|
const dbConnection = options.db ?? config.db?.connection;
|
|
121
116
|
if (!dbConnection) {
|
|
@@ -143,38 +138,49 @@ async function executeMigrationApplyCommand(
|
|
|
143
138
|
);
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
let
|
|
147
|
-
|
|
141
|
+
let refEntry: RefEntry | undefined;
|
|
142
|
+
const refName = options.ref;
|
|
148
143
|
|
|
149
|
-
if (
|
|
150
|
-
refName = options.ref;
|
|
144
|
+
if (refName) {
|
|
151
145
|
try {
|
|
152
146
|
const refs = await readRefs(refsDir);
|
|
153
|
-
|
|
147
|
+
refEntry = resolveRef(refs, refName);
|
|
154
148
|
} catch (error) {
|
|
155
149
|
if (MigrationToolsError.is(error)) {
|
|
156
150
|
return notOk(mapMigrationToolsError(error));
|
|
157
151
|
}
|
|
158
152
|
throw error;
|
|
159
153
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Resolve and parse the contract envelope. The aggregate-walking
|
|
157
|
+
// operation needs the validated app contract to load the aggregate.
|
|
158
|
+
const contractPathAbsolute = resolveContractPath(config);
|
|
159
|
+
let contractRaw: Contract;
|
|
160
|
+
try {
|
|
161
|
+
const contractContent = await readFile(contractPathAbsolute, 'utf-8');
|
|
162
|
+
contractRaw = JSON.parse(contractContent) as Contract;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
|
|
165
165
|
return notOk(
|
|
166
|
-
|
|
167
|
-
why: `
|
|
166
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
167
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
168
168
|
fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry apply.',
|
|
169
169
|
}),
|
|
170
170
|
);
|
|
171
171
|
}
|
|
172
|
+
return notOk(
|
|
173
|
+
errorContractValidationFailed(
|
|
174
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
175
|
+
{ where: { path: contractPathAbsolute } },
|
|
176
|
+
),
|
|
177
|
+
);
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
if (!flags.json && !flags.quiet) {
|
|
175
181
|
const details: Array<{ label: string; value: string }> = [
|
|
176
182
|
{ label: 'config', value: configPath },
|
|
177
|
-
{ label: 'migrations', value:
|
|
183
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
178
184
|
];
|
|
179
185
|
if (typeof dbConnection === 'string') {
|
|
180
186
|
details.push({
|
|
@@ -195,10 +201,11 @@ async function executeMigrationApplyCommand(
|
|
|
195
201
|
ui.stderr(header);
|
|
196
202
|
}
|
|
197
203
|
|
|
198
|
-
//
|
|
199
|
-
|
|
204
|
+
// Load app-space migration packages — the aggregate operation
|
|
205
|
+
// needs them to hydrate the app member's graph for graph-walk.
|
|
206
|
+
let appPackages: Awaited<ReturnType<typeof loadMigrationPackages>>;
|
|
200
207
|
try {
|
|
201
|
-
|
|
208
|
+
appPackages = await loadMigrationPackages(appMigrationsDir);
|
|
202
209
|
} catch (error) {
|
|
203
210
|
if (MigrationToolsError.is(error)) {
|
|
204
211
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -206,27 +213,6 @@ async function executeMigrationApplyCommand(
|
|
|
206
213
|
throw error;
|
|
207
214
|
}
|
|
208
215
|
|
|
209
|
-
// Defense in depth: re-hash every bundle and confirm the recorded
|
|
210
|
-
// `migrationId` matches the on-disk `(manifest, ops)`. Catches FS
|
|
211
|
-
// corruption, partial writes, and post-emit hand edits before we
|
|
212
|
-
// start touching the database.
|
|
213
|
-
for (const bundle of migrations.bundles) {
|
|
214
|
-
const verified = verifyMigrationBundle(bundle);
|
|
215
|
-
if (!verified.ok) {
|
|
216
|
-
return notOk(
|
|
217
|
-
errorRuntime(`Migration package is corrupt: ${bundle.dirName}`, {
|
|
218
|
-
why: `Stored migrationId "${verified.storedMigrationId}" does not match the recomputed hash "${verified.computedMigrationId}" for ${migrationsRelative}/${bundle.dirName}. The migration.json or ops.json has been edited or partially written since emit.`,
|
|
219
|
-
fix: `Re-emit the package by running \`node "${migrationsRelative}/${bundle.dirName}/migration.ts"\`, or restore the directory from version control.`,
|
|
220
|
-
meta: {
|
|
221
|
-
dirName: bundle.dirName,
|
|
222
|
-
storedMigrationId: verified.storedMigrationId,
|
|
223
|
-
computedMigrationId: verified.computedMigrationId,
|
|
224
|
-
},
|
|
225
|
-
}),
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
216
|
const client = createControlClient({
|
|
231
217
|
family: config.family,
|
|
232
218
|
target: config.target,
|
|
@@ -237,134 +223,45 @@ async function executeMigrationApplyCommand(
|
|
|
237
223
|
|
|
238
224
|
try {
|
|
239
225
|
await client.connect(dbConnection);
|
|
240
|
-
const marker = await client.readMarker();
|
|
241
226
|
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
227
|
+
// Pre-check unknown invariants against `(declared by app graph) ∪
|
|
228
|
+
// (already on the app marker)`. The marker side of the union
|
|
229
|
+
// catches the case where the ref carries an invariant whose
|
|
230
|
+
// declaring migration was retired (history rewritten) but whose
|
|
231
|
+
// id is recorded on the marker — surfacing UNKNOWN_INVARIANT
|
|
232
|
+
// there would be misleading because the database has already
|
|
233
|
+
// satisfied the requirement.
|
|
234
|
+
if (refEntry && refEntry.invariants.length > 0) {
|
|
235
|
+
const allMarkers = await client.readAllMarkers();
|
|
236
|
+
const appMarker = allMarkers.get('app') ?? null;
|
|
237
|
+
const declared = collectDeclaredInvariants(appPackages.graph);
|
|
238
|
+
const known = new Set<string>(declared);
|
|
239
|
+
for (const id of appMarker?.invariants ?? []) known.add(id);
|
|
240
|
+
const unknown = refEntry.invariants.filter((id) => !known.has(id));
|
|
241
|
+
if (unknown.length > 0) {
|
|
255
242
|
return notOk(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
243
|
+
mapMigrationToolsError(
|
|
244
|
+
errorUnknownInvariant({
|
|
245
|
+
...ifDefined('refName', refName),
|
|
246
|
+
unknown,
|
|
247
|
+
declared: [...declared].sort(),
|
|
248
|
+
}),
|
|
249
|
+
),
|
|
261
250
|
);
|
|
262
251
|
}
|
|
263
|
-
// Empty contract + no migrations = nothing to do.
|
|
264
|
-
return ok({
|
|
265
|
-
ok: true,
|
|
266
|
-
migrationsApplied: 0,
|
|
267
|
-
migrationsTotal: 0,
|
|
268
|
-
markerHash: EMPTY_CONTRACT_HASH,
|
|
269
|
-
applied: [],
|
|
270
|
-
summary: 'No migrations found',
|
|
271
|
-
timings: { total: Date.now() - startTime },
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// --- Validate marker state ---
|
|
276
|
-
|
|
277
|
-
// The empty sentinel should never appear in a real marker row — if it does,
|
|
278
|
-
// the marker was corrupted and replaying all migrations would be dangerous.
|
|
279
|
-
if (marker?.storageHash === EMPTY_CONTRACT_HASH) {
|
|
280
|
-
return notOk(
|
|
281
|
-
errorRuntime('Database marker contains the empty sentinel hash', {
|
|
282
|
-
why: `The marker row exists but contains the empty sentinel value "${EMPTY_CONTRACT_HASH}". This should never happen — the marker should contain the hash of the last applied contract.`,
|
|
283
|
-
fix: 'The marker is corrupted. Run `prisma-next db sign` to overwrite it with the correct contract hash, or drop and recreate the database.',
|
|
284
|
-
meta: { markerHash: EMPTY_CONTRACT_HASH },
|
|
285
|
-
}),
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const markerHash = marker?.storageHash;
|
|
290
|
-
|
|
291
|
-
if (markerHash !== undefined && !migrations.graph.nodes.has(markerHash)) {
|
|
292
|
-
return notOk(
|
|
293
|
-
errorRuntime('Database marker does not match any known migration', {
|
|
294
|
-
why: `The database marker hash "${markerHash}" is not found in the migration history at ${migrationsRelative}`,
|
|
295
|
-
fix: 'Ensure the migrations directory matches this database. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',
|
|
296
|
-
meta: { markerHash, knownNodes: [...migrations.graph.nodes] },
|
|
297
|
-
}),
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!migrations.graph.nodes.has(destinationHash)) {
|
|
302
|
-
return notOk(
|
|
303
|
-
errorRuntime('Current contract has no planned migration path', {
|
|
304
|
-
why: `Current contract hash "${destinationHash}" is not present in the migration history at ${migrationsRelative}`,
|
|
305
|
-
fix: 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',
|
|
306
|
-
meta: { destinationHash, knownNodes: [...migrations.graph.nodes] },
|
|
307
|
-
}),
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// --- Resolve path and apply ---
|
|
312
|
-
|
|
313
|
-
// "No marker" means the database is fresh — start from the empty contract hash.
|
|
314
|
-
const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
|
|
315
|
-
|
|
316
|
-
const decision = findPathWithDecision(migrations.graph, originHash, destinationHash, refName);
|
|
317
|
-
if (!decision) {
|
|
318
|
-
return notOk(
|
|
319
|
-
errorRuntime('No migration path from current state to target', {
|
|
320
|
-
why: `Cannot find a path from "${originHash}" to target "${destinationHash}"`,
|
|
321
|
-
fix: 'Check the migration history for gaps or inconsistencies.',
|
|
322
|
-
meta: { markerHash: originHash, destinationHash },
|
|
323
|
-
}),
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const pendingPath = decision.selectedPath;
|
|
328
|
-
const pathDecision = toPathDecisionResult(decision);
|
|
329
|
-
|
|
330
|
-
if (pendingPath.length === 0) {
|
|
331
|
-
return ok({
|
|
332
|
-
ok: true,
|
|
333
|
-
migrationsApplied: 0,
|
|
334
|
-
migrationsTotal: 0,
|
|
335
|
-
markerHash: originHash,
|
|
336
|
-
applied: [],
|
|
337
|
-
summary: 'Already up to date',
|
|
338
|
-
pathDecision,
|
|
339
|
-
timings: { total: Date.now() - startTime },
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const bundleByDir = new Map(migrations.bundles.map((b) => [b.dirName, b]));
|
|
344
|
-
const pendingMigrations: MigrationApplyStep[] = [];
|
|
345
|
-
for (const migration of pendingPath) {
|
|
346
|
-
const pkg = bundleByDir.get(migration.dirName);
|
|
347
|
-
if (!pkg) {
|
|
348
|
-
return notOk(
|
|
349
|
-
errorRuntime(`Migration package not found: ${migration.dirName}`, {
|
|
350
|
-
why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,
|
|
351
|
-
fix: 'Ensure all migration directories are present and intact.',
|
|
352
|
-
}),
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
pendingMigrations.push(packageToStep(pkg));
|
|
356
252
|
}
|
|
357
253
|
|
|
358
254
|
if (!flags.quiet && !flags.json) {
|
|
359
|
-
|
|
360
|
-
ui.step(`Pending ${migration.dirName}`);
|
|
361
|
-
}
|
|
255
|
+
ui.step('Loading contract spaces…');
|
|
362
256
|
}
|
|
363
257
|
|
|
364
258
|
const applyResult = await client.migrationApply({
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
259
|
+
contract: contractRaw,
|
|
260
|
+
migrationsDir,
|
|
261
|
+
appMigrationPackages: appPackages.bundles,
|
|
262
|
+
...ifDefined('refHash', refEntry?.hash),
|
|
263
|
+
...(refEntry?.invariants ? { refInvariants: refEntry.invariants } : {}),
|
|
264
|
+
...(refEntry !== undefined ? ifDefined('refName', refName) : {}),
|
|
368
265
|
});
|
|
369
266
|
|
|
370
267
|
if (!applyResult.ok) {
|
|
@@ -376,17 +273,21 @@ async function executeMigrationApplyCommand(
|
|
|
376
273
|
return ok({
|
|
377
274
|
ok: true,
|
|
378
275
|
migrationsApplied: value.migrationsApplied,
|
|
379
|
-
migrationsTotal:
|
|
276
|
+
migrationsTotal: value.perSpace.length,
|
|
380
277
|
markerHash: value.markerHash,
|
|
381
278
|
applied: value.applied,
|
|
382
279
|
summary: value.summary,
|
|
383
|
-
|
|
280
|
+
perSpace: value.perSpace,
|
|
281
|
+
...ifDefined('pathDecision', value.pathDecision),
|
|
384
282
|
timings: { total: Date.now() - startTime },
|
|
385
283
|
});
|
|
386
284
|
} catch (error) {
|
|
387
285
|
if (CliStructuredError.is(error)) {
|
|
388
286
|
return notOk(error);
|
|
389
287
|
}
|
|
288
|
+
if (MigrationToolsError.is(error)) {
|
|
289
|
+
return notOk(mapMigrationToolsError(error));
|
|
290
|
+
}
|
|
390
291
|
return notOk(
|
|
391
292
|
errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
392
293
|
why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -402,16 +303,17 @@ export function createMigrationApplyCommand(): Command {
|
|
|
402
303
|
setCommandDescriptions(
|
|
403
304
|
command,
|
|
404
305
|
'Apply planned migrations to the database',
|
|
405
|
-
'
|
|
406
|
-
'
|
|
407
|
-
'
|
|
408
|
-
|
|
306
|
+
'Walks every contract space (app + extensions) and applies pending\n' +
|
|
307
|
+
'on-disk migrations in canonical order (extensions alphabetically,\n' +
|
|
308
|
+
'then app). Graph-walks the on-disk migration graph for every space —\n' +
|
|
309
|
+
"no introspection, no synth. Each space's marker advances inside its\n" +
|
|
310
|
+
"transaction; per-space failure rolls back every space's writes.",
|
|
409
311
|
);
|
|
410
312
|
setCommandExamples(command, ['prisma-next migration apply --db $DATABASE_URL']);
|
|
411
313
|
addGlobalOptions(command)
|
|
412
314
|
.option('--db <url>', 'Database connection string')
|
|
413
315
|
.option('--config <path>', 'Path to prisma-next.config.ts')
|
|
414
|
-
.option('--ref <name>', '
|
|
316
|
+
.option('--ref <name>', 'App-space target ref name from migrations/app/refs/')
|
|
415
317
|
.action(async (options: MigrationApplyCommandOptions) => {
|
|
416
318
|
const flags = parseGlobalFlags(options);
|
|
417
319
|
const startTime = Date.now();
|