limina 0.0.2 → 0.0.3

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,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunks/dep-lkQg1P9Q.js";
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 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
- import { builtinModules } from "node:module";
3
+ import { c as getCheckerAdapter, d as normalizeAbsolutePath, f as normalizeSlashes, g as toRelativePath, h as toPosixPath, i as loadConfig, l as normalizeExtensions, m as toAbsolutePath, n as getActiveCheckerExtensions, p as normalizeWorkspacePath, r as getActiveCheckers, u as isPathInsideDirectory } from "./chunks/dep-DzYrmtQJ.js";
4
+ import { A as shouldResolveThroughGraph$1, B as collectGraphProjectRoutes, C as formatArtifactDependencyPolicy, D as isRelativeSpecifier, E as isDtsProjectConfig, F as getPublishDependencySections, G as formatReferences, H as createExtensionPattern, I as isWorkspaceDependencySpecifier, J as isOrdinaryTypecheckConfigPath, K as getDtsCompanionConfigPath, L as collectCheckerEntryProjectRoutes, M as collectWorkspacePackages, N as findPackageForSpecifier, O as parseProject$1, P as getPackageRootSpecifier, Q as resolveReferencePath, R as collectGraphProjectRoute, S as findTargetProject, T as inferPackageProject$1, U as createExtraFileExtensions, V as collectSourceGraphProjectExtensions, W as createFormatHost, X as readJsonConfig, Y as parseProjectFileNamesForExtensions, Z as resolveProjectConfigPath, _ 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 getRawReferencePaths, r as runCheckerBuild, s as CliLogger, u as PathsLogger, v as collectImportsFromFile$1, w as getTypecheckConfigPath, x as findPackageForFile, y as createFileOwnerLookup$1, z as collectGraphProjectRouteFromRoot } from "./chunks/dep-UWxsul2A.js";
5
+ import { builtinModules, createRequire } from "node:module";
6
6
  import { cac } from "cac";
7
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
- import { spawn, spawnSync } from "node:child_process";
11
+ import { execFile, spawn, spawnSync } from "node:child_process";
12
12
  import { glob } from "tinyglobby";
13
13
  import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
14
14
  import { checkPackage, createPackageFromTarballData } from "@arethetypeswrong/core";
15
- import { pack } from "@publint/pack";
15
+ import { pack, unpack } from "@publint/pack";
16
16
  import { init, parse } from "es-module-lexer";
17
17
  import { tmpdir } from "node:os";
18
18
  import { publint } from "publint";
@@ -112,7 +112,7 @@ function addTypecheckParityProblems(config, dtsProject, problems) {
112
112
  ].join("\n"));
113
113
  return;
114
114
  }
115
- const typecheckProject = parseProject$1(config, typecheckConfigPath);
115
+ const typecheckProject = parseProject$1(config, typecheckConfigPath, dtsProject.extensions);
116
116
  for (const optionName of comparableTypecheckOptions) {
117
117
  const buildValue = dtsProject.options[optionName];
118
118
  const typecheckValue = typecheckProject.options[optionName];
@@ -224,9 +224,9 @@ function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath
224
224
  }
225
225
  }
226
226
  async function runGraphCheckInternal(config, options = {}) {
227
- const graphRoute = collectGraphProjectRoute(config);
228
- const projectPaths = graphRoute.projectPaths;
229
- const projects = projectPaths.map((projectPath) => parseProject$1(config, projectPath));
227
+ const graphRoute = collectSourceGraphProjectExtensions(config);
228
+ const projectPaths = [...graphRoute.projectExtensionsByPath.keys()].sort();
229
+ const projects = projectPaths.map((projectPath) => parseProject$1(config, projectPath, graphRoute.projectExtensionsByPath.get(projectPath)));
230
230
  const projectsByPath = new Map(projects.map((project) => [project.configPath, project]));
231
231
  const fileOwnerLookup = createFileOwnerLookup$1(projects);
232
232
  const packages = await collectWorkspacePackages(config);
@@ -255,7 +255,7 @@ async function runGraphCheckInternal(config, options = {}) {
255
255
  rules: graphRules
256
256
  });
257
257
  addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems);
