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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present, Senao Xi. and docs islands contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,383 @@
1
+ # limina
2
+
3
+ <p align="center">
4
+ <a href="https://npmjs.com/package/limina"><img src="https://img.shields.io/npm/v/limina.svg" alt="npm package"></a>
5
+ <a href="https://nodejs.org/en/about/previous-releases"><img src="https://img.shields.io/node/v/limina.svg" alt="node compatibility"></a>
6
+ <a href="https://github.com/XiSenao/docs-islands/actions/workflows/ci.yml"><img src="https://github.com/XiSenao/docs-islands/actions/workflows/ci.yml/badge.svg?branch=main" alt="build status"></a>
7
+ <a href="https://github.com/XiSenao/docs-islands/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/limina.svg" alt="license"></a>
8
+ </p>
9
+
10
+ English | [简体中文](./README.zh-CN.md)
11
+
12
+ `limina` is a configurable governance CLI for TypeScript monorepos. It keeps TypeScript project references, source typechecks, compatibility paths, package export policy, and publish-time package checks in one explicit `limina.config.mjs` file.
13
+
14
+ Limina is not a bundler and does not replace `tsc`, `vue-tsc`, tests, or release tooling. It coordinates them and verifies that the architecture they depend on stays consistent.
15
+
16
+ ## Why Limina?
17
+
18
+ Large TypeScript workspaces often need more than `tsc --noEmit`:
19
+
20
+ - project references must match real cross-project imports;
21
+ - production graph projects should not depend on tools or tests;
22
+ - browser/runtime output should not import Node builtins;
23
+ - `workspace:*` dependencies should resolve to source during graph checks;
24
+ - generated compatibility `paths` should not silently drift;
25
+ - built package outputs need consumer-facing checks before release;
26
+ - Vue, docs, playground, and smoke checks may need checker-specific tooling outside native `tsc -b`.
27
+
28
+ Limina makes these rules reviewable, runnable, and suitable for CI.
29
+
30
+ ## Features
31
+
32
+ - **Project graph validation**: checks reachable TypeScript declaration leaves, references, graph-owned imports, package boundaries, and label-based deny rules.
33
+ - **Typecheck coverage proof**: verifies that reachable declaration leaves match strict local typecheck companions and that source files are covered by checker entries or allowlist entries.
34
+ - **Compatibility path generation**: writes opt-in `tsconfig.dts.paths.generated.json` files for `workspace:*` dependencies whose package exports still point at build artifacts.
35
+ - **Checker target runner**: runs configured TypeScript and UI-framework checker entries in `typecheck` or `build` execution mode.
36
+ - **Published package checks**: validates built package outputs with `publint`, Are The Types Wrong, and a runtime import boundary audit.
37
+ - **Composable pipelines**: combines built-in checks and shell commands into named workflows such as `typecheck`, `package`, and `publish`.
38
+ - **Typed configuration**: ships `defineConfig(...)` for editor hints and typed user configs.
39
+
40
+ ## Requirements
41
+
42
+ - Node.js `^20.19.0 || >=22.12.0`
43
+ - pnpm workspace with `pnpm-workspace.yaml`
44
+ - TypeScript installed in the consuming repository
45
+ - ESM-compatible `limina.config.mjs`
46
+
47
+ ## Installation
48
+
49
+ ```sh
50
+ pnpm add -D limina typescript
51
+ ```
52
+
53
+ ## Quick start
54
+
55
+ Create `limina.config.mjs` at the workspace root:
56
+
57
+ ```js
58
+ import { defineConfig } from 'limina';
59
+
60
+ export default defineConfig({
61
+ config: {
62
+ checkers: {
63
+ typescript: {
64
+ preset: 'tsc',
65
+ entry: 'tsconfig.build.json',
66
+ },
67
+ vue: {
68
+ preset: 'vue-tsc',
69
+ entry: 'tsconfig.vue.build.json',
70
+ },
71
+ },
72
+ },
73
+
74
+ graph: {
75
+ rules: {
76
+ 'runtime-client': {
77
+ deny: {
78
+ refs: [
79
+ {
80
+ path: 'packages/app/src/node/tsconfig.lib.dts.json',
81
+ reason: 'client runtime must not depend on the Node runtime',
82
+ },
83
+ ],
84
+ },
85
+ },
86
+ },
87
+ },
88
+
89
+ proof: {
90
+ allowlist: [
91
+ {
92
+ file: 'src/generated/runtime.d.ts',
93
+ reason: 'Generated declaration stub covered by the runtime build process.',
94
+ },
95
+ ],
96
+ },
97
+
98
+ packageChecks: {
99
+ targets: [
100
+ {
101
+ name: '@acme/core',
102
+ outDir: 'packages/core/dist',
103
+ },
104
+ ],
105
+ },
106
+
107
+ pipelines: {
108
+ package: ['package:check'],
109
+ publish: ['graph:check', 'proof:check', 'package:check'],
110
+ },
111
+ });
112
+ ```
113
+
114
+ Add scripts:
115
+
116
+ ```json
117
+ {
118
+ "scripts": {
119
+ "typecheck": "limina check",
120
+ "lint:package": "limina package check",
121
+ "prepublishOnly": "limina check publish"
122
+ }
123
+ }
124
+ ```
125
+
126
+ Run checks:
127
+
128
+ ```sh
129
+ pnpm typecheck
130
+ pnpm exec limina graph check
131
+ pnpm exec limina package check --package @acme/core
132
+ ```
133
+
134
+ ## Concepts
135
+
136
+ ### Checker entry
137
+
138
+ Each checker has one required `config.checkers.<name>.entry`, usually a `tsconfig*.build.json` graph aggregator. Built-in first-class presets (`tsc` and `vue-tsc`) participate in graph, source, proof, and build checks. Source-only presets such as `svelte-check` prove source coverage and run direct checker execution through `limina checker typecheck`.
139
+
140
+ ### Declaration leaf and local companion
141
+
142
+ Declaration leaves should have strict local companions. For example, `tsconfig.lib.dts.json` pairs with `tsconfig.lib.json`, and `tsconfig.dts.json` pairs with `tsconfig.json`.
143
+
144
+ The default `tsconfig.json` is the IDE/typecheck entry for its directory. A single-environment directory should use it as the local leaf; a multi-environment directory should make it a pure aggregator with `files: []` and `references`.
145
+
146
+ ### Source dependencies and artifact dependencies
147
+
148
+ A dependency declared as `workspace:*` is considered a source dependency. It should be represented by project references and source-facing package exports.
149
+
150
+ A dependency declared as `link:`, `file:`, `catalog:`, or normal semver is treated as an artifact dependency. It should not be modeled as a project reference unless it is intentionally consumed as source.
151
+
152
+ ### Package checks
153
+
154
+ Source graph checks do not prove that an installed package works for consumers. `limina package check` inspects built package outputs under `packageChecks.targets[].outDir` and checks the actual package manifest, exports, type resolution, and runtime imports. Publishable outputs whose `package.json` does not set `private: true` must also include root `README.md` and `LICENSE.md` files.
155
+
156
+ ## CLI
157
+
158
+ ```sh
159
+ limina [--config limina.config.mjs] [--mode mode] <command>
160
+ ```
161
+
162
+ | Command | Description |
163
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------- |
164
+ | `limina check` | Run the built-in default check pipeline. |
165
+ | `limina check <pipeline>` | Run a named user pipeline from `pipelines`. |
166
+ | `limina graph check` | Validate project references and architecture import rules. |
167
+ | `limina proof check` | Prove declaration configs, local typecheck configs, and source coverage stay aligned. |
168
+ | `limina paths generate` | Generate compatibility source `paths` configs for artifact-facing workspace exports. |
169
+ | `limina paths apply` | Compatibility alias for `paths generate`. |
170
+ | `limina paths check` | Fail when generated path files are stale. |
171
+ | `limina checker build` | Run build execution for checker entries that support it. |
172
+ | `limina checker typecheck` | Run source-only checker entries such as `svelte-check`. |
173
+ | `limina package check` | Run configured package output checks. |
174
+ | `limina package check --package <name>` | Check one configured package target. |
175
+ | `limina package check --tool <tool>` | Run only `publint`, `attw`, or `boundary`. |
176
+ | `limina package check --attw-profile <profile>` | Override the ATTW profile: `strict`, `node16`, or `esm-only`. |
177
+
178
+ ## Configuration reference
179
+
180
+ ### `config`
181
+
182
+ ```js
183
+ config: {
184
+ checkers: {
185
+ typescript: {
186
+ preset: 'tsc',
187
+ entry: 'tsconfig.build.json',
188
+ },
189
+ vue: {
190
+ preset: 'vue-tsc',
191
+ entry: 'tsconfig.vue.build.json',
192
+ },
193
+ },
194
+ source: {
195
+ exclude: ['node_modules', 'dist', '.tsbuild'],
196
+ },
197
+ }
198
+ ```
199
+
200
+ `config.checkers` defines checker entries. Every configured checker must declare a non-empty `entry` and use a built-in preset. Checker `extensions` are fixed by Limina and cannot be configured; if `source.include` is omitted, Limina derives the source boundary from configured checker extensions, then applies `source.exclude`.
201
+
202
+ ### `graph`
203
+
204
+ ```js
205
+ graph: {
206
+ rules: {
207
+ 'runtime-client': {
208
+ deny: {
209
+ refs: [
210
+ {
211
+ path: 'packages/app/src/node/tsconfig.lib.dts.json',
212
+ reason: 'client runtime must stay independent from Node runtime',
213
+ },
214
+ ],
215
+ deps: [
216
+ {
217
+ name: '@acme/internal-node',
218
+ reason: 'client runtime must not consume Node-only packages',
219
+ },
220
+ {
221
+ name: 'node:*',
222
+ reason: 'client runtime must not import Node builtins',
223
+ },
224
+ {
225
+ name: '#server/*',
226
+ reason: 'client runtime must not use server-only package imports',
227
+ },
228
+ ],
229
+ },
230
+ },
231
+ },
232
+ }
233
+ ```
234
+
235
+ A declaration leaf opts into a rule by adding a `limina` label:
236
+
237
+ ```jsonc
238
+ {
239
+ "limina": "runtime-client",
240
+ "extends": ["./tsconfig.json", "../../tsconfig.dts.base.json"],
241
+ "references": [],
242
+ }
243
+ ```
244
+
245
+ ### `paths`
246
+
247
+ ```js
248
+ paths: {
249
+ generatedFileName: 'tsconfig.dts.paths.generated.json',
250
+ conditionPriority: ['source', 'development', 'types'],
251
+ artifactDirectories: ['dist', 'build', 'lib', 'esm', 'cjs', 'out'],
252
+ }
253
+ ```
254
+
255
+ Use generated paths only when a workspace package must keep artifact-facing exports while still being consumed as a graph-owned source dependency.
256
+
257
+ ### `proof`
258
+
259
+ ```js
260
+ proof: {
261
+ allowlist: [
262
+ {
263
+ file: 'src/generated/runtime.d.ts',
264
+ reason: 'Generated file validated by the build pipeline.',
265
+ },
266
+ ],
267
+ }
268
+ ```
269
+
270
+ Checker entries cover files validated by TypeScript or framework-aware tools. Allowlist entries are the final fallback after all configured checker entries fail to cover a source file; they should be rare and must include a reason.
271
+
272
+ ### `packageChecks`
273
+
274
+ ```js
275
+ packageChecks: {
276
+ targets: [
277
+ {
278
+ name: '@acme/core',
279
+ outDir: 'packages/core/dist',
280
+ checks: ['publint', 'attw', 'boundary'],
281
+ publint: { strict: true },
282
+ attw: { profile: 'esm-only' },
283
+ boundary: {
284
+ environment: (file) => file.startsWith('node/') ? 'node' : 'browser',
285
+ ignoredExternalPackages: ['@acme/runtime-shim'],
286
+ },
287
+ },
288
+ ],
289
+ }
290
+ ```
291
+
292
+ `outDir` must point at the built package directory that contains the publish-ready `package.json`. If that manifest does not set `private: true`, the same directory must also contain `README.md` and `LICENSE.md`.
293
+
294
+ ### `pipelines`
295
+
296
+ ```js
297
+ pipelines: {
298
+ package: [
299
+ { type: 'command', command: 'pnpm', args: ['build'] },
300
+ 'package:check',
301
+ ],
302
+ }
303
+ ```
304
+
305
+ `limina check` runs the built-in default pipeline: `graph:check`, `source:check`, `proof:check`, `checker:build`, and `checker:typecheck`. `limina check <pipeline>` only runs user pipelines from `limina.config.mjs#pipelines`; if the name is missing, Limina reports the missing pipeline and asks you to define it there.
306
+
307
+ String steps can be built-in task names or simple commands. Use object command steps when arguments, `cwd`, or `env` need to be explicit.
308
+
309
+ ## CI example
310
+
311
+ ```yaml
312
+ name: Typecheck
313
+
314
+ on:
315
+ pull_request:
316
+ push:
317
+ branches: [main]
318
+
319
+ jobs:
320
+ typecheck:
321
+ runs-on: ubuntu-latest
322
+ steps:
323
+ - uses: actions/checkout@v4
324
+ - uses: pnpm/action-setup@v4
325
+ - uses: actions/setup-node@v4
326
+ with:
327
+ node-version: 20.19.0
328
+ cache: pnpm
329
+ - run: pnpm install --frozen-lockfile
330
+ - run: pnpm exec limina check
331
+ ```
332
+
333
+ ## Programmatic API
334
+
335
+ ```ts
336
+ import { defineConfig, loadConfig } from 'limina';
337
+
338
+ export default defineConfig({
339
+ pipelines: {
340
+ typecheck: ['graph:check'],
341
+ },
342
+ });
343
+
344
+ const config = await loadConfig();
345
+ ```
346
+
347
+ Most users only need `defineConfig(...)`. `loadConfig(...)` is available for custom wrappers and tests.
348
+
349
+ ## Troubleshooting
350
+
351
+ ### `Unable to find limina config`
352
+
353
+ Run the command from inside the workspace, or pass `--config ./limina.config.mjs`.
354
+
355
+ ### `no pnpm-workspace.yaml was found`
356
+
357
+ Limina infers the workspace root from `pnpm-workspace.yaml`. Place the config inside the workspace or pass a config path located under the workspace root.
358
+
359
+ ### `packageChecks.targets[x].outDir` is invalid
360
+
361
+ Set `outDir` to the built package directory, not the source package directory, unless that directory is itself the publish-ready package output.
362
+
363
+ ### Generated paths are stale
364
+
365
+ Run:
366
+
367
+ ```sh
368
+ pnpm exec limina paths generate
369
+ ```
370
+
371
+ Then add the generated file to the first position of the listed `extends` arrays and commit the generated file if your repository policy requires reproducible `tsc -b` without a pre-generation step.
372
+
373
+ ## Design principles
374
+
375
+ - Explicit policy is better than hidden presets.
376
+ - Source graph checks and package artifact checks validate different surfaces.
377
+ - Build graph configs should be strict, small, and directly referenced.
378
+ - Generated compatibility paths should be transitional, not the default architecture.
379
+ - Limina should fail with actionable messages instead of silently accepting graph drift.
380
+
381
+ ## License
382
+
383
+ MIT
package/bin/limina.js CHANGED
File without changes
@@ -35,39 +35,30 @@ function isPathInsideDirectory(filePath, directoryPath) {
35
35
  function createTscCommandTarget(options) {
36
36
  const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
37
37
  return {
38
- args: options.executionKind === "build" ? [
38
+ args: [
39
39
  "-b",
40
40
  relativeConfigPath,
41
41
  "--pretty",
42
42
  "false"
43
- ] : [
44
- "-p",
45
- relativeConfigPath,
46
- "--noEmit"
47
43
  ],
48
44
  command: options.commandOverride ?? "tsc",
49
- label: options.executionKind === "build" ? `tsc -b ${relativeConfigPath}` : `tsc: ${relativeConfigPath}`
45
+ label: `tsc -b ${relativeConfigPath}`
50
46
  };
51
47
  }
52
48
  function createVueTscCommandTarget(options) {
53
49
  const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
54
50
  return {
55
- args: options.executionKind === "build" ? [
51
+ args: [
56
52
  "-b",
57
53
  relativeConfigPath,
58
54
  "--pretty",
59
55
  "false"
60
- ] : [
61
- "-p",
62
- relativeConfigPath,
63
- "--noEmit"
64
56
  ],
65
57
  command: "vue-tsc",
66
- label: options.executionKind === "build" ? `${options.checker.name}: vue-tsc -b ${relativeConfigPath}` : `${options.checker.name}: vue-tsc -p ${relativeConfigPath}`
58
+ label: `${options.checker.name}: vue-tsc -b ${relativeConfigPath}`
67
59
  };
68
60
  }
69
61
  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
62
  const relativeConfigPath = toRelativePath(options.projectRootDir, options.configPath);
72
63
  return {
73
64
  args: ["--tsconfig", relativeConfigPath],
@@ -79,10 +70,11 @@ const builtinCheckerAdapters = {
79
70
  "svelte-check": {
80
71
  createCommandTarget: createSvelteCheckCommandTarget,
81
72
  defaultExtensions: [".svelte"],
82
- graph: false,
83
- packageName: "svelte-check",
73
+ execution: "typecheck",
74
+ packageNames: ["svelte-check"],
84
75
  preset: "svelte-check",
85
- supportedExecutions: ["typecheck"]
76
+ sourceGraph: false,
77
+ tier: "source-only"
86
78
  },
87
79
  tsc: {
88
80
  createCommandTarget: createTscCommandTarget,
@@ -96,18 +88,20 @@ const builtinCheckerAdapters = {
96
88
  ".d.mts",
97
89
  ".json"
98
90
  ],
99
- graph: true,
100
- packageName: "typescript",
91
+ execution: "build",
92
+ packageNames: ["typescript"],
101
93
  preset: "tsc",
102
- supportedExecutions: ["typecheck", "build"]
94
+ sourceGraph: true,
95
+ tier: "first-class"
103
96
  },
104
97
  "vue-tsc": {
105
98
  createCommandTarget: createVueTscCommandTarget,
106
99
  defaultExtensions: [".vue"],
107
- graph: false,
108
- packageName: "vue-tsc",
100
+ execution: "build",
101
+ packageNames: ["vue-tsc", "@vue/compiler-sfc"],
109
102
  preset: "vue-tsc",
110
- supportedExecutions: ["typecheck", "build"]
103
+ sourceGraph: true,
104
+ tier: "first-class"
111
105
  }
112
106
  };
113
107
  function isBuiltinCheckerPreset(value) {
@@ -130,15 +124,16 @@ function collectMissingCheckerPeerDependencies(options) {
130
124
  const resolvePackage = options.resolvePackage ?? resolveCheckerPackageFromRoot;
131
125
  const missingCheckersByPackage = /* @__PURE__ */ new Map();
132
126
  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);
127
+ const packageNames = getCheckerAdapter(checker.preset)?.packageNames ?? [];
128
+ for (const packageName of packageNames) {
129
+ if (resolvePackage({
130
+ packageName,
131
+ projectRootDir: options.projectRootDir
132
+ })) continue;
133
+ const checkerNames = missingCheckersByPackage.get(packageName) ?? /* @__PURE__ */ new Set();
134
+ checkerNames.add(checker.name);
135
+ missingCheckersByPackage.set(packageName, checkerNames);
136
+ }
142
137
  }
143
138
  return [...missingCheckersByPackage.entries()].map(([packageName, checkerNames]) => ({
144
139
  checkerNames: [...checkerNames].sort((left, right) => left.localeCompare(right)),
@@ -157,10 +152,9 @@ function formatMissingCheckerPeerDependencies(missingDependencies) {
157
152
  ].join("\n");
158
153
  }
159
154
  function getCheckerExtensions(checker) {
160
- if (checker.extensions) return normalizeExtensions(checker.extensions);
161
155
  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.`);
156
+ if (adapter) return normalizeExtensions(adapter.defaultExtensions);
157
+ throw new Error(`Checker preset "${checker.preset}" is not supported.`);
164
158
  }
165
159
  function getResolvedCheckers(config) {
166
160
  const checkers = config.config?.checkers;
@@ -185,11 +179,9 @@ function defineConfig(config) {
185
179
  return config;
186
180
  }
187
181
  const nonEmptyStringSchema = z.string().refine((value) => value.trim().length > 0);
188
- const dotPrefixedStringSchema = nonEmptyStringSchema.refine((value) => value.startsWith("."));
189
182
  const checkerObjectSchema = z.looseObject({});
190
183
  const checkerConfigShapeSchema = z.looseObject({
191
184
  entry: nonEmptyStringSchema,
192
- extensions: z.array(dotPrefixedStringSchema).nonempty().optional(),
193
185
  preset: nonEmptyStringSchema
194
186
  });
195
187
  const liminaConfigShapeSchema = z.looseObject({ config: z.looseObject({ checkers: z.record(z.string(), checkerConfigShapeSchema).optional() }).optional() });
@@ -245,15 +237,6 @@ function formatLiminaConfigShapeIssue(value, issue) {
245
237
  ` value: ${formatUnknownValue(getValueAtPath(value, pathSegments))}`,
246
238
  " reason: checker entry must be a non-empty string path."
247
239
  ].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
240
  }
258
241
  return [
259
242
  "Invalid Limina config:",
@@ -279,11 +262,11 @@ function collectCheckerConfigProblems(config) {
279
262
  if (!checkerObjectResult.success) continue;
280
263
  const checkerRecord = checkerObjectResult.data;
281
264
  const preset = checkerRecord.preset;
282
- if (checkerRecord.extensions === void 0 && typeof preset === "string" && !isBuiltinCheckerPreset(preset)) problems.push([
265
+ if (Object.hasOwn(checkerRecord, "extensions")) problems.push([
283
266
  "Invalid Limina checker config:",
284
267
  ` field: ${field}.extensions`,
285
- " value: undefined",
286
- " reason: extensions may only be omitted for built-in presets."
268
+ ` value: ${formatUnknownValue(checkerRecord.extensions)}`,
269
+ " reason: checker extensions are fixed by built-in presets and cannot be configured."
287
270
  ].join("\n"));
288
271
  if (Object.hasOwn(checkerRecord, "routes")) problems.push([
289
272
  "Invalid Limina checker config:",
@@ -374,4 +357,4 @@ async function loadConfig(options = {}) {
374
357
  }
375
358
 
376
359
  //#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 };
360
+ export { validateLiminaConfig as a, getCheckerAdapter as c, normalizeAbsolutePath as d, normalizeSlashes as f, toRelativePath as g, toPosixPath as h, loadConfig as i, normalizeExtensions as l, toAbsolutePath as m, getActiveCheckerExtensions as n, collectMissingCheckerPeerDependencies as o, normalizeWorkspacePath as p, getActiveCheckers as r, formatMissingCheckerPeerDependencies as s, defineConfig as t, isPathInsideDirectory as u };