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.
package/cli.js CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunks/dep-lkQg1P9Q.js";
3
3
  import { d as normalizeSlashes, f as normalizeWorkspacePath, h as toRelativePath, i as loadConfig, l as isPathInsideDirectory, m as toPosixPath, n as getActiveCheckerExtensions, p as toAbsolutePath, r as getActiveCheckers, u as normalizeAbsolutePath } from "./chunks/dep-jgc7X0zw.js";
4
- import { A as collectImporters, B as createFormatHost, C as inferPackageProject$1, D as parseProject$1, E as isWorkspacePackageFile, F as collectGraphProjectRoute, G as isOrdinaryTypecheckConfigPath, H as getDtsCompanionConfigPath, I as collectGraphProjectRouteFromRoot, J as readJsonConfig, K as parseProjectFileNames, L as collectGraphProjectRoutes, M as findPackageForSpecifier, N as getPackageRootSpecifier, O as resolveInternalImport$1, P as collectCheckerEntryProjectRoutes, S as getTypecheckConfigPath, T as isRelativeSpecifier, U as getRawReferencePaths, V as formatReferences, W as isDtsConfigPath, X as resolveReferencePath, Y as resolveProjectConfigPath, _ as createFileOwnerLookup$1, a as runSourceCheck, b as findTargetProject, c as PackageLogger, d as clearCliScreen, f as formatErrorMessage$1, g as collectImportsFromFile$1, h as normalizeGraphRules, i as runCheckerTypecheck, j as collectWorkspacePackages, k as shouldResolveThroughGraph$1, l as PathsLogger, m as getDeniedWorkspaceDepRule, n as createLiminaFlowReporter, o as CliLogger, p as getDeniedRefRule, q as parseProjectFileNamesForExtensions, r as runCheckerBuild, s as GraphLogger, u as ProofLogger, v as findImporterForFile$1, w as isDtsProjectConfig, x as formatArtifactDependencyPolicy, y as findPackageForFile, z as createExtensionPattern } from "./chunks/dep-DoSHsBSP.js";
4
+ import { A as shouldResolveThroughGraph$1, B as createExtensionPattern, C as formatArtifactDependencyPolicy, D as isRelativeSpecifier, E as isDtsProjectConfig, F as collectCheckerEntryProjectRoutes, G as isDtsConfigPath, H as formatReferences, I as collectGraphProjectRoute, J as parseProjectFileNamesForExtensions, K as isOrdinaryTypecheckConfigPath, L as collectGraphProjectRouteFromRoot, M as collectWorkspacePackages, N as findPackageForSpecifier, O as parseProject$1, P as getPackageRootSpecifier, R as collectGraphProjectRoutes, S as findTargetProject, T as inferPackageProject$1, U as getDtsCompanionConfigPath, V as createFormatHost, W as getRawReferencePaths, X as resolveProjectConfigPath, Y as readJsonConfig, Z as resolveReferencePath, _ as normalizeGraphRules, a as runSourceCheck, b as findImporterForFile$1, c as GraphLogger, d as ProofLogger, f as clearCliScreen, g as getDeniedRefRule, h as getDeniedDepRuleForSpecifier, i as runCheckerTypecheck, j as collectImporters, k as resolveInternalImport$1, l as PackageLogger, m as getDeniedDepRuleForPackage, n as createLiminaFlowReporter, o as runInit, p as formatErrorMessage$1, q as parseProjectFileNames, r as runCheckerBuild, s as CliLogger, u as PathsLogger, v as collectImportsFromFile$1, w as getTypecheckConfigPath, x as findPackageForFile, y as createFileOwnerLookup$1 } from "./chunks/dep-uPXyoC0V.js";
5
5
  import { builtinModules } from "node:module";
6
6
  import { cac } from "cac";
7
- import { createElapsedTimer } from "@docs-islands/logger/helper";
7
+ import { createElapsedTimer } from "logaria/helper";
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import path from "node:path";
10
10
  import ts from "typescript";
11
11
  import { spawn, spawnSync } from "node:child_process";
12
12
  import { glob } from "tinyglobby";
13
+ import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
13
14
  import { checkPackage, createPackageFromTarballData } from "@arethetypeswrong/core";
14
15
  import { pack } from "@publint/pack";
15
16
  import { init, parse } from "es-module-lexer";
16
- import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
17
17
  import { tmpdir } from "node:os";
18
18
  import { publint } from "publint";
19
19
  import { formatMessage } from "publint/utils";
