@prisma-next/cli 0.12.0-dev.43 → 0.12.0-dev.45
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.mjs +12 -12
- package/dist/{client-Dk-zRFuT.mjs → client-CJWPvdKj.mjs} +4 -4
- package/dist/{client-Dk-zRFuT.mjs.map → client-CJWPvdKj.mjs.map} +1 -1
- package/dist/{command-helpers-xvg9oq4T.mjs → command-helpers-C6lqKJG1.mjs} +18 -2
- package/dist/command-helpers-C6lqKJG1.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 +4 -4
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.mjs +4 -4
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.mjs +3 -3
- package/dist/commands/migration-list.mjs +1 -1
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.mjs +3 -3
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/commands/ref.mjs +2 -2
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-Wj3u4Xco.mjs → contract-at-errors-CVQsgp3V.mjs} +2 -2
- package/dist/{contract-at-errors-Wj3u4Xco.mjs.map → contract-at-errors-CVQsgp3V.mjs.map} +1 -1
- package/dist/{contract-emit-CZU0UO6M.mjs → contract-emit-DnUkRd-7.mjs} +3 -3
- package/dist/{contract-emit-CZU0UO6M.mjs.map → contract-emit-DnUkRd-7.mjs.map} +1 -1
- package/dist/{contract-emit-FetLZ3jn.mjs → contract-emit-Iyq5V7OU.mjs} +3 -3
- package/dist/{contract-emit-FetLZ3jn.mjs.map → contract-emit-Iyq5V7OU.mjs.map} +1 -1
- package/dist/{contract-infer-3wtOsS_H.mjs → contract-infer-MmQMIms7.mjs} +3 -3
- package/dist/{contract-infer-3wtOsS_H.mjs.map → contract-infer-MmQMIms7.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-BdRPfM3Q.mjs → contract-space-aggregate-loader-BSWfMkFY.mjs} +2 -2
- package/dist/{contract-space-aggregate-loader-BdRPfM3Q.mjs.map → contract-space-aggregate-loader-BSWfMkFY.mjs.map} +1 -1
- package/dist/{db-verify-BisylXFZ.mjs → db-verify-B-YOEESk.mjs} +4 -4
- package/dist/{db-verify-BisylXFZ.mjs.map → db-verify-B-YOEESk.mjs.map} +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/{framework-components-Be4inY3I.mjs → framework-components-D6VMhw2T.mjs} +2 -2
- package/dist/{framework-components-Be4inY3I.mjs.map → framework-components-D6VMhw2T.mjs.map} +1 -1
- package/dist/{init-BTE2U7lG.mjs → init-Ceib8PNc.mjs} +3 -3
- package/dist/{init-BTE2U7lG.mjs.map → init-Ceib8PNc.mjs.map} +1 -1
- package/dist/{inspect-live-schema-Cy9Y4wsL.mjs → inspect-live-schema-CjMZ0RkS.mjs} +3 -3
- package/dist/{inspect-live-schema-Cy9Y4wsL.mjs.map → inspect-live-schema-CjMZ0RkS.mjs.map} +1 -1
- package/dist/{migration-check-CUavU7U9.mjs → migration-check-DkNcCXZC.mjs} +112 -48
- package/dist/migration-check-DkNcCXZC.mjs.map +1 -0
- package/dist/{migration-command-scaffold-BxOxtyJ6.mjs → migration-command-scaffold-CrRysYxV.mjs} +3 -3
- package/dist/{migration-command-scaffold-BxOxtyJ6.mjs.map → migration-command-scaffold-CrRysYxV.mjs.map} +1 -1
- package/dist/{migration-list-jK6QeczE.mjs → migration-list-ip7fKesI.mjs} +3 -3
- package/dist/{migration-list-jK6QeczE.mjs.map → migration-list-ip7fKesI.mjs.map} +1 -1
- package/dist/{migration-log-DWI6dZyi.mjs → migration-log-BX9vu5c9.mjs} +3 -3
- package/dist/{migration-log-DWI6dZyi.mjs.map → migration-log-BX9vu5c9.mjs.map} +1 -1
- package/dist/{migration-path-target-DqcrbOis.mjs → migration-path-target-ByduK0eI.mjs} +17 -3
- package/dist/migration-path-target-ByduK0eI.mjs.map +1 -0
- package/dist/{migration-plan-NHdlUwPG.mjs → migration-plan-DLbbc_7z.mjs} +5 -5
- package/dist/{migration-plan-NHdlUwPG.mjs.map → migration-plan-DLbbc_7z.mjs.map} +1 -1
- package/dist/{migration-status-DI6Ldjbo.mjs → migration-status-BtdaOuQJ.mjs} +5 -5
- package/dist/{migration-status-DI6Ldjbo.mjs.map → migration-status-BtdaOuQJ.mjs.map} +1 -1
- package/dist/{telemetry-DQP0BvKv.mjs → telemetry-CxbY4NKn.mjs} +2 -2
- package/dist/{telemetry-DQP0BvKv.mjs.map → telemetry-CxbY4NKn.mjs.map} +1 -1
- package/dist/{verify-tvHRBBVP.mjs → verify-Df-8Kwat.mjs} +2 -2
- package/dist/{verify-tvHRBBVP.mjs.map → verify-Df-8Kwat.mjs.map} +1 -1
- package/package.json +18 -18
- package/src/commands/migration-check.ts +147 -44
- package/src/utils/cli-errors.ts +23 -0
- package/src/utils/migration-path-target.ts +21 -0
- package/dist/command-helpers-xvg9oq4T.mjs.map +0 -1
- package/dist/migration-check-CUavU7U9.mjs.map +0 -1
- package/dist/migration-path-target-DqcrbOis.mjs.map +0 -1
|
@@ -8,12 +8,12 @@ import type {
|
|
|
8
8
|
import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
|
|
9
9
|
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
10
10
|
import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
|
|
11
|
-
import { readMigrationsDir } from '@prisma-next/migration-tools/io';
|
|
12
|
-
import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
|
|
13
11
|
import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
|
|
14
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
parseMigrationRef,
|
|
14
|
+
type RefResolutionError,
|
|
15
|
+
} from '@prisma-next/migration-tools/ref-resolution';
|
|
15
16
|
import type { Refs } from '@prisma-next/migration-tools/refs';
|
|
16
|
-
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
17
17
|
import {
|
|
18
18
|
isValidSpaceId,
|
|
19
19
|
listContractSpaceDirectories,
|
|
@@ -21,12 +21,14 @@ import {
|
|
|
21
21
|
spaceMigrationDirectory,
|
|
22
22
|
spaceRefsDirectory,
|
|
23
23
|
} from '@prisma-next/migration-tools/spaces';
|
|
24
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
24
25
|
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
25
26
|
import { Command } from 'commander';
|
|
26
27
|
import { join, relative } from 'pathe';
|
|
27
28
|
import { loadConfig } from '../config-loader';
|
|
28
29
|
import {
|
|
29
30
|
type CliStructuredError,
|
|
31
|
+
errorAmbiguousMigrationRef,
|
|
30
32
|
errorInvalidSpaceId,
|
|
31
33
|
errorSpaceNotFound,
|
|
32
34
|
mapRefResolutionError,
|
|
@@ -53,6 +55,7 @@ import {
|
|
|
53
55
|
findPackageByDirPath,
|
|
54
56
|
looksLikePath,
|
|
55
57
|
resolveAppTargetPath,
|
|
58
|
+
resolveTargetPathAcrossSpaces,
|
|
56
59
|
} from '../utils/migration-path-target';
|
|
57
60
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
58
61
|
import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
|
|
@@ -308,6 +311,7 @@ interface MigrationCheckOutcome {
|
|
|
308
311
|
readonly result?: MigrationCheckResult;
|
|
309
312
|
readonly error?: CliStructuredError;
|
|
310
313
|
readonly exitCode: number;
|
|
314
|
+
readonly resolvedSpaceId?: string;
|
|
311
315
|
}
|
|
312
316
|
|
|
313
317
|
async function executeMigrationCheckCommand(
|
|
@@ -317,7 +321,7 @@ async function executeMigrationCheckCommand(
|
|
|
317
321
|
ui: TerminalUI,
|
|
318
322
|
): Promise<MigrationCheckOutcome> {
|
|
319
323
|
const config = await loadConfig(options.config);
|
|
320
|
-
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative
|
|
324
|
+
const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
|
|
321
325
|
resolveMigrationPaths(options.config, config);
|
|
322
326
|
|
|
323
327
|
if (!flags.json && !flags.quiet) {
|
|
@@ -337,20 +341,22 @@ async function executeMigrationCheckCommand(
|
|
|
337
341
|
ui.stderr(header);
|
|
338
342
|
}
|
|
339
343
|
|
|
344
|
+
const loadedAggregate = await buildReadAggregate(config, { migrationsDir });
|
|
345
|
+
if (!loadedAggregate.ok) {
|
|
346
|
+
return { error: loadedAggregate.failure, exitCode: PRECONDITION };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);
|
|
350
|
+
|
|
340
351
|
if (target) {
|
|
341
352
|
return await checkSingleTarget(target, {
|
|
353
|
+
spaces,
|
|
354
|
+
...(options.space !== undefined ? { spaceFilter: options.space } : {}),
|
|
342
355
|
appMigrationsDir,
|
|
343
356
|
appMigrationsRelative,
|
|
344
|
-
refsDir,
|
|
345
357
|
});
|
|
346
358
|
}
|
|
347
359
|
|
|
348
|
-
const loadedAggregate = await buildReadAggregate(config, { migrationsDir });
|
|
349
|
-
if (!loadedAggregate.ok) {
|
|
350
|
-
return { error: loadedAggregate.failure, exitCode: PRECONDITION };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);
|
|
354
360
|
const checkResult = runMigrationCheck({
|
|
355
361
|
spaces,
|
|
356
362
|
...(options.space !== undefined ? { spaceFilter: options.space } : {}),
|
|
@@ -382,53 +388,140 @@ async function executeMigrationCheckCommand(
|
|
|
382
388
|
};
|
|
383
389
|
}
|
|
384
390
|
|
|
385
|
-
interface
|
|
391
|
+
interface SingleTargetInputs {
|
|
392
|
+
readonly spaces: readonly CheckSpace[];
|
|
393
|
+
readonly spaceFilter?: string;
|
|
386
394
|
readonly appMigrationsDir: string;
|
|
387
395
|
readonly appMigrationsRelative: string;
|
|
388
|
-
readonly refsDir: string;
|
|
389
396
|
}
|
|
390
397
|
|
|
391
398
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
399
|
+
* Ranks ref-resolution failure kinds by how informative they are, so a
|
|
400
|
+
* single-target check surfaces the most useful failure across spaces instead of
|
|
401
|
+
* whichever space failed first. `not-found` (the input matched nothing here)
|
|
402
|
+
* says the least; a malformed input, a wrong grammar, or an in-space ambiguity
|
|
403
|
+
* all say more.
|
|
404
|
+
*/
|
|
405
|
+
function refFailureSpecificity(error: RefResolutionError): number {
|
|
406
|
+
switch (error.kind) {
|
|
407
|
+
case 'wrong-grammar':
|
|
408
|
+
return 3;
|
|
409
|
+
case 'ambiguous':
|
|
410
|
+
return 2;
|
|
411
|
+
case 'invalid-format':
|
|
412
|
+
return 1;
|
|
413
|
+
case 'not-found':
|
|
414
|
+
return 0;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Single-target (`check <ref/path>`) mode — resolves a migration reference
|
|
420
|
+
* across all contract spaces (or the one space narrowed by `--space <id>`).
|
|
421
|
+
*
|
|
422
|
+
* Resolution:
|
|
423
|
+
* - filesystem path → find the owning space by checking which space's
|
|
424
|
+
* `migrationsDir` contains the resolved path; falls back to app-relative
|
|
425
|
+
* validation when the path is outside every space dir.
|
|
426
|
+
* - ref → `parseMigrationRef` against each in-scope space; collect every
|
|
427
|
+
* (space, package) hit; 0 hits = not-found, 1 = check it, >1 = ambiguity
|
|
428
|
+
* error (qualify with `--space`).
|
|
429
|
+
*
|
|
430
|
+
* `--space <id>` is validated the same way the holistic path does it:
|
|
431
|
+
* invalid id → `errorInvalidSpaceId`; no on-disk space → `errorSpaceNotFound`.
|
|
397
432
|
*/
|
|
398
433
|
async function checkSingleTarget(
|
|
399
434
|
target: string,
|
|
400
|
-
|
|
435
|
+
inputs: SingleTargetInputs,
|
|
401
436
|
): Promise<MigrationCheckOutcome> {
|
|
402
|
-
const { appMigrationsDir, appMigrationsRelative
|
|
403
|
-
const loaded = await readMigrationsDir(appMigrationsDir);
|
|
404
|
-
const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
|
|
405
|
-
const appSpace: CheckSpace = {
|
|
406
|
-
spaceId: 'app',
|
|
407
|
-
packages: bundles,
|
|
408
|
-
refs: await readRefs(refsDir),
|
|
409
|
-
graph: reconstructGraph(bundles),
|
|
410
|
-
migrationsDir: appMigrationsDir,
|
|
411
|
-
refsDir,
|
|
412
|
-
};
|
|
437
|
+
const { spaces, spaceFilter, appMigrationsDir, appMigrationsRelative } = inputs;
|
|
413
438
|
|
|
414
|
-
|
|
439
|
+
if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
|
|
440
|
+
return { error: errorInvalidSpaceId(spaceFilter), exitCode: PRECONDITION };
|
|
441
|
+
}
|
|
442
|
+
if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
|
|
443
|
+
return {
|
|
444
|
+
error: errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()),
|
|
445
|
+
exitCode: PRECONDITION,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
415
448
|
|
|
449
|
+
const scopedSpaces =
|
|
450
|
+
spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
|
|
451
|
+
|
|
452
|
+
let matchedSpace: CheckSpace | undefined;
|
|
416
453
|
let matchedPkg: OnDiskMigrationPackage | undefined;
|
|
454
|
+
|
|
417
455
|
if (looksLikePath(target)) {
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
420
|
-
|
|
456
|
+
const resolvedPath = resolveTargetPathAcrossSpaces(target, scopedSpaces);
|
|
457
|
+
if (resolvedPath !== null) {
|
|
458
|
+
for (const space of scopedSpaces) {
|
|
459
|
+
const found = findPackageByDirPath(space.packages, resolvedPath);
|
|
460
|
+
if (found) {
|
|
461
|
+
matchedSpace = space;
|
|
462
|
+
matchedPkg = found;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
// Path outside every space dir — fall back to app-relative validation
|
|
468
|
+
const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
|
|
469
|
+
if (!resolved.ok) {
|
|
470
|
+
return { error: resolved.failure, exitCode: PRECONDITION };
|
|
471
|
+
}
|
|
472
|
+
const appSpace = scopedSpaces.find((s) => s.spaceId === 'app');
|
|
473
|
+
if (appSpace) {
|
|
474
|
+
matchedSpace = appSpace;
|
|
475
|
+
matchedPkg = findPackageByDirPath(appSpace.packages, resolved.value);
|
|
476
|
+
}
|
|
421
477
|
}
|
|
422
|
-
matchedPkg = findPackageByDirPath(bundles, resolved.value);
|
|
423
478
|
} else {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
479
|
+
// Ref resolution: try each in-scope space, collect all hits.
|
|
480
|
+
const hits: Array<{ space: CheckSpace; pkg: OnDiskMigrationPackage }> = [];
|
|
481
|
+
let bestParseFailure: RefResolutionError | undefined;
|
|
482
|
+
for (const space of scopedSpaces) {
|
|
483
|
+
const migResult = parseMigrationRef(target, { graph: space.graph, refs: space.refs });
|
|
484
|
+
if (!migResult.ok) {
|
|
485
|
+
// Keep scanning — a later space may hold a hit that must not be discarded.
|
|
486
|
+
// When no space yields a hit, keep the most informative failure rather than
|
|
487
|
+
// whichever space failed first (the kind is space-dependent).
|
|
488
|
+
if (
|
|
489
|
+
bestParseFailure === undefined ||
|
|
490
|
+
refFailureSpecificity(migResult.failure) > refFailureSpecificity(bestParseFailure)
|
|
491
|
+
) {
|
|
492
|
+
bestParseFailure = migResult.failure;
|
|
493
|
+
}
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
const pkg = space.packages.find(
|
|
497
|
+
(p) => p.metadata.migrationHash === migResult.value.migrationHash,
|
|
498
|
+
);
|
|
499
|
+
if (pkg) {
|
|
500
|
+
hits.push({ space, pkg });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (hits.length > 1) {
|
|
505
|
+
const spaceIds = hits.map((h) => h.space.spaceId);
|
|
506
|
+
return {
|
|
507
|
+
error: errorAmbiguousMigrationRef(target, spaceIds),
|
|
508
|
+
exitCode: PRECONDITION,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (hits.length === 1) {
|
|
513
|
+
matchedSpace = hits[0]!.space;
|
|
514
|
+
matchedPkg = hits[0]!.pkg;
|
|
515
|
+
} else if (bestParseFailure !== undefined) {
|
|
516
|
+
// The ref didn't resolve in any in-scope space — surface the most informative
|
|
517
|
+
// parse failure through the shared ref-resolution envelope (PN-RUN-3000) the
|
|
518
|
+
// earlier work established, rather than a bespoke string. (Ref-resolved-but-
|
|
519
|
+
// no-package falls through to the "not found on disk" result below.)
|
|
520
|
+
return { error: mapRefResolutionError(bestParseFailure), exitCode: PRECONDITION };
|
|
427
521
|
}
|
|
428
|
-
matchedPkg = bundles.find((p) => p.metadata.migrationHash === migResult.value.migrationHash);
|
|
429
522
|
}
|
|
430
523
|
|
|
431
|
-
if (!matchedPkg) {
|
|
524
|
+
if (!matchedPkg || !matchedSpace) {
|
|
432
525
|
return {
|
|
433
526
|
result: {
|
|
434
527
|
ok: false,
|
|
@@ -439,6 +532,8 @@ async function checkSingleTarget(
|
|
|
439
532
|
};
|
|
440
533
|
}
|
|
441
534
|
|
|
535
|
+
const failures: CheckFailure[] = [...checkManifestFilesPresent(matchedSpace)];
|
|
536
|
+
|
|
442
537
|
for (const f of ['migration.json', 'ops.json']) {
|
|
443
538
|
const fail = checkFileExists(matchedPkg.dirPath, matchedPkg.dirName, f);
|
|
444
539
|
if (fail) failures.push(fail);
|
|
@@ -457,15 +552,19 @@ async function checkSingleTarget(
|
|
|
457
552
|
const snapshotFailure = checkSnapshotConsistency(matchedPkg);
|
|
458
553
|
if (snapshotFailure) failures.push(snapshotFailure);
|
|
459
554
|
|
|
555
|
+
const resolvedSpaceId = matchedSpace.spaceId !== 'app' ? matchedSpace.spaceId : undefined;
|
|
556
|
+
|
|
460
557
|
if (failures.length === 0) {
|
|
461
558
|
return {
|
|
462
559
|
result: { ok: true, failures: [], summary: 'All checks passed' },
|
|
463
560
|
exitCode: OK,
|
|
561
|
+
...ifDefined('resolvedSpaceId', resolvedSpaceId),
|
|
464
562
|
};
|
|
465
563
|
}
|
|
466
564
|
return {
|
|
467
565
|
result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
|
|
468
566
|
exitCode: INTEGRITY_FAILED,
|
|
567
|
+
...ifDefined('resolvedSpaceId', resolvedSpaceId),
|
|
469
568
|
};
|
|
470
569
|
}
|
|
471
570
|
|
|
@@ -477,8 +576,9 @@ export function createMigrationCheckCommand(): Command {
|
|
|
477
576
|
'Validates that on-disk migration packages are internally consistent\n' +
|
|
478
577
|
'(hashes match, manifests are complete) and that the graph is well-formed\n' +
|
|
479
578
|
'(edges connect, refs point at valid nodes). The whole-graph check spans\n' +
|
|
480
|
-
'every contract space by default; pass --space <id> to narrow to one
|
|
481
|
-
'
|
|
579
|
+
'every contract space by default; pass --space <id> to narrow to one. A\n' +
|
|
580
|
+
'migration reference checks a single package, resolved across all contract\n' +
|
|
581
|
+
'spaces (narrow with --space; an ambiguous reference is a precondition failure).\n' +
|
|
482
582
|
'Offline — does not consult the database.\n' +
|
|
483
583
|
'Exit codes: 0 = all checks passed, 2 = precondition failed\n' +
|
|
484
584
|
'(unresolved target or unknown --space), 4 = integrity failure(s) found.',
|
|
@@ -487,6 +587,7 @@ export function createMigrationCheckCommand(): Command {
|
|
|
487
587
|
'prisma-next migration check',
|
|
488
588
|
'prisma-next migration check --space app',
|
|
489
589
|
'prisma-next migration check 20260101-add-users',
|
|
590
|
+
'prisma-next migration check 20260101-add-users --space app',
|
|
490
591
|
'prisma-next migration check --json',
|
|
491
592
|
]);
|
|
492
593
|
setCommandSeeAlso(command, [
|
|
@@ -535,7 +636,9 @@ export function createMigrationCheckCommand(): Command {
|
|
|
535
636
|
ui.output(JSON.stringify(result, null, 2));
|
|
536
637
|
} else if (!flags.quiet) {
|
|
537
638
|
if (result.ok) {
|
|
538
|
-
|
|
639
|
+
const spaceSuffix =
|
|
640
|
+
outcome.resolvedSpaceId !== undefined ? ` (space: ${outcome.resolvedSpaceId})` : '';
|
|
641
|
+
ui.log(`✔ ${result.summary}${spaceSuffix}`);
|
|
539
642
|
} else {
|
|
540
643
|
for (const f of result.failures) {
|
|
541
644
|
ui.log(`✗ [${f.pnCode}] ${f.where}: ${f.why}`);
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -374,6 +374,29 @@ export function requireLiveDatabase(args: {
|
|
|
374
374
|
* Maps a `RefResolutionError` from the contract/migration reference
|
|
375
375
|
* resolver into a CLI structured error envelope.
|
|
376
376
|
*/
|
|
377
|
+
/**
|
|
378
|
+
* A migration ref (dirName or hash-prefix) resolves in more than one contract
|
|
379
|
+
* space. The user must qualify with `--space <id>` to disambiguate.
|
|
380
|
+
*/
|
|
381
|
+
export function errorAmbiguousMigrationRef(
|
|
382
|
+
ref: string,
|
|
383
|
+
spaceIds: readonly string[],
|
|
384
|
+
): CliStructuredError {
|
|
385
|
+
const spaceList = spaceIds.join(', ');
|
|
386
|
+
return errorRuntime(
|
|
387
|
+
`Ambiguous migration reference: "${ref}" resolves in multiple spaces — qualify with --space <id>`,
|
|
388
|
+
{
|
|
389
|
+
why: `"${ref}" matches migrations in spaces: ${spaceList}.`,
|
|
390
|
+
fix: `Qualify with --space <id> to select one space. Available matching spaces: ${spaceList}.`,
|
|
391
|
+
meta: {
|
|
392
|
+
code: 'MIGRATION.AMBIGUOUS_MIGRATION_REF',
|
|
393
|
+
ref,
|
|
394
|
+
spaceIds: [...spaceIds],
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
377
400
|
export function mapRefResolutionError(error: RefResolutionError): CliStructuredError {
|
|
378
401
|
switch (error.kind) {
|
|
379
402
|
case 'not-found':
|
|
@@ -30,6 +30,27 @@ export function resolveAppTargetPath(
|
|
|
30
30
|
return ok(targetPath);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a filesystem-path target to the migration dir that contains it,
|
|
35
|
+
* searching each in-scope space's `migrationsDir`. A path is explicit, so
|
|
36
|
+
* it can belong to at most one space — returns the first match, or `null`
|
|
37
|
+
* when the path falls outside every space dir.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveTargetPathAcrossSpaces(
|
|
40
|
+
target: string,
|
|
41
|
+
spaces: ReadonlyArray<{ readonly migrationsDir: string }>,
|
|
42
|
+
): string | null {
|
|
43
|
+
const targetPath = resolve(target);
|
|
44
|
+
for (const space of spaces) {
|
|
45
|
+
const rel = relative(space.migrationsDir, targetPath);
|
|
46
|
+
const isOutside = rel === '' || rel === '.' || rel.startsWith('..') || isAbsolute(rel);
|
|
47
|
+
if (!isOutside) {
|
|
48
|
+
return targetPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
export function findPackageByDirPath(
|
|
34
55
|
packages: readonly OnDiskMigrationPackage[],
|
|
35
56
|
resolvedDirPath: string,
|