limina 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +11 -0
- package/README.md +16 -372
- package/README.zh-CN.md +30 -0
- package/bin/limina.js +32 -21
- package/chunks/dep-CBKvJc4Y.js +846 -0
- package/chunks/dep-DTGmTTL7.js +4968 -0
- package/cli.js +1144 -802
- package/config.d.ts +202 -12
- package/config.js +2 -2
- package/index.d.ts +22 -2
- package/index.js +3 -3
- package/package.json +18 -3
- package/schemas/tsconfig-schema.json +42 -0
- package/chunks/dep-DzYrmtQJ.js +0 -360
- package/chunks/dep-ZRIm_-Zk.js +0 -2401
package/cli.js
CHANGED
|
@@ -1,423 +1,278 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./chunks/dep-lkQg1P9Q.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { a as loadConfig, b as toPosixPath, d as parseCheckerProjectConfigForContext, f as resolveCheckerProjectExtensions, g as normalizeAbsolutePathIdentity, h as normalizeAbsolutePath, i as isStrictConfig, l as getCheckerAdapter, m as isPathInsideDirectory, n as getActiveCheckerExtensions, r as getActiveCheckers, u as normalizeExtensions, x as toRelativePath, y as toAbsolutePath } from "./chunks/dep-CBKvJc4Y.js";
|
|
4
|
+
import { A as isLocalPackageDependencySpecifier, B as isDtsConfigPath, C as resolveInternalImport, D as getDependencySections, E as findPackageForSpecifier, F as collectSourceGraphProjectExtensions, G as resolveReferencePath, H as parseProjectFileNamesForExtensions, I as createExtensionPattern, L as createLiminaTsconfigSchemaPath, M as collectCheckerEntryProjectRoutes, N as collectGraphProjectRouteFromRoot, O as getPackageRootSpecifier, P as collectGraphProjectRoutes, R as getDtsCompanionConfigPath, S as createImportAnalysisContext, T as collectWorkspacePackages, U as readJsonConfig, V as isOrdinaryTypecheckConfigPath, W as resolveProjectConfigPath, _ as ReleaseLogger, a as runCheckerTypecheck, b as parseProject, c as runGraphCheck, d as collectExportEntries, f as CliLogger, g as ProofLogger, h as PathsLogger, i as runCheckerBuild, j as isWorkspaceDependencySpecifier, k as getPublishDependencySections, l as runGraphSync, m as PackageLogger, n as createLiminaFlowReporter, o as runSourceCheck, p as NxLogger, r as prepareVueTsgoCache, s as runInit, u as aliasMatchesSpecifier, v as clearCliScreen, w as collectImporters, x as collectImportsFromFile, y as formatErrorMessage$1, z as isBuildGraphConfigPath } from "./chunks/dep-DTGmTTL7.js";
|
|
5
5
|
import { builtinModules, createRequire } from "node:module";
|
|
6
6
|
import { cac } from "cac";
|
|
7
7
|
import { createElapsedTimer } from "logaria/helper";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
-
import path from "node:path";
|
|
10
|
-
import ts from "typescript";
|
|
11
|
-
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
12
|
-
import { glob } from "tinyglobby";
|
|
13
9
|
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import path from "pathe";
|
|
11
|
+
import { glob } from "tinyglobby";
|
|
12
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
14
13
|
import { checkPackage, createPackageFromTarballData } from "@arethetypeswrong/core";
|
|
15
14
|
import { pack, unpack } from "@publint/pack";
|
|
16
15
|
import { init, parse } from "es-module-lexer";
|
|
17
16
|
import { tmpdir } from "node:os";
|
|
18
17
|
import { publint } from "publint";
|
|
19
18
|
import { formatMessage } from "publint/utils";
|
|
19
|
+
import rawPicomatch from "picomatch";
|
|
20
20
|
|
|
21
|
-
//#region src/commands/
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
["declaration", true],
|
|
27
|
-
["emitDeclarationOnly", true]
|
|
28
|
-
];
|
|
29
|
-
const requiredDtsPathOptions = [
|
|
30
|
-
"rootDir",
|
|
31
|
-
"outDir",
|
|
32
|
-
"tsBuildInfoFile"
|
|
33
|
-
];
|
|
34
|
-
const comparableTypecheckOptions = [
|
|
35
|
-
"allowArbitraryExtensions",
|
|
36
|
-
"allowImportingTsExtensions",
|
|
37
|
-
"allowJs",
|
|
38
|
-
"allowSyntheticDefaultImports",
|
|
39
|
-
"checkJs",
|
|
40
|
-
"customConditions",
|
|
41
|
-
"esModuleInterop",
|
|
42
|
-
"exactOptionalPropertyTypes",
|
|
43
|
-
"forceConsistentCasingInFileNames",
|
|
44
|
-
"isolatedDeclarations",
|
|
45
|
-
"isolatedModules",
|
|
46
|
-
"jsx",
|
|
47
|
-
"jsxImportSource",
|
|
48
|
-
"lib",
|
|
49
|
-
"module",
|
|
50
|
-
"moduleDetection",
|
|
51
|
-
"moduleResolution",
|
|
52
|
-
"noFallthroughCasesInSwitch",
|
|
53
|
-
"noImplicitAny",
|
|
54
|
-
"noImplicitOverride",
|
|
55
|
-
"noImplicitReturns",
|
|
56
|
-
"noImplicitThis",
|
|
57
|
-
"noPropertyAccessFromIndexSignature",
|
|
58
|
-
"noUncheckedIndexedAccess",
|
|
59
|
-
"resolveJsonModule",
|
|
60
|
-
"skipLibCheck",
|
|
61
|
-
"strict",
|
|
62
|
-
"strictBindCallApply",
|
|
63
|
-
"strictFunctionTypes",
|
|
64
|
-
"strictNullChecks",
|
|
65
|
-
"strictPropertyInitialization",
|
|
66
|
-
"target",
|
|
67
|
-
"typeRoots",
|
|
68
|
-
"types",
|
|
69
|
-
"useDefineForClassFields",
|
|
70
|
-
"verbatimModuleSyntax"
|
|
71
|
-
];
|
|
72
|
-
function formatCompilerOptionValue(value) {
|
|
73
|
-
if (value === void 0) return "undefined";
|
|
74
|
-
return JSON.stringify(value);
|
|
21
|
+
//#region src/commands/nx.ts
|
|
22
|
+
const defaultArtifactDirectories = ["dist"];
|
|
23
|
+
const defaultNxTargets = ["build"];
|
|
24
|
+
function stringifyJson(value) {
|
|
25
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
75
26
|
}
|
|
76
|
-
function
|
|
77
|
-
return
|
|
27
|
+
function configuredArtifactDirectories(config) {
|
|
28
|
+
return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
|
|
78
29
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
for (const [optionName, expected] of requiredDtsCompilerOptions) {
|
|
82
|
-
const actual = project.options[optionName];
|
|
83
|
-
if (actual === expected) continue;
|
|
84
|
-
problems.push([
|
|
85
|
-
"Invalid declaration leaf compiler option:",
|
|
86
|
-
` project: ${toRelativePath(config.rootDir, project.configPath)}`,
|
|
87
|
-
` option: compilerOptions.${optionName}`,
|
|
88
|
-
` expected: ${formatCompilerOptionValue(expected)}`,
|
|
89
|
-
` actual: ${formatCompilerOptionValue(actual)}`,
|
|
90
|
-
" reason: tsconfig*.dts.json projects are consumed by tsc -b and must emit declarations through composite incremental builds."
|
|
91
|
-
].join("\n"));
|
|
92
|
-
}
|
|
93
|
-
for (const optionName of requiredDtsPathOptions) {
|
|
94
|
-
if (project.options[optionName]) continue;
|
|
95
|
-
problems.push([
|
|
96
|
-
"Missing declaration leaf output option:",
|
|
97
|
-
` project: ${toRelativePath(config.rootDir, project.configPath)}`,
|
|
98
|
-
` option: compilerOptions.${optionName}`,
|
|
99
|
-
" reason: declaration leaves need explicit root/output state so declaration output and tsbuildinfo files do not collide."
|
|
100
|
-
].join("\n"));
|
|
101
|
-
}
|
|
30
|
+
function isLinkDependencySpecifier$1(specifier) {
|
|
31
|
+
return specifier.startsWith("link:");
|
|
102
32
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
const typecheckConfigPath = getTypecheckConfigPath(dtsProject.configPath);
|
|
106
|
-
if (!existsSync(typecheckConfigPath)) {
|
|
107
|
-
problems.push([
|
|
108
|
-
"Missing typecheck companion config:",
|
|
109
|
-
` declaration leaf: ${toRelativePath(config.rootDir, dtsProject.configPath)}`,
|
|
110
|
-
` expected typecheck config: ${toRelativePath(config.rootDir, typecheckConfigPath)}`,
|
|
111
|
-
" reason: every tsconfig*.dts.json project should have a matching tsconfig*.json file with the same typechecking semantics."
|
|
112
|
-
].join("\n"));
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const typecheckProject = parseProject$1(config, typecheckConfigPath, dtsProject.extensions);
|
|
116
|
-
for (const optionName of comparableTypecheckOptions) {
|
|
117
|
-
const buildValue = dtsProject.options[optionName];
|
|
118
|
-
const typecheckValue = typecheckProject.options[optionName];
|
|
119
|
-
if (compilerOptionEquals(buildValue, typecheckValue)) continue;
|
|
120
|
-
problems.push([
|
|
121
|
-
"Typecheck option mismatch between declaration leaf and companion config:",
|
|
122
|
-
` declaration leaf: ${toRelativePath(config.rootDir, dtsProject.configPath)}`,
|
|
123
|
-
` typecheck config: ${toRelativePath(config.rootDir, typecheckConfigPath)}`,
|
|
124
|
-
` option: compilerOptions.${optionName}`,
|
|
125
|
-
` declaration value: ${formatCompilerOptionValue(buildValue)}`,
|
|
126
|
-
` typecheck value: ${formatCompilerOptionValue(typecheckValue)}`,
|
|
127
|
-
" reason: tsconfig*.dts.json should emit with the same typechecking semantics as its matching tsconfig*.json companion."
|
|
128
|
-
].join("\n"));
|
|
129
|
-
}
|
|
130
|
-
const typecheckFiles = new Set(typecheckProject.fileNames);
|
|
131
|
-
const missingFiles = dtsProject.fileNames.filter((fileName) => !typecheckFiles.has(fileName));
|
|
132
|
-
if (missingFiles.length === 0) return;
|
|
133
|
-
problems.push([
|
|
134
|
-
"Declaration leaf includes files missing from its companion typecheck config:",
|
|
135
|
-
` declaration leaf: ${toRelativePath(config.rootDir, dtsProject.configPath)}`,
|
|
136
|
-
` typecheck config: ${toRelativePath(config.rootDir, typecheckConfigPath)}`,
|
|
137
|
-
" files:",
|
|
138
|
-
...missingFiles.slice(0, 10).map((fileName) => ` - ${toRelativePath(config.rootDir, fileName)}`),
|
|
139
|
-
...missingFiles.length > 10 ? [` ...and ${missingFiles.length - 10} more`] : [],
|
|
140
|
-
" reason: a declaration leaf must not emit declarations for files that are not covered by the matching typecheck target."
|
|
141
|
-
].join("\n"));
|
|
33
|
+
function resolveLinkTargetPath(importerPackage, specifier) {
|
|
34
|
+
return normalizeAbsolutePath(path.resolve(importerPackage.directory, specifier.slice(5)));
|
|
142
35
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const deniedRefRule = getDeniedRefRule(options.rules, options.project.label, referencePath);
|
|
148
|
-
const targetPackage = findPackageForFile(referencePath, options.packages);
|
|
149
|
-
const deniedDepRule = targetPackage ? getDeniedDepRuleForPackage(options.rules, options.project.label, targetPackage.name) : null;
|
|
150
|
-
if (!deniedRefRule && !deniedDepRule) continue;
|
|
151
|
-
const lines = [
|
|
152
|
-
"Denied graph access:",
|
|
153
|
-
` rule: ${options.project.label}`,
|
|
154
|
-
` referencing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
|
|
155
|
-
` referenced project: ${toRelativePath(options.config.rootDir, referencePath)}`
|
|
156
|
-
];
|
|
157
|
-
if (deniedDepRule) lines.push(` denied dependency: ${deniedDepRule.name}`, ` reason: ${deniedDepRule.reason}`);
|
|
158
|
-
else if (deniedRefRule) lines.push(` denied ref: ${toRelativePath(options.config.rootDir, deniedRefRule.path)}`, ` reason: ${deniedRefRule.reason}`);
|
|
159
|
-
options.problems.push(lines.join("\n"));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
function addDeniedDepImportProblem(options) {
|
|
163
|
-
options.problems.push([
|
|
164
|
-
"Denied graph access:",
|
|
165
|
-
` rule: ${options.project.label}`,
|
|
166
|
-
` importing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
|
|
167
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
168
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
169
|
-
` denied dependency: ${options.rule.name}`,
|
|
170
|
-
` reason: ${options.rule.reason}`
|
|
171
|
-
].join("\n"));
|
|
36
|
+
function matchesArtifactDirectory(config, targetPackage, linkTargetPath) {
|
|
37
|
+
return configuredArtifactDirectories(config).some((artifactDirectory) => {
|
|
38
|
+
return isPathInsideDirectory(linkTargetPath, normalizeAbsolutePath(path.join(targetPackage.directory, artifactDirectory)));
|
|
39
|
+
});
|
|
172
40
|
}
|
|
173
|
-
function
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
` rule: ${options.project.label}`,
|
|
177
|
-
` importing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
|
|
178
|
-
` file: ${toRelativePath(options.config.rootDir, options.importRecord.filePath)}:${options.importRecord.line}`,
|
|
179
|
-
` imported specifier: ${options.importRecord.specifier}`,
|
|
180
|
-
` target project: ${toRelativePath(options.config.rootDir, options.targetProjectPath)}`,
|
|
181
|
-
` denied ref: ${toRelativePath(options.config.rootDir, options.rule.path)}`,
|
|
182
|
-
` reason: ${options.rule.reason}`
|
|
183
|
-
].join("\n"));
|
|
41
|
+
function createSchemaPath(config, packageDir) {
|
|
42
|
+
const schemaPath = toPosixPath(path.relative(packageDir, path.join(config.rootDir, "node_modules/nx/schemas/project-schema.json")));
|
|
43
|
+
return schemaPath.startsWith(".") ? schemaPath : `./${schemaPath}`;
|
|
184
44
|
}
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
const nodeModulesIndex = parts.lastIndexOf("node_modules");
|
|
188
|
-
if (nodeModulesIndex === -1) return null;
|
|
189
|
-
const packageName = parts[nodeModulesIndex + 1];
|
|
190
|
-
if (!packageName) return null;
|
|
191
|
-
if (packageName.startsWith("@")) {
|
|
192
|
-
const scopedName = parts[nodeModulesIndex + 2];
|
|
193
|
-
return scopedName ? `${packageName}/${scopedName}` : null;
|
|
194
|
-
}
|
|
195
|
-
return packageName;
|
|
196
|
-
}
|
|
197
|
-
function getResolvedPackageName(filePath, packages) {
|
|
198
|
-
return getNodeModulesPackageName(filePath) ?? findPackageForFile(filePath, packages)?.name ?? null;
|
|
199
|
-
}
|
|
200
|
-
function getResolvedWorkspacePackage(filePath, packages) {
|
|
201
|
-
if (getNodeModulesPackageName(filePath)) return null;
|
|
202
|
-
return findPackageForFile(filePath, packages);
|
|
203
|
-
}
|
|
204
|
-
function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems) {
|
|
205
|
-
if (!isDtsProjectConfig(project.configPath)) return;
|
|
206
|
-
const sourcePackage = findPackageForFile(project.configPath, packages);
|
|
207
|
-
const importer = sourcePackage ? findImporterForFile$1(project.configPath, importers) : null;
|
|
208
|
-
if (!sourcePackage) return;
|
|
209
|
-
for (const referencePath of project.references) {
|
|
210
|
-
if (!projectsByPath.has(referencePath)) continue;
|
|
211
|
-
const targetPackage = findPackageForFile(referencePath, packages);
|
|
212
|
-
if (!targetPackage || targetPackage.name === sourcePackage.name) continue;
|
|
213
|
-
if (importer?.workspaceDependencies.has(targetPackage.name)) continue;
|
|
214
|
-
problems.push([
|
|
215
|
-
"Project reference crosses workspace packages without a workspace:* dependency:",
|
|
216
|
-
` referencing project: ${toRelativePath(config.rootDir, project.configPath)}`,
|
|
217
|
-
` referenced project: ${toRelativePath(config.rootDir, referencePath)}`,
|
|
218
|
-
` referencing package: ${sourcePackage.name}`,
|
|
219
|
-
` referenced package: ${targetPackage.name}`,
|
|
220
|
-
` package manifest: ${toRelativePath(config.rootDir, path.join(sourcePackage.directory, "package.json"))}`,
|
|
221
|
-
` reason: a cross-package tsconfig*.dts.json reference is a source dependency edge, so ${sourcePackage.name} must declare ${targetPackage.name} with the workspace: protocol.`,
|
|
222
|
-
` fix: add "${targetPackage.name}": "workspace:*" to dependencies, devDependencies, peerDependencies, or optionalDependencies in the referencing package manifest. If this package intentionally consumes built artifacts, remove the project reference; ${formatArtifactDependencyPolicy(targetPackage)}`
|
|
223
|
-
].join("\n"));
|
|
224
|
-
}
|
|
45
|
+
async function readJsonFile(filePath) {
|
|
46
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
225
47
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
48
|
+
function isRecord$2(value) {
|
|
49
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
50
|
+
}
|
|
51
|
+
function normalizeNxTargets(targets) {
|
|
52
|
+
const normalizedTargets = (targets ?? defaultNxTargets).map((target) => target.trim()).filter(Boolean);
|
|
53
|
+
const uniqueTargets = [];
|
|
54
|
+
for (const target of normalizedTargets) if (!uniqueTargets.includes(target)) uniqueTargets.push(target);
|
|
55
|
+
return uniqueTargets.length > 0 ? uniqueTargets : [...defaultNxTargets];
|
|
56
|
+
}
|
|
57
|
+
function formatNxTargets(targets) {
|
|
58
|
+
return targets.join(" ");
|
|
59
|
+
}
|
|
60
|
+
function createDependsOn(dependencyNames) {
|
|
61
|
+
return dependencyNames.length > 0 ? [{
|
|
62
|
+
projects: dependencyNames,
|
|
63
|
+
target: "build"
|
|
64
|
+
}] : [];
|
|
65
|
+
}
|
|
66
|
+
function createGeneratedProjectJsonContent(config, plan, targets) {
|
|
67
|
+
return stringifyJson({
|
|
68
|
+
$schema: createSchemaPath(config, path.dirname(plan.outputPath)),
|
|
69
|
+
name: plan.packageName,
|
|
70
|
+
targets: Object.fromEntries(targets.map((target) => [target, { dependsOn: createDependsOn(plan.dependencyNames) }])),
|
|
71
|
+
metadata: { limina: { generated: true } }
|
|
244
72
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
config,
|
|
292
|
-
importRecord,
|
|
293
|
-
problems,
|
|
294
|
-
project,
|
|
295
|
-
rule: deniedDepRule
|
|
296
|
-
});
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (isRelativeSpecifier(importRecord.specifier)) {
|
|
300
|
-
const sourcePackage = findPackageForFile(importRecord.filePath, packages);
|
|
301
|
-
if (sourcePackage && targetWorkspacePackageForResolved && sourcePackage.name !== targetWorkspacePackageForResolved.name) {
|
|
302
|
-
problems.push([
|
|
303
|
-
"Cross-package relative import:",
|
|
304
|
-
` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
|
|
305
|
-
` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
306
|
-
` imported specifier: ${importRecord.specifier}`,
|
|
307
|
-
` source package: ${sourcePackage.name}`,
|
|
308
|
-
` target package: ${targetWorkspacePackageForResolved.name}`,
|
|
309
|
-
` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
|
|
310
|
-
" reason: workspace packages must depend through package exports."
|
|
311
|
-
].join("\n"));
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph) && !fileOwnerLookup.has(resolvedFilePath)) {
|
|
316
|
-
const referencedProjectPath = inferPackageProject$1(resolvedFilePath, targetPackageForGraph, projectPaths);
|
|
317
|
-
const hasProjectReference = referencedProjectPath && project.references.has(referencedProjectPath);
|
|
73
|
+
}
|
|
74
|
+
function addUniqueDependency(dependencyNames, dependencyName) {
|
|
75
|
+
if (!dependencyNames.includes(dependencyName)) dependencyNames.push(dependencyName);
|
|
76
|
+
}
|
|
77
|
+
function findArtifactCycle(edgesByPackageName) {
|
|
78
|
+
const visited = /* @__PURE__ */ new Set();
|
|
79
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
80
|
+
const stack = [];
|
|
81
|
+
const visit = (packageName) => {
|
|
82
|
+
if (visiting.has(packageName)) {
|
|
83
|
+
const start = stack.indexOf(packageName);
|
|
84
|
+
return [...stack.slice(start), packageName];
|
|
85
|
+
}
|
|
86
|
+
if (visited.has(packageName)) return null;
|
|
87
|
+
visiting.add(packageName);
|
|
88
|
+
stack.push(packageName);
|
|
89
|
+
for (const dependencyName of edgesByPackageName.get(packageName) ?? []) {
|
|
90
|
+
if (!edgesByPackageName.has(dependencyName)) continue;
|
|
91
|
+
const cycle = visit(dependencyName);
|
|
92
|
+
if (cycle) return cycle;
|
|
93
|
+
}
|
|
94
|
+
stack.pop();
|
|
95
|
+
visiting.delete(packageName);
|
|
96
|
+
visited.add(packageName);
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
99
|
+
for (const packageName of edgesByPackageName.keys()) {
|
|
100
|
+
const cycle = visit(packageName);
|
|
101
|
+
if (cycle) return cycle;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
async function collectNxProjectSyncPlans(config) {
|
|
106
|
+
const rootDirIdentity = normalizeAbsolutePathIdentity(config.rootDir);
|
|
107
|
+
const workspacePackages = (await collectWorkspacePackages(config)).filter((workspacePackage) => normalizeAbsolutePathIdentity(workspacePackage.directory) !== rootDirIdentity);
|
|
108
|
+
const packagesByName = new Map(workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]));
|
|
109
|
+
const buildablePackageNames = new Set(workspacePackages.filter((workspacePackage) => workspacePackage.manifest.scripts?.build).map((workspacePackage) => workspacePackage.name));
|
|
110
|
+
const edgesByPackageName = new Map(workspacePackages.map((workspacePackage) => [workspacePackage.name, []]));
|
|
111
|
+
const problems = [];
|
|
112
|
+
for (const workspacePackage of workspacePackages) {
|
|
113
|
+
const dependencyNames = edgesByPackageName.get(workspacePackage.name) ?? [];
|
|
114
|
+
for (const dependencies of getDependencySections(workspacePackage.manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) {
|
|
115
|
+
if (!isLinkDependencySpecifier$1(specifier)) continue;
|
|
116
|
+
const targetPackage = packagesByName.get(dependencyName);
|
|
117
|
+
const dependencyLabel = `${workspacePackage.name} -> ${dependencyName}`;
|
|
118
|
+
if (!targetPackage) {
|
|
318
119
|
problems.push([
|
|
319
|
-
|
|
320
|
-
`
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
` imported specifier: ${importRecord.specifier}`,
|
|
324
|
-
` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
|
|
325
|
-
" reason: workspace:* dependencies are source dependencies, but TypeScript resolved this package export to a file not owned by the source graph. tsc -b does not rewrite package exports through project references.",
|
|
326
|
-
` fix: expose source files from the dependency package exports, add a source paths config to this declaration leaf extends, or stop using workspace:* plus project references for artifact consumption; ${formatArtifactDependencyPolicy(targetPackageForGraph)}`,
|
|
327
|
-
" hint: run `limina paths generate` to create a compatibility paths file, then manually add it to the first position of the listed tsconfig*.dts.json extends array."
|
|
120
|
+
"Nx build dependency points at an unknown workspace package:",
|
|
121
|
+
` dependency: ${dependencyLabel}`,
|
|
122
|
+
` specifier: ${specifier}`,
|
|
123
|
+
" reason: link: workspace artifact dependencies must name a package from the pnpm workspace."
|
|
328
124
|
].join("\n"));
|
|
329
125
|
continue;
|
|
330
126
|
}
|
|
331
|
-
|
|
332
|
-
fileOwnerLookup,
|
|
333
|
-
packages,
|
|
334
|
-
projectPaths,
|
|
335
|
-
resolvedFilePath,
|
|
336
|
-
specifier: importRecord.specifier
|
|
337
|
-
});
|
|
338
|
-
if (!targetProjectPath) {
|
|
339
|
-
if (!targetPackageForGraph) continue;
|
|
340
|
-
if (!targetWorkspacePackageForResolved) {
|
|
341
|
-
if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph)) problems.push([
|
|
342
|
-
"Workspace source import resolved outside the workspace graph:",
|
|
343
|
-
` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
|
|
344
|
-
` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
345
|
-
` imported specifier: ${importRecord.specifier}`,
|
|
346
|
-
` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
|
|
347
|
-
` reason: workspace:* dependencies are source dependency edges and must resolve to files owned by the source graph; ${formatArtifactDependencyPolicy(targetPackageForGraph)}`
|
|
348
|
-
].join("\n"));
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
127
|
+
if (!buildablePackageNames.has(dependencyName)) {
|
|
351
128
|
problems.push([
|
|
352
|
-
"
|
|
353
|
-
`
|
|
354
|
-
`
|
|
355
|
-
|
|
356
|
-
` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
|
|
357
|
-
` current references: ${formatReferences(config.rootDir, project.references)}`
|
|
129
|
+
"Nx build dependency target has no build script:",
|
|
130
|
+
` dependency: ${dependencyLabel}`,
|
|
131
|
+
` package: ${toRelativePath(config.rootDir, targetPackage.directory)}`,
|
|
132
|
+
" reason: link: artifact dependencies require the target package to define scripts.build."
|
|
358
133
|
].join("\n"));
|
|
359
134
|
continue;
|
|
360
135
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (deniedRefRule) {
|
|
364
|
-
addDeniedRefImportProblem({
|
|
365
|
-
config,
|
|
366
|
-
importRecord,
|
|
367
|
-
problems,
|
|
368
|
-
project,
|
|
369
|
-
rule: deniedRefRule,
|
|
370
|
-
targetProjectPath
|
|
371
|
-
});
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
if (targetPackageForGraph && !shouldResolveThroughGraph$1(importer, targetPackageForGraph)) continue;
|
|
375
|
-
if (!projectsByPath.has(targetProjectPath)) {
|
|
136
|
+
const linkTargetPath = resolveLinkTargetPath(workspacePackage, specifier);
|
|
137
|
+
if (!matchesArtifactDirectory(config, targetPackage, linkTargetPath)) {
|
|
376
138
|
problems.push([
|
|
377
|
-
"
|
|
378
|
-
`
|
|
379
|
-
`
|
|
380
|
-
`
|
|
381
|
-
` expected
|
|
139
|
+
"Nx build dependency does not point at an artifact directory:",
|
|
140
|
+
` dependency: ${dependencyLabel}`,
|
|
141
|
+
` specifier: ${specifier}`,
|
|
142
|
+
` resolved: ${toRelativePath(config.rootDir, linkTargetPath)}`,
|
|
143
|
+
` expected artifact directories: ${configuredArtifactDirectories(config).join(", ")}`
|
|
382
144
|
].join("\n"));
|
|
383
145
|
continue;
|
|
384
146
|
}
|
|
385
|
-
|
|
386
|
-
"Missing project reference for workspace import:",
|
|
387
|
-
` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
|
|
388
|
-
` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
|
|
389
|
-
` imported specifier: ${importRecord.specifier}`,
|
|
390
|
-
` expected reference: ${toRelativePath(config.rootDir, targetProjectPath)}`,
|
|
391
|
-
` current references: ${formatReferences(config.rootDir, project.references)}`
|
|
392
|
-
].join("\n"));
|
|
147
|
+
addUniqueDependency(dependencyNames, dependencyName);
|
|
393
148
|
}
|
|
394
149
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
150
|
+
const cycle = findArtifactCycle(edgesByPackageName);
|
|
151
|
+
if (cycle) problems.push([
|
|
152
|
+
"Nx artifact build dependency cycle:",
|
|
153
|
+
` cycle: ${cycle.join(" -> ")}`,
|
|
154
|
+
" reason: link: artifact dependencies must form an acyclic build graph."
|
|
155
|
+
].join("\n"));
|
|
156
|
+
if (problems.length > 0) throw new Error(problems.join("\n\n"));
|
|
157
|
+
return workspacePackages.flatMap((workspacePackage) => {
|
|
158
|
+
return [{
|
|
159
|
+
dependencyNames: edgesByPackageName.get(workspacePackage.name) ?? [],
|
|
160
|
+
outputPath: path.join(workspacePackage.directory, "project.json"),
|
|
161
|
+
packageName: workspacePackage.name
|
|
162
|
+
}];
|
|
163
|
+
}).sort((left, right) => left.outputPath.localeCompare(right.outputPath));
|
|
164
|
+
}
|
|
165
|
+
function readTargetConfigs(projectJson) {
|
|
166
|
+
return isRecord$2(projectJson.targets) ? projectJson.targets : void 0;
|
|
167
|
+
}
|
|
168
|
+
function createInvalidProjectJsonError(config, plan) {
|
|
169
|
+
return new Error([
|
|
170
|
+
"Invalid Nx project config:",
|
|
171
|
+
` package: ${plan.packageName}`,
|
|
172
|
+
` file: ${toRelativePath(config.rootDir, plan.outputPath)}`,
|
|
173
|
+
" reason: project.json must contain a JSON object."
|
|
174
|
+
].join("\n"));
|
|
175
|
+
}
|
|
176
|
+
function createInvalidTargetConfigError(config, plan, target) {
|
|
177
|
+
return new Error([
|
|
178
|
+
"Invalid Nx project target config:",
|
|
179
|
+
` package: ${plan.packageName}`,
|
|
180
|
+
` file: ${toRelativePath(config.rootDir, plan.outputPath)}`,
|
|
181
|
+
` target: ${target}`,
|
|
182
|
+
" reason: Limina can only sync dependsOn for target configs that are JSON objects."
|
|
183
|
+
].join("\n"));
|
|
184
|
+
}
|
|
185
|
+
function readDependsOnProjectSet(dependsOn) {
|
|
186
|
+
if (!Array.isArray(dependsOn)) return null;
|
|
187
|
+
const projectNames = /* @__PURE__ */ new Set();
|
|
188
|
+
for (const entry of dependsOn) {
|
|
189
|
+
if (!isRecord$2(entry) || !Array.isArray(entry.projects)) return null;
|
|
190
|
+
for (const projectName of entry.projects) {
|
|
191
|
+
if (typeof projectName !== "string") return null;
|
|
192
|
+
projectNames.add(projectName);
|
|
193
|
+
}
|
|
398
194
|
}
|
|
399
|
-
|
|
195
|
+
return projectNames;
|
|
196
|
+
}
|
|
197
|
+
function areProjectSetsEqual(currentProjectNames, expectedProjectNames) {
|
|
198
|
+
if (!currentProjectNames) return false;
|
|
199
|
+
if (currentProjectNames.size !== expectedProjectNames.size) return false;
|
|
200
|
+
for (const projectName of currentProjectNames) if (!expectedProjectNames.has(projectName)) return false;
|
|
400
201
|
return true;
|
|
401
202
|
}
|
|
402
|
-
async function
|
|
203
|
+
async function checkNxProjectSyncPlan(config, plan, targets) {
|
|
204
|
+
if (!existsSync(plan.outputPath)) return true;
|
|
205
|
+
const currentJson = await readJsonFile(plan.outputPath);
|
|
206
|
+
if (!isRecord$2(currentJson)) throw createInvalidProjectJsonError(config, plan);
|
|
207
|
+
const targetConfigs = readTargetConfigs(currentJson);
|
|
208
|
+
if (!targetConfigs) return false;
|
|
209
|
+
const expectedProjectNames = new Set(plan.dependencyNames);
|
|
210
|
+
for (const target of targets) {
|
|
211
|
+
if (!Object.hasOwn(targetConfigs, target)) continue;
|
|
212
|
+
const targetConfig = targetConfigs[target];
|
|
213
|
+
if (!isRecord$2(targetConfig)) return true;
|
|
214
|
+
if (!areProjectSetsEqual(readDependsOnProjectSet(targetConfig.dependsOn), expectedProjectNames)) return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
async function writeNxProjectSyncPlan(config, plan, targets) {
|
|
219
|
+
if (!existsSync(plan.outputPath)) {
|
|
220
|
+
await mkdir(path.dirname(plan.outputPath), { recursive: true });
|
|
221
|
+
await writeFile(plan.outputPath, createGeneratedProjectJsonContent(config, plan, targets));
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
const currentJson = await readJsonFile(plan.outputPath);
|
|
225
|
+
if (!isRecord$2(currentJson)) throw createInvalidProjectJsonError(config, plan);
|
|
226
|
+
const targetConfigs = readTargetConfigs(currentJson);
|
|
227
|
+
if (!targetConfigs) return false;
|
|
228
|
+
let didChange = false;
|
|
229
|
+
const expectedDependsOn = createDependsOn(plan.dependencyNames);
|
|
230
|
+
for (const target of targets) {
|
|
231
|
+
if (!Object.hasOwn(targetConfigs, target)) continue;
|
|
232
|
+
const targetConfig = targetConfigs[target];
|
|
233
|
+
if (!isRecord$2(targetConfig)) throw createInvalidTargetConfigError(config, plan, target);
|
|
234
|
+
if (JSON.stringify(targetConfig.dependsOn) === JSON.stringify(expectedDependsOn)) continue;
|
|
235
|
+
targetConfig.dependsOn = expectedDependsOn;
|
|
236
|
+
didChange = true;
|
|
237
|
+
}
|
|
238
|
+
if (!didChange) return false;
|
|
239
|
+
await writeFile(plan.outputPath, stringifyJson(currentJson));
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
async function runNxInternal(config, options = {}) {
|
|
243
|
+
const targets = normalizeNxTargets(options.targets);
|
|
244
|
+
const syncPlans = await collectNxProjectSyncPlans(config);
|
|
245
|
+
const didChange = (await Promise.all(syncPlans.map((syncPlan) => options.check ? checkNxProjectSyncPlan(config, syncPlan, targets) : writeNxProjectSyncPlan(config, syncPlan, targets)))).some(Boolean);
|
|
246
|
+
const edgeCount = syncPlans.reduce((total, syncPlan) => total + syncPlan.dependencyNames.length, 0) * targets.length;
|
|
247
|
+
const action = options.check ? didChange ? "Would update" : "Checked unchanged" : didChange ? "Synced" : "Skipped unchanged";
|
|
248
|
+
NxLogger.info(`${action} ${syncPlans.length} Nx project config files across ${targets.length} target${targets.length === 1 ? "" : "s"} with ${edgeCount} build dependency edges.`);
|
|
249
|
+
if (options.check && didChange) NxLogger.error(`Nx project config state is stale; run \`limina nx sync ${formatNxTargets(targets)}\`.`);
|
|
250
|
+
return {
|
|
251
|
+
changed: didChange,
|
|
252
|
+
edgeCount,
|
|
253
|
+
outputCount: syncPlans.length
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async function runNx(config, options = {}) {
|
|
403
257
|
if (options.clearScreen ?? true) clearCliScreen();
|
|
404
258
|
const elapsed = createElapsedTimer();
|
|
405
|
-
const
|
|
406
|
-
|
|
259
|
+
const targets = normalizeNxTargets(options.targets);
|
|
260
|
+
const action = options.check ? `nx check ${formatNxTargets(targets)}` : `nx sync ${formatNxTargets(targets)}`;
|
|
261
|
+
const task = options.flow?.start(action, { depth: options.flowDepth ?? 0 });
|
|
262
|
+
NxLogger.info(`${action} started`);
|
|
407
263
|
try {
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
task?.pass();
|
|
264
|
+
const result = await runNxInternal(config, options);
|
|
265
|
+
if (options.check && result.changed) {
|
|
266
|
+
NxLogger.error(`${action} finished with stale files`, elapsed());
|
|
267
|
+
task?.fail(`${action} finished with stale files`);
|
|
413
268
|
} else {
|
|
414
|
-
|
|
415
|
-
task?.
|
|
269
|
+
if (!options.flow?.interactive) NxLogger.success(`${action} finished`, elapsed());
|
|
270
|
+
task?.pass();
|
|
416
271
|
}
|
|
417
|
-
return
|
|
272
|
+
return result;
|
|
418
273
|
} catch (error) {
|
|
419
|
-
|
|
420
|
-
task?.fail(
|
|
274
|
+
NxLogger.error(`${action} failed: ${formatErrorMessage$1(error)}`, elapsed());
|
|
275
|
+
task?.fail(`${action} failed`, { error });
|
|
421
276
|
throw error;
|
|
422
277
|
}
|
|
423
278
|
}
|
|
@@ -439,6 +294,49 @@ const nodeBuiltinSpecifiers = new Set(builtinModules.flatMap((specifier) => spec
|
|
|
439
294
|
function isRecord$1(value) {
|
|
440
295
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
441
296
|
}
|
|
297
|
+
function collectPackageDependencyEntries$1(manifest) {
|
|
298
|
+
const sectionNames = [
|
|
299
|
+
"dependencies",
|
|
300
|
+
"devDependencies",
|
|
301
|
+
"peerDependencies",
|
|
302
|
+
"optionalDependencies"
|
|
303
|
+
];
|
|
304
|
+
const entries = [];
|
|
305
|
+
for (const sectionName of sectionNames) {
|
|
306
|
+
const section = manifest[sectionName];
|
|
307
|
+
if (!isRecord$1(section)) continue;
|
|
308
|
+
for (const [dependencyName, specifier] of Object.entries(section)) {
|
|
309
|
+
if (typeof specifier !== "string") continue;
|
|
310
|
+
entries.push({
|
|
311
|
+
dependencyName,
|
|
312
|
+
sectionName,
|
|
313
|
+
specifier
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return entries;
|
|
318
|
+
}
|
|
319
|
+
function collectStrictBuiltPackageManifestProblems(options) {
|
|
320
|
+
const problems = [];
|
|
321
|
+
if (typeof options.manifest.name !== "string" || options.manifest.name.trim().length === 0) problems.push([
|
|
322
|
+
`[${options.label}] [strict] output package.json is not a complete npm package manifest`,
|
|
323
|
+
` package.json: ${options.packageJsonPath}`,
|
|
324
|
+
" field: name",
|
|
325
|
+
" reason: strict: true requires built package outputs to include a non-empty package name."
|
|
326
|
+
].join("\n"));
|
|
327
|
+
for (const entry of collectPackageDependencyEntries$1(options.manifest)) {
|
|
328
|
+
if (!isLocalPackageDependencySpecifier(entry.specifier)) continue;
|
|
329
|
+
problems.push([
|
|
330
|
+
`[${options.label}] [strict] output package.json exposes a pnpm-local dependency specifier`,
|
|
331
|
+
` package.json: ${options.packageJsonPath}`,
|
|
332
|
+
` dependency: ${entry.dependencyName}`,
|
|
333
|
+
` section: ${entry.sectionName}`,
|
|
334
|
+
` specifier: ${entry.specifier}`,
|
|
335
|
+
" reason: strict: true requires built package manifests to be publish-ready npm package manifests without workspace:, link:, file:, or catalog: specifiers."
|
|
336
|
+
].join("\n"));
|
|
337
|
+
}
|
|
338
|
+
return problems;
|
|
339
|
+
}
|
|
442
340
|
function isPackageCheckTool(value) {
|
|
443
341
|
return PACKAGE_CHECK_TOOLS.has(value);
|
|
444
342
|
}
|
|
@@ -767,11 +665,16 @@ async function runPackageCheckEntry(options) {
|
|
|
767
665
|
const task = options.flow?.start(`package entry: ${label}`, { depth: options.flowDepth ?? 0 });
|
|
768
666
|
let packedDist;
|
|
769
667
|
try {
|
|
770
|
-
await readDistPackageJson({
|
|
668
|
+
const outputManifest = await readDistPackageJson({
|
|
771
669
|
config: options.config,
|
|
772
670
|
label,
|
|
773
671
|
packageJsonPath: outputPackageJsonPath
|
|
774
672
|
});
|
|
673
|
+
const strictManifestProblems = isStrictConfig(options.config) ? collectStrictBuiltPackageManifestProblems({
|
|
674
|
+
label,
|
|
675
|
+
manifest: outputManifest,
|
|
676
|
+
packageJsonPath: toRelativePath(options.config.rootDir, outputPackageJsonPath)
|
|
677
|
+
}) : [];
|
|
775
678
|
if (options.checks.includes("publint") || options.checks.includes("attw")) {
|
|
776
679
|
const packTask = options.flow?.start(`package tarball: ${label}`, { depth: (options.flowDepth ?? 0) + 1 });
|
|
777
680
|
PackageLogger.info(`package tarball packing started: ${label}`);
|
|
@@ -786,7 +689,8 @@ async function runPackageCheckEntry(options) {
|
|
|
786
689
|
if (!options.flow?.interactive) PackageLogger.success(`package tarball packed: ${label}`, packElapsed());
|
|
787
690
|
packTask?.pass();
|
|
788
691
|
}
|
|
789
|
-
let passed =
|
|
692
|
+
let passed = strictManifestProblems.length === 0;
|
|
693
|
+
for (const problem of strictManifestProblems) PackageLogger.error(problem);
|
|
790
694
|
if (options.checks.includes("publint")) passed = await runPublintCheck({
|
|
791
695
|
flow: options.flow,
|
|
792
696
|
flowDepth: (options.flowDepth ?? 0) + 1,
|
|
@@ -910,194 +814,6 @@ async function runPackageCheck(options) {
|
|
|
910
814
|
}
|
|
911
815
|
}
|
|
912
816
|
|
|
913
|
-
//#endregion
|
|
914
|
-
//#region src/package-exports.ts
|
|
915
|
-
const defaultSourceExtensions = [
|
|
916
|
-
".ts",
|
|
917
|
-
".tsx",
|
|
918
|
-
".mts",
|
|
919
|
-
".cts",
|
|
920
|
-
".d.ts",
|
|
921
|
-
".d.mts",
|
|
922
|
-
".d.cts"
|
|
923
|
-
];
|
|
924
|
-
const defaultConditionPriority = [
|
|
925
|
-
"source",
|
|
926
|
-
"development",
|
|
927
|
-
"types",
|
|
928
|
-
"import",
|
|
929
|
-
"module",
|
|
930
|
-
"default",
|
|
931
|
-
"require"
|
|
932
|
-
];
|
|
933
|
-
const defaultArtifactDirectories = [
|
|
934
|
-
"dist",
|
|
935
|
-
"build",
|
|
936
|
-
"lib",
|
|
937
|
-
"esm",
|
|
938
|
-
"cjs",
|
|
939
|
-
"out"
|
|
940
|
-
];
|
|
941
|
-
function configuredArtifactDirectories(config) {
|
|
942
|
-
return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
|
|
943
|
-
}
|
|
944
|
-
function configuredSourceExtensions(config) {
|
|
945
|
-
return config.paths?.sourceExtensions ?? defaultSourceExtensions;
|
|
946
|
-
}
|
|
947
|
-
function collectTargetCandidates(config, value) {
|
|
948
|
-
if (typeof value === "string") return [value];
|
|
949
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
950
|
-
const record = value;
|
|
951
|
-
const candidates = [];
|
|
952
|
-
const visitedKeys = /* @__PURE__ */ new Set();
|
|
953
|
-
for (const key of config.paths?.conditionPriority ?? defaultConditionPriority) {
|
|
954
|
-
if (!(key in record)) continue;
|
|
955
|
-
visitedKeys.add(key);
|
|
956
|
-
candidates.push(...collectTargetCandidates(config, record[key]));
|
|
957
|
-
}
|
|
958
|
-
for (const key of Object.keys(record).sort()) {
|
|
959
|
-
if (visitedKeys.has(key)) continue;
|
|
960
|
-
candidates.push(...collectTargetCandidates(config, record[key]));
|
|
961
|
-
}
|
|
962
|
-
return candidates;
|
|
963
|
-
}
|
|
964
|
-
function normalizePackageTarget(target) {
|
|
965
|
-
if (!target.startsWith("./")) return null;
|
|
966
|
-
return target.slice(2);
|
|
967
|
-
}
|
|
968
|
-
function packageExportKeyToAlias(packageName, exportKey) {
|
|
969
|
-
if (exportKey === ".") return packageName;
|
|
970
|
-
if (!exportKey.startsWith("./")) return "";
|
|
971
|
-
return `${packageName}/${exportKey.slice(2)}`;
|
|
972
|
-
}
|
|
973
|
-
function removeKnownExtension(filePath) {
|
|
974
|
-
for (const extension of [
|
|
975
|
-
".d.mts",
|
|
976
|
-
".d.cts",
|
|
977
|
-
".d.ts",
|
|
978
|
-
".mts",
|
|
979
|
-
".cts",
|
|
980
|
-
".mjs",
|
|
981
|
-
".cjs",
|
|
982
|
-
".js",
|
|
983
|
-
".tsx",
|
|
984
|
-
".ts"
|
|
985
|
-
]) if (filePath.endsWith(extension)) return filePath.slice(0, -extension.length);
|
|
986
|
-
return filePath;
|
|
987
|
-
}
|
|
988
|
-
function hasConfiguredSourceExtension(config, target) {
|
|
989
|
-
return configuredSourceExtensions(config).some((extension) => target.endsWith(extension));
|
|
990
|
-
}
|
|
991
|
-
function isInsideArtifactDirectory(config, target) {
|
|
992
|
-
const normalizedTarget = normalizeSlashes(target);
|
|
993
|
-
return configuredArtifactDirectories(config).some((directoryName) => {
|
|
994
|
-
const normalizedDirectoryName = normalizeSlashes(directoryName).replace(/\/+$/u, "");
|
|
995
|
-
return normalizedTarget === normalizedDirectoryName || normalizedTarget.startsWith(`${normalizedDirectoryName}/`);
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
function isLikelySourceTarget(config, target) {
|
|
999
|
-
return hasConfiguredSourceExtension(config, target) && !isInsideArtifactDirectory(config, target);
|
|
1000
|
-
}
|
|
1001
|
-
function stripArtifactPrefix(config, target) {
|
|
1002
|
-
const normalizedTarget = normalizeSlashes(target);
|
|
1003
|
-
for (const directoryName of configuredArtifactDirectories(config)) {
|
|
1004
|
-
const normalizedDirectoryName = normalizeSlashes(directoryName).replace(/\/+$/u, "");
|
|
1005
|
-
if (normalizedTarget.startsWith(`${normalizedDirectoryName}/`)) return normalizedTarget.slice(normalizedDirectoryName.length + 1);
|
|
1006
|
-
}
|
|
1007
|
-
return normalizedTarget;
|
|
1008
|
-
}
|
|
1009
|
-
function sourceFileCandidates(config, target) {
|
|
1010
|
-
const normalizedTarget = normalizeSlashes(target);
|
|
1011
|
-
const withoutKnownExtension = removeKnownExtension(normalizedTarget);
|
|
1012
|
-
const withoutArtifactPrefix = stripArtifactPrefix(config, normalizedTarget);
|
|
1013
|
-
const sourceBase = removeKnownExtension(withoutArtifactPrefix);
|
|
1014
|
-
const bases = withoutArtifactPrefix === normalizedTarget ? [
|
|
1015
|
-
withoutKnownExtension,
|
|
1016
|
-
sourceBase,
|
|
1017
|
-
`src/${sourceBase}`,
|
|
1018
|
-
`${sourceBase}/index`,
|
|
1019
|
-
`src/${sourceBase}/index`
|
|
1020
|
-
] : [
|
|
1021
|
-
sourceBase,
|
|
1022
|
-
`src/${sourceBase}`,
|
|
1023
|
-
`${sourceBase}/index`,
|
|
1024
|
-
`src/${sourceBase}/index`
|
|
1025
|
-
];
|
|
1026
|
-
const candidates = [];
|
|
1027
|
-
for (const base of bases) for (const extension of configuredSourceExtensions(config)) candidates.push(`${base}${extension}`);
|
|
1028
|
-
return [...new Set(candidates)];
|
|
1029
|
-
}
|
|
1030
|
-
function wildcardBaseDirectory(pattern) {
|
|
1031
|
-
const wildcardIndex = pattern.indexOf("*");
|
|
1032
|
-
const prefix = wildcardIndex === -1 ? pattern : pattern.slice(0, wildcardIndex);
|
|
1033
|
-
const lastSlashIndex = prefix.lastIndexOf("/");
|
|
1034
|
-
return lastSlashIndex === -1 ? "." : prefix.slice(0, lastSlashIndex);
|
|
1035
|
-
}
|
|
1036
|
-
function sourceWildcardPatternCandidates(config, target) {
|
|
1037
|
-
const strippedPattern = stripArtifactPrefix(config, target);
|
|
1038
|
-
const sourcePattern = removeKnownExtension(strippedPattern);
|
|
1039
|
-
const preferSrcPrefix = strippedPattern !== normalizeSlashes(target);
|
|
1040
|
-
const candidates = [];
|
|
1041
|
-
for (const extension of configuredSourceExtensions(config)) if (preferSrcPrefix) {
|
|
1042
|
-
candidates.push(`src/${sourcePattern}${extension}`);
|
|
1043
|
-
candidates.push(`${sourcePattern}${extension}`);
|
|
1044
|
-
} else {
|
|
1045
|
-
candidates.push(`${sourcePattern}${extension}`);
|
|
1046
|
-
candidates.push(`src/${sourcePattern}${extension}`);
|
|
1047
|
-
}
|
|
1048
|
-
return [...new Set(candidates)];
|
|
1049
|
-
}
|
|
1050
|
-
function resolveWildcardTarget(config, packageDirectory, target) {
|
|
1051
|
-
const sourcePatterns = sourceWildcardPatternCandidates(config, target);
|
|
1052
|
-
const candidatePatterns = isLikelySourceTarget(config, target) ? [target, ...sourcePatterns] : sourcePatterns;
|
|
1053
|
-
for (const candidatePattern of candidatePatterns) {
|
|
1054
|
-
const baseDirectory = wildcardBaseDirectory(candidatePattern);
|
|
1055
|
-
if (existsSync(path.join(packageDirectory, baseDirectory))) return toPosixPath(path.join(toRelativePath(config.rootDir, packageDirectory), candidatePattern));
|
|
1056
|
-
}
|
|
1057
|
-
return null;
|
|
1058
|
-
}
|
|
1059
|
-
function resolveExactTarget(config, packageDirectory, target) {
|
|
1060
|
-
const absoluteTarget = path.join(packageDirectory, target);
|
|
1061
|
-
if (existsSync(absoluteTarget) && isLikelySourceTarget(config, target)) return normalizeWorkspacePath(config.rootDir, absoluteTarget);
|
|
1062
|
-
for (const candidate of sourceFileCandidates(config, target)) {
|
|
1063
|
-
const absoluteCandidate = path.join(packageDirectory, candidate);
|
|
1064
|
-
if (existsSync(absoluteCandidate)) return normalizeWorkspacePath(config.rootDir, absoluteCandidate);
|
|
1065
|
-
}
|
|
1066
|
-
return null;
|
|
1067
|
-
}
|
|
1068
|
-
function resolvePackageTarget(config, packageDirectory, rawTarget) {
|
|
1069
|
-
const target = normalizePackageTarget(rawTarget);
|
|
1070
|
-
if (!target) return null;
|
|
1071
|
-
if (target.includes("*")) return resolveWildcardTarget(config, packageDirectory, target);
|
|
1072
|
-
return resolveExactTarget(config, packageDirectory, target);
|
|
1073
|
-
}
|
|
1074
|
-
function collectExportEntries(config, workspacePackage) {
|
|
1075
|
-
const exportsField = workspacePackage.manifest.exports;
|
|
1076
|
-
if (!exportsField) return [];
|
|
1077
|
-
const exportEntries = typeof exportsField === "object" && exportsField !== null && !Array.isArray(exportsField) && Object.keys(exportsField).some((key) => key.startsWith(".")) ? Object.entries(exportsField) : [[".", exportsField]];
|
|
1078
|
-
const entries = [];
|
|
1079
|
-
for (const [exportKey, exportValue] of exportEntries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
1080
|
-
const alias = packageExportKeyToAlias(workspacePackage.name, exportKey);
|
|
1081
|
-
if (!alias) continue;
|
|
1082
|
-
for (const candidate of collectTargetCandidates(config, exportValue)) {
|
|
1083
|
-
const resolvedTarget = resolvePackageTarget(config, workspacePackage.directory, candidate);
|
|
1084
|
-
if (resolvedTarget) {
|
|
1085
|
-
entries.push([alias, resolvedTarget]);
|
|
1086
|
-
break;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
return entries;
|
|
1091
|
-
}
|
|
1092
|
-
function aliasMatchesSpecifier(alias, specifier) {
|
|
1093
|
-
if (alias === specifier) return true;
|
|
1094
|
-
const wildcardIndex = alias.indexOf("*");
|
|
1095
|
-
if (wildcardIndex === -1) return false;
|
|
1096
|
-
const prefix = alias.slice(0, wildcardIndex);
|
|
1097
|
-
const suffix = alias.slice(wildcardIndex + 1);
|
|
1098
|
-
return specifier.startsWith(prefix) && specifier.endsWith(suffix);
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
817
|
//#endregion
|
|
1102
818
|
//#region src/commands/paths.ts
|
|
1103
819
|
function generatedFileName(config) {
|
|
@@ -1106,78 +822,13 @@ function generatedFileName(config) {
|
|
|
1106
822
|
function generatedFileMarker(config) {
|
|
1107
823
|
return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
|
|
1108
824
|
}
|
|
1109
|
-
function
|
|
1110
|
-
|
|
1111
|
-
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
1112
|
-
...ts.sys,
|
|
1113
|
-
onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
|
|
1114
|
-
diagnostics.push(diagnostic);
|
|
1115
|
-
}
|
|
1116
|
-
});
|
|
1117
|
-
if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
|
|
1118
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
1119
|
-
getCurrentDirectory: () => config.rootDir,
|
|
1120
|
-
getNewLine: () => "\n"
|
|
1121
|
-
}));
|
|
1122
|
-
if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
|
|
1123
|
-
getCanonicalFileName: (fileName) => fileName,
|
|
1124
|
-
getCurrentDirectory: () => config.rootDir,
|
|
1125
|
-
getNewLine: () => "\n"
|
|
1126
|
-
}));
|
|
1127
|
-
return {
|
|
1128
|
-
configPath: normalizeAbsolutePath(configPath),
|
|
1129
|
-
fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
|
|
1130
|
-
options: parsed.options,
|
|
1131
|
-
references: new Set(getRawReferencePaths(config, configPath))
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
function getSourceFileKind(filePath) {
|
|
1135
|
-
if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
1136
|
-
if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
1137
|
-
return ts.ScriptKind.TS;
|
|
1138
|
-
}
|
|
1139
|
-
function stringLiteralValue(node) {
|
|
1140
|
-
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
1141
|
-
}
|
|
1142
|
-
function collectImportsFromFile(filePath) {
|
|
1143
|
-
const sourceText = readFileSync(filePath, "utf8");
|
|
1144
|
-
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
|
|
1145
|
-
const imports = [];
|
|
1146
|
-
const addImport = (specifier, node) => {
|
|
1147
|
-
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
1148
|
-
imports.push({
|
|
1149
|
-
filePath,
|
|
1150
|
-
line: location.line + 1,
|
|
1151
|
-
specifier
|
|
1152
|
-
});
|
|
1153
|
-
};
|
|
1154
|
-
const visit = (node) => {
|
|
1155
|
-
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
1156
|
-
const specifier = stringLiteralValue(node.moduleSpecifier);
|
|
1157
|
-
if (specifier) addImport(specifier, node);
|
|
1158
|
-
} else if (ts.isImportTypeNode(node)) {
|
|
1159
|
-
const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
|
|
1160
|
-
if (specifier) addImport(specifier, node);
|
|
1161
|
-
} else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
1162
|
-
const specifier = stringLiteralValue(node.arguments[0]);
|
|
1163
|
-
if (specifier) addImport(specifier, node);
|
|
1164
|
-
}
|
|
1165
|
-
ts.forEachChild(node, visit);
|
|
1166
|
-
};
|
|
1167
|
-
visit(sourceFile);
|
|
1168
|
-
return imports;
|
|
1169
|
-
}
|
|
1170
|
-
function resolveInternalImport(specifier, containingFile, options) {
|
|
1171
|
-
const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
|
|
1172
|
-
return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
|
|
1173
|
-
}
|
|
1174
|
-
function resolveImportWithoutMatchingPaths(specifier, containingFile, options) {
|
|
1175
|
-
if (!options.paths) return resolveInternalImport(specifier, containingFile, options);
|
|
825
|
+
function resolveImportWithoutMatchingPaths(specifier, containingFile, options, project, importAnalysis) {
|
|
826
|
+
if (!options.paths) return resolveInternalImport(specifier, containingFile, options, project, importAnalysis);
|
|
1176
827
|
const paths = Object.fromEntries(Object.entries(options.paths).filter(([alias]) => !aliasMatchesSpecifier(alias, specifier)));
|
|
1177
828
|
return resolveInternalImport(specifier, containingFile, {
|
|
1178
829
|
...options,
|
|
1179
830
|
paths: Object.keys(paths).length > 0 ? paths : void 0
|
|
1180
|
-
});
|
|
831
|
+
}, project, importAnalysis);
|
|
1181
832
|
}
|
|
1182
833
|
function createFileOwnerLookup(projects) {
|
|
1183
834
|
const ownerLookup = /* @__PURE__ */ new Map();
|
|
@@ -1244,7 +895,7 @@ function formatPaths(config, paths, outputDirectory) {
|
|
|
1244
895
|
function formatGeneratedConfig(config, paths, outputPath) {
|
|
1245
896
|
const outputDirectory = path.dirname(outputPath);
|
|
1246
897
|
return `{
|
|
1247
|
-
"$schema": "
|
|
898
|
+
"$schema": "${createLiminaTsconfigSchemaPath(config.rootDir, outputPath)}",
|
|
1248
899
|
/**
|
|
1249
900
|
* ${generatedFileMarker(config)}
|
|
1250
901
|
*
|
|
@@ -1280,24 +931,25 @@ function createGeneratedConfigs(config, drafts) {
|
|
|
1280
931
|
})).filter((generatedConfig) => generatedConfig.aliasCount > 0).sort((left, right) => left.outputPath.localeCompare(right.outputPath));
|
|
1281
932
|
}
|
|
1282
933
|
async function collectGeneratedConfigs(config) {
|
|
1283
|
-
const graphRoute =
|
|
1284
|
-
const projectPaths = graphRoute.
|
|
934
|
+
const graphRoute = collectSourceGraphProjectExtensions(config);
|
|
935
|
+
const projectPaths = [...graphRoute.projectContextsByPath.keys()].sort();
|
|
1285
936
|
if (graphRoute.problems.length > 0) throw new Error(graphRoute.problems.join("\n\n"));
|
|
1286
|
-
const projects = projectPaths.map((projectPath) => parseProject(config, projectPath));
|
|
937
|
+
const projects = projectPaths.map((projectPath) => parseProject(config, projectPath, graphRoute.projectContextsByPath.get(projectPath)));
|
|
1287
938
|
const fileOwnerLookup = createFileOwnerLookup(projects);
|
|
1288
939
|
const packages = await collectWorkspacePackages(config);
|
|
1289
940
|
const importers = collectImporters(config, packages);
|
|
1290
941
|
const exportEntriesByPackage = /* @__PURE__ */ new Map();
|
|
1291
942
|
const drafts = /* @__PURE__ */ new Map();
|
|
943
|
+
const importAnalysis = createImportAnalysisContext();
|
|
1292
944
|
for (const project of projects) {
|
|
1293
945
|
const keepsGeneratedPaths = projectExtendsGeneratedConfig(config, project.configPath);
|
|
1294
|
-
for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile(filePath)) {
|
|
946
|
+
for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile(filePath, config.rootDir, importAnalysis)) {
|
|
1295
947
|
const targetPackage = findPackageForSpecifier(importRecord.specifier, packages);
|
|
1296
948
|
const importer = targetPackage ? findImporterForFile(importRecord.filePath, importers) : null;
|
|
1297
949
|
if (!targetPackage || !shouldResolveThroughGraph(importer, targetPackage)) continue;
|
|
1298
|
-
const resolvedFilePath = resolveInternalImport(importRecord.specifier, filePath, project.options);
|
|
950
|
+
const resolvedFilePath = resolveInternalImport(importRecord.specifier, filePath, project.options, project, importAnalysis);
|
|
1299
951
|
if (!resolvedFilePath) continue;
|
|
1300
|
-
const artifactResolvedFilePath = fileOwnerLookup.has(resolvedFilePath) && keepsGeneratedPaths ? resolveImportWithoutMatchingPaths(importRecord.specifier, filePath, project.options) : resolvedFilePath;
|
|
952
|
+
const artifactResolvedFilePath = fileOwnerLookup.has(resolvedFilePath) && keepsGeneratedPaths ? resolveImportWithoutMatchingPaths(importRecord.specifier, filePath, project.options, project, importAnalysis) : resolvedFilePath;
|
|
1301
953
|
if (!artifactResolvedFilePath || fileOwnerLookup.has(artifactResolvedFilePath)) continue;
|
|
1302
954
|
const targetProjectPath = inferPackageProject(artifactResolvedFilePath, targetPackage, projectPaths);
|
|
1303
955
|
if (!targetProjectPath || !project.references.has(targetProjectPath)) continue;
|
|
@@ -1421,6 +1073,7 @@ const defaultSourceExclude = [
|
|
|
1421
1073
|
"coverage",
|
|
1422
1074
|
"**/tsconfig*.json",
|
|
1423
1075
|
"**/package.json",
|
|
1076
|
+
"**/project.json",
|
|
1424
1077
|
".prettierrc.json",
|
|
1425
1078
|
".markdownlint.json",
|
|
1426
1079
|
"vercel.json"
|
|
@@ -1456,13 +1109,32 @@ const ignoredSemanticCompilerOptions = new Set([
|
|
|
1456
1109
|
"sourceRoot",
|
|
1457
1110
|
"tsBuildInfoFile"
|
|
1458
1111
|
]);
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1112
|
+
const javaScriptConfigFilePattern = /(?:^|[/\\])[^/\\]+\.config\.[cm]?js$/u;
|
|
1113
|
+
const typeScriptFamilyCheckerPresets = new Set([
|
|
1114
|
+
"tsc",
|
|
1115
|
+
"tsgo",
|
|
1116
|
+
"vue-tsc",
|
|
1117
|
+
"vue-tsgo"
|
|
1118
|
+
]);
|
|
1463
1119
|
function getCheckerCoverageExtensions(checker) {
|
|
1464
|
-
|
|
1465
|
-
|
|
1120
|
+
return checker.extensions;
|
|
1121
|
+
}
|
|
1122
|
+
function getActiveCheckerContext(config) {
|
|
1123
|
+
return {
|
|
1124
|
+
checkerPresets: [...new Set(getActiveCheckers(config).map((checker) => checker.preset))],
|
|
1125
|
+
extensions: getActiveCheckerExtensions(config)
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function createCheckerProjectContext(options) {
|
|
1129
|
+
const adapterExtensions = resolveCheckerProjectExtensions({
|
|
1130
|
+
configPath: options.configPath,
|
|
1131
|
+
preset: options.preset,
|
|
1132
|
+
projectRootDir: options.config.rootDir
|
|
1133
|
+
});
|
|
1134
|
+
return {
|
|
1135
|
+
checkerPresets: [options.preset],
|
|
1136
|
+
extensions: normalizeExtensions([...options.extensions, ...adapterExtensions])
|
|
1137
|
+
};
|
|
1466
1138
|
}
|
|
1467
1139
|
async function collectTsconfigPaths(config, pattern) {
|
|
1468
1140
|
return (await glob(pattern, {
|
|
@@ -1577,7 +1249,7 @@ function collectConfiguredAllowlistEntries(config) {
|
|
|
1577
1249
|
problems
|
|
1578
1250
|
};
|
|
1579
1251
|
}
|
|
1580
|
-
rawEntries.
|
|
1252
|
+
for (const [index, entry] of rawEntries.entries()) {
|
|
1581
1253
|
const field = `proof.allowlist[${index}]`;
|
|
1582
1254
|
if (!isPlainRecord(entry)) {
|
|
1583
1255
|
problems.push([
|
|
@@ -1586,7 +1258,7 @@ function collectConfiguredAllowlistEntries(config) {
|
|
|
1586
1258
|
` value: ${formatUnknownValue(entry)}`,
|
|
1587
1259
|
" reason: allowlist entries must be objects with non-empty file and reason fields."
|
|
1588
1260
|
].join("\n"));
|
|
1589
|
-
|
|
1261
|
+
continue;
|
|
1590
1262
|
}
|
|
1591
1263
|
const fileValue = entry.file;
|
|
1592
1264
|
const reasonValue = entry.reason;
|
|
@@ -1597,7 +1269,7 @@ function collectConfiguredAllowlistEntries(config) {
|
|
|
1597
1269
|
` value: ${formatUnknownValue(fileValue)}`,
|
|
1598
1270
|
" reason: allowlist file must be a non-empty string."
|
|
1599
1271
|
].join("\n"));
|
|
1600
|
-
|
|
1272
|
+
continue;
|
|
1601
1273
|
}
|
|
1602
1274
|
if (typeof reasonValue !== "string" || reasonValue.trim().length === 0) {
|
|
1603
1275
|
problems.push([
|
|
@@ -1606,13 +1278,13 @@ function collectConfiguredAllowlistEntries(config) {
|
|
|
1606
1278
|
` value: ${formatUnknownValue(reasonValue)}`,
|
|
1607
1279
|
" reason: allowlist reason must be a non-empty string."
|
|
1608
1280
|
].join("\n"));
|
|
1609
|
-
|
|
1281
|
+
continue;
|
|
1610
1282
|
}
|
|
1611
1283
|
entries.push({
|
|
1612
1284
|
filePath: normalizeAbsolutePath(path.join(config.rootDir, fileValue)),
|
|
1613
1285
|
reason: reasonValue.trim()
|
|
1614
1286
|
});
|
|
1615
|
-
}
|
|
1287
|
+
}
|
|
1616
1288
|
return {
|
|
1617
1289
|
entries,
|
|
1618
1290
|
problems
|
|
@@ -1623,45 +1295,123 @@ function addCoverage(coverageByFile, filePath, source) {
|
|
|
1623
1295
|
sources.push(source);
|
|
1624
1296
|
coverageByFile.set(filePath, sources);
|
|
1625
1297
|
}
|
|
1298
|
+
function findTypeScriptFamilyChecker(config) {
|
|
1299
|
+
return getActiveCheckers(config).find((checker) => typeScriptFamilyCheckerPresets.has(checker.preset)) ?? null;
|
|
1300
|
+
}
|
|
1301
|
+
function addPresetJavaScriptConfigCoverage(options) {
|
|
1302
|
+
const checker = findTypeScriptFamilyChecker(options.config);
|
|
1303
|
+
if (!checker) return;
|
|
1304
|
+
for (const filePath of options.sourceFiles) {
|
|
1305
|
+
if (options.coverageByFile.has(filePath) || !javaScriptConfigFilePattern.test(filePath)) continue;
|
|
1306
|
+
addCoverage(options.coverageByFile, filePath, {
|
|
1307
|
+
label: `${checker.name}:${checker.preset} preset`,
|
|
1308
|
+
type: "preset"
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
function parseProjectCoverageFileNames(options) {
|
|
1313
|
+
return parseCheckerProjectConfigForContext({
|
|
1314
|
+
configPath: options.configPath,
|
|
1315
|
+
context: options.context,
|
|
1316
|
+
projectRootDir: options.config.rootDir
|
|
1317
|
+
}).fileNames;
|
|
1318
|
+
}
|
|
1626
1319
|
function collectCoverage(options) {
|
|
1627
1320
|
const coverageByFile = /* @__PURE__ */ new Map();
|
|
1628
|
-
for (const route of options.graphRoutes) for (const graphProjectPath of route.projectPaths)
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1321
|
+
for (const route of options.graphRoutes) for (const graphProjectPath of route.projectPaths) {
|
|
1322
|
+
const projectContext = createCheckerProjectContext({
|
|
1323
|
+
config: options.config,
|
|
1324
|
+
configPath: graphProjectPath,
|
|
1325
|
+
extensions: route.extensions,
|
|
1326
|
+
preset: route.checkerPreset
|
|
1633
1327
|
});
|
|
1328
|
+
for (const filePath of parseProjectCoverageFileNames({
|
|
1329
|
+
config: options.config,
|
|
1330
|
+
configPath: graphProjectPath,
|
|
1331
|
+
context: projectContext
|
|
1332
|
+
})) {
|
|
1333
|
+
const coverageSource = {
|
|
1334
|
+
label: toRelativePath(options.config.rootDir, graphProjectPath),
|
|
1335
|
+
type: "graph"
|
|
1336
|
+
};
|
|
1337
|
+
if (!options.sourceFiles.has(filePath)) {
|
|
1338
|
+
if (options.outsideSourceCoverageByFile) addCoverage(options.outsideSourceCoverageByFile, filePath, coverageSource);
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
addCoverage(coverageByFile, filePath, coverageSource);
|
|
1342
|
+
}
|
|
1634
1343
|
}
|
|
1635
|
-
for (const checkerTarget of options.checkerTargets) for (const configPath of checkerTarget.coverageConfigPaths)
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1344
|
+
for (const checkerTarget of options.checkerTargets) for (const configPath of checkerTarget.coverageConfigPaths) {
|
|
1345
|
+
const projectContext = createCheckerProjectContext({
|
|
1346
|
+
config: options.config,
|
|
1347
|
+
configPath,
|
|
1348
|
+
extensions: getCheckerCoverageExtensions(checkerTarget.checker),
|
|
1349
|
+
preset: checkerTarget.checker.preset
|
|
1640
1350
|
});
|
|
1351
|
+
for (const filePath of parseProjectCoverageFileNames({
|
|
1352
|
+
config: options.config,
|
|
1353
|
+
configPath,
|
|
1354
|
+
context: projectContext
|
|
1355
|
+
})) {
|
|
1356
|
+
const coverageSource = {
|
|
1357
|
+
label: `${toRelativePath(options.config.rootDir, configPath)} via ${checkerTarget.label}`,
|
|
1358
|
+
type: "checker"
|
|
1359
|
+
};
|
|
1360
|
+
if (!options.sourceFiles.has(filePath)) {
|
|
1361
|
+
if (options.outsideSourceCoverageByFile) addCoverage(options.outsideSourceCoverageByFile, filePath, coverageSource);
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
addCoverage(coverageByFile, filePath, coverageSource);
|
|
1365
|
+
}
|
|
1641
1366
|
}
|
|
1642
|
-
|
|
1367
|
+
addPresetJavaScriptConfigCoverage({
|
|
1368
|
+
config: options.config,
|
|
1369
|
+
coverageByFile,
|
|
1370
|
+
sourceFiles: options.sourceFiles
|
|
1371
|
+
});
|
|
1372
|
+
return coverageByFile;
|
|
1373
|
+
}
|
|
1374
|
+
function cloneCoverageByFile(coverageByFile) {
|
|
1375
|
+
return new Map([...coverageByFile.entries()].map(([filePath, sources]) => [filePath, [...sources]]));
|
|
1376
|
+
}
|
|
1377
|
+
function addAllowlistCoverage(options) {
|
|
1378
|
+
for (const entry of options.allowlistEntries) {
|
|
1643
1379
|
if (!options.sourceFiles.has(entry.filePath)) continue;
|
|
1644
|
-
addCoverage(coverageByFile, entry.filePath, {
|
|
1380
|
+
addCoverage(options.coverageByFile, entry.filePath, {
|
|
1645
1381
|
label: entry.reason,
|
|
1646
1382
|
type: "allowlist"
|
|
1647
1383
|
});
|
|
1648
1384
|
}
|
|
1649
|
-
return coverageByFile;
|
|
1650
1385
|
}
|
|
1651
|
-
function
|
|
1652
|
-
const
|
|
1386
|
+
function collectProjectContextsByPath(config, routes) {
|
|
1387
|
+
const projectContextsByPath = /* @__PURE__ */ new Map();
|
|
1653
1388
|
for (const route of routes) for (const projectPath of route.projectPaths) {
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1389
|
+
const existingContext = projectContextsByPath.get(projectPath) ?? {
|
|
1390
|
+
checkerPresets: [],
|
|
1391
|
+
extensions: []
|
|
1392
|
+
};
|
|
1393
|
+
const routeContext = createCheckerProjectContext({
|
|
1394
|
+
config,
|
|
1395
|
+
configPath: projectPath,
|
|
1396
|
+
extensions: route.extensions,
|
|
1397
|
+
preset: route.checkerPreset
|
|
1398
|
+
});
|
|
1399
|
+
projectContextsByPath.set(projectPath, {
|
|
1400
|
+
checkerPresets: [...new Set([...existingContext.checkerPresets, ...routeContext.checkerPresets])],
|
|
1401
|
+
extensions: normalizeExtensions([...existingContext.extensions, ...routeContext.extensions])
|
|
1402
|
+
});
|
|
1656
1403
|
}
|
|
1657
|
-
return
|
|
1404
|
+
return projectContextsByPath;
|
|
1658
1405
|
}
|
|
1659
|
-
function parseConfig(config, configPath,
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1406
|
+
function parseConfig(config, configPath, context = {
|
|
1407
|
+
checkerPresets: [],
|
|
1408
|
+
extensions: []
|
|
1409
|
+
}) {
|
|
1410
|
+
const parsed = parseCheckerProjectConfigForContext({
|
|
1411
|
+
configPath,
|
|
1412
|
+
context,
|
|
1413
|
+
projectRootDir: config.rootDir
|
|
1414
|
+
});
|
|
1665
1415
|
return {
|
|
1666
1416
|
fileNames: parsed.fileNames.map(normalizeAbsolutePath).sort(),
|
|
1667
1417
|
options: parsed.options
|
|
@@ -1711,8 +1461,55 @@ function addDtsConfigSemanticProblems(options) {
|
|
|
1711
1461
|
].join("\n"));
|
|
1712
1462
|
}
|
|
1713
1463
|
}
|
|
1464
|
+
function getDtsConfigPathForTypecheckConfig(configPath) {
|
|
1465
|
+
const directory = path.dirname(configPath);
|
|
1466
|
+
const fileName = path.basename(configPath);
|
|
1467
|
+
const dtsFileName = fileName === "tsconfig.json" ? "tsconfig.dts.json" : fileName.replace(/\.json$/u, ".dts.json");
|
|
1468
|
+
return normalizeAbsolutePath(path.join(directory, dtsFileName));
|
|
1469
|
+
}
|
|
1470
|
+
function isDefaultTypecheckAggregator(configObject) {
|
|
1471
|
+
return Object.hasOwn(configObject, "references");
|
|
1472
|
+
}
|
|
1473
|
+
function normalizeRawExtends(value) {
|
|
1474
|
+
if (typeof value === "string") return [value];
|
|
1475
|
+
if (!Array.isArray(value)) return [];
|
|
1476
|
+
return value.filter((entry) => typeof entry === "string");
|
|
1477
|
+
}
|
|
1478
|
+
function resolveRawExtendsPath(configPath, rawExtends) {
|
|
1479
|
+
const resolvedPath = path.resolve(path.dirname(configPath), rawExtends);
|
|
1480
|
+
return normalizeAbsolutePath(path.extname(resolvedPath) ? resolvedPath : `${resolvedPath}.json`);
|
|
1481
|
+
}
|
|
1482
|
+
function configExtendsPathTransitively(options) {
|
|
1483
|
+
const visited = new Set([options.configPath]);
|
|
1484
|
+
const pending = normalizeRawExtends(options.configObject.extends).map((entry) => resolveRawExtendsPath(options.configPath, entry));
|
|
1485
|
+
for (const configPath of pending) {
|
|
1486
|
+
if (configPath === options.targetConfigPath) return true;
|
|
1487
|
+
if (visited.has(configPath) || !existsSync(configPath)) continue;
|
|
1488
|
+
visited.add(configPath);
|
|
1489
|
+
const configObject = readJsonConfig(options.config, configPath);
|
|
1490
|
+
pending.push(...normalizeRawExtends(configObject.extends).map((entry) => resolveRawExtendsPath(configPath, entry)));
|
|
1491
|
+
}
|
|
1492
|
+
return false;
|
|
1493
|
+
}
|
|
1494
|
+
function addStrictDtsCompanionExtendsProblems(options) {
|
|
1495
|
+
const rawExtends = normalizeRawExtends(options.configObject.extends);
|
|
1496
|
+
if (configExtendsPathTransitively({
|
|
1497
|
+
config: options.config,
|
|
1498
|
+
configObject: options.configObject,
|
|
1499
|
+
configPath: options.dtsConfigPath,
|
|
1500
|
+
targetConfigPath: options.localConfigPath
|
|
1501
|
+
})) return;
|
|
1502
|
+
options.problems.push([
|
|
1503
|
+
"Strict mode requires declaration leaves to transitively extend their companion typecheck config:",
|
|
1504
|
+
` declaration leaf: ${toRelativePath(options.config.rootDir, options.dtsConfigPath)}`,
|
|
1505
|
+
` expected companion: ${toRelativePath(options.config.rootDir, options.localConfigPath)}`,
|
|
1506
|
+
` direct extends: ${rawExtends.length > 0 ? rawExtends.join(", ") : "(none)"}`,
|
|
1507
|
+
" reason: strict: true requires tsconfig*.dts.json to add only declaration/build output behavior on top of the matching tsconfig*.json."
|
|
1508
|
+
].join("\n"));
|
|
1509
|
+
}
|
|
1714
1510
|
function addDtsConfigProblems(options) {
|
|
1715
1511
|
for (const configPath of options.dtsConfigPaths) {
|
|
1512
|
+
const configObject = readJsonConfig(options.config, configPath);
|
|
1716
1513
|
if (!options.graphProjectPaths.has(configPath)) options.problems.push(["DTS config is not reachable from any checker entry:", ` config: ${toRelativePath(options.config.rootDir, configPath)}`].join("\n"));
|
|
1717
1514
|
const localConfigPath = getDtsCompanionConfigPath(configPath);
|
|
1718
1515
|
if (!existsSync(localConfigPath)) {
|
|
@@ -1723,9 +1520,16 @@ function addDtsConfigProblems(options) {
|
|
|
1723
1520
|
].join("\n"));
|
|
1724
1521
|
continue;
|
|
1725
1522
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1523
|
+
if (isStrictConfig(options.config)) addStrictDtsCompanionExtendsProblems({
|
|
1524
|
+
config: options.config,
|
|
1525
|
+
configObject,
|
|
1526
|
+
dtsConfigPath: configPath,
|
|
1527
|
+
localConfigPath,
|
|
1528
|
+
problems: options.problems
|
|
1529
|
+
});
|
|
1530
|
+
const context = options.projectContextsByPath.get(configPath);
|
|
1531
|
+
const dtsConfig = parseConfig(options.config, configPath, context);
|
|
1532
|
+
const localConfig = parseConfig(options.config, localConfigPath, context);
|
|
1729
1533
|
if (dtsConfig.options.composite !== true) options.problems.push([
|
|
1730
1534
|
"DTS config is not valid for tsc -b:",
|
|
1731
1535
|
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
@@ -1800,6 +1604,21 @@ function addBuildGraphConfigProblems(options) {
|
|
|
1800
1604
|
problems: options.problems,
|
|
1801
1605
|
role: "build graph"
|
|
1802
1606
|
});
|
|
1607
|
+
if (!isStrictConfig(options.config)) continue;
|
|
1608
|
+
if (!Array.isArray(configObject.references)) continue;
|
|
1609
|
+
for (const [index, reference] of configObject.references.entries()) {
|
|
1610
|
+
if (!isPlainRecord(reference) || typeof reference.path !== "string") continue;
|
|
1611
|
+
const referencePath = resolveReferencePath(configPath, reference.path);
|
|
1612
|
+
if (isBuildGraphConfigPath(referencePath) || isDtsConfigPath(referencePath)) continue;
|
|
1613
|
+
options.problems.push([
|
|
1614
|
+
"Strict mode build graph references a non-build project:",
|
|
1615
|
+
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1616
|
+
` field: references[${index}].path`,
|
|
1617
|
+
` reference: ${reference.path}`,
|
|
1618
|
+
` resolved: ${toRelativePath(options.config.rootDir, referencePath)}`,
|
|
1619
|
+
" reason: strict: true requires tsconfig*.build.json to reference only tsconfig*.build.json aggregators or tsconfig*.dts.json declaration leaves."
|
|
1620
|
+
].join("\n"));
|
|
1621
|
+
}
|
|
1803
1622
|
}
|
|
1804
1623
|
}
|
|
1805
1624
|
function addDefaultTsconfigShapeProblems(options) {
|
|
@@ -1814,10 +1633,10 @@ function addDefaultTsconfigShapeProblems(options) {
|
|
|
1814
1633
|
role: "tsconfig.json"
|
|
1815
1634
|
});
|
|
1816
1635
|
if (!Array.isArray(configObject.references)) continue;
|
|
1817
|
-
configObject.references.
|
|
1818
|
-
if (!isPlainRecord(reference) || typeof reference.path !== "string")
|
|
1636
|
+
for (const [index, reference] of configObject.references.entries()) {
|
|
1637
|
+
if (!isPlainRecord(reference) || typeof reference.path !== "string") continue;
|
|
1819
1638
|
const referencePath = resolveReferencePath(configPath, reference.path);
|
|
1820
|
-
if (isOrdinaryTypecheckConfigPath(referencePath))
|
|
1639
|
+
if (isOrdinaryTypecheckConfigPath(referencePath)) continue;
|
|
1821
1640
|
options.problems.push([
|
|
1822
1641
|
"Default tsconfig.json references a non-typecheck config:",
|
|
1823
1642
|
` config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
@@ -1826,7 +1645,7 @@ function addDefaultTsconfigShapeProblems(options) {
|
|
|
1826
1645
|
` resolved: ${toRelativePath(options.config.rootDir, referencePath)}`,
|
|
1827
1646
|
" reason: tsconfig.json is the default IDE/typecheck entry and must not reference declaration build graph configs."
|
|
1828
1647
|
].join("\n"));
|
|
1829
|
-
}
|
|
1648
|
+
}
|
|
1830
1649
|
}
|
|
1831
1650
|
}
|
|
1832
1651
|
function addDefaultTsconfigEnvironmentProblems(options) {
|
|
@@ -1870,10 +1689,19 @@ function collectConfigFileOwners(config, graphRoutes, sourceFiles) {
|
|
|
1870
1689
|
const ownersByFile = /* @__PURE__ */ new Map();
|
|
1871
1690
|
for (const route of graphRoutes) for (const configPath of route.projectPaths) {
|
|
1872
1691
|
if (!existsSync(configPath)) continue;
|
|
1873
|
-
|
|
1692
|
+
const projectContext = createCheckerProjectContext({
|
|
1693
|
+
config,
|
|
1694
|
+
configPath,
|
|
1695
|
+
extensions: route.extensions,
|
|
1696
|
+
preset: route.checkerPreset
|
|
1697
|
+
});
|
|
1698
|
+
for (const filePath of parseProjectFileNamesForExtensions(config, configPath, projectContext)) {
|
|
1874
1699
|
if (!sourceFiles.has(filePath)) continue;
|
|
1875
1700
|
const owners = ownersByFile.get(filePath) ?? [];
|
|
1876
|
-
owners.push(
|
|
1701
|
+
owners.push({
|
|
1702
|
+
checkerPreset: route.checkerPreset,
|
|
1703
|
+
configPath
|
|
1704
|
+
});
|
|
1877
1705
|
ownersByFile.set(filePath, owners);
|
|
1878
1706
|
}
|
|
1879
1707
|
}
|
|
@@ -1881,14 +1709,60 @@ function collectConfigFileOwners(config, graphRoutes, sourceFiles) {
|
|
|
1881
1709
|
}
|
|
1882
1710
|
function addDuplicateGraphCoverageProblems(options) {
|
|
1883
1711
|
for (const [filePath, owners] of [...options.ownersByFile.entries()].sort(([left], [right]) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right)))) {
|
|
1712
|
+
const ownersByPreset = /* @__PURE__ */ new Map();
|
|
1713
|
+
for (const owner of owners) {
|
|
1714
|
+
const presetOwners = ownersByPreset.get(owner.checkerPreset) ?? [];
|
|
1715
|
+
presetOwners.push(owner);
|
|
1716
|
+
ownersByPreset.set(owner.checkerPreset, presetOwners);
|
|
1717
|
+
}
|
|
1718
|
+
for (const presetOwners of ownersByPreset.values()) {
|
|
1719
|
+
const uniqueOwners = [...new Set(presetOwners.map((owner) => owner.configPath))];
|
|
1720
|
+
if (uniqueOwners.length <= 1) continue;
|
|
1721
|
+
options.problems.push([
|
|
1722
|
+
"Duplicate checker graph coverage:",
|
|
1723
|
+
` file: ${toRelativePath(options.config.rootDir, filePath)}`,
|
|
1724
|
+
" covered by:",
|
|
1725
|
+
...uniqueOwners.sort((left, right) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right))).map((configPath) => ` - ${toRelativePath(options.config.rootDir, configPath)}`),
|
|
1726
|
+
" reason: a checker graph file must have a single declaration owner; move the file to one dts leaf or narrow include/exclude patterns."
|
|
1727
|
+
].join("\n"));
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
function addStrictOrdinaryTypecheckCompanionProblems(options) {
|
|
1732
|
+
for (const configPath of options.ordinaryConfigPaths) {
|
|
1733
|
+
const configObject = readJsonConfig(options.config, configPath);
|
|
1734
|
+
if (path.basename(configPath) === "tsconfig.json" && isDefaultTypecheckAggregator(configObject)) continue;
|
|
1735
|
+
const expectedDtsConfigPath = getDtsConfigPathForTypecheckConfig(configPath);
|
|
1736
|
+
if (existsSync(expectedDtsConfigPath)) continue;
|
|
1737
|
+
options.problems.push([
|
|
1738
|
+
"Strict mode typecheck config is missing its declaration leaf:",
|
|
1739
|
+
` typecheck config: ${toRelativePath(options.config.rootDir, configPath)}`,
|
|
1740
|
+
` expected declaration leaf: ${toRelativePath(options.config.rootDir, expectedDtsConfigPath)}`,
|
|
1741
|
+
" reason: strict: true requires every tsconfig*.json typecheck leaf to have a same-named tsconfig*.dts.json build leaf."
|
|
1742
|
+
].join("\n"));
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function addStrictDuplicateTypecheckOwnershipProblems(options) {
|
|
1746
|
+
const fileOwners = /* @__PURE__ */ new Map();
|
|
1747
|
+
const context = getActiveCheckerContext(options.config);
|
|
1748
|
+
for (const configPath of options.ordinaryConfigPaths) {
|
|
1749
|
+
const configObject = readJsonConfig(options.config, configPath);
|
|
1750
|
+
if (path.basename(configPath) === "tsconfig.json" && isDefaultTypecheckAggregator(configObject)) continue;
|
|
1751
|
+
for (const fileName of parseConfig(options.config, configPath, context).fileNames) {
|
|
1752
|
+
const owners = fileOwners.get(fileName) ?? [];
|
|
1753
|
+
owners.push(configPath);
|
|
1754
|
+
fileOwners.set(fileName, owners);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
for (const [fileName, owners] of [...fileOwners.entries()].sort(([left], [right]) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right)))) {
|
|
1884
1758
|
const uniqueOwners = [...new Set(owners)];
|
|
1885
1759
|
if (uniqueOwners.length <= 1) continue;
|
|
1886
1760
|
options.problems.push([
|
|
1887
|
-
"
|
|
1888
|
-
` file: ${toRelativePath(options.config.rootDir,
|
|
1889
|
-
"
|
|
1890
|
-
...uniqueOwners.sort((left, right) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right))).map((
|
|
1891
|
-
" reason:
|
|
1761
|
+
"Strict mode source file belongs to multiple typecheck configs:",
|
|
1762
|
+
` file: ${toRelativePath(options.config.rootDir, fileName)}`,
|
|
1763
|
+
" typecheck configs:",
|
|
1764
|
+
...uniqueOwners.sort((left, right) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right))).map((owner) => ` - ${toRelativePath(options.config.rootDir, owner)}`),
|
|
1765
|
+
" reason: strict: true requires each source module to belong to exactly one tsconfig*.json typecheck leaf."
|
|
1892
1766
|
].join("\n"));
|
|
1893
1767
|
}
|
|
1894
1768
|
}
|
|
@@ -1919,25 +1793,39 @@ function addUncoveredSourceProblems(options) {
|
|
|
1919
1793
|
" reason: every file in config.source must be covered by a checker entry or an explicit allowlist entry."
|
|
1920
1794
|
].filter(Boolean).join("\n"));
|
|
1921
1795
|
}
|
|
1796
|
+
function addSourceBoundaryMismatchProblems(options) {
|
|
1797
|
+
const outsideSourceFiles = [...options.outsideSourceCoverageByFile.entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
1798
|
+
if (outsideSourceFiles.length === 0) return;
|
|
1799
|
+
options.problems.push([
|
|
1800
|
+
"Typecheck proof source boundary does not match tsconfig coverage:",
|
|
1801
|
+
...outsideSourceFiles.slice(0, 20).flatMap(([filePath, sources]) => [
|
|
1802
|
+
` - ${toRelativePath(options.config.rootDir, filePath)}`,
|
|
1803
|
+
...sources.slice(0, 3).map((source) => ` covered by: ${source.label}`),
|
|
1804
|
+
sources.length > 3 ? ` ... ${sources.length - 3} more` : ""
|
|
1805
|
+
]),
|
|
1806
|
+
outsideSourceFiles.length > 20 ? ` ... ${outsideSourceFiles.length - 20} more` : "",
|
|
1807
|
+
" reason: config.source and tsconfig*.json coverage describe different module sets.",
|
|
1808
|
+
" fix: include these files in config.source, exclude them from the related tsconfig*.json, or move intentionally unmanaged files out of checker coverage."
|
|
1809
|
+
].filter(Boolean).join("\n"));
|
|
1810
|
+
}
|
|
1922
1811
|
async function runProofCheckInternal(config, options = {}) {
|
|
1923
1812
|
const problems = [];
|
|
1924
1813
|
const graphRouteCollection = collectGraphProjectRoutes(config);
|
|
1925
1814
|
const entryRouteCollection = collectCheckerEntryProjectRoutes(config);
|
|
1926
1815
|
const entryProjectPaths = [...new Set(entryRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
|
|
1927
1816
|
const entryProjectPathSet = new Set(entryProjectPaths);
|
|
1928
|
-
const
|
|
1817
|
+
const entryProjectContextsByPath = collectProjectContextsByPath(config, entryRouteCollection.routes);
|
|
1929
1818
|
const dtsConfigPaths = await collectDtsConfigPaths(config);
|
|
1930
1819
|
const buildGraphConfigPaths = await collectBuildGraphConfigPaths(config);
|
|
1931
1820
|
const defaultTsconfigPaths = await collectDefaultTsconfigPaths(config);
|
|
1932
1821
|
const ordinaryTypecheckConfigPaths = await collectOrdinaryTypecheckConfigPaths(config);
|
|
1933
|
-
problems.push(...graphRouteCollection.problems);
|
|
1934
|
-
problems.push(...entryRouteCollection.problems);
|
|
1822
|
+
problems.push(...graphRouteCollection.problems, ...entryRouteCollection.problems);
|
|
1935
1823
|
addDtsConfigProblems({
|
|
1936
1824
|
config,
|
|
1937
1825
|
dtsConfigPaths,
|
|
1938
1826
|
graphProjectPaths: entryProjectPathSet,
|
|
1939
1827
|
problems,
|
|
1940
|
-
|
|
1828
|
+
projectContextsByPath: entryProjectContextsByPath
|
|
1941
1829
|
});
|
|
1942
1830
|
addBuildGraphConfigProblems({
|
|
1943
1831
|
buildGraphConfigPaths,
|
|
@@ -1954,6 +1842,18 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1954
1842
|
ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
|
|
1955
1843
|
problems
|
|
1956
1844
|
});
|
|
1845
|
+
if (isStrictConfig(config)) {
|
|
1846
|
+
addStrictOrdinaryTypecheckCompanionProblems({
|
|
1847
|
+
config,
|
|
1848
|
+
ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
|
|
1849
|
+
problems
|
|
1850
|
+
});
|
|
1851
|
+
addStrictDuplicateTypecheckOwnershipProblems({
|
|
1852
|
+
config,
|
|
1853
|
+
ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
|
|
1854
|
+
problems
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1957
1857
|
if (problems.length > 0) {
|
|
1958
1858
|
ProofLogger.error(problems.join("\n\n"));
|
|
1959
1859
|
return false;
|
|
@@ -1969,19 +1869,18 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
1969
1869
|
const allowlistCollection = collectConfiguredAllowlistEntries(config);
|
|
1970
1870
|
const allowlistEntries = allowlistCollection.entries;
|
|
1971
1871
|
problems.push(...allowlistCollection.problems);
|
|
1872
|
+
const outsideSourceCoverageByFile = /* @__PURE__ */ new Map();
|
|
1972
1873
|
const baseCoverageByFile = collectCoverage({
|
|
1973
|
-
allowlistEntries,
|
|
1974
1874
|
checkerTargets,
|
|
1975
1875
|
config,
|
|
1976
1876
|
graphRoutes: graphRouteCollection.routes,
|
|
1977
|
-
|
|
1877
|
+
outsideSourceCoverageByFile,
|
|
1978
1878
|
sourceFiles
|
|
1979
1879
|
});
|
|
1980
|
-
const coverageByFile =
|
|
1880
|
+
const coverageByFile = cloneCoverageByFile(baseCoverageByFile);
|
|
1881
|
+
addAllowlistCoverage({
|
|
1981
1882
|
allowlistEntries,
|
|
1982
|
-
|
|
1983
|
-
config,
|
|
1984
|
-
graphRoutes: graphRouteCollection.routes,
|
|
1883
|
+
coverageByFile,
|
|
1985
1884
|
sourceFiles
|
|
1986
1885
|
});
|
|
1987
1886
|
addDuplicateGraphCoverageProblems({
|
|
@@ -2002,6 +1901,11 @@ async function runProofCheckInternal(config, options = {}) {
|
|
|
2002
1901
|
problems,
|
|
2003
1902
|
sourceFiles
|
|
2004
1903
|
});
|
|
1904
|
+
addSourceBoundaryMismatchProblems({
|
|
1905
|
+
config,
|
|
1906
|
+
outsideSourceCoverageByFile,
|
|
1907
|
+
problems
|
|
1908
|
+
});
|
|
2005
1909
|
if (problems.length > 0) {
|
|
2006
1910
|
ProofLogger.error(problems.join("\n\n"));
|
|
2007
1911
|
return false;
|
|
@@ -2044,9 +1948,68 @@ async function runProofCheck(config, options = {}) {
|
|
|
2044
1948
|
var PackageReleaseConsistencyError = class extends Error {
|
|
2045
1949
|
name = "PackageReleaseConsistencyError";
|
|
2046
1950
|
};
|
|
2047
|
-
const
|
|
1951
|
+
const require = createRequire(import.meta.url);
|
|
1952
|
+
const semver = require("semver");
|
|
1953
|
+
const { NpmPackageJsonLint } = require("npm-package-json-lint");
|
|
1954
|
+
const picomatch = rawPicomatch;
|
|
1955
|
+
const DEFAULT_CONTENT_HASH_BASELINE_TAG = "latest";
|
|
1956
|
+
const CONTENT_HASH_DIFF_KINDS = [
|
|
1957
|
+
"local-only",
|
|
1958
|
+
"remote-only",
|
|
1959
|
+
"changed"
|
|
1960
|
+
];
|
|
2048
1961
|
const REQUIRED_RELEASE_FILES = ["README.md", "LICENSE.md"];
|
|
2049
|
-
const
|
|
1962
|
+
const ARTIFACT_HASH_IGNORED_FILES = new Set([
|
|
1963
|
+
"README",
|
|
1964
|
+
"README.md",
|
|
1965
|
+
"CHANGELOG.md",
|
|
1966
|
+
"HISTORY.md",
|
|
1967
|
+
"CONTRIBUTING.md",
|
|
1968
|
+
"CODE_OF_CONDUCT.md",
|
|
1969
|
+
"SECURITY.md"
|
|
1970
|
+
]);
|
|
1971
|
+
const SOURCE_MAPPING_URL_PATTERN = /\/\/\s*#\s*sourceMappingURL\s*=|\/\*\s*#\s*sourceMappingURL\s*=/u;
|
|
1972
|
+
const PACKED_MANIFEST_LINT_CONFIG = { rules: {
|
|
1973
|
+
"bin-type": "error",
|
|
1974
|
+
"bundledDependencies-type": "error",
|
|
1975
|
+
"config-type": "error",
|
|
1976
|
+
"cpu-type": "error",
|
|
1977
|
+
"dependencies-type": "error",
|
|
1978
|
+
"description-type": "error",
|
|
1979
|
+
"devDependencies-type": "error",
|
|
1980
|
+
"directories-type": "error",
|
|
1981
|
+
"engines-type": "error",
|
|
1982
|
+
"files-type": "error",
|
|
1983
|
+
"homepage-type": "error",
|
|
1984
|
+
"keywords-type": "error",
|
|
1985
|
+
"license-type": "error",
|
|
1986
|
+
"main-type": "error",
|
|
1987
|
+
"man-type": "error",
|
|
1988
|
+
"name-format": "error",
|
|
1989
|
+
"name-type": "error",
|
|
1990
|
+
"no-archive-dependencies": "error",
|
|
1991
|
+
"no-archive-devDependencies": "error",
|
|
1992
|
+
"no-file-dependencies": "error",
|
|
1993
|
+
"no-file-devDependencies": "error",
|
|
1994
|
+
"no-git-dependencies": "error",
|
|
1995
|
+
"no-git-devDependencies": "error",
|
|
1996
|
+
"no-repeated-dependencies": "error",
|
|
1997
|
+
"optionalDependencies-type": "error",
|
|
1998
|
+
"os-type": "error",
|
|
1999
|
+
"peerDependencies-type": "error",
|
|
2000
|
+
"preferGlobal-type": "error",
|
|
2001
|
+
"private-type": "error",
|
|
2002
|
+
"repository-type": "error",
|
|
2003
|
+
"require-license": "error",
|
|
2004
|
+
"require-name": "error",
|
|
2005
|
+
"require-types": "error",
|
|
2006
|
+
"require-version": "error",
|
|
2007
|
+
"scripts-type": "error",
|
|
2008
|
+
"type-type": "error",
|
|
2009
|
+
"valid-values-private": ["error", [false]],
|
|
2010
|
+
"version-format": "error",
|
|
2011
|
+
"version-type": "error"
|
|
2012
|
+
} };
|
|
2050
2013
|
function isRecord(value) {
|
|
2051
2014
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2052
2015
|
}
|
|
@@ -2058,6 +2021,7 @@ function createReleaseConsistencyState() {
|
|
|
2058
2021
|
directWorkspaceDependencies: [],
|
|
2059
2022
|
edges: /* @__PURE__ */ new Map(),
|
|
2060
2023
|
missingWorkspaceDependencies: [],
|
|
2024
|
+
packedManifestLintProblems: [],
|
|
2061
2025
|
packedManifestProblems: [],
|
|
2062
2026
|
privateWorkspaceDependencies: [],
|
|
2063
2027
|
releaseHygieneProblems: [],
|
|
@@ -2077,6 +2041,36 @@ function collectPublishDependencyEntries(manifest) {
|
|
|
2077
2041
|
});
|
|
2078
2042
|
return entries;
|
|
2079
2043
|
}
|
|
2044
|
+
function collectPackageDependencyEntries(manifest) {
|
|
2045
|
+
const entries = [];
|
|
2046
|
+
const sections = [
|
|
2047
|
+
{
|
|
2048
|
+
dependencies: manifest.dependencies,
|
|
2049
|
+
name: "dependencies"
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
dependencies: manifest.devDependencies,
|
|
2053
|
+
name: "devDependencies"
|
|
2054
|
+
},
|
|
2055
|
+
{
|
|
2056
|
+
dependencies: manifest.peerDependencies,
|
|
2057
|
+
name: "peerDependencies"
|
|
2058
|
+
},
|
|
2059
|
+
{
|
|
2060
|
+
dependencies: manifest.optionalDependencies,
|
|
2061
|
+
name: "optionalDependencies"
|
|
2062
|
+
}
|
|
2063
|
+
];
|
|
2064
|
+
for (const { dependencies, name } of sections) {
|
|
2065
|
+
if (!dependencies) continue;
|
|
2066
|
+
for (const [dependencyName, specifier] of Object.entries(dependencies)) entries.push({
|
|
2067
|
+
dependencyName,
|
|
2068
|
+
sectionName: name,
|
|
2069
|
+
specifier
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
return entries;
|
|
2073
|
+
}
|
|
2080
2074
|
function addEdge(edges, importerName, dependencyName) {
|
|
2081
2075
|
const dependencies = edges.get(importerName) ?? /* @__PURE__ */ new Set();
|
|
2082
2076
|
dependencies.add(dependencyName);
|
|
@@ -2122,126 +2116,319 @@ function findRegistryVersionMetadata(metadata, version) {
|
|
|
2122
2116
|
const versionMetadata = metadata.versions[version];
|
|
2123
2117
|
return isRecord(versionMetadata) ? versionMetadata : null;
|
|
2124
2118
|
}
|
|
2125
|
-
function
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2119
|
+
function findRegistryDistTagVersion(metadata, distTag) {
|
|
2120
|
+
if (!isRecord(metadata["dist-tags"])) return null;
|
|
2121
|
+
const version = metadata["dist-tags"][distTag];
|
|
2122
|
+
return typeof version === "string" && version.trim().length > 0 ? version : null;
|
|
2123
|
+
}
|
|
2124
|
+
function getRegistryTarballUrl(versionMetadata) {
|
|
2125
|
+
if (!isRecord(versionMetadata.dist)) return null;
|
|
2126
|
+
const tarballUrl = versionMetadata.dist.tarball;
|
|
2127
|
+
return typeof tarballUrl === "string" && tarballUrl.trim().length > 0 ? tarballUrl : null;
|
|
2128
|
+
}
|
|
2129
|
+
async function fetchRegistryTarball(tarballUrl) {
|
|
2130
|
+
const response = await fetch(tarballUrl, { headers: { accept: "application/octet-stream" } });
|
|
2131
|
+
if (!response.ok) throw new Error(`unable to download npm tarball ${tarballUrl}: ${response.status} ${response.statusText}`);
|
|
2132
|
+
return Buffer.from(await response.arrayBuffer());
|
|
2133
|
+
}
|
|
2134
|
+
function resolveWorkspacePackageOutputDir(config, workspacePackage) {
|
|
2135
|
+
const configuredEntry = config.package?.entries?.find((entry) => entry.name === workspacePackage.name);
|
|
2136
|
+
return configuredEntry ? path.resolve(config.rootDir, configuredEntry.outDir) : path.join(workspacePackage.directory, "dist");
|
|
2137
|
+
}
|
|
2138
|
+
function isIgnoredArtifactHashFile(relativePath) {
|
|
2139
|
+
return ARTIFACT_HASH_IGNORED_FILES.has(relativePath) || relativePath.startsWith("docs/") || relativePath.startsWith("examples/");
|
|
2140
|
+
}
|
|
2141
|
+
function resolveReleaseContentHashBaselineTag(config, args) {
|
|
2142
|
+
const configuredBaselineTag = config.release?.contentHash?.baselineTag;
|
|
2143
|
+
const baselineTag = typeof configuredBaselineTag === "function" ? configuredBaselineTag(args) : configuredBaselineTag ?? DEFAULT_CONTENT_HASH_BASELINE_TAG;
|
|
2144
|
+
if (typeof baselineTag !== "string" || baselineTag.trim().length === 0) throw new Error("release.contentHash.baselineTag must resolve to a non-empty string");
|
|
2145
|
+
return baselineTag.trim();
|
|
2146
|
+
}
|
|
2147
|
+
function normalizeReleaseContentHashIgnorePatterns(value) {
|
|
2148
|
+
if (!Array.isArray(value)) throw new TypeError("release.contentHash.ignore must resolve to an array of non-empty strings or undefined");
|
|
2149
|
+
return value.map((pattern, index) => {
|
|
2150
|
+
if (typeof pattern !== "string" || pattern.trim().length === 0) throw new Error(`release.contentHash.ignore[${index}] must resolve to a non-empty string`);
|
|
2151
|
+
return pattern.trim();
|
|
2141
2152
|
});
|
|
2142
2153
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2154
|
+
function createUserContentHashIgnoreRules(patterns) {
|
|
2155
|
+
return patterns.map((pattern) => {
|
|
2156
|
+
const matches = picomatch(pattern, { dot: true });
|
|
2157
|
+
return {
|
|
2158
|
+
label: `user "${pattern}"`,
|
|
2159
|
+
matches
|
|
2160
|
+
};
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
function createBuiltinContentHashIgnoreRule() {
|
|
2164
|
+
return {
|
|
2165
|
+
label: "builtin",
|
|
2166
|
+
matches: isIgnoredArtifactHashFile
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
function resolveReleaseContentHashIgnoreRules(config, args) {
|
|
2170
|
+
const contentHash = config.release?.contentHash;
|
|
2171
|
+
const configuredIgnore = contentHash?.ignore;
|
|
2172
|
+
const useBuiltinFallback = contentHash?.builtinIgnore === true;
|
|
2173
|
+
if (configuredIgnore === void 0) return useBuiltinFallback ? [createBuiltinContentHashIgnoreRule()] : [];
|
|
2174
|
+
if (typeof configuredIgnore === "function") {
|
|
2175
|
+
const resolvedIgnore = configuredIgnore(args);
|
|
2176
|
+
if (resolvedIgnore === void 0) return useBuiltinFallback ? [createBuiltinContentHashIgnoreRule()] : [];
|
|
2177
|
+
return createUserContentHashIgnoreRules(normalizeReleaseContentHashIgnorePatterns(resolvedIgnore));
|
|
2178
|
+
}
|
|
2179
|
+
return createUserContentHashIgnoreRules(normalizeReleaseContentHashIgnorePatterns(configuredIgnore));
|
|
2180
|
+
}
|
|
2181
|
+
function createContentHashDiffGroup() {
|
|
2182
|
+
return {
|
|
2183
|
+
changed: [],
|
|
2184
|
+
"local-only": [],
|
|
2185
|
+
"remote-only": []
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
function addContentHashDiff(group, diff) {
|
|
2189
|
+
group[diff.kind].push(diff.relativePath);
|
|
2190
|
+
}
|
|
2191
|
+
function sortContentHashDiffGroup(group) {
|
|
2192
|
+
for (const kind of CONTENT_HASH_DIFF_KINDS) group[kind].sort((a, b) => a.localeCompare(b));
|
|
2193
|
+
}
|
|
2194
|
+
function countContentHashDiffs(group) {
|
|
2195
|
+
return CONTENT_HASH_DIFF_KINDS.reduce((count, kind) => count + group[kind].length, 0);
|
|
2196
|
+
}
|
|
2197
|
+
function hasContentHashDiffs(group) {
|
|
2198
|
+
return countContentHashDiffs(group) > 0;
|
|
2199
|
+
}
|
|
2200
|
+
function readPackedPackageVersion(contentFiles) {
|
|
2201
|
+
const packageJsonFile = contentFiles.find((file) => file.relativePath === "package.json");
|
|
2202
|
+
if (!packageJsonFile) return null;
|
|
2145
2203
|
try {
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2204
|
+
const manifest = JSON.parse(Buffer.from(packageJsonFile.data).toString("utf8"));
|
|
2205
|
+
if (!isRecord(manifest) || typeof manifest.version !== "string") return null;
|
|
2206
|
+
const version = manifest.version.trim();
|
|
2207
|
+
return version.length > 0 ? version : null;
|
|
2208
|
+
} catch {
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
async function readPackedArtifactContent(tarball) {
|
|
2213
|
+
const contentFiles = getPackedContentFiles(await unpackPackedPackage(tarball));
|
|
2214
|
+
return {
|
|
2215
|
+
filesByPath: new Map(contentFiles.map((file) => [file.relativePath, file])),
|
|
2216
|
+
packageVersion: readPackedPackageVersion(contentFiles)
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
function fileDataEquals(left, right) {
|
|
2220
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
2221
|
+
for (let index = 0; index < left.byteLength; index += 1) if (left[index] !== right[index]) return false;
|
|
2222
|
+
return true;
|
|
2223
|
+
}
|
|
2224
|
+
function createContentHashDiffs(options) {
|
|
2225
|
+
const paths = new Set([...options.localArtifact.filesByPath.keys(), ...options.remoteArtifact.filesByPath.keys()]);
|
|
2226
|
+
const diffs = [];
|
|
2227
|
+
for (const relativePath of [...paths].sort((a, b) => a.localeCompare(b))) {
|
|
2228
|
+
const localFile = options.localArtifact.filesByPath.get(relativePath);
|
|
2229
|
+
const remoteFile = options.remoteArtifact.filesByPath.get(relativePath);
|
|
2230
|
+
if (localFile && !remoteFile) {
|
|
2231
|
+
diffs.push({
|
|
2232
|
+
kind: "local-only",
|
|
2233
|
+
relativePath
|
|
2234
|
+
});
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
if (!localFile && remoteFile) {
|
|
2238
|
+
diffs.push({
|
|
2239
|
+
kind: "remote-only",
|
|
2240
|
+
relativePath
|
|
2241
|
+
});
|
|
2242
|
+
continue;
|
|
2243
|
+
}
|
|
2244
|
+
if (localFile && remoteFile && !fileDataEquals(localFile.data, remoteFile.data)) diffs.push({
|
|
2245
|
+
kind: "changed",
|
|
2246
|
+
relativePath
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
return diffs;
|
|
2250
|
+
}
|
|
2251
|
+
function partitionContentHashDiffs(options) {
|
|
2252
|
+
const releaseRelevantDiffs = createContentHashDiffGroup();
|
|
2253
|
+
const ignoredDiffGroups = options.ignoreRules.map((rule) => ({
|
|
2254
|
+
diffs: createContentHashDiffGroup(),
|
|
2255
|
+
label: rule.label
|
|
2256
|
+
}));
|
|
2257
|
+
for (const diff of options.diffs) {
|
|
2258
|
+
const ignoredGroupIndex = options.ignoreRules.findIndex((rule) => rule.matches(diff.relativePath));
|
|
2259
|
+
if (ignoredGroupIndex === -1) {
|
|
2260
|
+
addContentHashDiff(releaseRelevantDiffs, diff);
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2263
|
+
addContentHashDiff(ignoredDiffGroups[ignoredGroupIndex].diffs, diff);
|
|
2264
|
+
}
|
|
2265
|
+
sortContentHashDiffGroup(releaseRelevantDiffs);
|
|
2266
|
+
for (const group of ignoredDiffGroups) sortContentHashDiffGroup(group.diffs);
|
|
2267
|
+
return {
|
|
2268
|
+
ignoredDiffGroups: ignoredDiffGroups.filter((group) => hasContentHashDiffs(group.diffs)),
|
|
2269
|
+
releaseRelevantDiffs
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
function formatReleaseRelevantContentHashDiffs(diffs) {
|
|
2273
|
+
if (!hasContentHashDiffs(diffs)) return [];
|
|
2274
|
+
const lines = ["", "Release-relevant diffs:"];
|
|
2275
|
+
for (const kind of CONTENT_HASH_DIFF_KINDS) {
|
|
2276
|
+
const paths = diffs[kind];
|
|
2277
|
+
if (paths.length === 0) continue;
|
|
2278
|
+
lines.push(` ${kind}:`, ...paths.map((relativePath) => ` ${relativePath}`));
|
|
2279
|
+
}
|
|
2280
|
+
return lines;
|
|
2281
|
+
}
|
|
2282
|
+
function formatIgnoredContentHashDiffs(groups) {
|
|
2283
|
+
if (groups.length === 0) return [];
|
|
2284
|
+
const lines = ["", "Ignored contentHash diffs:"];
|
|
2285
|
+
for (const group of groups) lines.push(` ${group.label}:`, ...CONTENT_HASH_DIFF_KINDS.map((kind) => ` ${kind}: ${group.diffs[kind].length}`));
|
|
2286
|
+
return lines;
|
|
2287
|
+
}
|
|
2288
|
+
function formatContentHashComparisonReport(options) {
|
|
2289
|
+
const { comparison, dependencyName } = options;
|
|
2290
|
+
const status = comparison.matchesBaseline ? "PASS" : "FAIL";
|
|
2291
|
+
const localVersion = comparison.localVersion ?? options.localVersionFallback ?? "(missing version)";
|
|
2292
|
+
return [
|
|
2293
|
+
`[release-check] ${status} ${options.importerName} -> ${dependencyName}`,
|
|
2294
|
+
`Baseline: npm ${options.baselineTag} -> ${dependencyName}@${options.baselineVersion}`,
|
|
2295
|
+
`Local: ${dependencyName}@${localVersion}`,
|
|
2296
|
+
...formatReleaseRelevantContentHashDiffs(comparison.releaseRelevantDiffs),
|
|
2297
|
+
...formatIgnoredContentHashDiffs(comparison.ignoredDiffGroups)
|
|
2298
|
+
].join("\n");
|
|
2299
|
+
}
|
|
2300
|
+
async function compareLocalWorkspacePackageOutputToBaseline(options) {
|
|
2301
|
+
let localPackedTarball;
|
|
2302
|
+
try {
|
|
2303
|
+
const localOutDir = resolveWorkspacePackageOutputDir(options.config, options.workspacePackage);
|
|
2304
|
+
const publishedTarball = await fetchRegistryTarball(options.tarballUrl);
|
|
2305
|
+
localPackedTarball = await packOutputTarball(localOutDir);
|
|
2306
|
+
const [remoteArtifact, localArtifact] = await Promise.all([readPackedArtifactContent(publishedTarball), readPackedArtifactContent(localPackedTarball.tarball)]);
|
|
2307
|
+
const { ignoredDiffGroups, releaseRelevantDiffs } = partitionContentHashDiffs({
|
|
2308
|
+
diffs: createContentHashDiffs({
|
|
2309
|
+
localArtifact,
|
|
2310
|
+
remoteArtifact
|
|
2311
|
+
}),
|
|
2312
|
+
ignoreRules: options.ignoreRules
|
|
2313
|
+
});
|
|
2314
|
+
return {
|
|
2315
|
+
ignoredDiffGroups,
|
|
2316
|
+
localVersion: localArtifact.packageVersion,
|
|
2317
|
+
matchesBaseline: !hasContentHashDiffs(releaseRelevantDiffs),
|
|
2318
|
+
releaseRelevantDiffs
|
|
2319
|
+
};
|
|
2320
|
+
} finally {
|
|
2321
|
+
if (localPackedTarball) await localPackedTarball.cleanup();
|
|
2156
2322
|
}
|
|
2157
|
-
return (await execGitCommand(options.config, [
|
|
2158
|
-
"ls-files",
|
|
2159
|
-
"--others",
|
|
2160
|
-
"--exclude-standard",
|
|
2161
|
-
"--",
|
|
2162
|
-
relativeDirectory
|
|
2163
|
-
])).trim().length > 0;
|
|
2164
2323
|
}
|
|
2165
2324
|
async function verifyWorkspacePackagePublished(options) {
|
|
2166
|
-
const { state, workspacePackage } = options;
|
|
2167
|
-
const
|
|
2168
|
-
|
|
2169
|
-
|
|
2325
|
+
const { importerName, state, workspacePackage } = options;
|
|
2326
|
+
const dependencyName = workspacePackage.name;
|
|
2327
|
+
const problemBase = {
|
|
2328
|
+
dependencyName,
|
|
2329
|
+
importerName,
|
|
2330
|
+
packageName: dependencyName
|
|
2331
|
+
};
|
|
2332
|
+
const contentHashArgs = {
|
|
2333
|
+
dependencyName,
|
|
2334
|
+
importerName
|
|
2335
|
+
};
|
|
2336
|
+
let baselineTag;
|
|
2337
|
+
let ignoreRules;
|
|
2338
|
+
try {
|
|
2339
|
+
baselineTag = resolveReleaseContentHashBaselineTag(options.config, contentHashArgs);
|
|
2340
|
+
ignoreRules = resolveReleaseContentHashIgnoreRules(options.config, contentHashArgs);
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2170
2343
|
state.registryProblems.push({
|
|
2171
|
-
|
|
2172
|
-
message: [
|
|
2173
|
-
packageName: workspacePackage.name
|
|
2344
|
+
...problemBase,
|
|
2345
|
+
message: [`invalid release.contentHash config for ${dependencyName}:`, formatErrorMessage$1(error)].join(" ")
|
|
2174
2346
|
});
|
|
2175
2347
|
return;
|
|
2176
2348
|
}
|
|
2177
2349
|
let metadata;
|
|
2178
2350
|
try {
|
|
2179
|
-
metadata = await fetchRegistryPackageMetadata(
|
|
2351
|
+
metadata = await fetchRegistryPackageMetadata(dependencyName, state);
|
|
2180
2352
|
} catch (error) {
|
|
2181
|
-
state.unpublishedPackageNames.add(
|
|
2353
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2182
2354
|
state.registryProblems.push({
|
|
2183
|
-
|
|
2184
|
-
message: [`unable to read npm registry metadata for ${
|
|
2185
|
-
packageName: workspacePackage.name
|
|
2355
|
+
...problemBase,
|
|
2356
|
+
message: [`unable to read npm registry metadata for ${dependencyName}:`, formatErrorMessage$1(error)].join(" ")
|
|
2186
2357
|
});
|
|
2187
2358
|
return;
|
|
2188
2359
|
}
|
|
2189
2360
|
if (!metadata) {
|
|
2190
|
-
state.unpublishedPackageNames.add(
|
|
2361
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2191
2362
|
state.registryProblems.push({
|
|
2192
|
-
|
|
2193
|
-
message: `${
|
|
2194
|
-
|
|
2363
|
+
...problemBase,
|
|
2364
|
+
message: `${dependencyName} is not published to the npm registry`
|
|
2365
|
+
});
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
const baselineVersion = findRegistryDistTagVersion(metadata, baselineTag);
|
|
2369
|
+
if (!baselineVersion) {
|
|
2370
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2371
|
+
state.registryProblems.push({
|
|
2372
|
+
...problemBase,
|
|
2373
|
+
message: `${dependencyName} registry metadata has no "${baselineTag}" dist-tag`
|
|
2195
2374
|
});
|
|
2196
2375
|
return;
|
|
2197
2376
|
}
|
|
2198
|
-
const versionMetadata = findRegistryVersionMetadata(metadata,
|
|
2377
|
+
const versionMetadata = findRegistryVersionMetadata(metadata, baselineVersion);
|
|
2199
2378
|
if (!versionMetadata) {
|
|
2200
|
-
state.unpublishedPackageNames.add(
|
|
2379
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2201
2380
|
state.registryProblems.push({
|
|
2202
|
-
|
|
2203
|
-
message: `${
|
|
2204
|
-
packageName: workspacePackage.name
|
|
2381
|
+
...problemBase,
|
|
2382
|
+
message: `${dependencyName}@${baselineVersion} is not published to the npm registry`
|
|
2205
2383
|
});
|
|
2206
2384
|
return;
|
|
2207
2385
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2386
|
+
const tarballUrl = getRegistryTarballUrl(versionMetadata);
|
|
2387
|
+
if (!tarballUrl) {
|
|
2388
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2210
2389
|
state.registryProblems.push({
|
|
2211
|
-
|
|
2212
|
-
message:
|
|
2213
|
-
packageName: workspacePackage.name
|
|
2390
|
+
...problemBase,
|
|
2391
|
+
message: `${dependencyName}@${baselineVersion} registry metadata has no dist.tarball`
|
|
2214
2392
|
});
|
|
2215
2393
|
return;
|
|
2216
2394
|
}
|
|
2217
|
-
let
|
|
2395
|
+
let comparison;
|
|
2218
2396
|
try {
|
|
2219
|
-
|
|
2397
|
+
comparison = await compareLocalWorkspacePackageOutputToBaseline({
|
|
2220
2398
|
config: options.config,
|
|
2221
|
-
|
|
2399
|
+
ignoreRules,
|
|
2400
|
+
tarballUrl,
|
|
2222
2401
|
workspacePackage
|
|
2223
2402
|
});
|
|
2224
2403
|
} catch (error) {
|
|
2225
|
-
state.unpublishedPackageNames.add(
|
|
2404
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2226
2405
|
state.registryProblems.push({
|
|
2227
|
-
|
|
2406
|
+
...problemBase,
|
|
2228
2407
|
message: [
|
|
2229
|
-
`unable to compare ${
|
|
2230
|
-
`against
|
|
2408
|
+
`unable to compare local package output for ${dependencyName}`,
|
|
2409
|
+
`against npm ${baselineTag} ${dependencyName}@${baselineVersion}:`,
|
|
2231
2410
|
formatErrorMessage$1(error)
|
|
2232
|
-
].join(" ")
|
|
2233
|
-
packageName: workspacePackage.name
|
|
2411
|
+
].join(" ")
|
|
2234
2412
|
});
|
|
2235
2413
|
return;
|
|
2236
2414
|
}
|
|
2237
|
-
|
|
2238
|
-
|
|
2415
|
+
const comparisonReport = formatContentHashComparisonReport({
|
|
2416
|
+
baselineTag,
|
|
2417
|
+
baselineVersion,
|
|
2418
|
+
comparison,
|
|
2419
|
+
dependencyName,
|
|
2420
|
+
importerName,
|
|
2421
|
+
localVersionFallback: workspacePackage.manifest.version
|
|
2422
|
+
});
|
|
2423
|
+
if (!comparison.matchesBaseline) {
|
|
2424
|
+
state.unpublishedPackageNames.add(dependencyName);
|
|
2239
2425
|
state.registryProblems.push({
|
|
2240
|
-
|
|
2241
|
-
message:
|
|
2242
|
-
packageName: workspacePackage.name
|
|
2426
|
+
...problemBase,
|
|
2427
|
+
message: comparisonReport
|
|
2243
2428
|
});
|
|
2429
|
+
return;
|
|
2244
2430
|
}
|
|
2431
|
+
ReleaseLogger.info(comparisonReport);
|
|
2245
2432
|
}
|
|
2246
2433
|
async function visitWorkspacePackageDependencies(options) {
|
|
2247
2434
|
const { config, importerName, isRoot, manifest, state, workspacePackagesByName } = options;
|
|
@@ -2289,6 +2476,7 @@ async function visitWorkspacePackageDependencies(options) {
|
|
|
2289
2476
|
state.visitedPackages.add(targetPackage.name);
|
|
2290
2477
|
await verifyWorkspacePackagePublished({
|
|
2291
2478
|
config,
|
|
2479
|
+
importerName,
|
|
2292
2480
|
state,
|
|
2293
2481
|
workspacePackage: targetPackage
|
|
2294
2482
|
});
|
|
@@ -2337,6 +2525,27 @@ function readPackedPackageJson(options) {
|
|
|
2337
2525
|
return null;
|
|
2338
2526
|
}
|
|
2339
2527
|
}
|
|
2528
|
+
function formatNpmPackageJsonLintIssue(issue) {
|
|
2529
|
+
return `${issue.lintId} [${issue.node || "package.json"}]: ${issue.lintMessage}`;
|
|
2530
|
+
}
|
|
2531
|
+
function validatePackedManifestLint(options) {
|
|
2532
|
+
const lintResult = new NpmPackageJsonLint({
|
|
2533
|
+
config: PACKED_MANIFEST_LINT_CONFIG,
|
|
2534
|
+
cwd: options.config.rootDir,
|
|
2535
|
+
packageJsonFilePath: path.join(options.outDir, "package.json"),
|
|
2536
|
+
packageJsonObject: options.manifest
|
|
2537
|
+
}).lint();
|
|
2538
|
+
for (const result of lintResult.results) {
|
|
2539
|
+
if (result.errorCount === 0) continue;
|
|
2540
|
+
for (const issue of result.issues) {
|
|
2541
|
+
if (issue.severity !== "error") continue;
|
|
2542
|
+
options.state.packedManifestLintProblems.push({
|
|
2543
|
+
importerName: options.rootPackageName,
|
|
2544
|
+
message: formatNpmPackageJsonLintIssue(issue)
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2340
2549
|
function isJavaScriptPackageFile(relativePath) {
|
|
2341
2550
|
return /\.(?:cjs|mjs|js)$/u.test(relativePath);
|
|
2342
2551
|
}
|
|
@@ -2372,6 +2581,17 @@ function validatePackedManifest(options) {
|
|
|
2372
2581
|
sectionName: entry.sectionName,
|
|
2373
2582
|
specifier: entry.specifier
|
|
2374
2583
|
});
|
|
2584
|
+
if (options.strict) for (const entry of collectPackageDependencyEntries(manifest)) {
|
|
2585
|
+
if (!isLocalPackageDependencySpecifier(entry.specifier)) continue;
|
|
2586
|
+
if (entry.sectionName !== "devDependencies" && (isWorkspaceDependencySpecifier(entry.specifier) || isLinkDependencySpecifier(entry.specifier))) continue;
|
|
2587
|
+
state.packedManifestProblems.push({
|
|
2588
|
+
dependencyName: entry.dependencyName,
|
|
2589
|
+
importerName: rootPackageName,
|
|
2590
|
+
message: "strict packed package manifest must not expose workspace:, link:, file:, or catalog: dependency specifiers in any dependency section",
|
|
2591
|
+
sectionName: entry.sectionName,
|
|
2592
|
+
specifier: entry.specifier
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2375
2595
|
for (const dependency of state.directWorkspaceDependencies) {
|
|
2376
2596
|
const packedSpecifier = getPackedDependencySpecifier(manifest, dependency.dependencyName);
|
|
2377
2597
|
if (!packedSpecifier) {
|
|
@@ -2408,7 +2628,7 @@ function createPublishOrder(rootPackageName, state) {
|
|
|
2408
2628
|
}
|
|
2409
2629
|
function createReleaseConsistencyError(options) {
|
|
2410
2630
|
const { config, label, outDir, rootPackageName, state } = options;
|
|
2411
|
-
if (state.sourceLinkDependencies.length + state.privateWorkspaceDependencies.length + state.missingWorkspaceDependencies.length + state.registryProblems.length + state.releaseHygieneProblems.length + state.packedManifestProblems.length === 0) return null;
|
|
2631
|
+
if (state.sourceLinkDependencies.length + state.privateWorkspaceDependencies.length + state.missingWorkspaceDependencies.length + state.registryProblems.length + state.releaseHygieneProblems.length + state.packedManifestLintProblems.length + state.packedManifestProblems.length === 0) return null;
|
|
2412
2632
|
const publishOrder = createPublishOrder(rootPackageName, state);
|
|
2413
2633
|
const lines = [
|
|
2414
2634
|
`package release check failed for ${label}:`,
|
|
@@ -2418,6 +2638,7 @@ function createReleaseConsistencyError(options) {
|
|
|
2418
2638
|
...formatProblemLines("Source manifest depends on private workspace packages:", state.privateWorkspaceDependencies),
|
|
2419
2639
|
...formatProblemLines("Source manifest has invalid workspace: publish dependencies:", state.missingWorkspaceDependencies),
|
|
2420
2640
|
...formatProblemLines("Workspace packages must be published before this package:", state.registryProblems),
|
|
2641
|
+
...formatProblemLines("Packed package manifest failed npm-package-json-lint:", state.packedManifestLintProblems),
|
|
2421
2642
|
...formatProblemLines("Packed package manifest is inconsistent with workspace publish dependencies:", state.packedManifestProblems)
|
|
2422
2643
|
];
|
|
2423
2644
|
if (publishOrder.length > 1) lines.push("", `Suggested publish order: ${publishOrder.join(" -> ")}`);
|
|
@@ -2449,11 +2670,21 @@ async function assertPackageReleaseConsistency(options) {
|
|
|
2449
2670
|
rootPackageName: options.outputManifest.name,
|
|
2450
2671
|
state
|
|
2451
2672
|
});
|
|
2452
|
-
if (packedManifest)
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2673
|
+
if (packedManifest) {
|
|
2674
|
+
validatePackedManifestLint({
|
|
2675
|
+
config: options.config,
|
|
2676
|
+
manifest: packedManifest,
|
|
2677
|
+
outDir: options.outDir,
|
|
2678
|
+
rootPackageName: options.outputManifest.name,
|
|
2679
|
+
state
|
|
2680
|
+
});
|
|
2681
|
+
validatePackedManifest({
|
|
2682
|
+
manifest: packedManifest,
|
|
2683
|
+
rootPackageName: options.outputManifest.name,
|
|
2684
|
+
state,
|
|
2685
|
+
strict: options.config.strict === true
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2457
2688
|
const error = createReleaseConsistencyError({
|
|
2458
2689
|
config: options.config,
|
|
2459
2690
|
label: options.label,
|
|
@@ -2476,6 +2707,24 @@ function logReleaseCheckPlan(options) {
|
|
|
2476
2707
|
...options.plan.entries.map((entry) => [` - ${entry.label}`, ` outDir: ${toRelativePath(options.config.rootDir, entry.outDir)}`].join("\n"))
|
|
2477
2708
|
].join("\n"));
|
|
2478
2709
|
}
|
|
2710
|
+
function collectStrictOutputManifestProblems(options) {
|
|
2711
|
+
const sections = [
|
|
2712
|
+
"dependencies",
|
|
2713
|
+
"devDependencies",
|
|
2714
|
+
"peerDependencies",
|
|
2715
|
+
"optionalDependencies"
|
|
2716
|
+
];
|
|
2717
|
+
const problems = [];
|
|
2718
|
+
for (const sectionName of sections) {
|
|
2719
|
+
const section = options.manifest[sectionName];
|
|
2720
|
+
if (!section || typeof section !== "object" || Array.isArray(section)) continue;
|
|
2721
|
+
for (const [dependencyName, specifier] of Object.entries(section)) {
|
|
2722
|
+
if (typeof specifier !== "string" || !isLocalPackageDependencySpecifier(specifier)) continue;
|
|
2723
|
+
problems.push([`${options.label}: ${options.manifest.name} -> ${dependencyName} [${sectionName}] (${specifier}): output package manifest must not expose workspace:, link:, file:, or catalog: dependency specifiers when strict: true`, ` output: ${toRelativePath(options.rootDir, options.outDir)}`].join("\n"));
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
return problems;
|
|
2727
|
+
}
|
|
2479
2728
|
async function packReleaseTarball(options) {
|
|
2480
2729
|
const packTask = options.flow?.start(`release tarball: ${options.label}`, { depth: options.flowDepth ?? 0 });
|
|
2481
2730
|
ReleaseLogger.info(`release tarball packing started: ${options.label}`);
|
|
@@ -2501,6 +2750,19 @@ async function runReleaseCheckEntry(options) {
|
|
|
2501
2750
|
label: options.label,
|
|
2502
2751
|
packageJsonPath: outputPackageJsonPath
|
|
2503
2752
|
});
|
|
2753
|
+
const strictOutputProblems = isStrictConfig(options.config) ? collectStrictOutputManifestProblems({
|
|
2754
|
+
label: options.label,
|
|
2755
|
+
manifest: outputManifest,
|
|
2756
|
+
outDir: options.outDir,
|
|
2757
|
+
rootDir: options.config.rootDir
|
|
2758
|
+
}) : [];
|
|
2759
|
+
if (strictOutputProblems.length > 0) throw new PackageReleaseConsistencyError([
|
|
2760
|
+
`package release check failed for ${options.label}:`,
|
|
2761
|
+
` output: ${toRelativePath(options.config.rootDir, options.outDir)}`,
|
|
2762
|
+
"",
|
|
2763
|
+
"Output package manifest is not publish-ready:",
|
|
2764
|
+
...strictOutputProblems.map((problem) => ` - ${problem}`)
|
|
2765
|
+
].join("\n"));
|
|
2504
2766
|
if (outputManifest.private === true) throw new PackageReleaseConsistencyError([
|
|
2505
2767
|
`package release check failed for ${options.label}:`,
|
|
2506
2768
|
` output: ${toRelativePath(options.config.rootDir, options.outDir)}`,
|
|
@@ -2584,6 +2846,7 @@ const builtInTaskNames = new Set([
|
|
|
2584
2846
|
"checker:build",
|
|
2585
2847
|
"checker:typecheck",
|
|
2586
2848
|
"graph:check",
|
|
2849
|
+
"nx:check",
|
|
2587
2850
|
"package:check",
|
|
2588
2851
|
"proof:check",
|
|
2589
2852
|
"release:check",
|
|
@@ -2592,34 +2855,77 @@ const builtInTaskNames = new Set([
|
|
|
2592
2855
|
const defaultCheckPipeline = [
|
|
2593
2856
|
"graph:check",
|
|
2594
2857
|
"source:check",
|
|
2858
|
+
"nx:check",
|
|
2595
2859
|
"proof:check",
|
|
2596
2860
|
"checker:build",
|
|
2597
2861
|
"checker:typecheck"
|
|
2598
2862
|
];
|
|
2599
2863
|
function reportCheckerCapabilities(config, flow) {
|
|
2600
2864
|
if (!flow) return;
|
|
2601
|
-
const
|
|
2602
|
-
const
|
|
2865
|
+
const buildExecution = [];
|
|
2866
|
+
const typecheckExecution = [];
|
|
2867
|
+
const sourceGraph = [];
|
|
2868
|
+
const noSourceGraph = [];
|
|
2603
2869
|
for (const checker of getActiveCheckers(config)) {
|
|
2604
2870
|
const adapter = getCheckerAdapter(checker.preset);
|
|
2605
2871
|
const label = `${checker.name} (${checker.preset})`;
|
|
2606
|
-
if (adapter?.
|
|
2607
|
-
else if (adapter?.
|
|
2872
|
+
if (adapter?.execution === "build") buildExecution.push(label);
|
|
2873
|
+
else if (adapter?.execution === "typecheck") typecheckExecution.push(label);
|
|
2874
|
+
if (adapter?.sourceGraph) sourceGraph.push(label);
|
|
2875
|
+
else noSourceGraph.push(label);
|
|
2608
2876
|
}
|
|
2609
2877
|
flow.info([
|
|
2610
2878
|
"checker capability summary:",
|
|
2611
|
-
` first-class: ${
|
|
2612
|
-
`
|
|
2613
|
-
|
|
2879
|
+
` first-class build execution: ${buildExecution.length > 0 ? buildExecution.join(", ") : "(none)"}`,
|
|
2880
|
+
` second-class typecheck execution: ${typecheckExecution.length > 0 ? typecheckExecution.join(", ") : "(none)"}`,
|
|
2881
|
+
` source graph: ${sourceGraph.length > 0 ? sourceGraph.join(", ") : "(none)"}`,
|
|
2882
|
+
` no source graph: ${noSourceGraph.length > 0 ? noSourceGraph.join(", ") : "(none)"}`,
|
|
2883
|
+
...typecheckExecution.length > 0 ? [" note: second-class checkers run through checker:typecheck; source graph participation is reported separately."] : []
|
|
2614
2884
|
].join("\n"), { depth: 1 });
|
|
2615
2885
|
}
|
|
2616
2886
|
function isBuiltinTaskName(value) {
|
|
2617
2887
|
return builtInTaskNames.has(value);
|
|
2618
2888
|
}
|
|
2889
|
+
function assertNeverTaskName(taskName) {
|
|
2890
|
+
throw new Error(`Unsupported built-in task: ${taskName}`);
|
|
2891
|
+
}
|
|
2619
2892
|
function getPipelineStepLabel(step) {
|
|
2620
2893
|
if (step.type === "task") return step.name;
|
|
2621
2894
|
return [step.command, ...step.args ?? []].join(" ");
|
|
2622
2895
|
}
|
|
2896
|
+
function createCommandStepEnvironment(cwd, step) {
|
|
2897
|
+
const basePath = step.env?.PATH ?? process.env.PATH;
|
|
2898
|
+
return {
|
|
2899
|
+
...process.env,
|
|
2900
|
+
...step.env,
|
|
2901
|
+
PATH: [path.join(cwd, "node_modules/.bin"), basePath].filter(Boolean).join(path.delimiter)
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
function isVueTsgoCommand(command) {
|
|
2905
|
+
const commandName = path.basename(command).toLowerCase();
|
|
2906
|
+
return commandName === "vue-tsgo" || commandName === "vue-tsgo.cmd";
|
|
2907
|
+
}
|
|
2908
|
+
function collectVueTsgoCommandConfigPaths(step, cwd) {
|
|
2909
|
+
if (!isVueTsgoCommand(step.command)) return [];
|
|
2910
|
+
const args = step.args ?? [];
|
|
2911
|
+
const configPaths = [];
|
|
2912
|
+
for (const [index, arg] of args.entries()) {
|
|
2913
|
+
if (arg !== "--build" && arg !== "-b" && arg !== "--project" && arg !== "-p") continue;
|
|
2914
|
+
const configArg = args[index + 1];
|
|
2915
|
+
if (!configArg || configArg.startsWith("-")) continue;
|
|
2916
|
+
configPaths.push(path.resolve(cwd, configArg));
|
|
2917
|
+
}
|
|
2918
|
+
return configPaths.length > 0 ? configPaths : [path.resolve(cwd, "tsconfig.json")];
|
|
2919
|
+
}
|
|
2920
|
+
async function prepareCommandStepCache(step, cwd) {
|
|
2921
|
+
await Promise.all(collectVueTsgoCommandConfigPaths(step, cwd).map((configPath) => prepareVueTsgoCache({
|
|
2922
|
+
args: step.args ?? [],
|
|
2923
|
+
command: step.command,
|
|
2924
|
+
configPath,
|
|
2925
|
+
cwd,
|
|
2926
|
+
label: getPipelineStepLabel(step)
|
|
2927
|
+
})));
|
|
2928
|
+
}
|
|
2623
2929
|
async function runBuiltinTask(config, taskName, options = {}) {
|
|
2624
2930
|
switch (taskName) {
|
|
2625
2931
|
case "graph:check": return runGraphCheck(config, {
|
|
@@ -2637,6 +2943,12 @@ async function runBuiltinTask(config, taskName, options = {}) {
|
|
|
2637
2943
|
flow: options.flow,
|
|
2638
2944
|
flowDepth: 1
|
|
2639
2945
|
});
|
|
2946
|
+
case "nx:check": return !(await runNx(config, {
|
|
2947
|
+
check: true,
|
|
2948
|
+
clearScreen: false,
|
|
2949
|
+
flow: options.flow,
|
|
2950
|
+
flowDepth: 1
|
|
2951
|
+
})).changed;
|
|
2640
2952
|
case "package:check": return runPackageCheck({
|
|
2641
2953
|
clearScreen: false,
|
|
2642
2954
|
config,
|
|
@@ -2667,6 +2979,7 @@ async function runBuiltinTask(config, taskName, options = {}) {
|
|
|
2667
2979
|
flow: options.flow,
|
|
2668
2980
|
flowDepth: 1
|
|
2669
2981
|
})).passed;
|
|
2982
|
+
default: return assertNeverTaskName(taskName);
|
|
2670
2983
|
}
|
|
2671
2984
|
}
|
|
2672
2985
|
function normalizePipelineStep(step) {
|
|
@@ -2683,17 +2996,16 @@ function normalizePipelineStep(step) {
|
|
|
2683
2996
|
type: "command"
|
|
2684
2997
|
};
|
|
2685
2998
|
}
|
|
2686
|
-
function runCommandStep(config, step, options = {}) {
|
|
2999
|
+
async function runCommandStep(config, step, options = {}) {
|
|
2687
3000
|
const label = getPipelineStepLabel(step);
|
|
2688
3001
|
const task = options.flow?.start(`command: ${label}`, { depth: 1 });
|
|
3002
|
+
const cwd = step.cwd ? path.resolve(config.rootDir, step.cwd) : config.rootDir;
|
|
2689
3003
|
const commandOptions = {
|
|
2690
|
-
cwd
|
|
2691
|
-
env:
|
|
2692
|
-
...process.env,
|
|
2693
|
-
...step.env
|
|
2694
|
-
},
|
|
3004
|
+
cwd,
|
|
3005
|
+
env: createCommandStepEnvironment(cwd, step),
|
|
2695
3006
|
shell: process.platform === "win32"
|
|
2696
3007
|
};
|
|
3008
|
+
await prepareCommandStepCache(step, cwd);
|
|
2697
3009
|
if (options.flow?.interactive) return new Promise((resolve, reject) => {
|
|
2698
3010
|
const child = spawn(step.command, step.args ?? [], {
|
|
2699
3011
|
...commandOptions,
|
|
@@ -2833,16 +3145,46 @@ async function main() {
|
|
|
2833
3145
|
if (action === "check" && result.changed) process.exitCode = 1;
|
|
2834
3146
|
flow.outro(action === "check" && result.changed ? "limina paths failed" : "limina paths passed");
|
|
2835
3147
|
});
|
|
2836
|
-
cli.command("
|
|
2837
|
-
if (action !== "check") throw new Error(`Unknown
|
|
3148
|
+
cli.command("nx <action> [...targets]", "Sync or check Nx project target dependencies from workspace artifact dependencies").action(async (action, targets, flags) => {
|
|
3149
|
+
if (action !== "sync" && action !== "check") throw new Error(`Unknown nx action "${action}". Expected sync or check.`);
|
|
2838
3150
|
const flow = createCliFlow();
|
|
2839
|
-
flow.intro(
|
|
2840
|
-
const
|
|
3151
|
+
flow.intro(`limina nx ${action}`);
|
|
3152
|
+
const result = await runNx(await load(flags, "nx"), {
|
|
3153
|
+
check: action === "check",
|
|
2841
3154
|
clearScreen: false,
|
|
2842
|
-
flow
|
|
3155
|
+
flow,
|
|
3156
|
+
targets
|
|
2843
3157
|
});
|
|
2844
|
-
if (
|
|
2845
|
-
flow.outro(
|
|
3158
|
+
if (action === "check" && result.changed) process.exitCode = 1;
|
|
3159
|
+
flow.outro(action === "check" && result.changed ? "limina nx failed" : "limina nx passed");
|
|
3160
|
+
});
|
|
3161
|
+
cli.command("graph <action> [entryPath]", "Check or sync TypeScript graph architecture").action(async (action, entryPath, flags) => {
|
|
3162
|
+
if (action !== "check" && action !== "sync") throw new Error(`Unknown graph action "${action}". Expected check or sync.`);
|
|
3163
|
+
const flow = createCliFlow();
|
|
3164
|
+
flow.intro(`limina graph ${action}`);
|
|
3165
|
+
const config = await load(flags, "graph");
|
|
3166
|
+
if (action === "check") {
|
|
3167
|
+
const passed = await runGraphCheck(config, {
|
|
3168
|
+
clearScreen: false,
|
|
3169
|
+
flow
|
|
3170
|
+
});
|
|
3171
|
+
if (!passed) process.exitCode = 1;
|
|
3172
|
+
flow.outro(passed ? "limina graph passed" : "limina graph failed");
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
try {
|
|
3176
|
+
await runGraphSync(config, {
|
|
3177
|
+
clearScreen: false,
|
|
3178
|
+
cwd: process.cwd(),
|
|
3179
|
+
entryPath,
|
|
3180
|
+
flow
|
|
3181
|
+
});
|
|
3182
|
+
} catch (error) {
|
|
3183
|
+
process.exitCode = 1;
|
|
3184
|
+
flow.outro("limina graph failed");
|
|
3185
|
+
throw error;
|
|
3186
|
+
}
|
|
3187
|
+
flow.outro("limina graph passed");
|
|
2846
3188
|
});
|
|
2847
3189
|
cli.command("proof <action>", "Check root typecheck coverage proof").action(async (action, flags) => {
|
|
2848
3190
|
if (action !== "check") throw new Error(`Unknown proof action "${action}". Expected check.`);
|