@prisma-next/cli 0.14.0 → 0.15.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.mjs +33 -14
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-CJzuo5wX.mjs → client-DigVI8zX.mjs} +90 -104
- package/dist/client-DigVI8zX.mjs.map +1 -0
- package/dist/{command-helpers-DGMvGBeX.mjs → command-helpers-CUoKYTlG.mjs} +13 -10
- package/dist/command-helpers-CUoKYTlG.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +3 -3
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +5 -5
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +37 -38
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +1 -1
- package/dist/commands/migration-graph.mjs +9 -9
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +6 -6
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.d.mts +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +4 -4
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +4 -4
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +3 -3
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-CFXsstzm.mjs → contract-at-errors-D8CNDD7g.mjs} +2 -2
- package/dist/{contract-at-errors-CFXsstzm.mjs.map → contract-at-errors-D8CNDD7g.mjs.map} +1 -1
- package/dist/{contract-emit-C8HmtboH.mjs → contract-emit-sYrqzsdN.mjs} +9 -11
- package/dist/contract-emit-sYrqzsdN.mjs.map +1 -0
- package/dist/{contract-emit-B_qriF8B.mjs → contract-emit-wgqrOXUI.mjs} +6 -6
- package/dist/contract-emit-wgqrOXUI.mjs.map +1 -0
- package/dist/{contract-infer-BYT_ra_U.mjs → contract-infer-6SQnnVj7.mjs} +3 -3
- package/dist/{contract-infer-BYT_ra_U.mjs.map → contract-infer-6SQnnVj7.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-ClI1KN6d.mjs → contract-space-aggregate-loader-DpB4x97a.mjs} +63 -5
- package/dist/contract-space-aggregate-loader-DpB4x97a.mjs.map +1 -0
- package/dist/{db-verify-C24FKhb7.mjs → db-verify-CEsG5Gek.mjs} +57 -61
- package/dist/db-verify-CEsG5Gek.mjs.map +1 -0
- package/dist/exports/control-api.d.mts +3 -3
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.d.mts +5 -1
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +4 -3
- package/dist/exports/index.mjs.map +1 -1
- package/dist/format-CjongNb1.mjs +102 -0
- package/dist/format-CjongNb1.mjs.map +1 -0
- package/dist/{framework-components-YVQHhPH7.mjs → framework-components-BbnM1RYm.mjs} +2 -2
- package/dist/{framework-components-YVQHhPH7.mjs.map → framework-components-BbnM1RYm.mjs.map} +1 -1
- package/dist/{init-0HwB-Vh8.mjs → init-D8YFfaD6.mjs} +27 -19
- package/dist/init-D8YFfaD6.mjs.map +1 -0
- package/dist/{inspect-live-schema-DF6IwcDl.mjs → inspect-live-schema-CEt_ztBJ.mjs} +4 -4
- package/dist/inspect-live-schema-CEt_ztBJ.mjs.map +1 -0
- package/dist/{migration-check-VwM8xCZV.mjs → migration-check-ByeQHOhK.mjs} +10 -12
- package/dist/migration-check-ByeQHOhK.mjs.map +1 -0
- package/dist/migration-cli.mjs +1 -1
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-DA-Lhx6o.mjs → migration-command-scaffold-CJO3tjiR.mjs} +4 -4
- package/dist/migration-command-scaffold-CJO3tjiR.mjs.map +1 -0
- package/dist/{migration-graph-command-render-CEez7YUK.mjs → migration-graph-command-render-B83xrnNy.mjs} +24 -24
- package/dist/{migration-graph-command-render-CEez7YUK.mjs.map → migration-graph-command-render-B83xrnNy.mjs.map} +1 -1
- package/dist/{migration-list-DlJJ_38Z.mjs → migration-list-DTYM8xvm.mjs} +16 -16
- package/dist/migration-list-DTYM8xvm.mjs.map +1 -0
- package/dist/{migration-log-CG0qQAFm.mjs → migration-log-qZ8LpQIl.mjs} +5 -5
- package/dist/migration-log-qZ8LpQIl.mjs.map +1 -0
- package/dist/{migration-path-target-Ce6OZImp.mjs → migration-path-target-DmNwsDgs.mjs} +2 -2
- package/dist/{migration-path-target-Ce6OZImp.mjs.map → migration-path-target-DmNwsDgs.mjs.map} +1 -1
- package/dist/{migration-plan-W_E8FQOk.mjs → migration-plan-CfPrB80t.mjs} +30 -30
- package/dist/migration-plan-CfPrB80t.mjs.map +1 -0
- package/dist/{migration-status-CD-LC2Ip.mjs → migration-status-DRuS-rP5.mjs} +14 -15
- package/dist/migration-status-DRuS-rP5.mjs.map +1 -0
- package/dist/{telemetry-BIM4beEO.mjs → telemetry-DHBBq5wT.mjs} +2 -2
- package/dist/{telemetry-BIM4beEO.mjs.map → telemetry-DHBBq5wT.mjs.map} +1 -1
- package/dist/{types-C_tYiJYx.d.mts → types-DFoqNIcQ.d.mts} +27 -21
- package/dist/types-DFoqNIcQ.d.mts.map +1 -0
- package/dist/{verify-DcOYZ1tH.mjs → verify-DDCb0R2r.mjs} +57 -159
- package/dist/verify-DDCb0R2r.mjs.map +1 -0
- package/package.json +28 -30
- package/src/cli.ts +7 -0
- package/src/commands/contract-emit.ts +1 -1
- package/src/commands/db-sign.ts +1 -1
- package/src/commands/db-verify.ts +34 -20
- package/src/commands/format.ts +75 -0
- package/src/commands/init/detect-package-manager.ts +8 -4
- package/src/commands/init/hygiene-package-scripts.ts +16 -3
- package/src/commands/inspect-live-schema.ts +1 -1
- package/src/commands/lsp.ts +32 -0
- package/src/commands/migrate.ts +47 -44
- package/src/commands/migration-check.ts +6 -6
- package/src/commands/migration-graph.ts +5 -5
- package/src/commands/migration-list.ts +13 -13
- package/src/commands/migration-log.ts +1 -1
- package/src/commands/migration-new.ts +1 -1
- package/src/commands/migration-plan.ts +15 -4
- package/src/commands/migration-show.ts +1 -1
- package/src/commands/migration-status.ts +10 -10
- package/src/commands/ref.ts +1 -1
- package/src/control-api/client.ts +8 -23
- package/src/control-api/operations/contract-emit.ts +9 -27
- package/src/control-api/operations/db-init.ts +2 -2
- package/src/control-api/operations/db-run.ts +18 -17
- package/src/control-api/operations/db-verify.ts +52 -44
- package/src/control-api/operations/format.ts +98 -0
- package/src/control-api/operations/migrate.ts +72 -72
- package/src/control-api/operations/run-migration.ts +6 -5
- package/src/control-api/types.ts +8 -8
- package/src/exports/index.ts +1 -0
- package/src/migration-cli.ts +1 -1
- package/src/utils/combine-verify-results.ts +94 -0
- package/src/utils/extension-pack-inputs.ts +1 -1
- package/src/utils/formatters/errors.ts +13 -5
- package/src/utils/formatters/verify.ts +88 -227
- package/src/utils/migration-command-scaffold.ts +1 -1
- package/src/utils/plan-resolution.ts +18 -18
- package/dist/client-CJzuo5wX.mjs.map +0 -1
- package/dist/command-helpers-DGMvGBeX.mjs.map +0 -1
- package/dist/config-loader-p9JMrekQ.mjs +0 -88
- package/dist/config-loader-p9JMrekQ.mjs.map +0 -1
- package/dist/config-loader.d.mts +0 -16
- package/dist/config-loader.d.mts.map +0 -1
- package/dist/config-loader.mjs +0 -2
- package/dist/contract-emit-B_qriF8B.mjs.map +0 -1
- package/dist/contract-emit-C8HmtboH.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-ClI1KN6d.mjs.map +0 -1
- package/dist/db-verify-C24FKhb7.mjs.map +0 -1
- package/dist/extension-pack-inputs-1ySHqxKG.mjs +0 -62
- package/dist/extension-pack-inputs-1ySHqxKG.mjs.map +0 -1
- package/dist/init-0HwB-Vh8.mjs.map +0 -1
- package/dist/inspect-live-schema-DF6IwcDl.mjs.map +0 -1
- package/dist/migration-check-VwM8xCZV.mjs.map +0 -1
- package/dist/migration-command-scaffold-DA-Lhx6o.mjs.map +0 -1
- package/dist/migration-list-DlJJ_38Z.mjs.map +0 -1
- package/dist/migration-log-CG0qQAFm.mjs.map +0 -1
- package/dist/migration-plan-W_E8FQOk.mjs.map +0 -1
- package/dist/migration-status-CD-LC2Ip.mjs.map +0 -1
- package/dist/types-C_tYiJYx.d.mts.map +0 -1
- package/dist/verify-DcOYZ1tH.mjs.map +0 -1
- package/src/config-loader.ts +0 -92
- package/src/config-path-validation.ts +0 -74
- package/src/utils/combine-schema-results.ts +0 -84
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { loadConfig } from '@prisma-next/config-loader';
|
|
2
3
|
import type { Contract } from '@prisma-next/contract/types';
|
|
3
4
|
import { emit, getEmittedArtifactPaths } from '@prisma-next/emitter';
|
|
4
5
|
import { createControlStack } from '@prisma-next/framework-components/control';
|
|
5
6
|
import { abortable } from '@prisma-next/utils/abortable';
|
|
6
|
-
import { blindCast } from '@prisma-next/utils/casts';
|
|
7
7
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
8
8
|
import type { JsonObject } from '@prisma-next/utils/json';
|
|
9
9
|
import { dirname, join } from 'pathe';
|
|
10
|
-
import { loadConfig } from '../../config-loader';
|
|
11
10
|
import { errorContractConfigMissing, errorRuntime } from '../../utils/cli-errors';
|
|
12
11
|
import { queueEmitByOutput } from '../../utils/emit-queue';
|
|
13
|
-
import { toExtensionInputs } from '../../utils/extension-pack-inputs';
|
|
14
12
|
import { assertFrameworkComponentsCompatible } from '../../utils/framework-components';
|
|
15
13
|
import { publishContractArtifactPair } from '../../utils/publish-contract-artifact-pair';
|
|
16
14
|
import { validateContractDeps } from '../../utils/validate-contract-deps';
|
|
@@ -196,31 +194,15 @@ export async function executeContractEmit(
|
|
|
196
194
|
return queueEmitByOutput(outputJsonPath, async () => {
|
|
197
195
|
const stack = createControlStack(config);
|
|
198
196
|
|
|
199
|
-
const extensionInputs = toExtensionInputs(
|
|
200
|
-
blindCast<
|
|
201
|
-
readonly unknown[],
|
|
202
|
-
'toExtensionInputs accepts readonly unknown[] per its documented structural cast boundary'
|
|
203
|
-
>(stack.extensionPacks),
|
|
204
|
-
);
|
|
205
|
-
const composedExtensionContracts = new Map<string, Contract>(
|
|
206
|
-
extensionInputs
|
|
207
|
-
.filter((p) => p.contractSpace !== undefined)
|
|
208
|
-
.map((p) => [
|
|
209
|
-
p.id,
|
|
210
|
-
blindCast<
|
|
211
|
-
Contract,
|
|
212
|
-
'contractSpace.contractJson is the typed contract for this extension space'
|
|
213
|
-
>(p.contractSpace!.contractJson),
|
|
214
|
-
]),
|
|
215
|
-
);
|
|
216
197
|
const sourceContext = {
|
|
217
198
|
composedExtensionPacks: stack.extensionPacks.map((p) => p.id),
|
|
218
|
-
composedExtensionContracts,
|
|
199
|
+
composedExtensionContracts: stack.extensionContracts,
|
|
219
200
|
scalarTypeDescriptors: stack.scalarTypeDescriptors,
|
|
220
201
|
authoringContributions: stack.authoringContributions,
|
|
221
202
|
codecLookup: stack.codecLookup,
|
|
222
203
|
controlMutationDefaults: stack.controlMutationDefaults,
|
|
223
204
|
resolvedInputs: contractConfig.source.inputs ?? [],
|
|
205
|
+
capabilities: stack.capabilities,
|
|
224
206
|
};
|
|
225
207
|
|
|
226
208
|
startSpan(onProgress, 'resolveSource', 'Resolving contract source...');
|
|
@@ -258,16 +240,16 @@ export async function executeContractEmit(
|
|
|
258
240
|
// Blind cast: `validateProviderResult` upstream has already
|
|
259
241
|
// pinned `validatedContract.value` to the provider's loose
|
|
260
242
|
// `Contract` envelope, but the local `Contract` type at this
|
|
261
|
-
// call site is the precise structural interface.
|
|
262
|
-
// the
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
// by one statement so `enrichContract` can decorate first.
|
|
243
|
+
// call site is the precise structural interface. The cast just
|
|
244
|
+
// defers the structural check by one statement so `enrichContract`
|
|
245
|
+
// can decorate first; the subsequent serialize→deserialize round-trip
|
|
246
|
+
// re-narrows the envelope into the precise type.
|
|
266
247
|
const enrichedIR = enrichContract(
|
|
267
248
|
validatedContract.value as unknown as Contract,
|
|
268
249
|
frameworkComponents,
|
|
269
250
|
);
|
|
270
|
-
const
|
|
251
|
+
const rawContractJson = config.target.contractSerializer.serializeContract(enrichedIR);
|
|
252
|
+
const deserializedContract = familyInstance.deserializeContract(rawContractJson);
|
|
271
253
|
// Each target's descriptor ships a `contractSerializer` SPI; the
|
|
272
254
|
// framework canonicalizer threads its `serializeContract` so the
|
|
273
255
|
// on-disk JSON envelope is constructed by target-owned code
|
|
@@ -20,7 +20,7 @@ import { executeRun } from './db-run';
|
|
|
20
20
|
* {@link import('@prisma-next/migration-tools/aggregate').loadContractSpaceAggregate}
|
|
21
21
|
* from the supplied descriptor set + on-disk on-disk artefacts.
|
|
22
22
|
* 2. The aggregate planner runs with `callerPolicy.ignoreGraphFor`
|
|
23
|
-
* locked to the app
|
|
23
|
+
* locked to the app space — synth strategy for the app, graph-walk
|
|
24
24
|
* for every extension.
|
|
25
25
|
* 3. The runner's `execute` applies the per-space plans
|
|
26
26
|
* inside one outer transaction.
|
|
@@ -53,7 +53,7 @@ export interface ExecuteDbInitOptions<TFamilyId extends string, TTargetId extend
|
|
|
53
53
|
/**
|
|
54
54
|
* Declared extension descriptors. Defaults to an empty list, which
|
|
55
55
|
* routes through the same loader → planner → runner pipeline with no
|
|
56
|
-
* extension
|
|
56
|
+
* extension spaces in the aggregate.
|
|
57
57
|
*/
|
|
58
58
|
readonly extensionPacks?: ReadonlyArray<ControlExtensionDescriptor<TFamilyId, TTargetId>>;
|
|
59
59
|
/** Optional progress callback for observing operation progress */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Backs `db init` / `db update`. Strategy: introspect → planMigration;
|
|
2
|
+
* Backs `db init` / `db update`. Strategy: introspect → planMigration; planFromDiff-for-app + resolveRecordedPath-extensions; plan-mode + orphan-marker preflight.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { Contract } from '@prisma-next/contract/types';
|
|
@@ -97,10 +97,11 @@ export interface ExecuteRunOptions<TFamilyId extends string, TTargetId extends s
|
|
|
97
97
|
* integrity violation short-circuits with a structured error.
|
|
98
98
|
* 2. **Read DB state**: marker rows (`familyInstance.readAllMarkers`)
|
|
99
99
|
* + introspected schema (`familyInstance.introspect`).
|
|
100
|
-
* 3. **Plan**: {@link planMigration} chooses
|
|
101
|
-
*
|
|
102
|
-
* is forced through
|
|
103
|
-
* extension
|
|
100
|
+
* 3. **Plan**: {@link planMigration} chooses `resolveRecordedPath` vs
|
|
101
|
+
* `planFromDiff` per space according to `callerPolicy.ignoreGraphFor`.
|
|
102
|
+
* The app space is forced through `planFromDiff` (today's daily-driver
|
|
103
|
+
* behaviour); every extension space walks its on-disk graph via
|
|
104
|
+
* `resolveRecordedPath`.
|
|
104
105
|
* 4. **Apply** (when `mode === 'apply'`): every per-space `MigrationPlan`
|
|
105
106
|
* feeds into the runner's `execute` — one outer
|
|
106
107
|
* transaction across every space; failure on any space rolls back
|
|
@@ -168,9 +169,9 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
|
|
|
168
169
|
});
|
|
169
170
|
onProgress?.({ action, kind: 'spanEnd', spanId: SPAN_IDS.introspect, outcome: 'ok' });
|
|
170
171
|
|
|
171
|
-
// 3. Plan via aggregate planner. App is forced through
|
|
172
|
-
// `db init` / `db update` daily-driver behaviour); extensions
|
|
173
|
-
// their on-disk migration graphs.
|
|
172
|
+
// 3. Plan via aggregate planner. App is forced through planFromDiff
|
|
173
|
+
// (today's `db init` / `db update` daily-driver behaviour); extensions
|
|
174
|
+
// walk their on-disk migration graphs via resolveRecordedPath.
|
|
174
175
|
onProgress?.({
|
|
175
176
|
action,
|
|
176
177
|
kind: 'spanStart',
|
|
@@ -200,7 +201,7 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
|
|
|
200
201
|
const appResolution = orderedResolutions.find((r) => r.spaceId === aggregate.app.spaceId);
|
|
201
202
|
if (!appResolution) {
|
|
202
203
|
throw new Error(
|
|
203
|
-
'Aggregate planner returned no plan for the app
|
|
204
|
+
'Aggregate planner returned no plan for the app space — the planner is supposed to always emit one.',
|
|
204
205
|
);
|
|
205
206
|
}
|
|
206
207
|
const appPlan = appResolution.entry.plan;
|
|
@@ -228,9 +229,9 @@ export async function executeRun<TFamilyId extends string, TTargetId extends str
|
|
|
228
229
|
// 5. Run mode: hand off to the shared `runMigration` primitive.
|
|
229
230
|
// The runner-driving tail is identical for `db init` / `db update` /
|
|
230
231
|
// `migrate` — only how each caller produces `perSpacePlans`
|
|
231
|
-
// differs (
|
|
232
|
-
// only for migrate). Each caller produces
|
|
233
|
-
// this helper handles the shared run tail.
|
|
232
|
+
// differs (planFromDiff + resolveRecordedPath via planMigration here;
|
|
233
|
+
// resolveRecordedPath only for migrate). Each caller produces
|
|
234
|
+
// perSpacePlans differently; this helper handles the shared run tail.
|
|
234
235
|
const applied = await runMigration({
|
|
235
236
|
aggregate,
|
|
236
237
|
perSpacePlans: planResult.value.perSpace,
|
|
@@ -280,14 +281,14 @@ function aggregatePlannerWarnings(
|
|
|
280
281
|
|
|
281
282
|
/**
|
|
282
283
|
* Compare the live `_prisma_marker` rows against the aggregate's
|
|
283
|
-
* declared
|
|
284
|
+
* declared contract spaces. Any marker row whose `space` is not a space of
|
|
284
285
|
* the aggregate is an "orphan" — typically a marker left behind by
|
|
285
286
|
* an extension that was removed from `extensionPacks` without first
|
|
286
287
|
* cleaning up its on-disk migrations / database tables.
|
|
287
288
|
*
|
|
288
289
|
* Returns a {@link CliStructuredError} envelope (code `5002`,
|
|
289
290
|
* `kind: 'orphanMarker'`) for the first orphan it finds, or `null`
|
|
290
|
-
* when every marker row maps to a declared
|
|
291
|
+
* when every marker row maps to a declared contract space. Mirrors the M2
|
|
291
292
|
* `runContractSpaceVerifierMarkerCheck` envelope so downstream
|
|
292
293
|
* tooling (integration tests, JSON consumers) keeps asserting on the
|
|
293
294
|
* same shape.
|
|
@@ -296,13 +297,13 @@ function detectOrphanMarkers(
|
|
|
296
297
|
aggregate: ContractSpaceAggregate,
|
|
297
298
|
markerRows: ReadonlyMap<string, unknown>,
|
|
298
299
|
): CliStructuredError | null {
|
|
299
|
-
const
|
|
300
|
+
const aggregateSpaceIds = new Set<string>([
|
|
300
301
|
aggregate.app.spaceId,
|
|
301
302
|
...aggregate.extensions.map((m) => m.spaceId),
|
|
302
303
|
]);
|
|
303
304
|
const orphans: string[] = [];
|
|
304
305
|
for (const [spaceId, row] of markerRows) {
|
|
305
|
-
if (row !== null && row !== undefined && !
|
|
306
|
+
if (row !== null && row !== undefined && !aggregateSpaceIds.has(spaceId)) {
|
|
306
307
|
orphans.push(spaceId);
|
|
307
308
|
}
|
|
308
309
|
}
|
|
@@ -328,7 +329,7 @@ function detectOrphanMarkers(
|
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
function mapPlannerError(error: PlannerError): DbInitResult | DbUpdateResult {
|
|
331
|
-
if (error.kind === '
|
|
332
|
+
if (error.kind === 'planFromDiffFailed') {
|
|
332
333
|
const failure: DbInitFailure | DbUpdateFailure = {
|
|
333
334
|
code: 'PLANNING_FAILED',
|
|
334
335
|
summary: 'Migration planning failed due to conflicts',
|
|
@@ -4,16 +4,19 @@ import type {
|
|
|
4
4
|
ControlDriverInstance,
|
|
5
5
|
ControlExtensionDescriptor,
|
|
6
6
|
ControlFamilyInstance,
|
|
7
|
+
SchemaDiffIssue,
|
|
7
8
|
VerifyDatabaseSchemaResult,
|
|
8
9
|
} from '@prisma-next/framework-components/control';
|
|
10
|
+
import { hasSchemaSubjectClassifier } from '@prisma-next/framework-components/control';
|
|
9
11
|
import {
|
|
10
|
-
type
|
|
12
|
+
type AggregateContractSpace,
|
|
11
13
|
collectAggregateNamespaces,
|
|
12
14
|
requireHeadRef,
|
|
13
15
|
type VerifierOutput,
|
|
14
16
|
verifyMigration,
|
|
15
17
|
} from '@prisma-next/migration-tools/aggregate';
|
|
16
|
-
import {
|
|
18
|
+
import { castAs } from '@prisma-next/utils/casts';
|
|
19
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
17
20
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
18
21
|
import { CliStructuredError } from '../../utils/cli-errors';
|
|
19
22
|
import {
|
|
@@ -37,9 +40,9 @@ const SPAN_IDS = {
|
|
|
37
40
|
*
|
|
38
41
|
* Loader → verifier pipeline. The loader (sole descriptor-import
|
|
39
42
|
* boundary) builds a {@link import('@prisma-next/migration-tools/aggregate').ContractSpaceAggregate};
|
|
40
|
-
* the aggregate verifier bundles `markerCheck` + per-space
|
|
41
|
-
*
|
|
42
|
-
* facing `--strict` flag.
|
|
43
|
+
* the aggregate verifier bundles `markerCheck` + per-space `schemaCheck`
|
|
44
|
+
* (each contract space verified against the full schema; extras stripped to its own view).
|
|
45
|
+
* `mode: 'strict' | 'lenient'` maps directly to the user facing `--strict` flag.
|
|
43
46
|
*/
|
|
44
47
|
export interface ExecuteDbVerifyOptions<TFamilyId extends string, TTargetId extends string> {
|
|
45
48
|
readonly driver: ControlDriverInstance<TFamilyId, TTargetId>;
|
|
@@ -63,13 +66,19 @@ export interface ExecuteDbVerifyOptions<TFamilyId extends string, TTargetId exte
|
|
|
63
66
|
* emitted, so downstream tooling and integration tests assert on the
|
|
64
67
|
* same shape).
|
|
65
68
|
*
|
|
66
|
-
* On success, the per-space
|
|
69
|
+
* On success, the per-space verify results are returned for the CLI to
|
|
67
70
|
* render. When `skipSchema` is true (`--marker-only`), the schema map
|
|
68
71
|
* is empty.
|
|
69
72
|
*/
|
|
70
73
|
export interface ExecuteDbVerifySuccess {
|
|
71
74
|
readonly schemaResults: ReadonlyMap<string, VerifyDatabaseSchemaResult>;
|
|
72
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Live element names no contract space declares, deduplicated and reported
|
|
77
|
+
* once for the whole database (never per space). Strict mode fails on a
|
|
78
|
+
* non-empty list; lenient mode surfaces it informationally.
|
|
79
|
+
*/
|
|
80
|
+
readonly unclaimed: readonly string[];
|
|
81
|
+
readonly spaceOrder: readonly string[];
|
|
73
82
|
readonly appSpaceId: string;
|
|
74
83
|
}
|
|
75
84
|
|
|
@@ -85,11 +94,11 @@ export type ExecuteDbVerifyResult = Result<ExecuteDbVerifySuccess, CliStructured
|
|
|
85
94
|
* structured CLI error.
|
|
86
95
|
* 2. **Read DB state**: marker rows + (when `skipSchema` is `false`)
|
|
87
96
|
* schema introspection.
|
|
88
|
-
* 3. **Verify**: {@link verifyMigration} returns per-space
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* callers (CLI command) can render
|
|
92
|
-
* returned to the caller verbatim.
|
|
97
|
+
* 3. **Verify**: {@link verifyMigration} returns per-space `markerCheck` +
|
|
98
|
+
* per-space `schemaCheck` (each contract space verified against the full schema,
|
|
99
|
+
* then scoped to its own contract space). Marker mismatches map to
|
|
100
|
+
* `CliStructuredError` (code `5002`) so callers (CLI command) can render
|
|
101
|
+
* and exit. Verify results are returned to the caller verbatim.
|
|
93
102
|
*/
|
|
94
103
|
export async function executeDbVerify<TFamilyId extends string, TTargetId extends string>(
|
|
95
104
|
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
@@ -109,13 +118,26 @@ export async function executeDbVerify<TFamilyId extends string, TTargetId extend
|
|
|
109
118
|
contract: collectAggregateNamespaces(aggregate),
|
|
110
119
|
});
|
|
111
120
|
|
|
121
|
+
// The subject-granularity + entity-kind classifiers are an injected
|
|
122
|
+
// capability — not every family provides one — detected the same way the
|
|
123
|
+
// other optional per-family capabilities are (`hasSchemaView`, …), never
|
|
124
|
+
// assumed.
|
|
125
|
+
const classifySubjectGranularity = hasSchemaSubjectClassifier(familyInstance)
|
|
126
|
+
? (issue: SchemaDiffIssue) => familyInstance.classifySubjectGranularity(issue)
|
|
127
|
+
: undefined;
|
|
128
|
+
const classifyEntityKind = hasSchemaSubjectClassifier(familyInstance)
|
|
129
|
+
? (issue: SchemaDiffIssue) => familyInstance.classifyEntityKind(issue)
|
|
130
|
+
: undefined;
|
|
131
|
+
|
|
112
132
|
emitVerifySpan(onProgress, 'spanStart');
|
|
113
133
|
const verifyResult = verifyMigration({
|
|
114
134
|
aggregate,
|
|
115
135
|
markersBySpaceId,
|
|
116
136
|
schemaIntrospection,
|
|
117
137
|
mode: options.mode,
|
|
118
|
-
|
|
138
|
+
verifySchemaForSpace: createPerSpaceVerifier(options),
|
|
139
|
+
...ifDefined('classifySubjectGranularity', classifySubjectGranularity),
|
|
140
|
+
...ifDefined('classifyEntityKind', classifyEntityKind),
|
|
119
141
|
});
|
|
120
142
|
return finaliseVerifyResult({ verifyResult, aggregate, skipMarker, onProgress });
|
|
121
143
|
}
|
|
@@ -166,30 +188,27 @@ async function runIntrospection<TFamilyId extends string, TTargetId extends stri
|
|
|
166
188
|
}
|
|
167
189
|
|
|
168
190
|
/**
|
|
169
|
-
* Build the per-
|
|
191
|
+
* Build the per-space schema callback handed to the aggregate verifier.
|
|
170
192
|
* When `skipSchema` is true the callback short-circuits with a synthetic
|
|
171
193
|
* `ok` result so the verifier still runs the (cheap) schemaCheck loop
|
|
172
194
|
* without invoking the family's verification path.
|
|
173
195
|
*/
|
|
174
|
-
export function
|
|
196
|
+
export function createPerSpaceVerifier<TFamilyId extends string, TTargetId extends string>(
|
|
175
197
|
options: ExecuteDbVerifyOptions<TFamilyId, TTargetId>,
|
|
176
198
|
): (
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
schema: unknown,
|
|
200
|
+
space: AggregateContractSpace,
|
|
179
201
|
verifyMode: 'strict' | 'lenient',
|
|
180
202
|
) => VerifyDatabaseSchemaResult {
|
|
181
203
|
const { skipSchema, familyInstance, frameworkComponents } = options;
|
|
182
|
-
return (
|
|
183
|
-
if (skipSchema) return buildSkippedSchemaResult(
|
|
204
|
+
return (schema, space, verifyMode) => {
|
|
205
|
+
if (skipSchema) return buildSkippedSchemaResult(space);
|
|
184
206
|
return familyInstance.verifySchema({
|
|
185
|
-
contract:
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
// family
|
|
189
|
-
schema
|
|
190
|
-
never,
|
|
191
|
-
'family TSchemaIR is opaque to migration-tools; projectedSchema is passed straight through'
|
|
192
|
-
>(projectedSchema),
|
|
207
|
+
contract: space.contract(),
|
|
208
|
+
// `familyInstance` is `ControlFamilyInstance<_, unknown>`, so `verifySchema`
|
|
209
|
+
// takes its `TSchemaIR` as `unknown` — the introspected schema passes
|
|
210
|
+
// straight through; the family narrows to its own IR node internally.
|
|
211
|
+
schema,
|
|
193
212
|
strict: verifyMode === 'strict',
|
|
194
213
|
frameworkComponents,
|
|
195
214
|
});
|
|
@@ -223,7 +242,7 @@ function emitVerifySpan(
|
|
|
223
242
|
* by the CLI's `--schema-only` mode.
|
|
224
243
|
*/
|
|
225
244
|
function finaliseVerifyResult(args: {
|
|
226
|
-
verifyResult: VerifierOutput
|
|
245
|
+
verifyResult: VerifierOutput;
|
|
227
246
|
aggregate: {
|
|
228
247
|
readonly app: { readonly spaceId: string };
|
|
229
248
|
readonly extensions: ReadonlyArray<{ readonly spaceId: string }>;
|
|
@@ -253,14 +272,15 @@ function finaliseVerifyResult(args: {
|
|
|
253
272
|
emitVerifySpan(onProgress, 'spanEndOk');
|
|
254
273
|
return ok({
|
|
255
274
|
schemaResults: verifyResult.value.schemaCheck.perSpace,
|
|
256
|
-
|
|
275
|
+
unclaimed: verifyResult.value.schemaCheck.unclaimed,
|
|
276
|
+
spaceOrder: [aggregate.app.spaceId, ...aggregate.extensions.map((e) => e.spaceId)],
|
|
257
277
|
appSpaceId: aggregate.app.spaceId,
|
|
258
278
|
});
|
|
259
279
|
}
|
|
260
280
|
|
|
261
|
-
function buildSkippedSchemaResult(
|
|
262
|
-
const contract =
|
|
263
|
-
const headRef = requireHeadRef(
|
|
281
|
+
function buildSkippedSchemaResult(space: AggregateContractSpace): VerifyDatabaseSchemaResult {
|
|
282
|
+
const contract = space.contract();
|
|
283
|
+
const headRef = requireHeadRef(space);
|
|
264
284
|
const profileHash = castAs<{ profileHash?: string }>(contract).profileHash;
|
|
265
285
|
return {
|
|
266
286
|
ok: true,
|
|
@@ -272,18 +292,6 @@ function buildSkippedSchemaResult(member: ContractSpaceMember): VerifyDatabaseSc
|
|
|
272
292
|
target: { expected: contract.target },
|
|
273
293
|
schema: {
|
|
274
294
|
issues: [],
|
|
275
|
-
root: {
|
|
276
|
-
status: 'pass',
|
|
277
|
-
kind: 'skipped',
|
|
278
|
-
name: member.spaceId,
|
|
279
|
-
contractPath: '',
|
|
280
|
-
code: 'SKIPPED',
|
|
281
|
-
message: 'Schema verification skipped',
|
|
282
|
-
expected: undefined,
|
|
283
|
-
actual: undefined,
|
|
284
|
-
children: [],
|
|
285
|
-
},
|
|
286
|
-
counts: { pass: 0, warn: 0, fail: 0, totalNodes: 0 },
|
|
287
295
|
},
|
|
288
296
|
timings: { total: 0 },
|
|
289
297
|
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { EOL } from 'node:os';
|
|
3
|
+
import { loadConfig } from '@prisma-next/config-loader';
|
|
4
|
+
import { type FormatOptions, format, PslFormatError } from '@prisma-next/psl-parser/format';
|
|
5
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
6
|
+
import { CliStructuredError, errorRuntime, errorUnexpected } from '../../utils/cli-errors';
|
|
7
|
+
|
|
8
|
+
export interface FormatOperationOptions {
|
|
9
|
+
readonly configPath?: string;
|
|
10
|
+
readonly eol?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FormatOperationResult {
|
|
14
|
+
readonly formatted: boolean;
|
|
15
|
+
readonly path?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveNewline(
|
|
19
|
+
formatterNewline: 'LF' | 'CRLF' | undefined,
|
|
20
|
+
eol: string,
|
|
21
|
+
): 'LF' | 'CRLF' {
|
|
22
|
+
if (formatterNewline !== undefined) {
|
|
23
|
+
return formatterNewline;
|
|
24
|
+
}
|
|
25
|
+
return eol === '\r\n' ? 'CRLF' : 'LF';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function executeFormat(
|
|
29
|
+
options: FormatOperationOptions,
|
|
30
|
+
): Promise<Result<FormatOperationResult, CliStructuredError>> {
|
|
31
|
+
const eol = options.eol ?? EOL;
|
|
32
|
+
|
|
33
|
+
let config: Awaited<ReturnType<typeof loadConfig>>;
|
|
34
|
+
try {
|
|
35
|
+
config = await loadConfig(options.configPath);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (CliStructuredError.is(error)) {
|
|
38
|
+
return notOk(error);
|
|
39
|
+
}
|
|
40
|
+
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const source = config.contract?.source;
|
|
44
|
+
if (source?.sourceFormat !== 'psl') {
|
|
45
|
+
return ok({ formatted: false });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const inputPath = source.inputs?.[0];
|
|
49
|
+
if (inputPath === undefined) {
|
|
50
|
+
return ok({ formatted: false });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let contents: string;
|
|
54
|
+
try {
|
|
55
|
+
contents = await readFile(inputPath, 'utf-8');
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return notOk(
|
|
58
|
+
errorRuntime('Failed to read contract source file', {
|
|
59
|
+
why: error instanceof Error ? error.message : String(error),
|
|
60
|
+
fix: `Check that ${inputPath} exists and is readable.`,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const formatOptions: FormatOptions = {
|
|
66
|
+
indent: config.formatter?.indent ?? 2,
|
|
67
|
+
newline: resolveNewline(config.formatter?.newline, eol),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let formatted: string;
|
|
71
|
+
try {
|
|
72
|
+
formatted = format(contents, formatOptions);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof PslFormatError) {
|
|
75
|
+
return notOk(
|
|
76
|
+
errorRuntime('Cannot format PSL with parse errors', {
|
|
77
|
+
why: error.message,
|
|
78
|
+
fix: 'Fix the parse errors in your schema and try again.',
|
|
79
|
+
meta: { diagnostics: error.diagnostics },
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return notOk(errorUnexpected(error instanceof Error ? error.message : String(error)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await writeFile(inputPath, formatted, 'utf-8');
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return notOk(
|
|
90
|
+
errorRuntime('Failed to write formatted contract source file', {
|
|
91
|
+
why: error instanceof Error ? error.message : String(error),
|
|
92
|
+
fix: `Check that ${inputPath} is writable.`,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return ok({ formatted: true, path: inputPath });
|
|
98
|
+
}
|