limina 0.0.2 → 0.0.4
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/README.md +17 -15
- package/chunks/{dep-jgc7X0zw.js → dep-DzYrmtQJ.js} +32 -49
- package/chunks/{dep-uPXyoC0V.js → dep-ZRIm_-Zk.js} +180 -253
- package/cli.js +732 -159
- package/config.d.ts +15 -23
- package/config.js +1 -1
- package/index.d.ts +7 -20
- package/index.js +3 -3
- package/package.json +7 -2
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
|
|
4
|
-
import { A as
|
|
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 { $ as resolveReferencePath, A as resolveInternalImport$1, B as collectGraphProjectRouteFromRoot, C as findTargetProject, D as isDtsProjectConfig, E as inferPackageProject$1, F as getPackageRootSpecifier, G as createFormatHost, H as collectSourceGraphProjectExtensions, I as getPublishDependencySections, J as getRawReferencePaths, K as formatReferences, L as isWorkspaceDependencySpecifier, M as collectImporters, N as collectWorkspacePackages, O as isRelativeSpecifier, P as findPackageForSpecifier, Q as resolveProjectConfigPath, R as collectCheckerEntryProjectRoutes, S as findPackageForFile, T as getTypecheckConfigPath, U as createExtensionPattern, V as collectGraphProjectRoutes, W as createExtraFileExtensions, X as parseProjectFileNamesForExtensions, Y as isOrdinaryTypecheckConfigPath, Z as readJsonConfig, _ as getDeniedRefRule, a as runSourceCheck, b as createFileOwnerLookup$1, c as GraphLogger, d as ProofLogger, f as ReleaseLogger, g as getDeniedDepRuleForSpecifier, h as getDeniedDepRuleForPackage, i as runCheckerTypecheck, j as shouldResolveThroughGraph$1, k as parseProject$1, l as PackageLogger, m as formatErrorMessage$1, n as createLiminaFlowReporter, o as runInit, p as clearCliScreen, q as getDtsCompanionConfigPath, r as runCheckerBuild, s as CliLogger, u as PathsLogger, v as normalizeGraphRules, w as formatArtifactDependencyPolicy, x as findImporterForFile$1, y as collectImportsFromFile$1, z as collectGraphProjectRoute } from "./chunks/dep-ZRIm_-Zk.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 =
|
|
228
|
-
const projectPaths = graphRoute.
|
|
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) {
|
|
@@ -436,8 +436,7 @@ const ATTW_PROFILE_IGNORED_RESOLUTIONS = {
|
|
|
436
436
|
"esm-only": ["node16-cjs"]
|
|
437
437
|
};
|
|
438
438
|
const nodeBuiltinSpecifiers = new Set(builtinModules.flatMap((specifier) => specifier.startsWith("node:") ? [specifier, specifier.slice(5)] : [specifier, `node:${specifier}`]));
|
|
439
|
-
|
|
440
|
-
function isRecord(value) {
|
|
439
|
+
function isRecord$1(value) {
|
|
441
440
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
442
441
|
}
|
|
443
442
|
function isPackageCheckTool(value) {
|
|
@@ -452,7 +451,7 @@ function toArrayBuffer(buffer) {
|
|
|
452
451
|
function collectSelfSpecifierMatchers(packageName, exportsField) {
|
|
453
452
|
const exact = new Set([packageName]);
|
|
454
453
|
const prefixes = [];
|
|
455
|
-
if (!isRecord(exportsField)) return {
|
|
454
|
+
if (!isRecord$1(exportsField)) return {
|
|
456
455
|
exact,
|
|
457
456
|
prefixes
|
|
458
457
|
};
|
|
@@ -535,8 +534,8 @@ function formatAttwProblem(problem) {
|
|
|
535
534
|
default: return `Unknown ATTW problem: ${JSON.stringify(problem)}`;
|
|
536
535
|
}
|
|
537
536
|
}
|
|
538
|
-
function
|
|
539
|
-
const checks =
|
|
537
|
+
function normalizeEntryChecks(entry) {
|
|
538
|
+
const checks = entry.checks ?? DEFAULT_PACKAGE_CHECKS;
|
|
540
539
|
const normalizedChecks = [];
|
|
541
540
|
for (const check of checks) {
|
|
542
541
|
if (!isPackageCheckTool(check)) throw new Error(`Invalid package check "${check}". Expected one of: publint, attw, boundary.`);
|
|
@@ -544,8 +543,8 @@ function normalizeTargetChecks(target) {
|
|
|
544
543
|
}
|
|
545
544
|
return normalizedChecks;
|
|
546
545
|
}
|
|
547
|
-
function
|
|
548
|
-
const configuredChecks =
|
|
546
|
+
function selectEntryChecks(entry, requestedTool) {
|
|
547
|
+
const configuredChecks = normalizeEntryChecks(entry);
|
|
549
548
|
if (!requestedTool || requestedTool === "all") return configuredChecks;
|
|
550
549
|
return configuredChecks.includes(requestedTool) ? [requestedTool] : [];
|
|
551
550
|
}
|
|
@@ -573,61 +572,84 @@ function readCwdPackageName(cwd, rootDir) {
|
|
|
573
572
|
throw new Error(`Unable to read package name from ${packageJsonPath}: ${formatErrorMessage$1(error)}`);
|
|
574
573
|
}
|
|
575
574
|
}
|
|
576
|
-
function
|
|
577
|
-
const names =
|
|
575
|
+
function formatConfiguredPackageEntryNames(entries) {
|
|
576
|
+
const names = entries.map((entry) => entry.name).filter(Boolean);
|
|
578
577
|
return names.length > 0 ? names.join(", ") : "(none)";
|
|
579
578
|
}
|
|
580
|
-
function
|
|
581
|
-
|
|
582
|
-
|
|
579
|
+
function getConfiguredPackageEntries(config) {
|
|
580
|
+
return config.package?.entries ?? [];
|
|
581
|
+
}
|
|
582
|
+
function normalizePackageNameFilters(packageNames) {
|
|
583
|
+
const normalizedNames = [];
|
|
584
|
+
for (const packageName of packageNames ?? []) {
|
|
585
|
+
const normalizedName = packageName.trim();
|
|
586
|
+
if (normalizedName && !normalizedNames.includes(normalizedName)) normalizedNames.push(normalizedName);
|
|
587
|
+
}
|
|
588
|
+
return normalizedNames;
|
|
589
|
+
}
|
|
590
|
+
function resolvePackageEntryOutDir(options) {
|
|
591
|
+
const outDir = options.entry.outDir;
|
|
592
|
+
if (typeof outDir !== "string" || outDir.trim().length === 0) throw new Error(`Invalid package entry at package.entries[${options.entryIndex}].outDir. Expected a non-empty string.`);
|
|
583
593
|
return path.resolve(options.config.rootDir, outDir);
|
|
584
594
|
}
|
|
585
|
-
function
|
|
586
|
-
|
|
595
|
+
function getPackageEntryLabel(options) {
|
|
596
|
+
const name = options.entry.name;
|
|
597
|
+
if (typeof name !== "string" || name.trim().length === 0) throw new Error(`Invalid package entry at package.entries[${options.entryIndex}].name. Expected a non-empty string.`);
|
|
598
|
+
return name.trim();
|
|
587
599
|
}
|
|
588
|
-
function
|
|
589
|
-
const outDir =
|
|
600
|
+
function createEntryPlan(options) {
|
|
601
|
+
const outDir = resolvePackageEntryOutDir({
|
|
590
602
|
config: options.config,
|
|
591
|
-
|
|
592
|
-
|
|
603
|
+
entry: options.entry,
|
|
604
|
+
entryIndex: options.entryIndex
|
|
593
605
|
});
|
|
594
606
|
return {
|
|
595
|
-
checks:
|
|
596
|
-
|
|
607
|
+
checks: selectEntryChecks(options.entry, options.requestedTool),
|
|
608
|
+
entryIndex: options.entryIndex,
|
|
609
|
+
label: getPackageEntryLabel({
|
|
610
|
+
entry: options.entry,
|
|
611
|
+
entryIndex: options.entryIndex
|
|
612
|
+
}),
|
|
597
613
|
outDir,
|
|
598
|
-
|
|
614
|
+
rawEntry: options.entry
|
|
599
615
|
};
|
|
600
616
|
}
|
|
601
|
-
function
|
|
602
|
-
const
|
|
603
|
-
if (
|
|
604
|
-
let
|
|
617
|
+
function createPackageEntrySelectionPlan(options) {
|
|
618
|
+
const entries = getConfiguredPackageEntries(options.config);
|
|
619
|
+
if (entries.length === 0) throw new Error("No package entries are configured.");
|
|
620
|
+
let selectedEntries;
|
|
605
621
|
let selectionReason;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
622
|
+
const packageNames = normalizePackageNameFilters(options.packageNames);
|
|
623
|
+
if (packageNames.length > 0) {
|
|
624
|
+
selectedEntries = packageNames.map((packageName) => {
|
|
625
|
+
const entry = entries.find((candidate) => candidate.name === packageName);
|
|
626
|
+
if (!entry) throw new Error([`No package entry named "${packageName}" is configured.`, `Configured package entries: ${formatConfiguredPackageEntryNames(entries)}.`].join(" "));
|
|
627
|
+
return entry;
|
|
628
|
+
});
|
|
629
|
+
selectionReason = `--package matched configured package entry name(s): ${packageNames.join(", ")}.`;
|
|
610
630
|
} else {
|
|
611
631
|
const cwdPackageName = readCwdPackageName(options.cwd, options.config.rootDir);
|
|
612
632
|
if (cwdPackageName) {
|
|
613
|
-
|
|
614
|
-
if (
|
|
633
|
+
selectedEntries = entries.filter((entry) => entry.name === cwdPackageName);
|
|
634
|
+
if (selectedEntries.length > 0) selectionReason = `nearest package.json name "${cwdPackageName}" matched configured package entry name.`;
|
|
635
|
+
else if (options.strictCwd) throw new Error([`Nearest package.json name "${cwdPackageName}" does not match a configured package entry.`, `Configured package entries: ${formatConfiguredPackageEntryNames(entries)}.`].join(" "));
|
|
615
636
|
else {
|
|
616
|
-
|
|
617
|
-
selectionReason = `nearest package.json name "${cwdPackageName}" did not match configured
|
|
637
|
+
selectedEntries = entries;
|
|
638
|
+
selectionReason = `nearest package.json name "${cwdPackageName}" did not match configured package entries; running all configured entries.`;
|
|
618
639
|
}
|
|
619
|
-
} else
|
|
620
|
-
|
|
621
|
-
|
|
640
|
+
} else if (options.strictCwd) throw new Error(["No package name was found from cwd up to the workspace root.", "Run from a configured package directory or pass --package <name>."].join(" "));
|
|
641
|
+
else {
|
|
642
|
+
selectedEntries = entries;
|
|
643
|
+
selectionReason = "No package name was found from cwd up to the workspace root; running all configured entries.";
|
|
622
644
|
}
|
|
623
645
|
}
|
|
624
646
|
return {
|
|
625
647
|
selectionReason,
|
|
626
|
-
|
|
648
|
+
entries: selectedEntries.map((entry) => createEntryPlan({
|
|
627
649
|
config: options.config,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
650
|
+
entry,
|
|
651
|
+
entryIndex: entries.indexOf(entry),
|
|
652
|
+
requestedTool: options.tool
|
|
631
653
|
}))
|
|
632
654
|
};
|
|
633
655
|
}
|
|
@@ -637,11 +659,11 @@ function logPackageCheckPlan(options) {
|
|
|
637
659
|
` config: ${toRelativePath(options.config.rootDir, options.config.configPath)}`,
|
|
638
660
|
` cwd: ${toRelativePath(options.config.rootDir, options.cwd)}`,
|
|
639
661
|
` selection: ${options.plan.selectionReason}`,
|
|
640
|
-
"
|
|
641
|
-
...options.plan.
|
|
642
|
-
` - ${
|
|
643
|
-
` outDir: ${toRelativePath(options.config.rootDir,
|
|
644
|
-
` checks: ${
|
|
662
|
+
" entries:",
|
|
663
|
+
...options.plan.entries.map((entry) => [
|
|
664
|
+
` - ${entry.label}`,
|
|
665
|
+
` outDir: ${toRelativePath(options.config.rootDir, entry.outDir)}`,
|
|
666
|
+
` checks: ${entry.checks.length > 0 ? entry.checks.join(", ") : "(none)"}`
|
|
645
667
|
].join("\n"))
|
|
646
668
|
].join("\n"));
|
|
647
669
|
}
|
|
@@ -665,16 +687,6 @@ async function readDistPackageJson(options) {
|
|
|
665
687
|
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
688
|
return JSON.parse(await readFile(options.packageJsonPath, "utf8"));
|
|
667
689
|
}
|
|
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
|
-
}
|
|
678
690
|
async function runPublintCheck(options) {
|
|
679
691
|
const task = options.flow?.start(`publint: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
680
692
|
PackageLogger.info(`publint started: ${options.label}`);
|
|
@@ -745,20 +757,19 @@ async function runBoundaryCheck(target, label, options = {}) {
|
|
|
745
757
|
task?.fail(`package boundary found ${violations.length} issue(s): ${label}`);
|
|
746
758
|
return false;
|
|
747
759
|
}
|
|
748
|
-
async function
|
|
749
|
-
const
|
|
750
|
-
...options.
|
|
760
|
+
async function runPackageCheckEntry(options) {
|
|
761
|
+
const entry = {
|
|
762
|
+
...options.rawEntry,
|
|
751
763
|
outDir: options.outDir
|
|
752
764
|
};
|
|
753
765
|
const label = options.label;
|
|
754
|
-
const outputPackageJsonPath = path.join(
|
|
755
|
-
const task = options.flow?.start(`package
|
|
766
|
+
const outputPackageJsonPath = path.join(entry.outDir, "package.json");
|
|
767
|
+
const task = options.flow?.start(`package entry: ${label}`, { depth: options.flowDepth ?? 0 });
|
|
756
768
|
let packedDist;
|
|
757
769
|
try {
|
|
758
|
-
await
|
|
770
|
+
await readDistPackageJson({
|
|
759
771
|
config: options.config,
|
|
760
772
|
label,
|
|
761
|
-
outDir: target.outDir,
|
|
762
773
|
packageJsonPath: outputPackageJsonPath
|
|
763
774
|
});
|
|
764
775
|
if (options.checks.includes("publint") || options.checks.includes("attw")) {
|
|
@@ -766,7 +777,7 @@ async function runPackageCheckTarget(options) {
|
|
|
766
777
|
PackageLogger.info(`package tarball packing started: ${label}`);
|
|
767
778
|
const packElapsed = createElapsedTimer();
|
|
768
779
|
try {
|
|
769
|
-
packedDist = await packOutputTarball(
|
|
780
|
+
packedDist = await packOutputTarball(entry.outDir);
|
|
770
781
|
} catch (error) {
|
|
771
782
|
PackageLogger.error(`package tarball failed: ${label}: ${formatErrorMessage$1(error)}`, packElapsed());
|
|
772
783
|
packTask?.fail(`package tarball failed: ${label}`, { error });
|
|
@@ -780,19 +791,19 @@ async function runPackageCheckTarget(options) {
|
|
|
780
791
|
flow: options.flow,
|
|
781
792
|
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
782
793
|
label,
|
|
783
|
-
strict:
|
|
794
|
+
strict: entry.publint?.strict ?? true,
|
|
784
795
|
tarball: packedDist.tarball
|
|
785
796
|
}) && passed;
|
|
786
797
|
if (options.checks.includes("attw")) passed = await runAttwCheck({
|
|
787
798
|
flow: options.flow,
|
|
788
799
|
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
789
800
|
label,
|
|
790
|
-
profile: options.attwProfile ??
|
|
801
|
+
profile: options.attwProfile ?? entry.attw?.profile ?? "esm-only",
|
|
791
802
|
tarball: packedDist.tarball
|
|
792
803
|
}) && passed;
|
|
793
804
|
if (options.checks.includes("boundary")) passed = await runBoundaryCheck({
|
|
794
|
-
...
|
|
795
|
-
outDir:
|
|
805
|
+
...entry.boundary,
|
|
806
|
+
outDir: entry.outDir
|
|
796
807
|
}, label, {
|
|
797
808
|
flow: options.flow,
|
|
798
809
|
flowDepth: (options.flowDepth ?? 0) + 1
|
|
@@ -859,10 +870,11 @@ async function runPackageCheck(options) {
|
|
|
859
870
|
const task = options.flow?.start("package check", { depth: options.flowDepth ?? 0 });
|
|
860
871
|
try {
|
|
861
872
|
PackageLogger.info("package check started");
|
|
862
|
-
const plan =
|
|
873
|
+
const plan = createPackageEntrySelectionPlan({
|
|
863
874
|
config: options.config,
|
|
864
875
|
cwd,
|
|
865
|
-
|
|
876
|
+
packageNames: options.packageNames,
|
|
877
|
+
strictCwd: false,
|
|
866
878
|
tool: options.tool
|
|
867
879
|
});
|
|
868
880
|
logPackageCheckPlan({
|
|
@@ -870,18 +882,18 @@ async function runPackageCheck(options) {
|
|
|
870
882
|
cwd,
|
|
871
883
|
plan
|
|
872
884
|
});
|
|
873
|
-
const
|
|
874
|
-
if (
|
|
885
|
+
const runnableEntries = plan.entries.filter((entry) => entry.checks.length > 0);
|
|
886
|
+
if (runnableEntries.length === 0) throw new Error(options.tool && options.tool !== "all" ? `No package entries have "${options.tool}" enabled.` : "No package checks are enabled.");
|
|
875
887
|
let passed = true;
|
|
876
|
-
for (const
|
|
888
|
+
for (const entry of runnableEntries) passed = await runPackageCheckEntry({
|
|
877
889
|
attwProfile: options.attwProfile,
|
|
878
|
-
checks:
|
|
890
|
+
checks: entry.checks,
|
|
879
891
|
config: options.config,
|
|
880
892
|
flow: options.flow,
|
|
881
893
|
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
882
|
-
label:
|
|
883
|
-
outDir:
|
|
884
|
-
|
|
894
|
+
label: entry.label,
|
|
895
|
+
outDir: entry.outDir,
|
|
896
|
+
rawEntry: entry.rawEntry
|
|
885
897
|
}) && passed;
|
|
886
898
|
if (passed) {
|
|
887
899
|
if (!options.flow?.interactive) PackageLogger.success("package check finished", elapsed());
|
|
@@ -1444,6 +1456,14 @@ const ignoredSemanticCompilerOptions = new Set([
|
|
|
1444
1456
|
"sourceRoot",
|
|
1445
1457
|
"tsBuildInfoFile"
|
|
1446
1458
|
]);
|
|
1459
|
+
const typeScriptCheckerExtensions = getCheckerAdapter("tsc")?.defaultExtensions ?? [];
|
|
1460
|
+
function getFirstClassCoverageExtensions(extensions) {
|
|
1461
|
+
return normalizeExtensions([...typeScriptCheckerExtensions, ...extensions]);
|
|
1462
|
+
}
|
|
1463
|
+
function getCheckerCoverageExtensions(checker) {
|
|
1464
|
+
if (!getCheckerAdapter(checker.preset)?.sourceGraph) return checker.extensions;
|
|
1465
|
+
return getFirstClassCoverageExtensions(checker.extensions);
|
|
1466
|
+
}
|
|
1447
1467
|
async function collectTsconfigPaths(config, pattern) {
|
|
1448
1468
|
return (await glob(pattern, {
|
|
1449
1469
|
cwd: config.rootDir,
|
|
@@ -1605,24 +1625,19 @@ function addCoverage(coverageByFile, filePath, source) {
|
|
|
1605
1625
|
}
|
|
1606
1626
|
function collectCoverage(options) {
|
|
1607
1627
|
const coverageByFile = /* @__PURE__ */ new Map();
|
|
1608
|
-
const
|
|
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)) {
|
|
1628
|
+
for (const route of options.graphRoutes) for (const graphProjectPath of route.projectPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, graphProjectPath, getFirstClassCoverageExtensions(route.extensions))) {
|
|
1611
1629
|
if (!options.sourceFiles.has(filePath)) continue;
|
|
1612
1630
|
addCoverage(coverageByFile, filePath, {
|
|
1613
1631
|
label: toRelativePath(options.config.rootDir, graphProjectPath),
|
|
1614
1632
|
type: "graph"
|
|
1615
1633
|
});
|
|
1616
1634
|
}
|
|
1617
|
-
for (const checkerTarget of options.checkerTargets) {
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
type: "checker"
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1635
|
+
for (const checkerTarget of options.checkerTargets) for (const configPath of checkerTarget.coverageConfigPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, configPath, getCheckerCoverageExtensions(checkerTarget.checker))) {
|
|
1636
|
+
if (!options.sourceFiles.has(filePath)) continue;
|
|
1637
|
+
addCoverage(coverageByFile, filePath, {
|
|
1638
|
+
label: `${toRelativePath(options.config.rootDir, configPath)} via ${checkerTarget.label}`,
|
|
1639
|
+
type: "checker"
|
|
1640
|
+
});
|
|
1626
1641
|
}
|
|
1627
1642
|
if (options.includeAllowlist !== false) for (const entry of options.allowlistEntries) {
|
|
1628
1643
|
if (!options.sourceFiles.has(entry.filePath)) continue;
|
|
@@ -1633,15 +1648,19 @@ function collectCoverage(options) {
|
|
|
1633
1648
|
}
|
|
1634
1649
|
return coverageByFile;
|
|
1635
1650
|
}
|
|
1636
|
-
function
|
|
1651
|
+
function collectProjectExtensionsByPath(routes) {
|
|
1652
|
+
const projectExtensionsByPath = /* @__PURE__ */ new Map();
|
|
1653
|
+
for (const route of routes) for (const projectPath of route.projectPaths) {
|
|
1654
|
+
const extensions = new Set([...projectExtensionsByPath.get(projectPath) ?? [], ...route.extensions]);
|
|
1655
|
+
projectExtensionsByPath.set(projectPath, [...extensions].sort());
|
|
1656
|
+
}
|
|
1657
|
+
return projectExtensionsByPath;
|
|
1658
|
+
}
|
|
1659
|
+
function parseConfig(config, configPath, extensions = []) {
|
|
1637
1660
|
const diagnostics = [];
|
|
1638
|
-
const
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
diagnostics.push(diagnostic);
|
|
1642
|
-
}
|
|
1643
|
-
});
|
|
1644
|
-
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
|
|
1661
|
+
const configObject = readJsonConfig(config, configPath);
|
|
1662
|
+
const parsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
|
|
1663
|
+
if (diagnostics.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
|
|
1645
1664
|
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
|
|
1646
1665
|
return {
|
|
1647
1666
|
fileNames: parsed.fileNames.map(normalizeAbsolutePath).sort(),
|
|
@@ -1704,8 +1723,9 @@ function addDtsConfigProblems(options) {
|
|
|
1704
1723
|
].join("\n"));
|
|
1705
1724
|
continue;
|
|
1706
1725
|
}
|
|
1707
|
-
const
|
|
1708
|
-
const
|
|
1726
|
+
const extensions = options.projectExtensionsByPath.get(configPath) ?? [];
|
|
1727
|
+
const dtsConfig = parseConfig(options.config, configPath, extensions);
|
|
1728
|
+
const localConfig = parseConfig(options.config, localConfigPath, extensions);
|
|
1709
1729
|
if (dtsConfig.options.composite !== true) options.problems.push([
|
|
1710
1730
|
"DTS config is not valid for tsc -b:",
|
|
1711
1731
|
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
@@ -1846,12 +1866,11 @@ function addDefaultTsconfigEnvironmentProblems(options) {
|
|
|
1846
1866
|
].join("\n"));
|
|
1847
1867
|
}
|
|
1848
1868
|
}
|
|
1849
|
-
function collectConfigFileOwners(config,
|
|
1869
|
+
function collectConfigFileOwners(config, graphRoutes, sourceFiles) {
|
|
1850
1870
|
const ownersByFile = /* @__PURE__ */ new Map();
|
|
1851
|
-
const
|
|
1852
|
-
for (const configPath of configPaths) {
|
|
1871
|
+
for (const route of graphRoutes) for (const configPath of route.projectPaths) {
|
|
1853
1872
|
if (!existsSync(configPath)) continue;
|
|
1854
|
-
for (const filePath of
|
|
1873
|
+
for (const filePath of parseProjectFileNamesForExtensions(config, configPath, route.extensions)) {
|
|
1855
1874
|
if (!sourceFiles.has(filePath)) continue;
|
|
1856
1875
|
const owners = ownersByFile.get(filePath) ?? [];
|
|
1857
1876
|
owners.push(configPath);
|
|
@@ -1873,19 +1892,6 @@ function addDuplicateGraphCoverageProblems(options) {
|
|
|
1873
1892
|
].join("\n"));
|
|
1874
1893
|
}
|
|
1875
1894
|
}
|
|
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
1895
|
function addAllowlistProblems(options) {
|
|
1890
1896
|
for (const entry of options.allowlistEntries) {
|
|
1891
1897
|
if (!existsSync(entry.filePath)) {
|
|
@@ -1913,34 +1919,25 @@ function addUncoveredSourceProblems(options) {
|
|
|
1913
1919
|
" reason: every file in config.source must be covered by a checker entry or an explicit allowlist entry."
|
|
1914
1920
|
].filter(Boolean).join("\n"));
|
|
1915
1921
|
}
|
|
1916
|
-
function addGraphOwner(ownersByConfigPath, configPath, checkerName) {
|
|
1917
|
-
const owners = ownersByConfigPath.get(configPath) ?? [];
|
|
1918
|
-
owners.push(checkerName);
|
|
1919
|
-
ownersByConfigPath.set(configPath, owners);
|
|
1920
|
-
}
|
|
1921
1922
|
async function runProofCheckInternal(config, options = {}) {
|
|
1922
1923
|
const problems = [];
|
|
1923
1924
|
const graphRouteCollection = collectGraphProjectRoutes(config);
|
|
1924
1925
|
const entryRouteCollection = collectCheckerEntryProjectRoutes(config);
|
|
1925
|
-
const graphProjectPaths = [...new Set(graphRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
|
|
1926
1926
|
const entryProjectPaths = [...new Set(entryRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
|
|
1927
1927
|
const entryProjectPathSet = new Set(entryProjectPaths);
|
|
1928
|
+
const entryProjectExtensionsByPath = collectProjectExtensionsByPath(entryRouteCollection.routes);
|
|
1928
1929
|
const dtsConfigPaths = await collectDtsConfigPaths(config);
|
|
1929
1930
|
const buildGraphConfigPaths = await collectBuildGraphConfigPaths(config);
|
|
1930
1931
|
const defaultTsconfigPaths = await collectDefaultTsconfigPaths(config);
|
|
1931
1932
|
const ordinaryTypecheckConfigPaths = await collectOrdinaryTypecheckConfigPaths(config);
|
|
1932
|
-
const graphOwnersByConfigPath = /* @__PURE__ */ new Map();
|
|
1933
1933
|
problems.push(...graphRouteCollection.problems);
|
|
1934
1934
|
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
1935
|
addDtsConfigProblems({
|
|
1940
1936
|
config,
|
|
1941
1937
|
dtsConfigPaths,
|
|
1942
1938
|
graphProjectPaths: entryProjectPathSet,
|
|
1943
|
-
problems
|
|
1939
|
+
problems,
|
|
1940
|
+
projectExtensionsByPath: entryProjectExtensionsByPath
|
|
1944
1941
|
});
|
|
1945
1942
|
addBuildGraphConfigProblems({
|
|
1946
1943
|
buildGraphConfigPaths,
|
|
@@ -1957,11 +1954,6 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1957
1954
|
ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
|
|
1958
1955
|
problems
|
|
1959
1956
|
});
|
|
1960
|
-
addDuplicateGraphOwnerProblems({
|
|
1961
|
-
config,
|
|
1962
|
-
graphOwnersByConfigPath,
|
|
1963
|
-
problems
|
|
1964
|
-
});
|
|
1965
1957
|
if (problems.length > 0) {
|
|
1966
1958
|
ProofLogger.error(problems.join("\n\n"));
|
|
1967
1959
|
return false;
|
|
@@ -1981,7 +1973,7 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1981
1973
|
allowlistEntries,
|
|
1982
1974
|
checkerTargets,
|
|
1983
1975
|
config,
|
|
1984
|
-
|
|
1976
|
+
graphRoutes: graphRouteCollection.routes,
|
|
1985
1977
|
includeAllowlist: false,
|
|
1986
1978
|
sourceFiles
|
|
1987
1979
|
});
|
|
@@ -1989,12 +1981,12 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1989
1981
|
allowlistEntries,
|
|
1990
1982
|
checkerTargets,
|
|
1991
1983
|
config,
|
|
1992
|
-
|
|
1984
|
+
graphRoutes: graphRouteCollection.routes,
|
|
1993
1985
|
sourceFiles
|
|
1994
1986
|
});
|
|
1995
1987
|
addDuplicateGraphCoverageProblems({
|
|
1996
1988
|
config,
|
|
1997
|
-
ownersByFile: collectConfigFileOwners(config,
|
|
1989
|
+
ownersByFile: collectConfigFileOwners(config, graphRouteCollection.routes, sourceFiles),
|
|
1998
1990
|
problems
|
|
1999
1991
|
});
|
|
2000
1992
|
addAllowlistProblems({
|
|
@@ -2047,6 +2039,545 @@ async function runProofCheck(config, options = {}) {
|
|
|
2047
2039
|
}
|
|
2048
2040
|
}
|
|
2049
2041
|
|
|
2042
|
+
//#endregion
|
|
2043
|
+
//#region src/package-release-consistency.ts
|
|
2044
|
+
var PackageReleaseConsistencyError = class extends Error {
|
|
2045
|
+
name = "PackageReleaseConsistencyError";
|
|
2046
|
+
};
|
|
2047
|
+
const semver = createRequire(import.meta.url)("semver");
|
|
2048
|
+
const REQUIRED_RELEASE_FILES = ["README.md", "LICENSE.md"];
|
|
2049
|
+
const SOURCE_MAPPING_URL_PATTERN = /(?:\/\/\s*#\s*sourceMappingURL\s*=|\/\*\s*#\s*sourceMappingURL\s*=)/u;
|
|
2050
|
+
function isRecord(value) {
|
|
2051
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2052
|
+
}
|
|
2053
|
+
function isLinkDependencySpecifier(specifier) {
|
|
2054
|
+
return specifier.startsWith("link:");
|
|
2055
|
+
}
|
|
2056
|
+
function createReleaseConsistencyState() {
|
|
2057
|
+
return {
|
|
2058
|
+
directWorkspaceDependencies: [],
|
|
2059
|
+
edges: /* @__PURE__ */ new Map(),
|
|
2060
|
+
missingWorkspaceDependencies: [],
|
|
2061
|
+
packedManifestProblems: [],
|
|
2062
|
+
privateWorkspaceDependencies: [],
|
|
2063
|
+
releaseHygieneProblems: [],
|
|
2064
|
+
registryMetadataCache: /* @__PURE__ */ new Map(),
|
|
2065
|
+
registryProblems: [],
|
|
2066
|
+
sourceLinkDependencies: [],
|
|
2067
|
+
unpublishedPackageNames: /* @__PURE__ */ new Set(),
|
|
2068
|
+
visitedPackages: /* @__PURE__ */ new Set()
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
function collectPublishDependencyEntries(manifest) {
|
|
2072
|
+
const entries = [];
|
|
2073
|
+
for (const { dependencies, name } of getPublishDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) entries.push({
|
|
2074
|
+
dependencyName,
|
|
2075
|
+
sectionName: name,
|
|
2076
|
+
specifier
|
|
2077
|
+
});
|
|
2078
|
+
return entries;
|
|
2079
|
+
}
|
|
2080
|
+
function addEdge(edges, importerName, dependencyName) {
|
|
2081
|
+
const dependencies = edges.get(importerName) ?? /* @__PURE__ */ new Set();
|
|
2082
|
+
dependencies.add(dependencyName);
|
|
2083
|
+
edges.set(importerName, dependencies);
|
|
2084
|
+
}
|
|
2085
|
+
function formatDependencyLocation(problem) {
|
|
2086
|
+
const dependency = problem.dependencyName ? ` -> ${problem.dependencyName}` : "";
|
|
2087
|
+
const section = problem.sectionName ? ` [${problem.sectionName}]` : "";
|
|
2088
|
+
const specifier = problem.specifier ? ` (${problem.specifier})` : "";
|
|
2089
|
+
return `${problem.importerName}${dependency}${section}${specifier}`;
|
|
2090
|
+
}
|
|
2091
|
+
function formatProblemLines(title, problems) {
|
|
2092
|
+
if (problems.length === 0) return [];
|
|
2093
|
+
return [
|
|
2094
|
+
"",
|
|
2095
|
+
title,
|
|
2096
|
+
...problems.map((problem) => ` - ${formatDependencyLocation(problem)}: ${problem.message}`)
|
|
2097
|
+
];
|
|
2098
|
+
}
|
|
2099
|
+
function getPackedDependencySpecifier(manifest, dependencyName) {
|
|
2100
|
+
for (const { dependencies } of getPublishDependencySections(manifest)) {
|
|
2101
|
+
const specifier = dependencies[dependencyName];
|
|
2102
|
+
if (specifier) return specifier;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
function getNpmPackageMetadataUrl(packageName) {
|
|
2106
|
+
return `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
2107
|
+
}
|
|
2108
|
+
async function fetchRegistryPackageMetadata(packageName, state) {
|
|
2109
|
+
if (state.registryMetadataCache.has(packageName)) return state.registryMetadataCache.get(packageName) ?? null;
|
|
2110
|
+
const response = await fetch(getNpmPackageMetadataUrl(packageName), { headers: { accept: "application/json" } });
|
|
2111
|
+
if (!response.ok) {
|
|
2112
|
+
state.registryMetadataCache.set(packageName, null);
|
|
2113
|
+
return null;
|
|
2114
|
+
}
|
|
2115
|
+
const metadata = await response.json();
|
|
2116
|
+
const registryMetadata = isRecord(metadata) ? metadata : null;
|
|
2117
|
+
state.registryMetadataCache.set(packageName, registryMetadata);
|
|
2118
|
+
return registryMetadata;
|
|
2119
|
+
}
|
|
2120
|
+
function findRegistryVersionMetadata(metadata, version) {
|
|
2121
|
+
if (!isRecord(metadata.versions)) return null;
|
|
2122
|
+
const versionMetadata = metadata.versions[version];
|
|
2123
|
+
return isRecord(versionMetadata) ? versionMetadata : null;
|
|
2124
|
+
}
|
|
2125
|
+
function execGitCommand(config, args) {
|
|
2126
|
+
return new Promise((resolve, reject) => {
|
|
2127
|
+
execFile("git", [
|
|
2128
|
+
"-C",
|
|
2129
|
+
config.rootDir,
|
|
2130
|
+
...args
|
|
2131
|
+
], {
|
|
2132
|
+
encoding: "utf8",
|
|
2133
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2134
|
+
}, (error, stdout) => {
|
|
2135
|
+
if (error) {
|
|
2136
|
+
reject(error);
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
resolve(stdout);
|
|
2140
|
+
});
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
async function hasWorkspacePackageChangesSinceGitHead(options) {
|
|
2144
|
+
const relativeDirectory = toRelativePath(options.config.rootDir, options.workspacePackage.directory);
|
|
2145
|
+
try {
|
|
2146
|
+
await execGitCommand(options.config, [
|
|
2147
|
+
"diff",
|
|
2148
|
+
"--quiet",
|
|
2149
|
+
options.gitHead,
|
|
2150
|
+
"--",
|
|
2151
|
+
relativeDirectory
|
|
2152
|
+
]);
|
|
2153
|
+
} catch (error) {
|
|
2154
|
+
if (isRecord(error) && error.code === 1) return true;
|
|
2155
|
+
throw error;
|
|
2156
|
+
}
|
|
2157
|
+
return (await execGitCommand(options.config, [
|
|
2158
|
+
"ls-files",
|
|
2159
|
+
"--others",
|
|
2160
|
+
"--exclude-standard",
|
|
2161
|
+
"--",
|
|
2162
|
+
relativeDirectory
|
|
2163
|
+
])).trim().length > 0;
|
|
2164
|
+
}
|
|
2165
|
+
async function verifyWorkspacePackagePublished(options) {
|
|
2166
|
+
const { state, workspacePackage } = options;
|
|
2167
|
+
const version = workspacePackage.manifest.version;
|
|
2168
|
+
if (!version || !semver.valid(version)) {
|
|
2169
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
2170
|
+
state.registryProblems.push({
|
|
2171
|
+
importerName: workspacePackage.name,
|
|
2172
|
+
message: ["workspace package must declare a valid semver version", "before another publishable package can depend on it"].join(" "),
|
|
2173
|
+
packageName: workspacePackage.name
|
|
2174
|
+
});
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
let metadata;
|
|
2178
|
+
try {
|
|
2179
|
+
metadata = await fetchRegistryPackageMetadata(workspacePackage.name, state);
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
2182
|
+
state.registryProblems.push({
|
|
2183
|
+
importerName: workspacePackage.name,
|
|
2184
|
+
message: [`unable to read npm registry metadata for ${workspacePackage.name}@${version}:`, formatErrorMessage$1(error)].join(" "),
|
|
2185
|
+
packageName: workspacePackage.name
|
|
2186
|
+
});
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
if (!metadata) {
|
|
2190
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
2191
|
+
state.registryProblems.push({
|
|
2192
|
+
importerName: workspacePackage.name,
|
|
2193
|
+
message: `${workspacePackage.name}@${version} is not published to the npm registry`,
|
|
2194
|
+
packageName: workspacePackage.name
|
|
2195
|
+
});
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
const versionMetadata = findRegistryVersionMetadata(metadata, version);
|
|
2199
|
+
if (!versionMetadata) {
|
|
2200
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
2201
|
+
state.registryProblems.push({
|
|
2202
|
+
importerName: workspacePackage.name,
|
|
2203
|
+
message: `${workspacePackage.name}@${version} is not published to the npm registry`,
|
|
2204
|
+
packageName: workspacePackage.name
|
|
2205
|
+
});
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
if (typeof versionMetadata.gitHead !== "string" || versionMetadata.gitHead.trim().length === 0) {
|
|
2209
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
2210
|
+
state.registryProblems.push({
|
|
2211
|
+
importerName: workspacePackage.name,
|
|
2212
|
+
message: [`${workspacePackage.name}@${version} registry metadata has no gitHead,`, "so limina cannot prove the published source baseline"].join(" "),
|
|
2213
|
+
packageName: workspacePackage.name
|
|
2214
|
+
});
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
let hasChanges;
|
|
2218
|
+
try {
|
|
2219
|
+
hasChanges = await hasWorkspacePackageChangesSinceGitHead({
|
|
2220
|
+
config: options.config,
|
|
2221
|
+
gitHead: versionMetadata.gitHead,
|
|
2222
|
+
workspacePackage
|
|
2223
|
+
});
|
|
2224
|
+
} catch (error) {
|
|
2225
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
2226
|
+
state.registryProblems.push({
|
|
2227
|
+
importerName: workspacePackage.name,
|
|
2228
|
+
message: [
|
|
2229
|
+
`unable to compare ${workspacePackage.name}@${version}`,
|
|
2230
|
+
`against published gitHead ${versionMetadata.gitHead}:`,
|
|
2231
|
+
formatErrorMessage$1(error)
|
|
2232
|
+
].join(" "),
|
|
2233
|
+
packageName: workspacePackage.name
|
|
2234
|
+
});
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
if (hasChanges) {
|
|
2238
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
2239
|
+
state.registryProblems.push({
|
|
2240
|
+
importerName: workspacePackage.name,
|
|
2241
|
+
message: [`${workspacePackage.name}@${version} has workspace changes`, `after the published npm registry gitHead ${versionMetadata.gitHead}`].join(" "),
|
|
2242
|
+
packageName: workspacePackage.name
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
async function visitWorkspacePackageDependencies(options) {
|
|
2247
|
+
const { config, importerName, isRoot, manifest, state, workspacePackagesByName } = options;
|
|
2248
|
+
for (const entry of collectPublishDependencyEntries(manifest)) {
|
|
2249
|
+
if (isLinkDependencySpecifier(entry.specifier)) {
|
|
2250
|
+
state.sourceLinkDependencies.push({
|
|
2251
|
+
dependencyName: entry.dependencyName,
|
|
2252
|
+
importerName,
|
|
2253
|
+
message: "publishable dependency sections must not use link:",
|
|
2254
|
+
sectionName: entry.sectionName,
|
|
2255
|
+
specifier: entry.specifier
|
|
2256
|
+
});
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
if (!isWorkspaceDependencySpecifier(entry.specifier)) continue;
|
|
2260
|
+
const targetPackage = workspacePackagesByName.get(entry.dependencyName);
|
|
2261
|
+
if (!targetPackage) {
|
|
2262
|
+
state.missingWorkspaceDependencies.push({
|
|
2263
|
+
dependencyName: entry.dependencyName,
|
|
2264
|
+
importerName,
|
|
2265
|
+
message: "workspace: publish dependency does not match a named workspace package",
|
|
2266
|
+
sectionName: entry.sectionName,
|
|
2267
|
+
specifier: entry.specifier
|
|
2268
|
+
});
|
|
2269
|
+
continue;
|
|
2270
|
+
}
|
|
2271
|
+
addEdge(state.edges, importerName, targetPackage.name);
|
|
2272
|
+
if (isRoot) state.directWorkspaceDependencies.push({
|
|
2273
|
+
dependencyName: entry.dependencyName,
|
|
2274
|
+
sectionName: entry.sectionName,
|
|
2275
|
+
targetPackage
|
|
2276
|
+
});
|
|
2277
|
+
if (targetPackage.manifest.private === true) {
|
|
2278
|
+
state.privateWorkspaceDependencies.push({
|
|
2279
|
+
dependencyName: entry.dependencyName,
|
|
2280
|
+
importerName,
|
|
2281
|
+
message: "publishable packages cannot depend on a private workspace package",
|
|
2282
|
+
packageName: targetPackage.name,
|
|
2283
|
+
sectionName: entry.sectionName,
|
|
2284
|
+
specifier: entry.specifier
|
|
2285
|
+
});
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
if (!state.visitedPackages.has(targetPackage.name)) {
|
|
2289
|
+
state.visitedPackages.add(targetPackage.name);
|
|
2290
|
+
await verifyWorkspacePackagePublished({
|
|
2291
|
+
config,
|
|
2292
|
+
state,
|
|
2293
|
+
workspacePackage: targetPackage
|
|
2294
|
+
});
|
|
2295
|
+
await visitWorkspacePackageDependencies({
|
|
2296
|
+
config,
|
|
2297
|
+
importerName: targetPackage.name,
|
|
2298
|
+
isRoot: false,
|
|
2299
|
+
manifest: targetPackage.manifest,
|
|
2300
|
+
state,
|
|
2301
|
+
workspacePackagesByName
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
async function unpackPackedPackage(tarball) {
|
|
2307
|
+
return await unpack(tarball);
|
|
2308
|
+
}
|
|
2309
|
+
function getPackedContentFiles(packedPackage) {
|
|
2310
|
+
const rootPrefix = `${packedPackage.rootDir}/`;
|
|
2311
|
+
const files = [];
|
|
2312
|
+
for (const file of packedPackage.files) {
|
|
2313
|
+
if (!file.name.startsWith(rootPrefix)) continue;
|
|
2314
|
+
files.push({
|
|
2315
|
+
data: file.data,
|
|
2316
|
+
relativePath: file.name.slice(rootPrefix.length).replaceAll("\\", "/")
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
return files;
|
|
2320
|
+
}
|
|
2321
|
+
function readPackedPackageJson(options) {
|
|
2322
|
+
const packageJsonFile = options.contentFiles.find((file) => file.relativePath === "package.json");
|
|
2323
|
+
if (!packageJsonFile) {
|
|
2324
|
+
options.state.releaseHygieneProblems.push({
|
|
2325
|
+
importerName: options.rootPackageName,
|
|
2326
|
+
message: "tarball does not contain package.json"
|
|
2327
|
+
});
|
|
2328
|
+
return null;
|
|
2329
|
+
}
|
|
2330
|
+
try {
|
|
2331
|
+
return JSON.parse(Buffer.from(packageJsonFile.data).toString("utf8"));
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
options.state.releaseHygieneProblems.push({
|
|
2334
|
+
importerName: options.rootPackageName,
|
|
2335
|
+
message: `tarball package.json is not valid JSON: ${formatErrorMessage$1(error)}`
|
|
2336
|
+
});
|
|
2337
|
+
return null;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
function isJavaScriptPackageFile(relativePath) {
|
|
2341
|
+
return /\.(?:cjs|mjs|js)$/u.test(relativePath);
|
|
2342
|
+
}
|
|
2343
|
+
function validateReleaseTarballHygiene(options) {
|
|
2344
|
+
const filePaths = new Set(options.contentFiles.map((file) => file.relativePath));
|
|
2345
|
+
const missingReleaseFiles = REQUIRED_RELEASE_FILES.filter((fileName) => !filePaths.has(fileName));
|
|
2346
|
+
if (missingReleaseFiles.length > 0) options.state.releaseHygieneProblems.push({
|
|
2347
|
+
importerName: options.rootPackageName,
|
|
2348
|
+
message: `tarball is missing required file(s): ${missingReleaseFiles.join(", ")}`
|
|
2349
|
+
});
|
|
2350
|
+
for (const file of options.contentFiles) {
|
|
2351
|
+
if (/\.map$/u.test(file.relativePath)) {
|
|
2352
|
+
options.state.releaseHygieneProblems.push({
|
|
2353
|
+
importerName: options.rootPackageName,
|
|
2354
|
+
message: `tarball contains source map file: ${file.relativePath}`
|
|
2355
|
+
});
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
if (!isJavaScriptPackageFile(file.relativePath)) continue;
|
|
2359
|
+
const source = Buffer.from(file.data).toString("utf8");
|
|
2360
|
+
if (SOURCE_MAPPING_URL_PATTERN.test(source)) options.state.releaseHygieneProblems.push({
|
|
2361
|
+
importerName: options.rootPackageName,
|
|
2362
|
+
message: `tarball JavaScript file contains sourceMappingURL directive: ${file.relativePath}`
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
function validatePackedManifest(options) {
|
|
2367
|
+
const { manifest, rootPackageName, state } = options;
|
|
2368
|
+
for (const entry of collectPublishDependencyEntries(manifest)) if (isWorkspaceDependencySpecifier(entry.specifier) || isLinkDependencySpecifier(entry.specifier)) state.packedManifestProblems.push({
|
|
2369
|
+
dependencyName: entry.dependencyName,
|
|
2370
|
+
importerName: rootPackageName,
|
|
2371
|
+
message: "packed package manifest must not expose workspace: or link: dependency specifiers",
|
|
2372
|
+
sectionName: entry.sectionName,
|
|
2373
|
+
specifier: entry.specifier
|
|
2374
|
+
});
|
|
2375
|
+
for (const dependency of state.directWorkspaceDependencies) {
|
|
2376
|
+
const packedSpecifier = getPackedDependencySpecifier(manifest, dependency.dependencyName);
|
|
2377
|
+
if (!packedSpecifier) {
|
|
2378
|
+
state.packedManifestProblems.push({
|
|
2379
|
+
dependencyName: dependency.dependencyName,
|
|
2380
|
+
importerName: rootPackageName,
|
|
2381
|
+
message: "packed package manifest must keep every source workspace publish dependency",
|
|
2382
|
+
sectionName: dependency.sectionName
|
|
2383
|
+
});
|
|
2384
|
+
continue;
|
|
2385
|
+
}
|
|
2386
|
+
if (isWorkspaceDependencySpecifier(packedSpecifier) || isLinkDependencySpecifier(packedSpecifier)) continue;
|
|
2387
|
+
const targetVersion = dependency.targetPackage.manifest.version;
|
|
2388
|
+
if (!targetVersion || !semver.satisfies(targetVersion, packedSpecifier, { includePrerelease: true })) state.packedManifestProblems.push({
|
|
2389
|
+
dependencyName: dependency.dependencyName,
|
|
2390
|
+
importerName: rootPackageName,
|
|
2391
|
+
message: `packed dependency range must include ${dependency.targetPackage.name}@${targetVersion ?? "(missing version)"}`,
|
|
2392
|
+
sectionName: dependency.sectionName,
|
|
2393
|
+
specifier: packedSpecifier
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
function createPublishOrder(rootPackageName, state) {
|
|
2398
|
+
const publishOrder = [];
|
|
2399
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2400
|
+
function visit(packageName) {
|
|
2401
|
+
if (seen.has(packageName)) return;
|
|
2402
|
+
seen.add(packageName);
|
|
2403
|
+
for (const dependencyName of state.edges.get(packageName) ?? []) visit(dependencyName);
|
|
2404
|
+
if (packageName === rootPackageName || state.unpublishedPackageNames.has(packageName)) publishOrder.push(packageName);
|
|
2405
|
+
}
|
|
2406
|
+
visit(rootPackageName);
|
|
2407
|
+
return publishOrder;
|
|
2408
|
+
}
|
|
2409
|
+
function createReleaseConsistencyError(options) {
|
|
2410
|
+
const { config, label, outDir, rootPackageName, state } = options;
|
|
2411
|
+
if (state.sourceLinkDependencies.length + state.privateWorkspaceDependencies.length + state.missingWorkspaceDependencies.length + state.registryProblems.length + state.releaseHygieneProblems.length + state.packedManifestProblems.length === 0) return null;
|
|
2412
|
+
const publishOrder = createPublishOrder(rootPackageName, state);
|
|
2413
|
+
const lines = [
|
|
2414
|
+
`package release check failed for ${label}:`,
|
|
2415
|
+
` output: ${toRelativePath(config.rootDir, outDir)}`,
|
|
2416
|
+
...formatProblemLines("Release tarball is not publishable:", state.releaseHygieneProblems),
|
|
2417
|
+
...formatProblemLines("Source manifest contains local link: publish dependencies:", state.sourceLinkDependencies),
|
|
2418
|
+
...formatProblemLines("Source manifest depends on private workspace packages:", state.privateWorkspaceDependencies),
|
|
2419
|
+
...formatProblemLines("Source manifest has invalid workspace: publish dependencies:", state.missingWorkspaceDependencies),
|
|
2420
|
+
...formatProblemLines("Workspace packages must be published before this package:", state.registryProblems),
|
|
2421
|
+
...formatProblemLines("Packed package manifest is inconsistent with workspace publish dependencies:", state.packedManifestProblems)
|
|
2422
|
+
];
|
|
2423
|
+
if (publishOrder.length > 1) lines.push("", `Suggested publish order: ${publishOrder.join(" -> ")}`);
|
|
2424
|
+
return new PackageReleaseConsistencyError(lines.join("\n"));
|
|
2425
|
+
}
|
|
2426
|
+
async function assertPackageReleaseConsistency(options) {
|
|
2427
|
+
const workspacePackages = await collectWorkspacePackages(options.config);
|
|
2428
|
+
const sourcePackage = workspacePackages.find((workspacePackage) => workspacePackage.name === options.outputManifest.name);
|
|
2429
|
+
const state = createReleaseConsistencyState();
|
|
2430
|
+
if (sourcePackage) {
|
|
2431
|
+
state.visitedPackages.add(sourcePackage.name);
|
|
2432
|
+
await visitWorkspacePackageDependencies({
|
|
2433
|
+
config: options.config,
|
|
2434
|
+
importerName: sourcePackage.name,
|
|
2435
|
+
isRoot: true,
|
|
2436
|
+
manifest: sourcePackage.manifest,
|
|
2437
|
+
state,
|
|
2438
|
+
workspacePackagesByName: new Map(workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]))
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
const contentFiles = getPackedContentFiles(await unpackPackedPackage(options.packedTarball));
|
|
2442
|
+
validateReleaseTarballHygiene({
|
|
2443
|
+
contentFiles,
|
|
2444
|
+
rootPackageName: options.outputManifest.name,
|
|
2445
|
+
state
|
|
2446
|
+
});
|
|
2447
|
+
const packedManifest = readPackedPackageJson({
|
|
2448
|
+
contentFiles,
|
|
2449
|
+
rootPackageName: options.outputManifest.name,
|
|
2450
|
+
state
|
|
2451
|
+
});
|
|
2452
|
+
if (packedManifest) validatePackedManifest({
|
|
2453
|
+
manifest: packedManifest,
|
|
2454
|
+
rootPackageName: options.outputManifest.name,
|
|
2455
|
+
state
|
|
2456
|
+
});
|
|
2457
|
+
const error = createReleaseConsistencyError({
|
|
2458
|
+
config: options.config,
|
|
2459
|
+
label: options.label,
|
|
2460
|
+
outDir: options.outDir,
|
|
2461
|
+
rootPackageName: options.outputManifest.name,
|
|
2462
|
+
state
|
|
2463
|
+
});
|
|
2464
|
+
if (error) throw error;
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
//#endregion
|
|
2468
|
+
//#region src/commands/release.ts
|
|
2469
|
+
function logReleaseCheckPlan(options) {
|
|
2470
|
+
ReleaseLogger.info([
|
|
2471
|
+
"Release check plan:",
|
|
2472
|
+
` config: ${toRelativePath(options.config.rootDir, options.config.configPath)}`,
|
|
2473
|
+
` cwd: ${toRelativePath(options.config.rootDir, options.cwd)}`,
|
|
2474
|
+
` selection: ${options.plan.selectionReason}`,
|
|
2475
|
+
" entries:",
|
|
2476
|
+
...options.plan.entries.map((entry) => [` - ${entry.label}`, ` outDir: ${toRelativePath(options.config.rootDir, entry.outDir)}`].join("\n"))
|
|
2477
|
+
].join("\n"));
|
|
2478
|
+
}
|
|
2479
|
+
async function packReleaseTarball(options) {
|
|
2480
|
+
const packTask = options.flow?.start(`release tarball: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
2481
|
+
ReleaseLogger.info(`release tarball packing started: ${options.label}`);
|
|
2482
|
+
const packElapsed = createElapsedTimer();
|
|
2483
|
+
try {
|
|
2484
|
+
const packedDist = await packOutputTarball(options.outDir);
|
|
2485
|
+
if (!options.flow?.interactive) ReleaseLogger.success(`release tarball packed: ${options.label}`, packElapsed());
|
|
2486
|
+
packTask?.pass();
|
|
2487
|
+
return packedDist;
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
ReleaseLogger.error(`release tarball failed: ${options.label}: ${formatErrorMessage$1(error)}`, packElapsed());
|
|
2490
|
+
packTask?.fail(`release tarball failed: ${options.label}`, { error });
|
|
2491
|
+
throw error;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
async function runReleaseCheckEntry(options) {
|
|
2495
|
+
const task = options.flow?.start(`release entry: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
2496
|
+
let packedDist;
|
|
2497
|
+
try {
|
|
2498
|
+
const outputPackageJsonPath = path.join(options.outDir, "package.json");
|
|
2499
|
+
const outputManifest = await readDistPackageJson({
|
|
2500
|
+
config: options.config,
|
|
2501
|
+
label: options.label,
|
|
2502
|
+
packageJsonPath: outputPackageJsonPath
|
|
2503
|
+
});
|
|
2504
|
+
if (outputManifest.private === true) throw new PackageReleaseConsistencyError([
|
|
2505
|
+
`package release check failed for ${options.label}:`,
|
|
2506
|
+
` output: ${toRelativePath(options.config.rootDir, options.outDir)}`,
|
|
2507
|
+
"",
|
|
2508
|
+
"Release tarball is not publishable:",
|
|
2509
|
+
` - ${outputManifest.name}: selected release package has "private": true; npm publish would reject it`
|
|
2510
|
+
].join("\n"));
|
|
2511
|
+
packedDist = await packReleaseTarball({
|
|
2512
|
+
flow: options.flow,
|
|
2513
|
+
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
2514
|
+
label: options.label,
|
|
2515
|
+
outDir: options.outDir
|
|
2516
|
+
});
|
|
2517
|
+
await assertPackageReleaseConsistency({
|
|
2518
|
+
config: options.config,
|
|
2519
|
+
label: options.label,
|
|
2520
|
+
outDir: options.outDir,
|
|
2521
|
+
outputManifest,
|
|
2522
|
+
packedTarball: packedDist.tarball
|
|
2523
|
+
});
|
|
2524
|
+
if (!options.flow?.interactive) ReleaseLogger.success(`release checks passed: ${options.label}`);
|
|
2525
|
+
task?.pass();
|
|
2526
|
+
return true;
|
|
2527
|
+
} catch (error) {
|
|
2528
|
+
if (error instanceof PackageReleaseConsistencyError) {
|
|
2529
|
+
ReleaseLogger.error(formatErrorMessage$1(error));
|
|
2530
|
+
task?.fail(`release checks failed: ${options.label}`);
|
|
2531
|
+
return false;
|
|
2532
|
+
}
|
|
2533
|
+
ReleaseLogger.error(`release checks failed: ${options.label}: ${formatErrorMessage$1(error)}`);
|
|
2534
|
+
task?.fail(`release checks failed: ${options.label}`, { error });
|
|
2535
|
+
throw error;
|
|
2536
|
+
} finally {
|
|
2537
|
+
if (packedDist) await packedDist.cleanup();
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
async function runReleaseCheck(options) {
|
|
2541
|
+
if (options.clearScreen ?? true) clearCliScreen();
|
|
2542
|
+
const elapsed = createElapsedTimer();
|
|
2543
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
2544
|
+
const task = options.flow?.start("release check", { depth: options.flowDepth ?? 0 });
|
|
2545
|
+
try {
|
|
2546
|
+
ReleaseLogger.info("release check started");
|
|
2547
|
+
const plan = createPackageEntrySelectionPlan({
|
|
2548
|
+
config: options.config,
|
|
2549
|
+
cwd,
|
|
2550
|
+
packageNames: options.packageNames,
|
|
2551
|
+
strictCwd: true
|
|
2552
|
+
});
|
|
2553
|
+
logReleaseCheckPlan({
|
|
2554
|
+
config: options.config,
|
|
2555
|
+
cwd,
|
|
2556
|
+
plan
|
|
2557
|
+
});
|
|
2558
|
+
let passed = true;
|
|
2559
|
+
for (const entry of plan.entries) passed = await runReleaseCheckEntry({
|
|
2560
|
+
config: options.config,
|
|
2561
|
+
flow: options.flow,
|
|
2562
|
+
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
2563
|
+
label: entry.label,
|
|
2564
|
+
outDir: entry.outDir
|
|
2565
|
+
}) && passed;
|
|
2566
|
+
if (passed) {
|
|
2567
|
+
if (!options.flow?.interactive) ReleaseLogger.success("release check finished", elapsed());
|
|
2568
|
+
task?.pass();
|
|
2569
|
+
} else {
|
|
2570
|
+
ReleaseLogger.error("release check finished with failures", elapsed());
|
|
2571
|
+
task?.fail("release check finished with failures");
|
|
2572
|
+
}
|
|
2573
|
+
return passed;
|
|
2574
|
+
} catch (error) {
|
|
2575
|
+
ReleaseLogger.error(`release check failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
2576
|
+
task?.fail("release check failed", { error });
|
|
2577
|
+
throw error;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2050
2581
|
//#endregion
|
|
2051
2582
|
//#region src/pipeline.ts
|
|
2052
2583
|
const builtInTaskNames = new Set([
|
|
@@ -2055,14 +2586,33 @@ const builtInTaskNames = new Set([
|
|
|
2055
2586
|
"graph:check",
|
|
2056
2587
|
"package:check",
|
|
2057
2588
|
"proof:check",
|
|
2589
|
+
"release:check",
|
|
2058
2590
|
"source:check"
|
|
2059
2591
|
]);
|
|
2060
2592
|
const defaultCheckPipeline = [
|
|
2061
2593
|
"graph:check",
|
|
2062
2594
|
"source:check",
|
|
2063
2595
|
"proof:check",
|
|
2596
|
+
"checker:build",
|
|
2064
2597
|
"checker:typecheck"
|
|
2065
2598
|
];
|
|
2599
|
+
function reportCheckerCapabilities(config, flow) {
|
|
2600
|
+
if (!flow) return;
|
|
2601
|
+
const firstClass = [];
|
|
2602
|
+
const sourceOnly = [];
|
|
2603
|
+
for (const checker of getActiveCheckers(config)) {
|
|
2604
|
+
const adapter = getCheckerAdapter(checker.preset);
|
|
2605
|
+
const label = `${checker.name} (${checker.preset})`;
|
|
2606
|
+
if (adapter?.tier === "first-class") firstClass.push(label);
|
|
2607
|
+
else if (adapter?.tier === "source-only") sourceOnly.push(label);
|
|
2608
|
+
}
|
|
2609
|
+
flow.info([
|
|
2610
|
+
"checker capability summary:",
|
|
2611
|
+
` first-class: ${firstClass.length > 0 ? firstClass.join(", ") : "(none)"}`,
|
|
2612
|
+
` source-only: ${sourceOnly.length > 0 ? sourceOnly.join(", ") : "(none)"}`,
|
|
2613
|
+
...sourceOnly.length > 0 ? [" note: source-only checkers get coverage proof and direct typecheck, but Limina does not parse their internal import graph."] : []
|
|
2614
|
+
].join("\n"), { depth: 1 });
|
|
2615
|
+
}
|
|
2066
2616
|
function isBuiltinTaskName(value) {
|
|
2067
2617
|
return builtInTaskNames.has(value);
|
|
2068
2618
|
}
|
|
@@ -2092,7 +2642,16 @@ async function runBuiltinTask(config, taskName, options = {}) {
|
|
|
2092
2642
|
config,
|
|
2093
2643
|
cwd: options.cwd,
|
|
2094
2644
|
flow: options.flow,
|
|
2095
|
-
flowDepth: 1
|
|
2645
|
+
flowDepth: 1,
|
|
2646
|
+
packageNames: options.packageNames
|
|
2647
|
+
});
|
|
2648
|
+
case "release:check": return runReleaseCheck({
|
|
2649
|
+
clearScreen: false,
|
|
2650
|
+
config,
|
|
2651
|
+
cwd: options.cwd,
|
|
2652
|
+
flow: options.flow,
|
|
2653
|
+
flowDepth: 1,
|
|
2654
|
+
packageNames: options.packageNames
|
|
2096
2655
|
});
|
|
2097
2656
|
case "checker:typecheck": return (await runCheckerTypecheck({
|
|
2098
2657
|
clearScreen: false,
|
|
@@ -2191,6 +2750,7 @@ async function runPipeline(config, pipelineName, options = {}) {
|
|
|
2191
2750
|
async function runDefaultCheck(config, options = {}) {
|
|
2192
2751
|
const normalizedSteps = defaultCheckPipeline.map(normalizePipelineStep);
|
|
2193
2752
|
const pipelineTask = options.flow?.start("default check", { collapseOnSuccess: false });
|
|
2753
|
+
reportCheckerCapabilities(config, options.flow);
|
|
2194
2754
|
for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
|
|
2195
2755
|
const label = getPipelineStepLabel(step);
|
|
2196
2756
|
pipelineTask?.fail(`default check blocked at ${label}`);
|
|
@@ -2221,11 +2781,10 @@ function parsePackageAttwProfile(profile) {
|
|
|
2221
2781
|
if (profile === "strict" || profile === "node16" || profile === "esm-only") return profile;
|
|
2222
2782
|
throw new Error(`Invalid package check --attw-profile "${profile}". Expected one of: strict, node16, esm-only.`);
|
|
2223
2783
|
}
|
|
2224
|
-
function
|
|
2225
|
-
if (
|
|
2226
|
-
const
|
|
2227
|
-
|
|
2228
|
-
return parsed;
|
|
2784
|
+
function parsePackageNames(packageName) {
|
|
2785
|
+
if (!packageName) return;
|
|
2786
|
+
const packageNames = (Array.isArray(packageName) ? packageName : [packageName]).map((name) => name.trim()).filter(Boolean);
|
|
2787
|
+
return packageNames.length > 0 ? packageNames : void 0;
|
|
2229
2788
|
}
|
|
2230
2789
|
function createCliFlow() {
|
|
2231
2790
|
clearCliScreen();
|
|
@@ -2247,13 +2806,14 @@ async function main() {
|
|
|
2247
2806
|
});
|
|
2248
2807
|
flow.outro("limina init finished");
|
|
2249
2808
|
});
|
|
2250
|
-
cli.command("check [pipeline]", "Run the default check or a configured pipeline").action(async (pipeline, flags) => {
|
|
2809
|
+
cli.command("check [pipeline]", "Run the default check or a configured pipeline").option("-p, --package <name>", "Run package-aware pipeline tasks for one package entry").action(async (pipeline, flags) => {
|
|
2251
2810
|
const flow = createCliFlow();
|
|
2252
2811
|
flow.intro("limina check");
|
|
2253
2812
|
const config = await load(flags, "check");
|
|
2254
2813
|
const passed = pipeline ? await runPipeline(config, pipeline, {
|
|
2255
2814
|
cwd: process.cwd(),
|
|
2256
|
-
flow
|
|
2815
|
+
flow,
|
|
2816
|
+
packageNames: parsePackageNames(flags.package)
|
|
2257
2817
|
}) : await runDefaultCheck(config, {
|
|
2258
2818
|
cwd: process.cwd(),
|
|
2259
2819
|
flow
|
|
@@ -2306,8 +2866,8 @@ async function main() {
|
|
|
2306
2866
|
if (!passed) process.exitCode = 1;
|
|
2307
2867
|
flow.outro(passed ? "limina source passed" : "limina source failed");
|
|
2308
2868
|
});
|
|
2309
|
-
cli.command("checker <action>", "Run configured checker
|
|
2310
|
-
if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected
|
|
2869
|
+
cli.command("checker <action>", "Run configured checker build or typecheck entries").action(async (action, flags) => {
|
|
2870
|
+
if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected build or typecheck.`);
|
|
2311
2871
|
const flow = createCliFlow();
|
|
2312
2872
|
flow.intro(`limina checker ${action}`);
|
|
2313
2873
|
if (action === "build") {
|
|
@@ -2324,14 +2884,13 @@ async function main() {
|
|
|
2324
2884
|
const result = await runCheckerTypecheck({
|
|
2325
2885
|
clearScreen: false,
|
|
2326
2886
|
config: await load(flags, "check"),
|
|
2327
|
-
concurrency: parseConcurrency(flags.concurrency),
|
|
2328
2887
|
cwd: process.cwd(),
|
|
2329
2888
|
flow
|
|
2330
2889
|
});
|
|
2331
2890
|
if (!result.passed) process.exitCode = 1;
|
|
2332
2891
|
flow.outro(result.passed ? "limina checker passed" : "limina checker failed");
|
|
2333
2892
|
});
|
|
2334
|
-
cli.command("package <action>", "Check configured published package outputs").option("-p, --package <name>", "Run
|
|
2893
|
+
cli.command("package <action>", "Check configured published package outputs").option("-p, --package <name>", "Run one package check entry").option("--tool <tool>", "Run one package check tool").option("--attw-profile <profile>", "Override the configured ATTW profile").action(async (action, flags) => {
|
|
2335
2894
|
if (action !== "check") throw new Error(`Unknown package action "${action}". Expected check.`);
|
|
2336
2895
|
const flow = createCliFlow();
|
|
2337
2896
|
flow.intro("limina package check");
|
|
@@ -2342,12 +2901,26 @@ async function main() {
|
|
|
2342
2901
|
config,
|
|
2343
2902
|
cwd: process.cwd(),
|
|
2344
2903
|
flow,
|
|
2345
|
-
|
|
2904
|
+
packageNames: parsePackageNames(flags.package),
|
|
2346
2905
|
tool: parsePackageTool(flags.tool)
|
|
2347
2906
|
});
|
|
2348
2907
|
if (!passed) process.exitCode = 1;
|
|
2349
2908
|
flow.outro(passed ? "limina package passed" : "limina package failed");
|
|
2350
2909
|
});
|
|
2910
|
+
cli.command("release <action>", "Check package release readiness").option("-p, --package <name>", "Run one release check package entry").action(async (action, flags) => {
|
|
2911
|
+
if (action !== "check") throw new Error(`Unknown release action "${action}". Expected check.`);
|
|
2912
|
+
const flow = createCliFlow();
|
|
2913
|
+
flow.intro("limina release check");
|
|
2914
|
+
const passed = await runReleaseCheck({
|
|
2915
|
+
clearScreen: false,
|
|
2916
|
+
config: await load(flags, "release"),
|
|
2917
|
+
cwd: process.cwd(),
|
|
2918
|
+
flow,
|
|
2919
|
+
packageNames: parsePackageNames(flags.package)
|
|
2920
|
+
});
|
|
2921
|
+
if (!passed) process.exitCode = 1;
|
|
2922
|
+
flow.outro(passed ? "limina release passed" : "limina release failed");
|
|
2923
|
+
});
|
|
2351
2924
|
cli.parse(process.argv, { run: false });
|
|
2352
2925
|
try {
|
|
2353
2926
|
await cli.runMatchedCommand();
|