@@ -146,7 +146,7 @@ function addDeniedReferenceProblems(options) {
146
146
  if (!options.projectsByPath.has(referencePath)) continue;
147
147
  const deniedRefRule = getDeniedRefRule(options.rules, options.project.label, referencePath);
148
148
  const targetPackage = findPackageForFile(referencePath, options.packages);
149
- const deniedDepRule = targetPackage ? getDeniedWorkspaceDepRule(options.rules, options.project.label, targetPackage.name) : null;
149
+ const deniedDepRule = targetPackage ? getDeniedDepRuleForPackage(options.rules, options.project.label, targetPackage.name) : null;
150
150
  if (!deniedRefRule && !deniedDepRule) continue;
151
151
  const lines = [
152
152
  "Denied graph access:",
@@ -154,12 +154,12 @@ function addDeniedReferenceProblems(options) {
154
154
  ` referencing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
155
155
  ` referenced project: ${toRelativePath(options.config.rootDir, referencePath)}`
156
156
  ];
157
- if (deniedRefRule) lines.push(` denied ref: ${toRelativePath(options.config.rootDir, deniedRefRule.path)}`, ` reason: ${deniedRefRule.reason}`);
158
- else if (deniedDepRule) lines.push(` denied dependency: ${deniedDepRule.name}`, ` reason: ${deniedDepRule.reason}`);
157
+ if (deniedDepRule) lines.push(` denied dependency: ${deniedDepRule.name}`, ` reason: ${deniedDepRule.reason}`);
158
+ else if (deniedRefRule) lines.push(` denied ref: ${toRelativePath(options.config.rootDir, deniedRefRule.path)}`, ` reason: ${deniedRefRule.reason}`);
159
159
  options.problems.push(lines.join("\n"));
160
160
  }
161
161
  }
162
- function addDeniedPackageImportProblem(options) {
162
+ function addDeniedDepImportProblem(options) {
163
163
  options.problems.push([
164
164
  "Denied graph access:",
165
165
  ` rule: ${options.project.label}`,
@@ -182,6 +182,25 @@ function addDeniedRefImportProblem(options) {
182
182
  ` reason: ${options.rule.reason}`
183
183
  ].join("\n"));
184
184
  }
185
+ function getNodeModulesPackageName(filePath) {
186
+ const parts = filePath.split("/");
187
+ const nodeModulesIndex = parts.lastIndexOf("node_modules");
188
+ if (nodeModulesIndex === -1) return null;
189
+ const packageName = parts[nodeModulesIndex + 1];
190
+ if (!packageName) return null;
191
+ if (packageName.startsWith("@")) {
192
+ const scopedName = parts[nodeModulesIndex + 2];
193
+ return scopedName ? `${packageName}/${scopedName}` : null;
194
+ }
195
+ return packageName;
196
+ }
197
+ function getResolvedPackageName(filePath, packages) {
198
+ return getNodeModulesPackageName(filePath) ?? findPackageForFile(filePath, packages)?.name ?? null;
199
+ }
200
+ function getResolvedWorkspacePackage(filePath, packages) {
201
+ if (getNodeModulesPackageName(filePath)) return null;
202
+ return findPackageForFile(filePath, packages);
203
+ }
185
204
  function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems) {
186
205
  if (!isDtsProjectConfig(project.configPath)) return;
187
206
  const sourcePackage = findPackageForFile(project.configPath, packages);
@@ -216,9 +235,8 @@ async function runGraphCheckInternal(config, options = {}) {
216
235
  const graphRules = normalizeGraphRules({
217
236
  config,
218
237
  include: {
219
- nodeBuiltins: false,
220
- refs: true,
221
- workspaceDeps: true
238
+ deps: true,
239
+ refs: true
222
240
  },
223
241
  packages,
224
242
  problems,
@@ -238,21 +256,21 @@ async function runGraphCheckInternal(config, options = {}) {
238
256
  });
239
257
  addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems);
240
258
  for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile$1(filePath)) {
259
+ const rawDeniedDepRule = getDeniedDepRuleForSpecifier(graphRules, project.label, importRecord.specifier);
260
+ if (rawDeniedDepRule) {
261
+ addDeniedDepImportProblem({
262
+ config,
263
+ importRecord,
264
+ problems,
265
+ project,
266
+ rule: rawDeniedDepRule
267
+ });
268
+ continue;
269
+ }
241
270
  const resolvedFilePath = resolveInternalImport$1(importRecord.specifier, filePath, project.options);
242
271
  const targetPackage = findPackageForSpecifier(importRecord.specifier, packages);
243
- const importer = targetPackage ? findImporterForFile$1(importRecord.filePath, importers) : null;
244
- const unresolvedDeniedDepRule = targetPackage ? getDeniedWorkspaceDepRule(graphRules, project.label, targetPackage.name) : null;
272
+ const importer = findImporterForFile$1(importRecord.filePath, importers);
245
273
  if (!resolvedFilePath) {
246
- if (unresolvedDeniedDepRule) {
247
- addDeniedPackageImportProblem({
248
- config,
249
- importRecord,
250
- problems,
251
- project,
252
- rule: unresolvedDeniedDepRule
253
- });
254
- continue;
255
- }
256
274
  if (!targetPackage) continue;
257
275
  problems.push([
258
276
  "Unresolved workspace import:",
@@ -264,10 +282,12 @@ async function runGraphCheckInternal(config, options = {}) {
264
282
  ].join("\n"));
265
283
  continue;
266
284
  }
267
- const targetWorkspacePackageForResolved = findPackageForFile(resolvedFilePath, packages);
268
- const deniedDepRule = (targetPackage ? getDeniedWorkspaceDepRule(graphRules, project.label, targetPackage.name) : null) ?? (targetWorkspacePackageForResolved ? getDeniedWorkspaceDepRule(graphRules, project.label, targetWorkspacePackageForResolved.name) : null);
285
+ const targetWorkspacePackageForResolved = getResolvedWorkspacePackage(resolvedFilePath, packages);
286
+ const targetPackageForGraph = targetPackage;
287
+ const resolvedPackageName = getResolvedPackageName(resolvedFilePath, packages);
288
+ const deniedDepRule = resolvedPackageName ? getDeniedDepRuleForPackage(graphRules, project.label, resolvedPackageName) : null;
269
289
  if (deniedDepRule) {
270
- addDeniedPackageImportProblem({
290
+ addDeniedDepImportProblem({
271
291
  config,
272
292
  importRecord,
273
293
  problems,
@@ -292,9 +312,8 @@ async function runGraphCheckInternal(config, options = {}) {
292
312
  continue;
293
313
  }
294
314
  }
295
- if (targetPackage && !shouldResolveThroughGraph$1(importer, targetPackage)) continue;
296
- if (targetPackage && shouldResolveThroughGraph$1(importer, targetPackage) && !fileOwnerLookup.has(resolvedFilePath)) {
297
- const referencedProjectPath = inferPackageProject$1(resolvedFilePath, targetPackage, projectPaths);
315
+ if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph) && !fileOwnerLookup.has(resolvedFilePath)) {
316
+ const referencedProjectPath = inferPackageProject$1(resolvedFilePath, targetPackageForGraph, projectPaths);
298
317
  const hasProjectReference = referencedProjectPath && project.references.has(referencedProjectPath);
299
318
  problems.push([
300
319
  hasProjectReference ? "Referenced workspace dependency resolves through package exports to a build artifact:" : "Workspace source dependency resolved outside the source graph:",
@@ -304,7 +323,7 @@ async function runGraphCheckInternal(config, options = {}) {
304
323
  ` imported specifier: ${importRecord.specifier}`,
305
324
  ` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
306
325
  " reason: workspace:* dependencies are source dependencies, but TypeScript resolved this package export to a file not owned by the source graph. tsc -b does not rewrite package exports through project references.",
307
- ` fix: expose source files from the dependency package exports, add a source paths config to this declaration leaf extends, or stop using workspace:* plus project references for artifact consumption; ${formatArtifactDependencyPolicy(targetPackage)}`,
326
+ ` fix: expose source files from the dependency package exports, add a source paths config to this declaration leaf extends, or stop using workspace:* plus project references for artifact consumption; ${formatArtifactDependencyPolicy(targetPackageForGraph)}`,
308
327
  " hint: run `limina paths generate` to create a compatibility paths file, then manually add it to the first position of the listed tsconfig*.dts.json extends array."
309
328
  ].join("\n"));
310
329
  continue;
@@ -317,15 +336,15 @@ async function runGraphCheckInternal(config, options = {}) {
317
336
  specifier: importRecord.specifier
318
337
  });
