limina 0.0.1

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,377 @@
1
+ import { createRequire } from "node:module";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { z } from "zod";
6
+
7
+ //#region src/utils/path.ts
8
+ function toPosixPath(value) {
9
+ return value.split(path.sep).join("/");
10
+ }
11
+ function normalizeAbsolutePath(value) {
12
+ return toPosixPath(path.resolve(value));
13
+ }
14
+ function toRelativePath(rootDir, absolutePath) {
15
+ const relativePath = toPosixPath(path.relative(rootDir, path.resolve(absolutePath)));
16
+ return relativePath.length === 0 ? "." : relativePath;
17
+ }
18
+ function toAbsolutePath(rootDir, workspacePath) {
19
+ return path.resolve(rootDir, workspacePath);
20
+ }
21
+ function normalizeWorkspacePath(rootDir, value) {
22
+ return toRelativePath(rootDir, value);
23
+ }
24
+ function normalizeSlashes(value) {
25
+ return value.replaceAll("\\", "/");
26
+ }
27
+ function isPathInsideDirectory(filePath, directoryPath) {
28
+ const normalizedFilePath = normalizeAbsolutePath(filePath);
29
+ const normalizedDirectoryPath = normalizeAbsolutePath(directoryPath);
30
+ return normalizedFilePath === normalizedDirectoryPath || normalizedFilePath.startsWith(`${normalizedDirectoryPath}/`);
31
+ }
32
+
33
+ //#endregion
34
+ //#region src/checkers.ts
35
+ function createTscCommandTarget(options) {
36
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
37
+ return {
38
+ args: options.executionKind === "build" ? [
39
+ "-b",
40
+ relativeConfigPath,
41
+ "--pretty",
42
+ "false"
43
+ ] : [
44
+ "-p",
45
+ relativeConfigPath,
46
+ "--noEmit"
47
+ ],
48
+ command: options.commandOverride ?? "tsc",
49
+ label: options.executionKind === "build" ? `tsc -b ${relativeConfigPath}` : `tsc: ${relativeConfigPath}`
50
+ };
51
+ }
52
+ function createVueTscCommandTarget(options) {
53
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
54
+ return {
55
+ args: options.executionKind === "build" ? [
56
+ "-b",
57
+ relativeConfigPath,
58
+ "--pretty",
59
+ "false"
60
+ ] : [
61
+ "-p",
62
+ relativeConfigPath,
63
+ "--noEmit"
64
+ ],
65
+ command: "vue-tsc",
66
+ label: options.executionKind === "build" ? `${options.checker.name}: vue-tsc -b ${relativeConfigPath}` : `${options.checker.name}: vue-tsc -p ${relativeConfigPath}`
67
+ };
68
+ }
69
+ function createSvelteCheckCommandTarget(options) {
70
+ if (options.executionKind === "build") throw new Error(`Checker "${options.checker.name}" uses svelte-check, which does not support checker:build.`);
71
+ const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
72
+ return {
73
+ args: ["--tsconfig", relativeConfigPath],
74
+ command: "svelte-check",
75
+ label: `${options.checker.name}: svelte-check --tsconfig ${relativeConfigPath}`
76
+ };
77
+ }
78
+ const builtinCheckerAdapters = {
79
+ "svelte-check": {
80
+ createCommandTarget: createSvelteCheckCommandTarget,
81
+ defaultExtensions: [".svelte"],
82
+ graph: false,
83
+ packageName: "svelte-check",
84
+ preset: "svelte-check",
85
+ supportedExecutions: ["typecheck"]
86
+ },
87
+ tsc: {
88
+ createCommandTarget: createTscCommandTarget,
89
+ defaultExtensions: [
90
+ ".ts",
91
+ ".tsx",
92
+ ".cts",
93
+ ".mts",
94
+ ".d.ts",
95
+ ".d.cts",
96
+ ".d.mts",
97
+ ".json"
98
+ ],
99
+ graph: true,
100
+ packageName: "typescript",
101
+ preset: "tsc",
102
+ supportedExecutions: ["typecheck", "build"]
103
+ },
104
+ "vue-tsc": {
105
+ createCommandTarget: createVueTscCommandTarget,
106
+ defaultExtensions: [".vue"],
107
+ graph: false,
108
+ packageName: "vue-tsc",
109
+ preset: "vue-tsc",
110
+ supportedExecutions: ["typecheck", "build"]
111
+ }
112
+ };
113
+ function isBuiltinCheckerPreset(value) {
114
+ return Object.hasOwn(builtinCheckerAdapters, value);
115
+ }
116
+ function getCheckerAdapter(preset) {
117
+ return isBuiltinCheckerPreset(preset) ? builtinCheckerAdapters[preset] : null;
118
+ }
119
+ function resolveCheckerPackageFromRoot(options) {
120
+ const requireFromRoot = createRequire(path.join(options.projectRootDir, "package.json"));
121
+ try {
122
+ return requireFromRoot.resolve(`${options.packageName}/package.json`);
123
+ } catch (error) {
124
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED") return options.packageName;
125
+ if (error && typeof error === "object" && "code" in error && error.code === "MODULE_NOT_FOUND") return;
126
+ throw error;
127
+ }
128
+ }
129
+ function collectMissingCheckerPeerDependencies(options) {
130
+ const resolvePackage = options.resolvePackage ?? resolveCheckerPackageFromRoot;
131
+ const missingCheckersByPackage = /* @__PURE__ */ new Map();
132
+ for (const checker of options.checkers) {
133
+ const packageName = getCheckerAdapter(checker.preset)?.packageName;
134
+ if (!packageName) continue;
135
+ if (resolvePackage({
136
+ packageName,
137
+ projectRootDir: options.projectRootDir
138
+ })) continue;
139
+ const checkerNames = missingCheckersByPackage.get(packageName) ?? /* @__PURE__ */ new Set();
140
+ checkerNames.add(checker.name);
141
+ missingCheckersByPackage.set(packageName, checkerNames);
142
+ }
143
+ return [...missingCheckersByPackage.entries()].map(([packageName, checkerNames]) => ({
144
+ checkerNames: [...checkerNames].sort((left, right) => left.localeCompare(right)),
145
+ packageName
146
+ })).sort((left, right) => left.packageName.localeCompare(right.packageName));
147
+ }
148
+ function formatMissingCheckerPeerDependencies(missingDependencies) {
149
+ const packageNames = missingDependencies.map((dependency) => dependency.packageName);
150
+ return [
151
+ "Missing checker peer dependencies:",
152
+ ...missingDependencies.map((dependency) => {
153
+ const checkerList = dependency.checkerNames.map((checkerName) => `"${checkerName}"`).join(", ");
154
+ return ` - ${dependency.packageName} (used by checker ${checkerList})`;
155
+ }),
156
+ `Fix: pnpm add -D ${packageNames.join(" ")}`
157
+ ].join("\n");
158
+ }
159
+ function getCheckerExtensions(checker) {
160
+ if (checker.extensions) return normalizeExtensions(checker.extensions);
161
+ const adapter = getCheckerAdapter(checker.preset);
162
+ if (adapter?.defaultExtensions) return normalizeExtensions(adapter.defaultExtensions);
163
+ throw new Error(`Checker preset "${checker.preset}" must declare non-empty extensions because it is not a built-in preset.`);
164
+ }
165
+ function getResolvedCheckers(config) {
166
+ const checkers = config.config?.checkers;
167
+ if (!checkers) return [];
168
+ return Object.entries(checkers).map(([name, checker]) => ({
169
+ entry: checker.entry.trim(),
170
+ extensions: getCheckerExtensions(checker),
171
+ name,
172
+ preset: checker.preset
173
+ })).sort((left, right) => left.name.localeCompare(right.name));
174
+ }
175
+ function normalizeExtensions(extensions) {
176
+ return [...new Set(extensions)].sort((left, right) => {
177
+ const lengthDelta = right.length - left.length;
178
+ return lengthDelta === 0 ? left.localeCompare(right) : lengthDelta;
179
+ });
180
+ }
181
+
182
+ //#endregion
183
+ //#region src/config.ts
184
+ function defineConfig(config) {
185
+ return config;
186
+ }
187
+ const nonEmptyStringSchema = z.string().refine((value) => value.trim().length > 0);
188
+ const dotPrefixedStringSchema = nonEmptyStringSchema.refine((value) => value.startsWith("."));
189
+ const checkerObjectSchema = z.looseObject({});
190
+ const checkerConfigShapeSchema = z.looseObject({
191
+ entry: nonEmptyStringSchema,
192
+ extensions: z.array(dotPrefixedStringSchema).nonempty().optional(),
193
+ preset: nonEmptyStringSchema
194
+ });
195
+ const liminaConfigShapeSchema = z.looseObject({ config: z.looseObject({ checkers: z.record(z.string(), checkerConfigShapeSchema).optional() }).optional() });
196
+ function formatUnknownValue(value) {
197
+ if (value === void 0) return "undefined";
198
+ return JSON.stringify(value);
199
+ }
200
+ function formatZodPath(pathSegments) {
201
+ return pathSegments.map((segment) => typeof segment === "number" ? `[${segment}]` : `.${String(segment)}`).join("").replace(/^\./u, "");
202
+ }
203
+ function getValueAtPath(value, pathSegments) {
204
+ let current = value;
205
+ for (const segment of pathSegments) {
206
+ if (current === void 0 || current === null) return;
207
+ current = current[segment];
208
+ }
209
+ return current;
210
+ }
211
+ function formatLiminaConfigShapeIssue(value, issue) {
212
+ const pathSegments = issue.path;
213
+ const field = formatZodPath(pathSegments);
214
+ if (pathSegments.length === 0) return "limina config must export or return an object.";
215
+ if (field === "config") return [
216
+ "Invalid Limina config:",
217
+ " field: config",
218
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
219
+ " reason: config must be an object."
220
+ ].join("\n");
221
+ if (field === "config.checkers") return [
222
+ "Invalid Limina checker config:",
223
+ " field: config.checkers",
224
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
225
+ " reason: config.checkers must be an object keyed by checker name."
226
+ ].join("\n");
227
+ if (pathSegments[0] === "config" && pathSegments[1] === "checkers") {
228
+ const checkerName = pathSegments[2];
229
+ const checkerField = `config.checkers.${String(checkerName)}`;
230
+ if (pathSegments.length === 3) return [
231
+ "Invalid Limina checker config:",
232
+ ` field: ${checkerField}`,
233
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
234
+ " reason: checker entries must be objects."
235
+ ].join("\n");
236
+ if (pathSegments[3] === "preset") return [
237
+ "Invalid Limina checker config:",
238
+ ` field: ${checkerField}.preset`,
239
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
240
+ " reason: checker preset must be a non-empty string."
241
+ ].join("\n");
242
+ if (pathSegments[3] === "entry") return [
243
+ "Invalid Limina checker entry config:",
244
+ ` field: ${checkerField}.entry`,
245
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
246
+ " reason: checker entry must be a non-empty string path."
247
+ ].join("\n");
248
+ if (pathSegments[3] === "extensions") {
249
+ const extensionsPath = pathSegments.slice(0, 4);
250
+ return [
251
+ "Invalid Limina checker config:",
252
+ ` field: ${checkerField}.extensions`,
253
+ ` value: ${formatUnknownValue(getValueAtPath(value, extensionsPath))}`,
254
+ " reason: checker extensions must be a non-empty array of dot-prefixed strings."
255
+ ].join("\n");
256
+ }
257
+ }
258
+ return [
259
+ "Invalid Limina config:",
260
+ ` field: ${field}`,
261
+ ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
262
+ ` reason: ${issue.message}`
263
+ ].join("\n");
264
+ }
265
+ function collectLiminaConfigShapeProblems(value) {
266
+ const result = liminaConfigShapeSchema.safeParse(value);
267
+ if (result.success) return [];
268
+ return result.error.issues.map((issue) => formatLiminaConfigShapeIssue(value, issue));
269
+ }
270
+ function collectCheckerConfigProblems(config) {
271
+ const problems = collectLiminaConfigShapeProblems(config);
272
+ if (!checkerObjectSchema.safeParse(config).success) return problems;
273
+ const checkers = config.config?.checkers;
274
+ if (checkers === void 0) return problems;
275
+ if (!checkerObjectSchema.safeParse(checkers).success) return problems;
276
+ for (const [checkerName, checker] of Object.entries(checkers)) {
277
+ const field = `config.checkers.${checkerName}`;
278
+ const checkerObjectResult = checkerObjectSchema.safeParse(checker);
279
+ if (!checkerObjectResult.success) continue;
280
+ const checkerRecord = checkerObjectResult.data;
281
+ const preset = checkerRecord.preset;
282
+ if (checkerRecord.extensions === void 0 && typeof preset === "string" && !isBuiltinCheckerPreset(preset)) problems.push([
283
+ "Invalid Limina checker config:",
284
+ ` field: ${field}.extensions`,
285
+ " value: undefined",
286
+ " reason: extensions may only be omitted for built-in presets."
287
+ ].join("\n"));
288
+ if (Object.hasOwn(checkerRecord, "routes")) problems.push([
289
+ "Invalid Limina checker config:",
290
+ ` field: ${field}.routes`,
291
+ ` value: ${formatUnknownValue(checkerRecord.routes)}`,
292
+ " 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."
293
+ ].join("\n"));
294
+ if (typeof preset !== "string" || preset.trim().length === 0) continue;
295
+ if (!getCheckerAdapter(preset)) {
296
+ problems.push([
297
+ "Unsupported Limina checker preset:",
298
+ ` field: ${field}.preset`,
299
+ ` value: ${formatUnknownValue(preset)}`,
300
+ " reason: configured checker entries require a built-in checker adapter."
301
+ ].join("\n"));
302
+ continue;
303
+ }
304
+ }
305
+ return problems;
306
+ }
307
+ function validateLiminaConfig(config) {
308
+ const problems = collectCheckerConfigProblems(config);
309
+ if (problems.length > 0) throw new Error(problems.join("\n\n"));
310
+ }
311
+ function getActiveCheckers(config) {
312
+ validateLiminaConfig(config);
313
+ return getResolvedCheckers(config);
314
+ }
315
+ function getActiveCheckerExtensions(config) {
316
+ return normalizeExtensions(getActiveCheckers(config).flatMap((checker) => checker.extensions));
317
+ }
318
+ function normalizeConfig(value) {
319
+ const config = value;
320
+ validateLiminaConfig(config);
321
+ return config;
322
+ }
323
+ function createConfigEnv(options) {
324
+ return {
325
+ command: options.command ?? "check",
326
+ mode: options.mode ?? process.env.NODE_ENV ?? "default"
327
+ };
328
+ }
329
+ function findPnpmWorkspaceRoot(startDir) {
330
+ let currentDir = path.resolve(startDir);
331
+ while (true) {
332
+ if (existsSync(path.join(currentDir, "pnpm-workspace.yaml"))) return currentDir;
333
+ const parentDir = path.dirname(currentDir);
334
+ if (parentDir === currentDir) return null;
335
+ currentDir = parentDir;
336
+ }
337
+ }
338
+ function findLiminaConfigPath(startDir, rootDir) {
339
+ let currentDir = path.resolve(startDir);
340
+ const workspaceRootDir = path.resolve(rootDir);
341
+ while (isPathInsideDirectory(currentDir, workspaceRootDir)) {
342
+ const candidatePath = path.join(currentDir, "limina.config.mjs");
343
+ if (existsSync(candidatePath)) return candidatePath;
344
+ if (currentDir === workspaceRootDir) return null;
345
+ const parentDir = path.dirname(currentDir);
346
+ if (parentDir === currentDir) return null;
347
+ currentDir = parentDir;
348
+ }
349
+ return null;
350
+ }
351
+ function inferWorkspaceRoot(startDir) {
352
+ const rootDir = findPnpmWorkspaceRoot(startDir);
353
+ 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(" "));
354
+ return rootDir;
355
+ }
356
+ function validateConfigPathInsideWorkspace(configPath, rootDir) {
357
+ if (isPathInsideDirectory(configPath, rootDir)) return;
358
+ throw new Error([`Unable to load Limina config at ${configPath}:`, `config file must be inside the governed pnpm workspace at ${rootDir}.`].join(" "));
359
+ }
360
+ async function resolveConfigExport(configExport, configEnv) {
361
+ return normalizeConfig(typeof configExport === "function" ? await configExport(configEnv) : await configExport);
362
+ }
363
+ async function loadConfig(options = {}) {
364
+ const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
365
+ const rootDir = inferWorkspaceRoot(cwd);
366
+ const configPath = options.configPath ? path.resolve(cwd, options.configPath) : findLiminaConfigPath(cwd, rootDir);
367
+ if (configPath) validateConfigPathInsideWorkspace(configPath, rootDir);
368
+ 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}.`);
369
+ return {
370
+ ...await resolveConfigExport((await import(`${pathToFileURL(configPath).href}?t=${Date.now()}`)).default, createConfigEnv(options)),
371
+ configPath,
372
+ rootDir
373
+ };
374
+ }
375
+
376
+ //#endregion
377
+ export { validateLiminaConfig as a, getCheckerAdapter as c, normalizeSlashes as d, normalizeWorkspacePath as f, toRelativePath as h, loadConfig as i, isPathInsideDirectory as l, toPosixPath as m, getActiveCheckerExtensions as n, collectMissingCheckerPeerDependencies as o, toAbsolutePath as p, getActiveCheckers as r, formatMissingCheckerPeerDependencies as s, defineConfig as t, normalizeAbsolutePath as u };
@@ -0,0 +1,3 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ export { };