limina 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +383 -0
- package/bin/limina.js +0 -0
- package/chunks/{dep-jgc7X0zw.js → dep-DzYrmtQJ.js} +32 -49
- package/chunks/{dep-DoSHsBSP.js → dep-UWxsul2A.js} +827 -453
- package/cli.js +667 -229
- package/config.d.ts +8 -48
- package/config.js +1 -1
- package/index.d.ts +25 -20
- package/index.js +3 -3
- package/package.json +19 -2
package/cli.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunks/dep-lkQg1P9Q.js";
|
|
3
|
-
import { d as
|
|
4
|
-
import { A as
|
|
5
|
-
import { builtinModules } from "node:module";
|
|
3
|
+
import { c as getCheckerAdapter, d as normalizeAbsolutePath, f as normalizeSlashes, g as toRelativePath, h as toPosixPath, i as loadConfig, l as normalizeExtensions, m as toAbsolutePath, n as getActiveCheckerExtensions, p as normalizeWorkspacePath, r as getActiveCheckers, u as isPathInsideDirectory } from "./chunks/dep-DzYrmtQJ.js";
|
|
4
|
+
import { A as shouldResolveThroughGraph$1, B as collectGraphProjectRoutes, C as formatArtifactDependencyPolicy, D as isRelativeSpecifier, E as isDtsProjectConfig, F as getPublishDependencySections, G as formatReferences, H as createExtensionPattern, I as isWorkspaceDependencySpecifier, J as isOrdinaryTypecheckConfigPath, K as getDtsCompanionConfigPath, L as collectCheckerEntryProjectRoutes, M as collectWorkspacePackages, N as findPackageForSpecifier, O as parseProject$1, P as getPackageRootSpecifier, Q as resolveReferencePath, R as collectGraphProjectRoute, S as findTargetProject, T as inferPackageProject$1, U as createExtraFileExtensions, V as collectSourceGraphProjectExtensions, W as createFormatHost, X as readJsonConfig, Y as parseProjectFileNamesForExtensions, Z as resolveProjectConfigPath, _ as normalizeGraphRules, a as runSourceCheck, b as findImporterForFile$1, c as GraphLogger, d as ProofLogger, f as clearCliScreen, g as getDeniedRefRule, h as getDeniedDepRuleForSpecifier, i as runCheckerTypecheck, j as collectImporters, k as resolveInternalImport$1, l as PackageLogger, m as getDeniedDepRuleForPackage, n as createLiminaFlowReporter, o as runInit, p as formatErrorMessage$1, q as getRawReferencePaths, r as runCheckerBuild, s as CliLogger, u as PathsLogger, v as collectImportsFromFile$1, w as getTypecheckConfigPath, x as findPackageForFile, y as createFileOwnerLookup$1, z as collectGraphProjectRouteFromRoot } from "./chunks/dep-UWxsul2A.js";
|
|
5
|
+
import { builtinModules, createRequire } from "node:module";
|
|
6
6
|
import { cac } from "cac";
|
|
7
|
-
import { createElapsedTimer } from "
|
|
7
|
+
import { createElapsedTimer } from "logaria/helper";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import ts from "typescript";
|
|
11
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
11
|
+
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
12
12
|
import { glob } from "tinyglobby";
|
|
13
|
+
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
13
14
|
import { checkPackage, createPackageFromTarballData } from "@arethetypeswrong/core";
|
|
14
|
-
import { pack } from "@publint/pack";
|
|
15
|
+
import { pack, unpack } from "@publint/pack";
|
|
15
16
|
import { init, parse } from "es-module-lexer";
|
|
16
|
-
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
17
17
|
import { tmpdir } from "node:os";
|
|
18
18
|
import { publint } from "publint";
|
|
19
19
|
import { formatMessage } from "publint/utils";
|
|
@@ -112,7 +112,7 @@ function addTypecheckParityProblems(config, dtsProject, problems) {
|
|
|
112
112
|
].join("\n"));
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
-
const typecheckProject = parseProject$1(config, typecheckConfigPath);
|
|
115
|
+
const typecheckProject = parseProject$1(config, typecheckConfigPath, dtsProject.extensions);
|
|
116
116
|
for (const optionName of comparableTypecheckOptions) {
|
|
117
117
|
const buildValue = dtsProject.options[optionName];
|
|
118
118
|
const typecheckValue = typecheckProject.options[optionName];
|
|
@@ -146,7 +146,7 @@ function addDeniedReferenceProblems(options) {
|
|
|
146
146
|
if (!options.projectsByPath.has(referencePath)) continue;
|
|
147
147
|
const deniedRefRule = getDeniedRefRule(options.rules, options.project.label, referencePath);
|
|
148
148
|
const targetPackage = findPackageForFile(referencePath, options.packages);
|
|
149
|
-
const deniedDepRule = targetPackage ?
|
|
149
|
+
const deniedDepRule = targetPackage ? getDeniedDepRuleForPackage(options.rules, options.project.label, targetPackage.name) : null;
|
|
150
150
|
if (!deniedRefRule && !deniedDepRule) continue;
|
|
151
151
|
const lines = [
|
|
152
152
|
"Denied graph access:",
|
|
@@ -154,12 +154,12 @@ function addDeniedReferenceProblems(options) {
|
|
|
154
154
|
` referencing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
|
|
155
155
|
` referenced project: ${toRelativePath(options.config.rootDir, referencePath)}`
|
|
156
156
|
];
|
|
157
|
-
if (
|
|
158
|
-
else if (
|
|
157
|
+
if (deniedDepRule) lines.push(` denied dependency: ${deniedDepRule.name}`, ` reason: ${deniedDepRule.reason}`);
|
|
158
|
+
else if (deniedRefRule) lines.push(` denied ref: ${toRelativePath(options.config.rootDir, deniedRefRule.path)}`, ` reason: ${deniedRefRule.reason}`);
|
|
159
159
|
options.problems.push(lines.join("\n"));
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
function
|
|
162
|
+
function addDeniedDepImportProblem(options) {
|
|
163
163
|
options.problems.push([
|
|
164
164
|
"Denied graph access:",
|
|
165
165
|
` rule: ${options.project.label}`,
|
|
@@ -182,6 +182,25 @@ function addDeniedRefImportProblem(options) {
|
|
|
182
182
|
` reason: ${options.rule.reason}`
|
|
183
183
|
].join("\n"));
|
|
184
184
|
}
|
|
185
|
+
function getNodeModulesPackageName(filePath) {
|
|
186
|
+
const parts = filePath.split("/");
|
|
187
|
+
const nodeModulesIndex = parts.lastIndexOf("node_modules");
|
|
188
|
+
if (nodeModulesIndex === -1) return null;
|
|
189
|
+
const packageName = parts[nodeModulesIndex + 1];
|
|
190
|
+
if (!packageName) return null;
|
|
191
|
+
if (packageName.startsWith("@")) {
|
|
192
|
+
const scopedName = parts[nodeModulesIndex + 2];
|
|
193
|
+
return scopedName ? `${packageName}/${scopedName}` : null;
|
|
194
|
+
}
|
|
195
|
+
return packageName;
|
|
196
|
+
}
|
|
197
|
+
function getResolvedPackageName(filePath, packages) {
|
|
198
|
+
return getNodeModulesPackageName(filePath) ?? findPackageForFile(filePath, packages)?.name ?? null;
|
|
199
|
+
}
|
|
200
|
+
function getResolvedWorkspacePackage(filePath, packages) {
|
|
201
|
+
if (getNodeModulesPackageName(filePath)) return null;
|
|
202
|
+
return findPackageForFile(filePath, packages);
|
|
203
|
+
}
|
|
185
204
|
function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems) {
|
|
186
205
|
if (!isDtsProjectConfig(project.configPath)) return;
|
|
187
206
|
const sourcePackage = findPackageForFile(project.configPath, packages);
|
|
@@ -205,9 +224,9 @@ function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath
|
|
|
205
224
|
}
|
|
206
225
|
}
|
|
207
226
|
async function runGraphCheckInternal(config, options = {}) {
|
|
208
|
-
const graphRoute =
|
|
209
|
-
const projectPaths = graphRoute.
|
|
210
|
-
const projects = projectPaths.map((projectPath) => parseProject$1(config, projectPath));
|
|
227
|
+
const graphRoute = collectSourceGraphProjectExtensions(config);
|
|
228
|
+
const projectPaths = [...graphRoute.projectExtensionsByPath.keys()].sort();
|
|
229
|
+
const projects = projectPaths.map((projectPath) => parseProject$1(config, projectPath, graphRoute.projectExtensionsByPath.get(projectPath)));
|
|
211
230
|
const projectsByPath = new Map(projects.map((project) => [project.configPath, project]));
|
|
212
231
|
const fileOwnerLookup = createFileOwnerLookup$1(projects);
|
|
213
232
|
const packages = await collectWorkspacePackages(config);
|
|
@@ -216,9 +235,8 @@ async function runGraphCheckInternal(config, options = {}) {
|
|
|
216
235
|
const graphRules = normalizeGraphRules({
|
|
217
236
|
config,
|
|
218
237
|
include: {
|
|
219
|
-
|
|
220
|
-
refs: true
|
|
221
|
-
workspaceDeps: true
|
|
238
|
+
deps: true,
|
|
239
|
+
refs: true
|
|
222
240
|
},
|
|
223
241
|
packages,
|
|
224
242
|
problems,
|
|
@@ -237,22 +255,22 @@ async function runGraphCheckInternal(config, options = {}) {
|
|
|
237
255
|
rules: graphRules
|
|
238
256
|
});
|
|
239
257
|
addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems);
|
|
240
|
-
for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile$1(filePath)) {
|
|
241
|
-
const
|
|
258
|
+
for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile$1(filePath, config.rootDir)) {
|
|
259
|
+
const rawDeniedDepRule = getDeniedDepRuleForSpecifier(graphRules, project.label, importRecord.specifier);
|
|
260
|
+
if (rawDeniedDepRule) {
|
|
261
|
+
addDeniedDepImportProblem({
|
|
262
|
+
config,
|
|
263
|
+
importRecord,
|
|
264
|
+
problems,
|
|
265
|
+
project,
|
|
266
|
+
rule: rawDeniedDepRule
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const resolvedFilePath = resolveInternalImport$1(importRecord.specifier, filePath, project.options, project.extensions);
|
|
242
271
|
const targetPackage = findPackageForSpecifier(importRecord.specifier, packages);
|
|
243
|
-
const importer =
|
|
244
|
-
const unresolvedDeniedDepRule = targetPackage ? getDeniedWorkspaceDepRule(graphRules, project.label, targetPackage.name) : null;
|
|
272
|
+
const importer = findImporterForFile$1(importRecord.filePath, importers);
|
|
245
273
|
if (!resolvedFilePath) {
|
|
246
|
-
if (unresolvedDeniedDepRule) {
|
|
247
|
-
addDeniedPackageImportProblem({
|
|
248
|
-
config,
|
|
249
|
-
importRecord,
|
|
250
|
-
problems,
|
|
251
|
-
project,
|
|
252
|
-
rule: unresolvedDeniedDepRule
|
|
253
|
-
});
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
274
|
if (!targetPackage) continue;
|
|
257
275
|
problems.push([
|
|
258
276
|
"Unresolved workspace import:",
|
|
@@ -264,10 +282,12 @@ async function runGraphCheckInternal(config, options = {}) {
|
|
|
264
282
|
].join("\n"));
|
|
265
283
|
continue;
|
|
266
284
|
}
|
|
267
|
-
const targetWorkspacePackageForResolved =
|
|
268
|
-
const
|
|
285
|
+
const targetWorkspacePackageForResolved = getResolvedWorkspacePackage(resolvedFilePath, packages);
|
|
286
|
+
const targetPackageForGraph = targetPackage;
|
|
287
|
+
const resolvedPackageName = getResolvedPackageName(resolvedFilePath, packages);
|
|
288
|
+
const deniedDepRule = resolvedPackageName ? getDeniedDepRuleForPackage(graphRules, project.label, resolvedPackageName) : null;
|
|
269
289
|
if (deniedDepRule) {
|
|
270
|
-
|
|
290
|
+
addDeniedDepImportProblem({
|
|
271
291
|
config,
|
|
272
292
|
importRecord,
|
|
273
293
|
problems,
|
|
@@ -292,9 +312,8 @@ async function runGraphCheckInternal(config, options = {}) {
|
|
|
292
312
|
continue;
|
|
293
313
|
}
|
|
294
314
|
}
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
const referencedProjectPath = inferPackageProject$1(resolvedFilePath, targetPackage, projectPaths);
|
|
315
|
+
if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph) && !fileOwnerLookup.has(resolvedFilePath)) {
|
|
316
|
+
const referencedProjectPath = inferPackageProject$1(resolvedFilePath, targetPackageForGraph, projectPaths);
|
|
298
317
|
const hasProjectReference = referencedProjectPath && project.references.has(referencedProjectPath);
|
|
299
318
|
problems.push([
|
|
300
319
|
hasProjectReference ? "Referenced workspace dependency resolves through package exports to a build artifact:" : "Workspace source dependency resolved outside the source graph:",
|
|
@@ -304,7 +323,7 @@ async function runGraphCheckInternal(config, options = {}) {
|
|
|
304
323
|
` imported specifier: ${importRecord.specifier}`,
|
|
305
324
|
` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
|
|
306
325
|
" reason: workspace:* dependencies are source dependencies, but TypeScript resolved this package export to a file not owned by the source graph. tsc -b does not rewrite package exports through project references.",
|
|
307
|
-
` fix: expose source files from the dependency package exports, add a source paths config to this declaration leaf extends, or stop using workspace:* plus project references for artifact consumption; ${formatArtifactDependencyPolicy(
|
|
326
|
+
` fix: expose source files from the dependency package exports, add a source paths config to this declaration leaf extends, or stop using workspace:* plus project references for artifact consumption; ${formatArtifactDependencyPolicy(targetPackageForGraph)}`,
|
|
308
327
|
" hint: run `limina paths generate` to create a compatibility paths file, then manually add it to the first position of the listed tsconfig*.dts.json extends array."
|
|
309
328
|
].join("\n"));
|
|
310
329
|
continue;
|
|
@@ -317,15 +336,15 @@ async function runGraphCheckInternal(config, options = {}) {
|
|
|
317
336
|
specifier: importRecord.specifier
|
|
318
337
|
});
|
|
319
338
|
if (!targetProjectPath) {
|
|
320
|
-
if (!
|
|
321
|
-
if (!
|
|
322
|
-
if (
|
|
339
|
+
if (!targetPackageForGraph) continue;
|
|
340
|
+
if (!targetWorkspacePackageForResolved) {
|
|
341
|
+
if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph)) problems.push([
|
|
323
342
|
"Workspace source import resolved outside the workspace graph:",
|
|
324
343
|
` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
|
|
325
344
|
` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
326
345
|
` imported specifier: ${importRecord.specifier}`,
|
|
327
346
|
` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
|
|
328
|
-
` reason: workspace:* dependencies are source dependency edges and must resolve to files owned by the source graph; ${formatArtifactDependencyPolicy(
|
|
347
|
+
` reason: workspace:* dependencies are source dependency edges and must resolve to files owned by the source graph; ${formatArtifactDependencyPolicy(targetPackageForGraph)}`
|
|
329
348
|
].join("\n"));
|
|
330
349
|
continue;
|
|
331
350
|
}
|
|
@@ -352,6 +371,7 @@ async function runGraphCheckInternal(config, options = {}) {
|
|
|
352
371
|
});
|
|
353
372
|
continue;
|
|
354
373
|
}
|
|
374
|
+
if (targetPackageForGraph && !shouldResolveThroughGraph$1(importer, targetPackageForGraph)) continue;
|
|
355
375
|
if (!projectsByPath.has(targetProjectPath)) {
|
|
356
376
|
problems.push([
|
|
357
377
|
"Expected graph target is not reachable from any checker entry:",
|
|
@@ -402,6 +422,362 @@ async function runGraphCheck(config, options = {}) {
|
|
|
402
422
|
}
|
|
403
423
|
}
|
|
404
424
|
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/package-release-consistency.ts
|
|
427
|
+
var PackageReleaseConsistencyError = class extends Error {
|
|
428
|
+
name = "PackageReleaseConsistencyError";
|
|
429
|
+
};
|
|
430
|
+
const semver = createRequire(import.meta.url)("semver");
|
|
431
|
+
function isRecord$1(value) {
|
|
432
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
433
|
+
}
|
|
434
|
+
function isLinkDependencySpecifier(specifier) {
|
|
435
|
+
return specifier.startsWith("link:");
|
|
436
|
+
}
|
|
437
|
+
function createReleaseConsistencyState() {
|
|
438
|
+
return {
|
|
439
|
+
directWorkspaceDependencies: [],
|
|
440
|
+
edges: /* @__PURE__ */ new Map(),
|
|
441
|
+
missingWorkspaceDependencies: [],
|
|
442
|
+
packedManifestProblems: [],
|
|
443
|
+
privateWorkspaceDependencies: [],
|
|
444
|
+
registryMetadataCache: /* @__PURE__ */ new Map(),
|
|
445
|
+
registryProblems: [],
|
|
446
|
+
sourceLinkDependencies: [],
|
|
447
|
+
unpublishedPackageNames: /* @__PURE__ */ new Set(),
|
|
448
|
+
visitedPackages: /* @__PURE__ */ new Set()
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function collectPublishDependencyEntries(manifest) {
|
|
452
|
+
const entries = [];
|
|
453
|
+
for (const { dependencies, name } of getPublishDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) entries.push({
|
|
454
|
+
dependencyName,
|
|
455
|
+
sectionName: name,
|
|
456
|
+
specifier
|
|
457
|
+
});
|
|
458
|
+
return entries;
|
|
459
|
+
}
|
|
460
|
+
function addEdge(edges, importerName, dependencyName) {
|
|
461
|
+
const dependencies = edges.get(importerName) ?? /* @__PURE__ */ new Set();
|
|
462
|
+
dependencies.add(dependencyName);
|
|
463
|
+
edges.set(importerName, dependencies);
|
|
464
|
+
}
|
|
465
|
+
function formatDependencyLocation(problem) {
|
|
466
|
+
const dependency = problem.dependencyName ? ` -> ${problem.dependencyName}` : "";
|
|
467
|
+
const section = problem.sectionName ? ` [${problem.sectionName}]` : "";
|
|
468
|
+
const specifier = problem.specifier ? ` (${problem.specifier})` : "";
|
|
469
|
+
return `${problem.importerName}${dependency}${section}${specifier}`;
|
|
470
|
+
}
|
|
471
|
+
function formatProblemLines(title, problems) {
|
|
472
|
+
if (problems.length === 0) return [];
|
|
473
|
+
return [
|
|
474
|
+
"",
|
|
475
|
+
title,
|
|
476
|
+
...problems.map((problem) => ` - ${formatDependencyLocation(problem)}: ${problem.message}`)
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
function getPackedDependencySpecifier(manifest, dependencyName) {
|
|
480
|
+
for (const { dependencies } of getPublishDependencySections(manifest)) {
|
|
481
|
+
const specifier = dependencies[dependencyName];
|
|
482
|
+
if (specifier) return specifier;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function getNpmPackageMetadataUrl(packageName) {
|
|
486
|
+
return `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
487
|
+
}
|
|
488
|
+
async function fetchRegistryPackageMetadata(packageName, state) {
|
|
489
|
+
if (state.registryMetadataCache.has(packageName)) return state.registryMetadataCache.get(packageName) ?? null;
|
|
490
|
+
const response = await fetch(getNpmPackageMetadataUrl(packageName), { headers: { accept: "application/vnd.npm.install-v1+json, application/json" } });
|
|
491
|
+
if (!response.ok) {
|
|
492
|
+
state.registryMetadataCache.set(packageName, null);
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
const metadata = await response.json();
|
|
496
|
+
const registryMetadata = isRecord$1(metadata) ? metadata : null;
|
|
497
|
+
state.registryMetadataCache.set(packageName, registryMetadata);
|
|
498
|
+
return registryMetadata;
|
|
499
|
+
}
|
|
500
|
+
function findRegistryVersionMetadata(metadata, version) {
|
|
501
|
+
if (!isRecord$1(metadata.versions)) return null;
|
|
502
|
+
const versionMetadata = metadata.versions[version];
|
|
503
|
+
return isRecord$1(versionMetadata) ? versionMetadata : null;
|
|
504
|
+
}
|
|
505
|
+
function execGitCommand(config, args) {
|
|
506
|
+
return new Promise((resolve, reject) => {
|
|
507
|
+
execFile("git", [
|
|
508
|
+
"-C",
|
|
509
|
+
config.rootDir,
|
|
510
|
+
...args
|
|
511
|
+
], {
|
|
512
|
+
encoding: "utf8",
|
|
513
|
+
maxBuffer: 10 * 1024 * 1024
|
|
514
|
+
}, (error, stdout) => {
|
|
515
|
+
if (error) {
|
|
516
|
+
reject(error);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
resolve(stdout);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
async function hasWorkspacePackageChangesSinceGitHead(options) {
|
|
524
|
+
const relativeDirectory = toRelativePath(options.config.rootDir, options.workspacePackage.directory);
|
|
525
|
+
try {
|
|
526
|
+
await execGitCommand(options.config, [
|
|
527
|
+
"diff",
|
|
528
|
+
"--quiet",
|
|
529
|
+
options.gitHead,
|
|
530
|
+
"--",
|
|
531
|
+
relativeDirectory
|
|
532
|
+
]);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (isRecord$1(error) && error.code === 1) return true;
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
return (await execGitCommand(options.config, [
|
|
538
|
+
"ls-files",
|
|
539
|
+
"--others",
|
|
540
|
+
"--exclude-standard",
|
|
541
|
+
"--",
|
|
542
|
+
relativeDirectory
|
|
543
|
+
])).trim().length > 0;
|
|
544
|
+
}
|
|
545
|
+
async function verifyWorkspacePackagePublished(options) {
|
|
546
|
+
const { state, workspacePackage } = options;
|
|
547
|
+
const version = workspacePackage.manifest.version;
|
|
548
|
+
if (!version || !semver.valid(version)) {
|
|
549
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
550
|
+
state.registryProblems.push({
|
|
551
|
+
importerName: workspacePackage.name,
|
|
552
|
+
message: ["workspace package must declare a valid semver version", "before another publishable package can depend on it"].join(" "),
|
|
553
|
+
packageName: workspacePackage.name
|
|
554
|
+
});
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
let metadata;
|
|
558
|
+
try {
|
|
559
|
+
metadata = await fetchRegistryPackageMetadata(workspacePackage.name, state);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
562
|
+
state.registryProblems.push({
|
|
563
|
+
importerName: workspacePackage.name,
|
|
564
|
+
message: [`unable to read npm registry metadata for ${workspacePackage.name}@${version}:`, formatErrorMessage$1(error)].join(" "),
|
|
565
|
+
packageName: workspacePackage.name
|
|
566
|
+
});
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (!metadata) {
|
|
570
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
571
|
+
state.registryProblems.push({
|
|
572
|
+
importerName: workspacePackage.name,
|
|
573
|
+
message: `${workspacePackage.name}@${version} is not published to the npm registry`,
|
|
574
|
+
packageName: workspacePackage.name
|
|
575
|
+
});
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const versionMetadata = findRegistryVersionMetadata(metadata, version);
|
|
579
|
+
if (!versionMetadata) {
|
|
580
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
581
|
+
state.registryProblems.push({
|
|
582
|
+
importerName: workspacePackage.name,
|
|
583
|
+
message: `${workspacePackage.name}@${version} is not published to the npm registry`,
|
|
584
|
+
packageName: workspacePackage.name
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (typeof versionMetadata.gitHead !== "string" || versionMetadata.gitHead.trim().length === 0) {
|
|
589
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
590
|
+
state.registryProblems.push({
|
|
591
|
+
importerName: workspacePackage.name,
|
|
592
|
+
message: [`${workspacePackage.name}@${version} registry metadata has no gitHead,`, "so limina cannot prove the published source baseline"].join(" "),
|
|
593
|
+
packageName: workspacePackage.name
|
|
594
|
+
});
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
let hasChanges;
|
|
598
|
+
try {
|
|
599
|
+
hasChanges = await hasWorkspacePackageChangesSinceGitHead({
|
|
600
|
+
config: options.config,
|
|
601
|
+
gitHead: versionMetadata.gitHead,
|
|
602
|
+
workspacePackage
|
|
603
|
+
});
|
|
604
|
+
} catch (error) {
|
|
605
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
606
|
+
state.registryProblems.push({
|
|
607
|
+
importerName: workspacePackage.name,
|
|
608
|
+
message: [
|
|
609
|
+
`unable to compare ${workspacePackage.name}@${version}`,
|
|
610
|
+
`against published gitHead ${versionMetadata.gitHead}:`,
|
|
611
|
+
formatErrorMessage$1(error)
|
|
612
|
+
].join(" "),
|
|
613
|
+
packageName: workspacePackage.name
|
|
614
|
+
});
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (hasChanges) {
|
|
618
|
+
state.unpublishedPackageNames.add(workspacePackage.name);
|
|
619
|
+
state.registryProblems.push({
|
|
620
|
+
importerName: workspacePackage.name,
|
|
621
|
+
message: [`${workspacePackage.name}@${version} has workspace changes`, `after the published npm registry gitHead ${versionMetadata.gitHead}`].join(" "),
|
|
622
|
+
packageName: workspacePackage.name
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async function visitWorkspacePackageDependencies(options) {
|
|
627
|
+
const { config, importerName, isRoot, manifest, state, workspacePackagesByName } = options;
|
|
628
|
+
for (const entry of collectPublishDependencyEntries(manifest)) {
|
|
629
|
+
if (isLinkDependencySpecifier(entry.specifier)) {
|
|
630
|
+
state.sourceLinkDependencies.push({
|
|
631
|
+
dependencyName: entry.dependencyName,
|
|
632
|
+
importerName,
|
|
633
|
+
message: "publishable dependency sections must not use link:",
|
|
634
|
+
sectionName: entry.sectionName,
|
|
635
|
+
specifier: entry.specifier
|
|
636
|
+
});
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (!isWorkspaceDependencySpecifier(entry.specifier)) continue;
|
|
640
|
+
const targetPackage = workspacePackagesByName.get(entry.dependencyName);
|
|
641
|
+
if (!targetPackage) {
|
|
642
|
+
state.missingWorkspaceDependencies.push({
|
|
643
|
+
dependencyName: entry.dependencyName,
|
|
644
|
+
importerName,
|
|
645
|
+
message: "workspace: publish dependency does not match a named workspace package",
|
|
646
|
+
sectionName: entry.sectionName,
|
|
647
|
+
specifier: entry.specifier
|
|
648
|
+
});
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
addEdge(state.edges, importerName, targetPackage.name);
|
|
652
|
+
if (isRoot) state.directWorkspaceDependencies.push({
|
|
653
|
+
dependencyName: entry.dependencyName,
|
|
654
|
+
sectionName: entry.sectionName,
|
|
655
|
+
targetPackage
|
|
656
|
+
});
|
|
657
|
+
if (targetPackage.manifest.private === true) {
|
|
658
|
+
state.privateWorkspaceDependencies.push({
|
|
659
|
+
dependencyName: entry.dependencyName,
|
|
660
|
+
importerName,
|
|
661
|
+
message: "publishable packages cannot depend on a private workspace package",
|
|
662
|
+
packageName: targetPackage.name,
|
|
663
|
+
sectionName: entry.sectionName,
|
|
664
|
+
specifier: entry.specifier
|
|
665
|
+
});
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (!state.visitedPackages.has(targetPackage.name)) {
|
|
669
|
+
state.visitedPackages.add(targetPackage.name);
|
|
670
|
+
await verifyWorkspacePackagePublished({
|
|
671
|
+
config,
|
|
672
|
+
state,
|
|
673
|
+
workspacePackage: targetPackage
|
|
674
|
+
});
|
|
675
|
+
await visitWorkspacePackageDependencies({
|
|
676
|
+
config,
|
|
677
|
+
importerName: targetPackage.name,
|
|
678
|
+
isRoot: false,
|
|
679
|
+
manifest: targetPackage.manifest,
|
|
680
|
+
state,
|
|
681
|
+
workspacePackagesByName
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function readPackedPackageJson(tarball) {
|
|
687
|
+
const unpacked = await unpack(tarball);
|
|
688
|
+
const packageJsonFile = unpacked.files.find((file) => file.name === `${unpacked.rootDir}/package.json`);
|
|
689
|
+
if (!packageJsonFile) throw new Error("packed tarball does not contain package.json");
|
|
690
|
+
return JSON.parse(Buffer.from(packageJsonFile.data).toString("utf8"));
|
|
691
|
+
}
|
|
692
|
+
function validatePackedManifest(options) {
|
|
693
|
+
const { manifest, rootPackageName, state } = options;
|
|
694
|
+
for (const entry of collectPublishDependencyEntries(manifest)) if (isWorkspaceDependencySpecifier(entry.specifier) || isLinkDependencySpecifier(entry.specifier)) state.packedManifestProblems.push({
|
|
695
|
+
dependencyName: entry.dependencyName,
|
|
696
|
+
importerName: rootPackageName,
|
|
697
|
+
message: "packed package manifest must not expose workspace: or link: dependency specifiers",
|
|
698
|
+
sectionName: entry.sectionName,
|
|
699
|
+
specifier: entry.specifier
|
|
700
|
+
});
|
|
701
|
+
for (const dependency of state.directWorkspaceDependencies) {
|
|
702
|
+
const packedSpecifier = getPackedDependencySpecifier(manifest, dependency.dependencyName);
|
|
703
|
+
if (!packedSpecifier) {
|
|
704
|
+
state.packedManifestProblems.push({
|
|
705
|
+
dependencyName: dependency.dependencyName,
|
|
706
|
+
importerName: rootPackageName,
|
|
707
|
+
message: "packed package manifest must keep every source workspace publish dependency",
|
|
708
|
+
sectionName: dependency.sectionName
|
|
709
|
+
});
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (isWorkspaceDependencySpecifier(packedSpecifier) || isLinkDependencySpecifier(packedSpecifier)) continue;
|
|
713
|
+
const targetVersion = dependency.targetPackage.manifest.version;
|
|
714
|
+
if (!targetVersion || !semver.satisfies(targetVersion, packedSpecifier, { includePrerelease: true })) state.packedManifestProblems.push({
|
|
715
|
+
dependencyName: dependency.dependencyName,
|
|
716
|
+
importerName: rootPackageName,
|
|
717
|
+
message: `packed dependency range must include ${dependency.targetPackage.name}@${targetVersion ?? "(missing version)"}`,
|
|
718
|
+
sectionName: dependency.sectionName,
|
|
719
|
+
specifier: packedSpecifier
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function createPublishOrder(rootPackageName, state) {
|
|
724
|
+
const publishOrder = [];
|
|
725
|
+
const seen = /* @__PURE__ */ new Set();
|
|
726
|
+
function visit(packageName) {
|
|
727
|
+
if (seen.has(packageName)) return;
|
|
728
|
+
seen.add(packageName);
|
|
729
|
+
for (const dependencyName of state.edges.get(packageName) ?? []) visit(dependencyName);
|
|
730
|
+
if (packageName === rootPackageName || state.unpublishedPackageNames.has(packageName)) publishOrder.push(packageName);
|
|
731
|
+
}
|
|
732
|
+
visit(rootPackageName);
|
|
733
|
+
return publishOrder;
|
|
734
|
+
}
|
|
735
|
+
function createReleaseConsistencyError(options) {
|
|
736
|
+
const { config, label, outDir, rootPackageName, state } = options;
|
|
737
|
+
if (state.sourceLinkDependencies.length + state.privateWorkspaceDependencies.length + state.missingWorkspaceDependencies.length + state.registryProblems.length + state.packedManifestProblems.length === 0) return null;
|
|
738
|
+
const publishOrder = createPublishOrder(rootPackageName, state);
|
|
739
|
+
const lines = [
|
|
740
|
+
`package release dependency consistency failed for ${label}:`,
|
|
741
|
+
` output: ${toRelativePath(config.rootDir, outDir)}`,
|
|
742
|
+
...formatProblemLines("Source manifest contains local link: publish dependencies:", state.sourceLinkDependencies),
|
|
743
|
+
...formatProblemLines("Source manifest depends on private workspace packages:", state.privateWorkspaceDependencies),
|
|
744
|
+
...formatProblemLines("Source manifest has invalid workspace: publish dependencies:", state.missingWorkspaceDependencies),
|
|
745
|
+
...formatProblemLines("Workspace packages must be published before this package:", state.registryProblems),
|
|
746
|
+
...formatProblemLines("Packed package manifest is inconsistent with workspace publish dependencies:", state.packedManifestProblems)
|
|
747
|
+
];
|
|
748
|
+
if (publishOrder.length > 1) lines.push("", `Suggested publish order: ${publishOrder.join(" -> ")}`);
|
|
749
|
+
return new PackageReleaseConsistencyError(lines.join("\n"));
|
|
750
|
+
}
|
|
751
|
+
async function assertPackageReleaseConsistency(options) {
|
|
752
|
+
const workspacePackages = await collectWorkspacePackages(options.config);
|
|
753
|
+
const sourcePackage = workspacePackages.find((workspacePackage) => workspacePackage.name === options.outputManifest.name);
|
|
754
|
+
const state = createReleaseConsistencyState();
|
|
755
|
+
if (sourcePackage) {
|
|
756
|
+
state.visitedPackages.add(sourcePackage.name);
|
|
757
|
+
await visitWorkspacePackageDependencies({
|
|
758
|
+
config: options.config,
|
|
759
|
+
importerName: sourcePackage.name,
|
|
760
|
+
isRoot: true,
|
|
761
|
+
manifest: sourcePackage.manifest,
|
|
762
|
+
state,
|
|
763
|
+
workspacePackagesByName: new Map(workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]))
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
validatePackedManifest({
|
|
767
|
+
manifest: await readPackedPackageJson(options.packedTarball),
|
|
768
|
+
rootPackageName: options.outputManifest.name,
|
|
769
|
+
state
|
|
770
|
+
});
|
|
771
|
+
const error = createReleaseConsistencyError({
|
|
772
|
+
config: options.config,
|
|
773
|
+
label: options.label,
|
|
774
|
+
outDir: options.outDir,
|
|
775
|
+
rootPackageName: options.outputManifest.name,
|
|
776
|
+
state
|
|
777
|
+
});
|
|
778
|
+
if (error) throw error;
|
|
779
|
+
}
|
|
780
|
+
|
|
405
781
|
//#endregion
|
|
406
782
|
//#region src/commands/package.ts
|
|
407
783
|
const DEFAULT_PACKAGE_CHECKS = [
|
|
@@ -416,6 +792,7 @@ const ATTW_PROFILE_IGNORED_RESOLUTIONS = {
|
|
|
416
792
|
"esm-only": ["node16-cjs"]
|
|
417
793
|
};
|
|
418
794
|
const nodeBuiltinSpecifiers = new Set(builtinModules.flatMap((specifier) => specifier.startsWith("node:") ? [specifier, specifier.slice(5)] : [specifier, `node:${specifier}`]));
|
|
795
|
+
const REQUIRED_PUBLIC_PACKAGE_FILES = ["README.md", "LICENSE.md"];
|
|
419
796
|
function isRecord(value) {
|
|
420
797
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
421
798
|
}
|
|
@@ -625,7 +1002,7 @@ function logPackageCheckPlan(options) {
|
|
|
625
1002
|
].join("\n"));
|
|
626
1003
|
}
|
|
627
1004
|
async function packOutputTarball(outDir) {
|
|
628
|
-
const destination = await mkdtemp(path.join(tmpdir(), "
|
|
1005
|
+
const destination = await mkdtemp(path.join(tmpdir(), "__LIMINA_PACKAGE__"));
|
|
629
1006
|
return {
|
|
630
1007
|
cleanup: async () => {
|
|
631
1008
|
await rm(destination, {
|
|
@@ -640,6 +1017,21 @@ async function packOutputTarball(outDir) {
|
|
|
640
1017
|
}))
|
|
641
1018
|
};
|
|
642
1019
|
}
|
|
1020
|
+
async function readDistPackageJson(options) {
|
|
1021
|
+
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.`);
|
|
1022
|
+
return JSON.parse(await readFile(options.packageJsonPath, "utf8"));
|
|
1023
|
+
}
|
|
1024
|
+
async function assertPublicPackageMetadata(options) {
|
|
1025
|
+
const manifest = await readDistPackageJson({
|
|
1026
|
+
config: options.config,
|
|
1027
|
+
label: options.label,
|
|
1028
|
+
packageJsonPath: options.packageJsonPath
|
|
1029
|
+
});
|
|
1030
|
+
if (manifest.private === true) return manifest;
|
|
1031
|
+
const missingFiles = REQUIRED_PUBLIC_PACKAGE_FILES.filter((fileName) => !existsSync(path.join(options.outDir, fileName)));
|
|
1032
|
+
if (missingFiles.length === 0) return manifest;
|
|
1033
|
+
throw new Error(`publishable package output for ${options.label} at ${toRelativePath(options.config.rootDir, options.outDir)} is missing required file(s): ${missingFiles.join(", ")}. Add them to the built output or set "private": true in the output package.json.`);
|
|
1034
|
+
}
|
|
643
1035
|
async function runPublintCheck(options) {
|
|
644
1036
|
const task = options.flow?.start(`publint: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
645
1037
|
PackageLogger.info(`publint started: ${options.label}`);
|
|
@@ -720,8 +1112,13 @@ async function runPackageCheckTarget(options) {
|
|
|
720
1112
|
const task = options.flow?.start(`package target: ${label}`, { depth: options.flowDepth ?? 0 });
|
|
721
1113
|
let packedDist;
|
|
722
1114
|
try {
|
|
723
|
-
|
|
724
|
-
|
|
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")) {
|
|
725
1122
|
const packTask = options.flow?.start(`package tarball: ${label}`, { depth: (options.flowDepth ?? 0) + 1 });
|
|
726
1123
|
PackageLogger.info(`package tarball packing started: ${label}`);
|
|
727
1124
|
const packElapsed = createElapsedTimer();
|
|
@@ -735,6 +1132,20 @@ async function runPackageCheckTarget(options) {
|
|
|
735
1132
|
if (!options.flow?.interactive) PackageLogger.success(`package tarball packed: ${label}`, packElapsed());
|
|
736
1133
|
packTask?.pass();
|
|
737
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
|
+
}
|
|
738
1149
|
let passed = true;
|
|
739
1150
|
if (options.checks.includes("publint")) passed = await runPublintCheck({
|
|
740
1151
|
flow: options.flow,
|
|
@@ -774,8 +1185,7 @@ async function runPackageCheckTarget(options) {
|
|
|
774
1185
|
}
|
|
775
1186
|
}
|
|
776
1187
|
async function auditPublishedPackageBoundaries(target) {
|
|
777
|
-
const
|
|
778
|
-
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
1188
|
+
const manifest = await readDistPackageJson({ packageJsonPath: path.join(target.outDir, "package.json") });
|
|
779
1189
|
const allowedExternalPackages = new Set([
|
|
780
1190
|
...Object.keys(manifest.dependencies ?? {}),
|
|
781
1191
|
...Object.keys(manifest.peerDependencies ?? {}),
|
|
@@ -860,7 +1270,7 @@ async function runPackageCheck(options) {
|
|
|
860
1270
|
}
|
|
861
1271
|
|
|
862
1272
|
//#endregion
|
|
863
|
-
//#region src/
|
|
1273
|
+
//#region src/package-exports.ts
|
|
864
1274
|
const defaultSourceExtensions = [
|
|
865
1275
|
".ts",
|
|
866
1276
|
".tsx",
|
|
@@ -887,110 +1297,11 @@ const defaultArtifactDirectories = [
|
|
|
887
1297
|
"cjs",
|
|
888
1298
|
"out"
|
|
889
1299
|
];
|
|
890
|
-
function
|
|
891
|
-
return config.paths?.
|
|
892
|
-
}
|
|
893
|
-
function generatedFileMarker(config) {
|
|
894
|
-
return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
|
|
895
|
-
}
|
|
896
|
-
function parseProject(config, configPath) {
|
|
897
|
-
const diagnostics = [];
|
|
898
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
899
|
-
...ts.sys,
|
|
900
|
-
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
901
|
-
diagnostics.push(diagnostic);
|
|
902
|
-
}
|
|
903
|
-
});
|
|
904
|
-
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
905
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
906
|
-
getCurrentDirectory: () => config.rootDir,
|
|
907
|
-
getNewLine: () => "\n"
|
|
908
|
-
}));
|
|
909
|
-
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
910
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
911
|
-
getCurrentDirectory: () => config.rootDir,
|
|
912
|
-
getNewLine: () => "\n"
|
|
913
|
-
}));
|
|
914
|
-
return {
|
|
915
|
-
configPath: normalizeAbsolutePath(configPath),
|
|
916
|
-
fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
|
|
917
|
-
options: parsed.options,
|
|
918
|
-
references: new Set(getRawReferencePaths(config, configPath))
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
function getSourceFileKind(filePath) {
|
|
922
|
-
if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
923
|
-
if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
924
|
-
return ts.ScriptKind.TS;
|
|
925
|
-
}
|
|
926
|
-
function stringLiteralValue(node) {
|
|
927
|
-
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
928
|
-
}
|
|
929
|
-
function collectImportsFromFile(filePath) {
|
|
930
|
-
const sourceText = readFileSync(filePath, "utf8");
|
|
931
|
-
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
|
|
932
|
-
const imports = [];
|
|
933
|
-
const addImport = (specifier, node) => {
|
|
934
|
-
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
935
|
-
imports.push({
|
|
936
|
-
filePath,
|
|
937
|
-
line: location.line + 1,
|
|
938
|
-
specifier
|
|
939
|
-
});
|
|
940
|
-
};
|
|
941
|
-
const visit = (node) => {
|
|
942
|
-
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
943
|
-
const specifier = stringLiteralValue(node.moduleSpecifier);
|
|
944
|
-
if (specifier) addImport(specifier, node);
|
|
945
|
-
} else if (ts.isImportTypeNode(node)) {
|
|
946
|
-
const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
|
|
947
|
-
if (specifier) addImport(specifier, node);
|
|
948
|
-
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
949
|
-
const specifier = stringLiteralValue(node.arguments[0]);
|
|
950
|
-
if (specifier) addImport(specifier, node);
|
|
951
|
-
}
|
|
952
|
-
ts.forEachChild(node, visit);
|
|
953
|
-
};
|
|
954
|
-
visit(sourceFile);
|
|
955
|
-
return imports;
|
|
956
|
-
}
|
|
957
|
-
function resolveInternalImport(specifier, containingFile, options) {
|
|
958
|
-
const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
|
|
959
|
-
return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
|
|
960
|
-
}
|
|
961
|
-
function resolveImportWithoutMatchingPaths(specifier, containingFile, options) {
|
|
962
|
-
if (!options.paths) return resolveInternalImport(specifier, containingFile, options);
|
|
963
|
-
const paths = Object.fromEntries(Object.entries(options.paths).filter(([alias]) => !aliasMatchesSpecifier(alias, specifier)));
|
|
964
|
-
return resolveInternalImport(specifier, containingFile, {
|
|
965
|
-
...options,
|
|
966
|
-
paths: Object.keys(paths).length > 0 ? paths : void 0
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
function createFileOwnerLookup(projects) {
|
|
970
|
-
const ownerLookup = /* @__PURE__ */ new Map();
|
|
971
|
-
for (const project of projects) for (const fileName of project.fileNames) {
|
|
972
|
-
const owners = ownerLookup.get(fileName) ?? [];
|
|
973
|
-
owners.push(project.configPath);
|
|
974
|
-
ownerLookup.set(fileName, owners);
|
|
975
|
-
}
|
|
976
|
-
return ownerLookup;
|
|
977
|
-
}
|
|
978
|
-
function projectExtendsGeneratedConfig(config, configPath) {
|
|
979
|
-
const extendsValue = readJsonConfig(config, configPath).extends;
|
|
980
|
-
return (typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) ? extendsValue : []).some((entry) => typeof entry === "string" && path.basename(entry) === generatedFileName(config));
|
|
981
|
-
}
|
|
982
|
-
function findImporterForFile(filePath, importers) {
|
|
983
|
-
return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
|
|
984
|
-
}
|
|
985
|
-
function shouldResolveThroughGraph(importer, targetPackage) {
|
|
986
|
-
if (!importer || !targetPackage) return false;
|
|
987
|
-
return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
|
|
1300
|
+
function configuredArtifactDirectories(config) {
|
|
1301
|
+
return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
|
|
988
1302
|
}
|
|
989
|
-
function
|
|
990
|
-
|
|
991
|
-
return projectPaths.find((projectPath) => {
|
|
992
|
-
return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
|
|
993
|
-
}) ?? null;
|
|
1303
|
+
function configuredSourceExtensions(config) {
|
|
1304
|
+
return config.paths?.sourceExtensions ?? defaultSourceExtensions;
|
|
994
1305
|
}
|
|
995
1306
|
function collectTargetCandidates(config, value) {
|
|
996
1307
|
if (typeof value === "string") return [value];
|
|
@@ -1033,12 +1344,6 @@ function removeKnownExtension(filePath) {
|
|
|
1033
1344
|
]) if (filePath.endsWith(extension)) return filePath.slice(0, -extension.length);
|
|
1034
1345
|
return filePath;
|
|
1035
1346
|
}
|
|
1036
|
-
function configuredArtifactDirectories(config) {
|
|
1037
|
-
return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
|
|
1038
|
-
}
|
|
1039
|
-
function configuredSourceExtensions(config) {
|
|
1040
|
-
return config.paths?.sourceExtensions ?? defaultSourceExtensions;
|
|
1041
|
-
}
|
|
1042
1347
|
function hasConfiguredSourceExtension(config, target) {
|
|
1043
1348
|
return configuredSourceExtensions(config).some((extension) => target.endsWith(extension));
|
|
1044
1349
|
}
|
|
@@ -1151,6 +1456,114 @@ function aliasMatchesSpecifier(alias, specifier) {
|
|
|
1151
1456
|
const suffix = alias.slice(wildcardIndex + 1);
|
|
1152
1457
|
return specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
1153
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
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function getSourceFileKind(filePath) {
|
|
1494
|
+
if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
1495
|
+
if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
1496
|
+
return ts.ScriptKind.TS;
|
|
1497
|
+
}
|
|
1498
|
+
function stringLiteralValue(node) {
|
|
1499
|
+
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
1500
|
+
}
|
|
1501
|
+
function collectImportsFromFile(filePath) {
|
|
1502
|
+
const sourceText = readFileSync(filePath, "utf8");
|
|
1503
|
+
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
|
|
1504
|
+
const imports = [];
|
|
1505
|
+
const addImport = (specifier, node) => {
|
|
1506
|
+
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
1507
|
+
imports.push({
|
|
1508
|
+
filePath,
|
|
1509
|
+
line: location.line + 1,
|
|
1510
|
+
specifier
|
|
1511
|
+
});
|
|
1512
|
+
};
|
|
1513
|
+
const visit = (node) => {
|
|
1514
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
1515
|
+
const specifier = stringLiteralValue(node.moduleSpecifier);
|
|
1516
|
+
if (specifier) addImport(specifier, node);
|
|
1517
|
+
} else if (ts.isImportTypeNode(node)) {
|
|
1518
|
+
const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
|
|
1519
|
+
if (specifier) addImport(specifier, node);
|
|
1520
|
+
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
1521
|
+
const specifier = stringLiteralValue(node.arguments[0]);
|
|
1522
|
+
if (specifier) addImport(specifier, node);
|
|
1523
|
+
}
|
|
1524
|
+
ts.forEachChild(node, visit);
|
|
1525
|
+
};
|
|
1526
|
+
visit(sourceFile);
|
|
1527
|
+
return imports;
|
|
1528
|
+
}
|
|
1529
|
+
function resolveInternalImport(specifier, containingFile, options) {
|
|
1530
|
+
const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
|
|
1531
|
+
return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
|
|
1532
|
+
}
|
|
1533
|
+
function resolveImportWithoutMatchingPaths(specifier, containingFile, options) {
|
|
1534
|
+
if (!options.paths) return resolveInternalImport(specifier, containingFile, options);
|
|
1535
|
+
const paths = Object.fromEntries(Object.entries(options.paths).filter(([alias]) => !aliasMatchesSpecifier(alias, specifier)));
|
|
1536
|
+
return resolveInternalImport(specifier, containingFile, {
|
|
1537
|
+
...options,
|
|
1538
|
+
paths: Object.keys(paths).length > 0 ? paths : void 0
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
function createFileOwnerLookup(projects) {
|
|
1542
|
+
const ownerLookup = /* @__PURE__ */ new Map();
|
|
1543
|
+
for (const project of projects) for (const fileName of project.fileNames) {
|
|
1544
|
+
const owners = ownerLookup.get(fileName) ?? [];
|
|
1545
|
+
owners.push(project.configPath);
|
|
1546
|
+
ownerLookup.set(fileName, owners);
|
|
1547
|
+
}
|
|
1548
|
+
return ownerLookup;
|
|
1549
|
+
}
|
|
1550
|
+
function projectExtendsGeneratedConfig(config, configPath) {
|
|
1551
|
+
const extendsValue = readJsonConfig(config, configPath).extends;
|
|
1552
|
+
return (typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) ? extendsValue : []).some((entry) => typeof entry === "string" && path.basename(entry) === generatedFileName(config));
|
|
1553
|
+
}
|
|
1554
|
+
function findImporterForFile(filePath, importers) {
|
|
1555
|
+
return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
|
|
1556
|
+
}
|
|
1557
|
+
function shouldResolveThroughGraph(importer, targetPackage) {
|
|
1558
|
+
if (!importer || !targetPackage) return false;
|
|
1559
|
+
return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
|
|
1560
|
+
}
|
|
1561
|
+
function inferPackageProject(resolvedFilePath, workspacePackage, projectPaths) {
|
|
1562
|
+
if (!isPathInsideDirectory(resolvedFilePath, workspacePackage.directory)) return null;
|
|
1563
|
+
return projectPaths.find((projectPath) => {
|
|
1564
|
+
return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
|
|
1565
|
+
}) ?? null;
|
|
1566
|
+
}
|
|
1154
1567
|
function addPathEntry(paths, alias, target) {
|
|
1155
1568
|
const targets = paths.get(alias) ?? [];
|
|
1156
1569
|
if (!targets.includes(target)) targets.push(target);
|
|
@@ -1402,6 +1815,14 @@ const ignoredSemanticCompilerOptions = new Set([
|
|
|
1402
1815
|
"sourceRoot",
|
|
1403
1816
|
"tsBuildInfoFile"
|
|
1404
1817
|
]);
|
|
1818
|
+
const typeScriptCheckerExtensions = getCheckerAdapter("tsc")?.defaultExtensions ?? [];
|
|
1819
|
+
function getFirstClassCoverageExtensions(extensions) {
|
|
1820
|
+
return normalizeExtensions([...typeScriptCheckerExtensions, ...extensions]);
|
|
1821
|
+
}
|
|
1822
|
+
function getCheckerCoverageExtensions(checker) {
|
|
1823
|
+
if (!getCheckerAdapter(checker.preset)?.sourceGraph) return checker.extensions;
|
|
1824
|
+
return getFirstClassCoverageExtensions(checker.extensions);
|
|
1825
|
+
}
|
|
1405
1826
|
async function collectTsconfigPaths(config, pattern) {
|
|
1406
1827
|
return (await glob(pattern, {
|
|
1407
1828
|
cwd: config.rootDir,
|
|
@@ -1563,24 +1984,19 @@ function addCoverage(coverageByFile, filePath, source) {
|
|
|
1563
1984
|
}
|
|
1564
1985
|
function collectCoverage(options) {
|
|
1565
1986
|
const coverageByFile = /* @__PURE__ */ new Map();
|
|
1566
|
-
const
|
|
1567
|
-
const typeScriptChecker = getActiveCheckers(options.config).find((checker) => checker.preset === "tsc");
|
|
1568
|
-
for (const graphProjectPath of options.graphProjectPaths) for (const filePath of parseProjectFileNames(options.config, graphProjectPath, proofFilePattern)) {
|
|
1987
|
+
for (const route of options.graphRoutes) for (const graphProjectPath of route.projectPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, graphProjectPath, getFirstClassCoverageExtensions(route.extensions))) {
|
|
1569
1988
|
if (!options.sourceFiles.has(filePath)) continue;
|
|
1570
1989
|
addCoverage(coverageByFile, filePath, {
|
|
1571
1990
|
label: toRelativePath(options.config.rootDir, graphProjectPath),
|
|
1572
1991
|
type: "graph"
|
|
1573
1992
|
});
|
|
1574
1993
|
}
|
|
1575
|
-
for (const checkerTarget of options.checkerTargets) {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
type: "checker"
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
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
|
+
});
|
|
1584
2000
|
}
|
|
1585
2001
|
if (options.includeAllowlist !== false) for (const entry of options.allowlistEntries) {
|
|
1586
2002
|
if (!options.sourceFiles.has(entry.filePath)) continue;
|
|
@@ -1591,15 +2007,19 @@ function collectCoverage(options) {
|
|
|
1591
2007
|
}
|
|
1592
2008
|
return coverageByFile;
|
|
1593
2009
|
}
|
|
1594
|
-
function
|
|
2010
|
+
function collectProjectExtensionsByPath(routes) {
|
|
2011
|
+
const projectExtensionsByPath = /* @__PURE__ */ new Map();
|
|
2012
|
+
for (const route of routes) for (const projectPath of route.projectPaths) {
|
|
2013
|
+
const extensions = new Set([...projectExtensionsByPath.get(projectPath) ?? [], ...route.extensions]);
|
|
2014
|
+
projectExtensionsByPath.set(projectPath, [...extensions].sort());
|
|
2015
|
+
}
|
|
2016
|
+
return projectExtensionsByPath;
|
|
2017
|
+
}
|
|
2018
|
+
function parseConfig(config, configPath, extensions = []) {
|
|
1595
2019
|
const diagnostics = [];
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
diagnostics.push(diagnostic);
|
|
1600
|
-
}
|
|
1601
|
-
});
|
|
1602
|
-
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
|
|
2020
|
+
const configObject = readJsonConfig(config, configPath);
|
|
2021
|
+
const parsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
|
|
2022
|
+
if (diagnostics.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
|
|
1603
2023
|
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
|
|
1604
2024
|
return {
|
|
1605
2025
|
fileNames: parsed.fileNames.map(normalizeAbsolutePath).sort(),
|
|
@@ -1662,8 +2082,9 @@ function addDtsConfigProblems(options) {
|
|
|
1662
2082
|
].join("\n"));
|
|
1663
2083
|
continue;
|
|
1664
2084
|
}
|
|
1665
|
-
const
|
|
1666
|
-
const
|
|
2085
|
+
const extensions = options.projectExtensionsByPath.get(configPath) ?? [];
|
|
2086
|
+
const dtsConfig = parseConfig(options.config, configPath, extensions);
|
|
2087
|
+
const localConfig = parseConfig(options.config, localConfigPath, extensions);
|
|
1667
2088
|
if (dtsConfig.options.composite !== true) options.problems.push([
|
|
1668
2089
|
"DTS config is not valid for tsc -b:",
|
|
1669
2090
|
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
@@ -1804,12 +2225,11 @@ function addDefaultTsconfigEnvironmentProblems(options) {
|
|
|
1804
2225
|
].join("\n"));
|
|
1805
2226
|
}
|
|
1806
2227
|
}
|
|
1807
|
-
function collectConfigFileOwners(config,
|
|
2228
|
+
function collectConfigFileOwners(config, graphRoutes, sourceFiles) {
|
|
1808
2229
|
const ownersByFile = /* @__PURE__ */ new Map();
|
|
1809
|
-
const
|
|
1810
|
-
for (const configPath of configPaths) {
|
|
2230
|
+
for (const route of graphRoutes) for (const configPath of route.projectPaths) {
|
|
1811
2231
|
if (!existsSync(configPath)) continue;
|
|
1812
|
-
for (const filePath of
|
|
2232
|
+
for (const filePath of parseProjectFileNamesForExtensions(config, configPath, route.extensions)) {
|
|
1813
2233
|
if (!sourceFiles.has(filePath)) continue;
|
|
1814
2234
|
const owners = ownersByFile.get(filePath) ?? [];
|
|
1815
2235
|
owners.push(configPath);
|
|
@@ -1831,19 +2251,6 @@ function addDuplicateGraphCoverageProblems(options) {
|
|
|
1831
2251
|
].join("\n"));
|
|
1832
2252
|
}
|
|
1833
2253
|
}
|
|
1834
|
-
function addDuplicateGraphOwnerProblems(options) {
|
|
1835
|
-
for (const [configPath, ownerCheckerNames] of options.graphOwnersByConfigPath.entries()) {
|
|
1836
|
-
const uniqueOwnerCheckerNames = [...new Set(ownerCheckerNames)].sort();
|
|
1837
|
-
if (uniqueOwnerCheckerNames.length <= 1) continue;
|
|
1838
|
-
options.problems.push([
|
|
1839
|
-
"Duplicate checker graph declaration owner:",
|
|
1840
|
-
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1841
|
-
" owned by:",
|
|
1842
|
-
...uniqueOwnerCheckerNames.map((checkerName) => ` - ${checkerName}`),
|
|
1843
|
-
" reason: each tsconfig*.dts.json must be reached by exactly one graph-capable checker entry."
|
|
1844
|
-
].join("\n"));
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
2254
|
function addAllowlistProblems(options) {
|
|
1848
2255
|
for (const entry of options.allowlistEntries) {
|
|
1849
2256
|
if (!existsSync(entry.filePath)) {
|
|
@@ -1871,34 +2278,25 @@ function addUncoveredSourceProblems(options) {
|
|
|
1871
2278
|
" reason: every file in config.source must be covered by a checker entry or an explicit allowlist entry."
|
|
1872
2279
|
].filter(Boolean).join("\n"));
|
|
1873
2280
|
}
|
|
1874
|
-
function addGraphOwner(ownersByConfigPath, configPath, checkerName) {
|
|
1875
|
-
const owners = ownersByConfigPath.get(configPath) ?? [];
|
|
1876
|
-
owners.push(checkerName);
|
|
1877
|
-
ownersByConfigPath.set(configPath, owners);
|
|
1878
|
-
}
|
|
1879
2281
|
async function runProofCheckInternal(config, options = {}) {
|
|
1880
2282
|
const problems = [];
|
|
1881
2283
|
const graphRouteCollection = collectGraphProjectRoutes(config);
|
|
1882
2284
|
const entryRouteCollection = collectCheckerEntryProjectRoutes(config);
|
|
1883
|
-
const graphProjectPaths = [...new Set(graphRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
|
|
1884
2285
|
const entryProjectPaths = [...new Set(entryRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
|
|
1885
2286
|
const entryProjectPathSet = new Set(entryProjectPaths);
|
|
2287
|
+
const entryProjectExtensionsByPath = collectProjectExtensionsByPath(entryRouteCollection.routes);
|
|
1886
2288
|
const dtsConfigPaths = await collectDtsConfigPaths(config);
|
|
1887
2289
|
const buildGraphConfigPaths = await collectBuildGraphConfigPaths(config);
|
|
1888
2290
|
const defaultTsconfigPaths = await collectDefaultTsconfigPaths(config);
|
|
1889
2291
|
const ordinaryTypecheckConfigPaths = await collectOrdinaryTypecheckConfigPaths(config);
|
|
1890
|
-
const graphOwnersByConfigPath = /* @__PURE__ */ new Map();
|
|
1891
2292
|
problems.push(...graphRouteCollection.problems);
|
|
1892
2293
|
problems.push(...entryRouteCollection.problems);
|
|
1893
|
-
for (const route of graphRouteCollection.routes) for (const projectPath of route.projectPaths) {
|
|
1894
|
-
if (!isDtsConfigPath(projectPath)) continue;
|
|
1895
|
-
addGraphOwner(graphOwnersByConfigPath, projectPath, route.checkerName);
|
|
1896
|
-
}
|
|
1897
2294
|
addDtsConfigProblems({
|
|
1898
2295
|
config,
|
|
1899
2296
|
dtsConfigPaths,
|
|
1900
2297
|
graphProjectPaths: entryProjectPathSet,
|
|
1901
|
-
problems
|
|
2298
|
+
problems,
|
|
2299
|
+
projectExtensionsByPath: entryProjectExtensionsByPath
|
|
1902
2300
|
});
|
|
1903
2301
|
addBuildGraphConfigProblems({
|
|
1904
2302
|
buildGraphConfigPaths,
|
|
@@ -1915,11 +2313,6 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1915
2313
|
ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
|
|
1916
2314
|
problems
|
|
1917
2315
|
});
|
|
1918
|
-
addDuplicateGraphOwnerProblems({
|
|
1919
|
-
config,
|
|
1920
|
-
graphOwnersByConfigPath,
|
|
1921
|
-
problems
|
|
1922
|
-
});
|
|
1923
2316
|
if (problems.length > 0) {
|
|
1924
2317
|
ProofLogger.error(problems.join("\n\n"));
|
|
1925
2318
|
return false;
|
|
@@ -1939,7 +2332,7 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1939
2332
|
allowlistEntries,
|
|
1940
2333
|
checkerTargets,
|
|
1941
2334
|
config,
|
|
1942
|
-
|
|
2335
|
+
graphRoutes: graphRouteCollection.routes,
|
|
1943
2336
|
includeAllowlist: false,
|
|
1944
2337
|
sourceFiles
|
|
1945
2338
|
});
|
|
@@ -1947,12 +2340,12 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1947
2340
|
allowlistEntries,
|
|
1948
2341
|
checkerTargets,
|
|
1949
2342
|
config,
|
|
1950
|
-
|
|
2343
|
+
graphRoutes: graphRouteCollection.routes,
|
|
1951
2344
|
sourceFiles
|
|
1952
2345
|
});
|
|
1953
2346
|
addDuplicateGraphCoverageProblems({
|
|
1954
2347
|
config,
|
|
1955
|
-
ownersByFile: collectConfigFileOwners(config,
|
|
2348
|
+
ownersByFile: collectConfigFileOwners(config, graphRouteCollection.routes, sourceFiles),
|
|
1956
2349
|
problems
|
|
1957
2350
|
});
|
|
1958
2351
|
addAllowlistProblems({
|
|
@@ -2015,6 +2408,30 @@ const builtInTaskNames = new Set([
|
|
|
2015
2408
|
"proof:check",
|
|
2016
2409
|
"source:check"
|
|
2017
2410
|
]);
|
|
2411
|
+
const defaultCheckPipeline = [
|
|
2412
|
+
"graph:check",
|
|
2413
|
+
"source:check",
|
|
2414
|
+
"proof:check",
|
|
2415
|
+
"checker:build",
|
|
2416
|
+
"checker:typecheck"
|
|
2417
|
+
];
|
|
2418
|
+
function reportCheckerCapabilities(config, flow) {
|
|
2419
|
+
if (!flow) return;
|
|
2420
|
+
const firstClass = [];
|
|
2421
|
+
const sourceOnly = [];
|
|
2422
|
+
for (const checker of getActiveCheckers(config)) {
|
|
2423
|
+
const adapter = getCheckerAdapter(checker.preset);
|
|
2424
|
+
const label = `${checker.name} (${checker.preset})`;
|
|
2425
|
+
if (adapter?.tier === "first-class") firstClass.push(label);
|
|
2426
|
+
else if (adapter?.tier === "source-only") sourceOnly.push(label);
|
|
2427
|
+
}
|
|
2428
|
+
flow.info([
|
|
2429
|
+
"checker capability summary:",
|
|
2430
|
+
` first-class: ${firstClass.length > 0 ? firstClass.join(", ") : "(none)"}`,
|
|
2431
|
+
` source-only: ${sourceOnly.length > 0 ? sourceOnly.join(", ") : "(none)"}`,
|
|
2432
|
+
...sourceOnly.length > 0 ? [" note: source-only checkers get coverage proof and direct typecheck, but Limina does not parse their internal import graph."] : []
|
|
2433
|
+
].join("\n"), { depth: 1 });
|
|
2434
|
+
}
|
|
2018
2435
|
function isBuiltinTaskName(value) {
|
|
2019
2436
|
return builtInTaskNames.has(value);
|
|
2020
2437
|
}
|
|
@@ -2128,7 +2545,7 @@ function runCommandStep(config, step, options = {}) {
|
|
|
2128
2545
|
}
|
|
2129
2546
|
async function runPipeline(config, pipelineName, options = {}) {
|
|
2130
2547
|
const steps = config.pipelines?.[pipelineName];
|
|
2131
|
-
if (!steps) throw new Error(`
|
|
2548
|
+
if (!steps) throw new Error([`Pipeline instruction "${pipelineName}" was not found.`, `Define it in ${path.relative(config.rootDir, config.configPath)} under the "pipelines" field, then run "limina check ${pipelineName}" again.`].join("\n"));
|
|
2132
2549
|
const normalizedSteps = steps.map(normalizePipelineStep);
|
|
2133
2550
|
const pipelineTask = options.flow?.start(`pipeline: ${pipelineName}`, { collapseOnSuccess: false });
|
|
2134
2551
|
for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
|
|
@@ -2140,6 +2557,19 @@ async function runPipeline(config, pipelineName, options = {}) {
|
|
|
2140
2557
|
pipelineTask?.pass();
|
|
2141
2558
|
return true;
|
|
2142
2559
|
}
|
|
2560
|
+
async function runDefaultCheck(config, options = {}) {
|
|
2561
|
+
const normalizedSteps = defaultCheckPipeline.map(normalizePipelineStep);
|
|
2562
|
+
const pipelineTask = options.flow?.start("default check", { collapseOnSuccess: false });
|
|
2563
|
+
reportCheckerCapabilities(config, options.flow);
|
|
2564
|
+
for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
|
|
2565
|
+
const label = getPipelineStepLabel(step);
|
|
2566
|
+
pipelineTask?.fail(`default check blocked at ${label}`);
|
|
2567
|
+
for (const remainingStep of normalizedSteps.slice(stepIndex + 1)) options.flow?.skip(`skipped: ${getPipelineStepLabel(remainingStep)}`, { depth: 1 });
|
|
2568
|
+
return false;
|
|
2569
|
+
}
|
|
2570
|
+
pipelineTask?.pass();
|
|
2571
|
+
return true;
|
|
2572
|
+
}
|
|
2143
2573
|
|
|
2144
2574
|
//#endregion
|
|
2145
2575
|
//#region src/cli.ts
|
|
@@ -2161,12 +2591,6 @@ function parsePackageAttwProfile(profile) {
|
|
|
2161
2591
|
if (profile === "strict" || profile === "node16" || profile === "esm-only") return profile;
|
|
2162
2592
|
throw new Error(`Invalid package check --attw-profile "${profile}". Expected one of: strict, node16, esm-only.`);
|
|
2163
2593
|
}
|
|
2164
|
-
function parseConcurrency(value) {
|
|
2165
|
-
if (value === void 0) return;
|
|
2166
|
-
const parsed = Number(value);
|
|
2167
|
-
if (!Number.isInteger(parsed) || parsed < 1) throw new Error("Invalid --concurrency value. Expected a positive integer.");
|
|
2168
|
-
return parsed;
|
|
2169
|
-
}
|
|
2170
2594
|
function createCliFlow() {
|
|
2171
2595
|
clearCliScreen();
|
|
2172
2596
|
return createLiminaFlowReporter();
|
|
@@ -2176,10 +2600,25 @@ async function main() {
|
|
|
2176
2600
|
cli.option("--config <path>", "Path to limina.config.mjs");
|
|
2177
2601
|
cli.option("--mode <mode>", "Mode passed to limina config functions");
|
|
2178
2602
|
cli.help();
|
|
2179
|
-
cli.command("
|
|
2603
|
+
cli.command("init", "Initialize Limina files for a pnpm workspace").option("--yes", "Accept all init prompts").action(async (flags) => {
|
|
2604
|
+
const flow = createCliFlow();
|
|
2605
|
+
flow.intro("limina init");
|
|
2606
|
+
await runInit({
|
|
2607
|
+
clearScreen: false,
|
|
2608
|
+
cwd: process.cwd(),
|
|
2609
|
+
flow,
|
|
2610
|
+
yes: flags.yes
|
|
2611
|
+
});
|
|
2612
|
+
flow.outro("limina init finished");
|
|
2613
|
+
});
|
|
2614
|
+
cli.command("check [pipeline]", "Run the default check or a configured pipeline").action(async (pipeline, flags) => {
|
|
2180
2615
|
const flow = createCliFlow();
|
|
2181
2616
|
flow.intro("limina check");
|
|
2182
|
-
const
|
|
2617
|
+
const config = await load(flags, "check");
|
|
2618
|
+
const passed = pipeline ? await runPipeline(config, pipeline, {
|
|
2619
|
+
cwd: process.cwd(),
|
|
2620
|
+
flow
|
|
2621
|
+
}) : await runDefaultCheck(config, {
|
|
2183
2622
|
cwd: process.cwd(),
|
|
2184
2623
|
flow
|
|
2185
2624
|
});
|
|
@@ -2231,8 +2670,8 @@ async function main() {
|
|
|
2231
2670
|
if (!passed) process.exitCode = 1;
|
|
2232
2671
|
flow.outro(passed ? "limina source passed" : "limina source failed");
|
|
2233
2672
|
});
|
|
2234
|
-
cli.command("checker <action>", "Run configured checker
|
|
2235
|
-
if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected
|
|
2673
|
+
cli.command("checker <action>", "Run configured checker build or typecheck entries").action(async (action, flags) => {
|
|
2674
|
+
if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected build or typecheck.`);
|
|
2236
2675
|
const flow = createCliFlow();
|
|
2237
2676
|
flow.intro(`limina checker ${action}`);
|
|
2238
2677
|
if (action === "build") {
|
|
@@ -2249,7 +2688,6 @@ async function main() {
|
|
|
2249
2688
|
const result = await runCheckerTypecheck({
|
|
2250
2689
|
clearScreen: false,
|
|
2251
2690
|
config: await load(flags, "check"),
|
|
2252
|
-
concurrency: parseConcurrency(flags.concurrency),
|
|
2253
2691
|
cwd: process.cwd(),
|
|
2254
2692
|
flow
|
|
2255
2693
|
});
|