limina 0.1.1 → 0.1.2

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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as __toESM, i as __require, n as __commonJSMin, r as __exportAll, t as require_picocolors } from "./chunks/dep-BPK-6PAr.js";
3
- import { A as isRelativeSpecifier, C as isPathInsideDirectory, D as toRelativePath, E as toPosixPath, F as uniqueSortedStrings, I as uniqueTrimmedNonEmptySortedStrings, L as uniqueValues, M as isVirtualModuleSpecifier, N as posix, O as isBarePackageSpecifier, P as uniqueBy, S as resolveRelativeModuleCandidate, T as normalizeSlashes, _ as resolveModuleNameWithCheckers, a as formatUnknownValue, b as resolveExistingFilePath, c as clearCheckerProjectConfigCache, d as getBuildCheckerSupportedExtensions, f as getCheckerAdapter, g as resolveCheckerProjectExtensions, h as parseCheckerProjectConfigForContext, i as loadConfig, j as isUrlOrDataOrFileSpecifier, k as isPackageImportSpecifier, l as collectMissingCheckerPeerDependencies, m as normalizeExtensions, n as getActiveCheckers, o as isNonEmptyString, p as getCheckerExtensions, r as isAutoCheckerConfigMode, s as isPlainRecord, u as formatMissingCheckerPeerDependencies, v as candidatePathsForBasePath, w as normalizeAbsolutePath, x as resolvePathMappedModuleCandidate, y as resolveBaseUrlModuleCandidate } from "./chunks/dep-C5gcXDHp.js";
4
- import { a as hasRunningSnapshotWork, c as toWritableText, i as formatMessageWithElapsed, n as SPINNER_INTERVAL_MS, o as renderSnapshotLines, r as formatInteractiveLine, s as toTreeFlowStatus, t as SPINNER_FRAMES } from "./chunks/dep-CuMHuWBQ.js";
3
+ import { A as isPackageImportSpecifier, C as resolveRelativeModuleCandidate, D as toPosixPath, E as normalizeSlashes, F as uniqueBy, I as uniqueSortedStrings, L as uniqueTrimmedNonEmptySortedStrings, M as isUrlOrDataOrFileSpecifier, N as isVirtualModuleSpecifier, O as toRelativePath, P as posix, R as uniqueValues, S as resolvePathMappedModuleCandidate, T as normalizeAbsolutePath, _ as resolveModuleNameWithCheckers, a as formatUnknownValue, b as resolveBaseUrlModuleCandidate, c as clearCheckerProjectConfigCache, d as getBuildCheckerSupportedExtensions, f as getCheckerAdapter, g as resolveCheckerProjectExtensions, h as parseCheckerProjectConfigForContext, i as loadConfig, j as isRelativeSpecifier, k as isBarePackageSpecifier, l as collectMissingCheckerPeerDependencies, m as normalizeExtensions, n as getActiveCheckers, o as isNonEmptyString, p as getCheckerExtensions, r as isAutoCheckerConfigMode, s as isPlainRecord, u as formatMissingCheckerPeerDependencies, v as resolveModuleNameWithCheckersDetailed, w as isPathInsideDirectory, x as resolveExistingFilePath, y as candidatePathsForBasePath } from "./chunks/dep-DJz9JBTi.js";
4
+ import { a as SPINNER_FRAMES, c as formatMessageWithElapsed, d as toTreeFlowStatus, f as toWritableText, i as writeWithFlowArgs, l as hasRunningSnapshotWork, n as TerminalFrameTracker, o as SPINNER_INTERVAL_MS, r as patchWriteStream, s as formatInteractiveLine, t as DEFAULT_TERMINAL_COLUMNS, u as renderSnapshotLinesForTerminal } from "./chunks/dep-mnWOqiGN.js";
5
5
  import { builtinModules, createRequire } from "node:module";
6
6
  import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from "node:fs";
7
7
  import ts from "typescript";
@@ -12,7 +12,7 @@ import { parseSync, rawTransferSupported } from "oxc-parser";
12
12
  import { ResolverFactory } from "oxc-resolver";
13
13
  import fs, { access, mkdir, mkdtemp, readFile, readdir, rm, rmdir, writeFile } from "node:fs/promises";
14
14
  import { glob } from "tinyglobby";
15
- import V, { stdin, stdout } from "node:process";
15
+ import process$1, { stdin, stdout } from "node:process";
16
16
  import os, { availableParallelism, tmpdir } from "node:os";
17
17
  import tty, { ReadStream } from "node:tty";
18
18
  import { createHash } from "node:crypto";
@@ -361,7 +361,7 @@ function parsePnpmWorkspaceListJson(source) {
361
361
  }];
362
362
  });
363
363
  }
