limina 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import "./chunks/dep-lkQg1P9Q.js";
3
- import { d as normalizeSlashes, f as normalizeWorkspacePath, h as toRelativePath, i as loadConfig, l as isPathInsideDirectory, m as toPosixPath, n as getActiveCheckerExtensions, p as toAbsolutePath, r as getActiveCheckers, u as normalizeAbsolutePath } from "./chunks/dep-jgc7X0zw.js";
4
- import { A as collectImporters, B as createFormatHost, C as inferPackageProject$1, D as parseProject$1, E as isWorkspacePackageFile, F as collectGraphProjectRoute, G as isOrdinaryTypecheckConfigPath, H as getDtsCompanionConfigPath, I as collectGraphProjectRouteFromRoot, J as readJsonConfig, K as parseProjectFileNames, L as collectGraphProjectRoutes, M as findPackageForSpecifier, N as getPackageRootSpecifier, O as resolveInternalImport$1, P as collectCheckerEntryProjectRoutes, S as getTypecheckConfigPath, T as isRelativeSpecifier, U as getRawReferencePaths, V as formatReferences, W as isDtsConfigPath, X as resolveReferencePath, Y as resolveProjectConfigPath, _ as createFileOwnerLookup$1, a as runSourceCheck, b as findTargetProject, c as PackageLogger, d as clearCliScreen, f as formatErrorMessage$1, g as collectImportsFromFile$1, h as normalizeGraphRules, i as runCheckerTypecheck, j as collectWorkspacePackages, k as shouldResolveThroughGraph$1, l as PathsLogger, m as getDeniedWorkspaceDepRule, n as createLiminaFlowReporter, o as CliLogger, p as getDeniedRefRule, q as parseProjectFileNamesForExtensions, r as runCheckerBuild, s as GraphLogger, u as ProofLogger, v as findImporterForFile$1, w as isDtsProjectConfig, x as formatArtifactDependencyPolicy, y as findPackageForFile, z as createExtensionPattern } from "./chunks/dep-DoSHsBSP.js";
5
- import { builtinModules } from "node:module";
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 { A as shouldResolveThroughGraph$1, B as collectGraphProjectRoutes, C as formatArtifactDependencyPolicy, D as isRelativeSpecifier, E as isDtsProjectConfig, F as getPublishDependencySections, G as formatReferences, H as createExtensionPattern, I as isWorkspaceDependencySpecifier, J as isOrdinaryTypecheckConfigPath, K as getDtsCompanionConfigPath, L as collectCheckerEntryProjectRoutes, M as collectWorkspacePackages, N as findPackageForSpecifier, O as parseProject$1, P as getPackageRootSpecifier, Q as resolveReferencePath, R as collectGraphProjectRoute, S as findTargetProject, T as inferPackageProject$1, U as createExtraFileExtensions, V as collectSourceGraphProjectExtensions, W as createFormatHost, X as readJsonConfig, Y as parseProjectFileNamesForExtensions, Z as resolveProjectConfigPath, _ as normalizeGraphRules, a as runSourceCheck, b as findImporterForFile$1, c as GraphLogger, d as ProofLogger, f as clearCliScreen, g as getDeniedRefRule, h as getDeniedDepRuleForSpecifier, i as runCheckerTypecheck, j as collectImporters, k as resolveInternalImport$1, l as PackageLogger, m as getDeniedDepRuleForPackage, n as createLiminaFlowReporter, o as runInit, p as formatErrorMessage$1, q as getRawReferencePaths, r as runCheckerBuild, s as CliLogger, u as PathsLogger, v as collectImportsFromFile$1, w as getTypecheckConfigPath, x as findPackageForFile, y as createFileOwnerLookup$1, z as collectGraphProjectRouteFromRoot } from "./chunks/dep-UWxsul2A.js";
5
+ import { builtinModules, createRequire } from "node:module";
6
6
  import { cac } from "cac";
7
- import { createElapsedTimer } from "@docs-islands/logger/helper";
7
+ import { createElapsedTimer } from "logaria/helper";
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import path from "node:path";
10
10
  import ts from "typescript";
11
- import { spawn, spawnSync } from "node:child_process";
11
+ import { execFile, spawn, spawnSync } from "node:child_process";
12
12
  import { glob } from "tinyglobby";
13
+ import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
13
14
  import { checkPackage, createPackageFromTarballData } from "@arethetypeswrong/core";
14
- import { pack } from "@publint/pack";
15
+ import { pack, unpack } from "@publint/pack";
15
16
  import { init, parse } from "es-module-lexer";
16
- import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
17
17
  import { tmpdir } from "node:os";
18
18
  import { publint } from "publint";
19
19
  import { formatMessage } from "publint/utils";
@@ -112,7 +112,7 @@ function addTypecheckParityProblems(config, dtsProject, problems) {
112
112
  ].join("\n"));
113
113
  return;
114
114
  }
115
- const typecheckProject = parseProject$1(config, typecheckConfigPath);
115
+ const typecheckProject = parseProject$1(config, typecheckConfigPath, dtsProject.extensions);
116
116
  for (const optionName of comparableTypecheckOptions) {
117
117
  const buildValue = dtsProject.options[optionName];
118
118
  const typecheckValue = typecheckProject.options[optionName];
@@ -146,7 +146,7 @@ function addDeniedReferenceProblems(options) {
146
146
  if (!options.projectsByPath.has(referencePath)) continue;
147
147
  const deniedRefRule = getDeniedRefRule(options.rules, options.project.label, referencePath);
148
148
  const targetPackage = findPackageForFile(referencePath, options.packages);
149
- const deniedDepRule = targetPackage ? getDeniedWorkspaceDepRule(options.rules, options.project.label, targetPackage.name) : null;
149
+ const deniedDepRule = targetPackage ? getDeniedDepRuleForPackage(options.rules, options.project.label, targetPackage.name) : null;
150
150
  if (!deniedRefRule && !deniedDepRule) continue;
151
151
  const lines = [
152
152
  "Denied graph access:",
@@ -154,12 +154,12 @@ function addDeniedReferenceProblems(options) {
154
154
  ` referencing project: ${toRelativePath(options.config.rootDir, options.project.configPath)}`,
155
155
  ` referenced project: ${toRelativePath(options.config.rootDir, referencePath)}`
156
156
  ];
157
- if (deniedRefRule) lines.push(` denied ref: ${toRelativePath(options.config.rootDir, deniedRefRule.path)}`, ` reason: ${deniedRefRule.reason}`);
158
- else if (deniedDepRule) lines.push(` denied dependency: ${deniedDepRule.name}`, ` reason: ${deniedDepRule.reason}`);
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
159
  options.problems.push(lines.join("\n"));
160
160
  }
161
161
  }
162
- function addDeniedPackageImportProblem(options) {
162
+ function addDeniedDepImportProblem(options) {
163
163
  options.problems.push([
164
164
  "Denied graph access:",
165
165
  ` rule: ${options.project.label}`,
@@ -182,6 +182,25 @@ function addDeniedRefImportProblem(options) {
182
182
  ` reason: ${options.rule.reason}`
183
183
  ].join("\n"));
184
184
  }
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
+ }
185
204
  function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems) {
186
205
  if (!isDtsProjectConfig(project.configPath)) return;
187
206
  const sourcePackage = findPackageForFile(project.configPath, packages);
@@ -205,9 +224,9 @@ function addWorkspaceReferenceDependencyProblems(config, project, projectsByPath
205
224
  }
206
225
  }
207
226
  async function runGraphCheckInternal(config, options = {}) {
208
- const graphRoute = collectGraphProjectRoute(config);
209
- const projectPaths = graphRoute.projectPaths;
210
- const projects = projectPaths.map((projectPath) => parseProject$1(config, projectPath));
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)));
211
230
  const projectsByPath = new Map(projects.map((project) => [project.configPath, project]));
212
231
  const fileOwnerLookup = createFileOwnerLookup$1(projects);
213
232
  const packages = await collectWorkspacePackages(config);
