limina 0.0.1 → 0.0.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.
@@ -1,20 +1,21 @@
1
- import { c as getCheckerAdapter, h as toRelativePath, l as isPathInsideDirectory, o as collectMissingCheckerPeerDependencies, r as getActiveCheckers, s as formatMissingCheckerPeerDependencies, u as normalizeAbsolutePath } from "./dep-jgc7X0zw.js";
2
- import { builtinModules } from "node:module";
3
- import { createElapsedTimer, formatErrorMessage, formatErrorMessage as formatErrorMessage$1 } from "@docs-islands/logger/helper";
1
+ import { c as getCheckerAdapter, h as toRelativePath, l as isPathInsideDirectory, m as toPosixPath, o as collectMissingCheckerPeerDependencies, r as getActiveCheckers, s as formatMissingCheckerPeerDependencies, u as normalizeAbsolutePath } from "./dep-jgc7X0zw.js";
2
+ import { builtinModules, createRequire } from "node:module";
3
+ import { createElapsedTimer, formatErrorMessage, formatErrorMessage as formatErrorMessage$1 } from "logaria/helper";
4
4
  import { existsSync, readFileSync, statSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import ts from "typescript";
7
7
  import { execFile, spawn } from "node:child_process";
8
8
  import { glob } from "tinyglobby";
9
- import { createLogger } from "@docs-islands/logger";
9
+ import { createLogger } from "logaria";
10
10
  import readline from "node:readline";
11
- import { availableParallelism } from "node:os";
12
11
  import * as prompts from "@clack/prompts";
12
+ import { mkdir, writeFile } from "node:fs/promises";
13
+ import { parse } from "yaml";
14
+ import { availableParallelism } from "node:os";
13
15
 
14
16
  //#region src/tsconfig.ts
15
17
  const dtsConfigFilePattern = /^tsconfig(?:\..+)?\.dts\.json$/u;
16
18
  const buildGraphConfigFilePattern = /^tsconfig(?:\..+)?\.build\.json$/u;
17
- const deprecatedGraphConfigFilePattern = /^tsconfig(?:\..+)?\.graph\.json$/u;
18
19
  const generatedConfigFilePattern = /^tsconfig(?:\..+)?\.paths\.generated\.json$/u;
19
20
  const baseConfigFilePattern = /^tsconfig(?:\..+)?\.base\.json$/u;
20
21
  const checkConfigFilePattern = /^tsconfig(?:\..+)?\.check\.json$/u;
@@ -161,11 +162,8 @@ function getDtsCompanionConfigPath(dtsConfigPath) {
161
162
  function isBuildGraphConfigPath(configPath) {
162
163
  return buildGraphConfigFilePattern.test(path.basename(configPath));
163
164
  }
164
- function isDeprecatedGraphConfigPath(configPath) {
165
- return deprecatedGraphConfigFilePattern.test(path.basename(configPath));
166
- }
167
165
  function isReservedTypeScriptConfigFile(fileName) {
168
- return dtsConfigFilePattern.test(fileName) || buildGraphConfigFilePattern.test(fileName) || deprecatedGraphConfigFilePattern.test(fileName) || generatedConfigFilePattern.test(fileName) || baseConfigFilePattern.test(fileName) || checkConfigFilePattern.test(fileName);
166
+ return dtsConfigFilePattern.test(fileName) || buildGraphConfigFilePattern.test(fileName) || generatedConfigFilePattern.test(fileName) || baseConfigFilePattern.test(fileName) || checkConfigFilePattern.test(fileName);
169
167
  }
170
168
  function isOrdinaryTypecheckConfigPath(configPath) {
171
169
  const fileName = path.basename(configPath);
@@ -269,13 +267,7 @@ function collectGraphProjectRouteFromRoot(options) {
269
267
  }));
270
268
  const formatConfigPath = (configPath) => toRelativePath(options.rootDir, configPath);
271
269
  problems.push(...rootReferences.problems);
272
- if (isDeprecatedGraphConfigPath(rootGraphConfigPath)) problems.push([
273
- "Checker entry uses a deprecated tsconfig name:",
274
- ` config: ${formatConfigPath(rootGraphConfigPath)}`,
275
- " reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
276
- " fix: rename the checker entry to tsconfig*.build.json."
277
- ].join("\n"));
278
- else if (!isBuildGraphConfigPath(rootGraphConfigPath) && !isDtsConfigPath(rootGraphConfigPath)) problems.push([
270
+ if (!isBuildGraphConfigPath(rootGraphConfigPath) && !isDtsConfigPath(rootGraphConfigPath)) problems.push([
279
271
  "Invalid checker entry config:",
280
272
  ` config: ${formatConfigPath(rootGraphConfigPath)}`,
281
273
  " reason: checker entries should point to a tsconfig*.build.json graph aggregator or a direct tsconfig*.dts.json declaration leaf."
@@ -287,17 +279,6 @@ function collectGraphProjectRouteFromRoot(options) {
287
279
  for (const { projectPath } of queue) seen.add(projectPath);
288
280
  for (const { projectPath, rawReferencePath, referrerPath } of queue) {
289
281
  if (!projectPath) continue;
290
- if (isDeprecatedGraphConfigPath(projectPath)) {
291
- problems.push([
292
- "Deprecated checker entry reference:",
293
- ` from: ${formatConfigPath(referrerPath)}`,
294
- ` reference: ${rawReferencePath}`,
295
- ` resolved: ${formatConfigPath(projectPath)}`,
296
- " reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
297
- " fix: rename the referenced config to tsconfig*.build.json and update this reference."
298
- ].join("\n"));
299
- continue;
300
- }
301
282
  if (!existsSync(projectPath)) {
302
283
  problems.push([
303
284
  "Checker entry references a missing tsconfig:",
@@ -343,16 +324,6 @@ function collectGraphProjectRoutes(config) {
343
324
  for (const checker of getActiveCheckers(config)) {
344
325
  if (!getCheckerAdapter(checker.preset)?.graph) continue;
345
326
  const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
346
- if (isDeprecatedGraphConfigPath(rootConfigPath)) {
347
- problems.push([
348
- "Checker graph entry uses a deprecated tsconfig name:",
349
- ` checker: ${checker.name}`,
350
- ` config: ${toRelativePath(config.rootDir, rootConfigPath)}`,
351
- " reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
352
- " fix: rename the checker entry to tsconfig*.build.json."
353
- ].join("\n"));
354
- continue;
355
- }
356
327
  if (!existsSync(rootConfigPath)) {
357
328
  problems.push([
358
329
  "Checker graph entry references a missing tsconfig:",
@@ -382,16 +353,6 @@ function collectCheckerEntryProjectRoutes(config) {
382
353
  const problems = [];
383
354
  for (const checker of getActiveCheckers(config)) {
384
355
  const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
385
- if (isDeprecatedGraphConfigPath(rootConfigPath)) {
386
- problems.push([
387
- "Checker entry uses a deprecated tsconfig name:",
388
- ` checker: ${checker.name}`,
389
- ` config: ${toRelativePath(config.rootDir, rootConfigPath)}`,
390
- " reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
391
- " fix: rename the checker entry to tsconfig*.build.json."
392
- ].join("\n"));
393
- continue;
394
- }
395
356
  if (!existsSync(rootConfigPath)) {
396
357
  problems.push([
397
358
  "Checker entry references a missing tsconfig:",
@@ -441,7 +402,7 @@ function formatReferences(rootDir, references) {
441
402
 
442
403
  //#endregion
443
404
  //#region src/workspace.ts
444
- const pnpmWorkspaceFileName = "pnpm-workspace.yaml";
405
+ const pnpmWorkspaceFileName$1 = "pnpm-workspace.yaml";
445
406
  const pnpmWorkspaceListTimeoutMs = 3e3;
446
407
  function readJsonFile(filePath) {
447
408
  return JSON.parse(readFileSync(filePath, "utf8"));
@@ -488,7 +449,7 @@ function collectWorkspacePatterns(config) {
488
449
  const rootPackageJson = readJsonFile(rootPackageJsonPath);
489
450
  if (Array.isArray(rootPackageJson.workspaces)) for (const pattern of rootPackageJson.workspaces) patterns.add(pattern);
490
451
  }
491
- const workspacePath = path.join(config.rootDir, pnpmWorkspaceFileName);
452
+ const workspacePath = path.join(config.rootDir, pnpmWorkspaceFileName$1);
492
453
  if (existsSync(workspacePath)) for (const pattern of collectPnpmWorkspacePatterns(readFileSync(workspacePath, "utf8"))) patterns.add(pattern);
493
454
  return [...patterns].sort();
494
455
  }
@@ -663,7 +624,7 @@ function collectImporters(config, packages) {
663
624
 
664
625
  //#endregion
665
626
  //#region src/graph-context.ts
666
- function isRelativeSpecifier(specifier) {
627
+ function isRelativeSpecifier$1(specifier) {
667
628
  return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
668
629
  }
669
630
  function isDtsProjectConfig(configPath) {
@@ -779,9 +740,6 @@ function chooseOwningProject(projectPaths) {
779
740
  function findPackageForFile(filePath, packages) {
780
741
  return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
781
742
  }
782
- function isWorkspacePackageFile(filePath, packages) {
783
- return packages.some((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory));
784
- }
785
743
  function findImporterForFile(filePath, importers) {
786
744
  return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
787
745
  }
@@ -834,6 +792,60 @@ function formatUnknownValue(value) {
834
792
  function addRuleEntryConfigProblem(problems, details) {
835
793
  problems.push(["Invalid graph rule config:", ...details].join("\n"));
836
794
  }
795
+ function isUrlOrDataOrFileSpecifier$1(specifier) {
796
+ return specifier.startsWith("data:") || specifier.startsWith("file:") || specifier.startsWith("http:") || specifier.startsWith("https:");
797
+ }
798
+ function isRelativeSpecifier(specifier) {
799
+ return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
800
+ }
801
+ function isPackageImportPattern(name) {
802
+ return name.startsWith("#");
803
+ }
804
+ function matchWildcardPattern(pattern, value) {
805
+ if (pattern === value) return true;
806
+ const wildcardIndex = pattern.indexOf("*");
807
+ if (wildcardIndex === -1) return false;
808
+ const prefix = pattern.slice(0, wildcardIndex);
809
+ const suffix = pattern.slice(wildcardIndex + 1);
810
+ return value.startsWith(prefix) && value.endsWith(suffix);
811
+ }
812
+ function getNodeBuiltinRuleName(name) {
813
+ if (name === "node:*") return {
814
+ matchAllNodeBuiltins: true,
815
+ normalizedName: "*"
816
+ };
817
+ const normalizedName = name.startsWith("node:") ? name.slice(5) : name;
818
+ if (!nodeBuiltinNames.has(normalizedName)) return null;
819
+ return {
820
+ matchAllNodeBuiltins: false,
821
+ normalizedName
822
+ };
823
+ }
824
+ function createNormalizedDep(name, reason) {
825
+ const nodeBuiltin = getNodeBuiltinRuleName(name);
826
+ if (nodeBuiltin) return {
827
+ kind: "node-builtin",
828
+ matchAllNodeBuiltins: nodeBuiltin.matchAllNodeBuiltins,
829
+ name,
830
+ normalizedName: nodeBuiltin.normalizedName,
831
+ reason
832
+ };
833
+ if (isPackageImportPattern(name)) return {
834
+ kind: "package-import",
835
+ matchAllNodeBuiltins: false,
836
+ name,
837
+ normalizedName: name,
838
+ reason
839
+ };
840
+ if (isRelativeSpecifier(name) || isUrlOrDataOrFileSpecifier$1(name) || path.isAbsolute(name) || getPackageRootSpecifier(name) !== name) return null;
841
+ return {
842
+ kind: "package",
843
+ matchAllNodeBuiltins: false,
844
+ name,
845
+ normalizedName: name,
846
+ reason
847
+ };
848
+ }
837
849
  function getRulesRecord(config, problems) {
838
850
  const rules = config.graph?.rules;
839
851
  if (rules === void 0) return {};
@@ -900,57 +912,13 @@ function addNormalizedRuleRef(options) {
900
912
  });
901
913
  options.refsByLabel.set(options.label, refs);
902
914
  }
903
- function addNormalizedWorkspaceDep(options) {
904
- const field = `${options.fieldPrefix}[${options.index}]`;
905
- if (!isPlainRecord(options.entry)) {
906
- addRuleEntryConfigProblem(options.problems, [
907
- ` field: ${field}`,
908
- ` value: ${formatUnknownValue(options.entry)}`,
909
- " reason: deny workspace dependency entries must be objects with non-empty name and reason fields."
910
- ]);
911
- return;
912
- }
913
- const nameValue = options.entry.name;
914
- const reasonValue = options.entry.reason;
915
- if (!isNonEmptyString(nameValue)) {
916
- addRuleEntryConfigProblem(options.problems, [
917
- ` field: ${field}.name`,
918
- ` value: ${formatUnknownValue(nameValue)}`,
919
- " reason: workspace dependency name is required and must be a non-empty string."
920
- ]);
921
- return;
922
- }
923
- if (!isNonEmptyString(reasonValue)) {
924
- addRuleEntryConfigProblem(options.problems, [
925
- ` field: ${field}.reason`,
926
- ` value: ${formatUnknownValue(reasonValue)}`,
927
- " reason: workspace dependency reason is required and must be a non-empty string."
928
- ]);
929
- return;
930
- }
931
- const packageName = nameValue.trim();
932
- if (!options.packageNames.has(packageName)) {
933
- addRuleEntryConfigProblem(options.problems, [
934
- ` field: ${field}.name`,
935
- ` name: ${packageName}`,
936
- " reason: deny.workspaceDeps and legacy deny.deps only accept discovered workspace package names. Use deny.nodeBuiltins for Node builtins."
937
- ]);
938
- return;
939
- }
940
- const deps = options.workspaceDepsByLabel.get(options.label) ?? /* @__PURE__ */ new Map();
941
- deps.set(packageName, {
942
- name: packageName,
943
- reason: reasonValue.trim()
944
- });
945
- options.workspaceDepsByLabel.set(options.label, deps);
946
- }
947
- function addNormalizedNodeBuiltin(options) {
948
- const field = `graph.rules.${options.label}.deny.nodeBuiltins[${options.index}]`;
915
+ function addNormalizedDep(options) {
916
+ const field = `graph.rules.${options.label}.deny.deps[${options.index}]`;
949
917
  if (!isPlainRecord(options.entry)) {
950
918
  addRuleEntryConfigProblem(options.problems, [
951
919
  ` field: ${field}`,
952
920
  ` value: ${formatUnknownValue(options.entry)}`,
953
- " reason: deny.nodeBuiltins entries must be objects with non-empty name and reason fields."
921
+ " reason: deny.deps entries must be objects with non-empty name and reason fields."
954
922
  ]);
955
923
  return;
956
924
  }
@@ -960,7 +928,7 @@ function addNormalizedNodeBuiltin(options) {
960
928
  addRuleEntryConfigProblem(options.problems, [
961
929
  ` field: ${field}.name`,
962
930
  ` value: ${formatUnknownValue(nameValue)}`,
963
- " reason: deny.nodeBuiltins name is required and must be a non-empty string."
931
+ " reason: deny.deps name is required and must be a non-empty string."
964
932
  ]);
965
933
  return;
966
934
  }
@@ -968,38 +936,31 @@ function addNormalizedNodeBuiltin(options) {
968
936
  addRuleEntryConfigProblem(options.problems, [
969
937
  ` field: ${field}.reason`,
970
938
  ` value: ${formatUnknownValue(reasonValue)}`,
971
- " reason: deny.nodeBuiltins reason is required and must be a non-empty string."
939
+ " reason: deny.deps reason is required and must be a non-empty string."
972
940
  ]);
973
941
  return;
974
942
  }
975
943
  const name = nameValue.trim();
976
- const normalizedName = name.startsWith("node:") ? name.slice(5) : name;
977
- const matchAll = name === "node:*";
978
- if (!matchAll && !nodeBuiltinNames.has(normalizedName)) {
944
+ const normalizedDep = createNormalizedDep(name, reasonValue.trim());
945
+ if (!normalizedDep) {
979
946
  addRuleEntryConfigProblem(options.problems, [
980
947
  ` field: ${field}.name`,
981
948
  ` name: ${name}`,
982
- " reason: deny.nodeBuiltins name must be \"node:*\" or a Node builtin specifier such as \"fs\" or \"node:fs\"."
949
+ " reason: deny.deps name must be a package root, a package.json imports specifier such as \"#internal/*\", or a Node builtin such as \"fs\", \"node:fs\", or \"node:*\"."
983
950
  ]);
984
951
  return;
985
952
  }
986
- const entries = options.nodeBuiltinsByLabel.get(options.label) ?? [];
987
- entries.push({
988
- matchAll,
989
- name: normalizedName,
990
- reason: reasonValue.trim()
991
- });
992
- options.nodeBuiltinsByLabel.set(options.label, entries);
953
+ const deps = options.depsByLabel.get(options.label) ?? [];
954
+ deps.push(normalizedDep);
955
+ options.depsByLabel.set(options.label, deps);
993
956
  }
994
957
  function shouldNormalizeRuleKind(include, kind) {
995
958
  return include?.[kind] ?? true;
996
959
  }
997
960
  function normalizeGraphRules(options) {
961
+ const depsByLabel = /* @__PURE__ */ new Map();
998
962
  const refsByLabel = /* @__PURE__ */ new Map();
999
- const workspaceDepsByLabel = /* @__PURE__ */ new Map();
1000
- const nodeBuiltinsByLabel = /* @__PURE__ */ new Map();
1001
963
  const projectPathSet = new Set(options.projectPaths);
1002
- const packageNames = new Set(options.packages.map((workspacePackage) => workspacePackage.name));
1003
964
  for (const [rawLabel, rawRule] of Object.entries(getRulesRecord(options.config, options.problems))) {
1004
965
  const label = rawLabel.trim();
1005
966
  if (!label) {
@@ -1040,60 +1001,35 @@ function normalizeGraphRules(options) {
1040
1001
  refsByLabel
1041
1002
  });
1042
1003
  });
1043
- const legacyDeps = rawRule.deny.deps;
1044
- if (shouldNormalizeRuleKind(options.include, "workspaceDeps") && legacyDeps !== void 0) if (!Array.isArray(legacyDeps)) addRuleEntryConfigProblem(options.problems, [
1045
- ` field: graph.rules.${label}.deny.deps`,
1046
- ` value: ${formatUnknownValue(legacyDeps)}`,
1047
- " reason: deny.deps must be an array."
1048
- ]);
1049
- else legacyDeps.forEach((entry, index) => {
1050
- addNormalizedWorkspaceDep({
1051
- entry,
1052
- fieldPrefix: `graph.rules.${label}.deny.deps`,
1053
- index,
1054
- label,
1055
- packageNames,
1056
- problems: options.problems,
1057
- workspaceDepsByLabel
1058
- });
1059
- });
1060
- const workspaceDeps = rawRule.deny.workspaceDeps;
1061
- if (shouldNormalizeRuleKind(options.include, "workspaceDeps") && workspaceDeps !== void 0) if (!Array.isArray(workspaceDeps)) addRuleEntryConfigProblem(options.problems, [
1004
+ if (shouldNormalizeRuleKind(options.include, "deps") && rawRule.deny.workspaceDeps !== void 0) addRuleEntryConfigProblem(options.problems, [
1062
1005
  ` field: graph.rules.${label}.deny.workspaceDeps`,
1063
- ` value: ${formatUnknownValue(workspaceDeps)}`,
1064
- " reason: deny.workspaceDeps must be an array."
1006
+ ` value: ${formatUnknownValue(rawRule.deny.workspaceDeps)}`,
1007
+ " reason: deny.workspaceDeps has been removed; use deny.deps."
1065
1008
  ]);
1066
- else workspaceDeps.forEach((entry, index) => {
1067
- addNormalizedWorkspaceDep({
1068
- entry,
1069
- fieldPrefix: `graph.rules.${label}.deny.workspaceDeps`,
1070
- index,
1071
- label,
1072
- packageNames,
1073
- problems: options.problems,
1074
- workspaceDepsByLabel
1075
- });
1076
- });
1077
- const nodeBuiltins = rawRule.deny.nodeBuiltins;
1078
- if (shouldNormalizeRuleKind(options.include, "nodeBuiltins") && nodeBuiltins !== void 0) if (!Array.isArray(nodeBuiltins)) addRuleEntryConfigProblem(options.problems, [
1009
+ if (shouldNormalizeRuleKind(options.include, "deps") && rawRule.deny.nodeBuiltins !== void 0) addRuleEntryConfigProblem(options.problems, [
1079
1010
  ` field: graph.rules.${label}.deny.nodeBuiltins`,
1080
- ` value: ${formatUnknownValue(nodeBuiltins)}`,
1081
- " reason: deny.nodeBuiltins must be an array."
1011
+ ` value: ${formatUnknownValue(rawRule.deny.nodeBuiltins)}`,
1012
+ " reason: deny.nodeBuiltins has been removed; use deny.deps."
1082
1013
  ]);
1083
- else nodeBuiltins.forEach((entry, index) => {
1084
- addNormalizedNodeBuiltin({
1014
+ const deps = rawRule.deny.deps;
1015
+ if (shouldNormalizeRuleKind(options.include, "deps") && deps !== void 0) if (!Array.isArray(deps)) addRuleEntryConfigProblem(options.problems, [
1016
+ ` field: graph.rules.${label}.deny.deps`,
1017
+ ` value: ${formatUnknownValue(deps)}`,
1018
+ " reason: deny.deps must be an array."
1019
+ ]);
1020
+ else deps.forEach((entry, index) => {
1021
+ addNormalizedDep({
1022
+ depsByLabel,
1085
1023
  entry,
1086
1024
  index,
1087
1025
  label,
1088
- nodeBuiltinsByLabel,
1089
1026
  problems: options.problems
1090
1027
  });
1091
1028
  });
1092
1029
  }
1093
1030
  return {
1094
- nodeBuiltinsByLabel,
1095
- refsByLabel,
1096
- workspaceDepsByLabel
1031
+ depsByLabel,
1032
+ refsByLabel
1097
1033
  };
1098
1034
  }
1099
1035
  function isNodeBuiltinSpecifier(specifier) {
@@ -1103,14 +1039,24 @@ function getDeniedRefRule(rules, label, targetProjectPath) {
1103
1039
  if (!label) return null;
1104
1040
  return rules.refsByLabel.get(label)?.get(targetProjectPath) ?? null;
1105
1041
  }
1106
- function getDeniedWorkspaceDepRule(rules, label, targetPackageName) {
1107
- if (!label) return null;
1108
- return rules.workspaceDepsByLabel.get(label)?.get(targetPackageName) ?? null;
1109
- }
1110
- function getDeniedNodeBuiltinRule(rules, label, specifier) {
1111
- if (!label || !isNodeBuiltinSpecifier(specifier)) return null;
1112
- const normalizedSpecifier = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
1113
- return rules.nodeBuiltinsByLabel.get(label)?.find((rule) => rule.matchAll || rule.name === normalizedSpecifier) ?? null;
1042
+ function getRuleDeps(rules, label) {
1043
+ if (!label) return [];
1044
+ return rules.depsByLabel.get(label) ?? [];
1045
+ }
1046
+ function getDeniedDepRuleForPackage(rules, label, packageName) {
1047
+ return getRuleDeps(rules, label).find((rule) => rule.kind === "package" && rule.normalizedName === packageName) ?? null;
1048
+ }
1049
+ function getDeniedDepRuleForSpecifier(rules, label, specifier) {
1050
+ const deps = getRuleDeps(rules, label);
1051
+ const packageImportRule = deps.find((rule) => rule.kind === "package-import" && matchWildcardPattern(rule.normalizedName, specifier));
1052
+ if (packageImportRule) return packageImportRule;
1053
+ if (isNodeBuiltinSpecifier(specifier)) {
1054
+ const normalizedSpecifier = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
1055
+ const nodeRule = deps.find((rule) => rule.kind === "node-builtin" && (rule.matchAllNodeBuiltins || rule.normalizedName === normalizedSpecifier));
1056
+ if (nodeRule) return nodeRule;
1057
+ }
1058
+ if (isRelativeSpecifier(specifier) || isPackageImportPattern(specifier) || isUrlOrDataOrFileSpecifier$1(specifier) || path.isAbsolute(specifier)) return null;
1059
+ return getDeniedDepRuleForPackage(rules, label, getPackageRootSpecifier(specifier));
1114
1060
  }
1115
1061
 
1116
1062
  //#endregion
@@ -1118,6 +1064,7 @@ function getDeniedNodeBuiltinRule(rules, label, specifier) {
1118
1064
  const logger = createLogger({ main: "limina" });
1119
1065
  const CliLogger = logger.getLoggerByGroup("task.cli");
1120
1066
  const GraphLogger = logger.getLoggerByGroup("task.graph");
1067
+ const InitLogger = logger.getLoggerByGroup("task.init");
1121
1068
  const PackageLogger = logger.getLoggerByGroup("task.package");
1122
1069
  const PathsLogger = logger.getLoggerByGroup("task.paths");
1123
1070
  const ProofLogger = logger.getLoggerByGroup("task.proof");
@@ -1132,6 +1079,541 @@ function clearCliScreen() {
1132
1079
  readline.clearScreenDown(process.stdout);
1133
1080
  }
1134
1081
 
1082
+ //#endregion
1083
+ //#region src/commands/init.ts
1084
+ const pnpmWorkspaceFileName = "pnpm-workspace.yaml";
1085
+ const liminaConfigFileName = "limina.config.mjs";
1086
+ const liminaCheckScriptName = "limina:check";
1087
+ const liminaCheckScriptValue = "limina check";
1088
+ const ignoredGlobPatterns = [
1089
+ "**/.git/**",
1090
+ "**/.limina/**",
1091
+ "**/.pnpm-store/**",
1092
+ "**/.tsbuild/**",
1093
+ "**/coverage/**",
1094
+ "**/dist/**",
1095
+ "**/node_modules/**"
1096
+ ];
1097
+ function findPnpmWorkspaceRoot(startDir) {
1098
+ let currentDir = path.resolve(startDir);
1099
+ while (true) {
1100
+ if (existsSync(path.join(currentDir, pnpmWorkspaceFileName))) return normalizeAbsolutePath(currentDir);
1101
+ const parentDir = path.dirname(currentDir);
1102
+ if (parentDir === currentDir) return null;
1103
+ currentDir = parentDir;
1104
+ }
1105
+ }
1106
+ function createInitConfig(rootDir) {
1107
+ return {
1108
+ configPath: path.join(rootDir, liminaConfigFileName),
1109
+ rootDir
1110
+ };
1111
+ }
1112
+ function formatConfigPath(rootDir, configPath) {
1113
+ return toRelativePath(rootDir, configPath);
1114
+ }
1115
+ function formatReferencePath(fromConfigPath, toConfigPath) {
1116
+ const relativePath = toPosixPath(path.relative(path.dirname(fromConfigPath), toConfigPath));
1117
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
1118
+ }
1119
+ function stringifyJson(value) {
1120
+ return `${JSON.stringify(value, null, 2)}\n`;
1121
+ }
1122
+ function getProjectScope(configPath) {
1123
+ const fileName = path.basename(configPath);
1124
+ if (fileName === "tsconfig.json") return "tsconfig";
1125
+ return /^tsconfig\.(.+)\.json$/u.exec(fileName)?.[1] ?? "tsconfig";
1126
+ }
1127
+ function getDtsConfigPath(configPath) {
1128
+ const directory = path.dirname(configPath);
1129
+ const fileName = path.basename(configPath);
1130
+ const dtsFileName = fileName === "tsconfig.json" ? "tsconfig.dts.json" : fileName.replace(/\.json$/u, ".dts.json");
1131
+ return path.join(directory, dtsFileName);
1132
+ }
1133
+ function hasReferences(configObject) {
1134
+ return Array.isArray(configObject.references) && configObject.references.length > 0;
1135
+ }
1136
+ function parseTypeScriptConfig(rootDir, configPath) {
1137
+ const diagnostics = [];
1138
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
1139
+ ...ts.sys,
1140
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
1141
+ diagnostics.push(diagnostic);
1142
+ }
1143
+ });
1144
+ if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
1145
+ getCanonicalFileName: (fileName) => fileName,
1146
+ getCurrentDirectory: () => rootDir,
1147
+ getNewLine: () => "\n"
1148
+ }));
1149
+ if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
1150
+ getCanonicalFileName: (fileName) => fileName,
1151
+ getCurrentDirectory: () => rootDir,
1152
+ getNewLine: () => "\n"
1153
+ }));
1154
+ return {
1155
+ fileNames: parsed.fileNames.map(normalizeAbsolutePath),
1156
+ options: parsed.options
1157
+ };
1158
+ }
1159
+ function findNearestWorkspacePackage(filePath, packages) {
1160
+ return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
1161
+ }
1162
+ function collectWorkspaceDependencyNames(manifest) {
1163
+ const dependencyNames = /* @__PURE__ */ new Set();
1164
+ for (const dependencies of getDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) if (isWorkspaceDependencySpecifier(specifier)) dependencyNames.add(dependencyName);
1165
+ return dependencyNames;
1166
+ }
1167
+ function findOwningProjectForFile(filePath, projects) {
1168
+ const normalizedFilePath = normalizeAbsolutePath(filePath);
1169
+ return projects.filter((project) => project.fileNames.includes(normalizedFilePath)).sort((left, right) => {
1170
+ const depthDelta = path.dirname(right.configPath).length - path.dirname(left.configPath).length;
1171
+ return depthDelta === 0 ? left.configPath.localeCompare(right.configPath) : depthDelta;
1172
+ })[0] ?? null;
1173
+ }
1174
+ function isDirectory(filePath) {
1175
+ try {
1176
+ return statSync(filePath).isDirectory();
1177
+ } catch {
1178
+ return false;
1179
+ }
1180
+ }
1181
+ function mapVirtualWorkspacePath(filePath, packageByName) {
1182
+ const segments = normalizeAbsolutePath(filePath).split("/");
1183
+ for (let index = 0; index < segments.length; index += 1) {
1184
+ if (segments[index] !== "node_modules") continue;
1185
+ const firstPackageSegment = segments[index + 1];
1186
+ if (!firstPackageSegment) continue;
1187
+ const isScopedPackage = firstPackageSegment.startsWith("@");
1188
+ const packageName = isScopedPackage ? `${firstPackageSegment}/${segments[index + 2] ?? ""}` : firstPackageSegment;
1189
+ const restStart = index + (isScopedPackage ? 3 : 2);
1190
+ const workspacePackage = packageByName.get(packageName);
1191
+ if (!workspacePackage) continue;
1192
+ return path.join(workspacePackage.directory, ...segments.slice(restStart));
1193
+ }
1194
+ return null;
1195
+ }
1196
+ function isVirtualWorkspaceDirectory(directoryPath, packageByName) {
1197
+ const segments = normalizeAbsolutePath(directoryPath).split("/");
1198
+ for (let index = 0; index < segments.length; index += 1) {
1199
+ if (segments[index] !== "node_modules") continue;
1200
+ const firstPackageSegment = segments[index + 1];
1201
+ if (!firstPackageSegment) return packageByName.size > 0;
1202
+ if (firstPackageSegment.startsWith("@") && segments[index + 2] === void 0) return [...packageByName.keys()].some((packageName) => packageName.startsWith(`${firstPackageSegment}/`));
1203
+ }
1204
+ const mappedPath = mapVirtualWorkspacePath(directoryPath, packageByName);
1205
+ return mappedPath ? isDirectory(mappedPath) : false;
1206
+ }
1207
+ function createWorkspaceModuleResolutionHost(options) {
1208
+ const mapPath = (filePath) => mapVirtualWorkspacePath(filePath, options.packageByName) ?? filePath;
1209
+ return {
1210
+ directoryExists: (directoryPath) => isVirtualWorkspaceDirectory(directoryPath, options.packageByName) || (ts.sys.directoryExists?.(directoryPath) ?? isDirectory(directoryPath)),
1211
+ fileExists: (filePath) => {
1212
+ const mappedPath = mapPath(filePath);
1213
+ return existsSync(mappedPath) && !isDirectory(mappedPath);
1214
+ },
1215
+ getCurrentDirectory: () => options.rootDir,
1216
+ readFile: (filePath) => ts.sys.readFile(mapPath(filePath)),
1217
+ realpath: (filePath) => normalizeAbsolutePath(mapPath(filePath)),
1218
+ useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames
1219
+ };
1220
+ }
1221
+ function resolveImportWithTypeScript(options) {
1222
+ const resolvedModule = ts.resolveModuleName(options.importRecord.specifier, options.importRecord.filePath, options.project.options, options.host, options.cache).resolvedModule;
1223
+ if (!resolvedModule?.resolvedFileName) return null;
1224
+ return normalizeAbsolutePath(mapVirtualWorkspacePath(resolvedModule.resolvedFileName, options.packageByName) ?? resolvedModule.resolvedFileName);
1225
+ }
1226
+ function isPackageImportSpecifier$1(specifier) {
1227
+ return specifier.startsWith("#");
1228
+ }
1229
+ function createDtsConfig(project) {
1230
+ const fileName = path.basename(project.configPath);
1231
+ const scope = project.scope;
1232
+ const output = {
1233
+ $schema: "https://json.schemastore.org/tsconfig",
1234
+ extends: [`./${fileName}`],
1235
+ compilerOptions: {
1236
+ composite: true,
1237
+ incremental: true,
1238
+ noEmit: false,
1239
+ declaration: true,
1240
+ emitDeclarationOnly: true,
1241
+ declarationMap: false,
1242
+ rootDir: ".",
1243
+ outDir: "./.limina",
1244
+ tsBuildInfoFile: `./.limina/${scope}.tsbuildinfo`
1245
+ }
1246
+ };
1247
+ if (project.references.length > 0) output.references = project.references.map((referencePath) => ({ path: referencePath }));
1248
+ return output;
1249
+ }
1250
+ function createBuildAggregator(references) {
1251
+ return {
1252
+ $schema: "https://json.schemastore.org/tsconfig",
1253
+ files: [],
1254
+ references: references.map((referencePath) => ({ path: referencePath }))
1255
+ };
1256
+ }
1257
+ function createLiminaConfigContent() {
1258
+ return `import { defineConfig } from 'limina';
1259
+
1260
+ export default defineConfig({
1261
+ // Shared checker entries used by graph, proof, paths, and typecheck checks.
1262
+ config: {
1263
+ checkers: {
1264
+ typescript: {
1265
+ preset: 'tsc',
1266
+ entry: 'tsconfig.build.json',
1267
+ },
1268
+ },
1269
+ },
1270
+ });
1271
+ `;
1272
+ }
1273
+ async function confirmAction(options, message) {
1274
+ if (options.yes) return true;
1275
+ if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error(`${message} Run limina init --yes to accept the default confirmation in non-interactive environments.`);
1276
+ const result = await prompts.confirm({
1277
+ initialValue: true,
1278
+ message
1279
+ });
1280
+ if (prompts.isCancel(result)) throw new Error("limina init canceled.");
1281
+ return result;
1282
+ }
1283
+ async function writeTextFile(filePath, content, writtenFiles) {
1284
+ await mkdir(path.dirname(filePath), { recursive: true });
1285
+ await writeFile(filePath, content);
1286
+ writtenFiles.push(filePath);
1287
+ }
1288
+ async function writeBuildAggregatorFile(options) {
1289
+ if (options.references.length === 0) return false;
1290
+ await writeTextFile(options.configPath, stringifyJson(createBuildAggregator(options.references)), options.writtenFiles);
1291
+ return true;
1292
+ }
1293
+ function findPnpmWorkspacePath(startDir) {
1294
+ const rootDir = findPnpmWorkspaceRoot(startDir);
1295
+ return rootDir ? path.join(rootDir, pnpmWorkspaceFileName) : null;
1296
+ }
1297
+ function resolveCatalogRange(range, packageName, packageManifestPath) {
1298
+ if (!range) return null;
1299
+ if (!range.startsWith("catalog:")) return range;
1300
+ const workspacePath = findPnpmWorkspacePath(path.dirname(packageManifestPath));
1301
+ if (!workspacePath || !existsSync(workspacePath)) return null;
1302
+ const parsed = parse(readFileSync(workspacePath, "utf8"));
1303
+ const catalogName = range.slice(8);
1304
+ if (catalogName.length === 0 || catalogName === "default") return parsed?.catalog?.[packageName] ?? null;
1305
+ return parsed?.catalogs?.[catalogName]?.[packageName] ?? null;
1306
+ }
1307
+ function readLiminaPackageMetadata() {
1308
+ const manifestPath = createRequire(import.meta.url).resolve("limina/package.json");
1309
+ const manifest = readJsonFile(manifestPath);
1310
+ const versionRange = manifest.version ? `^${manifest.version}` : "^0.0.1";
1311
+ const rawTypeScriptRange = manifest.peerDependencies?.typescript ?? manifest.devDependencies?.typescript ?? manifest.dependencies?.typescript;
1312
+ return {
1313
+ typescriptRange: resolveCatalogRange(rawTypeScriptRange, "typescript", manifestPath) ?? rawTypeScriptRange ?? "^5.9.0",
1314
+ versionRange
1315
+ };
1316
+ }
1317
+ function hasDependency(manifest, dependencyName) {
1318
+ return Boolean(manifest.dependencies?.[dependencyName] || manifest.devDependencies?.[dependencyName] || manifest.optionalDependencies?.[dependencyName] || manifest.peerDependencies?.[dependencyName]);
1319
+ }
1320
+ async function updateRootPackageJson(options) {
1321
+ const packageJsonPath = path.join(options.rootDir, "package.json");
1322
+ let installRequired = false;
1323
+ if (!existsSync(packageJsonPath)) {
1324
+ if (!await confirmAction(options.prompt, `No package.json found at ${formatConfigPath(options.rootDir, packageJsonPath)}. Create one?`)) {
1325
+ options.skippedFiles.push(packageJsonPath);
1326
+ return false;
1327
+ }
1328
+ await writeTextFile(packageJsonPath, stringifyJson({
1329
+ private: true,
1330
+ type: "module",
1331
+ scripts: { [liminaCheckScriptName]: liminaCheckScriptValue },
1332
+ devDependencies: {
1333
+ limina: options.metadata.versionRange,
1334
+ typescript: options.metadata.typescriptRange
1335
+ }
1336
+ }), options.writtenFiles);
1337
+ return true;
1338
+ }
1339
+ const manifest = readJsonFile(packageJsonPath);
1340
+ const scripts = { ...manifest.scripts ?? {} };
1341
+ let changed = false;
1342
+ if (scripts[liminaCheckScriptName] && scripts[liminaCheckScriptName] !== liminaCheckScriptValue) {
1343
+ if (await confirmAction(options.prompt, `Script "${liminaCheckScriptName}" already exists in package.json. Overwrite it?`)) {
1344
+ scripts[liminaCheckScriptName] = liminaCheckScriptValue;
1345
+ changed = true;
1346
+ }
1347
+ } else if (!scripts[liminaCheckScriptName]) {
1348
+ scripts[liminaCheckScriptName] = liminaCheckScriptValue;
1349
+ changed = true;
1350
+ }
1351
+ if (!hasDependency(manifest, "limina")) {
1352
+ manifest.devDependencies = {
1353
+ ...manifest.devDependencies ?? {},
1354
+ limina: options.metadata.versionRange
1355
+ };
1356
+ installRequired = true;
1357
+ changed = true;
1358
+ }
1359
+ if (changed) await writeTextFile(packageJsonPath, stringifyJson({
1360
+ ...manifest,
1361
+ scripts
1362
+ }), options.writtenFiles);
1363
+ return installRequired;
1364
+ }
1365
+ async function collectReservedConfigConflicts(rootDir) {
1366
+ const conflicts = await glob([
1367
+ "tsconfig*.build.json",
1368
+ "**/tsconfig*.build.json",
1369
+ "tsconfig*.dts.json",
1370
+ "**/tsconfig*.dts.json"
1371
+ ], {
1372
+ absolute: false,
1373
+ cwd: rootDir,
1374
+ ignore: ignoredGlobPatterns
1375
+ });
1376
+ return [...new Set(conflicts)].sort();
1377
+ }
1378
+ async function collectOrdinaryTsconfigPaths(rootDir) {
1379
+ const configPaths = await glob(["tsconfig*.json", "**/tsconfig*.json"], {
1380
+ absolute: false,
1381
+ cwd: rootDir,
1382
+ ignore: ignoredGlobPatterns
1383
+ });
1384
+ return [...new Set(configPaths)].map((configPath) => normalizeAbsolutePath(path.join(rootDir, configPath))).filter(isOrdinaryTypecheckConfigPath).sort();
1385
+ }
1386
+ function analyzeTypecheckProjects(options) {
1387
+ const problems = [];
1388
+ const projects = [];
1389
+ for (const configPath of options.configPaths) {
1390
+ const configObject = readJsonConfig(options.config, configPath);
1391
+ const parsed = parseTypeScriptConfig(options.config.rootDir, configPath);
1392
+ const hasProjectReferences = hasReferences(configObject);
1393
+ const fileName = path.basename(configPath);
1394
+ if (fileName === "tsconfig.json" && hasProjectReferences && parsed.fileNames.length > 0) {
1395
+ problems.push([
1396
+ "Invalid tsconfig role:",
1397
+ ` config: ${formatConfigPath(options.config.rootDir, configPath)}`,
1398
+ " reason: tsconfig.json must be either a pure aggregator with files: [] and references, or a typecheck leaf with source files, but not both."
1399
+ ].join("\n"));
1400
+ continue;
1401
+ }
1402
+ if (fileName !== "tsconfig.json" && hasProjectReferences) {
1403
+ problems.push([
1404
+ "Invalid scoped tsconfig role:",
1405
+ ` config: ${formatConfigPath(options.config.rootDir, configPath)}`,
1406
+ " reason: tsconfig.<scope>.json files may only be typecheck leaves; graph aggregation belongs in tsconfig*.build.json."
1407
+ ].join("\n"));
1408
+ continue;
1409
+ }
1410
+ if (fileName === "tsconfig.json" && hasProjectReferences) continue;
1411
+ projects.push({
1412
+ configPath,
1413
+ dtsConfigPath: getDtsConfigPath(configPath),
1414
+ fileNames: parsed.fileNames,
1415
+ options: parsed.options,
1416
+ owner: findNearestWorkspacePackage(configPath, options.workspacePackages),
1417
+ references: [],
1418
+ scope: getProjectScope(configPath)
1419
+ });
1420
+ }
1421
+ return {
1422
+ problems,
1423
+ projects
1424
+ };
1425
+ }
1426
+ function inferProjectReferences(options) {
1427
+ const problems = [];
1428
+ const packageByName = new Map(options.workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]));
1429
+ const host = createWorkspaceModuleResolutionHost({
1430
+ packageByName,
1431
+ rootDir: options.config.rootDir
1432
+ });
1433
+ for (const project of options.projects) {
1434
+ if (!project.owner) continue;
1435
+ const workspaceDependencyNames = collectWorkspaceDependencyNames(project.owner.manifest);
1436
+ const resolutionCache = ts.createModuleResolutionCache(path.dirname(project.configPath), (fileName) => fileName, project.options);
1437
+ const referencePaths = /* @__PURE__ */ new Set();
1438
+ for (const fileName of project.fileNames) {
1439
+ if (!/\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)) continue;
1440
+ for (const importRecord of collectImportsFromFile(fileName)) {
1441
+ const resolvedFilePath = resolveImportWithTypeScript({
1442
+ cache: resolutionCache,
1443
+ host,
1444
+ importRecord,
1445
+ packageByName,
1446
+ project
1447
+ });
1448
+ const packageName = isRelativeSpecifier$1(importRecord.specifier) || isPackageImportSpecifier$1(importRecord.specifier) ? null : getPackageRootSpecifier(importRecord.specifier);
1449
+ const targetPackage = packageName ? packageByName.get(packageName) : null;
1450
+ const isWorkspaceGraphDependency = targetPackage && (targetPackage.name === project.owner.name || workspaceDependencyNames.has(targetPackage.name));
1451
+ if (packageName && workspaceDependencyNames.has(packageName) && !targetPackage) {
1452
+ problems.push([
1453
+ "Workspace dependency was not discovered by pnpm:",
1454
+ ` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
1455
+ ` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
1456
+ ` imported specifier: ${importRecord.specifier}`,
1457
+ ` package: ${packageName}`,
1458
+ " reason: package.json declares this dependency with the workspace: protocol, but limina init could not find a matching workspace package."
1459
+ ].join("\n"));
1460
+ continue;
1461
+ }
1462
+ if (targetPackage && !isWorkspaceGraphDependency) continue;
1463
+ if (!resolvedFilePath) {
1464
+ if (targetPackage && isWorkspaceGraphDependency) problems.push([
1465
+ "Unable to resolve workspace import with TypeScript:",
1466
+ ` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
1467
+ ` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
1468
+ ` imported specifier: ${importRecord.specifier}`,
1469
+ ` package: ${targetPackage.name}`,
1470
+ " reason: workspace:* imports must resolve with the project TypeScript compilerOptions before limina init can generate project references."
1471
+ ].join("\n"));
1472
+ continue;
1473
+ }
1474
+ if (isRelativeSpecifier$1(importRecord.specifier)) {
1475
+ const sourcePackage = findNearestWorkspacePackage(importRecord.filePath, options.workspacePackages);
1476
+ const resolvedPackage = findNearestWorkspacePackage(resolvedFilePath, options.workspacePackages);
1477
+ if (sourcePackage && resolvedPackage && sourcePackage.name !== resolvedPackage.name) continue;
1478
+ }
1479
+ const targetProject = findOwningProjectForFile(resolvedFilePath, options.projects);
1480
+ if (!targetProject) {
1481
+ if (targetPackage && isWorkspaceGraphDependency) problems.push([
1482
+ "Unable to map workspace import to a generated declaration leaf:",
1483
+ ` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
1484
+ ` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
1485
+ ` imported specifier: ${importRecord.specifier}`,
1486
+ ` resolved file: ${formatConfigPath(options.config.rootDir, resolvedFilePath)}`,
1487
+ " reason: TypeScript resolved this workspace import, but the resolved module is not covered by any ordinary tsconfig*.json leaf."
1488
+ ].join("\n"));
1489
+ continue;
1490
+ }
1491
+ if (targetProject.dtsConfigPath !== project.dtsConfigPath) referencePaths.add(formatReferencePath(project.dtsConfigPath, targetProject.dtsConfigPath));
1492
+ }
1493
+ }
1494
+ project.references = [...referencePaths].sort();
1495
+ }
1496
+ return problems;
1497
+ }
1498
+ function collectProjectReferencesForOwner(options) {
1499
+ return options.projects.filter((project) => project.owner?.directory === options.owner?.directory).map((project) => formatReferencePath(options.targetConfigPath, project.dtsConfigPath)).sort();
1500
+ }
1501
+ function collectRootBuildProjectReferences(options) {
1502
+ return options.projects.filter((project) => !project.owner || project.owner.directory === options.rootDir || path.dirname(project.configPath) === options.rootDir).map((project) => formatReferencePath(options.targetConfigPath, project.dtsConfigPath)).sort();
1503
+ }
1504
+ async function writeGeneratedTsconfigs(options) {
1505
+ for (const project of options.projects) await writeTextFile(project.dtsConfigPath, stringifyJson(createDtsConfig(project)), options.writtenFiles);
1506
+ const nonRootWorkspacePackages = options.workspacePackages.filter((workspacePackage) => workspacePackage.directory !== options.rootDir);
1507
+ const workspaceBuildConfigPaths = [];
1508
+ for (const workspacePackage of nonRootWorkspacePackages) {
1509
+ const buildConfigPath = path.join(workspacePackage.directory, "tsconfig.build.json");
1510
+ if (await writeBuildAggregatorFile({
1511
+ configPath: buildConfigPath,
1512
+ references: collectProjectReferencesForOwner({
1513
+ owner: workspacePackage,
1514
+ projects: options.projects,
1515
+ targetConfigPath: buildConfigPath
1516
+ }),
1517
+ writtenFiles: options.writtenFiles
1518
+ })) workspaceBuildConfigPaths.push(buildConfigPath);
1519
+ }
1520
+ const rootBuildConfigPath = path.join(options.rootDir, "tsconfig.build.json");
1521
+ await writeBuildAggregatorFile({
1522
+ configPath: rootBuildConfigPath,
1523
+ references: [...collectRootBuildProjectReferences({
1524
+ projects: options.projects,
1525
+ rootDir: options.rootDir,
1526
+ targetConfigPath: rootBuildConfigPath
1527
+ }), ...workspaceBuildConfigPaths.map((buildConfigPath) => formatReferencePath(rootBuildConfigPath, buildConfigPath))].sort(),
1528
+ writtenFiles: options.writtenFiles
1529
+ });
1530
+ }
1531
+ async function writeLiminaConfig(options) {
1532
+ const configPath = path.join(options.rootDir, liminaConfigFileName);
1533
+ if (existsSync(configPath)) {
1534
+ if (!await confirmAction(options.prompt, `${liminaConfigFileName} already exists. Overwrite it?`)) {
1535
+ options.skippedFiles.push(configPath);
1536
+ return;
1537
+ }
1538
+ }
1539
+ await writeTextFile(configPath, createLiminaConfigContent(), options.writtenFiles);
1540
+ }
1541
+ async function runInitInternal(options) {
1542
+ const cwd = normalizeAbsolutePath(options.cwd ?? process.cwd());
1543
+ const rootDir = findPnpmWorkspaceRoot(cwd);
1544
+ if (!rootDir) throw new Error(`Unable to run limina init from ${cwd}: no pnpm-workspace.yaml was found in this directory or its parents.`);
1545
+ const rootPackageJsonPath = path.join(rootDir, "package.json");
1546
+ const rootPackageName = existsSync(rootPackageJsonPath) ? readJsonFile(rootPackageJsonPath).name : void 0;
1547
+ if (!await confirmAction(options, `Use pnpm workspace ${rootPackageName ? `"${rootPackageName}" ` : ""}at ${rootDir}?`)) throw new Error("limina init canceled.");
1548
+ const reservedConflicts = await collectReservedConfigConflicts(rootDir);
1549
+ if (reservedConflicts.length > 0) throw new Error([
1550
+ "Unable to run limina init because reserved Limina tsconfig names already exist:",
1551
+ ...reservedConflicts.map((configPath) => ` - ${configPath}`),
1552
+ "reason: tsconfig*.build.json and tsconfig*.dts.json are Limina init output names; rename existing files before running init."
1553
+ ].join("\n"));
1554
+ const config = createInitConfig(rootDir);
1555
+ const workspacePackages = (await collectWorkspacePackages(config)).filter((workspacePackage) => workspacePackage.directory !== rootDir);
1556
+ const projectAnalysis = analyzeTypecheckProjects({
1557
+ config,
1558
+ configPaths: await collectOrdinaryTsconfigPaths(rootDir),
1559
+ workspacePackages
1560
+ });
1561
+ const problems = [...projectAnalysis.problems];
1562
+ if (problems.length === 0) problems.push(...inferProjectReferences({
1563
+ config,
1564
+ projects: projectAnalysis.projects,
1565
+ workspacePackages
1566
+ }));
1567
+ if (problems.length > 0) throw new Error(problems.join("\n\n"));
1568
+ const metadata = readLiminaPackageMetadata();
1569
+ const writtenFiles = [];
1570
+ const skippedFiles = [];
1571
+ await writeGeneratedTsconfigs({
1572
+ projects: projectAnalysis.projects,
1573
+ rootDir,
1574
+ workspacePackages,
1575
+ writtenFiles
1576
+ });
1577
+ await writeLiminaConfig({
1578
+ prompt: options,
1579
+ rootDir,
1580
+ skippedFiles,
1581
+ writtenFiles
1582
+ });
1583
+ return {
1584
+ checkCommand: "pnpm limina:check",
1585
+ installRequired: await updateRootPackageJson({
1586
+ metadata,
1587
+ prompt: options,
1588
+ rootDir,
1589
+ skippedFiles,
1590
+ writtenFiles
1591
+ }),
1592
+ rootDir,
1593
+ skippedFiles,
1594
+ workspacePackageCount: workspacePackages.length,
1595
+ writtenFiles
1596
+ };
1597
+ }
1598
+ async function runInit(options = {}) {
1599
+ if (options.clearScreen ?? true) clearCliScreen();
1600
+ const elapsed = createElapsedTimer();
1601
+ const task = options.flow?.start("init workspace", { depth: options.flowDepth ?? 0 });
1602
+ InitLogger.info("init started");
1603
+ try {
1604
+ const result = await runInitInternal(options);
1605
+ InitLogger.success(`init generated ${result.writtenFiles.length} files for ${result.workspacePackageCount} workspace packages.`, elapsed());
1606
+ if (result.installRequired) InitLogger.info("limina was added to devDependencies; run pnpm i before checking.");
1607
+ InitLogger.info(`next: ${result.installRequired ? "pnpm i && " : ""}${result.checkCommand}`);
1608
+ task?.pass();
1609
+ return result;
1610
+ } catch (error) {
1611
+ InitLogger.error(`init failed: ${formatErrorMessage$1(error)}`, elapsed());
1612
+ task?.fail("init failed", { error });
1613
+ throw error;
1614
+ }
1615
+ }
1616
+
1135
1617
  //#endregion
1136
1618
  //#region src/commands/source.ts
1137
1619
  function findOwnerForFile(filePath, owners) {
@@ -1144,7 +1626,7 @@ function isPackageImportSpecifier(specifier) {
1144
1626
  return specifier.startsWith("#");
1145
1627
  }
1146
1628
  function isBarePackageSpecifier(specifier) {
1147
- return !isRelativeSpecifier(specifier) && !isPackageImportSpecifier(specifier) && !isUrlOrDataOrFileSpecifier(specifier) && !path.isAbsolute(specifier);
1629
+ return !isRelativeSpecifier$1(specifier) && !isPackageImportSpecifier(specifier) && !isUrlOrDataOrFileSpecifier(specifier) && !path.isAbsolute(specifier);
1148
1630
  }
1149
1631
  function isDependencyAuthorized(manifest, packageName) {
1150
1632
  return Boolean(manifest.dependencies?.[packageName] || manifest.devDependencies?.[packageName]);
@@ -1218,17 +1700,6 @@ function addPackageImportAuthorizationProblem(options) {
1218
1700
  " reason: source imports must be authorized by the nearest package.json dependencies or devDependencies."
1219
1701
  ].join("\n"));
1220
1702
  }
1221
- function addNodeBuiltinDenyProblem(options) {
1222
- options.problems.push([
1223
- "Denied Node builtin import:",
1224
- ` rule: ${options.project.label}`,
1225
- ` importing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
1226
- ` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
1227
- ` imported specifier: ${options.importRecord.specifier}`,
1228
- ` denied builtin: ${options.ruleName}`,
1229
- ` reason: ${options.reason}`
1230
- ].join("\n"));
1231
- }
1232
1703
  function addPackageImportProblem(options) {
1233
1704
  if (!packageImportsMatch(options.owner.manifest.imports, options.importRecord.specifier)) {
1234
1705
  options.problems.push([
@@ -1272,23 +1743,11 @@ function createSourceProjectEntries(config, projects) {
1272
1743
  }
1273
1744
  async function runSourceCheckInternal(config, options = {}) {
1274
1745
  const graphRoute = collectGraphProjectRoute(config);
1275
- const projectPaths = graphRoute.projectPaths;
1276
- const projects = projectPaths.map((projectPath) => parseProject(config, projectPath));
1746
+ const projects = graphRoute.projectPaths.map((projectPath) => parseProject(config, projectPath));
1277
1747
  const sourceProjectEntries = createSourceProjectEntries(config, projects);
1278
1748
  const packages = await collectWorkspacePackages(config);
1279
1749
  const packageOwners = await collectPackageOwners(config);
1280
1750
  const problems = [...graphRoute.problems];
1281
- const graphRules = normalizeGraphRules({
1282
- config,
1283
- include: {
1284
- nodeBuiltins: true,
1285
- refs: false,
1286
- workspaceDeps: false
1287
- },
1288
- packages,
1289
- problems,
1290
- projectPaths
1291
- });
1292
1751
  for (const project of projects) {
1293
1752
  if (project.labelProblem) problems.push(project.labelProblem);
1294
1753
  if (!isDtsProjectConfig(project.configPath)) continue;
@@ -1315,7 +1774,7 @@ async function runSourceCheckInternal(config, options = {}) {
1315
1774
  if (!owner) continue;
1316
1775
  for (const importRecord of collectImportsFromFile(filePath)) {
1317
1776
  const resolvedFilePath = resolveInternalImport(importRecord.specifier, filePath, project.options);
1318
- if (isRelativeSpecifier(importRecord.specifier)) {
1777
+ if (isRelativeSpecifier$1(importRecord.specifier)) {
1319
1778
  if (!resolvedFilePath) continue;
1320
1779
  const targetOwner = findOwnerForFile(resolvedFilePath, packageOwners);
1321
1780
  if (targetOwner?.packageJsonPath !== owner.packageJsonPath) addRelativeImportOwnerProblem({
@@ -1340,18 +1799,7 @@ async function runSourceCheckInternal(config, options = {}) {
1340
1799
  }
1341
1800
  if (isUrlOrDataOrFileSpecifier(importRecord.specifier)) continue;
1342
1801
  if (!isBarePackageSpecifier(importRecord.specifier)) continue;
1343
- if (isNodeBuiltinSpecifier(importRecord.specifier)) {
1344
- const deniedBuiltinRule = getDeniedNodeBuiltinRule(graphRules, project.label, importRecord.specifier);
1345
- if (deniedBuiltinRule) addNodeBuiltinDenyProblem({
1346
- config,
1347
- importRecord,
1348
- problems,
1349
- project,
1350
- reason: deniedBuiltinRule.reason,
1351
- ruleName: deniedBuiltinRule.matchAll ? "node:*" : deniedBuiltinRule.name
1352
- });
1353
- continue;
1354
- }
1802
+ if (isNodeBuiltinSpecifier(importRecord.specifier)) continue;
1355
1803
  const packageName = getPackageRootSpecifier(importRecord.specifier);
1356
1804
  if (owner.name === packageName) continue;
1357
1805
  const workspacePackage = packages.find((candidate) => candidate.name === packageName) ?? null;
@@ -2023,4 +2471,4 @@ function createLiminaFlowReporter(options = {}) {
2023
2471
  }
2024
2472
 
2025
2473
  //#endregion
2026
- export { collectImporters as A, createFormatHost as B, inferPackageProject as C, parseProject as D, isWorkspacePackageFile as E, collectGraphProjectRoute as F, isOrdinaryTypecheckConfigPath as G, getDtsCompanionConfigPath as H, collectGraphProjectRouteFromRoot as I, readJsonConfig as J, parseProjectFileNames as K, collectGraphProjectRoutes as L, findPackageForSpecifier as M, getPackageRootSpecifier as N, resolveInternalImport as O, collectCheckerEntryProjectRoutes as P, collectTypecheckTargetProjectPaths as R, getTypecheckConfigPath as S, isRelativeSpecifier as T, getRawReferencePaths as U, formatReferences as V, isDtsConfigPath as W, resolveReferencePath as X, resolveProjectConfigPath as Y, createFileOwnerLookup as _, runSourceCheck as a, findTargetProject as b, PackageLogger as c, clearCliScreen as d, formatErrorMessage$1 as f, collectImportsFromFile as g, normalizeGraphRules as h, runCheckerTypecheck as i, collectWorkspacePackages as j, shouldResolveThroughGraph as k, PathsLogger as l, getDeniedWorkspaceDepRule as m, createLiminaFlowReporter as n, CliLogger as o, getDeniedRefRule as p, parseProjectFileNamesForExtensions as q, runCheckerBuild as r, GraphLogger as s, LiminaFlowReporter as t, ProofLogger as u, findImporterForFile as v, isDtsProjectConfig as w, formatArtifactDependencyPolicy as x, findPackageForFile as y, createExtensionPattern as z };
2474
+ export { shouldResolveThroughGraph as A, createExtensionPattern as B, formatArtifactDependencyPolicy as C, isRelativeSpecifier$1 as D, isDtsProjectConfig as E, collectCheckerEntryProjectRoutes as F, isDtsConfigPath as G, formatReferences as H, collectGraphProjectRoute as I, parseProjectFileNamesForExtensions as J, isOrdinaryTypecheckConfigPath as K, collectGraphProjectRouteFromRoot as L, collectWorkspacePackages as M, findPackageForSpecifier as N, parseProject as O, getPackageRootSpecifier as P, collectGraphProjectRoutes as R, findTargetProject as S, inferPackageProject as T, getDtsCompanionConfigPath as U, createFormatHost as V, getRawReferencePaths as W, resolveProjectConfigPath as X, readJsonConfig as Y, resolveReferencePath as Z, normalizeGraphRules as _, runSourceCheck as a, findImporterForFile as b, GraphLogger as c, ProofLogger as d, clearCliScreen as f, getDeniedRefRule as g, getDeniedDepRuleForSpecifier as h, runCheckerTypecheck as i, collectImporters as j, resolveInternalImport as k, PackageLogger as l, getDeniedDepRuleForPackage as m, createLiminaFlowReporter as n, runInit as o, formatErrorMessage$1 as p, parseProjectFileNames as q, runCheckerBuild as r, CliLogger as s, LiminaFlowReporter as t, PathsLogger as u, collectImportsFromFile as v, getTypecheckConfigPath as w, findPackageForFile as x, createFileOwnerLookup as y, collectTypecheckTargetProjectPaths as z };