364
- function getManifestPackageName$1(manifest) {
364
+ function getManifestPackageName$2(manifest) {
365
365
  return typeof manifest.name === "string" && manifest.name.trim().length > 0 ? manifest.name.trim() : null;
366
366
  }
367
367
  function isNamedWorkspacePackage(workspacePackage) {
@@ -370,7 +370,7 @@ function isNamedWorkspacePackage(workspacePackage) {
370
370
  function readWorkspacePackage(options) {
371
371
  const packageJsonPath = normalizeAbsolutePath(options.packageJsonPath);
372
372
  const manifest = readJsonFile(packageJsonPath);
373
- const name = getManifestPackageName$1(manifest);
373
+ const name = getManifestPackageName$2(manifest);
374
374
  return {
375
375
  directory: normalizeAbsolutePath(posix.dirname(packageJsonPath)),
376
376
  manifest,
@@ -1217,6 +1217,18 @@ function createImportAnalysisContext(options = {}) {
1217
1217
  caches.importsCache.set(cacheKey, imports);
1218
1218
  return imports;
1219
1219
  };
1220
+ const resolveTypeScriptImport = (specifier, containingFile, compilerOptions, contextOrExtensions) => resolveModuleNameWithCheckersDetailed({
1221
+ compilerOptions,
1222
+ containingFile: normalizeAbsolutePath(containingFile),
1223
+ context: normalizeContextInput(contextOrExtensions),
1224
+ specifier
1225
+ });
1226
+ const resolveOxcImport = (specifier, containingFile, compilerOptions, contextOrExtensions) => resolveModuleNameWithOxcCaches(caches, {
1227
+ compilerOptions,
1228
+ containingFile: normalizeAbsolutePath(containingFile),
1229
+ context: normalizeContextInput(contextOrExtensions),
1230
+ specifier
1231
+ });
1220
1232
  const resolveInternalImport = (specifier, containingFile, options, contextOrExtensions) => {
1221
1233
  const normalizedContainingFile = normalizeAbsolutePath(containingFile);
1222
1234
  const context = normalizeContextInput(contextOrExtensions);
@@ -1248,12 +1260,7 @@ function createImportAnalysisContext(options = {}) {
1248
1260
  const cached = caches.resolutionCache.get(cacheKey);
1249
1261
  if (cached !== void 0) return cached;
1250
1262
  const preferTypeScriptResolver = hasTypeScriptOnlyResolutionOptions(options);
1251
- const resolved = (preferTypeScriptResolver ? resolveModuleNameWithCheckers({
1252
- compilerOptions: options,
1253
- containingFile: normalizedContainingFile,
1254
- context,
1255
- specifier
1256
- }) : null) ?? resolveRelativeModuleCandidate({
1263
+ const resolved = (preferTypeScriptResolver ? resolveTypeScriptImport(specifier, normalizedContainingFile, options, context)?.resolvedFileName : null) ?? resolveRelativeModuleCandidate({
1257
1264
  containingFile: normalizedContainingFile,
1258
1265
  extensions,
1259
1266
  specifier
@@ -1265,12 +1272,7 @@ function createImportAnalysisContext(options = {}) {
1265
1272
  compilerOptions: options,
1266
1273
  extensions,
1267
1274
  specifier
1268
- }) ?? (preferTypeScriptResolver ? null : resolveModuleNameWithOxcCaches(caches, {
1269
- compilerOptions: options,
1270
- containingFile: normalizedContainingFile,
1271
- context,
1272
- specifier
1273
- }) ?? resolveModuleNameWithCheckers({
1275
+ }) ?? (preferTypeScriptResolver ? null : resolveOxcImport(specifier, normalizedContainingFile, options, context) ?? resolveModuleNameWithCheckers({
1274
1276
  compilerOptions: options,
1275
1277
  containingFile: normalizedContainingFile,
1276
1278
  context,
@@ -1281,7 +1283,9 @@ function createImportAnalysisContext(options = {}) {
1281
1283
  };
1282
1284
  return {
1283
1285
  collectImportsFromFile,
1284
- resolveInternalImport
1286
+ resolveInternalImport,
1287
+ resolveOxcImport,
1288
+ resolveTypeScriptImport
1285
1289
  };
1286
1290
  }
1287
1291
  function collectImportsFromFile(filePath, rootDir, context) {
@@ -1408,9 +1412,6 @@ function chooseOwningProject(projectPaths) {
1408
1412
  function findPackageForFile(filePath, packages) {
1409
1413
  return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
1410
1414
  }
1411
- function findImporterForFile(filePath, importers) {
1412
- return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
1413
- }
1414
1415
  function shouldResolveThroughGraph(importer, targetPackage) {
1415
1416
  if (!importer || !targetPackage?.name) return false;
1416
1417
  return importer.name === targetPackage.name || importer.declaredWorkspaceDependencies.has(targetPackage.name);
@@ -1459,6 +1460,7 @@ const LIMINA_CHECK_ISSUE_CODES = {
1459
1460
  graphConfigInvalid: "LIMINA_GRAPH_CONFIG_INVALID",
1460
1461
  graphImportTargetUnmapped: "LIMINA_GRAPH_IMPORT_TARGET_UNMAPPED",
1461
1462
  graphPrepareFailed: "LIMINA_GRAPH_PREPARE_FAILED",
1463
+ graphReferenceCycle: "LIMINA_GRAPH_REFERENCE_CYCLE",
1462
1464
  graphReferenceExtra: "LIMINA_GRAPH_REFERENCE_EXTRA",
1463
1465
  graphReferenceMissing: "LIMINA_GRAPH_REFERENCE_MISSING",
1464
1466
  graphTargetUnreachable: "LIMINA_GRAPH_TARGET_UNREACHABLE",
@@ -1553,6 +1555,11 @@ const LIMINA_CHECK_ISSUE_RULE_METADATA = {
1553
1555
  description: "Generated graph preparation failed.",
1554
1556
  task: "graph:prepare"
1555
1557
  },
1558
+ [LIMINA_CHECK_ISSUE_CODES.graphReferenceCycle]: {
1559
+ code: LIMINA_CHECK_ISSUE_CODES.graphReferenceCycle,
1560
+ description: "Generated TypeScript project references contain a cycle.",
1561
+ task: "graph:check"
1562
+ },
1556
1563
  [LIMINA_CHECK_ISSUE_CODES.graphReferenceExtra]: {
1557
1564
  code: LIMINA_CHECK_ISSUE_CODES.graphReferenceExtra,
1558
1565
  description: "A TypeScript project reference exists without a matching source edge.",
@@ -2002,13 +2009,13 @@ const ansiStyles$1 = assembleStyles$1();
2002
2009
 
2003
2010
  //#endregion
2004
2011
  //#region ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
2005
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : V.argv) {
2012
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
2006
2013
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
2007
2014
  const position = argv.indexOf(prefix + flag);
2008
2015
  const terminatorPosition = argv.indexOf("--");
2009
2016
  return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
2010
2017
  }
2011
- const { env } = V;
2018
+ const { env } = process$1;
2012
2019
  let flagForceColor;
2013
2020
  if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) flagForceColor = 0;
2014
2021
  else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) flagForceColor = 1;
@@ -2041,7 +2048,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
2041
2048
  if (haveStream && !streamIsTTY && forceColor === void 0) return 0;
2042
2049
  const min = forceColor || 0;
2043
2050
  if (env.TERM === "dumb") return min;
2044
- if (V.platform === "win32") {
2051
+ if (process$1.platform === "win32") {
2045
2052
  const osRelease = os.release().split(".");
2046
2053
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
2047
2054
  return 1;
@@ -2821,7 +2828,7 @@ const NEWLINE = "\n";
2821
2828
  const PAD = " ";
2822
2829
  const NONE = "none";
2823
2830
  const terminalColumns = () => {
2824
- const { env, stdout, stderr } = V;
2831
+ const { env, stdout, stderr } = process$1;
2825
2832
  if (stdout?.columns) return stdout.columns;
2826
2833
  if (stderr?.columns) return stderr.columns;
2827
2834
  if (env.COLUMNS) return Number.parseInt(env.COLUMNS, 10);
@@ -2964,8 +2971,8 @@ const boxContent = (content, contentWidth, options) => {
2964
2971
  return result;
2965
2972
  };
2966
2973
  const sanitizeOptions = (options) => {
2967
- if (options.fullscreen && V?.stdout) {
2968
- let newDimensions = [V.stdout.columns, V.stdout.rows];
2974
+ if (options.fullscreen && process$1?.stdout) {
2975
+ let newDimensions = [process$1.stdout.columns, process$1.stdout.rows];
2969
2976
  if (typeof options.fullscreen === "function") newDimensions = options.fullscreen(...newDimensions);
2970
2977
  options.width ||= newDimensions[0];
2971
2978
  options.height ||= newDimensions[1];
@@ -3699,6 +3706,9 @@ function createSourceConfigScope(sourceConfigPath) {
3699
3706
  if (fileName === "tsconfig.json") return "tsconfig";
3700
3707
  return fileName.replace(/^tsconfig\./u, "").replace(/\.json$/u, "");
3701
3708
  }
3709
+ function createOutputFileName(sourceFileName) {
3710
+ return sourceFileName === "tsconfig.json" ? "tsconfig.output.json" : sourceFileName.replace(/\.json$/u, ".output.json");
3711
+ }
3702
3712
  function getGeneratedDtsConfigPath(options) {
3703
3713
  const relativeSourcePath = toRelativePath(options.rootDir, options.sourceConfigPath);
3704
3714
  const relativeDir = posix.dirname(relativeSourcePath);
@@ -3710,6 +3720,17 @@ function getGeneratedSolutionBuildConfigPath(options) {
3710
3720
  const relativeDir = posix.dirname(relativeSourcePath);
3711
3721
  return normalizeAbsolutePath(posix.join(options.rootDir, generatedTsconfigDir, "checkers", options.checkerName, "solutions", relativeDir === "." ? "" : relativeDir, "tsconfig.build.json"));
3712
3722
  }
3723
+ function getGeneratedOutputProjectConfigPath(options) {
3724
+ const relativeSourcePath = toRelativePath(options.rootDir, options.sourceConfigPath);
3725
+ const relativeDir = posix.dirname(relativeSourcePath);
3726
+ const outputFileName = createOutputFileName(posix.basename(relativeSourcePath));
3727
+ return normalizeAbsolutePath(posix.join(options.rootDir, generatedTsconfigDir, "checkers", options.checkerName, "outputs", "projects", relativeDir === "." ? "" : relativeDir, outputFileName));
3728
+ }
3729
+ function getGeneratedOutputSolutionConfigPath(options) {
3730
+ const relativeSourcePath = toRelativePath(options.rootDir, options.sourceConfigPath);
3731
+ const relativeDir = posix.dirname(relativeSourcePath);
3732
+ return normalizeAbsolutePath(posix.join(options.rootDir, generatedTsconfigDir, "checkers", options.checkerName, "outputs", "solutions", relativeDir === "." ? "" : relativeDir, "tsconfig.output.json"));
3733
+ }
3713
3734
  function getGeneratedCheckerEntryPath(options) {
3714
3735
  return normalizeAbsolutePath(posix.join(options.rootDir, generatedTsconfigDir, "checkers", options.checkerName, "tsconfig.build.json"));
3715
3736
  }
@@ -4622,6 +4643,37 @@ function formatCheckIssueSnapshotInventory(options) {
4622
4643
  return issueSummary;
4623
4644
  }
4624
4645
 
4646
+ //#endregion
4647
+ //#region src/core/import-graph/declaration-provider.ts
4648
+ const declarationFileFamilyPattern = /\.d\.(?:cts|mts|ts)$/u;
4649
+ function isDeclarationFileFamily(filePath) {
4650
+ return declarationFileFamilyPattern.test(filePath);
4651
+ }
4652
+ function resolveDeclarationProvider(options) {
4653
+ const typeScriptResolution = options.importAnalysis.resolveTypeScriptImport(options.importRecord.specifier, options.containingFile, options.compilerOptions, options.project);
4654
+ const oxcResolvedFilePath = options.importAnalysis.resolveOxcImport(options.importRecord.specifier, options.containingFile, options.compilerOptions, options.project);
4655
+ if (!typeScriptResolution) return oxcResolvedFilePath ? {
4656
+ kind: "oxc-only",
4657
+ oxcResolvedFilePath,
4658
+ typeScriptResolution: null
4659
+ } : {
4660
+ kind: "unresolved",
4661
+ oxcResolvedFilePath: null,
4662
+ typeScriptResolution: null
4663
+ };
4664
+ if (isDeclarationFileFamily(typeScriptResolution.resolvedFileName)) return {
4665
+ kind: "declaration",
4666
+ oxcResolvedFilePath,
4667
+ typeScriptResolution
4668
+ };
4669
+ return {
4670
+ kind: "source",
4671
+ ownerProjectPaths: options.fileOwnerLookup.get(typeScriptResolution.resolvedFileName) ?? [],
4672
+ oxcResolvedFilePath,
4673
+ typeScriptResolution
4674
+ };
4675
+ }
4676
+
4625
4677
  //#endregion
4626
4678
  //#region src/core/packages/build-scripts.ts
4627
4679
  const supportedBuildCheckers = new Set([
@@ -4629,10 +4681,7 @@ const supportedBuildCheckers = new Set([
4629
4681
  "vue-tsc",
4630
4682
  "tsgo"
4631
4683
  ]);
4632
- function hasLiminaCheckerBuildIntent(command) {
4633
- return /\blimina\s+checker\s+build\b/u.test(command);
4634
- }
4635
- function hasLegacyLiminaBuildIntent(command) {
4684
+ function hasLiminaBuildIntent(command) {
4636
4685
  return /\blimina\s+build\b/u.test(command);
4637
4686
  }
4638
4687
  function hasShellControlOperator(command) {
@@ -4666,10 +4715,10 @@ function tokenizeStaticCommand(command) {
4666
4715
  if (current.length > 0) tokens.push(current);
4667
4716
  return tokens;
4668
4717
  }
4669
- function getLiminaCheckerBuildArgumentOffset(tokens) {
4670
- if (tokens[0] === "limina" && tokens[1] === "checker" && tokens[2] === "build") return 3;
4671
- if (tokens[0] === "pnpm" && tokens[1] === "limina" && tokens[2] === "checker" && tokens[3] === "build") return 4;
4672
- if (tokens[0] === "pnpm" && tokens[1] === "exec" && tokens[2] === "limina" && tokens[3] === "checker" && tokens[4] === "build") return 5;
4718
+ function getLiminaBuildArgumentOffset(tokens) {
4719
+ if (tokens[0] === "limina" && tokens[1] === "build") return 2;
4720
+ if (tokens[0] === "pnpm" && tokens[1] === "limina" && tokens[2] === "build") return 3;
4721
+ if (tokens[0] === "pnpm" && tokens[1] === "exec" && tokens[2] === "limina" && tokens[3] === "build") return 4;
4673
4722
  return null;
4674
4723
  }
4675
4724
  function parseChecker(value) {
@@ -4679,12 +4728,12 @@ function createDiagnostic(options) {
4679
4728
  return options;
4680
4729
  }
4681
4730
  function parsePackageBuildScript(options) {
4682
- if (!hasLiminaCheckerBuildIntent(options.command) && !hasLegacyLiminaBuildIntent(options.command)) return null;
4731
+ if (!hasLiminaBuildIntent(options.command)) return null;
4683
4732
  if (hasShellControlOperator(options.command) || /[$`]/u.test(options.command)) return createDiagnostic({
4684
4733
  command: options.command,
4685
4734
  packageJsonPath: options.packageJsonPath,
4686
4735
  packageName: options.packageName,
4687
- reason: "Limina only derives Knip source configs from static limina checker build scripts without shell control operators or dynamic expansion.",
4736
+ reason: "Limina only derives Knip source configs from static limina build scripts without shell control operators or dynamic expansion.",
4688
4737
  scriptName: options.scriptName
4689
4738
  });
4690
4739
  const tokens = tokenizeStaticCommand(options.command);
@@ -4695,21 +4744,22 @@ function parsePackageBuildScript(options) {
4695
4744
  reason: "Limina could not statically tokenize this package script.",
4696
4745
  scriptName: options.scriptName
4697
4746
  });
4698
- const argumentOffset = getLiminaCheckerBuildArgumentOffset(tokens);
4747
+ const argumentOffset = getLiminaBuildArgumentOffset(tokens);
4699
4748
  if (argumentOffset === null) return createDiagnostic({
4700
4749
  command: options.command,
4701
4750
  packageJsonPath: options.packageJsonPath,
4702
4751
  packageName: options.packageName,
4703
- reason: "Limina only recognizes limina checker build, pnpm limina checker build, and pnpm exec limina checker build.",
4752
+ reason: "Limina only recognizes direct limina build, pnpm limina build, and pnpm exec limina build package scripts.",
4704
4753
  scriptName: options.scriptName
4705
4754
  });
4706
4755
  let checker;
4707
4756
  let configPath;
4708
- let sawTargetOption = false;
4757
+ let raw = false;
4709
4758
  for (let index = argumentOffset; index < tokens.length; index += 1) {
4710
4759
  const token = tokens[index];
4711
- if (token === "-w" || token === "--watch") {
4712
- sawTargetOption = true;
4760
+ if (token === "-w" || token === "--watch") continue;
4761
+ if (token === "--raw") {
4762
+ raw = true;
4713
4763
  continue;
4714
4764
  }
4715
4765
  if (token === "--checker" || token.startsWith("--checker=")) return createDiagnostic({
@@ -4720,7 +4770,6 @@ function parsePackageBuildScript(options) {
4720
4770
  scriptName: options.scriptName
4721
4771
  });
4722
4772
  if (token === "--preset") {
4723
- sawTargetOption = true;
4724
4773
  const parsedChecker = parseChecker(tokens[index + 1]);
4725
4774
  if (!parsedChecker) return createDiagnostic({
4726
4775
  command: options.command,
@@ -4734,7 +4783,6 @@ function parsePackageBuildScript(options) {
4734
4783
  continue;
4735
4784
  }
4736
4785
  if (token.startsWith("--preset=")) {
4737
- sawTargetOption = true;
4738
4786
  const parsedChecker = parseChecker(token.slice(9));
4739
4787
  if (!parsedChecker) return createDiagnostic({
4740
4788
  command: options.command,
@@ -4750,32 +4798,37 @@ function parsePackageBuildScript(options) {
4750
4798
  command: options.command,
4751
4799
  packageJsonPath: options.packageJsonPath,
4752
4800
  packageName: options.packageName,
4753
- reason: "Limina checker build script analysis only supports --preset, -w/--watch, plus one config argument.",
4801
+ reason: "Limina build script analysis only supports --raw, --preset, -w/--watch, plus one literal config argument.",
4754
4802
  scriptName: options.scriptName
4755
4803
  });
4756
4804
  if (configPath) return createDiagnostic({
4757
4805
  command: options.command,
4758
4806
  packageJsonPath: options.packageJsonPath,
4759
4807
  packageName: options.packageName,
4760
- reason: "Limina checker build script analysis found multiple config arguments.",
4808
+ reason: "Limina build script analysis found multiple config arguments.",
4761
4809
  scriptName: options.scriptName
4762
4810
  });
4763
4811
  configPath = token;
4764
4812
  }
4765
- if (!configPath) {
4766
- if (!sawTargetOption) return null;
4767
- return createDiagnostic({
4768
- command: options.command,
4769
- packageJsonPath: options.packageJsonPath,
4770
- packageName: options.packageName,
4771
- reason: "Limina checker build script analysis requires a config argument.",
4772
- scriptName: options.scriptName
4773
- });
4774
- }
4813
+ if (!configPath) return createDiagnostic({
4814
+ command: options.command,
4815
+ packageJsonPath: options.packageJsonPath,
4816
+ packageName: options.packageName,
4817
+ reason: "Limina build script analysis requires a config argument.",
4818
+ scriptName: options.scriptName
4819
+ });
4820
+ if (raw && !checker) return createDiagnostic({
4821
+ command: options.command,
4822
+ packageJsonPath: options.packageJsonPath,
4823
+ packageName: options.packageName,
4824
+ reason: "limina build --raw package scripts require --preset.",
4825
+ scriptName: options.scriptName
4826
+ });
4775
4827
  return {
4776
4828
  ...checker ? { checker } : {},
4777
4829
  command: options.command,
4778
4830
  configPath: normalizeAbsolutePath(posix.resolve(options.packageDirectory, configPath)),
4831
+ raw,
4779
4832
  name: options.scriptName,
4780
4833
  packageJsonPath: options.packageJsonPath,
4781
4834
  packageName: options.packageName
@@ -4816,9 +4869,14 @@ function getGeneratedKnipConfigPath(options) {
4816
4869
  const relativeOutputPath = relativePackageDirectory === "." ? posix.join(".limina/knip", "tsconfig.knip.json") : posix.join(".limina/knip", relativePackageDirectory, "tsconfig.knip.json");
4817
4870
  return normalizeAbsolutePath(posix.join(options.rootDir, relativeOutputPath));
4818
4871
  }
4819
- function isManagedBuildConfig(options) {
4820
- for (const sourceToBuild of options.sourceToBuildByChecker.values()) if (sourceToBuild.has(options.configPath)) return true;
4821
- return false;
4872
+ function resolveManagedBuildConfigPaths(options) {
4873
+ const configPaths = /* @__PURE__ */ new Set();
4874
+ for (const checker of options.checkers) {
4875
+ if (options.script.checker && checker.preset !== options.script.checker) continue;
4876
+ const outputModule = options.configToOutputBuildByChecker.get(checker.name)?.get(options.script.configPath);
4877
+ if (outputModule) configPaths.add(outputModule.path);
4878
+ }
4879
+ return [...configPaths].sort((left, right) => left.localeCompare(right));
4822
4880
  }
4823
4881
  function toPackageScriptDiagnostic(diagnostic) {
4824
4882
  return {
@@ -4928,10 +4986,12 @@ function prepareGeneratedKnipPackageConfigs(options) {
4928
4986
  const references = /* @__PURE__ */ new Set();
4929
4987
  const scripts = [];
4930
4988
  for (const script of packageScripts) {
4931
- const mode = isManagedBuildConfig({
4932
- configPath: script.configPath,
4933
- sourceToBuildByChecker: options.sourceToBuildByChecker
4934
- }) ? "managed" : "raw";
4989
+ const managedConfigPaths = script.raw ? [] : resolveManagedBuildConfigPaths({
4990
+ checkers: options.checkers,
4991
+ configToOutputBuildByChecker: options.configToOutputBuildByChecker,
4992
+ script
4993
+ });
4994
+ const mode = script.raw ? "raw" : "managed";
4935
4995
  const diagnostic = validatePackageBuildScript({
4936
4996
  config: options.config,
4937
4997
  mode,
@@ -4942,7 +5002,17 @@ function prepareGeneratedKnipPackageConfigs(options) {
4942
5002
  diagnostics.push(diagnostic);
4943
5003
  continue;
4944
5004
  }
4945
- references.add(script.configPath);
5005
+ if (!script.raw && managedConfigPaths.length === 0) {
5006
+ diagnostics.push({
5007
+ command: script.command,
5008
+ packageJsonPath: script.packageJsonPath,
5009
+ packageName: script.packageName,
5010
+ reason: "managed limina build package scripts must point to a Limina-managed config with liminaOptions.outputs.",
5011
+ scriptName: script.name
5012
+ });
5013
+ continue;
5014
+ }
5015
+ for (const referencePath of script.raw ? [script.configPath] : managedConfigPaths) references.add(referencePath);
4946
5016
  scripts.push({
4947
5017
  ...script.checker ? { checker: script.checker } : {},
4948
5018
  command: script.command,
@@ -5000,6 +5070,154 @@ function readGraphRules(config, sourceConfigPath) {
5000
5070
  const graphRules = liminaOptions.graphRules;
5001
5071
  return Array.isArray(graphRules) ? uniqueValues(graphRules.filter((label) => typeof label === "string" && label.trim().length > 0)).map((label) => label.trim()) : [];
5002
5072
  }
5073
+ function addOutputOptionsProblem(options) {
5074
+ options.problems.push([
5075
+ "Invalid Limina output options:",
5076
+ ` config: ${toRelativePath(options.config.rootDir, options.sourceConfigPath)}`,
5077
+ ` field: ${options.field}`,
5078
+ ...Object.hasOwn(options, "value") ? [` value: ${formatUnknownValue(options.value)}`] : [],
5079
+ ` reason: ${options.reason}`
5080
+ ].join("\n"));
5081
+ }
5082
+ function normalizeExtendsConfigPath(configPath, extendsValue) {
5083
+ const trimmedValue = extendsValue.trim();
5084
+ if (trimmedValue.length === 0) return null;
5085
+ if (posix.isAbsolute(trimmedValue) || trimmedValue.startsWith("./") || trimmedValue.startsWith("../")) {
5086
+ const resolvedPath = posix.resolve(posix.dirname(configPath), trimmedValue);
5087
+ return normalizeAbsolutePath(posix.extname(resolvedPath) ? resolvedPath : `${resolvedPath}.json`);
5088
+ }
5089
+ try {
5090
+ return normalizeAbsolutePath(createRequire(configPath).resolve(trimmedValue));
5091
+ } catch {
5092
+ return null;
5093
+ }
5094
+ }
5095
+ function readExplicitSourceCompilerTarget(options) {
5096
+ const seenConfigPaths = options.seenConfigPaths ?? /* @__PURE__ */ new Set();
5097
+ const configPath = normalizeAbsolutePath(options.configPath);
5098
+ if (seenConfigPaths.has(configPath) || !existsSync(configPath)) return null;
5099
+ seenConfigPaths.add(configPath);
5100
+ const configObject = readJsonConfig(options.config, configPath);
5101
+ const extendsValue = configObject.extends;
5102
+ const extendsValues = typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) ? extendsValue.filter((entry) => typeof entry === "string") : [];
5103
+ let target = null;
5104
+ for (const entry of extendsValues) {
5105
+ const extendedConfigPath = normalizeExtendsConfigPath(configPath, entry);
5106
+ if (!extendedConfigPath) continue;
5107
+ target = readExplicitSourceCompilerTarget({
5108
+ config: options.config,
5109
+ configPath: extendedConfigPath,
5110
+ seenConfigPaths
5111
+ }) ?? target;
5112
+ }
5113
+ const compilerOptions = configObject.compilerOptions;
5114
+ if (isPlainRecord(compilerOptions)) {
5115
+ const targetValue = compilerOptions.target;
5116
+ if (isNonEmptyString(targetValue)) target = targetValue.trim();
5117
+ }
5118
+ return target;
5119
+ }
5120
+ function readOutputOptions(config, sourceConfigPath) {
5121
+ const liminaOptions = readJsonConfig(config, sourceConfigPath).liminaOptions;
5122
+ const problems = [];
5123
+ if (liminaOptions === void 0) return {
5124
+ outputs: null,
5125
+ problems
5126
+ };
5127
+ if (!isPlainRecord(liminaOptions)) {
5128
+ addOutputOptionsProblem({
5129
+ config,
5130
+ field: "liminaOptions",
5131
+ problems,
5132
+ reason: "liminaOptions must be an object before outputs can be read.",
5133
+ sourceConfigPath,
5134
+ value: liminaOptions
5135
+ });
5136
+ return {
5137
+ outputs: null,
5138
+ problems
5139
+ };
5140
+ }
5141
+ const outputs = liminaOptions.outputs;
5142
+ if (outputs === void 0) return {
5143
+ outputs: null,
5144
+ problems
5145
+ };
5146
+ if (!isPlainRecord(outputs)) {
5147
+ addOutputOptionsProblem({
5148
+ config,
5149
+ field: "liminaOptions.outputs",
5150
+ problems,
5151
+ reason: "outputs must be an object.",
5152
+ sourceConfigPath,
5153
+ value: outputs
5154
+ });
5155
+ return {
5156
+ outputs: null,
5157
+ problems
5158
+ };
5159
+ }
5160
+ const allowedFields = new Set([
5161
+ "outDir",
5162
+ "rootDir",
5163
+ "target",
5164
+ "tsBuildInfoFile"
5165
+ ]);
5166
+ for (const fieldName of Object.keys(outputs)) if (!allowedFields.has(fieldName)) addOutputOptionsProblem({
5167
+ config,
5168
+ field: `liminaOptions.outputs.${fieldName}`,
5169
+ problems,
5170
+ reason: "outputs only supports target, rootDir, outDir, and tsBuildInfoFile.",
5171
+ sourceConfigPath,
5172
+ value: outputs[fieldName]
5173
+ });
5174
+ const outputValues = {};
5175
+ for (const fieldName of allowedFields) {
5176
+ const fieldValue = outputs[fieldName];
5177
+ if (fieldValue === void 0) continue;
5178
+ if (!isNonEmptyString(fieldValue)) {
5179
+ addOutputOptionsProblem({
5180
+ config,
5181
+ field: `liminaOptions.outputs.${fieldName}`,
5182
+ problems,
5183
+ reason: "output option fields must be non-empty strings.",
5184
+ sourceConfigPath,
5185
+ value: fieldValue
5186
+ });
5187
+ continue;
5188
+ }
5189
+ if ((fieldName === "rootDir" || fieldName === "outDir" || fieldName === "tsBuildInfoFile") && posix.isAbsolute(fieldValue)) {
5190
+ addOutputOptionsProblem({
5191
+ config,
5192
+ field: `liminaOptions.outputs.${fieldName}`,
5193
+ problems,
5194
+ reason: "output path fields must be relative to the tsconfig that declares them.",
5195
+ sourceConfigPath,
5196
+ value: fieldValue
5197
+ });
5198
+ continue;
5199
+ }
5200
+ outputValues[fieldName] = fieldValue.trim();
5201
+ }
5202
+ if (problems.length > 0) return {
5203
+ outputs: null,
5204
+ problems
5205
+ };
5206
+ const sourceConfigDirectory = posix.dirname(sourceConfigPath);
5207
+ const scope = createSourceConfigScope(sourceConfigPath);
5208
+ return {
5209
+ outputs: {
5210
+ target: outputValues.target ?? readExplicitSourceCompilerTarget({
5211
+ config,
5212
+ configPath: sourceConfigPath
5213
+ }) ?? "ESNext",
5214
+ rootDir: normalizeAbsolutePath(posix.resolve(sourceConfigDirectory, outputValues.rootDir ?? ".")),
5215
+ outDir: normalizeAbsolutePath(posix.resolve(sourceConfigDirectory, outputValues.outDir ?? "./dist")),
5216
+ tsBuildInfoFile: normalizeAbsolutePath(posix.resolve(sourceConfigDirectory, outputValues.tsBuildInfoFile ?? `./dist/.${scope}_tsbuildinfo`))
5217
+ },
5218
+ problems
5219
+ };
5220
+ }
5003
5221
  function addImplicitRefProblem(options) {
5004
5222
  options.problems.push([
5005
5223
  "Invalid Limina implicit reference:",
@@ -5372,12 +5590,26 @@ function createSolutionBuildModule(options) {
5372
5590
  path: getGeneratedSolutionBuildConfigPath(options)
5373
5591
  };
5374
5592
  }
5593
+ function createOutputProjectBuildModule(options) {
5594
+ return {
5595
+ kind: "project",
5596
+ path: getGeneratedOutputProjectConfigPath(options)
5597
+ };
5598
+ }
5599
+ function createOutputSolutionBuildModule(options) {
5600
+ return {
5601
+ kind: "solution",
5602
+ path: getGeneratedOutputSolutionConfigPath(options)
5603
+ };
5604
+ }
5375
5605
  function collectCheckerSourceConfigModules(options) {
5376
5606
  if (options.excludedConfigPaths.has(options.sourceConfigPath)) return;
5377
5607
  if (options.seenConfigs.has(options.sourceConfigPath)) return;
5378
5608
  options.seenConfigs.add(options.sourceConfigPath);
5379
5609
  const configObject = readJsonConfig(options.config, options.sourceConfigPath);
5380
5610
  const hasReferences = Object.hasOwn(configObject, "references");
5611
+ const outputOptions = readOutputOptions(options.config, options.sourceConfigPath);
5612
+ options.problems.push(...outputOptions.problems);
5381
5613
  if (hasReferences && !isDefaultTsconfigPath(options.sourceConfigPath)) {
5382
5614
  addSourceReferenceConfigProblems({
5383
5615
  config: options.config,
@@ -5387,6 +5619,13 @@ function collectCheckerSourceConfigModules(options) {
5387
5619
  return;
5388
5620
  }
5389
5621
  if (hasReferences && isDefaultTsconfigPath(options.sourceConfigPath)) {
5622
+ if (outputOptions.outputs) options.problems.push([
5623
+ "Invalid Limina output options:",
5624
+ ` config: ${toRelativePath(options.config.rootDir, options.sourceConfigPath)}`,
5625
+ " field: liminaOptions.outputs",
5626
+ " reason: liminaOptions.outputs is only allowed on ordinary source leaf configs, not solution-style configs.",
5627
+ " fix: move liminaOptions.outputs to one or more referenced source leaf tsconfigs."
5628
+ ].join("\n"));
5390
5629
  options.collection.solutionConfigPaths.add(options.sourceConfigPath);
5391
5630
  options.collection.buildModulesBySourcePath.set(options.sourceConfigPath, createSolutionBuildModule({
5392
5631
  checkerName: options.checkerName,
@@ -5702,6 +5941,7 @@ function createSourceProject(options) {
5702
5941
  projectRootDir: options.config.rootDir
5703
5942
  });
5704
5943
  const ownedFileNames = parsed.fileNames.map(normalizeAbsolutePath).sort();
5944
+ const outputOptions = readOutputOptions(options.config, options.sourceConfigPath);
5705
5945
  return {
5706
5946
  checkerName: options.checkerName,
5707
5947
  configPath: options.sourceConfigPath,
@@ -5714,6 +5954,13 @@ function createSourceProject(options) {
5714
5954
  fileNames: uniqueSortedStrings([...ownedFileNames, ...readRelativeTypeFiles$1(options.config, options.sourceConfigPath)]),
5715
5955
  graphRules: readGraphRules(options.config, options.sourceConfigPath),
5716
5956
  ownedFileNames,
5957
+ outputConfigPath: getGeneratedOutputProjectConfigPath({
5958
+ checkerName: options.checkerName,
5959
+ rootDir: options.config.rootDir,
5960
+ sourceConfigPath: options.sourceConfigPath
5961
+ }),
5962
+ outputOptions: outputOptions.outputs,
5963
+ outputReferences: /* @__PURE__ */ new Set(),
5717
5964
  options: parsed.options,
5718
5965
  references: /* @__PURE__ */ new Set()
5719
5966
  };
@@ -5731,6 +5978,104 @@ function createSolutionProject(options) {
5731
5978
  references: new Set(referenceSourceConfigPaths.map((sourceConfigPath) => options.collection.buildModulesBySourcePath.get(sourceConfigPath)?.path).filter((buildPath) => Boolean(buildPath)))
5732
5979
  };
5733
5980
  }
5981
+ function createSourceProjectsByDtsPath(projects) {
5982
+ return new Map(projects.map((project) => [project.dtsConfigPath, project]));
5983
+ }
5984
+ function createOutputSolutionProject(options) {
5985
+ return {
5986
+ buildConfigPath: getGeneratedOutputSolutionConfigPath({
5987
+ checkerName: options.checkerName,
5988
+ rootDir: options.config.rootDir,
5989
+ sourceConfigPath: options.sourceConfigPath
5990
+ }),
5991
+ checkerName: options.checkerName,
5992
+ configPath: options.sourceConfigPath,
5993
+ references: new Set(options.references)
5994
+ };
5995
+ }
5996
+ function collectFlattenedOutputSolutionReferences(options) {
5997
+ const seenConfigPaths = options.seenConfigPaths ?? /* @__PURE__ */ new Set();
5998
+ if (seenConfigPaths.has(options.sourceConfigPath)) return [];
5999
+ seenConfigPaths.add(options.sourceConfigPath);
6000
+ const references = options.collection.solutionReferencesBySourcePath.get(options.sourceConfigPath) ?? [];
6001
+ const outputReferences = /* @__PURE__ */ new Set();
6002
+ for (const referencePath of references) {
6003
+ const outputProjectModule = options.outputProjectModuleBySourcePath.get(referencePath);
6004
+ if (outputProjectModule) {
6005
+ outputReferences.add(outputProjectModule.path);
6006
+ continue;
6007
+ }
6008
+ for (const nestedReferencePath of collectFlattenedOutputSolutionReferences({
6009
+ collection: options.collection,
6010
+ outputProjectModuleBySourcePath: options.outputProjectModuleBySourcePath,
6011
+ sourceConfigPath: referencePath,
6012
+ seenConfigPaths
6013
+ })) outputReferences.add(nestedReferencePath);
6014
+ }
6015
+ return [...outputReferences].sort((left, right) => left.localeCompare(right));
6016
+ }
6017
+ function formatMissingOutputDependencyProblem(options) {
6018
+ return [
6019
+ "Missing Limina output options for referenced source project:",
6020
+ ` config: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
6021
+ ` referenced config: ${toRelativePath(options.config.rootDir, options.targetProject.configPath)}`,
6022
+ " reason: this output-enabled source project has a Limina-generated project reference to another managed source project that does not declare liminaOptions.outputs.",
6023
+ " fix: add liminaOptions.outputs to the referenced source config, or move the dependency behind a declaration or artifact boundary."
6024
+ ].join("\n");
6025
+ }
6026
+ function createCheckerOutputGraph(options) {
6027
+ if (getCheckerAdapter(options.checker.preset)?.execution !== "build") return {
6028
+ configToOutputBuild: /* @__PURE__ */ new Map(),
6029
+ outputProjects: [],
6030
+ outputSolutions: []
6031
+ };
6032
+ const outputProjects = options.projects.filter((project) => Boolean(project.outputOptions));
6033
+ const outputProjectModuleBySourcePath = new Map(outputProjects.map((project) => [project.configPath, createOutputProjectBuildModule({
6034
+ checkerName: options.checker.name,
6035
+ rootDir: options.config.rootDir,
6036
+ sourceConfigPath: project.configPath
6037
+ })]));
6038
+ const configToOutputBuild = new Map(outputProjectModuleBySourcePath);
6039
+ for (const project of outputProjects) for (const referencePath of project.references) {
6040
+ const targetProject = options.allProjectsByDtsPath.get(referencePath);
6041
+ if (!targetProject) continue;
6042
+ if (!targetProject.outputOptions) {
6043
+ options.problems.push(formatMissingOutputDependencyProblem({
6044
+ config: options.config,
6045
+ project,
6046
+ targetProject
6047
+ }));
6048
+ continue;
6049
+ }
6050
+ project.outputReferences.add(targetProject.outputConfigPath);
6051
+ }
6052
+ const outputSolutions = [];
6053
+ for (const sourceConfigPath of [...options.collection.solutionConfigPaths].sort((left, right) => left.localeCompare(right))) {
6054
+ const references = collectFlattenedOutputSolutionReferences({
6055
+ collection: options.collection,
6056
+ outputProjectModuleBySourcePath,
6057
+ sourceConfigPath
6058
+ });
6059
+ if (references.length === 0) continue;
6060
+ const outputSolution = createOutputSolutionProject({
6061
+ checkerName: options.checker.name,
6062
+ config: options.config,
6063
+ references,
6064
+ sourceConfigPath
6065
+ });
6066
+ outputSolutions.push(outputSolution);
6067
+ configToOutputBuild.set(sourceConfigPath, createOutputSolutionBuildModule({
6068
+ checkerName: options.checker.name,
6069
+ rootDir: options.config.rootDir,
6070
+ sourceConfigPath
6071
+ }));
6072
+ }
6073
+ return {
6074
+ configToOutputBuild,
6075
+ outputProjects,
6076
+ outputSolutions
6077
+ };
6078
+ }
5734
6079
  function createDtsProjectsBySourcePath(projects) {
5735
6080
  const projectsBySourcePath = /* @__PURE__ */ new Map();
5736
6081
  for (const project of projects) projectsBySourcePath.set(project.configPath, [...projectsBySourcePath.get(project.configPath) ?? [], project]);
@@ -5849,6 +6194,17 @@ function formatMissingCrossCheckerProviderProblem(options) {
5849
6194
  " fix: cover the target source config with a build-capable checker preset such as tsc, tsgo, or vue-tsc."
5850
6195
  ].join("\n");
5851
6196
  }
6197
+ function formatOxcOnlyDeclarationProviderProblem(options) {
6198
+ return [
6199
+ "Oxc can resolve this specifier, but TypeScript cannot:",
6200
+ ` importing config: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
6201
+ ` file: ${formatImportRecordLocation(options.config.rootDir, options.importRecord)}`,
6202
+ ` imported specifier: ${options.importRecord.specifier}`,
6203
+ ` Oxc resolved file: ${toRelativePath(options.config.rootDir, options.oxcResolvedFilePath)}`,
6204
+ " reason: generated declaration references follow the checker-aware TypeScript declaration provider, not the Oxc runtime-like resolver.",
6205
+ " fix: check moduleResolution, exports.types/types conditions, paths, customConditions, and package boundaries."
6206
+ ].join("\n");
6207
+ }
5852
6208
  function inferProjectReferences(config, projects, ownerProjects = projects, options = {}) {
5853
6209
  const problems = [];
5854
6210
  const providerEdgesByKey = /* @__PURE__ */ new Map();
@@ -5900,13 +6256,31 @@ function inferProjectReferences(config, projects, ownerProjects = projects, opti
5900
6256
  }
5901
6257
  }
5902
6258
  for (const project of projects) for (const fileName of project.ownedFileNames) for (const importRecord of collectImportsFromFile(fileName, config.rootDir, importAnalysis)) {
5903
- const resolvedFilePath = resolveInternalImport(importRecord.specifier, fileName, project.options, {
5904
- ...project.context,
5905
- configPath: project.configPath,
5906
- resolverConfigPath: project.configPath
5907
- }, importAnalysis);
5908
- if (!resolvedFilePath) continue;
5909
- const owners = ownerLookup.get(resolvedFilePath);
6259
+ const declarationProvider = resolveDeclarationProvider({
6260
+ compilerOptions: project.options,
6261
+ containingFile: fileName,
6262
+ fileOwnerLookup: ownerLookup,
6263
+ importAnalysis,
6264
+ importRecord,
6265
+ project: {
6266
+ ...project.context,
6267
+ configPath: project.configPath,
6268
+ resolverConfigPath: project.configPath
6269
+ }
6270
+ });
6271
+ if (declarationProvider.kind === "declaration") continue;
6272
+ if (declarationProvider.kind === "oxc-only") {
6273
+ problems.push(formatOxcOnlyDeclarationProviderProblem({
6274
+ config,
6275
+ importRecord,
6276
+ oxcResolvedFilePath: declarationProvider.oxcResolvedFilePath,
6277
+ project
6278
+ }));
6279
+ continue;
6280
+ }
6281
+ if (declarationProvider.kind === "unresolved") continue;
6282
+ const resolvedFilePath = declarationProvider.typeScriptResolution.resolvedFileName;
6283
+ const owners = declarationProvider.ownerProjectPaths;
5910
6284
  if (!owners || owners.length === 0) continue;
5911
6285
  const targetSourceConfigPath = owners.filter((owner) => owner !== project.configPath).sort((left, right) => posix.dirname(right).length - posix.dirname(left).length || left.localeCompare(right))[0] ?? null;
5912
6286
  if (!targetSourceConfigPath) continue;
@@ -6044,6 +6418,33 @@ function createGeneratedDtsConfig(config, project) {
6044
6418
  liminaOptions
6045
6419
  };
6046
6420
  }
6421
+ function createGeneratedOutputProjectConfig(config, project) {
6422
+ if (!project.outputOptions) throw new Error(`Missing output options for ${toRelativePath(config.rootDir, project.configPath)}.`);
6423
+ const outputOptions = project.outputOptions;
6424
+ return {
6425
+ $schema: createLiminaTsconfigSchemaPath(config.rootDir, project.outputConfigPath),
6426
+ extends: [createRelativePath(project.outputConfigPath, project.configPath)],
6427
+ files: project.fileNames.map((fileName) => createRelativePath(project.outputConfigPath, fileName)),
6428
+ compilerOptions: {
6429
+ composite: true,
6430
+ incremental: true,
6431
+ noEmit: false,
6432
+ declaration: true,
6433
+ emitDeclarationOnly: false,
6434
+ target: outputOptions.target,
6435
+ rootDir: createRelativePath(project.outputConfigPath, outputOptions.rootDir),
6436
+ outDir: createRelativePath(project.outputConfigPath, outputOptions.outDir),
6437
+ declarationDir: createRelativePath(project.outputConfigPath, outputOptions.outDir),
6438
+ tsBuildInfoFile: createRelativePath(project.outputConfigPath, outputOptions.tsBuildInfoFile)
6439
+ },
6440
+ references: [...project.outputReferences].sort().map((referencePath) => ({ path: createRelativePath(project.outputConfigPath, referencePath) })),
6441
+ liminaOptions: {
6442
+ generated: true,
6443
+ checker: project.checkerName,
6444
+ sourceConfig: createRelativePath(project.outputConfigPath, project.configPath)
6445
+ }
6446
+ };
6447
+ }
6047
6448
  function createGeneratedSolutionBuildConfig(config, solution) {
6048
6449
  return {
6049
6450
  $schema: createLiminaTsconfigSchemaPath(config.rootDir, solution.buildConfigPath),
@@ -6056,6 +6457,18 @@ function createGeneratedSolutionBuildConfig(config, solution) {
6056
6457
  }
6057
6458
  };
6058
6459
  }
6460
+ function createGeneratedOutputSolutionConfig(config, solution) {
6461
+ return {
6462
+ $schema: createLiminaTsconfigSchemaPath(config.rootDir, solution.buildConfigPath),
6463
+ files: [],
6464
+ references: [...solution.references].sort().map((referencePath) => ({ path: createRelativePath(solution.buildConfigPath, referencePath) })),
6465
+ liminaOptions: {
6466
+ generated: true,
6467
+ checker: solution.checkerName,
6468
+ sourceConfig: createRelativePath(solution.buildConfigPath, solution.configPath)
6469
+ }
6470
+ };
6471
+ }
6059
6472
  function createCheckerBuildConfig(options) {
6060
6473
  return {
6061
6474
  $schema: createLiminaTsconfigSchemaPath(options.rootDir, options.entryPath),
@@ -6098,11 +6511,20 @@ function createManifest(options) {
6098
6511
  for (const checker of options.checkers) {
6099
6512
  const projects = options.projectsByChecker.get(checker.name) ?? [];
6100
6513
  const entryPath = options.checkerEntries.get(checker.name);
6514
+ const configToOutputBuildByConfigPath = options.configToOutputBuildByChecker.get(checker.name) ?? /* @__PURE__ */ new Map();
6101
6515
  const sourceToBuildBySourcePath = options.sourceToBuildByChecker.get(checker.name) ?? /* @__PURE__ */ new Map();
6102
6516
  if (!entryPath) continue;
6517
+ const configToOutputBuild = {};
6103
6518
  const sourceToBuild = {};
6104
6519
  const sourceToDts = {};
6105
6520
  const dtsToSource = {};
6521
+ for (const [sourceConfigPath, outputModule] of configToOutputBuildByConfigPath) {
6522
+ const sourcePath = toPosixPath(toRelativePath(options.rootDir, sourceConfigPath));
6523
+ configToOutputBuild[sourcePath] = {
6524
+ kind: outputModule.kind,
6525
+ path: toPosixPath(toRelativePath(options.rootDir, outputModule.path))
6526
+ };
6527
+ }
6106
6528
  for (const [sourceConfigPath, buildModule] of sourceToBuildBySourcePath) {
6107
6529
  const sourcePath = toPosixPath(toRelativePath(options.rootDir, sourceConfigPath));
6108
6530
  sourceToBuild[sourcePath] = {
@@ -6117,6 +6539,7 @@ function createManifest(options) {
6117
6539
  dtsToSource[dtsPath] = sourcePath;
6118
6540
  }
6119
6541
  manifestCheckers[checker.name] = {
6542
+ configToOutputBuild: Object.fromEntries(Object.entries(configToOutputBuild).sort(([left], [right]) => left.localeCompare(right))),
6120
6543
  preset: checker.preset,
6121
6544
  entry: toPosixPath(toRelativePath(options.rootDir, entryPath)),
6122
6545
  roots: projects.map((project) => toPosixPath(toRelativePath(options.rootDir, project.configPath))).sort(),
@@ -6126,7 +6549,7 @@ function createManifest(options) {
6126
6549
  };
6127
6550
  }
6128
6551
  return {
6129
- version: 1,
6552
+ version: 2,
6130
6553
  generatedBy: "limina",
6131
6554
  checkers: manifestCheckers,
6132
6555
  knip: {
@@ -6146,6 +6569,7 @@ function createManifest(options) {
6146
6569
  }
6147
6570
  function createResult(options) {
6148
6571
  const checkerEntries = /* @__PURE__ */ new Map();
6572
+ const configToOutputBuild = /* @__PURE__ */ new Map();
6149
6573
  const sourceToBuild = /* @__PURE__ */ new Map();
6150
6574
  const sourceToDts = /* @__PURE__ */ new Map();
6151
6575
  const dtsToSource = /* @__PURE__ */ new Map();
@@ -6160,6 +6584,10 @@ function createResult(options) {
6160
6584
  }));
6161
6585
  for (const [checkerName, checkerManifest] of Object.entries(options.manifest.checkers)) {
6162
6586
  checkerEntries.set(checkerName, normalizeAbsolutePath(posix.join(options.rootDir, checkerManifest.entry)));
6587
+ configToOutputBuild.set(checkerName, new Map(Object.entries(checkerManifest.configToOutputBuild ?? {}).map(([sourcePath, buildModule]) => [normalizeAbsolutePath(posix.join(options.rootDir, sourcePath)), {
6588
+ kind: buildModule.kind,
6589
+ path: normalizeAbsolutePath(posix.join(options.rootDir, buildModule.path))
6590
+ }])));
6163
6591
  sourceToBuild.set(checkerName, new Map(Object.entries(checkerManifest.sourceToBuild ?? {}).map(([sourcePath, buildModule]) => [normalizeAbsolutePath(posix.join(options.rootDir, sourcePath)), {
6164
6592
  kind: buildModule.kind,
6165
6593
  path: normalizeAbsolutePath(posix.join(options.rootDir, buildModule.path))
@@ -6172,6 +6600,7 @@ function createResult(options) {
6172
6600
  checkers: options.checkers,
6173
6601
  manifestPath: options.manifestPath,
6174
6602
  checkerEntries,
6603
+ configToOutputBuild,
6175
6604
  sourceToBuild,
6176
6605
  sourceToDts,
6177
6606
  dtsToSource,
@@ -6193,6 +6622,9 @@ function collectGeneratedSourceConfigPaths(generatedGraph) {
6193
6622
  async function prepareGeneratedTsconfigGraph(config, options = {}) {
6194
6623
  const checkers = await resolveGeneratedGraphCheckers(config, options);
6195
6624
  const checkerCollectionsByName = /* @__PURE__ */ new Map();
6625
+ const configToOutputBuildByChecker = /* @__PURE__ */ new Map();
6626
+ const outputProjectsByChecker = /* @__PURE__ */ new Map();
6627
+ const outputSolutionsByChecker = /* @__PURE__ */ new Map();
6196
6628
  const projectsByChecker = /* @__PURE__ */ new Map();
6197
6629
  const rootBuildPathsByChecker = /* @__PURE__ */ new Map();
6198
6630
  const solutionsByChecker = /* @__PURE__ */ new Map();
@@ -6269,6 +6701,27 @@ async function prepareGeneratedTsconfigGraph(config, options = {}) {
6269
6701
  projects: allProjects,
6270
6702
  providerEdges
6271
6703
  });
6704
+ const allProjectsByDtsPath = createSourceProjectsByDtsPath(allProjects);
6705
+ for (const checker of checkers) {
6706
+ const outputGraph = createCheckerOutputGraph({
6707
+ allProjectsByDtsPath,
6708
+ checker,
6709
+ collection: checkerCollectionsByName.get(checker.name) ?? {
6710
+ buildModulesBySourcePath: /* @__PURE__ */ new Map(),
6711
+ entryConfigPaths: /* @__PURE__ */ new Set(),
6712
+ projectConfigPaths: /* @__PURE__ */ new Set(),
6713
+ rootConfigPaths: [],
6714
+ solutionConfigPaths: /* @__PURE__ */ new Set(),
6715
+ solutionReferencesBySourcePath: /* @__PURE__ */ new Map()
6716
+ },
6717
+ config,
6718
+ problems,
6719
+ projects: projectsByChecker.get(checker.name) ?? []
6720
+ });
6721
+ configToOutputBuildByChecker.set(checker.name, outputGraph.configToOutputBuild);
6722
+ outputProjectsByChecker.set(checker.name, outputGraph.outputProjects);
6723
+ outputSolutionsByChecker.set(checker.name, outputGraph.outputSolutions);
6724
+ }
6272
6725
  providerEdges.sort((left, right) => left.fromChecker.localeCompare(right.fromChecker) || left.fromConfigPath.localeCompare(right.fromConfigPath) || left.toChecker.localeCompare(right.toChecker) || left.toConfigPath.localeCompare(right.toConfigPath) || left.file.localeCompare(right.file) || left.importedSpecifier.localeCompare(right.importedSpecifier));
6273
6726
  if (problems.length > 0) throw createGeneratedGraphStructuredError({
6274
6727
  config,
@@ -6276,20 +6729,25 @@ async function prepareGeneratedTsconfigGraph(config, options = {}) {
6276
6729
  problems
6277
6730
  });
6278
6731
  const generatedKnip = prepareGeneratedKnipPackageConfigs({
6732
+ checkers,
6733
+ configToOutputBuildByChecker,
6279
6734
  config,
6280
- sourceToBuildByChecker,
6281
6735
  workspacePackages: options.workspacePackagesProvider ? await options.workspacePackagesProvider() : await collectWorkspacePackages(config)
6282
6736
  });
6283
6737
  await Promise.all(checkers.map(async (checker) => {
6284
6738
  const checkerName = checker.name;
6285
6739
  const projects = projectsByChecker.get(checkerName) ?? [];
6286
6740
  const entryPath = checkerEntries.get(checkerName);
6741
+ const outputProjects = outputProjectsByChecker.get(checkerName) ?? [];
6742
+ const outputSolutions = outputSolutionsByChecker.get(checkerName) ?? [];
6287
6743
  const solutions = solutionsByChecker.get(checkerName) ?? [];
6288
6744
  const rootBuildPaths = rootBuildPathsByChecker.get(checkerName) ?? [];
6289
6745
  if (!entryPath) return;
6290
6746
  await Promise.all([
6291
6747
  ...projects.map((project) => writeGeneratedJson(writeContext, project.dtsConfigPath, createGeneratedDtsConfig(config, project))),
6292
6748
  ...solutions.map((solution) => writeGeneratedJson(writeContext, solution.buildConfigPath, createGeneratedSolutionBuildConfig(config, solution))),
6749
+ ...outputProjects.map((project) => writeGeneratedJson(writeContext, project.outputConfigPath, createGeneratedOutputProjectConfig(config, project))),
6750
+ ...outputSolutions.map((solution) => writeGeneratedJson(writeContext, solution.buildConfigPath, createGeneratedOutputSolutionConfig(config, solution))),
6293
6751
  writeGeneratedJson(writeContext, entryPath, createCheckerBuildConfig({
6294
6752
  checkerName,
6295
6753
  entryPath,
@@ -6302,6 +6760,7 @@ async function prepareGeneratedTsconfigGraph(config, options = {}) {
6302
6760
  const manifest = createManifest({
6303
6761
  checkerEntries,
6304
6762
  checkers,
6763
+ configToOutputBuildByChecker,
6305
6764
  generatedKnipDiagnostics: generatedKnip.diagnostics,
6306
6765
  generatedKnipPackageConfigs: generatedKnip.configs.map((entry) => entry.config),
6307
6766
  projectsByChecker,
@@ -7557,17 +8016,17 @@ function createProjectResolveContext(project) {
7557
8016
  function findOwnerForFile(filePath, owners) {
7558
8017
  return owners.filter((owner) => isPathInsideDirectory(filePath, owner.directory)).sort((left, right) => right.directory.length - left.directory.length)[0] ?? null;
7559
8018
  }
7560
- function getManifestPackageName(manifest) {
8019
+ function getManifestPackageName$1(manifest) {
7561
8020
  return typeof manifest.name === "string" && manifest.name.trim().length > 0 ? manifest.name.trim() : void 0;
7562
8021
  }
7563
- function isNodeModulesPackageRoot(directory) {
8022
+ function isNodeModulesPackageRoot$1(directory) {
7564
8023
  const parentDirectory = posix.dirname(directory);
7565
8024
  const parentName = posix.basename(parentDirectory);
7566
8025
  if (parentName === "node_modules") return true;
7567
8026
  if (parentName.startsWith("@")) return posix.basename(posix.dirname(parentDirectory)) === "node_modules";
7568
8027
  return false;
7569
8028
  }
7570
- function isPackageInfoInsideNodeModules(packageInfo) {
8029
+ function isPackageInfoInsideNodeModules$1(packageInfo) {
7571
8030
  return normalizeAbsolutePath(packageInfo.directory).split("/").includes("node_modules");
7572
8031
  }
7573
8032
  function readPackageInfo(packageJsonPath) {
@@ -7577,21 +8036,10 @@ function readPackageInfo(packageJsonPath) {
7577
8036
  return {
7578
8037
  directory,
7579
8038
  manifest,
7580
- name: getManifestPackageName(manifest),
8039
+ name: getManifestPackageName$1(manifest),
7581
8040
  packageJsonPath: normalizedPackageJsonPath
7582
8041
  };
7583
8042
  }
7584
- function findNearestPackageScopeInfo(filePath) {
7585
- const normalizedFilePath = normalizeAbsolutePath(filePath);
7586
- let currentDir = normalizeAbsolutePath(posix.dirname(normalizedFilePath));
7587
- while (true) {
7588
- const packageJsonPath = normalizeAbsolutePath(posix.join(currentDir, "package.json"));
7589
- if (existsSync(packageJsonPath)) return readPackageInfo(packageJsonPath);
7590
- const parentDir = posix.dirname(currentDir);
7591
- if (parentDir === currentDir) return null;
7592
- currentDir = parentDir;
7593
- }
7594
- }
7595
8043
  function findNearestPackageInfo(filePath) {
7596
8044
  const normalizedFilePath = normalizeAbsolutePath(filePath);
7597
8045
  let currentDir = normalizeAbsolutePath(posix.dirname(normalizedFilePath));
@@ -7599,7 +8047,7 @@ function findNearestPackageInfo(filePath) {
7599
8047
  const packageJsonPath = normalizeAbsolutePath(posix.join(currentDir, "package.json"));
7600
8048
  if (existsSync(packageJsonPath)) {
7601
8049
  const packageInfo = readPackageInfo(packageJsonPath);
7602
- if (packageInfo.name || isNodeModulesPackageRoot(currentDir)) return packageInfo;
8050
+ if (packageInfo.name || isNodeModulesPackageRoot$1(currentDir)) return packageInfo;
7603
8051
  }
7604
8052
  const parentDir = posix.dirname(currentDir);
7605
8053
  if (parentDir === currentDir) return null;
@@ -7626,7 +8074,7 @@ function classifyResolvedPackageTarget(options) {
7626
8074
  };
7627
8075
  return { kind: "unowned" };
7628
8076
  }
7629
- if (targetOwner?.packageJsonPath === options.owner.packageJsonPath && !isPackageInfoInsideNodeModules(packageInfo)) return {
8077
+ if (targetOwner?.packageJsonPath === options.owner.packageJsonPath && !isPackageInfoInsideNodeModules$1(packageInfo)) return {
7630
8078
  kind: "current-owner",
7631
8079
  packageInfo
7632
8080
  };
@@ -7872,15 +8320,55 @@ function isDependencyAuthorized(manifest, packageName) {
7872
8320
  function findPackageImportMatch(importsField, specifier) {
7873
8321
  if (!importsField || typeof importsField !== "object") return null;
7874
8322
  for (const key of Object.keys(importsField)) {
7875
- if (key === specifier) return { key };
8323
+ const value = importsField[key];
8324
+ if (key === specifier) return {
8325
+ key,
8326
+ targetKind: classifyPackageImportTarget(value),
8327
+ value
8328
+ };
7876
8329
  const wildcardIndex = key.indexOf("*");
7877
8330
  if (wildcardIndex === -1) continue;
7878
8331
  const prefix = key.slice(0, wildcardIndex);
7879
8332
  const suffix = key.slice(wildcardIndex + 1);
7880
- if (specifier.startsWith(prefix) && specifier.endsWith(suffix)) return { key };
8333
+ if (specifier.startsWith(prefix) && specifier.endsWith(suffix)) return {
8334
+ key,
8335
+ targetKind: classifyPackageImportTarget(value),
8336
+ value
8337
+ };
7881
8338
  }
7882
8339
  return null;
7883
8340
  }
8341
+ function classifyPackageImportTarget(value) {
8342
+ const kinds = /* @__PURE__ */ new Set();
8343
+ collectPackageImportTargetKinds(value, kinds);
8344
+ if (kinds.size === 0) return "unknown";
8345
+ if (kinds.size === 1) return kinds.values().next().value ?? "unknown";
8346
+ return "mixed";
8347
+ }
8348
+ function collectPackageImportTargetKinds(value, kinds) {
8349
+ if (typeof value === "string") {
8350
+ const target = value.trim();
8351
+ if (isRelativeSpecifier(target)) {
8352
+ kinds.add("relative");
8353
+ return;
8354
+ }
8355
+ if (isBarePackageSpecifier(target)) {
8356
+ kinds.add("package");
8357
+ return;
8358
+ }
8359
+ kinds.add("unknown");
8360
+ return;
8361
+ }
8362
+ if (Array.isArray(value)) {
8363
+ for (const item of value) collectPackageImportTargetKinds(item, kinds);
8364
+ return;
8365
+ }
8366
+ if (isPlainRecord(value)) {
8367
+ for (const item of Object.values(value)) collectPackageImportTargetKinds(item, kinds);
8368
+ return;
8369
+ }
8370
+ if (value !== null && value !== void 0) kinds.add("unknown");
8371
+ }
7884
8372
 
7885
8373
  //#endregion
7886
8374
  //#region src/core/workspace.ts
@@ -8062,7 +8550,7 @@ function sortEdges(edges) {
8062
8550
  function resolveTargetPackage(options) {
8063
8551
  return options.declaredTargetPackage ?? (options.graphResolvedFilePath ? findPackageForFile(options.graphResolvedFilePath, options.packages) : null) ?? findPackageForFile(options.resolvedFilePath, options.packages);
8064
8552
  }
8065
- function shouldUseWorkspaceExportResolution$1(options) {
8553
+ function shouldUseWorkspaceExportResolution(options) {
8066
8554
  if (!options.declaredTargetPackage) return false;
8067
8555
  return !options.resolvedFilePath;
8068
8556
  }
@@ -8107,7 +8595,7 @@ async function collectDependencyGraph(config, options = {}) {
8107
8595
  const declaredTargetPackage = findPackageForSpecifier(importRecord.specifier, workspacePackages);
8108
8596
  const workspaceExportResolution = declaredTargetPackage && workspaceExports.hasExports(declaredTargetPackage.name) ? workspaceExports.get(project.configPath, importRecord.specifier) : null;
8109
8597
  const internalResolvedFilePath = resolveInternalImport(importRecord.specifier, fileName, project.options, project, importAnalysis);
8110
- const useWorkspaceExportResolution = shouldUseWorkspaceExportResolution$1({
8598
+ const useWorkspaceExportResolution = shouldUseWorkspaceExportResolution({
8111
8599
  declaredTargetPackage,
8112
8600
  resolvedFilePath: internalResolvedFilePath
8113
8601
  });
@@ -9109,6 +9597,202 @@ function clearCliScreen() {
9109
9597
  readline.clearScreenDown(process.stdout);
9110
9598
  }
9111
9599
 
9600
+ //#endregion
9601
+ //#region src/core/workspace/lookup.ts
9602
+ var WorkspaceLookupIndex = class {
9603
+ rootDir;
9604
+ #importers;
9605
+ #namedPackagesByName = /* @__PURE__ */ new Map();
9606
+ #ownersByDirectory;
9607
+ #packageInfoByPackageJsonPath = /* @__PURE__ */ new Map();
9608
+ #packagesByDirectory;
9609
+ #packagesByPackageJsonPath = /* @__PURE__ */ new Map();
9610
+ #importerByFilePath = /* @__PURE__ */ new Map();
9611
+ #nearestNamedPackageInfoByDirectory = /* @__PURE__ */ new Map();
9612
+ #nearestPackageScopeInfoByDirectory = /* @__PURE__ */ new Map();
9613
+ #ownerByFilePath = /* @__PURE__ */ new Map();
9614
+ #packageByFilePath = /* @__PURE__ */ new Map();
9615
+ #resolvedPackageTargetByKey = /* @__PURE__ */ new Map();
9616
+ constructor(options) {
9617
+ this.rootDir = normalizeAbsolutePath(options.rootDir);
9618
+ this.#importers = options.importers.map((importer) => ({
9619
+ directory: normalizeAbsolutePath(importer.directory),
9620
+ importer
9621
+ }));
9622
+ this.#ownersByDirectory = createDirectoryMap(options.owners);
9623
+ this.#packagesByDirectory = createDirectoryMap(options.packages);
9624
+ for (const workspacePackage of options.packages) {
9625
+ const packageJsonPath = normalizeAbsolutePath(posix.join(workspacePackage.directory, "package.json"));
9626
+ this.#packagesByPackageJsonPath.set(packageJsonPath, workspacePackage);
9627
+ if (isNamedWorkspacePackage(workspacePackage) && !this.#namedPackagesByName.has(workspacePackage.name)) this.#namedPackagesByName.set(workspacePackage.name, workspacePackage);
9628
+ }
9629
+ }
9630
+ findPackageForFile(filePath) {
9631
+ const normalizedFilePath = normalizeAbsolutePath(filePath);
9632
+ const cached = this.#packageByFilePath.get(normalizedFilePath);
9633
+ if (cached !== void 0) return cached;
9634
+ const workspacePackage = findNearestDirectoryItem(normalizedFilePath, this.#packagesByDirectory);
9635
+ this.#packageByFilePath.set(normalizedFilePath, workspacePackage);
9636
+ return workspacePackage;
9637
+ }
9638
+ findOwnerForFile(filePath) {
9639
+ const normalizedFilePath = normalizeAbsolutePath(filePath);
9640
+ const cached = this.#ownerByFilePath.get(normalizedFilePath);
9641
+ if (cached !== void 0) return cached;
9642
+ const owner = findNearestDirectoryItem(normalizedFilePath, this.#ownersByDirectory);
9643
+ this.#ownerByFilePath.set(normalizedFilePath, owner);
9644
+ return owner;
9645
+ }
9646
+ findImporterForFile(filePath) {
9647
+ const normalizedFilePath = normalizeAbsolutePath(filePath);
9648
+ const cached = this.#importerByFilePath.get(normalizedFilePath);
9649
+ if (cached !== void 0) return cached;
9650
+ const importer = this.#importers.find((candidate) => isPathInsideNormalizedDirectory(normalizedFilePath, candidate.directory))?.importer ?? null;
9651
+ this.#importerByFilePath.set(normalizedFilePath, importer);
9652
+ return importer;
9653
+ }
9654
+ findNearestPackageScopeInfo(filePath) {
9655
+ const directory = normalizeAbsolutePath(posix.dirname(normalizeAbsolutePath(filePath)));
9656
+ return this.#findNearestPackageInfoFromDirectory(directory, { requireNamedPackageOrNodeModulesPackage: false });
9657
+ }
9658
+ findPackageForSpecifier(specifier) {
9659
+ return this.#namedPackagesByName.get(getPackageRootSpecifier(specifier)) ?? null;
9660
+ }
9661
+ classifyResolvedPackageTarget(options) {
9662
+ const normalizedResolvedFilePath = normalizeAbsolutePath(options.resolvedFilePath);
9663
+ const cacheKey = `${options.owner.packageJsonPath}\0${normalizedResolvedFilePath}`;
9664
+ const cached = this.#resolvedPackageTargetByKey.get(cacheKey);
9665
+ if (cached) return cached;
9666
+ const packageInfo = this.#findNearestPackageInfoFromDirectory(normalizeAbsolutePath(posix.dirname(normalizedResolvedFilePath)), { requireNamedPackageOrNodeModulesPackage: true });
9667
+ const targetOwner = this.findOwnerForFile(normalizedResolvedFilePath);
9668
+ const target = this.#classifyResolvedPackageTarget({
9669
+ owner: options.owner,
9670
+ packageInfo,
9671
+ targetOwner
9672
+ });
9673
+ this.#resolvedPackageTargetByKey.set(cacheKey, target);
9674
+ return target;
9675
+ }
9676
+ #classifyResolvedPackageTarget(options) {
9677
+ if (!options.packageInfo) {
9678
+ if (options.targetOwner?.packageJsonPath === options.owner.packageJsonPath) return {
9679
+ kind: "current-owner",
9680
+ packageInfo: {
9681
+ directory: options.owner.directory,
9682
+ manifest: options.owner.manifest,
9683
+ ...options.owner.name ? { name: options.owner.name } : {},
9684
+ packageJsonPath: options.owner.packageJsonPath
9685
+ }
9686
+ };
9687
+ return { kind: "unowned" };
9688
+ }
9689
+ if (options.targetOwner?.packageJsonPath === options.owner.packageJsonPath && !isPackageInfoInsideNodeModules(options.packageInfo)) return {
9690
+ kind: "current-owner",
9691
+ packageInfo: options.packageInfo
9692
+ };
9693
+ if (options.targetOwner && options.targetOwner.packageJsonPath !== options.owner.packageJsonPath) return {
9694
+ kind: "other-owner",
9695
+ packageInfo: options.packageInfo,
9696
+ targetOwner: options.targetOwner,
9697
+ workspacePackage: this.#findWorkspacePackageForPackageInfo(options.packageInfo)
9698
+ };
9699
+ return {
9700
+ kind: "artifact-package",
9701
+ packageInfo: options.packageInfo
9702
+ };
9703
+ }
9704
+ #findNearestPackageInfoFromDirectory(directory, options) {
9705
+ const cache = options.requireNamedPackageOrNodeModulesPackage ? this.#nearestNamedPackageInfoByDirectory : this.#nearestPackageScopeInfoByDirectory;
9706
+ const visitedDirectories = [];
9707
+ let currentDir = normalizeAbsolutePath(directory);
9708
+ while (true) {
9709
+ const cached = cache.get(currentDir);
9710
+ if (cached !== void 0) {
9711
+ setCachedPackageInfo(cache, visitedDirectories, cached);
9712
+ return cached;
9713
+ }
9714
+ visitedDirectories.push(currentDir);
9715
+ const packageJsonPath = normalizeAbsolutePath(posix.join(currentDir, "package.json"));
9716
+ if (existsSync(packageJsonPath)) {
9717
+ const packageInfo = this.#readPackageInfo(packageJsonPath);
9718
+ if (!options.requireNamedPackageOrNodeModulesPackage || packageInfo.name || isNodeModulesPackageRoot(currentDir)) {
9719
+ setCachedPackageInfo(cache, visitedDirectories, packageInfo);
9720
+ return packageInfo;
9721
+ }
9722
+ }
9723
+ const parentDir = posix.dirname(currentDir);
9724
+ if (parentDir === currentDir) {
9725
+ setCachedPackageInfo(cache, visitedDirectories, null);
9726
+ return null;
9727
+ }
9728
+ currentDir = parentDir;
9729
+ }
9730
+ }
9731
+ #findWorkspacePackageForPackageInfo(packageInfo) {
9732
+ return this.#packagesByPackageJsonPath.get(packageInfo.packageJsonPath) ?? (packageInfo.name ? this.#namedPackagesByName.get(packageInfo.name) : void 0) ?? null;
9733
+ }
9734
+ #readPackageInfo(packageJsonPath) {
9735
+ const normalizedPackageJsonPath = normalizeAbsolutePath(packageJsonPath);
9736
+ const cached = this.#packageInfoByPackageJsonPath.get(normalizedPackageJsonPath);
9737
+ if (cached) return cached;
9738
+ const directory = normalizeAbsolutePath(posix.dirname(packageJsonPath));
9739
+ const manifest = readJsonFile(normalizedPackageJsonPath);
9740
+ const name = getManifestPackageName(manifest);
9741
+ const packageInfo = {
9742
+ directory,
9743
+ manifest,
9744
+ ...name ? { name } : {},
9745
+ packageJsonPath: normalizedPackageJsonPath
9746
+ };
9747
+ this.#packageInfoByPackageJsonPath.set(normalizedPackageJsonPath, packageInfo);
9748
+ return packageInfo;
9749
+ }
9750
+ };
9751
+ function createWorkspaceLookupIndex(options) {
9752
+ return new WorkspaceLookupIndex(options);
9753
+ }
9754
+ function createDirectoryMap(items) {
9755
+ const itemsByDirectory = /* @__PURE__ */ new Map();
9756
+ for (const item of items) {
9757
+ const directory = normalizeAbsolutePath(item.directory);
9758
+ if (!itemsByDirectory.has(directory)) itemsByDirectory.set(directory, item);
9759
+ }
9760
+ return itemsByDirectory;
9761
+ }
9762
+ function findNearestDirectoryItem(filePath, itemsByDirectory) {
9763
+ let currentDir = normalizeAbsolutePath(filePath);
9764
+ const exactMatch = itemsByDirectory.get(currentDir);
9765
+ if (exactMatch) return exactMatch;
9766
+ currentDir = normalizeAbsolutePath(posix.dirname(currentDir));
9767
+ while (true) {
9768
+ const item = itemsByDirectory.get(currentDir);
9769
+ if (item) return item;
9770
+ const parentDir = posix.dirname(currentDir);
9771
+ if (parentDir === currentDir) return null;
9772
+ currentDir = parentDir;
9773
+ }
9774
+ }
9775
+ function getManifestPackageName(manifest) {
9776
+ return typeof manifest.name === "string" && manifest.name.trim().length > 0 ? manifest.name.trim() : void 0;
9777
+ }
9778
+ function isNodeModulesPackageRoot(directory) {
9779
+ const parentDirectory = posix.dirname(directory);
9780
+ const parentName = posix.basename(parentDirectory);
9781
+ if (parentName === "node_modules") return true;
9782
+ if (parentName.startsWith("@")) return posix.basename(posix.dirname(parentDirectory)) === "node_modules";
9783
+ return false;
9784
+ }
9785
+ function isPackageInfoInsideNodeModules(packageInfo) {
9786
+ return normalizeAbsolutePath(packageInfo.directory).split("/").includes("node_modules");
9787
+ }
9788
+ function isPathInsideNormalizedDirectory(normalizedFilePath, normalizedDirectoryPath) {
9789
+ const directoryPrefix = normalizedDirectoryPath.endsWith("/") ? normalizedDirectoryPath : `${normalizedDirectoryPath}/`;
9790
+ return normalizedFilePath === normalizedDirectoryPath || normalizedFilePath.startsWith(directoryPrefix);
9791
+ }
9792
+ function setCachedPackageInfo(cache, directories, packageInfo) {
9793
+ for (const directory of directories) cache.set(directory, packageInfo);
9794
+ }
9795
+
9112
9796
  //#endregion
9113
9797
  //#region src/package-check/entry-selection.ts
9114
9798
  const DEFAULT_PACKAGE_CHECKS = [
@@ -9585,6 +10269,14 @@ var LiminaPreflightManager = class {
9585
10269
  ensureImporters() {
9586
10270
  return this.#ensure("importers", () => this.core.workspace.getImporters());
9587
10271
  }
10272
+ ensureWorkspaceLookupIndex() {
10273
+ return this.#ensure("workspaceLookupIndex", async () => createWorkspaceLookupIndex({
10274
+ importers: await this.ensureImporters(),
10275
+ owners: await this.ensurePackageOwners(),
10276
+ packages: await this.ensureWorkspacePackages(),
10277
+ rootDir: this.config.rootDir
10278
+ }));
10279
+ }
9588
10280
  ensureWorkspaceDependencyDeclarations() {
9589
10281
  return this.#ensure("workspaceDependencyDeclarations", () => this.core.workspace.getWorkspaceDependencyDeclarations());
9590
10282
  }
@@ -10318,6 +11010,8 @@ const GRAPH_CHECK_ITEM_NAMES = [
10318
11010
  "condition domains",
10319
11011
  "reference completeness"
10320
11012
  ];
11013
+ const GENERATED_REFERENCE_CYCLE_REASON = "Generated declaration project references must be acyclic so build-mode checkers can order declaration builds.";
11014
+ const GENERATED_REFERENCE_CYCLE_FIX = "Break the cycle by merging tightly coupled source scopes, extracting shared contracts, moving runtime wiring to a higher-level entry, or using an intentional declaration boundary.";
10321
11015
  function getGeneratedCheckerNamespace(configPath) {
10322
11016
  const markerIndex = configPath.indexOf("/.limina/tsconfig/checkers/");
10323
11017
  if (markerIndex === -1) return null;
@@ -10389,7 +11083,7 @@ function addDeniedReferenceProblems(options) {
10389
11083
  options.checks.add();
10390
11084
  if (!options.projectsByPath.has(referencePath)) continue;
10391
11085
  const deniedRefRule = getDeniedRefRule(options.rules, options.project.labels, referencePath);
10392
- const targetPackage = findPackageForFile(referencePath, options.packages);
11086
+ const targetPackage = options.workspaceLookup.findPackageForFile(referencePath);
10393
11087
  const deniedDepRule = targetPackage?.name ? getDeniedDepRuleForPackage(options.rules, options.project.labels, targetPackage.name) : null;
10394
11088
  if (!deniedRefRule && !deniedDepRule) continue;
10395
11089
  const lines = [
@@ -10456,8 +11150,8 @@ function getNodeModulesPackageName(filePath) {
10456
11150
  }
10457
11151
  return packageName;
10458
11152
  }
10459
- function getResolvedPackageName(filePath, packages) {
10460
- return getNodeModulesPackageName(filePath) ?? findPackageForFile(filePath, packages)?.name ?? null;
11153
+ function getResolvedPackageName(filePath, workspaceLookup) {
11154
+ return getNodeModulesPackageName(filePath) ?? workspaceLookup.findPackageForFile(filePath)?.name ?? null;
10461
11155
  }
10462
11156
  function addNamelessWorkspaceReferenceProblem(options) {
10463
11157
  const packageManifestPath = posix.join(options.workspacePackage.directory, "package.json");
@@ -10478,23 +11172,19 @@ function addNamelessWorkspaceReferenceProblem(options) {
10478
11172
  title: "Project reference crosses workspace package without package identity"
10479
11173
  });
10480
11174
  }
10481
- function getResolvedWorkspacePackage(filePath, packages) {
11175
+ function getResolvedWorkspacePackage(filePath, workspaceLookup) {
10482
11176
  if (getNodeModulesPackageName(filePath)) return null;
10483
- return findPackageForFile(filePath, packages);
11177
+ return workspaceLookup.findPackageForFile(filePath);
10484
11178
  }
10485
- function shouldUseWorkspaceExportResolution(options) {
10486
- if (!options.targetPackage) return false;
10487
- return !options.resolvedFilePath;
10488
- }
10489
- function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems, checks) {
11179
+ function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, workspaceLookup, problems, checks) {
10490
11180
  if (!isDtsProjectConfig(project.configPath)) return;
10491
- const sourcePackage = findPackageForFile(project.configPath, packages);
10492
- const importer = sourcePackage ? findImporterForFile(project.configPath, importers) : null;
11181
+ const sourcePackage = workspaceLookup.findPackageForFile(project.configPath);
11182
+ const importer = sourcePackage ? workspaceLookup.findImporterForFile(project.configPath) : null;
10493
11183
  if (!sourcePackage) return;
10494
11184
  for (const referencePath of project.references) {
10495
11185
  checks.add();
10496
11186
  if (!projectsByPath.has(referencePath)) continue;
10497
- const targetPackage = findPackageForFile(referencePath, packages);
11187
+ const targetPackage = workspaceLookup.findPackageForFile(referencePath);
10498
11188
  if (!targetPackage || targetPackage.directory === sourcePackage.directory) continue;
10499
11189
  if (!isNamedWorkspacePackage(sourcePackage)) {
10500
11190
  addNamelessWorkspaceReferenceProblem({
@@ -10540,6 +11230,80 @@ function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath
10540
11230
  });
10541
11231
  }
10542
11232
  }
11233
+ function createGeneratedReferenceGraph(projects) {
11234
+ const dtsProjects = projects.filter((project) => isDtsProjectConfig(project.configPath));
11235
+ const dtsProjectPaths = new Set(dtsProjects.map((project) => project.configPath));
11236
+ const graph = /* @__PURE__ */ new Map();
11237
+ for (const project of dtsProjects) graph.set(project.configPath, new Set([...project.references].filter((referencePath) => dtsProjectPaths.has(referencePath)).sort()));
11238
+ return graph;
11239
+ }
11240
+ function collectGeneratedReferenceComponents(graph) {
11241
+ const components = [];
11242
+ const indexByPath = /* @__PURE__ */ new Map();
11243
+ const lowLinkByPath = /* @__PURE__ */ new Map();
11244
+ const stack = [];
11245
+ const pathsOnStack = /* @__PURE__ */ new Set();
11246
+ let nextIndex = 0;
11247
+ function visit(configPath) {
11248
+ indexByPath.set(configPath, nextIndex);
11249
+ lowLinkByPath.set(configPath, nextIndex);
11250
+ nextIndex += 1;
11251
+ stack.push(configPath);
11252
+ pathsOnStack.add(configPath);
11253
+ for (const referencePath of graph.get(configPath) ?? []) {
11254
+ if (!indexByPath.has(referencePath)) {
11255
+ visit(referencePath);
11256
+ lowLinkByPath.set(configPath, Math.min(lowLinkByPath.get(configPath), lowLinkByPath.get(referencePath)));
11257
+ continue;
11258
+ }
11259
+ if (pathsOnStack.has(referencePath)) lowLinkByPath.set(configPath, Math.min(lowLinkByPath.get(configPath), indexByPath.get(referencePath)));
11260
+ }
11261
+ if (lowLinkByPath.get(configPath) !== indexByPath.get(configPath)) return;
11262
+ const component = [];
11263
+ while (stack.length > 0) {
11264
+ const currentPath = stack.pop();
11265
+ pathsOnStack.delete(currentPath);
11266
+ component.push(currentPath);
11267
+ if (currentPath === configPath) break;
11268
+ }
11269
+ components.push(component.sort());
11270
+ }
11271
+ for (const configPath of [...graph.keys()].sort()) if (!indexByPath.has(configPath)) visit(configPath);
11272
+ return components.sort((left, right) => left[0].localeCompare(right[0]));
11273
+ }
11274
+ function getGeneratedReferenceCycleEdges(graph, members) {
11275
+ const memberPaths = new Set(members);
11276
+ return members.flatMap((from) => [...graph.get(from) ?? []].filter((to) => memberPaths.has(to)).map((to) => ({
11277
+ from,
11278
+ to
11279
+ }))).sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to));
11280
+ }
11281
+ function addGeneratedReferenceCycleProblems(options) {
11282
+ const graph = createGeneratedReferenceGraph(options.projects);
11283
+ options.checks.add(graph.size);
11284
+ for (const component of collectGeneratedReferenceComponents(graph)) {
11285
+ const hasSelfReference = Boolean(component[0] && graph.get(component[0])?.has(component[0]));
11286
+ if (component.length === 1 && !hasSelfReference) continue;
11287
+ const members = [...component].sort();
11288
+ const internalEdges = getGeneratedReferenceCycleEdges(graph, members);
11289
+ const lines = [
11290
+ "Generated project reference cycle:",
11291
+ " projects:",
11292
+ ...members.map((member) => ` - ${toRelativePath(options.config.rootDir, member)}`),
11293
+ " references in cycle:",
11294
+ ...internalEdges.map((edge) => ` - ${toRelativePath(options.config.rootDir, edge.from)} -> ${toRelativePath(options.config.rootDir, edge.to)}`),
11295
+ ` reason: ${GENERATED_REFERENCE_CYCLE_REASON}`,
11296
+ ` fix: ${GENERATED_REFERENCE_CYCLE_FIX}`
11297
+ ];
11298
+ addGraphProblem(options.problems, lines, {
11299
+ code: LIMINA_CHECK_ISSUE_CODES.graphReferenceCycle,
11300
+ filePath: members[0],
11301
+ fix: GENERATED_REFERENCE_CYCLE_FIX,
11302
+ reason: GENERATED_REFERENCE_CYCLE_REASON,
11303
+ title: "Generated project reference cycle"
11304
+ });
11305
+ }
11306
+ }
10543
11307
  function getExpectedReferencesForProject(expectedReferencesByProjectPath, project) {
10544
11308
  const expectedReferences = expectedReferencesByProjectPath.get(project.configPath) ?? /* @__PURE__ */ new Map();
10545
11309
  expectedReferencesByProjectPath.set(project.configPath, expectedReferences);
@@ -10673,30 +11437,34 @@ function collectExpectedReferenceForImport(options) {
10673
11437
  });
10674
11438
  }
10675
11439
  function resolveImportForReferenceExpectation(options) {
10676
- const targetPackage = findPackageForSpecifier(options.importRecord.specifier, options.context.packages);
10677
- const importer = findImporterForFile(options.importRecord.filePath, options.context.importers);
11440
+ const targetPackage = options.context.workspaceLookup.findPackageForSpecifier(options.importRecord.specifier);
11441
+ const importer = options.context.workspaceLookup.findImporterForFile(options.importRecord.filePath);
10678
11442
  const workspaceExportResolution = getWorkspaceExportResolution({
10679
11443
  context: options.context,
10680
11444
  importRecord: options.importRecord,
10681
11445
  project: options.project,
10682
11446
  targetPackage
10683
11447
  });
10684
- if (workspaceExportResolution && !workspaceExportResolution.hasTypeScriptStableEntry) {
10685
- addWorkspacePackageExportWithoutTypeEntryProblem({
11448
+ const declarationProvider = resolveDeclarationProvider({
11449
+ compilerOptions: options.project.options,
11450
+ containingFile: options.filePath,
11451
+ fileOwnerLookup: options.context.fileOwnerLookup,
11452
+ importAnalysis: options.context.importAnalysis,
11453
+ importRecord: options.importRecord,
11454
+ project: options.project
11455
+ });
11456
+ const workspaceTypeScriptResolvedFilePath = workspaceExportResolution?.typeScriptResolvedFileName ?? null;
11457
+ const graphResolvedFilePath = declarationProvider.kind === "declaration" || declarationProvider.kind === "source" ? declarationProvider.typeScriptResolution.resolvedFileName : workspaceTypeScriptResolvedFilePath;
11458
+ if (!graphResolvedFilePath && declarationProvider.kind === "oxc-only") {
11459
+ addOxcOnlyDeclarationProviderProblem({
10686
11460
  context: options.context,
10687
11461
  importRecord: options.importRecord,
10688
- project: options.project,
10689
- resolution: workspaceExportResolution
11462
+ oxcResolvedFilePath: declarationProvider.oxcResolvedFilePath,
11463
+ project: options.project
10690
11464
  });
10691
11465
  return null;
10692
11466
  }
10693
- const internalResolvedFilePath = resolveInternalImport(options.importRecord.specifier, options.filePath, options.project.options, options.project, options.context.importAnalysis);
10694
- const useWorkspaceExportResolution = shouldUseWorkspaceExportResolution({
10695
- resolvedFilePath: internalResolvedFilePath,
10696
- targetPackage
10697
- });
10698
- const resolvedFilePath = (useWorkspaceExportResolution ? workspaceExportResolution?.oxcResolvedFileName : null) ?? internalResolvedFilePath;
10699
- if (!resolvedFilePath) {
11467
+ if (!graphResolvedFilePath && declarationProvider.kind === "unresolved") {
10700
11468
  addUnresolvedWorkspaceImportProblem({
10701
11469
  context: options.context,
10702
11470
  importRecord: options.importRecord,
@@ -10706,22 +11474,22 @@ function resolveImportForReferenceExpectation(options) {
10706
11474
  });
10707
11475
  return null;
10708
11476
  }
10709
- const graphResolvedFilePath = (useWorkspaceExportResolution ? workspaceExportResolution?.typeScriptResolvedFileName : null) ?? resolvedFilePath;
10710
- if (!graphResolvedFilePath) {
10711
- addUnresolvedWorkspaceImportProblem({
11477
+ if (workspaceExportResolution && !workspaceExportResolution.hasTypeScriptStableEntry) {
11478
+ addWorkspacePackageExportWithoutTypeEntryProblem({
10712
11479
  context: options.context,
10713
11480
  importRecord: options.importRecord,
10714
11481
  project: options.project,
10715
- targetPackage,
10716
- title: "Unresolved workspace import in TypeScript:"
11482
+ resolution: workspaceExportResolution
10717
11483
  });
10718
11484
  return null;
10719
11485
  }
10720
- const targetWorkspacePackageForResolved = getResolvedWorkspacePackage(graphResolvedFilePath, options.context.packages);
11486
+ if (!graphResolvedFilePath) return null;
11487
+ const resolvedFilePath = graphResolvedFilePath;
11488
+ const targetWorkspacePackageForResolved = getResolvedWorkspacePackage(graphResolvedFilePath, options.context.workspaceLookup);
10721
11489
  const targetPackageForGraph = getTargetPackageForGraph({
10722
11490
  targetPackage,
10723
11491
  targetWorkspacePackageForResolved,
10724
- useWorkspaceExportResolution
11492
+ useWorkspaceExportResolution: Boolean(workspaceExportResolution)
10725
11493
  });
10726
11494
  const deniedDepRule = getDeniedDepRuleForResolvedPackage({
10727
11495
  context: options.context,
@@ -10738,6 +11506,7 @@ function resolveImportForReferenceExpectation(options) {
10738
11506
  });
10739
11507
  return null;
10740
11508
  }
11509
+ if (isDeclarationFileFamily(graphResolvedFilePath)) return null;
10741
11510
  return {
10742
11511
  graphResolvedFilePath,
10743
11512
  importer,
@@ -10774,7 +11543,7 @@ function getTargetPackageForGraph(options) {
10774
11543
  return null;
10775
11544
  }
10776
11545
  function getDeniedDepRuleForResolvedPackage(options) {
10777
- const resolvedPackageName = getResolvedPackageName(options.resolvedFilePath, options.context.packages);
11546
+ const resolvedPackageName = getResolvedPackageName(options.resolvedFilePath, options.context.workspaceLookup);
10778
11547
  if (!resolvedPackageName) return null;
10779
11548
  return getDeniedDepRuleForPackage(options.context.graphRules, options.project.labels, resolvedPackageName);
10780
11549
  }
@@ -10789,6 +11558,23 @@ function addUnresolvedWorkspaceImportProblem(options) {
10789
11558
  ` current references: ${formatReferences(options.context.config.rootDir, options.project.references)}`
10790
11559
  ].join("\n"));
10791
11560
  }
11561
+ function addOxcOnlyDeclarationProviderProblem(options) {
11562
+ addGraphProblem(options.context.problems, [
11563
+ "Oxc can resolve this specifier, but TypeScript cannot:",
11564
+ ` importing project: ${toRelativePath(options.context.config.rootDir, options.project.configPath)}`,
11565
+ ` file: ${formatImportRecordLocation(options.context.config.rootDir, options.importRecord)}`,
11566
+ ` imported specifier: ${options.importRecord.specifier}`,
11567
+ ` Oxc resolved file: ${toRelativePath(options.context.config.rootDir, options.oxcResolvedFilePath)}`,
11568
+ " reason: declaration references follow the checker-aware TypeScript declaration provider, not the Oxc runtime-like resolver.",
11569
+ " fix: check moduleResolution, exports.types/types conditions, paths, customConditions, and package boundaries."
11570
+ ], {
11571
+ code: LIMINA_CHECK_ISSUE_CODES.graphWorkspaceImportUnresolved,
11572
+ filePath: options.importRecord.filePath,
11573
+ fix: "Check moduleResolution, exports.types/types conditions, paths, customConditions, and package boundaries.",
11574
+ reason: "Oxc resolved this specifier, but TypeScript could not resolve a declaration provider.",
11575
+ title: "Oxc can resolve this specifier, but TypeScript cannot"
11576
+ });
11577
+ }
10792
11578
  function findExpectedReferenceTargetProjectPath(options) {
10793
11579
  if (shouldSkipWorkspaceExportResolvedOutsideGraph(options)) return null;
10794
11580
  if (addBuildArtifactImportProblem(options)) return null;
@@ -10929,7 +11715,7 @@ async function runGraphCheckImpl(config, options = {}) {
10929
11715
  const projectsByPath = new Map(projects.map((project) => [project.configPath, project]));
10930
11716
  const fileOwnerLookup = createFileOwnerLookup(projects);
10931
11717
  const packages = await preflight.ensureWorkspacePackages();
10932
- const importers = await preflight.ensureImporters();
11718
+ const workspaceLookup = await preflight.ensureWorkspaceLookupIndex();
10933
11719
  checkItems.start("source graph routes");
10934
11720
  problems.push(...graphRoute.problems);
10935
11721
  const customConditionConsistencyContext = createCustomConditionConsistencyContext(projectsByPath);
@@ -10960,14 +11746,20 @@ async function runGraphCheckImpl(config, options = {}) {
10960
11746
  addDeniedReferenceProblems({
10961
11747
  checks,
10962
11748
  config,
10963
- packages,
10964
11749
  problems,
10965
11750
  project,
10966
11751
  projectsByPath,
10967
- rules: graphRules
11752
+ rules: graphRules,
11753
+ workspaceLookup
10968
11754
  });
10969
- addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems, checks);
11755
+ addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, workspaceLookup, problems, checks);
10970
11756
  }
11757
+ addGeneratedReferenceCycleProblems({
11758
+ checks,
11759
+ config,
11760
+ problems,
11761
+ projects
11762
+ });
10971
11763
  checkItems.record("project references");
10972
11764
  checkItems.start("condition domains");
10973
11765
  addDefaultCustomConditionProblems({
@@ -10996,13 +11788,13 @@ async function runGraphCheckImpl(config, options = {}) {
10996
11788
  generatedGraph,
10997
11789
  graphRules,
10998
11790
  importAnalysis: preflight.importAnalysis,
10999
- importers,
11000
11791
  packages,
11001
11792
  problems,
11002
11793
  projectPaths,
11003
11794
  projects,
11004
11795
  projectsByPath,
11005
- workspaceExports
11796
+ workspaceExports,
11797
+ workspaceLookup
11006
11798
  }),
11007
11799
  generatedGraph,
11008
11800
  graphRules,
@@ -12474,7 +13266,7 @@ var dist_exports = /* @__PURE__ */ __exportAll({
12474
13266
  updateSettings: () => j$1
12475
13267
  });
12476
13268
  function ee() {
12477
- return V.platform !== "win32" ? V.env.TERM !== "linux" : !!V.env.CI || !!V.env.WT_SESSION || !!V.env.TERMINUS_SUBLIME || V.env.ConEmuTask === "{cmd::Cmder}" || V.env.TERM_PROGRAM === "Terminus-Sublime" || V.env.TERM_PROGRAM === "vscode" || V.env.TERM === "xterm-256color" || V.env.TERM === "alacritty" || V.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
13269
+ return process$1.platform !== "win32" ? process$1.env.TERM !== "linux" : !!process$1.env.CI || !!process$1.env.WT_SESSION || !!process$1.env.TERMINUS_SUBLIME || process$1.env.ConEmuTask === "{cmd::Cmder}" || process$1.env.TERM_PROGRAM === "Terminus-Sublime" || process$1.env.TERM_PROGRAM === "vscode" || process$1.env.TERM === "xterm-256color" || process$1.env.TERM === "alacritty" || process$1.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
12478
13270
  }
12479
13271
  const tt$1 = ee(), ot = () => process.env.CI === "true", It = (t) => t.isTTY === !0, w$1 = (t, i) => tt$1 ? t : i, Tt = w$1("◆", "*"), at = w$1("■", "x"), ut = w$1("▲", "x"), H = w$1("◇", "o"), lt = w$1("┌", "T"), $ = w$1("│", "|"), x = w$1("└", "—"), _t = w$1("┐", "T"), xt = w$1("┘", "—"), z$1 = w$1("●", ">"), U = w$1("○", " "), et$1 = w$1("◻", "[•]"), K = w$1("◼", "[+]"), Y = w$1("◻", "[ ]"), Et = w$1("▪", "•"), st = w$1("─", "-"), ct = w$1("╮", "+"), Gt = w$1("├", "+"), $t = w$1("╯", "+"), dt = w$1("╰", "+"), Mt = w$1("╭", "+"), ht = w$1("●", "•"), pt = w$1("◆", "*"), mt = w$1("▲", "!"), gt = w$1("■", "x"), P = (t) => {
12480
13272
  switch (t) {
@@ -13088,7 +13880,7 @@ ${r ? styleText("cyan", x) : ""}
13088
13880
  `), n = u.reduce((o, l) => Math.max(fastStringWidth(l), o), 0);
13089
13881
  return wrapAnsi(t, i - (u.map(s).reduce((o, l) => Math.max(fastStringWidth(l), o), 0) - n), r);
13090
13882
  }, Se = (t = "", i = "", s) => {
13091
- const r = s?.output ?? V.stdout, u = s?.withGuide ?? h.withGuide, n = s?.format ?? we$1, a = [
13883
+ const r = s?.output ?? process$1.stdout, u = s?.withGuide ?? h.withGuide, n = s?.format ?? we$1, a = [
13092
13884
  "",
13093
13885
  ...be$1(t, A$1(r) - 6, n).split(`
13094
13886
  `).map(n),
@@ -44249,8 +45041,8 @@ function collectSourceKnipWorkspaceConfigs(options) {
44249
45041
  if (Object.hasOwn(rawWorkspaceConfig, "tsConfig")) options.problems.push([
44250
45042
  "Unsupported source Knip workspace config:",
44251
45043
  ` field: ${field}.tsConfig`,
44252
- " reason: tsConfig is no longer supported. Limina uses Knip default tsconfig behavior unless a package has a static limina checker build script.",
44253
- " fix: remove tsConfig, or add a static package script such as \"build\": \"limina checker build tsconfig.json\" when this package needs a specific Knip tsconfig source."
45044
+ " reason: tsConfig is no longer supported. Limina uses Knip default tsconfig behavior unless a package has a static limina build script.",
45045
+ " fix: remove tsConfig, or add a static package script such as \"build\": \"limina build tsconfig.json\" when this package needs a specific Knip tsconfig source."
44254
45046
  ].join("\n"));
44255
45047
  workspaceConfigs.set(packageName, rawWorkspaceConfig);
44256
45048
  }
@@ -45401,7 +46193,7 @@ function addProjectOwnerProblems(options) {
45401
46193
  const missingOwnerFiles = [];
45402
46194
  for (const fileName of options.fileNames) {
45403
46195
  options.checks.add();
45404
- const owner = findOwnerForFile(fileName, options.owners);
46196
+ const owner = options.workspaceLookup.findOwnerForFile(fileName);
45405
46197
  if (!owner) {
45406
46198
  missingOwnerFiles.push(fileName);
45407
46199
  continue;
@@ -45617,14 +46409,79 @@ function addPackageImportOtherOwnerProblem(options) {
45617
46409
  " reason: #... package imports must not resolve to modules governed by another source owner."
45618
46410
  ].join("\n"));
45619
46411
  }
46412
+ function addPackageImportRelativeScopeProblem(options) {
46413
+ options.problems.push([
46414
+ "Package import relative target escapes package scope:",
46415
+ ` source owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
46416
+ ` package scope: ${toRelativePath(options.config.rootDir, options.packageScope.packageJsonPath)}`,
46417
+ ` file: ${formatImportRecordLocation(options.config.rootDir, options.importRecord)}`,
46418
+ ` imported specifier: ${options.importRecord.specifier}`,
46419
+ ` resolved file: ${toRelativePath(options.config.rootDir, options.resolvedFilePath)}`,
46420
+ ...options.targetPackageScope ? [` target package scope: ${toRelativePath(options.config.rootDir, options.targetPackageScope.packageJsonPath)}`] : [],
46421
+ " reason: #... package imports with relative targets must stay inside the declaring package scope."
46422
+ ].join("\n"));
46423
+ }
46424
+ function isResolvedInsidePackageScope(options) {
46425
+ return options.workspaceLookup.findNearestPackageScopeInfo(options.resolvedFilePath)?.packageJsonPath === options.packageScope.packageJsonPath;
46426
+ }
46427
+ function addPackageImportArtifactAuthorizationProblem(options) {
46428
+ if (!options.packageInfo.name) {
46429
+ addResolvedPackageWithoutNameProblem({
46430
+ config: options.config,
46431
+ importRecord: options.importRecord,
46432
+ owner: options.owner,
46433
+ packageInfo: options.packageInfo,
46434
+ problems: options.problems
46435
+ });
46436
+ return;
46437
+ }
46438
+ const packageName = getPackageNameForAuthorization({
46439
+ importRecord: options.importRecord,
46440
+ resolvedPackageName: options.packageInfo.name
46441
+ });
46442
+ if (isDependencyAuthorizedBySourceAuthority({
46443
+ config: options.config,
46444
+ importAuthorityAllowRules: options.importAuthorityAllowRules,
46445
+ importRecord: options.importRecord,
46446
+ owner: options.owner,
46447
+ packageName,
46448
+ rootPackage: options.rootPackage
46449
+ })) return;
46450
+ addPackageImportAuthorizationProblem({
46451
+ authorityManifestPaths: getDependencyAuthorityManifestPaths({
46452
+ config: options.config,
46453
+ importAuthorityAllowRules: options.importAuthorityAllowRules,
46454
+ importRecord: options.importRecord,
46455
+ owner: options.owner,
46456
+ packageName,
46457
+ rootPackage: options.rootPackage
46458
+ }),
46459
+ config: options.config,
46460
+ ...packageName === options.packageInfo.name ? {} : { dependencySpecifier: options.packageInfo.name },
46461
+ importRecord: options.importRecord,
46462
+ owner: options.owner,
46463
+ packageName,
46464
+ problems: options.problems,
46465
+ workspacePackage: options.workspacePackage
46466
+ });
46467
+ }
46468
+ function shouldTreatPackageImportAsRelativeTarget(match) {
46469
+ return match.targetKind === "relative";
46470
+ }
46471
+ function shouldTreatPackageImportAsPackageTarget(match) {
46472
+ return match.targetKind === "package" || match.targetKind === "mixed";
46473
+ }
45620
46474
  function addPackageImportProblem(options) {
45621
- if (!findPackageImportMatch(options.owner.manifest.imports, options.importRecord.specifier)) {
46475
+ const packageScope = options.workspaceLookup.findNearestPackageScopeInfo(options.importRecord.filePath);
46476
+ const match = findPackageImportMatch(packageScope?.manifest.imports, options.importRecord.specifier);
46477
+ if (!match) {
45622
46478
  options.problems.push([
45623
46479
  "Unauthorized package import specifier:",
45624
46480
  ` source owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
46481
+ ...packageScope ? [` package scope: ${toRelativePath(options.config.rootDir, packageScope.packageJsonPath)}`] : [],
45625
46482
  ` file: ${formatImportRecordLocation(options.config.rootDir, options.importRecord)}`,
45626
46483
  ` imported specifier: ${options.importRecord.specifier}`,
45627
- " reason: #... package imports must match the source owner package.json imports field."
46484
+ " reason: #... package imports must match the nearest package scope package.json imports field."
45628
46485
  ].join("\n"));
45629
46486
  return;
45630
46487
  }
@@ -45632,68 +46489,82 @@ function addPackageImportProblem(options) {
45632
46489
  options.problems.push([
45633
46490
  "Unresolved package import specifier:",
45634
46491
  ` source owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
46492
+ ...packageScope ? [` package scope: ${toRelativePath(options.config.rootDir, packageScope.packageJsonPath)}`] : [],
45635
46493
  ` file: ${formatImportRecordLocation(options.config.rootDir, options.importRecord)}`,
45636
46494
  ` imported specifier: ${options.importRecord.specifier}`,
45637
- " reason: matched #... package imports must resolve to a file within the same source owner scope."
46495
+ " reason: matched #... package imports must resolve from the nearest package scope package.json imports field."
45638
46496
  ].join("\n"));
45639
46497
  return;
45640
46498
  }
45641
- const target = classifyResolvedPackageTarget({
46499
+ if (packageScope && shouldTreatPackageImportAsRelativeTarget(match) && !isResolvedInsidePackageScope({
46500
+ packageScope,
46501
+ resolvedFilePath: options.resolvedFilePath,
46502
+ workspaceLookup: options.workspaceLookup
46503
+ })) {
46504
+ addPackageImportRelativeScopeProblem({
46505
+ config: options.config,
46506
+ importRecord: options.importRecord,
46507
+ owner: options.owner,
46508
+ packageScope,
46509
+ problems: options.problems,
46510
+ resolvedFilePath: options.resolvedFilePath,
46511
+ targetPackageScope: options.workspaceLookup.findNearestPackageScopeInfo(options.resolvedFilePath)
46512
+ });
46513
+ return;
46514
+ }
46515
+ const target = options.workspaceLookup.classifyResolvedPackageTarget({
45642
46516
  owner: options.owner,
45643
- owners: options.owners,
45644
- packages: options.packages,
45645
46517
  resolvedFilePath: options.resolvedFilePath
45646
46518
  });
45647
- if (target.kind === "current-owner") return;
45648
- if (target.kind === "other-owner") {
45649
- addPackageImportOtherOwnerProblem({
46519
+ if (target.kind === "current-owner") {
46520
+ if (packageScope && !shouldTreatPackageImportAsPackageTarget(match) && !isResolvedInsidePackageScope({
46521
+ packageScope,
46522
+ resolvedFilePath: options.resolvedFilePath,
46523
+ workspaceLookup: options.workspaceLookup
46524
+ })) addPackageImportRelativeScopeProblem({
45650
46525
  config: options.config,
45651
46526
  importRecord: options.importRecord,
45652
46527
  owner: options.owner,
46528
+ packageScope,
45653
46529
  problems: options.problems,
45654
- targetOwner: target.targetOwner,
45655
- workspacePackage: target.workspacePackage
46530
+ resolvedFilePath: options.resolvedFilePath,
46531
+ targetPackageScope: options.workspaceLookup.findNearestPackageScopeInfo(options.resolvedFilePath)
45656
46532
  });
45657
46533
  return;
45658
46534
  }
45659
- if (target.kind === "artifact-package") {
45660
- if (!target.packageInfo.name) {
45661
- addResolvedPackageWithoutNameProblem({
46535
+ if (target.kind === "other-owner") {
46536
+ if (shouldTreatPackageImportAsPackageTarget(match)) {
46537
+ addPackageImportArtifactAuthorizationProblem({
45662
46538
  config: options.config,
46539
+ importAuthorityAllowRules: options.importAuthorityAllowRules,
45663
46540
  importRecord: options.importRecord,
45664
46541
  owner: options.owner,
45665
46542
  packageInfo: target.packageInfo,
45666
- problems: options.problems
46543
+ problems: options.problems,
46544
+ rootPackage: options.rootPackage,
46545
+ workspacePackage: target.workspacePackage
45667
46546
  });
45668
46547
  return;
45669
46548
  }
45670
- const packageName = getPackageNameForAuthorization({
45671
- importRecord: options.importRecord,
45672
- resolvedPackageName: target.packageInfo.name
45673
- });
45674
- if (isDependencyAuthorizedBySourceAuthority({
46549
+ addPackageImportOtherOwnerProblem({
45675
46550
  config: options.config,
45676
- importAuthorityAllowRules: options.importAuthorityAllowRules,
45677
46551
  importRecord: options.importRecord,
45678
46552
  owner: options.owner,
45679
- packageName,
45680
- rootPackage: options.rootPackage
45681
- })) return;
45682
- addPackageImportAuthorizationProblem({
45683
- authorityManifestPaths: getDependencyAuthorityManifestPaths({
45684
- config: options.config,
45685
- importAuthorityAllowRules: options.importAuthorityAllowRules,
45686
- importRecord: options.importRecord,
45687
- owner: options.owner,
45688
- packageName,
45689
- rootPackage: options.rootPackage
45690
- }),
46553
+ problems: options.problems,
46554
+ targetOwner: target.targetOwner,
46555
+ workspacePackage: target.workspacePackage
46556
+ });
46557
+ return;
46558
+ }
46559
+ if (target.kind === "artifact-package") {
46560
+ addPackageImportArtifactAuthorizationProblem({
45691
46561
  config: options.config,
45692
- dependencySpecifier: target.packageInfo.name,
46562
+ importAuthorityAllowRules: options.importAuthorityAllowRules,
45693
46563
  importRecord: options.importRecord,
45694
46564
  owner: options.owner,
45695
- packageName,
46565
+ packageInfo: target.packageInfo,
45696
46566
  problems: options.problems,
46567
+ rootPackage: options.rootPackage,
45697
46568
  workspacePackage: null
45698
46569
  });
45699
46570
  return;
@@ -45831,7 +46702,7 @@ async function addTsconfigGovernanceProblems(options) {
45831
46702
  for (const configPath of configPaths) {
45832
46703
  if (shouldSkipGovernedTsconfig(readJsonConfig(options.config, configPath))) continue;
45833
46704
  options.checks.add();
45834
- const owner = findOwnerForFile(configPath, options.owners);
46705
+ const owner = options.workspaceLookup.findOwnerForFile(configPath);
45835
46706
  if (!owner) {
45836
46707
  options.problems.push([
45837
46708
  "Tsconfig has no source owner:",
@@ -45845,7 +46716,7 @@ async function addTsconfigGovernanceProblems(options) {
45845
46716
  projectFileSetsByConfigPath.set(configPath, new Set(project.fileNames));
45846
46717
  for (const fileName of project.fileNames) {
45847
46718
  options.checks.add();
45848
- const fileOwner = findOwnerForFile(fileName, options.owners);
46719
+ const fileOwner = options.workspaceLookup.findOwnerForFile(fileName);
45849
46720
  if (fileOwner?.packageJsonPath !== owner.packageJsonPath) options.problems.push([
45850
46721
  "Tsconfig source file set crosses source owner scope:",
45851
46722
  ` config: ${toRelativePath(options.config.rootDir, configPath)}`,
@@ -46057,9 +46928,9 @@ async function addSourceProjectOwnerProblems(options) {
46057
46928
  config: options.config,
46058
46929
  configPath: project.configPath,
46059
46930
  fileNames: project.fileNames,
46060
- owners: options.owners,
46061
46931
  problems: options.problems,
46062
- role: "declaration leaf"
46932
+ role: "declaration leaf",
46933
+ workspaceLookup: options.workspaceLookup
46063
46934
  });
46064
46935
  const typecheckConfigPath = getTypecheckConfigPath(project.configPath);
46065
46936
  if (!existsSync(typecheckConfigPath)) continue;
@@ -46068,16 +46939,16 @@ async function addSourceProjectOwnerProblems(options) {
46068
46939
  config: options.config,
46069
46940
  configPath: typecheckConfigPath,
46070
46941
  fileNames: (await options.core.tsconfig.getProject(typecheckConfigPath, project)).fileNames,
46071
- owners: options.owners,
46072
46942
  problems: options.problems,
46073
- role: "typecheck companion"
46943
+ role: "typecheck companion",
46944
+ workspaceLookup: options.workspaceLookup
46074
46945
  });
46075
46946
  }
46076
46947
  }
46077
46948
  function addRelativeImportProblems(options) {
46078
46949
  if (!options.resolvedFilePath) return;
46079
- const sourcePackageScope = findNearestPackageScopeInfo(options.filePath);
46080
- const targetPackageScope = findNearestPackageScopeInfo(options.resolvedFilePath);
46950
+ const sourcePackageScope = options.workspaceLookup.findNearestPackageScopeInfo(options.filePath);
46951
+ const targetPackageScope = options.workspaceLookup.findNearestPackageScopeInfo(options.resolvedFilePath);
46081
46952
  if (sourcePackageScope?.packageJsonPath === targetPackageScope?.packageJsonPath) return;
46082
46953
  addRelativeImportOwnerProblem({
46083
46954
  config: options.config,
@@ -46176,10 +47047,8 @@ function addResolvedArtifactBarePackageProblems(options) {
46176
47047
  });
46177
47048
  }
46178
47049
  function addResolvedBarePackageImportProblems(options) {
46179
- const target = classifyResolvedPackageTarget({
47050
+ const target = options.workspaceLookup.classifyResolvedPackageTarget({
46180
47051
  owner: options.owner,
46181
- owners: options.owners,
46182
- packages: options.packages,
46183
47052
  resolvedFilePath: options.resolvedFilePath
46184
47053
  });
46185
47054
  if (target.kind === "current-owner") return true;
@@ -46216,11 +47085,10 @@ function addBarePackageImportProblems(options) {
46216
47085
  importAuthorityAllowRules: options.importAuthorityAllowRules,
46217
47086
  importRecord: options.importRecord,
46218
47087
  owner: options.owner,
46219
- owners: options.owners,
46220
- packages: options.packages,
46221
47088
  problems: options.problems,
46222
47089
  resolvedFilePath: options.resolvedFilePath,
46223
- rootPackage: options.rootPackage
47090
+ rootPackage: options.rootPackage,
47091
+ workspaceLookup: options.workspaceLookup
46224
47092
  })) return;
46225
47093
  const workspacePackage = options.packages.find((candidate) => candidate.name === options.fallbackPackageName) ?? null;
46226
47094
  if (isDependencyAuthorizedBySourceAuthority({
@@ -46257,7 +47125,8 @@ function addImportRecordProblems(options) {
46257
47125
  importRecord: options.importRecord,
46258
47126
  owner: options.owner,
46259
47127
  problems: options.problems,
46260
- resolvedFilePath
47128
+ resolvedFilePath,
47129
+ workspaceLookup: options.workspaceLookup
46261
47130
  });
46262
47131
  return;
46263
47132
  }
@@ -46266,12 +47135,11 @@ function addImportRecordProblems(options) {
46266
47135
  config: options.config,
46267
47136
  importRecord: options.importRecord,
46268
47137
  owner: options.owner,
46269
- owners: options.owners,
46270
- packages: options.packages,
46271
47138
  problems: options.problems,
46272
47139
  resolvedFilePath,
46273
47140
  importAuthorityAllowRules: options.importAuthorityAllowRules,
46274
- rootPackage: options.rootPackage
47141
+ rootPackage: options.rootPackage,
47142
+ workspaceLookup: options.workspaceLookup
46275
47143
  });
46276
47144
  return;
46277
47145
  }
@@ -46282,16 +47150,16 @@ function addImportRecordProblems(options) {
46282
47150
  importAuthorityAllowRules: options.importAuthorityAllowRules,
46283
47151
  importRecord: options.importRecord,
46284
47152
  owner: options.owner,
46285
- owners: options.owners,
46286
47153
  packages: options.packages,
46287
47154
  problems: options.problems,
46288
47155
  resolvedFilePath,
46289
- rootPackage: options.rootPackage
47156
+ rootPackage: options.rootPackage,
47157
+ workspaceLookup: options.workspaceLookup
46290
47158
  });
46291
47159
  }
46292
47160
  function addSourceImportProblems(options) {
46293
47161
  for (const { fileNames, project } of options.sourceProjectEntries) for (const filePath of fileNames) {
46294
- const owner = findOwnerForFile(filePath, options.owners);
47162
+ const owner = options.workspaceLookup.findOwnerForFile(filePath);
46295
47163
  if (!owner) continue;
46296
47164
  for (const importRecord of collectImportsFromFile(filePath, options.config.rootDir, options.importAnalysis)) {
46297
47165
  options.checks.add();
@@ -46302,11 +47170,11 @@ function addSourceImportProblems(options) {
46302
47170
  importAuthorityAllowRules: options.importAuthorityAllowRules,
46303
47171
  importRecord,
46304
47172
  owner,
46305
- owners: options.owners,
46306
47173
  packages: options.packages,
46307
47174
  problems: options.problems,
46308
47175
  project,
46309
- rootPackage: options.rootPackage
47176
+ rootPackage: options.rootPackage,
47177
+ workspaceLookup: options.workspaceLookup
46310
47178
  });
46311
47179
  }
46312
47180
  }
@@ -46404,6 +47272,7 @@ async function runSourceCheckImpl(config, options = {}) {
46404
47272
  const sourceProjectEntries = await createSourceProjectEntries(core, projects);
46405
47273
  const packages = await preflight.ensureWorkspacePackages();
46406
47274
  const packageOwners = await preflight.ensurePackageOwners();
47275
+ const workspaceLookup = await preflight.ensureWorkspaceLookupIndex();
46407
47276
  const workspaceDependencyDeclarations = await preflight.ensureWorkspaceDependencyDeclarations();
46408
47277
  const rootPackage = findWorkspaceRootPackage({
46409
47278
  config,
@@ -46424,8 +47293,8 @@ async function runSourceCheckImpl(config, options = {}) {
46424
47293
  config,
46425
47294
  configPaths: collectGeneratedSourceConfigPaths(generatedGraph),
46426
47295
  generatedGraph,
46427
- owners: packageOwners,
46428
- problems
47296
+ problems,
47297
+ workspaceLookup
46429
47298
  });
46430
47299
  checkItems.record("tsconfig governance");
46431
47300
  checkItems.start("knip source usage");
@@ -46446,9 +47315,9 @@ async function runSourceCheckImpl(config, options = {}) {
46446
47315
  checks,
46447
47316
  config,
46448
47317
  core,
46449
- owners: packageOwners,
46450
47318
  problems,
46451
- projects
47319
+ projects,
47320
+ workspaceLookup
46452
47321
  });
46453
47322
  checkItems.record("source project ownership");
46454
47323
  checkItems.start("source import authority");
@@ -46468,11 +47337,11 @@ async function runSourceCheckImpl(config, options = {}) {
46468
47337
  config,
46469
47338
  importAnalysis,
46470
47339
  importAuthorityAllowRules,
46471
- owners: packageOwners,
46472
47340
  packages,
46473
47341
  problems,
46474
47342
  rootPackage,
46475
- sourceProjectEntries
47343
+ sourceProjectEntries,
47344
+ workspaceLookup
46476
47345
  });
46477
47346
  checkItems.record("source import authority");
46478
47347
  const ownerNamesByManifestPath = createSourceProblemOwnerLookup(packageOwners);
@@ -46939,6 +47808,135 @@ async function runCheckerBuildImpl(options) {
46939
47808
  });
46940
47809
  const flowDepth = options.flowDepth ?? 0;
46941
47810
  const rootConfigPaths = [];
47811
+ if (options.configPath) {
47812
+ const sourceConfigPath = resolveBuildConfigPath({
47813
+ configPath: options.configPath,
47814
+ cwd,
47815
+ rootDir: projectRootDir
47816
+ });
47817
+ const managedTargets = collectManagedDeclarationBuildTargets({
47818
+ allCheckers,
47819
+ generatedGraph,
47820
+ sourceConfigPath
47821
+ });
47822
+ const buildCapableTargets = managedTargets.filter(({ checker }) => getCheckerAdapter(checker.preset)?.execution === "build");
47823
+ const availableCheckers = uniqueSortedStrings(buildCapableTargets.map(({ checker }) => checker.preset));
47824
+ const checkerTargets = options.checker ? buildCapableTargets.filter(({ checker }) => checker.preset === options.checker) : buildCapableTargets;
47825
+ if (checkerTargets.length === 0) {
47826
+ const selectionProblem = options.checker ? formatManagedBuildCheckerSelectionProblem({
47827
+ availableCheckers,
47828
+ projectRootDir,
47829
+ selectedChecker: options.checker,
47830
+ sourceConfigPath
47831
+ }) : managedTargets.length > 0 ? formatTypecheckOnlyBuildProblem({
47832
+ checkers: managedTargets.map(({ checker }) => checker),
47833
+ projectRootDir,
47834
+ sourceConfigPath
47835
+ }) : [
47836
+ "Unmanaged Limina checker build config:",
47837
+ ` config: ${toRelativePath(projectRootDir, sourceConfigPath)}`,
47838
+ " reason: limina checker build <config> only accepts source configs managed by Limina checker.include.",
47839
+ " fix: add the owning tsconfig.json entry to checker.include, or use limina build <config> --raw --preset <checker> for a direct raw build."
47840
+ ].join("\n");
47841
+ if (shouldLogCheckReport(options.report)) TypecheckLogger.error(formatTypecheckProblemSummaryReport({
47842
+ pluralIssueLabel: "checker build selection issues",
47843
+ problems: [selectionProblem],
47844
+ singularIssueLabel: "checker build selection issue",
47845
+ title: "Checker build summary"
47846
+ }));
47847
+ return {
47848
+ failedTargets: [],
47849
+ failureKind: "target-selection",
47850
+ passed: false,
47851
+ problems: [selectionProblem],
47852
+ projectRootDir,
47853
+ rootConfigPaths,
47854
+ targetResults: []
47855
+ };
47856
+ }
47857
+ const problems = collectCheckerPeerDependencyProblems({
47858
+ checkers: checkerTargets.map(({ checker }) => checker),
47859
+ imports: options.config.config?.imports,
47860
+ projectRootDir,
47861
+ resolvePackage: options.checkerPackageResolver
47862
+ });
47863
+ if (problems.length > 0) {
47864
+ if (shouldLogCheckReport(options.report)) TypecheckLogger.error(formatTypecheckProblemSummaryReport({
47865
+ pluralIssueLabel: "checker build issues",
47866
+ problems,
47867
+ singularIssueLabel: "checker build issue",
47868
+ title: "Checker build summary"
47869
+ }));
47870
+ return {
47871
+ failedTargets: [],
47872
+ failureKind: "peer-dependency",
47873
+ passed: false,
47874
+ problems,
47875
+ projectRootDir,
47876
+ rootConfigPaths,
47877
+ targetResults: []
47878
+ };
47879
+ }
47880
+ const targets = checkerTargets.map(({ buildModule, checker, sourceConfigPath }) => {
47881
+ rootConfigPaths.push(buildModule.path);
47882
+ return {
47883
+ ...createCheckerTarget({
47884
+ checker,
47885
+ commandOverride: options.tscCommand,
47886
+ configPath: buildModule.path,
47887
+ executionKind: "build",
47888
+ projectRootDir,
47889
+ watch: options.watch
47890
+ }),
47891
+ sourceConfigPath
47892
+ };
47893
+ });
47894
+ options.flow?.info(`found ${targets.length} checker build target(s)`, { depth: flowDepth + 1 });
47895
+ const targetTasks = new Map(createPlannedCheckerTargetTasks(targets, options.progress, "checker build", projectRootDir));
47896
+ const results = await runBuildTargets(targets, generatedGraph.providerEdges, resolveTypecheckRunner(options), {
47897
+ config: options.config,
47898
+ onTargetResult: (target, result) => {
47899
+ const task = targetTasks.get(target.configPath);
47900
+ if (!task) return;
47901
+ if (result.status === 0) task.pass();
47902
+ else {
47903
+ const suffix = result.error ? formatErrorMessage(result.error) : `exited with code ${result.status}`;
47904
+ task.fail(void 0, { error: suffix });
47905
+ }
47906
+ },
47907
+ onTargetStart: (target) => {
47908
+ if (options.progress) {
47909
+ targetTasks.get(target.configPath)?.start();
47910
+ return;
47911
+ }
47912
+ if (!options.flow) return;
47913
+ targetTasks.set(target.configPath, options.flow.start(getCheckerTargetFlowLabel(target, "checker build", projectRootDir), {
47914
+ collapseOnSuccess: false,
47915
+ depth: flowDepth + 1
47916
+ }));
47917
+ },
47918
+ watch: options.watch
47919
+ });
47920
+ const failedResults = results.filter((result) => result.status !== 0);
47921
+ const failedTargets = collectFailedCheckerTargets(targets, failedResults);
47922
+ const passed = failedResults.length === 0;
47923
+ if (!passed && shouldLogCheckReport(options.report)) TypecheckLogger.error(formatFailedTargetSummaryReport({
47924
+ failedResults,
47925
+ heading: "build checks failed:",
47926
+ pluralIssueLabel: "failed checker build targets",
47927
+ projectRootDir,
47928
+ singularIssueLabel: "failed checker build target",
47929
+ title: "Checker build summary"
47930
+ }));
47931
+ else if (passed && shouldLogCheckReport(options.report) && !options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} checker build target(s).`);
47932
+ return {
47933
+ failedTargets,
47934
+ passed,
47935
+ projectRootDir,
47936
+ rootConfigPaths,
47937
+ targetResults: results
47938
+ };
47939
+ }
46942
47940
  const problems = collectCheckerPeerDependencyProblems({
46943
47941
  checkers: allCheckers,
46944
47942
  imports: options.config.config?.imports,
@@ -47116,13 +48114,49 @@ function formatTypecheckOnlyBuildProblem(options) {
47116
48114
  }
47117
48115
  function formatManagedBuildCheckerSelectionProblem(options) {
47118
48116
  return [
47119
- "Invalid Limina checker build preset:",
48117
+ `Invalid Limina ${options.commandLabel ?? "checker build"} preset:`,
47120
48118
  ` config: ${toRelativePath(options.projectRootDir, options.sourceConfigPath)}`,
47121
48119
  ` preset: ${options.selectedChecker}`,
47122
48120
  " reason: --preset must select a build-capable checker preset that reaches this Limina-managed target.",
47123
48121
  ...options.availableCheckers.length > 0 ? [" available presets:", ...options.availableCheckers.map((checker) => ` - ${checker}`)] : [" available presets: none"]
47124
48122
  ].join("\n");
47125
48123
  }
48124
+ function formatMultipleOutputBuildPresetProblem(options) {
48125
+ return [
48126
+ "Ambiguous Limina output build preset:",
48127
+ ` config: ${toRelativePath(options.projectRootDir, options.sourceConfigPath)}`,
48128
+ " reason: multiple build-capable checker presets can produce output artifacts for this config.",
48129
+ " fix: pass --preset with one of the available presets.",
48130
+ " available presets:",
48131
+ ...options.availableCheckers.map((checker) => ` - ${checker}`)
48132
+ ].join("\n");
48133
+ }
48134
+ function formatOutputBuildTargetResolutionProblem(options) {
48135
+ const configLine = ` config: ${toRelativePath(options.projectRootDir, options.sourceConfigPath)}`;
48136
+ if (options.resolutionKind === "unmanaged") return [
48137
+ "Unmanaged Limina output build config:",
48138
+ configLine,
48139
+ " reason: limina build <config> only accepts source configs managed by Limina checker.include.",
48140
+ " fix: add the owning tsconfig.json entry to a build-capable checker include, or use limina build <config> --raw --preset <checker> for a direct raw build."
48141
+ ].join("\n");
48142
+ if (options.resolutionKind === "outputless-solution") return [
48143
+ "No output-enabled source configs were found under this solution config.",
48144
+ configLine,
48145
+ " reason: the solution is Limina-managed, but none of its recursive referenced source leaves declare liminaOptions.outputs.",
48146
+ " fix: Add liminaOptions.outputs to at least one referenced source leaf."
48147
+ ].join("\n");
48148
+ if (options.resolutionKind === "outputless-project") return [
48149
+ "Missing Limina output build options:",
48150
+ configLine,
48151
+ " reason: this Limina-managed source config does not declare liminaOptions.outputs.",
48152
+ " fix: add liminaOptions.outputs to this source config, or use limina build <config> --raw --preset <checker> for a direct raw build."
48153
+ ].join("\n");
48154
+ return formatTypecheckOnlyBuildProblem({
48155
+ checkers: options.matchingCheckers,
48156
+ projectRootDir: options.projectRootDir,
48157
+ sourceConfigPath: options.sourceConfigPath
48158
+ });
48159
+ }
47126
48160
  function getBuildTargetDescriptorKey(descriptor) {
47127
48161
  return `${descriptor.checker.name}\0${descriptor.sourceConfigPath}`;
47128
48162
  }
@@ -47138,7 +48172,7 @@ function createRawBuildChecker(options) {
47138
48172
  preset: options.preset
47139
48173
  };
47140
48174
  }
47141
- function collectManagedBuildTargets(options) {
48175
+ function collectManagedDeclarationBuildTargets(options) {
47142
48176
  return options.allCheckers.flatMap((checker) => {
47143
48177
  const buildModule = options.generatedGraph.sourceToBuild.get(checker.name)?.get(options.sourceConfigPath);
47144
48178
  if (!buildModule) return [];
@@ -47149,6 +48183,17 @@ function collectManagedBuildTargets(options) {
47149
48183
  }];
47150
48184
  });
47151
48185
  }
48186
+ function collectManagedOutputBuildTargets(options) {
48187
+ return options.allCheckers.flatMap((checker) => {
48188
+ const buildModule = options.generatedGraph.configToOutputBuild.get(checker.name)?.get(options.sourceConfigPath);
48189
+ if (!buildModule) return [];
48190
+ return [{
48191
+ buildModule,
48192
+ checker,
48193
+ sourceConfigPath: options.sourceConfigPath
48194
+ }];
48195
+ });
48196
+ }
47152
48197
  async function resolveBuildTarget(options) {
47153
48198
  const projectRootDir = normalizeAbsolutePath(options.config.rootDir);
47154
48199
  const targetConfigPath = resolveBuildConfigPath({
@@ -47157,26 +48202,49 @@ async function resolveBuildTarget(options) {
47157
48202
  project: options.project,
47158
48203
  rootDir: projectRootDir
47159
48204
  });
48205
+ if (options.raw) {
48206
+ if (!options.checker) throw new Error([
48207
+ "Invalid raw build invocation:",
48208
+ ` config: ${toRelativePath(projectRootDir, targetConfigPath)}`,
48209
+ " reason: limina build --raw requires --preset."
48210
+ ].join("\n"));
48211
+ if (targetConfigPath.split(posix.sep).includes(".limina")) throw new Error([
48212
+ "Invalid raw build config:",
48213
+ ` config: ${toRelativePath(projectRootDir, targetConfigPath)}`,
48214
+ " reason: raw build expects a user-authored tsconfig, not a .limina generated config."
48215
+ ].join("\n"));
48216
+ return {
48217
+ checker: options.checker,
48218
+ kind: "raw",
48219
+ targetConfigPath
48220
+ };
48221
+ }
47160
48222
  const generatedGraph = await (options.core ?? createLiminaCore(options.config)).buildGraph.getGraph();
47161
48223
  const allCheckers = generatedGraph.checkers;
47162
- const managedTargets = isOrdinarySourceTypecheckConfigPath(targetConfigPath) ? collectManagedBuildTargets({
48224
+ const declarationTargets = isOrdinarySourceTypecheckConfigPath(targetConfigPath) ? collectManagedDeclarationBuildTargets({
47163
48225
  allCheckers,
47164
48226
  generatedGraph,
47165
48227
  sourceConfigPath: targetConfigPath
47166
48228
  }) : [];
47167
- if (managedTargets.length === 0) return {
47168
- checker: options.checker ?? "tsc",
47169
- kind: "raw",
47170
- targetConfigPath
47171
- };
47172
- const buildCapableTargets = managedTargets.filter(({ checker }) => getCheckerAdapter(checker.preset)?.execution === "build");
48229
+ const buildCapableTargets = (isOrdinarySourceTypecheckConfigPath(targetConfigPath) ? collectManagedOutputBuildTargets({
48230
+ allCheckers,
48231
+ generatedGraph,
48232
+ sourceConfigPath: targetConfigPath
48233
+ }) : []).filter(({ checker }) => getCheckerAdapter(checker.preset)?.execution === "build");
48234
+ const buildCapableDeclarationTargets = declarationTargets.filter(({ checker }) => getCheckerAdapter(checker.preset)?.execution === "build");
48235
+ const availableCheckers = uniqueSortedStrings(buildCapableTargets.map(({ checker }) => checker.preset));
48236
+ const checkerTargets = options.checker ? buildCapableTargets.filter(({ checker }) => checker.preset === options.checker) : buildCapableTargets;
48237
+ const managedBuildModules = declarationTargets.length > 0;
48238
+ const targetBuildModuleKinds = uniqueSortedStrings(buildCapableDeclarationTargets.map(({ buildModule }) => buildModule.kind));
48239
+ const resolutionKind = checkerTargets.length > 0 ? "managed-output" : managedBuildModules ? buildCapableDeclarationTargets.length === 0 ? "typecheck-only" : targetBuildModuleKinds.includes("solution") ? "outputless-solution" : "outputless-project" : "unmanaged";
47173
48240
  return {
47174
- availableCheckers: uniqueSortedStrings(buildCapableTargets.map(({ checker }) => checker.preset)),
48241
+ availableCheckers,
47175
48242
  allCheckers,
47176
- checkerTargets: options.checker ? buildCapableTargets.filter(({ checker }) => checker.preset === options.checker) : buildCapableTargets,
48243
+ checkerTargets,
47177
48244
  generatedGraph,
47178
48245
  kind: "managed",
47179
- matchingCheckers: managedTargets.map(({ checker }) => checker),
48246
+ matchingCheckers: declarationTargets.map(({ checker }) => checker),
48247
+ resolutionKind,
47180
48248
  ...options.checker ? { selectedChecker: options.checker } : {},
47181
48249
  sourceConfigPath: targetConfigPath
47182
48250
  };
@@ -47306,7 +48374,7 @@ function collectBuildTargetProviderClosure(options) {
47306
48374
  if (edge.fromChecker !== current.checker.name || edge.fromConfigPath !== current.sourceConfigPath) continue;
47307
48375
  const checker = checkerByName.get(edge.toChecker);
47308
48376
  if (!checker || getCheckerAdapter(checker.preset)?.execution !== "build") continue;
47309
- const buildModule = options.generatedGraph.sourceToBuild.get(checker.name)?.get(edge.toConfigPath);
48377
+ const buildModule = options.generatedGraph.configToOutputBuild.get(checker.name)?.get(edge.toConfigPath);
47310
48378
  if (!buildModule) continue;
47311
48379
  const descriptor = {
47312
48380
  buildModule,
@@ -47330,7 +48398,8 @@ async function runBuildImpl(options) {
47330
48398
  configPath: options.configPath,
47331
48399
  core: options.core,
47332
48400
  cwd,
47333
- project: options.project
48401
+ project: options.project,
48402
+ raw: options.raw
47334
48403
  });
47335
48404
  const flowDepth = options.flowDepth ?? 0;
47336
48405
  const rootConfigPaths = [];
@@ -47424,11 +48493,36 @@ async function runBuildImpl(options) {
47424
48493
  if (resolvedTarget.checkerTargets.length === 0) {
47425
48494
  const selectionProblem = resolvedTarget.selectedChecker ? formatManagedBuildCheckerSelectionProblem({
47426
48495
  availableCheckers: resolvedTarget.availableCheckers,
48496
+ commandLabel: "build",
47427
48497
  projectRootDir,
47428
48498
  selectedChecker: resolvedTarget.selectedChecker,
47429
48499
  sourceConfigPath: resolvedTarget.sourceConfigPath
47430
- }) : formatTypecheckOnlyBuildProblem({
47431
- checkers: resolvedTarget.matchingCheckers,
48500
+ }) : formatOutputBuildTargetResolutionProblem({
48501
+ matchingCheckers: resolvedTarget.matchingCheckers,
48502
+ projectRootDir,
48503
+ resolutionKind: resolvedTarget.resolutionKind,
48504
+ sourceConfigPath: resolvedTarget.sourceConfigPath
48505
+ });
48506
+ if (shouldLogCheckReport(options.report)) TypecheckLogger.error(formatCheckIssueSummaryReport({
48507
+ details: selectionProblem,
48508
+ issueCount: 1,
48509
+ pluralIssueLabel: "build selection issues",
48510
+ singularIssueLabel: "build selection issue",
48511
+ title: "Build summary"
48512
+ }));
48513
+ return {
48514
+ failedTargets: [],
48515
+ failureKind: "target-selection",
48516
+ passed: false,
48517
+ problems: [selectionProblem],
48518
+ projectRootDir,
48519
+ rootConfigPaths,
48520
+ sourceConfigPath: resolvedTarget.sourceConfigPath
48521
+ };
48522
+ }
48523
+ if (!resolvedTarget.selectedChecker && resolvedTarget.checkerTargets.length > 1) {
48524
+ const selectionProblem = formatMultipleOutputBuildPresetProblem({
48525
+ availableCheckers: resolvedTarget.availableCheckers,
47432
48526
  projectRootDir,
47433
48527
  sourceConfigPath: resolvedTarget.sourceConfigPath
47434
48528
  });
@@ -47520,21 +48614,6 @@ async function runBuildImpl(options) {
47520
48614
  })).filter((result) => result.status !== 0);
47521
48615
  const failedTargets = collectFailedCheckerTargets(targets, failedResults);
47522
48616
  const passed = failedResults.length === 0;
47523
- reportBuildCheckerCombinationWarning({
47524
- entries: collectBuildGraphCombinationEntries({
47525
- generatedGraph: resolvedTarget.generatedGraph,
47526
- projectRootDir,
47527
- roots: buildTargetDescriptors.map(({ buildModule, checker, sourceConfigPath }) => ({
47528
- checker,
47529
- configPath: buildModule.path,
47530
- entryConfigPath: sourceConfigPath
47531
- }))
47532
- }),
47533
- flow: options.flow,
47534
- flowDepth,
47535
- projectRootDir,
47536
- report: options.report
47537
- });
47538
48617
  if (!passed) {
47539
48618
  if (shouldLogCheckReport(options.report)) TypecheckLogger.error(formatFailedTargetSummaryReport({
47540
48619
  failedResults,
@@ -47843,7 +48922,7 @@ async function runBuild(options) {
47843
48922
  failedTargets: result.failedTargets,
47844
48923
  fallbackReason: "Checker build finished with failures.",
47845
48924
  failureKind: result.failureKind,
47846
- fix: "Inspect the checker build output above, then rerun `limina checker build`.",
48925
+ fix: "Inspect the build output above, then rerun `limina build <config>`.",
47847
48926
  projectRootDir: result.projectRootDir,
47848
48927
  problems: result.problems,
47849
48928
  task: "checker:build",
@@ -47868,7 +48947,7 @@ async function runBuild(options) {
47868
48947
  const issue = createTaskFailureIssue({
47869
48948
  code: "LIMINA_CHECKER_BUILD_FAILED",
47870
48949
  detailLines: [formatErrorMessage(error)],
47871
- fix: "Inspect the build error above, then rerun `limina checker build`.",
48950
+ fix: "Inspect the build error above, then rerun `limina build <config>`.",
47872
48951
  reason: `Checker build failed: ${formatErrorMessage(error)}.`,
47873
48952
  rootDir: options.config.rootDir,
47874
48953
  task: "checker:build",
@@ -47960,32 +49039,44 @@ async function runCheckerTypecheck(options) {
47960
49039
 
47961
49040
  //#endregion
47962
49041
  //#region src/flow/process-renderer.ts
47963
- function findTsxBinary(packageDir) {
47964
- const tsxBinName = process.platform === "win32" ? "tsx.cmd" : "tsx";
47965
- return [posix.join(packageDir, "node_modules/.bin", tsxBinName), posix.join(packageDir, "../../node_modules/.bin", tsxBinName)].find((candidate) => existsSync(candidate)) ?? "tsx";
49042
+ const requireFromRenderer = createRequire(import.meta.url);
49043
+ function resolveTsxCliPath(packageDir) {
49044
+ try {
49045
+ return requireFromRenderer.resolve("tsx/cli");
49046
+ } catch {
49047
+ return [posix.join(packageDir, "node_modules/tsx/dist/cli.mjs"), posix.join(packageDir, "../../node_modules/tsx/dist/cli.mjs")].find((candidate) => existsSync(candidate));
49048
+ }
47966
49049
  }
47967
49050
  function resolveRendererEntry() {
47968
49051
  const currentDir = fileURLToPath(new URL(".", import.meta.url));
47969
- const sourceEntries = [posix.resolve(currentDir, "flow/renderer-process.ts"), posix.resolve(process.cwd(), "src/flow/renderer-process.ts")];
49052
+ const sourceEntries = [posix.resolve(currentDir, "renderer-process.ts"), posix.resolve(process.cwd(), "src/flow/renderer-process.ts")];
47970
49053
  const distEntries = [
47971
49054
  posix.resolve(currentDir, "flow-renderer-process.js"),
47972
49055
  posix.resolve(currentDir, "../flow-renderer-process.js"),
47973
49056
  posix.resolve(process.cwd(), "dist/flow-renderer-process.js")
47974
49057
  ];
47975
49058
  const sourceEntry = sourceEntries.find((candidate) => existsSync(candidate));
47976
- if (sourceEntry) return {
47977
- args: [sourceEntry],
47978
- command: findTsxBinary(posix.resolve(posix.dirname(sourceEntry), "../.."))
47979
- };
49059
+ if (sourceEntry) {
49060
+ const tsxCliPath = resolveTsxCliPath(posix.resolve(posix.dirname(sourceEntry), "../.."));
49061
+ if (!tsxCliPath) return;
49062
+ return {
49063
+ args: [tsxCliPath, sourceEntry],
49064
+ command: process.execPath
49065
+ };
49066
+ }
47980
49067
  const distEntry = distEntries.find((candidate) => existsSync(candidate));
47981
49068
  if (distEntry) return {
47982
49069
  args: [distEntry],
47983
49070
  command: process.execPath
47984
49071
  };
47985
49072
  }
49073
+ function getWriteCallback(args) {
49074
+ if (args.length === 3) return args[2];
49075
+ if (typeof args[1] === "function") return args[1];
49076
+ }
47986
49077
  function callWriteCallback(args) {
47987
- const callback = args.findLast((arg) => typeof arg === "function");
47988
- if (typeof callback === "function") queueMicrotask(callback);
49078
+ const callback = getWriteCallback(args);
49079
+ if (callback) queueMicrotask(callback);
47989
49080
  }
47990
49081
  var FlowProcessRenderer = class FlowProcessRenderer {
47991
49082
  #child;
@@ -48018,7 +49109,6 @@ var FlowProcessRenderer = class FlowProcessRenderer {
48018
49109
  if (!entry) return;
48019
49110
  const renderer = new FlowProcessRenderer(spawn(entry.command, entry.args, {
48020
49111
  env: process.env,
48021
- shell: process.platform === "win32",
48022
49112
  stdio: [
48023
49113
  "ignore",
48024
49114
  "inherit",
@@ -48092,7 +49182,7 @@ var FlowProcessRenderer = class FlowProcessRenderer {
48092
49182
  callWriteCallback(args);
48093
49183
  return true;
48094
49184
  }
48095
- return Reflect.apply(originalWrite, stream, args);
49185
+ return writeWithFlowArgs(originalWrite, args);
48096
49186
  });
48097
49187
  return () => {
48098
49188
  stream.write = originalWrite;
@@ -48108,15 +49198,48 @@ var FlowProcessRenderer = class FlowProcessRenderer {
48108
49198
  }
48109
49199
  };
48110
49200
 
49201
+ //#endregion
49202
+ //#region src/flow/tree-state.ts
49203
+ function createFlowTreeNode(message, depth) {
49204
+ return {
49205
+ children: [],
49206
+ depth,
49207
+ message,
49208
+ status: "planned"
49209
+ };
49210
+ }
49211
+ function appendFlowTreeChild(parent, message, depth) {
49212
+ const childNode = createFlowTreeNode(message, depth);
49213
+ parent.children.push(childNode);
49214
+ return childNode;
49215
+ }
49216
+ function cloneFlowTreeNode(node) {
49217
+ return {
49218
+ children: node.children.map(cloneFlowTreeNode),
49219
+ depth: node.depth,
49220
+ elapsedTimeMs: node.elapsedTimeMs,
49221
+ message: node.message,
49222
+ status: node.status
49223
+ };
49224
+ }
49225
+ function skipPlannedTreeDescendants(node) {
49226
+ for (const child of node.children) {
49227
+ if (child.status === "planned") child.status = "skipped";
49228
+ skipPlannedTreeDescendants(child);
49229
+ }
49230
+ }
49231
+
48111
49232
  //#endregion
48112
49233
  //#region src/flow.ts
48113
49234
  const CHECK_FLOW_STATUS_ONLY_OPTION = Symbol("limina.checkFlowStatusOnly");
48114
49235
  const DEFAULT_CI_ENV_VALUES = new Set(["1", "true"]);
48115
- const DEFAULT_TERMINAL_COLUMNS = 80;
48116
- const ANSI_ESCAPE = String.fromCodePoint(27);
48117
- const ANSI_PATTERN = new RegExp(String.raw`${ANSI_ESCAPE}\[[\d:;<=>?]*[\u0020-\u002F]*[\u0040-\u007E]`, "gu");
49236
+ const FLOW_RENDERER_TEST_ROWS_ENV = "LIMINA_FLOW_RENDERER_TEST_ROWS";
48118
49237
  function isCiEnvironment(env) {
48119
- return DEFAULT_CI_ENV_VALUES.has(String(env.CI).toLowerCase());
49238
+ return DEFAULT_CI_ENV_VALUES.has(String(env.CI).toLowerCase()) || DEFAULT_CI_ENV_VALUES.has(String(env.CODEX_CI).toLowerCase());
49239
+ }
49240
+ function supportsInteractiveTerminal(env, stdout) {
49241
+ if (!stdout.isTTY || isCiEnvironment(env)) return false;
49242
+ return String(env.TERM).toLowerCase() !== "dumb";
48120
49243
  }
48121
49244
  function formatFailureMessage(message, error) {
48122
49245
  if (error === void 0) return message;
@@ -48126,22 +49249,27 @@ function formatFailureMessage(message, error) {
48126
49249
  function writeLine(output, message) {
48127
49250
  output.write(`${message}\n`);
48128
49251
  }
48129
- function stripControlSequences(text) {
48130
- return text.replaceAll(ANSI_PATTERN, "").replaceAll("\r", "");
48131
- }
48132
- function isTreeNodeTerminal(node) {
48133
- return node.status === "failed" || node.status === "passed" || node.status === "skipped";
49252
+ function createTaskFinishOptions(options, depth, startTime) {
49253
+ return {
49254
+ ...options,
49255
+ depth,
49256
+ elapsedTimeMs: options?.elapsedTimeMs ?? performance.now() - startTime
49257
+ };
48134
49258
  }
48135
- function areTreeNodeDescendantsTerminal(node) {
48136
- return node.children.every((child) => isTreeNodeTerminal(child) && areTreeNodeDescendantsTerminal(child));
49259
+ function readPositiveInteger(value) {
49260
+ if (value === void 0) return;
49261
+ const parsed = Number.parseInt(value, 10);
49262
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
48137
49263
  }
48138
49264
  var LiminaFlowReporter = class {
48139
49265
  #clack;
49266
+ #env;
48140
49267
  #interactive;
48141
49268
  #output;
48142
49269
  #statusOnly;
48143
49270
  #stderr;
48144
49271
  #stdout;
49272
+ #terminalFrame;
48145
49273
  #tracksProcessWrites;
48146
49274
  #interactiveHistory = [];
48147
49275
  #treeRoots = [];
@@ -48155,13 +49283,12 @@ var LiminaFlowReporter = class {
48155
49283
  #spinnerFrameIndex = 0;
48156
49284
  #spinnerTimer;
48157
49285
  #trackedTaskCount = 0;
48158
- #terminalColumn = 0;
48159
- #terminalLineCount = 0;
48160
49286
  constructor(options = {}) {
48161
49287
  const env = options.env ?? process.env;
48162
49288
  const stdout = options.stdout ?? process.stdout;
48163
49289
  this.#statusOnly = options[CHECK_FLOW_STATUS_ONLY_OPTION] === true;
48164
- this.#interactive = options.forceTty ?? Boolean(stdout.isTTY && !isCiEnvironment(env));
49290
+ this.#env = env;
49291
+ this.#interactive = options.forceTty ?? supportsInteractiveTerminal(env, stdout);
48165
49292
  this.#clack = options.clack ?? dist_exports;
48166
49293
  this.#output = options.output ?? { write: (message) => {
48167
49294
  if (typeof stdout.write === "function") {
@@ -48172,6 +49299,7 @@ var LiminaFlowReporter = class {
48172
49299
  } };
48173
49300
  this.#stdout = stdout;
48174
49301
  this.#stderr = options.stderr ?? process.stderr;
49302
+ this.#terminalFrame = new TerminalFrameTracker(() => this.#stdout?.columns ?? DEFAULT_TERMINAL_COLUMNS);
48175
49303
  this.#processRenderer = this.#createProcessRenderer(options);
48176
49304
  this.#tracksProcessWrites = this.#interactive && !this.#statusOnly && options.output === void 0 && !this.#processRenderer;
48177
49305
  }
@@ -48189,20 +49317,21 @@ var LiminaFlowReporter = class {
48189
49317
  if (!this.#interactive || rendererMode === "inline" || options.output !== void 0 || options.stdout !== void 0 || options.stderr !== void 0 || options.clack !== void 0) return;
48190
49318
  return FlowProcessRenderer.start();
48191
49319
  }
48192
- #cloneTreeNode(node) {
48193
- return {
48194
- children: node.children.map((child) => this.#cloneTreeNode(child)),
48195
- depth: node.depth,
48196
- elapsedTimeMs: node.elapsedTimeMs,
48197
- message: node.message,
48198
- status: node.status
48199
- };
48200
- }
48201
49320
  #createRenderSnapshot() {
49321
+ const terminalDimensions = this.#getTerminalDimensions();
49322
+ const hasTerminalDimensions = terminalDimensions.columns !== void 0 || terminalDimensions.rows !== void 0;
48202
49323
  return {
49324
+ ...this.#statusOnly ? { compactMode: "check-flow" } : {},
48203
49325
  entries: [...this.#interactiveHistory, ...this.#processTransientHistory.map(({ entry }) => entry)],
48204
49326
  ...this.#outroMessage === void 0 ? {} : { outroMessage: this.#outroMessage },
48205
- treeRoots: this.#treeRoots.map((root) => this.#cloneTreeNode(root))
49327
+ ...hasTerminalDimensions ? { terminalDimensions } : {},
49328
+ treeRoots: this.#treeRoots.map(cloneFlowTreeNode)
49329
+ };
49330
+ }
49331
+ #getTerminalDimensions() {
49332
+ return {
49333
+ columns: this.#stdout?.columns,
49334
+ rows: readPositiveInteger(this.#env[FLOW_RENDERER_TEST_ROWS_ENV]) ?? this.#stdout?.rows
48206
49335
  };
48207
49336
  }
48208
49337
  #sendProcessSnapshot() {
@@ -48210,7 +49339,8 @@ var LiminaFlowReporter = class {
48210
49339
  this.#processRenderer.sendSnapshot(this.#createRenderSnapshot());
48211
49340
  }
48212
49341
  #writeRenderSnapshotInline(snapshot) {
48213
- for (const line of renderSnapshotLines(snapshot, this.#spinnerFrameIndex)) writeLine(this.#output, line);
49342
+ const lines = renderSnapshotLinesForTerminal(snapshot, this.#spinnerFrameIndex, this.#getTerminalDimensions());
49343
+ for (const line of lines) writeLine(this.#output, line);
48214
49344
  }
48215
49345
  intro(message) {
48216
49346
  if (this.#interactive) {
@@ -48227,7 +49357,7 @@ var LiminaFlowReporter = class {
48227
49357
  kind: "line",
48228
49358
  line: `┌ ${message}`
48229
49359
  });
48230
- this.#recordTerminalWrite(`${message}\n`);
49360
+ this.#terminalFrame.record(`${message}\n`);
48231
49361
  return;
48232
49362
  }
48233
49363
  writeLine(this.#output, `[start] ${message}`);
@@ -48239,6 +49369,11 @@ var LiminaFlowReporter = class {
48239
49369
  this.#sendProcessSnapshot();
48240
49370
  return;
48241
49371
  }
49372
+ if (this.#statusOnly) {
49373
+ this.#outroMessage = message;
49374
+ this.#redrawInteractiveHistory();
49375
+ return;
49376
+ }
48242
49377
  this.#clack.outro(message);
48243
49378
  return;
48244
49379
  }
@@ -48249,7 +49384,7 @@ var LiminaFlowReporter = class {
48249
49384
  const collapseOnSuccess = this.#statusOnly ? false : options.collapseOnSuccess ?? true;
48250
49385
  const shouldTrackTask = this.#interactive && collapseOnSuccess;
48251
49386
  const persistStart = !shouldTrackTask && this.#trackedTaskCount === 0;
48252
- const startLine = this.#terminalLineCount;
49387
+ const startLine = this.#terminalFrame.lineCount;
48253
49388
  const startTime = performance.now();
48254
49389
  const processTransientTaskId = shouldTrackTask && this.#processRenderer ? this.#nextProcessTransientTaskId++ : void 0;
48255
49390
  let completed = false;
@@ -48265,11 +49400,7 @@ var LiminaFlowReporter = class {
48265
49400
  };
48266
49401
  return {
48267
49402
  fail: (nextMessage, nextOptions) => {
48268
- const failOptions = {
48269
- ...nextOptions,
48270
- depth,
48271
- elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
48272
- };
49403
+ const failOptions = createTaskFinishOptions(nextOptions, depth, startTime);
48273
49404
  const failMessage = this.#statusOnly ? message : nextMessage ?? message;
48274
49405
  if (!shouldTrackTask && this.#interactive && persistedStartIndex !== void 0) {
48275
49406
  this.#replaceInteractiveHistoryLine(persistedStartIndex, "fail", this.#formatFailureMessage(failMessage, nextOptions), failOptions);
@@ -48278,7 +49409,6 @@ var LiminaFlowReporter = class {
48278
49409
  return;
48279
49410
  }
48280
49411
  this.#emit("fail", this.#formatFailureMessage(failMessage, nextOptions), failOptions, { persistInteractive: true });
48281
- if (!this.#interactive) return;
48282
49412
  finishTrackedTask();
48283
49413
  },
48284
49414
  info: (nextMessage, nextOptions) => {
@@ -48288,11 +49418,7 @@ var LiminaFlowReporter = class {
48288
49418
  });
48289
49419
  },
48290
49420
  pass: (nextMessage, nextOptions) => {
48291
- const passOptions = {
48292
- ...nextOptions,
48293
- depth,
48294
- elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
48295
- };
49421
+ const passOptions = createTaskFinishOptions(nextOptions, depth, startTime);
48296
49422
  const persistInteractive = shouldTrackTask ? this.#trackedTaskCount <= 1 : this.#trackedTaskCount === 0;
48297
49423
  if (shouldTrackTask) if (this.#processRenderer?.active) {
48298
49424
  this.#processTransientHistory = this.#processTransientHistory.filter((entry) => entry.taskId !== processTransientTaskId);
@@ -48308,11 +49434,7 @@ var LiminaFlowReporter = class {
48308
49434
  finishTrackedTask();
48309
49435
  },
48310
49436
  skip: (nextMessage, nextOptions) => {
48311
- const skipOptions = {
48312
- ...nextOptions,
48313
- depth,
48314
- elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
48315
- };
49437
+ const skipOptions = createTaskFinishOptions(nextOptions, depth, startTime);
48316
49438
  if (!shouldTrackTask && this.#interactive && persistedStartIndex !== void 0) {
48317
49439
  this.#replaceInteractiveHistoryLine(persistedStartIndex, "skip", nextMessage ?? message, skipOptions);
48318
49440
  this.#redrawInteractiveHistory();
@@ -48331,12 +49453,7 @@ var LiminaFlowReporter = class {
48331
49453
  };
48332
49454
  }
48333
49455
  tree(message, options = {}) {
48334
- const node = {
48335
- children: [],
48336
- depth: options.depth ?? 0,
48337
- message,
48338
- status: "planned"
48339
- };
49456
+ const node = createFlowTreeNode(message, options.depth ?? 0);
48340
49457
  this.#treeRoots.push(node);
48341
49458
  this.#ensureInteractiveTree();
48342
49459
  this.#renderTreeChange();
@@ -48370,7 +49487,7 @@ var LiminaFlowReporter = class {
48370
49487
  }
48371
49488
  const stream = options.stream === "stderr" ? this.#stderr : this.#stdout;
48372
49489
  if (this.#interactive && this.#tracksProcessWrites && typeof stream?.write === "function") {
48373
- if (this.#restoreWriteStreams === void 0) this.#recordTerminalWrite(message);
49490
+ if (this.#restoreWriteStreams === void 0) this.#terminalFrame.record(message);
48374
49491
  stream.write(message);
48375
49492
  return;
48376
49493
  }
@@ -48437,8 +49554,12 @@ var LiminaFlowReporter = class {
48437
49554
  #beginTerminalTracking() {
48438
49555
  this.#trackedTaskCount += 1;
48439
49556
  if (this.#trackedTaskCount > 1 || !this.#tracksProcessWrites) return;
48440
- const restoreStdout = this.#patchWriteStream(this.#stdout);
48441
- const restoreStderr = this.#patchWriteStream(this.#stderr);
49557
+ const restoreStdout = patchWriteStream(this.#stdout, (chunk) => {
49558
+ this.#terminalFrame.record(chunk);
49559
+ });
49560
+ const restoreStderr = patchWriteStream(this.#stderr, (chunk) => {
49561
+ this.#terminalFrame.record(chunk);
49562
+ });
48442
49563
  this.#restoreWriteStreams = () => {
48443
49564
  restoreStdout?.();
48444
49565
  restoreStderr?.();
@@ -48446,64 +49567,30 @@ var LiminaFlowReporter = class {
48446
49567
  };
48447
49568
  }
48448
49569
  #clearInteractiveTaskBlock(startLine, options = {}) {
48449
- const linesToClear = this.#terminalLineCount - startLine;
49570
+ const linesToClear = this.#terminalFrame.lineCount - startLine;
48450
49571
  if (linesToClear <= 0) return;
48451
49572
  if (options.redrawHistory) {
48452
49573
  this.#redrawInteractiveHistory();
48453
49574
  return;
48454
49575
  }
48455
49576
  this.#writeControl(`\r\u001B[${linesToClear}A\u001B[J`);
48456
- this.#terminalLineCount = startLine;
48457
- this.#terminalColumn = 0;
49577
+ this.#terminalFrame.setLineCount(startLine);
48458
49578
  }
48459
49579
  #endTerminalTracking() {
48460
49580
  this.#trackedTaskCount = Math.max(0, this.#trackedTaskCount - 1);
48461
49581
  if (this.#trackedTaskCount === 0) this.#restoreWriteStreams?.();
48462
49582
  }
48463
- #patchWriteStream(stream) {
48464
- if (typeof stream?.write !== "function") return;
48465
- const originalWrite = stream.write;
48466
- stream.write = (...args) => {
48467
- this.#recordTerminalWrite(args[0]);
48468
- return Reflect.apply(originalWrite, stream, args);
48469
- };
48470
- return () => {
48471
- stream.write = originalWrite;
48472
- };
48473
- }
48474
- #recordTerminalWrite(chunk) {
48475
- const text = stripControlSequences(toWritableText(chunk));
48476
- const columns = Math.max(1, this.#stdout?.columns ?? DEFAULT_TERMINAL_COLUMNS);
48477
- for (const char of text) {
48478
- if (char === "\n") {
48479
- this.#terminalLineCount += 1;
48480
- this.#terminalColumn = 0;
48481
- continue;
48482
- }
48483
- this.#terminalColumn += 1;
48484
- if (this.#terminalColumn >= columns) {
48485
- this.#terminalLineCount += 1;
48486
- this.#terminalColumn = 0;
48487
- }
48488
- }
48489
- }
48490
49583
  #redrawInteractiveHistory() {
48491
49584
  if (this.#processRenderer?.active) {
48492
49585
  this.#sendProcessSnapshot();
48493
49586
  return;
48494
49587
  }
48495
- if (this.#terminalLineCount > 0) this.#writeControl(`\r\u001B[${this.#terminalLineCount}A\u001B[J`);
48496
- this.#terminalLineCount = 0;
48497
- this.#terminalColumn = 0;
48498
- for (const entry of this.#interactiveHistory) {
48499
- const lines = entry.kind === "line" ? [entry.line] : entry.kind === "flow-line" ? [this.#renderInteractiveHistoryFlowLine(entry)] : this.#renderTreeLines();
48500
- for (const line of lines) writeLine({ write: (message) => {
48501
- this.#writeTracked(message, { forceRecord: this.#restoreWriteStreams === void 0 });
48502
- } }, line);
48503
- }
48504
- }
48505
- #renderInteractiveHistoryFlowLine(entry) {
48506
- return this.#formatInteractiveLine(entry.status, formatMessageWithElapsed(entry.message, entry.elapsedTimeMs), entry.depth);
49588
+ if (this.#terminalFrame.lineCount > 0) this.#writeControl(`\r\u001B[${this.#terminalFrame.lineCount}A\u001B[J`);
49589
+ this.#terminalFrame.reset();
49590
+ const frameLines = renderSnapshotLinesForTerminal(this.#createRenderSnapshot(), this.#spinnerFrameIndex, this.#getTerminalDimensions());
49591
+ for (const line of frameLines) writeLine({ write: (message) => {
49592
+ this.#writeTracked(message, { forceRecord: this.#restoreWriteStreams === void 0 });
49593
+ } }, line);
48507
49594
  }
48508
49595
  #formatInteractiveLine(status, message, depth) {
48509
49596
  return formatInteractiveLine(status, message, depth, this.#spinnerFrameIndex);
@@ -48530,10 +49617,12 @@ var LiminaFlowReporter = class {
48530
49617
  #createTreeNodeHandle(node) {
48531
49618
  return {
48532
49619
  child: (message, options = {}) => {
48533
- return this.#createTreeNodeHandle(this.#appendTreeChild(node, message, options, { redraw: true }));
49620
+ const childNode = appendFlowTreeChild(node, message, options.depth ?? node.depth + 1);
49621
+ this.#renderTreeChange();
49622
+ return this.#createTreeNodeHandle(childNode);
48534
49623
  },
48535
49624
  children: (messages, options = {}) => {
48536
- const childNodes = messages.map((message) => this.#appendTreeChild(node, message, options, { redraw: false }));
49625
+ const childNodes = messages.map((message) => appendFlowTreeChild(node, message, options.depth ?? node.depth + 1));
48537
49626
  if (childNodes.length > 0) this.#renderTreeChange();
48538
49627
  return childNodes.map((childNode) => this.#createTreeNodeHandle(childNode));
48539
49628
  },
@@ -48563,25 +49652,13 @@ var LiminaFlowReporter = class {
48563
49652
  }
48564
49653
  };
48565
49654
  }
48566
- #appendTreeChild(parent, message, options, meta) {
48567
- const childNode = {
48568
- children: [],
48569
- depth: options.depth ?? parent.depth + 1,
48570
- message,
48571
- parent,
48572
- status: "planned"
48573
- };
48574
- parent.children.push(childNode);
48575
- if (meta.redraw) this.#renderTreeChange();
48576
- return childNode;
48577
- }
48578
49655
  #ensureInteractiveTree() {
48579
49656
  if (!this.#interactive || this.#hasInteractiveTree) return;
48580
49657
  this.#interactiveHistory.push({ kind: "tree" });
48581
49658
  this.#hasInteractiveTree = true;
48582
49659
  }
48583
49660
  #finishTreeNode(node, status, message, options) {
48584
- this.#skipPlannedTreeDescendants(node);
49661
+ skipPlannedTreeDescendants(node);
48585
49662
  if (message) node.message = status === "failed" ? this.#formatFailureMessage(message, options) : message;
48586
49663
  else if (status === "failed") node.message = this.#formatFailureMessage(node.message, options);
48587
49664
  if (options?.depth !== void 0) node.depth = options.depth;
@@ -48598,12 +49675,6 @@ var LiminaFlowReporter = class {
48598
49675
  }
48599
49676
  this.#renderTreeChange();
48600
49677
  }
48601
- #skipPlannedTreeDescendants(node) {
48602
- for (const child of node.children) {
48603
- if (child.status === "planned") child.status = "skipped";
48604
- this.#skipPlannedTreeDescendants(child);
48605
- }
48606
- }
48607
49678
  #renderTreeChange() {
48608
49679
  if (!this.#interactive) return;
48609
49680
  if (this.#processRenderer?.active) {
@@ -48613,13 +49684,6 @@ var LiminaFlowReporter = class {
48613
49684
  this.#syncSpinnerTimer();
48614
49685
  this.#redrawInteractiveHistory();
48615
49686
  }
48616
- #renderTreeLines() {
48617
- return this.#treeRoots.flatMap((root) => this.#renderTreeNodeLines(root));
48618
- }
48619
- #renderTreeNodeLines(node) {
48620
- const elapsedTimeMs = isTreeNodeTerminal(node) && areTreeNodeDescendantsTerminal(node) ? node.elapsedTimeMs : void 0;
48621
- return [this.#formatInteractiveLine(toTreeFlowStatus(node.status), formatMessageWithElapsed(node.message, elapsedTimeMs), node.depth), ...node.children.flatMap((child) => this.#renderTreeNodeLines(child))];
48622
- }
48623
49687
  #hasRunningInteractiveWork() {
48624
49688
  return hasRunningSnapshotWork(this.#createRenderSnapshot());
48625
49689
  }
@@ -48646,7 +49710,7 @@ var LiminaFlowReporter = class {
48646
49710
  this.#output.write(message);
48647
49711
  }
48648
49712
  #writeTracked(message, options = {}) {
48649
- if (options.forceRecord || !this.#tracksProcessWrites) this.#recordTerminalWrite(message);
49713
+ if (options.forceRecord || !this.#tracksProcessWrites) this.#terminalFrame.record(message);
48650
49714
  this.#output.write(message);
48651
49715
  }
48652
49716
  };
@@ -49549,10 +50613,10 @@ function parsePackageTool(tool) {
49549
50613
  if (tool === "all" || tool === "publint" || tool === "attw" || tool === "boundary") return tool;
49550
50614
  throw new Error(`Invalid package check --tool "${tool}". Expected one of: all, publint, attw, boundary.`);
49551
50615
  }
49552
- function parseBuildPreset(preset) {
50616
+ function parseBuildPreset(preset, commandLabel = "checker build") {
49553
50617
  if (!preset) return;
49554
50618
  if (preset === "tsc" || preset === "vue-tsc" || preset === "tsgo") return preset;
49555
- throw new Error(`Invalid checker build --preset "${preset}". Expected one of: tsc, vue-tsc, tsgo.`);
50619
+ throw new Error(`Invalid ${commandLabel} --preset "${preset}". Expected one of: tsc, vue-tsc, tsgo.`);
49556
50620
  }
49557
50621
  function rejectUnknownCheckerOptions(flags) {
49558
50622
  if (flags.checker !== void 0) throw new Error("Unknown option: --checker. Use --preset instead.");
@@ -49570,9 +50634,25 @@ function rejectUnknownCheckerOptions(flags) {
49570
50634
  ]);
49571
50635
  for (const option of Object.keys(flags)) if (!knownOptions.has(option)) throw new Error(`Unknown option: --${option}.`);
49572
50636
  }
50637
+ function rejectUnknownBuildOptions(flags) {
50638
+ const knownOptions = new Set([
50639
+ "--",
50640
+ "config",
50641
+ "mode",
50642
+ "preset",
50643
+ "raw",
50644
+ "verbose",
50645
+ "w",
50646
+ "watch"
50647
+ ]);
50648
+ for (const option of Object.keys(flags)) if (!knownOptions.has(option)) throw new Error(`Unknown option: --${option}.`);
50649
+ }
49573
50650
  function getCheckerWatchFlag(flags) {
49574
50651
  return flags.watch ?? flags.w;
49575
50652
  }
50653
+ function getBuildWatchFlag(flags) {
50654
+ return flags.watch ?? flags.w;
50655
+ }
49576
50656
  function parsePackageAttwProfile(profile) {
49577
50657
  if (!profile) return;
49578
50658
  if (profile === "strict" || profile === "node16" || profile === "esm-only") return profile;
@@ -49937,6 +51017,35 @@ function createLiminaCli() {
49937
51017
  if (!passed) process.exitCode = 1;
49938
51018
  await closeCliFlow(flow, passed ? "limina source passed" : "limina source failed");
49939
51019
  });
51020
+ cli.command("build <config>", "Build user-facing artifacts").option("--preset <preset>", "Build checker preset: tsc, vue-tsc, or tsgo").option("--raw", "Run the selected checker directly against the config").option("-w, --watch", "Watch input files and rebuild on changes").option("--verbose", "Show full build issue details").allowUnknownOptions().action(async (configPath, flags) => {
51021
+ rejectUnknownBuildOptions(flags);
51022
+ const watch = getBuildWatchFlag(flags);
51023
+ if (flags.raw && !flags.preset) throw new Error("limina build --raw requires --preset.");
51024
+ const checker = parseBuildPreset(flags.preset, "build");
51025
+ const flow = createCliFlow();
51026
+ flow.intro("limina build");
51027
+ const config = await load(flags, "build");
51028
+ await writeNotRunCheckIssueSnapshot({
51029
+ command: "limina build",
51030
+ rootDir: config.rootDir
51031
+ });
51032
+ const result = await runBuild({
51033
+ checker,
51034
+ clearScreen: false,
51035
+ config,
51036
+ configPath,
51037
+ cwd: process.cwd(),
51038
+ flow,
51039
+ raw: flags.raw,
51040
+ report: {
51041
+ command: "limina build",
51042
+ verbose: flags.verbose
51043
+ },
51044
+ watch
51045
+ });
51046
+ if (!result.passed) process.exitCode = 1;
51047
+ await closeCliFlow(flow, result.passed ? "limina build passed" : "limina build failed");
51048
+ });
49940
51049
  cli.command("checker <action> [config]", "Run checker build or typecheck entries").option("--preset <preset>", "Build checker preset: tsc, vue-tsc, or tsgo").option("-w, --watch", "Watch input files and rebuild on changes").option("--verbose", "Show full checker issue details").allowUnknownOptions().action(async (action, configPath, flags) => {
49941
51050
  rejectUnknownCheckerOptions(flags);
49942
51051
  if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected build or typecheck.`);
@@ -49951,19 +51060,12 @@ function createLiminaCli() {
49951
51060
  command: "limina checker build",
49952
51061
  rootDir: config.rootDir
49953
51062
  });
49954
- const result = configPath ? await runBuild({
49955
- checker: parseBuildPreset(flags.preset),
49956
- clearScreen: false,
49957
- config,
49958
- configPath,
49959
- cwd: process.cwd(),
49960
- flow,
49961
- report: {
49962
- command: "limina checker build",
49963
- verbose: flags.verbose
49964
- },
49965
- watch
49966
- }) : await runCheckerBuild({
51063
+ const result = await runCheckerBuild({
51064
+ ...configPath ? {
51065
+ checker: parseBuildPreset(flags.preset),
51066
+ configPath,
51067
+ watch
51068
+ } : {},
49967
51069
  clearScreen: false,
49968
51070
  config,
49969
51071
  cwd: process.cwd(),