limina 0.0.3 → 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 +13 -10
- package/chunks/{dep-UWxsul2A.js → dep-ZRIm_-Zk.js} +2 -1
- package/cli.js +1691 -1481
- package/config.d.ts +14 -16
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunks/dep-lkQg1P9Q.js";
|
|
3
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
|
|
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
5
|
import { builtinModules, createRequire } from "node:module";
|
|
6
6
|
import { cac } from "cac";
|
|
7
7
|
import { createElapsedTimer } from "logaria/helper";
|
|
@@ -423,1072 +423,713 @@ async function runGraphCheck(config, options = {}) {
|
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
//#endregion
|
|
426
|
-
//#region src/package
|
|
427
|
-
|
|
428
|
-
|
|
426
|
+
//#region src/commands/package.ts
|
|
427
|
+
const DEFAULT_PACKAGE_CHECKS = [
|
|
428
|
+
"publint",
|
|
429
|
+
"attw",
|
|
430
|
+
"boundary"
|
|
431
|
+
];
|
|
432
|
+
const PACKAGE_CHECK_TOOLS = new Set(DEFAULT_PACKAGE_CHECKS);
|
|
433
|
+
const ATTW_PROFILE_IGNORED_RESOLUTIONS = {
|
|
434
|
+
strict: [],
|
|
435
|
+
node16: [],
|
|
436
|
+
"esm-only": ["node16-cjs"]
|
|
429
437
|
};
|
|
430
|
-
const
|
|
438
|
+
const nodeBuiltinSpecifiers = new Set(builtinModules.flatMap((specifier) => specifier.startsWith("node:") ? [specifier, specifier.slice(5)] : [specifier, `node:${specifier}`]));
|
|
431
439
|
function isRecord$1(value) {
|
|
432
440
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
433
441
|
}
|
|
434
|
-
function
|
|
435
|
-
return
|
|
442
|
+
function isPackageCheckTool(value) {
|
|
443
|
+
return PACKAGE_CHECK_TOOLS.has(value);
|
|
436
444
|
}
|
|
437
|
-
function
|
|
445
|
+
function isRelativeOrAbsoluteSpecifier(specifier) {
|
|
446
|
+
return specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("http:") || specifier.startsWith("https:") || specifier.startsWith("data:");
|
|
447
|
+
}
|
|
448
|
+
function toArrayBuffer(buffer) {
|
|
449
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
450
|
+
}
|
|
451
|
+
function collectSelfSpecifierMatchers(packageName, exportsField) {
|
|
452
|
+
const exact = new Set([packageName]);
|
|
453
|
+
const prefixes = [];
|
|
454
|
+
if (!isRecord$1(exportsField)) return {
|
|
455
|
+
exact,
|
|
456
|
+
prefixes
|
|
457
|
+
};
|
|
458
|
+
for (const exportKey of Object.keys(exportsField)) {
|
|
459
|
+
if (exportKey === ".") {
|
|
460
|
+
exact.add(packageName);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (!exportKey.startsWith("./")) continue;
|
|
464
|
+
const normalizedSubpath = exportKey.slice(2);
|
|
465
|
+
if (normalizedSubpath.length === 0) continue;
|
|
466
|
+
const wildcardIndex = normalizedSubpath.indexOf("*");
|
|
467
|
+
if (wildcardIndex !== -1) {
|
|
468
|
+
prefixes.push(`${packageName}/${normalizedSubpath.slice(0, wildcardIndex)}`);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
exact.add(`${packageName}/${normalizedSubpath}`);
|
|
472
|
+
}
|
|
438
473
|
return {
|
|
439
|
-
|
|
440
|
-
|
|
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()
|
|
474
|
+
exact,
|
|
475
|
+
prefixes
|
|
449
476
|
};
|
|
450
477
|
}
|
|
451
|
-
function
|
|
452
|
-
|
|
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;
|
|
478
|
+
function isAllowedSelfSpecifier(specifier, matchers) {
|
|
479
|
+
return matchers.exact.has(specifier) || matchers.prefixes.some((prefix) => specifier.startsWith(prefix));
|
|
459
480
|
}
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
dependencies.add(dependencyName);
|
|
463
|
-
edges.set(importerName, dependencies);
|
|
481
|
+
function normalizePublishedModulePath(relativeFilePath) {
|
|
482
|
+
return relativeFilePath.replaceAll("\\", "/");
|
|
464
483
|
}
|
|
465
|
-
function
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
return
|
|
484
|
+
function classifyRuntimeEnvironment(target, relativeFilePath) {
|
|
485
|
+
if (typeof target.environment === "function") return target.environment(relativeFilePath);
|
|
486
|
+
if (target.environment) return target.environment;
|
|
487
|
+
const normalizedPath = normalizePublishedModulePath(relativeFilePath);
|
|
488
|
+
return normalizedPath.startsWith("node/") || normalizedPath.startsWith("plugin/") ? "node" : "browser";
|
|
470
489
|
}
|
|
471
|
-
function
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
490
|
+
async function collectPublishedModuleFiles(directoryPath) {
|
|
491
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
492
|
+
const files = [];
|
|
493
|
+
for (const entry of entries) {
|
|
494
|
+
const absolutePath = path.join(directoryPath, entry.name);
|
|
495
|
+
if (entry.isDirectory()) {
|
|
496
|
+
files.push(...await collectPublishedModuleFiles(absolutePath));
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (/\.[cm]?js$/u.test(entry.name)) files.push(absolutePath);
|
|
500
|
+
}
|
|
501
|
+
return files;
|
|
478
502
|
}
|
|
479
|
-
function
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
503
|
+
function validatePublishedSpecifier(options) {
|
|
504
|
+
const { allowedExternalPackages, environment, packageName, selfSpecifiers, specifier } = options;
|
|
505
|
+
if (isRelativeOrAbsoluteSpecifier(specifier)) return null;
|
|
506
|
+
if (nodeBuiltinSpecifiers.has(specifier)) {
|
|
507
|
+
if (environment === "node") return null;
|
|
508
|
+
return `browser/runtime output must not import Node builtin "${specifier}"`;
|
|
509
|
+
}
|
|
510
|
+
const packageRoot = getPackageRootSpecifier(specifier);
|
|
511
|
+
if (packageRoot === packageName) {
|
|
512
|
+
if (isAllowedSelfSpecifier(specifier, selfSpecifiers)) return null;
|
|
513
|
+
return `self import "${specifier}" is not exposed by output package.json exports`;
|
|
483
514
|
}
|
|
515
|
+
if (allowedExternalPackages.has(packageRoot)) return null;
|
|
516
|
+
return `"${specifier}" resolves to package "${packageRoot}" which is not listed in dependencies, peerDependencies, optionalDependencies, or self exports`;
|
|
484
517
|
}
|
|
485
|
-
function
|
|
486
|
-
|
|
518
|
+
function formatAttwProblem(problem) {
|
|
519
|
+
const resolutionKind = "resolutionKind" in problem ? ` [resolution: ${problem.resolutionKind}]` : "";
|
|
520
|
+
const entrypoint = "entrypoint" in problem ? ` [entrypoint: ${problem.entrypoint}]` : "";
|
|
521
|
+
switch (problem.kind) {
|
|
522
|
+
case "NoResolution": return `No resolution${resolutionKind}${entrypoint}`;
|
|
523
|
+
case "UntypedResolution": return `Untyped resolution${resolutionKind}${entrypoint}`;
|
|
524
|
+
case "FalseESM": return `False ESM: ${problem.typesFileName} -> ${problem.implementationFileName}`;
|
|
525
|
+
case "FalseCJS": return `False CJS: ${problem.typesFileName} -> ${problem.implementationFileName}`;
|
|
526
|
+
case "CJSResolvesToESM": return `CJS resolves to ESM${resolutionKind}${entrypoint}`;
|
|
527
|
+
case "FallbackCondition": return `Fallback condition used${resolutionKind}${entrypoint}`;
|
|
528
|
+
case "NamedExports": return problem.isMissingAllNamed ? `Named exports missing: all named exports [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]` : `Named exports missing: ${problem.missing.join(", ") || "(none)"} [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]`;
|
|
529
|
+
case "FalseExportDefault": return `False export default [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]`;
|
|
530
|
+
case "MissingExportEquals": return `Missing export equals [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]`;
|
|
531
|
+
case "InternalResolutionError": return `Internal resolution error in ${problem.fileName} [option: ${problem.resolutionOption}] [module: ${problem.moduleSpecifier}]`;
|
|
532
|
+
case "UnexpectedModuleSyntax": return `Unexpected module syntax in ${problem.fileName}`;
|
|
533
|
+
case "CJSOnlyExportsDefault": return `CJS only exports default in ${problem.fileName}`;
|
|
534
|
+
default: return `Unknown ATTW problem: ${JSON.stringify(problem)}`;
|
|
535
|
+
}
|
|
487
536
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
537
|
+
function normalizeEntryChecks(entry) {
|
|
538
|
+
const checks = entry.checks ?? DEFAULT_PACKAGE_CHECKS;
|
|
539
|
+
const normalizedChecks = [];
|
|
540
|
+
for (const check of checks) {
|
|
541
|
+
if (!isPackageCheckTool(check)) throw new Error(`Invalid package check "${check}". Expected one of: publint, attw, boundary.`);
|
|
542
|
+
if (!normalizedChecks.includes(check)) normalizedChecks.push(check);
|
|
494
543
|
}
|
|
495
|
-
|
|
496
|
-
const registryMetadata = isRecord$1(metadata) ? metadata : null;
|
|
497
|
-
state.registryMetadataCache.set(packageName, registryMetadata);
|
|
498
|
-
return registryMetadata;
|
|
544
|
+
return normalizedChecks;
|
|
499
545
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
return
|
|
546
|
+
function selectEntryChecks(entry, requestedTool) {
|
|
547
|
+
const configuredChecks = normalizeEntryChecks(entry);
|
|
548
|
+
if (!requestedTool || requestedTool === "all") return configuredChecks;
|
|
549
|
+
return configuredChecks.includes(requestedTool) ? [requestedTool] : [];
|
|
504
550
|
}
|
|
505
|
-
function
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
resolve(stdout);
|
|
520
|
-
});
|
|
521
|
-
});
|
|
551
|
+
function findNearestPackageJsonPath(cwd, rootDir) {
|
|
552
|
+
const resolvedRootDir = path.resolve(rootDir);
|
|
553
|
+
let currentDir = path.resolve(cwd);
|
|
554
|
+
while (true) {
|
|
555
|
+
const relativeToRoot = path.relative(resolvedRootDir, currentDir);
|
|
556
|
+
if (!(relativeToRoot === "" || relativeToRoot !== ".." && !relativeToRoot.startsWith(`..${path.sep}`) && !path.isAbsolute(relativeToRoot))) return;
|
|
557
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
558
|
+
if (existsSync(packageJsonPath)) return packageJsonPath;
|
|
559
|
+
if (currentDir === resolvedRootDir) return;
|
|
560
|
+
const parentDir = path.dirname(currentDir);
|
|
561
|
+
if (parentDir === currentDir) return;
|
|
562
|
+
currentDir = parentDir;
|
|
563
|
+
}
|
|
522
564
|
}
|
|
523
|
-
|
|
524
|
-
const
|
|
565
|
+
function readCwdPackageName(cwd, rootDir) {
|
|
566
|
+
const packageJsonPath = findNearestPackageJsonPath(cwd, rootDir);
|
|
567
|
+
if (!packageJsonPath) return;
|
|
525
568
|
try {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
"--quiet",
|
|
529
|
-
options.gitHead,
|
|
530
|
-
"--",
|
|
531
|
-
relativeDirectory
|
|
532
|
-
]);
|
|
569
|
+
const manifest = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
570
|
+
return typeof manifest.name === "string" && manifest.name.trim() ? manifest.name.trim() : void 0;
|
|
533
571
|
} catch (error) {
|
|
534
|
-
|
|
535
|
-
throw error;
|
|
572
|
+
throw new Error(`Unable to read package name from ${packageJsonPath}: ${formatErrorMessage$1(error)}`);
|
|
536
573
|
}
|
|
537
|
-
return (await execGitCommand(options.config, [
|
|
538
|
-
"ls-files",
|
|
539
|
-
"--others",
|
|
540
|
-
"--exclude-standard",
|
|
541
|
-
"--",
|
|
542
|
-
relativeDirectory
|
|
543
|
-
])).trim().length > 0;
|
|
544
574
|
}
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
575
|
+
function formatConfiguredPackageEntryNames(entries) {
|
|
576
|
+
const names = entries.map((entry) => entry.name).filter(Boolean);
|
|
577
|
+
return names.length > 0 ? names.join(", ") : "(none)";
|
|
578
|
+
}
|
|
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);
|
|
556
587
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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.`);
|
|
593
|
+
return path.resolve(options.config.rootDir, outDir);
|
|
594
|
+
}
|
|
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();
|
|
599
|
+
}
|
|
600
|
+
function createEntryPlan(options) {
|
|
601
|
+
const outDir = resolvePackageEntryOutDir({
|
|
602
|
+
config: options.config,
|
|
603
|
+
entry: options.entry,
|
|
604
|
+
entryIndex: options.entryIndex
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
checks: selectEntryChecks(options.entry, options.requestedTool),
|
|
608
|
+
entryIndex: options.entryIndex,
|
|
609
|
+
label: getPackageEntryLabel({
|
|
610
|
+
entry: options.entry,
|
|
611
|
+
entryIndex: options.entryIndex
|
|
612
|
+
}),
|
|
613
|
+
outDir,
|
|
614
|
+
rawEntry: options.entry
|
|
615
|
+
};
|
|
616
|
+
}
|
|
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;
|
|
621
|
+
let selectionReason;
|
|
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;
|
|
566
628
|
});
|
|
567
|
-
|
|
629
|
+
selectionReason = `--package matched configured package entry name(s): ${packageNames.join(", ")}.`;
|
|
630
|
+
} else {
|
|
631
|
+
const cwdPackageName = readCwdPackageName(options.cwd, options.config.rootDir);
|
|
632
|
+
if (cwdPackageName) {
|
|
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(" "));
|
|
636
|
+
else {
|
|
637
|
+
selectedEntries = entries;
|
|
638
|
+
selectionReason = `nearest package.json name "${cwdPackageName}" did not match configured package entries; running all configured entries.`;
|
|
639
|
+
}
|
|
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.";
|
|
644
|
+
}
|
|
568
645
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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({
|
|
646
|
+
return {
|
|
647
|
+
selectionReason,
|
|
648
|
+
entries: selectedEntries.map((entry) => createEntryPlan({
|
|
600
649
|
config: options.config,
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
}
|
|
650
|
+
entry,
|
|
651
|
+
entryIndex: entries.indexOf(entry),
|
|
652
|
+
requestedTool: options.tool
|
|
653
|
+
}))
|
|
654
|
+
};
|
|
625
655
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
656
|
+
function logPackageCheckPlan(options) {
|
|
657
|
+
PackageLogger.info([
|
|
658
|
+
"Package check plan:",
|
|
659
|
+
` config: ${toRelativePath(options.config.rootDir, options.config.configPath)}`,
|
|
660
|
+
` cwd: ${toRelativePath(options.config.rootDir, options.cwd)}`,
|
|
661
|
+
` selection: ${options.plan.selectionReason}`,
|
|
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)"}`
|
|
667
|
+
].join("\n"))
|
|
668
|
+
].join("\n"));
|
|
669
|
+
}
|
|
670
|
+
async function packOutputTarball(outDir) {
|
|
671
|
+
const destination = await mkdtemp(path.join(tmpdir(), "__LIMINA_PACKAGE__"));
|
|
672
|
+
return {
|
|
673
|
+
cleanup: async () => {
|
|
674
|
+
await rm(destination, {
|
|
675
|
+
force: true,
|
|
676
|
+
recursive: true
|
|
677
|
+
}).catch(() => null);
|
|
678
|
+
},
|
|
679
|
+
tarball: await readFile(await pack(outDir, {
|
|
680
|
+
destination,
|
|
681
|
+
ignoreScripts: true,
|
|
682
|
+
packageManager: "pnpm"
|
|
683
|
+
}))
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
async function readDistPackageJson(options) {
|
|
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.`);
|
|
688
|
+
return JSON.parse(await readFile(options.packageJsonPath, "utf8"));
|
|
689
|
+
}
|
|
690
|
+
async function runPublintCheck(options) {
|
|
691
|
+
const task = options.flow?.start(`publint: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
692
|
+
PackageLogger.info(`publint started: ${options.label}`);
|
|
693
|
+
const publintElapsed = createElapsedTimer();
|
|
694
|
+
const { messages, pkg } = await publint({
|
|
695
|
+
pack: { tarball: toArrayBuffer(options.tarball) },
|
|
696
|
+
strict: options.strict
|
|
697
|
+
});
|
|
698
|
+
if (messages.length === 0) {
|
|
699
|
+
if (!options.flow?.interactive) PackageLogger.success(`publint passed: ${options.label}`, publintElapsed());
|
|
700
|
+
task?.pass();
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
for (const message of messages) {
|
|
704
|
+
const rendered = formatMessage(message, pkg) ?? message.code;
|
|
705
|
+
if (message.type === "error") {
|
|
706
|
+
PackageLogger.error(`[${options.label}] [publint] ${rendered}`);
|
|
649
707
|
continue;
|
|
650
708
|
}
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
});
|
|
709
|
+
if (message.type === "warning") {
|
|
710
|
+
PackageLogger.warn(`[${options.label}] [publint] ${rendered}`);
|
|
666
711
|
continue;
|
|
667
712
|
}
|
|
668
|
-
|
|
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
|
-
}
|
|
713
|
+
PackageLogger.info(`[${options.label}] [publint] ${rendered}`);
|
|
684
714
|
}
|
|
715
|
+
PackageLogger.error(`publint found ${messages.length} issue(s): ${options.label}`, publintElapsed());
|
|
716
|
+
task?.fail(`publint found ${messages.length} issue(s): ${options.label}`);
|
|
717
|
+
return false;
|
|
685
718
|
}
|
|
686
|
-
async function
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
719
|
+
async function runAttwCheck(options) {
|
|
720
|
+
const task = options.flow?.start(`attw: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
721
|
+
PackageLogger.info(`attw started: ${options.label} (profile: ${options.profile})`);
|
|
722
|
+
const attwElapsed = createElapsedTimer();
|
|
723
|
+
const result = await checkPackage(createPackageFromTarballData(options.tarball));
|
|
724
|
+
if (!result.types) {
|
|
725
|
+
PackageLogger.error(`[${options.label}] [attw] package has no types`);
|
|
726
|
+
PackageLogger.error(`attw failed: ${options.label}`, attwElapsed());
|
|
727
|
+
task?.fail(`attw failed: ${options.label}`);
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
const ignoredResolutions = ATTW_PROFILE_IGNORED_RESOLUTIONS[options.profile];
|
|
731
|
+
const problems = result.problems.filter((problem) => {
|
|
732
|
+
if ("resolutionKind" in problem) return !ignoredResolutions.includes(problem.resolutionKind);
|
|
733
|
+
return true;
|
|
700
734
|
});
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
});
|
|
735
|
+
if (problems.length === 0) {
|
|
736
|
+
if (!options.flow?.interactive) PackageLogger.success(`attw passed: ${options.label}`, attwElapsed());
|
|
737
|
+
task?.pass();
|
|
738
|
+
return true;
|
|
721
739
|
}
|
|
740
|
+
for (const problem of problems) PackageLogger.error(`[${options.label}] [attw] ${formatAttwProblem(problem)}`);
|
|
741
|
+
PackageLogger.error(`attw found ${problems.length} problem(s): ${options.label}`, attwElapsed());
|
|
742
|
+
task?.fail(`attw found ${problems.length} problem(s): ${options.label}`);
|
|
743
|
+
return false;
|
|
722
744
|
}
|
|
723
|
-
function
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
745
|
+
async function runBoundaryCheck(target, label, options = {}) {
|
|
746
|
+
const task = options.flow?.start(`package boundary: ${label}`, { depth: options.flowDepth ?? 0 });
|
|
747
|
+
PackageLogger.info(`package boundary started: ${label}`);
|
|
748
|
+
const boundaryElapsed = createElapsedTimer();
|
|
749
|
+
const violations = await auditPublishedPackageBoundaries(target);
|
|
750
|
+
if (violations.length === 0) {
|
|
751
|
+
if (!options.flow?.interactive) PackageLogger.success(`package boundary passed: ${label}`, boundaryElapsed());
|
|
752
|
+
task?.pass();
|
|
753
|
+
return true;
|
|
731
754
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
|
|
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"));
|
|
755
|
+
for (const violation of violations) PackageLogger.error(`[${label}] [boundary] ${violation.filePath} (${violation.environment}) imports "${violation.specifier}": ${violation.message}`);
|
|
756
|
+
PackageLogger.error(`package boundary found ${violations.length} issue(s): ${label}`, boundaryElapsed());
|
|
757
|
+
task?.fail(`package boundary found ${violations.length} issue(s): ${label}`);
|
|
758
|
+
return false;
|
|
750
759
|
}
|
|
751
|
-
async function
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
760
|
+
async function runPackageCheckEntry(options) {
|
|
761
|
+
const entry = {
|
|
762
|
+
...options.rawEntry,
|
|
763
|
+
outDir: options.outDir
|
|
764
|
+
};
|
|
765
|
+
const label = options.label;
|
|
766
|
+
const outputPackageJsonPath = path.join(entry.outDir, "package.json");
|
|
767
|
+
const task = options.flow?.start(`package entry: ${label}`, { depth: options.flowDepth ?? 0 });
|
|
768
|
+
let packedDist;
|
|
769
|
+
try {
|
|
770
|
+
await readDistPackageJson({
|
|
758
771
|
config: options.config,
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
manifest: sourcePackage.manifest,
|
|
762
|
-
state,
|
|
763
|
-
workspacePackagesByName: new Map(workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]))
|
|
772
|
+
label,
|
|
773
|
+
packageJsonPath: outputPackageJsonPath
|
|
764
774
|
});
|
|
775
|
+
if (options.checks.includes("publint") || options.checks.includes("attw")) {
|
|
776
|
+
const packTask = options.flow?.start(`package tarball: ${label}`, { depth: (options.flowDepth ?? 0) + 1 });
|
|
777
|
+
PackageLogger.info(`package tarball packing started: ${label}`);
|
|
778
|
+
const packElapsed = createElapsedTimer();
|
|
779
|
+
try {
|
|
780
|
+
packedDist = await packOutputTarball(entry.outDir);
|
|
781
|
+
} catch (error) {
|
|
782
|
+
PackageLogger.error(`package tarball failed: ${label}: ${formatErrorMessage$1(error)}`, packElapsed());
|
|
783
|
+
packTask?.fail(`package tarball failed: ${label}`, { error });
|
|
784
|
+
throw error;
|
|
785
|
+
}
|
|
786
|
+
if (!options.flow?.interactive) PackageLogger.success(`package tarball packed: ${label}`, packElapsed());
|
|
787
|
+
packTask?.pass();
|
|
788
|
+
}
|
|
789
|
+
let passed = true;
|
|
790
|
+
if (options.checks.includes("publint")) passed = await runPublintCheck({
|
|
791
|
+
flow: options.flow,
|
|
792
|
+
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
793
|
+
label,
|
|
794
|
+
strict: entry.publint?.strict ?? true,
|
|
795
|
+
tarball: packedDist.tarball
|
|
796
|
+
}) && passed;
|
|
797
|
+
if (options.checks.includes("attw")) passed = await runAttwCheck({
|
|
798
|
+
flow: options.flow,
|
|
799
|
+
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
800
|
+
label,
|
|
801
|
+
profile: options.attwProfile ?? entry.attw?.profile ?? "esm-only",
|
|
802
|
+
tarball: packedDist.tarball
|
|
803
|
+
}) && passed;
|
|
804
|
+
if (options.checks.includes("boundary")) passed = await runBoundaryCheck({
|
|
805
|
+
...entry.boundary,
|
|
806
|
+
outDir: entry.outDir
|
|
807
|
+
}, label, {
|
|
808
|
+
flow: options.flow,
|
|
809
|
+
flowDepth: (options.flowDepth ?? 0) + 1
|
|
810
|
+
}) && passed;
|
|
811
|
+
if (passed) {
|
|
812
|
+
if (!options.flow?.interactive) PackageLogger.success(`package checks passed: ${label}`);
|
|
813
|
+
task?.pass();
|
|
814
|
+
} else {
|
|
815
|
+
PackageLogger.error(`package checks failed: ${label}`);
|
|
816
|
+
task?.fail(`package checks failed: ${label}`);
|
|
817
|
+
}
|
|
818
|
+
return passed;
|
|
819
|
+
} catch (error) {
|
|
820
|
+
PackageLogger.error(`package checks failed: ${label}: ${formatErrorMessage$1(error)}`);
|
|
821
|
+
task?.fail(`package checks failed: ${label}`, { error });
|
|
822
|
+
throw error;
|
|
823
|
+
} finally {
|
|
824
|
+
if (packedDist) await packedDist.cleanup();
|
|
765
825
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
826
|
+
}
|
|
827
|
+
async function auditPublishedPackageBoundaries(target) {
|
|
828
|
+
const manifest = await readDistPackageJson({ packageJsonPath: path.join(target.outDir, "package.json") });
|
|
829
|
+
const allowedExternalPackages = new Set([
|
|
830
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
831
|
+
...Object.keys(manifest.peerDependencies ?? {}),
|
|
832
|
+
...Object.keys(manifest.optionalDependencies ?? {}),
|
|
833
|
+
...target.ignoredExternalPackages ?? []
|
|
834
|
+
]);
|
|
835
|
+
const selfSpecifiers = collectSelfSpecifierMatchers(manifest.name, manifest.exports);
|
|
836
|
+
const publishedFiles = await collectPublishedModuleFiles(target.outDir);
|
|
837
|
+
const violations = [];
|
|
838
|
+
await init;
|
|
839
|
+
for (const filePath of publishedFiles) {
|
|
840
|
+
const relativeFilePath = path.relative(target.outDir, filePath);
|
|
841
|
+
const environment = classifyRuntimeEnvironment(target, relativeFilePath);
|
|
842
|
+
const [importSpecifiers] = parse(await readFile(filePath, "utf8"));
|
|
843
|
+
for (const importSpecifier of importSpecifiers) {
|
|
844
|
+
if (!importSpecifier.n) continue;
|
|
845
|
+
const message = validatePublishedSpecifier({
|
|
846
|
+
allowedExternalPackages,
|
|
847
|
+
environment,
|
|
848
|
+
packageName: manifest.name,
|
|
849
|
+
selfSpecifiers,
|
|
850
|
+
specifier: importSpecifier.n
|
|
851
|
+
});
|
|
852
|
+
if (!message) continue;
|
|
853
|
+
violations.push({
|
|
854
|
+
environment,
|
|
855
|
+
filePath: relativeFilePath,
|
|
856
|
+
message,
|
|
857
|
+
specifier: importSpecifier.n
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return violations.toSorted((left, right) => {
|
|
862
|
+
if (left.filePath === right.filePath) return left.specifier.localeCompare(right.specifier);
|
|
863
|
+
return left.filePath.localeCompare(right.filePath);
|
|
777
864
|
});
|
|
778
|
-
|
|
865
|
+
}
|
|
866
|
+
async function runPackageCheck(options) {
|
|
867
|
+
if (options.clearScreen ?? true) clearCliScreen();
|
|
868
|
+
const elapsed = createElapsedTimer();
|
|
869
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
870
|
+
const task = options.flow?.start("package check", { depth: options.flowDepth ?? 0 });
|
|
871
|
+
try {
|
|
872
|
+
PackageLogger.info("package check started");
|
|
873
|
+
const plan = createPackageEntrySelectionPlan({
|
|
874
|
+
config: options.config,
|
|
875
|
+
cwd,
|
|
876
|
+
packageNames: options.packageNames,
|
|
877
|
+
strictCwd: false,
|
|
878
|
+
tool: options.tool
|
|
879
|
+
});
|
|
880
|
+
logPackageCheckPlan({
|
|
881
|
+
config: options.config,
|
|
882
|
+
cwd,
|
|
883
|
+
plan
|
|
884
|
+
});
|
|
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.");
|
|
887
|
+
let passed = true;
|
|
888
|
+
for (const entry of runnableEntries) passed = await runPackageCheckEntry({
|
|
889
|
+
attwProfile: options.attwProfile,
|
|
890
|
+
checks: entry.checks,
|
|
891
|
+
config: options.config,
|
|
892
|
+
flow: options.flow,
|
|
893
|
+
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
894
|
+
label: entry.label,
|
|
895
|
+
outDir: entry.outDir,
|
|
896
|
+
rawEntry: entry.rawEntry
|
|
897
|
+
}) && passed;
|
|
898
|
+
if (passed) {
|
|
899
|
+
if (!options.flow?.interactive) PackageLogger.success("package check finished", elapsed());
|
|
900
|
+
task?.pass();
|
|
901
|
+
} else {
|
|
902
|
+
PackageLogger.error("package check finished with failures", elapsed());
|
|
903
|
+
task?.fail("package check finished with failures");
|
|
904
|
+
}
|
|
905
|
+
return passed;
|
|
906
|
+
} catch (error) {
|
|
907
|
+
PackageLogger.error(`package check failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
908
|
+
task?.fail("package check failed", { error });
|
|
909
|
+
throw error;
|
|
910
|
+
}
|
|
779
911
|
}
|
|
780
912
|
|
|
781
913
|
//#endregion
|
|
782
|
-
//#region src/
|
|
783
|
-
const
|
|
784
|
-
"
|
|
785
|
-
"
|
|
786
|
-
"
|
|
914
|
+
//#region src/package-exports.ts
|
|
915
|
+
const defaultSourceExtensions = [
|
|
916
|
+
".ts",
|
|
917
|
+
".tsx",
|
|
918
|
+
".mts",
|
|
919
|
+
".cts",
|
|
920
|
+
".d.ts",
|
|
921
|
+
".d.mts",
|
|
922
|
+
".d.cts"
|
|
787
923
|
];
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
"
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
924
|
+
const defaultConditionPriority = [
|
|
925
|
+
"source",
|
|
926
|
+
"development",
|
|
927
|
+
"types",
|
|
928
|
+
"import",
|
|
929
|
+
"module",
|
|
930
|
+
"default",
|
|
931
|
+
"require"
|
|
932
|
+
];
|
|
933
|
+
const defaultArtifactDirectories = [
|
|
934
|
+
"dist",
|
|
935
|
+
"build",
|
|
936
|
+
"lib",
|
|
937
|
+
"esm",
|
|
938
|
+
"cjs",
|
|
939
|
+
"out"
|
|
940
|
+
];
|
|
941
|
+
function configuredArtifactDirectories(config) {
|
|
942
|
+
return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
|
|
804
943
|
}
|
|
805
|
-
function
|
|
806
|
-
return
|
|
944
|
+
function configuredSourceExtensions(config) {
|
|
945
|
+
return config.paths?.sourceExtensions ?? defaultSourceExtensions;
|
|
807
946
|
}
|
|
808
|
-
function
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
if (!exportKey.startsWith("./")) continue;
|
|
821
|
-
const normalizedSubpath = exportKey.slice(2);
|
|
822
|
-
if (normalizedSubpath.length === 0) continue;
|
|
823
|
-
const wildcardIndex = normalizedSubpath.indexOf("*");
|
|
824
|
-
if (wildcardIndex !== -1) {
|
|
825
|
-
prefixes.push(`${packageName}/${normalizedSubpath.slice(0, wildcardIndex)}`);
|
|
826
|
-
continue;
|
|
827
|
-
}
|
|
828
|
-
exact.add(`${packageName}/${normalizedSubpath}`);
|
|
947
|
+
function collectTargetCandidates(config, value) {
|
|
948
|
+
if (typeof value === "string") return [value];
|
|
949
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
950
|
+
const record = value;
|
|
951
|
+
const candidates = [];
|
|
952
|
+
const visitedKeys = /* @__PURE__ */ new Set();
|
|
953
|
+
for (const key of config.paths?.conditionPriority ?? defaultConditionPriority) {
|
|
954
|
+
if (!(key in record)) continue;
|
|
955
|
+
visitedKeys.add(key);
|
|
956
|
+
candidates.push(...collectTargetCandidates(config, record[key]));
|
|
829
957
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
function isAllowedSelfSpecifier(specifier, matchers) {
|
|
836
|
-
return matchers.exact.has(specifier) || matchers.prefixes.some((prefix) => specifier.startsWith(prefix));
|
|
958
|
+
for (const key of Object.keys(record).sort()) {
|
|
959
|
+
if (visitedKeys.has(key)) continue;
|
|
960
|
+
candidates.push(...collectTargetCandidates(config, record[key]));
|
|
961
|
+
}
|
|
962
|
+
return candidates;
|
|
837
963
|
}
|
|
838
|
-
function
|
|
839
|
-
|
|
964
|
+
function normalizePackageTarget(target) {
|
|
965
|
+
if (!target.startsWith("./")) return null;
|
|
966
|
+
return target.slice(2);
|
|
840
967
|
}
|
|
841
|
-
function
|
|
842
|
-
if (
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
return normalizedPath.startsWith("node/") || normalizedPath.startsWith("plugin/") ? "node" : "browser";
|
|
968
|
+
function packageExportKeyToAlias(packageName, exportKey) {
|
|
969
|
+
if (exportKey === ".") return packageName;
|
|
970
|
+
if (!exportKey.startsWith("./")) return "";
|
|
971
|
+
return `${packageName}/${exportKey.slice(2)}`;
|
|
846
972
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
973
|
+
function removeKnownExtension(filePath) {
|
|
974
|
+
for (const extension of [
|
|
975
|
+
".d.mts",
|
|
976
|
+
".d.cts",
|
|
977
|
+
".d.ts",
|
|
978
|
+
".mts",
|
|
979
|
+
".cts",
|
|
980
|
+
".mjs",
|
|
981
|
+
".cjs",
|
|
982
|
+
".js",
|
|
983
|
+
".tsx",
|
|
984
|
+
".ts"
|
|
985
|
+
]) if (filePath.endsWith(extension)) return filePath.slice(0, -extension.length);
|
|
986
|
+
return filePath;
|
|
859
987
|
}
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
if (isRelativeOrAbsoluteSpecifier(specifier)) return null;
|
|
863
|
-
if (nodeBuiltinSpecifiers.has(specifier)) {
|
|
864
|
-
if (environment === "node") return null;
|
|
865
|
-
return `browser/runtime output must not import Node builtin "${specifier}"`;
|
|
866
|
-
}
|
|
867
|
-
const packageRoot = getPackageRootSpecifier(specifier);
|
|
868
|
-
if (packageRoot === packageName) {
|
|
869
|
-
if (isAllowedSelfSpecifier(specifier, selfSpecifiers)) return null;
|
|
870
|
-
return `self import "${specifier}" is not exposed by output package.json exports`;
|
|
871
|
-
}
|
|
872
|
-
if (allowedExternalPackages.has(packageRoot)) return null;
|
|
873
|
-
return `"${specifier}" resolves to package "${packageRoot}" which is not listed in dependencies, peerDependencies, optionalDependencies, or self exports`;
|
|
988
|
+
function hasConfiguredSourceExtension(config, target) {
|
|
989
|
+
return configuredSourceExtensions(config).some((extension) => target.endsWith(extension));
|
|
874
990
|
}
|
|
875
|
-
function
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
case "FalseESM": return `False ESM: ${problem.typesFileName} -> ${problem.implementationFileName}`;
|
|
882
|
-
case "FalseCJS": return `False CJS: ${problem.typesFileName} -> ${problem.implementationFileName}`;
|
|
883
|
-
case "CJSResolvesToESM": return `CJS resolves to ESM${resolutionKind}${entrypoint}`;
|
|
884
|
-
case "FallbackCondition": return `Fallback condition used${resolutionKind}${entrypoint}`;
|
|
885
|
-
case "NamedExports": return problem.isMissingAllNamed ? `Named exports missing: all named exports [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]` : `Named exports missing: ${problem.missing.join(", ") || "(none)"} [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]`;
|
|
886
|
-
case "FalseExportDefault": return `False export default [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]`;
|
|
887
|
-
case "MissingExportEquals": return `Missing export equals [types: ${problem.typesFileName}] [implementation: ${problem.implementationFileName}]`;
|
|
888
|
-
case "InternalResolutionError": return `Internal resolution error in ${problem.fileName} [option: ${problem.resolutionOption}] [module: ${problem.moduleSpecifier}]`;
|
|
889
|
-
case "UnexpectedModuleSyntax": return `Unexpected module syntax in ${problem.fileName}`;
|
|
890
|
-
case "CJSOnlyExportsDefault": return `CJS only exports default in ${problem.fileName}`;
|
|
891
|
-
default: return `Unknown ATTW problem: ${JSON.stringify(problem)}`;
|
|
892
|
-
}
|
|
991
|
+
function isInsideArtifactDirectory(config, target) {
|
|
992
|
+
const normalizedTarget = normalizeSlashes(target);
|
|
993
|
+
return configuredArtifactDirectories(config).some((directoryName) => {
|
|
994
|
+
const normalizedDirectoryName = normalizeSlashes(directoryName).replace(/\/+$/u, "");
|
|
995
|
+
return normalizedTarget === normalizedDirectoryName || normalizedTarget.startsWith(`${normalizedDirectoryName}/`);
|
|
996
|
+
});
|
|
893
997
|
}
|
|
894
|
-
function
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
998
|
+
function isLikelySourceTarget(config, target) {
|
|
999
|
+
return hasConfiguredSourceExtension(config, target) && !isInsideArtifactDirectory(config, target);
|
|
1000
|
+
}
|
|
1001
|
+
function stripArtifactPrefix(config, target) {
|
|
1002
|
+
const normalizedTarget = normalizeSlashes(target);
|
|
1003
|
+
for (const directoryName of configuredArtifactDirectories(config)) {
|
|
1004
|
+
const normalizedDirectoryName = normalizeSlashes(directoryName).replace(/\/+$/u, "");
|
|
1005
|
+
if (normalizedTarget.startsWith(`${normalizedDirectoryName}/`)) return normalizedTarget.slice(normalizedDirectoryName.length + 1);
|
|
900
1006
|
}
|
|
901
|
-
return
|
|
1007
|
+
return normalizedTarget;
|
|
902
1008
|
}
|
|
903
|
-
function
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
1009
|
+
function sourceFileCandidates(config, target) {
|
|
1010
|
+
const normalizedTarget = normalizeSlashes(target);
|
|
1011
|
+
const withoutKnownExtension = removeKnownExtension(normalizedTarget);
|
|
1012
|
+
const withoutArtifactPrefix = stripArtifactPrefix(config, normalizedTarget);
|
|
1013
|
+
const sourceBase = removeKnownExtension(withoutArtifactPrefix);
|
|
1014
|
+
const bases = withoutArtifactPrefix === normalizedTarget ? [
|
|
1015
|
+
withoutKnownExtension,
|
|
1016
|
+
sourceBase,
|
|
1017
|
+
`src/${sourceBase}`,
|
|
1018
|
+
`${sourceBase}/index`,
|
|
1019
|
+
`src/${sourceBase}/index`
|
|
1020
|
+
] : [
|
|
1021
|
+
sourceBase,
|
|
1022
|
+
`src/${sourceBase}`,
|
|
1023
|
+
`${sourceBase}/index`,
|
|
1024
|
+
`src/${sourceBase}/index`
|
|
1025
|
+
];
|
|
1026
|
+
const candidates = [];
|
|
1027
|
+
for (const base of bases) for (const extension of configuredSourceExtensions(config)) candidates.push(`${base}${extension}`);
|
|
1028
|
+
return [...new Set(candidates)];
|
|
907
1029
|
}
|
|
908
|
-
function
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
if (!(relativeToRoot === "" || relativeToRoot !== ".." && !relativeToRoot.startsWith(`..${path.sep}`) && !path.isAbsolute(relativeToRoot))) return;
|
|
914
|
-
const packageJsonPath = path.join(currentDir, "package.json");
|
|
915
|
-
if (existsSync(packageJsonPath)) return packageJsonPath;
|
|
916
|
-
if (currentDir === resolvedRootDir) return;
|
|
917
|
-
const parentDir = path.dirname(currentDir);
|
|
918
|
-
if (parentDir === currentDir) return;
|
|
919
|
-
currentDir = parentDir;
|
|
920
|
-
}
|
|
1030
|
+
function wildcardBaseDirectory(pattern) {
|
|
1031
|
+
const wildcardIndex = pattern.indexOf("*");
|
|
1032
|
+
const prefix = wildcardIndex === -1 ? pattern : pattern.slice(0, wildcardIndex);
|
|
1033
|
+
const lastSlashIndex = prefix.lastIndexOf("/");
|
|
1034
|
+
return lastSlashIndex === -1 ? "." : prefix.slice(0, lastSlashIndex);
|
|
921
1035
|
}
|
|
922
|
-
function
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1036
|
+
function sourceWildcardPatternCandidates(config, target) {
|
|
1037
|
+
const strippedPattern = stripArtifactPrefix(config, target);
|
|
1038
|
+
const sourcePattern = removeKnownExtension(strippedPattern);
|
|
1039
|
+
const preferSrcPrefix = strippedPattern !== normalizeSlashes(target);
|
|
1040
|
+
const candidates = [];
|
|
1041
|
+
for (const extension of configuredSourceExtensions(config)) if (preferSrcPrefix) {
|
|
1042
|
+
candidates.push(`src/${sourcePattern}${extension}`);
|
|
1043
|
+
candidates.push(`${sourcePattern}${extension}`);
|
|
1044
|
+
} else {
|
|
1045
|
+
candidates.push(`${sourcePattern}${extension}`);
|
|
1046
|
+
candidates.push(`src/${sourcePattern}${extension}`);
|
|
930
1047
|
}
|
|
1048
|
+
return [...new Set(candidates)];
|
|
931
1049
|
}
|
|
932
|
-
function
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
return
|
|
1050
|
+
function resolveWildcardTarget(config, packageDirectory, target) {
|
|
1051
|
+
const sourcePatterns = sourceWildcardPatternCandidates(config, target);
|
|
1052
|
+
const candidatePatterns = isLikelySourceTarget(config, target) ? [target, ...sourcePatterns] : sourcePatterns;
|
|
1053
|
+
for (const candidatePattern of candidatePatterns) {
|
|
1054
|
+
const baseDirectory = wildcardBaseDirectory(candidatePattern);
|
|
1055
|
+
if (existsSync(path.join(packageDirectory, baseDirectory))) return toPosixPath(path.join(toRelativePath(config.rootDir, packageDirectory), candidatePattern));
|
|
1056
|
+
}
|
|
1057
|
+
return null;
|
|
940
1058
|
}
|
|
941
|
-
function
|
|
942
|
-
|
|
1059
|
+
function resolveExactTarget(config, packageDirectory, target) {
|
|
1060
|
+
const absoluteTarget = path.join(packageDirectory, target);
|
|
1061
|
+
if (existsSync(absoluteTarget) && isLikelySourceTarget(config, target)) return normalizeWorkspacePath(config.rootDir, absoluteTarget);
|
|
1062
|
+
for (const candidate of sourceFileCandidates(config, target)) {
|
|
1063
|
+
const absoluteCandidate = path.join(packageDirectory, candidate);
|
|
1064
|
+
if (existsSync(absoluteCandidate)) return normalizeWorkspacePath(config.rootDir, absoluteCandidate);
|
|
1065
|
+
}
|
|
1066
|
+
return null;
|
|
943
1067
|
}
|
|
944
|
-
function
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
});
|
|
950
|
-
return {
|
|
951
|
-
checks: selectTargetChecks(options.target, options.requestedTool),
|
|
952
|
-
label: getTargetLabel(options.config, options.target, outDir),
|
|
953
|
-
outDir,
|
|
954
|
-
rawTarget: options.target
|
|
955
|
-
};
|
|
1068
|
+
function resolvePackageTarget(config, packageDirectory, rawTarget) {
|
|
1069
|
+
const target = normalizePackageTarget(rawTarget);
|
|
1070
|
+
if (!target) return null;
|
|
1071
|
+
if (target.includes("*")) return resolveWildcardTarget(config, packageDirectory, target);
|
|
1072
|
+
return resolveExactTarget(config, packageDirectory, target);
|
|
956
1073
|
}
|
|
957
|
-
function
|
|
958
|
-
const
|
|
959
|
-
if (
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
if (
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
if (selectedTargets.length > 0) selectionReason = `nearest package.json name "${cwdPackageName}" matched configured target name.`;
|
|
971
|
-
else {
|
|
972
|
-
selectedTargets = targets;
|
|
973
|
-
selectionReason = `nearest package.json name "${cwdPackageName}" did not match configured target names; running all configured targets.`;
|
|
1074
|
+
function collectExportEntries(config, workspacePackage) {
|
|
1075
|
+
const exportsField = workspacePackage.manifest.exports;
|
|
1076
|
+
if (!exportsField) return [];
|
|
1077
|
+
const exportEntries = typeof exportsField === "object" && exportsField !== null && !Array.isArray(exportsField) && Object.keys(exportsField).some((key) => key.startsWith(".")) ? Object.entries(exportsField) : [[".", exportsField]];
|
|
1078
|
+
const entries = [];
|
|
1079
|
+
for (const [exportKey, exportValue] of exportEntries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
1080
|
+
const alias = packageExportKeyToAlias(workspacePackage.name, exportKey);
|
|
1081
|
+
if (!alias) continue;
|
|
1082
|
+
for (const candidate of collectTargetCandidates(config, exportValue)) {
|
|
1083
|
+
const resolvedTarget = resolvePackageTarget(config, workspacePackage.directory, candidate);
|
|
1084
|
+
if (resolvedTarget) {
|
|
1085
|
+
entries.push([alias, resolvedTarget]);
|
|
1086
|
+
break;
|
|
974
1087
|
}
|
|
975
|
-
} else {
|
|
976
|
-
selectedTargets = targets;
|
|
977
|
-
selectionReason = "No package name was found from cwd up to the workspace root; running all configured targets.";
|
|
978
1088
|
}
|
|
979
1089
|
}
|
|
980
|
-
return
|
|
981
|
-
selectionReason,
|
|
982
|
-
targets: selectedTargets.map((target) => createTargetPlan({
|
|
983
|
-
config: options.config,
|
|
984
|
-
requestedTool: options.tool,
|
|
985
|
-
target,
|
|
986
|
-
targetIndex: targets.indexOf(target)
|
|
987
|
-
}))
|
|
988
|
-
};
|
|
1090
|
+
return entries;
|
|
989
1091
|
}
|
|
990
|
-
function
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
...options.plan.targets.map((target) => [
|
|
998
|
-
` - ${target.label}`,
|
|
999
|
-
` outDir: ${toRelativePath(options.config.rootDir, target.outDir)}`,
|
|
1000
|
-
` checks: ${target.checks.length > 0 ? target.checks.join(", ") : "(none)"}`
|
|
1001
|
-
].join("\n"))
|
|
1002
|
-
].join("\n"));
|
|
1092
|
+
function aliasMatchesSpecifier(alias, specifier) {
|
|
1093
|
+
if (alias === specifier) return true;
|
|
1094
|
+
const wildcardIndex = alias.indexOf("*");
|
|
1095
|
+
if (wildcardIndex === -1) return false;
|
|
1096
|
+
const prefix = alias.slice(0, wildcardIndex);
|
|
1097
|
+
const suffix = alias.slice(wildcardIndex + 1);
|
|
1098
|
+
return specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
1003
1099
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
force: true,
|
|
1010
|
-
recursive: true
|
|
1011
|
-
}).catch(() => null);
|
|
1012
|
-
},
|
|
1013
|
-
tarball: await readFile(await pack(outDir, {
|
|
1014
|
-
destination,
|
|
1015
|
-
ignoreScripts: true,
|
|
1016
|
-
packageManager: "pnpm"
|
|
1017
|
-
}))
|
|
1018
|
-
};
|
|
1100
|
+
|
|
1101
|
+
//#endregion
|
|
1102
|
+
//#region src/commands/paths.ts
|
|
1103
|
+
function generatedFileName(config) {
|
|
1104
|
+
return config.paths?.generatedFileName ?? "tsconfig.dts.paths.generated.json";
|
|
1019
1105
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
return JSON.parse(await readFile(options.packageJsonPath, "utf8"));
|
|
1106
|
+
function generatedFileMarker(config) {
|
|
1107
|
+
return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
|
|
1023
1108
|
}
|
|
1024
|
-
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1109
|
+
function parseProject(config, configPath) {
|
|
1110
|
+
const diagnostics = [];
|
|
1111
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
1112
|
+
...ts.sys,
|
|
1113
|
+
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
1114
|
+
diagnostics.push(diagnostic);
|
|
1115
|
+
}
|
|
1029
1116
|
});
|
|
1030
|
-
if (
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
return true;
|
|
1047
|
-
}
|
|
1048
|
-
for (const message of messages) {
|
|
1049
|
-
const rendered = formatMessage(message, pkg) ?? message.code;
|
|
1050
|
-
if (message.type === "error") {
|
|
1051
|
-
PackageLogger.error(`[${options.label}] [publint] ${rendered}`);
|
|
1052
|
-
continue;
|
|
1053
|
-
}
|
|
1054
|
-
if (message.type === "warning") {
|
|
1055
|
-
PackageLogger.warn(`[${options.label}] [publint] ${rendered}`);
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
PackageLogger.info(`[${options.label}] [publint] ${rendered}`);
|
|
1059
|
-
}
|
|
1060
|
-
PackageLogger.error(`publint found ${messages.length} issue(s): ${options.label}`, publintElapsed());
|
|
1061
|
-
task?.fail(`publint found ${messages.length} issue(s): ${options.label}`);
|
|
1062
|
-
return false;
|
|
1063
|
-
}
|
|
1064
|
-
async function runAttwCheck(options) {
|
|
1065
|
-
const task = options.flow?.start(`attw: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
1066
|
-
PackageLogger.info(`attw started: ${options.label} (profile: ${options.profile})`);
|
|
1067
|
-
const attwElapsed = createElapsedTimer();
|
|
1068
|
-
const result = await checkPackage(createPackageFromTarballData(options.tarball));
|
|
1069
|
-
if (!result.types) {
|
|
1070
|
-
PackageLogger.error(`[${options.label}] [attw] package has no types`);
|
|
1071
|
-
PackageLogger.error(`attw failed: ${options.label}`, attwElapsed());
|
|
1072
|
-
task?.fail(`attw failed: ${options.label}`);
|
|
1073
|
-
return false;
|
|
1074
|
-
}
|
|
1075
|
-
const ignoredResolutions = ATTW_PROFILE_IGNORED_RESOLUTIONS[options.profile];
|
|
1076
|
-
const problems = result.problems.filter((problem) => {
|
|
1077
|
-
if ("resolutionKind" in problem) return !ignoredResolutions.includes(problem.resolutionKind);
|
|
1078
|
-
return true;
|
|
1079
|
-
});
|
|
1080
|
-
if (problems.length === 0) {
|
|
1081
|
-
if (!options.flow?.interactive) PackageLogger.success(`attw passed: ${options.label}`, attwElapsed());
|
|
1082
|
-
task?.pass();
|
|
1083
|
-
return true;
|
|
1084
|
-
}
|
|
1085
|
-
for (const problem of problems) PackageLogger.error(`[${options.label}] [attw] ${formatAttwProblem(problem)}`);
|
|
1086
|
-
PackageLogger.error(`attw found ${problems.length} problem(s): ${options.label}`, attwElapsed());
|
|
1087
|
-
task?.fail(`attw found ${problems.length} problem(s): ${options.label}`);
|
|
1088
|
-
return false;
|
|
1089
|
-
}
|
|
1090
|
-
async function runBoundaryCheck(target, label, options = {}) {
|
|
1091
|
-
const task = options.flow?.start(`package boundary: ${label}`, { depth: options.flowDepth ?? 0 });
|
|
1092
|
-
PackageLogger.info(`package boundary started: ${label}`);
|
|
1093
|
-
const boundaryElapsed = createElapsedTimer();
|
|
1094
|
-
const violations = await auditPublishedPackageBoundaries(target);
|
|
1095
|
-
if (violations.length === 0) {
|
|
1096
|
-
if (!options.flow?.interactive) PackageLogger.success(`package boundary passed: ${label}`, boundaryElapsed());
|
|
1097
|
-
task?.pass();
|
|
1098
|
-
return true;
|
|
1099
|
-
}
|
|
1100
|
-
for (const violation of violations) PackageLogger.error(`[${label}] [boundary] ${violation.filePath} (${violation.environment}) imports "${violation.specifier}": ${violation.message}`);
|
|
1101
|
-
PackageLogger.error(`package boundary found ${violations.length} issue(s): ${label}`, boundaryElapsed());
|
|
1102
|
-
task?.fail(`package boundary found ${violations.length} issue(s): ${label}`);
|
|
1103
|
-
return false;
|
|
1104
|
-
}
|
|
1105
|
-
async function runPackageCheckTarget(options) {
|
|
1106
|
-
const target = {
|
|
1107
|
-
...options.rawTarget,
|
|
1108
|
-
outDir: options.outDir
|
|
1109
|
-
};
|
|
1110
|
-
const label = options.label;
|
|
1111
|
-
const outputPackageJsonPath = path.join(target.outDir, "package.json");
|
|
1112
|
-
const task = options.flow?.start(`package target: ${label}`, { depth: options.flowDepth ?? 0 });
|
|
1113
|
-
let packedDist;
|
|
1114
|
-
try {
|
|
1115
|
-
const outputManifest = await assertPublicPackageMetadata({
|
|
1116
|
-
config: options.config,
|
|
1117
|
-
label,
|
|
1118
|
-
outDir: target.outDir,
|
|
1119
|
-
packageJsonPath: outputPackageJsonPath
|
|
1120
|
-
});
|
|
1121
|
-
if (outputManifest.private !== true || options.checks.includes("publint") || options.checks.includes("attw")) {
|
|
1122
|
-
const packTask = options.flow?.start(`package tarball: ${label}`, { depth: (options.flowDepth ?? 0) + 1 });
|
|
1123
|
-
PackageLogger.info(`package tarball packing started: ${label}`);
|
|
1124
|
-
const packElapsed = createElapsedTimer();
|
|
1125
|
-
try {
|
|
1126
|
-
packedDist = await packOutputTarball(target.outDir);
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
PackageLogger.error(`package tarball failed: ${label}: ${formatErrorMessage$1(error)}`, packElapsed());
|
|
1129
|
-
packTask?.fail(`package tarball failed: ${label}`, { error });
|
|
1130
|
-
throw error;
|
|
1131
|
-
}
|
|
1132
|
-
if (!options.flow?.interactive) PackageLogger.success(`package tarball packed: ${label}`, packElapsed());
|
|
1133
|
-
packTask?.pass();
|
|
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
|
-
}
|
|
1149
|
-
let passed = true;
|
|
1150
|
-
if (options.checks.includes("publint")) passed = await runPublintCheck({
|
|
1151
|
-
flow: options.flow,
|
|
1152
|
-
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
1153
|
-
label,
|
|
1154
|
-
strict: target.publint?.strict ?? true,
|
|
1155
|
-
tarball: packedDist.tarball
|
|
1156
|
-
}) && passed;
|
|
1157
|
-
if (options.checks.includes("attw")) passed = await runAttwCheck({
|
|
1158
|
-
flow: options.flow,
|
|
1159
|
-
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
1160
|
-
label,
|
|
1161
|
-
profile: options.attwProfile ?? target.attw?.profile ?? "esm-only",
|
|
1162
|
-
tarball: packedDist.tarball
|
|
1163
|
-
}) && passed;
|
|
1164
|
-
if (options.checks.includes("boundary")) passed = await runBoundaryCheck({
|
|
1165
|
-
...target.boundary,
|
|
1166
|
-
outDir: target.outDir
|
|
1167
|
-
}, label, {
|
|
1168
|
-
flow: options.flow,
|
|
1169
|
-
flowDepth: (options.flowDepth ?? 0) + 1
|
|
1170
|
-
}) && passed;
|
|
1171
|
-
if (passed) {
|
|
1172
|
-
if (!options.flow?.interactive) PackageLogger.success(`package checks passed: ${label}`);
|
|
1173
|
-
task?.pass();
|
|
1174
|
-
} else {
|
|
1175
|
-
PackageLogger.error(`package checks failed: ${label}`);
|
|
1176
|
-
task?.fail(`package checks failed: ${label}`);
|
|
1177
|
-
}
|
|
1178
|
-
return passed;
|
|
1179
|
-
} catch (error) {
|
|
1180
|
-
PackageLogger.error(`package checks failed: ${label}: ${formatErrorMessage$1(error)}`);
|
|
1181
|
-
task?.fail(`package checks failed: ${label}`, { error });
|
|
1182
|
-
throw error;
|
|
1183
|
-
} finally {
|
|
1184
|
-
if (packedDist) await packedDist.cleanup();
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
async function auditPublishedPackageBoundaries(target) {
|
|
1188
|
-
const manifest = await readDistPackageJson({ packageJsonPath: path.join(target.outDir, "package.json") });
|
|
1189
|
-
const allowedExternalPackages = new Set([
|
|
1190
|
-
...Object.keys(manifest.dependencies ?? {}),
|
|
1191
|
-
...Object.keys(manifest.peerDependencies ?? {}),
|
|
1192
|
-
...Object.keys(manifest.optionalDependencies ?? {}),
|
|
1193
|
-
...target.ignoredExternalPackages ?? []
|
|
1194
|
-
]);
|
|
1195
|
-
const selfSpecifiers = collectSelfSpecifierMatchers(manifest.name, manifest.exports);
|
|
1196
|
-
const publishedFiles = await collectPublishedModuleFiles(target.outDir);
|
|
1197
|
-
const violations = [];
|
|
1198
|
-
await init;
|
|
1199
|
-
for (const filePath of publishedFiles) {
|
|
1200
|
-
const relativeFilePath = path.relative(target.outDir, filePath);
|
|
1201
|
-
const environment = classifyRuntimeEnvironment(target, relativeFilePath);
|
|
1202
|
-
const [importSpecifiers] = parse(await readFile(filePath, "utf8"));
|
|
1203
|
-
for (const importSpecifier of importSpecifiers) {
|
|
1204
|
-
if (!importSpecifier.n) continue;
|
|
1205
|
-
const message = validatePublishedSpecifier({
|
|
1206
|
-
allowedExternalPackages,
|
|
1207
|
-
environment,
|
|
1208
|
-
packageName: manifest.name,
|
|
1209
|
-
selfSpecifiers,
|
|
1210
|
-
specifier: importSpecifier.n
|
|
1211
|
-
});
|
|
1212
|
-
if (!message) continue;
|
|
1213
|
-
violations.push({
|
|
1214
|
-
environment,
|
|
1215
|
-
filePath: relativeFilePath,
|
|
1216
|
-
message,
|
|
1217
|
-
specifier: importSpecifier.n
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
return violations.toSorted((left, right) => {
|
|
1222
|
-
if (left.filePath === right.filePath) return left.specifier.localeCompare(right.specifier);
|
|
1223
|
-
return left.filePath.localeCompare(right.filePath);
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
1226
|
-
async function runPackageCheck(options) {
|
|
1227
|
-
if (options.clearScreen ?? true) clearCliScreen();
|
|
1228
|
-
const elapsed = createElapsedTimer();
|
|
1229
|
-
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1230
|
-
const task = options.flow?.start("package check", { depth: options.flowDepth ?? 0 });
|
|
1231
|
-
try {
|
|
1232
|
-
PackageLogger.info("package check started");
|
|
1233
|
-
const plan = createPackageCheckPlan({
|
|
1234
|
-
config: options.config,
|
|
1235
|
-
cwd,
|
|
1236
|
-
targetName: options.targetName,
|
|
1237
|
-
tool: options.tool
|
|
1238
|
-
});
|
|
1239
|
-
logPackageCheckPlan({
|
|
1240
|
-
config: options.config,
|
|
1241
|
-
cwd,
|
|
1242
|
-
plan
|
|
1243
|
-
});
|
|
1244
|
-
const runnableTargets = plan.targets.filter((target) => target.checks.length > 0);
|
|
1245
|
-
if (runnableTargets.length === 0) throw new Error(options.tool && options.tool !== "all" ? `No package check targets have "${options.tool}" enabled.` : "No package checks are enabled.");
|
|
1246
|
-
let passed = true;
|
|
1247
|
-
for (const target of runnableTargets) passed = await runPackageCheckTarget({
|
|
1248
|
-
attwProfile: options.attwProfile,
|
|
1249
|
-
checks: target.checks,
|
|
1250
|
-
config: options.config,
|
|
1251
|
-
flow: options.flow,
|
|
1252
|
-
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
1253
|
-
label: target.label,
|
|
1254
|
-
outDir: target.outDir,
|
|
1255
|
-
rawTarget: target.rawTarget
|
|
1256
|
-
}) && passed;
|
|
1257
|
-
if (passed) {
|
|
1258
|
-
if (!options.flow?.interactive) PackageLogger.success("package check finished", elapsed());
|
|
1259
|
-
task?.pass();
|
|
1260
|
-
} else {
|
|
1261
|
-
PackageLogger.error("package check finished with failures", elapsed());
|
|
1262
|
-
task?.fail("package check finished with failures");
|
|
1263
|
-
}
|
|
1264
|
-
return passed;
|
|
1265
|
-
} catch (error) {
|
|
1266
|
-
PackageLogger.error(`package check failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
1267
|
-
task?.fail("package check failed", { error });
|
|
1268
|
-
throw error;
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
//#endregion
|
|
1273
|
-
//#region src/package-exports.ts
|
|
1274
|
-
const defaultSourceExtensions = [
|
|
1275
|
-
".ts",
|
|
1276
|
-
".tsx",
|
|
1277
|
-
".mts",
|
|
1278
|
-
".cts",
|
|
1279
|
-
".d.ts",
|
|
1280
|
-
".d.mts",
|
|
1281
|
-
".d.cts"
|
|
1282
|
-
];
|
|
1283
|
-
const defaultConditionPriority = [
|
|
1284
|
-
"source",
|
|
1285
|
-
"development",
|
|
1286
|
-
"types",
|
|
1287
|
-
"import",
|
|
1288
|
-
"module",
|
|
1289
|
-
"default",
|
|
1290
|
-
"require"
|
|
1291
|
-
];
|
|
1292
|
-
const defaultArtifactDirectories = [
|
|
1293
|
-
"dist",
|
|
1294
|
-
"build",
|
|
1295
|
-
"lib",
|
|
1296
|
-
"esm",
|
|
1297
|
-
"cjs",
|
|
1298
|
-
"out"
|
|
1299
|
-
];
|
|
1300
|
-
function configuredArtifactDirectories(config) {
|
|
1301
|
-
return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
|
|
1302
|
-
}
|
|
1303
|
-
function configuredSourceExtensions(config) {
|
|
1304
|
-
return config.paths?.sourceExtensions ?? defaultSourceExtensions;
|
|
1305
|
-
}
|
|
1306
|
-
function collectTargetCandidates(config, value) {
|
|
1307
|
-
if (typeof value === "string") return [value];
|
|
1308
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
1309
|
-
const record = value;
|
|
1310
|
-
const candidates = [];
|
|
1311
|
-
const visitedKeys = /* @__PURE__ */ new Set();
|
|
1312
|
-
for (const key of config.paths?.conditionPriority ?? defaultConditionPriority) {
|
|
1313
|
-
if (!(key in record)) continue;
|
|
1314
|
-
visitedKeys.add(key);
|
|
1315
|
-
candidates.push(...collectTargetCandidates(config, record[key]));
|
|
1316
|
-
}
|
|
1317
|
-
for (const key of Object.keys(record).sort()) {
|
|
1318
|
-
if (visitedKeys.has(key)) continue;
|
|
1319
|
-
candidates.push(...collectTargetCandidates(config, record[key]));
|
|
1320
|
-
}
|
|
1321
|
-
return candidates;
|
|
1322
|
-
}
|
|
1323
|
-
function normalizePackageTarget(target) {
|
|
1324
|
-
if (!target.startsWith("./")) return null;
|
|
1325
|
-
return target.slice(2);
|
|
1326
|
-
}
|
|
1327
|
-
function packageExportKeyToAlias(packageName, exportKey) {
|
|
1328
|
-
if (exportKey === ".") return packageName;
|
|
1329
|
-
if (!exportKey.startsWith("./")) return "";
|
|
1330
|
-
return `${packageName}/${exportKey.slice(2)}`;
|
|
1331
|
-
}
|
|
1332
|
-
function removeKnownExtension(filePath) {
|
|
1333
|
-
for (const extension of [
|
|
1334
|
-
".d.mts",
|
|
1335
|
-
".d.cts",
|
|
1336
|
-
".d.ts",
|
|
1337
|
-
".mts",
|
|
1338
|
-
".cts",
|
|
1339
|
-
".mjs",
|
|
1340
|
-
".cjs",
|
|
1341
|
-
".js",
|
|
1342
|
-
".tsx",
|
|
1343
|
-
".ts"
|
|
1344
|
-
]) if (filePath.endsWith(extension)) return filePath.slice(0, -extension.length);
|
|
1345
|
-
return filePath;
|
|
1346
|
-
}
|
|
1347
|
-
function hasConfiguredSourceExtension(config, target) {
|
|
1348
|
-
return configuredSourceExtensions(config).some((extension) => target.endsWith(extension));
|
|
1349
|
-
}
|
|
1350
|
-
function isInsideArtifactDirectory(config, target) {
|
|
1351
|
-
const normalizedTarget = normalizeSlashes(target);
|
|
1352
|
-
return configuredArtifactDirectories(config).some((directoryName) => {
|
|
1353
|
-
const normalizedDirectoryName = normalizeSlashes(directoryName).replace(/\/+$/u, "");
|
|
1354
|
-
return normalizedTarget === normalizedDirectoryName || normalizedTarget.startsWith(`${normalizedDirectoryName}/`);
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
function isLikelySourceTarget(config, target) {
|
|
1358
|
-
return hasConfiguredSourceExtension(config, target) && !isInsideArtifactDirectory(config, target);
|
|
1359
|
-
}
|
|
1360
|
-
function stripArtifactPrefix(config, target) {
|
|
1361
|
-
const normalizedTarget = normalizeSlashes(target);
|
|
1362
|
-
for (const directoryName of configuredArtifactDirectories(config)) {
|
|
1363
|
-
const normalizedDirectoryName = normalizeSlashes(directoryName).replace(/\/+$/u, "");
|
|
1364
|
-
if (normalizedTarget.startsWith(`${normalizedDirectoryName}/`)) return normalizedTarget.slice(normalizedDirectoryName.length + 1);
|
|
1365
|
-
}
|
|
1366
|
-
return normalizedTarget;
|
|
1367
|
-
}
|
|
1368
|
-
function sourceFileCandidates(config, target) {
|
|
1369
|
-
const normalizedTarget = normalizeSlashes(target);
|
|
1370
|
-
const withoutKnownExtension = removeKnownExtension(normalizedTarget);
|
|
1371
|
-
const withoutArtifactPrefix = stripArtifactPrefix(config, normalizedTarget);
|
|
1372
|
-
const sourceBase = removeKnownExtension(withoutArtifactPrefix);
|
|
1373
|
-
const bases = withoutArtifactPrefix === normalizedTarget ? [
|
|
1374
|
-
withoutKnownExtension,
|
|
1375
|
-
sourceBase,
|
|
1376
|
-
`src/${sourceBase}`,
|
|
1377
|
-
`${sourceBase}/index`,
|
|
1378
|
-
`src/${sourceBase}/index`
|
|
1379
|
-
] : [
|
|
1380
|
-
sourceBase,
|
|
1381
|
-
`src/${sourceBase}`,
|
|
1382
|
-
`${sourceBase}/index`,
|
|
1383
|
-
`src/${sourceBase}/index`
|
|
1384
|
-
];
|
|
1385
|
-
const candidates = [];
|
|
1386
|
-
for (const base of bases) for (const extension of configuredSourceExtensions(config)) candidates.push(`${base}${extension}`);
|
|
1387
|
-
return [...new Set(candidates)];
|
|
1388
|
-
}
|
|
1389
|
-
function wildcardBaseDirectory(pattern) {
|
|
1390
|
-
const wildcardIndex = pattern.indexOf("*");
|
|
1391
|
-
const prefix = wildcardIndex === -1 ? pattern : pattern.slice(0, wildcardIndex);
|
|
1392
|
-
const lastSlashIndex = prefix.lastIndexOf("/");
|
|
1393
|
-
return lastSlashIndex === -1 ? "." : prefix.slice(0, lastSlashIndex);
|
|
1394
|
-
}
|
|
1395
|
-
function sourceWildcardPatternCandidates(config, target) {
|
|
1396
|
-
const strippedPattern = stripArtifactPrefix(config, target);
|
|
1397
|
-
const sourcePattern = removeKnownExtension(strippedPattern);
|
|
1398
|
-
const preferSrcPrefix = strippedPattern !== normalizeSlashes(target);
|
|
1399
|
-
const candidates = [];
|
|
1400
|
-
for (const extension of configuredSourceExtensions(config)) if (preferSrcPrefix) {
|
|
1401
|
-
candidates.push(`src/${sourcePattern}${extension}`);
|
|
1402
|
-
candidates.push(`${sourcePattern}${extension}`);
|
|
1403
|
-
} else {
|
|
1404
|
-
candidates.push(`${sourcePattern}${extension}`);
|
|
1405
|
-
candidates.push(`src/${sourcePattern}${extension}`);
|
|
1406
|
-
}
|
|
1407
|
-
return [...new Set(candidates)];
|
|
1408
|
-
}
|
|
1409
|
-
function resolveWildcardTarget(config, packageDirectory, target) {
|
|
1410
|
-
const sourcePatterns = sourceWildcardPatternCandidates(config, target);
|
|
1411
|
-
const candidatePatterns = isLikelySourceTarget(config, target) ? [target, ...sourcePatterns] : sourcePatterns;
|
|
1412
|
-
for (const candidatePattern of candidatePatterns) {
|
|
1413
|
-
const baseDirectory = wildcardBaseDirectory(candidatePattern);
|
|
1414
|
-
if (existsSync(path.join(packageDirectory, baseDirectory))) return toPosixPath(path.join(toRelativePath(config.rootDir, packageDirectory), candidatePattern));
|
|
1415
|
-
}
|
|
1416
|
-
return null;
|
|
1417
|
-
}
|
|
1418
|
-
function resolveExactTarget(config, packageDirectory, target) {
|
|
1419
|
-
const absoluteTarget = path.join(packageDirectory, target);
|
|
1420
|
-
if (existsSync(absoluteTarget) && isLikelySourceTarget(config, target)) return normalizeWorkspacePath(config.rootDir, absoluteTarget);
|
|
1421
|
-
for (const candidate of sourceFileCandidates(config, target)) {
|
|
1422
|
-
const absoluteCandidate = path.join(packageDirectory, candidate);
|
|
1423
|
-
if (existsSync(absoluteCandidate)) return normalizeWorkspacePath(config.rootDir, absoluteCandidate);
|
|
1424
|
-
}
|
|
1425
|
-
return null;
|
|
1426
|
-
}
|
|
1427
|
-
function resolvePackageTarget(config, packageDirectory, rawTarget) {
|
|
1428
|
-
const target = normalizePackageTarget(rawTarget);
|
|
1429
|
-
if (!target) return null;
|
|
1430
|
-
if (target.includes("*")) return resolveWildcardTarget(config, packageDirectory, target);
|
|
1431
|
-
return resolveExactTarget(config, packageDirectory, target);
|
|
1432
|
-
}
|
|
1433
|
-
function collectExportEntries(config, workspacePackage) {
|
|
1434
|
-
const exportsField = workspacePackage.manifest.exports;
|
|
1435
|
-
if (!exportsField) return [];
|
|
1436
|
-
const exportEntries = typeof exportsField === "object" && exportsField !== null && !Array.isArray(exportsField) && Object.keys(exportsField).some((key) => key.startsWith(".")) ? Object.entries(exportsField) : [[".", exportsField]];
|
|
1437
|
-
const entries = [];
|
|
1438
|
-
for (const [exportKey, exportValue] of exportEntries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
1439
|
-
const alias = packageExportKeyToAlias(workspacePackage.name, exportKey);
|
|
1440
|
-
if (!alias) continue;
|
|
1441
|
-
for (const candidate of collectTargetCandidates(config, exportValue)) {
|
|
1442
|
-
const resolvedTarget = resolvePackageTarget(config, workspacePackage.directory, candidate);
|
|
1443
|
-
if (resolvedTarget) {
|
|
1444
|
-
entries.push([alias, resolvedTarget]);
|
|
1445
|
-
break;
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
return entries;
|
|
1450
|
-
}
|
|
1451
|
-
function aliasMatchesSpecifier(alias, specifier) {
|
|
1452
|
-
if (alias === specifier) return true;
|
|
1453
|
-
const wildcardIndex = alias.indexOf("*");
|
|
1454
|
-
if (wildcardIndex === -1) return false;
|
|
1455
|
-
const prefix = alias.slice(0, wildcardIndex);
|
|
1456
|
-
const suffix = alias.slice(wildcardIndex + 1);
|
|
1457
|
-
return specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
//#endregion
|
|
1461
|
-
//#region src/commands/paths.ts
|
|
1462
|
-
function generatedFileName(config) {
|
|
1463
|
-
return config.paths?.generatedFileName ?? "tsconfig.dts.paths.generated.json";
|
|
1464
|
-
}
|
|
1465
|
-
function generatedFileMarker(config) {
|
|
1466
|
-
return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
|
|
1467
|
-
}
|
|
1468
|
-
function parseProject(config, configPath) {
|
|
1469
|
-
const diagnostics = [];
|
|
1470
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
1471
|
-
...ts.sys,
|
|
1472
|
-
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
1473
|
-
diagnostics.push(diagnostic);
|
|
1474
|
-
}
|
|
1475
|
-
});
|
|
1476
|
-
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
1477
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
1478
|
-
getCurrentDirectory: () => config.rootDir,
|
|
1479
|
-
getNewLine: () => "\n"
|
|
1480
|
-
}));
|
|
1481
|
-
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
1482
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
1483
|
-
getCurrentDirectory: () => config.rootDir,
|
|
1484
|
-
getNewLine: () => "\n"
|
|
1485
|
-
}));
|
|
1486
|
-
return {
|
|
1487
|
-
configPath: normalizeAbsolutePath(configPath),
|
|
1488
|
-
fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
|
|
1489
|
-
options: parsed.options,
|
|
1490
|
-
references: new Set(getRawReferencePaths(config, configPath))
|
|
1491
|
-
};
|
|
1117
|
+
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
1118
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
1119
|
+
getCurrentDirectory: () => config.rootDir,
|
|
1120
|
+
getNewLine: () => "\n"
|
|
1121
|
+
}));
|
|
1122
|
+
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
1123
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
1124
|
+
getCurrentDirectory: () => config.rootDir,
|
|
1125
|
+
getNewLine: () => "\n"
|
|
1126
|
+
}));
|
|
1127
|
+
return {
|
|
1128
|
+
configPath: normalizeAbsolutePath(configPath),
|
|
1129
|
+
fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
|
|
1130
|
+
options: parsed.options,
|
|
1131
|
+
references: new Set(getRawReferencePaths(config, configPath))
|
|
1132
|
+
};
|
|
1492
1133
|
}
|
|
1493
1134
|
function getSourceFileKind(filePath) {
|
|
1494
1135
|
if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
@@ -1836,564 +1477,1103 @@ async function collectTsconfigPaths(config, pattern) {
|
|
|
1836
1477
|
]
|
|
1837
1478
|
})).map(normalizeAbsolutePath).sort();
|
|
1838
1479
|
}
|
|
1839
|
-
async function collectDtsConfigPaths(config) {
|
|
1840
|
-
return collectTsconfigPaths(config, dtsConfigPattern);
|
|
1480
|
+
async function collectDtsConfigPaths(config) {
|
|
1481
|
+
return collectTsconfigPaths(config, dtsConfigPattern);
|
|
1482
|
+
}
|
|
1483
|
+
async function collectBuildGraphConfigPaths(config) {
|
|
1484
|
+
return collectTsconfigPaths(config, buildGraphConfigPattern);
|
|
1485
|
+
}
|
|
1486
|
+
async function collectDefaultTsconfigPaths(config) {
|
|
1487
|
+
return collectTsconfigPaths(config, tsconfigJsonPattern);
|
|
1488
|
+
}
|
|
1489
|
+
async function collectOrdinaryTypecheckConfigPaths(config) {
|
|
1490
|
+
return (await collectTsconfigPaths(config, tsconfigFilePattern)).filter(isOrdinaryTypecheckConfigPath);
|
|
1491
|
+
}
|
|
1492
|
+
function isPlainRecord(value) {
|
|
1493
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1494
|
+
}
|
|
1495
|
+
function formatUnknownValue(value) {
|
|
1496
|
+
if (value === void 0) return "undefined";
|
|
1497
|
+
return JSON.stringify(value);
|
|
1498
|
+
}
|
|
1499
|
+
function hasGlobSyntax(pattern) {
|
|
1500
|
+
return /[*?[\]{}()!+@]/u.test(pattern);
|
|
1501
|
+
}
|
|
1502
|
+
function isDirectoryShorthand(pattern) {
|
|
1503
|
+
return !hasGlobSyntax(pattern) && !pattern.includes("/") && !path.extname(pattern);
|
|
1504
|
+
}
|
|
1505
|
+
function normalizeSourceExcludePattern(pattern) {
|
|
1506
|
+
const normalized = pattern.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
1507
|
+
if (!normalized) return [];
|
|
1508
|
+
if (isDirectoryShorthand(normalized)) return [`${normalized}/**`, `**/${normalized}/**`];
|
|
1509
|
+
if (hasGlobSyntax(normalized)) return [normalized];
|
|
1510
|
+
if (normalized.includes("/")) return [normalized, `${normalized}/**`];
|
|
1511
|
+
return [normalized, `**/${normalized}`];
|
|
1512
|
+
}
|
|
1513
|
+
function sourceIncludePatterns(config) {
|
|
1514
|
+
if (config.config?.source?.include) return config.config.source.include;
|
|
1515
|
+
return getActiveCheckerExtensions(config).map((extension) => `**/*${extension}`);
|
|
1516
|
+
}
|
|
1517
|
+
function sourceExcludePatterns(config) {
|
|
1518
|
+
return (config.config?.source?.exclude ?? defaultSourceExclude).flatMap(normalizeSourceExcludePattern);
|
|
1519
|
+
}
|
|
1520
|
+
async function collectExpectedSourceFiles(config) {
|
|
1521
|
+
const proofFilePattern = config.config?.source?.include !== void 0 ? null : createExtensionPattern(getActiveCheckerExtensions(config));
|
|
1522
|
+
const files = await glob(sourceIncludePatterns(config), {
|
|
1523
|
+
cwd: config.rootDir,
|
|
1524
|
+
absolute: true,
|
|
1525
|
+
ignore: sourceExcludePatterns(config),
|
|
1526
|
+
onlyFiles: true
|
|
1527
|
+
});
|
|
1528
|
+
return new Set(files.map(normalizeAbsolutePath).filter((filePath) => proofFilePattern?.test(filePath) ?? true).sort());
|
|
1529
|
+
}
|
|
1530
|
+
function collectCheckerCoverageTargets(config) {
|
|
1531
|
+
const problems = [];
|
|
1532
|
+
const targets = [];
|
|
1533
|
+
for (const checker of getActiveCheckers(config)) {
|
|
1534
|
+
const configPath = resolveProjectConfigPath(config.rootDir, checker.entry);
|
|
1535
|
+
if (!existsSync(configPath)) {
|
|
1536
|
+
problems.push([
|
|
1537
|
+
"Checker proof entry references a missing tsconfig:",
|
|
1538
|
+
` checker: ${checker.name}`,
|
|
1539
|
+
` config: ${toRelativePath(config.rootDir, configPath)}`
|
|
1540
|
+
].join("\n"));
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
1544
|
+
rootConfigPath: configPath,
|
|
1545
|
+
rootDir: config.rootDir
|
|
1546
|
+
});
|
|
1547
|
+
problems.push(...routeCollection.problems);
|
|
1548
|
+
targets.push({
|
|
1549
|
+
checker,
|
|
1550
|
+
configPath,
|
|
1551
|
+
coverageConfigPaths: routeCollection.projectPaths,
|
|
1552
|
+
label: `${checker.name}:entry`
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
problems,
|
|
1557
|
+
targets
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function collectConfiguredAllowlistEntries(config) {
|
|
1561
|
+
const entries = [];
|
|
1562
|
+
const problems = [];
|
|
1563
|
+
const rawEntries = config.proof?.allowlist;
|
|
1564
|
+
if (rawEntries === void 0) return {
|
|
1565
|
+
entries,
|
|
1566
|
+
problems
|
|
1567
|
+
};
|
|
1568
|
+
if (!Array.isArray(rawEntries)) {
|
|
1569
|
+
problems.push([
|
|
1570
|
+
"Invalid proof allowlist config:",
|
|
1571
|
+
" field: proof.allowlist",
|
|
1572
|
+
` value: ${formatUnknownValue(rawEntries)}`,
|
|
1573
|
+
" reason: proof.allowlist must be an array."
|
|
1574
|
+
].join("\n"));
|
|
1575
|
+
return {
|
|
1576
|
+
entries,
|
|
1577
|
+
problems
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
rawEntries.forEach((entry, index) => {
|
|
1581
|
+
const field = `proof.allowlist[${index}]`;
|
|
1582
|
+
if (!isPlainRecord(entry)) {
|
|
1583
|
+
problems.push([
|
|
1584
|
+
"Invalid proof allowlist config:",
|
|
1585
|
+
` field: ${field}`,
|
|
1586
|
+
` value: ${formatUnknownValue(entry)}`,
|
|
1587
|
+
" reason: allowlist entries must be objects with non-empty file and reason fields."
|
|
1588
|
+
].join("\n"));
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
const fileValue = entry.file;
|
|
1592
|
+
const reasonValue = entry.reason;
|
|
1593
|
+
if (typeof fileValue !== "string" || fileValue.trim().length === 0) {
|
|
1594
|
+
problems.push([
|
|
1595
|
+
"Invalid proof allowlist config:",
|
|
1596
|
+
` field: ${field}.file`,
|
|
1597
|
+
` value: ${formatUnknownValue(fileValue)}`,
|
|
1598
|
+
" reason: allowlist file must be a non-empty string."
|
|
1599
|
+
].join("\n"));
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
if (typeof reasonValue !== "string" || reasonValue.trim().length === 0) {
|
|
1603
|
+
problems.push([
|
|
1604
|
+
"Invalid proof allowlist config:",
|
|
1605
|
+
` field: ${field}.reason`,
|
|
1606
|
+
` value: ${formatUnknownValue(reasonValue)}`,
|
|
1607
|
+
" reason: allowlist reason must be a non-empty string."
|
|
1608
|
+
].join("\n"));
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
entries.push({
|
|
1612
|
+
filePath: normalizeAbsolutePath(path.join(config.rootDir, fileValue)),
|
|
1613
|
+
reason: reasonValue.trim()
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1616
|
+
return {
|
|
1617
|
+
entries,
|
|
1618
|
+
problems
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
function addCoverage(coverageByFile, filePath, source) {
|
|
1622
|
+
const sources = coverageByFile.get(filePath) ?? [];
|
|
1623
|
+
sources.push(source);
|
|
1624
|
+
coverageByFile.set(filePath, sources);
|
|
1841
1625
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1626
|
+
function collectCoverage(options) {
|
|
1627
|
+
const coverageByFile = /* @__PURE__ */ new Map();
|
|
1628
|
+
for (const route of options.graphRoutes) for (const graphProjectPath of route.projectPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, graphProjectPath, getFirstClassCoverageExtensions(route.extensions))) {
|
|
1629
|
+
if (!options.sourceFiles.has(filePath)) continue;
|
|
1630
|
+
addCoverage(coverageByFile, filePath, {
|
|
1631
|
+
label: toRelativePath(options.config.rootDir, graphProjectPath),
|
|
1632
|
+
type: "graph"
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
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
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
if (options.includeAllowlist !== false) for (const entry of options.allowlistEntries) {
|
|
1643
|
+
if (!options.sourceFiles.has(entry.filePath)) continue;
|
|
1644
|
+
addCoverage(coverageByFile, entry.filePath, {
|
|
1645
|
+
label: entry.reason,
|
|
1646
|
+
type: "allowlist"
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
return coverageByFile;
|
|
1844
1650
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
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;
|
|
1847
1658
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
1659
|
+
function parseConfig(config, configPath, extensions = []) {
|
|
1660
|
+
const diagnostics = [];
|
|
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)));
|
|
1664
|
+
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
|
|
1665
|
+
return {
|
|
1666
|
+
fileNames: parsed.fileNames.map(normalizeAbsolutePath).sort(),
|
|
1667
|
+
options: parsed.options
|
|
1668
|
+
};
|
|
1850
1669
|
}
|
|
1851
|
-
function
|
|
1852
|
-
return
|
|
1670
|
+
function formatJsonValue(value) {
|
|
1671
|
+
return JSON.stringify(value ?? null);
|
|
1853
1672
|
}
|
|
1854
|
-
function
|
|
1855
|
-
if (value
|
|
1856
|
-
return
|
|
1673
|
+
function normalizeCompilerOptionValue(value) {
|
|
1674
|
+
if (Array.isArray(value)) return value;
|
|
1675
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
|
|
1676
|
+
return value;
|
|
1857
1677
|
}
|
|
1858
|
-
function
|
|
1859
|
-
|
|
1678
|
+
function addDtsConfigSemanticProblems(options) {
|
|
1679
|
+
const dtsFileNames = new Set(options.dtsConfig.fileNames);
|
|
1680
|
+
const localFileNames = new Set(options.localConfig.fileNames);
|
|
1681
|
+
const onlyInDts = options.dtsConfig.fileNames.filter((fileName) => !localFileNames.has(fileName));
|
|
1682
|
+
const onlyInLocal = options.localConfig.fileNames.filter((fileName) => !dtsFileNames.has(fileName));
|
|
1683
|
+
if (onlyInDts.length > 0 || onlyInLocal.length > 0) options.problems.push([
|
|
1684
|
+
"DTS config file set does not match its strict local tsconfig:",
|
|
1685
|
+
` config: ${toRelativePath(options.config.rootDir, options.dtsConfigPath)}`,
|
|
1686
|
+
` local: ${toRelativePath(options.config.rootDir, options.localConfigPath)}`,
|
|
1687
|
+
...onlyInDts.length > 0 ? [
|
|
1688
|
+
" only in dts config:",
|
|
1689
|
+
...onlyInDts.slice(0, 10).map((fileName) => ` - ${toRelativePath(options.config.rootDir, fileName)}`),
|
|
1690
|
+
onlyInDts.length > 10 ? ` ... ${onlyInDts.length - 10} more` : ""
|
|
1691
|
+
] : [],
|
|
1692
|
+
...onlyInLocal.length > 0 ? [
|
|
1693
|
+
" only in local config:",
|
|
1694
|
+
...onlyInLocal.slice(0, 10).map((fileName) => ` - ${toRelativePath(options.config.rootDir, fileName)}`),
|
|
1695
|
+
onlyInLocal.length > 10 ? ` ... ${onlyInLocal.length - 10} more` : ""
|
|
1696
|
+
] : []
|
|
1697
|
+
].filter(Boolean).join("\n"));
|
|
1698
|
+
const optionNames = new Set([...Object.keys(options.localConfig.options), ...Object.keys(options.dtsConfig.options)]);
|
|
1699
|
+
for (const optionName of [...optionNames].sort()) {
|
|
1700
|
+
if (ignoredSemanticCompilerOptions.has(optionName)) continue;
|
|
1701
|
+
const localValue = normalizeCompilerOptionValue(options.localConfig.options[optionName]);
|
|
1702
|
+
const dtsValue = normalizeCompilerOptionValue(options.dtsConfig.options[optionName]);
|
|
1703
|
+
if (formatJsonValue(localValue) === formatJsonValue(dtsValue)) continue;
|
|
1704
|
+
options.problems.push([
|
|
1705
|
+
"DTS config overrides a typecheck compiler option from its strict local tsconfig:",
|
|
1706
|
+
` config: ${toRelativePath(options.config.rootDir, options.dtsConfigPath)}`,
|
|
1707
|
+
` local: ${toRelativePath(options.config.rootDir, options.localConfigPath)}`,
|
|
1708
|
+
` option: compilerOptions.${optionName}`,
|
|
1709
|
+
` local: ${formatJsonValue(localValue)}`,
|
|
1710
|
+
` dts: ${formatJsonValue(dtsValue)}`
|
|
1711
|
+
].join("\n"));
|
|
1712
|
+
}
|
|
1860
1713
|
}
|
|
1861
|
-
function
|
|
1862
|
-
|
|
1714
|
+
function addDtsConfigProblems(options) {
|
|
1715
|
+
for (const configPath of options.dtsConfigPaths) {
|
|
1716
|
+
if (!options.graphProjectPaths.has(configPath)) options.problems.push(["DTS config is not reachable from any checker entry:", ` config: ${toRelativePath(options.config.rootDir, configPath)}`].join("\n"));
|
|
1717
|
+
const localConfigPath = getDtsCompanionConfigPath(configPath);
|
|
1718
|
+
if (!existsSync(localConfigPath)) {
|
|
1719
|
+
options.problems.push([
|
|
1720
|
+
"DTS config is missing its strict local tsconfig:",
|
|
1721
|
+
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1722
|
+
` expected: ${toRelativePath(options.config.rootDir, localConfigPath)}`
|
|
1723
|
+
].join("\n"));
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
const extensions = options.projectExtensionsByPath.get(configPath) ?? [];
|
|
1727
|
+
const dtsConfig = parseConfig(options.config, configPath, extensions);
|
|
1728
|
+
const localConfig = parseConfig(options.config, localConfigPath, extensions);
|
|
1729
|
+
if (dtsConfig.options.composite !== true) options.problems.push([
|
|
1730
|
+
"DTS config is not valid for tsc -b:",
|
|
1731
|
+
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1732
|
+
" reason: final compilerOptions.composite must be true."
|
|
1733
|
+
].join("\n"));
|
|
1734
|
+
if (dtsConfig.options.noEmit === true) options.problems.push([
|
|
1735
|
+
"DTS config is not valid for tsc -b:",
|
|
1736
|
+
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1737
|
+
" reason: final compilerOptions.noEmit must not be true."
|
|
1738
|
+
].join("\n"));
|
|
1739
|
+
if (dtsConfig.options.declaration !== true) options.problems.push([
|
|
1740
|
+
"DTS config is not valid for declaration emit:",
|
|
1741
|
+
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1742
|
+
" reason: final compilerOptions.declaration must be true."
|
|
1743
|
+
].join("\n"));
|
|
1744
|
+
if (dtsConfig.options.emitDeclarationOnly !== true) options.problems.push([
|
|
1745
|
+
"DTS config is not valid for declaration emit:",
|
|
1746
|
+
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1747
|
+
" reason: final compilerOptions.emitDeclarationOnly must be true."
|
|
1748
|
+
].join("\n"));
|
|
1749
|
+
addDtsConfigSemanticProblems({
|
|
1750
|
+
config: options.config,
|
|
1751
|
+
dtsConfig,
|
|
1752
|
+
dtsConfigPath: configPath,
|
|
1753
|
+
localConfig,
|
|
1754
|
+
localConfigPath,
|
|
1755
|
+
problems: options.problems
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1863
1758
|
}
|
|
1864
|
-
function
|
|
1865
|
-
|
|
1866
|
-
if (!normalized) return [];
|
|
1867
|
-
if (isDirectoryShorthand(normalized)) return [`${normalized}/**`, `**/${normalized}/**`];
|
|
1868
|
-
if (hasGlobSyntax(normalized)) return [normalized];
|
|
1869
|
-
if (normalized.includes("/")) return [normalized, `${normalized}/**`];
|
|
1870
|
-
return [normalized, `**/${normalized}`];
|
|
1759
|
+
function isEmptyArray(value) {
|
|
1760
|
+
return Array.isArray(value) && value.length === 0;
|
|
1871
1761
|
}
|
|
1872
|
-
function
|
|
1873
|
-
|
|
1874
|
-
return getActiveCheckerExtensions(config).map((extension) => `**/*${extension}`);
|
|
1762
|
+
function formatConfigRole(role) {
|
|
1763
|
+
return role === "build graph" ? "Build graph config" : "Default tsconfig.json";
|
|
1875
1764
|
}
|
|
1876
|
-
function
|
|
1877
|
-
|
|
1765
|
+
function addPureAggregatorProblems(options) {
|
|
1766
|
+
const roleLabel = formatConfigRole(options.role);
|
|
1767
|
+
const allowedKeys = new Set([
|
|
1768
|
+
"$schema",
|
|
1769
|
+
"files",
|
|
1770
|
+
"references"
|
|
1771
|
+
]);
|
|
1772
|
+
const extraKeys = Object.keys(options.configObject).filter((key) => !allowedKeys.has(key));
|
|
1773
|
+
if (!Object.hasOwn(options.configObject, "files")) options.problems.push([
|
|
1774
|
+
`${roleLabel} is not a pure aggregator:`,
|
|
1775
|
+
` config: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
1776
|
+
" field: files",
|
|
1777
|
+
" reason: configs with project references must declare files: []."
|
|
1778
|
+
].join("\n"));
|
|
1779
|
+
else if (!isEmptyArray(options.configObject.files)) options.problems.push([
|
|
1780
|
+
`${roleLabel} is not a pure aggregator:`,
|
|
1781
|
+
` config: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
1782
|
+
" field: files",
|
|
1783
|
+
` value: ${formatUnknownValue(options.configObject.files)}`,
|
|
1784
|
+
" reason: configs with project references must declare files: []."
|
|
1785
|
+
].join("\n"));
|
|
1786
|
+
if (extraKeys.length > 0) options.problems.push([
|
|
1787
|
+
`${roleLabel} is not a pure aggregator:`,
|
|
1788
|
+
` config: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
1789
|
+
` fields: ${extraKeys.sort().join(", ")}`,
|
|
1790
|
+
" reason: pure aggregators may only declare $schema, files, and references; move source inputs and compiler options into leaf configs."
|
|
1791
|
+
].join("\n"));
|
|
1878
1792
|
}
|
|
1879
|
-
|
|
1880
|
-
const
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1793
|
+
function addBuildGraphConfigProblems(options) {
|
|
1794
|
+
for (const configPath of options.buildGraphConfigPaths) {
|
|
1795
|
+
const configObject = readJsonConfig(options.config, configPath);
|
|
1796
|
+
addPureAggregatorProblems({
|
|
1797
|
+
config: options.config,
|
|
1798
|
+
configObject,
|
|
1799
|
+
configPath,
|
|
1800
|
+
problems: options.problems,
|
|
1801
|
+
role: "build graph"
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1888
1804
|
}
|
|
1889
|
-
function
|
|
1890
|
-
const
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1805
|
+
function addDefaultTsconfigShapeProblems(options) {
|
|
1806
|
+
for (const configPath of options.tsconfigPaths) {
|
|
1807
|
+
const configObject = readJsonConfig(options.config, configPath);
|
|
1808
|
+
if (!Object.hasOwn(configObject, "references")) continue;
|
|
1809
|
+
addPureAggregatorProblems({
|
|
1810
|
+
config: options.config,
|
|
1811
|
+
configObject,
|
|
1812
|
+
configPath,
|
|
1813
|
+
problems: options.problems,
|
|
1814
|
+
role: "tsconfig.json"
|
|
1815
|
+
});
|
|
1816
|
+
if (!Array.isArray(configObject.references)) continue;
|
|
1817
|
+
configObject.references.forEach((reference, index) => {
|
|
1818
|
+
if (!isPlainRecord(reference) || typeof reference.path !== "string") return;
|
|
1819
|
+
const referencePath = resolveReferencePath(configPath, reference.path);
|
|
1820
|
+
if (isOrdinaryTypecheckConfigPath(referencePath)) return;
|
|
1821
|
+
options.problems.push([
|
|
1822
|
+
"Default tsconfig.json references a non-typecheck config:",
|
|
1823
|
+
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1824
|
+
` field: references[${index}].path`,
|
|
1825
|
+
` reference: ${reference.path}`,
|
|
1826
|
+
` resolved: ${toRelativePath(options.config.rootDir, referencePath)}`,
|
|
1827
|
+
" reason: tsconfig.json is the default IDE/typecheck entry and must not reference declaration build graph configs."
|
|
1899
1828
|
].join("\n"));
|
|
1900
|
-
continue;
|
|
1901
|
-
}
|
|
1902
|
-
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
1903
|
-
rootConfigPath: configPath,
|
|
1904
|
-
rootDir: config.rootDir
|
|
1905
|
-
});
|
|
1906
|
-
problems.push(...routeCollection.problems);
|
|
1907
|
-
targets.push({
|
|
1908
|
-
checker,
|
|
1909
|
-
configPath,
|
|
1910
|
-
coverageConfigPaths: routeCollection.projectPaths,
|
|
1911
|
-
label: `${checker.name}:entry`
|
|
1912
1829
|
});
|
|
1913
1830
|
}
|
|
1914
|
-
return {
|
|
1915
|
-
problems,
|
|
1916
|
-
targets
|
|
1917
|
-
};
|
|
1918
1831
|
}
|
|
1919
|
-
function
|
|
1920
|
-
const
|
|
1921
|
-
const
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
};
|
|
1927
|
-
if (!Array.isArray(rawEntries)) {
|
|
1928
|
-
problems.push([
|
|
1929
|
-
"Invalid proof allowlist config:",
|
|
1930
|
-
" field: proof.allowlist",
|
|
1931
|
-
` value: ${formatUnknownValue(rawEntries)}`,
|
|
1932
|
-
" reason: proof.allowlist must be an array."
|
|
1933
|
-
].join("\n"));
|
|
1934
|
-
return {
|
|
1935
|
-
entries,
|
|
1936
|
-
problems
|
|
1937
|
-
};
|
|
1832
|
+
function addDefaultTsconfigEnvironmentProblems(options) {
|
|
1833
|
+
const configsByDirectory = /* @__PURE__ */ new Map();
|
|
1834
|
+
for (const configPath of options.ordinaryConfigPaths) {
|
|
1835
|
+
const directory = path.dirname(configPath);
|
|
1836
|
+
const configs = configsByDirectory.get(directory) ?? [];
|
|
1837
|
+
configs.push(configPath);
|
|
1838
|
+
configsByDirectory.set(directory, configs);
|
|
1938
1839
|
}
|
|
1939
|
-
|
|
1940
|
-
const
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1840
|
+
for (const [directory, configPaths] of configsByDirectory.entries()) {
|
|
1841
|
+
const scopedConfigPaths = configPaths.filter((configPath) => path.basename(configPath) !== "tsconfig.json");
|
|
1842
|
+
if (scopedConfigPaths.length === 0) continue;
|
|
1843
|
+
const defaultConfigPath = normalizeAbsolutePath(path.join(directory, "tsconfig.json"));
|
|
1844
|
+
if (!existsSync(defaultConfigPath)) {
|
|
1845
|
+
options.problems.push([
|
|
1846
|
+
"Directory with typecheck environments is missing default tsconfig.json:",
|
|
1847
|
+
` directory: ${toRelativePath(options.config.rootDir, directory)}`,
|
|
1848
|
+
" reason: tsconfig.json is the default IDE/typecheck entry for its directory."
|
|
1947
1849
|
].join("\n"));
|
|
1948
|
-
|
|
1850
|
+
continue;
|
|
1949
1851
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
` value: ${formatUnknownValue(fileValue)}`,
|
|
1957
|
-
" reason: allowlist file must be a non-empty string."
|
|
1852
|
+
if (scopedConfigPaths.length === 1) {
|
|
1853
|
+
options.problems.push([
|
|
1854
|
+
"Single typecheck environment should use default tsconfig.json:",
|
|
1855
|
+
` config: ${toRelativePath(options.config.rootDir, scopedConfigPaths[0])}`,
|
|
1856
|
+
` default: ${toRelativePath(options.config.rootDir, defaultConfigPath)}`,
|
|
1857
|
+
" reason: directories with only one type environment should make tsconfig.json the leaf entry."
|
|
1958
1858
|
].join("\n"));
|
|
1959
|
-
|
|
1859
|
+
continue;
|
|
1960
1860
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1861
|
+
const defaultConfigObject = readJsonConfig(options.config, defaultConfigPath);
|
|
1862
|
+
if (!Object.hasOwn(defaultConfigObject, "references")) options.problems.push([
|
|
1863
|
+
"Directory with multiple typecheck environments must use tsconfig.json as an aggregator:",
|
|
1864
|
+
` config: ${toRelativePath(options.config.rootDir, defaultConfigPath)}`,
|
|
1865
|
+
" reason: multiple type environments require a default IDE/typecheck aggregator."
|
|
1866
|
+
].join("\n"));
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
function collectConfigFileOwners(config, graphRoutes, sourceFiles) {
|
|
1870
|
+
const ownersByFile = /* @__PURE__ */ new Map();
|
|
1871
|
+
for (const route of graphRoutes) for (const configPath of route.projectPaths) {
|
|
1872
|
+
if (!existsSync(configPath)) continue;
|
|
1873
|
+
for (const filePath of parseProjectFileNamesForExtensions(config, configPath, route.extensions)) {
|
|
1874
|
+
if (!sourceFiles.has(filePath)) continue;
|
|
1875
|
+
const owners = ownersByFile.get(filePath) ?? [];
|
|
1876
|
+
owners.push(configPath);
|
|
1877
|
+
ownersByFile.set(filePath, owners);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
return ownersByFile;
|
|
1881
|
+
}
|
|
1882
|
+
function addDuplicateGraphCoverageProblems(options) {
|
|
1883
|
+
for (const [filePath, owners] of [...options.ownersByFile.entries()].sort(([left], [right]) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right)))) {
|
|
1884
|
+
const uniqueOwners = [...new Set(owners)];
|
|
1885
|
+
if (uniqueOwners.length <= 1) continue;
|
|
1886
|
+
options.problems.push([
|
|
1887
|
+
"Duplicate checker graph coverage:",
|
|
1888
|
+
` file: ${toRelativePath(options.config.rootDir, filePath)}`,
|
|
1889
|
+
" covered by:",
|
|
1890
|
+
...uniqueOwners.sort((left, right) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right))).map((configPath) => ` - ${toRelativePath(options.config.rootDir, configPath)}`),
|
|
1891
|
+
" reason: a checker graph file must have a single declaration owner; move the file to one dts leaf or narrow include/exclude patterns."
|
|
1892
|
+
].join("\n"));
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
function addAllowlistProblems(options) {
|
|
1896
|
+
for (const entry of options.allowlistEntries) {
|
|
1897
|
+
if (!existsSync(entry.filePath)) {
|
|
1898
|
+
options.problems.push(["Typecheck proof allowlist references a missing file:", ` file: ${toRelativePath(options.config.rootDir, entry.filePath)}`].join("\n"));
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
if (!options.sourceFiles.has(entry.filePath)) {
|
|
1902
|
+
options.problems.push([
|
|
1903
|
+
"Typecheck proof allowlist file is outside the configured source boundary:",
|
|
1904
|
+
` file: ${toRelativePath(options.config.rootDir, entry.filePath)}`,
|
|
1905
|
+
" reason: allowlist entries should only describe source files that proof would otherwise require coverage for."
|
|
1967
1906
|
].join("\n"));
|
|
1968
|
-
|
|
1907
|
+
continue;
|
|
1969
1908
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1909
|
+
if (options.baseCoverageByFile.has(entry.filePath)) options.problems.push(["Typecheck proof allowlist file is already covered without the allowlist:", ` file: ${toRelativePath(options.config.rootDir, entry.filePath)}`].join("\n"));
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
function addUncoveredSourceProblems(options) {
|
|
1913
|
+
const uncoveredFiles = [...options.sourceFiles].filter((filePath) => !options.coverageByFile.has(filePath));
|
|
1914
|
+
if (uncoveredFiles.length === 0) return;
|
|
1915
|
+
options.problems.push([
|
|
1916
|
+
"Source files are not covered by typecheck proof:",
|
|
1917
|
+
...uncoveredFiles.slice(0, 20).map((filePath) => ` - ${toRelativePath(options.config.rootDir, filePath)}`),
|
|
1918
|
+
uncoveredFiles.length > 20 ? ` ... ${uncoveredFiles.length - 20} more` : "",
|
|
1919
|
+
" reason: every file in config.source must be covered by a checker entry or an explicit allowlist entry."
|
|
1920
|
+
].filter(Boolean).join("\n"));
|
|
1921
|
+
}
|
|
1922
|
+
async function runProofCheckInternal(config, options = {}) {
|
|
1923
|
+
const problems = [];
|
|
1924
|
+
const graphRouteCollection = collectGraphProjectRoutes(config);
|
|
1925
|
+
const entryRouteCollection = collectCheckerEntryProjectRoutes(config);
|
|
1926
|
+
const entryProjectPaths = [...new Set(entryRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
|
|
1927
|
+
const entryProjectPathSet = new Set(entryProjectPaths);
|
|
1928
|
+
const entryProjectExtensionsByPath = collectProjectExtensionsByPath(entryRouteCollection.routes);
|
|
1929
|
+
const dtsConfigPaths = await collectDtsConfigPaths(config);
|
|
1930
|
+
const buildGraphConfigPaths = await collectBuildGraphConfigPaths(config);
|
|
1931
|
+
const defaultTsconfigPaths = await collectDefaultTsconfigPaths(config);
|
|
1932
|
+
const ordinaryTypecheckConfigPaths = await collectOrdinaryTypecheckConfigPaths(config);
|
|
1933
|
+
problems.push(...graphRouteCollection.problems);
|
|
1934
|
+
problems.push(...entryRouteCollection.problems);
|
|
1935
|
+
addDtsConfigProblems({
|
|
1936
|
+
config,
|
|
1937
|
+
dtsConfigPaths,
|
|
1938
|
+
graphProjectPaths: entryProjectPathSet,
|
|
1939
|
+
problems,
|
|
1940
|
+
projectExtensionsByPath: entryProjectExtensionsByPath
|
|
1941
|
+
});
|
|
1942
|
+
addBuildGraphConfigProblems({
|
|
1943
|
+
buildGraphConfigPaths,
|
|
1944
|
+
config,
|
|
1945
|
+
problems
|
|
1946
|
+
});
|
|
1947
|
+
addDefaultTsconfigShapeProblems({
|
|
1948
|
+
config,
|
|
1949
|
+
problems,
|
|
1950
|
+
tsconfigPaths: defaultTsconfigPaths
|
|
1951
|
+
});
|
|
1952
|
+
addDefaultTsconfigEnvironmentProblems({
|
|
1953
|
+
config,
|
|
1954
|
+
ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
|
|
1955
|
+
problems
|
|
1956
|
+
});
|
|
1957
|
+
if (problems.length > 0) {
|
|
1958
|
+
ProofLogger.error(problems.join("\n\n"));
|
|
1959
|
+
return false;
|
|
1960
|
+
}
|
|
1961
|
+
const checkerTargetCollection = collectCheckerCoverageTargets(config);
|
|
1962
|
+
const checkerTargets = checkerTargetCollection.targets;
|
|
1963
|
+
problems.push(...checkerTargetCollection.problems);
|
|
1964
|
+
if (problems.length > 0) {
|
|
1965
|
+
ProofLogger.error(problems.join("\n\n"));
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
const sourceFiles = await collectExpectedSourceFiles(config);
|
|
1969
|
+
const allowlistCollection = collectConfiguredAllowlistEntries(config);
|
|
1970
|
+
const allowlistEntries = allowlistCollection.entries;
|
|
1971
|
+
problems.push(...allowlistCollection.problems);
|
|
1972
|
+
const baseCoverageByFile = collectCoverage({
|
|
1973
|
+
allowlistEntries,
|
|
1974
|
+
checkerTargets,
|
|
1975
|
+
config,
|
|
1976
|
+
graphRoutes: graphRouteCollection.routes,
|
|
1977
|
+
includeAllowlist: false,
|
|
1978
|
+
sourceFiles
|
|
1979
|
+
});
|
|
1980
|
+
const coverageByFile = collectCoverage({
|
|
1981
|
+
allowlistEntries,
|
|
1982
|
+
checkerTargets,
|
|
1983
|
+
config,
|
|
1984
|
+
graphRoutes: graphRouteCollection.routes,
|
|
1985
|
+
sourceFiles
|
|
1986
|
+
});
|
|
1987
|
+
addDuplicateGraphCoverageProblems({
|
|
1988
|
+
config,
|
|
1989
|
+
ownersByFile: collectConfigFileOwners(config, graphRouteCollection.routes, sourceFiles),
|
|
1990
|
+
problems
|
|
1991
|
+
});
|
|
1992
|
+
addAllowlistProblems({
|
|
1993
|
+
allowlistEntries,
|
|
1994
|
+
baseCoverageByFile,
|
|
1995
|
+
config,
|
|
1996
|
+
problems,
|
|
1997
|
+
sourceFiles
|
|
1998
|
+
});
|
|
1999
|
+
addUncoveredSourceProblems({
|
|
2000
|
+
config,
|
|
2001
|
+
coverageByFile,
|
|
2002
|
+
problems,
|
|
2003
|
+
sourceFiles
|
|
1974
2004
|
});
|
|
2005
|
+
if (problems.length > 0) {
|
|
2006
|
+
ProofLogger.error(problems.join("\n\n"));
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
const graphFileCount = [...coverageByFile.values()].filter((sources) => sources.some((source) => source.type === "graph")).length;
|
|
2010
|
+
const checkerFileCount = [...coverageByFile.values()].filter((sources) => sources.some((source) => source.type === "checker")).length;
|
|
2011
|
+
if (options.logSuccess ?? true) ProofLogger.success([
|
|
2012
|
+
`Checked ${entryProjectPaths.length} checker entry projects and ${dtsConfigPaths.length} dts configs.`,
|
|
2013
|
+
`Graph-capable checker entries cover ${graphFileCount} files; checker entries cover ${checkerFileCount} files.`,
|
|
2014
|
+
`Configured source boundary covers ${sourceFiles.size} files.`
|
|
2015
|
+
].join("\n"));
|
|
2016
|
+
if ((options.logSuccess ?? true) && (config.proof?.allowlist ?? []).length > 0) ProofLogger.info(`Explicit typecheck proof allowlist: ${(config.proof?.allowlist ?? []).map((entry) => entry.file).join(", ")}`);
|
|
2017
|
+
return true;
|
|
2018
|
+
}
|
|
2019
|
+
async function runProofCheck(config, options = {}) {
|
|
2020
|
+
if (options.clearScreen ?? true) clearCliScreen();
|
|
2021
|
+
const elapsed = createElapsedTimer();
|
|
2022
|
+
const task = options.flow?.start("proof check", { depth: options.flowDepth ?? 0 });
|
|
2023
|
+
ProofLogger.info("proof check started");
|
|
2024
|
+
try {
|
|
2025
|
+
const logSuccess = !options.flow?.interactive;
|
|
2026
|
+
const passed = await runProofCheckInternal(config, { logSuccess });
|
|
2027
|
+
if (passed) {
|
|
2028
|
+
if (logSuccess) ProofLogger.success("proof check finished", elapsed());
|
|
2029
|
+
task?.pass();
|
|
2030
|
+
} else {
|
|
2031
|
+
ProofLogger.error("proof check finished with failures", elapsed());
|
|
2032
|
+
task?.fail("proof check finished with failures");
|
|
2033
|
+
}
|
|
2034
|
+
return passed;
|
|
2035
|
+
} catch (error) {
|
|
2036
|
+
ProofLogger.error(`proof check failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
2037
|
+
task?.fail("proof check failed", { error });
|
|
2038
|
+
throw error;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
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() {
|
|
1975
2057
|
return {
|
|
1976
|
-
|
|
1977
|
-
|
|
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()
|
|
1978
2069
|
};
|
|
1979
2070
|
}
|
|
1980
|
-
function
|
|
1981
|
-
const
|
|
1982
|
-
|
|
1983
|
-
|
|
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;
|
|
1984
2079
|
}
|
|
1985
|
-
function
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
addCoverage(coverageByFile, filePath, {
|
|
1990
|
-
label: toRelativePath(options.config.rootDir, graphProjectPath),
|
|
1991
|
-
type: "graph"
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
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
|
-
});
|
|
2000
|
-
}
|
|
2001
|
-
if (options.includeAllowlist !== false) for (const entry of options.allowlistEntries) {
|
|
2002
|
-
if (!options.sourceFiles.has(entry.filePath)) continue;
|
|
2003
|
-
addCoverage(coverageByFile, entry.filePath, {
|
|
2004
|
-
label: entry.reason,
|
|
2005
|
-
type: "allowlist"
|
|
2006
|
-
});
|
|
2007
|
-
}
|
|
2008
|
-
return coverageByFile;
|
|
2080
|
+
function addEdge(edges, importerName, dependencyName) {
|
|
2081
|
+
const dependencies = edges.get(importerName) ?? /* @__PURE__ */ new Set();
|
|
2082
|
+
dependencies.add(dependencyName);
|
|
2083
|
+
edges.set(importerName, dependencies);
|
|
2009
2084
|
}
|
|
2010
|
-
function
|
|
2011
|
-
const
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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;
|
|
2015
2103
|
}
|
|
2016
|
-
return projectExtensionsByPath;
|
|
2017
2104
|
}
|
|
2018
|
-
function
|
|
2019
|
-
|
|
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)));
|
|
2023
|
-
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
|
|
2024
|
-
return {
|
|
2025
|
-
fileNames: parsed.fileNames.map(normalizeAbsolutePath).sort(),
|
|
2026
|
-
options: parsed.options
|
|
2027
|
-
};
|
|
2105
|
+
function getNpmPackageMetadataUrl(packageName) {
|
|
2106
|
+
return `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
2028
2107
|
}
|
|
2029
|
-
function
|
|
2030
|
-
return
|
|
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;
|
|
2031
2119
|
}
|
|
2032
|
-
function
|
|
2033
|
-
if (
|
|
2034
|
-
|
|
2035
|
-
return
|
|
2120
|
+
function findRegistryVersionMetadata(metadata, version) {
|
|
2121
|
+
if (!isRecord(metadata.versions)) return null;
|
|
2122
|
+
const versionMetadata = metadata.versions[version];
|
|
2123
|
+
return isRecord(versionMetadata) ? versionMetadata : null;
|
|
2036
2124
|
}
|
|
2037
|
-
function
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
` local: ${formatJsonValue(localValue)}`,
|
|
2069
|
-
` dts: ${formatJsonValue(dtsValue)}`
|
|
2070
|
-
].join("\n"));
|
|
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;
|
|
2071
2156
|
}
|
|
2157
|
+
return (await execGitCommand(options.config, [
|
|
2158
|
+
"ls-files",
|
|
2159
|
+
"--others",
|
|
2160
|
+
"--exclude-standard",
|
|
2161
|
+
"--",
|
|
2162
|
+
relativeDirectory
|
|
2163
|
+
])).trim().length > 0;
|
|
2072
2164
|
}
|
|
2073
|
-
function
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
"DTS config is not valid for tsc -b:",
|
|
2095
|
-
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
2096
|
-
" reason: final compilerOptions.noEmit must not be true."
|
|
2097
|
-
].join("\n"));
|
|
2098
|
-
if (dtsConfig.options.declaration !== true) options.problems.push([
|
|
2099
|
-
"DTS config is not valid for declaration emit:",
|
|
2100
|
-
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
2101
|
-
" reason: final compilerOptions.declaration must be true."
|
|
2102
|
-
].join("\n"));
|
|
2103
|
-
if (dtsConfig.options.emitDeclarationOnly !== true) options.problems.push([
|
|
2104
|
-
"DTS config is not valid for declaration emit:",
|
|
2105
|
-
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
2106
|
-
" reason: final compilerOptions.emitDeclarationOnly must be true."
|
|
2107
|
-
].join("\n"));
|
|
2108
|
-
addDtsConfigSemanticProblems({
|
|
2109
|
-
config: options.config,
|
|
2110
|
-
dtsConfig,
|
|
2111
|
-
dtsConfigPath: configPath,
|
|
2112
|
-
localConfig,
|
|
2113
|
-
localConfigPath,
|
|
2114
|
-
problems: options.problems
|
|
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
|
|
2115
2186
|
});
|
|
2187
|
+
return;
|
|
2116
2188
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
}
|
|
2124
|
-
function addPureAggregatorProblems(options) {
|
|
2125
|
-
const roleLabel = formatConfigRole(options.role);
|
|
2126
|
-
const allowedKeys = new Set([
|
|
2127
|
-
"$schema",
|
|
2128
|
-
"files",
|
|
2129
|
-
"references"
|
|
2130
|
-
]);
|
|
2131
|
-
const extraKeys = Object.keys(options.configObject).filter((key) => !allowedKeys.has(key));
|
|
2132
|
-
if (!Object.hasOwn(options.configObject, "files")) options.problems.push([
|
|
2133
|
-
`${roleLabel} is not a pure aggregator:`,
|
|
2134
|
-
` config: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
2135
|
-
" field: files",
|
|
2136
|
-
" reason: configs with project references must declare files: []."
|
|
2137
|
-
].join("\n"));
|
|
2138
|
-
else if (!isEmptyArray(options.configObject.files)) options.problems.push([
|
|
2139
|
-
`${roleLabel} is not a pure aggregator:`,
|
|
2140
|
-
` config: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
2141
|
-
" field: files",
|
|
2142
|
-
` value: ${formatUnknownValue(options.configObject.files)}`,
|
|
2143
|
-
" reason: configs with project references must declare files: []."
|
|
2144
|
-
].join("\n"));
|
|
2145
|
-
if (extraKeys.length > 0) options.problems.push([
|
|
2146
|
-
`${roleLabel} is not a pure aggregator:`,
|
|
2147
|
-
` config: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
2148
|
-
` fields: ${extraKeys.sort().join(", ")}`,
|
|
2149
|
-
" reason: pure aggregators may only declare $schema, files, and references; move source inputs and compiler options into leaf configs."
|
|
2150
|
-
].join("\n"));
|
|
2151
|
-
}
|
|
2152
|
-
function addBuildGraphConfigProblems(options) {
|
|
2153
|
-
for (const configPath of options.buildGraphConfigPaths) {
|
|
2154
|
-
const configObject = readJsonConfig(options.config, configPath);
|
|
2155
|
-
addPureAggregatorProblems({
|
|
2156
|
-
config: options.config,
|
|
2157
|
-
configObject,
|
|
2158
|
-
configPath,
|
|
2159
|
-
problems: options.problems,
|
|
2160
|
-
role: "build graph"
|
|
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
|
|
2161
2195
|
});
|
|
2196
|
+
return;
|
|
2162
2197
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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({
|
|
2169
2220
|
config: options.config,
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
problems: options.problems,
|
|
2173
|
-
role: "tsconfig.json"
|
|
2221
|
+
gitHead: versionMetadata.gitHead,
|
|
2222
|
+
workspacePackage
|
|
2174
2223
|
});
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
` resolved: ${toRelativePath(options.config.rootDir, referencePath)}`,
|
|
2186
|
-
" reason: tsconfig.json is the default IDE/typecheck entry and must not reference declaration build graph configs."
|
|
2187
|
-
].join("\n"));
|
|
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
|
|
2188
2234
|
});
|
|
2235
|
+
return;
|
|
2189
2236
|
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
configsByDirectory.set(directory, configs);
|
|
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
|
+
});
|
|
2198
2244
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
if (
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
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
|
+
});
|
|
2209
2257
|
continue;
|
|
2210
2258
|
}
|
|
2211
|
-
if (
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
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
|
+
});
|
|
2218
2269
|
continue;
|
|
2219
2270
|
}
|
|
2220
|
-
|
|
2221
|
-
if (
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
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
|
+
});
|
|
2237
2303
|
}
|
|
2238
2304
|
}
|
|
2239
|
-
return ownersByFile;
|
|
2240
2305
|
}
|
|
2241
|
-
function
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
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
|
+
});
|
|
2252
2318
|
}
|
|
2319
|
+
return files;
|
|
2253
2320
|
}
|
|
2254
|
-
function
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
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;
|
|
2269
2338
|
}
|
|
2270
2339
|
}
|
|
2271
|
-
function
|
|
2272
|
-
|
|
2273
|
-
if (uncoveredFiles.length === 0) return;
|
|
2274
|
-
options.problems.push([
|
|
2275
|
-
"Source files are not covered by typecheck proof:",
|
|
2276
|
-
...uncoveredFiles.slice(0, 20).map((filePath) => ` - ${toRelativePath(options.config.rootDir, filePath)}`),
|
|
2277
|
-
uncoveredFiles.length > 20 ? ` ... ${uncoveredFiles.length - 20} more` : "",
|
|
2278
|
-
" reason: every file in config.source must be covered by a checker entry or an explicit allowlist entry."
|
|
2279
|
-
].filter(Boolean).join("\n"));
|
|
2340
|
+
function isJavaScriptPackageFile(relativePath) {
|
|
2341
|
+
return /\.(?:cjs|mjs|js)$/u.test(relativePath);
|
|
2280
2342
|
}
|
|
2281
|
-
|
|
2282
|
-
const
|
|
2283
|
-
const
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
const entryProjectExtensionsByPath = collectProjectExtensionsByPath(entryRouteCollection.routes);
|
|
2288
|
-
const dtsConfigPaths = await collectDtsConfigPaths(config);
|
|
2289
|
-
const buildGraphConfigPaths = await collectBuildGraphConfigPaths(config);
|
|
2290
|
-
const defaultTsconfigPaths = await collectDefaultTsconfigPaths(config);
|
|
2291
|
-
const ordinaryTypecheckConfigPaths = await collectOrdinaryTypecheckConfigPaths(config);
|
|
2292
|
-
problems.push(...graphRouteCollection.problems);
|
|
2293
|
-
problems.push(...entryRouteCollection.problems);
|
|
2294
|
-
addDtsConfigProblems({
|
|
2295
|
-
config,
|
|
2296
|
-
dtsConfigPaths,
|
|
2297
|
-
graphProjectPaths: entryProjectPathSet,
|
|
2298
|
-
problems,
|
|
2299
|
-
projectExtensionsByPath: entryProjectExtensionsByPath
|
|
2300
|
-
});
|
|
2301
|
-
addBuildGraphConfigProblems({
|
|
2302
|
-
buildGraphConfigPaths,
|
|
2303
|
-
config,
|
|
2304
|
-
problems
|
|
2305
|
-
});
|
|
2306
|
-
addDefaultTsconfigShapeProblems({
|
|
2307
|
-
config,
|
|
2308
|
-
problems,
|
|
2309
|
-
tsconfigPaths: defaultTsconfigPaths
|
|
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(", ")}`
|
|
2310
2349
|
});
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
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
|
|
2315
2374
|
});
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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
|
+
});
|
|
2319
2395
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
return
|
|
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);
|
|
2326
2405
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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
|
|
2345
2446
|
});
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2447
|
+
const packedManifest = readPackedPackageJson({
|
|
2448
|
+
contentFiles,
|
|
2449
|
+
rootPackageName: options.outputManifest.name,
|
|
2450
|
+
state
|
|
2350
2451
|
});
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
problems,
|
|
2356
|
-
sourceFiles
|
|
2452
|
+
if (packedManifest) validatePackedManifest({
|
|
2453
|
+
manifest: packedManifest,
|
|
2454
|
+
rootPackageName: options.outputManifest.name,
|
|
2455
|
+
state
|
|
2357
2456
|
});
|
|
2358
|
-
|
|
2359
|
-
config,
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2457
|
+
const error = createReleaseConsistencyError({
|
|
2458
|
+
config: options.config,
|
|
2459
|
+
label: options.label,
|
|
2460
|
+
outDir: options.outDir,
|
|
2461
|
+
rootPackageName: options.outputManifest.name,
|
|
2462
|
+
state
|
|
2363
2463
|
});
|
|
2364
|
-
if (
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
`
|
|
2373
|
-
`
|
|
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"))
|
|
2374
2477
|
].join("\n"));
|
|
2375
|
-
if ((options.logSuccess ?? true) && (config.proof?.allowlist ?? []).length > 0) ProofLogger.info(`Explicit typecheck proof allowlist: ${(config.proof?.allowlist ?? []).map((entry) => entry.file).join(", ")}`);
|
|
2376
|
-
return true;
|
|
2377
2478
|
}
|
|
2378
|
-
async function
|
|
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) {
|
|
2379
2541
|
if (options.clearScreen ?? true) clearCliScreen();
|
|
2380
2542
|
const elapsed = createElapsedTimer();
|
|
2381
|
-
const
|
|
2382
|
-
|
|
2543
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
2544
|
+
const task = options.flow?.start("release check", { depth: options.flowDepth ?? 0 });
|
|
2383
2545
|
try {
|
|
2384
|
-
|
|
2385
|
-
const
|
|
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;
|
|
2386
2566
|
if (passed) {
|
|
2387
|
-
if (
|
|
2567
|
+
if (!options.flow?.interactive) ReleaseLogger.success("release check finished", elapsed());
|
|
2388
2568
|
task?.pass();
|
|
2389
2569
|
} else {
|
|
2390
|
-
|
|
2391
|
-
task?.fail("
|
|
2570
|
+
ReleaseLogger.error("release check finished with failures", elapsed());
|
|
2571
|
+
task?.fail("release check finished with failures");
|
|
2392
2572
|
}
|
|
2393
2573
|
return passed;
|
|
2394
2574
|
} catch (error) {
|
|
2395
|
-
|
|
2396
|
-
task?.fail("
|
|
2575
|
+
ReleaseLogger.error(`release check failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
2576
|
+
task?.fail("release check failed", { error });
|
|
2397
2577
|
throw error;
|
|
2398
2578
|
}
|
|
2399
2579
|
}
|
|
@@ -2406,6 +2586,7 @@ const builtInTaskNames = new Set([
|
|
|
2406
2586
|
"graph:check",
|
|
2407
2587
|
"package:check",
|
|
2408
2588
|
"proof:check",
|
|
2589
|
+
"release:check",
|
|
2409
2590
|
"source:check"
|
|
2410
2591
|
]);
|
|
2411
2592
|
const defaultCheckPipeline = [
|
|
@@ -2461,7 +2642,16 @@ async function runBuiltinTask(config, taskName, options = {}) {
|
|
|
2461
2642
|
config,
|
|
2462
2643
|
cwd: options.cwd,
|
|
2463
2644
|
flow: options.flow,
|
|
2464
|
-
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
|
|
2465
2655
|
});
|
|
2466
2656
|
case "checker:typecheck": return (await runCheckerTypecheck({
|
|
2467
2657
|
clearScreen: false,
|
|
@@ -2591,6 +2781,11 @@ function parsePackageAttwProfile(profile) {
|
|
|
2591
2781
|
if (profile === "strict" || profile === "node16" || profile === "esm-only") return profile;
|
|
2592
2782
|
throw new Error(`Invalid package check --attw-profile "${profile}". Expected one of: strict, node16, esm-only.`);
|
|
2593
2783
|
}
|
|
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;
|
|
2788
|
+
}
|
|
2594
2789
|
function createCliFlow() {
|
|
2595
2790
|
clearCliScreen();
|
|
2596
2791
|
return createLiminaFlowReporter();
|
|
@@ -2611,13 +2806,14 @@ async function main() {
|
|
|
2611
2806
|
});
|
|
2612
2807
|
flow.outro("limina init finished");
|
|
2613
2808
|
});
|
|
2614
|
-
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) => {
|
|
2615
2810
|
const flow = createCliFlow();
|
|
2616
2811
|
flow.intro("limina check");
|
|
2617
2812
|
const config = await load(flags, "check");
|
|
2618
2813
|
const passed = pipeline ? await runPipeline(config, pipeline, {
|
|
2619
2814
|
cwd: process.cwd(),
|
|
2620
|
-
flow
|
|
2815
|
+
flow,
|
|
2816
|
+
packageNames: parsePackageNames(flags.package)
|
|
2621
2817
|
}) : await runDefaultCheck(config, {
|
|
2622
2818
|
cwd: process.cwd(),
|
|
2623
2819
|
flow
|
|
@@ -2694,7 +2890,7 @@ async function main() {
|
|
|
2694
2890
|
if (!result.passed) process.exitCode = 1;
|
|
2695
2891
|
flow.outro(result.passed ? "limina checker passed" : "limina checker failed");
|
|
2696
2892
|
});
|
|
2697
|
-
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) => {
|
|
2698
2894
|
if (action !== "check") throw new Error(`Unknown package action "${action}". Expected check.`);
|
|
2699
2895
|
const flow = createCliFlow();
|
|
2700
2896
|
flow.intro("limina package check");
|
|
@@ -2705,12 +2901,26 @@ async function main() {
|
|
|
2705
2901
|
config,
|
|
2706
2902
|
cwd: process.cwd(),
|
|
2707
2903
|
flow,
|
|
2708
|
-
|
|
2904
|
+
packageNames: parsePackageNames(flags.package),
|
|
2709
2905
|
tool: parsePackageTool(flags.tool)
|
|
2710
2906
|
});
|
|
2711
2907
|
if (!passed) process.exitCode = 1;
|
|
2712
2908
|
flow.outro(passed ? "limina package passed" : "limina package failed");
|
|
2713
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
|
+
});
|
|
2714
2924
|
cli.parse(process.argv, { run: false });
|
|
2715
2925
|
try {
|
|
2716
2926
|
await cli.runMatchedCommand();
|