258
- for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile$1(filePath)) {
258
+ for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile$1(filePath, config.rootDir)) {
259
259
  const rawDeniedDepRule = getDeniedDepRuleForSpecifier(graphRules, project.label, importRecord.specifier);
260
260
  if (rawDeniedDepRule) {
261
261
  addDeniedDepImportProblem({
@@ -267,7 +267,7 @@ async function runGraphCheckInternal(config, options = {}) {
267
267
  });
268
268
  continue;
269
269
  }
270
- const resolvedFilePath = resolveInternalImport$1(importRecord.specifier, filePath, project.options);
270
+ const resolvedFilePath = resolveInternalImport$1(importRecord.specifier, filePath, project.options, project.extensions);
271
271
  const targetPackage = findPackageForSpecifier(importRecord.specifier, packages);
272
272
  const importer = findImporterForFile$1(importRecord.filePath, importers);
273
273
  if (!resolvedFilePath) {
@@ -422,6 +422,362 @@ async function runGraphCheck(config, options = {}) {
422
422
  }
423
423
  }
424
424
 
425
+ //#endregion
426
+ //#region src/package-release-consistency.ts
427
+ var PackageReleaseConsistencyError = class extends Error {
428
+ name = "PackageReleaseConsistencyError";
429
+ };
430
+ const semver = createRequire(import.meta.url)("semver");
431
+ function isRecord$1(value) {
432
+ return typeof value === "object" && value !== null && !Array.isArray(value);
433
+ }
434
+ function isLinkDependencySpecifier(specifier) {
435
+ return specifier.startsWith("link:");
436
+ }
437
+ function createReleaseConsistencyState() {
438
+ return {
439
+ directWorkspaceDependencies: [],
440
+ edges: /* @__PURE__ */ new Map(),
441
+ missingWorkspaceDependencies: [],
442
+ packedManifestProblems: [],
443
+ privateWorkspaceDependencies: [],
444
+ registryMetadataCache: /* @__PURE__ */ new Map(),
445
+ registryProblems: [],
446
+ sourceLinkDependencies: [],
447
+ unpublishedPackageNames: /* @__PURE__ */ new Set(),
448
+ visitedPackages: /* @__PURE__ */ new Set()
449
+ };
450
+ }
451
+ function collectPublishDependencyEntries(manifest) {
452
+ const entries = [];
453
+ for (const { dependencies, name } of getPublishDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) entries.push({
454
+ dependencyName,
455
+ sectionName: name,
456
+ specifier
457
+ });
458
+ return entries;
459
+ }
460
+ function addEdge(edges, importerName, dependencyName) {
461
+ const dependencies = edges.get(importerName) ?? /* @__PURE__ */ new Set();
462
+ dependencies.add(dependencyName);
463
+ edges.set(importerName, dependencies);
464
+ }
465
+ function formatDependencyLocation(problem) {
466
+ const dependency = problem.dependencyName ? ` -> ${problem.dependencyName}` : "";
467
+ const section = problem.sectionName ? ` [${problem.sectionName}]` : "";
468
+ const specifier = problem.specifier ? ` (${problem.specifier})` : "";
469
+ return `${problem.importerName}${dependency}${section}${specifier}`;
470
+ }
471
+ function formatProblemLines(title, problems) {
472
+ if (problems.length === 0) return [];
473
+ return [
474
+ "",
475
+ title,
476
+ ...problems.map((problem) => ` - ${formatDependencyLocation(problem)}: ${problem.message}`)
477
+ ];
478
+ }
479
+ function getPackedDependencySpecifier(manifest, dependencyName) {
480
+ for (const { dependencies } of getPublishDependencySections(manifest)) {
481
+ const specifier = dependencies[dependencyName];
482
+ if (specifier) return specifier;
483
+ }
484
+ }
485
+ function getNpmPackageMetadataUrl(packageName) {
486
+ return `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
487
+ }
488
+ async function fetchRegistryPackageMetadata(packageName, state) {
489
+ if (state.registryMetadataCache.has(packageName)) return state.registryMetadataCache.get(packageName) ?? null;
490
+ const response = await fetch(getNpmPackageMetadataUrl(packageName), { headers: { accept: "application/vnd.npm.install-v1+json, application/json" } });
491
+ if (!response.ok) {
492
+ state.registryMetadataCache.set(packageName, null);
493
+ return null;
494
+ }
495
+ const metadata = await response.json();
496
+ const registryMetadata = isRecord$1(metadata) ? metadata : null;
497
+ state.registryMetadataCache.set(packageName, registryMetadata);
498
+ return registryMetadata;
499
+ }
500
+ function findRegistryVersionMetadata(metadata, version) {
501
+ if (!isRecord$1(metadata.versions)) return null;
502
+ const versionMetadata = metadata.versions[version];
503
+ return isRecord$1(versionMetadata) ? versionMetadata : null;
504
+ }
505
+ function execGitCommand(config, args) {
506
+ return new Promise((resolve, reject) => {
507
+ execFile("git", [
508
+ "-C",
509
+ config.rootDir,
510
+ ...args
511
+ ], {
512
+ encoding: "utf8",
513
+ maxBuffer: 10 * 1024 * 1024
514
+ }, (error, stdout) => {
515
+ if (error) {
516
+ reject(error);
517
+ return;
518
+ }
519
+ resolve(stdout);
520
+ });
521
+ });
522
+ }
523
+ async function hasWorkspacePackageChangesSinceGitHead(options) {
524
+ const relativeDirectory = toRelativePath(options.config.rootDir, options.workspacePackage.directory);
525
+ try {
526
+ await execGitCommand(options.config, [
527
+ "diff",
528
+ "--quiet",
529
+ options.gitHead,
530
+ "--",
531
+ relativeDirectory
532
+ ]);
533
+ } catch (error) {
534
+ if (isRecord$1(error) && error.code === 1) return true;
535
+ throw error;
536
+ }
537
+ return (await execGitCommand(options.config, [
538
+ "ls-files",
539
+ "--others",
540
+ "--exclude-standard",
541
+ "--",
542
+ relativeDirectory
543
+ ])).trim().length > 0;
544
+ }
545
+ async function verifyWorkspacePackagePublished(options) {
546
+ const { state, workspacePackage } = options;
547
+ const version = workspacePackage.manifest.version;
548
+ if (!version || !semver.valid(version)) {
549
+ state.unpublishedPackageNames.add(workspacePackage.name);
550
+ state.registryProblems.push({
551
+ importerName: workspacePackage.name,
552
+ message: ["workspace package must declare a valid semver version", "before another publishable package can depend on it"].join(" "),
553
+ packageName: workspacePackage.name
554
+ });
555
+ return;
556
+ }
557
+ let metadata;
558
+ try {
559
+ metadata = await fetchRegistryPackageMetadata(workspacePackage.name, state);
560
+ } catch (error) {
561
+ state.unpublishedPackageNames.add(workspacePackage.name);
562
+ state.registryProblems.push({
563
+ importerName: workspacePackage.name,
564
+ message: [`unable to read npm registry metadata for ${workspacePackage.name}@${version}:`, formatErrorMessage$1(error)].join(" "),
565
+ packageName: workspacePackage.name
566
+ });
567
+ return;
568
+ }
569
+ if (!metadata) {
570
+ state.unpublishedPackageNames.add(workspacePackage.name);
571
+ state.registryProblems.push({
572
+ importerName: workspacePackage.name,
573
+ message: `${workspacePackage.name}@${version} is not published to the npm registry`,
574
+ packageName: workspacePackage.name
575
+ });
576
+ return;
577
+ }
578
+ const versionMetadata = findRegistryVersionMetadata(metadata, version);
579
+ if (!versionMetadata) {
580
+ state.unpublishedPackageNames.add(workspacePackage.name);
581
+ state.registryProblems.push({
582
+ importerName: workspacePackage.name,
583
+ message: `${workspacePackage.name}@${version} is not published to the npm registry`,
584
+ packageName: workspacePackage.name
585
+ });
586
+ return;
587
+ }
588
+ if (typeof versionMetadata.gitHead !== "string" || versionMetadata.gitHead.trim().length === 0) {
589
+ state.unpublishedPackageNames.add(workspacePackage.name);
590
+ state.registryProblems.push({
591
+ importerName: workspacePackage.name,
592
+ message: [`${workspacePackage.name}@${version} registry metadata has no gitHead,`, "so limina cannot prove the published source baseline"].join(" "),
593
+ packageName: workspacePackage.name
594
+ });
595
+ return;
596
+ }
597
+ let hasChanges;
598
+ try {
599
+ hasChanges = await hasWorkspacePackageChangesSinceGitHead({
600
+ config: options.config,
601
+ gitHead: versionMetadata.gitHead,
602
+ workspacePackage
603
+ });
604
+ } catch (error) {
605
+ state.unpublishedPackageNames.add(workspacePackage.name);
606
+ state.registryProblems.push({
607
+ importerName: workspacePackage.name,
608
+ message: [
609
+ `unable to compare ${workspacePackage.name}@${version}`,
610
+ `against published gitHead ${versionMetadata.gitHead}:`,
611
+ formatErrorMessage$1(error)
612
+ ].join(" "),
613
+ packageName: workspacePackage.name
614
+ });
615
+ return;
616
+ }
617
+ if (hasChanges) {
618
+ state.unpublishedPackageNames.add(workspacePackage.name);
619
+ state.registryProblems.push({
620
+ importerName: workspacePackage.name,
621
+ message: [`${workspacePackage.name}@${version} has workspace changes`, `after the published npm registry gitHead ${versionMetadata.gitHead}`].join(" "),
622
+ packageName: workspacePackage.name
623
+ });
624
+ }
625
+ }
626
+ async function visitWorkspacePackageDependencies(options) {
627
+ const { config, importerName, isRoot, manifest, state, workspacePackagesByName } = options;
628
+ for (const entry of collectPublishDependencyEntries(manifest)) {
629
+ if (isLinkDependencySpecifier(entry.specifier)) {
630
+ state.sourceLinkDependencies.push({
631
+ dependencyName: entry.dependencyName,
632
+ importerName,
633
+ message: "publishable dependency sections must not use link:",
634
+ sectionName: entry.sectionName,
635
+ specifier: entry.specifier
636
+ });
637
+ continue;
638
+ }
639
+ if (!isWorkspaceDependencySpecifier(entry.specifier)) continue;
640
+ const targetPackage = workspacePackagesByName.get(entry.dependencyName);
641
+ if (!targetPackage) {
642
+ state.missingWorkspaceDependencies.push({
643
+ dependencyName: entry.dependencyName,
644
+ importerName,
645
+ message: "workspace: publish dependency does not match a named workspace package",
646
+ sectionName: entry.sectionName,
647
+ specifier: entry.specifier
648
+ });
649
+ continue;
650
+ }
651
+ addEdge(state.edges, importerName, targetPackage.name);
652
+ if (isRoot) state.directWorkspaceDependencies.push({
653
+ dependencyName: entry.dependencyName,
654
+ sectionName: entry.sectionName,
655
+ targetPackage
656
+ });
657
+ if (targetPackage.manifest.private === true) {
658
+ state.privateWorkspaceDependencies.push({
659
+ dependencyName: entry.dependencyName,
660
+ importerName,
661
+ message: "publishable packages cannot depend on a private workspace package",
662
+ packageName: targetPackage.name,
663
+ sectionName: entry.sectionName,
664
+ specifier: entry.specifier
665
+ });
666
+ continue;
667
+ }
668
+ if (!state.visitedPackages.has(targetPackage.name)) {
669
+ state.visitedPackages.add(targetPackage.name);
670
+ await verifyWorkspacePackagePublished({
671
+ config,
672
+ state,
673
+ workspacePackage: targetPackage
674
+ });
675
+ await visitWorkspacePackageDependencies({
676
+ config,
677
+ importerName: targetPackage.name,
678
+ isRoot: false,
679
+ manifest: targetPackage.manifest,
680
+ state,
681
+ workspacePackagesByName
682
+ });
683
+ }
684
+ }
685
+ }
686
+ async function readPackedPackageJson(tarball) {
687
+ const unpacked = await unpack(tarball);
688
+ const packageJsonFile = unpacked.files.find((file) => file.name === `${unpacked.rootDir}/package.json`);
689
+ if (!packageJsonFile) throw new Error("packed tarball does not contain package.json");
690
+ return JSON.parse(Buffer.from(packageJsonFile.data).toString("utf8"));
691
+ }
692
+ function validatePackedManifest(options) {
693
+ const { manifest, rootPackageName, state } = options;
694
+ for (const entry of collectPublishDependencyEntries(manifest)) if (isWorkspaceDependencySpecifier(entry.specifier) || isLinkDependencySpecifier(entry.specifier)) state.packedManifestProblems.push({
695
+ dependencyName: entry.dependencyName,
696
+ importerName: rootPackageName,
697
+ message: "packed package manifest must not expose workspace: or link: dependency specifiers",
698
+ sectionName: entry.sectionName,
699
+ specifier: entry.specifier
700
+ });
701
+ for (const dependency of state.directWorkspaceDependencies) {
702
+ const packedSpecifier = getPackedDependencySpecifier(manifest, dependency.dependencyName);
703
+ if (!packedSpecifier) {
704
+ state.packedManifestProblems.push({
705
+ dependencyName: dependency.dependencyName,
706
+ importerName: rootPackageName,
707
+ message: "packed package manifest must keep every source workspace publish dependency",
708
+ sectionName: dependency.sectionName
709
+ });
710
+ continue;
711
+ }
712
+ if (isWorkspaceDependencySpecifier(packedSpecifier) || isLinkDependencySpecifier(packedSpecifier)) continue;
713
+ const targetVersion = dependency.targetPackage.manifest.version;
714
+ if (!targetVersion || !semver.satisfies(targetVersion, packedSpecifier, { includePrerelease: true })) state.packedManifestProblems.push({
715
+ dependencyName: dependency.dependencyName,
716
+ importerName: rootPackageName,
717
+ message: `packed dependency range must include ${dependency.targetPackage.name}@${targetVersion ?? "(missing version)"}`,
718
+ sectionName: dependency.sectionName,
719
+ specifier: packedSpecifier
720
+ });
721
+ }
722
+ }
723
+ function createPublishOrder(rootPackageName, state) {
724
+ const publishOrder = [];
725
+ const seen = /* @__PURE__ */ new Set();
726
+ function visit(packageName) {
727
+ if (seen.has(packageName)) return;
728
+ seen.add(packageName);
729
+ for (const dependencyName of state.edges.get(packageName) ?? []) visit(dependencyName);
730
+ if (packageName === rootPackageName || state.unpublishedPackageNames.has(packageName)) publishOrder.push(packageName);
731
+ }
732
+ visit(rootPackageName);
733
+ return publishOrder;
734
+ }
735
+ function createReleaseConsistencyError(options) {
736
+ const { config, label, outDir, rootPackageName, state } = options;
737
+ if (state.sourceLinkDependencies.length + state.privateWorkspaceDependencies.length + state.missingWorkspaceDependencies.length + state.registryProblems.length + state.packedManifestProblems.length === 0) return null;
738
+ const publishOrder = createPublishOrder(rootPackageName, state);
739
+ const lines = [
740
+ `package release dependency consistency failed for ${label}:`,
741
+ ` output: ${toRelativePath(config.rootDir, outDir)}`,
742
+ ...formatProblemLines("Source manifest contains local link: publish dependencies:", state.sourceLinkDependencies),
743
+ ...formatProblemLines("Source manifest depends on private workspace packages:", state.privateWorkspaceDependencies),
744
+ ...formatProblemLines("Source manifest has invalid workspace: publish dependencies:", state.missingWorkspaceDependencies),
745
+ ...formatProblemLines("Workspace packages must be published before this package:", state.registryProblems),
746
+ ...formatProblemLines("Packed package manifest is inconsistent with workspace publish dependencies:", state.packedManifestProblems)
747
+ ];
748
+ if (publishOrder.length > 1) lines.push("", `Suggested publish order: ${publishOrder.join(" -> ")}`);
749
+ return new PackageReleaseConsistencyError(lines.join("\n"));
750
+ }
751
+ async function assertPackageReleaseConsistency(options) {
752
+ const workspacePackages = await collectWorkspacePackages(options.config);
753
+ const sourcePackage = workspacePackages.find((workspacePackage) => workspacePackage.name === options.outputManifest.name);
754
+ const state = createReleaseConsistencyState();
755
+ if (sourcePackage) {
756
+ state.visitedPackages.add(sourcePackage.name);
757
+ await visitWorkspacePackageDependencies({
758
+ config: options.config,
759
+ importerName: sourcePackage.name,
760
+ isRoot: true,
761
+ manifest: sourcePackage.manifest,
762
+ state,
763
+ workspacePackagesByName: new Map(workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]))
764
+ });
765
+ }
766
+ validatePackedManifest({
767
+ manifest: await readPackedPackageJson(options.packedTarball),
768
+ rootPackageName: options.outputManifest.name,
769
+ state
770
+ });
771
+ const error = createReleaseConsistencyError({
772
+ config: options.config,
773
+ label: options.label,
774
+ outDir: options.outDir,
775
+ rootPackageName: options.outputManifest.name,
776
+ state
777
+ });
778
+ if (error) throw error;
779
+ }
780
+
425
781
  //#endregion
426
782
  //#region src/commands/package.ts
427
783
  const DEFAULT_PACKAGE_CHECKS = [
@@ -666,13 +1022,14 @@ async function readDistPackageJson(options) {
666
1022
  return JSON.parse(await readFile(options.packageJsonPath, "utf8"));
667
1023
  }
668
1024
  async function assertPublicPackageMetadata(options) {
669
- if ((await readDistPackageJson({
1025
+ const manifest = await readDistPackageJson({
670
1026
  config: options.config,
671
1027
  label: options.label,
672
1028
  packageJsonPath: options.packageJsonPath
673
- })).private === true) return;
1029
+ });
1030
+ if (manifest.private === true) return manifest;
674
1031
  const missingFiles = REQUIRED_PUBLIC_PACKAGE_FILES.filter((fileName) => !existsSync(path.join(options.outDir, fileName)));
675
- if (missingFiles.length === 0) return;
1032
+ if (missingFiles.length === 0) return manifest;
676
1033
  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
1034
  }
678
1035
  async function runPublintCheck(options) {
@@ -755,13 +1112,13 @@ async function runPackageCheckTarget(options) {
755
1112
  const task = options.flow?.start(`package target: ${label}`, { depth: options.flowDepth ?? 0 });
756
1113
  let packedDist;
757
1114
  try {
758
- await assertPublicPackageMetadata({
1115
+ const outputManifest = await assertPublicPackageMetadata({
759
1116
  config: options.config,
760
1117
  label,
761
1118
  outDir: target.outDir,
762
1119
  packageJsonPath: outputPackageJsonPath
763
1120
  });
764
- if (options.checks.includes("publint") || options.checks.includes("attw")) {
1121
+ if (outputManifest.private !== true || options.checks.includes("publint") || options.checks.includes("attw")) {
765
1122
  const packTask = options.flow?.start(`package tarball: ${label}`, { depth: (options.flowDepth ?? 0) + 1 });
766
1123
  PackageLogger.info(`package tarball packing started: ${label}`);
767
1124
  const packElapsed = createElapsedTimer();
@@ -775,6 +1132,20 @@ async function runPackageCheckTarget(options) {
775
1132
  if (!options.flow?.interactive) PackageLogger.success(`package tarball packed: ${label}`, packElapsed());
776
1133
  packTask?.pass();
777
1134
  }
1135
+ if (outputManifest.private !== true) try {
1136
+ await assertPackageReleaseConsistency({
1137
+ config: options.config,
1138
+ label,
1139
+ outDir: target.outDir,
1140
+ outputManifest,
1141
+ packedTarball: packedDist.tarball
1142
+ });
1143
+ } catch (error) {
1144
+ if (!(error instanceof PackageReleaseConsistencyError)) throw error;
1145
+ PackageLogger.error(formatErrorMessage$1(error));
1146
+ task?.fail(`package checks failed: ${label}`);
1147
+ return false;
1148
+ }
778
1149
  let passed = true;
779
1150
  if (options.checks.includes("publint")) passed = await runPublintCheck({
780
1151
  flow: options.flow,
@@ -1444,6 +1815,14 @@ const ignoredSemanticCompilerOptions = new Set([
1444
1815
  "sourceRoot",
1445
1816
  "tsBuildInfoFile"
1446
1817
  ]);
1818
+ const typeScriptCheckerExtensions = getCheckerAdapter("tsc")?.defaultExtensions ?? [];
1819
+ function getFirstClassCoverageExtensions(extensions) {
1820
+ return normalizeExtensions([...typeScriptCheckerExtensions, ...extensions]);
1821
+ }
1822
+ function getCheckerCoverageExtensions(checker) {
1823
+ if (!getCheckerAdapter(checker.preset)?.sourceGraph) return checker.extensions;
1824
+ return getFirstClassCoverageExtensions(checker.extensions);
1825
+ }
1447
1826
  async function collectTsconfigPaths(config, pattern) {
1448
1827
  return (await glob(pattern, {
1449
1828
  cwd: config.rootDir,
@@ -1605,24 +1984,19 @@ function addCoverage(coverageByFile, filePath, source) {
1605
1984
  }
1606
1985
  function collectCoverage(options) {
1607
1986
  const coverageByFile = /* @__PURE__ */ new Map();
1608
- const proofFilePattern = createExtensionPattern(getActiveCheckerExtensions(options.config));
1609
- const typeScriptChecker = getActiveCheckers(options.config).find((checker) => checker.preset === "tsc");
1610
- for (const graphProjectPath of options.graphProjectPaths) for (const filePath of parseProjectFileNames(options.config, graphProjectPath, proofFilePattern)) {
1987
+ for (const route of options.graphRoutes) for (const graphProjectPath of route.projectPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, graphProjectPath, getFirstClassCoverageExtensions(route.extensions))) {
1611
1988
  if (!options.sourceFiles.has(filePath)) continue;
1612
1989
  addCoverage(coverageByFile, filePath, {
1613
1990
  label: toRelativePath(options.config.rootDir, graphProjectPath),
1614
1991
  type: "graph"
1615
1992
  });
1616
1993
  }
1617
- for (const checkerTarget of options.checkerTargets) {
1618
- const checkerExtensions = [...typeScriptChecker?.extensions ?? [], ...checkerTarget.checker.extensions];
1619
- for (const configPath of checkerTarget.coverageConfigPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, configPath, checkerExtensions)) {
1620
- if (!options.sourceFiles.has(filePath)) continue;
1621
- addCoverage(coverageByFile, filePath, {
1622
- label: `${toRelativePath(options.config.rootDir, configPath)} via ${checkerTarget.label}`,
1623
- type: "checker"
1624
- });
1625
- }
1994
+ for (const checkerTarget of options.checkerTargets) for (const configPath of checkerTarget.coverageConfigPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, configPath, getCheckerCoverageExtensions(checkerTarget.checker))) {
1995
+ if (!options.sourceFiles.has(filePath)) continue;
1996
+ addCoverage(coverageByFile, filePath, {
1997
+ label: `${toRelativePath(options.config.rootDir, configPath)} via ${checkerTarget.label}`,
1998
+ type: "checker"
1999
+ });
1626
2000
  }
1627
2001
  if (options.includeAllowlist !== false) for (const entry of options.allowlistEntries) {
1628
2002
  if (!options.sourceFiles.has(entry.filePath)) continue;
@@ -1633,15 +2007,19 @@ function collectCoverage(options) {
1633
2007
  }
1634
2008
  return coverageByFile;
1635
2009
  }
1636
- function parseConfig(config, configPath) {
2010
+ function collectProjectExtensionsByPath(routes) {
2011
+ const projectExtensionsByPath = /* @__PURE__ */ new Map();
2012
+ for (const route of routes) for (const projectPath of route.projectPaths) {
2013
+ const extensions = new Set([...projectExtensionsByPath.get(projectPath) ?? [], ...route.extensions]);
2014
+ projectExtensionsByPath.set(projectPath, [...extensions].sort());
2015
+ }
2016
+ return projectExtensionsByPath;
2017
+ }
2018
+ function parseConfig(config, configPath, extensions = []) {
1637
2019
  const diagnostics = [];
1638
- const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
1639
- ...ts.sys,
1640
- onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
1641
- diagnostics.push(diagnostic);
1642
- }
1643
- });
1644
- if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
2020
+ const configObject = readJsonConfig(config, configPath);
2021
+ const parsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
2022
+ if (diagnostics.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
1645
2023
  if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
1646
2024
  return {
1647
2025
  fileNames: parsed.fileNames.map(normalizeAbsolutePath).sort(),
@@ -1704,8 +2082,9 @@ function addDtsConfigProblems(options) {
1704
2082
  ].join("\n"));
1705
2083
  continue;
1706
2084
  }
1707
- const dtsConfig = parseConfig(options.config, configPath);
1708
- const localConfig = parseConfig(options.config, localConfigPath);
2085
+ const extensions = options.projectExtensionsByPath.get(configPath) ?? [];
2086
+ const dtsConfig = parseConfig(options.config, configPath, extensions);
2087
+ const localConfig = parseConfig(options.config, localConfigPath, extensions);
1709
2088
  if (dtsConfig.options.composite !== true) options.problems.push([
1710
2089
  "DTS config is not valid for tsc -b:",
1711
2090
  ` config: ${toRelativePath(options.config.rootDir, configPath)}`,
@@ -1846,12 +2225,11 @@ function addDefaultTsconfigEnvironmentProblems(options) {
1846
2225
  ].join("\n"));
1847
2226
  }
1848
2227
  }
1849
- function collectConfigFileOwners(config, configPaths, sourceFiles) {
2228
+ function collectConfigFileOwners(config, graphRoutes, sourceFiles) {
1850
2229
  const ownersByFile = /* @__PURE__ */ new Map();
1851
- const proofFilePattern = createExtensionPattern(getActiveCheckerExtensions(config));
1852
- for (const configPath of configPaths) {
2230
+ for (const route of graphRoutes) for (const configPath of route.projectPaths) {
1853
2231
  if (!existsSync(configPath)) continue;
1854
- for (const filePath of parseProjectFileNames(config, configPath, proofFilePattern)) {
2232
+ for (const filePath of parseProjectFileNamesForExtensions(config, configPath, route.extensions)) {
1855
2233
  if (!sourceFiles.has(filePath)) continue;
1856
2234
  const owners = ownersByFile.get(filePath) ?? [];
1857
2235
  owners.push(configPath);
@@ -1873,19 +2251,6 @@ function addDuplicateGraphCoverageProblems(options) {
1873
2251
  ].join("\n"));
1874
2252
  }
1875
2253
  }
1876
- function addDuplicateGraphOwnerProblems(options) {
1877
- for (const [configPath, ownerCheckerNames] of options.graphOwnersByConfigPath.entries()) {
1878
- const uniqueOwnerCheckerNames = [...new Set(ownerCheckerNames)].sort();
1879
- if (uniqueOwnerCheckerNames.length <= 1) continue;
1880
- options.problems.push([
1881
- "Duplicate checker graph declaration owner:",
1882
- ` config: ${toRelativePath(options.config.rootDir, configPath)}`,
1883
- " owned by:",
1884
- ...uniqueOwnerCheckerNames.map((checkerName) => ` - ${checkerName}`),
1885
- " reason: each tsconfig*.dts.json must be reached by exactly one graph-capable checker entry."
1886
- ].join("\n"));
1887
- }
1888
- }
1889
2254
  function addAllowlistProblems(options) {
1890
2255
  for (const entry of options.allowlistEntries) {
1891
2256
  if (!existsSync(entry.filePath)) {
@@ -1913,34 +2278,25 @@ function addUncoveredSourceProblems(options) {
1913
2278
  " reason: every file in config.source must be covered by a checker entry or an explicit allowlist entry."
1914
2279
  ].filter(Boolean).join("\n"));
1915
2280
  }
1916
- function addGraphOwner(ownersByConfigPath, configPath, checkerName) {
1917
- const owners = ownersByConfigPath.get(configPath) ?? [];
1918
- owners.push(checkerName);
1919
- ownersByConfigPath.set(configPath, owners);
1920
- }
1921
2281
  async function runProofCheckInternal(config, options = {}) {
1922
2282
  const problems = [];
1923
2283
  const graphRouteCollection = collectGraphProjectRoutes(config);
1924
2284
  const entryRouteCollection = collectCheckerEntryProjectRoutes(config);
1925
- const graphProjectPaths = [...new Set(graphRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
1926
2285
  const entryProjectPaths = [...new Set(entryRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
1927
2286
  const entryProjectPathSet = new Set(entryProjectPaths);
2287
+ const entryProjectExtensionsByPath = collectProjectExtensionsByPath(entryRouteCollection.routes);
1928
2288
  const dtsConfigPaths = await collectDtsConfigPaths(config);
1929
2289
  const buildGraphConfigPaths = await collectBuildGraphConfigPaths(config);
1930
2290
  const defaultTsconfigPaths = await collectDefaultTsconfigPaths(config);
1931
2291
  const ordinaryTypecheckConfigPaths = await collectOrdinaryTypecheckConfigPaths(config);
1932
- const graphOwnersByConfigPath = /* @__PURE__ */ new Map();
1933
2292
  problems.push(...graphRouteCollection.problems);
1934
2293
  problems.push(...entryRouteCollection.problems);
1935
- for (const route of graphRouteCollection.routes) for (const projectPath of route.projectPaths) {
1936
- if (!isDtsConfigPath(projectPath)) continue;
1937
- addGraphOwner(graphOwnersByConfigPath, projectPath, route.checkerName);
1938
- }
1939
2294
  addDtsConfigProblems({
1940
2295
  config,
1941
2296
  dtsConfigPaths,
1942
2297
  graphProjectPaths: entryProjectPathSet,
1943
- problems
2298
+ problems,
2299
+ projectExtensionsByPath: entryProjectExtensionsByPath
1944
2300
  });
1945
2301
  addBuildGraphConfigProblems({
1946
2302
  buildGraphConfigPaths,
@@ -1957,11 +2313,6 @@ async function runProofCheckInternal(config, options = {}) {
1957
2313
  ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
1958
2314
  problems
1959
2315
  });
1960
- addDuplicateGraphOwnerProblems({
1961
- config,
1962
- graphOwnersByConfigPath,
1963
- problems
1964
- });
1965
2316
  if (problems.length > 0) {
1966
2317
  ProofLogger.error(problems.join("\n\n"));
1967
2318
  return false;
@@ -1981,7 +2332,7 @@ async function runProofCheckInternal(config, options = {}) {
1981
2332
  allowlistEntries,
1982
2333
  checkerTargets,
1983
2334
  config,
1984
- graphProjectPaths,
2335
+ graphRoutes: graphRouteCollection.routes,
1985
2336
  includeAllowlist: false,
1986
2337
  sourceFiles
1987
2338
  });
@@ -1989,12 +2340,12 @@ async function runProofCheckInternal(config, options = {}) {
1989
2340
  allowlistEntries,
1990
2341
  checkerTargets,
1991
2342
  config,
1992
- graphProjectPaths,
2343
+ graphRoutes: graphRouteCollection.routes,
1993
2344
  sourceFiles
1994
2345
  });
1995
2346
  addDuplicateGraphCoverageProblems({
1996
2347
  config,
1997
- ownersByFile: collectConfigFileOwners(config, graphProjectPaths, sourceFiles),
2348
+ ownersByFile: collectConfigFileOwners(config, graphRouteCollection.routes, sourceFiles),
1998
2349
  problems
1999
2350
  });
2000
2351
  addAllowlistProblems({
@@ -2061,8 +2412,26 @@ const defaultCheckPipeline = [
2061
2412
  "graph:check",
2062
2413
  "source:check",
2063
2414
  "proof:check",
2415
+ "checker:build",
2064
2416
  "checker:typecheck"
2065
2417
  ];
2418
+ function reportCheckerCapabilities(config, flow) {
2419
+ if (!flow) return;
2420
+ const firstClass = [];
2421
+ const sourceOnly = [];
2422
+ for (const checker of getActiveCheckers(config)) {
2423
+ const adapter = getCheckerAdapter(checker.preset);
2424
+ const label = `${checker.name} (${checker.preset})`;
2425
+ if (adapter?.tier === "first-class") firstClass.push(label);
2426
+ else if (adapter?.tier === "source-only") sourceOnly.push(label);
2427
+ }
2428
+ flow.info([
2429
+ "checker capability summary:",
2430
+ ` first-class: ${firstClass.length > 0 ? firstClass.join(", ") : "(none)"}`,
2431
+ ` source-only: ${sourceOnly.length > 0 ? sourceOnly.join(", ") : "(none)"}`,
2432
+ ...sourceOnly.length > 0 ? [" note: source-only checkers get coverage proof and direct typecheck, but Limina does not parse their internal import graph."] : []
2433
+ ].join("\n"), { depth: 1 });
2434
+ }
2066
2435
  function isBuiltinTaskName(value) {
2067
2436
  return builtInTaskNames.has(value);
2068
2437
  }
@@ -2191,6 +2560,7 @@ async function runPipeline(config, pipelineName, options = {}) {
2191
2560
  async function runDefaultCheck(config, options = {}) {
2192
2561
  const normalizedSteps = defaultCheckPipeline.map(normalizePipelineStep);
2193
2562
  const pipelineTask = options.flow?.start("default check", { collapseOnSuccess: false });
2563
+ reportCheckerCapabilities(config, options.flow);
2194
2564
  for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
2195
2565
  const label = getPipelineStepLabel(step);
2196
2566
  pipelineTask?.fail(`default check blocked at ${label}`);
@@ -2221,12 +2591,6 @@ function parsePackageAttwProfile(profile) {
2221
2591
  if (profile === "strict" || profile === "node16" || profile === "esm-only") return profile;
2222
2592
  throw new Error(`Invalid package check --attw-profile "${profile}". Expected one of: strict, node16, esm-only.`);
2223
2593
  }
2224
- function parseConcurrency(value) {
2225
- if (value === void 0) return;
2226
- const parsed = Number(value);
2227
- if (!Number.isInteger(parsed) || parsed < 1) throw new Error("Invalid --concurrency value. Expected a positive integer.");
2228
- return parsed;
2229
- }
2230
2594
  function createCliFlow() {
2231
2595
  clearCliScreen();
2232
2596
  return createLiminaFlowReporter();
@@ -2306,8 +2670,8 @@ async function main() {
2306
2670
  if (!passed) process.exitCode = 1;
2307
2671
  flow.outro(passed ? "limina source passed" : "limina source failed");
2308
2672
  });
2309
- cli.command("checker <action>", "Run configured checker typecheck or build entries").option("--concurrency <n>", "Maximum concurrent checker processes").action(async (action, flags) => {
2310
- if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected typecheck or build.`);
2673
+ cli.command("checker <action>", "Run configured checker build or typecheck entries").action(async (action, flags) => {
2674
+ if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected build or typecheck.`);
2311
2675
  const flow = createCliFlow();
2312
2676
  flow.intro(`limina checker ${action}`);
2313
2677
  if (action === "build") {
@@ -2324,7 +2688,6 @@ async function main() {
2324
2688
  const result = await runCheckerTypecheck({
2325
2689
  clearScreen: false,
2326
2690
  config: await load(flags, "check"),
2327
- concurrency: parseConcurrency(flags.concurrency),
2328
2691
  cwd: process.cwd(),
2329
2692
  flow
2330
2693
  });