@@ -216,9 +235,8 @@ async function runGraphCheckInternal(config, options = {}) {
216
235
  const graphRules = normalizeGraphRules({
217
236
  config,
218
237
  include: {
219
- nodeBuiltins: false,
220
- refs: true,
221
- workspaceDeps: true
238
+ deps: true,
239
+ refs: true
222
240
  },
223
241
  packages,
224
242
  problems,
@@ -237,22 +255,22 @@ async function runGraphCheckInternal(config, options = {}) {
237
255
  rules: graphRules
238
256
  });
239
257
  addWorkspaceReferenceDependencyProblems(config, project, projectsByPath, packages, importers, problems);
240
- for (const filePath of project.fileNames) for (const importRecord of collectImportsFromFile$1(filePath)) {
241
- const resolvedFilePath = resolveInternalImport$1(importRecord.specifier, filePath, project.options);
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);
242
271
  const targetPackage = findPackageForSpecifier(importRecord.specifier, packages);
243
- const importer = targetPackage ? findImporterForFile$1(importRecord.filePath, importers) : null;
244
- const unresolvedDeniedDepRule = targetPackage ? getDeniedWorkspaceDepRule(graphRules, project.label, targetPackage.name) : null;
272
+ const importer = findImporterForFile$1(importRecord.filePath, importers);
245
273
  if (!resolvedFilePath) {
246
- if (unresolvedDeniedDepRule) {
247
- addDeniedPackageImportProblem({
248
- config,
249
- importRecord,
250
- problems,
251
- project,
252
- rule: unresolvedDeniedDepRule
253
- });
254
- continue;
255
- }
256
274
  if (!targetPackage) continue;
257
275
  problems.push([
258
276
  "Unresolved workspace import:",
@@ -264,10 +282,12 @@ async function runGraphCheckInternal(config, options = {}) {
264
282
  ].join("\n"));
265
283
  continue;
266
284
  }
267
- const targetWorkspacePackageForResolved = findPackageForFile(resolvedFilePath, packages);
268
- const deniedDepRule = (targetPackage ? getDeniedWorkspaceDepRule(graphRules, project.label, targetPackage.name) : null) ?? (targetWorkspacePackageForResolved ? getDeniedWorkspaceDepRule(graphRules, project.label, targetWorkspacePackageForResolved.name) : null);
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;
269
289
  if (deniedDepRule) {
270
- addDeniedPackageImportProblem({
290
+ addDeniedDepImportProblem({
271
291
  config,
272
292
  importRecord,
273
293
  problems,
@@ -292,9 +312,8 @@ async function runGraphCheckInternal(config, options = {}) {
292
312
  continue;
293
313
  }
294
314
  }
295
- if (targetPackage && !shouldResolveThroughGraph$1(importer, targetPackage)) continue;
296
- if (targetPackage && shouldResolveThroughGraph$1(importer, targetPackage) && !fileOwnerLookup.has(resolvedFilePath)) {
297
- const referencedProjectPath = inferPackageProject$1(resolvedFilePath, targetPackage, projectPaths);
315
+ if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph) && !fileOwnerLookup.has(resolvedFilePath)) {
316
+ const referencedProjectPath = inferPackageProject$1(resolvedFilePath, targetPackageForGraph, projectPaths);
298
317
  const hasProjectReference = referencedProjectPath && project.references.has(referencedProjectPath);
299
318
  problems.push([
300
319
  hasProjectReference ? "Referenced workspace dependency resolves through package exports to a build artifact:" : "Workspace source dependency resolved outside the source graph:",
@@ -304,7 +323,7 @@ async function runGraphCheckInternal(config, options = {}) {
304
323
  ` imported specifier: ${importRecord.specifier}`,
305
324
  ` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
306
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.",
307
- ` 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(targetPackage)}`,
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)}`,
308
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."
309
328
  ].join("\n"));
310
329
  continue;
@@ -317,15 +336,15 @@ async function runGraphCheckInternal(config, options = {}) {
317
336
  specifier: importRecord.specifier
318
337
  });
319
338
  if (!targetProjectPath) {
320
- if (!targetPackage) continue;
321
- if (!isWorkspacePackageFile(resolvedFilePath, packages)) {
322
- if (targetPackage && shouldResolveThroughGraph$1(importer, targetPackage)) problems.push([
339
+ if (!targetPackageForGraph) continue;
340
+ if (!targetWorkspacePackageForResolved) {
341
+ if (targetPackageForGraph && shouldResolveThroughGraph$1(importer, targetPackageForGraph)) problems.push([
323
342
  "Workspace source import resolved outside the workspace graph:",
324
343
  ` importing project: ${toRelativePath(config.rootDir, project.configPath)}`,
325
344
  ` file: ${toRelativePath(config.rootDir, importRecord.filePath)}:${importRecord.line}`,
326
345
  ` imported specifier: ${importRecord.specifier}`,
327
346
  ` resolved file: ${toRelativePath(config.rootDir, resolvedFilePath)}`,
328
- ` reason: workspace:* dependencies are source dependency edges and must resolve to files owned by the source graph; ${formatArtifactDependencyPolicy(targetPackage)}`
347
+ ` reason: workspace:* dependencies are source dependency edges and must resolve to files owned by the source graph; ${formatArtifactDependencyPolicy(targetPackageForGraph)}`
329
348
  ].join("\n"));
330
349
  continue;
331
350
  }
@@ -352,6 +371,7 @@ async function runGraphCheckInternal(config, options = {}) {
352
371
  });
353
372
  continue;
354
373
  }
374
+ if (targetPackageForGraph && !shouldResolveThroughGraph$1(importer, targetPackageForGraph)) continue;
355
375
  if (!projectsByPath.has(targetProjectPath)) {
356
376
  problems.push([
357
377
  "Expected graph target is not reachable from any checker entry:",
@@ -402,6 +422,362 @@ async function runGraphCheck(config, options = {}) {
402
422
  }
403
423
  }
404
424
 
425
+ //#endregion
426
+ //#region src/package-release-consistency.ts
427
+ var PackageReleaseConsistencyError = class extends Error {
428
+ name = "PackageReleaseConsistencyError";
429
+ };
430
+ const semver = createRequire(import.meta.url)("semver");
431
+ function isRecord$1(value) {
432
+ return typeof value === "object" && value !== null && !Array.isArray(value);
433
+ }
434
+ function isLinkDependencySpecifier(specifier) {
435
+ return specifier.startsWith("link:");
436
+ }
437
+ function createReleaseConsistencyState() {
438
+ return {
439
+ directWorkspaceDependencies: [],
440
+ edges: /* @__PURE__ */ new Map(),
441
+ missingWorkspaceDependencies: [],
442
+ packedManifestProblems: [],
443
+ privateWorkspaceDependencies: [],
444
+ registryMetadataCache: /* @__PURE__ */ new Map(),
445
+ registryProblems: [],
446
+ sourceLinkDependencies: [],
447
+ unpublishedPackageNames: /* @__PURE__ */ new Set(),
448
+ visitedPackages: /* @__PURE__ */ new Set()
449
+ };
450
+ }
451
+ function collectPublishDependencyEntries(manifest) {
452
+ const entries = [];
453
+ for (const { dependencies, name } of getPublishDependencySections(manifest)) for (const [dependencyName, specifier] of Object.entries(dependencies)) entries.push({
454
+ dependencyName,
455
+ sectionName: name,
456
+ specifier
457
+ });
458
+ return entries;
459
+ }
460
+ function addEdge(edges, importerName, dependencyName) {
461
+ const dependencies = edges.get(importerName) ?? /* @__PURE__ */ new Set();
462
+ dependencies.add(dependencyName);
463
+ edges.set(importerName, dependencies);
464
+ }
465
+ function formatDependencyLocation(problem) {
466
+ const dependency = problem.dependencyName ? ` -> ${problem.dependencyName}` : "";
467
+ const section = problem.sectionName ? ` [${problem.sectionName}]` : "";
468
+ const specifier = problem.specifier ? ` (${problem.specifier})` : "";
469
+ return `${problem.importerName}${dependency}${section}${specifier}`;
470
+ }
471
+ function formatProblemLines(title, problems) {
472
+ if (problems.length === 0) return [];
473
+ return [
474
+ "",
475
+ title,
476
+ ...problems.map((problem) => ` - ${formatDependencyLocation(problem)}: ${problem.message}`)
477
+ ];
478
+ }
479
+ function getPackedDependencySpecifier(manifest, dependencyName) {
480
+ for (const { dependencies } of getPublishDependencySections(manifest)) {
481
+ const specifier = dependencies[dependencyName];
482
+ if (specifier) return specifier;
483
+ }
484
+ }
485
+ function getNpmPackageMetadataUrl(packageName) {
486
+ return `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
487
+ }
488
+ async function fetchRegistryPackageMetadata(packageName, state) {
489
+ if (state.registryMetadataCache.has(packageName)) return state.registryMetadataCache.get(packageName) ?? null;
490
+ const response = await fetch(getNpmPackageMetadataUrl(packageName), { headers: { accept: "application/vnd.npm.install-v1+json, application/json" } });
491
+ if (!response.ok) {
492
+ state.registryMetadataCache.set(packageName, null);
493
+ return null;
494
+ }
495
+ const metadata = await response.json();
496
+ const registryMetadata = isRecord$1(metadata) ? metadata : null;
497
+ state.registryMetadataCache.set(packageName, registryMetadata);
498
+ return registryMetadata;
499
+ }
500
+ function findRegistryVersionMetadata(metadata, version) {
501
+ if (!isRecord$1(metadata.versions)) return null;
502
+ const versionMetadata = metadata.versions[version];
503
+ return isRecord$1(versionMetadata) ? versionMetadata : null;
504
+ }
505
+ function execGitCommand(config, args) {
506
+ return new Promise((resolve, reject) => {
507
+ execFile("git", [
508
+ "-C",
509
+ config.rootDir,
510
+ ...args
511
+ ], {
512
+ encoding: "utf8",
513
+ maxBuffer: 10 * 1024 * 1024
514
+ }, (error, stdout) => {
515
+ if (error) {
516
+ reject(error);
517
+ return;
518
+ }
519
+ resolve(stdout);
520
+ });
521
+ });
522
+ }
523
+ async function hasWorkspacePackageChangesSinceGitHead(options) {
524
+ const relativeDirectory = toRelativePath(options.config.rootDir, options.workspacePackage.directory);
525
+ try {
526
+ await execGitCommand(options.config, [
527
+ "diff",
528
+ "--quiet",
529
+ options.gitHead,
530
+ "--",
531
+ relativeDirectory
532
+ ]);
533
+ } catch (error) {
534
+ if (isRecord$1(error) && error.code === 1) return true;
535
+ throw error;
536
+ }
537
+ return (await execGitCommand(options.config, [
538
+ "ls-files",
539
+ "--others",
540
+ "--exclude-standard",
541
+ "--",
542
+ relativeDirectory
543
+ ])).trim().length > 0;
544
+ }
545
+ async function verifyWorkspacePackagePublished(options) {
546
+ const { state, workspacePackage } = options;
547
+ const version = workspacePackage.manifest.version;
548
+ if (!version || !semver.valid(version)) {
549
+ state.unpublishedPackageNames.add(workspacePackage.name);
550
+ state.registryProblems.push({
551
+ importerName: workspacePackage.name,
552
+ message: ["workspace package must declare a valid semver version", "before another publishable package can depend on it"].join(" "),
553
+ packageName: workspacePackage.name
554
+ });
555
+ return;
556
+ }
557
+ let metadata;
558
+ try {
559
+ metadata = await fetchRegistryPackageMetadata(workspacePackage.name, state);
560
+ } catch (error) {
561
+ state.unpublishedPackageNames.add(workspacePackage.name);
562
+ state.registryProblems.push({
563
+ importerName: workspacePackage.name,
564
+ message: [`unable to read npm registry metadata for ${workspacePackage.name}@${version}:`, formatErrorMessage$1(error)].join(" "),
565
+ packageName: workspacePackage.name
566
+ });
567
+ return;
568
+ }
569
+ if (!metadata) {
570
+ state.unpublishedPackageNames.add(workspacePackage.name);
571
+ state.registryProblems.push({
572
+ importerName: workspacePackage.name,
573
+ message: `${workspacePackage.name}@${version} is not published to the npm registry`,
574
+ packageName: workspacePackage.name
575
+ });
576
+ return;
577
+ }
578
+ const versionMetadata = findRegistryVersionMetadata(metadata, version);
579
+ if (!versionMetadata) {
580
+ state.unpublishedPackageNames.add(workspacePackage.name);
581
+ state.registryProblems.push({
582
+ importerName: workspacePackage.name,
583
+ message: `${workspacePackage.name}@${version} is not published to the npm registry`,
584
+ packageName: workspacePackage.name
585
+ });
586
+ return;
587
+ }
588
+ if (typeof versionMetadata.gitHead !== "string" || versionMetadata.gitHead.trim().length === 0) {
589
+ state.unpublishedPackageNames.add(workspacePackage.name);
590
+ state.registryProblems.push({
591
+ importerName: workspacePackage.name,
592
+ message: [`${workspacePackage.name}@${version} registry metadata has no gitHead,`, "so limina cannot prove the published source baseline"].join(" "),
593
+ packageName: workspacePackage.name
594
+ });
595
+ return;
596
+ }
597
+ let hasChanges;
598
+ try {
599
+ hasChanges = await hasWorkspacePackageChangesSinceGitHead({
600
+ config: options.config,
601
+ gitHead: versionMetadata.gitHead,
602
+ workspacePackage
603
+ });
604
+ } catch (error) {
605
+ state.unpublishedPackageNames.add(workspacePackage.name);
606
+ state.registryProblems.push({
607
+ importerName: workspacePackage.name,
608
+ message: [
609
+ `unable to compare ${workspacePackage.name}@${version}`,
610
+ `against published gitHead ${versionMetadata.gitHead}:`,
611
+ formatErrorMessage$1(error)
612
+ ].join(" "),
613
+ packageName: workspacePackage.name
614
+ });
615
+ return;
616
+ }
617
+ if (hasChanges) {
618
+ state.unpublishedPackageNames.add(workspacePackage.name);
619
+ state.registryProblems.push({
620
+ importerName: workspacePackage.name,
621
+ message: [`${workspacePackage.name}@${version} has workspace changes`, `after the published npm registry gitHead ${versionMetadata.gitHead}`].join(" "),
622
+ packageName: workspacePackage.name
623
+ });
624
+ }
625
+ }
626
+ async function visitWorkspacePackageDependencies(options) {
627
+ const { config, importerName, isRoot, manifest, state, workspacePackagesByName } = options;
628
+ for (const entry of collectPublishDependencyEntries(manifest)) {
629
+ if (isLinkDependencySpecifier(entry.specifier)) {
630
+ state.sourceLinkDependencies.push({
631
+ dependencyName: entry.dependencyName,
632
+ importerName,
633
+ message: "publishable dependency sections must not use link:",
634
+ sectionName: entry.sectionName,
635
+ specifier: entry.specifier
636
+ });
637
+ continue;
638
+ }
639
+ if (!isWorkspaceDependencySpecifier(entry.specifier)) continue;
640
+ const targetPackage = workspacePackagesByName.get(entry.dependencyName);
641
+ if (!targetPackage) {
642
+ state.missingWorkspaceDependencies.push({
643
+ dependencyName: entry.dependencyName,
644
+ importerName,
645
+ message: "workspace: publish dependency does not match a named workspace package",
646
+ sectionName: entry.sectionName,
647
+ specifier: entry.specifier
648
+ });
649
+ continue;
650
+ }
651
+ addEdge(state.edges, importerName, targetPackage.name);
652
+ if (isRoot) state.directWorkspaceDependencies.push({
653
+ dependencyName: entry.dependencyName,
654
+ sectionName: entry.sectionName,
655
+ targetPackage
656
+ });
657
+ if (targetPackage.manifest.private === true) {
658
+ state.privateWorkspaceDependencies.push({
659
+ dependencyName: entry.dependencyName,
660
+ importerName,
661
+ message: "publishable packages cannot depend on a private workspace package",
662
+ packageName: targetPackage.name,
663
+ sectionName: entry.sectionName,
664
+ specifier: entry.specifier
665
+ });
666
+ continue;
667
+ }
668
+ if (!state.visitedPackages.has(targetPackage.name)) {
669
+ state.visitedPackages.add(targetPackage.name);
670
+ await verifyWorkspacePackagePublished({
671
+ config,
672
+ state,
673
+ workspacePackage: targetPackage
674
+ });
675
+ await visitWorkspacePackageDependencies({
676
+ config,
677
+ importerName: targetPackage.name,
678
+ isRoot: false,
679
+ manifest: targetPackage.manifest,
680
+ state,
681
+ workspacePackagesByName
682
+ });
683
+ }
684
+ }
685
+ }
686
+ async function readPackedPackageJson(tarball) {
687
+ const unpacked = await unpack(tarball);
688
+ const packageJsonFile = unpacked.files.find((file) => file.name === `${unpacked.rootDir}/package.json`);
689
+ if (!packageJsonFile) throw new Error("packed tarball does not contain package.json");
690
+ return JSON.parse(Buffer.from(packageJsonFile.data).toString("utf8"));
691
+ }
692
+ function validatePackedManifest(options) {
693
+ const { manifest, rootPackageName, state } = options;
694
+ for (const entry of collectPublishDependencyEntries(manifest)) if (isWorkspaceDependencySpecifier(entry.specifier) || isLinkDependencySpecifier(entry.specifier)) state.packedManifestProblems.push({
695
+ dependencyName: entry.dependencyName,
696
+ importerName: rootPackageName,
697
+ message: "packed package manifest must not expose workspace: or link: dependency specifiers",
698
+ sectionName: entry.sectionName,
699
+ specifier: entry.specifier
700
+ });
701
+ for (const dependency of state.directWorkspaceDependencies) {
702
+ const packedSpecifier = getPackedDependencySpecifier(manifest, dependency.dependencyName);
703
+ if (!packedSpecifier) {
704
+ state.packedManifestProblems.push({
705
+ dependencyName: dependency.dependencyName,
706
+ importerName: rootPackageName,
707
+ message: "packed package manifest must keep every source workspace publish dependency",
708
+ sectionName: dependency.sectionName
709
+ });
710
+ continue;
711
+ }
712
+ if (isWorkspaceDependencySpecifier(packedSpecifier) || isLinkDependencySpecifier(packedSpecifier)) continue;
713
+ const targetVersion = dependency.targetPackage.manifest.version;
714
+ if (!targetVersion || !semver.satisfies(targetVersion, packedSpecifier, { includePrerelease: true })) state.packedManifestProblems.push({
715
+ dependencyName: dependency.dependencyName,
716
+ importerName: rootPackageName,
717
+ message: `packed dependency range must include ${dependency.targetPackage.name}@${targetVersion ?? "(missing version)"}`,
718
+ sectionName: dependency.sectionName,
719
+ specifier: packedSpecifier
720
+ });
721
+ }
722
+ }
723
+ function createPublishOrder(rootPackageName, state) {
724
+ const publishOrder = [];
725
+ const seen = /* @__PURE__ */ new Set();
726
+ function visit(packageName) {
727
+ if (seen.has(packageName)) return;
728
+ seen.add(packageName);
729
+ for (const dependencyName of state.edges.get(packageName) ?? []) visit(dependencyName);
730
+ if (packageName === rootPackageName || state.unpublishedPackageNames.has(packageName)) publishOrder.push(packageName);
731
+ }
732
+ visit(rootPackageName);
733
+ return publishOrder;
734
+ }
735
+ function createReleaseConsistencyError(options) {
736
+ const { config, label, outDir, rootPackageName, state } = options;
737
+ if (state.sourceLinkDependencies.length + state.privateWorkspaceDependencies.length + state.missingWorkspaceDependencies.length + state.registryProblems.length + state.packedManifestProblems.length === 0) return null;
738
+ const publishOrder = createPublishOrder(rootPackageName, state);
739
+ const lines = [
740
+ `package release dependency consistency failed for ${label}:`,
741
+ ` output: ${toRelativePath(config.rootDir, outDir)}`,
742
+ ...formatProblemLines("Source manifest contains local link: publish dependencies:", state.sourceLinkDependencies),
743
+ ...formatProblemLines("Source manifest depends on private workspace packages:", state.privateWorkspaceDependencies),
744
+ ...formatProblemLines("Source manifest has invalid workspace: publish dependencies:", state.missingWorkspaceDependencies),
745
+ ...formatProblemLines("Workspace packages must be published before this package:", state.registryProblems),
746
+ ...formatProblemLines("Packed package manifest is inconsistent with workspace publish dependencies:", state.packedManifestProblems)
747
+ ];
748
+ if (publishOrder.length > 1) lines.push("", `Suggested publish order: ${publishOrder.join(" -> ")}`);
749
+ return new PackageReleaseConsistencyError(lines.join("\n"));
750
+ }
751
+ async function assertPackageReleaseConsistency(options) {
752
+ const workspacePackages = await collectWorkspacePackages(options.config);
753
+ const sourcePackage = workspacePackages.find((workspacePackage) => workspacePackage.name === options.outputManifest.name);
754
+ const state = createReleaseConsistencyState();
755
+ if (sourcePackage) {
756
+ state.visitedPackages.add(sourcePackage.name);
757
+ await visitWorkspacePackageDependencies({
758
+ config: options.config,
759
+ importerName: sourcePackage.name,
760
+ isRoot: true,
761
+ manifest: sourcePackage.manifest,
762
+ state,
763
+ workspacePackagesByName: new Map(workspacePackages.map((workspacePackage) => [workspacePackage.name, workspacePackage]))
764
+ });
765
+ }
766
+ validatePackedManifest({
767
+ manifest: await readPackedPackageJson(options.packedTarball),
768
+ rootPackageName: options.outputManifest.name,
769
+ state
770
+ });
771
+ const error = createReleaseConsistencyError({
772
+ config: options.config,
773
+ label: options.label,
774
+ outDir: options.outDir,
775
+ rootPackageName: options.outputManifest.name,
776
+ state
777
+ });
778
+ if (error) throw error;
779
+ }
780
+
405
781
  //#endregion
406
782
  //#region src/commands/package.ts
407
783
  const DEFAULT_PACKAGE_CHECKS = [
@@ -416,6 +792,7 @@ const ATTW_PROFILE_IGNORED_RESOLUTIONS = {
416
792
  "esm-only": ["node16-cjs"]
417
793
  };
418
794
  const nodeBuiltinSpecifiers = new Set(builtinModules.flatMap((specifier) => specifier.startsWith("node:") ? [specifier, specifier.slice(5)] : [specifier, `node:${specifier}`]));
795
+ const REQUIRED_PUBLIC_PACKAGE_FILES = ["README.md", "LICENSE.md"];
419
796
  function isRecord(value) {
420
797
  return typeof value === "object" && value !== null && !Array.isArray(value);
421
798
  }
@@ -625,7 +1002,7 @@ function logPackageCheckPlan(options) {
625
1002
  ].join("\n"));
626
1003
  }
627
1004
  async function packOutputTarball(outDir) {
628
- const destination = await mkdtemp(path.join(tmpdir(), "__LATTICE_PACKAGE__"));
1005
+ const destination = await mkdtemp(path.join(tmpdir(), "__LIMINA_PACKAGE__"));
629
1006
  return {
630
1007
  cleanup: async () => {
631
1008
  await rm(destination, {
@@ -640,6 +1017,21 @@ async function packOutputTarball(outDir) {
640
1017
  }))
641
1018
  };
642
1019
  }
1020
+ async function readDistPackageJson(options) {
1021
+ if (!existsSync(options.packageJsonPath)) throw new Error(`outDir package.json not found${options.label ? ` for ${options.label}` : ""} at ${options.config ? toRelativePath(options.config.rootDir, options.packageJsonPath) : options.packageJsonPath}. Run the package build first.`);
1022
+ return JSON.parse(await readFile(options.packageJsonPath, "utf8"));
1023
+ }
1024
+ async function assertPublicPackageMetadata(options) {
1025
+ const manifest = await readDistPackageJson({
1026
+ config: options.config,
1027
+ label: options.label,
1028
+ packageJsonPath: options.packageJsonPath
1029
+ });
1030
+ if (manifest.private === true) return manifest;
1031
+ const missingFiles = REQUIRED_PUBLIC_PACKAGE_FILES.filter((fileName) => !existsSync(path.join(options.outDir, fileName)));
1032
+ if (missingFiles.length === 0) return manifest;
1033
+ throw new Error(`publishable package output for ${options.label} at ${toRelativePath(options.config.rootDir, options.outDir)} is missing required file(s): ${missingFiles.join(", ")}. Add them to the built output or set "private": true in the output package.json.`);
1034
+ }
643
1035
  async function runPublintCheck(options) {
644
1036
  const task = options.flow?.start(`publint: ${options.label}`, { depth: options.flowDepth ?? 0 });
645
1037
  PackageLogger.info(`publint started: ${options.label}`);
@@ -720,8 +1112,13 @@ async function runPackageCheckTarget(options) {
720
1112
  const task = options.flow?.start(`package target: ${label}`, { depth: options.flowDepth ?? 0 });
721
1113
  let packedDist;
722
1114
  try {
723
- if (!existsSync(outputPackageJsonPath)) throw new Error(`outDir package.json not found for ${label} at ${toRelativePath(options.config.rootDir, outputPackageJsonPath)}. Run the package build first.`);
724
- if (options.checks.includes("publint") || options.checks.includes("attw")) {
1115
+ const outputManifest = await assertPublicPackageMetadata({
1116
+ config: options.config,
1117
+ label,
1118
+ outDir: target.outDir,
1119
+ packageJsonPath: outputPackageJsonPath
1120
+ });
1121
+ if (outputManifest.private !== true || options.checks.includes("publint") || options.checks.includes("attw")) {
725
1122
  const packTask = options.flow?.start(`package tarball: ${label}`, { depth: (options.flowDepth ?? 0) + 1 });
726
1123
  PackageLogger.info(`package tarball packing started: ${label}`);
727
1124
  const packElapsed = createElapsedTimer();
@@ -735,6 +1132,20 @@ async function runPackageCheckTarget(options) {
735
1132
  if (!options.flow?.interactive) PackageLogger.success(`package tarball packed: ${label}`, packElapsed());
736
1133
  packTask?.pass();
737
1134
  }
1135
+ if (outputManifest.private !== true) try {
1136
+ await assertPackageReleaseConsistency({
1137
+ config: options.config,
1138
+ label,
1139
+ outDir: target.outDir,
1140
+ outputManifest,
1141
+ packedTarball: packedDist.tarball
1142
+ });
1143
+ } catch (error) {
1144
+ if (!(error instanceof PackageReleaseConsistencyError)) throw error;
1145
+ PackageLogger.error(formatErrorMessage$1(error));
1146
+ task?.fail(`package checks failed: ${label}`);
1147
+ return false;
1148
+ }
738
1149
  let passed = true;
739
1150
  if (options.checks.includes("publint")) passed = await runPublintCheck({
740
1151
  flow: options.flow,
@@ -774,8 +1185,7 @@ async function runPackageCheckTarget(options) {
774
1185
  }
775
1186
  }
776
1187
  async function auditPublishedPackageBoundaries(target) {
777
- const manifestPath = path.join(target.outDir, "package.json");
778
- const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
1188
+ const manifest = await readDistPackageJson({ packageJsonPath: path.join(target.outDir, "package.json") });
779
1189
  const allowedExternalPackages = new Set([
780
1190
  ...Object.keys(manifest.dependencies ?? {}),
781
1191
  ...Object.keys(manifest.peerDependencies ?? {}),
@@ -860,7 +1270,7 @@ async function runPackageCheck(options) {
860
1270
  }
861
1271
 
862
1272
  //#endregion
863
- //#region src/commands/paths.ts
1273
+ //#region src/package-exports.ts
864
1274
  const defaultSourceExtensions = [
865
1275
  ".ts",
866
1276
  ".tsx",
@@ -887,110 +1297,11 @@ const defaultArtifactDirectories = [
887
1297
  "cjs",
888
1298
  "out"
889
1299
  ];
890
- function generatedFileName(config) {
891
- return config.paths?.generatedFileName ?? "tsconfig.dts.paths.generated.json";
892
- }
893
- function generatedFileMarker(config) {
894
- return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
895
- }
896
- function parseProject(config, configPath) {
897
- const diagnostics = [];
898
- const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
899
- ...ts.sys,
900
- onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
901
- diagnostics.push(diagnostic);
902
- }
903
- });
904
- if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
905
- getCanonicalFileName: (fileName) => fileName,
906
- getCurrentDirectory: () => config.rootDir,
907
- getNewLine: () => "\n"
908
- }));
909
- if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
910
- getCanonicalFileName: (fileName) => fileName,
911
- getCurrentDirectory: () => config.rootDir,
912
- getNewLine: () => "\n"
913
- }));
914
- return {
915
- configPath: normalizeAbsolutePath(configPath),
916
- fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
917
- options: parsed.options,
918
- references: new Set(getRawReferencePaths(config, configPath))
919
- };
920
- }
921
- function getSourceFileKind(filePath) {
922
- if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
923
- if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
924
- return ts.ScriptKind.TS;
925
- }
926
- function stringLiteralValue(node) {
927
- return node && ts.isStringLiteralLike(node) ? node.text : null;
928
- }
929
- function collectImportsFromFile(filePath) {
930
- const sourceText = readFileSync(filePath, "utf8");
931
- const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
932
- const imports = [];
933
- const addImport = (specifier, node) => {
934
- const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
935
- imports.push({
936
- filePath,
937
- line: location.line + 1,
938
- specifier
939
- });
940
- };
941
- const visit = (node) => {
942
- if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
943
- const specifier = stringLiteralValue(node.moduleSpecifier);
944
- if (specifier) addImport(specifier, node);
945
- } else if (ts.isImportTypeNode(node)) {
946
- const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
947
- if (specifier) addImport(specifier, node);
948
- } else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
949
- const specifier = stringLiteralValue(node.arguments[0]);
950
- if (specifier) addImport(specifier, node);
951
- }
952
- ts.forEachChild(node, visit);
953
- };
954
- visit(sourceFile);
955
- return imports;
956
- }
957
- function resolveInternalImport(specifier, containingFile, options) {
958
- const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
959
- return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
960
- }
961
- function resolveImportWithoutMatchingPaths(specifier, containingFile, options) {
962
- if (!options.paths) return resolveInternalImport(specifier, containingFile, options);
963
- const paths = Object.fromEntries(Object.entries(options.paths).filter(([alias]) => !aliasMatchesSpecifier(alias, specifier)));
964
- return resolveInternalImport(specifier, containingFile, {
965
- ...options,
966
- paths: Object.keys(paths).length > 0 ? paths : void 0
967
- });
968
- }
969
- function createFileOwnerLookup(projects) {
970
- const ownerLookup = /* @__PURE__ */ new Map();
971
- for (const project of projects) for (const fileName of project.fileNames) {
972
- const owners = ownerLookup.get(fileName) ?? [];
973
- owners.push(project.configPath);
974
- ownerLookup.set(fileName, owners);
975
- }
976
- return ownerLookup;
977
- }
978
- function projectExtendsGeneratedConfig(config, configPath) {
979
- const extendsValue = readJsonConfig(config, configPath).extends;
980
- return (typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) ? extendsValue : []).some((entry) => typeof entry === "string" && path.basename(entry) === generatedFileName(config));
981
- }
982
- function findImporterForFile(filePath, importers) {
983
- return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
984
- }
985
- function shouldResolveThroughGraph(importer, targetPackage) {
986
- if (!importer || !targetPackage) return false;
987
- return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
1300
+ function configuredArtifactDirectories(config) {
1301
+ return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
988
1302
  }
989
- function inferPackageProject(resolvedFilePath, workspacePackage, projectPaths) {
990
- if (!isPathInsideDirectory(resolvedFilePath, workspacePackage.directory)) return null;
991
- return projectPaths.find((projectPath) => {
992
- return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
993
- }) ?? null;
1303
+ function configuredSourceExtensions(config) {
1304
+ return config.paths?.sourceExtensions ?? defaultSourceExtensions;
994
1305
  }
995
1306
  function collectTargetCandidates(config, value) {
996
1307
  if (typeof value === "string") return [value];
@@ -1033,12 +1344,6 @@ function removeKnownExtension(filePath) {
1033
1344
  ]) if (filePath.endsWith(extension)) return filePath.slice(0, -extension.length);
1034
1345
  return filePath;
1035
1346
  }
1036
- function configuredArtifactDirectories(config) {
1037
- return config.paths?.artifactDirectories ?? defaultArtifactDirectories;
1038
- }
1039
- function configuredSourceExtensions(config) {
1040
- return config.paths?.sourceExtensions ?? defaultSourceExtensions;
1041
- }
1042
1347
  function hasConfiguredSourceExtension(config, target) {
1043
1348
  return configuredSourceExtensions(config).some((extension) => target.endsWith(extension));
1044
1349
  }
@@ -1151,6 +1456,114 @@ function aliasMatchesSpecifier(alias, specifier) {
1151
1456
  const suffix = alias.slice(wildcardIndex + 1);
1152
1457
  return specifier.startsWith(prefix) && specifier.endsWith(suffix);
1153
1458
  }
1459
+
1460
+ //#endregion
1461
+ //#region src/commands/paths.ts
1462
+ function generatedFileName(config) {
1463
+ return config.paths?.generatedFileName ?? "tsconfig.dts.paths.generated.json";
1464
+ }
1465
+ function generatedFileMarker(config) {
1466
+ return config.paths?.generatedFileMarker ?? "GENERATED FILE - DO NOT EDIT BY HAND.";
1467
+ }
1468
+ function parseProject(config, configPath) {
1469
+ const diagnostics = [];
1470
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
1471
+ ...ts.sys,
1472
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
1473
+ diagnostics.push(diagnostic);
1474
+ }
1475
+ });
1476
+ if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, {
1477
+ getCanonicalFileName: (fileName) => fileName,
1478
+ getCurrentDirectory: () => config.rootDir,
1479
+ getNewLine: () => "\n"
1480
+ }));
1481
+ if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, {
1482
+ getCanonicalFileName: (fileName) => fileName,
1483
+ getCurrentDirectory: () => config.rootDir,
1484
+ getNewLine: () => "\n"
1485
+ }));
1486
+ return {
1487
+ configPath: normalizeAbsolutePath(configPath),
1488
+ fileNames: parsed.fileNames.filter((fileName) => /\.(?:[cm]?tsx?|d\.[cm]?ts)$/u.test(fileName)).map(normalizeAbsolutePath),
1489
+ options: parsed.options,
1490
+ references: new Set(getRawReferencePaths(config, configPath))
1491
+ };
1492
+ }
1493
+ function getSourceFileKind(filePath) {
1494
+ if (filePath.endsWith(".tsx")) return ts.ScriptKind.TSX;
1495
+ if (filePath.endsWith(".jsx")) return ts.ScriptKind.JSX;
1496
+ return ts.ScriptKind.TS;
1497
+ }
1498
+ function stringLiteralValue(node) {
1499
+ return node && ts.isStringLiteralLike(node) ? node.text : null;
1500
+ }
1501
+ function collectImportsFromFile(filePath) {
1502
+ const sourceText = readFileSync(filePath, "utf8");
1503
+ const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, getSourceFileKind(filePath));
1504
+ const imports = [];
1505
+ const addImport = (specifier, node) => {
1506
+ const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
1507
+ imports.push({
1508
+ filePath,
1509
+ line: location.line + 1,
1510
+ specifier
1511
+ });
1512
+ };
1513
+ const visit = (node) => {
1514
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
1515
+ const specifier = stringLiteralValue(node.moduleSpecifier);
1516
+ if (specifier) addImport(specifier, node);
1517
+ } else if (ts.isImportTypeNode(node)) {
1518
+ const specifier = ts.isLiteralTypeNode(node.argument) ? stringLiteralValue(node.argument.literal) : null;
1519
+ if (specifier) addImport(specifier, node);
1520
+ } else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
1521
+ const specifier = stringLiteralValue(node.arguments[0]);
1522
+ if (specifier) addImport(specifier, node);
1523
+ }
1524
+ ts.forEachChild(node, visit);
1525
+ };
1526
+ visit(sourceFile);
1527
+ return imports;
1528
+ }
1529
+ function resolveInternalImport(specifier, containingFile, options) {
1530
+ const resolved = ts.resolveModuleName(specifier, containingFile, options, ts.sys).resolvedModule;
1531
+ return resolved?.resolvedFileName ? normalizeAbsolutePath(resolved.resolvedFileName) : null;
1532
+ }
1533
+ function resolveImportWithoutMatchingPaths(specifier, containingFile, options) {
1534
+ if (!options.paths) return resolveInternalImport(specifier, containingFile, options);
1535
+ const paths = Object.fromEntries(Object.entries(options.paths).filter(([alias]) => !aliasMatchesSpecifier(alias, specifier)));
1536
+ return resolveInternalImport(specifier, containingFile, {
1537
+ ...options,
1538
+ paths: Object.keys(paths).length > 0 ? paths : void 0
1539
+ });
1540
+ }
1541
+ function createFileOwnerLookup(projects) {
1542
+ const ownerLookup = /* @__PURE__ */ new Map();
1543
+ for (const project of projects) for (const fileName of project.fileNames) {
1544
+ const owners = ownerLookup.get(fileName) ?? [];
1545
+ owners.push(project.configPath);
1546
+ ownerLookup.set(fileName, owners);
1547
+ }
1548
+ return ownerLookup;
1549
+ }
1550
+ function projectExtendsGeneratedConfig(config, configPath) {
1551
+ const extendsValue = readJsonConfig(config, configPath).extends;
1552
+ return (typeof extendsValue === "string" ? [extendsValue] : Array.isArray(extendsValue) ? extendsValue : []).some((entry) => typeof entry === "string" && path.basename(entry) === generatedFileName(config));
1553
+ }
1554
+ function findImporterForFile(filePath, importers) {
1555
+ return importers.find((importer) => isPathInsideDirectory(filePath, importer.directory)) ?? null;
1556
+ }
1557
+ function shouldResolveThroughGraph(importer, targetPackage) {
1558
+ if (!importer || !targetPackage) return false;
1559
+ return importer.name === targetPackage.name || importer.workspaceDependencies.has(targetPackage.name);
1560
+ }
1561
+ function inferPackageProject(resolvedFilePath, workspacePackage, projectPaths) {
1562
+ if (!isPathInsideDirectory(resolvedFilePath, workspacePackage.directory)) return null;
1563
+ return projectPaths.find((projectPath) => {
1564
+ return projectPath.startsWith(`${workspacePackage.directory}/`) && projectPath.endsWith("/tsconfig.lib.dts.json");
1565
+ }) ?? null;
1566
+ }
1154
1567
  function addPathEntry(paths, alias, target) {
1155
1568
  const targets = paths.get(alias) ?? [];
1156
1569
  if (!targets.includes(target)) targets.push(target);
@@ -1402,6 +1815,14 @@ const ignoredSemanticCompilerOptions = new Set([
1402
1815
  "sourceRoot",
1403
1816
  "tsBuildInfoFile"
1404
1817
  ]);
1818
+ const typeScriptCheckerExtensions = getCheckerAdapter("tsc")?.defaultExtensions ?? [];
1819
+ function getFirstClassCoverageExtensions(extensions) {
1820
+ return normalizeExtensions([...typeScriptCheckerExtensions, ...extensions]);
1821
+ }
1822
+ function getCheckerCoverageExtensions(checker) {
1823
+ if (!getCheckerAdapter(checker.preset)?.sourceGraph) return checker.extensions;
1824
+ return getFirstClassCoverageExtensions(checker.extensions);
1825
+ }
1405
1826
  async function collectTsconfigPaths(config, pattern) {
1406
1827
  return (await glob(pattern, {
1407
1828
  cwd: config.rootDir,
@@ -1563,24 +1984,19 @@ function addCoverage(coverageByFile, filePath, source) {
1563
1984
  }
1564
1985
  function collectCoverage(options) {
1565
1986
  const coverageByFile = /* @__PURE__ */ new Map();
1566
- const proofFilePattern = createExtensionPattern(getActiveCheckerExtensions(options.config));
1567
- const typeScriptChecker = getActiveCheckers(options.config).find((checker) => checker.preset === "tsc");
1568
- for (const graphProjectPath of options.graphProjectPaths) for (const filePath of parseProjectFileNames(options.config, graphProjectPath, proofFilePattern)) {
1987
+ for (const route of options.graphRoutes) for (const graphProjectPath of route.projectPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, graphProjectPath, getFirstClassCoverageExtensions(route.extensions))) {
1569
1988
  if (!options.sourceFiles.has(filePath)) continue;
1570
1989
  addCoverage(coverageByFile, filePath, {
1571
1990
  label: toRelativePath(options.config.rootDir, graphProjectPath),
1572
1991
  type: "graph"
1573
1992
  });
1574
1993
  }
1575
- for (const checkerTarget of options.checkerTargets) {
1576
- const checkerExtensions = [...typeScriptChecker?.extensions ?? [], ...checkerTarget.checker.extensions];
1577
- for (const configPath of checkerTarget.coverageConfigPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, configPath, checkerExtensions)) {
1578
- if (!options.sourceFiles.has(filePath)) continue;
1579
- addCoverage(coverageByFile, filePath, {
1580
- label: `${toRelativePath(options.config.rootDir, configPath)} via ${checkerTarget.label}`,
1581
- type: "checker"
1582
- });
1583
- }
1994
+ for (const checkerTarget of options.checkerTargets) for (const configPath of checkerTarget.coverageConfigPaths) for (const filePath of parseProjectFileNamesForExtensions(options.config, configPath, getCheckerCoverageExtensions(checkerTarget.checker))) {
1995
+ if (!options.sourceFiles.has(filePath)) continue;
1996
+ addCoverage(coverageByFile, filePath, {
1997
+ label: `${toRelativePath(options.config.rootDir, configPath)} via ${checkerTarget.label}`,
1998
+ type: "checker"
1999
+ });
1584
2000
  }
1585
2001
  if (options.includeAllowlist !== false) for (const entry of options.allowlistEntries) {
1586
2002
  if (!options.sourceFiles.has(entry.filePath)) continue;
@@ -1591,15 +2007,19 @@ function collectCoverage(options) {
1591
2007
  }
1592
2008
  return coverageByFile;
1593
2009
  }
1594
- function parseConfig(config, configPath) {
2010
+ function collectProjectExtensionsByPath(routes) {
2011
+ const projectExtensionsByPath = /* @__PURE__ */ new Map();
2012
+ for (const route of routes) for (const projectPath of route.projectPaths) {
2013
+ const extensions = new Set([...projectExtensionsByPath.get(projectPath) ?? [], ...route.extensions]);
2014
+ projectExtensionsByPath.set(projectPath, [...extensions].sort());
2015
+ }
2016
+ return projectExtensionsByPath;
2017
+ }
2018
+ function parseConfig(config, configPath, extensions = []) {
1595
2019
  const diagnostics = [];
1596
- const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
1597
- ...ts.sys,
1598
- onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
1599
- diagnostics.push(diagnostic);
1600
- }
1601
- });
1602
- if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
2020
+ const configObject = readJsonConfig(config, configPath);
2021
+ const parsed = ts.parseJsonConfigFileContent(configObject, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
2022
+ if (diagnostics.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(config.rootDir)));
1603
2023
  if (parsed.errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(parsed.errors, createFormatHost(config.rootDir)));
1604
2024
  return {
1605
2025
  fileNames: parsed.fileNames.map(normalizeAbsolutePath).sort(),
@@ -1662,8 +2082,9 @@ function addDtsConfigProblems(options) {
1662
2082
  ].join("\n"));
1663
2083
  continue;
1664
2084
  }
1665
- const dtsConfig = parseConfig(options.config, configPath);
1666
- const localConfig = parseConfig(options.config, localConfigPath);
2085
+ const extensions = options.projectExtensionsByPath.get(configPath) ?? [];
2086
+ const dtsConfig = parseConfig(options.config, configPath, extensions);
2087
+ const localConfig = parseConfig(options.config, localConfigPath, extensions);
1667
2088
  if (dtsConfig.options.composite !== true) options.problems.push([
1668
2089
  "DTS config is not valid for tsc -b:",
1669
2090
  ` config: ${toRelativePath(options.config.rootDir, configPath)}`,
@@ -1804,12 +2225,11 @@ function addDefaultTsconfigEnvironmentProblems(options) {
1804
2225
  ].join("\n"));
1805
2226
  }
1806
2227
  }
1807
- function collectConfigFileOwners(config, configPaths, sourceFiles) {
2228
+ function collectConfigFileOwners(config, graphRoutes, sourceFiles) {
1808
2229
  const ownersByFile = /* @__PURE__ */ new Map();
1809
- const proofFilePattern = createExtensionPattern(getActiveCheckerExtensions(config));
1810
- for (const configPath of configPaths) {
2230
+ for (const route of graphRoutes) for (const configPath of route.projectPaths) {
1811
2231
  if (!existsSync(configPath)) continue;
1812
- for (const filePath of parseProjectFileNames(config, configPath, proofFilePattern)) {
2232
+ for (const filePath of parseProjectFileNamesForExtensions(config, configPath, route.extensions)) {
1813
2233
  if (!sourceFiles.has(filePath)) continue;
1814
2234
  const owners = ownersByFile.get(filePath) ?? [];
1815
2235
  owners.push(configPath);
@@ -1831,19 +2251,6 @@ function addDuplicateGraphCoverageProblems(options) {
1831
2251
  ].join("\n"));
1832
2252
  }
1833
2253
  }
1834
- function addDuplicateGraphOwnerProblems(options) {
1835
- for (const [configPath, ownerCheckerNames] of options.graphOwnersByConfigPath.entries()) {
1836
- const uniqueOwnerCheckerNames = [...new Set(ownerCheckerNames)].sort();
1837
- if (uniqueOwnerCheckerNames.length <= 1) continue;
1838
- options.problems.push([
1839
- "Duplicate checker graph declaration owner:",
1840
- ` config: ${toRelativePath(options.config.rootDir, configPath)}`,
1841
- " owned by:",
1842
- ...uniqueOwnerCheckerNames.map((checkerName) => ` - ${checkerName}`),
1843
- " reason: each tsconfig*.dts.json must be reached by exactly one graph-capable checker entry."
1844
- ].join("\n"));
1845
- }
1846
- }
1847
2254
  function addAllowlistProblems(options) {
1848
2255
  for (const entry of options.allowlistEntries) {
1849
2256
  if (!existsSync(entry.filePath)) {
@@ -1871,34 +2278,25 @@ function addUncoveredSourceProblems(options) {
1871
2278
  " reason: every file in config.source must be covered by a checker entry or an explicit allowlist entry."
1872
2279
  ].filter(Boolean).join("\n"));
1873
2280
  }
1874
- function addGraphOwner(ownersByConfigPath, configPath, checkerName) {
1875
- const owners = ownersByConfigPath.get(configPath) ?? [];
1876
- owners.push(checkerName);
1877
- ownersByConfigPath.set(configPath, owners);
1878
- }
1879
2281
  async function runProofCheckInternal(config, options = {}) {
1880
2282
  const problems = [];
1881
2283
  const graphRouteCollection = collectGraphProjectRoutes(config);
1882
2284
  const entryRouteCollection = collectCheckerEntryProjectRoutes(config);
1883
- const graphProjectPaths = [...new Set(graphRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
1884
2285
  const entryProjectPaths = [...new Set(entryRouteCollection.routes.flatMap((route) => route.projectPaths))].sort();
1885
2286
  const entryProjectPathSet = new Set(entryProjectPaths);
2287
+ const entryProjectExtensionsByPath = collectProjectExtensionsByPath(entryRouteCollection.routes);
1886
2288
  const dtsConfigPaths = await collectDtsConfigPaths(config);
1887
2289
  const buildGraphConfigPaths = await collectBuildGraphConfigPaths(config);
1888
2290
  const defaultTsconfigPaths = await collectDefaultTsconfigPaths(config);
1889
2291
  const ordinaryTypecheckConfigPaths = await collectOrdinaryTypecheckConfigPaths(config);
1890
- const graphOwnersByConfigPath = /* @__PURE__ */ new Map();
1891
2292
  problems.push(...graphRouteCollection.problems);
1892
2293
  problems.push(...entryRouteCollection.problems);
1893
- for (const route of graphRouteCollection.routes) for (const projectPath of route.projectPaths) {
1894
- if (!isDtsConfigPath(projectPath)) continue;
1895
- addGraphOwner(graphOwnersByConfigPath, projectPath, route.checkerName);
1896
- }
1897
2294
  addDtsConfigProblems({
1898
2295
  config,
1899
2296
  dtsConfigPaths,
1900
2297
  graphProjectPaths: entryProjectPathSet,
1901
- problems
2298
+ problems,
2299
+ projectExtensionsByPath: entryProjectExtensionsByPath
1902
2300
  });
1903
2301
  addBuildGraphConfigProblems({
1904
2302
  buildGraphConfigPaths,
@@ -1915,11 +2313,6 @@ async function runProofCheckInternal(config, options = {}) {
1915
2313
  ordinaryConfigPaths: ordinaryTypecheckConfigPaths,
1916
2314
  problems
1917
2315
  });
1918
- addDuplicateGraphOwnerProblems({
1919
- config,
1920
- graphOwnersByConfigPath,
1921
- problems
1922
- });
1923
2316
  if (problems.length > 0) {
1924
2317
  ProofLogger.error(problems.join("\n\n"));
1925
2318
  return false;
@@ -1939,7 +2332,7 @@ async function runProofCheckInternal(config, options = {}) {
1939
2332
  allowlistEntries,
1940
2333
  checkerTargets,
1941
2334
  config,
1942
- graphProjectPaths,
2335
+ graphRoutes: graphRouteCollection.routes,
1943
2336
  includeAllowlist: false,
1944
2337
  sourceFiles
1945
2338
  });
@@ -1947,12 +2340,12 @@ async function runProofCheckInternal(config, options = {}) {
1947
2340
  allowlistEntries,
1948
2341
  checkerTargets,
1949
2342
  config,
1950
- graphProjectPaths,
2343
+ graphRoutes: graphRouteCollection.routes,
1951
2344
  sourceFiles
1952
2345
  });
1953
2346
  addDuplicateGraphCoverageProblems({
1954
2347
  config,
1955
- ownersByFile: collectConfigFileOwners(config, graphProjectPaths, sourceFiles),
2348
+ ownersByFile: collectConfigFileOwners(config, graphRouteCollection.routes, sourceFiles),
1956
2349
  problems
1957
2350
  });
1958
2351
  addAllowlistProblems({
@@ -2015,6 +2408,30 @@ const builtInTaskNames = new Set([
2015
2408
  "proof:check",
2016
2409
  "source:check"
2017
2410
  ]);
2411
+ const defaultCheckPipeline = [
2412
+ "graph:check",
2413
+ "source:check",
2414
+ "proof:check",
2415
+ "checker:build",
2416
+ "checker:typecheck"
2417
+ ];
2418
+ function reportCheckerCapabilities(config, flow) {
2419
+ if (!flow) return;
2420
+ const firstClass = [];
2421
+ const sourceOnly = [];
2422
+ for (const checker of getActiveCheckers(config)) {
2423
+ const adapter = getCheckerAdapter(checker.preset);
2424
+ const label = `${checker.name} (${checker.preset})`;
2425
+ if (adapter?.tier === "first-class") firstClass.push(label);
2426
+ else if (adapter?.tier === "source-only") sourceOnly.push(label);
2427
+ }
2428
+ flow.info([
2429
+ "checker capability summary:",
2430
+ ` first-class: ${firstClass.length > 0 ? firstClass.join(", ") : "(none)"}`,
2431
+ ` source-only: ${sourceOnly.length > 0 ? sourceOnly.join(", ") : "(none)"}`,
2432
+ ...sourceOnly.length > 0 ? [" note: source-only checkers get coverage proof and direct typecheck, but Limina does not parse their internal import graph."] : []
2433
+ ].join("\n"), { depth: 1 });
2434
+ }
2018
2435
  function isBuiltinTaskName(value) {
2019
2436
  return builtInTaskNames.has(value);
2020
2437
  }
@@ -2128,7 +2545,7 @@ function runCommandStep(config, step, options = {}) {
2128
2545
  }
2129
2546
  async function runPipeline(config, pipelineName, options = {}) {
2130
2547
  const steps = config.pipelines?.[pipelineName];
2131
- if (!steps) throw new Error(`Unknown limina pipeline "${pipelineName}".`);
2548
+ if (!steps) throw new Error([`Pipeline instruction "${pipelineName}" was not found.`, `Define it in ${path.relative(config.rootDir, config.configPath)} under the "pipelines" field, then run "limina check ${pipelineName}" again.`].join("\n"));
2132
2549
  const normalizedSteps = steps.map(normalizePipelineStep);
2133
2550
  const pipelineTask = options.flow?.start(`pipeline: ${pipelineName}`, { collapseOnSuccess: false });
2134
2551
  for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
@@ -2140,6 +2557,19 @@ async function runPipeline(config, pipelineName, options = {}) {
2140
2557
  pipelineTask?.pass();
2141
2558
  return true;
2142
2559
  }
2560
+ async function runDefaultCheck(config, options = {}) {
2561
+ const normalizedSteps = defaultCheckPipeline.map(normalizePipelineStep);
2562
+ const pipelineTask = options.flow?.start("default check", { collapseOnSuccess: false });
2563
+ reportCheckerCapabilities(config, options.flow);
2564
+ for (const [stepIndex, step] of normalizedSteps.entries()) if (!(step.type === "task" ? await runBuiltinTask(config, step.name, options) : await runCommandStep(config, step, options))) {
2565
+ const label = getPipelineStepLabel(step);
2566
+ pipelineTask?.fail(`default check blocked at ${label}`);
2567
+ for (const remainingStep of normalizedSteps.slice(stepIndex + 1)) options.flow?.skip(`skipped: ${getPipelineStepLabel(remainingStep)}`, { depth: 1 });
2568
+ return false;
2569
+ }
2570
+ pipelineTask?.pass();
2571
+ return true;
2572
+ }
2143
2573
 
2144
2574
  //#endregion
2145
2575
  //#region src/cli.ts
@@ -2161,12 +2591,6 @@ function parsePackageAttwProfile(profile) {
2161
2591
  if (profile === "strict" || profile === "node16" || profile === "esm-only") return profile;
2162
2592
  throw new Error(`Invalid package check --attw-profile "${profile}". Expected one of: strict, node16, esm-only.`);
2163
2593
  }
2164
- function parseConcurrency(value) {
2165
- if (value === void 0) return;
2166
- const parsed = Number(value);
2167
- if (!Number.isInteger(parsed) || parsed < 1) throw new Error("Invalid --concurrency value. Expected a positive integer.");
2168
- return parsed;
2169
- }
2170
2594
  function createCliFlow() {
2171
2595
  clearCliScreen();
2172
2596
  return createLiminaFlowReporter();
@@ -2176,10 +2600,25 @@ async function main() {
2176
2600
  cli.option("--config <path>", "Path to limina.config.mjs");
2177
2601
  cli.option("--mode <mode>", "Mode passed to limina config functions");
2178
2602
  cli.help();
2179
- cli.command("check <pipeline>", "Run a configured governance pipeline").action(async (pipeline, flags) => {
2603
+ cli.command("init", "Initialize Limina files for a pnpm workspace").option("--yes", "Accept all init prompts").action(async (flags) => {
2604
+ const flow = createCliFlow();
2605
+ flow.intro("limina init");
2606
+ await runInit({
2607
+ clearScreen: false,
2608
+ cwd: process.cwd(),
2609
+ flow,
2610
+ yes: flags.yes
2611
+ });
2612
+ flow.outro("limina init finished");
2613
+ });
2614
+ cli.command("check [pipeline]", "Run the default check or a configured pipeline").action(async (pipeline, flags) => {
2180
2615
  const flow = createCliFlow();
2181
2616
  flow.intro("limina check");
2182
- const passed = await runPipeline(await load(flags, "check"), pipeline, {
2617
+ const config = await load(flags, "check");
2618
+ const passed = pipeline ? await runPipeline(config, pipeline, {
2619
+ cwd: process.cwd(),
2620
+ flow
2621
+ }) : await runDefaultCheck(config, {
2183
2622
  cwd: process.cwd(),
2184
2623
  flow
2185
2624
  });
@@ -2231,8 +2670,8 @@ async function main() {
2231
2670
  if (!passed) process.exitCode = 1;
2232
2671
  flow.outro(passed ? "limina source passed" : "limina source failed");
2233
2672
  });
2234
- cli.command("checker <action>", "Run configured checker typecheck or build entries").option("--concurrency <n>", "Maximum concurrent checker processes").action(async (action, flags) => {
2235
- if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected typecheck or build.`);
2673
+ cli.command("checker <action>", "Run configured checker build or typecheck entries").action(async (action, flags) => {
2674
+ if (action !== "typecheck" && action !== "build") throw new Error(`Unknown checker action "${action}". Expected build or typecheck.`);
2236
2675
  const flow = createCliFlow();
2237
2676
  flow.intro(`limina checker ${action}`);
2238
2677
  if (action === "build") {
@@ -2249,7 +2688,6 @@ async function main() {
2249
2688
  const result = await runCheckerTypecheck({
2250
2689
  clearScreen: false,
2251
2690
  config: await load(flags, "check"),
2252
- concurrency: parseConcurrency(flags.concurrency),
2253
2691
  cwd: process.cwd(),
2254
2692
  flow
2255
2693
  });