@prisma-next/cli 0.5.0-dev.66 → 0.5.0-dev.68
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/dist/{cli-errors-By1iVE3z.mjs → cli-errors-D3_sMh2K.mjs} +2 -3
- package/dist/{cli-errors-By1iVE3z.mjs.map → cli-errors-D3_sMh2K.mjs.map} +1 -1
- package/dist/{cli-errors-DDeVsP2Y.d.mts → cli-errors-QH8kf-C2.d.mts} +0 -2
- package/dist/cli.mjs +12 -76
- package/dist/cli.mjs.map +1 -1
- package/dist/client-0ZX24FXF.mjs +1398 -0
- package/dist/client-0ZX24FXF.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +2 -4
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +2 -4
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +11 -11
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +5 -7
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +8 -9
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +11 -11
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -321
- package/dist/commands/migration-apply.d.mts.map +1 -1
- package/dist/commands/migration-apply.mjs +16 -17
- package/dist/commands/migration-apply.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts +0 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +10 -11
- 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 -345
- 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 +5 -6
- package/dist/commands/migration-ref.mjs.map +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +13 -13
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -4
- package/dist/{config-loader-ih8ViDb_.mjs → config-loader-B6sJjXTv.mjs} +2 -4
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
- package/dist/config-loader.d.mts +0 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/config-loader.mjs +2 -3
- package/dist/{contract-emit-CnTXVVbF.mjs → contract-emit-B3ChISB_.mjs} +22 -13
- package/dist/contract-emit-B3ChISB_.mjs.map +1 -0
- package/dist/{contract-emit-CcZr3HS9.mjs → contract-emit-DkMqO7f2.mjs} +8 -10
- package/dist/contract-emit-DkMqO7f2.mjs.map +1 -0
- package/dist/{contract-enrichment-xDeJBC-o.mjs → contract-enrichment-CF6ogEJ_.mjs} +2 -2
- package/dist/contract-enrichment-CF6ogEJ_.mjs.map +1 -0
- package/dist/{contract-infer-sER84Le-.mjs → contract-infer-BDKAE0B0.mjs} +5 -7
- package/dist/{contract-infer-sER84Le-.mjs.map → contract-infer-BDKAE0B0.mjs.map} +1 -1
- package/dist/db-verify-B4TdDKOI.mjs +403 -0
- package/dist/db-verify-B4TdDKOI.mjs.map +1 -0
- package/dist/exports/config-types.mjs +1 -2
- package/dist/exports/control-api.d.mts +202 -7
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +4 -6
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +28 -30
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts +2 -4
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +2 -3
- package/dist/{framework-components-Bgcre3Z6.mjs → framework-components-gwAHl7ml.mjs} +3 -4
- package/dist/{framework-components-Bgcre3Z6.mjs.map → framework-components-gwAHl7ml.mjs.map} +1 -1
- package/dist/{init-DC4sL4Rp.mjs → init-Deo7U8_U.mjs} +13 -30
- package/dist/init-Deo7U8_U.mjs.map +1 -0
- package/dist/{inspect-live-schema-BQN21nNO.mjs → inspect-live-schema-BAgQMYpD.mjs} +7 -8
- package/dist/inspect-live-schema-BAgQMYpD.mjs.map +1 -0
- package/dist/migration-cli.d.mts +0 -1
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +2 -3
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-DLmYGRug.mjs → migration-command-scaffold-B8J702Uh.mjs} +7 -8
- package/dist/migration-command-scaffold-B8J702Uh.mjs.map +1 -0
- package/dist/migration-plan-BcKNnTM7.mjs +530 -0
- package/dist/migration-plan-BcKNnTM7.mjs.map +1 -0
- package/dist/{migration-status-CDW4RDsO.mjs → migration-status-CjwB2of-.mjs} +10 -14
- package/dist/migration-status-CjwB2of-.mjs.map +1 -0
- package/dist/{migrations-MEoKMiV5.mjs → migrations-CIK94AJf.mjs} +3 -4
- package/dist/migrations-CIK94AJf.mjs.map +1 -0
- package/dist/{output-BpcQrnnq.mjs → output-DnjfCC_u.mjs} +9 -3
- package/dist/output-DnjfCC_u.mjs.map +1 -0
- package/dist/{progress-adapter-DgRGldpT.mjs → progress-adapter-xASh41wr.mjs} +2 -2
- package/dist/{progress-adapter-DgRGldpT.mjs.map → progress-adapter-xASh41wr.mjs.map} +1 -1
- package/dist/{result-handler-Ch6hVnOo.mjs → result-handler-DWb1rFS-.mjs} +20 -10
- package/dist/result-handler-DWb1rFS-.mjs.map +1 -0
- package/dist/{terminal-ui-u2YgKghu.mjs → terminal-ui-zaRDhJnP.mjs} +2 -6
- package/dist/{terminal-ui-u2YgKghu.mjs.map → terminal-ui-zaRDhJnP.mjs.map} +1 -1
- package/dist/{verify-BT9tgCOH.mjs → verify-BEIa9638.mjs} +3 -4
- package/dist/verify-BEIa9638.mjs.map +1 -0
- package/package.json +24 -24
- package/src/commands/db-init.ts +13 -3
- package/src/commands/db-update.ts +7 -3
- package/src/commands/db-verify.ts +47 -15
- package/src/commands/init/index.ts +1 -1
- package/src/commands/init/init.ts +2 -2
- package/src/commands/migration-apply.ts +9 -9
- package/src/commands/migration-new.ts +4 -4
- package/src/commands/migration-plan.ts +66 -9
- package/src/commands/migration-show.ts +7 -5
- package/src/commands/migration-status.ts +3 -3
- package/src/control-api/client.ts +42 -0
- package/src/control-api/operations/db-apply-aggregate.ts +446 -0
- package/src/control-api/operations/db-init.ts +51 -258
- package/src/control-api/operations/db-update.ts +66 -188
- package/src/control-api/operations/db-verify.ts +342 -0
- package/src/control-api/types.ts +56 -0
- package/src/exports/control-api.ts +13 -2
- package/src/load-ts-contract.ts +28 -26
- package/src/utils/combine-schema-results.ts +84 -0
- package/src/utils/command-helpers.ts +24 -2
- package/src/utils/contract-space-aggregate-loader.ts +236 -0
- package/src/utils/contract-space-extension-migrations-pass.ts +120 -0
- package/src/utils/contract-space-migrate-pass.ts +156 -0
- package/dist/client-hUCMXFE_.mjs +0 -1031
- package/dist/client-hUCMXFE_.mjs.map +0 -1
- package/dist/commands/db-verify.mjs.map +0 -1
- package/dist/commands/migration-plan.mjs.map +0 -1
- package/dist/config-loader-ih8ViDb_.mjs.map +0 -1
- package/dist/contract-emit-BkRH9lGt.mjs +0 -4
- package/dist/contract-emit-CcZr3HS9.mjs.map +0 -1
- package/dist/contract-emit-CnTXVVbF.mjs.map +0 -1
- package/dist/contract-enrichment-xDeJBC-o.mjs.map +0 -1
- package/dist/init-DC4sL4Rp.mjs.map +0 -1
- package/dist/inspect-live-schema-BQN21nNO.mjs.map +0 -1
- package/dist/migration-command-scaffold-DLmYGRug.mjs.map +0 -1
- package/dist/migration-status-CDW4RDsO.mjs.map +0 -1
- package/dist/migrations-MEoKMiV5.mjs.map +0 -1
- package/dist/output-BpcQrnnq.mjs.map +0 -1
- package/dist/result-handler-Ch6hVnOo.mjs.map +0 -1
- package/dist/verify-BT9tgCOH.mjs.map +0 -1
|
@@ -27,10 +27,12 @@ import {
|
|
|
27
27
|
errorTargetMismatch,
|
|
28
28
|
errorUnexpected,
|
|
29
29
|
} from '../utils/cli-errors';
|
|
30
|
+
import { combineSchemaResults } from '../utils/combine-schema-results';
|
|
30
31
|
import {
|
|
31
32
|
addGlobalOptions,
|
|
32
33
|
maskConnectionUrl,
|
|
33
34
|
resolveContractPath,
|
|
35
|
+
resolveMigrationPaths,
|
|
34
36
|
setCommandDescriptions,
|
|
35
37
|
setCommandExamples,
|
|
36
38
|
} from '../utils/command-helpers';
|
|
@@ -346,22 +348,42 @@ async function executeDbVerifyCommand(
|
|
|
346
348
|
const setupResult = await resolveVerifySetup(paths, options, mode);
|
|
347
349
|
if (!setupResult.ok) return setupResult;
|
|
348
350
|
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
|
|
351
|
+
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
|
|
349
352
|
|
|
350
353
|
const client = createVerifyClient(setupResult.value);
|
|
351
354
|
const onProgress = createProgressAdapter({ ui, flags });
|
|
352
355
|
|
|
353
356
|
try {
|
|
357
|
+
// Single-contract marker verification preserved for the existing
|
|
358
|
+
// marker / target / hash failure surface (`PN-RUN-3001/3002/3003`).
|
|
359
|
+
// The aggregate verifier (run below for the per-space marker /
|
|
360
|
+
// schema checks) does not duplicate this: it concerns itself with
|
|
361
|
+
// marker-vs-on-disk and orphan-marker drift, not the
|
|
362
|
+
// hash-mismatch-against-the-app-contract lane that today's
|
|
363
|
+
// `client.verify` covers.
|
|
354
364
|
const verifyResult = await client.verify({
|
|
355
365
|
contract: contractJson,
|
|
356
366
|
connection: dbConnection,
|
|
357
367
|
onProgress,
|
|
358
368
|
});
|
|
359
369
|
|
|
360
|
-
// If verification failed, map to CLI structured error
|
|
361
370
|
if (!verifyResult.ok) {
|
|
362
371
|
return notOk(mapVerifyFailure(verifyResult));
|
|
363
372
|
}
|
|
364
373
|
|
|
374
|
+
// Aggregate verifier (loader → verifier pipeline). Runs the layout
|
|
375
|
+
// precheck, marker-aware per-space verifier, and (full mode only)
|
|
376
|
+
// per-space pre-projected schema verification (closes F23).
|
|
377
|
+
const aggregateResult = await client.dbVerify({
|
|
378
|
+
contract: contractJson,
|
|
379
|
+
migrationsDir,
|
|
380
|
+
strict: options.strict ?? false,
|
|
381
|
+
skipSchema: mode === 'marker-only',
|
|
382
|
+
skipMarker: false,
|
|
383
|
+
onProgress,
|
|
384
|
+
});
|
|
385
|
+
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
|
|
386
|
+
|
|
365
387
|
if (mode === 'marker-only') {
|
|
366
388
|
return ok({
|
|
367
389
|
ok: true,
|
|
@@ -381,14 +403,13 @@ async function executeDbVerifyCommand(
|
|
|
381
403
|
});
|
|
382
404
|
}
|
|
383
405
|
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return notOk(schemaVerifyResult);
|
|
406
|
+
const combined = combineSchemaResults(
|
|
407
|
+
aggregateResult.value.schemaResults,
|
|
408
|
+
aggregateResult.value.appSpaceId,
|
|
409
|
+
options.strict ?? false,
|
|
410
|
+
);
|
|
411
|
+
if (!combined.ok) {
|
|
412
|
+
return notOk(combined);
|
|
392
413
|
}
|
|
393
414
|
|
|
394
415
|
return ok({
|
|
@@ -401,9 +422,9 @@ async function executeDbVerifyCommand(
|
|
|
401
422
|
...ifDefined('missingCodecs', verifyResult.missingCodecs),
|
|
402
423
|
...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),
|
|
403
424
|
schema: {
|
|
404
|
-
summary:
|
|
405
|
-
counts:
|
|
406
|
-
strict:
|
|
425
|
+
summary: combined.summary,
|
|
426
|
+
counts: combined.schema.counts,
|
|
427
|
+
strict: combined.meta?.strict ?? false,
|
|
407
428
|
},
|
|
408
429
|
meta: {
|
|
409
430
|
...(verifyResult.meta ?? {}),
|
|
@@ -429,19 +450,30 @@ async function executeDbSchemaOnlyVerifyCommand(
|
|
|
429
450
|
const setupResult = await resolveVerifySetup(paths, options, 'schema-only');
|
|
430
451
|
if (!setupResult.ok) return setupResult;
|
|
431
452
|
const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
|
|
453
|
+
const { migrationsDir } = resolveMigrationPaths(options.config, setupResult.value.config);
|
|
432
454
|
|
|
433
455
|
const client = createVerifyClient(setupResult.value);
|
|
434
456
|
const onProgress = createProgressAdapter({ ui, flags });
|
|
435
457
|
|
|
436
458
|
try {
|
|
437
|
-
|
|
459
|
+
await client.connect(dbConnection);
|
|
460
|
+
const aggregateResult = await client.dbVerify({
|
|
438
461
|
contract: contractJson,
|
|
462
|
+
migrationsDir,
|
|
439
463
|
strict: options.strict ?? false,
|
|
440
|
-
|
|
464
|
+
skipSchema: false,
|
|
465
|
+
skipMarker: true,
|
|
441
466
|
onProgress,
|
|
442
467
|
});
|
|
468
|
+
if (!aggregateResult.ok) return notOk(aggregateResult.failure);
|
|
443
469
|
|
|
444
|
-
return ok(
|
|
470
|
+
return ok(
|
|
471
|
+
combineSchemaResults(
|
|
472
|
+
aggregateResult.value.schemaResults,
|
|
473
|
+
aggregateResult.value.appSpaceId,
|
|
474
|
+
options.strict ?? false,
|
|
475
|
+
),
|
|
476
|
+
);
|
|
445
477
|
} catch (error) {
|
|
446
478
|
return wrapVerifyError(error, contractPathAbsolute, 'db verify --schema-only');
|
|
447
479
|
} finally {
|
|
@@ -113,7 +113,7 @@ export function createInitCommand(): Command {
|
|
|
113
113
|
* we honour it (e.g. testing flows where stdin is stubbed).
|
|
114
114
|
*
|
|
115
115
|
* Exported so callers and tests can derive the same value without
|
|
116
|
-
* touching `process` globals
|
|
116
|
+
* touching `process` globals.
|
|
117
117
|
*/
|
|
118
118
|
export function deriveCanPrompt(opts: {
|
|
119
119
|
readonly flagsInteractive: boolean | undefined;
|
|
@@ -569,7 +569,7 @@ async function runInstall(ctx: {
|
|
|
569
569
|
/**
|
|
570
570
|
* FR2.1 — set when the user already declares `@types/node` directly in
|
|
571
571
|
* `dependencies` or `devDependencies`. We then skip adding it so a
|
|
572
|
-
*
|
|
572
|
+
* locked major (e.g. `^18` for a Node 18 runtime) survives `init`
|
|
573
573
|
* unchanged. Transitive presence is intentionally ignored: detecting
|
|
574
574
|
* it requires lockfile introspection and the realistic clobber risk
|
|
575
575
|
* is the direct-pin case.
|
|
@@ -585,7 +585,7 @@ async function runInstall(ctx: {
|
|
|
585
585
|
// Pin it as a devDep rather than relying on a transitive resolution
|
|
586
586
|
// through `dotenv` (whose types bundle is internal and not guaranteed
|
|
587
587
|
// across versions). Skip when the user already declares `@types/node`
|
|
588
|
-
// directly so a
|
|
588
|
+
// directly so a locked major (e.g. `^18` for a Node 18 runtime) is
|
|
589
589
|
// preserved. Listed last so the install log still leads with the
|
|
590
590
|
// user-relevant `prisma-next` line.
|
|
591
591
|
const devDeps = hasTypesNode ? ['prisma-next'] : ['prisma-next', '@types/node'];
|
|
@@ -110,7 +110,7 @@ async function executeMigrationApplyCommand(
|
|
|
110
110
|
startTime: number,
|
|
111
111
|
): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {
|
|
112
112
|
const config = await loadConfig(options.config);
|
|
113
|
-
const { configPath,
|
|
113
|
+
const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
|
|
114
114
|
options.config,
|
|
115
115
|
config,
|
|
116
116
|
);
|
|
@@ -173,7 +173,7 @@ async function executeMigrationApplyCommand(
|
|
|
173
173
|
if (!flags.json && !flags.quiet) {
|
|
174
174
|
const details: Array<{ label: string; value: string }> = [
|
|
175
175
|
{ label: 'config', value: configPath },
|
|
176
|
-
{ label: 'migrations', value:
|
|
176
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
177
177
|
];
|
|
178
178
|
if (typeof dbConnection === 'string') {
|
|
179
179
|
details.push({
|
|
@@ -197,7 +197,7 @@ async function executeMigrationApplyCommand(
|
|
|
197
197
|
// Read migrations and build migration chain model (offline — no DB needed)
|
|
198
198
|
let migrations: Awaited<ReturnType<typeof loadMigrationPackages>>;
|
|
199
199
|
try {
|
|
200
|
-
migrations = await loadMigrationPackages(
|
|
200
|
+
migrations = await loadMigrationPackages(appMigrationsDir);
|
|
201
201
|
} catch (error) {
|
|
202
202
|
if (MigrationToolsError.is(error)) {
|
|
203
203
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -248,9 +248,9 @@ async function executeMigrationApplyCommand(
|
|
|
248
248
|
if (marker?.storageHash) {
|
|
249
249
|
return notOk(
|
|
250
250
|
errorRuntime('Database has state but no migrations exist', {
|
|
251
|
-
why: `The database marker hash "${marker.storageHash}" exists but no migrations were found in ${
|
|
251
|
+
why: `The database marker hash "${marker.storageHash}" exists but no migrations were found in ${appMigrationsRelative}`,
|
|
252
252
|
fix: 'Ensure the migrations directory is correct. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',
|
|
253
|
-
meta: { markerHash: marker.storageHash, migrationsDir:
|
|
253
|
+
meta: { markerHash: marker.storageHash, migrationsDir: appMigrationsRelative },
|
|
254
254
|
}),
|
|
255
255
|
);
|
|
256
256
|
}
|
|
@@ -258,9 +258,9 @@ async function executeMigrationApplyCommand(
|
|
|
258
258
|
if (destinationHash !== EMPTY_CONTRACT_HASH) {
|
|
259
259
|
return notOk(
|
|
260
260
|
errorRuntime('Current contract has no planned migrations', {
|
|
261
|
-
why: `No migrations were found in ${
|
|
261
|
+
why: `No migrations were found in ${appMigrationsRelative}, but current contract hash is "${destinationHash}"`,
|
|
262
262
|
fix: 'Run `prisma-next migration plan` to create a migration for the current contract.',
|
|
263
|
-
meta: { destinationHash, migrationsDir:
|
|
263
|
+
meta: { destinationHash, migrationsDir: appMigrationsRelative },
|
|
264
264
|
}),
|
|
265
265
|
);
|
|
266
266
|
}
|
|
@@ -295,7 +295,7 @@ async function executeMigrationApplyCommand(
|
|
|
295
295
|
if (markerHash !== undefined && !migrations.graph.nodes.has(markerHash)) {
|
|
296
296
|
return notOk(
|
|
297
297
|
errorRuntime('Database marker does not match any known migration', {
|
|
298
|
-
why: `The database marker hash "${markerHash}" is not found in the migration history at ${
|
|
298
|
+
why: `The database marker hash "${markerHash}" is not found in the migration history at ${appMigrationsRelative}`,
|
|
299
299
|
fix: 'Ensure the migrations directory matches this database. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',
|
|
300
300
|
meta: { markerHash, knownNodes: [...migrations.graph.nodes] },
|
|
301
301
|
}),
|
|
@@ -305,7 +305,7 @@ async function executeMigrationApplyCommand(
|
|
|
305
305
|
if (!migrations.graph.nodes.has(destinationHash)) {
|
|
306
306
|
return notOk(
|
|
307
307
|
errorRuntime('Current contract has no planned migration path', {
|
|
308
|
-
why: `Current contract hash "${destinationHash}" is not present in the migration history at ${
|
|
308
|
+
why: `Current contract hash "${destinationHash}" is not present in the migration history at ${appMigrationsRelative}`,
|
|
309
309
|
fix: 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',
|
|
310
310
|
meta: { destinationHash, knownNodes: [...migrations.graph.nodes] },
|
|
311
311
|
}),
|
|
@@ -70,7 +70,7 @@ async function executeMigrationNewCommand(
|
|
|
70
70
|
options: MigrationNewOptions,
|
|
71
71
|
): Promise<Result<MigrationNewResult, CliStructuredError>> {
|
|
72
72
|
const config = await loadConfig(options.config);
|
|
73
|
-
const {
|
|
73
|
+
const { appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
|
|
74
74
|
|
|
75
75
|
const contractPathAbsolute = resolveContractPath(config);
|
|
76
76
|
|
|
@@ -120,7 +120,7 @@ async function executeMigrationNewCommand(
|
|
|
120
120
|
let fromContractSourceDir: string | null = null;
|
|
121
121
|
|
|
122
122
|
try {
|
|
123
|
-
const packages = await readMigrationsDir(
|
|
123
|
+
const packages = await readMigrationsDir(appMigrationsDir);
|
|
124
124
|
|
|
125
125
|
if (packages.length > 0) {
|
|
126
126
|
const graph = reconstructGraph(packages);
|
|
@@ -130,7 +130,7 @@ async function executeMigrationNewCommand(
|
|
|
130
130
|
if (!match) {
|
|
131
131
|
return notOk(
|
|
132
132
|
errorRuntime('Starting contract not found', {
|
|
133
|
-
why: `No migration with to hash matching "${options.from}" exists in ${
|
|
133
|
+
why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
|
|
134
134
|
fix: 'Check that the --from hash matches a known migration target hash.',
|
|
135
135
|
}),
|
|
136
136
|
);
|
|
@@ -171,7 +171,7 @@ async function executeMigrationNewCommand(
|
|
|
171
171
|
const timestamp = new Date();
|
|
172
172
|
const slug = options.name ?? 'migration';
|
|
173
173
|
const dirName = formatMigrationDirName(timestamp, slug);
|
|
174
|
-
const packageDir = join(
|
|
174
|
+
const packageDir = join(appMigrationsDir, dirName);
|
|
175
175
|
|
|
176
176
|
// `migration new` scaffolds an empty `migration.ts` for the user to
|
|
177
177
|
// fill, so we attest over `ops: []`. Re-running self-emit after the
|
|
@@ -43,6 +43,15 @@ import {
|
|
|
43
43
|
setCommandDescriptions,
|
|
44
44
|
setCommandExamples,
|
|
45
45
|
} from '../utils/command-helpers';
|
|
46
|
+
import {
|
|
47
|
+
type ExtensionMigrationsExtensionInput,
|
|
48
|
+
runContractSpaceExtensionMigrationsPass,
|
|
49
|
+
} from '../utils/contract-space-extension-migrations-pass';
|
|
50
|
+
import {
|
|
51
|
+
formatContractSpaceDriftWarning,
|
|
52
|
+
type MigrateExtensionInput,
|
|
53
|
+
runContractSpaceMigratePass,
|
|
54
|
+
} from '../utils/contract-space-migrate-pass';
|
|
46
55
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
47
56
|
import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
|
|
48
57
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
@@ -92,10 +101,8 @@ async function executeMigrationPlanCommand(
|
|
|
92
101
|
startTime: number,
|
|
93
102
|
): Promise<Result<MigrationPlanResult, CliStructuredError>> {
|
|
94
103
|
const config = await loadConfig(options.config);
|
|
95
|
-
const { configPath, migrationsDir,
|
|
96
|
-
options.config,
|
|
97
|
-
config,
|
|
98
|
-
);
|
|
104
|
+
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
|
|
105
|
+
resolveMigrationPaths(options.config, config);
|
|
99
106
|
|
|
100
107
|
const contractPathAbsolute = resolveContractPath(config);
|
|
101
108
|
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
@@ -104,7 +111,7 @@ async function executeMigrationPlanCommand(
|
|
|
104
111
|
const details: Array<{ label: string; value: string }> = [
|
|
105
112
|
{ label: 'config', value: configPath },
|
|
106
113
|
{ label: 'contract', value: contractPath },
|
|
107
|
-
{ label: 'migrations', value:
|
|
114
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
108
115
|
];
|
|
109
116
|
if (options.from) {
|
|
110
117
|
details.push({ label: 'from', value: options.from });
|
|
@@ -170,7 +177,7 @@ async function executeMigrationPlanCommand(
|
|
|
170
177
|
let fromContractSourceDir: string | null = null;
|
|
171
178
|
|
|
172
179
|
try {
|
|
173
|
-
const { bundles, graph } = await loadMigrationPackages(
|
|
180
|
+
const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
|
|
174
181
|
|
|
175
182
|
if (options.from) {
|
|
176
183
|
const resolved = resolveBundleByPrefix(bundles, options.from);
|
|
@@ -179,11 +186,11 @@ async function executeMigrationPlanCommand(
|
|
|
179
186
|
return notOk(
|
|
180
187
|
f.reason === 'ambiguous'
|
|
181
188
|
? errorRuntime('Multiple matching migrations found', {
|
|
182
|
-
why: `Prefix "${options.from}" matches ${f.count} migrations in ${
|
|
189
|
+
why: `Prefix "${options.from}" matches ${f.count} migrations in ${appMigrationsRelative}`,
|
|
183
190
|
fix: 'Provide a longer prefix to disambiguate, or omit --from to use the latest migration target.',
|
|
184
191
|
})
|
|
185
192
|
: errorRuntime('Starting contract not found', {
|
|
186
|
-
why: `No migration with to hash matching "${options.from}" exists in ${
|
|
193
|
+
why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
|
|
187
194
|
fix: 'Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target.',
|
|
188
195
|
}),
|
|
189
196
|
);
|
|
@@ -220,6 +227,56 @@ async function executeMigrationPlanCommand(
|
|
|
220
227
|
);
|
|
221
228
|
}
|
|
222
229
|
|
|
230
|
+
// Per-space migrate pass: drift detection + on-disk artefact emission for
|
|
231
|
+
// every loaded extension that exposes a `contractSpace`. Runs *before*
|
|
232
|
+
// the app-space no-op check so that an extension bump alone (with no
|
|
233
|
+
// structural app-space change) still re-pins extension artefacts on
|
|
234
|
+
// disk. Drift warnings are non-fatal — the on-disk artefacts are refreshed
|
|
235
|
+
// and the user is notified that the bump is being captured.
|
|
236
|
+
const extensionInputs: readonly MigrateExtensionInput[] = (config.extensionPacks ?? []).map(
|
|
237
|
+
(pack) => {
|
|
238
|
+
const cs = (pack as { readonly contractSpace?: MigrateExtensionInput['contractSpace'] })
|
|
239
|
+
.contractSpace;
|
|
240
|
+
return cs !== undefined ? { id: pack.id, contractSpace: cs } : { id: pack.id };
|
|
241
|
+
},
|
|
242
|
+
);
|
|
243
|
+
const migratePass = await runContractSpaceMigratePass({
|
|
244
|
+
migrationsDir,
|
|
245
|
+
extensionPacks: extensionInputs,
|
|
246
|
+
});
|
|
247
|
+
if (!flags.json && !flags.quiet) {
|
|
248
|
+
for (const drift of migratePass.drifts) {
|
|
249
|
+
if (drift.kind === 'drift') {
|
|
250
|
+
ui.stderr(formatContractSpaceDriftWarning(drift));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Materialise descriptor-shipped migration packages onto disk under
|
|
256
|
+
// `migrations/<spaceId>/<dirName>/` for any package not yet present.
|
|
257
|
+
// Idempotent (existing dirs are left untouched).
|
|
258
|
+
// Uses `planAllSpaces` for deterministic ordering + duplicate-spaceId
|
|
259
|
+
// detection.
|
|
260
|
+
const extensionMigrationsInputs: readonly ExtensionMigrationsExtensionInput[] = (
|
|
261
|
+
config.extensionPacks ?? []
|
|
262
|
+
).map((pack) => {
|
|
263
|
+
const cs = (
|
|
264
|
+
pack as {
|
|
265
|
+
readonly contractSpace?: ExtensionMigrationsExtensionInput['contractSpace'];
|
|
266
|
+
}
|
|
267
|
+
).contractSpace;
|
|
268
|
+
return cs !== undefined ? { id: pack.id, contractSpace: cs } : { id: pack.id };
|
|
269
|
+
});
|
|
270
|
+
const extensionMigrationsResult = await runContractSpaceExtensionMigrationsPass({
|
|
271
|
+
migrationsDir,
|
|
272
|
+
extensionPacks: extensionMigrationsInputs,
|
|
273
|
+
});
|
|
274
|
+
if (!flags.json && !flags.quiet) {
|
|
275
|
+
for (const entry of extensionMigrationsResult.emitted) {
|
|
276
|
+
ui.step(`Emitted ${entry.spaceId}/${entry.dirName}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
223
280
|
// Check for no-op (same hash means no changes)
|
|
224
281
|
if (fromHash === toStorageHash) {
|
|
225
282
|
const result: MigrationPlanResult = {
|
|
@@ -253,7 +310,7 @@ async function executeMigrationPlanCommand(
|
|
|
253
310
|
const timestamp = new Date();
|
|
254
311
|
const slug = options.name ?? 'migration';
|
|
255
312
|
const dirName = formatMigrationDirName(timestamp, slug);
|
|
256
|
-
const packageDir = join(
|
|
313
|
+
const packageDir = join(appMigrationsDir, dirName);
|
|
257
314
|
|
|
258
315
|
const baseMetadata: Omit<MigrationMetadata, 'migrationHash' | 'providedInvariants'> = {
|
|
259
316
|
from: fromHash,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
reconstructGraph,
|
|
10
10
|
} from '@prisma-next/migration-tools/migration-graph';
|
|
11
11
|
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
12
|
+
import { APP_SPACE_ID, spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';
|
|
12
13
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
13
14
|
import { Command } from 'commander';
|
|
14
15
|
import { relative, resolve } from 'pathe';
|
|
@@ -103,16 +104,17 @@ async function executeMigrationShowCommand(
|
|
|
103
104
|
? relative(process.cwd(), resolve(options.config))
|
|
104
105
|
: 'prisma-next.config.ts';
|
|
105
106
|
|
|
106
|
-
const
|
|
107
|
+
const migrationsDirRoot = resolve(
|
|
107
108
|
options.config ? resolve(options.config, '..') : process.cwd(),
|
|
108
109
|
config.migrations?.dir ?? 'migrations',
|
|
109
110
|
);
|
|
110
|
-
const
|
|
111
|
+
const appMigrationsDir = spaceMigrationDirectory(migrationsDirRoot, APP_SPACE_ID);
|
|
112
|
+
const appMigrationsRelative = relative(process.cwd(), appMigrationsDir);
|
|
111
113
|
|
|
112
114
|
if (!flags.json && !flags.quiet) {
|
|
113
115
|
const details: Array<{ label: string; value: string }> = [
|
|
114
116
|
{ label: 'config', value: configPath },
|
|
115
|
-
{ label: 'migrations', value:
|
|
117
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
116
118
|
];
|
|
117
119
|
if (target) {
|
|
118
120
|
details.push({ label: 'target', value: target });
|
|
@@ -132,11 +134,11 @@ async function executeMigrationShowCommand(
|
|
|
132
134
|
if (target && looksLikePath(target)) {
|
|
133
135
|
pkg = await readMigrationPackage(resolve(target));
|
|
134
136
|
} else {
|
|
135
|
-
const allPackages = await readMigrationsDir(
|
|
137
|
+
const allPackages = await readMigrationsDir(appMigrationsDir);
|
|
136
138
|
if (allPackages.length === 0) {
|
|
137
139
|
return notOk(
|
|
138
140
|
errorRuntime('No migrations found', {
|
|
139
|
-
why: `No migration packages found in ${
|
|
141
|
+
why: `No migration packages found in ${appMigrationsRelative}`,
|
|
140
142
|
fix: 'Run `prisma-next migration plan` to create a migration first.',
|
|
141
143
|
}),
|
|
142
144
|
);
|
|
@@ -369,7 +369,7 @@ async function executeMigrationStatusCommand(
|
|
|
369
369
|
ui: TerminalUI,
|
|
370
370
|
): Promise<Result<MigrationStatusResult, CliStructuredError>> {
|
|
371
371
|
const config = await loadConfig(options.config);
|
|
372
|
-
const { configPath,
|
|
372
|
+
const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
|
|
373
373
|
options.config,
|
|
374
374
|
config,
|
|
375
375
|
);
|
|
@@ -414,7 +414,7 @@ async function executeMigrationStatusCommand(
|
|
|
414
414
|
if (!flags.json && !flags.quiet) {
|
|
415
415
|
const details: Array<{ label: string; value: string }> = [
|
|
416
416
|
{ label: 'config', value: configPath },
|
|
417
|
-
{ label: 'migrations', value:
|
|
417
|
+
{ label: 'migrations', value: appMigrationsRelative },
|
|
418
418
|
];
|
|
419
419
|
if (dbConnection && hasDriver) {
|
|
420
420
|
details.push({ label: 'database', value: maskConnectionUrl(String(dbConnection)) });
|
|
@@ -454,7 +454,7 @@ async function executeMigrationStatusCommand(
|
|
|
454
454
|
let bundles: readonly OnDiskMigrationPackage[];
|
|
455
455
|
let graph: MigrationGraph;
|
|
456
456
|
try {
|
|
457
|
-
({ bundles, graph } = await loadMigrationPackages(
|
|
457
|
+
({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
|
|
458
458
|
} catch (error) {
|
|
459
459
|
if (MigrationToolsError.is(error)) {
|
|
460
460
|
return notOk(mapMigrationToolsError(error));
|
|
@@ -28,7 +28,9 @@ import { enrichContract } from './contract-enrichment';
|
|
|
28
28
|
import { ContractValidationError } from './errors';
|
|
29
29
|
import { executeDbInit } from './operations/db-init';
|
|
30
30
|
import { executeDbUpdate } from './operations/db-update';
|
|
31
|
+
import { type ExecuteDbVerifyResult, executeDbVerify } from './operations/db-verify';
|
|
31
32
|
import { executeMigrationApply } from './operations/migration-apply';
|
|
33
|
+
|
|
32
34
|
import type {
|
|
33
35
|
ControlActionName,
|
|
34
36
|
ControlClient,
|
|
@@ -37,6 +39,7 @@ import type {
|
|
|
37
39
|
DbInitResult,
|
|
38
40
|
DbUpdateOptions,
|
|
39
41
|
DbUpdateResult,
|
|
42
|
+
DbVerifyOptions,
|
|
40
43
|
EmitOptions,
|
|
41
44
|
EmitResult,
|
|
42
45
|
IntrospectOptions,
|
|
@@ -368,6 +371,9 @@ class ControlClientImpl implements ControlClient {
|
|
|
368
371
|
mode: options.mode,
|
|
369
372
|
migrations: this.options.target.migrations,
|
|
370
373
|
frameworkComponents,
|
|
374
|
+
migrationsDir: options.migrationsDir,
|
|
375
|
+
targetId: this.options.target.targetId,
|
|
376
|
+
extensionPacks: this.options.extensionPacks ?? [],
|
|
371
377
|
...ifDefined('onProgress', onProgress),
|
|
372
378
|
});
|
|
373
379
|
}
|
|
@@ -396,11 +402,42 @@ class ControlClientImpl implements ControlClient {
|
|
|
396
402
|
mode: options.mode,
|
|
397
403
|
migrations: this.options.target.migrations,
|
|
398
404
|
frameworkComponents,
|
|
405
|
+
migrationsDir: options.migrationsDir,
|
|
406
|
+
targetId: this.options.target.targetId,
|
|
407
|
+
extensionPacks: this.options.extensionPacks ?? [],
|
|
399
408
|
...ifDefined('acceptDataLoss', options.acceptDataLoss),
|
|
400
409
|
...ifDefined('onProgress', onProgress),
|
|
401
410
|
});
|
|
402
411
|
}
|
|
403
412
|
|
|
413
|
+
async dbVerify(options: DbVerifyOptions): Promise<ExecuteDbVerifyResult> {
|
|
414
|
+
const { onProgress } = options;
|
|
415
|
+
await this.connectWithProgress(options.connection, 'dbVerify', onProgress);
|
|
416
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
417
|
+
|
|
418
|
+
let contract: Contract;
|
|
419
|
+
try {
|
|
420
|
+
contract = familyInstance.validateContract(options.contract);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
423
|
+
throw new ContractValidationError(message, error);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return executeDbVerify({
|
|
427
|
+
driver,
|
|
428
|
+
familyInstance,
|
|
429
|
+
contract,
|
|
430
|
+
migrationsDir: options.migrationsDir,
|
|
431
|
+
targetId: this.options.target.targetId,
|
|
432
|
+
extensionPacks: this.options.extensionPacks ?? [],
|
|
433
|
+
frameworkComponents,
|
|
434
|
+
mode: options.strict ? 'strict' : 'lenient',
|
|
435
|
+
skipSchema: options.skipSchema,
|
|
436
|
+
skipMarker: options.skipMarker,
|
|
437
|
+
...ifDefined('onProgress', onProgress),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
404
441
|
async readMarker(): Promise<ContractMarkerRecord | null> {
|
|
405
442
|
const { driver, familyInstance } = await this.ensureConnected();
|
|
406
443
|
// The CLI client's readMarker reads the app's marker. Per-extension
|
|
@@ -410,6 +447,11 @@ class ControlClientImpl implements ControlClient {
|
|
|
410
447
|
return familyInstance.readMarker({ driver, space: APP_SPACE_ID });
|
|
411
448
|
}
|
|
412
449
|
|
|
450
|
+
async readAllMarkers(): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
|
|
451
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
452
|
+
return familyInstance.readAllMarkers({ driver });
|
|
453
|
+
}
|
|
454
|
+
|
|
413
455
|
async migrationApply(options: MigrationApplyOptions): Promise<MigrationApplyResult> {
|
|
414
456
|
const { onProgress } = options;
|
|
415
457
|
await this.connectWithProgress(options.connection, 'migrationApply', onProgress);
|