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/cli.js CHANGED
@@ -1,423 +1,278 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunks/dep-lkQg1P9Q.js";
3
- import { c as getCheckerAdapter, d as normalizeAbsolutePath, f as normalizeSlashes, g as toRelativePath, h as toPosixPath, i as loadConfig, l as normalizeExtensions, m as toAbsolutePath, n as getActiveCheckerExtensions, p as normalizeWorkspacePath, r as getActiveCheckers, u as isPathInsideDirectory } from "./chunks/dep-DzYrmtQJ.js";
4
- import { $ as resolveReferencePath, A as resolveInternalImport$1, B as collectGraphProjectRouteFromRoot, C as findTargetProject, D as isDtsProjectConfig, E as inferPackageProject$1, F as getPackageRootSpecifier, G as createFormatHost, H as collectSourceGraphProjectExtensions, I as getPublishDependencySections, J as getRawReferencePaths, K as formatReferences, L as isWorkspaceDependencySpecifier, M as collectImporters, N as collectWorkspacePackages, O as isRelativeSpecifier, P as findPackageForSpecifier, Q as resolveProjectConfigPath, R as collectCheckerEntryProjectRoutes, S as findPackageForFile, T as getTypecheckConfigPath, U as createExtensionPattern, V as collectGraphProjectRoutes, W as createExtraFileExtensions, X as parseProjectFileNamesForExtensions, Y as isOrdinaryTypecheckConfigPath, Z as readJsonConfig, _ as getDeniedRefRule, a as runSourceCheck, b as createFileOwnerLookup$1, c as GraphLogger, d as ProofLogger, f as ReleaseLogger, g as getDeniedDepRuleForSpecifier, h as getDeniedDepRuleForPackage, i as runCheckerTypecheck, j as shouldResolveThroughGraph$1, k as parseProject$1, l as PackageLogger, m as formatErrorMessage$1, n as createLiminaFlowReporter, o as runInit, p as clearCliScreen, q as getDtsCompanionConfigPath, r as runCheckerBuild, s as CliLogger, u as PathsLogger, v as normalizeGraphRules, w as formatArtifactDependencyPolicy, x as findImporterForFile$1, y as collectImportsFromFile$1, z as collectGraphProjectRoute } from "./chunks/dep-ZRIm_-Zk.js";
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/graph.ts
22
- const requiredDtsCompilerOptions = [
23
- ["composite", true],
24
- ["incremental", true],
25
- ["noEmit", false],
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 compilerOptionEquals(left, right) {
77
- return JSON.stringify(left) === JSON.stringify(right);
27
+ function configuredArtifactDirectories(config) {
28
+ return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
78
29
  }
79
- function addDtsOptionProblems(config, project, problems) {
80
- if (!isDtsProjectConfig(project.configPath)) return;
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 addTypecheckParityProblems(config, dtsProject, problems) {
104
- if (!isDtsProjectConfig(dtsProject.configPath)) return;
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 addDeniedReferenceProblems(options) {
144
- if (!options.project.label) return;
145
- for (const referencePath of options.project.references) {
146
- if (!options.projectsByPath.has(referencePath)) continue;
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 addDeniedRefImportProblem(options) {
174
- options.problems.push([
175
- "Denied graph access:",
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 getNodeModulesPackageName(filePath) {
186
- const parts = filePath.split("/");
187
- const nodeModulesIndex = parts.lastIndexOf("node_modules");
188
- if (nodeModulesIndex === -1) return null;
189
- const packageName = parts[nodeModulesIndex + 1];
190
- if (!packageName) return null;
191
- if (packageName.startsWith("@")) {
192
- const scopedName = parts[nodeModulesIndex + 2];
193
- return scopedName ? `${packageName}/${scopedName}` : null;
194
- }
195
- return packageName;
196
- }
197
- function getResolvedPackageName(filePath, packages) {
198
- return getNodeModulesPackageName(filePath) ?? findPackageForFile(filePath, packages)?.name ?? null;
199
- }
200
- function getResolvedWorkspacePackage(filePath, packages) {
201
- if (getNodeModulesPackageName(filePath)) return null;
202
- return findPackageForFile(filePath, packages);
203
- }
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
- async function runGraphCheckInternal(config, options = {}) {
227
- const graphRoute = collectSourceGraphProjectExtensions(config);
228
- const projectPaths = [...graphRoute.projectExtensionsByPath.keys()].sort();
229
- const projects = projectPaths.map((projectPath) => parseProject$1(config, projectPath, graphRoute.projectExtensionsByPath.get(projectPath)));
230
- const projectsByPath = new Map(projects.map((project) => [project.configPath, project]));
231
- const fileOwnerLookup = createFileOwnerLookup$1(projects);
232
- const packages = await collectWorkspacePackages(config);
233
- const importers = collectImporters(config, packages);
234
- const problems = [...graphRoute.problems];
235
- const graphRules = normalizeGraphRules({
236
- config,
237
- include: {
238
- deps: true,
239
- refs: true
240
- },
241
- packages,
242
- problems,
243
- projectPaths
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
- for (const project of projects) {
246
- if (project.labelProblem) problems.push(project.labelProblem);
247
- addDtsOptionProblems(config, project, problems);
248
- addTypecheckParityProblems(config, project, problems);
249
- addDeniedReferenceProblems({
250
- config,
251
- packages,
252
- problems,
253
- project,
254
- projectsByPath,
255
- rules: graphRules
256
- });
257
- addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems);
258
- for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile$1(filePath, config.rootDir)) {
259
- const rawDeniedDepRule = getDeniedDepRuleForSpecifier(graphRules, project.label, importRecord.specifier);
260
- if (rawDeniedDepRule) {
261
- addDeniedDepImportProblem({
262
- config,
263
- importRecord,
264
- problems,
265
- project,
266
- rule: rawDeniedDepRule
267
- });
268
- continue;
269
- }
270
- const resolvedFilePath = resolveInternalImport$1(importRecord.specifier, filePath, project.options, project.extensions);
271
- const targetPackage = findPackageForSpecifier(importRecord.specifier, packages);
272
- const importer = findImporterForFile$1(importRecord.filePath, importers);
273
- if (!resolvedFilePath) {
274
- if (!targetPackage) continue;
275
- problems.push([
276
- "Unresolved workspace import:",
277
- ` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
278
- ` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
279
- ` imported specifier: ${importRecord.specifier}`,
280
- ` matched workspace package: ${targetPackage.name}`,
281
- ` current references: ${formatReferences(config.rootDir, project.references)}`
282
- ].join("\n"));
283
- continue;
284
- }
285
- const targetWorkspacePackageForResolved = getResolvedWorkspacePackage(resolvedFilePath, packages);
286
- const targetPackageForGraph = targetPackage;
287
- const resolvedPackageName = getResolvedPackageName(resolvedFilePath, packages);
288
- const deniedDepRule = resolvedPackageName ? getDeniedDepRuleForPackage(graphRules, project.label, resolvedPackageName) : null;
289
- if (deniedDepRule) {
290
- addDeniedDepImportProblem({
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
- hasProjectReference ? "Referenced workspace dependency resolves through package exports to a build artifact:" : "Workspace source dependency resolved outside the source graph:",
320
- ` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
321
- ...referencedProjectPath ? [` referenced project: ${toRelativePath(config.rootDir, referencedProjectPath)}`, ` project reference present: ${hasProjectReference ? "yes" : "no"}`] : [],
322
- ` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
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
- const targetProjectPath = findTargetProject({
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
- "Unable to map workspace import to a graph project:",
353
- ` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
354
- ` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
355
- ` imported specifier: ${importRecord.specifier}`,
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
- if (targetProjectPath === project.configPath) continue;
362
- const deniedRefRule = getDeniedRefRule(graphRules, project.label, targetProjectPath);
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
- "Expected graph target is not reachable from any checker entry:",
378
- ` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
379
- ` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
380
- ` imported specifier: ${importRecord.specifier}`,
381
- ` expected graph project: ${toRelativePath(config.rootDir, targetProjectPath)}`
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
- if (!project.references.has(targetProjectPath)) problems.push([
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
- if (problems.length > 0) {
396
- GraphLogger.error(problems.join("\n\n"));
397
- return false;
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
- if (options.logSuccess ?? true) GraphLogger.success(`Checked ${projects.length} graph projects; references are valid.`);
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 runGraphCheck(config, options = {}) {
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 task = options.flow?.start("graph check", { depth: options.flowDepth ?? 0 });
406
- GraphLogger.info("graph check started");
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 logSuccess = !options.flow?.interactive;
409
- const passed = await runGraphCheckInternal(config, { logSuccess });
410
- if (passed) {
411
- if (logSuccess) GraphLogger.success("graph check finished", elapsed());
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
- GraphLogger.error("graph check finished with failures", elapsed());
415
- task?.fail("graph check finished with failures");
269
+ if (!options.flow?.interactive) NxLogger.success(`${action} finished`, elapsed());
270
+ task?.pass();
416
271
  }
417
- return passed;
272
+ return result;
418
273
  } catch (error) {
419
- GraphLogger.error(`graph check failed: ${formatErrorMessage$1(error)}`, elapsed());
420
- task?.fail("graph check failed", { error });
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 = true;
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 parseProject(config, configPath) {
1110
- const diagnostics = [];
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": "https://json.schemastore.org/tsconfig",
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 = collectGraphProjectRoute(config);
1284
- const projectPaths = graphRoute.projectPaths;
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 typeScriptCheckerExtensions = getCheckerAdapter("tsc")?.defaultExtensions ?? [];
1460
- function getFirstClassCoverageExtensions(extensions) {
1461
- return normalizeExtensions([...typeScriptCheckerExtensions, ...extensions]);
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
- if (!getCheckerAdapter(checker.preset)?.sourceGraph) return checker.extensions;
1465
- return getFirstClassCoverageExtensions(checker.extensions);
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.forEach((entry, index) => {
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
- return;
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
- return;
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
- return;
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) for (const filePath of parseProjectFileNamesForExtensions(options.config, graphProjectPath, getFirstClassCoverageExtensions(route.extensions))) {
1629
- if (!options.sourceFiles.has(filePath)) continue;
1630
- addCoverage(coverageByFile, filePath, {
1631
- label: toRelativePath(options.config.rootDir, graphProjectPath),
1632
- type: "graph"
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) for (const filePath of parseProjectFileNamesForExtensions(options.config, configPath, getCheckerCoverageExtensions(checkerTarget.checker))) {
1636
- if (!options.sourceFiles.has(filePath)) continue;
1637
- addCoverage(coverageByFile, filePath, {
1638
- label: `${toRelativePath(options.config.rootDir, configPath)} via ${checkerTarget.label}`,
1639
- type: "checker"
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
- if (options.includeAllowlist !== false) for (const entry of options.allowlistEntries) {
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 collectProjectExtensionsByPath(routes) {
1652
- const projectExtensionsByPath = /* @__PURE__ */ new Map();
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 extensions = new Set([...projectExtensionsByPath.get(projectPath) ?? [], ...route.extensions]);
1655
- projectExtensionsByPath.set(projectPath, [...extensions].sort());
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 projectExtensionsByPath;
1404
+ return projectContextsByPath;
1658
1405
  }
1659
- function parseConfig(config, configPath, extensions = []) {
1660
- const diagnostics = [];
1661
- const configObject = readJsonConfig(config, configPath);
1662
- const parsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
1663
- if (diagnostics.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
1664
- if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
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
- const extensions = options.projectExtensionsByPath.get(configPath) ?? [];
1727
- const dtsConfig = parseConfig(options.config, configPath, extensions);
1728
- const localConfig = parseConfig(options.config, localConfigPath, extensions);
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.forEach((reference, index) => {
1818
- if (!isPlainRecord(reference) || typeof reference.path !== "string") return;
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)) return;
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
- for (const filePath of parseProjectFileNamesForExtensions(config, configPath, route.extensions)) {
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(configPath);
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
- "Duplicate checker graph coverage:",
1888
- ` file: ${toRelativePath(options.config.rootDir, filePath)}`,
1889
- " covered by:",
1890
- ...uniqueOwners.sort((left, right) => toRelativePath(options.config.rootDir, left).localeCompare(toRelativePath(options.config.rootDir, right))).map((configPath) => ` - ${toRelativePath(options.config.rootDir, configPath)}`),
1891
- " reason: a checker graph file must have a single declaration owner; move the file to one dts leaf or narrow include/exclude patterns."
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 entryProjectExtensionsByPath = collectProjectExtensionsByPath(entryRouteCollection.routes);
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
- projectExtensionsByPath: entryProjectExtensionsByPath
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
- includeAllowlist: false,
1877
+ outsideSourceCoverageByFile,
1978
1878
  sourceFiles
1979
1879
  });
1980
- const coverageByFile = collectCoverage({
1880
+ const coverageByFile = cloneCoverageByFile(baseCoverageByFile);
1881
+ addAllowlistCoverage({
1981
1882
  allowlistEntries,
1982
- checkerTargets,
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 semver = createRequire(import.meta.url)("semver");
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 SOURCE_MAPPING_URL_PATTERN = /(?:\/\/\s*#\s*sourceMappingURL\s*=|\/\*\s*#\s*sourceMappingURL\s*=)/u;
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 execGitCommand(config, args) {
2126
- return new Promise((resolve, reject) => {
2127
- execFile("git", [
2128
- "-C",
2129
- config.rootDir,
2130
- ...args
2131
- ], {
2132
- encoding: "utf8",
2133
- maxBuffer: 10 * 1024 * 1024
2134
- }, (error, stdout) => {
2135
- if (error) {
2136
- reject(error);
2137
- return;
2138
- }
2139
- resolve(stdout);
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
- async function hasWorkspacePackageChangesSinceGitHead(options) {
2144
- const relativeDirectory = toRelativePath(options.config.rootDir, options.workspacePackage.directory);
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
- await execGitCommand(options.config, [
2147
- "diff",
2148
- "--quiet",
2149
- options.gitHead,
2150
- "--",
2151
- relativeDirectory
2152
- ]);
2153
- } catch (error) {
2154
- if (isRecord(error) && error.code === 1) return true;
2155
- throw error;
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 version = workspacePackage.manifest.version;
2168
- if (!version || !semver.valid(version)) {
2169
- state.unpublishedPackageNames.add(workspacePackage.name);
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
- importerName: workspacePackage.name,
2172
- message: ["workspace package must declare a valid semver version", "before another publishable package can depend on it"].join(" "),
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(workspacePackage.name, state);
2351
+ metadata = await fetchRegistryPackageMetadata(dependencyName, state);
2180
2352
  } catch (error) {
2181
- state.unpublishedPackageNames.add(workspacePackage.name);
2353
+ state.unpublishedPackageNames.add(dependencyName);
2182
2354
  state.registryProblems.push({
2183
- importerName: workspacePackage.name,
2184
- message: [`unable to read npm registry metadata for ${workspacePackage.name}@${version}:`, formatErrorMessage$1(error)].join(" "),
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(workspacePackage.name);
2361
+ state.unpublishedPackageNames.add(dependencyName);
2191
2362
  state.registryProblems.push({
2192
- importerName: workspacePackage.name,
2193
- message: `${workspacePackage.name}@${version} is not published to the npm registry`,
2194
- packageName: workspacePackage.name
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, version);
2377
+ const versionMetadata = findRegistryVersionMetadata(metadata, baselineVersion);
2199
2378
  if (!versionMetadata) {
2200
- state.unpublishedPackageNames.add(workspacePackage.name);
2379
+ state.unpublishedPackageNames.add(dependencyName);
2201
2380
  state.registryProblems.push({
2202
- importerName: workspacePackage.name,
2203
- message: `${workspacePackage.name}@${version} is not published to the npm registry`,
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
- if (typeof versionMetadata.gitHead !== "string" || versionMetadata.gitHead.trim().length === 0) {
2209
- state.unpublishedPackageNames.add(workspacePackage.name);
2386
+ const tarballUrl = getRegistryTarballUrl(versionMetadata);
2387
+ if (!tarballUrl) {
2388
+ state.unpublishedPackageNames.add(dependencyName);
2210
2389
  state.registryProblems.push({
2211
- importerName: workspacePackage.name,
2212
- message: [`${workspacePackage.name}@${version} registry metadata has no gitHead,`, "so limina cannot prove the published source baseline"].join(" "),
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 hasChanges;
2395
+ let comparison;
2218
2396
  try {
2219
- hasChanges = await hasWorkspacePackageChangesSinceGitHead({
2397
+ comparison = await compareLocalWorkspacePackageOutputToBaseline({
2220
2398
  config: options.config,
2221
- gitHead: versionMetadata.gitHead,
2399
+ ignoreRules,
2400
+ tarballUrl,
2222
2401
  workspacePackage
2223
2402
  });
2224
2403
  } catch (error) {
2225
- state.unpublishedPackageNames.add(workspacePackage.name);
2404
+ state.unpublishedPackageNames.add(dependencyName);
2226
2405
  state.registryProblems.push({
2227
- importerName: workspacePackage.name,
2406
+ ...problemBase,
2228
2407
  message: [
2229
- `unable to compare ${workspacePackage.name}@${version}`,
2230
- `against published gitHead ${versionMetadata.gitHead}:`,
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
- if (hasChanges) {
2238
- state.unpublishedPackageNames.add(workspacePackage.name);
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
- importerName: workspacePackage.name,
2241
- message: [`${workspacePackage.name}@${version} has workspace changes`, `after the published npm registry gitHead ${versionMetadata.gitHead}`].join(" "),
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) validatePackedManifest({
2453
- manifest: packedManifest,
2454
- rootPackageName: options.outputManifest.name,
2455
- state
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 firstClass = [];
2602
- const sourceOnly = [];
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?.tier === "first-class") firstClass.push(label);
2607
- else if (adapter?.tier === "source-only") sourceOnly.push(label);
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: ${firstClass.length > 0 ? firstClass.join(", ") : "(none)"}`,
2612
- ` source-only: ${sourceOnly.length > 0 ? sourceOnly.join(", ") : "(none)"}`,
2613
- ...sourceOnly.length > 0 ? [" note: source-only checkers get coverage proof and direct typecheck, but Limina does not parse their internal import graph."] : []
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: step.cwd ? path.resolve(config.rootDir, step.cwd) : config.rootDir,
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("graph <action>", "Check TypeScript graph architecture").action(async (action, flags) => {
2837
- if (action !== "check") throw new Error(`Unknown graph action "${action}". Expected check.`);
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("limina graph check");
2840
- const passed = await runGraphCheck(await load(flags, "graph"), {
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 (!passed) process.exitCode = 1;
2845
- flow.outro(passed ? "limina graph passed" : "limina graph failed");
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.`);