@prisma-next/cli 0.5.0-dev.4 → 0.5.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 +56 -21
- package/dist/agent-skill-mongo.md +63 -31
- package/dist/agent-skill-postgres.md +1 -1
- package/dist/cli-errors-By1iVE3z.mjs +34 -0
- package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
- package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
- package/dist/cli.mjs +123 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-TG7rbCWT.mjs → client-1JqqkiC7.mjs} +45 -20
- package/dist/client-1JqqkiC7.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -2
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -2
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +10 -9
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +5 -5
- package/dist/commands/db-sign.mjs +7 -7
- package/dist/commands/db-update.mjs +9 -9
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +9 -9
- package/dist/commands/migration-apply.d.mts +5 -2
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +55 -56
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +26 -32
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +14 -5
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +45 -48
- package/dist/commands/migration-plan.mjs.map +1 -1
- package/dist/commands/migration-ref.d.mts +1 -1
- package/dist/commands/migration-ref.d.mts.map +1 -1
- package/dist/commands/migration-ref.mjs +6 -10
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +13 -7
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +27 -29
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +23 -5
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +3 -3
- package/dist/{config-loader-_W4T21X1.mjs → config-loader-ih8ViDb_.mjs} +2 -2
- package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
- package/dist/config-loader.mjs +1 -1
- package/dist/contract-emit-LjzCoicC.mjs +4 -0
- package/dist/contract-emit-RZBWzkop.mjs +329 -0
- package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
- package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
- package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
- package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
- package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
- package/dist/{contract-infer-BS4kIX9c.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
- package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +86 -21
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +5 -5
- package/dist/exports/index.mjs +3 -3
- package/dist/exports/init-output.d.mts +39 -0
- package/dist/exports/init-output.d.mts.map +1 -0
- package/dist/exports/init-output.mjs +3 -0
- package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
- package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
- package/dist/init-C7dE9KOJ.mjs +2062 -0
- package/dist/init-C7dE9KOJ.mjs.map +1 -0
- package/dist/{inspect-live-schema-BsoFVoS1.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
- package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
- package/dist/migration-cli.d.mts +41 -11
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +308 -84
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-DOXnheFa.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
- package/dist/{migration-command-scaffold-DOXnheFa.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
- package/dist/{migration-status-Ry3TnEya.mjs → migration-status-DoPrFIOQ.mjs} +114 -57
- package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
- package/dist/{migrations-fU0xoKjS.mjs → migrations-MEoKMiV5.mjs} +42 -21
- package/dist/migrations-MEoKMiV5.mjs.map +1 -0
- package/dist/output-BpcQrnnq.mjs +103 -0
- package/dist/output-BpcQrnnq.mjs.map +1 -0
- package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
- package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
- package/dist/quick-reference-mongo.md +34 -13
- package/dist/quick-reference-postgres.md +11 -9
- package/dist/{result-handler-BJwA7ufw.mjs → result-handler-Ch6hVnOo.mjs} +35 -93
- package/dist/result-handler-Ch6hVnOo.mjs.map +1 -0
- package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
- package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
- package/dist/{verify-bl__PkXk.mjs → verify-BT9tgCOH.mjs} +2 -2
- package/dist/{verify-bl__PkXk.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
- package/package.json +22 -16
- 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 +1 -0
- package/src/commands/db-update.ts +1 -1
- package/src/commands/init/detect-pnpm-catalog.ts +141 -0
- package/src/commands/init/errors.ts +254 -0
- package/src/commands/init/exit-codes.ts +62 -0
- package/src/commands/init/hygiene-gitattributes.ts +97 -0
- package/src/commands/init/hygiene-gitignore.ts +48 -0
- package/src/commands/init/hygiene-package-scripts.ts +91 -0
- package/src/commands/init/index.ts +112 -7
- package/src/commands/init/init.ts +766 -144
- package/src/commands/init/inputs.ts +421 -0
- package/src/commands/init/output.ts +147 -0
- package/src/commands/init/probe-db.ts +308 -0
- package/src/commands/init/reinit-cleanup.ts +83 -0
- package/src/commands/init/templates/agent-skill-mongo.md +63 -31
- package/src/commands/init/templates/agent-skill-postgres.md +1 -1
- package/src/commands/init/templates/agent-skill.ts +25 -3
- package/src/commands/init/templates/code-templates.ts +125 -32
- package/src/commands/init/templates/env.ts +80 -0
- package/src/commands/init/templates/quick-reference-mongo.md +34 -13
- package/src/commands/init/templates/quick-reference-postgres.md +11 -9
- package/src/commands/init/templates/quick-reference.ts +42 -3
- package/src/commands/init/templates/tsconfig.ts +167 -5
- package/src/commands/inspect-live-schema.ts +10 -5
- package/src/commands/migration-apply.ts +84 -63
- package/src/commands/migration-new.ts +28 -34
- package/src/commands/migration-plan.ts +80 -56
- package/src/commands/migration-ref.ts +8 -7
- package/src/commands/migration-show.ts +53 -36
- package/src/commands/migration-status.ts +194 -58
- package/src/config-path-validation.ts +0 -1
- package/src/control-api/client.ts +21 -0
- package/src/control-api/operations/contract-emit.ts +198 -115
- package/src/control-api/operations/db-init.ts +10 -6
- package/src/control-api/operations/db-update.ts +10 -6
- package/src/control-api/operations/migration-apply.ts +30 -9
- package/src/control-api/types.ts +69 -7
- package/src/exports/control-api.ts +2 -1
- package/src/exports/init-output.ts +10 -0
- package/src/migration-cli.ts +445 -122
- package/src/utils/cli-errors.ts +49 -2
- package/src/utils/command-helpers.ts +45 -23
- package/src/utils/emit-queue.ts +26 -0
- package/src/utils/formatters/graph-migration-mapper.ts +7 -3
- package/src/utils/formatters/migrations.ts +62 -26
- package/src/utils/publish-contract-artifact-pair.ts +134 -0
- package/dist/cli-errors-DHq6GQGu.mjs +0 -5
- package/dist/client-TG7rbCWT.mjs.map +0 -1
- package/dist/config-loader-_W4T21X1.mjs.map +0 -1
- package/dist/contract-emit-CQfj7xJn.mjs +0 -122
- package/dist/contract-emit-CQfj7xJn.mjs.map +0 -1
- package/dist/contract-emit-DpPjuFy-.mjs +0 -195
- package/dist/contract-emit-DpPjuFy-.mjs.map +0 -1
- package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
- package/dist/contract-infer-BS4kIX9c.mjs.map +0 -1
- package/dist/extract-operation-statements-DZUJNmL3.mjs +0 -13
- package/dist/extract-operation-statements-DZUJNmL3.mjs.map +0 -1
- package/dist/extract-sql-ddl-DDMX-9mz.mjs +0 -26
- package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +0 -1
- package/dist/init-CQfo_4Ro.mjs +0 -430
- package/dist/init-CQfo_4Ro.mjs.map +0 -1
- package/dist/inspect-live-schema-BsoFVoS1.mjs.map +0 -1
- package/dist/migration-status-Ry3TnEya.mjs.map +0 -1
- package/dist/migrations-fU0xoKjS.mjs.map +0 -1
- package/dist/result-handler-BJwA7ufw.mjs.map +0 -1
- package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
- package/dist/validate-contract-deps-esa-VQ0h.mjs +0 -37
- package/dist/validate-contract-deps-esa-VQ0h.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,5 +1,5 @@
|
|
|
1
1
|
import type { CoreSchemaView } from '@prisma-next/framework-components/control';
|
|
2
|
-
import {
|
|
2
|
+
import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
|
|
3
3
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
4
4
|
import { relative, resolve } from 'pathe';
|
|
5
5
|
import { loadConfig } from '../config-loader';
|
|
@@ -33,6 +33,12 @@ export interface InspectLiveSchemaResult {
|
|
|
33
33
|
readonly config: LoadedCliConfig;
|
|
34
34
|
readonly schema: unknown;
|
|
35
35
|
readonly schemaView: CoreSchemaView | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* PSL AST inferred from the introspected schema, when the configured family
|
|
38
|
+
* implements `PslContractInferCapable`. `undefined` for families that do not
|
|
39
|
+
* support inference (e.g. Mongo today).
|
|
40
|
+
*/
|
|
41
|
+
readonly pslContractAst: PslDocumentAst | undefined;
|
|
36
42
|
readonly target: {
|
|
37
43
|
readonly familyId: string;
|
|
38
44
|
readonly id: string;
|
|
@@ -122,14 +128,12 @@ export async function inspectLiveSchema(
|
|
|
122
128
|
const onProgress = createProgressAdapter({ ui, flags });
|
|
123
129
|
|
|
124
130
|
try {
|
|
125
|
-
const
|
|
131
|
+
const schema = await client.introspect({
|
|
126
132
|
connection: dbConnection,
|
|
127
133
|
onProgress,
|
|
128
134
|
});
|
|
129
|
-
// TODO(TML-2251): Remove SQL-specific branching — SQL should use the same family-agnostic path as Mongo.
|
|
130
|
-
const schema =
|
|
131
|
-
config.family.familyId === 'sql' ? validatePrintableSqlSchemaIR(schemaIR) : schemaIR;
|
|
132
135
|
const schemaView = client.toSchemaView(schema);
|
|
136
|
+
const pslContractAst = client.inferPslContract(schema);
|
|
133
137
|
|
|
134
138
|
const dbUrl = typeof dbConnection === 'string' ? maskConnectionUrl(dbConnection) : undefined;
|
|
135
139
|
|
|
@@ -137,6 +141,7 @@ export async function inspectLiveSchema(
|
|
|
137
141
|
config,
|
|
138
142
|
schema,
|
|
139
143
|
schemaView,
|
|
144
|
+
pslContractAst,
|
|
140
145
|
target: {
|
|
141
146
|
familyId: config.family.familyId,
|
|
142
147
|
id: config.target.targetId,
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { verifyMigrationBundle } from '@prisma-next/migration-tools/attestation';
|
|
2
1
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
errorNoInvariantPath,
|
|
4
|
+
errorUnknownInvariant,
|
|
5
|
+
MigrationToolsError,
|
|
6
|
+
} from '@prisma-next/migration-tools/errors';
|
|
7
|
+
import { findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';
|
|
8
|
+
import type { MigrationPackage } from '@prisma-next/migration-tools/package';
|
|
9
|
+
import type { RefEntry } from '@prisma-next/migration-tools/refs';
|
|
4
10
|
import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
|
|
5
|
-
import
|
|
6
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
11
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
7
12
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
8
13
|
import { Command } from 'commander';
|
|
9
14
|
|
|
@@ -18,11 +23,12 @@ import {
|
|
|
18
23
|
errorRuntime,
|
|
19
24
|
errorTargetMigrationNotSupported,
|
|
20
25
|
errorUnexpected,
|
|
26
|
+
mapMigrationToolsError,
|
|
21
27
|
} from '../utils/cli-errors';
|
|
22
28
|
import {
|
|
23
29
|
addGlobalOptions,
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
collectDeclaredInvariants,
|
|
31
|
+
loadMigrationPackages,
|
|
26
32
|
maskConnectionUrl,
|
|
27
33
|
readContractEnvelope,
|
|
28
34
|
resolveMigrationPaths,
|
|
@@ -30,6 +36,7 @@ import {
|
|
|
30
36
|
setCommandExamples,
|
|
31
37
|
targetSupportsMigrations,
|
|
32
38
|
toPathDecisionResult,
|
|
39
|
+
toStructuralEdge,
|
|
33
40
|
} from '../utils/command-helpers';
|
|
34
41
|
import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
|
|
35
42
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
@@ -51,7 +58,7 @@ export interface MigrationApplyResult {
|
|
|
51
58
|
readonly markerHash: string;
|
|
52
59
|
readonly applied: readonly {
|
|
53
60
|
readonly dirName: string;
|
|
54
|
-
readonly from: string;
|
|
61
|
+
readonly from: string | null;
|
|
55
62
|
readonly to: string;
|
|
56
63
|
readonly operationsExecuted: number;
|
|
57
64
|
}[];
|
|
@@ -62,11 +69,14 @@ export interface MigrationApplyResult {
|
|
|
62
69
|
readonly alternativeCount: number;
|
|
63
70
|
readonly tieBreakReasons: readonly string[];
|
|
64
71
|
readonly refName?: string;
|
|
72
|
+
readonly requiredInvariants: readonly string[];
|
|
73
|
+
readonly satisfiedInvariants: readonly string[];
|
|
65
74
|
readonly selectedPath: readonly {
|
|
66
75
|
readonly dirName: string;
|
|
67
|
-
readonly
|
|
76
|
+
readonly migrationHash: string;
|
|
68
77
|
readonly from: string;
|
|
69
78
|
readonly to: string;
|
|
79
|
+
readonly invariants: readonly string[];
|
|
70
80
|
}[];
|
|
71
81
|
};
|
|
72
82
|
readonly timings: {
|
|
@@ -74,19 +84,6 @@ export interface MigrationApplyResult {
|
|
|
74
84
|
};
|
|
75
85
|
}
|
|
76
86
|
|
|
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
87
|
function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
|
|
91
88
|
return errorRuntime(failure.summary, {
|
|
92
89
|
why: failure.why ?? 'Migration runner failed',
|
|
@@ -95,13 +92,14 @@ function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType
|
|
|
95
92
|
});
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
function packageToStep(pkg:
|
|
95
|
+
function packageToStep(pkg: MigrationPackage): MigrationApplyStep {
|
|
99
96
|
return {
|
|
100
97
|
dirName: pkg.dirName,
|
|
101
|
-
from: pkg.
|
|
102
|
-
to: pkg.
|
|
103
|
-
toContract: pkg.
|
|
98
|
+
from: pkg.metadata.from,
|
|
99
|
+
to: pkg.metadata.to,
|
|
100
|
+
toContract: pkg.metadata.toContract,
|
|
104
101
|
operations: pkg.ops,
|
|
102
|
+
providedInvariants: pkg.metadata.providedInvariants,
|
|
105
103
|
};
|
|
106
104
|
}
|
|
107
105
|
|
|
@@ -143,14 +141,14 @@ async function executeMigrationApplyCommand(
|
|
|
143
141
|
);
|
|
144
142
|
}
|
|
145
143
|
|
|
146
|
-
let
|
|
147
|
-
let
|
|
144
|
+
let refEntry: RefEntry | undefined;
|
|
145
|
+
let envelopeHash: string | undefined;
|
|
146
|
+
const refName = options.ref;
|
|
148
147
|
|
|
149
|
-
if (
|
|
150
|
-
refName = options.ref;
|
|
148
|
+
if (refName) {
|
|
151
149
|
try {
|
|
152
150
|
const refs = await readRefs(refsDir);
|
|
153
|
-
|
|
151
|
+
refEntry = resolveRef(refs, refName);
|
|
154
152
|
} catch (error) {
|
|
155
153
|
if (MigrationToolsError.is(error)) {
|
|
156
154
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -160,7 +158,7 @@ async function executeMigrationApplyCommand(
|
|
|
160
158
|
} else {
|
|
161
159
|
try {
|
|
162
160
|
const envelope = await readContractEnvelope(config);
|
|
163
|
-
|
|
161
|
+
envelopeHash = envelope.storageHash;
|
|
164
162
|
} catch (error) {
|
|
165
163
|
return notOk(
|
|
166
164
|
errorRuntime('Current contract is unavailable', {
|
|
@@ -170,6 +168,7 @@ async function executeMigrationApplyCommand(
|
|
|
170
168
|
);
|
|
171
169
|
}
|
|
172
170
|
}
|
|
171
|
+
const destinationHash = refEntry?.hash ?? envelopeHash!;
|
|
173
172
|
|
|
174
173
|
if (!flags.json && !flags.quiet) {
|
|
175
174
|
const details: Array<{ label: string; value: string }> = [
|
|
@@ -196,9 +195,9 @@ async function executeMigrationApplyCommand(
|
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
// Read migrations and build migration chain model (offline — no DB needed)
|
|
199
|
-
let migrations:
|
|
198
|
+
let migrations: Awaited<ReturnType<typeof loadMigrationPackages>>;
|
|
200
199
|
try {
|
|
201
|
-
migrations = await
|
|
200
|
+
migrations = await loadMigrationPackages(migrationsDir);
|
|
202
201
|
} catch (error) {
|
|
203
202
|
if (MigrationToolsError.is(error)) {
|
|
204
203
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -206,27 +205,6 @@ async function executeMigrationApplyCommand(
|
|
|
206
205
|
throw error;
|
|
207
206
|
}
|
|
208
207
|
|
|
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
208
|
const client = createControlClient({
|
|
231
209
|
family: config.family,
|
|
232
210
|
target: config.target,
|
|
@@ -239,6 +217,32 @@ async function executeMigrationApplyCommand(
|
|
|
239
217
|
await client.connect(dbConnection);
|
|
240
218
|
const marker = await client.readMarker();
|
|
241
219
|
|
|
220
|
+
// Pre-check unknown invariants against `(declared by graph) ∪
|
|
221
|
+
// (already on the marker)`. The union catches the edge case where the
|
|
222
|
+
// ref carries an invariant whose declaring migration was retired (e.g.
|
|
223
|
+
// history rewritten) but whose id is recorded on the marker —
|
|
224
|
+
// surfacing that as MIGRATION.UNKNOWN_INVARIANT would be misleading
|
|
225
|
+
// because the database has already satisfied the requirement, so the
|
|
226
|
+
// marker-subtraction below empties `effectiveRequired` and apply
|
|
227
|
+
// short-circuits to "Already up to date".
|
|
228
|
+
if (refEntry && refEntry.invariants.length > 0) {
|
|
229
|
+
const declared = collectDeclaredInvariants(migrations.graph);
|
|
230
|
+
const known = new Set<string>(declared);
|
|
231
|
+
for (const id of marker?.invariants ?? []) known.add(id);
|
|
232
|
+
const unknown = refEntry.invariants.filter((id) => !known.has(id));
|
|
233
|
+
if (unknown.length > 0) {
|
|
234
|
+
return notOk(
|
|
235
|
+
mapMigrationToolsError(
|
|
236
|
+
errorUnknownInvariant({
|
|
237
|
+
...ifDefined('refName', refName),
|
|
238
|
+
unknown,
|
|
239
|
+
declared: [...declared].sort(),
|
|
240
|
+
}),
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
242
246
|
// --- No migrations on disk ---
|
|
243
247
|
if (migrations.bundles.length === 0) {
|
|
244
248
|
if (marker?.storageHash) {
|
|
@@ -308,13 +312,31 @@ async function executeMigrationApplyCommand(
|
|
|
308
312
|
);
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
// --- Resolve path and apply ---
|
|
312
|
-
|
|
313
315
|
// "No marker" means the database is fresh — start from the empty contract hash.
|
|
314
316
|
const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
|
|
315
317
|
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
+
const appliedInvariants = new Set(marker?.invariants ?? []);
|
|
319
|
+
const effectiveRequired = new Set(
|
|
320
|
+
(refEntry?.invariants ?? []).filter((id) => !appliedInvariants.has(id)),
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const outcome = findPathWithDecision(migrations.graph, originHash, destinationHash, {
|
|
324
|
+
...ifDefined('refName', refName),
|
|
325
|
+
required: effectiveRequired,
|
|
326
|
+
});
|
|
327
|
+
if (outcome.kind === 'unsatisfiable') {
|
|
328
|
+
return notOk(
|
|
329
|
+
mapMigrationToolsError(
|
|
330
|
+
errorNoInvariantPath({
|
|
331
|
+
...ifDefined('refName', refName),
|
|
332
|
+
required: [...effectiveRequired].sort(),
|
|
333
|
+
missing: outcome.missing,
|
|
334
|
+
structuralPath: outcome.structuralPath.map(toStructuralEdge),
|
|
335
|
+
}),
|
|
336
|
+
),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (outcome.kind === 'unreachable') {
|
|
318
340
|
return notOk(
|
|
319
341
|
errorRuntime('No migration path from current state to target', {
|
|
320
342
|
why: `Cannot find a path from "${originHash}" to target "${destinationHash}"`,
|
|
@@ -324,10 +346,9 @@ async function executeMigrationApplyCommand(
|
|
|
324
346
|
);
|
|
325
347
|
}
|
|
326
348
|
|
|
327
|
-
const
|
|
328
|
-
const pathDecision = toPathDecisionResult(decision);
|
|
349
|
+
const pathDecision = toPathDecisionResult(outcome.decision);
|
|
329
350
|
|
|
330
|
-
if (
|
|
351
|
+
if (outcome.decision.selectedPath.length === 0) {
|
|
331
352
|
return ok({
|
|
332
353
|
ok: true,
|
|
333
354
|
migrationsApplied: 0,
|
|
@@ -342,7 +363,7 @@ async function executeMigrationApplyCommand(
|
|
|
342
363
|
|
|
343
364
|
const bundleByDir = new Map(migrations.bundles.map((b) => [b.dirName, b]));
|
|
344
365
|
const pendingMigrations: MigrationApplyStep[] = [];
|
|
345
|
-
for (const migration of
|
|
366
|
+
for (const migration of outcome.decision.selectedPath) {
|
|
346
367
|
const pkg = bundleByDir.get(migration.dirName);
|
|
347
368
|
if (!pkg) {
|
|
348
369
|
return notOk(
|
|
@@ -376,7 +397,7 @@ async function executeMigrationApplyCommand(
|
|
|
376
397
|
return ok({
|
|
377
398
|
ok: true,
|
|
378
399
|
migrationsApplied: value.migrationsApplied,
|
|
379
|
-
migrationsTotal:
|
|
400
|
+
migrationsTotal: outcome.decision.selectedPath.length,
|
|
380
401
|
markerHash: value.markerHash,
|
|
381
402
|
applied: value.applied,
|
|
382
403
|
summary: value.summary,
|
|
@@ -12,31 +12,35 @@ import { readFileSync } from 'node:fs';
|
|
|
12
12
|
import type { Contract } from '@prisma-next/contract/types';
|
|
13
13
|
import { getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
14
14
|
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';
|
|
15
|
+
import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
16
|
+
import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
18
17
|
import {
|
|
19
18
|
copyFilesWithRename,
|
|
20
19
|
formatMigrationDirName,
|
|
21
20
|
readMigrationsDir,
|
|
22
21
|
writeMigrationPackage,
|
|
23
22
|
} from '@prisma-next/migration-tools/io';
|
|
23
|
+
import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
|
|
24
|
+
import {
|
|
25
|
+
findLatestMigration,
|
|
26
|
+
reconstructGraph,
|
|
27
|
+
} from '@prisma-next/migration-tools/migration-graph';
|
|
24
28
|
import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
|
|
25
|
-
import type { MigrationManifest } from '@prisma-next/migration-tools/types';
|
|
26
|
-
import { MigrationToolsError } from '@prisma-next/migration-tools/types';
|
|
27
29
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
28
30
|
import { Command } from 'commander';
|
|
29
|
-
import { join, relative
|
|
31
|
+
import { join, relative } from 'pathe';
|
|
30
32
|
import { loadConfig } from '../config-loader';
|
|
31
33
|
import {
|
|
32
34
|
CliStructuredError,
|
|
33
35
|
errorRuntime,
|
|
34
36
|
errorTargetMigrationNotSupported,
|
|
35
37
|
errorUnexpected,
|
|
38
|
+
mapMigrationToolsError,
|
|
36
39
|
} from '../utils/cli-errors';
|
|
37
40
|
import {
|
|
38
41
|
addGlobalOptions,
|
|
39
42
|
getTargetMigrations,
|
|
43
|
+
resolveContractPath,
|
|
40
44
|
resolveMigrationPaths,
|
|
41
45
|
setCommandDescriptions,
|
|
42
46
|
setCommandExamples,
|
|
@@ -57,7 +61,7 @@ interface MigrationNewOptions extends CommonCommandOptions {
|
|
|
57
61
|
interface MigrationNewResult {
|
|
58
62
|
readonly ok: true;
|
|
59
63
|
readonly dir: string;
|
|
60
|
-
readonly from: string;
|
|
64
|
+
readonly from: string | null;
|
|
61
65
|
readonly to: string;
|
|
62
66
|
readonly summary: string;
|
|
63
67
|
}
|
|
@@ -68,11 +72,7 @@ async function executeMigrationNewCommand(
|
|
|
68
72
|
const config = await loadConfig(options.config);
|
|
69
73
|
const { migrationsDir, migrationsRelative } = resolveMigrationPaths(options.config, config);
|
|
70
74
|
|
|
71
|
-
const
|
|
72
|
-
const contractPathAbsolute = resolve(
|
|
73
|
-
options.config ? resolve(options.config, '..') : process.cwd(),
|
|
74
|
-
contractPath,
|
|
75
|
-
);
|
|
75
|
+
const contractPathAbsolute = resolveContractPath(config);
|
|
76
76
|
|
|
77
77
|
let contractJsonContent: string;
|
|
78
78
|
try {
|
|
@@ -116,7 +116,7 @@ async function executeMigrationNewCommand(
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
let fromContract: Contract | null = null;
|
|
119
|
-
let fromHash: string =
|
|
119
|
+
let fromHash: string | null = null;
|
|
120
120
|
let fromContractSourceDir: string | null = null;
|
|
121
121
|
|
|
122
122
|
try {
|
|
@@ -126,7 +126,7 @@ async function executeMigrationNewCommand(
|
|
|
126
126
|
const graph = reconstructGraph(packages);
|
|
127
127
|
|
|
128
128
|
if (options.from) {
|
|
129
|
-
const match = packages.find((p) => p.
|
|
129
|
+
const match = packages.find((p) => p.metadata.to.startsWith(options.from!));
|
|
130
130
|
if (!match) {
|
|
131
131
|
return notOk(
|
|
132
132
|
errorRuntime('Starting contract not found', {
|
|
@@ -135,18 +135,18 @@ async function executeMigrationNewCommand(
|
|
|
135
135
|
}),
|
|
136
136
|
);
|
|
137
137
|
}
|
|
138
|
-
fromHash = match.
|
|
139
|
-
fromContract = match.
|
|
138
|
+
fromHash = match.metadata.to;
|
|
139
|
+
fromContract = match.metadata.toContract;
|
|
140
140
|
fromContractSourceDir = match.dirPath;
|
|
141
141
|
} else {
|
|
142
142
|
const latestMigration = findLatestMigration(graph);
|
|
143
143
|
if (latestMigration) {
|
|
144
144
|
fromHash = latestMigration.to;
|
|
145
145
|
const leafPkg = packages.find(
|
|
146
|
-
(p) => p.
|
|
146
|
+
(p) => p.metadata.migrationHash === latestMigration.migrationHash,
|
|
147
147
|
);
|
|
148
148
|
if (leafPkg) {
|
|
149
|
-
fromContract = leafPkg.
|
|
149
|
+
fromContract = leafPkg.metadata.toContract;
|
|
150
150
|
fromContractSourceDir = leafPkg.dirPath;
|
|
151
151
|
}
|
|
152
152
|
}
|
|
@@ -154,22 +154,16 @@ async function executeMigrationNewCommand(
|
|
|
154
154
|
}
|
|
155
155
|
} catch (error) {
|
|
156
156
|
if (MigrationToolsError.is(error)) {
|
|
157
|
-
return notOk(
|
|
158
|
-
errorRuntime(error.message, {
|
|
159
|
-
why: error.why,
|
|
160
|
-
fix: error.fix,
|
|
161
|
-
meta: { code: error.code },
|
|
162
|
-
}),
|
|
163
|
-
);
|
|
157
|
+
return notOk(mapMigrationToolsError(error));
|
|
164
158
|
}
|
|
165
159
|
throw error;
|
|
166
160
|
}
|
|
167
161
|
|
|
168
|
-
if (fromHash === toStorageHash) {
|
|
162
|
+
if (fromHash === toStorageHash && !options.from) {
|
|
169
163
|
return notOk(
|
|
170
164
|
errorRuntime('No changes detected', {
|
|
171
165
|
why: 'The from and to contract hashes are identical — there is nothing to migrate.',
|
|
172
|
-
fix: 'Change the contract and run `prisma-next contract emit` before creating a new migration.',
|
|
166
|
+
fix: 'Change the contract and run `prisma-next contract emit` before creating a new migration. To author a data-only migration on the current contract hash, pass `--from <hash>` explicitly.',
|
|
173
167
|
}),
|
|
174
168
|
);
|
|
175
169
|
}
|
|
@@ -181,12 +175,11 @@ async function executeMigrationNewCommand(
|
|
|
181
175
|
|
|
182
176
|
// `migration new` scaffolds an empty `migration.ts` for the user to
|
|
183
177
|
// fill, so we attest over `ops: []`. Re-running self-emit after the
|
|
184
|
-
// user adds operations will produce a different `
|
|
178
|
+
// user adds operations will produce a different `migrationHash` (over
|
|
185
179
|
// the real ops). This is intentional — there is no on-disk draft.
|
|
186
|
-
const
|
|
180
|
+
const baseMetadata: Omit<MigrationMetadata, 'migrationHash'> = {
|
|
187
181
|
from: fromHash,
|
|
188
182
|
to: toStorageHash,
|
|
189
|
-
kind: 'regular',
|
|
190
183
|
fromContract,
|
|
191
184
|
toContract: toContractJson,
|
|
192
185
|
hints: {
|
|
@@ -195,11 +188,12 @@ async function executeMigrationNewCommand(
|
|
|
195
188
|
plannerVersion: '1.0.0',
|
|
196
189
|
},
|
|
197
190
|
labels: [],
|
|
191
|
+
providedInvariants: [],
|
|
198
192
|
createdAt: timestamp.toISOString(),
|
|
199
193
|
};
|
|
200
|
-
const
|
|
201
|
-
...
|
|
202
|
-
|
|
194
|
+
const metadata: MigrationMetadata = {
|
|
195
|
+
...baseMetadata,
|
|
196
|
+
migrationHash: computeMigrationHash(baseMetadata, []),
|
|
203
197
|
};
|
|
204
198
|
|
|
205
199
|
const migrations = getTargetMigrations(config.target);
|
|
@@ -218,7 +212,7 @@ async function executeMigrationNewCommand(
|
|
|
218
212
|
...(config.extensionPacks ?? []),
|
|
219
213
|
]);
|
|
220
214
|
|
|
221
|
-
await writeMigrationPackage(packageDir,
|
|
215
|
+
await writeMigrationPackage(packageDir, metadata, []);
|
|
222
216
|
const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
|
|
223
217
|
await copyFilesWithRename(packageDir, [
|
|
224
218
|
{ sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
|