@prisma-next/cli 0.12.0-dev.42 → 0.12.0-dev.44

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.
Files changed (69) hide show
  1. package/dist/cli.mjs +12 -12
  2. package/dist/{client-Dk-zRFuT.mjs → client-CJWPvdKj.mjs} +4 -4
  3. package/dist/{client-Dk-zRFuT.mjs.map → client-CJWPvdKj.mjs.map} +1 -1
  4. package/dist/{command-helpers-xvg9oq4T.mjs → command-helpers-C6lqKJG1.mjs} +18 -2
  5. package/dist/command-helpers-C6lqKJG1.mjs.map +1 -0
  6. package/dist/commands/contract-emit.mjs +1 -1
  7. package/dist/commands/contract-infer.mjs +1 -1
  8. package/dist/commands/db-init.mjs +3 -3
  9. package/dist/commands/db-schema.mjs +3 -3
  10. package/dist/commands/db-sign.mjs +4 -4
  11. package/dist/commands/db-update.mjs +4 -4
  12. package/dist/commands/db-verify.mjs +1 -1
  13. package/dist/commands/migrate.mjs +4 -4
  14. package/dist/commands/migration-check.d.mts.map +1 -1
  15. package/dist/commands/migration-check.mjs +1 -1
  16. package/dist/commands/migration-graph.mjs +3 -3
  17. package/dist/commands/migration-list.mjs +1 -1
  18. package/dist/commands/migration-log.mjs +1 -1
  19. package/dist/commands/migration-new.mjs +3 -3
  20. package/dist/commands/migration-plan.mjs +1 -1
  21. package/dist/commands/migration-show.mjs +3 -3
  22. package/dist/commands/migration-status.mjs +1 -1
  23. package/dist/commands/ref.mjs +2 -2
  24. package/dist/commands/telemetry/index.mjs +1 -1
  25. package/dist/{contract-at-errors-Wj3u4Xco.mjs → contract-at-errors-CVQsgp3V.mjs} +2 -2
  26. package/dist/{contract-at-errors-Wj3u4Xco.mjs.map → contract-at-errors-CVQsgp3V.mjs.map} +1 -1
  27. package/dist/{contract-emit-CZU0UO6M.mjs → contract-emit-DnUkRd-7.mjs} +3 -3
  28. package/dist/{contract-emit-CZU0UO6M.mjs.map → contract-emit-DnUkRd-7.mjs.map} +1 -1
  29. package/dist/{contract-emit-FetLZ3jn.mjs → contract-emit-Iyq5V7OU.mjs} +3 -3
  30. package/dist/{contract-emit-FetLZ3jn.mjs.map → contract-emit-Iyq5V7OU.mjs.map} +1 -1
  31. package/dist/{contract-infer-3wtOsS_H.mjs → contract-infer-MmQMIms7.mjs} +3 -3
  32. package/dist/{contract-infer-3wtOsS_H.mjs.map → contract-infer-MmQMIms7.mjs.map} +1 -1
  33. package/dist/{contract-space-aggregate-loader-BdRPfM3Q.mjs → contract-space-aggregate-loader-BSWfMkFY.mjs} +2 -2
  34. package/dist/{contract-space-aggregate-loader-BdRPfM3Q.mjs.map → contract-space-aggregate-loader-BSWfMkFY.mjs.map} +1 -1
  35. package/dist/{db-verify-BisylXFZ.mjs → db-verify-B-YOEESk.mjs} +4 -4
  36. package/dist/{db-verify-BisylXFZ.mjs.map → db-verify-B-YOEESk.mjs.map} +1 -1
  37. package/dist/exports/control-api.mjs +2 -2
  38. package/dist/exports/index.mjs +1 -1
  39. package/dist/{framework-components-Be4inY3I.mjs → framework-components-D6VMhw2T.mjs} +2 -2
  40. package/dist/{framework-components-Be4inY3I.mjs.map → framework-components-D6VMhw2T.mjs.map} +1 -1
  41. package/dist/{init-BTE2U7lG.mjs → init-Ceib8PNc.mjs} +3 -3
  42. package/dist/{init-BTE2U7lG.mjs.map → init-Ceib8PNc.mjs.map} +1 -1
  43. package/dist/{inspect-live-schema-Cy9Y4wsL.mjs → inspect-live-schema-CjMZ0RkS.mjs} +3 -3
  44. package/dist/{inspect-live-schema-Cy9Y4wsL.mjs.map → inspect-live-schema-CjMZ0RkS.mjs.map} +1 -1
  45. package/dist/{migration-check-CUavU7U9.mjs → migration-check-DkNcCXZC.mjs} +112 -48
  46. package/dist/migration-check-DkNcCXZC.mjs.map +1 -0
  47. package/dist/{migration-command-scaffold-BxOxtyJ6.mjs → migration-command-scaffold-CrRysYxV.mjs} +3 -3
  48. package/dist/{migration-command-scaffold-BxOxtyJ6.mjs.map → migration-command-scaffold-CrRysYxV.mjs.map} +1 -1
  49. package/dist/{migration-list-jK6QeczE.mjs → migration-list-ip7fKesI.mjs} +3 -3
  50. package/dist/{migration-list-jK6QeczE.mjs.map → migration-list-ip7fKesI.mjs.map} +1 -1
  51. package/dist/{migration-log-DWI6dZyi.mjs → migration-log-BX9vu5c9.mjs} +3 -3
  52. package/dist/{migration-log-DWI6dZyi.mjs.map → migration-log-BX9vu5c9.mjs.map} +1 -1
  53. package/dist/{migration-path-target-DqcrbOis.mjs → migration-path-target-ByduK0eI.mjs} +17 -3
  54. package/dist/migration-path-target-ByduK0eI.mjs.map +1 -0
  55. package/dist/{migration-plan-NHdlUwPG.mjs → migration-plan-DLbbc_7z.mjs} +5 -5
  56. package/dist/{migration-plan-NHdlUwPG.mjs.map → migration-plan-DLbbc_7z.mjs.map} +1 -1
  57. package/dist/{migration-status-DI6Ldjbo.mjs → migration-status-BtdaOuQJ.mjs} +5 -5
  58. package/dist/{migration-status-DI6Ldjbo.mjs.map → migration-status-BtdaOuQJ.mjs.map} +1 -1
  59. package/dist/{telemetry-DQP0BvKv.mjs → telemetry-CxbY4NKn.mjs} +2 -2
  60. package/dist/{telemetry-DQP0BvKv.mjs.map → telemetry-CxbY4NKn.mjs.map} +1 -1
  61. package/dist/{verify-tvHRBBVP.mjs → verify-Df-8Kwat.mjs} +2 -2
  62. package/dist/{verify-tvHRBBVP.mjs.map → verify-Df-8Kwat.mjs.map} +1 -1
  63. package/package.json +18 -18
  64. package/src/commands/migration-check.ts +147 -44
  65. package/src/utils/cli-errors.ts +23 -0
  66. package/src/utils/migration-path-target.ts +21 -0
  67. package/dist/command-helpers-xvg9oq4T.mjs.map +0 -1
  68. package/dist/migration-check-CUavU7U9.mjs.map +0 -1
  69. 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 { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
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, refsDir } =
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 SingleTargetPaths {
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
- * Single-target (`check <ref/path>`) mode app-space only by design (the
393
- * migration's space is pinned by the reference; multi-space single-target
394
- * resolution is a deliberate follow-up, see the slice spec § Out of scope).
395
- * Resolves the one referenced package and verifies its hash / manifest /
396
- * snapshot, plus the app-space orphan-manifest check the prior behaviour ran.
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
- paths: SingleTargetPaths,
435
+ inputs: SingleTargetInputs,
401
436
  ): Promise<MigrationCheckOutcome> {
402
- const { appMigrationsDir, appMigrationsRelative, refsDir } = paths;
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
- const failures: CheckFailure[] = [...checkManifestFilesPresent(appSpace)];
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 resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
419
- if (!resolved.ok) {
420
- return { error: resolved.failure, exitCode: PRECONDITION };
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
- const migResult = parseMigrationRef(target, { graph: appSpace.graph, refs: appSpace.refs });
425
- if (!migResult.ok) {
426
- return { error: mapRefResolutionError(migResult.failure), exitCode: PRECONDITION };
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, or\n' +
481
- 'a migration reference to check a single app-space package.\n' +
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
- ui.log(`✔ ${result.summary}`);
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}`);
@@ -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,