limina 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +383 -0
- package/bin/limina.js +0 -0
- package/chunks/{dep-jgc7X0zw.js → dep-DzYrmtQJ.js} +32 -49
- package/chunks/{dep-DoSHsBSP.js → dep-UWxsul2A.js} +827 -453
- package/cli.js +667 -229
- package/config.d.ts +8 -48
- package/config.js +1 -1
- package/index.d.ts +25 -20
- package/index.js +3 -3
- package/package.json +19 -2
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { c as getCheckerAdapter,
|
|
2
|
-
import { builtinModules } from "node:module";
|
|
3
|
-
import { createElapsedTimer, formatErrorMessage, formatErrorMessage as formatErrorMessage$1 } from "
|
|
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
4
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import ts from "typescript";
|
|
7
7
|
import { execFile, spawn } from "node:child_process";
|
|
8
8
|
import { glob } from "tinyglobby";
|
|
9
|
-
import { createLogger } from "
|
|
9
|
+
import { createLogger } from "logaria";
|
|
10
10
|
import readline from "node:readline";
|
|
11
|
-
import { availableParallelism } from "node:os";
|
|
12
11
|
import * as prompts from "@clack/prompts";
|
|
12
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
13
|
+
import { parse } from "yaml";
|
|
13
14
|
|
|
14
15
|
//#region src/tsconfig.ts
|
|
15
16
|
const dtsConfigFilePattern = /^tsconfig(?:\..+)?\.dts\.json$/u;
|
|
16
17
|
const buildGraphConfigFilePattern = /^tsconfig(?:\..+)?\.build\.json$/u;
|
|
17
|
-
const deprecatedGraphConfigFilePattern = /^tsconfig(?:\..+)?\.graph\.json$/u;
|
|
18
18
|
const generatedConfigFilePattern = /^tsconfig(?:\..+)?\.paths\.generated\.json$/u;
|
|
19
19
|
const baseConfigFilePattern = /^tsconfig(?:\..+)?\.base\.json$/u;
|
|
20
20
|
const checkConfigFilePattern = /^tsconfig(?:\..+)?\.check\.json$/u;
|
|
@@ -30,7 +30,7 @@ function escapeRegExp(value) {
|
|
|
30
30
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
31
31
|
}
|
|
32
32
|
function createExtensionPattern(extensions) {
|
|
33
|
-
if (extensions.length === 0) return
|
|
33
|
+
if (extensions.length === 0) return /(?!)/u;
|
|
34
34
|
return new RegExp(`(?:${extensions.sort((left, right) => right.length - left.length).map(escapeRegExp).join("|")})$`, "u");
|
|
35
35
|
}
|
|
36
36
|
function createExtraFileExtensions(extensions) {
|
|
@@ -140,13 +140,6 @@ function collectReferencePathInfosFromConfigObject(rootDir, configPath, configOb
|
|
|
140
140
|
references: referenceInfos
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
-
function isNonEmptyStringArray(value) {
|
|
144
|
-
return Array.isArray(value) && value.some((item) => typeof item === "string");
|
|
145
|
-
}
|
|
146
|
-
function hasOwnTypecheckInputs(configObject) {
|
|
147
|
-
if (!Object.hasOwn(configObject, "files") && !Object.hasOwn(configObject, "include")) return true;
|
|
148
|
-
return isNonEmptyStringArray(configObject.files) || isNonEmptyStringArray(configObject.include);
|
|
149
|
-
}
|
|
150
143
|
function isDtsConfigPath(configPath) {
|
|
151
144
|
return dtsConfigFilePattern.test(path.basename(configPath));
|
|
152
145
|
}
|
|
@@ -161,101 +154,13 @@ function getDtsCompanionConfigPath(dtsConfigPath) {
|
|
|
161
154
|
function isBuildGraphConfigPath(configPath) {
|
|
162
155
|
return buildGraphConfigFilePattern.test(path.basename(configPath));
|
|
163
156
|
}
|
|
164
|
-
function isDeprecatedGraphConfigPath(configPath) {
|
|
165
|
-
return deprecatedGraphConfigFilePattern.test(path.basename(configPath));
|
|
166
|
-
}
|
|
167
157
|
function isReservedTypeScriptConfigFile(fileName) {
|
|
168
|
-
return dtsConfigFilePattern.test(fileName) || buildGraphConfigFilePattern.test(fileName) ||
|
|
158
|
+
return dtsConfigFilePattern.test(fileName) || buildGraphConfigFilePattern.test(fileName) || generatedConfigFilePattern.test(fileName) || baseConfigFilePattern.test(fileName) || checkConfigFilePattern.test(fileName);
|
|
169
159
|
}
|
|
170
160
|
function isOrdinaryTypecheckConfigPath(configPath) {
|
|
171
161
|
const fileName = path.basename(configPath);
|
|
172
162
|
return tsconfigFilePattern.test(fileName) && !isReservedTypeScriptConfigFile(fileName);
|
|
173
163
|
}
|
|
174
|
-
function collectTypecheckTargetProjectPaths(options) {
|
|
175
|
-
const rootConfigPath = normalizeAbsolutePath(options.rootConfigPath);
|
|
176
|
-
const reportedCycles = /* @__PURE__ */ new Set();
|
|
177
|
-
const seen = /* @__PURE__ */ new Set();
|
|
178
|
-
const problems = [];
|
|
179
|
-
const projectPaths = [];
|
|
180
|
-
const targetProjectPaths = [];
|
|
181
|
-
const formatConfigPath = (configPath) => toRelativePath(options.rootDir, configPath);
|
|
182
|
-
const addCycleProblem = (referencePath, stack) => {
|
|
183
|
-
const cycleStartIndex = stack.indexOf(referencePath);
|
|
184
|
-
const cyclePaths = cycleStartIndex === -1 ? [...stack, referencePath] : [...stack.slice(cycleStartIndex), referencePath];
|
|
185
|
-
const cycleKey = cyclePaths.join("\0");
|
|
186
|
-
if (reportedCycles.has(cycleKey)) return;
|
|
187
|
-
reportedCycles.add(cycleKey);
|
|
188
|
-
problems.push([
|
|
189
|
-
"Circular reference in ordinary tsconfig references:",
|
|
190
|
-
` cycle: ${cyclePaths.map(formatConfigPath).join(" -> ")}`,
|
|
191
|
-
" reason: ordinary tsconfig references used by limina checker typecheck must form an acyclic graph.",
|
|
192
|
-
" fix: remove one reference from the cycle, or move shared options into extends instead of references."
|
|
193
|
-
].join("\n"));
|
|
194
|
-
};
|
|
195
|
-
const visitProject = (projectPath, stack) => {
|
|
196
|
-
if (stack.includes(projectPath)) {
|
|
197
|
-
addCycleProblem(projectPath, stack);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (seen.has(projectPath)) return;
|
|
201
|
-
if (!existsSync(projectPath)) {
|
|
202
|
-
problems.push(["Ordinary tsconfig reference graph references a missing tsconfig:", ` config: ${formatConfigPath(projectPath)}`].join("\n"));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (!isOrdinaryTypecheckConfigPath(projectPath)) {
|
|
206
|
-
problems.push([
|
|
207
|
-
"Invalid config in ordinary tsconfig reference graph:",
|
|
208
|
-
` config: ${formatConfigPath(projectPath)}`,
|
|
209
|
-
" reason: ordinary tsconfig references must stay on ordinary tsconfig*.json files; tsconfig*.build.json graph aggregators and tsconfig*.dts.json declaration leaves belong to checker entries."
|
|
210
|
-
].join("\n"));
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
seen.add(projectPath);
|
|
214
|
-
projectPaths.push(projectPath);
|
|
215
|
-
const configObject = readJsonConfigFile(options.rootDir, projectPath);
|
|
216
|
-
const referenceCollection = collectReferencePathInfosFromConfigObject(options.rootDir, projectPath, configObject);
|
|
217
|
-
const referencePaths = referenceCollection.references.map((reference) => reference.resolvedPath);
|
|
218
|
-
problems.push(...referenceCollection.problems);
|
|
219
|
-
if (referencePaths.length === 0 || hasOwnTypecheckInputs(configObject)) targetProjectPaths.push(projectPath);
|
|
220
|
-
const nextStack = [...stack, projectPath];
|
|
221
|
-
for (const referencePath of referencePaths) {
|
|
222
|
-
if (isBuildGraphConfigPath(referencePath) || isDtsConfigPath(referencePath)) {
|
|
223
|
-
problems.push([
|
|
224
|
-
"Invalid reference in ordinary tsconfig reference graph:",
|
|
225
|
-
` from: ${formatConfigPath(projectPath)}`,
|
|
226
|
-
` to: ${formatConfigPath(referencePath)}`,
|
|
227
|
-
" reason: ordinary tsconfig references must stay on ordinary tsconfig*.json files; build graph configs and declaration leaves are checked through checker entries."
|
|
228
|
-
].join("\n"));
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (!isOrdinaryTypecheckConfigPath(referencePath)) {
|
|
232
|
-
problems.push([
|
|
233
|
-
"Invalid reference in ordinary tsconfig reference graph:",
|
|
234
|
-
` from: ${formatConfigPath(projectPath)}`,
|
|
235
|
-
` to: ${formatConfigPath(referencePath)}`,
|
|
236
|
-
" reason: referenced config must be an ordinary tsconfig*.json file."
|
|
237
|
-
].join("\n"));
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (nextStack.includes(referencePath)) {
|
|
241
|
-
addCycleProblem(referencePath, nextStack);
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
visitProject(referencePath, nextStack);
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
visitProject(rootConfigPath, []);
|
|
248
|
-
if (problems.length === 0 && targetProjectPaths.length === 0) problems.push([
|
|
249
|
-
"Ordinary tsconfig reference graph has no tsconfig targets:",
|
|
250
|
-
` root: ${toRelativePath(options.rootDir, rootConfigPath)}`,
|
|
251
|
-
" reason: limina checker typecheck runs ordinary tsconfig*.json files without references, plus configs that have references and their own source inputs."
|
|
252
|
-
].join("\n"));
|
|
253
|
-
return {
|
|
254
|
-
problems,
|
|
255
|
-
projectPaths,
|
|
256
|
-
targetProjectPaths
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
164
|
function collectGraphProjectRouteFromRoot(options) {
|
|
260
165
|
const rootGraphConfigPath = normalizeAbsolutePath(options.rootConfigPath);
|
|
261
166
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -269,13 +174,7 @@ function collectGraphProjectRouteFromRoot(options) {
|
|
|
269
174
|
}));
|
|
270
175
|
const formatConfigPath = (configPath) => toRelativePath(options.rootDir, configPath);
|
|
271
176
|
problems.push(...rootReferences.problems);
|
|
272
|
-
if (
|
|
273
|
-
"Checker entry uses a deprecated tsconfig name:",
|
|
274
|
-
` config: ${formatConfigPath(rootGraphConfigPath)}`,
|
|
275
|
-
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
276
|
-
" fix: rename the checker entry to tsconfig*.build.json."
|
|
277
|
-
].join("\n"));
|
|
278
|
-
else if (!isBuildGraphConfigPath(rootGraphConfigPath) && !isDtsConfigPath(rootGraphConfigPath)) problems.push([
|
|
177
|
+
if (!isBuildGraphConfigPath(rootGraphConfigPath) && !isDtsConfigPath(rootGraphConfigPath)) problems.push([
|
|
279
178
|
"Invalid checker entry config:",
|
|
280
179
|
` config: ${formatConfigPath(rootGraphConfigPath)}`,
|
|
281
180
|
" reason: checker entries should point to a tsconfig*.build.json graph aggregator or a direct tsconfig*.dts.json declaration leaf."
|
|
@@ -287,17 +186,6 @@ function collectGraphProjectRouteFromRoot(options) {
|
|
|
287
186
|
for (const { projectPath } of queue) seen.add(projectPath);
|
|
288
187
|
for (const { projectPath, rawReferencePath, referrerPath } of queue) {
|
|
289
188
|
if (!projectPath) continue;
|
|
290
|
-
if (isDeprecatedGraphConfigPath(projectPath)) {
|
|
291
|
-
problems.push([
|
|
292
|
-
"Deprecated checker entry reference:",
|
|
293
|
-
` from: ${formatConfigPath(referrerPath)}`,
|
|
294
|
-
` reference: ${rawReferencePath}`,
|
|
295
|
-
` resolved: ${formatConfigPath(projectPath)}`,
|
|
296
|
-
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
297
|
-
" fix: rename the referenced config to tsconfig*.build.json and update this reference."
|
|
298
|
-
].join("\n"));
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
189
|
if (!existsSync(projectPath)) {
|
|
302
190
|
problems.push([
|
|
303
191
|
"Checker entry references a missing tsconfig:",
|
|
@@ -341,18 +229,8 @@ function collectGraphProjectRoutes(config) {
|
|
|
341
229
|
const routes = [];
|
|
342
230
|
const problems = [];
|
|
343
231
|
for (const checker of getActiveCheckers(config)) {
|
|
344
|
-
if (!getCheckerAdapter(checker.preset)?.
|
|
232
|
+
if (!getCheckerAdapter(checker.preset)?.sourceGraph) continue;
|
|
345
233
|
const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
|
|
346
|
-
if (isDeprecatedGraphConfigPath(rootConfigPath)) {
|
|
347
|
-
problems.push([
|
|
348
|
-
"Checker graph entry uses a deprecated tsconfig name:",
|
|
349
|
-
` checker: ${checker.name}`,
|
|
350
|
-
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`,
|
|
351
|
-
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
352
|
-
" fix: rename the checker entry to tsconfig*.build.json."
|
|
353
|
-
].join("\n"));
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
234
|
if (!existsSync(rootConfigPath)) {
|
|
357
235
|
problems.push([
|
|
358
236
|
"Checker graph entry references a missing tsconfig:",
|
|
@@ -368,6 +246,7 @@ function collectGraphProjectRoutes(config) {
|
|
|
368
246
|
problems.push(...routeCollection.problems);
|
|
369
247
|
routes.push({
|
|
370
248
|
checkerName: checker.name,
|
|
249
|
+
extensions: checker.extensions,
|
|
371
250
|
projectPaths: routeCollection.projectPaths,
|
|
372
251
|
rootConfigPath
|
|
373
252
|
});
|
|
@@ -382,16 +261,6 @@ function collectCheckerEntryProjectRoutes(config) {
|
|
|
382
261
|
const problems = [];
|
|
383
262
|
for (const checker of getActiveCheckers(config)) {
|
|
384
263
|
const rootConfigPath = resolveProjectConfigPath(config.rootDir, checker.entry);
|
|
385
|
-
if (isDeprecatedGraphConfigPath(rootConfigPath)) {
|
|
386
|
-
problems.push([
|
|
387
|
-
"Checker entry uses a deprecated tsconfig name:",
|
|
388
|
-
` checker: ${checker.name}`,
|
|
389
|
-
` config: ${toRelativePath(config.rootDir, rootConfigPath)}`,
|
|
390
|
-
" reason: tsconfig*.graph.json has been renamed to tsconfig*.build.json for declaration build graph aggregators.",
|
|
391
|
-
" fix: rename the checker entry to tsconfig*.build.json."
|
|
392
|
-
].join("\n"));
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
264
|
if (!existsSync(rootConfigPath)) {
|
|
396
265
|
problems.push([
|
|
397
266
|
"Checker entry references a missing tsconfig:",
|
|
@@ -407,6 +276,7 @@ function collectCheckerEntryProjectRoutes(config) {
|
|
|
407
276
|
problems.push(...routeCollection.problems);
|
|
408
277
|
routes.push({
|
|
409
278
|
checkerName: checker.name,
|
|
279
|
+
extensions: checker.extensions,
|
|
410
280
|
projectPaths: routeCollection.projectPaths,
|
|
411
281
|
rootConfigPath
|
|
412
282
|
});
|
|
@@ -423,8 +293,18 @@ function collectGraphProjectRoute(config) {
|
|
|
423
293
|
projectPaths: [...new Set(routeCollection.routes.flatMap((route) => route.projectPaths))].sort()
|
|
424
294
|
};
|
|
425
295
|
}
|
|
426
|
-
function
|
|
427
|
-
|
|
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
|
+
};
|
|
428
308
|
}
|
|
429
309
|
function parseProjectFileNamesForExtensions(config, configPath, extensions, pattern = createExtensionPattern(extensions)) {
|
|
430
310
|
const diagnostics = [];
|
|
@@ -441,7 +321,7 @@ function formatReferences(rootDir, references) {
|
|
|
441
321
|
|
|
442
322
|
//#endregion
|
|
443
323
|
//#region src/workspace.ts
|
|
444
|
-
const pnpmWorkspaceFileName = "pnpm-workspace.yaml";
|
|
324
|
+
const pnpmWorkspaceFileName$1 = "pnpm-workspace.yaml";
|
|
445
325
|
const pnpmWorkspaceListTimeoutMs = 3e3;
|
|
446
326
|
function readJsonFile(filePath) {
|
|
447
327
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
@@ -488,7 +368,7 @@ function collectWorkspacePatterns(config) {
|
|
|
488
368
|
const rootPackageJson = readJsonFile(rootPackageJsonPath);
|
|
489
369
|
if (Array.isArray(rootPackageJson.workspaces)) for (const pattern of rootPackageJson.workspaces) patterns.add(pattern);
|
|
490
370
|
}
|
|
491
|
-
const workspacePath = path.join(config.rootDir, pnpmWorkspaceFileName);
|
|
371
|
+
const workspacePath = path.join(config.rootDir, pnpmWorkspaceFileName$1);
|
|
492
372
|
if (existsSync(workspacePath)) for (const pattern of collectPnpmWorkspacePatterns(readFileSync(workspacePath, "utf8"))) patterns.add(pattern);
|
|
493
373
|
return [...patterns].sort();
|
|
494
374
|
}
|
|
@@ -625,6 +505,22 @@ function getDependencySections(importer) {
|
|
|
625
505
|
importer.peerDependencies
|
|
626
506
|
].filter((section) => Boolean(section));
|
|
627
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
|
+
}
|
|
628
524
|
function isWorkspaceDependencySpecifier(specifier) {
|
|
629
525
|
return specifier.startsWith("workspace:");
|
|
630
526
|
}
|
|
@@ -663,7 +559,7 @@ function collectImporters(config, packages) {
|
|
|
663
559
|
|
|
664
560
|
//#endregion
|
|
665
561
|
//#region src/graph-context.ts
|
|
666
|
-
function isRelativeSpecifier(specifier) {
|
|
562
|
+
function isRelativeSpecifier$1(specifier) {
|
|
667
563
|
return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
|
|
668
564
|
}
|
|
669
565
|
function isDtsProjectConfig(configPath) {
|
|
@@ -702,9 +598,9 @@ function readProjectLabel(config, configPath) {
|
|
|
702
598
|
].join("\n")
|
|
703
599
|
};
|
|
704
600
|
}
|
|
705
|
-
function parseProject(config, configPath) {
|
|
601
|
+
function parseProject(config, configPath, extensions) {
|
|
706
602
|
const diagnostics = [];
|
|
707
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
603
|
+
const parsed = extensions ? ts.parseJsonConfigFileContent(readJsonConfig(config, configPath), ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions)) : ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
708
604
|
...ts.sys,
|
|
709
605
|
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
710
606
|
diagnostics.push(diagnostic);
|
|
@@ -721,9 +617,20 @@ function parseProject(config, configPath) {
|
|
|
721
617
|
getNewLine: () => "\n"
|
|
722
618
|
}));
|
|
723
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);
|
|
724
630
|
return {
|
|
725
631
|
configPath: normalizeAbsolutePath(configPath),
|
|
726
|
-
|
|
632
|
+
extensions: projectExtensions,
|
|
633
|
+
fileNames: parsed.fileNames.filter((fileName) => filePattern.test(fileName)).map(normalizeAbsolutePath),
|
|
727
634
|
label: labelInfo.label,
|
|
728
635
|
labelProblem: labelInfo.labelProblem,
|
|
729
636
|
options: parsed.options,
|
|
@@ -738,15 +645,27 @@ function getSourceFileKind(filePath) {
|
|
|
738
645
|
function stringLiteralValue(node) {
|
|
739
646
|
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
740
647
|
}
|
|
741
|
-
function
|
|
742
|
-
const
|
|
743
|
-
|
|
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);
|
|
744
662
|
const imports = [];
|
|
663
|
+
const lineOffset = options.lineOffset ?? 0;
|
|
745
664
|
const addImport = (specifier, node) => {
|
|
746
665
|
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
747
666
|
imports.push({
|
|
748
|
-
filePath,
|
|
749
|
-
line: location.line + 1,
|
|
667
|
+
filePath: options.filePath,
|
|
668
|
+
line: lineOffset + location.line + 1,
|
|
750
669
|
specifier
|
|
751
670
|
});
|
|
752
671
|
};
|
|
@@ -766,9 +685,37 @@ function collectImportsFromFile(filePath) {
|
|
|
766
685
|
visit(sourceFile);
|
|
767
686
|
return imports;
|
|
768
687
|
}
|
|
769
|
-
function
|
|
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 = []) {
|
|
770
708
|
const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
|
|
771
|
-
|
|
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;
|
|
772
719
|
}
|
|
773
720
|
function chooseOwningProject(projectPaths) {
|
|
774
721
|
return [...projectPaths].sort((left, right) => {
|
|
@@ -779,9 +726,6 @@ function chooseOwningProject(projectPaths) {
|
|
|
779
726
|
function findPackageForFile(filePath, packages) {
|
|
780
727
|
return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
|
|
781
728
|
}
|
|
782
|
-
function isWorkspacePackageFile(filePath, packages) {
|
|
783
|
-
return packages.some((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory));
|
|
784
|
-
}
|
|
785
729
|
function findImporterForFile(filePath, importers) {
|
|
786
730
|
return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
|
|
787
731
|
}
|
|
@@ -834,6 +778,60 @@ function formatUnknownValue(value) {
|
|
|
834
778
|
function addRuleEntryConfigProblem(problems, details) {
|
|
835
779
|
problems.push(["Invalid graph rule config:", ...details].join("\n"));
|
|
836
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
|
+
}
|
|
837
835
|
function getRulesRecord(config, problems) {
|
|
838
836
|
const rules = config.graph?.rules;
|
|
839
837
|
if (rules === void 0) return {};
|
|
@@ -900,13 +898,13 @@ function addNormalizedRuleRef(options) {
|
|
|
900
898
|
});
|
|
901
899
|
options.refsByLabel.set(options.label, refs);
|
|
902
900
|
}
|
|
903
|
-
function
|
|
904
|
-
const field =
|
|
901
|
+
function addNormalizedDep(options) {
|
|
902
|
+
const field = `graph.rules.${options.label}.deny.deps[${options.index}]`;
|
|
905
903
|
if (!isPlainRecord(options.entry)) {
|
|
906
904
|
addRuleEntryConfigProblem(options.problems, [
|
|
907
905
|
` field: ${field}`,
|
|
908
906
|
` value: ${formatUnknownValue(options.entry)}`,
|
|
909
|
-
" reason: deny
|
|
907
|
+
" reason: deny.deps entries must be objects with non-empty name and reason fields."
|
|
910
908
|
]);
|
|
911
909
|
return;
|
|
912
910
|
}
|
|
@@ -916,7 +914,7 @@ function addNormalizedWorkspaceDep(options) {
|
|
|
916
914
|
addRuleEntryConfigProblem(options.problems, [
|
|
917
915
|
` field: ${field}.name`,
|
|
918
916
|
` value: ${formatUnknownValue(nameValue)}`,
|
|
919
|
-
" reason:
|
|
917
|
+
" reason: deny.deps name is required and must be a non-empty string."
|
|
920
918
|
]);
|
|
921
919
|
return;
|
|
922
920
|
}
|
|
@@ -924,82 +922,31 @@ function addNormalizedWorkspaceDep(options) {
|
|
|
924
922
|
addRuleEntryConfigProblem(options.problems, [
|
|
925
923
|
` field: ${field}.reason`,
|
|
926
924
|
` value: ${formatUnknownValue(reasonValue)}`,
|
|
927
|
-
" reason:
|
|
928
|
-
]);
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
const packageName = nameValue.trim();
|
|
932
|
-
if (!options.packageNames.has(packageName)) {
|
|
933
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
934
|
-
` field: ${field}.name`,
|
|
935
|
-
` name: ${packageName}`,
|
|
936
|
-
" reason: deny.workspaceDeps and legacy deny.deps only accept discovered workspace package names. Use deny.nodeBuiltins for Node builtins."
|
|
937
|
-
]);
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
const deps = options.workspaceDepsByLabel.get(options.label) ?? /* @__PURE__ */ new Map();
|
|
941
|
-
deps.set(packageName, {
|
|
942
|
-
name: packageName,
|
|
943
|
-
reason: reasonValue.trim()
|
|
944
|
-
});
|
|
945
|
-
options.workspaceDepsByLabel.set(options.label, deps);
|
|
946
|
-
}
|
|
947
|
-
function addNormalizedNodeBuiltin(options) {
|
|
948
|
-
const field = `graph.rules.${options.label}.deny.nodeBuiltins[${options.index}]`;
|
|
949
|
-
if (!isPlainRecord(options.entry)) {
|
|
950
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
951
|
-
` field: ${field}`,
|
|
952
|
-
` value: ${formatUnknownValue(options.entry)}`,
|
|
953
|
-
" reason: deny.nodeBuiltins entries must be objects with non-empty name and reason fields."
|
|
954
|
-
]);
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
const nameValue = options.entry.name;
|
|
958
|
-
const reasonValue = options.entry.reason;
|
|
959
|
-
if (!isNonEmptyString(nameValue)) {
|
|
960
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
961
|
-
` field: ${field}.name`,
|
|
962
|
-
` value: ${formatUnknownValue(nameValue)}`,
|
|
963
|
-
" reason: deny.nodeBuiltins name is required and must be a non-empty string."
|
|
964
|
-
]);
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
if (!isNonEmptyString(reasonValue)) {
|
|
968
|
-
addRuleEntryConfigProblem(options.problems, [
|
|
969
|
-
` field: ${field}.reason`,
|
|
970
|
-
` value: ${formatUnknownValue(reasonValue)}`,
|
|
971
|
-
" reason: deny.nodeBuiltins reason is required and must be a non-empty string."
|
|
925
|
+
" reason: deny.deps reason is required and must be a non-empty string."
|
|
972
926
|
]);
|
|
973
927
|
return;
|
|
974
928
|
}
|
|
975
929
|
const name = nameValue.trim();
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
if (!matchAll && !nodeBuiltinNames.has(normalizedName)) {
|
|
930
|
+
const normalizedDep = createNormalizedDep(name, reasonValue.trim());
|
|
931
|
+
if (!normalizedDep) {
|
|
979
932
|
addRuleEntryConfigProblem(options.problems, [
|
|
980
933
|
` field: ${field}.name`,
|
|
981
934
|
` name: ${name}`,
|
|
982
|
-
" reason: deny.
|
|
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:*\"."
|
|
983
936
|
]);
|
|
984
937
|
return;
|
|
985
938
|
}
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
name: normalizedName,
|
|
990
|
-
reason: reasonValue.trim()
|
|
991
|
-
});
|
|
992
|
-
options.nodeBuiltinsByLabel.set(options.label, entries);
|
|
939
|
+
const deps = options.depsByLabel.get(options.label) ?? [];
|
|
940
|
+
deps.push(normalizedDep);
|
|
941
|
+
options.depsByLabel.set(options.label, deps);
|
|
993
942
|
}
|
|
994
943
|
function shouldNormalizeRuleKind(include, kind) {
|
|
995
944
|
return include?.[kind] ?? true;
|
|
996
945
|
}
|
|
997
946
|
function normalizeGraphRules(options) {
|
|
947
|
+
const depsByLabel = /* @__PURE__ */ new Map();
|
|
998
948
|
const refsByLabel = /* @__PURE__ */ new Map();
|
|
999
|
-
const workspaceDepsByLabel = /* @__PURE__ */ new Map();
|
|
1000
|
-
const nodeBuiltinsByLabel = /* @__PURE__ */ new Map();
|
|
1001
949
|
const projectPathSet = new Set(options.projectPaths);
|
|
1002
|
-
const packageNames = new Set(options.packages.map((workspacePackage) => workspacePackage.name));
|
|
1003
950
|
for (const [rawLabel, rawRule] of Object.entries(getRulesRecord(options.config, options.problems))) {
|
|
1004
951
|
const label = rawLabel.trim();
|
|
1005
952
|
if (!label) {
|
|
@@ -1040,60 +987,35 @@ function normalizeGraphRules(options) {
|
|
|
1040
987
|
refsByLabel
|
|
1041
988
|
});
|
|
1042
989
|
});
|
|
1043
|
-
|
|
1044
|
-
if (shouldNormalizeRuleKind(options.include, "workspaceDeps") && legacyDeps !== void 0) if (!Array.isArray(legacyDeps)) addRuleEntryConfigProblem(options.problems, [
|
|
1045
|
-
` field: graph.rules.${label}.deny.deps`,
|
|
1046
|
-
` value: ${formatUnknownValue(legacyDeps)}`,
|
|
1047
|
-
" reason: deny.deps must be an array."
|
|
1048
|
-
]);
|
|
1049
|
-
else legacyDeps.forEach((entry, index) => {
|
|
1050
|
-
addNormalizedWorkspaceDep({
|
|
1051
|
-
entry,
|
|
1052
|
-
fieldPrefix: `graph.rules.${label}.deny.deps`,
|
|
1053
|
-
index,
|
|
1054
|
-
label,
|
|
1055
|
-
packageNames,
|
|
1056
|
-
problems: options.problems,
|
|
1057
|
-
workspaceDepsByLabel
|
|
1058
|
-
});
|
|
1059
|
-
});
|
|
1060
|
-
const workspaceDeps = rawRule.deny.workspaceDeps;
|
|
1061
|
-
if (shouldNormalizeRuleKind(options.include, "workspaceDeps") && workspaceDeps !== void 0) if (!Array.isArray(workspaceDeps)) addRuleEntryConfigProblem(options.problems, [
|
|
990
|
+
if (shouldNormalizeRuleKind(options.include, "deps") && rawRule.deny.workspaceDeps !== void 0) addRuleEntryConfigProblem(options.problems, [
|
|
1062
991
|
` field: graph.rules.${label}.deny.workspaceDeps`,
|
|
1063
|
-
` value: ${formatUnknownValue(workspaceDeps)}`,
|
|
1064
|
-
" reason: deny.workspaceDeps
|
|
992
|
+
` value: ${formatUnknownValue(rawRule.deny.workspaceDeps)}`,
|
|
993
|
+
" reason: deny.workspaceDeps has been removed; use deny.deps."
|
|
1065
994
|
]);
|
|
1066
|
-
|
|
1067
|
-
addNormalizedWorkspaceDep({
|
|
1068
|
-
entry,
|
|
1069
|
-
fieldPrefix: `graph.rules.${label}.deny.workspaceDeps`,
|
|
1070
|
-
index,
|
|
1071
|
-
label,
|
|
1072
|
-
packageNames,
|
|
1073
|
-
problems: options.problems,
|
|
1074
|
-
workspaceDepsByLabel
|
|
1075
|
-
});
|
|
1076
|
-
});
|
|
1077
|
-
const nodeBuiltins = rawRule.deny.nodeBuiltins;
|
|
1078
|
-
if (shouldNormalizeRuleKind(options.include, "nodeBuiltins") && nodeBuiltins !== void 0) if (!Array.isArray(nodeBuiltins)) addRuleEntryConfigProblem(options.problems, [
|
|
995
|
+
if (shouldNormalizeRuleKind(options.include, "deps") && rawRule.deny.nodeBuiltins !== void 0) addRuleEntryConfigProblem(options.problems, [
|
|
1079
996
|
` field: graph.rules.${label}.deny.nodeBuiltins`,
|
|
1080
|
-
` value: ${formatUnknownValue(nodeBuiltins)}`,
|
|
1081
|
-
" reason: deny.nodeBuiltins
|
|
997
|
+
` value: ${formatUnknownValue(rawRule.deny.nodeBuiltins)}`,
|
|
998
|
+
" reason: deny.nodeBuiltins has been removed; use deny.deps."
|
|
1082
999
|
]);
|
|
1083
|
-
|
|
1084
|
-
|
|
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,
|
|
1085
1009
|
entry,
|
|
1086
1010
|
index,
|
|
1087
1011
|
label,
|
|
1088
|
-
nodeBuiltinsByLabel,
|
|
1089
1012
|
problems: options.problems
|
|
1090
1013
|
});
|
|
1091
1014
|
});
|
|
1092
1015
|
}
|
|
1093
1016
|
return {
|
|
1094
|
-
|
|
1095
|
-
refsByLabel
|
|
1096
|
-
workspaceDepsByLabel
|
|
1017
|
+
depsByLabel,
|
|
1018
|
+
refsByLabel
|
|
1097
1019
|
};
|
|
1098
1020
|
}
|
|
1099
1021
|
function isNodeBuiltinSpecifier(specifier) {
|
|
@@ -1103,14 +1025,24 @@ function getDeniedRefRule(rules, label, targetProjectPath) {
|
|
|
1103
1025
|
if (!label) return null;
|
|
1104
1026
|
return rules.refsByLabel.get(label)?.get(targetProjectPath) ?? null;
|
|
1105
1027
|
}
|
|
1106
|
-
function
|
|
1107
|
-
if (!label) return
|
|
1108
|
-
return rules.
|
|
1109
|
-
}
|
|
1110
|
-
function
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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));
|
|
1114
1046
|
}
|
|
1115
1047
|
|
|
1116
1048
|
//#endregion
|
|
@@ -1118,6 +1050,7 @@ function getDeniedNodeBuiltinRule(rules, label, specifier) {
|
|
|
1118
1050
|
const logger = createLogger({ main: "limina" });
|
|
1119
1051
|
const CliLogger = logger.getLoggerByGroup("task.cli");
|
|
1120
1052
|
const GraphLogger = logger.getLoggerByGroup("task.graph");
|
|
1053
|
+
const InitLogger = logger.getLoggerByGroup("task.init");
|
|
1121
1054
|
const PackageLogger = logger.getLoggerByGroup("task.package");
|
|
1122
1055
|
const PathsLogger = logger.getLoggerByGroup("task.paths");
|
|
1123
1056
|
const ProofLogger = logger.getLoggerByGroup("task.proof");
|
|
@@ -1132,6 +1065,541 @@ function clearCliScreen() {
|
|
|
1132
1065
|
readline.clearScreenDown(process.stdout);
|
|
1133
1066
|
}
|
|
1134
1067
|
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region src/commands/init.ts
|
|
1070
|
+
const pnpmWorkspaceFileName = "pnpm-workspace.yaml";
|
|
1071
|
+
const liminaConfigFileName = "limina.config.mjs";
|
|
1072
|
+
const liminaCheckScriptName = "limina:check";
|
|
1073
|
+
const liminaCheckScriptValue = "limina check";
|
|
1074
|
+
const ignoredGlobPatterns = [
|
|
1075
|
+
"**/.git/**",
|
|
1076
|
+
"**/.limina/**",
|
|
1077
|
+
"**/.pnpm-store/**",
|
|
1078
|
+
"**/.tsbuild/**",
|
|
1079
|
+
"**/coverage/**",
|
|
1080
|
+
"**/dist/**",
|
|
1081
|
+
"**/node_modules/**"
|
|
1082
|
+
];
|
|
1083
|
+
function findPnpmWorkspaceRoot(startDir) {
|
|
1084
|
+
let currentDir = path.resolve(startDir);
|
|
1085
|
+
while (true) {
|
|
1086
|
+
if (existsSync(path.join(currentDir, pnpmWorkspaceFileName))) return normalizeAbsolutePath(currentDir);
|
|
1087
|
+
const parentDir = path.dirname(currentDir);
|
|
1088
|
+
if (parentDir === currentDir) return null;
|
|
1089
|
+
currentDir = parentDir;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function createInitConfig(rootDir) {
|
|
1093
|
+
return {
|
|
1094
|
+
configPath: path.join(rootDir, liminaConfigFileName),
|
|
1095
|
+
rootDir
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function formatConfigPath(rootDir, configPath) {
|
|
1099
|
+
return toRelativePath(rootDir, configPath);
|
|
1100
|
+
}
|
|
1101
|
+
function formatReferencePath(fromConfigPath, toConfigPath) {
|
|
1102
|
+
const relativePath = toPosixPath(path.relative(path.dirname(fromConfigPath), toConfigPath));
|
|
1103
|
+
return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
|
|
1104
|
+
}
|
|
1105
|
+
function stringifyJson(value) {
|
|
1106
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
1107
|
+
}
|
|
1108
|
+
function getProjectScope(configPath) {
|
|
1109
|
+
const fileName = path.basename(configPath);
|
|
1110
|
+
if (fileName === "tsconfig.json") return "tsconfig";
|
|
1111
|
+
return /^tsconfig\.(.+)\.json$/u.exec(fileName)?.[1] ?? "tsconfig";
|
|
1112
|
+
}
|
|
1113
|
+
function getDtsConfigPath(configPath) {
|
|
1114
|
+
const directory = path.dirname(configPath);
|
|
1115
|
+
const fileName = path.basename(configPath);
|
|
1116
|
+
const dtsFileName = fileName === "tsconfig.json" ? "tsconfig.dts.json" : fileName.replace(/\.json$/u, ".dts.json");
|
|
1117
|
+
return path.join(directory, dtsFileName);
|
|
1118
|
+
}
|
|
1119
|
+
function hasReferences(configObject) {
|
|
1120
|
+
return Array.isArray(configObject.references) && configObject.references.length > 0;
|
|
1121
|
+
}
|
|
1122
|
+
function parseTypeScriptConfig(rootDir, configPath) {
|
|
1123
|
+
const diagnostics = [];
|
|
1124
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
1125
|
+
...ts.sys,
|
|
1126
|
+
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
1127
|
+
diagnostics.push(diagnostic);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
1131
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
1132
|
+
getCurrentDirectory: () => rootDir,
|
|
1133
|
+
getNewLine: () => "\n"
|
|
1134
|
+
}));
|
|
1135
|
+
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
1136
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
1137
|
+
getCurrentDirectory: () => rootDir,
|
|
1138
|
+
getNewLine: () => "\n"
|
|
1139
|
+
}));
|
|
1140
|
+
return {
|
|
1141
|
+
fileNames: parsed.fileNames.map(normalizeAbsolutePath),
|
|
1142
|
+
options: parsed.options
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function findNearestWorkspacePackage(filePath, packages) {
|
|
1146
|
+
return [...packages].sort((left, right) => right.directory.length - left.directory.length).find((workspacePackage) => isPathInsideDirectory(filePath, workspacePackage.directory)) ?? null;
|
|
1147
|
+
}
|
|
1148
|
+
function collectWorkspaceDependencyNames(manifest) {
|
|
1149
|
+
const dependencyNames = /* @__PURE__ */ new Set();
|
|
1150
|
+
for (const dependencies of getDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) if (isWorkspaceDependencySpecifier(specifier)) dependencyNames.add(dependencyName);
|
|
1151
|
+
return dependencyNames;
|
|
1152
|
+
}
|
|
1153
|
+
function findOwningProjectForFile(filePath, projects) {
|
|
1154
|
+
const normalizedFilePath = normalizeAbsolutePath(filePath);
|
|
1155
|
+
return projects.filter((project) => project.fileNames.includes(normalizedFilePath)).sort((left, right) => {
|
|
1156
|
+
const depthDelta = path.dirname(right.configPath).length - path.dirname(left.configPath).length;
|
|
1157
|
+
return depthDelta === 0 ? left.configPath.localeCompare(right.configPath) : depthDelta;
|
|
1158
|
+
})[0] ?? null;
|
|
1159
|
+
}
|
|
1160
|
+
function isDirectory(filePath) {
|
|
1161
|
+
try {
|
|
1162
|
+
return statSync(filePath).isDirectory();
|
|
1163
|
+
} catch {
|
|
1164
|
+
return false;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function mapVirtualWorkspacePath(filePath, packageByName) {
|
|
1168
|
+
const segments = normalizeAbsolutePath(filePath).split("/");
|
|
1169
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
1170
|
+
if (segments[index] !== "node_modules") continue;
|
|
1171
|
+
const firstPackageSegment = segments[index + 1];
|
|
1172
|
+
if (!firstPackageSegment) continue;
|
|
1173
|
+
const isScopedPackage = firstPackageSegment.startsWith("@");
|
|
1174
|
+
const packageName = isScopedPackage ? `${firstPackageSegment}/${segments[index + 2] ?? ""}` : firstPackageSegment;
|
|
1175
|
+
const restStart = index + (isScopedPackage ? 3 : 2);
|
|
1176
|
+
const workspacePackage = packageByName.get(packageName);
|
|
1177
|
+
if (!workspacePackage) continue;
|
|
1178
|
+
return path.join(workspacePackage.directory, ...segments.slice(restStart));
|
|
1179
|
+
}
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
function isVirtualWorkspaceDirectory(directoryPath, packageByName) {
|
|
1183
|
+
const segments = normalizeAbsolutePath(directoryPath).split("/");
|
|
1184
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
1185
|
+
if (segments[index] !== "node_modules") continue;
|
|
1186
|
+
const firstPackageSegment = segments[index + 1];
|
|
1187
|
+
if (!firstPackageSegment) return packageByName.size > 0;
|
|
1188
|
+
if (firstPackageSegment.startsWith("@") && segments[index + 2] === void 0) return [...packageByName.keys()].some((packageName) => packageName.startsWith(`${firstPackageSegment}/`));
|
|
1189
|
+
}
|
|
1190
|
+
const mappedPath = mapVirtualWorkspacePath(directoryPath, packageByName);
|
|
1191
|
+
return mappedPath ? isDirectory(mappedPath) : false;
|
|
1192
|
+
}
|
|
1193
|
+
function createWorkspaceModuleResolutionHost(options) {
|
|
1194
|
+
const mapPath = (filePath) => mapVirtualWorkspacePath(filePath, options.packageByName) ?? filePath;
|
|
1195
|
+
return {
|
|
1196
|
+
directoryExists: (directoryPath) => isVirtualWorkspaceDirectory(directoryPath, options.packageByName) || (ts.sys.directoryExists?.(directoryPath) ?? isDirectory(directoryPath)),
|
|
1197
|
+
fileExists: (filePath) => {
|
|
1198
|
+
const mappedPath = mapPath(filePath);
|
|
1199
|
+
return existsSync(mappedPath) && !isDirectory(mappedPath);
|
|
1200
|
+
},
|
|
1201
|
+
getCurrentDirectory: () => options.rootDir,
|
|
1202
|
+
readFile: (filePath) => ts.sys.readFile(mapPath(filePath)),
|
|
1203
|
+
realpath: (filePath) => normalizeAbsolutePath(mapPath(filePath)),
|
|
1204
|
+
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function resolveImportWithTypeScript(options) {
|
|
1208
|
+
const resolvedModule = ts.resolveModuleName(options.importRecord.specifier, options.importRecord.filePath, options.project.options, options.host, options.cache).resolvedModule;
|
|
1209
|
+
if (!resolvedModule?.resolvedFileName) return null;
|
|
1210
|
+
return normalizeAbsolutePath(mapVirtualWorkspacePath(resolvedModule.resolvedFileName, options.packageByName) ?? resolvedModule.resolvedFileName);
|
|
1211
|
+
}
|
|
1212
|
+
function isPackageImportSpecifier$1(specifier) {
|
|
1213
|
+
return specifier.startsWith("#");
|
|
1214
|
+
}
|
|
1215
|
+
function createDtsConfig(project) {
|
|
1216
|
+
const fileName = path.basename(project.configPath);
|
|
1217
|
+
const scope = project.scope;
|
|
1218
|
+
const output = {
|
|
1219
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
1220
|
+
extends: [`./${fileName}`],
|
|
1221
|
+
compilerOptions: {
|
|
1222
|
+
composite: true,
|
|
1223
|
+
incremental: true,
|
|
1224
|
+
noEmit: false,
|
|
1225
|
+
declaration: true,
|
|
1226
|
+
emitDeclarationOnly: true,
|
|
1227
|
+
declarationMap: false,
|
|
1228
|
+
rootDir: ".",
|
|
1229
|
+
outDir: "./.limina",
|
|
1230
|
+
tsBuildInfoFile: `./.limina/${scope}.tsbuildinfo`
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
if (project.references.length > 0) output.references = project.references.map((referencePath) => ({ path: referencePath }));
|
|
1234
|
+
return output;
|
|
1235
|
+
}
|
|
1236
|
+
function createBuildAggregator(references) {
|
|
1237
|
+
return {
|
|
1238
|
+
$schema: "https://json.schemastore.org/tsconfig",
|
|
1239
|
+
files: [],
|
|
1240
|
+
references: references.map((referencePath) => ({ path: referencePath }))
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function createLiminaConfigContent() {
|
|
1244
|
+
return `import { defineConfig } from 'limina';
|
|
1245
|
+
|
|
1246
|
+
export default defineConfig({
|
|
1247
|
+
// Shared checker entries used by graph, proof, paths, and typecheck checks.
|
|
1248
|
+
config: {
|
|
1249
|
+
checkers: {
|
|
1250
|
+
typescript: {
|
|
1251
|
+
preset: 'tsc',
|
|
1252
|
+
entry: 'tsconfig.build.json',
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
});
|
|
1257
|
+
`;
|
|
1258
|
+
}
|
|
1259
|
+
async function confirmAction(options, message) {
|
|
1260
|
+
if (options.yes) return true;
|
|
1261
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error(`${message} Run limina init --yes to accept the default confirmation in non-interactive environments.`);
|
|
1262
|
+
const result = await prompts.confirm({
|
|
1263
|
+
initialValue: true,
|
|
1264
|
+
message
|
|
1265
|
+
});
|
|
1266
|
+
if (prompts.isCancel(result)) throw new Error("limina init canceled.");
|
|
1267
|
+
return result;
|
|
1268
|
+
}
|
|
1269
|
+
async function writeTextFile(filePath, content, writtenFiles) {
|
|
1270
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
1271
|
+
await writeFile(filePath, content);
|
|
1272
|
+
writtenFiles.push(filePath);
|
|
1273
|
+
}
|
|
1274
|
+
async function writeBuildAggregatorFile(options) {
|
|
1275
|
+
if (options.references.length === 0) return false;
|
|
1276
|
+
await writeTextFile(options.configPath, stringifyJson(createBuildAggregator(options.references)), options.writtenFiles);
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1279
|
+
function findPnpmWorkspacePath(startDir) {
|
|
1280
|
+
const rootDir = findPnpmWorkspaceRoot(startDir);
|
|
1281
|
+
return rootDir ? path.join(rootDir, pnpmWorkspaceFileName) : null;
|
|
1282
|
+
}
|
|
1283
|
+
function resolveCatalogRange(range, packageName, packageManifestPath) {
|
|
1284
|
+
if (!range) return null;
|
|
1285
|
+
if (!range.startsWith("catalog:")) return range;
|
|
1286
|
+
const workspacePath = findPnpmWorkspacePath(path.dirname(packageManifestPath));
|
|
1287
|
+
if (!workspacePath || !existsSync(workspacePath)) return null;
|
|
1288
|
+
const parsed = parse(readFileSync(workspacePath, "utf8"));
|
|
1289
|
+
const catalogName = range.slice(8);
|
|
1290
|
+
if (catalogName.length === 0 || catalogName === "default") return parsed?.catalog?.[packageName] ?? null;
|
|
1291
|
+
return parsed?.catalogs?.[catalogName]?.[packageName] ?? null;
|
|
1292
|
+
}
|
|
1293
|
+
function readLiminaPackageMetadata() {
|
|
1294
|
+
const manifestPath = createRequire(import.meta.url).resolve("limina/package.json");
|
|
1295
|
+
const manifest = readJsonFile(manifestPath);
|
|
1296
|
+
const versionRange = manifest.version ? `^${manifest.version}` : "^0.0.1";
|
|
1297
|
+
const rawTypeScriptRange = manifest.peerDependencies?.typescript ?? manifest.devDependencies?.typescript ?? manifest.dependencies?.typescript;
|
|
1298
|
+
return {
|
|
1299
|
+
typescriptRange: resolveCatalogRange(rawTypeScriptRange, "typescript", manifestPath) ?? rawTypeScriptRange ?? "^5.9.0",
|
|
1300
|
+
versionRange
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
function hasDependency(manifest, dependencyName) {
|
|
1304
|
+
return Boolean(manifest.dependencies?.[dependencyName] || manifest.devDependencies?.[dependencyName] || manifest.optionalDependencies?.[dependencyName] || manifest.peerDependencies?.[dependencyName]);
|
|
1305
|
+
}
|
|
1306
|
+
async function updateRootPackageJson(options) {
|
|
1307
|
+
const packageJsonPath = path.join(options.rootDir, "package.json");
|
|
1308
|
+
let installRequired = false;
|
|
1309
|
+
if (!existsSync(packageJsonPath)) {
|
|
1310
|
+
if (!await confirmAction(options.prompt, `No package.json found at ${formatConfigPath(options.rootDir, packageJsonPath)}. Create one?`)) {
|
|
1311
|
+
options.skippedFiles.push(packageJsonPath);
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
await writeTextFile(packageJsonPath, stringifyJson({
|
|
1315
|
+
private: true,
|
|
1316
|
+
type: "module",
|
|
1317
|
+
scripts: { [liminaCheckScriptName]: liminaCheckScriptValue },
|
|
1318
|
+
devDependencies: {
|
|
1319
|
+
limina: options.metadata.versionRange,
|
|
1320
|
+
typescript: options.metadata.typescriptRange
|
|
1321
|
+
}
|
|
1322
|
+
}), options.writtenFiles);
|
|
1323
|
+
return true;
|
|
1324
|
+
}
|
|
1325
|
+
const manifest = readJsonFile(packageJsonPath);
|
|
1326
|
+
const scripts = { ...manifest.scripts ?? {} };
|
|
1327
|
+
let changed = false;
|
|
1328
|
+
if (scripts[liminaCheckScriptName] && scripts[liminaCheckScriptName] !== liminaCheckScriptValue) {
|
|
1329
|
+
if (await confirmAction(options.prompt, `Script "${liminaCheckScriptName}" already exists in package.json. Overwrite it?`)) {
|
|
1330
|
+
scripts[liminaCheckScriptName] = liminaCheckScriptValue;
|
|
1331
|
+
changed = true;
|
|
1332
|
+
}
|
|
1333
|
+
} else if (!scripts[liminaCheckScriptName]) {
|
|
1334
|
+
scripts[liminaCheckScriptName] = liminaCheckScriptValue;
|
|
1335
|
+
changed = true;
|
|
1336
|
+
}
|
|
1337
|
+
if (!hasDependency(manifest, "limina")) {
|
|
1338
|
+
manifest.devDependencies = {
|
|
1339
|
+
...manifest.devDependencies ?? {},
|
|
1340
|
+
limina: options.metadata.versionRange
|
|
1341
|
+
};
|
|
1342
|
+
installRequired = true;
|
|
1343
|
+
changed = true;
|
|
1344
|
+
}
|
|
1345
|
+
if (changed) await writeTextFile(packageJsonPath, stringifyJson({
|
|
1346
|
+
...manifest,
|
|
1347
|
+
scripts
|
|
1348
|
+
}), options.writtenFiles);
|
|
1349
|
+
return installRequired;
|
|
1350
|
+
}
|
|
1351
|
+
async function collectReservedConfigConflicts(rootDir) {
|
|
1352
|
+
const conflicts = await glob([
|
|
1353
|
+
"tsconfig*.build.json",
|
|
1354
|
+
"**/tsconfig*.build.json",
|
|
1355
|
+
"tsconfig*.dts.json",
|
|
1356
|
+
"**/tsconfig*.dts.json"
|
|
1357
|
+
], {
|
|
1358
|
+
absolute: false,
|
|
1359
|
+
cwd: rootDir,
|
|
1360
|
+
ignore: ignoredGlobPatterns
|
|
1361
|
+
});
|
|
1362
|
+
return [...new Set(conflicts)].sort();
|
|
1363
|
+
}
|
|
1364
|
+
async function collectOrdinaryTsconfigPaths(rootDir) {
|
|
1365
|
+
const configPaths = await glob(["tsconfig*.json", "**/tsconfig*.json"], {
|
|
1366
|
+
absolute: false,
|
|
1367
|
+
cwd: rootDir,
|
|
1368
|
+
ignore: ignoredGlobPatterns
|
|
1369
|
+
});
|
|
1370
|
+
return [...new Set(configPaths)].map((configPath) => normalizeAbsolutePath(path.join(rootDir, configPath))).filter(isOrdinaryTypecheckConfigPath).sort();
|
|
1371
|
+
}
|
|
1372
|
+
function analyzeTypecheckProjects(options) {
|
|
1373
|
+
const problems = [];
|
|
1374
|
+
const projects = [];
|
|
1375
|
+
for (const configPath of options.configPaths) {
|
|
1376
|
+
const configObject = readJsonConfig(options.config, configPath);
|
|
1377
|
+
const parsed = parseTypeScriptConfig(options.config.rootDir, configPath);
|
|
1378
|
+
const hasProjectReferences = hasReferences(configObject);
|
|
1379
|
+
const fileName = path.basename(configPath);
|
|
1380
|
+
if (fileName === "tsconfig.json" && hasProjectReferences && parsed.fileNames.length > 0) {
|
|
1381
|
+
problems.push([
|
|
1382
|
+
"Invalid tsconfig role:",
|
|
1383
|
+
` config: ${formatConfigPath(options.config.rootDir, configPath)}`,
|
|
1384
|
+
" reason: tsconfig.json must be either a pure aggregator with files: [] and references, or a typecheck leaf with source files, but not both."
|
|
1385
|
+
].join("\n"));
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
if (fileName !== "tsconfig.json" && hasProjectReferences) {
|
|
1389
|
+
problems.push([
|
|
1390
|
+
"Invalid scoped tsconfig role:",
|
|
1391
|
+
` config: ${formatConfigPath(options.config.rootDir, configPath)}`,
|
|
1392
|
+
" reason: tsconfig.<scope>.json files may only be typecheck leaves; graph aggregation belongs in tsconfig*.build.json."
|
|
1393
|
+
].join("\n"));
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
if (fileName === "tsconfig.json" && hasProjectReferences) continue;
|
|
1397
|
+
projects.push({
|
|
1398
|
+
configPath,
|
|
1399
|
+
dtsConfigPath: getDtsConfigPath(configPath),
|
|
1400
|
+
fileNames: parsed.fileNames,
|
|
1401
|
+
options: parsed.options,
|
|
1402
|
+
owner: findNearestWorkspacePackage(configPath, options.workspacePackages),
|
|
1403
|
+
references: [],
|
|
1404
|
+
scope: getProjectScope(configPath)
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
return {
|
|
1408
|
+
problems,
|
|
1409
|
+
projects
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
function inferProjectReferences(options) {
|
|
1413
|
+
const problems = [];
|
|
1414
|
+
const packageByName = new Map(options.workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]));
|
|
1415
|
+
const host = createWorkspaceModuleResolutionHost({
|
|
1416
|
+
packageByName,
|
|
1417
|
+
rootDir: options.config.rootDir
|
|
1418
|
+
});
|
|
1419
|
+
for (const project of options.projects) {
|
|
1420
|
+
if (!project.owner) continue;
|
|
1421
|
+
const workspaceDependencyNames = collectWorkspaceDependencyNames(project.owner.manifest);
|
|
1422
|
+
const resolutionCache = ts.createModuleResolutionCache(path.dirname(project.configPath), (fileName) => fileName, project.options);
|
|
1423
|
+
const referencePaths = /* @__PURE__ */ new Set();
|
|
1424
|
+
for (const fileName of project.fileNames) {
|
|
1425
|
+
if (!/\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)) continue;
|
|
1426
|
+
for (const importRecord of collectImportsFromFile(fileName, options.config.rootDir)) {
|
|
1427
|
+
const resolvedFilePath = resolveImportWithTypeScript({
|
|
1428
|
+
cache: resolutionCache,
|
|
1429
|
+
host,
|
|
1430
|
+
importRecord,
|
|
1431
|
+
packageByName,
|
|
1432
|
+
project
|
|
1433
|
+
});
|
|
1434
|
+
const packageName = isRelativeSpecifier$1(importRecord.specifier) || isPackageImportSpecifier$1(importRecord.specifier) ? null : getPackageRootSpecifier(importRecord.specifier);
|
|
1435
|
+
const targetPackage = packageName ? packageByName.get(packageName) : null;
|
|
1436
|
+
const isWorkspaceGraphDependency = targetPackage && (targetPackage.name === project.owner.name || workspaceDependencyNames.has(targetPackage.name));
|
|
1437
|
+
if (packageName && workspaceDependencyNames.has(packageName) && !targetPackage) {
|
|
1438
|
+
problems.push([
|
|
1439
|
+
"Workspace dependency was not discovered by pnpm:",
|
|
1440
|
+
` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
|
|
1441
|
+
` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
1442
|
+
` imported specifier: ${importRecord.specifier}`,
|
|
1443
|
+
` package: ${packageName}`,
|
|
1444
|
+
" reason: package.json declares this dependency with the workspace: protocol, but limina init could not find a matching workspace package."
|
|
1445
|
+
].join("\n"));
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
if (targetPackage && !isWorkspaceGraphDependency) continue;
|
|
1449
|
+
if (!resolvedFilePath) {
|
|
1450
|
+
if (targetPackage && isWorkspaceGraphDependency) problems.push([
|
|
1451
|
+
"Unable to resolve workspace import with TypeScript:",
|
|
1452
|
+
` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
|
|
1453
|
+
` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
1454
|
+
` imported specifier: ${importRecord.specifier}`,
|
|
1455
|
+
` package: ${targetPackage.name}`,
|
|
1456
|
+
" reason: workspace:* imports must resolve with the project TypeScript compilerOptions before limina init can generate project references."
|
|
1457
|
+
].join("\n"));
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
if (isRelativeSpecifier$1(importRecord.specifier)) {
|
|
1461
|
+
const sourcePackage = findNearestWorkspacePackage(importRecord.filePath, options.workspacePackages);
|
|
1462
|
+
const resolvedPackage = findNearestWorkspacePackage(resolvedFilePath, options.workspacePackages);
|
|
1463
|
+
if (sourcePackage && resolvedPackage && sourcePackage.name !== resolvedPackage.name) continue;
|
|
1464
|
+
}
|
|
1465
|
+
const targetProject = findOwningProjectForFile(resolvedFilePath, options.projects);
|
|
1466
|
+
if (!targetProject) {
|
|
1467
|
+
if (targetPackage && isWorkspaceGraphDependency) problems.push([
|
|
1468
|
+
"Unable to map workspace import to a generated declaration leaf:",
|
|
1469
|
+
` importing project: ${formatConfigPath(options.config.rootDir, project.dtsConfigPath)}`,
|
|
1470
|
+
` file: ${formatConfigPath(options.config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
1471
|
+
` imported specifier: ${importRecord.specifier}`,
|
|
1472
|
+
` resolved file: ${formatConfigPath(options.config.rootDir, resolvedFilePath)}`,
|
|
1473
|
+
" reason: TypeScript resolved this workspace import, but the resolved module is not covered by any ordinary tsconfig*.json leaf."
|
|
1474
|
+
].join("\n"));
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
if (targetProject.dtsConfigPath !== project.dtsConfigPath) referencePaths.add(formatReferencePath(project.dtsConfigPath, targetProject.dtsConfigPath));
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
project.references = [...referencePaths].sort();
|
|
1481
|
+
}
|
|
1482
|
+
return problems;
|
|
1483
|
+
}
|
|
1484
|
+
function collectProjectReferencesForOwner(options) {
|
|
1485
|
+
return options.projects.filter((project) => project.owner?.directory === options.owner?.directory).map((project) => formatReferencePath(options.targetConfigPath, project.dtsConfigPath)).sort();
|
|
1486
|
+
}
|
|
1487
|
+
function collectRootBuildProjectReferences(options) {
|
|
1488
|
+
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();
|
|
1489
|
+
}
|
|
1490
|
+
async function writeGeneratedTsconfigs(options) {
|
|
1491
|
+
for (const project of options.projects) await writeTextFile(project.dtsConfigPath, stringifyJson(createDtsConfig(project)), options.writtenFiles);
|
|
1492
|
+
const nonRootWorkspacePackages = options.workspacePackages.filter((workspacePackage) => workspacePackage.directory !== options.rootDir);
|
|
1493
|
+
const workspaceBuildConfigPaths = [];
|
|
1494
|
+
for (const workspacePackage of nonRootWorkspacePackages) {
|
|
1495
|
+
const buildConfigPath = path.join(workspacePackage.directory, "tsconfig.build.json");
|
|
1496
|
+
if (await writeBuildAggregatorFile({
|
|
1497
|
+
configPath: buildConfigPath,
|
|
1498
|
+
references: collectProjectReferencesForOwner({
|
|
1499
|
+
owner: workspacePackage,
|
|
1500
|
+
projects: options.projects,
|
|
1501
|
+
targetConfigPath: buildConfigPath
|
|
1502
|
+
}),
|
|
1503
|
+
writtenFiles: options.writtenFiles
|
|
1504
|
+
})) workspaceBuildConfigPaths.push(buildConfigPath);
|
|
1505
|
+
}
|
|
1506
|
+
const rootBuildConfigPath = path.join(options.rootDir, "tsconfig.build.json");
|
|
1507
|
+
await writeBuildAggregatorFile({
|
|
1508
|
+
configPath: rootBuildConfigPath,
|
|
1509
|
+
references: [...collectRootBuildProjectReferences({
|
|
1510
|
+
projects: options.projects,
|
|
1511
|
+
rootDir: options.rootDir,
|
|
1512
|
+
targetConfigPath: rootBuildConfigPath
|
|
1513
|
+
}), ...workspaceBuildConfigPaths.map((buildConfigPath) => formatReferencePath(rootBuildConfigPath, buildConfigPath))].sort(),
|
|
1514
|
+
writtenFiles: options.writtenFiles
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
async function writeLiminaConfig(options) {
|
|
1518
|
+
const configPath = path.join(options.rootDir, liminaConfigFileName);
|
|
1519
|
+
if (existsSync(configPath)) {
|
|
1520
|
+
if (!await confirmAction(options.prompt, `${liminaConfigFileName} already exists. Overwrite it?`)) {
|
|
1521
|
+
options.skippedFiles.push(configPath);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
await writeTextFile(configPath, createLiminaConfigContent(), options.writtenFiles);
|
|
1526
|
+
}
|
|
1527
|
+
async function runInitInternal(options) {
|
|
1528
|
+
const cwd = normalizeAbsolutePath(options.cwd ?? process.cwd());
|
|
1529
|
+
const rootDir = findPnpmWorkspaceRoot(cwd);
|
|
1530
|
+
if (!rootDir) throw new Error(`Unable to run limina init from ${cwd}: no pnpm-workspace.yaml was found in this directory or its parents.`);
|
|
1531
|
+
const rootPackageJsonPath = path.join(rootDir, "package.json");
|
|
1532
|
+
const rootPackageName = existsSync(rootPackageJsonPath) ? readJsonFile(rootPackageJsonPath).name : void 0;
|
|
1533
|
+
if (!await confirmAction(options, `Use pnpm workspace ${rootPackageName ? `"${rootPackageName}" ` : ""}at ${rootDir}?`)) throw new Error("limina init canceled.");
|
|
1534
|
+
const reservedConflicts = await collectReservedConfigConflicts(rootDir);
|
|
1535
|
+
if (reservedConflicts.length > 0) throw new Error([
|
|
1536
|
+
"Unable to run limina init because reserved Limina tsconfig names already exist:",
|
|
1537
|
+
...reservedConflicts.map((configPath) => ` - ${configPath}`),
|
|
1538
|
+
"reason: tsconfig*.build.json and tsconfig*.dts.json are Limina init output names; rename existing files before running init."
|
|
1539
|
+
].join("\n"));
|
|
1540
|
+
const config = createInitConfig(rootDir);
|
|
1541
|
+
const workspacePackages = (await collectWorkspacePackages(config)).filter((workspacePackage) => workspacePackage.directory !== rootDir);
|
|
1542
|
+
const projectAnalysis = analyzeTypecheckProjects({
|
|
1543
|
+
config,
|
|
1544
|
+
configPaths: await collectOrdinaryTsconfigPaths(rootDir),
|
|
1545
|
+
workspacePackages
|
|
1546
|
+
});
|
|
1547
|
+
const problems = [...projectAnalysis.problems];
|
|
1548
|
+
if (problems.length === 0) problems.push(...inferProjectReferences({
|
|
1549
|
+
config,
|
|
1550
|
+
projects: projectAnalysis.projects,
|
|
1551
|
+
workspacePackages
|
|
1552
|
+
}));
|
|
1553
|
+
if (problems.length > 0) throw new Error(problems.join("\n\n"));
|
|
1554
|
+
const metadata = readLiminaPackageMetadata();
|
|
1555
|
+
const writtenFiles = [];
|
|
1556
|
+
const skippedFiles = [];
|
|
1557
|
+
await writeGeneratedTsconfigs({
|
|
1558
|
+
projects: projectAnalysis.projects,
|
|
1559
|
+
rootDir,
|
|
1560
|
+
workspacePackages,
|
|
1561
|
+
writtenFiles
|
|
1562
|
+
});
|
|
1563
|
+
await writeLiminaConfig({
|
|
1564
|
+
prompt: options,
|
|
1565
|
+
rootDir,
|
|
1566
|
+
skippedFiles,
|
|
1567
|
+
writtenFiles
|
|
1568
|
+
});
|
|
1569
|
+
return {
|
|
1570
|
+
checkCommand: "pnpm limina:check",
|
|
1571
|
+
installRequired: await updateRootPackageJson({
|
|
1572
|
+
metadata,
|
|
1573
|
+
prompt: options,
|
|
1574
|
+
rootDir,
|
|
1575
|
+
skippedFiles,
|
|
1576
|
+
writtenFiles
|
|
1577
|
+
}),
|
|
1578
|
+
rootDir,
|
|
1579
|
+
skippedFiles,
|
|
1580
|
+
workspacePackageCount: workspacePackages.length,
|
|
1581
|
+
writtenFiles
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
async function runInit(options = {}) {
|
|
1585
|
+
if (options.clearScreen ?? true) clearCliScreen();
|
|
1586
|
+
const elapsed = createElapsedTimer();
|
|
1587
|
+
const task = options.flow?.start("init workspace", { depth: options.flowDepth ?? 0 });
|
|
1588
|
+
InitLogger.info("init started");
|
|
1589
|
+
try {
|
|
1590
|
+
const result = await runInitInternal(options);
|
|
1591
|
+
InitLogger.success(`init generated ${result.writtenFiles.length} files for ${result.workspacePackageCount} workspace packages.`, elapsed());
|
|
1592
|
+
if (result.installRequired) InitLogger.info("limina was added to devDependencies; run pnpm i before checking.");
|
|
1593
|
+
InitLogger.info(`next: ${result.installRequired ? "pnpm i && " : ""}${result.checkCommand}`);
|
|
1594
|
+
task?.pass();
|
|
1595
|
+
return result;
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
InitLogger.error(`init failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
1598
|
+
task?.fail("init failed", { error });
|
|
1599
|
+
throw error;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1135
1603
|
//#endregion
|
|
1136
1604
|
//#region src/commands/source.ts
|
|
1137
1605
|
function findOwnerForFile(filePath, owners) {
|
|
@@ -1140,11 +1608,14 @@ function findOwnerForFile(filePath, owners) {
|
|
|
1140
1608
|
function isUrlOrDataOrFileSpecifier(specifier) {
|
|
1141
1609
|
return specifier.startsWith("data:") || specifier.startsWith("file:") || specifier.startsWith("http:") || specifier.startsWith("https:");
|
|
1142
1610
|
}
|
|
1611
|
+
function isVirtualModuleSpecifier(specifier) {
|
|
1612
|
+
return specifier.startsWith("virtual:");
|
|
1613
|
+
}
|
|
1143
1614
|
function isPackageImportSpecifier(specifier) {
|
|
1144
1615
|
return specifier.startsWith("#");
|
|
1145
1616
|
}
|
|
1146
1617
|
function isBarePackageSpecifier(specifier) {
|
|
1147
|
-
return !isRelativeSpecifier(specifier) && !isPackageImportSpecifier(specifier) && !isUrlOrDataOrFileSpecifier(specifier) && !path.isAbsolute(specifier);
|
|
1618
|
+
return !isRelativeSpecifier$1(specifier) && !isPackageImportSpecifier(specifier) && !isUrlOrDataOrFileSpecifier(specifier) && !isVirtualModuleSpecifier(specifier) && !path.isAbsolute(specifier);
|
|
1148
1619
|
}
|
|
1149
1620
|
function isDependencyAuthorized(manifest, packageName) {
|
|
1150
1621
|
return Boolean(manifest.dependencies?.[packageName] || manifest.devDependencies?.[packageName]);
|
|
@@ -1218,17 +1689,6 @@ function addPackageImportAuthorizationProblem(options) {
|
|
|
1218
1689
|
" reason: source imports must be authorized by the nearest package.json dependencies or devDependencies."
|
|
1219
1690
|
].join("\n"));
|
|
1220
1691
|
}
|
|
1221
|
-
function addNodeBuiltinDenyProblem(options) {
|
|
1222
|
-
options.problems.push([
|
|
1223
|
-
"Denied Node builtin import:",
|
|
1224
|
-
` rule: ${options.project.label}`,
|
|
1225
|
-
` importing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
|
|
1226
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
1227
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
1228
|
-
` denied builtin: ${options.ruleName}`,
|
|
1229
|
-
` reason: ${options.reason}`
|
|
1230
|
-
].join("\n"));
|
|
1231
|
-
}
|
|
1232
1692
|
function addPackageImportProblem(options) {
|
|
1233
1693
|
if (!packageImportsMatch(options.owner.manifest.imports, options.importRecord.specifier)) {
|
|
1234
1694
|
options.problems.push([
|
|
@@ -1263,7 +1723,7 @@ function createSourceProjectEntries(config, projects) {
|
|
|
1263
1723
|
return projects.filter((project) => isDtsProjectConfig(project.configPath)).map((project) => {
|
|
1264
1724
|
const typecheckConfigPath = getTypecheckConfigPath(project.configPath);
|
|
1265
1725
|
const fileNames = new Set(project.fileNames);
|
|
1266
|
-
if (existsSync(typecheckConfigPath)) for (const fileName of parseProject(config, typecheckConfigPath).fileNames) fileNames.add(fileName);
|
|
1726
|
+
if (existsSync(typecheckConfigPath)) for (const fileName of parseProject(config, typecheckConfigPath, project.extensions).fileNames) fileNames.add(fileName);
|
|
1267
1727
|
return {
|
|
1268
1728
|
fileNames: [...fileNames].sort(),
|
|
1269
1729
|
project
|
|
@@ -1271,24 +1731,12 @@ function createSourceProjectEntries(config, projects) {
|
|
|
1271
1731
|
});
|
|
1272
1732
|
}
|
|
1273
1733
|
async function runSourceCheckInternal(config, options = {}) {
|
|
1274
|
-
const graphRoute =
|
|
1275
|
-
const
|
|
1276
|
-
const projects = projectPaths.map((projectPath) => parseProject(config, projectPath));
|
|
1734
|
+
const graphRoute = collectSourceGraphProjectExtensions(config);
|
|
1735
|
+
const projects = [...graphRoute.projectExtensionsByPath.keys()].sort().map((projectPath) => parseProject(config, projectPath, graphRoute.projectExtensionsByPath.get(projectPath)));
|
|
1277
1736
|
const sourceProjectEntries = createSourceProjectEntries(config, projects);
|
|
1278
1737
|
const packages = await collectWorkspacePackages(config);
|
|
1279
1738
|
const packageOwners = await collectPackageOwners(config);
|
|
1280
1739
|
const problems = [...graphRoute.problems];
|
|
1281
|
-
const graphRules = normalizeGraphRules({
|
|
1282
|
-
config,
|
|
1283
|
-
include: {
|
|
1284
|
-
nodeBuiltins: true,
|
|
1285
|
-
refs: false,
|
|
1286
|
-
workspaceDeps: false
|
|
1287
|
-
},
|
|
1288
|
-
packages,
|
|
1289
|
-
problems,
|
|
1290
|
-
projectPaths
|
|
1291
|
-
});
|
|
1292
1740
|
for (const project of projects) {
|
|
1293
1741
|
if (project.labelProblem) problems.push(project.labelProblem);
|
|
1294
1742
|
if (!isDtsProjectConfig(project.configPath)) continue;
|
|
@@ -1304,7 +1752,7 @@ async function runSourceCheckInternal(config, options = {}) {
|
|
|
1304
1752
|
if (existsSync(typecheckConfigPath)) addProjectOwnerProblems({
|
|
1305
1753
|
config,
|
|
1306
1754
|
configPath: typecheckConfigPath,
|
|
1307
|
-
fileNames: parseProject(config, typecheckConfigPath).fileNames,
|
|
1755
|
+
fileNames: parseProject(config, typecheckConfigPath, project.extensions).fileNames,
|
|
1308
1756
|
owners: packageOwners,
|
|
1309
1757
|
problems,
|
|
1310
1758
|
role: "typecheck companion"
|
|
@@ -1313,9 +1761,9 @@ async function runSourceCheckInternal(config, options = {}) {
|
|
|
1313
1761
|
for (const { fileNames, project } of sourceProjectEntries) for (const filePath of fileNames) {
|
|
1314
1762
|
const owner = findOwnerForFile(filePath, packageOwners);
|
|
1315
1763
|
if (!owner) continue;
|
|
1316
|
-
for (const importRecord of collectImportsFromFile(filePath)) {
|
|
1317
|
-
const resolvedFilePath = resolveInternalImport(importRecord.specifier, filePath, project.options);
|
|
1318
|
-
if (isRelativeSpecifier(importRecord.specifier)) {
|
|
1764
|
+
for (const importRecord of collectImportsFromFile(filePath, config.rootDir)) {
|
|
1765
|
+
const resolvedFilePath = resolveInternalImport(importRecord.specifier, filePath, project.options, project.extensions);
|
|
1766
|
+
if (isRelativeSpecifier$1(importRecord.specifier)) {
|
|
1319
1767
|
if (!resolvedFilePath) continue;
|
|
1320
1768
|
const targetOwner = findOwnerForFile(resolvedFilePath, packageOwners);
|
|
1321
1769
|
if (targetOwner?.packageJsonPath !== owner.packageJsonPath) addRelativeImportOwnerProblem({
|
|
@@ -1338,20 +1786,9 @@ async function runSourceCheckInternal(config, options = {}) {
|
|
|
1338
1786
|
});
|
|
1339
1787
|
continue;
|
|
1340
1788
|
}
|
|
1341
|
-
if (isUrlOrDataOrFileSpecifier(importRecord.specifier)) continue;
|
|
1789
|
+
if (isUrlOrDataOrFileSpecifier(importRecord.specifier) || isVirtualModuleSpecifier(importRecord.specifier)) continue;
|
|
1342
1790
|
if (!isBarePackageSpecifier(importRecord.specifier)) continue;
|
|
1343
|
-
if (isNodeBuiltinSpecifier(importRecord.specifier))
|
|
1344
|
-
const deniedBuiltinRule = getDeniedNodeBuiltinRule(graphRules, project.label, importRecord.specifier);
|
|
1345
|
-
if (deniedBuiltinRule) addNodeBuiltinDenyProblem({
|
|
1346
|
-
config,
|
|
1347
|
-
importRecord,
|
|
1348
|
-
problems,
|
|
1349
|
-
project,
|
|
1350
|
-
reason: deniedBuiltinRule.reason,
|
|
1351
|
-
ruleName: deniedBuiltinRule.matchAll ? "node:*" : deniedBuiltinRule.name
|
|
1352
|
-
});
|
|
1353
|
-
continue;
|
|
1354
|
-
}
|
|
1791
|
+
if (isNodeBuiltinSpecifier(importRecord.specifier)) continue;
|
|
1355
1792
|
const packageName = getPackageRootSpecifier(importRecord.specifier);
|
|
1356
1793
|
if (owner.name === packageName) continue;
|
|
1357
1794
|
const workspacePackage = packages.find((candidate) => candidate.name === packageName) ?? null;
|
|
@@ -1398,14 +1835,9 @@ async function runSourceCheck(config, options = {}) {
|
|
|
1398
1835
|
|
|
1399
1836
|
//#endregion
|
|
1400
1837
|
//#region src/commands/typecheck.ts
|
|
1401
|
-
function normalizeConcurrency(value) {
|
|
1402
|
-
if (value === void 0) return Math.max(1, availableParallelism());
|
|
1403
|
-
if (!Number.isInteger(value) || value < 1) throw new Error("Typecheck concurrency must be a positive integer.");
|
|
1404
|
-
return value;
|
|
1405
|
-
}
|
|
1406
1838
|
function getExecutionCheckers(options) {
|
|
1407
1839
|
return options.checkers.filter((checker) => {
|
|
1408
|
-
return getCheckerAdapter(checker.preset)?.
|
|
1840
|
+
return getCheckerAdapter(checker.preset)?.execution === options.executionKind;
|
|
1409
1841
|
});
|
|
1410
1842
|
}
|
|
1411
1843
|
function collectCheckerPeerDependencyProblems(options) {
|
|
@@ -1427,49 +1859,14 @@ function createCheckerTarget(options) {
|
|
|
1427
1859
|
executionKind: options.executionKind
|
|
1428
1860
|
};
|
|
1429
1861
|
}
|
|
1430
|
-
function collectCompanionTypecheckTargets(options) {
|
|
1431
|
-
const entryConfigPath = resolveProjectConfigPath(options.projectRootDir, options.checker.entry);
|
|
1432
|
-
if (!existsSync(entryConfigPath)) return {
|
|
1433
|
-
entryConfigPath,
|
|
1434
|
-
problems: [[
|
|
1435
|
-
"Checker entry references a missing tsconfig:",
|
|
1436
|
-
` checker: ${options.checker.name}`,
|
|
1437
|
-
` config: ${toRelativePath(options.projectRootDir, entryConfigPath)}`
|
|
1438
|
-
].join("\n")],
|
|
1439
|
-
targetProjectPaths: []
|
|
1440
|
-
};
|
|
1441
|
-
const routeCollection = collectGraphProjectRouteFromRoot({
|
|
1442
|
-
rootConfigPath: entryConfigPath,
|
|
1443
|
-
rootDir: options.projectRootDir
|
|
1444
|
-
});
|
|
1445
|
-
const dtsConfigPaths = routeCollection.projectPaths.filter(isDtsConfigPath);
|
|
1446
|
-
const targetProjectPaths = [...new Set(dtsConfigPaths.map(getDtsCompanionConfigPath))].sort();
|
|
1447
|
-
const problems = [...routeCollection.problems];
|
|
1448
|
-
if (dtsConfigPaths.length === 0) problems.push([
|
|
1449
|
-
"Checker entry has no declaration leaf targets:",
|
|
1450
|
-
` checker: ${options.checker.name}`,
|
|
1451
|
-
` entry: ${toRelativePath(options.projectRootDir, entryConfigPath)}`,
|
|
1452
|
-
" reason: checker:typecheck derives targets from tsconfig*.dts.json leaves reachable from the checker entry."
|
|
1453
|
-
].join("\n"));
|
|
1454
|
-
for (const configPath of targetProjectPaths) {
|
|
1455
|
-
if (existsSync(configPath)) continue;
|
|
1456
|
-
problems.push([
|
|
1457
|
-
"DTS leaf companion config is missing:",
|
|
1458
|
-
` checker: ${options.checker.name}`,
|
|
1459
|
-
` expected: ${toRelativePath(options.projectRootDir, configPath)}`,
|
|
1460
|
-
" reason: checker:typecheck runs the strict local tsconfig companion for each reachable declaration leaf."
|
|
1461
|
-
].join("\n"));
|
|
1462
|
-
}
|
|
1463
|
-
return {
|
|
1464
|
-
entryConfigPath,
|
|
1465
|
-
problems,
|
|
1466
|
-
targetProjectPaths
|
|
1467
|
-
};
|
|
1468
|
-
}
|
|
1469
1862
|
function createDefaultRunner() {
|
|
1470
1863
|
return async (target) => await new Promise((resolve) => {
|
|
1471
1864
|
const child = spawn(target.command, target.args, {
|
|
1472
1865
|
cwd: target.cwd,
|
|
1866
|
+
env: {
|
|
1867
|
+
...process.env,
|
|
1868
|
+
PATH: [path.join(target.cwd, "node_modules/.bin"), process.env.PATH].filter(Boolean).join(path.delimiter)
|
|
1869
|
+
},
|
|
1473
1870
|
shell: process.platform === "win32",
|
|
1474
1871
|
stdio: "inherit"
|
|
1475
1872
|
});
|
|
@@ -1514,138 +1911,106 @@ async function runWithConcurrency(targets, concurrency, runner, options = {}) {
|
|
|
1514
1911
|
}));
|
|
1515
1912
|
return results;
|
|
1516
1913
|
}
|
|
1517
|
-
async function
|
|
1914
|
+
async function runCheckerBuildInternal(options) {
|
|
1518
1915
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1519
1916
|
const projectRootDir = normalizeAbsolutePath(options.config.rootDir);
|
|
1520
1917
|
const allCheckers = getActiveCheckers(options.config);
|
|
1521
1918
|
const checkers = getExecutionCheckers({
|
|
1522
1919
|
checkers: allCheckers,
|
|
1523
|
-
executionKind: "
|
|
1920
|
+
executionKind: "build"
|
|
1524
1921
|
});
|
|
1525
1922
|
const flowDepth = options.flowDepth ?? 0;
|
|
1526
|
-
const
|
|
1923
|
+
const rootConfigPaths = [];
|
|
1527
1924
|
const problems = collectCheckerPeerDependencyProblems({
|
|
1528
1925
|
checkers: allCheckers,
|
|
1529
1926
|
projectRootDir,
|
|
1530
1927
|
resolvePackage: options.checkerPackageResolver
|
|
1531
1928
|
});
|
|
1532
|
-
const rootConfigPaths = [];
|
|
1533
|
-
const targetProjectPaths = [];
|
|
1534
|
-
const targets = [];
|
|
1535
1929
|
if (problems.length > 0) {
|
|
1536
1930
|
options.flow?.fail("checker dependency preflight failed", { depth: flowDepth + 1 });
|
|
1537
1931
|
TypecheckLogger.error(problems.join("\n\n"));
|
|
1538
1932
|
return {
|
|
1539
1933
|
passed: false,
|
|
1540
1934
|
projectRootDir,
|
|
1541
|
-
|
|
1542
|
-
rootConfigPaths,
|
|
1543
|
-
targetProjectPaths
|
|
1935
|
+
rootConfigPaths
|
|
1544
1936
|
};
|
|
1545
1937
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
projectRootDir
|
|
1551
|
-
});
|
|
1552
|
-
problems.push(...targetCollection.problems);
|
|
1553
|
-
rootConfigPaths.push(targetCollection.entryConfigPath);
|
|
1554
|
-
targetProjectPaths.push(...targetCollection.targetProjectPaths);
|
|
1555
|
-
targets.push(...targetCollection.targetProjectPaths.map((configPath) => createCheckerTarget({
|
|
1938
|
+
const targets = checkers.flatMap((checker) => {
|
|
1939
|
+
const configPath = resolveProjectConfigPath(projectRootDir, checker.entry);
|
|
1940
|
+
rootConfigPaths.push(configPath);
|
|
1941
|
+
return [createCheckerTarget({
|
|
1556
1942
|
checker,
|
|
1557
1943
|
commandOverride: options.tscCommand,
|
|
1558
1944
|
configPath,
|
|
1559
|
-
executionKind: "
|
|
1945
|
+
executionKind: "build",
|
|
1560
1946
|
projectRootDir
|
|
1561
|
-
})
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
options.flow?.fail("typecheck target discovery failed", { depth: flowDepth + 1 });
|
|
1565
|
-
TypecheckLogger.error(problems.join("\n\n"));
|
|
1566
|
-
return {
|
|
1567
|
-
passed: false,
|
|
1568
|
-
projectRootDir,
|
|
1569
|
-
results: [],
|
|
1570
|
-
rootConfigPaths,
|
|
1571
|
-
targetProjectPaths
|
|
1572
|
-
};
|
|
1573
|
-
}
|
|
1574
|
-
options.flow?.info(`found ${targets.length} typecheck target config(s) across ${checkers.length} checker(s); concurrency ${concurrency}`, { depth: flowDepth + 1 });
|
|
1947
|
+
})];
|
|
1948
|
+
});
|
|
1949
|
+
options.flow?.info(`found ${targets.length} checker build entry(s)`, { depth: flowDepth + 1 });
|
|
1575
1950
|
TypecheckLogger.info([
|
|
1576
|
-
`Running
|
|
1951
|
+
`Running build checks for ${targets.length} checker entry(s).`,
|
|
1577
1952
|
`CWD: ${toRelativePath(cwd, projectRootDir)}`,
|
|
1578
1953
|
`Entries: ${rootConfigPaths.map((configPath) => toRelativePath(projectRootDir, configPath)).join(", ")}`
|
|
1579
1954
|
].join("\n"));
|
|
1580
|
-
const
|
|
1581
|
-
const
|
|
1955
|
+
const targetTasks = /* @__PURE__ */ new Map();
|
|
1956
|
+
const failedResults = (await runWithConcurrency(targets, 1, options.runner ?? createDefaultRunner(), {
|
|
1582
1957
|
onTargetResult: (target, result) => {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
if (
|
|
1586
|
-
const resultOptions = {
|
|
1587
|
-
depth: flowDepth + 1,
|
|
1588
|
-
elapsedTimeMs: performance.now() - flowState.startedAt
|
|
1589
|
-
};
|
|
1590
|
-
if (result.status === 0) options.flow.pass(flowState.label, resultOptions);
|
|
1958
|
+
const task = targetTasks.get(target.configPath);
|
|
1959
|
+
if (!task) return;
|
|
1960
|
+
if (result.status === 0) task.pass();
|
|
1591
1961
|
else {
|
|
1592
1962
|
const suffix = result.error ? formatErrorMessage$1(result.error) : `exited with code ${result.status}`;
|
|
1593
|
-
|
|
1594
|
-
...resultOptions,
|
|
1595
|
-
error: suffix
|
|
1596
|
-
});
|
|
1963
|
+
task.fail(void 0, { error: suffix });
|
|
1597
1964
|
}
|
|
1598
1965
|
},
|
|
1599
1966
|
onTargetStart: (target) => {
|
|
1600
1967
|
if (!options.flow) return;
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
});
|
|
1968
|
+
targetTasks.set(target.configPath, options.flow.start(target.label ?? `checker build: ${toRelativePath(projectRootDir, target.configPath)}`, {
|
|
1969
|
+
collapseOnSuccess: false,
|
|
1970
|
+
depth: flowDepth + 1
|
|
1971
|
+
}));
|
|
1605
1972
|
}
|
|
1606
|
-
});
|
|
1607
|
-
const
|
|
1608
|
-
if (
|
|
1973
|
+
})).filter((result) => result.status !== 0);
|
|
1974
|
+
const passed = failedResults.length === 0;
|
|
1975
|
+
if (!passed) TypecheckLogger.error(["build checks failed:", ...failedResults.map((result) => {
|
|
1609
1976
|
const suffix = result.error ? `: ${formatErrorMessage$1(result.error)}` : ` exited with code ${result.status}`;
|
|
1610
1977
|
return ` ${toRelativePath(projectRootDir, result.configPath)}${suffix}`;
|
|
1611
1978
|
})].join("\n"));
|
|
1612
|
-
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length}
|
|
1979
|
+
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} checker build entry(s).`);
|
|
1613
1980
|
return {
|
|
1614
|
-
passed
|
|
1981
|
+
passed,
|
|
1615
1982
|
projectRootDir,
|
|
1616
|
-
|
|
1617
|
-
rootConfigPaths,
|
|
1618
|
-
targetProjectPaths
|
|
1983
|
+
rootConfigPaths
|
|
1619
1984
|
};
|
|
1620
1985
|
}
|
|
1621
|
-
async function
|
|
1986
|
+
async function runCheckerBuild(options) {
|
|
1622
1987
|
if (options.clearScreen ?? true) clearCliScreen();
|
|
1623
1988
|
const elapsed = createElapsedTimer();
|
|
1624
|
-
const task = options.flow?.start("checker
|
|
1625
|
-
TypecheckLogger.info("checker
|
|
1989
|
+
const task = options.flow?.start("checker build", { depth: options.flowDepth ?? 0 });
|
|
1990
|
+
TypecheckLogger.info("checker build started");
|
|
1626
1991
|
try {
|
|
1627
|
-
const result = await
|
|
1992
|
+
const result = await runCheckerBuildInternal(options);
|
|
1628
1993
|
if (result.passed) {
|
|
1629
|
-
if (!options.flow?.interactive) TypecheckLogger.success("checker
|
|
1994
|
+
if (!options.flow?.interactive) TypecheckLogger.success("checker build finished", elapsed());
|
|
1630
1995
|
task?.pass();
|
|
1631
1996
|
} else {
|
|
1632
|
-
TypecheckLogger.error("checker
|
|
1633
|
-
task?.fail("checker
|
|
1997
|
+
TypecheckLogger.error("checker build finished with failures", elapsed());
|
|
1998
|
+
task?.fail("checker build finished with failures");
|
|
1634
1999
|
}
|
|
1635
2000
|
return result;
|
|
1636
2001
|
} catch (error) {
|
|
1637
|
-
TypecheckLogger.error(`checker
|
|
1638
|
-
task?.fail("checker
|
|
2002
|
+
TypecheckLogger.error(`checker build failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
2003
|
+
task?.fail("checker build failed", { error });
|
|
1639
2004
|
throw error;
|
|
1640
2005
|
}
|
|
1641
2006
|
}
|
|
1642
|
-
async function
|
|
2007
|
+
async function runCheckerTypecheckInternal(options) {
|
|
1643
2008
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1644
2009
|
const projectRootDir = normalizeAbsolutePath(options.config.rootDir);
|
|
1645
2010
|
const allCheckers = getActiveCheckers(options.config);
|
|
1646
2011
|
const checkers = getExecutionCheckers({
|
|
1647
2012
|
checkers: allCheckers,
|
|
1648
|
-
executionKind: "
|
|
2013
|
+
executionKind: "typecheck"
|
|
1649
2014
|
});
|
|
1650
2015
|
const flowDepth = options.flowDepth ?? 0;
|
|
1651
2016
|
const rootConfigPaths = [];
|
|
@@ -1663,20 +2028,29 @@ async function runCheckerBuildInternal(options) {
|
|
|
1663
2028
|
rootConfigPaths
|
|
1664
2029
|
};
|
|
1665
2030
|
}
|
|
1666
|
-
const targets = checkers.
|
|
2031
|
+
const targets = checkers.map((checker) => {
|
|
1667
2032
|
const configPath = resolveProjectConfigPath(projectRootDir, checker.entry);
|
|
1668
2033
|
rootConfigPaths.push(configPath);
|
|
1669
|
-
return
|
|
2034
|
+
return createCheckerTarget({
|
|
1670
2035
|
checker,
|
|
1671
2036
|
commandOverride: options.tscCommand,
|
|
1672
2037
|
configPath,
|
|
1673
|
-
executionKind: "
|
|
2038
|
+
executionKind: "typecheck",
|
|
1674
2039
|
projectRootDir
|
|
1675
|
-
})
|
|
2040
|
+
});
|
|
1676
2041
|
});
|
|
1677
|
-
|
|
2042
|
+
if (targets.length === 0) {
|
|
2043
|
+
options.flow?.info("no source-only checker entries configured", { depth: flowDepth + 1 });
|
|
2044
|
+
if (!options.flow?.interactive) TypecheckLogger.success("No source-only checker entries configured.");
|
|
2045
|
+
return {
|
|
2046
|
+
passed: true,
|
|
2047
|
+
projectRootDir,
|
|
2048
|
+
rootConfigPaths
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
options.flow?.info(`found ${targets.length} checker typecheck entry(s)`, { depth: flowDepth + 1 });
|
|
1678
2052
|
TypecheckLogger.info([
|
|
1679
|
-
`Running
|
|
2053
|
+
`Running typecheck for ${targets.length} checker entry(s).`,
|
|
1680
2054
|
`CWD: ${toRelativePath(cwd, projectRootDir)}`,
|
|
1681
2055
|
`Entries: ${rootConfigPaths.map((configPath) => toRelativePath(projectRootDir, configPath)).join(", ")}`
|
|
1682
2056
|
].join("\n"));
|
|
@@ -1693,42 +2067,42 @@ async function runCheckerBuildInternal(options) {
|
|
|
1693
2067
|
},
|
|
1694
2068
|
onTargetStart: (target) => {
|
|
1695
2069
|
if (!options.flow) return;
|
|
1696
|
-
targetTasks.set(target.configPath, options.flow.start(target.label ?? `checker
|
|
2070
|
+
targetTasks.set(target.configPath, options.flow.start(target.label ?? `checker typecheck: ${toRelativePath(projectRootDir, target.configPath)}`, {
|
|
1697
2071
|
collapseOnSuccess: false,
|
|
1698
2072
|
depth: flowDepth + 1
|
|
1699
2073
|
}));
|
|
1700
2074
|
}
|
|
1701
2075
|
})).filter((result) => result.status !== 0);
|
|
1702
2076
|
const passed = failedResults.length === 0;
|
|
1703
|
-
if (!passed) TypecheckLogger.error(["
|
|
2077
|
+
if (!passed) TypecheckLogger.error(["typecheck checks failed:", ...failedResults.map((result) => {
|
|
1704
2078
|
const suffix = result.error ? `: ${formatErrorMessage$1(result.error)}` : ` exited with code ${result.status}`;
|
|
1705
2079
|
return ` ${toRelativePath(projectRootDir, result.configPath)}${suffix}`;
|
|
1706
2080
|
})].join("\n"));
|
|
1707
|
-
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} checker
|
|
2081
|
+
else if (!options.flow?.interactive) TypecheckLogger.success(`Checked ${targets.length} checker typecheck entry(s).`);
|
|
1708
2082
|
return {
|
|
1709
2083
|
passed,
|
|
1710
2084
|
projectRootDir,
|
|
1711
2085
|
rootConfigPaths
|
|
1712
2086
|
};
|
|
1713
2087
|
}
|
|
1714
|
-
async function
|
|
2088
|
+
async function runCheckerTypecheck(options) {
|
|
1715
2089
|
if (options.clearScreen ?? true) clearCliScreen();
|
|
1716
2090
|
const elapsed = createElapsedTimer();
|
|
1717
|
-
const task = options.flow?.start("checker
|
|
1718
|
-
TypecheckLogger.info("checker
|
|
2091
|
+
const task = options.flow?.start("checker typecheck", { depth: options.flowDepth ?? 0 });
|
|
2092
|
+
TypecheckLogger.info("checker typecheck started");
|
|
1719
2093
|
try {
|
|
1720
|
-
const result = await
|
|
2094
|
+
const result = await runCheckerTypecheckInternal(options);
|
|
1721
2095
|
if (result.passed) {
|
|
1722
|
-
if (!options.flow?.interactive) TypecheckLogger.success("checker
|
|
2096
|
+
if (!options.flow?.interactive) TypecheckLogger.success("checker typecheck finished", elapsed());
|
|
1723
2097
|
task?.pass();
|
|
1724
2098
|
} else {
|
|
1725
|
-
TypecheckLogger.error("checker
|
|
1726
|
-
task?.fail("checker
|
|
2099
|
+
TypecheckLogger.error("checker typecheck finished with failures", elapsed());
|
|
2100
|
+
task?.fail("checker typecheck finished with failures");
|
|
1727
2101
|
}
|
|
1728
2102
|
return result;
|
|
1729
2103
|
} catch (error) {
|
|
1730
|
-
TypecheckLogger.error(`checker
|
|
1731
|
-
task?.fail("checker
|
|
2104
|
+
TypecheckLogger.error(`checker typecheck failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
2105
|
+
task?.fail("checker typecheck failed", { error });
|
|
1732
2106
|
throw error;
|
|
1733
2107
|
}
|
|
1734
2108
|
}
|
|
@@ -2023,4 +2397,4 @@ function createLiminaFlowReporter(options = {}) {
|
|
|
2023
2397
|
}
|
|
2024
2398
|
|
|
2025
2399
|
//#endregion
|
|
2026
|
-
export {
|
|
2400
|
+
export { shouldResolveThroughGraph as A, collectGraphProjectRoutes as B, formatArtifactDependencyPolicy as C, isRelativeSpecifier$1 as D, isDtsProjectConfig as E, getPublishDependencySections as F, formatReferences as G, createExtensionPattern as H, isWorkspaceDependencySpecifier as I, isOrdinaryTypecheckConfigPath as J, getDtsCompanionConfigPath as K, collectCheckerEntryProjectRoutes as L, collectWorkspacePackages as M, findPackageForSpecifier as N, parseProject as O, getPackageRootSpecifier as P, resolveReferencePath as Q, collectGraphProjectRoute as R, findTargetProject as S, inferPackageProject as T, createExtraFileExtensions as U, collectSourceGraphProjectExtensions as V, createFormatHost as W, readJsonConfig as X, parseProjectFileNamesForExtensions as Y, resolveProjectConfigPath as Z, normalizeGraphRules as _, runSourceCheck as a, findImporterForFile as b, GraphLogger as c, ProofLogger as d, clearCliScreen as f, getDeniedRefRule as g, getDeniedDepRuleForSpecifier as h, runCheckerTypecheck as i, collectImporters as j, resolveInternalImport as k, PackageLogger as l, getDeniedDepRuleForPackage as m, createLiminaFlowReporter as n, runInit as o, formatErrorMessage$1 as p, getRawReferencePaths as q, runCheckerBuild as r, CliLogger as s, LiminaFlowReporter as t, PathsLogger as u, collectImportsFromFile as v, getTypecheckConfigPath as w, findPackageForFile as x, createFileOwnerLookup as y, collectGraphProjectRouteFromRoot as z };
|