limina 0.0.1
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/bin/limina.js +25 -0
- package/chunks/dep-DoSHsBSP.js +2026 -0
- package/chunks/dep-jgc7X0zw.js +377 -0
- package/chunks/dep-lkQg1P9Q.js +3 -0
- package/cli.js +2287 -0
- package/config.d.ts +483 -0
- package/config.js +4 -0
- package/index.d.ts +144 -0
- package/index.js +5 -0
- package/package.json +56 -0
|
@@ -0,0 +1,2026 @@
|
|
|
1
|
+
import { c as getCheckerAdapter, h as toRelativePath, l as isPathInsideDirectory, o as collectMissingCheckerPeerDependencies, r as getActiveCheckers, s as formatMissingCheckerPeerDependencies, u as normalizeAbsolutePath } from "./dep-jgc7X0zw.js";
|
|
2
|
+
import { builtinModules } from "node:module";
|
|
3
|
+
import { createElapsedTimer, formatErrorMessage, formatErrorMessage as formatErrorMessage$1 } from "@docs-islands/logger/helper";
|
|
4
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { execFile, spawn } from "node:child_process";
|
|
8
|
+
import { glob } from "tinyglobby";
|
|
9
|
+
import { createLogger } from "@docs-islands/logger";
|
|
10
|
+
import readline from "node:readline";
|
|
11
|
+
import { availableParallelism } from "node:os";
|
|
12
|
+
import * as prompts from "@clack/prompts";
|
|
13
|
+
|
|
14
|
+
//#region src/tsconfig.ts
|
|
15
|
+
const dtsConfigFilePattern = /^tsconfig(?:\..+)?\.dts\.json$/u;
|
|
16
|
+
const buildGraphConfigFilePattern = /^tsconfig(?:\..+)?\.build\.json$/u;
|
|
17
|
+
const deprecatedGraphConfigFilePattern = /^tsconfig(?:\..+)?\.graph\.json$/u;
|
|
18
|
+
const generatedConfigFilePattern = /^tsconfig(?:\..+)?\.paths\.generated\.json$/u;
|
|
19
|
+
const baseConfigFilePattern = /^tsconfig(?:\..+)?\.base\.json$/u;
|
|
20
|
+
const checkConfigFilePattern = /^tsconfig(?:\..+)?\.check\.json$/u;
|
|
21
|
+
const tsconfigFilePattern = /^tsconfig(?:\..+)?\.json$/u;
|
|
22
|
+
function createFormatHost(rootDir) {
|
|
23
|
+
return {
|
|
24
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
25
|
+
getCurrentDirectory: () => rootDir,
|
|
26
|
+
getNewLine: () => "\n"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function escapeRegExp(value) {
|
|
30
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
31
|
+
}
|
|
32
|
+
function createExtensionPattern(extensions) {
|
|
33
|
+
if (extensions.length === 0) return /$./u;
|
|
34
|
+
return new RegExp(`(?:${extensions.sort((left, right) => right.length - left.length).map(escapeRegExp).join("|")})$`, "u");
|
|
35
|
+
}
|
|
36
|
+
function createExtraFileExtensions(extensions) {
|
|
37
|
+
const nativeExtensions = new Set([
|
|
38
|
+
".ts",
|
|
39
|
+
".tsx",
|
|
40
|
+
".cts",
|
|
41
|
+
".mts",
|
|
42
|
+
".d.ts",
|
|
43
|
+
".d.cts",
|
|
44
|
+
".d.mts",
|
|
45
|
+
".js",
|
|
46
|
+
".jsx",
|
|
47
|
+
".cjs",
|
|
48
|
+
".mjs",
|
|
49
|
+
".json"
|
|
50
|
+
]);
|
|
51
|
+
return extensions.filter((extension) => !nativeExtensions.has(extension)).map((extension) => ({
|
|
52
|
+
extension,
|
|
53
|
+
isMixedContent: true,
|
|
54
|
+
scriptKind: ts.ScriptKind.Deferred
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
function readJsonConfigFile(rootDir, configPath) {
|
|
58
|
+
const result = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
59
|
+
if (result.error) throw new Error(ts.formatDiagnostic(result.error, createFormatHost(rootDir)));
|
|
60
|
+
return result.config;
|
|
61
|
+
}
|
|
62
|
+
function readJsonConfig(config, configPath) {
|
|
63
|
+
return readJsonConfigFile(config.rootDir, configPath);
|
|
64
|
+
}
|
|
65
|
+
function resolveProjectConfigPath(baseDirectory, value) {
|
|
66
|
+
const candidate = value ? path.resolve(baseDirectory, value) : path.join(baseDirectory, "tsconfig.json");
|
|
67
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) return normalizeAbsolutePath(path.join(candidate, "tsconfig.json"));
|
|
68
|
+
return normalizeAbsolutePath(candidate);
|
|
69
|
+
}
|
|
70
|
+
function resolveReferencePath(configPath, referencePath) {
|
|
71
|
+
const absoluteReferencePath = path.resolve(path.dirname(configPath), referencePath);
|
|
72
|
+
if (path.extname(absoluteReferencePath) === ".json") return normalizeAbsolutePath(absoluteReferencePath);
|
|
73
|
+
return normalizeAbsolutePath(path.join(absoluteReferencePath, "tsconfig.json"));
|
|
74
|
+
}
|
|
75
|
+
function getRawReferencePaths(config, configPath) {
|
|
76
|
+
return getRawReferencePathsForConfig(config.rootDir, configPath);
|
|
77
|
+
}
|
|
78
|
+
function getRawReferencePathsForConfig(rootDir, configPath) {
|
|
79
|
+
return collectReferencePathInfosForConfig(rootDir, configPath).references.map((reference) => reference.resolvedPath);
|
|
80
|
+
}
|
|
81
|
+
function formatUnknownValue$2(value) {
|
|
82
|
+
if (value === void 0) return "undefined";
|
|
83
|
+
return JSON.stringify(value);
|
|
84
|
+
}
|
|
85
|
+
function collectReferencePathInfosForConfig(rootDir, configPath) {
|
|
86
|
+
return collectReferencePathInfosFromConfigObject(rootDir, configPath, readJsonConfigFile(rootDir, configPath));
|
|
87
|
+
}
|
|
88
|
+
function collectReferencePathInfosFromConfigObject(rootDir, configPath, configObject) {
|
|
89
|
+
const references = configObject.references;
|
|
90
|
+
const problems = [];
|
|
91
|
+
const referenceInfos = [];
|
|
92
|
+
const formatConfigPath = (pathValue) => toRelativePath(rootDir, pathValue);
|
|
93
|
+
if (references === void 0) return {
|
|
94
|
+
problems,
|
|
95
|
+
references: referenceInfos
|
|
96
|
+
};
|
|
97
|
+
if (!Array.isArray(references)) {
|
|
98
|
+
problems.push([
|
|
99
|
+
"Invalid tsconfig references field:",
|
|
100
|
+
` config: ${formatConfigPath(configPath)}`,
|
|
101
|
+
` field: references`,
|
|
102
|
+
` value: ${formatUnknownValue$2(references)}`,
|
|
103
|
+
" reason: references must be an array of objects with a non-empty string path."
|
|
104
|
+
].join("\n"));
|
|
105
|
+
return {
|
|
106
|
+
problems,
|
|
107
|
+
references: referenceInfos
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
references.forEach((reference, index) => {
|
|
111
|
+
const field = `references[${index}]`;
|
|
112
|
+
if (!reference || typeof reference !== "object" || Array.isArray(reference)) {
|
|
113
|
+
problems.push([
|
|
114
|
+
"Invalid tsconfig reference entry:",
|
|
115
|
+
` config: ${formatConfigPath(configPath)}`,
|
|
116
|
+
` field: ${field}`,
|
|
117
|
+
` value: ${formatUnknownValue$2(reference)}`,
|
|
118
|
+
" reason: each reference entry must be an object with a non-empty string path."
|
|
119
|
+
].join("\n"));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const pathValue = reference.path;
|
|
123
|
+
if (typeof pathValue !== "string" || pathValue.trim().length === 0) {
|
|
124
|
+
problems.push([
|
|
125
|
+
"Invalid tsconfig reference path:",
|
|
126
|
+
` config: ${formatConfigPath(configPath)}`,
|
|
127
|
+
` field: ${field}.path`,
|
|
128
|
+
` value: ${formatUnknownValue$2(pathValue)}`,
|
|
129
|
+
" reason: reference path must be a non-empty string."
|
|
130
|
+
].join("\n"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
referenceInfos.push({
|
|
134
|
+
rawPath: pathValue,
|
|
135
|
+
resolvedPath: resolveReferencePath(configPath, pathValue)
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
problems,
|
|
140
|
+
references: referenceInfos
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function isNonEmptyStringArray(value) {
|
|
144
|
+
return Array.isArray(value) && value.some((item) => typeof item === "string");
|
|
145
|
+
}
|
|
146
|
+
function hasOwnTypecheckInputs(configObject) {
|
|
147
|
+
if (!Object.hasOwn(configObject, "files") && !Object.hasOwn(configObject, "include")) return true;
|
|
148
|
+
return isNonEmptyStringArray(configObject.files) || isNonEmptyStringArray(configObject.include);
|
|
149
|
+
}
|
|
150
|
+
function isDtsConfigPath(configPath) {
|
|
151
|
+
return dtsConfigFilePattern.test(path.basename(configPath));
|
|
152
|
+
}
|
|
153
|
+
function getDtsCompanionConfigPath(dtsConfigPath) {
|
|
154
|
+
const directory = path.dirname(dtsConfigPath);
|
|
155
|
+
const fileName = path.basename(dtsConfigPath);
|
|
156
|
+
const companionFileName = fileName === "tsconfig.dts.json" ? "tsconfig.json" : fileName.replace(/\.dts\.json$/u, ".json");
|
|
157
|
+
const scopedCompanionPath = normalizeAbsolutePath(path.join(directory, companionFileName));
|
|
158
|
+
if (companionFileName === "tsconfig.json" || existsSync(scopedCompanionPath)) return scopedCompanionPath;
|
|
159
|
+
return normalizeAbsolutePath(path.join(directory, "tsconfig.json"));
|
|
160
|
+
}
|
|
161
|
+
function isBuildGraphConfigPath(configPath) {
|
|
162
|
+
return buildGraphConfigFilePattern.test(path.basename(configPath));
|
|
163
|
+
}
|
|
164
|
+
function isDeprecatedGraphConfigPath(configPath) {
|
|
165
|
+
return deprecatedGraphConfigFilePattern.test(path.basename(configPath));
|
|
166
|
+
}
|
|
167
|
+
function isReservedTypeScriptConfigFile(fileName) {
|
|
168
|
+
return dtsConfigFilePattern.test(fileName) || buildGraphConfigFilePattern.test(fileName) || deprecatedGraphConfigFilePattern.test(fileName) || generatedConfigFilePattern.test(fileName) || baseConfigFilePattern.test(fileName) || checkConfigFilePattern.test(fileName);
|
|
169
|
+
}
|
|
170
|
+
function isOrdinaryTypecheckConfigPath(configPath) {
|
|
171
|
+
const fileName = path.basename(configPath);
|
|
172
|
+
return tsconfigFilePattern.test(fileName) && !isReservedTypeScriptConfigFile(fileName);
|
|
173
|
+
}
|
|
174
|
+
function collectTypecheckTargetProjectPaths(options) {
|
|
175
|
+
const rootConfigPath = normalizeAbsolutePath(options.rootConfigPath);
|
|
176
|
+
const reportedCycles = /* @__PURE__ */ new Set();
|
|
177
|
+
const seen = /* @__PURE__ */ new Set();
|
|
178
|
+
const problems = [];
|
|
179
|
+
const projectPaths = [];
|
|
180
|
+
const targetProjectPaths = [];
|
|
181
|
+
const formatConfigPath = (configPath) => toRelativePath(options.rootDir, configPath);
|
|
182
|
+
const addCycleProblem = (referencePath, stack) => {
|
|
183
|
+
const cycleStartIndex = stack.indexOf(referencePath);
|
|
184
|
+
const cyclePaths = cycleStartIndex === -1 ? [...stack, referencePath] : [...stack.slice(cycleStartIndex), referencePath];
|
|
185
|
+
const cycleKey = cyclePaths.join("\0");
|
|
186
|
+
if (reportedCycles.has(cycleKey)) return;
|
|
187
|
+
reportedCycles.add(cycleKey);
|
|
188
|
+
problems.push([
|
|
189
|
+
"Circular reference in ordinary tsconfig references:",
|
|
190
|
+
` cycle: ${cyclePaths.map(formatConfigPath).join(" -> ")}`,
|
|
191
|
+
" reason: ordinary tsconfig references used by limina checker typecheck must form an acyclic graph.",
|
|
192
|
+
" fix: remove one reference from the cycle, or move shared options into extends instead of references."
|
|
193
|
+
].join("\n"));
|
|
194
|
+
};
|
|
195
|
+
const visitProject = (projectPath, stack) => {
|
|
196
|
+
if (stack.includes(projectPath)) {
|
|
197
|
+
addCycleProblem(projectPath, stack);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (seen.has(projectPath)) return;
|
|
201
|
+
if (!existsSync(projectPath)) {
|
|
202
|
+
problems.push(["Ordinary tsconfig reference graph references a missing tsconfig:", ` config: ${formatConfigPath(projectPath)}`].join("\n"));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (!isOrdinaryTypecheckConfigPath(projectPath)) {
|
|
206
|
+
problems.push([
|
|
207
|
+
"Invalid config in ordinary tsconfig reference graph:",
|
|
208
|
+
` config: ${formatConfigPath(projectPath)}`,
|
|
209
|
+
" reason: ordinary tsconfig references must stay on ordinary tsconfig*.json files; tsconfig*.build.json graph aggregators and tsconfig*.dts.json declaration leaves belong to checker entries."
|
|
210
|
+
].join("\n"));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
seen.add(projectPath);
|
|
214
|
+
projectPaths.push(projectPath);
|
|
215
|
+
const configObject = readJsonConfigFile(options.rootDir, projectPath);
|
|
216
|
+
const referenceCollection = collectReferencePathInfosFromConfigObject(options.rootDir, projectPath, configObject);
|
|
217
|
+
const referencePaths = referenceCollection.references.map((reference) => reference.resolvedPath);
|
|
218
|
+
problems.push(...referenceCollection.problems);
|
|
219
|
+
if (referencePaths.length === 0 || hasOwnTypecheckInputs(configObject)) targetProjectPaths.push(projectPath);
|
|
220
|
+
const nextStack = [...stack, projectPath];
|
|
221
|
+
for (const referencePath of referencePaths) {
|
|
222
|
+
if (isBuildGraphConfigPath(referencePath) || isDtsConfigPath(referencePath)) {
|
|
223
|
+
problems.push([
|
|
224
|
+
"Invalid reference in ordinary tsconfig reference graph:",
|
|
225
|
+
` from: ${formatConfigPath(projectPath)}`,
|
|
226
|
+
` to: ${formatConfigPath(referencePath)}`,
|
|
227
|
+
" reason: ordinary tsconfig references must stay on ordinary tsconfig*.json files; build graph configs and declaration leaves are checked through checker entries."
|
|
228
|
+
].join("\n"));
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (!isOrdinaryTypecheckConfigPath(referencePath)) {
|
|
232
|
+
problems.push([
|
|
233
|
+
"Invalid reference in ordinary tsconfig reference graph:",
|
|
234
|
+
` from: ${formatConfigPath(projectPath)}`,
|
|
235
|
+
` to: ${formatConfigPath(referencePath)}`,
|
|
236
|
+
" reason: referenced config must be an ordinary tsconfig*.json file."
|
|
237
|
+
].join("\n"));
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (nextStack.includes(referencePath)) {
|
|
241
|
+
addCycleProblem(referencePath, nextStack);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
visitProject(referencePath, nextStack);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
visitProject(rootConfigPath, []);
|
|
248
|
+
if (problems.length === 0 && targetProjectPaths.length === 0) problems.push([
|
|
249
|
+
"Ordinary tsconfig reference graph has no tsconfig targets:",
|
|
250
|
+
` root: ${toRelativePath(options.rootDir, rootConfigPath)}`,
|
|
251
|
+
" reason: limina checker typecheck runs ordinary tsconfig*.json files without references, plus configs that have references and their own source inputs."
|
|
252
|
+
].join("\n"));
|
|
253
|
+
return {
|
|
254
|
+
problems,
|
|
255
|
+
projectPaths,
|
|
256
|
+
targetProjectPaths
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function collectGraphProjectRouteFromRoot(options) {
|
|
260
|
+
const rootGraphConfigPath = normalizeAbsolutePath(options.rootConfigPath);
|
|
261
|
+
const seen = /* @__PURE__ */ new Set();
|
|
262
|
+
const orderedProjects = [];
|
|
263
|
+
const problems = [];
|
|
264
|
+
const rootReferences = collectReferencePathInfosForConfig(options.rootDir, rootGraphConfigPath);
|
|
265
|
+
const queue = rootReferences.references.map((reference) => ({
|
|
266
|
+
projectPath: reference.resolvedPath,
|
|
267
|
+
rawReferencePath: reference.rawPath,
|
|
268
|
+
referrerPath: rootGraphConfigPath
|
|
269
|
+
}));
|
|
270
|
+
const formatConfigPath = (configPath) => toRelativePath(options.rootDir, configPath);
|
|
271
|
+
problems.push(...rootReferences.problems);
|
|
272
|
+
if (isDeprecatedGraphConfigPath(rootGraphConfigPath)) problems.push([
|
|
273
|
+
"Checker entry uses a deprecated tsconfig name:",
|
|
274
|
+
` config: ${formatConfigPath(rootGraphConfigPath)}`,
|
|
275
|
+
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
276
|
+
" fix: rename the checker entry to tsconfig*.build.json."
|
|
277
|
+
].join("\n"));
|
|
278
|
+
else if (!isBuildGraphConfigPath(rootGraphConfigPath) && !isDtsConfigPath(rootGraphConfigPath)) problems.push([
|
|
279
|
+
"Invalid checker entry config:",
|
|
280
|
+
` config: ${formatConfigPath(rootGraphConfigPath)}`,
|
|
281
|
+
" reason: checker entries should point to a tsconfig*.build.json graph aggregator or a direct tsconfig*.dts.json declaration leaf."
|
|
282
|
+
].join("\n"));
|
|
283
|
+
if (isDtsConfigPath(rootGraphConfigPath)) {
|
|
284
|
+
seen.add(rootGraphConfigPath);
|
|
285
|
+
orderedProjects.push(rootGraphConfigPath);
|
|
286
|
+
}
|
|
287
|
+
for (const { projectPath } of queue) seen.add(projectPath);
|
|
288
|
+
for (const { projectPath, rawReferencePath, referrerPath } of queue) {
|
|
289
|
+
if (!projectPath) continue;
|
|
290
|
+
if (isDeprecatedGraphConfigPath(projectPath)) {
|
|
291
|
+
problems.push([
|
|
292
|
+
"Deprecated checker entry reference:",
|
|
293
|
+
` from: ${formatConfigPath(referrerPath)}`,
|
|
294
|
+
` reference: ${rawReferencePath}`,
|
|
295
|
+
` resolved: ${formatConfigPath(projectPath)}`,
|
|
296
|
+
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
297
|
+
" fix: rename the referenced config to tsconfig*.build.json and update this reference."
|
|
298
|
+
].join("\n"));
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (!existsSync(projectPath)) {
|
|
302
|
+
problems.push([
|
|
303
|
+
"Checker entry references a missing tsconfig:",
|
|
304
|
+
` from: ${formatConfigPath(referrerPath)}`,
|
|
305
|
+
` reference: ${rawReferencePath}`,
|
|
306
|
+
` resolved: ${formatConfigPath(projectPath)}`,
|
|
307
|
+
" reason: every project reference reachable from a checker entry must point to an existing tsconfig file or directory with tsconfig.json."
|
|
308
|
+
].join("\n"));
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (!isBuildGraphConfigPath(projectPath) && !isDtsConfigPath(projectPath)) {
|
|
312
|
+
problems.push([
|
|
313
|
+
"Invalid checker entry reference:",
|
|
314
|
+
` from: ${formatConfigPath(referrerPath)}`,
|
|
315
|
+
` reference: ${rawReferencePath}`,
|
|
316
|
+
` resolved: ${formatConfigPath(projectPath)}`,
|
|
317
|
+
" reason: checker entries may only reach tsconfig*.build.json graph aggregators and tsconfig*.dts.json declaration leaves."
|
|
318
|
+
].join("\n"));
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
orderedProjects.push(projectPath);
|
|
322
|
+
const referenceCollection = collectReferencePathInfosForConfig(options.rootDir, projectPath);
|
|
323
|
+
problems.push(...referenceCollection.problems);
|
|
324
|
+
for (const reference of referenceCollection.references) {
|
|
325
|
+
const referencePath = reference.resolvedPath;
|
|
326
|
+
if (seen.has(referencePath)) continue;
|
|
327
|
+
seen.add(referencePath);
|
|
328
|
+
queue.push({
|
|
329
|
+
projectPath: referencePath,
|
|
330
|
+
rawReferencePath: reference.rawPath,
|
|
331
|
+
referrerPath: projectPath
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
problems,
|
|
337
|
+
projectPaths: orderedProjects
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function collectGraphProjectRoutes(config) {
|
|
341
|
+
const routes = [];
|
|
342
|
+
const problems = [];
|
|
343
|
+
for (const checker of getActiveCheckers(config)) {
|
|
344
|
+
if (!getCheckerAdapter(checker.preset)?.graph) continue;
|
|
345
|
+
const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
|
|
346
|
+
if (isDeprecatedGraphConfigPath(rootConfigPath)) {
|
|
347
|
+
problems.push([
|
|
348
|
+
"Checker graph entry uses a deprecated tsconfig name:",
|
|
349
|
+
` checker: ${checker.name}`,
|
|
350
|
+
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`,
|
|
351
|
+
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
352
|
+
" fix: rename the checker entry to tsconfig*.build.json."
|
|
353
|
+
].join("\n"));
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (!existsSync(rootConfigPath)) {
|
|
357
|
+
problems.push([
|
|
358
|
+
"Checker graph entry references a missing tsconfig:",
|
|
359
|
+
` checker: ${checker.name}`,
|
|
360
|
+
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`
|
|
361
|
+
].join("\n"));
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
365
|
+
rootConfigPath,
|
|
366
|
+
rootDir: config.rootDir
|
|
367
|
+
});
|
|
368
|
+
problems.push(...routeCollection.problems);
|
|
369
|
+
routes.push({
|
|
370
|
+
checkerName: checker.name,
|
|
371
|
+
projectPaths: routeCollection.projectPaths,
|
|
372
|
+
rootConfigPath
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
problems,
|
|
377
|
+
routes
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function collectCheckerEntryProjectRoutes(config) {
|
|
381
|
+
const routes = [];
|
|
382
|
+
const problems = [];
|
|
383
|
+
for (const checker of getActiveCheckers(config)) {
|
|
384
|
+
const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
|
|
385
|
+
if (isDeprecatedGraphConfigPath(rootConfigPath)) {
|
|
386
|
+
problems.push([
|
|
387
|
+
"Checker entry uses a deprecated tsconfig name:",
|
|
388
|
+
` checker: ${checker.name}`,
|
|
389
|
+
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`,
|
|
390
|
+
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
391
|
+
" fix: rename the checker entry to tsconfig*.build.json."
|
|
392
|
+
].join("\n"));
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (!existsSync(rootConfigPath)) {
|
|
396
|
+
problems.push([
|
|
397
|
+
"Checker entry references a missing tsconfig:",
|
|
398
|
+
` checker: ${checker.name}`,
|
|
399
|
+
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`
|
|
400
|
+
].join("\n"));
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
404
|
+
rootConfigPath,
|
|
405
|
+
rootDir: config.rootDir
|
|
406
|
+
});
|
|
407
|
+
problems.push(...routeCollection.problems);
|
|
408
|
+
routes.push({
|
|
409
|
+
checkerName: checker.name,
|
|
410
|
+
projectPaths: routeCollection.projectPaths,
|
|
411
|
+
rootConfigPath
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
problems,
|
|
416
|
+
routes
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function collectGraphProjectRoute(config) {
|
|
420
|
+
const routeCollection = collectGraphProjectRoutes(config);
|
|
421
|
+
return {
|
|
422
|
+
problems: routeCollection.problems,
|
|
423
|
+
projectPaths: [...new Set(routeCollection.routes.flatMap((route) => route.projectPaths))].sort()
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function parseProjectFileNames(config, configPath, pattern = /\.(?:[cm]?tsx?|d\.[cm]?ts|json)$/u) {
|
|
427
|
+
return parseProjectFileNamesForExtensions(config, configPath, [], pattern);
|
|
428
|
+
}
|
|
429
|
+
function parseProjectFileNamesForExtensions(config, configPath, extensions, pattern = createExtensionPattern(extensions)) {
|
|
430
|
+
const diagnostics = [];
|
|
431
|
+
const configObject = readJsonConfig(config, configPath);
|
|
432
|
+
const parsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
|
|
433
|
+
if (diagnostics.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
|
|
434
|
+
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
|
|
435
|
+
return parsed.fileNames.filter((fileName) => pattern.test(fileName)).map(normalizeAbsolutePath);
|
|
436
|
+
}
|
|
437
|
+
function formatReferences(rootDir, references) {
|
|
438
|
+
if (references.size === 0) return "(none)";
|
|
439
|
+
return [...references].sort().map((value) => toRelativePath(rootDir, value)).join(", ");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/workspace.ts
|
|
444
|
+
const pnpmWorkspaceFileName = "pnpm-workspace.yaml";
|
|
445
|
+
const pnpmWorkspaceListTimeoutMs = 3e3;
|
|
446
|
+
function readJsonFile(filePath) {
|
|
447
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
448
|
+
}
|
|
449
|
+
function stripYamlQuotes(value) {
|
|
450
|
+
const trimmed = value.trim();
|
|
451
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith("\"") && trimmed.endsWith("\"")) return trimmed.slice(1, -1);
|
|
452
|
+
return trimmed;
|
|
453
|
+
}
|
|
454
|
+
function collectPnpmWorkspacePatterns(source) {
|
|
455
|
+
const patterns = [];
|
|
456
|
+
const lines = source.split(/\r?\n/u);
|
|
457
|
+
let isInsidePackagesSection = false;
|
|
458
|
+
for (const rawLine of lines) {
|
|
459
|
+
const line = rawLine.replaceAll(" ", " ");
|
|
460
|
+
const trimmedLine = line.trim();
|
|
461
|
+
if (!isInsidePackagesSection) {
|
|
462
|
+
if (trimmedLine === "packages:") isInsidePackagesSection = true;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (trimmedLine.length === 0 || trimmedLine.startsWith("#")) continue;
|
|
466
|
+
if (line.length - line.trimStart().length === 0) break;
|
|
467
|
+
if (trimmedLine.startsWith("- ")) patterns.push(stripYamlQuotes(trimmedLine.slice(2)));
|
|
468
|
+
}
|
|
469
|
+
return patterns;
|
|
470
|
+
}
|
|
471
|
+
function parsePnpmWorkspaceListJson(source) {
|
|
472
|
+
const parsed = JSON.parse(source);
|
|
473
|
+
if (!Array.isArray(parsed)) return [];
|
|
474
|
+
return parsed.flatMap((entry) => {
|
|
475
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
476
|
+
const record = entry;
|
|
477
|
+
if (typeof record.name !== "string" || typeof record.path !== "string") return [];
|
|
478
|
+
return [{
|
|
479
|
+
name: record.name,
|
|
480
|
+
path: record.path
|
|
481
|
+
}];
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
function collectWorkspacePatterns(config) {
|
|
485
|
+
const rootPackageJsonPath = path.join(config.rootDir, "package.json");
|
|
486
|
+
const patterns = /* @__PURE__ */ new Set();
|
|
487
|
+
if (existsSync(rootPackageJsonPath)) {
|
|
488
|
+
const rootPackageJson = readJsonFile(rootPackageJsonPath);
|
|
489
|
+
if (Array.isArray(rootPackageJson.workspaces)) for (const pattern of rootPackageJson.workspaces) patterns.add(pattern);
|
|
490
|
+
}
|
|
491
|
+
const workspacePath = path.join(config.rootDir, pnpmWorkspaceFileName);
|
|
492
|
+
if (existsSync(workspacePath)) for (const pattern of collectPnpmWorkspacePatterns(readFileSync(workspacePath, "utf8"))) patterns.add(pattern);
|
|
493
|
+
return [...patterns].sort();
|
|
494
|
+
}
|
|
495
|
+
function runTextCommand(command, args, cwd) {
|
|
496
|
+
return new Promise((resolve, reject) => {
|
|
497
|
+
execFile(command, args, {
|
|
498
|
+
cwd,
|
|
499
|
+
encoding: "utf8",
|
|
500
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
501
|
+
timeout: pnpmWorkspaceListTimeoutMs
|
|
502
|
+
}, (error, stdout) => {
|
|
503
|
+
if (error) {
|
|
504
|
+
reject(error);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
resolve(stdout);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function getPnpmCommandCandidates() {
|
|
512
|
+
const candidates = [];
|
|
513
|
+
const npmExecPath = process.env.npm_execpath;
|
|
514
|
+
if (npmExecPath?.includes("pnpm")) candidates.push({
|
|
515
|
+
argsPrefix: [npmExecPath],
|
|
516
|
+
command: process.execPath
|
|
517
|
+
});
|
|
518
|
+
candidates.push({
|
|
519
|
+
argsPrefix: ["pnpm"],
|
|
520
|
+
command: "corepack"
|
|
521
|
+
}, {
|
|
522
|
+
argsPrefix: [],
|
|
523
|
+
command: "pnpm"
|
|
524
|
+
});
|
|
525
|
+
const seen = /* @__PURE__ */ new Set();
|
|
526
|
+
return candidates.filter((candidate) => {
|
|
527
|
+
const key = `${candidate.command}\0${candidate.argsPrefix.join("\0")}`;
|
|
528
|
+
if (seen.has(key)) return false;
|
|
529
|
+
seen.add(key);
|
|
530
|
+
return true;
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
async function collectPnpmListedPackages(config) {
|
|
534
|
+
const args = [
|
|
535
|
+
"recursive",
|
|
536
|
+
"list",
|
|
537
|
+
"--depth",
|
|
538
|
+
"-1",
|
|
539
|
+
"--json"
|
|
540
|
+
];
|
|
541
|
+
for (const candidate of getPnpmCommandCandidates()) try {
|
|
542
|
+
const entries = parsePnpmWorkspaceListJson(await runTextCommand(candidate.command, [...candidate.argsPrefix, ...args], config.rootDir));
|
|
543
|
+
const packages = [];
|
|
544
|
+
for (const entry of entries) {
|
|
545
|
+
if (!entry.path) continue;
|
|
546
|
+
const directory = normalizeAbsolutePath(entry.path);
|
|
547
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
548
|
+
if (!existsSync(packageJsonPath)) continue;
|
|
549
|
+
const manifest = readJsonFile(packageJsonPath);
|
|
550
|
+
const name = manifest.name ?? entry.name;
|
|
551
|
+
if (!name) continue;
|
|
552
|
+
packages.push({
|
|
553
|
+
directory,
|
|
554
|
+
manifest,
|
|
555
|
+
name
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (packages.length > 0) return packages;
|
|
559
|
+
} catch {}
|
|
560
|
+
return [];
|
|
561
|
+
}
|
|
562
|
+
async function collectWorkspacePackagesFromPatterns(config) {
|
|
563
|
+
const workspacePatterns = collectWorkspacePatterns(config);
|
|
564
|
+
const includePatterns = workspacePatterns.filter((pattern) => !pattern.startsWith("!")).map((pattern) => `${pattern.replace(/\/$/u, "")}/package.json`);
|
|
565
|
+
const ignorePatterns = workspacePatterns.filter((pattern) => pattern.startsWith("!")).map((pattern) => `${pattern.slice(1).replace(/\/$/u, "")}/**`);
|
|
566
|
+
const packageJsonPaths = await glob(includePatterns, {
|
|
567
|
+
cwd: config.rootDir,
|
|
568
|
+
absolute: false,
|
|
569
|
+
ignore: [
|
|
570
|
+
"**/node_modules/**",
|
|
571
|
+
"**/dist/**",
|
|
572
|
+
...ignorePatterns
|
|
573
|
+
]
|
|
574
|
+
});
|
|
575
|
+
const packages = [];
|
|
576
|
+
for (const packageJsonPath of [...new Set(packageJsonPaths)].sort()) {
|
|
577
|
+
const manifest = readJsonFile(path.join(config.rootDir, packageJsonPath));
|
|
578
|
+
if (!manifest.name) continue;
|
|
579
|
+
packages.push({
|
|
580
|
+
directory: normalizeAbsolutePath(path.dirname(path.join(config.rootDir, packageJsonPath))),
|
|
581
|
+
manifest,
|
|
582
|
+
name: manifest.name
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
return packages;
|
|
586
|
+
}
|
|
587
|
+
function mergeWorkspacePackages(packages) {
|
|
588
|
+
const byDirectory = /* @__PURE__ */ new Map();
|
|
589
|
+
for (const workspacePackage of packages) byDirectory.set(workspacePackage.directory, workspacePackage);
|
|
590
|
+
return [...byDirectory.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
591
|
+
}
|
|
592
|
+
async function collectWorkspacePackages(config) {
|
|
593
|
+
const [pnpmPackages, patternPackages] = await Promise.all([collectPnpmListedPackages(config), collectWorkspacePackagesFromPatterns(config)]);
|
|
594
|
+
return mergeWorkspacePackages([...pnpmPackages, ...patternPackages]);
|
|
595
|
+
}
|
|
596
|
+
async function collectPackageOwners(config) {
|
|
597
|
+
const packageJsonPaths = await glob(["package.json", "**/package.json"], {
|
|
598
|
+
cwd: config.rootDir,
|
|
599
|
+
absolute: false,
|
|
600
|
+
ignore: [
|
|
601
|
+
"**/.git/**",
|
|
602
|
+
"**/.pnpm-store/**",
|
|
603
|
+
"**/.tsbuild/**",
|
|
604
|
+
"**/coverage/**",
|
|
605
|
+
"**/dist/**",
|
|
606
|
+
"**/node_modules/**"
|
|
607
|
+
]
|
|
608
|
+
});
|
|
609
|
+
return [...new Set(packageJsonPaths)].sort().map((packageJsonPath) => {
|
|
610
|
+
const absolutePackageJsonPath = normalizeAbsolutePath(path.join(config.rootDir, packageJsonPath));
|
|
611
|
+
const manifest = readJsonFile(absolutePackageJsonPath);
|
|
612
|
+
return {
|
|
613
|
+
directory: normalizeAbsolutePath(path.dirname(absolutePackageJsonPath)),
|
|
614
|
+
manifest,
|
|
615
|
+
name: manifest.name,
|
|
616
|
+
packageJsonPath: absolutePackageJsonPath
|
|
617
|
+
};
|
|
618
|
+
}).sort((left, right) => right.directory.length - left.directory.length);
|
|
619
|
+
}
|
|
620
|
+
function getDependencySections(importer) {
|
|
621
|
+
return [
|
|
622
|
+
importer.dependencies,
|
|
623
|
+
importer.devDependencies,
|
|
624
|
+
importer.optionalDependencies,
|
|
625
|
+
importer.peerDependencies
|
|
626
|
+
].filter((section) => Boolean(section));
|
|
627
|
+
}
|
|
628
|
+
function isWorkspaceDependencySpecifier(specifier) {
|
|
629
|
+
return specifier.startsWith("workspace:");
|
|
630
|
+
}
|
|
631
|
+
function getPackageRootSpecifier(specifier) {
|
|
632
|
+
if (specifier.startsWith("@")) {
|
|
633
|
+
const [scope, name] = specifier.split("/");
|
|
634
|
+
return scope && name ? `${scope}/${name}` : specifier;
|
|
635
|
+
}
|
|
636
|
+
return specifier.split("/")[0] ?? specifier;
|
|
637
|
+
}
|
|
638
|
+
function findPackageForSpecifier(specifier, packages) {
|
|
639
|
+
const packageName = getPackageRootSpecifier(specifier);
|
|
640
|
+
return packages.find((workspacePackage) => workspacePackage.name === packageName) ?? null;
|
|
641
|
+
}
|
|
642
|
+
function collectImporters(config, packages) {
|
|
643
|
+
const workspacePackageNames = new Set(packages.map((workspacePackage) => workspacePackage.name));
|
|
644
|
+
const importerDirectories = new Set([config.rootDir, ...packages.map((workspacePackage) => workspacePackage.directory)]);
|
|
645
|
+
const importers = [];
|
|
646
|
+
for (const importerDirectory of importerDirectories) {
|
|
647
|
+
const packageJsonPath = path.join(importerDirectory, "package.json");
|
|
648
|
+
if (!existsSync(packageJsonPath)) continue;
|
|
649
|
+
const manifest = readJsonFile(packageJsonPath);
|
|
650
|
+
const workspaceDependencies = /* @__PURE__ */ new Set();
|
|
651
|
+
for (const dependencies of getDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) {
|
|
652
|
+
if (!workspacePackageNames.has(dependencyName) || !isWorkspaceDependencySpecifier(specifier)) continue;
|
|
653
|
+
workspaceDependencies.add(dependencyName);
|
|
654
|
+
}
|
|
655
|
+
importers.push({
|
|
656
|
+
directory: importerDirectory,
|
|
657
|
+
name: manifest.name,
|
|
658
|
+
workspaceDependencies
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
return importers.sort((left, right) => right.directory.length - left.directory.length);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region src/graph-context.ts
|
|
666
|
+
function isRelativeSpecifier(specifier) {
|
|
667
|
+
return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
|
|
668
|
+
}
|
|
669
|
+
function isDtsProjectConfig(configPath) {
|
|
670
|
+
return isDtsConfigPath(configPath);
|
|
671
|
+
}
|
|
672
|
+
function getTypecheckConfigPath(dtsConfigPath) {
|
|
673
|
+
return getDtsCompanionConfigPath(dtsConfigPath);
|
|
674
|
+
}
|
|
675
|
+
function formatUnknownValue$1(value) {
|
|
676
|
+
if (value === void 0) return "undefined";
|
|
677
|
+
return JSON.stringify(value);
|
|
678
|
+
}
|
|
679
|
+
function readProjectLabel(config, configPath) {
|
|
680
|
+
if (!isDtsProjectConfig(configPath)) return {
|
|
681
|
+
label: null,
|
|
682
|
+
labelProblem: null
|
|
683
|
+
};
|
|
684
|
+
const configObject = readJsonConfig(config, configPath);
|
|
685
|
+
if (!Object.hasOwn(configObject, "limina")) return {
|
|
686
|
+
label: null,
|
|
687
|
+
labelProblem: null
|
|
688
|
+
};
|
|
689
|
+
const value = configObject.limina;
|
|
690
|
+
if (typeof value === "string" && value.trim()) return {
|
|
691
|
+
label: value.trim(),
|
|
692
|
+
labelProblem: null
|
|
693
|
+
};
|
|
694
|
+
return {
|
|
695
|
+
label: null,
|
|
696
|
+
labelProblem: [
|
|
697
|
+
"Invalid Limina graph label:",
|
|
698
|
+
` project: ${toRelativePath(config.rootDir, configPath)}`,
|
|
699
|
+
` field: limina`,
|
|
700
|
+
` value: ${formatUnknownValue$1(value)}`,
|
|
701
|
+
" reason: tsconfig*.dts.json may declare one non-empty string label with \"limina\"."
|
|
702
|
+
].join("\n")
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function parseProject(config, configPath) {
|
|
706
|
+
const diagnostics = [];
|
|
707
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
708
|
+
...ts.sys,
|
|
709
|
+
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
710
|
+
diagnostics.push(diagnostic);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
714
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
715
|
+
getCurrentDirectory: () => config.rootDir,
|
|
716
|
+
getNewLine: () => "\n"
|
|
717
|
+
}));
|
|
718
|
+
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
719
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
720
|
+
getCurrentDirectory: () => config.rootDir,
|
|
721
|
+
getNewLine: () => "\n"
|
|
722
|
+
}));
|
|
723
|
+
const labelInfo = readProjectLabel(config, configPath);
|
|
724
|
+
return {
|
|
725
|
+
configPath: normalizeAbsolutePath(configPath),
|
|
726
|
+
fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
|
|
727
|
+
label: labelInfo.label,
|
|
728
|
+
labelProblem: labelInfo.labelProblem,
|
|
729
|
+
options: parsed.options,
|
|
730
|
+
references: new Set(getRawReferencePaths(config, configPath))
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
function getSourceFileKind(filePath) {
|
|
734
|
+
if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
735
|
+
if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
736
|
+
return ts.ScriptKind.TS;
|
|
737
|
+
}
|
|
738
|
+
function stringLiteralValue(node) {
|
|
739
|
+
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
740
|
+
}
|
|
741
|
+
function collectImportsFromFile(filePath) {
|
|
742
|
+
const sourceText = readFileSync(filePath, "utf8");
|
|
743
|
+
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
|
|
744
|
+
const imports = [];
|
|
745
|
+
const addImport = (specifier, node) => {
|
|
746
|
+
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
747
|
+
imports.push({
|
|
748
|
+
filePath,
|
|
749
|
+
line: location.line + 1,
|
|
750
|
+
specifier
|
|
751
|
+
});
|
|
752
|
+
};
|
|
753
|
+
const visit = (node) => {
|
|
754
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
755
|
+
const specifier = stringLiteralValue(node.moduleSpecifier);
|
|
756
|
+
if (specifier) addImport(specifier, node);
|
|
757
|
+
} else if (ts.isImportTypeNode(node)) {
|
|
758
|
+
const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
|
|
759
|
+
if (specifier) addImport(specifier, node);
|
|
760
|
+
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
761
|
+
const specifier = stringLiteralValue(node.arguments[0]);
|
|
762
|
+
if (specifier) addImport(specifier, node);
|
|
763
|
+
}
|
|
764
|
+
ts.forEachChild(node, visit);
|
|
765
|
+
};
|
|
766
|
+
visit(sourceFile);
|
|
767
|
+
return imports;
|
|
768
|
+
}
|
|
769
|
+
function resolveInternalImport(specifier, containingFile, options) {
|
|
770
|
+
const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
|
|
771
|
+
return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
|
|
772
|
+
}
|
|
773
|
+
function chooseOwningProject(projectPaths) {
|
|
774
|
+
return [...projectPaths].sort((left, right) => {
|
|
775
|
+
const directoryDepthDelta = path.dirname(right).length - path.dirname(left).length;
|
|
776
|
+
return directoryDepthDelta === 0 ? left.localeCompare(right) : directoryDepthDelta;
|
|
777
|
+
})[0];
|
|
778
|
+
}
|
|
779
|
+
function findPackageForFile(filePath, packages) {
|
|
780
|
+
return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
|
|
781
|
+
}
|
|
782
|
+
function isWorkspacePackageFile(filePath, packages) {
|
|
783
|
+
return packages.some((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory));
|
|
784
|
+
}
|
|
785
|
+
function findImporterForFile(filePath, importers) {
|
|
786
|
+
return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
|
|
787
|
+
}
|
|
788
|
+
function shouldResolveThroughGraph(importer, targetPackage) {
|
|
789
|
+
if (!importer || !targetPackage) return false;
|
|
790
|
+
return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
|
|
791
|
+
}
|
|
792
|
+
function formatArtifactDependencyPolicy(targetPackage) {
|
|
793
|
+
return targetPackage.manifest.private === true ? "private workspace packages cannot be consumed from a registry, so artifact consumers should use link: and should not keep a project reference." : "artifact consumers should use link: for local dist output, or catalog:/semver to consume the published production package, and should not keep a project reference.";
|
|
794
|
+
}
|
|
795
|
+
function inferPackageProject(resolvedFilePath, workspacePackage, projectPaths) {
|
|
796
|
+
if (!isPathInsideDirectory(resolvedFilePath, workspacePackage.directory)) return null;
|
|
797
|
+
return projectPaths.find((projectPath) => {
|
|
798
|
+
return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
|
|
799
|
+
}) ?? null;
|
|
800
|
+
}
|
|
801
|
+
function createFileOwnerLookup(projects) {
|
|
802
|
+
const ownerLookup = /* @__PURE__ */ new Map();
|
|
803
|
+
for (const project of projects) for (const fileName of project.fileNames) {
|
|
804
|
+
const owners = ownerLookup.get(fileName) ?? [];
|
|
805
|
+
owners.push(project.configPath);
|
|
806
|
+
ownerLookup.set(fileName, owners);
|
|
807
|
+
}
|
|
808
|
+
return ownerLookup;
|
|
809
|
+
}
|
|
810
|
+
function findTargetProject(options) {
|
|
811
|
+
const ownerProjects = options.fileOwnerLookup.get(options.resolvedFilePath);
|
|
812
|
+
if (ownerProjects && ownerProjects.length > 0) return chooseOwningProject(ownerProjects);
|
|
813
|
+
const workspacePackage = findPackageForSpecifier(options.specifier, options.packages);
|
|
814
|
+
if (!workspacePackage) return null;
|
|
815
|
+
return inferPackageProject(options.resolvedFilePath, workspacePackage, options.projectPaths);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region src/graph-rules.ts
|
|
820
|
+
const nodeBuiltinNames = new Set(builtinModules.flatMap((specifier) => {
|
|
821
|
+
const normalized = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
|
822
|
+
return [normalized, `node:${normalized}`];
|
|
823
|
+
}));
|
|
824
|
+
function isPlainRecord(value) {
|
|
825
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
826
|
+
}
|
|
827
|
+
function isNonEmptyString(value) {
|
|
828
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
829
|
+
}
|
|
830
|
+
function formatUnknownValue(value) {
|
|
831
|
+
if (value === void 0) return "undefined";
|
|
832
|
+
return JSON.stringify(value);
|
|
833
|
+
}
|
|
834
|
+
function addRuleEntryConfigProblem(problems, details) {
|
|
835
|
+
problems.push(["Invalid graph rule config:", ...details].join("\n"));
|
|
836
|
+
}
|
|
837
|
+
function getRulesRecord(config, problems) {
|
|
838
|
+
const rules = config.graph?.rules;
|
|
839
|
+
if (rules === void 0) return {};
|
|
840
|
+
if (!isPlainRecord(rules)) {
|
|
841
|
+
problems.push([
|
|
842
|
+
"Invalid graph rules config:",
|
|
843
|
+
" field: graph.rules",
|
|
844
|
+
` value: ${formatUnknownValue(rules)}`,
|
|
845
|
+
" reason: graph.rules must be an object keyed by Limina labels."
|
|
846
|
+
].join("\n"));
|
|
847
|
+
return {};
|
|
848
|
+
}
|
|
849
|
+
return rules;
|
|
850
|
+
}
|
|
851
|
+
function addNormalizedRuleRef(options) {
|
|
852
|
+
const field = `graph.rules.${options.label}.deny.refs[${options.index}]`;
|
|
853
|
+
if (!isPlainRecord(options.entry)) {
|
|
854
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
855
|
+
` field: ${field}`,
|
|
856
|
+
` value: ${formatUnknownValue(options.entry)}`,
|
|
857
|
+
" reason: deny.refs entries must be objects with non-empty path and reason fields."
|
|
858
|
+
]);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const pathValue = options.entry.path;
|
|
862
|
+
const reasonValue = options.entry.reason;
|
|
863
|
+
if (!isNonEmptyString(pathValue)) {
|
|
864
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
865
|
+
` field: ${field}.path`,
|
|
866
|
+
` value: ${formatUnknownValue(pathValue)}`,
|
|
867
|
+
" reason: deny.refs path is required and must be a non-empty string."
|
|
868
|
+
]);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (!isNonEmptyString(reasonValue)) {
|
|
872
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
873
|
+
` field: ${field}.reason`,
|
|
874
|
+
` value: ${formatUnknownValue(reasonValue)}`,
|
|
875
|
+
" reason: deny.refs reason is required and must be a non-empty string."
|
|
876
|
+
]);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
const refPath = normalizeAbsolutePath(path.resolve(options.config.rootDir, pathValue));
|
|
880
|
+
if (!options.projectPathSet.has(refPath)) {
|
|
881
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
882
|
+
` field: ${field}.path`,
|
|
883
|
+
` path: ${pathValue}`,
|
|
884
|
+
" reason: deny.refs path must point to a project reachable from a checker entry."
|
|
885
|
+
]);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (!isDtsProjectConfig(refPath)) {
|
|
889
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
890
|
+
` field: ${field}.path`,
|
|
891
|
+
` path: ${pathValue}`,
|
|
892
|
+
" reason: deny.refs path must point to a tsconfig*.dts.json declaration leaf."
|
|
893
|
+
]);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const refs = options.refsByLabel.get(options.label) ?? /* @__PURE__ */ new Map();
|
|
897
|
+
refs.set(refPath, {
|
|
898
|
+
path: refPath,
|
|
899
|
+
reason: reasonValue.trim()
|
|
900
|
+
});
|
|
901
|
+
options.refsByLabel.set(options.label, refs);
|
|
902
|
+
}
|
|
903
|
+
function addNormalizedWorkspaceDep(options) {
|
|
904
|
+
const field = `${options.fieldPrefix}[${options.index}]`;
|
|
905
|
+
if (!isPlainRecord(options.entry)) {
|
|
906
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
907
|
+
` field: ${field}`,
|
|
908
|
+
` value: ${formatUnknownValue(options.entry)}`,
|
|
909
|
+
" reason: deny workspace dependency entries must be objects with non-empty name and reason fields."
|
|
910
|
+
]);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const nameValue = options.entry.name;
|
|
914
|
+
const reasonValue = options.entry.reason;
|
|
915
|
+
if (!isNonEmptyString(nameValue)) {
|
|
916
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
917
|
+
` field: ${field}.name`,
|
|
918
|
+
` value: ${formatUnknownValue(nameValue)}`,
|
|
919
|
+
" reason: workspace dependency name is required and must be a non-empty string."
|
|
920
|
+
]);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
if (!isNonEmptyString(reasonValue)) {
|
|
924
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
925
|
+
` field: ${field}.reason`,
|
|
926
|
+
` value: ${formatUnknownValue(reasonValue)}`,
|
|
927
|
+
" reason: workspace dependency reason is required and must be a non-empty string."
|
|
928
|
+
]);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const packageName = nameValue.trim();
|
|
932
|
+
if (!options.packageNames.has(packageName)) {
|
|
933
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
934
|
+
` field: ${field}.name`,
|
|
935
|
+
` name: ${packageName}`,
|
|
936
|
+
" reason: deny.workspaceDeps and legacy deny.deps only accept discovered workspace package names. Use deny.nodeBuiltins for Node builtins."
|
|
937
|
+
]);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const deps = options.workspaceDepsByLabel.get(options.label) ?? /* @__PURE__ */ new Map();
|
|
941
|
+
deps.set(packageName, {
|
|
942
|
+
name: packageName,
|
|
943
|
+
reason: reasonValue.trim()
|
|
944
|
+
});
|
|
945
|
+
options.workspaceDepsByLabel.set(options.label, deps);
|
|
946
|
+
}
|
|
947
|
+
function addNormalizedNodeBuiltin(options) {
|
|
948
|
+
const field = `graph.rules.${options.label}.deny.nodeBuiltins[${options.index}]`;
|
|
949
|
+
if (!isPlainRecord(options.entry)) {
|
|
950
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
951
|
+
` field: ${field}`,
|
|
952
|
+
` value: ${formatUnknownValue(options.entry)}`,
|
|
953
|
+
" reason: deny.nodeBuiltins entries must be objects with non-empty name and reason fields."
|
|
954
|
+
]);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const nameValue = options.entry.name;
|
|
958
|
+
const reasonValue = options.entry.reason;
|
|
959
|
+
if (!isNonEmptyString(nameValue)) {
|
|
960
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
961
|
+
` field: ${field}.name`,
|
|
962
|
+
` value: ${formatUnknownValue(nameValue)}`,
|
|
963
|
+
" reason: deny.nodeBuiltins name is required and must be a non-empty string."
|
|
964
|
+
]);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (!isNonEmptyString(reasonValue)) {
|
|
968
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
969
|
+
` field: ${field}.reason`,
|
|
970
|
+
` value: ${formatUnknownValue(reasonValue)}`,
|
|
971
|
+
" reason: deny.nodeBuiltins reason is required and must be a non-empty string."
|
|
972
|
+
]);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const name = nameValue.trim();
|
|
976
|
+
const normalizedName = name.startsWith("node:") ? name.slice(5) : name;
|
|
977
|
+
const matchAll = name === "node:*";
|
|
978
|
+
if (!matchAll && !nodeBuiltinNames.has(normalizedName)) {
|
|
979
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
980
|
+
` field: ${field}.name`,
|
|
981
|
+
` name: ${name}`,
|
|
982
|
+
" reason: deny.nodeBuiltins name must be \"node:*\" or a Node builtin specifier such as \"fs\" or \"node:fs\"."
|
|
983
|
+
]);
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
const entries = options.nodeBuiltinsByLabel.get(options.label) ?? [];
|
|
987
|
+
entries.push({
|
|
988
|
+
matchAll,
|
|
989
|
+
name: normalizedName,
|
|
990
|
+
reason: reasonValue.trim()
|
|
991
|
+
});
|
|
992
|
+
options.nodeBuiltinsByLabel.set(options.label, entries);
|
|
993
|
+
}
|
|
994
|
+
function shouldNormalizeRuleKind(include, kind) {
|
|
995
|
+
return include?.[kind] ?? true;
|
|
996
|
+
}
|
|
997
|
+
function normalizeGraphRules(options) {
|
|
998
|
+
const refsByLabel = /* @__PURE__ */ new Map();
|
|
999
|
+
const workspaceDepsByLabel = /* @__PURE__ */ new Map();
|
|
1000
|
+
const nodeBuiltinsByLabel = /* @__PURE__ */ new Map();
|
|
1001
|
+
const projectPathSet = new Set(options.projectPaths);
|
|
1002
|
+
const packageNames = new Set(options.packages.map((workspacePackage) => workspacePackage.name));
|
|
1003
|
+
for (const [rawLabel, rawRule] of Object.entries(getRulesRecord(options.config, options.problems))) {
|
|
1004
|
+
const label = rawLabel.trim();
|
|
1005
|
+
if (!label) {
|
|
1006
|
+
addRuleEntryConfigProblem(options.problems, [" field: graph.rules", " reason: graph.rules keys must be non-empty labels."]);
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (!isPlainRecord(rawRule)) {
|
|
1010
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
1011
|
+
` field: graph.rules.${rawLabel}`,
|
|
1012
|
+
` value: ${formatUnknownValue(rawRule)}`,
|
|
1013
|
+
" reason: each graph rule must be an object."
|
|
1014
|
+
]);
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (rawRule.deny === void 0) continue;
|
|
1018
|
+
if (!isPlainRecord(rawRule.deny)) {
|
|
1019
|
+
addRuleEntryConfigProblem(options.problems, [
|
|
1020
|
+
` field: graph.rules.${label}.deny`,
|
|
1021
|
+
` value: ${formatUnknownValue(rawRule.deny)}`,
|
|
1022
|
+
" reason: graph rule deny must be an object."
|
|
1023
|
+
]);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const refs = rawRule.deny.refs;
|
|
1027
|
+
if (shouldNormalizeRuleKind(options.include, "refs") && refs !== void 0) if (!Array.isArray(refs)) addRuleEntryConfigProblem(options.problems, [
|
|
1028
|
+
` field: graph.rules.${label}.deny.refs`,
|
|
1029
|
+
` value: ${formatUnknownValue(refs)}`,
|
|
1030
|
+
" reason: deny.refs must be an array."
|
|
1031
|
+
]);
|
|
1032
|
+
else refs.forEach((entry, index) => {
|
|
1033
|
+
addNormalizedRuleRef({
|
|
1034
|
+
config: options.config,
|
|
1035
|
+
entry,
|
|
1036
|
+
index,
|
|
1037
|
+
label,
|
|
1038
|
+
problems: options.problems,
|
|
1039
|
+
projectPathSet,
|
|
1040
|
+
refsByLabel
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
const legacyDeps = rawRule.deny.deps;
|
|
1044
|
+
if (shouldNormalizeRuleKind(options.include, "workspaceDeps") && legacyDeps !== void 0) if (!Array.isArray(legacyDeps)) addRuleEntryConfigProblem(options.problems, [
|
|
1045
|
+
` field: graph.rules.${label}.deny.deps`,
|
|
1046
|
+
` value: ${formatUnknownValue(legacyDeps)}`,
|
|
1047
|
+
" reason: deny.deps must be an array."
|
|
1048
|
+
]);
|
|
1049
|
+
else legacyDeps.forEach((entry, index) => {
|
|
1050
|
+
addNormalizedWorkspaceDep({
|
|
1051
|
+
entry,
|
|
1052
|
+
fieldPrefix: `graph.rules.${label}.deny.deps`,
|
|
1053
|
+
index,
|
|
1054
|
+
label,
|
|
1055
|
+
packageNames,
|
|
1056
|
+
problems: options.problems,
|
|
1057
|
+
workspaceDepsByLabel
|
|
1058
|
+
});
|
|
1059
|
+
});
|
|
1060
|
+
const workspaceDeps = rawRule.deny.workspaceDeps;
|
|
1061
|
+
if (shouldNormalizeRuleKind(options.include, "workspaceDeps") && workspaceDeps !== void 0) if (!Array.isArray(workspaceDeps)) addRuleEntryConfigProblem(options.problems, [
|
|
1062
|
+
` field: graph.rules.${label}.deny.workspaceDeps`,
|
|
1063
|
+
` value: ${formatUnknownValue(workspaceDeps)}`,
|
|
1064
|
+
" reason: deny.workspaceDeps must be an array."
|
|
1065
|
+
]);
|
|
1066
|
+
else workspaceDeps.forEach((entry, index) => {
|
|
1067
|
+
addNormalizedWorkspaceDep({
|
|
1068
|
+
entry,
|
|
1069
|
+
fieldPrefix: `graph.rules.${label}.deny.workspaceDeps`,
|
|
1070
|
+
index,
|
|
1071
|
+
label,
|
|
1072
|
+
packageNames,
|
|
1073
|
+
problems: options.problems,
|
|
1074
|
+
workspaceDepsByLabel
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
const nodeBuiltins = rawRule.deny.nodeBuiltins;
|
|
1078
|
+
if (shouldNormalizeRuleKind(options.include, "nodeBuiltins") && nodeBuiltins !== void 0) if (!Array.isArray(nodeBuiltins)) addRuleEntryConfigProblem(options.problems, [
|
|
1079
|
+
` field: graph.rules.${label}.deny.nodeBuiltins`,
|
|
1080
|
+
` value: ${formatUnknownValue(nodeBuiltins)}`,
|
|
1081
|
+
" reason: deny.nodeBuiltins must be an array."
|
|
1082
|
+
]);
|
|
1083
|
+
else nodeBuiltins.forEach((entry, index) => {
|
|
1084
|
+
addNormalizedNodeBuiltin({
|
|
1085
|
+
entry,
|
|
1086
|
+
index,
|
|
1087
|
+
label,
|
|
1088
|
+
nodeBuiltinsByLabel,
|
|
1089
|
+
problems: options.problems
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
nodeBuiltinsByLabel,
|
|
1095
|
+
refsByLabel,
|
|
1096
|
+
workspaceDepsByLabel
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
function isNodeBuiltinSpecifier(specifier) {
|
|
1100
|
+
return nodeBuiltinNames.has(specifier);
|
|
1101
|
+
}
|
|
1102
|
+
function getDeniedRefRule(rules, label, targetProjectPath) {
|
|
1103
|
+
if (!label) return null;
|
|
1104
|
+
return rules.refsByLabel.get(label)?.get(targetProjectPath) ?? null;
|
|
1105
|
+
}
|
|
1106
|
+
function getDeniedWorkspaceDepRule(rules, label, targetPackageName) {
|
|
1107
|
+
if (!label) return null;
|
|
1108
|
+
return rules.workspaceDepsByLabel.get(label)?.get(targetPackageName) ?? null;
|
|
1109
|
+
}
|
|
1110
|
+
function getDeniedNodeBuiltinRule(rules, label, specifier) {
|
|
1111
|
+
if (!label || !isNodeBuiltinSpecifier(specifier)) return null;
|
|
1112
|
+
const normalizedSpecifier = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
|
1113
|
+
return rules.nodeBuiltinsByLabel.get(label)?.find((rule) => rule.matchAll || rule.name === normalizedSpecifier) ?? null;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/logger.ts
|
|
1118
|
+
const logger = createLogger({ main: "limina" });
|
|
1119
|
+
const CliLogger = logger.getLoggerByGroup("task.cli");
|
|
1120
|
+
const GraphLogger = logger.getLoggerByGroup("task.graph");
|
|
1121
|
+
const PackageLogger = logger.getLoggerByGroup("task.package");
|
|
1122
|
+
const PathsLogger = logger.getLoggerByGroup("task.paths");
|
|
1123
|
+
const ProofLogger = logger.getLoggerByGroup("task.proof");
|
|
1124
|
+
const SourceLogger = logger.getLoggerByGroup("task.source");
|
|
1125
|
+
const TypecheckLogger = logger.getLoggerByGroup("task.typecheck");
|
|
1126
|
+
function clearCliScreen() {
|
|
1127
|
+
if (!process.stdout.isTTY || process.env.CI) return;
|
|
1128
|
+
const repeatCount = (process.stdout.rows ?? 0) - 2;
|
|
1129
|
+
const blank = repeatCount > 0 ? "\n".repeat(repeatCount) : "";
|
|
1130
|
+
process.stdout.write(blank);
|
|
1131
|
+
readline.cursorTo(process.stdout, 0, 0);
|
|
1132
|
+
readline.clearScreenDown(process.stdout);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
//#endregion
|
|
1136
|
+
//#region src/commands/source.ts
|
|
1137
|
+
function findOwnerForFile(filePath, owners) {
|
|
1138
|
+
return owners.find((owner) => isPathInsideDirectory(filePath, owner.directory)) ?? null;
|
|
1139
|
+
}
|
|
1140
|
+
function isUrlOrDataOrFileSpecifier(specifier) {
|
|
1141
|
+
return specifier.startsWith("data:") || specifier.startsWith("file:") || specifier.startsWith("http:") || specifier.startsWith("https:");
|
|
1142
|
+
}
|
|
1143
|
+
function isPackageImportSpecifier(specifier) {
|
|
1144
|
+
return specifier.startsWith("#");
|
|
1145
|
+
}
|
|
1146
|
+
function isBarePackageSpecifier(specifier) {
|
|
1147
|
+
return !isRelativeSpecifier(specifier) && !isPackageImportSpecifier(specifier) && !isUrlOrDataOrFileSpecifier(specifier) && !path.isAbsolute(specifier);
|
|
1148
|
+
}
|
|
1149
|
+
function isDependencyAuthorized(manifest, packageName) {
|
|
1150
|
+
return Boolean(manifest.dependencies?.[packageName] || manifest.devDependencies?.[packageName]);
|
|
1151
|
+
}
|
|
1152
|
+
function findNonAuthorizingDependencySection(manifest, packageName) {
|
|
1153
|
+
if (manifest.peerDependencies?.[packageName]) return "peerDependencies";
|
|
1154
|
+
if (manifest.optionalDependencies?.[packageName]) return "optionalDependencies";
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
function packageImportsMatch(importsField, specifier) {
|
|
1158
|
+
if (!importsField || typeof importsField !== "object") return false;
|
|
1159
|
+
for (const key of Object.keys(importsField)) {
|
|
1160
|
+
if (key === specifier) return true;
|
|
1161
|
+
const wildcardIndex = key.indexOf("*");
|
|
1162
|
+
if (wildcardIndex === -1) continue;
|
|
1163
|
+
const prefix = key.slice(0, wildcardIndex);
|
|
1164
|
+
const suffix = key.slice(wildcardIndex + 1);
|
|
1165
|
+
if (specifier.startsWith(prefix) && specifier.endsWith(suffix)) return true;
|
|
1166
|
+
}
|
|
1167
|
+
return false;
|
|
1168
|
+
}
|
|
1169
|
+
function addProjectOwnerProblems(options) {
|
|
1170
|
+
const ownerPaths = /* @__PURE__ */ new Map();
|
|
1171
|
+
const missingOwnerFiles = [];
|
|
1172
|
+
for (const fileName of options.fileNames) {
|
|
1173
|
+
const owner = findOwnerForFile(fileName, options.owners);
|
|
1174
|
+
if (!owner) {
|
|
1175
|
+
missingOwnerFiles.push(fileName);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
ownerPaths.set(owner.packageJsonPath, owner);
|
|
1179
|
+
}
|
|
1180
|
+
if (missingOwnerFiles.length > 0) options.problems.push([
|
|
1181
|
+
"Source file has no package owner:",
|
|
1182
|
+
` ${options.role}: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
1183
|
+
" files:",
|
|
1184
|
+
...missingOwnerFiles.slice(0, 10).map((fileName) => ` - ${toRelativePath(options.config.rootDir, fileName)}`),
|
|
1185
|
+
...missingOwnerFiles.length > 10 ? [` ...and ${missingOwnerFiles.length - 10} more`] : [],
|
|
1186
|
+
" reason: every source file checked by Limina must be governed by the nearest package.json owner."
|
|
1187
|
+
].join("\n"));
|
|
1188
|
+
if (ownerPaths.size <= 1) return;
|
|
1189
|
+
options.problems.push([
|
|
1190
|
+
"Tsconfig source file set mixes package owners:",
|
|
1191
|
+
` ${options.role}: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
1192
|
+
" owners:",
|
|
1193
|
+
...[...ownerPaths.values()].map((owner) => ` - ${toRelativePath(options.config.rootDir, owner.packageJsonPath)}`),
|
|
1194
|
+
" reason: non-aggregator tsconfig leaves and their companion typecheck configs must stay within one nearest package.json owner scope."
|
|
1195
|
+
].join("\n"));
|
|
1196
|
+
}
|
|
1197
|
+
function addRelativeImportOwnerProblem(options) {
|
|
1198
|
+
options.problems.push([
|
|
1199
|
+
"Relative import escapes package owner scope:",
|
|
1200
|
+
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1201
|
+
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1202
|
+
` imported specifier: ${options.importRecord.specifier}`,
|
|
1203
|
+
` resolved file: ${toRelativePath(options.config.rootDir, options.resolvedFilePath)}`,
|
|
1204
|
+
...options.targetOwner ? [` target owner: ${toRelativePath(options.config.rootDir, options.targetOwner.packageJsonPath)}`] : [],
|
|
1205
|
+
" reason: relative source imports must not cross the nearest package.json owner boundary."
|
|
1206
|
+
].join("\n"));
|
|
1207
|
+
}
|
|
1208
|
+
function addPackageImportAuthorizationProblem(options) {
|
|
1209
|
+
const nonAuthorizingSection = findNonAuthorizingDependencySection(options.owner.manifest, options.packageName);
|
|
1210
|
+
options.problems.push([
|
|
1211
|
+
"Unauthorized bare package import:",
|
|
1212
|
+
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1213
|
+
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1214
|
+
` imported specifier: ${options.importRecord.specifier}`,
|
|
1215
|
+
` package: ${options.packageName}`,
|
|
1216
|
+
...options.workspacePackage ? [` workspace package: ${options.workspacePackage.name}`] : [],
|
|
1217
|
+
...nonAuthorizingSection ? [` found in: ${nonAuthorizingSection}`] : [],
|
|
1218
|
+
" reason: source imports must be authorized by the nearest package.json dependencies or devDependencies."
|
|
1219
|
+
].join("\n"));
|
|
1220
|
+
}
|
|
1221
|
+
function addNodeBuiltinDenyProblem(options) {
|
|
1222
|
+
options.problems.push([
|
|
1223
|
+
"Denied Node builtin import:",
|
|
1224
|
+
` rule: ${options.project.label}`,
|
|
1225
|
+
` importing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
|
|
1226
|
+
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1227
|
+
` imported specifier: ${options.importRecord.specifier}`,
|
|
1228
|
+
` denied builtin: ${options.ruleName}`,
|
|
1229
|
+
` reason: ${options.reason}`
|
|
1230
|
+
].join("\n"));
|
|
1231
|
+
}
|
|
1232
|
+
function addPackageImportProblem(options) {
|
|
1233
|
+
if (!packageImportsMatch(options.owner.manifest.imports, options.importRecord.specifier)) {
|
|
1234
|
+
options.problems.push([
|
|
1235
|
+
"Unauthorized package import specifier:",
|
|
1236
|
+
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1237
|
+
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1238
|
+
` imported specifier: ${options.importRecord.specifier}`,
|
|
1239
|
+
" reason: #... package imports must match the nearest package.json imports field."
|
|
1240
|
+
].join("\n"));
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (!options.resolvedFilePath) {
|
|
1244
|
+
options.problems.push([
|
|
1245
|
+
"Unresolved package import specifier:",
|
|
1246
|
+
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1247
|
+
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1248
|
+
` imported specifier: ${options.importRecord.specifier}`,
|
|
1249
|
+
" reason: matched #... package imports must resolve to a file within the same package owner scope."
|
|
1250
|
+
].join("\n"));
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (!isPathInsideDirectory(options.resolvedFilePath, options.owner.directory)) options.problems.push([
|
|
1254
|
+
"Package import escapes package owner scope:",
|
|
1255
|
+
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1256
|
+
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1257
|
+
` imported specifier: ${options.importRecord.specifier}`,
|
|
1258
|
+
` resolved file: ${toRelativePath(options.config.rootDir, options.resolvedFilePath)}`,
|
|
1259
|
+
" reason: #... package imports must resolve within the nearest package.json owner scope."
|
|
1260
|
+
].join("\n"));
|
|
1261
|
+
}
|
|
1262
|
+
function createSourceProjectEntries(config, projects) {
|
|
1263
|
+
return projects.filter((project) => isDtsProjectConfig(project.configPath)).map((project) => {
|
|
1264
|
+
const typecheckConfigPath = getTypecheckConfigPath(project.configPath);
|
|
1265
|
+
const fileNames = new Set(project.fileNames);
|
|
1266
|
+
if (existsSync(typecheckConfigPath)) for (const fileName of parseProject(config, typecheckConfigPath).fileNames) fileNames.add(fileName);
|
|
1267
|
+
return {
|
|
1268
|
+
fileNames: [...fileNames].sort(),
|
|
1269
|
+
project
|
|
1270
|
+
};
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
async function runSourceCheckInternal(config, options = {}) {
|
|
1274
|
+
const graphRoute = collectGraphProjectRoute(config);
|
|
1275
|
+
const projectPaths = graphRoute.projectPaths;
|
|
1276
|
+
const projects = projectPaths.map((projectPath) => parseProject(config, projectPath));
|
|
1277
|
+
const sourceProjectEntries = createSourceProjectEntries(config, projects);
|
|
1278
|
+
const packages = await collectWorkspacePackages(config);
|
|
1279
|
+
const packageOwners = await collectPackageOwners(config);
|
|
1280
|
+
const problems = [...graphRoute.problems];
|
|
1281
|
+
const graphRules = normalizeGraphRules({
|
|
1282
|
+
config,
|
|
1283
|
+
include: {
|
|
1284
|
+
nodeBuiltins: true,
|
|
1285
|
+
refs: false,
|
|
1286
|
+
workspaceDeps: false
|
|
1287
|
+
},
|
|
1288
|
+
packages,
|
|
1289
|
+
problems,
|
|
1290
|
+
projectPaths
|
|
1291
|
+
});
|
|
1292
|
+
for (const project of projects) {
|
|
1293
|
+
if (project.labelProblem) problems.push(project.labelProblem);
|
|
1294
|
+
if (!isDtsProjectConfig(project.configPath)) continue;
|
|
1295
|
+
addProjectOwnerProblems({
|
|
1296
|
+
config,
|
|
1297
|
+
configPath: project.configPath,
|
|
1298
|
+
fileNames: project.fileNames,
|
|
1299
|
+
owners: packageOwners,
|
|
1300
|
+
problems,
|
|
1301
|
+
role: "declaration leaf"
|
|
1302
|
+
});
|
|
1303
|
+
const typecheckConfigPath = getTypecheckConfigPath(project.configPath);
|
|
1304
|
+
if (existsSync(typecheckConfigPath)) addProjectOwnerProblems({
|
|
1305
|
+
config,
|
|
1306
|
+
configPath: typecheckConfigPath,
|
|
1307
|
+
fileNames: parseProject(config, typecheckConfigPath).fileNames,
|
|
1308
|
+
owners: packageOwners,
|
|
1309
|
+
problems,
|
|
1310
|
+
role: "typecheck companion"
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
for (const { fileNames, project } of sourceProjectEntries) for (const filePath of fileNames) {
|
|
1314
|
+
const owner = findOwnerForFile(filePath, packageOwners);
|
|
1315
|
+
if (!owner) continue;
|
|
1316
|
+
for (const importRecord of collectImportsFromFile(filePath)) {
|
|
1317
|
+
const resolvedFilePath = resolveInternalImport(importRecord.specifier, filePath, project.options);
|
|
1318
|
+
if (isRelativeSpecifier(importRecord.specifier)) {
|
|
1319
|
+
if (!resolvedFilePath) continue;
|
|
1320
|
+
const targetOwner = findOwnerForFile(resolvedFilePath, packageOwners);
|
|
1321
|
+
if (targetOwner?.packageJsonPath !== owner.packageJsonPath) addRelativeImportOwnerProblem({
|
|
1322
|
+
config,
|
|
1323
|
+
importRecord,
|
|
1324
|
+
owner,
|
|
1325
|
+
problems,
|
|
1326
|
+
resolvedFilePath,
|
|
1327
|
+
targetOwner
|
|
1328
|
+
});
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
if (isPackageImportSpecifier(importRecord.specifier)) {
|
|
1332
|
+
addPackageImportProblem({
|
|
1333
|
+
config,
|
|
1334
|
+
importRecord,
|
|
1335
|
+
owner,
|
|
1336
|
+
problems,
|
|
1337
|
+
resolvedFilePath
|
|
1338
|
+
});
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
if (isUrlOrDataOrFileSpecifier(importRecord.specifier)) continue;
|
|
1342
|
+
if (!isBarePackageSpecifier(importRecord.specifier)) continue;
|
|
1343
|
+
if (isNodeBuiltinSpecifier(importRecord.specifier)) {
|
|
1344
|
+
const deniedBuiltinRule = getDeniedNodeBuiltinRule(graphRules, project.label, importRecord.specifier);
|
|
1345
|
+
if (deniedBuiltinRule) addNodeBuiltinDenyProblem({
|
|
1346
|
+
config,
|
|
1347
|
+
importRecord,
|
|
1348
|
+
problems,
|
|
1349
|
+
project,
|
|
1350
|
+
reason: deniedBuiltinRule.reason,
|
|
1351
|
+
ruleName: deniedBuiltinRule.matchAll ? "node:*" : deniedBuiltinRule.name
|
|
1352
|
+
});
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
const packageName = getPackageRootSpecifier(importRecord.specifier);
|
|
1356
|
+
if (owner.name === packageName) continue;
|
|
1357
|
+
const workspacePackage = packages.find((candidate) => candidate.name === packageName) ?? null;
|
|
1358
|
+
if (isDependencyAuthorized(owner.manifest, packageName)) continue;
|
|
1359
|
+
addPackageImportAuthorizationProblem({
|
|
1360
|
+
config,
|
|
1361
|
+
importRecord,
|
|
1362
|
+
owner,
|
|
1363
|
+
packageName,
|
|
1364
|
+
problems,
|
|
1365
|
+
workspacePackage
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
if (problems.length > 0) {
|
|
1370
|
+
SourceLogger.error(problems.join("\n\n"));
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
if (options.logSuccess ?? true) SourceLogger.success(`Checked ${sourceProjectEntries.length} source project owners; package scopes are valid.`);
|
|
1374
|
+
return true;
|
|
1375
|
+
}
|
|
1376
|
+
async function runSourceCheck(config, options = {}) {
|
|
1377
|
+
if (options.clearScreen ?? true) clearCliScreen();
|
|
1378
|
+
const elapsed = createElapsedTimer();
|
|
1379
|
+
const task = options.flow?.start("source check", { depth: options.flowDepth ?? 0 });
|
|
1380
|
+
SourceLogger.info("source check started");
|
|
1381
|
+
try {
|
|
1382
|
+
const logSuccess = !options.flow?.interactive;
|
|
1383
|
+
const passed = await runSourceCheckInternal(config, { logSuccess });
|
|
1384
|
+
if (passed) {
|
|
1385
|
+
if (logSuccess) SourceLogger.success("source check finished", elapsed());
|
|
1386
|
+
task?.pass();
|
|
1387
|
+
} else {
|
|
1388
|
+
SourceLogger.error("source check finished with failures", elapsed());
|
|
1389
|
+
task?.fail("source check finished with failures");
|
|
1390
|
+
}
|
|
1391
|
+
return passed;
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
SourceLogger.error(`source check failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
1394
|
+
task?.fail("source check failed", { error });
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/commands/typecheck.ts
|
|
1401
|
+
function normalizeConcurrency(value) {
|
|
1402
|
+
if (value === void 0) return Math.max(1, availableParallelism());
|
|
1403
|
+
if (!Number.isInteger(value) || value < 1) throw new Error("Typecheck concurrency must be a positive integer.");
|
|
1404
|
+
return value;
|
|
1405
|
+
}
|
|
1406
|
+
function getExecutionCheckers(options) {
|
|
1407
|
+
return options.checkers.filter((checker) => {
|
|
1408
|
+
return getCheckerAdapter(checker.preset)?.supportedExecutions.includes(options.executionKind);
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
function collectCheckerPeerDependencyProblems(options) {
|
|
1412
|
+
const missingDependencies = collectMissingCheckerPeerDependencies({
|
|
1413
|
+
checkers: options.checkers,
|
|
1414
|
+
projectRootDir: options.projectRootDir,
|
|
1415
|
+
resolvePackage: options.resolvePackage
|
|
1416
|
+
});
|
|
1417
|
+
return missingDependencies.length === 0 ? [] : [formatMissingCheckerPeerDependencies(missingDependencies)];
|
|
1418
|
+
}
|
|
1419
|
+
function createCheckerTarget(options) {
|
|
1420
|
+
const adapter = getCheckerAdapter(options.checker.preset);
|
|
1421
|
+
if (!adapter) throw new Error(`Checker "${options.checker.name}" uses unsupported preset "${options.checker.preset}".`);
|
|
1422
|
+
return {
|
|
1423
|
+
...adapter.createCommandTarget(options),
|
|
1424
|
+
checkerName: options.checker.name,
|
|
1425
|
+
configPath: options.configPath,
|
|
1426
|
+
cwd: options.projectRootDir,
|
|
1427
|
+
executionKind: options.executionKind
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
function collectCompanionTypecheckTargets(options) {
|
|
1431
|
+
const entryConfigPath = resolveProjectConfigPath(options.projectRootDir, options.checker.entry);
|
|
1432
|
+
if (!existsSync(entryConfigPath)) return {
|
|
1433
|
+
entryConfigPath,
|
|
1434
|
+
problems: [[
|
|
1435
|
+
"Checker entry references a missing tsconfig:",
|
|
1436
|
+
` checker: ${options.checker.name}`,
|
|
1437
|
+
` config: ${toRelativePath(options.projectRootDir, entryConfigPath)}`
|
|
1438
|
+
].join("\n")],
|
|
1439
|
+
targetProjectPaths: []
|
|
1440
|
+
};
|
|
1441
|
+
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
1442
|
+
rootConfigPath: entryConfigPath,
|
|
1443
|
+
rootDir: options.projectRootDir
|
|
1444
|
+
});
|
|
1445
|
+
const dtsConfigPaths = routeCollection.projectPaths.filter(isDtsConfigPath);
|
|
1446
|
+
const targetProjectPaths = [...new Set(dtsConfigPaths.map(getDtsCompanionConfigPath))].sort();
|
|
1447
|
+
const problems = [...routeCollection.problems];
|
|
1448
|
+
if (dtsConfigPaths.length === 0) problems.push([
|
|
1449
|
+
"Checker entry has no declaration leaf targets:",
|
|
1450
|
+
` checker: ${options.checker.name}`,
|
|
1451
|
+
` entry: ${toRelativePath(options.projectRootDir, entryConfigPath)}`,
|
|
1452
|
+
" reason: checker:typecheck derives targets from tsconfig*.dts.json leaves reachable from the checker entry."
|
|
1453
|
+
].join("\n"));
|
|
1454
|
+
for (const configPath of targetProjectPaths) {
|
|
1455
|
+
if (existsSync(configPath)) continue;
|
|
1456
|
+
problems.push([
|
|
1457
|
+
"DTS leaf companion config is missing:",
|
|
1458
|
+
` checker: ${options.checker.name}`,
|
|
1459
|
+
` expected: ${toRelativePath(options.projectRootDir, configPath)}`,
|
|
1460
|
+
" reason: checker:typecheck runs the strict local tsconfig companion for each reachable declaration leaf."
|
|
1461
|
+
].join("\n"));
|
|
1462
|
+
}
|
|
1463
|
+
return {
|
|
1464
|
+
entryConfigPath,
|
|
1465
|
+
problems,
|
|
1466
|
+
targetProjectPaths
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function createDefaultRunner() {
|
|
1470
|
+
return async (target) => await new Promise((resolve) => {
|
|
1471
|
+
const child = spawn(target.command, target.args, {
|
|
1472
|
+
cwd: target.cwd,
|
|
1473
|
+
shell: process.platform === "win32",
|
|
1474
|
+
stdio: "inherit"
|
|
1475
|
+
});
|
|
1476
|
+
child.on("error", (error) => {
|
|
1477
|
+
resolve({
|
|
1478
|
+
configPath: target.configPath,
|
|
1479
|
+
error,
|
|
1480
|
+
status: 1
|
|
1481
|
+
});
|
|
1482
|
+
});
|
|
1483
|
+
child.on("close", (code) => {
|
|
1484
|
+
resolve({
|
|
1485
|
+
configPath: target.configPath,
|
|
1486
|
+
status: code ?? 1
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
async function runWithConcurrency(targets, concurrency, runner, options = {}) {
|
|
1492
|
+
const results = new Array(targets.length);
|
|
1493
|
+
let nextIndex = 0;
|
|
1494
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, targets.length) }).map(async () => {
|
|
1495
|
+
for (;;) {
|
|
1496
|
+
const targetIndex = nextIndex;
|
|
1497
|
+
nextIndex += 1;
|
|
1498
|
+
if (targetIndex >= targets.length) return;
|
|
1499
|
+
try {
|
|
1500
|
+
const target = targets[targetIndex];
|
|
1501
|
+
options.onTargetStart?.(target);
|
|
1502
|
+
results[targetIndex] = await runner(target);
|
|
1503
|
+
options.onTargetResult?.(target, results[targetIndex]);
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
const target = targets[targetIndex];
|
|
1506
|
+
results[targetIndex] = {
|
|
1507
|
+
configPath: target.configPath,
|
|
1508
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
1509
|
+
status: 1
|
|
1510
|
+
};
|
|
1511
|
+
options.onTargetResult?.(target, results[targetIndex]);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}));
|
|
1515
|
+
return results;
|
|
1516
|
+
}
|
|
1517
|
+
async function runCheckerTypecheckInternal(options) {
|
|
1518
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1519
|
+
const projectRootDir = normalizeAbsolutePath(options.config.rootDir);
|
|
1520
|
+
const allCheckers = getActiveCheckers(options.config);
|
|
1521
|
+
const checkers = getExecutionCheckers({
|
|
1522
|
+
checkers: allCheckers,
|
|
1523
|
+
executionKind: "typecheck"
|
|
1524
|
+
});
|
|
1525
|
+
const flowDepth = options.flowDepth ?? 0;
|
|
1526
|
+
const concurrency = normalizeConcurrency(options.concurrency);
|
|
1527
|
+
const problems = collectCheckerPeerDependencyProblems({
|
|
1528
|
+
checkers: allCheckers,
|
|
1529
|
+
projectRootDir,
|
|
1530
|
+
resolvePackage: options.checkerPackageResolver
|
|
1531
|
+
});
|
|
1532
|
+
const rootConfigPaths = [];
|
|
1533
|
+
const targetProjectPaths = [];
|
|
1534
|
+
const targets = [];
|
|
1535
|
+
if (problems.length > 0) {
|
|
1536
|
+
options.flow?.fail("checker dependency preflight failed", { depth: flowDepth + 1 });
|
|
1537
|
+
TypecheckLogger.error(problems.join("\n\n"));
|
|
1538
|
+
return {
|
|
1539
|
+
passed: false,
|
|
1540
|
+
projectRootDir,
|
|
1541
|
+
results: [],
|
|
1542
|
+
rootConfigPaths,
|
|
1543
|
+
targetProjectPaths
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
if (checkers.length === 0) problems.push(["No checker typecheck entries configured:", " reason: configure config.checkers.<name>.entry with a preset that supports checker:typecheck."].join("\n"));
|
|
1547
|
+
for (const checker of checkers) {
|
|
1548
|
+
const targetCollection = collectCompanionTypecheckTargets({
|
|
1549
|
+
checker,
|
|
1550
|
+
projectRootDir
|
|
1551
|
+
});
|
|
1552
|
+
problems.push(...targetCollection.problems);
|
|
1553
|
+
rootConfigPaths.push(targetCollection.entryConfigPath);
|
|
1554
|
+
targetProjectPaths.push(...targetCollection.targetProjectPaths);
|
|
1555
|
+
targets.push(...targetCollection.targetProjectPaths.map((configPath) => createCheckerTarget({
|
|
1556
|
+
checker,
|
|
1557
|
+
commandOverride: options.tscCommand,
|
|
1558
|
+
configPath,
|
|
1559
|
+
executionKind: "typecheck",
|
|
1560
|
+
projectRootDir
|
|
1561
|
+
})));
|
|
1562
|
+
}
|
|
1563
|
+
if (problems.length > 0) {
|
|
1564
|
+
options.flow?.fail("typecheck target discovery failed", { depth: flowDepth + 1 });
|
|
1565
|
+
TypecheckLogger.error(problems.join("\n\n"));
|
|
1566
|
+
return {
|
|
1567
|
+
passed: false,
|
|
1568
|
+
projectRootDir,
|
|
1569
|
+
results: [],
|
|
1570
|
+
rootConfigPaths,
|
|
1571
|
+
targetProjectPaths
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
options.flow?.info(`found ${targets.length} typecheck target config(s) across ${checkers.length} checker(s); concurrency ${concurrency}`, { depth: flowDepth + 1 });
|
|
1575
|
+
TypecheckLogger.info([
|
|
1576
|
+
`Running checker typechecks for ${targets.length} target config(s).`,
|
|
1577
|
+
`CWD: ${toRelativePath(cwd, projectRootDir)}`,
|
|
1578
|
+
`Entries: ${rootConfigPaths.map((configPath) => toRelativePath(projectRootDir, configPath)).join(", ")}`
|
|
1579
|
+
].join("\n"));
|
|
1580
|
+
const targetFlowStates = /* @__PURE__ */ new Map();
|
|
1581
|
+
const results = await runWithConcurrency(targets, concurrency, options.runner ?? createDefaultRunner(), {
|
|
1582
|
+
onTargetResult: (target, result) => {
|
|
1583
|
+
if (!options.flow) return;
|
|
1584
|
+
const flowState = targetFlowStates.get(target.configPath);
|
|
1585
|
+
if (!flowState) return;
|
|
1586
|
+
const resultOptions = {
|
|
1587
|
+
depth: flowDepth + 1,
|
|
1588
|
+
elapsedTimeMs: performance.now() - flowState.startedAt
|
|
1589
|
+
};
|
|
1590
|
+
if (result.status === 0) options.flow.pass(flowState.label, resultOptions);
|
|
1591
|
+
else {
|
|
1592
|
+
const suffix = result.error ? formatErrorMessage$1(result.error) : `exited with code ${result.status}`;
|
|
1593
|
+
options.flow.fail(flowState.label, {
|
|
1594
|
+
...resultOptions,
|
|
1595
|
+
error: suffix
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
},
|
|
1599
|
+
onTargetStart: (target) => {
|
|
1600
|
+
if (!options.flow) return;
|
|
1601
|
+
targetFlowStates.set(target.configPath, {
|
|
1602
|
+
label: target.label ?? `checker: ${toRelativePath(projectRootDir, target.configPath)}`,
|
|
1603
|
+
startedAt: performance.now()
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
const failedResults = results.filter((result) => result.status !== 0);
|
|
1608
|
+
if (failedResults.length > 0) TypecheckLogger.error([`Typecheck failed for ${failedResults.length} config(s):`, ...failedResults.map((result) => {
|
|
1609
|
+
const suffix = result.error ? `: ${formatErrorMessage$1(result.error)}` : ` exited with code ${result.status}`;
|
|
1610
|
+
return ` ${toRelativePath(projectRootDir, result.configPath)}${suffix}`;
|
|
1611
|
+
})].join("\n"));
|
|
1612
|
+
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} typecheck target config(s) from ${rootConfigPaths.length} checker entry(s).`);
|
|
1613
|
+
return {
|
|
1614
|
+
passed: failedResults.length === 0,
|
|
1615
|
+
projectRootDir,
|
|
1616
|
+
results,
|
|
1617
|
+
rootConfigPaths,
|
|
1618
|
+
targetProjectPaths
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
async function runCheckerTypecheck(options) {
|
|
1622
|
+
if (options.clearScreen ?? true) clearCliScreen();
|
|
1623
|
+
const elapsed = createElapsedTimer();
|
|
1624
|
+
const task = options.flow?.start("checker typecheck", { depth: options.flowDepth ?? 0 });
|
|
1625
|
+
TypecheckLogger.info("checker typecheck started");
|
|
1626
|
+
try {
|
|
1627
|
+
const result = await runCheckerTypecheckInternal(options);
|
|
1628
|
+
if (result.passed) {
|
|
1629
|
+
if (!options.flow?.interactive) TypecheckLogger.success("checker typecheck finished", elapsed());
|
|
1630
|
+
task?.pass();
|
|
1631
|
+
} else {
|
|
1632
|
+
TypecheckLogger.error("checker typecheck finished with failures", elapsed());
|
|
1633
|
+
task?.fail("checker typecheck finished with failures");
|
|
1634
|
+
}
|
|
1635
|
+
return result;
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
TypecheckLogger.error(`checker typecheck failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
1638
|
+
task?.fail("checker typecheck failed", { error });
|
|
1639
|
+
throw error;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
async function runCheckerBuildInternal(options) {
|
|
1643
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1644
|
+
const projectRootDir = normalizeAbsolutePath(options.config.rootDir);
|
|
1645
|
+
const allCheckers = getActiveCheckers(options.config);
|
|
1646
|
+
const checkers = getExecutionCheckers({
|
|
1647
|
+
checkers: allCheckers,
|
|
1648
|
+
executionKind: "build"
|
|
1649
|
+
});
|
|
1650
|
+
const flowDepth = options.flowDepth ?? 0;
|
|
1651
|
+
const rootConfigPaths = [];
|
|
1652
|
+
const problems = collectCheckerPeerDependencyProblems({
|
|
1653
|
+
checkers: allCheckers,
|
|
1654
|
+
projectRootDir,
|
|
1655
|
+
resolvePackage: options.checkerPackageResolver
|
|
1656
|
+
});
|
|
1657
|
+
if (problems.length > 0) {
|
|
1658
|
+
options.flow?.fail("checker dependency preflight failed", { depth: flowDepth + 1 });
|
|
1659
|
+
TypecheckLogger.error(problems.join("\n\n"));
|
|
1660
|
+
return {
|
|
1661
|
+
passed: false,
|
|
1662
|
+
projectRootDir,
|
|
1663
|
+
rootConfigPaths
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
const targets = checkers.flatMap((checker) => {
|
|
1667
|
+
const configPath = resolveProjectConfigPath(projectRootDir, checker.entry);
|
|
1668
|
+
rootConfigPaths.push(configPath);
|
|
1669
|
+
return [createCheckerTarget({
|
|
1670
|
+
checker,
|
|
1671
|
+
commandOverride: options.tscCommand,
|
|
1672
|
+
configPath,
|
|
1673
|
+
executionKind: "build",
|
|
1674
|
+
projectRootDir
|
|
1675
|
+
})];
|
|
1676
|
+
});
|
|
1677
|
+
options.flow?.info(`found ${targets.length} checker build entry(s)`, { depth: flowDepth + 1 });
|
|
1678
|
+
TypecheckLogger.info([
|
|
1679
|
+
`Running build checks for ${targets.length} checker entry(s).`,
|
|
1680
|
+
`CWD: ${toRelativePath(cwd, projectRootDir)}`,
|
|
1681
|
+
`Entries: ${rootConfigPaths.map((configPath) => toRelativePath(projectRootDir, configPath)).join(", ")}`
|
|
1682
|
+
].join("\n"));
|
|
1683
|
+
const targetTasks = /* @__PURE__ */ new Map();
|
|
1684
|
+
const failedResults = (await runWithConcurrency(targets, 1, options.runner ?? createDefaultRunner(), {
|
|
1685
|
+
onTargetResult: (target, result) => {
|
|
1686
|
+
const task = targetTasks.get(target.configPath);
|
|
1687
|
+
if (!task) return;
|
|
1688
|
+
if (result.status === 0) task.pass();
|
|
1689
|
+
else {
|
|
1690
|
+
const suffix = result.error ? formatErrorMessage$1(result.error) : `exited with code ${result.status}`;
|
|
1691
|
+
task.fail(void 0, { error: suffix });
|
|
1692
|
+
}
|
|
1693
|
+
},
|
|
1694
|
+
onTargetStart: (target) => {
|
|
1695
|
+
if (!options.flow) return;
|
|
1696
|
+
targetTasks.set(target.configPath, options.flow.start(target.label ?? `checker build: ${toRelativePath(projectRootDir, target.configPath)}`, {
|
|
1697
|
+
collapseOnSuccess: false,
|
|
1698
|
+
depth: flowDepth + 1
|
|
1699
|
+
}));
|
|
1700
|
+
}
|
|
1701
|
+
})).filter((result) => result.status !== 0);
|
|
1702
|
+
const passed = failedResults.length === 0;
|
|
1703
|
+
if (!passed) TypecheckLogger.error(["build checks failed:", ...failedResults.map((result) => {
|
|
1704
|
+
const suffix = result.error ? `: ${formatErrorMessage$1(result.error)}` : ` exited with code ${result.status}`;
|
|
1705
|
+
return ` ${toRelativePath(projectRootDir, result.configPath)}${suffix}`;
|
|
1706
|
+
})].join("\n"));
|
|
1707
|
+
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} checker build entry(s).`);
|
|
1708
|
+
return {
|
|
1709
|
+
passed,
|
|
1710
|
+
projectRootDir,
|
|
1711
|
+
rootConfigPaths
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
async function runCheckerBuild(options) {
|
|
1715
|
+
if (options.clearScreen ?? true) clearCliScreen();
|
|
1716
|
+
const elapsed = createElapsedTimer();
|
|
1717
|
+
const task = options.flow?.start("checker build", { depth: options.flowDepth ?? 0 });
|
|
1718
|
+
TypecheckLogger.info("checker build started");
|
|
1719
|
+
try {
|
|
1720
|
+
const result = await runCheckerBuildInternal(options);
|
|
1721
|
+
if (result.passed) {
|
|
1722
|
+
if (!options.flow?.interactive) TypecheckLogger.success("checker build finished", elapsed());
|
|
1723
|
+
task?.pass();
|
|
1724
|
+
} else {
|
|
1725
|
+
TypecheckLogger.error("checker build finished with failures", elapsed());
|
|
1726
|
+
task?.fail("checker build finished with failures");
|
|
1727
|
+
}
|
|
1728
|
+
return result;
|
|
1729
|
+
} catch (error) {
|
|
1730
|
+
TypecheckLogger.error(`checker build failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
1731
|
+
task?.fail("checker build failed", { error });
|
|
1732
|
+
throw error;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
//#endregion
|
|
1737
|
+
//#region src/flow.ts
|
|
1738
|
+
const DEFAULT_CI_ENV_VALUES = ["1", "true"];
|
|
1739
|
+
const DEFAULT_TERMINAL_COLUMNS = 80;
|
|
1740
|
+
const ANSI_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/gu;
|
|
1741
|
+
const ANSI_RESET = "\x1B[0m";
|
|
1742
|
+
const ANSI_GREEN = "\x1B[32m";
|
|
1743
|
+
const ANSI_RED = "\x1B[31m";
|
|
1744
|
+
const ANSI_YELLOW = "\x1B[33m";
|
|
1745
|
+
const FLOW_SYMBOL_BY_STATUS = {
|
|
1746
|
+
fail: "■",
|
|
1747
|
+
info: "│",
|
|
1748
|
+
pass: "◆",
|
|
1749
|
+
skip: "◇",
|
|
1750
|
+
start: "◇",
|
|
1751
|
+
warn: "▲"
|
|
1752
|
+
};
|
|
1753
|
+
function isCiEnvironment(env) {
|
|
1754
|
+
return DEFAULT_CI_ENV_VALUES.includes(String(env.CI).toLowerCase());
|
|
1755
|
+
}
|
|
1756
|
+
function formatElapsedTime(milliseconds) {
|
|
1757
|
+
if (milliseconds < 1e3) return `${Math.round(milliseconds)}ms`;
|
|
1758
|
+
return `${(milliseconds / 1e3).toFixed(2)}s`;
|
|
1759
|
+
}
|
|
1760
|
+
function formatMessageWithElapsed(message, elapsedTimeMs) {
|
|
1761
|
+
return typeof elapsedTimeMs === "number" ? `${message} (${formatElapsedTime(elapsedTimeMs)})` : message;
|
|
1762
|
+
}
|
|
1763
|
+
function formatFailureMessage(message, error) {
|
|
1764
|
+
if (error === void 0) return message;
|
|
1765
|
+
const detail = formatErrorMessage(error).replace(/\s+/gu, " ").trim();
|
|
1766
|
+
return detail ? `${message}: ${detail}` : message;
|
|
1767
|
+
}
|
|
1768
|
+
function indentMessage(message, depth) {
|
|
1769
|
+
if (depth <= 0) return message;
|
|
1770
|
+
return `${" ".repeat(depth)}${message}`;
|
|
1771
|
+
}
|
|
1772
|
+
function writeLine(output, message) {
|
|
1773
|
+
output.write(`${message}\n`);
|
|
1774
|
+
}
|
|
1775
|
+
function toWritableText(chunk) {
|
|
1776
|
+
if (chunk instanceof Uint8Array) return Buffer.from(chunk).toString();
|
|
1777
|
+
return String(chunk);
|
|
1778
|
+
}
|
|
1779
|
+
function stripControlSequences(text) {
|
|
1780
|
+
return text.replace(ANSI_PATTERN, "").replaceAll("\r", "");
|
|
1781
|
+
}
|
|
1782
|
+
function colorInteractiveSymbol(status, symbol) {
|
|
1783
|
+
if (status === "pass") return `${ANSI_GREEN}${symbol}${ANSI_RESET}`;
|
|
1784
|
+
if (status === "fail") return `${ANSI_RED}${symbol}${ANSI_RESET}`;
|
|
1785
|
+
if (status === "warn") return `${ANSI_YELLOW}${symbol}${ANSI_RESET}`;
|
|
1786
|
+
return symbol;
|
|
1787
|
+
}
|
|
1788
|
+
var LiminaFlowReporter = class {
|
|
1789
|
+
#clack;
|
|
1790
|
+
#interactive;
|
|
1791
|
+
#output;
|
|
1792
|
+
#stderr;
|
|
1793
|
+
#stdout;
|
|
1794
|
+
#tracksProcessWrites;
|
|
1795
|
+
#interactiveHistory = [];
|
|
1796
|
+
#restoreWriteStreams;
|
|
1797
|
+
#trackedTaskCount = 0;
|
|
1798
|
+
#terminalColumn = 0;
|
|
1799
|
+
#terminalLineCount = 0;
|
|
1800
|
+
constructor(options = {}) {
|
|
1801
|
+
const env = options.env ?? process.env;
|
|
1802
|
+
const stdout = options.stdout ?? process.stdout;
|
|
1803
|
+
this.#interactive = options.forceTty ?? Boolean(stdout.isTTY && !isCiEnvironment(env));
|
|
1804
|
+
this.#clack = options.clack ?? prompts;
|
|
1805
|
+
this.#output = options.output ?? { write: (message) => {
|
|
1806
|
+
if (typeof stdout.write === "function") {
|
|
1807
|
+
stdout.write(message);
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
process.stdout.write(message);
|
|
1811
|
+
} };
|
|
1812
|
+
this.#stdout = stdout;
|
|
1813
|
+
this.#stderr = options.stderr ?? process.stderr;
|
|
1814
|
+
this.#tracksProcessWrites = this.#interactive && options.output === void 0;
|
|
1815
|
+
}
|
|
1816
|
+
get interactive() {
|
|
1817
|
+
return this.#interactive;
|
|
1818
|
+
}
|
|
1819
|
+
intro(message) {
|
|
1820
|
+
if (this.#interactive) {
|
|
1821
|
+
this.#clack.intro(message);
|
|
1822
|
+
this.#interactiveHistory.push(`┌ ${message}`);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
writeLine(this.#output, `[start] ${message}`);
|
|
1826
|
+
}
|
|
1827
|
+
outro(message) {
|
|
1828
|
+
if (this.#interactive) {
|
|
1829
|
+
this.#clack.outro(message);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
writeLine(this.#output, `[done] ${message}`);
|
|
1833
|
+
}
|
|
1834
|
+
start(message, options = {}) {
|
|
1835
|
+
const depth = options.depth ?? 0;
|
|
1836
|
+
const collapseOnSuccess = options.collapseOnSuccess ?? true;
|
|
1837
|
+
const shouldTrackTask = this.#interactive && collapseOnSuccess;
|
|
1838
|
+
const persistStart = !shouldTrackTask && this.#trackedTaskCount === 0;
|
|
1839
|
+
const startLine = this.#terminalLineCount;
|
|
1840
|
+
const startTime = performance.now();
|
|
1841
|
+
let completed = false;
|
|
1842
|
+
if (shouldTrackTask) this.#beginTerminalTracking();
|
|
1843
|
+
const persistedStartIndex = this.#emit("start", message, options, { persistInteractive: persistStart });
|
|
1844
|
+
const finishTrackedTask = () => {
|
|
1845
|
+
if (!shouldTrackTask || completed) return;
|
|
1846
|
+
completed = true;
|
|
1847
|
+
this.#endTerminalTracking();
|
|
1848
|
+
};
|
|
1849
|
+
return {
|
|
1850
|
+
fail: (nextMessage, nextOptions) => {
|
|
1851
|
+
this.#emit("fail", formatFailureMessage(nextMessage ?? message, nextOptions?.error), {
|
|
1852
|
+
...nextOptions,
|
|
1853
|
+
depth,
|
|
1854
|
+
elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
|
|
1855
|
+
}, { persistInteractive: true });
|
|
1856
|
+
if (!this.#interactive) return;
|
|
1857
|
+
finishTrackedTask();
|
|
1858
|
+
},
|
|
1859
|
+
info: (nextMessage, nextOptions) => {
|
|
1860
|
+
this.info(nextMessage, {
|
|
1861
|
+
...nextOptions,
|
|
1862
|
+
depth: nextOptions?.depth ?? depth + 1
|
|
1863
|
+
});
|
|
1864
|
+
},
|
|
1865
|
+
pass: (nextMessage, nextOptions) => {
|
|
1866
|
+
const passOptions = {
|
|
1867
|
+
...nextOptions,
|
|
1868
|
+
depth,
|
|
1869
|
+
elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
|
|
1870
|
+
};
|
|
1871
|
+
const persistInteractive = shouldTrackTask ? this.#trackedTaskCount <= 1 : this.#trackedTaskCount === 0;
|
|
1872
|
+
if (shouldTrackTask) this.#clearInteractiveTaskBlock(startLine, { redrawHistory: persistInteractive && depth === 0 });
|
|
1873
|
+
if (!shouldTrackTask && this.#interactive && persistedStartIndex !== void 0) {
|
|
1874
|
+
this.#replaceInteractiveHistoryLine(persistedStartIndex, "pass", nextMessage ?? message, passOptions);
|
|
1875
|
+
this.#redrawInteractiveHistory();
|
|
1876
|
+
finishTrackedTask();
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
this.#emit("pass", nextMessage ?? message, passOptions, { persistInteractive });
|
|
1880
|
+
finishTrackedTask();
|
|
1881
|
+
},
|
|
1882
|
+
skip: (nextMessage, nextOptions) => {
|
|
1883
|
+
const skipOptions = {
|
|
1884
|
+
...nextOptions,
|
|
1885
|
+
depth,
|
|
1886
|
+
elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
|
|
1887
|
+
};
|
|
1888
|
+
if (!shouldTrackTask && this.#interactive && persistedStartIndex !== void 0) {
|
|
1889
|
+
this.#replaceInteractiveHistoryLine(persistedStartIndex, "skip", nextMessage ?? message, skipOptions);
|
|
1890
|
+
this.#redrawInteractiveHistory();
|
|
1891
|
+
finishTrackedTask();
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
this.#emit("skip", nextMessage ?? message, skipOptions, { persistInteractive: this.#trackedTaskCount === 0 });
|
|
1895
|
+
finishTrackedTask();
|
|
1896
|
+
},
|
|
1897
|
+
warn: (nextMessage, nextOptions) => {
|
|
1898
|
+
this.warn(nextMessage, {
|
|
1899
|
+
...nextOptions,
|
|
1900
|
+
depth: nextOptions?.depth ?? depth + 1
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
fail(message, options = {}) {
|
|
1906
|
+
this.#emit("fail", formatFailureMessage(message, options.error), options, { persistInteractive: true });
|
|
1907
|
+
}
|
|
1908
|
+
info(message, options = {}) {
|
|
1909
|
+
this.#emit("info", message, options);
|
|
1910
|
+
}
|
|
1911
|
+
pass(message, options = {}) {
|
|
1912
|
+
this.#emit("pass", message, options, { persistInteractive: true });
|
|
1913
|
+
}
|
|
1914
|
+
skip(message, options = {}) {
|
|
1915
|
+
this.#emit("skip", message, options, { persistInteractive: true });
|
|
1916
|
+
}
|
|
1917
|
+
warn(message, options = {}) {
|
|
1918
|
+
this.#emit("warn", message, options);
|
|
1919
|
+
}
|
|
1920
|
+
writeOutput(message, options = {}) {
|
|
1921
|
+
const stream = options.stream === "stderr" ? this.#stderr : this.#stdout;
|
|
1922
|
+
if (this.#interactive && this.#tracksProcessWrites && typeof stream?.write === "function") {
|
|
1923
|
+
stream.write(message);
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
this.#writeTracked(toWritableText(message));
|
|
1927
|
+
}
|
|
1928
|
+
#emit(status, rawMessage, options, meta = {}) {
|
|
1929
|
+
const message = formatMessageWithElapsed(rawMessage, options.elapsedTimeMs);
|
|
1930
|
+
const depth = options.depth ?? 0;
|
|
1931
|
+
if (this.#interactive) {
|
|
1932
|
+
const renderedLine = this.#formatInteractiveLine(status, message, depth);
|
|
1933
|
+
let historyIndex;
|
|
1934
|
+
if (meta.persistInteractive) {
|
|
1935
|
+
historyIndex = this.#interactiveHistory.length;
|
|
1936
|
+
this.#interactiveHistory.push(renderedLine);
|
|
1937
|
+
}
|
|
1938
|
+
writeLine({ write: (nextMessage) => {
|
|
1939
|
+
this.#writeTracked(nextMessage);
|
|
1940
|
+
} }, renderedLine);
|
|
1941
|
+
return historyIndex;
|
|
1942
|
+
}
|
|
1943
|
+
writeLine(this.#output, `${" ".repeat(depth)}[${status}] ${message}`);
|
|
1944
|
+
}
|
|
1945
|
+
#beginTerminalTracking() {
|
|
1946
|
+
this.#trackedTaskCount += 1;
|
|
1947
|
+
if (this.#trackedTaskCount > 1 || !this.#tracksProcessWrites) return;
|
|
1948
|
+
const restoreStdout = this.#patchWriteStream(this.#stdout);
|
|
1949
|
+
const restoreStderr = this.#patchWriteStream(this.#stderr);
|
|
1950
|
+
this.#restoreWriteStreams = () => {
|
|
1951
|
+
restoreStdout?.();
|
|
1952
|
+
restoreStderr?.();
|
|
1953
|
+
this.#restoreWriteStreams = void 0;
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
#clearInteractiveTaskBlock(startLine, options = {}) {
|
|
1957
|
+
const linesToClear = this.#terminalLineCount - startLine;
|
|
1958
|
+
if (linesToClear <= 0) return;
|
|
1959
|
+
if (options.redrawHistory) {
|
|
1960
|
+
this.#redrawInteractiveHistory();
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
this.#writeControl(`\r\u001B[${linesToClear}A\u001B[J`);
|
|
1964
|
+
this.#terminalLineCount = startLine;
|
|
1965
|
+
this.#terminalColumn = 0;
|
|
1966
|
+
}
|
|
1967
|
+
#endTerminalTracking() {
|
|
1968
|
+
this.#trackedTaskCount = Math.max(0, this.#trackedTaskCount - 1);
|
|
1969
|
+
if (this.#trackedTaskCount === 0) this.#restoreWriteStreams?.();
|
|
1970
|
+
}
|
|
1971
|
+
#patchWriteStream(stream) {
|
|
1972
|
+
if (typeof stream?.write !== "function") return;
|
|
1973
|
+
const originalWrite = stream.write;
|
|
1974
|
+
stream.write = (...args) => {
|
|
1975
|
+
this.#recordTerminalWrite(args[0]);
|
|
1976
|
+
return Reflect.apply(originalWrite, stream, args);
|
|
1977
|
+
};
|
|
1978
|
+
return () => {
|
|
1979
|
+
stream.write = originalWrite;
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
#recordTerminalWrite(chunk) {
|
|
1983
|
+
const text = stripControlSequences(toWritableText(chunk));
|
|
1984
|
+
const columns = Math.max(1, this.#stdout?.columns ?? DEFAULT_TERMINAL_COLUMNS);
|
|
1985
|
+
for (const char of text) {
|
|
1986
|
+
if (char === "\n") {
|
|
1987
|
+
this.#terminalLineCount += 1;
|
|
1988
|
+
this.#terminalColumn = 0;
|
|
1989
|
+
continue;
|
|
1990
|
+
}
|
|
1991
|
+
this.#terminalColumn += 1;
|
|
1992
|
+
if (this.#terminalColumn >= columns) {
|
|
1993
|
+
this.#terminalLineCount += 1;
|
|
1994
|
+
this.#terminalColumn = 0;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
#redrawInteractiveHistory() {
|
|
1999
|
+
this.#writeControl("\r\x1B[H\x1B[2J\x1B[3J");
|
|
2000
|
+
this.#terminalLineCount = 0;
|
|
2001
|
+
this.#terminalColumn = 0;
|
|
2002
|
+
for (const line of this.#interactiveHistory) writeLine({ write: (message) => {
|
|
2003
|
+
this.#writeTracked(message);
|
|
2004
|
+
} }, line);
|
|
2005
|
+
}
|
|
2006
|
+
#formatInteractiveLine(status, message, depth) {
|
|
2007
|
+
const renderedMessage = indentMessage(message, depth);
|
|
2008
|
+
return `${colorInteractiveSymbol(status, FLOW_SYMBOL_BY_STATUS[status])} ${renderedMessage}`;
|
|
2009
|
+
}
|
|
2010
|
+
#replaceInteractiveHistoryLine(index, status, rawMessage, options) {
|
|
2011
|
+
this.#interactiveHistory[index] = this.#formatInteractiveLine(status, formatMessageWithElapsed(rawMessage, options.elapsedTimeMs), options.depth ?? 0);
|
|
2012
|
+
}
|
|
2013
|
+
#writeControl(message) {
|
|
2014
|
+
this.#output.write(message);
|
|
2015
|
+
}
|
|
2016
|
+
#writeTracked(message) {
|
|
2017
|
+
if (!this.#tracksProcessWrites) this.#recordTerminalWrite(message);
|
|
2018
|
+
this.#output.write(message);
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
function createLiminaFlowReporter(options = {}) {
|
|
2022
|
+
return new LiminaFlowReporter(options);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
//#endregion
|
|
2026
|
+
export { collectImporters as A, createFormatHost as B, inferPackageProject as C, parseProject as D, isWorkspacePackageFile as E, collectGraphProjectRoute as F, isOrdinaryTypecheckConfigPath as G, getDtsCompanionConfigPath as H, collectGraphProjectRouteFromRoot as I, readJsonConfig as J, parseProjectFileNames as K, collectGraphProjectRoutes as L, findPackageForSpecifier as M, getPackageRootSpecifier as N, resolveInternalImport as O, collectCheckerEntryProjectRoutes as P, collectTypecheckTargetProjectPaths as R, getTypecheckConfigPath as S, isRelativeSpecifier as T, getRawReferencePaths as U, formatReferences as V, isDtsConfigPath as W, resolveReferencePath as X, resolveProjectConfigPath as Y, createFileOwnerLookup as _, runSourceCheck as a, findTargetProject as b, PackageLogger as c, clearCliScreen as d, formatErrorMessage$1 as f, collectImportsFromFile as g, normalizeGraphRules as h, runCheckerTypecheck as i, collectWorkspacePackages as j, shouldResolveThroughGraph as k, PathsLogger as l, getDeniedWorkspaceDepRule as m, createLiminaFlowReporter as n, CliLogger as o, getDeniedRefRule as p, parseProjectFileNamesForExtensions as q, runCheckerBuild as r, GraphLogger as s, LiminaFlowReporter as t, ProofLogger as u, findImporterForFile as v, isDtsProjectConfig as w, formatArtifactDependencyPolicy as x, findPackageForFile as y, createExtensionPattern as z };
|