limina 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +11 -0
- package/README.md +16 -372
- package/README.zh-CN.md +30 -0
- package/bin/limina.js +32 -21
- package/chunks/dep-CBKvJc4Y.js +846 -0
- package/chunks/dep-DTGmTTL7.js +4968 -0
- package/cli.js +1144 -802
- package/config.d.ts +202 -12
- package/config.js +2 -2
- package/index.d.ts +22 -2
- package/index.js +3 -3
- package/package.json +18 -3
- package/schemas/tsconfig-schema.json +42 -0
- package/chunks/dep-DzYrmtQJ.js +0 -360
- package/chunks/dep-ZRIm_-Zk.js +0 -2401
package/chunks/dep-ZRIm_-Zk.js
DELETED
|
@@ -1,2401 +0,0 @@
|
|
|
1
|
-
import { c as getCheckerAdapter, d as normalizeAbsolutePath, g as toRelativePath, h as toPosixPath, l as normalizeExtensions, o as collectMissingCheckerPeerDependencies, r as getActiveCheckers, s as formatMissingCheckerPeerDependencies, u as isPathInsideDirectory } from "./dep-DzYrmtQJ.js";
|
|
2
|
-
import { builtinModules, createRequire } from "node:module";
|
|
3
|
-
import { createElapsedTimer, formatErrorMessage, formatErrorMessage as formatErrorMessage$1 } from "logaria/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 "logaria";
|
|
10
|
-
import readline from "node:readline";
|
|
11
|
-
import * as prompts from "@clack/prompts";
|
|
12
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
13
|
-
import { parse } from "yaml";
|
|
14
|
-
|
|
15
|
-
//#region src/tsconfig.ts
|
|
16
|
-
const dtsConfigFilePattern = /^tsconfig(?:\..+)?\.dts\.json$/u;
|
|
17
|
-
const buildGraphConfigFilePattern = /^tsconfig(?:\..+)?\.build\.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 isDtsConfigPath(configPath) {
|
|
144
|
-
return dtsConfigFilePattern.test(path.basename(configPath));
|
|
145
|
-
}
|
|
146
|
-
function getDtsCompanionConfigPath(dtsConfigPath) {
|
|
147
|
-
const directory = path.dirname(dtsConfigPath);
|
|
148
|
-
const fileName = path.basename(dtsConfigPath);
|
|
149
|
-
const companionFileName = fileName === "tsconfig.dts.json" ? "tsconfig.json" : fileName.replace(/\.dts\.json$/u, ".json");
|
|
150
|
-
const scopedCompanionPath = normalizeAbsolutePath(path.join(directory, companionFileName));
|
|
151
|
-
if (companionFileName === "tsconfig.json" || existsSync(scopedCompanionPath)) return scopedCompanionPath;
|
|
152
|
-
return normalizeAbsolutePath(path.join(directory, "tsconfig.json"));
|
|
153
|
-
}
|
|
154
|
-
function isBuildGraphConfigPath(configPath) {
|
|
155
|
-
return buildGraphConfigFilePattern.test(path.basename(configPath));
|
|
156
|
-
}
|
|
157
|
-
function isReservedTypeScriptConfigFile(fileName) {
|
|
158
|
-
return dtsConfigFilePattern.test(fileName) || buildGraphConfigFilePattern.test(fileName) || generatedConfigFilePattern.test(fileName) || baseConfigFilePattern.test(fileName) || checkConfigFilePattern.test(fileName);
|
|
159
|
-
}
|
|
160
|
-
function isOrdinaryTypecheckConfigPath(configPath) {
|
|
161
|
-
const fileName = path.basename(configPath);
|
|
162
|
-
return tsconfigFilePattern.test(fileName) && !isReservedTypeScriptConfigFile(fileName);
|
|
163
|
-
}
|
|
164
|
-
function collectGraphProjectRouteFromRoot(options) {
|
|
165
|
-
const rootGraphConfigPath = normalizeAbsolutePath(options.rootConfigPath);
|
|
166
|
-
const seen = /* @__PURE__ */ new Set();
|
|
167
|
-
const orderedProjects = [];
|
|
168
|
-
const problems = [];
|
|
169
|
-
const rootReferences = collectReferencePathInfosForConfig(options.rootDir, rootGraphConfigPath);
|
|
170
|
-
const queue = rootReferences.references.map((reference) => ({
|
|
171
|
-
projectPath: reference.resolvedPath,
|
|
172
|
-
rawReferencePath: reference.rawPath,
|
|
173
|
-
referrerPath: rootGraphConfigPath
|
|
174
|
-
}));
|
|
175
|
-
const formatConfigPath = (configPath) => toRelativePath(options.rootDir, configPath);
|
|
176
|
-
problems.push(...rootReferences.problems);
|
|
177
|
-
if (!isBuildGraphConfigPath(rootGraphConfigPath) && !isDtsConfigPath(rootGraphConfigPath)) problems.push([
|
|
178
|
-
"Invalid checker entry config:",
|
|
179
|
-
` config: ${formatConfigPath(rootGraphConfigPath)}`,
|
|
180
|
-
" reason: checker entries should point to a tsconfig*.build.json graph aggregator or a direct tsconfig*.dts.json declaration leaf."
|
|
181
|
-
].join("\n"));
|
|
182
|
-
if (isDtsConfigPath(rootGraphConfigPath)) {
|
|
183
|
-
seen.add(rootGraphConfigPath);
|
|
184
|
-
orderedProjects.push(rootGraphConfigPath);
|
|
185
|
-
}
|
|
186
|
-
for (const { projectPath } of queue) seen.add(projectPath);
|
|
187
|
-
for (const { projectPath, rawReferencePath, referrerPath } of queue) {
|
|
188
|
-
if (!projectPath) continue;
|
|
189
|
-
if (!existsSync(projectPath)) {
|
|
190
|
-
problems.push([
|
|
191
|
-
"Checker entry references a missing tsconfig:",
|
|
192
|
-
` from: ${formatConfigPath(referrerPath)}`,
|
|
193
|
-
` reference: ${rawReferencePath}`,
|
|
194
|
-
` resolved: ${formatConfigPath(projectPath)}`,
|
|
195
|
-
" reason: every project reference reachable from a checker entry must point to an existing tsconfig file or directory with tsconfig.json."
|
|
196
|
-
].join("\n"));
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (!isBuildGraphConfigPath(projectPath) && !isDtsConfigPath(projectPath)) {
|
|
200
|
-
problems.push([
|
|
201
|
-
"Invalid checker entry reference:",
|
|
202
|
-
` from: ${formatConfigPath(referrerPath)}`,
|
|
203
|
-
` reference: ${rawReferencePath}`,
|
|
204
|
-
` resolved: ${formatConfigPath(projectPath)}`,
|
|
205
|
-
" reason: checker entries may only reach tsconfig*.build.json graph aggregators and tsconfig*.dts.json declaration leaves."
|
|
206
|
-
].join("\n"));
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
orderedProjects.push(projectPath);
|
|
210
|
-
const referenceCollection = collectReferencePathInfosForConfig(options.rootDir, projectPath);
|
|
211
|
-
problems.push(...referenceCollection.problems);
|
|
212
|
-
for (const reference of referenceCollection.references) {
|
|
213
|
-
const referencePath = reference.resolvedPath;
|
|
214
|
-
if (seen.has(referencePath)) continue;
|
|
215
|
-
seen.add(referencePath);
|
|
216
|
-
queue.push({
|
|
217
|
-
projectPath: referencePath,
|
|
218
|
-
rawReferencePath: reference.rawPath,
|
|
219
|
-
referrerPath: projectPath
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
problems,
|
|
225
|
-
projectPaths: orderedProjects
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
function collectGraphProjectRoutes(config) {
|
|
229
|
-
const routes = [];
|
|
230
|
-
const problems = [];
|
|
231
|
-
for (const checker of getActiveCheckers(config)) {
|
|
232
|
-
if (!getCheckerAdapter(checker.preset)?.sourceGraph) continue;
|
|
233
|
-
const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
|
|
234
|
-
if (!existsSync(rootConfigPath)) {
|
|
235
|
-
problems.push([
|
|
236
|
-
"Checker graph entry references a missing tsconfig:",
|
|
237
|
-
` checker: ${checker.name}`,
|
|
238
|
-
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`
|
|
239
|
-
].join("\n"));
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
243
|
-
rootConfigPath,
|
|
244
|
-
rootDir: config.rootDir
|
|
245
|
-
});
|
|
246
|
-
problems.push(...routeCollection.problems);
|
|
247
|
-
routes.push({
|
|
248
|
-
checkerName: checker.name,
|
|
249
|
-
extensions: checker.extensions,
|
|
250
|
-
projectPaths: routeCollection.projectPaths,
|
|
251
|
-
rootConfigPath
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
return {
|
|
255
|
-
problems,
|
|
256
|
-
routes
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
function collectCheckerEntryProjectRoutes(config) {
|
|
260
|
-
const routes = [];
|
|
261
|
-
const problems = [];
|
|
262
|
-
for (const checker of getActiveCheckers(config)) {
|
|
263
|
-
const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
|
|
264
|
-
if (!existsSync(rootConfigPath)) {
|
|
265
|
-
problems.push([
|
|
266
|
-
"Checker entry references a missing tsconfig:",
|
|
267
|
-
` checker: ${checker.name}`,
|
|
268
|
-
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`
|
|
269
|
-
].join("\n"));
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
273
|
-
rootConfigPath,
|
|
274
|
-
rootDir: config.rootDir
|
|
275
|
-
});
|
|
276
|
-
problems.push(...routeCollection.problems);
|
|
277
|
-
routes.push({
|
|
278
|
-
checkerName: checker.name,
|
|
279
|
-
extensions: checker.extensions,
|
|
280
|
-
projectPaths: routeCollection.projectPaths,
|
|
281
|
-
rootConfigPath
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
return {
|
|
285
|
-
problems,
|
|
286
|
-
routes
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
function collectGraphProjectRoute(config) {
|
|
290
|
-
const routeCollection = collectGraphProjectRoutes(config);
|
|
291
|
-
return {
|
|
292
|
-
problems: routeCollection.problems,
|
|
293
|
-
projectPaths: [...new Set(routeCollection.routes.flatMap((route) => route.projectPaths))].sort()
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
function collectSourceGraphProjectExtensions(config) {
|
|
297
|
-
const routeCollection = collectGraphProjectRoutes(config);
|
|
298
|
-
const projectExtensionsByPath = /* @__PURE__ */ new Map();
|
|
299
|
-
const typeScriptExtensions = getCheckerAdapter("tsc")?.defaultExtensions ?? [];
|
|
300
|
-
for (const route of routeCollection.routes) {
|
|
301
|
-
const routeExtensions = normalizeExtensions([...typeScriptExtensions, ...route.extensions]);
|
|
302
|
-
for (const projectPath of route.projectPaths) projectExtensionsByPath.set(projectPath, normalizeExtensions([...projectExtensionsByPath.get(projectPath) ?? [], ...routeExtensions]));
|
|
303
|
-
}
|
|
304
|
-
return {
|
|
305
|
-
problems: routeCollection.problems,
|
|
306
|
-
projectExtensionsByPath
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
function parseProjectFileNamesForExtensions(config, configPath, extensions, pattern = createExtensionPattern(extensions)) {
|
|
310
|
-
const diagnostics = [];
|
|
311
|
-
const configObject = readJsonConfig(config, configPath);
|
|
312
|
-
const parsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
|
|
313
|
-
if (diagnostics.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
|
|
314
|
-
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
|
|
315
|
-
return parsed.fileNames.filter((fileName) => pattern.test(fileName)).map(normalizeAbsolutePath);
|
|
316
|
-
}
|
|
317
|
-
function formatReferences(rootDir, references) {
|
|
318
|
-
if (references.size === 0) return "(none)";
|
|
319
|
-
return [...references].sort().map((value) => toRelativePath(rootDir, value)).join(", ");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
//#endregion
|
|
323
|
-
//#region src/workspace.ts
|
|
324
|
-
const pnpmWorkspaceFileName$1 = "pnpm-workspace.yaml";
|
|
325
|
-
const pnpmWorkspaceListTimeoutMs = 3e3;
|
|
326
|
-
function readJsonFile(filePath) {
|
|
327
|
-
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
328
|
-
}
|
|
329
|
-
function stripYamlQuotes(value) {
|
|
330
|
-
const trimmed = value.trim();
|
|
331
|
-
if (trimmed.startsWith("'") && trimmed.endsWith("'") || trimmed.startsWith("\"") && trimmed.endsWith("\"")) return trimmed.slice(1, -1);
|
|
332
|
-
return trimmed;
|
|
333
|
-
}
|
|
334
|
-
function collectPnpmWorkspacePatterns(source) {
|
|
335
|
-
const patterns = [];
|
|
336
|
-
const lines = source.split(/\r?\n/u);
|
|
337
|
-
let isInsidePackagesSection = false;
|
|
338
|
-
for (const rawLine of lines) {
|
|
339
|
-
const line = rawLine.replaceAll(" ", " ");
|
|
340
|
-
const trimmedLine = line.trim();
|
|
341
|
-
if (!isInsidePackagesSection) {
|
|
342
|
-
if (trimmedLine === "packages:") isInsidePackagesSection = true;
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
if (trimmedLine.length === 0 || trimmedLine.startsWith("#")) continue;
|
|
346
|
-
if (line.length - line.trimStart().length === 0) break;
|
|
347
|
-
if (trimmedLine.startsWith("- ")) patterns.push(stripYamlQuotes(trimmedLine.slice(2)));
|
|
348
|
-
}
|
|
349
|
-
return patterns;
|
|
350
|
-
}
|
|
351
|
-
function parsePnpmWorkspaceListJson(source) {
|
|
352
|
-
const parsed = JSON.parse(source);
|
|
353
|
-
if (!Array.isArray(parsed)) return [];
|
|
354
|
-
return parsed.flatMap((entry) => {
|
|
355
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
356
|
-
const record = entry;
|
|
357
|
-
if (typeof record.name !== "string" || typeof record.path !== "string") return [];
|
|
358
|
-
return [{
|
|
359
|
-
name: record.name,
|
|
360
|
-
path: record.path
|
|
361
|
-
}];
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
function collectWorkspacePatterns(config) {
|
|
365
|
-
const rootPackageJsonPath = path.join(config.rootDir, "package.json");
|
|
366
|
-
const patterns = /* @__PURE__ */ new Set();
|
|
367
|
-
if (existsSync(rootPackageJsonPath)) {
|
|
368
|
-
const rootPackageJson = readJsonFile(rootPackageJsonPath);
|
|
369
|
-
if (Array.isArray(rootPackageJson.workspaces)) for (const pattern of rootPackageJson.workspaces) patterns.add(pattern);
|
|
370
|
-
}
|
|
371
|
-
const workspacePath = path.join(config.rootDir, pnpmWorkspaceFileName$1);
|
|
372
|
-
if (existsSync(workspacePath)) for (const pattern of collectPnpmWorkspacePatterns(readFileSync(workspacePath, "utf8"))) patterns.add(pattern);
|
|
373
|
-
return [...patterns].sort();
|
|
374
|
-
}
|
|
375
|
-
function runTextCommand(command, args, cwd) {
|
|
376
|
-
return new Promise((resolve, reject) => {
|
|
377
|
-
execFile(command, args, {
|
|
378
|
-
cwd,
|
|
379
|
-
encoding: "utf8",
|
|
380
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
381
|
-
timeout: pnpmWorkspaceListTimeoutMs
|
|
382
|
-
}, (error, stdout) => {
|
|
383
|
-
if (error) {
|
|
384
|
-
reject(error);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
resolve(stdout);
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
function getPnpmCommandCandidates() {
|
|
392
|
-
const candidates = [];
|
|
393
|
-
const npmExecPath = process.env.npm_execpath;
|
|
394
|
-
if (npmExecPath?.includes("pnpm")) candidates.push({
|
|
395
|
-
argsPrefix: [npmExecPath],
|
|
396
|
-
command: process.execPath
|
|
397
|
-
});
|
|
398
|
-
candidates.push({
|
|
399
|
-
argsPrefix: ["pnpm"],
|
|
400
|
-
command: "corepack"
|
|
401
|
-
}, {
|
|
402
|
-
argsPrefix: [],
|
|
403
|
-
command: "pnpm"
|
|
404
|
-
});
|
|
405
|
-
const seen = /* @__PURE__ */ new Set();
|
|
406
|
-
return candidates.filter((candidate) => {
|
|
407
|
-
const key = `${candidate.command}\0${candidate.argsPrefix.join("\0")}`;
|
|
408
|
-
if (seen.has(key)) return false;
|
|
409
|
-
seen.add(key);
|
|
410
|
-
return true;
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
async function collectPnpmListedPackages(config) {
|
|
414
|
-
const args = [
|
|
415
|
-
"recursive",
|
|
416
|
-
"list",
|
|
417
|
-
"--depth",
|
|
418
|
-
"-1",
|
|
419
|
-
"--json"
|
|
420
|
-
];
|
|
421
|
-
for (const candidate of getPnpmCommandCandidates()) try {
|
|
422
|
-
const entries = parsePnpmWorkspaceListJson(await runTextCommand(candidate.command, [...candidate.argsPrefix, ...args], config.rootDir));
|
|
423
|
-
const packages = [];
|
|
424
|
-
for (const entry of entries) {
|
|
425
|
-
if (!entry.path) continue;
|
|
426
|
-
const directory = normalizeAbsolutePath(entry.path);
|
|
427
|
-
const packageJsonPath = path.join(directory, "package.json");
|
|
428
|
-
if (!existsSync(packageJsonPath)) continue;
|
|
429
|
-
const manifest = readJsonFile(packageJsonPath);
|
|
430
|
-
const name = manifest.name ?? entry.name;
|
|
431
|
-
if (!name) continue;
|
|
432
|
-
packages.push({
|
|
433
|
-
directory,
|
|
434
|
-
manifest,
|
|
435
|
-
name
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
if (packages.length > 0) return packages;
|
|
439
|
-
} catch {}
|
|
440
|
-
return [];
|
|
441
|
-
}
|
|
442
|
-
async function collectWorkspacePackagesFromPatterns(config) {
|
|
443
|
-
const workspacePatterns = collectWorkspacePatterns(config);
|
|
444
|
-
const includePatterns = workspacePatterns.filter((pattern) => !pattern.startsWith("!")).map((pattern) => `${pattern.replace(/\/$/u, "")}/package.json`);
|
|
445
|
-
const ignorePatterns = workspacePatterns.filter((pattern) => pattern.startsWith("!")).map((pattern) => `${pattern.slice(1).replace(/\/$/u, "")}/**`);
|
|
446
|
-
const packageJsonPaths = await glob(includePatterns, {
|
|
447
|
-
cwd: config.rootDir,
|
|
448
|
-
absolute: false,
|
|
449
|
-
ignore: [
|
|
450
|
-
"**/node_modules/**",
|
|
451
|
-
"**/dist/**",
|
|
452
|
-
...ignorePatterns
|
|
453
|
-
]
|
|
454
|
-
});
|
|
455
|
-
const packages = [];
|
|
456
|
-
for (const packageJsonPath of [...new Set(packageJsonPaths)].sort()) {
|
|
457
|
-
const manifest = readJsonFile(path.join(config.rootDir, packageJsonPath));
|
|
458
|
-
if (!manifest.name) continue;
|
|
459
|
-
packages.push({
|
|
460
|
-
directory: normalizeAbsolutePath(path.dirname(path.join(config.rootDir, packageJsonPath))),
|
|
461
|
-
manifest,
|
|
462
|
-
name: manifest.name
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
return packages;
|
|
466
|
-
}
|
|
467
|
-
function mergeWorkspacePackages(packages) {
|
|
468
|
-
const byDirectory = /* @__PURE__ */ new Map();
|
|
469
|
-
for (const workspacePackage of packages) byDirectory.set(workspacePackage.directory, workspacePackage);
|
|
470
|
-
return [...byDirectory.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
471
|
-
}
|
|
472
|
-
async function collectWorkspacePackages(config) {
|
|
473
|
-
const [pnpmPackages, patternPackages] = await Promise.all([collectPnpmListedPackages(config), collectWorkspacePackagesFromPatterns(config)]);
|
|
474
|
-
return mergeWorkspacePackages([...pnpmPackages, ...patternPackages]);
|
|
475
|
-
}
|
|
476
|
-
async function collectPackageOwners(config) {
|
|
477
|
-
const packageJsonPaths = await glob(["package.json", "**/package.json"], {
|
|
478
|
-
cwd: config.rootDir,
|
|
479
|
-
absolute: false,
|
|
480
|
-
ignore: [
|
|
481
|
-
"**/.git/**",
|
|
482
|
-
"**/.pnpm-store/**",
|
|
483
|
-
"**/.tsbuild/**",
|
|
484
|
-
"**/coverage/**",
|
|
485
|
-
"**/dist/**",
|
|
486
|
-
"**/node_modules/**"
|
|
487
|
-
]
|
|
488
|
-
});
|
|
489
|
-
return [...new Set(packageJsonPaths)].sort().map((packageJsonPath) => {
|
|
490
|
-
const absolutePackageJsonPath = normalizeAbsolutePath(path.join(config.rootDir, packageJsonPath));
|
|
491
|
-
const manifest = readJsonFile(absolutePackageJsonPath);
|
|
492
|
-
return {
|
|
493
|
-
directory: normalizeAbsolutePath(path.dirname(absolutePackageJsonPath)),
|
|
494
|
-
manifest,
|
|
495
|
-
name: manifest.name,
|
|
496
|
-
packageJsonPath: absolutePackageJsonPath
|
|
497
|
-
};
|
|
498
|
-
}).sort((left, right) => right.directory.length - left.directory.length);
|
|
499
|
-
}
|
|
500
|
-
function getDependencySections(importer) {
|
|
501
|
-
return [
|
|
502
|
-
importer.dependencies,
|
|
503
|
-
importer.devDependencies,
|
|
504
|
-
importer.optionalDependencies,
|
|
505
|
-
importer.peerDependencies
|
|
506
|
-
].filter((section) => Boolean(section));
|
|
507
|
-
}
|
|
508
|
-
function getPublishDependencySections(importer) {
|
|
509
|
-
return [
|
|
510
|
-
{
|
|
511
|
-
dependencies: importer.dependencies,
|
|
512
|
-
name: "dependencies"
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
dependencies: importer.peerDependencies,
|
|
516
|
-
name: "peerDependencies"
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
dependencies: importer.optionalDependencies,
|
|
520
|
-
name: "optionalDependencies"
|
|
521
|
-
}
|
|
522
|
-
].filter((section) => Boolean(section.dependencies));
|
|
523
|
-
}
|
|
524
|
-
function isWorkspaceDependencySpecifier(specifier) {
|
|
525
|
-
return specifier.startsWith("workspace:");
|
|
526
|
-
}
|
|
527
|
-
function getPackageRootSpecifier(specifier) {
|
|
528
|
-
if (specifier.startsWith("@")) {
|
|
529
|
-
const [scope, name] = specifier.split("/");
|
|
530
|
-
return scope && name ? `${scope}/${name}` : specifier;
|
|
531
|
-
}
|
|
532
|
-
return specifier.split("/")[0] ?? specifier;
|
|
533
|
-
}
|
|
534
|
-
function findPackageForSpecifier(specifier, packages) {
|
|
535
|
-
const packageName = getPackageRootSpecifier(specifier);
|
|
536
|
-
return packages.find((workspacePackage) => workspacePackage.name === packageName) ?? null;
|
|
537
|
-
}
|
|
538
|
-
function collectImporters(config, packages) {
|
|
539
|
-
const workspacePackageNames = new Set(packages.map((workspacePackage) => workspacePackage.name));
|
|
540
|
-
const importerDirectories = new Set([config.rootDir, ...packages.map((workspacePackage) => workspacePackage.directory)]);
|
|
541
|
-
const importers = [];
|
|
542
|
-
for (const importerDirectory of importerDirectories) {
|
|
543
|
-
const packageJsonPath = path.join(importerDirectory, "package.json");
|
|
544
|
-
if (!existsSync(packageJsonPath)) continue;
|
|
545
|
-
const manifest = readJsonFile(packageJsonPath);
|
|
546
|
-
const workspaceDependencies = /* @__PURE__ */ new Set();
|
|
547
|
-
for (const dependencies of getDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) {
|
|
548
|
-
if (!workspacePackageNames.has(dependencyName) || !isWorkspaceDependencySpecifier(specifier)) continue;
|
|
549
|
-
workspaceDependencies.add(dependencyName);
|
|
550
|
-
}
|
|
551
|
-
importers.push({
|
|
552
|
-
directory: importerDirectory,
|
|
553
|
-
name: manifest.name,
|
|
554
|
-
workspaceDependencies
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
return importers.sort((left, right) => right.directory.length - left.directory.length);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
//#endregion
|
|
561
|
-
//#region src/graph-context.ts
|
|
562
|
-
function isRelativeSpecifier$1(specifier) {
|
|
563
|
-
return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
|
|
564
|
-
}
|
|
565
|
-
function isDtsProjectConfig(configPath) {
|
|
566
|
-
return isDtsConfigPath(configPath);
|
|
567
|
-
}
|
|
568
|
-
function getTypecheckConfigPath(dtsConfigPath) {
|
|
569
|
-
return getDtsCompanionConfigPath(dtsConfigPath);
|
|
570
|
-
}
|
|
571
|
-
function formatUnknownValue$1(value) {
|
|
572
|
-
if (value === void 0) return "undefined";
|
|
573
|
-
return JSON.stringify(value);
|
|
574
|
-
}
|
|
575
|
-
function readProjectLabel(config, configPath) {
|
|
576
|
-
if (!isDtsProjectConfig(configPath)) return {
|
|
577
|
-
label: null,
|
|
578
|
-
labelProblem: null
|
|
579
|
-
};
|
|
580
|
-
const configObject = readJsonConfig(config, configPath);
|
|
581
|
-
if (!Object.hasOwn(configObject, "limina")) return {
|
|
582
|
-
label: null,
|
|
583
|
-
labelProblem: null
|
|
584
|
-
};
|
|
585
|
-
const value = configObject.limina;
|
|
586
|
-
if (typeof value === "string" && value.trim()) return {
|
|
587
|
-
label: value.trim(),
|
|
588
|
-
labelProblem: null
|
|
589
|
-
};
|
|
590
|
-
return {
|
|
591
|
-
label: null,
|
|
592
|
-
labelProblem: [
|
|
593
|
-
"Invalid Limina graph label:",
|
|
594
|
-
` project: ${toRelativePath(config.rootDir, configPath)}`,
|
|
595
|
-
` field: limina`,
|
|
596
|
-
` value: ${formatUnknownValue$1(value)}`,
|
|
597
|
-
" reason: tsconfig*.dts.json may declare one non-empty string label with \"limina\"."
|
|
598
|
-
].join("\n")
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
function parseProject(config, configPath, extensions) {
|
|
602
|
-
const diagnostics = [];
|
|
603
|
-
const parsed = extensions ? ts.parseJsonConfigFileContent(readJsonConfig(config, configPath), ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions)) : ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
604
|
-
...ts.sys,
|
|
605
|
-
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
606
|
-
diagnostics.push(diagnostic);
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
610
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
611
|
-
getCurrentDirectory: () => config.rootDir,
|
|
612
|
-
getNewLine: () => "\n"
|
|
613
|
-
}));
|
|
614
|
-
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
615
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
616
|
-
getCurrentDirectory: () => config.rootDir,
|
|
617
|
-
getNewLine: () => "\n"
|
|
618
|
-
}));
|
|
619
|
-
const labelInfo = readProjectLabel(config, configPath);
|
|
620
|
-
const projectExtensions = extensions ?? [
|
|
621
|
-
".ts",
|
|
622
|
-
".tsx",
|
|
623
|
-
".cts",
|
|
624
|
-
".mts",
|
|
625
|
-
".d.ts",
|
|
626
|
-
".d.cts",
|
|
627
|
-
".d.mts"
|
|
628
|
-
];
|
|
629
|
-
const filePattern = createExtensionPattern(projectExtensions);
|
|
630
|
-
return {
|
|
631
|
-
configPath: normalizeAbsolutePath(configPath),
|
|
632
|
-
extensions: projectExtensions,
|
|
633
|
-
fileNames: parsed.fileNames.filter((fileName) => filePattern.test(fileName)).map(normalizeAbsolutePath),
|
|
634
|
-
label: labelInfo.label,
|
|
635
|
-
labelProblem: labelInfo.labelProblem,
|
|
636
|
-
options: parsed.options,
|
|
637
|
-
references: new Set(getRawReferencePaths(config, configPath))
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
function getSourceFileKind(filePath) {
|
|
641
|
-
if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
642
|
-
if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
643
|
-
return ts.ScriptKind.TS;
|
|
644
|
-
}
|
|
645
|
-
function stringLiteralValue(node) {
|
|
646
|
-
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
647
|
-
}
|
|
648
|
-
function getVueCompilerSfc(rootDir) {
|
|
649
|
-
const requireFromRoot = createRequire(path.join(rootDir, "package.json"));
|
|
650
|
-
try {
|
|
651
|
-
return requireFromRoot("@vue/compiler-sfc");
|
|
652
|
-
} catch (error) {
|
|
653
|
-
if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") throw new Error("Vue source graph support requires @vue/compiler-sfc. Fix: pnpm add -D @vue/compiler-sfc");
|
|
654
|
-
throw error;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
function getVueBlockScriptKind(block) {
|
|
658
|
-
return block.lang === "tsx" || block.lang === "jsx" ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
659
|
-
}
|
|
660
|
-
function collectImportsFromSourceText(options) {
|
|
661
|
-
const sourceFile = ts.createSourceFile(options.filePath, options.sourceText, ts.ScriptTarget.Latest, true, options.scriptKind);
|
|
662
|
-
const imports = [];
|
|
663
|
-
const lineOffset = options.lineOffset ?? 0;
|
|
664
|
-
const addImport = (specifier, node) => {
|
|
665
|
-
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
666
|
-
imports.push({
|
|
667
|
-
filePath: options.filePath,
|
|
668
|
-
line: lineOffset + location.line + 1,
|
|
669
|
-
specifier
|
|
670
|
-
});
|
|
671
|
-
};
|
|
672
|
-
const visit = (node) => {
|
|
673
|
-
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
674
|
-
const specifier = stringLiteralValue(node.moduleSpecifier);
|
|
675
|
-
if (specifier) addImport(specifier, node);
|
|
676
|
-
} else if (ts.isImportTypeNode(node)) {
|
|
677
|
-
const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
|
|
678
|
-
if (specifier) addImport(specifier, node);
|
|
679
|
-
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
680
|
-
const specifier = stringLiteralValue(node.arguments[0]);
|
|
681
|
-
if (specifier) addImport(specifier, node);
|
|
682
|
-
}
|
|
683
|
-
ts.forEachChild(node, visit);
|
|
684
|
-
};
|
|
685
|
-
visit(sourceFile);
|
|
686
|
-
return imports;
|
|
687
|
-
}
|
|
688
|
-
function collectVueImportsFromFile(filePath, rootDir) {
|
|
689
|
-
const sourceText = readFileSync(filePath, "utf8");
|
|
690
|
-
const result = getVueCompilerSfc(rootDir).parse(sourceText, { filename: filePath });
|
|
691
|
-
if (result.errors.length > 0) throw new Error(`Failed to parse Vue SFC imports for ${toRelativePath(rootDir, filePath)}: ${String(result.errors[0])}`);
|
|
692
|
-
return [result.descriptor.script, result.descriptor.scriptSetup].filter((block) => Boolean(block && !block.src)).flatMap((block) => collectImportsFromSourceText({
|
|
693
|
-
filePath,
|
|
694
|
-
lineOffset: block.loc.start.line - 1,
|
|
695
|
-
scriptKind: getVueBlockScriptKind(block),
|
|
696
|
-
sourceText: block.content
|
|
697
|
-
}));
|
|
698
|
-
}
|
|
699
|
-
function collectImportsFromFile(filePath, rootDir) {
|
|
700
|
-
if (filePath.endsWith(".vue")) return collectVueImportsFromFile(filePath, rootDir);
|
|
701
|
-
return collectImportsFromSourceText({
|
|
702
|
-
filePath,
|
|
703
|
-
scriptKind: getSourceFileKind(filePath),
|
|
704
|
-
sourceText: readFileSync(filePath, "utf8")
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
function resolveInternalImport(specifier, containingFile, options, extensions = []) {
|
|
708
|
-
const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
|
|
709
|
-
if (resolved?.resolvedFileName) return normalizeAbsolutePath(resolved.resolvedFileName);
|
|
710
|
-
if (!isRelativeSpecifier$1(specifier)) return null;
|
|
711
|
-
const resolvedSpecifierPath = path.resolve(path.dirname(containingFile), specifier);
|
|
712
|
-
const candidatePaths = path.extname(specifier) ? [resolvedSpecifierPath] : extensions.flatMap((extension) => [`${resolvedSpecifierPath}${extension}`, path.join(resolvedSpecifierPath, `index${extension}`)]);
|
|
713
|
-
for (const candidatePath of candidatePaths) {
|
|
714
|
-
if (!existsSync(candidatePath)) continue;
|
|
715
|
-
if (!statSync(candidatePath).isFile()) continue;
|
|
716
|
-
return normalizeAbsolutePath(candidatePath);
|
|
717
|
-
}
|
|
718
|
-
return null;
|
|
719
|
-
}
|
|
720
|
-
function chooseOwningProject(projectPaths) {
|
|
721
|
-
return [...projectPaths].sort((left, right) => {
|
|
722
|
-
const directoryDepthDelta = path.dirname(right).length - path.dirname(left).length;
|
|
723
|
-
return directoryDepthDelta === 0 ? left.localeCompare(right) : directoryDepthDelta;
|
|
724
|
-
})[0];
|
|
725
|
-
}
|
|
726
|
-
function findPackageForFile(filePath, packages) {
|
|
727
|
-
return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
|
|
728
|
-
}
|
|
729
|
-
function findImporterForFile(filePath, importers) {
|
|
730
|
-
return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
|
|
731
|
-
}
|
|
732
|
-
function shouldResolveThroughGraph(importer, targetPackage) {
|
|
733
|
-
if (!importer || !targetPackage) return false;
|
|
734
|
-
return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
|
|
735
|
-
}
|
|
736
|
-
function formatArtifactDependencyPolicy(targetPackage) {
|
|
737
|
-
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.";
|
|
738
|
-
}
|
|
739
|
-
function inferPackageProject(resolvedFilePath, workspacePackage, projectPaths) {
|
|
740
|
-
if (!isPathInsideDirectory(resolvedFilePath, workspacePackage.directory)) return null;
|
|
741
|
-
return projectPaths.find((projectPath) => {
|
|
742
|
-
return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
|
|
743
|
-
}) ?? null;
|
|
744
|
-
}
|
|
745
|
-
function createFileOwnerLookup(projects) {
|
|
746
|
-
const ownerLookup = /* @__PURE__ */ new Map();
|
|
747
|
-
for (const project of projects) for (const fileName of project.fileNames) {
|
|
748
|
-
const owners = ownerLookup.get(fileName) ?? [];
|
|
749
|
-
owners.push(project.configPath);
|
|
750
|
-
ownerLookup.set(fileName, owners);
|
|
751
|
-
}
|
|
752
|
-
return ownerLookup;
|
|
753
|
-
}
|
|
754
|
-
function findTargetProject(options) {
|
|
755
|
-
const ownerProjects = options.fileOwnerLookup.get(options.resolvedFilePath);
|
|
756
|
-
if (ownerProjects && ownerProjects.length > 0) return chooseOwningProject(ownerProjects);
|
|
757
|
-
const workspacePackage = findPackageForSpecifier(options.specifier, options.packages);
|
|
758
|
-
if (!workspacePackage) return null;
|
|
759
|
-
return inferPackageProject(options.resolvedFilePath, workspacePackage, options.projectPaths);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
//#endregion
|
|
763
|
-
//#region src/graph-rules.ts
|
|
764
|
-
const nodeBuiltinNames = new Set(builtinModules.flatMap((specifier) => {
|
|
765
|
-
const normalized = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
|
766
|
-
return [normalized, `node:${normalized}`];
|
|
767
|
-
}));
|
|
768
|
-
function isPlainRecord(value) {
|
|
769
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
770
|
-
}
|
|
771
|
-
function isNonEmptyString(value) {
|
|
772
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
773
|
-
}
|
|
774
|
-
function formatUnknownValue(value) {
|
|
775
|
-
if (value === void 0) return "undefined";
|
|
776
|
-
return JSON.stringify(value);
|
|
777
|
-
}
|
|
778
|
-
function addRuleEntryConfigProblem(problems, details) {
|
|
779
|
-
problems.push(["Invalid graph rule config:", ...details].join("\n"));
|
|
780
|
-
}
|
|
781
|
-
function isUrlOrDataOrFileSpecifier$1(specifier) {
|
|
782
|
-
return specifier.startsWith("data:") || specifier.startsWith("file:") || specifier.startsWith("http:") || specifier.startsWith("https:");
|
|
783
|
-
}
|
|
784
|
-
function isRelativeSpecifier(specifier) {
|
|
785
|
-
return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
|
|
786
|
-
}
|
|
787
|
-
function isPackageImportPattern(name) {
|
|
788
|
-
return name.startsWith("#");
|
|
789
|
-
}
|
|
790
|
-
function matchWildcardPattern(pattern, value) {
|
|
791
|
-
if (pattern === value) return true;
|
|
792
|
-
const wildcardIndex = pattern.indexOf("*");
|
|
793
|
-
if (wildcardIndex === -1) return false;
|
|
794
|
-
const prefix = pattern.slice(0, wildcardIndex);
|
|
795
|
-
const suffix = pattern.slice(wildcardIndex + 1);
|
|
796
|
-
return value.startsWith(prefix) && value.endsWith(suffix);
|
|
797
|
-
}
|
|
798
|
-
function getNodeBuiltinRuleName(name) {
|
|
799
|
-
if (name === "node:*") return {
|
|
800
|
-
matchAllNodeBuiltins: true,
|
|
801
|
-
normalizedName: "*"
|
|
802
|
-
};
|
|
803
|
-
const normalizedName = name.startsWith("node:") ? name.slice(5) : name;
|
|
804
|
-
if (!nodeBuiltinNames.has(normalizedName)) return null;
|
|
805
|
-
return {
|
|
806
|
-
matchAllNodeBuiltins: false,
|
|
807
|
-
normalizedName
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
function createNormalizedDep(name, reason) {
|
|
811
|
-
const nodeBuiltin = getNodeBuiltinRuleName(name);
|
|
812
|
-
if (nodeBuiltin) return {
|
|
813
|
-
kind: "node-builtin",
|
|
814
|
-
matchAllNodeBuiltins: nodeBuiltin.matchAllNodeBuiltins,
|
|
815
|
-
name,
|
|
816
|
-
normalizedName: nodeBuiltin.normalizedName,
|
|
817
|
-
reason
|
|
818
|
-
};
|
|
819
|
-
if (isPackageImportPattern(name)) return {
|
|
820
|
-
kind: "package-import",
|
|
821
|
-
matchAllNodeBuiltins: false,
|
|
822
|
-
name,
|
|
823
|
-
normalizedName: name,
|
|
824
|
-
reason
|
|
825
|
-
};
|
|
826
|
-
if (isRelativeSpecifier(name) || isUrlOrDataOrFileSpecifier$1(name) || path.isAbsolute(name) || getPackageRootSpecifier(name) !== name) return null;
|
|
827
|
-
return {
|
|
828
|
-
kind: "package",
|
|
829
|
-
matchAllNodeBuiltins: false,
|
|
830
|
-
name,
|
|
831
|
-
normalizedName: name,
|
|
832
|
-
reason
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
function getRulesRecord(config, problems) {
|
|
836
|
-
const rules = config.graph?.rules;
|
|
837
|
-
if (rules === void 0) return {};
|
|
838
|
-
if (!isPlainRecord(rules)) {
|
|
839
|
-
problems.push([
|
|
840
|
-
"Invalid graph rules config:",
|
|
841
|
-
" field: graph.rules",
|
|
842
|
-
` value: ${formatUnknownValue(rules)}`,
|
|
843
|
-
" reason: graph.rules must be an object keyed by Limina labels."
|
|
844
|
-
].join("\n"));
|
|
845
|
-
return {};
|
|
846
|
-
}
|
|
847
|
-
return rules;
|
|
848
|
-
}
|
|
849
|
-
function addNormalizedRuleRef(options) {
|
|
850
|
-
const field = `graph.rules.${options.label}.deny.refs[${options.index}]`;
|
|
851
|
-
if (!isPlainRecord(options.entry)) {
|
|
852
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
853
|
-
` field: ${field}`,
|
|
854
|
-
` value: ${formatUnknownValue(options.entry)}`,
|
|
855
|
-
" reason: deny.refs entries must be objects with non-empty path and reason fields."
|
|
856
|
-
]);
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
const pathValue = options.entry.path;
|
|
860
|
-
const reasonValue = options.entry.reason;
|
|
861
|
-
if (!isNonEmptyString(pathValue)) {
|
|
862
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
863
|
-
` field: ${field}.path`,
|
|
864
|
-
` value: ${formatUnknownValue(pathValue)}`,
|
|
865
|
-
" reason: deny.refs path is required and must be a non-empty string."
|
|
866
|
-
]);
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
if (!isNonEmptyString(reasonValue)) {
|
|
870
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
871
|
-
` field: ${field}.reason`,
|
|
872
|
-
` value: ${formatUnknownValue(reasonValue)}`,
|
|
873
|
-
" reason: deny.refs reason is required and must be a non-empty string."
|
|
874
|
-
]);
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
const refPath = normalizeAbsolutePath(path.resolve(options.config.rootDir, pathValue));
|
|
878
|
-
if (!options.projectPathSet.has(refPath)) {
|
|
879
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
880
|
-
` field: ${field}.path`,
|
|
881
|
-
` path: ${pathValue}`,
|
|
882
|
-
" reason: deny.refs path must point to a project reachable from a checker entry."
|
|
883
|
-
]);
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
if (!isDtsProjectConfig(refPath)) {
|
|
887
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
888
|
-
` field: ${field}.path`,
|
|
889
|
-
` path: ${pathValue}`,
|
|
890
|
-
" reason: deny.refs path must point to a tsconfig*.dts.json declaration leaf."
|
|
891
|
-
]);
|
|
892
|
-
return;
|
|
893
|
-
}
|
|
894
|
-
const refs = options.refsByLabel.get(options.label) ?? /* @__PURE__ */ new Map();
|
|
895
|
-
refs.set(refPath, {
|
|
896
|
-
path: refPath,
|
|
897
|
-
reason: reasonValue.trim()
|
|
898
|
-
});
|
|
899
|
-
options.refsByLabel.set(options.label, refs);
|
|
900
|
-
}
|
|
901
|
-
function addNormalizedDep(options) {
|
|
902
|
-
const field = `graph.rules.${options.label}.deny.deps[${options.index}]`;
|
|
903
|
-
if (!isPlainRecord(options.entry)) {
|
|
904
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
905
|
-
` field: ${field}`,
|
|
906
|
-
` value: ${formatUnknownValue(options.entry)}`,
|
|
907
|
-
" reason: deny.deps entries must be objects with non-empty name and reason fields."
|
|
908
|
-
]);
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const nameValue = options.entry.name;
|
|
912
|
-
const reasonValue = options.entry.reason;
|
|
913
|
-
if (!isNonEmptyString(nameValue)) {
|
|
914
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
915
|
-
` field: ${field}.name`,
|
|
916
|
-
` value: ${formatUnknownValue(nameValue)}`,
|
|
917
|
-
" reason: deny.deps name is required and must be a non-empty string."
|
|
918
|
-
]);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
if (!isNonEmptyString(reasonValue)) {
|
|
922
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
923
|
-
` field: ${field}.reason`,
|
|
924
|
-
` value: ${formatUnknownValue(reasonValue)}`,
|
|
925
|
-
" reason: deny.deps reason is required and must be a non-empty string."
|
|
926
|
-
]);
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
const name = nameValue.trim();
|
|
930
|
-
const normalizedDep = createNormalizedDep(name, reasonValue.trim());
|
|
931
|
-
if (!normalizedDep) {
|
|
932
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
933
|
-
` field: ${field}.name`,
|
|
934
|
-
` name: ${name}`,
|
|
935
|
-
" reason: deny.deps name must be a package root, a package.json imports specifier such as \"#internal/*\", or a Node builtin such as \"fs\", \"node:fs\", or \"node:*\"."
|
|
936
|
-
]);
|
|
937
|
-
return;
|
|
938
|
-
}
|
|
939
|
-
const deps = options.depsByLabel.get(options.label) ?? [];
|
|
940
|
-
deps.push(normalizedDep);
|
|
941
|
-
options.depsByLabel.set(options.label, deps);
|
|
942
|
-
}
|
|
943
|
-
function shouldNormalizeRuleKind(include, kind) {
|
|
944
|
-
return include?.[kind] ?? true;
|
|
945
|
-
}
|
|
946
|
-
function normalizeGraphRules(options) {
|
|
947
|
-
const depsByLabel = /* @__PURE__ */ new Map();
|
|
948
|
-
const refsByLabel = /* @__PURE__ */ new Map();
|
|
949
|
-
const projectPathSet = new Set(options.projectPaths);
|
|
950
|
-
for (const [rawLabel, rawRule] of Object.entries(getRulesRecord(options.config, options.problems))) {
|
|
951
|
-
const label = rawLabel.trim();
|
|
952
|
-
if (!label) {
|
|
953
|
-
addRuleEntryConfigProblem(options.problems, [" field: graph.rules", " reason: graph.rules keys must be non-empty labels."]);
|
|
954
|
-
continue;
|
|
955
|
-
}
|
|
956
|
-
if (!isPlainRecord(rawRule)) {
|
|
957
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
958
|
-
` field: graph.rules.${rawLabel}`,
|
|
959
|
-
` value: ${formatUnknownValue(rawRule)}`,
|
|
960
|
-
" reason: each graph rule must be an object."
|
|
961
|
-
]);
|
|
962
|
-
continue;
|
|
963
|
-
}
|
|
964
|
-
if (rawRule.deny === void 0) continue;
|
|
965
|
-
if (!isPlainRecord(rawRule.deny)) {
|
|
966
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
967
|
-
` field: graph.rules.${label}.deny`,
|
|
968
|
-
` value: ${formatUnknownValue(rawRule.deny)}`,
|
|
969
|
-
" reason: graph rule deny must be an object."
|
|
970
|
-
]);
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
const refs = rawRule.deny.refs;
|
|
974
|
-
if (shouldNormalizeRuleKind(options.include, "refs") && refs !== void 0) if (!Array.isArray(refs)) addRuleEntryConfigProblem(options.problems, [
|
|
975
|
-
` field: graph.rules.${label}.deny.refs`,
|
|
976
|
-
` value: ${formatUnknownValue(refs)}`,
|
|
977
|
-
" reason: deny.refs must be an array."
|
|
978
|
-
]);
|
|
979
|
-
else refs.forEach((entry, index) => {
|
|
980
|
-
addNormalizedRuleRef({
|
|
981
|
-
config: options.config,
|
|
982
|
-
entry,
|
|
983
|
-
index,
|
|
984
|
-
label,
|
|
985
|
-
problems: options.problems,
|
|
986
|
-
projectPathSet,
|
|
987
|
-
refsByLabel
|
|
988
|
-
});
|
|
989
|
-
});
|
|
990
|
-
if (shouldNormalizeRuleKind(options.include, "deps") && rawRule.deny.workspaceDeps !== void 0) addRuleEntryConfigProblem(options.problems, [
|
|
991
|
-
` field: graph.rules.${label}.deny.workspaceDeps`,
|
|
992
|
-
` value: ${formatUnknownValue(rawRule.deny.workspaceDeps)}`,
|
|
993
|
-
" reason: deny.workspaceDeps has been removed; use deny.deps."
|
|
994
|
-
]);
|
|
995
|
-
if (shouldNormalizeRuleKind(options.include, "deps") && rawRule.deny.nodeBuiltins !== void 0) addRuleEntryConfigProblem(options.problems, [
|
|
996
|
-
` field: graph.rules.${label}.deny.nodeBuiltins`,
|
|
997
|
-
` value: ${formatUnknownValue(rawRule.deny.nodeBuiltins)}`,
|
|
998
|
-
" reason: deny.nodeBuiltins has been removed; use deny.deps."
|
|
999
|
-
]);
|
|
1000
|
-
const deps = rawRule.deny.deps;
|
|
1001
|
-
if (shouldNormalizeRuleKind(options.include, "deps") && deps !== void 0) if (!Array.isArray(deps)) addRuleEntryConfigProblem(options.problems, [
|
|
1002
|
-
` field: graph.rules.${label}.deny.deps`,
|
|
1003
|
-
` value: ${formatUnknownValue(deps)}`,
|
|
1004
|
-
" reason: deny.deps must be an array."
|
|
1005
|
-
]);
|
|
1006
|
-
else deps.forEach((entry, index) => {
|
|
1007
|
-
addNormalizedDep({
|
|
1008
|
-
depsByLabel,
|
|
1009
|
-
entry,
|
|
1010
|
-
index,
|
|
1011
|
-
label,
|
|
1012
|
-
problems: options.problems
|
|
1013
|
-
});
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
return {
|
|
1017
|
-
depsByLabel,
|
|
1018
|
-
refsByLabel
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
function isNodeBuiltinSpecifier(specifier) {
|
|
1022
|
-
return nodeBuiltinNames.has(specifier);
|
|
1023
|
-
}
|
|
1024
|
-
function getDeniedRefRule(rules, label, targetProjectPath) {
|
|
1025
|
-
if (!label) return null;
|
|
1026
|
-
return rules.refsByLabel.get(label)?.get(targetProjectPath) ?? null;
|
|
1027
|
-
}
|
|
1028
|
-
function getRuleDeps(rules, label) {
|
|
1029
|
-
if (!label) return [];
|
|
1030
|
-
return rules.depsByLabel.get(label) ?? [];
|
|
1031
|
-
}
|
|
1032
|
-
function getDeniedDepRuleForPackage(rules, label, packageName) {
|
|
1033
|
-
return getRuleDeps(rules, label).find((rule) => rule.kind === "package" && rule.normalizedName === packageName) ?? null;
|
|
1034
|
-
}
|
|
1035
|
-
function getDeniedDepRuleForSpecifier(rules, label, specifier) {
|
|
1036
|
-
const deps = getRuleDeps(rules, label);
|
|
1037
|
-
const packageImportRule = deps.find((rule) => rule.kind === "package-import" && matchWildcardPattern(rule.normalizedName, specifier));
|
|
1038
|
-
if (packageImportRule) return packageImportRule;
|
|
1039
|
-
if (isNodeBuiltinSpecifier(specifier)) {
|
|
1040
|
-
const normalizedSpecifier = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
|
|
1041
|
-
const nodeRule = deps.find((rule) => rule.kind === "node-builtin" && (rule.matchAllNodeBuiltins || rule.normalizedName === normalizedSpecifier));
|
|
1042
|
-
if (nodeRule) return nodeRule;
|
|
1043
|
-
}
|
|
1044
|
-
if (isRelativeSpecifier(specifier) || isPackageImportPattern(specifier) || isUrlOrDataOrFileSpecifier$1(specifier) || path.isAbsolute(specifier)) return null;
|
|
1045
|
-
return getDeniedDepRuleForPackage(rules, label, getPackageRootSpecifier(specifier));
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
//#endregion
|
|
1049
|
-
//#region src/logger.ts
|
|
1050
|
-
const logger = createLogger({ main: "limina" });
|
|
1051
|
-
const CliLogger = logger.getLoggerByGroup("task.cli");
|
|
1052
|
-
const GraphLogger = logger.getLoggerByGroup("task.graph");
|
|
1053
|
-
const InitLogger = logger.getLoggerByGroup("task.init");
|
|
1054
|
-
const PackageLogger = logger.getLoggerByGroup("task.package");
|
|
1055
|
-
const PathsLogger = logger.getLoggerByGroup("task.paths");
|
|
1056
|
-
const ProofLogger = logger.getLoggerByGroup("task.proof");
|
|
1057
|
-
const ReleaseLogger = logger.getLoggerByGroup("task.release");
|
|
1058
|
-
const SourceLogger = logger.getLoggerByGroup("task.source");
|
|
1059
|
-
const TypecheckLogger = logger.getLoggerByGroup("task.typecheck");
|
|
1060
|
-
function clearCliScreen() {
|
|
1061
|
-
if (!process.stdout.isTTY || process.env.CI) return;
|
|
1062
|
-
const repeatCount = (process.stdout.rows ?? 0) - 2;
|
|
1063
|
-
const blank = repeatCount > 0 ? "\n".repeat(repeatCount) : "";
|
|
1064
|
-
process.stdout.write(blank);
|
|
1065
|
-
readline.cursorTo(process.stdout, 0, 0);
|
|
1066
|
-
readline.clearScreenDown(process.stdout);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
//#endregion
|
|
1070
|
-
//#region src/commands/init.ts
|
|
1071
|
-
const pnpmWorkspaceFileName = "pnpm-workspace.yaml";
|
|
1072
|
-
const liminaConfigFileName = "limina.config.mjs";
|
|
1073
|
-
const liminaCheckScriptName = "limina:check";
|
|
1074
|
-
const liminaCheckScriptValue = "limina check";
|
|
1075
|
-
const ignoredGlobPatterns = [
|
|
1076
|
-
"**/.git/**",
|
|
1077
|
-
"**/.limina/**",
|
|
1078
|
-
"**/.pnpm-store/**",
|
|
1079
|
-
"**/.tsbuild/**",
|
|
1080
|
-
"**/coverage/**",
|
|
1081
|
-
"**/dist/**",
|
|
1082
|
-
"**/node_modules/**"
|
|
1083
|
-
];
|
|
1084
|
-
function findPnpmWorkspaceRoot(startDir) {
|
|
1085
|
-
let currentDir = path.resolve(startDir);
|
|
1086
|
-
while (true) {
|
|
1087
|
-
if (existsSync(path.join(currentDir, pnpmWorkspaceFileName))) return normalizeAbsolutePath(currentDir);
|
|
1088
|
-
const parentDir = path.dirname(currentDir);
|
|
1089
|
-
if (parentDir === currentDir) return null;
|
|
1090
|
-
currentDir = parentDir;
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
function createInitConfig(rootDir) {
|
|
1094
|
-
return {
|
|
1095
|
-
configPath: path.join(rootDir, liminaConfigFileName),
|
|
1096
|
-
rootDir
|
|
1097
|
-
};
|
|
1098
|
-
}
|
|
1099
|
-
function formatConfigPath(rootDir, configPath) {
|
|
1100
|
-
return toRelativePath(rootDir, configPath);
|
|
1101
|
-
}
|
|
1102
|
-
function formatReferencePath(fromConfigPath, toConfigPath) {
|
|
1103
|
-
const relativePath = toPosixPath(path.relative(path.dirname(fromConfigPath), toConfigPath));
|
|
1104
|
-
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
1105
|
-
}
|
|
1106
|
-
function stringifyJson(value) {
|
|
1107
|
-
return `${JSON.stringify(value, null, 2)}\n`;
|
|
1108
|
-
}
|
|
1109
|
-
function getProjectScope(configPath) {
|
|
1110
|
-
const fileName = path.basename(configPath);
|
|
1111
|
-
if (fileName === "tsconfig.json") return "tsconfig";
|
|
1112
|
-
return /^tsconfig\.(.+)\.json$/u.exec(fileName)?.[1] ?? "tsconfig";
|
|
1113
|
-
}
|
|
1114
|
-
function getDtsConfigPath(configPath) {
|
|
1115
|
-
const directory = path.dirname(configPath);
|
|
1116
|
-
const fileName = path.basename(configPath);
|
|
1117
|
-
const dtsFileName = fileName === "tsconfig.json" ? "tsconfig.dts.json" : fileName.replace(/\.json$/u, ".dts.json");
|
|
1118
|
-
return path.join(directory, dtsFileName);
|
|
1119
|
-
}
|
|
1120
|
-
function hasReferences(configObject) {
|
|
1121
|
-
return Array.isArray(configObject.references) && configObject.references.length > 0;
|
|
1122
|
-
}
|
|
1123
|
-
function parseTypeScriptConfig(rootDir, configPath) {
|
|
1124
|
-
const diagnostics = [];
|
|
1125
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
1126
|
-
...ts.sys,
|
|
1127
|
-
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
1128
|
-
diagnostics.push(diagnostic);
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
1132
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
1133
|
-
getCurrentDirectory: () => rootDir,
|
|
1134
|
-
getNewLine: () => "\n"
|
|
1135
|
-
}));
|
|
1136
|
-
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
1137
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
1138
|
-
getCurrentDirectory: () => rootDir,
|
|
1139
|
-
getNewLine: () => "\n"
|
|
1140
|
-
}));
|
|
1141
|
-
return {
|
|
1142
|
-
fileNames: parsed.fileNames.map(normalizeAbsolutePath),
|
|
1143
|
-
options: parsed.options
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
function findNearestWorkspacePackage(filePath, packages) {
|
|
1147
|
-
return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
|
|
1148
|
-
}
|
|
1149
|
-
function collectWorkspaceDependencyNames(manifest) {
|
|
1150
|
-
const dependencyNames = /* @__PURE__ */ new Set();
|
|
1151
|
-
for (const dependencies of getDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) if (isWorkspaceDependencySpecifier(specifier)) dependencyNames.add(dependencyName);
|
|
1152
|
-
return dependencyNames;
|
|
1153
|
-
}
|
|
1154
|
-
function findOwningProjectForFile(filePath, projects) {
|
|
1155
|
-
const normalizedFilePath = normalizeAbsolutePath(filePath);
|
|
1156
|
-
return projects.filter((project) => project.fileNames.includes(normalizedFilePath)).sort((left, right) => {
|
|
1157
|
-
const depthDelta = path.dirname(right.configPath).length - path.dirname(left.configPath).length;
|
|
1158
|
-
return depthDelta === 0 ? left.configPath.localeCompare(right.configPath) : depthDelta;
|
|
1159
|
-
})[0] ?? null;
|
|
1160
|
-
}
|
|
1161
|
-
function isDirectory(filePath) {
|
|
1162
|
-
try {
|
|
1163
|
-
return statSync(filePath).isDirectory();
|
|
1164
|
-
} catch {
|
|
1165
|
-
return false;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
function mapVirtualWorkspacePath(filePath, packageByName) {
|
|
1169
|
-
const segments = normalizeAbsolutePath(filePath).split("/");
|
|
1170
|
-
for (let index = 0; index < segments.length; index += 1) {
|
|
1171
|
-
if (segments[index] !== "node_modules") continue;
|
|
1172
|
-
const firstPackageSegment = segments[index + 1];
|
|
1173
|
-
if (!firstPackageSegment) continue;
|
|
1174
|
-
const isScopedPackage = firstPackageSegment.startsWith("@");
|
|
1175
|
-
const packageName = isScopedPackage ? `${firstPackageSegment}/${segments[index + 2] ?? ""}` : firstPackageSegment;
|
|
1176
|
-
const restStart = index + (isScopedPackage ? 3 : 2);
|
|
1177
|
-
const workspacePackage = packageByName.get(packageName);
|
|
1178
|
-
if (!workspacePackage) continue;
|
|
1179
|
-
return path.join(workspacePackage.directory, ...segments.slice(restStart));
|
|
1180
|
-
}
|
|
1181
|
-
return null;
|
|
1182
|
-
}
|
|
1183
|
-
function isVirtualWorkspaceDirectory(directoryPath, packageByName) {
|
|
1184
|
-
const segments = normalizeAbsolutePath(directoryPath).split("/");
|
|
1185
|
-
for (let index = 0; index < segments.length; index += 1) {
|
|
1186
|
-
if (segments[index] !== "node_modules") continue;
|
|
1187
|
-
const firstPackageSegment = segments[index + 1];
|
|
1188
|
-
if (!firstPackageSegment) return packageByName.size > 0;
|
|
1189
|
-
if (firstPackageSegment.startsWith("@") && segments[index + 2] === void 0) return [...packageByName.keys()].some((packageName) => packageName.startsWith(`${firstPackageSegment}/`));
|
|
1190
|
-
}
|
|
1191
|
-
const mappedPath = mapVirtualWorkspacePath(directoryPath, packageByName);
|
|
1192
|
-
return mappedPath ? isDirectory(mappedPath) : false;
|
|
1193
|
-
}
|
|
1194
|
-
function createWorkspaceModuleResolutionHost(options) {
|
|
1195
|
-
const mapPath = (filePath) => mapVirtualWorkspacePath(filePath, options.packageByName) ?? filePath;
|
|
1196
|
-
return {
|
|
1197
|
-
directoryExists: (directoryPath) => isVirtualWorkspaceDirectory(directoryPath, options.packageByName) || (ts.sys.directoryExists?.(directoryPath) ?? isDirectory(directoryPath)),
|
|
1198
|
-
fileExists: (filePath) => {
|
|
1199
|
-
const mappedPath = mapPath(filePath);
|
|
1200
|
-
return existsSync(mappedPath) && !isDirectory(mappedPath);
|
|
1201
|
-
},
|
|
1202
|
-
getCurrentDirectory: () => options.rootDir,
|
|
1203
|
-
readFile: (filePath) => ts.sys.readFile(mapPath(filePath)),
|
|
1204
|
-
realpath: (filePath) => normalizeAbsolutePath(mapPath(filePath)),
|
|
1205
|
-
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
function resolveImportWithTypeScript(options) {
|
|
1209
|
-
const resolvedModule = ts.resolveModuleName(options.importRecord.specifier, options.importRecord.filePath, options.project.options, options.host, options.cache).resolvedModule;
|
|
1210
|
-
if (!resolvedModule?.resolvedFileName) return null;
|
|
1211
|
-
return normalizeAbsolutePath(mapVirtualWorkspacePath(resolvedModule.resolvedFileName, options.packageByName) ?? resolvedModule.resolvedFileName);
|
|
1212
|
-
}
|
|
1213
|
-
function isPackageImportSpecifier$1(specifier) {
|
|
1214
|
-
return specifier.startsWith("#");
|
|
1215
|
-
}
|
|
1216
|
-
function createDtsConfig(project) {
|
|
1217
|
-
const fileName = path.basename(project.configPath);
|
|
1218
|
-
const scope = project.scope;
|
|
1219
|
-
const output = {
|
|
1220
|
-
$schema: "https://json.schemastore.org/tsconfig",
|
|
1221
|
-
extends: [`./${fileName}`],
|
|
1222
|
-
compilerOptions: {
|
|
1223
|
-
composite: true,
|
|
1224
|
-
incremental: true,
|
|
1225
|
-
noEmit: false,
|
|
1226
|
-
declaration: true,
|
|
1227
|
-
emitDeclarationOnly: true,
|
|
1228
|
-
declarationMap: false,
|
|
1229
|
-
rootDir: ".",
|
|
1230
|
-
outDir: "./.limina",
|
|
1231
|
-
tsBuildInfoFile: `./.limina/${scope}.tsbuildinfo`
|
|
1232
|
-
}
|
|
1233
|
-
};
|
|
1234
|
-
if (project.references.length > 0) output.references = project.references.map((referencePath) => ({ path: referencePath }));
|
|
1235
|
-
return output;
|
|
1236
|
-
}
|
|
1237
|
-
function createBuildAggregator(references) {
|
|
1238
|
-
return {
|
|
1239
|
-
$schema: "https://json.schemastore.org/tsconfig",
|
|
1240
|
-
files: [],
|
|
1241
|
-
references: references.map((referencePath) => ({ path: referencePath }))
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
function createLiminaConfigContent() {
|
|
1245
|
-
return `import { defineConfig } from 'limina';
|
|
1246
|
-
|
|
1247
|
-
export default defineConfig({
|
|
1248
|
-
// Shared checker entries used by graph, proof, paths, and typecheck checks.
|
|
1249
|
-
config: {
|
|
1250
|
-
checkers: {
|
|
1251
|
-
typescript: {
|
|
1252
|
-
preset: 'tsc',
|
|
1253
|
-
entry: 'tsconfig.build.json',
|
|
1254
|
-
},
|
|
1255
|
-
},
|
|
1256
|
-
},
|
|
1257
|
-
});
|
|
1258
|
-
`;
|
|
1259
|
-
}
|
|
1260
|
-
async function confirmAction(options, message) {
|
|
1261
|
-
if (options.yes) return true;
|
|
1262
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error(`${message} Run limina init --yes to accept the default confirmation in non-interactive environments.`);
|
|
1263
|
-
const result = await prompts.confirm({
|
|
1264
|
-
initialValue: true,
|
|
1265
|
-
message
|
|
1266
|
-
});
|
|
1267
|
-
if (prompts.isCancel(result)) throw new Error("limina init canceled.");
|
|
1268
|
-
return result;
|
|
1269
|
-
}
|
|
1270
|
-
async function writeTextFile(filePath, content, writtenFiles) {
|
|
1271
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
1272
|
-
await writeFile(filePath, content);
|
|
1273
|
-
writtenFiles.push(filePath);
|
|
1274
|
-
}
|
|
1275
|
-
async function writeBuildAggregatorFile(options) {
|
|
1276
|
-
if (options.references.length === 0) return false;
|
|
1277
|
-
await writeTextFile(options.configPath, stringifyJson(createBuildAggregator(options.references)), options.writtenFiles);
|
|
1278
|
-
return true;
|
|
1279
|
-
}
|
|
1280
|
-
function findPnpmWorkspacePath(startDir) {
|
|
1281
|
-
const rootDir = findPnpmWorkspaceRoot(startDir);
|
|
1282
|
-
return rootDir ? path.join(rootDir, pnpmWorkspaceFileName) : null;
|
|
1283
|
-
}
|
|
1284
|
-
function resolveCatalogRange(range, packageName, packageManifestPath) {
|
|
1285
|
-
if (!range) return null;
|
|
1286
|
-
if (!range.startsWith("catalog:")) return range;
|
|
1287
|
-
const workspacePath = findPnpmWorkspacePath(path.dirname(packageManifestPath));
|
|
1288
|
-
if (!workspacePath || !existsSync(workspacePath)) return null;
|
|
1289
|
-
const parsed = parse(readFileSync(workspacePath, "utf8"));
|
|
1290
|
-
const catalogName = range.slice(8);
|
|
1291
|
-
if (catalogName.length === 0 || catalogName === "default") return parsed?.catalog?.[packageName] ?? null;
|
|
1292
|
-
return parsed?.catalogs?.[catalogName]?.[packageName] ?? null;
|
|
1293
|
-
}
|
|
1294
|
-
function readLiminaPackageMetadata() {
|
|
1295
|
-
const manifestPath = createRequire(import.meta.url).resolve("limina/package.json");
|
|
1296
|
-
const manifest = readJsonFile(manifestPath);
|
|
1297
|
-
const versionRange = manifest.version ? `^${manifest.version}` : "^0.0.1";
|
|
1298
|
-
const rawTypeScriptRange = manifest.peerDependencies?.typescript ?? manifest.devDependencies?.typescript ?? manifest.dependencies?.typescript;
|
|
1299
|
-
return {
|
|
1300
|
-
typescriptRange: resolveCatalogRange(rawTypeScriptRange, "typescript", manifestPath) ?? rawTypeScriptRange ?? "^5.9.0",
|
|
1301
|
-
versionRange
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
|
-
function hasDependency(manifest, dependencyName) {
|
|
1305
|
-
return Boolean(manifest.dependencies?.[dependencyName] || manifest.devDependencies?.[dependencyName] || manifest.optionalDependencies?.[dependencyName] || manifest.peerDependencies?.[dependencyName]);
|
|
1306
|
-
}
|
|
1307
|
-
async function updateRootPackageJson(options) {
|
|
1308
|
-
const packageJsonPath = path.join(options.rootDir, "package.json");
|
|
1309
|
-
let installRequired = false;
|
|
1310
|
-
if (!existsSync(packageJsonPath)) {
|
|
1311
|
-
if (!await confirmAction(options.prompt, `No package.json found at ${formatConfigPath(options.rootDir, packageJsonPath)}. Create one?`)) {
|
|
1312
|
-
options.skippedFiles.push(packageJsonPath);
|
|
1313
|
-
return false;
|
|
1314
|
-
}
|
|
1315
|
-
await writeTextFile(packageJsonPath, stringifyJson({
|
|
1316
|
-
private: true,
|
|
1317
|
-
type: "module",
|
|
1318
|
-
scripts: { [liminaCheckScriptName]: liminaCheckScriptValue },
|
|
1319
|
-
devDependencies: {
|
|
1320
|
-
limina: options.metadata.versionRange,
|
|
1321
|
-
typescript: options.metadata.typescriptRange
|
|
1322
|
-
}
|
|
1323
|
-
}), options.writtenFiles);
|
|
1324
|
-
return true;
|
|
1325
|
-
}
|
|
1326
|
-
const manifest = readJsonFile(packageJsonPath);
|
|
1327
|
-
const scripts = { ...manifest.scripts ?? {} };
|
|
1328
|
-
let changed = false;
|
|
1329
|
-
if (scripts[liminaCheckScriptName] && scripts[liminaCheckScriptName] !== liminaCheckScriptValue) {
|
|
1330
|
-
if (await confirmAction(options.prompt, `Script "${liminaCheckScriptName}" already exists in package.json. Overwrite it?`)) {
|
|
1331
|
-
scripts[liminaCheckScriptName] = liminaCheckScriptValue;
|
|
1332
|
-
changed = true;
|
|
1333
|
-
}
|
|
1334
|
-
} else if (!scripts[liminaCheckScriptName]) {
|
|
1335
|
-
scripts[liminaCheckScriptName] = liminaCheckScriptValue;
|
|
1336
|
-
changed = true;
|
|
1337
|
-
}
|
|
1338
|
-
if (!hasDependency(manifest, "limina")) {
|
|
1339
|
-
manifest.devDependencies = {
|
|
1340
|
-
...manifest.devDependencies ?? {},
|
|
1341
|
-
limina: options.metadata.versionRange
|
|
1342
|
-
};
|
|
1343
|
-
installRequired = true;
|
|
1344
|
-
changed = true;
|
|
1345
|
-
}
|
|
1346
|
-
if (changed) await writeTextFile(packageJsonPath, stringifyJson({
|
|
1347
|
-
...manifest,
|
|
1348
|
-
scripts
|
|
1349
|
-
}), options.writtenFiles);
|
|
1350
|
-
return installRequired;
|
|
1351
|
-
}
|
|
1352
|
-
async function collectReservedConfigConflicts(rootDir) {
|
|
1353
|
-
const conflicts = await glob([
|
|
1354
|
-
"tsconfig*.build.json",
|
|
1355
|
-
"**/tsconfig*.build.json",
|
|
1356
|
-
"tsconfig*.dts.json",
|
|
1357
|
-
"**/tsconfig*.dts.json"
|
|
1358
|
-
], {
|
|
1359
|
-
absolute: false,
|
|
1360
|
-
cwd: rootDir,
|
|
1361
|
-
ignore: ignoredGlobPatterns
|
|
1362
|
-
});
|
|
1363
|
-
return [...new Set(conflicts)].sort();
|
|
1364
|
-
}
|
|
1365
|
-
async function collectOrdinaryTsconfigPaths(rootDir) {
|
|
1366
|
-
const configPaths = await glob(["tsconfig*.json", "**/tsconfig*.json"], {
|
|
1367
|
-
absolute: false,
|
|
1368
|
-
cwd: rootDir,
|
|
1369
|
-
ignore: ignoredGlobPatterns
|
|
1370
|
-
});
|
|
1371
|
-
return [...new Set(configPaths)].map((configPath) => normalizeAbsolutePath(path.join(rootDir, configPath))).filter(isOrdinaryTypecheckConfigPath).sort();
|
|
1372
|
-
}
|
|
1373
|
-
function analyzeTypecheckProjects(options) {
|
|
1374
|
-
const problems = [];
|
|
1375
|
-
const projects = [];
|
|
1376
|
-
for (const configPath of options.configPaths) {
|
|
1377
|
-
const configObject = readJsonConfig(options.config, configPath);
|
|
1378
|
-
const parsed = parseTypeScriptConfig(options.config.rootDir, configPath);
|
|
1379
|
-
const hasProjectReferences = hasReferences(configObject);
|
|
1380
|
-
const fileName = path.basename(configPath);
|
|
1381
|
-
if (fileName === "tsconfig.json" && hasProjectReferences && parsed.fileNames.length > 0) {
|
|
1382
|
-
problems.push([
|
|
1383
|
-
"Invalid tsconfig role:",
|
|
1384
|
-
` config: ${formatConfigPath(options.config.rootDir, configPath)}`,
|
|
1385
|
-
" reason: tsconfig.json must be either a pure aggregator with files: [] and references, or a typecheck leaf with source files, but not both."
|
|
1386
|
-
].join("\n"));
|
|
1387
|
-
continue;
|
|
1388
|
-
}
|
|
1389
|
-
if (fileName !== "tsconfig.json" && hasProjectReferences) {
|
|
1390
|
-
problems.push([
|
|
1391
|
-
"Invalid scoped tsconfig role:",
|
|
1392
|
-
` config: ${formatConfigPath(options.config.rootDir, configPath)}`,
|
|
1393
|
-
" reason: tsconfig.<scope>.json files may only be typecheck leaves; graph aggregation belongs in tsconfig*.build.json."
|
|
1394
|
-
].join("\n"));
|
|
1395
|
-
continue;
|
|
1396
|
-
}
|
|
1397
|
-
if (fileName === "tsconfig.json" && hasProjectReferences) continue;
|
|
1398
|
-
projects.push({
|
|
1399
|
-
configPath,
|
|
1400
|
-
dtsConfigPath: getDtsConfigPath(configPath),
|
|
1401
|
-
fileNames: parsed.fileNames,
|
|
1402
|
-
options: parsed.options,
|
|
1403
|
-
owner: findNearestWorkspacePackage(configPath, options.workspacePackages),
|
|
1404
|
-
references: [],
|
|
1405
|
-
scope: getProjectScope(configPath)
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
return {
|
|
1409
|
-
problems,
|
|
1410
|
-
projects
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
function inferProjectReferences(options) {
|
|
1414
|
-
const problems = [];
|
|
1415
|
-
const packageByName = new Map(options.workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]));
|
|
1416
|
-
const host = createWorkspaceModuleResolutionHost({
|
|
1417
|
-
packageByName,
|
|
1418
|
-
rootDir: options.config.rootDir
|
|
1419
|
-
});
|
|
1420
|
-
for (const project of options.projects) {
|
|
1421
|
-
if (!project.owner) continue;
|
|
1422
|
-
const workspaceDependencyNames = collectWorkspaceDependencyNames(project.owner.manifest);
|
|
1423
|
-
const resolutionCache = ts.createModuleResolutionCache(path.dirname(project.configPath), (fileName) => fileName, project.options);
|
|
1424
|
-
const referencePaths = /* @__PURE__ */ new Set();
|
|
1425
|
-
for (const fileName of project.fileNames) {
|
|
1426
|
-
if (!/\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)) continue;
|
|
1427
|
-
for (const importRecord of collectImportsFromFile(fileName, options.config.rootDir)) {
|
|
1428
|
-
const resolvedFilePath = resolveImportWithTypeScript({
|
|
1429
|
-
cache: resolutionCache,
|
|
1430
|
-
host,
|
|
1431
|
-
importRecord,
|
|
1432
|
-
packageByName,
|
|
1433
|
-
project
|
|
1434
|
-
});
|
|
1435
|
-
const packageName = isRelativeSpecifier$1(importRecord.specifier) || isPackageImportSpecifier$1(importRecord.specifier) ? null : getPackageRootSpecifier(importRecord.specifier);
|
|
1436
|
-
const targetPackage = packageName ? packageByName.get(packageName) : null;
|
|
1437
|
-
const isWorkspaceGraphDependency = targetPackage && (targetPackage.name === project.owner.name || workspaceDependencyNames.has(targetPackage.name));
|
|
1438
|
-
if (packageName && workspaceDependencyNames.has(packageName) && !targetPackage) {
|
|
1439
|
-
problems.push([
|
|
1440
|
-
"Workspace dependency was not discovered by pnpm:",
|
|
1441
|
-
` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
|
|
1442
|
-
` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
1443
|
-
` imported specifier: ${importRecord.specifier}`,
|
|
1444
|
-
` package: ${packageName}`,
|
|
1445
|
-
" reason: package.json declares this dependency with the workspace: protocol, but limina init could not find a matching workspace package."
|
|
1446
|
-
].join("\n"));
|
|
1447
|
-
continue;
|
|
1448
|
-
}
|
|
1449
|
-
if (targetPackage && !isWorkspaceGraphDependency) continue;
|
|
1450
|
-
if (!resolvedFilePath) {
|
|
1451
|
-
if (targetPackage && isWorkspaceGraphDependency) problems.push([
|
|
1452
|
-
"Unable to resolve workspace import with TypeScript:",
|
|
1453
|
-
` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
|
|
1454
|
-
` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
1455
|
-
` imported specifier: ${importRecord.specifier}`,
|
|
1456
|
-
` package: ${targetPackage.name}`,
|
|
1457
|
-
" reason: workspace:* imports must resolve with the project TypeScript compilerOptions before limina init can generate project references."
|
|
1458
|
-
].join("\n"));
|
|
1459
|
-
continue;
|
|
1460
|
-
}
|
|
1461
|
-
if (isRelativeSpecifier$1(importRecord.specifier)) {
|
|
1462
|
-
const sourcePackage = findNearestWorkspacePackage(importRecord.filePath, options.workspacePackages);
|
|
1463
|
-
const resolvedPackage = findNearestWorkspacePackage(resolvedFilePath, options.workspacePackages);
|
|
1464
|
-
if (sourcePackage && resolvedPackage && sourcePackage.name !== resolvedPackage.name) continue;
|
|
1465
|
-
}
|
|
1466
|
-
const targetProject = findOwningProjectForFile(resolvedFilePath, options.projects);
|
|
1467
|
-
if (!targetProject) {
|
|
1468
|
-
if (targetPackage && isWorkspaceGraphDependency) problems.push([
|
|
1469
|
-
"Unable to map workspace import to a generated declaration leaf:",
|
|
1470
|
-
` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
|
|
1471
|
-
` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
1472
|
-
` imported specifier: ${importRecord.specifier}`,
|
|
1473
|
-
` resolved file: ${formatConfigPath(options.config.rootDir, resolvedFilePath)}`,
|
|
1474
|
-
" reason: TypeScript resolved this workspace import, but the resolved module is not covered by any ordinary tsconfig*.json leaf."
|
|
1475
|
-
].join("\n"));
|
|
1476
|
-
continue;
|
|
1477
|
-
}
|
|
1478
|
-
if (targetProject.dtsConfigPath !== project.dtsConfigPath) referencePaths.add(formatReferencePath(project.dtsConfigPath, targetProject.dtsConfigPath));
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
project.references = [...referencePaths].sort();
|
|
1482
|
-
}
|
|
1483
|
-
return problems;
|
|
1484
|
-
}
|
|
1485
|
-
function collectProjectReferencesForOwner(options) {
|
|
1486
|
-
return options.projects.filter((project) => project.owner?.directory === options.owner?.directory).map((project) => formatReferencePath(options.targetConfigPath, project.dtsConfigPath)).sort();
|
|
1487
|
-
}
|
|
1488
|
-
function collectRootBuildProjectReferences(options) {
|
|
1489
|
-
return options.projects.filter((project) => !project.owner || project.owner.directory === options.rootDir || path.dirname(project.configPath) === options.rootDir).map((project) => formatReferencePath(options.targetConfigPath, project.dtsConfigPath)).sort();
|
|
1490
|
-
}
|
|
1491
|
-
async function writeGeneratedTsconfigs(options) {
|
|
1492
|
-
for (const project of options.projects) await writeTextFile(project.dtsConfigPath, stringifyJson(createDtsConfig(project)), options.writtenFiles);
|
|
1493
|
-
const nonRootWorkspacePackages = options.workspacePackages.filter((workspacePackage) => workspacePackage.directory !== options.rootDir);
|
|
1494
|
-
const workspaceBuildConfigPaths = [];
|
|
1495
|
-
for (const workspacePackage of nonRootWorkspacePackages) {
|
|
1496
|
-
const buildConfigPath = path.join(workspacePackage.directory, "tsconfig.build.json");
|
|
1497
|
-
if (await writeBuildAggregatorFile({
|
|
1498
|
-
configPath: buildConfigPath,
|
|
1499
|
-
references: collectProjectReferencesForOwner({
|
|
1500
|
-
owner: workspacePackage,
|
|
1501
|
-
projects: options.projects,
|
|
1502
|
-
targetConfigPath: buildConfigPath
|
|
1503
|
-
}),
|
|
1504
|
-
writtenFiles: options.writtenFiles
|
|
1505
|
-
})) workspaceBuildConfigPaths.push(buildConfigPath);
|
|
1506
|
-
}
|
|
1507
|
-
const rootBuildConfigPath = path.join(options.rootDir, "tsconfig.build.json");
|
|
1508
|
-
await writeBuildAggregatorFile({
|
|
1509
|
-
configPath: rootBuildConfigPath,
|
|
1510
|
-
references: [...collectRootBuildProjectReferences({
|
|
1511
|
-
projects: options.projects,
|
|
1512
|
-
rootDir: options.rootDir,
|
|
1513
|
-
targetConfigPath: rootBuildConfigPath
|
|
1514
|
-
}), ...workspaceBuildConfigPaths.map((buildConfigPath) => formatReferencePath(rootBuildConfigPath, buildConfigPath))].sort(),
|
|
1515
|
-
writtenFiles: options.writtenFiles
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
async function writeLiminaConfig(options) {
|
|
1519
|
-
const configPath = path.join(options.rootDir, liminaConfigFileName);
|
|
1520
|
-
if (existsSync(configPath)) {
|
|
1521
|
-
if (!await confirmAction(options.prompt, `${liminaConfigFileName} already exists. Overwrite it?`)) {
|
|
1522
|
-
options.skippedFiles.push(configPath);
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
await writeTextFile(configPath, createLiminaConfigContent(), options.writtenFiles);
|
|
1527
|
-
}
|
|
1528
|
-
async function runInitInternal(options) {
|
|
1529
|
-
const cwd = normalizeAbsolutePath(options.cwd ?? process.cwd());
|
|
1530
|
-
const rootDir = findPnpmWorkspaceRoot(cwd);
|
|
1531
|
-
if (!rootDir) throw new Error(`Unable to run limina init from ${cwd}: no pnpm-workspace.yaml was found in this directory or its parents.`);
|
|
1532
|
-
const rootPackageJsonPath = path.join(rootDir, "package.json");
|
|
1533
|
-
const rootPackageName = existsSync(rootPackageJsonPath) ? readJsonFile(rootPackageJsonPath).name : void 0;
|
|
1534
|
-
if (!await confirmAction(options, `Use pnpm workspace ${rootPackageName ? `"${rootPackageName}" ` : ""}at ${rootDir}?`)) throw new Error("limina init canceled.");
|
|
1535
|
-
const reservedConflicts = await collectReservedConfigConflicts(rootDir);
|
|
1536
|
-
if (reservedConflicts.length > 0) throw new Error([
|
|
1537
|
-
"Unable to run limina init because reserved Limina tsconfig names already exist:",
|
|
1538
|
-
...reservedConflicts.map((configPath) => ` - ${configPath}`),
|
|
1539
|
-
"reason: tsconfig*.build.json and tsconfig*.dts.json are Limina init output names; rename existing files before running init."
|
|
1540
|
-
].join("\n"));
|
|
1541
|
-
const config = createInitConfig(rootDir);
|
|
1542
|
-
const workspacePackages = (await collectWorkspacePackages(config)).filter((workspacePackage) => workspacePackage.directory !== rootDir);
|
|
1543
|
-
const projectAnalysis = analyzeTypecheckProjects({
|
|
1544
|
-
config,
|
|
1545
|
-
configPaths: await collectOrdinaryTsconfigPaths(rootDir),
|
|
1546
|
-
workspacePackages
|
|
1547
|
-
});
|
|
1548
|
-
const problems = [...projectAnalysis.problems];
|
|
1549
|
-
if (problems.length === 0) problems.push(...inferProjectReferences({
|
|
1550
|
-
config,
|
|
1551
|
-
projects: projectAnalysis.projects,
|
|
1552
|
-
workspacePackages
|
|
1553
|
-
}));
|
|
1554
|
-
if (problems.length > 0) throw new Error(problems.join("\n\n"));
|
|
1555
|
-
const metadata = readLiminaPackageMetadata();
|
|
1556
|
-
const writtenFiles = [];
|
|
1557
|
-
const skippedFiles = [];
|
|
1558
|
-
await writeGeneratedTsconfigs({
|
|
1559
|
-
projects: projectAnalysis.projects,
|
|
1560
|
-
rootDir,
|
|
1561
|
-
workspacePackages,
|
|
1562
|
-
writtenFiles
|
|
1563
|
-
});
|
|
1564
|
-
await writeLiminaConfig({
|
|
1565
|
-
prompt: options,
|
|
1566
|
-
rootDir,
|
|
1567
|
-
skippedFiles,
|
|
1568
|
-
writtenFiles
|
|
1569
|
-
});
|
|
1570
|
-
return {
|
|
1571
|
-
checkCommand: "pnpm limina:check",
|
|
1572
|
-
installRequired: await updateRootPackageJson({
|
|
1573
|
-
metadata,
|
|
1574
|
-
prompt: options,
|
|
1575
|
-
rootDir,
|
|
1576
|
-
skippedFiles,
|
|
1577
|
-
writtenFiles
|
|
1578
|
-
}),
|
|
1579
|
-
rootDir,
|
|
1580
|
-
skippedFiles,
|
|
1581
|
-
workspacePackageCount: workspacePackages.length,
|
|
1582
|
-
writtenFiles
|
|
1583
|
-
};
|
|
1584
|
-
}
|
|
1585
|
-
async function runInit(options = {}) {
|
|
1586
|
-
if (options.clearScreen ?? true) clearCliScreen();
|
|
1587
|
-
const elapsed = createElapsedTimer();
|
|
1588
|
-
const task = options.flow?.start("init workspace", { depth: options.flowDepth ?? 0 });
|
|
1589
|
-
InitLogger.info("init started");
|
|
1590
|
-
try {
|
|
1591
|
-
const result = await runInitInternal(options);
|
|
1592
|
-
InitLogger.success(`init generated ${result.writtenFiles.length} files for ${result.workspacePackageCount} workspace packages.`, elapsed());
|
|
1593
|
-
if (result.installRequired) InitLogger.info("limina was added to devDependencies; run pnpm i before checking.");
|
|
1594
|
-
InitLogger.info(`next: ${result.installRequired ? "pnpm i && " : ""}${result.checkCommand}`);
|
|
1595
|
-
task?.pass();
|
|
1596
|
-
return result;
|
|
1597
|
-
} catch (error) {
|
|
1598
|
-
InitLogger.error(`init failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
1599
|
-
task?.fail("init failed", { error });
|
|
1600
|
-
throw error;
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
//#endregion
|
|
1605
|
-
//#region src/commands/source.ts
|
|
1606
|
-
function findOwnerForFile(filePath, owners) {
|
|
1607
|
-
return owners.find((owner) => isPathInsideDirectory(filePath, owner.directory)) ?? null;
|
|
1608
|
-
}
|
|
1609
|
-
function isUrlOrDataOrFileSpecifier(specifier) {
|
|
1610
|
-
return specifier.startsWith("data:") || specifier.startsWith("file:") || specifier.startsWith("http:") || specifier.startsWith("https:");
|
|
1611
|
-
}
|
|
1612
|
-
function isVirtualModuleSpecifier(specifier) {
|
|
1613
|
-
return specifier.startsWith("virtual:");
|
|
1614
|
-
}
|
|
1615
|
-
function isPackageImportSpecifier(specifier) {
|
|
1616
|
-
return specifier.startsWith("#");
|
|
1617
|
-
}
|
|
1618
|
-
function isBarePackageSpecifier(specifier) {
|
|
1619
|
-
return !isRelativeSpecifier$1(specifier) && !isPackageImportSpecifier(specifier) && !isUrlOrDataOrFileSpecifier(specifier) && !isVirtualModuleSpecifier(specifier) && !path.isAbsolute(specifier);
|
|
1620
|
-
}
|
|
1621
|
-
function isDependencyAuthorized(manifest, packageName) {
|
|
1622
|
-
return Boolean(manifest.dependencies?.[packageName] || manifest.devDependencies?.[packageName]);
|
|
1623
|
-
}
|
|
1624
|
-
function findNonAuthorizingDependencySection(manifest, packageName) {
|
|
1625
|
-
if (manifest.peerDependencies?.[packageName]) return "peerDependencies";
|
|
1626
|
-
if (manifest.optionalDependencies?.[packageName]) return "optionalDependencies";
|
|
1627
|
-
return null;
|
|
1628
|
-
}
|
|
1629
|
-
function packageImportsMatch(importsField, specifier) {
|
|
1630
|
-
if (!importsField || typeof importsField !== "object") return false;
|
|
1631
|
-
for (const key of Object.keys(importsField)) {
|
|
1632
|
-
if (key === specifier) return true;
|
|
1633
|
-
const wildcardIndex = key.indexOf("*");
|
|
1634
|
-
if (wildcardIndex === -1) continue;
|
|
1635
|
-
const prefix = key.slice(0, wildcardIndex);
|
|
1636
|
-
const suffix = key.slice(wildcardIndex + 1);
|
|
1637
|
-
if (specifier.startsWith(prefix) && specifier.endsWith(suffix)) return true;
|
|
1638
|
-
}
|
|
1639
|
-
return false;
|
|
1640
|
-
}
|
|
1641
|
-
function addProjectOwnerProblems(options) {
|
|
1642
|
-
const ownerPaths = /* @__PURE__ */ new Map();
|
|
1643
|
-
const missingOwnerFiles = [];
|
|
1644
|
-
for (const fileName of options.fileNames) {
|
|
1645
|
-
const owner = findOwnerForFile(fileName, options.owners);
|
|
1646
|
-
if (!owner) {
|
|
1647
|
-
missingOwnerFiles.push(fileName);
|
|
1648
|
-
continue;
|
|
1649
|
-
}
|
|
1650
|
-
ownerPaths.set(owner.packageJsonPath, owner);
|
|
1651
|
-
}
|
|
1652
|
-
if (missingOwnerFiles.length > 0) options.problems.push([
|
|
1653
|
-
"Source file has no package owner:",
|
|
1654
|
-
` ${options.role}: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
1655
|
-
" files:",
|
|
1656
|
-
...missingOwnerFiles.slice(0, 10).map((fileName) => ` - ${toRelativePath(options.config.rootDir, fileName)}`),
|
|
1657
|
-
...missingOwnerFiles.length > 10 ? [` ...and ${missingOwnerFiles.length - 10} more`] : [],
|
|
1658
|
-
" reason: every source file checked by Limina must be governed by the nearest package.json owner."
|
|
1659
|
-
].join("\n"));
|
|
1660
|
-
if (ownerPaths.size <= 1) return;
|
|
1661
|
-
options.problems.push([
|
|
1662
|
-
"Tsconfig source file set mixes package owners:",
|
|
1663
|
-
` ${options.role}: ${toRelativePath(options.config.rootDir, options.configPath)}`,
|
|
1664
|
-
" owners:",
|
|
1665
|
-
...[...ownerPaths.values()].map((owner) => ` - ${toRelativePath(options.config.rootDir, owner.packageJsonPath)}`),
|
|
1666
|
-
" reason: non-aggregator tsconfig leaves and their companion typecheck configs must stay within one nearest package.json owner scope."
|
|
1667
|
-
].join("\n"));
|
|
1668
|
-
}
|
|
1669
|
-
function addRelativeImportOwnerProblem(options) {
|
|
1670
|
-
options.problems.push([
|
|
1671
|
-
"Relative import escapes package owner scope:",
|
|
1672
|
-
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1673
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1674
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
1675
|
-
` resolved file: ${toRelativePath(options.config.rootDir, options.resolvedFilePath)}`,
|
|
1676
|
-
...options.targetOwner ? [` target owner: ${toRelativePath(options.config.rootDir, options.targetOwner.packageJsonPath)}`] : [],
|
|
1677
|
-
" reason: relative source imports must not cross the nearest package.json owner boundary."
|
|
1678
|
-
].join("\n"));
|
|
1679
|
-
}
|
|
1680
|
-
function addPackageImportAuthorizationProblem(options) {
|
|
1681
|
-
const nonAuthorizingSection = findNonAuthorizingDependencySection(options.owner.manifest, options.packageName);
|
|
1682
|
-
options.problems.push([
|
|
1683
|
-
"Unauthorized bare package import:",
|
|
1684
|
-
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1685
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1686
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
1687
|
-
` package: ${options.packageName}`,
|
|
1688
|
-
...options.workspacePackage ? [` workspace package: ${options.workspacePackage.name}`] : [],
|
|
1689
|
-
...nonAuthorizingSection ? [` found in: ${nonAuthorizingSection}`] : [],
|
|
1690
|
-
" reason: source imports must be authorized by the nearest package.json dependencies or devDependencies."
|
|
1691
|
-
].join("\n"));
|
|
1692
|
-
}
|
|
1693
|
-
function addPackageImportProblem(options) {
|
|
1694
|
-
if (!packageImportsMatch(options.owner.manifest.imports, options.importRecord.specifier)) {
|
|
1695
|
-
options.problems.push([
|
|
1696
|
-
"Unauthorized package import specifier:",
|
|
1697
|
-
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1698
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1699
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
1700
|
-
" reason: #... package imports must match the nearest package.json imports field."
|
|
1701
|
-
].join("\n"));
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
if (!options.resolvedFilePath) {
|
|
1705
|
-
options.problems.push([
|
|
1706
|
-
"Unresolved package import specifier:",
|
|
1707
|
-
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1708
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1709
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
1710
|
-
" reason: matched #... package imports must resolve to a file within the same package owner scope."
|
|
1711
|
-
].join("\n"));
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
|
-
if (!isPathInsideDirectory(options.resolvedFilePath, options.owner.directory)) options.problems.push([
|
|
1715
|
-
"Package import escapes package owner scope:",
|
|
1716
|
-
` package owner: ${toRelativePath(options.config.rootDir, options.owner.packageJsonPath)}`,
|
|
1717
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1718
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
1719
|
-
` resolved file: ${toRelativePath(options.config.rootDir, options.resolvedFilePath)}`,
|
|
1720
|
-
" reason: #... package imports must resolve within the nearest package.json owner scope."
|
|
1721
|
-
].join("\n"));
|
|
1722
|
-
}
|
|
1723
|
-
function createSourceProjectEntries(config, projects) {
|
|
1724
|
-
return projects.filter((project) => isDtsProjectConfig(project.configPath)).map((project) => {
|
|
1725
|
-
const typecheckConfigPath = getTypecheckConfigPath(project.configPath);
|
|
1726
|
-
const fileNames = new Set(project.fileNames);
|
|
1727
|
-
if (existsSync(typecheckConfigPath)) for (const fileName of parseProject(config, typecheckConfigPath, project.extensions).fileNames) fileNames.add(fileName);
|
|
1728
|
-
return {
|
|
1729
|
-
fileNames: [...fileNames].sort(),
|
|
1730
|
-
project
|
|
1731
|
-
};
|
|
1732
|
-
});
|
|
1733
|
-
}
|
|
1734
|
-
async function runSourceCheckInternal(config, options = {}) {
|
|
1735
|
-
const graphRoute = collectSourceGraphProjectExtensions(config);
|
|
1736
|
-
const projects = [...graphRoute.projectExtensionsByPath.keys()].sort().map((projectPath) => parseProject(config, projectPath, graphRoute.projectExtensionsByPath.get(projectPath)));
|
|
1737
|
-
const sourceProjectEntries = createSourceProjectEntries(config, projects);
|
|
1738
|
-
const packages = await collectWorkspacePackages(config);
|
|
1739
|
-
const packageOwners = await collectPackageOwners(config);
|
|
1740
|
-
const problems = [...graphRoute.problems];
|
|
1741
|
-
for (const project of projects) {
|
|
1742
|
-
if (project.labelProblem) problems.push(project.labelProblem);
|
|
1743
|
-
if (!isDtsProjectConfig(project.configPath)) continue;
|
|
1744
|
-
addProjectOwnerProblems({
|
|
1745
|
-
config,
|
|
1746
|
-
configPath: project.configPath,
|
|
1747
|
-
fileNames: project.fileNames,
|
|
1748
|
-
owners: packageOwners,
|
|
1749
|
-
problems,
|
|
1750
|
-
role: "declaration leaf"
|
|
1751
|
-
});
|
|
1752
|
-
const typecheckConfigPath = getTypecheckConfigPath(project.configPath);
|
|
1753
|
-
if (existsSync(typecheckConfigPath)) addProjectOwnerProblems({
|
|
1754
|
-
config,
|
|
1755
|
-
configPath: typecheckConfigPath,
|
|
1756
|
-
fileNames: parseProject(config, typecheckConfigPath, project.extensions).fileNames,
|
|
1757
|
-
owners: packageOwners,
|
|
1758
|
-
problems,
|
|
1759
|
-
role: "typecheck companion"
|
|
1760
|
-
});
|
|
1761
|
-
}
|
|
1762
|
-
for (const { fileNames, project } of sourceProjectEntries) for (const filePath of fileNames) {
|
|
1763
|
-
const owner = findOwnerForFile(filePath, packageOwners);
|
|
1764
|
-
if (!owner) continue;
|
|
1765
|
-
for (const importRecord of collectImportsFromFile(filePath, config.rootDir)) {
|
|
1766
|
-
const resolvedFilePath = resolveInternalImport(importRecord.specifier, filePath, project.options, project.extensions);
|
|
1767
|
-
if (isRelativeSpecifier$1(importRecord.specifier)) {
|
|
1768
|
-
if (!resolvedFilePath) continue;
|
|
1769
|
-
const targetOwner = findOwnerForFile(resolvedFilePath, packageOwners);
|
|
1770
|
-
if (targetOwner?.packageJsonPath !== owner.packageJsonPath) addRelativeImportOwnerProblem({
|
|
1771
|
-
config,
|
|
1772
|
-
importRecord,
|
|
1773
|
-
owner,
|
|
1774
|
-
problems,
|
|
1775
|
-
resolvedFilePath,
|
|
1776
|
-
targetOwner
|
|
1777
|
-
});
|
|
1778
|
-
continue;
|
|
1779
|
-
}
|
|
1780
|
-
if (isPackageImportSpecifier(importRecord.specifier)) {
|
|
1781
|
-
addPackageImportProblem({
|
|
1782
|
-
config,
|
|
1783
|
-
importRecord,
|
|
1784
|
-
owner,
|
|
1785
|
-
problems,
|
|
1786
|
-
resolvedFilePath
|
|
1787
|
-
});
|
|
1788
|
-
continue;
|
|
1789
|
-
}
|
|
1790
|
-
if (isUrlOrDataOrFileSpecifier(importRecord.specifier) || isVirtualModuleSpecifier(importRecord.specifier)) continue;
|
|
1791
|
-
if (!isBarePackageSpecifier(importRecord.specifier)) continue;
|
|
1792
|
-
if (isNodeBuiltinSpecifier(importRecord.specifier)) continue;
|
|
1793
|
-
const packageName = getPackageRootSpecifier(importRecord.specifier);
|
|
1794
|
-
if (owner.name === packageName) continue;
|
|
1795
|
-
const workspacePackage = packages.find((candidate) => candidate.name === packageName) ?? null;
|
|
1796
|
-
if (isDependencyAuthorized(owner.manifest, packageName)) continue;
|
|
1797
|
-
addPackageImportAuthorizationProblem({
|
|
1798
|
-
config,
|
|
1799
|
-
importRecord,
|
|
1800
|
-
owner,
|
|
1801
|
-
packageName,
|
|
1802
|
-
problems,
|
|
1803
|
-
workspacePackage
|
|
1804
|
-
});
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
if (problems.length > 0) {
|
|
1808
|
-
SourceLogger.error(problems.join("\n\n"));
|
|
1809
|
-
return false;
|
|
1810
|
-
}
|
|
1811
|
-
if (options.logSuccess ?? true) SourceLogger.success(`Checked ${sourceProjectEntries.length} source project owners; package scopes are valid.`);
|
|
1812
|
-
return true;
|
|
1813
|
-
}
|
|
1814
|
-
async function runSourceCheck(config, options = {}) {
|
|
1815
|
-
if (options.clearScreen ?? true) clearCliScreen();
|
|
1816
|
-
const elapsed = createElapsedTimer();
|
|
1817
|
-
const task = options.flow?.start("source check", { depth: options.flowDepth ?? 0 });
|
|
1818
|
-
SourceLogger.info("source check started");
|
|
1819
|
-
try {
|
|
1820
|
-
const logSuccess = !options.flow?.interactive;
|
|
1821
|
-
const passed = await runSourceCheckInternal(config, { logSuccess });
|
|
1822
|
-
if (passed) {
|
|
1823
|
-
if (logSuccess) SourceLogger.success("source check finished", elapsed());
|
|
1824
|
-
task?.pass();
|
|
1825
|
-
} else {
|
|
1826
|
-
SourceLogger.error("source check finished with failures", elapsed());
|
|
1827
|
-
task?.fail("source check finished with failures");
|
|
1828
|
-
}
|
|
1829
|
-
return passed;
|
|
1830
|
-
} catch (error) {
|
|
1831
|
-
SourceLogger.error(`source check failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
1832
|
-
task?.fail("source check failed", { error });
|
|
1833
|
-
throw error;
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
//#endregion
|
|
1838
|
-
//#region src/commands/typecheck.ts
|
|
1839
|
-
function getExecutionCheckers(options) {
|
|
1840
|
-
return options.checkers.filter((checker) => {
|
|
1841
|
-
return getCheckerAdapter(checker.preset)?.execution === options.executionKind;
|
|
1842
|
-
});
|
|
1843
|
-
}
|
|
1844
|
-
function collectCheckerPeerDependencyProblems(options) {
|
|
1845
|
-
const missingDependencies = collectMissingCheckerPeerDependencies({
|
|
1846
|
-
checkers: options.checkers,
|
|
1847
|
-
projectRootDir: options.projectRootDir,
|
|
1848
|
-
resolvePackage: options.resolvePackage
|
|
1849
|
-
});
|
|
1850
|
-
return missingDependencies.length === 0 ? [] : [formatMissingCheckerPeerDependencies(missingDependencies)];
|
|
1851
|
-
}
|
|
1852
|
-
function createCheckerTarget(options) {
|
|
1853
|
-
const adapter = getCheckerAdapter(options.checker.preset);
|
|
1854
|
-
if (!adapter) throw new Error(`Checker "${options.checker.name}" uses unsupported preset "${options.checker.preset}".`);
|
|
1855
|
-
return {
|
|
1856
|
-
...adapter.createCommandTarget(options),
|
|
1857
|
-
checkerName: options.checker.name,
|
|
1858
|
-
configPath: options.configPath,
|
|
1859
|
-
cwd: options.projectRootDir,
|
|
1860
|
-
executionKind: options.executionKind
|
|
1861
|
-
};
|
|
1862
|
-
}
|
|
1863
|
-
function createDefaultRunner() {
|
|
1864
|
-
return async (target) => await new Promise((resolve) => {
|
|
1865
|
-
const child = spawn(target.command, target.args, {
|
|
1866
|
-
cwd: target.cwd,
|
|
1867
|
-
env: {
|
|
1868
|
-
...process.env,
|
|
1869
|
-
PATH: [path.join(target.cwd, "node_modules/.bin"), process.env.PATH].filter(Boolean).join(path.delimiter)
|
|
1870
|
-
},
|
|
1871
|
-
shell: process.platform === "win32",
|
|
1872
|
-
stdio: "inherit"
|
|
1873
|
-
});
|
|
1874
|
-
child.on("error", (error) => {
|
|
1875
|
-
resolve({
|
|
1876
|
-
configPath: target.configPath,
|
|
1877
|
-
error,
|
|
1878
|
-
status: 1
|
|
1879
|
-
});
|
|
1880
|
-
});
|
|
1881
|
-
child.on("close", (code) => {
|
|
1882
|
-
resolve({
|
|
1883
|
-
configPath: target.configPath,
|
|
1884
|
-
status: code ?? 1
|
|
1885
|
-
});
|
|
1886
|
-
});
|
|
1887
|
-
});
|
|
1888
|
-
}
|
|
1889
|
-
async function runWithConcurrency(targets, concurrency, runner, options = {}) {
|
|
1890
|
-
const results = new Array(targets.length);
|
|
1891
|
-
let nextIndex = 0;
|
|
1892
|
-
await Promise.all(Array.from({ length: Math.min(concurrency, targets.length) }).map(async () => {
|
|
1893
|
-
for (;;) {
|
|
1894
|
-
const targetIndex = nextIndex;
|
|
1895
|
-
nextIndex += 1;
|
|
1896
|
-
if (targetIndex >= targets.length) return;
|
|
1897
|
-
try {
|
|
1898
|
-
const target = targets[targetIndex];
|
|
1899
|
-
options.onTargetStart?.(target);
|
|
1900
|
-
results[targetIndex] = await runner(target);
|
|
1901
|
-
options.onTargetResult?.(target, results[targetIndex]);
|
|
1902
|
-
} catch (error) {
|
|
1903
|
-
const target = targets[targetIndex];
|
|
1904
|
-
results[targetIndex] = {
|
|
1905
|
-
configPath: target.configPath,
|
|
1906
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1907
|
-
status: 1
|
|
1908
|
-
};
|
|
1909
|
-
options.onTargetResult?.(target, results[targetIndex]);
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
}));
|
|
1913
|
-
return results;
|
|
1914
|
-
}
|
|
1915
|
-
async function runCheckerBuildInternal(options) {
|
|
1916
|
-
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1917
|
-
const projectRootDir = normalizeAbsolutePath(options.config.rootDir);
|
|
1918
|
-
const allCheckers = getActiveCheckers(options.config);
|
|
1919
|
-
const checkers = getExecutionCheckers({
|
|
1920
|
-
checkers: allCheckers,
|
|
1921
|
-
executionKind: "build"
|
|
1922
|
-
});
|
|
1923
|
-
const flowDepth = options.flowDepth ?? 0;
|
|
1924
|
-
const rootConfigPaths = [];
|
|
1925
|
-
const problems = collectCheckerPeerDependencyProblems({
|
|
1926
|
-
checkers: allCheckers,
|
|
1927
|
-
projectRootDir,
|
|
1928
|
-
resolvePackage: options.checkerPackageResolver
|
|
1929
|
-
});
|
|
1930
|
-
if (problems.length > 0) {
|
|
1931
|
-
options.flow?.fail("checker dependency preflight failed", { depth: flowDepth + 1 });
|
|
1932
|
-
TypecheckLogger.error(problems.join("\n\n"));
|
|
1933
|
-
return {
|
|
1934
|
-
passed: false,
|
|
1935
|
-
projectRootDir,
|
|
1936
|
-
rootConfigPaths
|
|
1937
|
-
};
|
|
1938
|
-
}
|
|
1939
|
-
const targets = checkers.flatMap((checker) => {
|
|
1940
|
-
const configPath = resolveProjectConfigPath(projectRootDir, checker.entry);
|
|
1941
|
-
rootConfigPaths.push(configPath);
|
|
1942
|
-
return [createCheckerTarget({
|
|
1943
|
-
checker,
|
|
1944
|
-
commandOverride: options.tscCommand,
|
|
1945
|
-
configPath,
|
|
1946
|
-
executionKind: "build",
|
|
1947
|
-
projectRootDir
|
|
1948
|
-
})];
|
|
1949
|
-
});
|
|
1950
|
-
options.flow?.info(`found ${targets.length} checker build entry(s)`, { depth: flowDepth + 1 });
|
|
1951
|
-
TypecheckLogger.info([
|
|
1952
|
-
`Running build checks for ${targets.length} checker entry(s).`,
|
|
1953
|
-
`CWD: ${toRelativePath(cwd, projectRootDir)}`,
|
|
1954
|
-
`Entries: ${rootConfigPaths.map((configPath) => toRelativePath(projectRootDir, configPath)).join(", ")}`
|
|
1955
|
-
].join("\n"));
|
|
1956
|
-
const targetTasks = /* @__PURE__ */ new Map();
|
|
1957
|
-
const failedResults = (await runWithConcurrency(targets, 1, options.runner ?? createDefaultRunner(), {
|
|
1958
|
-
onTargetResult: (target, result) => {
|
|
1959
|
-
const task = targetTasks.get(target.configPath);
|
|
1960
|
-
if (!task) return;
|
|
1961
|
-
if (result.status === 0) task.pass();
|
|
1962
|
-
else {
|
|
1963
|
-
const suffix = result.error ? formatErrorMessage$1(result.error) : `exited with code ${result.status}`;
|
|
1964
|
-
task.fail(void 0, { error: suffix });
|
|
1965
|
-
}
|
|
1966
|
-
},
|
|
1967
|
-
onTargetStart: (target) => {
|
|
1968
|
-
if (!options.flow) return;
|
|
1969
|
-
targetTasks.set(target.configPath, options.flow.start(target.label ?? `checker build: ${toRelativePath(projectRootDir, target.configPath)}`, {
|
|
1970
|
-
collapseOnSuccess: false,
|
|
1971
|
-
depth: flowDepth + 1
|
|
1972
|
-
}));
|
|
1973
|
-
}
|
|
1974
|
-
})).filter((result) => result.status !== 0);
|
|
1975
|
-
const passed = failedResults.length === 0;
|
|
1976
|
-
if (!passed) TypecheckLogger.error(["build checks failed:", ...failedResults.map((result) => {
|
|
1977
|
-
const suffix = result.error ? `: ${formatErrorMessage$1(result.error)}` : ` exited with code ${result.status}`;
|
|
1978
|
-
return ` ${toRelativePath(projectRootDir, result.configPath)}${suffix}`;
|
|
1979
|
-
})].join("\n"));
|
|
1980
|
-
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} checker build entry(s).`);
|
|
1981
|
-
return {
|
|
1982
|
-
passed,
|
|
1983
|
-
projectRootDir,
|
|
1984
|
-
rootConfigPaths
|
|
1985
|
-
};
|
|
1986
|
-
}
|
|
1987
|
-
async function runCheckerBuild(options) {
|
|
1988
|
-
if (options.clearScreen ?? true) clearCliScreen();
|
|
1989
|
-
const elapsed = createElapsedTimer();
|
|
1990
|
-
const task = options.flow?.start("checker build", { depth: options.flowDepth ?? 0 });
|
|
1991
|
-
TypecheckLogger.info("checker build started");
|
|
1992
|
-
try {
|
|
1993
|
-
const result = await runCheckerBuildInternal(options);
|
|
1994
|
-
if (result.passed) {
|
|
1995
|
-
if (!options.flow?.interactive) TypecheckLogger.success("checker build finished", elapsed());
|
|
1996
|
-
task?.pass();
|
|
1997
|
-
} else {
|
|
1998
|
-
TypecheckLogger.error("checker build finished with failures", elapsed());
|
|
1999
|
-
task?.fail("checker build finished with failures");
|
|
2000
|
-
}
|
|
2001
|
-
return result;
|
|
2002
|
-
} catch (error) {
|
|
2003
|
-
TypecheckLogger.error(`checker build failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
2004
|
-
task?.fail("checker build failed", { error });
|
|
2005
|
-
throw error;
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
async function runCheckerTypecheckInternal(options) {
|
|
2009
|
-
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
2010
|
-
const projectRootDir = normalizeAbsolutePath(options.config.rootDir);
|
|
2011
|
-
const allCheckers = getActiveCheckers(options.config);
|
|
2012
|
-
const checkers = getExecutionCheckers({
|
|
2013
|
-
checkers: allCheckers,
|
|
2014
|
-
executionKind: "typecheck"
|
|
2015
|
-
});
|
|
2016
|
-
const flowDepth = options.flowDepth ?? 0;
|
|
2017
|
-
const rootConfigPaths = [];
|
|
2018
|
-
const problems = collectCheckerPeerDependencyProblems({
|
|
2019
|
-
checkers: allCheckers,
|
|
2020
|
-
projectRootDir,
|
|
2021
|
-
resolvePackage: options.checkerPackageResolver
|
|
2022
|
-
});
|
|
2023
|
-
if (problems.length > 0) {
|
|
2024
|
-
options.flow?.fail("checker dependency preflight failed", { depth: flowDepth + 1 });
|
|
2025
|
-
TypecheckLogger.error(problems.join("\n\n"));
|
|
2026
|
-
return {
|
|
2027
|
-
passed: false,
|
|
2028
|
-
projectRootDir,
|
|
2029
|
-
rootConfigPaths
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
const targets = checkers.map((checker) => {
|
|
2033
|
-
const configPath = resolveProjectConfigPath(projectRootDir, checker.entry);
|
|
2034
|
-
rootConfigPaths.push(configPath);
|
|
2035
|
-
return createCheckerTarget({
|
|
2036
|
-
checker,
|
|
2037
|
-
commandOverride: options.tscCommand,
|
|
2038
|
-
configPath,
|
|
2039
|
-
executionKind: "typecheck",
|
|
2040
|
-
projectRootDir
|
|
2041
|
-
});
|
|
2042
|
-
});
|
|
2043
|
-
if (targets.length === 0) {
|
|
2044
|
-
options.flow?.info("no source-only checker entries configured", { depth: flowDepth + 1 });
|
|
2045
|
-
if (!options.flow?.interactive) TypecheckLogger.success("No source-only checker entries configured.");
|
|
2046
|
-
return {
|
|
2047
|
-
passed: true,
|
|
2048
|
-
projectRootDir,
|
|
2049
|
-
rootConfigPaths
|
|
2050
|
-
};
|
|
2051
|
-
}
|
|
2052
|
-
options.flow?.info(`found ${targets.length} checker typecheck entry(s)`, { depth: flowDepth + 1 });
|
|
2053
|
-
TypecheckLogger.info([
|
|
2054
|
-
`Running typecheck for ${targets.length} checker entry(s).`,
|
|
2055
|
-
`CWD: ${toRelativePath(cwd, projectRootDir)}`,
|
|
2056
|
-
`Entries: ${rootConfigPaths.map((configPath) => toRelativePath(projectRootDir, configPath)).join(", ")}`
|
|
2057
|
-
].join("\n"));
|
|
2058
|
-
const targetTasks = /* @__PURE__ */ new Map();
|
|
2059
|
-
const failedResults = (await runWithConcurrency(targets, 1, options.runner ?? createDefaultRunner(), {
|
|
2060
|
-
onTargetResult: (target, result) => {
|
|
2061
|
-
const task = targetTasks.get(target.configPath);
|
|
2062
|
-
if (!task) return;
|
|
2063
|
-
if (result.status === 0) task.pass();
|
|
2064
|
-
else {
|
|
2065
|
-
const suffix = result.error ? formatErrorMessage$1(result.error) : `exited with code ${result.status}`;
|
|
2066
|
-
task.fail(void 0, { error: suffix });
|
|
2067
|
-
}
|
|
2068
|
-
},
|
|
2069
|
-
onTargetStart: (target) => {
|
|
2070
|
-
if (!options.flow) return;
|
|
2071
|
-
targetTasks.set(target.configPath, options.flow.start(target.label ?? `checker typecheck: ${toRelativePath(projectRootDir, target.configPath)}`, {
|
|
2072
|
-
collapseOnSuccess: false,
|
|
2073
|
-
depth: flowDepth + 1
|
|
2074
|
-
}));
|
|
2075
|
-
}
|
|
2076
|
-
})).filter((result) => result.status !== 0);
|
|
2077
|
-
const passed = failedResults.length === 0;
|
|
2078
|
-
if (!passed) TypecheckLogger.error(["typecheck checks failed:", ...failedResults.map((result) => {
|
|
2079
|
-
const suffix = result.error ? `: ${formatErrorMessage$1(result.error)}` : ` exited with code ${result.status}`;
|
|
2080
|
-
return ` ${toRelativePath(projectRootDir, result.configPath)}${suffix}`;
|
|
2081
|
-
})].join("\n"));
|
|
2082
|
-
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} checker typecheck entry(s).`);
|
|
2083
|
-
return {
|
|
2084
|
-
passed,
|
|
2085
|
-
projectRootDir,
|
|
2086
|
-
rootConfigPaths
|
|
2087
|
-
};
|
|
2088
|
-
}
|
|
2089
|
-
async function runCheckerTypecheck(options) {
|
|
2090
|
-
if (options.clearScreen ?? true) clearCliScreen();
|
|
2091
|
-
const elapsed = createElapsedTimer();
|
|
2092
|
-
const task = options.flow?.start("checker typecheck", { depth: options.flowDepth ?? 0 });
|
|
2093
|
-
TypecheckLogger.info("checker typecheck started");
|
|
2094
|
-
try {
|
|
2095
|
-
const result = await runCheckerTypecheckInternal(options);
|
|
2096
|
-
if (result.passed) {
|
|
2097
|
-
if (!options.flow?.interactive) TypecheckLogger.success("checker typecheck finished", elapsed());
|
|
2098
|
-
task?.pass();
|
|
2099
|
-
} else {
|
|
2100
|
-
TypecheckLogger.error("checker typecheck finished with failures", elapsed());
|
|
2101
|
-
task?.fail("checker typecheck finished with failures");
|
|
2102
|
-
}
|
|
2103
|
-
return result;
|
|
2104
|
-
} catch (error) {
|
|
2105
|
-
TypecheckLogger.error(`checker typecheck failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
2106
|
-
task?.fail("checker typecheck failed", { error });
|
|
2107
|
-
throw error;
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
//#endregion
|
|
2112
|
-
//#region src/flow.ts
|
|
2113
|
-
const DEFAULT_CI_ENV_VALUES = ["1", "true"];
|
|
2114
|
-
const DEFAULT_TERMINAL_COLUMNS = 80;
|
|
2115
|
-
const ANSI_PATTERN = /\u001B\[[0-?]*[ -/]*[@-~]/gu;
|
|
2116
|
-
const ANSI_RESET = "\x1B[0m";
|
|
2117
|
-
const ANSI_GREEN = "\x1B[32m";
|
|
2118
|
-
const ANSI_RED = "\x1B[31m";
|
|
2119
|
-
const ANSI_YELLOW = "\x1B[33m";
|
|
2120
|
-
const FLOW_SYMBOL_BY_STATUS = {
|
|
2121
|
-
fail: "■",
|
|
2122
|
-
info: "│",
|
|
2123
|
-
pass: "◆",
|
|
2124
|
-
skip: "◇",
|
|
2125
|
-
start: "◇",
|
|
2126
|
-
warn: "▲"
|
|
2127
|
-
};
|
|
2128
|
-
function isCiEnvironment(env) {
|
|
2129
|
-
return DEFAULT_CI_ENV_VALUES.includes(String(env.CI).toLowerCase());
|
|
2130
|
-
}
|
|
2131
|
-
function formatElapsedTime(milliseconds) {
|
|
2132
|
-
if (milliseconds < 1e3) return `${Math.round(milliseconds)}ms`;
|
|
2133
|
-
return `${(milliseconds / 1e3).toFixed(2)}s`;
|
|
2134
|
-
}
|
|
2135
|
-
function formatMessageWithElapsed(message, elapsedTimeMs) {
|
|
2136
|
-
return typeof elapsedTimeMs === "number" ? `${message} (${formatElapsedTime(elapsedTimeMs)})` : message;
|
|
2137
|
-
}
|
|
2138
|
-
function formatFailureMessage(message, error) {
|
|
2139
|
-
if (error === void 0) return message;
|
|
2140
|
-
const detail = formatErrorMessage(error).replace(/\s+/gu, " ").trim();
|
|
2141
|
-
return detail ? `${message}: ${detail}` : message;
|
|
2142
|
-
}
|
|
2143
|
-
function indentMessage(message, depth) {
|
|
2144
|
-
if (depth <= 0) return message;
|
|
2145
|
-
return `${" ".repeat(depth)}${message}`;
|
|
2146
|
-
}
|
|
2147
|
-
function writeLine(output, message) {
|
|
2148
|
-
output.write(`${message}\n`);
|
|
2149
|
-
}
|
|
2150
|
-
function toWritableText(chunk) {
|
|
2151
|
-
if (chunk instanceof Uint8Array) return Buffer.from(chunk).toString();
|
|
2152
|
-
return String(chunk);
|
|
2153
|
-
}
|
|
2154
|
-
function stripControlSequences(text) {
|
|
2155
|
-
return text.replace(ANSI_PATTERN, "").replaceAll("\r", "");
|
|
2156
|
-
}
|
|
2157
|
-
function colorInteractiveSymbol(status, symbol) {
|
|
2158
|
-
if (status === "pass") return `${ANSI_GREEN}${symbol}${ANSI_RESET}`;
|
|
2159
|
-
if (status === "fail") return `${ANSI_RED}${symbol}${ANSI_RESET}`;
|
|
2160
|
-
if (status === "warn") return `${ANSI_YELLOW}${symbol}${ANSI_RESET}`;
|
|
2161
|
-
return symbol;
|
|
2162
|
-
}
|
|
2163
|
-
var LiminaFlowReporter = class {
|
|
2164
|
-
#clack;
|
|
2165
|
-
#interactive;
|
|
2166
|
-
#output;
|
|
2167
|
-
#stderr;
|
|
2168
|
-
#stdout;
|
|
2169
|
-
#tracksProcessWrites;
|
|
2170
|
-
#interactiveHistory = [];
|
|
2171
|
-
#restoreWriteStreams;
|
|
2172
|
-
#trackedTaskCount = 0;
|
|
2173
|
-
#terminalColumn = 0;
|
|
2174
|
-
#terminalLineCount = 0;
|
|
2175
|
-
constructor(options = {}) {
|
|
2176
|
-
const env = options.env ?? process.env;
|
|
2177
|
-
const stdout = options.stdout ?? process.stdout;
|
|
2178
|
-
this.#interactive = options.forceTty ?? Boolean(stdout.isTTY && !isCiEnvironment(env));
|
|
2179
|
-
this.#clack = options.clack ?? prompts;
|
|
2180
|
-
this.#output = options.output ?? { write: (message) => {
|
|
2181
|
-
if (typeof stdout.write === "function") {
|
|
2182
|
-
stdout.write(message);
|
|
2183
|
-
return;
|
|
2184
|
-
}
|
|
2185
|
-
process.stdout.write(message);
|
|
2186
|
-
} };
|
|
2187
|
-
this.#stdout = stdout;
|
|
2188
|
-
this.#stderr = options.stderr ?? process.stderr;
|
|
2189
|
-
this.#tracksProcessWrites = this.#interactive && options.output === void 0;
|
|
2190
|
-
}
|
|
2191
|
-
get interactive() {
|
|
2192
|
-
return this.#interactive;
|
|
2193
|
-
}
|
|
2194
|
-
intro(message) {
|
|
2195
|
-
if (this.#interactive) {
|
|
2196
|
-
this.#clack.intro(message);
|
|
2197
|
-
this.#interactiveHistory.push(`┌ ${message}`);
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
|
-
writeLine(this.#output, `[start] ${message}`);
|
|
2201
|
-
}
|
|
2202
|
-
outro(message) {
|
|
2203
|
-
if (this.#interactive) {
|
|
2204
|
-
this.#clack.outro(message);
|
|
2205
|
-
return;
|
|
2206
|
-
}
|
|
2207
|
-
writeLine(this.#output, `[done] ${message}`);
|
|
2208
|
-
}
|
|
2209
|
-
start(message, options = {}) {
|
|
2210
|
-
const depth = options.depth ?? 0;
|
|
2211
|
-
const collapseOnSuccess = options.collapseOnSuccess ?? true;
|
|
2212
|
-
const shouldTrackTask = this.#interactive && collapseOnSuccess;
|
|
2213
|
-
const persistStart = !shouldTrackTask && this.#trackedTaskCount === 0;
|
|
2214
|
-
const startLine = this.#terminalLineCount;
|
|
2215
|
-
const startTime = performance.now();
|
|
2216
|
-
let completed = false;
|
|
2217
|
-
if (shouldTrackTask) this.#beginTerminalTracking();
|
|
2218
|
-
const persistedStartIndex = this.#emit("start", message, options, { persistInteractive: persistStart });
|
|
2219
|
-
const finishTrackedTask = () => {
|
|
2220
|
-
if (!shouldTrackTask || completed) return;
|
|
2221
|
-
completed = true;
|
|
2222
|
-
this.#endTerminalTracking();
|
|
2223
|
-
};
|
|
2224
|
-
return {
|
|
2225
|
-
fail: (nextMessage, nextOptions) => {
|
|
2226
|
-
this.#emit("fail", formatFailureMessage(nextMessage ?? message, nextOptions?.error), {
|
|
2227
|
-
...nextOptions,
|
|
2228
|
-
depth,
|
|
2229
|
-
elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
|
|
2230
|
-
}, { persistInteractive: true });
|
|
2231
|
-
if (!this.#interactive) return;
|
|
2232
|
-
finishTrackedTask();
|
|
2233
|
-
},
|
|
2234
|
-
info: (nextMessage, nextOptions) => {
|
|
2235
|
-
this.info(nextMessage, {
|
|
2236
|
-
...nextOptions,
|
|
2237
|
-
depth: nextOptions?.depth ?? depth + 1
|
|
2238
|
-
});
|
|
2239
|
-
},
|
|
2240
|
-
pass: (nextMessage, nextOptions) => {
|
|
2241
|
-
const passOptions = {
|
|
2242
|
-
...nextOptions,
|
|
2243
|
-
depth,
|
|
2244
|
-
elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
|
|
2245
|
-
};
|
|
2246
|
-
const persistInteractive = shouldTrackTask ? this.#trackedTaskCount <= 1 : this.#trackedTaskCount === 0;
|
|
2247
|
-
if (shouldTrackTask) this.#clearInteractiveTaskBlock(startLine, { redrawHistory: persistInteractive && depth === 0 });
|
|
2248
|
-
if (!shouldTrackTask && this.#interactive && persistedStartIndex !== void 0) {
|
|
2249
|
-
this.#replaceInteractiveHistoryLine(persistedStartIndex, "pass", nextMessage ?? message, passOptions);
|
|
2250
|
-
this.#redrawInteractiveHistory();
|
|
2251
|
-
finishTrackedTask();
|
|
2252
|
-
return;
|
|
2253
|
-
}
|
|
2254
|
-
this.#emit("pass", nextMessage ?? message, passOptions, { persistInteractive });
|
|
2255
|
-
finishTrackedTask();
|
|
2256
|
-
},
|
|
2257
|
-
skip: (nextMessage, nextOptions) => {
|
|
2258
|
-
const skipOptions = {
|
|
2259
|
-
...nextOptions,
|
|
2260
|
-
depth,
|
|
2261
|
-
elapsedTimeMs: nextOptions?.elapsedTimeMs ?? performance.now() - startTime
|
|
2262
|
-
};
|
|
2263
|
-
if (!shouldTrackTask && this.#interactive && persistedStartIndex !== void 0) {
|
|
2264
|
-
this.#replaceInteractiveHistoryLine(persistedStartIndex, "skip", nextMessage ?? message, skipOptions);
|
|
2265
|
-
this.#redrawInteractiveHistory();
|
|
2266
|
-
finishTrackedTask();
|
|
2267
|
-
return;
|
|
2268
|
-
}
|
|
2269
|
-
this.#emit("skip", nextMessage ?? message, skipOptions, { persistInteractive: this.#trackedTaskCount === 0 });
|
|
2270
|
-
finishTrackedTask();
|
|
2271
|
-
},
|
|
2272
|
-
warn: (nextMessage, nextOptions) => {
|
|
2273
|
-
this.warn(nextMessage, {
|
|
2274
|
-
...nextOptions,
|
|
2275
|
-
depth: nextOptions?.depth ?? depth + 1
|
|
2276
|
-
});
|
|
2277
|
-
}
|
|
2278
|
-
};
|
|
2279
|
-
}
|
|
2280
|
-
fail(message, options = {}) {
|
|
2281
|
-
this.#emit("fail", formatFailureMessage(message, options.error), options, { persistInteractive: true });
|
|
2282
|
-
}
|
|
2283
|
-
info(message, options = {}) {
|
|
2284
|
-
this.#emit("info", message, options);
|
|
2285
|
-
}
|
|
2286
|
-
pass(message, options = {}) {
|
|
2287
|
-
this.#emit("pass", message, options, { persistInteractive: true });
|
|
2288
|
-
}
|
|
2289
|
-
skip(message, options = {}) {
|
|
2290
|
-
this.#emit("skip", message, options, { persistInteractive: true });
|
|
2291
|
-
}
|
|
2292
|
-
warn(message, options = {}) {
|
|
2293
|
-
this.#emit("warn", message, options);
|
|
2294
|
-
}
|
|
2295
|
-
writeOutput(message, options = {}) {
|
|
2296
|
-
const stream = options.stream === "stderr" ? this.#stderr : this.#stdout;
|
|
2297
|
-
if (this.#interactive && this.#tracksProcessWrites && typeof stream?.write === "function") {
|
|
2298
|
-
stream.write(message);
|
|
2299
|
-
return;
|
|
2300
|
-
}
|
|
2301
|
-
this.#writeTracked(toWritableText(message));
|
|
2302
|
-
}
|
|
2303
|
-
#emit(status, rawMessage, options, meta = {}) {
|
|
2304
|
-
const message = formatMessageWithElapsed(rawMessage, options.elapsedTimeMs);
|
|
2305
|
-
const depth = options.depth ?? 0;
|
|
2306
|
-
if (this.#interactive) {
|
|
2307
|
-
const renderedLine = this.#formatInteractiveLine(status, message, depth);
|
|
2308
|
-
let historyIndex;
|
|
2309
|
-
if (meta.persistInteractive) {
|
|
2310
|
-
historyIndex = this.#interactiveHistory.length;
|
|
2311
|
-
this.#interactiveHistory.push(renderedLine);
|
|
2312
|
-
}
|
|
2313
|
-
writeLine({ write: (nextMessage) => {
|
|
2314
|
-
this.#writeTracked(nextMessage);
|
|
2315
|
-
} }, renderedLine);
|
|
2316
|
-
return historyIndex;
|
|
2317
|
-
}
|
|
2318
|
-
writeLine(this.#output, `${" ".repeat(depth)}[${status}] ${message}`);
|
|
2319
|
-
}
|
|
2320
|
-
#beginTerminalTracking() {
|
|
2321
|
-
this.#trackedTaskCount += 1;
|
|
2322
|
-
if (this.#trackedTaskCount > 1 || !this.#tracksProcessWrites) return;
|
|
2323
|
-
const restoreStdout = this.#patchWriteStream(this.#stdout);
|
|
2324
|
-
const restoreStderr = this.#patchWriteStream(this.#stderr);
|
|
2325
|
-
this.#restoreWriteStreams = () => {
|
|
2326
|
-
restoreStdout?.();
|
|
2327
|
-
restoreStderr?.();
|
|
2328
|
-
this.#restoreWriteStreams = void 0;
|
|
2329
|
-
};
|
|
2330
|
-
}
|
|
2331
|
-
#clearInteractiveTaskBlock(startLine, options = {}) {
|
|
2332
|
-
const linesToClear = this.#terminalLineCount - startLine;
|
|
2333
|
-
if (linesToClear <= 0) return;
|
|
2334
|
-
if (options.redrawHistory) {
|
|
2335
|
-
this.#redrawInteractiveHistory();
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
this.#writeControl(`\r\u001B[${linesToClear}A\u001B[J`);
|
|
2339
|
-
this.#terminalLineCount = startLine;
|
|
2340
|
-
this.#terminalColumn = 0;
|
|
2341
|
-
}
|
|
2342
|
-
#endTerminalTracking() {
|
|
2343
|
-
this.#trackedTaskCount = Math.max(0, this.#trackedTaskCount - 1);
|
|
2344
|
-
if (this.#trackedTaskCount === 0) this.#restoreWriteStreams?.();
|
|
2345
|
-
}
|
|
2346
|
-
#patchWriteStream(stream) {
|
|
2347
|
-
if (typeof stream?.write !== "function") return;
|
|
2348
|
-
const originalWrite = stream.write;
|
|
2349
|
-
stream.write = (...args) => {
|
|
2350
|
-
this.#recordTerminalWrite(args[0]);
|
|
2351
|
-
return Reflect.apply(originalWrite, stream, args);
|
|
2352
|
-
};
|
|
2353
|
-
return () => {
|
|
2354
|
-
stream.write = originalWrite;
|
|
2355
|
-
};
|
|
2356
|
-
}
|
|
2357
|
-
#recordTerminalWrite(chunk) {
|
|
2358
|
-
const text = stripControlSequences(toWritableText(chunk));
|
|
2359
|
-
const columns = Math.max(1, this.#stdout?.columns ?? DEFAULT_TERMINAL_COLUMNS);
|
|
2360
|
-
for (const char of text) {
|
|
2361
|
-
if (char === "\n") {
|
|
2362
|
-
this.#terminalLineCount += 1;
|
|
2363
|
-
this.#terminalColumn = 0;
|
|
2364
|
-
continue;
|
|
2365
|
-
}
|
|
2366
|
-
this.#terminalColumn += 1;
|
|
2367
|
-
if (this.#terminalColumn >= columns) {
|
|
2368
|
-
this.#terminalLineCount += 1;
|
|
2369
|
-
this.#terminalColumn = 0;
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
#redrawInteractiveHistory() {
|
|
2374
|
-
this.#writeControl("\r\x1B[H\x1B[2J\x1B[3J");
|
|
2375
|
-
this.#terminalLineCount = 0;
|
|
2376
|
-
this.#terminalColumn = 0;
|
|
2377
|
-
for (const line of this.#interactiveHistory) writeLine({ write: (message) => {
|
|
2378
|
-
this.#writeTracked(message);
|
|
2379
|
-
} }, line);
|
|
2380
|
-
}
|
|
2381
|
-
#formatInteractiveLine(status, message, depth) {
|
|
2382
|
-
const renderedMessage = indentMessage(message, depth);
|
|
2383
|
-
return `${colorInteractiveSymbol(status, FLOW_SYMBOL_BY_STATUS[status])} ${renderedMessage}`;
|
|
2384
|
-
}
|
|
2385
|
-
#replaceInteractiveHistoryLine(index, status, rawMessage, options) {
|
|
2386
|
-
this.#interactiveHistory[index] = this.#formatInteractiveLine(status, formatMessageWithElapsed(rawMessage, options.elapsedTimeMs), options.depth ?? 0);
|
|
2387
|
-
}
|
|
2388
|
-
#writeControl(message) {
|
|
2389
|
-
this.#output.write(message);
|
|
2390
|
-
}
|
|
2391
|
-
#writeTracked(message) {
|
|
2392
|
-
if (!this.#tracksProcessWrites) this.#recordTerminalWrite(message);
|
|
2393
|
-
this.#output.write(message);
|
|
2394
|
-
}
|
|
2395
|
-
};
|
|
2396
|
-
function createLiminaFlowReporter(options = {}) {
|
|
2397
|
-
return new LiminaFlowReporter(options);
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
//#endregion
|
|
2401
|
-
export { resolveReferencePath as $, resolveInternalImport as A, collectGraphProjectRouteFromRoot as B, findTargetProject as C, isDtsProjectConfig as D, inferPackageProject as E, getPackageRootSpecifier as F, createFormatHost as G, collectSourceGraphProjectExtensions as H, getPublishDependencySections as I, getRawReferencePaths as J, formatReferences as K, isWorkspaceDependencySpecifier as L, collectImporters as M, collectWorkspacePackages as N, isRelativeSpecifier$1 as O, findPackageForSpecifier as P, resolveProjectConfigPath as Q, collectCheckerEntryProjectRoutes as R, findPackageForFile as S, getTypecheckConfigPath as T, createExtensionPattern as U, collectGraphProjectRoutes as V, createExtraFileExtensions as W, parseProjectFileNamesForExtensions as X, isOrdinaryTypecheckConfigPath as Y, readJsonConfig as Z, getDeniedRefRule as _, runSourceCheck as a, createFileOwnerLookup as b, GraphLogger as c, ProofLogger as d, ReleaseLogger as f, getDeniedDepRuleForSpecifier as g, getDeniedDepRuleForPackage as h, runCheckerTypecheck as i, shouldResolveThroughGraph as j, parseProject as k, PackageLogger as l, formatErrorMessage$1 as m, createLiminaFlowReporter as n, runInit as o, clearCliScreen as p, getDtsCompanionConfigPath as q, runCheckerBuild as r, CliLogger as s, LiminaFlowReporter as t, PathsLogger as u, normalizeGraphRules as v, formatArtifactDependencyPolicy as w, findImporterForFile as x, collectImportsFromFile as y, collectGraphProjectRoute as z };
|