319
338
  if (!targetProjectPath) {
320
- if (!targetPackage) continue;
321
- if (!isWorkspacePackageFile(resolvedFilePath, packages)) {
322
- if (targetPackage && shouldResolveThroughGraph$1(importer, targetPackage)) problems.push([
339
+ if (!targetPackageForGraph) continue;
340
+ if (!targetWorkspacePackageForResolved) {
341
+ if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph)) problems.push([
323
342
  "Workspace source import resolved outside the workspace graph:",
324
343
  ` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
325
344
  ` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
326
345
  ` imported specifier: ${importRecord.specifier}`,
327
346
  ` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
328
- ` reason: workspace:* dependencies are source dependency edges and must resolve to files owned by the source graph; ${formatArtifactDependencyPolicy(targetPackage)}`
347
+ ` reason: workspace:* dependencies are source dependency edges and must resolve to files owned by the source graph; ${formatArtifactDependencyPolicy(targetPackageForGraph)}`
329
348
  ].join("\n"));
330
349
  continue;
331
350
  }
@@ -352,6 +371,7 @@ async function runGraphCheckInternal(config, options = {}) {
352
371
  });
353
372
  continue;
354
373
  }
374
+ if (targetPackageForGraph && !shouldResolveThroughGraph$1(importer, targetPackageForGraph)) continue;
355
375
  if (!projectsByPath.has(targetProjectPath)) {
356
376
  problems.push([
357
377
  "Expected graph target is not reachable from any checker entry:",
@@ -416,6 +436,7 @@ const ATTW_PROFILE_IGNORED_RESOLUTIONS = {
416
436
  "esm-only": ["node16-cjs"]
417
437
  };
418
438
  const nodeBuiltinSpecifiers = new Set(builtinModules.flatMap((specifier) => specifier.startsWith("node:") ? [specifier, specifier.slice(5)] : [specifier, `node:${specifier}`]));
439
+ const REQUIRED_PUBLIC_PACKAGE_FILES = ["README.md", "LICENSE.md"];
419
440
  function isRecord(value) {
420
441
  return typeof value === "object" && value !== null && !Array.isArray(value);
421
442
  }
@@ -625,7 +646,7 @@ function logPackageCheckPlan(options) {
625
646
  ].join("\n"));
626
647
  }
627
648
  async function packOutputTarball(outDir) {
628
- const destination = await mkdtemp(path.join(tmpdir(), "__LATTICE_PACKAGE__"));
649
+ const destination = await mkdtemp(path.join(tmpdir(), "__LIMINA_PACKAGE__"));
629
650
  return {
630
651
  cleanup: async () => {
631
652
  await rm(destination, {
@@ -640,6 +661,20 @@ async function packOutputTarball(outDir) {
640
661
  }))
641
662
  };
642
663
  }
664
+ async function readDistPackageJson(options) {
665
+ if (!existsSync(options.packageJsonPath)) throw new Error(`outDir package.json not found${options.label ? ` for ${options.label}` : ""} at ${options.config ? toRelativePath(options.config.rootDir, options.packageJsonPath) : options.packageJsonPath}. Run the package build first.`);
666
+ return JSON.parse(await readFile(options.packageJsonPath, "utf8"));
667
+ }
668
+ async function assertPublicPackageMetadata(options) {
669
+ if ((await readDistPackageJson({
670
+ config: options.config,
671
+ label: options.label,
672
+ packageJsonPath: options.packageJsonPath
673
+ })).private === true) return;
674
+ const missingFiles = REQUIRED_PUBLIC_PACKAGE_FILES.filter((fileName) => !existsSync(path.join(options.outDir, fileName)));
675
+ if (missingFiles.length === 0) return;
676
+ throw new Error(`publishable package output for ${options.label} at ${toRelativePath(options.config.rootDir, options.outDir)} is missing required file(s): ${missingFiles.join(", ")}. Add them to the built output or set "private": true in the output package.json.`);
677
+ }
643
678
  async function runPublintCheck(options) {
644
679
  const task = options.flow?.start(`publint: ${options.label}`, { depth: options.flowDepth ?? 0 });
645
680
  PackageLogger.info(`publint started: ${options.label}`);
@@ -720,7 +755,12 @@ async function runPackageCheckTarget(options) {
720
755
  const task = options.flow?.start(`package target: ${label}`, { depth: options.flowDepth ?? 0 });
721
756
  let packedDist;
722
757
  try {
723
- if (!existsSync(outputPackageJsonPath)) throw new Error(`outDir package.json not found for ${label} at ${toRelativePath(options.config.rootDir, outputPackageJsonPath)}. Run the package build first.`);
758
+ await assertPublicPackageMetadata({
759
+ config: options.config,
760
+ label,
761
+ outDir: target.outDir,
762
+ packageJsonPath: outputPackageJsonPath
763
+ });
724
764
  if (options.checks.includes("publint") || options.checks.includes("attw")) {
725
765
  const packTask = options.flow?.start(`package tarball: ${label}`, { depth: (options.flowDepth ?? 0) + 1 });
726
766
  PackageLogger.info(`package tarball packing started: ${label}`);
@@ -774,8 +814,7 @@ async function runPackageCheckTarget(options) {
774
814
  }
775
815
  }
776
816
  async function auditPublishedPackageBoundaries(target) {
777
- const manifestPath = path.join(target.outDir, "package.json");
778
- const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
817
+ const manifest = await readDistPackageJson({ packageJsonPath: path.join(target.outDir, "package.json") });
779
818
  const allowedExternalPackages = new Set([
780
819
  ...Object.keys(manifest.dependencies ?? {}),
781
820
  ...Object.keys(manifest.peerDependencies ?? {}),
@@ -860,7 +899,7 @@ async function runPackageCheck(options) {
860
899
  }
861
900
 
862
901
  //#endregion
863
- //#region src/commands/paths.ts
902
+ //#region src/package-exports.ts
864
903
  const defaultSourceExtensions = [
865
904
  ".ts",
866
905
  ".tsx",
@@ -887,110 +926,11 @@ const defaultArtifactDirectories = [
887
926
  "cjs",
888
927
  "out"
889
928
  ];
890
- function generatedFileName(config) {
891
- return config.paths?.generatedFileName ?? "tsconfig.dts.paths.generated.json";
892
- }
893
- function generatedFileMarker(config) {
894
- return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
895
- }
896
- function parseProject(config, configPath) {
897
- const diagnostics = [];
898
- const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
899
- ...ts.sys,
900
- onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
901
- diagnostics.push(diagnostic);
902
- }
903
- });
904
- if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
905
- getCanonicalFileName: (fileName) => fileName,
906
- getCurrentDirectory: () => config.rootDir,
907
- getNewLine: () => "\n"
908
- }));
909
- if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
910
- getCanonicalFileName: (fileName) => fileName,
911
- getCurrentDirectory: () => config.rootDir,
912
- getNewLine: () => "\n"
913
- }));
914
- return {
915
- configPath: normalizeAbsolutePath(configPath),
916
- fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
917
- options: parsed.options,
918
- references: new Set(getRawReferencePaths(config, configPath))
919
- };
920
- }
921
- function getSourceFileKind(filePath) {
922
- if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
923
- if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
924
- return ts.ScriptKind.TS;
925
- }
926
- function stringLiteralValue(node) {
927
- return node && ts.isStringLiteralLike(node) ? node.text : null;
928
- }
929
- function collectImportsFromFile(filePath) {
930
- const sourceText = readFileSync(filePath, "utf8");
931
- const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
932
- const imports = [];
933
- const addImport = (specifier, node) => {
934
- const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
935
- imports.push({
936
- filePath,
937
- line: location.line + 1,
938
- specifier
939
- });
940
- };
941
- const visit = (node) => {
942
- if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
943
- const specifier = stringLiteralValue(node.moduleSpecifier);
944
- if (specifier) addImport(specifier, node);
945
- } else if (ts.isImportTypeNode(node)) {
946
- const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
947
- if (specifier) addImport(specifier, node);
948
- } else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
949
- const specifier = stringLiteralValue(node.arguments[0]);
950
- if (specifier) addImport(specifier, node);
951
- }
952
- ts.forEachChild(node, visit);
953
- };
954
- visit(sourceFile);
955
- return imports;
956
- }
957
- function resolveInternalImport(specifier, containingFile, options) {
958
- const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
959
- return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
960
- }
961
- function resolveImportWithoutMatchingPaths(specifier, containingFile, options) {
962
- if (!options.paths) return resolveInternalImport(specifier, containingFile, options);
963
- const paths = Object.fromEntries(Object.entries(options.paths).filter(([alias]) => !aliasMatchesSpecifier(alias, specifier)));
964
- return resolveInternalImport(specifier, containingFile, {
965
- ...options,
966
- paths: Object.keys(paths).length > 0 ? paths : void 0
967
- });
968
- }
969
- function createFileOwnerLookup(projects) {
970
- const ownerLookup = /* @__PURE__ */ new Map();
971
- for (const project of projects) for (const fileName of project.fileNames) {
972
- const owners = ownerLookup.get(fileName) ?? [];
973
- owners.push(project.configPath);
974
- ownerLookup.set(fileName, owners);
975
- }
976
- return ownerLookup;
977
- }
978
- function projectExtendsGeneratedConfig(config, configPath) {
979
- const extendsValue = readJsonConfig(config, configPath).extends;
980
- return (typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) ? extendsValue : []).some((entry) => typeof entry === "string" && path.basename(entry) === generatedFileName(config));
981
- }
982
- function findImporterForFile(filePath, importers) {
983
- return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
984
- }
985
- function shouldResolveThroughGraph(importer, targetPackage) {
986
- if (!importer || !targetPackage) return false;
987
- return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
929
+ function configuredArtifactDirectories(config) {
930
+ return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
988
931
  }
989
- function inferPackageProject(resolvedFilePath, workspacePackage, projectPaths) {
990
- if (!isPathInsideDirectory(resolvedFilePath, workspacePackage.directory)) return null;
991
- return projectPaths.find((projectPath) => {
992
- return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
993
- }) ?? null;
932
+ function configuredSourceExtensions(config) {
933
+ return config.paths?.sourceExtensions ?? defaultSourceExtensions;
994
934
  }
995
935
  function collectTargetCandidates(config, value) {
996
936
  if (typeof value === "string") return [value];
@@ -1033,12 +973,6 @@ function removeKnownExtension(filePath) {
1033
973
  ]) if (filePath.endsWith(extension)) return filePath.slice(0, -extension.length);
1034
974
  return filePath;
1035
975
  }
1036
- function configuredArtifactDirectories(config) {
1037
- return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
1038
- }
1039
- function configuredSourceExtensions(config) {
1040
- return config.paths?.sourceExtensions ?? defaultSourceExtensions;
1041
- }
1042
976
  function hasConfiguredSourceExtension(config, target) {
1043
977
  return configuredSourceExtensions(config).some((extension) => target.endsWith(extension));
1044
978
  }
@@ -1151,6 +1085,114 @@ function aliasMatchesSpecifier(alias, specifier) {
1151
1085
  const suffix = alias.slice(wildcardIndex + 1);
1152
1086
  return specifier.startsWith(prefix) && specifier.endsWith(suffix);
1153
1087
  }
1088
+
1089
+ //#endregion
1090
+ //#region src/commands/paths.ts
1091
+ function generatedFileName(config) {
1092
+ return config.paths?.generatedFileName ?? "tsconfig.dts.paths.generated.json";
1093
+ }
1094
+ function generatedFileMarker(config) {
1095
+ return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
1096
+ }
1097
+ function parseProject(config, configPath) {
1098
+ const diagnostics = [];
1099
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
1100
+ ...ts.sys,
1101
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
1102
+ diagnostics.push(diagnostic);
1103
+ }
1104
+ });
1105
+ if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
1106
+ getCanonicalFileName: (fileName) => fileName,
1107
+ getCurrentDirectory: () => config.rootDir,
1108
+ getNewLine: () => "\n"
1109
+ }));
1110
+ if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
1111
+ getCanonicalFileName: (fileName) => fileName,
1112
+ getCurrentDirectory: () => config.rootDir,
1113
+ getNewLine: () => "\n"
1114
+ }));
1115
+ return {
1116
+ configPath: normalizeAbsolutePath(configPath),
1117
+ fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
1118
+ options: parsed.options,
1119
+ references: new Set(getRawReferencePaths(config, configPath))
1120
+ };
1121
+ }
1122
+ function getSourceFileKind(filePath) {
1123
+ if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
1124
+ if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
1125
+ return ts.ScriptKind.TS;
1126
+ }
1127
+ function stringLiteralValue(node) {
1128
+ return node && ts.isStringLiteralLike(node) ? node.text : null;
1129
+ }
1130
+ function collectImportsFromFile(filePath) {
1131
+ const sourceText = readFileSync(filePath, "utf8");
1132
+ const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
1133
+ const imports = [];
1134
+ const addImport = (specifier, node) => {
1135
+ const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
1136
+ imports.push({
1137
+ filePath,
1138
+ line: location.line + 1,
1139
+ specifier
1140
+ });
1141
+ };
1142
+ const visit = (node) => {
1143
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
1144
+ const specifier = stringLiteralValue(node.moduleSpecifier);
1145
+ if (specifier) addImport(specifier, node);
1146
+ } else if (ts.isImportTypeNode(node)) {
1147
+ const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
1148
+ if (specifier) addImport(specifier, node);
1149
+ } else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
1150
+ const specifier = stringLiteralValue(node.arguments[0]);
1151
+ if (specifier) addImport(specifier, node);
1152
+ }
1153
+ ts.forEachChild(node, visit);
1154
+ };
1155
+ visit(sourceFile);
1156
+ return imports;
1157
+ }
1158
+ function resolveInternalImport(specifier, containingFile, options) {
1159
+ const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
1160
+ return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
1161
+ }
1162
+ function resolveImportWithoutMatchingPaths(specifier, containingFile, options) {
1163
+ if (!options.paths) return resolveInternalImport(specifier, containingFile, options);
1164
+ const paths = Object.fromEntries(Object.entries(options.paths).filter(([alias]) => !aliasMatchesSpecifier(alias, specifier)));
1165
+ return resolveInternalImport(specifier, containingFile, {
1166
+ ...options,
1167
+ paths: Object.keys(paths).length > 0 ? paths : void 0
1168
+ });
1169
+ }
1170
+ function createFileOwnerLookup(projects) {
1171
+ const ownerLookup = /* @__PURE__ */ new Map();
1172
+ for (const project of projects) for (const fileName of project.fileNames) {
1173
+ const owners = ownerLookup.get(fileName) ?? [];
1174
+ owners.push(project.configPath);
1175
+ ownerLookup.set(fileName, owners);
1176
+ }
1177
+ return ownerLookup;
1178
+ }
1179
+ function projectExtendsGeneratedConfig(config, configPath) {
1180
+ const extendsValue = readJsonConfig(config, configPath).extends;
1181
+ return (typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) ? extendsValue : []).some((entry) => typeof entry === "string" && path.basename(entry) === generatedFileName(config));
1182
+ }
1183
+ function findImporterForFile(filePath, importers) {
1184
+ return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
1185
+ }
1186
+ function shouldResolveThroughGraph(importer, targetPackage) {
1187
+ if (!importer || !targetPackage) return false;
1188
+ return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
1189
+ }
1190
+ function inferPackageProject(resolvedFilePath, workspacePackage, projectPaths) {
1191
+ if (!isPathInsideDirectory(resolvedFilePath, workspacePackage.directory)) return null;
1192
+ return projectPaths.find((projectPath) => {
1193
+ return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
1194
+ }) ?? null;
1195
+ }
1154
1196
  function addPathEntry(paths, alias, target) {
1155
1197
  const targets = paths.get(alias) ?? [];
1156
1198
  if (!targets.includes(target)) targets.push(target);
@@ -2015,6 +2057,12 @@ const builtInTaskNames = new Set([
2015
2057
  "proof:check",
2016
2058
  "source:check"
2017
2059
  ]);
2060
+ const defaultCheckPipeline = [
2061
+ "graph:check",
2062
+ "source:check",
2063
+ "proof:check",
2064
+ "checker:typecheck"
2065
+ ];
2018
2066
  function isBuiltinTaskName(value) {
2019
2067
  return builtInTaskNames.has(value);
2020
2068
  }
@@ -2128,7 +2176,7 @@ function runCommandStep(config, step, options = {}) {
2128
2176
  }
2129
2177
  async function runPipeline(config, pipelineName, options = {}) {
2130
2178
  const steps = config.pipelines?.[pipelineName];
2131
- if (!steps) throw new Error(`Unknown limina pipeline "${pipelineName}".`);
2179
+ if (!steps) throw new Error([`Pipeline instruction "${pipelineName}" was not found.`, `Define it in ${path.relative(config.rootDir, config.configPath)} under the "pipelines" field, then run "limina check ${pipelineName}" again.`].join("\n"));
2132
2180
  const normalizedSteps = steps.map(normalizePipelineStep);
2133
2181
  const pipelineTask = options.flow?.start(`pipeline: ${pipelineName}`, { collapseOnSuccess: false });
2134
2182
  for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
@@ -2140,6 +2188,18 @@ async function runPipeline(config, pipelineName, options = {}) {
2140
2188
  pipelineTask?.pass();
2141
2189
  return true;
2142
2190
  }
2191
+ async function runDefaultCheck(config, options = {}) {
2192
+ const normalizedSteps = defaultCheckPipeline.map(normalizePipelineStep);
2193
+ const pipelineTask = options.flow?.start("default check", { collapseOnSuccess: false });
2194
+ for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
2195
+ const label = getPipelineStepLabel(step);
2196
+ pipelineTask?.fail(`default check blocked at ${label}`);
2197
+ for (const remainingStep of normalizedSteps.slice(stepIndex + 1)) options.flow?.skip(`skipped: ${getPipelineStepLabel(remainingStep)}`, { depth: 1 });
2198
+ return false;
2199
+ }
2200
+ pipelineTask?.pass();
2201
+ return true;
2202
+ }
2143
2203
 
2144
2204
  //#endregion
2145
2205
  //#region src/cli.ts
@@ -2176,10 +2236,25 @@ async function main() {
2176
2236
  cli.option("--config <path>", "Path to limina.config.mjs");
2177
2237
  cli.option("--mode <mode>", "Mode passed to limina config functions");
2178
2238
  cli.help();
2179
- cli.command("check <pipeline>", "Run a configured governance pipeline").action(async (pipeline, flags) => {
2239
+ cli.command("init", "Initialize Limina files for a pnpm workspace").option("--yes", "Accept all init prompts").action(async (flags) => {
2240
+ const flow = createCliFlow();
2241
+ flow.intro("limina init");
2242
+ await runInit({
2243
+ clearScreen: false,
2244
+ cwd: process.cwd(),
2245
+ flow,
2246
+ yes: flags.yes
2247
+ });
2248
+ flow.outro("limina init finished");
2249
+ });
2250
+ cli.command("check [pipeline]", "Run the default check or a configured pipeline").action(async (pipeline, flags) => {
2180
2251
  const flow = createCliFlow();
2181
2252
  flow.intro("limina check");
2182
- const passed = await runPipeline(await load(flags, "check"), pipeline, {
2253
+ const config = await load(flags, "check");
2254
+ const passed = pipeline ? await runPipeline(config, pipeline, {
2255
+ cwd: process.cwd(),
2256
+ flow
2257
+ }) : await runDefaultCheck(config, {
2183
2258
  cwd: process.cwd(),
2184
2259
  flow
2185
2260
  });
package/config.d.ts CHANGED
@@ -173,39 +173,14 @@ interface GraphRuleRefDenyEntry {
173
173
  reason: string;
174
174
  }
175
175
  /**
176
- * Workspace package dependency denied to projects with a matching Limina label.
176
+ * Dependency denied to projects with a matching Limina label.
177
177
  */
178
178
  interface GraphRuleDepDenyEntry {
179
179
  /**
180
- * Target workspace package name.
180
+ * Target package root, package.json imports specifier, or Node builtin name.
181
181
  *
182
- * @deprecated Use `workspaceDeps` for new configs.
183
- */
184
- name: string;
185
- /**
186
- * Human-readable explanation shown when the rule fails.
187
- */
188
- reason: string;
189
- }
190
- /**
191
- * Workspace package dependency denied to projects with a matching Limina label.
192
- */
193
- interface GraphRuleWorkspaceDepDenyEntry {
194
- /**
195
- * Target workspace package name.
196
- */
197
- name: string;
198
- /**
199
- * Human-readable explanation shown when the rule fails.
200
- */
201
- reason: string;
202
- }
203
- /**
204
- * Node builtin denied to source files with a matching Limina label.
205
- */
206
- interface GraphRuleNodeBuiltinDenyEntry {
207
- /**
208
- * Target Node builtin name, such as `fs`, `node:fs`, or `node:*`.
182
+ * Examples: `@acme/internal`, `zod`, `#internal/*`, `fs`, `node:fs`,
183
+ * or `node:*`.
209
184
  */
210
185
  name: string;
211
186
  /**
@@ -222,19 +197,10 @@ interface GraphRuleDenyConfig {
222
197
  */
223
198
  refs?: GraphRuleRefDenyEntry[];
224
199
  /**
225
- * Workspace packages that matching projects must not reference or import.
226
- *
227
- * @deprecated Use `workspaceDeps` for new configs.
200
+ * Packages, package imports, and Node builtins that matching projects must
201
+ * not reference or import.
228
202
  */
229
203
  deps?: GraphRuleDepDenyEntry[];
230
- /**
231
- * Node builtins that matching projects must not import.
232
- */
233
- nodeBuiltins?: GraphRuleNodeBuiltinDenyEntry[];
234
- /**
235
- * Workspace packages that matching projects must not reference or import.
236
- */
237
- workspaceDeps?: GraphRuleWorkspaceDepDenyEntry[];
238
204
  }
239
205
  /**
240
206
  * Package-level graph governance rule keyed by a label declared in
@@ -480,4 +446,4 @@ interface LoadConfigOptions {
480
446
  }
481
447
  declare function loadConfig(options?: LoadConfigOptions): Promise<ResolvedLiminaConfig>;
482
448
  //#endregion
483
- export { BuiltinCheckerPreset, BuiltinTaskName, CheckerConfig, CheckerExecutionKind, CheckerPreset, GraphConfig, GraphRule, GraphRuleDenyConfig, GraphRuleDepDenyEntry, GraphRuleNodeBuiltinDenyEntry, GraphRuleRefDenyEntry, GraphRuleWorkspaceDepDenyEntry, LiminaCommand, LiminaConfig, LiminaConfigEnv, LiminaConfigExport, LiminaConfigFn, LiminaConfigFnObject, LiminaConfigFnPromise, LoadConfigOptions, PackageAttwCheckConfig, PackageAttwProfile, PackageBoundaryCheckConfig, PackageCheckTarget, PackageCheckTool, PackageCheckToolSelection, PackageChecksConfig, PackagePublintCheckConfig, PathsConfig, PipelineStep, ProofAllowlistEntry, ProofConfig, ResolvedCheckerConfig, ResolvedLiminaConfig, RuntimeEnvironment, SharedLiminaConfig, SourceBoundaryConfig, defineConfig, getActiveCheckerExtensions, getActiveCheckers, loadConfig, validateLiminaConfig };
449
+ export { BuiltinCheckerPreset, BuiltinTaskName, CheckerConfig, CheckerExecutionKind, CheckerPreset, GraphConfig, GraphRule, GraphRuleDenyConfig, GraphRuleDepDenyEntry, GraphRuleRefDenyEntry, LiminaCommand, LiminaConfig, LiminaConfigEnv, LiminaConfigExport, LiminaConfigFn, LiminaConfigFnObject, LiminaConfigFnPromise, LoadConfigOptions, PackageAttwCheckConfig, PackageAttwProfile, PackageBoundaryCheckConfig, PackageCheckTarget, PackageCheckTool, PackageCheckToolSelection, PackageChecksConfig, PackagePublintCheckConfig, PathsConfig, PipelineStep, ProofAllowlistEntry, ProofConfig, ResolvedCheckerConfig, ResolvedLiminaConfig, RuntimeEnvironment, SharedLiminaConfig, SourceBoundaryConfig, defineConfig, getActiveCheckerExtensions, getActiveCheckers, loadConfig, validateLiminaConfig };