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.
@@ -0,0 +1,846 @@
1
+ import { createRequire } from "node:module";
2
+ import { existsSync, statSync } from "node:fs";
3
+ import path, { normalize, relative, resolve } from "pathe";
4
+ import ts from "typescript";
5
+ import { pathToFileURL } from "node:url";
6
+ import { z } from "zod";
7
+
8
+ //#region src/utils/path.ts
9
+ function toPosixPath(value) {
10
+ return normalizeSlashes(value);
11
+ }
12
+ function normalizeAbsolutePath(value) {
13
+ return resolve(value);
14
+ }
15
+ function toRelativePath(rootDir, absolutePath) {
16
+ const relativePath = relative(rootDir, resolve(absolutePath));
17
+ return relativePath.length === 0 ? "." : relativePath;
18
+ }
19
+ function toAbsolutePath(rootDir, workspacePath) {
20
+ return resolve(rootDir, workspacePath);
21
+ }
22
+ function normalizeWorkspacePath(rootDir, value) {
23
+ return toRelativePath(rootDir, value);
24
+ }
25
+ function normalizeSlashes(value) {
26
+ return value.replaceAll("\\", "/");
27
+ }
28
+ function normalizeAbsolutePathIdentity(value) {
29
+ const normalizedPath = normalize(value);
30
+ return normalizedPath.length > 1 && !/^[A-Za-z]:\/$/u.test(normalizedPath) ? normalizedPath.replace(/\/+$/u, "") : normalizedPath;
31
+ }
32
+ function isPathInsideDirectory(filePath, directoryPath) {
33
+ const normalizedFilePath = normalizeAbsolutePath(filePath);
34
+ const normalizedDirectoryPath = normalizeAbsolutePath(directoryPath);
35
+ return normalizedFilePath === normalizedDirectoryPath || normalizedFilePath.startsWith(`${normalizedDirectoryPath}/`);
36
+ }
37
+
38
+ //#endregion
39
+ //#region src/checkers.ts
40
+ function getTypeScriptExtensionApi() {
41
+ const api = ts;
42
+ if (typeof api.getSupportedExtensions !== "function" || typeof api.getSupportedExtensionsWithJsonIfResolveJsonModule !== "function") throw new TypeError("Unable to resolve TypeScript checker extensions: the TypeScript compiler API does not expose supported extension metadata.");
43
+ return api;
44
+ }
45
+ function flattenTypeScriptExtensionGroups(groups) {
46
+ return groups.flatMap((group) => [...group]);
47
+ }
48
+ function getTypeScriptCheckerExtensions() {
49
+ const api = getTypeScriptExtensionApi();
50
+ const options = { resolveJsonModule: true };
51
+ return normalizeExtensions(flattenTypeScriptExtensionGroups(api.getSupportedExtensionsWithJsonIfResolveJsonModule(options, api.getSupportedExtensions(options))));
52
+ }
53
+ function getNativeTypeScriptProjectExtensions() {
54
+ const api = getTypeScriptExtensionApi();
55
+ const options = {
56
+ allowJs: true,
57
+ resolveJsonModule: true
58
+ };
59
+ return normalizeExtensions(flattenTypeScriptExtensionGroups(api.getSupportedExtensionsWithJsonIfResolveJsonModule(options, api.getSupportedExtensions(options))));
60
+ }
61
+ function getSvelteCheckerExtensions() {
62
+ return normalizeExtensions([...getTypeScriptCheckerExtensions(), ".svelte"]);
63
+ }
64
+ const parsedProjectConfigCache = /* @__PURE__ */ new Map();
65
+ function createFormatHost(rootDir) {
66
+ return {
67
+ getCanonicalFileName: (fileName) => fileName,
68
+ getCurrentDirectory: () => rootDir,
69
+ getNewLine: () => "\n"
70
+ };
71
+ }
72
+ function readTypeScriptProjectConfig(options) {
73
+ const diagnostics = [];
74
+ const parsed = ts.getParsedCommandLineOfConfigFile(options.configPath, {}, {
75
+ ...ts.sys,
76
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
77
+ diagnostics.push(diagnostic);
78
+ }
79
+ });
80
+ if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(options.projectRootDir)));
81
+ return {
82
+ diagnostics,
83
+ parsed
84
+ };
85
+ }
86
+ function createParsedCheckerProjectConfig(options) {
87
+ return {
88
+ extensions: normalizeExtensions(options.extensions),
89
+ fileNames: options.fileNames.map(normalizeAbsolutePath).sort(),
90
+ options: options.parsed.options
91
+ };
92
+ }
93
+ function cloneParsedCheckerProjectConfig(parsedConfig) {
94
+ return {
95
+ extensions: [...parsedConfig.extensions],
96
+ fileNames: [...parsedConfig.fileNames],
97
+ options: { ...parsedConfig.options }
98
+ };
99
+ }
100
+ function resolveContextCheckerPresets(context) {
101
+ return context.checkerPresets.length > 0 ? [...new Set(context.checkerPresets)].sort((left, right) => left.localeCompare(right)) : ["tsc"];
102
+ }
103
+ function createParsedProjectConfigCacheKey(options) {
104
+ const configStat = statSync(options.configPath);
105
+ return JSON.stringify({
106
+ checkerPresets: options.checkerPresets,
107
+ configPath: normalizeAbsolutePath(options.configPath),
108
+ configSize: configStat.size,
109
+ configTime: configStat.mtimeMs,
110
+ extensions: normalizeExtensions(options.extensions),
111
+ projectRootDir: normalizeAbsolutePath(options.projectRootDir)
112
+ });
113
+ }
114
+ function createExtraFileExtensions(extensions) {
115
+ const nativeExtensions = new Set(getNativeTypeScriptProjectExtensions());
116
+ return extensions.filter((extension) => !nativeExtensions.has(extension)).map((extension) => ({
117
+ extension: extension.startsWith(".") ? extension.slice(1) : extension,
118
+ isMixedContent: true,
119
+ scriptKind: ts.ScriptKind.Deferred
120
+ }));
121
+ }
122
+ function parseProjectConfigWithExtraFileExtensions(options, extensions) {
123
+ const diagnostics = [];
124
+ const parsed = ts.getParsedCommandLineOfConfigFile(options.configPath, {}, {
125
+ ...ts.sys,
126
+ onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
127
+ diagnostics.push(diagnostic);
128
+ }
129
+ }, void 0, void 0, createExtraFileExtensions(extensions));
130
+ if (!parsed) throw new Error(ts.formatDiagnosticsWithColorAndContext(diagnostics, createFormatHost(options.projectRootDir)));
131
+ const errors = [...diagnostics, ...parsed.errors];
132
+ if (errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(errors, createFormatHost(options.projectRootDir)));
133
+ return createParsedCheckerProjectConfig({
134
+ extensions,
135
+ fileNames: parsed.fileNames,
136
+ parsed
137
+ });
138
+ }
139
+ function parseTypeScriptProjectConfig(options, extensions) {
140
+ const { diagnostics, parsed } = readTypeScriptProjectConfig(options);
141
+ const errors = [...diagnostics, ...parsed.errors];
142
+ if (errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(errors, createFormatHost(options.projectRootDir)));
143
+ return createParsedCheckerProjectConfig({
144
+ extensions,
145
+ fileNames: parsed.fileNames,
146
+ parsed
147
+ });
148
+ }
149
+ function isModuleNotFoundError(error) {
150
+ return error !== null && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND";
151
+ }
152
+ function createCheckerPackageRequire(options) {
153
+ for (const basePath of [path.join(options.projectRootDir, "package.json"), import.meta.url]) {
154
+ const requireFromBase = createRequire(basePath);
155
+ try {
156
+ return createRequire(requireFromBase.resolve(`${options.packageName}/package.json`));
157
+ } catch (error) {
158
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED") return createRequire(requireFromBase.resolve(options.packageName));
159
+ if (isModuleNotFoundError(error)) continue;
160
+ throw error;
161
+ }
162
+ }
163
+ return null;
164
+ }
165
+ function getVueLanguageCore(options) {
166
+ const requireFromChecker = createCheckerPackageRequire(options);
167
+ if (!requireFromChecker) throw new Error([
168
+ "Unable to resolve Vue checker package:",
169
+ ` package: ${options.packageName}`,
170
+ ` root: ${options.projectRootDir}`
171
+ ].join("\n"));
172
+ try {
173
+ return requireFromChecker("@vue/language-core");
174
+ } catch (error) {
175
+ if (isModuleNotFoundError(error)) throw new Error([
176
+ "Unable to resolve Vue checker language core:",
177
+ ` checker package: ${options.packageName}`,
178
+ " required package: @vue/language-core"
179
+ ].join("\n"));
180
+ throw error;
181
+ }
182
+ }
183
+ function createVueParsedCommandLine(options) {
184
+ const vueLanguageCore = getVueLanguageCore({
185
+ packageName: options.packageName,
186
+ projectRootDir: options.projectRootDir
187
+ });
188
+ const configPath = normalizeAbsolutePath(options.configPath);
189
+ return {
190
+ commandLine: vueLanguageCore.createParsedCommandLine(ts, ts.sys, configPath),
191
+ configPath,
192
+ vueLanguageCore
193
+ };
194
+ }
195
+ function resolveVueProjectExtensions(options, packageName) {
196
+ const { commandLine, vueLanguageCore } = createVueParsedCommandLine({
197
+ configPath: options.configPath,
198
+ packageName,
199
+ projectRootDir: options.projectRootDir
200
+ });
201
+ try {
202
+ return normalizeExtensions([...getTypeScriptCheckerExtensions(), ...vueLanguageCore.getAllExtensions(commandLine.vueOptions)]);
203
+ } catch (error) {
204
+ throw new Error([
205
+ "Unable to resolve Vue checker extensions:",
206
+ ` checker package: ${packageName}`,
207
+ ` config: ${toRelativePath(options.projectRootDir, options.configPath)}`,
208
+ ` reason: ${String(error)}`
209
+ ].join("\n"));
210
+ }
211
+ }
212
+ function parseProjectConfigWithExtensions(options, extensions) {
213
+ const resolvedExtensions = options.extensions && options.extensions.length > 0 ? options.extensions : extensions;
214
+ return createExtraFileExtensions(resolvedExtensions).length > 0 ? parseProjectConfigWithExtraFileExtensions(options, resolvedExtensions) : parseTypeScriptProjectConfig(options, resolvedExtensions);
215
+ }
216
+ function parseVueProjectConfig(options, packageName) {
217
+ const { commandLine, configPath, vueLanguageCore } = createVueParsedCommandLine({
218
+ configPath: options.configPath,
219
+ packageName,
220
+ projectRootDir: options.projectRootDir
221
+ });
222
+ const extensions = normalizeExtensions([
223
+ ...options.extensions ?? [],
224
+ ...getTypeScriptCheckerExtensions(),
225
+ ...vueLanguageCore.getAllExtensions(commandLine.vueOptions)
226
+ ]);
227
+ const configFile = ts.readJsonConfigFile(configPath, ts.sys.readFile);
228
+ const parsed = ts.parseJsonSourceFileConfigFileContent(configFile, ts.sys, path.dirname(configPath), {}, configPath, void 0, createExtraFileExtensions(extensions));
229
+ const errors = parsed.errors;
230
+ if (errors.length > 0) throw new Error(ts.formatDiagnosticsWithColorAndContext(errors, createFormatHost(options.projectRootDir)));
231
+ return createParsedCheckerProjectConfig({
232
+ extensions,
233
+ fileNames: parsed.fileNames,
234
+ parsed
235
+ });
236
+ }
237
+ function resolveExtensionsForChecker(options, extensions) {
238
+ return normalizeExtensions(options.extensions && options.extensions.length > 0 ? options.extensions : extensions);
239
+ }
240
+ function resolveVueProjectExtensionsForChecker(options, packageName) {
241
+ return normalizeExtensions([...options.extensions ?? [], ...resolveVueProjectExtensions(options, packageName)]);
242
+ }
243
+ function isRelativeSpecifier(specifier) {
244
+ return specifier === "." || specifier === ".." || specifier.startsWith("./") || specifier.startsWith("../");
245
+ }
246
+ function pathHasExtension(value) {
247
+ return path.extname(value).length > 0;
248
+ }
249
+ function candidatePathsForBasePath(basePath, extensions) {
250
+ if (pathHasExtension(basePath)) return [basePath];
251
+ return extensions.flatMap((extension) => [`${basePath}${extension}`, path.join(basePath, `index${extension}`)]);
252
+ }
253
+ function resolveCandidatePath(candidatePath) {
254
+ if (!existsSync(candidatePath)) return null;
255
+ if (!statSync(candidatePath).isFile()) return null;
256
+ return normalizeAbsolutePath(candidatePath);
257
+ }
258
+ function resolveRelativeModuleCandidate(options) {
259
+ if (!isRelativeSpecifier(options.specifier)) return null;
260
+ const resolvedSpecifierPath = path.resolve(path.dirname(options.containingFile), options.specifier);
261
+ for (const candidatePath of candidatePathsForBasePath(resolvedSpecifierPath, options.extensions)) {
262
+ const resolvedPath = resolveCandidatePath(candidatePath);
263
+ if (resolvedPath) return resolvedPath;
264
+ }
265
+ return null;
266
+ }
267
+ function matchPathPattern(pattern, specifier) {
268
+ const wildcardIndex = pattern.indexOf("*");
269
+ if (wildcardIndex === -1) return pattern === specifier ? "" : null;
270
+ const prefix = pattern.slice(0, wildcardIndex);
271
+ const suffix = pattern.slice(wildcardIndex + 1);
272
+ if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) return null;
273
+ return specifier.slice(prefix.length, specifier.length - suffix.length);
274
+ }
275
+ function applyPathPattern(pattern, matchedText) {
276
+ return pattern.includes("*") ? pattern.replace("*", matchedText) : pattern;
277
+ }
278
+ function getPathsBasePath(compilerOptions) {
279
+ const pathsBasePath = compilerOptions.pathsBasePath;
280
+ if (typeof pathsBasePath === "string") return pathsBasePath;
281
+ return compilerOptions.baseUrl ?? null;
282
+ }
283
+ function resolvePathMappedModuleCandidate(options) {
284
+ const paths = options.compilerOptions.paths;
285
+ const pathsBasePath = getPathsBasePath(options.compilerOptions);
286
+ if (!paths || !pathsBasePath) return null;
287
+ const pathEntries = Object.entries(paths).sort(([left], [right]) => {
288
+ const leftPrefixLength = left.split("*")[0]?.length ?? left.length;
289
+ return (right.split("*")[0]?.length ?? right.length) - leftPrefixLength;
290
+ });
291
+ for (const [alias, targets] of pathEntries) {
292
+ const matchedText = matchPathPattern(alias, options.specifier);
293
+ if (matchedText === null) continue;
294
+ for (const target of targets) {
295
+ const resolvedTargetPath = path.resolve(pathsBasePath, applyPathPattern(target, matchedText));
296
+ for (const candidatePath of candidatePathsForBasePath(resolvedTargetPath, options.extensions)) {
297
+ const resolvedPath = resolveCandidatePath(candidatePath);
298
+ if (resolvedPath) return resolvedPath;
299
+ }
300
+ }
301
+ }
302
+ return null;
303
+ }
304
+ function resolveTypeScriptModuleName(options) {
305
+ const resolved = ts.resolveModuleName(options.specifier, options.containingFile, options.compilerOptions, ts.sys).resolvedModule;
306
+ if (resolved?.resolvedFileName) return normalizeAbsolutePath(resolved.resolvedFileName);
307
+ return resolveRelativeModuleCandidate(options) ?? resolvePathMappedModuleCandidate(options);
308
+ }
309
+ function mergeParsedProjectConfigs(parsedConfigs, extensions) {
310
+ const firstConfig = parsedConfigs[0];
311
+ if (!firstConfig) throw new Error("Unable to parse checker project config: no parser ran.");
312
+ return {
313
+ extensions: normalizeExtensions([...extensions, ...parsedConfigs.flatMap((parsedConfig) => parsedConfig.extensions)]),
314
+ fileNames: [...new Set(parsedConfigs.flatMap((parsedConfig) => parsedConfig.fileNames))].sort(),
315
+ options: firstConfig.options
316
+ };
317
+ }
318
+ function parseCheckerProjectConfigForContext(options) {
319
+ const checkerPresets = resolveContextCheckerPresets(options.context);
320
+ const cacheKey = createParsedProjectConfigCacheKey({
321
+ checkerPresets,
322
+ configPath: options.configPath,
323
+ extensions: options.context.extensions,
324
+ projectRootDir: options.projectRootDir
325
+ });
326
+ const cached = parsedProjectConfigCache.get(cacheKey);
327
+ if (cached) return cloneParsedCheckerProjectConfig(cached);
328
+ const parsedConfig = mergeParsedProjectConfigs(checkerPresets.map((preset) => {
329
+ const adapter = getCheckerAdapter(preset);
330
+ if (!adapter) throw new Error(`Checker preset "${preset}" is not supported.`);
331
+ return adapter.parseProjectConfig({
332
+ configPath: options.configPath,
333
+ extensions: options.context.extensions,
334
+ projectRootDir: options.projectRootDir
335
+ });
336
+ }), options.context.extensions);
337
+ parsedProjectConfigCache.set(cacheKey, cloneParsedCheckerProjectConfig(parsedConfig));
338
+ return cloneParsedCheckerProjectConfig(parsedConfig);
339
+ }
340
+ function resolveModuleNameWithCheckers(options) {
341
+ const checkerPresets = options.context.checkerPresets.length > 0 ? options.context.checkerPresets : ["tsc"];
342
+ for (const preset of checkerPresets) {
343
+ const adapter = getCheckerAdapter(preset);
344
+ if (!adapter) continue;
345
+ const resolved = adapter.resolveModuleName({
346
+ compilerOptions: options.compilerOptions,
347
+ containingFile: options.containingFile,
348
+ extensions: options.context.extensions,
349
+ specifier: options.specifier
350
+ });
351
+ if (resolved) return resolved;
352
+ }
353
+ return null;
354
+ }
355
+ function resolveCheckerProjectExtensions(options) {
356
+ const adapter = getCheckerAdapter(options.preset);
357
+ if (!adapter) throw new Error(`Checker preset "${options.preset}" is not supported.`);
358
+ return adapter.extensions({
359
+ configPath: options.configPath,
360
+ projectRootDir: options.projectRootDir
361
+ });
362
+ }
363
+ function createTscCommandTarget(options) {
364
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
365
+ return {
366
+ args: [
367
+ "-b",
368
+ relativeConfigPath,
369
+ "--pretty",
370
+ "false"
371
+ ],
372
+ command: options.commandOverride ?? "tsc",
373
+ label: `tsc -b ${relativeConfigPath}`
374
+ };
375
+ }
376
+ function createTsgoCommandTarget(options) {
377
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
378
+ return {
379
+ args: [
380
+ "-b",
381
+ relativeConfigPath,
382
+ "--pretty",
383
+ "false"
384
+ ],
385
+ command: "tsgo",
386
+ label: `tsgo -b ${relativeConfigPath}`
387
+ };
388
+ }
389
+ function createVueTscCommandTarget(options) {
390
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
391
+ return {
392
+ args: [
393
+ "-b",
394
+ relativeConfigPath,
395
+ "--pretty",
396
+ "false"
397
+ ],
398
+ command: "vue-tsc",
399
+ label: `${options.checker.name}: vue-tsc -b ${relativeConfigPath}`
400
+ };
401
+ }
402
+ function createVueTsgoCommandTarget(options) {
403
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
404
+ /**
405
+ * vue-tsgo exposes a --build flag, but in current releases that mode
406
+ * generates a transient virtual TS workspace and asks tsgo's LSP for
407
+ * diagnostics. It does not preserve TypeScript project-reference boundaries
408
+ * or provide incremental build semantics, so Limina only uses vue-tsgo as a
409
+ * second-class typecheck execution checker while still using its tsconfig
410
+ * entry for Limina's own graph and proof coverage. Prefer vue-tsc for
411
+ * first-class Vue build checks.
412
+ */
413
+ return {
414
+ args: ["--project", relativeConfigPath],
415
+ command: "vue-tsgo",
416
+ label: `${options.checker.name}: vue-tsgo --project ${relativeConfigPath}`
417
+ };
418
+ }
419
+ function createSvelteCheckCommandTarget(options) {
420
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
421
+ return {
422
+ args: ["--tsconfig", relativeConfigPath],
423
+ command: "svelte-check",
424
+ label: `${options.checker.name}: svelte-check --tsconfig ${relativeConfigPath}`
425
+ };
426
+ }
427
+ const builtinCheckerAdapters = {
428
+ "svelte-check": {
429
+ createCommandTarget: createSvelteCheckCommandTarget,
430
+ extensions: (options) => resolveExtensionsForChecker(options, getSvelteCheckerExtensions()),
431
+ execution: "typecheck",
432
+ packageNames: ["svelte-check"],
433
+ parseProjectConfig: (options) => parseProjectConfigWithExtensions(options, getSvelteCheckerExtensions()),
434
+ preset: "svelte-check",
435
+ resolveModuleName: resolveTypeScriptModuleName,
436
+ sourceGraph: false
437
+ },
438
+ tsc: {
439
+ createCommandTarget: createTscCommandTarget,
440
+ extensions: (options) => resolveExtensionsForChecker(options, getTypeScriptCheckerExtensions()),
441
+ execution: "build",
442
+ packageNames: ["typescript"],
443
+ parseProjectConfig: (options) => parseProjectConfigWithExtensions(options, getTypeScriptCheckerExtensions()),
444
+ preset: "tsc",
445
+ resolveModuleName: resolveTypeScriptModuleName,
446
+ sourceGraph: true
447
+ },
448
+ tsgo: {
449
+ createCommandTarget: createTsgoCommandTarget,
450
+ extensions: (options) => resolveExtensionsForChecker(options, getTypeScriptCheckerExtensions()),
451
+ execution: "build",
452
+ packageNames: ["@typescript/native-preview"],
453
+ parseProjectConfig: (options) => parseProjectConfigWithExtensions(options, getTypeScriptCheckerExtensions()),
454
+ preset: "tsgo",
455
+ resolveModuleName: resolveTypeScriptModuleName,
456
+ sourceGraph: true
457
+ },
458
+ "vue-tsc": {
459
+ createCommandTarget: createVueTscCommandTarget,
460
+ extensions: (options) => resolveVueProjectExtensionsForChecker(options, "vue-tsc"),
461
+ execution: "build",
462
+ packageNames: ["vue-tsc", "@vue/compiler-sfc"],
463
+ parseProjectConfig: (options) => parseVueProjectConfig(options, "vue-tsc"),
464
+ preset: "vue-tsc",
465
+ resolveModuleName: resolveTypeScriptModuleName,
466
+ sourceGraph: true
467
+ },
468
+ "vue-tsgo": {
469
+ createCommandTarget: createVueTsgoCommandTarget,
470
+ extensions: (options) => resolveVueProjectExtensionsForChecker(options, "vue-tsgo"),
471
+ execution: "typecheck",
472
+ packageNames: ["vue-tsgo", "@typescript/native-preview"],
473
+ parseProjectConfig: (options) => parseVueProjectConfig(options, "vue-tsgo"),
474
+ preset: "vue-tsgo",
475
+ resolveModuleName: resolveTypeScriptModuleName,
476
+ sourceGraph: true
477
+ }
478
+ };
479
+ function isBuiltinCheckerPreset(value) {
480
+ return Object.hasOwn(builtinCheckerAdapters, value);
481
+ }
482
+ function getCheckerAdapter(preset) {
483
+ return isBuiltinCheckerPreset(preset) ? builtinCheckerAdapters[preset] : null;
484
+ }
485
+ function isVueCheckerPreset(preset) {
486
+ return preset === "vue-tsc" || preset === "vue-tsgo";
487
+ }
488
+ function resolveCheckerPackageFromRoot(options) {
489
+ const requireFromRoot = createRequire(path.join(options.projectRootDir, "package.json"));
490
+ try {
491
+ return requireFromRoot.resolve(`${options.packageName}/package.json`);
492
+ } catch (error) {
493
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED") return options.packageName;
494
+ if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") return;
495
+ throw error;
496
+ }
497
+ }
498
+ function collectMissingCheckerPeerDependencies(options) {
499
+ const resolvePackage = options.resolvePackage ?? resolveCheckerPackageFromRoot;
500
+ const missingCheckersByPackage = /* @__PURE__ */ new Map();
501
+ for (const checker of options.checkers) {
502
+ const packageNames = getCheckerAdapter(checker.preset)?.packageNames ?? [];
503
+ for (const packageName of packageNames) {
504
+ if (resolvePackage({
505
+ packageName,
506
+ projectRootDir: options.projectRootDir
507
+ })) continue;
508
+ const checkerNames = missingCheckersByPackage.get(packageName) ?? /* @__PURE__ */ new Set();
509
+ checkerNames.add(checker.name);
510
+ missingCheckersByPackage.set(packageName, checkerNames);
511
+ }
512
+ }
513
+ return [...missingCheckersByPackage.entries()].map(([packageName, checkerNames]) => ({
514
+ checkerNames: [...checkerNames].sort((left, right) => left.localeCompare(right)),
515
+ packageName
516
+ })).sort((left, right) => left.packageName.localeCompare(right.packageName));
517
+ }
518
+ function formatMissingCheckerPeerDependencies(missingDependencies) {
519
+ const packageNames = missingDependencies.map((dependency) => dependency.packageName);
520
+ return [
521
+ "Missing checker peer dependencies:",
522
+ ...missingDependencies.map((dependency) => {
523
+ const checkerList = dependency.checkerNames.map((checkerName) => `"${checkerName}"`).join(", ");
524
+ return ` - ${dependency.packageName} (used by checker ${checkerList})`;
525
+ }),
526
+ `Fix: pnpm add -D ${packageNames.join(" ")}`
527
+ ].join("\n");
528
+ }
529
+ function getCheckerExtensions(checker, options = {}) {
530
+ const adapter = getCheckerAdapter(checker.preset);
531
+ if (adapter) {
532
+ if (isVueCheckerPreset(checker.preset)) {
533
+ if (!options.projectRootDir) throw new Error([
534
+ "Unable to resolve Vue checker extensions:",
535
+ ` preset: ${checker.preset}`,
536
+ " reason: Vue checker extensions must be read from the checker API and require a resolved project root."
537
+ ].join("\n"));
538
+ return adapter.extensions({
539
+ configPath: normalizeAbsolutePath(path.resolve(options.projectRootDir, checker.entry)),
540
+ projectRootDir: options.projectRootDir
541
+ });
542
+ }
543
+ return adapter.extensions({
544
+ configPath: normalizeAbsolutePath(path.resolve(options.projectRootDir ?? "", checker.entry)),
545
+ projectRootDir: options.projectRootDir ?? ""
546
+ });
547
+ }
548
+ throw new Error(`Checker preset "${checker.preset}" is not supported.`);
549
+ }
550
+ function getResolvedCheckers(config) {
551
+ const checkers = config.config?.checkers;
552
+ if (!checkers) return [];
553
+ return Object.entries(checkers).map(([name, checker]) => ({
554
+ entry: checker.entry.trim(),
555
+ extensions: getCheckerExtensions(checker, { projectRootDir: config.rootDir }),
556
+ name,
557
+ preset: checker.preset
558
+ })).sort((left, right) => left.name.localeCompare(right.name));
559
+ }
560
+ function normalizeExtensions(extensions) {
561
+ return [...new Set(extensions)].sort((left, right) => {
562
+ const lengthDelta = right.length - left.length;
563
+ return lengthDelta === 0 ? left.localeCompare(right) : lengthDelta;
564
+ });
565
+ }
566
+
567
+ //#endregion
568
+ //#region src/config.ts
569
+ function defineConfig(config) {
570
+ return config;
571
+ }
572
+ const nonEmptyStringSchema = z.string().refine((value) => value.trim().length > 0);
573
+ const checkerObjectSchema = z.looseObject({});
574
+ const checkerConfigShapeSchema = z.looseObject({
575
+ entry: nonEmptyStringSchema,
576
+ preset: nonEmptyStringSchema
577
+ });
578
+ const liminaConfigShapeSchema = z.looseObject({
579
+ strict: z.boolean().optional(),
580
+ config: z.looseObject({ checkers: z.record(z.string(), checkerConfigShapeSchema).optional() }).optional()
581
+ });
582
+ function formatUnknownValue(value) {
583
+ if (value === void 0) return "undefined";
584
+ return JSON.stringify(value);
585
+ }
586
+ function formatZodPath(pathSegments) {
587
+ return pathSegments.map((segment) => typeof segment === "number" ? `[${segment}]` : `.${String(segment)}`).join("").replace(/^\./u, "");
588
+ }
589
+ function getValueAtPath(value, pathSegments) {
590
+ let current = value;
591
+ for (const segment of pathSegments) {
592
+ if (current === void 0 || current === null) return;
593
+ current = current[segment];
594
+ }
595
+ return current;
596
+ }
597
+ function formatLiminaConfigShapeIssue(value, issue) {
598
+ const pathSegments = issue.path;
599
+ const field = formatZodPath(pathSegments);
600
+ if (pathSegments.length === 0) return "limina config must export or return an object.";
601
+ if (field === "config") return [
602
+ "Invalid Limina config:",
603
+ " field: config",
604
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
605
+ " reason: config must be an object."
606
+ ].join("\n");
607
+ if (field === "strict") return [
608
+ "Invalid Limina config:",
609
+ " field: strict",
610
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
611
+ " reason: strict must be a boolean."
612
+ ].join("\n");
613
+ if (field === "config.checkers") return [
614
+ "Invalid Limina checker config:",
615
+ " field: config.checkers",
616
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
617
+ " reason: config.checkers must be an object keyed by checker name."
618
+ ].join("\n");
619
+ if (pathSegments[0] === "config" && pathSegments[1] === "checkers") {
620
+ const checkerName = pathSegments[2];
621
+ const checkerField = `config.checkers.${String(checkerName)}`;
622
+ if (pathSegments.length === 3) return [
623
+ "Invalid Limina checker config:",
624
+ ` field: ${checkerField}`,
625
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
626
+ " reason: checker entries must be objects."
627
+ ].join("\n");
628
+ if (pathSegments[3] === "preset") return [
629
+ "Invalid Limina checker config:",
630
+ ` field: ${checkerField}.preset`,
631
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
632
+ " reason: checker preset must be a non-empty string."
633
+ ].join("\n");
634
+ if (pathSegments[3] === "entry") return [
635
+ "Invalid Limina checker entry config:",
636
+ ` field: ${checkerField}.entry`,
637
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
638
+ " reason: checker entry must be a non-empty string path."
639
+ ].join("\n");
640
+ }
641
+ return [
642
+ "Invalid Limina config:",
643
+ ` field: ${field}`,
644
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
645
+ ` reason: ${issue.message}`
646
+ ].join("\n");
647
+ }
648
+ function collectLiminaConfigShapeProblems(value) {
649
+ const result = liminaConfigShapeSchema.safeParse(value);
650
+ if (result.success) return [];
651
+ return result.error.issues.map((issue) => formatLiminaConfigShapeIssue(value, issue));
652
+ }
653
+ function collectCheckerConfigProblems(config) {
654
+ const problems = collectLiminaConfigShapeProblems(config);
655
+ if (!checkerObjectSchema.safeParse(config).success) return problems;
656
+ const checkers = config.config?.checkers;
657
+ if (checkers === void 0) return problems;
658
+ if (!checkerObjectSchema.safeParse(checkers).success) return problems;
659
+ for (const [checkerName, checker] of Object.entries(checkers)) {
660
+ const field = `config.checkers.${checkerName}`;
661
+ const checkerObjectResult = checkerObjectSchema.safeParse(checker);
662
+ if (!checkerObjectResult.success) continue;
663
+ const checkerRecord = checkerObjectResult.data;
664
+ const preset = checkerRecord.preset;
665
+ if (Object.hasOwn(checkerRecord, "extensions")) problems.push([
666
+ "Invalid Limina checker config:",
667
+ ` field: ${field}.extensions`,
668
+ ` value: ${formatUnknownValue(checkerRecord.extensions)}`,
669
+ " reason: checker extensions are fixed by built-in presets and cannot be configured."
670
+ ].join("\n"));
671
+ if (Object.hasOwn(checkerRecord, "routes")) problems.push([
672
+ "Invalid Limina checker config:",
673
+ ` field: ${field}.routes`,
674
+ ` value: ${formatUnknownValue(checkerRecord.routes)}`,
675
+ " reason: checker routes are not supported; move routes.build to entry and migrate routes.typecheck targets to tsconfig*.dts.json leaves reachable from that entry with local companions."
676
+ ].join("\n"));
677
+ if (typeof preset !== "string" || preset.trim().length === 0) continue;
678
+ if (!getCheckerAdapter(preset)) {
679
+ problems.push([
680
+ "Unsupported Limina checker preset:",
681
+ ` field: ${field}.preset`,
682
+ ` value: ${formatUnknownValue(preset)}`,
683
+ " reason: configured checker entries require a built-in checker adapter."
684
+ ].join("\n"));
685
+ continue;
686
+ }
687
+ }
688
+ return problems;
689
+ }
690
+ function collectReleaseConfigProblems(config) {
691
+ const problems = [];
692
+ if (!checkerObjectSchema.safeParse(config).success) return problems;
693
+ const release = config.release;
694
+ if (release === void 0) return problems;
695
+ if (!checkerObjectSchema.safeParse(release).success) {
696
+ problems.push([
697
+ "Invalid Limina release config:",
698
+ " field: release",
699
+ ` value: ${formatUnknownValue(release)}`,
700
+ " reason: release must be an object."
701
+ ].join("\n"));
702
+ return problems;
703
+ }
704
+ const contentHash = release.contentHash;
705
+ if (contentHash === void 0) return problems;
706
+ if (!checkerObjectSchema.safeParse(contentHash).success) {
707
+ problems.push([
708
+ "Invalid Limina release config:",
709
+ " field: release.contentHash",
710
+ ` value: ${formatUnknownValue(contentHash)}`,
711
+ " reason: release.contentHash must be an object."
712
+ ].join("\n"));
713
+ return problems;
714
+ }
715
+ const baselineTag = contentHash.baselineTag;
716
+ if (baselineTag !== void 0 && typeof baselineTag !== "function" && (typeof baselineTag !== "string" || baselineTag.trim().length === 0)) problems.push([
717
+ "Invalid Limina release config:",
718
+ " field: release.contentHash.baselineTag",
719
+ ` value: ${formatUnknownValue(baselineTag)}`,
720
+ " reason: baselineTag must be a non-empty string or function."
721
+ ].join("\n"));
722
+ const builtinIgnore = contentHash.builtinIgnore;
723
+ if (builtinIgnore !== void 0 && typeof builtinIgnore !== "boolean") problems.push([
724
+ "Invalid Limina release config:",
725
+ " field: release.contentHash.builtinIgnore",
726
+ ` value: ${formatUnknownValue(builtinIgnore)}`,
727
+ " reason: builtinIgnore must be a boolean."
728
+ ].join("\n"));
729
+ const ignore = contentHash.ignore;
730
+ if (ignore === void 0 || typeof ignore === "function") return problems;
731
+ if (!Array.isArray(ignore)) {
732
+ problems.push([
733
+ "Invalid Limina release config:",
734
+ " field: release.contentHash.ignore",
735
+ ` value: ${formatUnknownValue(ignore)}`,
736
+ " reason: ignore must be an array of non-empty strings or function."
737
+ ].join("\n"));
738
+ return problems;
739
+ }
740
+ for (const [index, pattern] of ignore.entries()) {
741
+ if (typeof pattern === "string" && pattern.trim().length > 0) continue;
742
+ problems.push([
743
+ "Invalid Limina release config:",
744
+ ` field: release.contentHash.ignore[${index}]`,
745
+ ` value: ${formatUnknownValue(pattern)}`,
746
+ " reason: ignore patterns must be non-empty strings."
747
+ ].join("\n"));
748
+ }
749
+ return problems;
750
+ }
751
+ function collectNestedSourceCheckConfigProblems(config) {
752
+ const source = config?.config?.source;
753
+ const problems = [];
754
+ if (!source || typeof source !== "object") return problems;
755
+ if (Object.hasOwn(source, "unusedDependencies")) problems.push([
756
+ "Invalid Limina source config:",
757
+ " field: config.source.unusedDependencies",
758
+ ` value: ${formatUnknownValue(source.unusedDependencies)}`,
759
+ " reason: source.unusedDependencies belongs at the top-level source config, not under config.source."
760
+ ].join("\n"));
761
+ if (Object.hasOwn(source, "unusedModules")) problems.push([
762
+ "Invalid Limina source config:",
763
+ " field: config.source.unusedModules.ignore",
764
+ ` value: ${formatUnknownValue(source.unusedModules?.ignore)}`,
765
+ " reason: source.unusedModules belongs at the top-level source config, not under config.source."
766
+ ].join("\n"));
767
+ return problems;
768
+ }
769
+ function validateLiminaConfig(config) {
770
+ const problems = [
771
+ ...collectCheckerConfigProblems(config),
772
+ ...collectReleaseConfigProblems(config),
773
+ ...collectNestedSourceCheckConfigProblems(config)
774
+ ];
775
+ if (problems.length > 0) throw new Error(problems.join("\n\n"));
776
+ }
777
+ function isStrictConfig(config) {
778
+ return config.strict === true;
779
+ }
780
+ function getActiveCheckers(config) {
781
+ validateLiminaConfig(config);
782
+ return getResolvedCheckers(config);
783
+ }
784
+ function getActiveCheckerExtensions(config) {
785
+ return normalizeExtensions(getActiveCheckers(config).flatMap((checker) => checker.extensions));
786
+ }
787
+ function normalizeConfig(value) {
788
+ const config = value;
789
+ validateLiminaConfig(config);
790
+ return config;
791
+ }
792
+ function createConfigEnv(options) {
793
+ return {
794
+ command: options.command ?? "check",
795
+ mode: options.mode ?? process.env.NODE_ENV ?? "default"
796
+ };
797
+ }
798
+ function findPnpmWorkspaceRoot(startDir) {
799
+ let currentDir = path.resolve(startDir);
800
+ while (true) {
801
+ if (existsSync(path.join(currentDir, "pnpm-workspace.yaml"))) return currentDir;
802
+ const parentDir = path.dirname(currentDir);
803
+ if (parentDir === currentDir) return null;
804
+ currentDir = parentDir;
805
+ }
806
+ }
807
+ function findLiminaConfigPath(startDir, rootDir) {
808
+ let currentDir = path.resolve(startDir);
809
+ const workspaceRootDir = path.resolve(rootDir);
810
+ while (isPathInsideDirectory(currentDir, workspaceRootDir)) {
811
+ const candidatePath = path.join(currentDir, "limina.config.mjs");
812
+ if (existsSync(candidatePath)) return candidatePath;
813
+ if (currentDir === workspaceRootDir) return null;
814
+ const parentDir = path.dirname(currentDir);
815
+ if (parentDir === currentDir) return null;
816
+ currentDir = parentDir;
817
+ }
818
+ return null;
819
+ }
820
+ function inferWorkspaceRoot(startDir) {
821
+ const rootDir = findPnpmWorkspaceRoot(startDir);
822
+ if (!rootDir) throw new Error([`Unable to infer Limina workspace root from ${startDir}:`, "no pnpm-workspace.yaml was found in this directory or its parents."].join(" "));
823
+ return rootDir;
824
+ }
825
+ function validateConfigPathInsideWorkspace(configPath, rootDir) {
826
+ if (isPathInsideDirectory(configPath, rootDir)) return;
827
+ throw new Error([`Unable to load Limina config at ${configPath}:`, `config file must be inside the governed pnpm workspace at ${rootDir}.`].join(" "));
828
+ }
829
+ async function resolveConfigExport(configExport, configEnv) {
830
+ return normalizeConfig(typeof configExport === "function" ? await configExport(configEnv) : await configExport);
831
+ }
832
+ async function loadConfig(options = {}) {
833
+ const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
834
+ const rootDir = inferWorkspaceRoot(cwd);
835
+ const configPath = options.configPath ? path.resolve(cwd, options.configPath) : findLiminaConfigPath(cwd, rootDir);
836
+ if (configPath) validateConfigPathInsideWorkspace(configPath, rootDir);
837
+ if (!configPath || !existsSync(configPath)) throw new Error(options.configPath ? `Unable to find limina config at ${configPath}` : `Unable to find limina config. Searched for limina.config.mjs from ${cwd} up to the pnpm workspace root at ${rootDir}.`);
838
+ return {
839
+ ...await resolveConfigExport((await import(`${pathToFileURL(configPath).href}?t=${Date.now()}`)).default, createConfigEnv(options)),
840
+ configPath,
841
+ rootDir
842
+ };
843
+ }
844
+
845
+ //#endregion
846
+ export { normalizeSlashes as _, loadConfig as a, toPosixPath as b, formatMissingCheckerPeerDependencies as c, parseCheckerProjectConfigForContext as d, resolveCheckerProjectExtensions as f, normalizeAbsolutePathIdentity as g, normalizeAbsolutePath as h, isStrictConfig as i, getCheckerAdapter as l, isPathInsideDirectory as m, getActiveCheckerExtensions as n, validateLiminaConfig as o, resolveModuleNameWithCheckers as p, getActiveCheckers as r, collectMissingCheckerPeerDependencies as s, defineConfig as t, normalizeExtensions as u, normalizeWorkspacePath as v, toRelativePath as x, toAbsolutePath as y };