@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
package/src/utils/cli-errors.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Re-export all domain error factories from @prisma-next/errors for convenience.
|
|
3
|
-
* CLI-specific errors (e.g., Commander
|
|
3
|
+
* CLI-specific errors (e.g., Commander argument validation in the main CLI, or
|
|
4
|
+
* clipanion parse errors in the migration-file CLI) can be added here if needed.
|
|
4
5
|
*/
|
|
5
6
|
export type { CliErrorConflict, CliErrorEnvelope } from '@prisma-next/errors/control';
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
import {
|
|
7
9
|
CliStructuredError,
|
|
8
10
|
errorConfigFileNotFound,
|
|
9
11
|
errorConfigValidation,
|
|
@@ -15,11 +17,33 @@ export {
|
|
|
15
17
|
errorFamilyReadMarkerSqlRequired,
|
|
16
18
|
errorFileNotFound,
|
|
17
19
|
errorMigrationCliInvalidConfigArg,
|
|
20
|
+
errorMigrationCliUnknownFlag,
|
|
18
21
|
errorMigrationPlanningFailed,
|
|
19
22
|
errorQueryRunnerFactoryRequired,
|
|
20
23
|
errorTargetMigrationNotSupported,
|
|
21
24
|
errorUnexpected,
|
|
22
25
|
} from '@prisma-next/errors/control';
|
|
26
|
+
import { errorRuntime } from '@prisma-next/errors/execution';
|
|
27
|
+
import type { MigrationToolsError } from '@prisma-next/migration-tools/errors';
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
CliStructuredError,
|
|
31
|
+
errorConfigFileNotFound,
|
|
32
|
+
errorConfigValidation,
|
|
33
|
+
errorContractConfigMissing,
|
|
34
|
+
errorContractMissingExtensionPacks,
|
|
35
|
+
errorContractValidationFailed,
|
|
36
|
+
errorDatabaseConnectionRequired,
|
|
37
|
+
errorDriverRequired,
|
|
38
|
+
errorFamilyReadMarkerSqlRequired,
|
|
39
|
+
errorFileNotFound,
|
|
40
|
+
errorMigrationCliInvalidConfigArg,
|
|
41
|
+
errorMigrationCliUnknownFlag,
|
|
42
|
+
errorMigrationPlanningFailed,
|
|
43
|
+
errorQueryRunnerFactoryRequired,
|
|
44
|
+
errorTargetMigrationNotSupported,
|
|
45
|
+
errorUnexpected,
|
|
46
|
+
};
|
|
23
47
|
export {
|
|
24
48
|
ERROR_CODE_DESTRUCTIVE_CHANGES,
|
|
25
49
|
errorDestructiveChanges,
|
|
@@ -38,3 +62,26 @@ export {
|
|
|
38
62
|
errorUnfilledPlaceholder,
|
|
39
63
|
placeholder,
|
|
40
64
|
} from '@prisma-next/errors/migration';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Maps a `MigrationToolsError` raised by the migration-tools loader/graph
|
|
68
|
+
* surface (`readMigrationPackage`, `readMigrationsDir`, `readRefs`,
|
|
69
|
+
* `resolveRef`, `reconstructGraph`, ...) into a CLI `errorRuntime` envelope.
|
|
70
|
+
*
|
|
71
|
+
* The full `error.details` payload is forwarded into `meta` so machine
|
|
72
|
+
* consumers (`--json`) see structural fields like `dir`, `storedHash`,
|
|
73
|
+
* `computedHash` (for `MIGRATION.HASH_MISMATCH`) alongside the stable
|
|
74
|
+
* `code`. The user-visible `summary`/`why`/`fix` text is unchanged.
|
|
75
|
+
*
|
|
76
|
+
* Callers are expected to gate on `MigrationToolsError.is(error)` first
|
|
77
|
+
* (mirroring the original inline pattern); non-`MigrationToolsError`
|
|
78
|
+
* values are caller-classified (rethrow, wrap with command-specific
|
|
79
|
+
* `errorUnexpected`, etc.).
|
|
80
|
+
*/
|
|
81
|
+
export function mapMigrationToolsError(error: MigrationToolsError): CliStructuredError {
|
|
82
|
+
return errorRuntime(error.message, {
|
|
83
|
+
why: error.why,
|
|
84
|
+
fix: error.fix,
|
|
85
|
+
meta: { code: error.code, ...(error.details ?? {}) },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import type { ControlTargetDescriptor } from '@prisma-next/framework-components/control';
|
|
3
3
|
import { hasMigrations } from '@prisma-next/framework-components/control';
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
4
|
+
import type { NoInvariantPathStructuralEdge } from '@prisma-next/migration-tools/errors';
|
|
5
|
+
import type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
6
6
|
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
7
|
-
import type {
|
|
7
|
+
import type { PathDecision } from '@prisma-next/migration-tools/migration-graph';
|
|
8
|
+
import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
|
|
9
|
+
import type { MigrationPackage } from '@prisma-next/migration-tools/package';
|
|
8
10
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
9
11
|
import type { Command } from 'commander';
|
|
10
12
|
import { relative, resolve } from 'pathe';
|
|
@@ -109,14 +111,45 @@ export interface PathDecisionResult {
|
|
|
109
111
|
readonly alternativeCount: number;
|
|
110
112
|
readonly tieBreakReasons: readonly string[];
|
|
111
113
|
readonly refName?: string;
|
|
114
|
+
readonly requiredInvariants: readonly string[];
|
|
115
|
+
readonly satisfiedInvariants: readonly string[];
|
|
112
116
|
readonly selectedPath: readonly {
|
|
113
117
|
readonly dirName: string;
|
|
114
|
-
readonly
|
|
118
|
+
readonly migrationHash: string;
|
|
115
119
|
readonly from: string;
|
|
116
120
|
readonly to: string;
|
|
121
|
+
readonly invariants: readonly string[];
|
|
117
122
|
}[];
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
export function collectDeclaredInvariants(graph: MigrationGraph): ReadonlySet<string> {
|
|
126
|
+
const declared = new Set<string>();
|
|
127
|
+
for (const edges of graph.forwardChain.values()) {
|
|
128
|
+
for (const edge of edges) {
|
|
129
|
+
for (const inv of edge.invariants) {
|
|
130
|
+
declared.add(inv);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return declared;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Maps a `MigrationEdge` to the structural-edge shape used in the
|
|
139
|
+
* `MIGRATION.NO_INVARIANT_PATH` error envelope. Shared between
|
|
140
|
+
* `migration apply` and `migration status` so both commands surface
|
|
141
|
+
* the same JSON wire shape when an invariant-aware route is unsatisfiable.
|
|
142
|
+
*/
|
|
143
|
+
export function toStructuralEdge(edge: MigrationEdge): NoInvariantPathStructuralEdge {
|
|
144
|
+
return {
|
|
145
|
+
dirName: edge.dirName,
|
|
146
|
+
migrationHash: edge.migrationHash,
|
|
147
|
+
from: edge.from,
|
|
148
|
+
to: edge.to,
|
|
149
|
+
invariants: edge.invariants,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
120
153
|
/**
|
|
121
154
|
* Maps a PathDecision to the slim CLI output representation.
|
|
122
155
|
*/
|
|
@@ -126,12 +159,15 @@ export function toPathDecisionResult(decision: PathDecision): PathDecisionResult
|
|
|
126
159
|
toHash: decision.toHash,
|
|
127
160
|
alternativeCount: decision.alternativeCount,
|
|
128
161
|
tieBreakReasons: decision.tieBreakReasons,
|
|
162
|
+
requiredInvariants: decision.requiredInvariants ?? [],
|
|
163
|
+
satisfiedInvariants: decision.satisfiedInvariants ?? [],
|
|
129
164
|
...ifDefined('refName', decision.refName),
|
|
130
165
|
selectedPath: decision.selectedPath.map((entry) => ({
|
|
131
166
|
dirName: entry.dirName,
|
|
132
|
-
|
|
167
|
+
migrationHash: entry.migrationHash,
|
|
133
168
|
from: entry.from,
|
|
134
169
|
to: entry.to,
|
|
170
|
+
invariants: entry.invariants,
|
|
135
171
|
})),
|
|
136
172
|
};
|
|
137
173
|
}
|
|
@@ -146,13 +182,13 @@ export function getTargetMigrations(target: ControlTargetDescriptor<string, stri
|
|
|
146
182
|
|
|
147
183
|
/**
|
|
148
184
|
* Reads the migrations directory and builds the migration graph from all
|
|
149
|
-
*
|
|
185
|
+
* packages. Throws on I/O or graph errors — callers handle error mapping.
|
|
150
186
|
*
|
|
151
|
-
* Every on-disk
|
|
187
|
+
* Every on-disk package is content-addressed (`migrationHash` is always a
|
|
152
188
|
* string); there is no draft state to filter out.
|
|
153
189
|
*/
|
|
154
|
-
export async function
|
|
155
|
-
bundles: readonly
|
|
190
|
+
export async function loadMigrationPackages(migrationsDir: string): Promise<{
|
|
191
|
+
bundles: readonly MigrationPackage[];
|
|
156
192
|
graph: MigrationGraph;
|
|
157
193
|
}> {
|
|
158
194
|
const bundles = await readMigrationsDir(migrationsDir);
|
|
@@ -160,20 +196,6 @@ export async function loadMigrationBundles(migrationsDir: string): Promise<{
|
|
|
160
196
|
return { bundles, graph };
|
|
161
197
|
}
|
|
162
198
|
|
|
163
|
-
export interface MigrationBundleSet {
|
|
164
|
-
readonly bundles: readonly MigrationBundle[];
|
|
165
|
-
readonly graph: MigrationGraph;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Alias of `loadMigrationBundles` retained for naming-clarity in commands
|
|
170
|
-
* that previously needed both attested and draft splits. With the
|
|
171
|
-
* collapse of the draft state, both helpers do the same thing.
|
|
172
|
-
*/
|
|
173
|
-
export async function loadAllBundles(migrationsDir: string): Promise<MigrationBundleSet> {
|
|
174
|
-
return loadMigrationBundles(migrationsDir);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
199
|
/**
|
|
178
200
|
* The subset of the emitted contract.json that the framework layer can
|
|
179
201
|
* safely type. The emitter adds these fields on top of the family-specific
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-output FIFO queue for `executeContractEmit`.
|
|
3
|
+
*
|
|
4
|
+
* Ensures that at most one emit (load → resolve source → emit bytes → publish)
|
|
5
|
+
* runs per output JSON path at a time. Concurrent calls for the same path
|
|
6
|
+
* line up behind the in-flight one and run in submission order; the user-visible
|
|
7
|
+
* outcome is "last submission wins on disk" without any supersession bookkeeping.
|
|
8
|
+
*
|
|
9
|
+
* Long-lived hosts (Vite dev server, watch CLIs) must call `disposeEmitQueue`
|
|
10
|
+
* when they stop publishing to a path, otherwise the module-global `Map`
|
|
11
|
+
* accumulates one entry per unique output path for the lifetime of the process.
|
|
12
|
+
*/
|
|
13
|
+
const emitQueues = new Map<string, Promise<unknown>>();
|
|
14
|
+
|
|
15
|
+
export function queueEmitByOutput<T>(outputJsonPath: string, action: () => Promise<T>): Promise<T> {
|
|
16
|
+
const previous = emitQueues.get(outputJsonPath) ?? Promise.resolve();
|
|
17
|
+
// Continue regardless of the previous task's outcome — a failed emit must not
|
|
18
|
+
// block subsequent ones. The current task's outcome propagates via `next`.
|
|
19
|
+
const next = previous.then(action, action);
|
|
20
|
+
emitQueues.set(outputJsonPath, next);
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function disposeEmitQueue(outputJsonPath: string): void {
|
|
25
|
+
emitQueues.delete(outputJsonPath);
|
|
26
|
+
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Maps MigrationGraph + status info to the generic graph renderer types.
|
|
3
3
|
*/
|
|
4
4
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
6
|
+
import { findPath } from '@prisma-next/migration-tools/migration-graph';
|
|
7
7
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
8
8
|
|
|
9
9
|
import type { StatusRef } from '../migration-types';
|
|
@@ -106,7 +106,11 @@ export function migrationGraphToRenderInput(input: MigrationGraphInput): Migrati
|
|
|
106
106
|
for (const entry of entries) {
|
|
107
107
|
const status = statusByDirName.get(entry.dirName);
|
|
108
108
|
const icon = status ? STATUS_ICON[status] : '';
|
|
109
|
-
const
|
|
109
|
+
const invariantsSuffix =
|
|
110
|
+
entry.invariants.length > 0
|
|
111
|
+
? ` provides [${entry.invariants.map((id) => JSON.stringify(id)).join(', ')}]`
|
|
112
|
+
: '';
|
|
113
|
+
const label = `${entry.dirName}${icon}${invariantsSuffix}`;
|
|
110
114
|
|
|
111
115
|
edgeList.push({
|
|
112
116
|
from: toShortId(entry.from),
|
|
@@ -1,8 +1,41 @@
|
|
|
1
|
+
import type { OperationPreview } from '@prisma-next/framework-components/control';
|
|
1
2
|
import { green, yellow } from 'colorette';
|
|
2
3
|
|
|
3
4
|
import type { GlobalFlags } from '../global-flags';
|
|
4
5
|
import { createColorFormatter, formatDim, isVerbose } from './helpers';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Render a single statement of an `OperationPreview` for the human-readable
|
|
9
|
+
* preview block. SQL statements get a trailing `;` if missing — matches the
|
|
10
|
+
* legacy `string[]`-based renderer byte-for-byte (per spec OQ-4). Other
|
|
11
|
+
* languages (`'mongodb-shell'`) render verbatim.
|
|
12
|
+
*/
|
|
13
|
+
function renderPreviewStatement(text: string, language: string): string | undefined {
|
|
14
|
+
const trimmed = text.trim();
|
|
15
|
+
if (!trimmed) return undefined;
|
|
16
|
+
if (language === 'sql') {
|
|
17
|
+
return trimmed.endsWith(';') ? trimmed : `${trimmed};`;
|
|
18
|
+
}
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Choose the header label for a preview block. SQL-only previews keep the
|
|
24
|
+
* legacy `DDL preview` label (preserves CLI byte-identity for SQL targets per
|
|
25
|
+
* spec OQ-4); previews from any other family — or a mix that includes any
|
|
26
|
+
* non-SQL language — use the family-agnostic `Operation preview` label.
|
|
27
|
+
*
|
|
28
|
+
* An empty `statements` array deliberately renders as `Operation preview`
|
|
29
|
+
* rather than `DDL preview`: `Array.prototype.every` is vacuously true for
|
|
30
|
+
* empty arrays, but we have no evidence the preview is SQL-only when no
|
|
31
|
+
* statements are present, so the family-agnostic label is the safer default.
|
|
32
|
+
*/
|
|
33
|
+
export function previewBlockHeader(preview: OperationPreview): string {
|
|
34
|
+
const allSql =
|
|
35
|
+
preview.statements.length > 0 && preview.statements.every((s) => s.language === 'sql');
|
|
36
|
+
return allSql ? 'DDL preview' : 'Operation preview';
|
|
37
|
+
}
|
|
38
|
+
|
|
6
39
|
// ============================================================================
|
|
7
40
|
// Migration Command Output Formatters (shared by db init and db update)
|
|
8
41
|
// ============================================================================
|
|
@@ -24,7 +57,12 @@ export interface MigrationCommandResult {
|
|
|
24
57
|
readonly label: string;
|
|
25
58
|
readonly operationClass: string;
|
|
26
59
|
}[];
|
|
27
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Family-agnostic textual preview of the planned operations. Replaces the
|
|
62
|
+
* previous `sql?: readonly string[]`. Consumers should read
|
|
63
|
+
* `plan.preview?.statements`.
|
|
64
|
+
*/
|
|
65
|
+
readonly preview?: OperationPreview;
|
|
28
66
|
};
|
|
29
67
|
readonly execution?: {
|
|
30
68
|
readonly operationsPlanned: number;
|
|
@@ -92,20 +130,20 @@ export function formatMigrationPlanOutput(
|
|
|
92
130
|
lines.push(`${formatDimText(`Destination hash: ${result.plan.destination.storageHash}`)}`);
|
|
93
131
|
}
|
|
94
132
|
|
|
95
|
-
//
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
133
|
+
// Statement preview (any family that implements OperationPreviewCapable)
|
|
134
|
+
const preview = result.plan?.preview;
|
|
135
|
+
if (preview) {
|
|
98
136
|
lines.push('');
|
|
99
|
-
lines.push(`${formatDimText(
|
|
100
|
-
if (
|
|
101
|
-
lines.push(`${formatDimText('No
|
|
137
|
+
lines.push(`${formatDimText(previewBlockHeader(preview))}`);
|
|
138
|
+
if (preview.statements.length === 0) {
|
|
139
|
+
lines.push(`${formatDimText('No operations.')}`);
|
|
102
140
|
} else {
|
|
103
141
|
lines.push('');
|
|
104
|
-
for (const statement of
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
for (const statement of preview.statements) {
|
|
143
|
+
const rendered = renderPreviewStatement(statement.text, statement.language);
|
|
144
|
+
if (rendered) {
|
|
145
|
+
lines.push(rendered);
|
|
146
|
+
}
|
|
109
147
|
}
|
|
110
148
|
}
|
|
111
149
|
}
|
|
@@ -181,17 +219,16 @@ export function formatMigrationApplyCommandOutput(
|
|
|
181
219
|
interface MigrationShowResult {
|
|
182
220
|
readonly dirName: string;
|
|
183
221
|
readonly dirPath: string;
|
|
184
|
-
readonly from: string;
|
|
222
|
+
readonly from: string | null;
|
|
185
223
|
readonly to: string;
|
|
186
|
-
readonly
|
|
187
|
-
readonly kind: string;
|
|
224
|
+
readonly migrationHash: string;
|
|
188
225
|
readonly createdAt: string;
|
|
189
226
|
readonly operations: readonly {
|
|
190
227
|
readonly id: string;
|
|
191
228
|
readonly label: string;
|
|
192
229
|
readonly operationClass: string;
|
|
193
230
|
}[];
|
|
194
|
-
readonly
|
|
231
|
+
readonly preview: OperationPreview;
|
|
195
232
|
readonly summary: string;
|
|
196
233
|
}
|
|
197
234
|
|
|
@@ -208,10 +245,9 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
208
245
|
const formatDimText = (text: string) => formatDim(useColor, text);
|
|
209
246
|
|
|
210
247
|
lines.push(`${formatGreen('✔')} ${result.dirName}`);
|
|
211
|
-
lines.push(`${formatDimText(`
|
|
212
|
-
lines.push(`${formatDimText(` from: ${result.from}`)}`);
|
|
248
|
+
lines.push(`${formatDimText(` from: ${result.from ?? '(baseline)'}`)}`);
|
|
213
249
|
lines.push(`${formatDimText(` to: ${result.to}`)}`);
|
|
214
|
-
lines.push(`${formatDimText(`
|
|
250
|
+
lines.push(`${formatDimText(` migrationHash: ${result.migrationHash}`)}`);
|
|
215
251
|
lines.push(`${formatDimText(` created: ${result.createdAt}`)}`);
|
|
216
252
|
|
|
217
253
|
lines.push('');
|
|
@@ -239,15 +275,15 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
|
|
|
239
275
|
}
|
|
240
276
|
}
|
|
241
277
|
|
|
242
|
-
if (result.
|
|
278
|
+
if (result.preview.statements.length > 0) {
|
|
243
279
|
lines.push('');
|
|
244
|
-
lines.push(`${formatDimText(
|
|
280
|
+
lines.push(`${formatDimText(previewBlockHeader(result.preview))}`);
|
|
245
281
|
lines.push('');
|
|
246
|
-
for (const statement of result.
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
282
|
+
for (const statement of result.preview.statements) {
|
|
283
|
+
const rendered = renderPreviewStatement(statement.text, statement.language);
|
|
284
|
+
if (rendered) {
|
|
285
|
+
lines.push(rendered);
|
|
286
|
+
}
|
|
251
287
|
}
|
|
252
288
|
}
|
|
253
289
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join } from 'pathe';
|
|
3
|
+
|
|
4
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
|
+
return typeof value === 'object' && value !== null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function createTempArtifactPath(path: string, publicationToken: string, phase: string): string {
|
|
9
|
+
return join(dirname(path), `.${basename(path)}.${process.pid}.${publicationToken}.${phase}.tmp`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type PreviousArtifact = { readonly content: string } | 'remove';
|
|
13
|
+
|
|
14
|
+
async function readExistingArtifact(path: string): Promise<PreviousArtifact> {
|
|
15
|
+
try {
|
|
16
|
+
return { content: await readFile(path, 'utf-8') };
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (isRecord(error) && error['code'] === 'ENOENT') {
|
|
19
|
+
return 'remove';
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function restoreArtifact(
|
|
26
|
+
path: string,
|
|
27
|
+
previous: PreviousArtifact,
|
|
28
|
+
publicationToken: string,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
if (previous === 'remove') {
|
|
31
|
+
await rm(path, { force: true });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const restorePath = createTempArtifactPath(path, publicationToken, 'rollback');
|
|
36
|
+
await writeFile(restorePath, previous.content, 'utf-8');
|
|
37
|
+
try {
|
|
38
|
+
await rename(restorePath, path);
|
|
39
|
+
} finally {
|
|
40
|
+
await rm(restorePath, { force: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface PublishEntry {
|
|
45
|
+
readonly tempPath: string;
|
|
46
|
+
readonly outputPath: string;
|
|
47
|
+
readonly previous: PreviousArtifact;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function withRollbackFailureCause(error: unknown, rollbackFailures: readonly unknown[]): Error {
|
|
51
|
+
const rollbackCause = new AggregateError(
|
|
52
|
+
rollbackFailures,
|
|
53
|
+
'Failed to restore published artifacts',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (error instanceof Error) {
|
|
57
|
+
Object.defineProperty(error, 'cause', {
|
|
58
|
+
value: rollbackCause,
|
|
59
|
+
configurable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
});
|
|
62
|
+
return error;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return new Error(String(error), { cause: rollbackCause });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function publishPairWithRollback(
|
|
69
|
+
entries: readonly PublishEntry[],
|
|
70
|
+
publicationToken: string,
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
const replaced: PublishEntry[] = [];
|
|
73
|
+
try {
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
await rename(entry.tempPath, entry.outputPath);
|
|
76
|
+
replaced.push(entry);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
const rollbackResults = await Promise.allSettled(
|
|
80
|
+
replaced.map((entry) => restoreArtifact(entry.outputPath, entry.previous, publicationToken)),
|
|
81
|
+
);
|
|
82
|
+
const rollbackFailures = rollbackResults.flatMap((result) =>
|
|
83
|
+
result.status === 'rejected' ? [result.reason] : [],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (rollbackFailures.length > 0) {
|
|
87
|
+
throw withRollbackFailureCause(error, rollbackFailures);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function publishContractArtifactPair({
|
|
95
|
+
outputJsonPath,
|
|
96
|
+
outputDtsPath,
|
|
97
|
+
contractJson,
|
|
98
|
+
contractDts,
|
|
99
|
+
publicationToken,
|
|
100
|
+
beforePublish,
|
|
101
|
+
}: {
|
|
102
|
+
readonly outputJsonPath: string;
|
|
103
|
+
readonly outputDtsPath: string;
|
|
104
|
+
readonly contractJson: string;
|
|
105
|
+
readonly contractDts: string;
|
|
106
|
+
readonly publicationToken: string;
|
|
107
|
+
readonly beforePublish?: () => Promise<boolean> | boolean;
|
|
108
|
+
}): Promise<boolean> {
|
|
109
|
+
const tempJsonPath = createTempArtifactPath(outputJsonPath, publicationToken, 'next');
|
|
110
|
+
const tempDtsPath = createTempArtifactPath(outputDtsPath, publicationToken, 'next');
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await writeFile(tempJsonPath, contractJson, 'utf-8');
|
|
114
|
+
await writeFile(tempDtsPath, contractDts, 'utf-8');
|
|
115
|
+
|
|
116
|
+
if ((await beforePublish?.()) === false) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const previousJson = await readExistingArtifact(outputJsonPath);
|
|
121
|
+
const previousDts = await readExistingArtifact(outputDtsPath);
|
|
122
|
+
|
|
123
|
+
await publishPairWithRollback(
|
|
124
|
+
[
|
|
125
|
+
{ tempPath: tempDtsPath, outputPath: outputDtsPath, previous: previousDts },
|
|
126
|
+
{ tempPath: tempJsonPath, outputPath: outputJsonPath, previous: previousJson },
|
|
127
|
+
],
|
|
128
|
+
publicationToken,
|
|
129
|
+
);
|
|
130
|
+
return true;
|
|
131
|
+
} finally {
|
|
132
|
+
await Promise.allSettled([rm(tempJsonPath, { force: true }), rm(tempDtsPath, { force: true })]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { CliStructuredError as CliStructuredError$1, errorConfigValidation as errorConfigValidation$1, errorContractConfigMissing as errorContractConfigMissing$1, errorContractValidationFailed, errorDatabaseConnectionRequired, errorDriverRequired, errorFileNotFound, errorMigrationPlanningFailed, errorTargetMigrationNotSupported, errorUnexpected as errorUnexpected$1 } from "@prisma-next/errors/control";
|
|
2
|
-
import { ERROR_CODE_DESTRUCTIVE_CHANGES, errorDestructiveChanges, errorHashMismatch, errorMarkerMissing, errorRunnerFailed, errorRuntime as errorRuntime$1, errorTargetMismatch } from "@prisma-next/errors/execution";
|
|
3
|
-
import "@prisma-next/errors/migration";
|
|
4
|
-
|
|
5
|
-
export { errorUnexpected$1 as _, errorContractValidationFailed as a, errorDriverRequired as c, errorMarkerMissing as d, errorMigrationPlanningFailed as f, errorTargetMismatch as g, errorTargetMigrationNotSupported as h, errorContractConfigMissing$1 as i, errorFileNotFound as l, errorRuntime$1 as m, ERROR_CODE_DESTRUCTIVE_CHANGES as n, errorDatabaseConnectionRequired as o, errorRunnerFailed as p, errorConfigValidation$1 as r, errorDestructiveChanges as s, CliStructuredError$1 as t, errorHashMismatch as u };
|