@evertondgn/solid-tooling 0.3.0 → 0.4.0

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/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # @evertondgn/solid-tooling
2
2
 
3
- Lint preset, formatter config, TypeScript configs and test configs for SolidJS 2
4
- applications built with [Vite+](https://viteplus.dev/guide/).
3
+ Lint preset, formatter config, TypeScript configs, test configs, the Knip and
4
+ Stylelint configs and the Vite+ application config for SolidJS 2 applications
5
+ built with [Vite+](https://viteplus.dev/guide/).
5
6
 
6
- The package contains the lint, format, TypeScript and test setup of
7
+ The package contains the lint, format, TypeScript, test, build, dead code and
8
+ CSS lint setup of
7
9
  [boilerplate_solidjs](https://github.com/everton-dgn/boilerplate_solidjs), a
8
10
  SolidJS application template. Applications created from it install the package
9
11
  instead of copying the files, so a rule change reaches them through a version
@@ -24,15 +26,24 @@ exact versions the package is tested with. Install the versions listed in its
24
26
  `peerDependencies`. `typescript` is not a peer dependency: the package is tested
25
27
  with the version in its `devDependencies`.
26
28
 
27
- `eslint-plugin-solid` comes with the package, so the application does not
28
- declare it.
29
+ `eslint-plugin-solid`, `stylelint` and `stylelint-use-nesting` come with the
30
+ package, so the application does not declare them.
29
31
 
30
- The test entries need the test tools the application already runs:
32
+ Some entries need tools the application already runs:
31
33
 
32
- | Entry | The application installs |
33
- | -------------- | ------------------------------------------------------------------------------ |
34
- | `./vitest` | `@vitest/browser-playwright`, `@vitest/coverage-v8`, `happy-dom`, `playwright` |
35
- | `./playwright` | `@playwright/test`, an optional peer dependency pinned like the others |
34
+ | Entry | The application installs |
35
+ | -------------- | ------------------------------------------------------------------------------- |
36
+ | `./vitest` | `@vitest/browser-playwright`, `@vitest/coverage-v8`, `happy-dom`, `playwright` |
37
+ | `./playwright` | `@playwright/test`, an optional peer dependency pinned like the others |
38
+ | `./knip` | `knip` and `filesystem-routing`, optional peer dependencies pinned the same way |
39
+
40
+ `./vite` also needs the test tools of `./vitest`, and loads four plugin
41
+ packages: `@solidjs/vite-plugin`, `nitro`, `filesystem-routing` and
42
+ `unplugin-icons`. They are optional peer dependencies pinned like the others.
43
+ Install the versions listed in `peerDependencies` as direct dependencies of the
44
+ application: Bun applies `overrides` and `patchedDependencies` only from the
45
+ root `package.json`, and the Knip plugin of `unplugin-icons` only turns on with
46
+ a direct dependency.
36
47
 
37
48
  ## Install
38
49
 
@@ -86,12 +97,20 @@ projects:
86
97
  | `dom` | `src/**/*.dom.test.{ts,tsx}` | happy-dom at `http://localhost/en` |
87
98
  | `browser` | `src/**/*.browser.test.{ts,tsx}` | Chromium through Playwright, with the CSS loaded |
88
99
 
100
+ `defineSolidApp` from `./vite` already calls `solidTest`. An application that
101
+ builds its own Vite config calls it directly:
102
+
89
103
  ```ts
90
104
  // vite.config.ts
91
105
  import { solidTest } from '@evertondgn/solid-tooling/vitest'
106
+ import solid from '@solidjs/vite-plugin'
107
+ import { defineConfig } from 'vite-plus'
92
108
 
93
109
  export default defineConfig({
94
- test: solidTest({ root: import.meta.dirname, plugins: componentPlugins })
110
+ test: solidTest({
111
+ root: import.meta.dirname,
112
+ plugins: () => [solid({ serverFunctions: true })]
113
+ })
95
114
  })
96
115
  ```
97
116
 
@@ -106,7 +125,8 @@ before each browser test.
106
125
  The `node` project does not load the Solid and routing plugins, so a plugin of
107
126
  the preset serves `server-only`, `virtual:file-routes` and
108
127
  `virtual:solid-server-function-handler`. Knip does not know `server-only`, a
109
- virtual module of `@solidjs/vite-plugin`: list it in `ignoreDependencies`.
128
+ virtual module of `@solidjs/vite-plugin`: pass it in the `ignoreDependencies` of
129
+ [`defineKnipConfig`](#knip).
110
130
 
111
131
  `definePlaywrightConfig` builds the Playwright config from the application root:
112
132
 
@@ -132,6 +152,257 @@ Each serial suite gets its own project: `markdown` runs with one worker,
132
152
  `serverLogs` runs last as the teardown of the others, and `publicationLoad` runs
133
153
  alone, against two servers, with `TEST_PUBLICATION_LOAD=true`.
134
154
 
155
+ ## Application config
156
+
157
+ `defineSolidApp` returns the whole `vite.config.ts` of a boilerplate
158
+ application: the Solid, routing, icons and Nitro plugins, the headers of the
159
+ static files, the build, CSS, server and preview blocks, the `vp run` tasks, and
160
+ `lint`, `fmt` and `test` from the presets above.
161
+
162
+ ```ts
163
+ // vite.config.ts
164
+ import { defineSolidApp } from '@evertondgn/solid-tooling/vite'
165
+
166
+ import { SECURITY_HEADERS } from './src/middleware/securityHeaders/constants.ts'
167
+
168
+ export default defineSolidApp({
169
+ root: import.meta.dirname,
170
+ middleware: './src/middleware/index.ts',
171
+ securityHeaders: SECURITY_HEADERS,
172
+ staticPaths: ['/favicon/**']
173
+ })
174
+ ```
175
+
176
+ | Option | Default | Use |
177
+ | ----------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
178
+ | `root` | required | Application folder. `.env`, `.env.test`, `project.inlang/settings.json` and the icon folders resolve from it |
179
+ | `securityHeaders` | required | Values of `strict-transport-security`, `x-content-type-options` and `cross-origin-resource-policy` for the static files |
180
+ | `middleware` | none | `start.middleware` of the Solid plugin. It has to sit inside the application folder |
181
+ | `serverSetup` | `serverFunctions: true` | `serverFunctions.serverSetup` of the Solid plugin |
182
+ | `e2e` | production paths | `{ middleware, routes }` for `--mode e2e`: the middleware chain and the route folder of the fixtures, without route types |
183
+ | `icons` | none | Own `unplugin-icons` collections, as name to folder |
184
+ | `publicAssets` | none | `publicAssets` of Nitro. Each entry needs a `baseURL`, which also gets the static headers |
185
+ | `staticPaths` | none | Other static paths that get the headers, such as `/favicon/**` |
186
+ | `routeRules` | none | More Nitro route rules, applied after the static ones. A rule with the same path replaces the static one |
187
+ | `extend` | none | `(context) => ({ plugins, nitroModules })`, for plugins and Nitro modules the application still owns |
188
+ | `tasks` | none | Adds or replaces `vp run` tasks by name |
189
+ | `workspace` | none | `{ dependsOn, inputs }` for an application inside a monorepo, whose `bun.lock`, `bunfig.toml` and package builds sit at the root |
190
+
191
+ The preset fixes the rest:
192
+
193
+ - Plugins, in this order: the `extend` plugins, `unplugin-icons` (Solid
194
+ compiler, `aria-hidden` on every icon), `@solidjs/vite-plugin` with SSR and
195
+ server functions, `filesystem-routing` with HTTP methods and the route types
196
+ in `src/@types/routes.d.ts`, and Nitro with the `vercel` preset. The `dom` and
197
+ `browser` test projects get the `extend` plugins, the icons, Solid with server
198
+ functions only and the routes. The `test` mode loads no application plugin and
199
+ reads `.env.test` into `process.env`.
200
+ - The static headers go to `/assets/**`, each `publicAssets` folder, the
201
+ `staticPaths`, `/<locale>/**.md` for each locale of
202
+ `project.inlang/settings.json`, `/llms.txt`, `/llms-full.txt`, `/sitemap.xml`,
203
+ `/sitemaps/pages/**` and `/_markdown/static.json`. The static files leave the
204
+ CDN without going through the middleware, so these rules give them the headers
205
+ the middleware writes on the other responses.
206
+ - `build.manifest` only in the `e2e` mode, which the `TEST_PREBUILT` check of
207
+ `./playwright` relies on, and no comments in the output.
208
+ - Lightning CSS, with CSS Modules classes named `[content-hash]_[local]`.
209
+ - `resolve.tsconfigPaths`, and the dev server and the preview on the `HOST` and
210
+ `PORT` of `.env`. The dev server forwards the browser console to the terminal.
211
+
212
+ The plugins are imported inside `lazyPlugins`, so `vp lint`, `vp fmt` and
213
+ `vp check` read the config without loading them.
214
+
215
+ Nitro compiles the route rules with the `preMerge` of h3, which refuses rules
216
+ that overlap in part, such as `/pt/blog/**` next to `/pt/**.md`, and falls back
217
+ to its simple mode, with the same result and a warning. `defineSolidApp` throws
218
+ when a `publicAssets` entry has no `baseURL`, or one with a trailing slash, and
219
+ when a static path does not start with `/`. Build, dev and preview fail to load
220
+ the plugins when a `publicAssets` folder or a static path sits inside a locale
221
+ folder.
222
+
223
+ ### `extend`
224
+
225
+ `extend` runs inside `lazyPlugins`: once for the application in build, dev and
226
+ preview (`target: 'app'`), and once for each test project with a Vite server
227
+ (`target: 'test'`). Every load of the config calls it again, so a build also
228
+ calls it for the two test projects, whose plugins Vite does not use; keep side
229
+ effects out of it. It never runs while Vite+ only reads the config. Return new
230
+ plugin instances on each call. The `plugins` go before the fixed ones, and the
231
+ `nitroModules` go to Nitro; the `test` target ignores them. One call returns
232
+ both, so a value that a plugin and a module share, such as a build id, stays the
233
+ same.
234
+
235
+ ```ts
236
+ export default defineSolidApp({
237
+ root: import.meta.dirname,
238
+ securityHeaders: SECURITY_HEADERS,
239
+ async extend() {
240
+ const { i18nPlugin } = await import('./tooling/i18n/i18nPlugin/index.ts')
241
+ return { plugins: [i18nPlugin()] }
242
+ }
243
+ })
244
+ ```
245
+
246
+ ### Tasks
247
+
248
+ | Task | Command |
249
+ | ------------------- | ------------------------------------------------------------------------------------------------------- |
250
+ | `i18n-generate` | `bun tooling/i18n/generate.ts` |
251
+ | `lint-css` | `bun tooling/css/check.ts` |
252
+ | `dead-code-project` | `knip-bun --config tooling/knip.ts`, then again with `--strict` |
253
+ | `lint-project` | `vp lint`, after `lint-css` |
254
+ | `check-project` | `vp fmt --check && vp lint`, after `lint-css` |
255
+ | `typecheck-node` | `tsc --project tsconfig.node.json` |
256
+ | `test-tooling` | `node --test` over `tooling/**/__tests__` except `tooling/release/`, three workers on CI, with no cache |
257
+
258
+ The commands run the scripts of the boilerplate layout. An application without
259
+ one of them replaces the task through `tasks`. Knip reads task commands only
260
+ from a `run.tasks` block written in `vite.config.ts`, so it does not see these:
261
+ list the scripts they run as Knip entries.
262
+
263
+ With `workspace`, the cache inputs read `bun.lock`, `bunfig.toml` and the
264
+ `inputs` from the workspace root, and `lint-css`, `dead-code-project` and
265
+ `typecheck-node` depend on the tasks in `dependsOn`:
266
+
267
+ ```ts
268
+ workspace: {
269
+ dependsOn: ['@evertondgn/solid-tooling#build'],
270
+ inputs: ['packages/solid-tooling/dist/**']
271
+ }
272
+ ```
273
+
274
+ ### Changing the result
275
+
276
+ Wrap the result in `defineConfig` and merge what the application needs. Vite+
277
+ removes the plugins it injected before it injects them again, so they are not
278
+ duplicated:
279
+
280
+ ```ts
281
+ import { defineSolidApp } from '@evertondgn/solid-tooling/vite'
282
+ import { defineConfig, mergeConfig } from 'vite-plus'
283
+
284
+ const app = defineSolidApp({ root: import.meta.dirname, securityHeaders })
285
+
286
+ export default defineConfig(env =>
287
+ mergeConfig(app(env), { server: { strictPort: true } })
288
+ )
289
+ ```
290
+
291
+ ## Knip
292
+
293
+ `defineKnipConfig` returns the Knip config of the application as a function, the
294
+ form Knip accepts as the default export of its config file:
295
+
296
+ ```ts
297
+ // tooling/knip.ts
298
+ import path from 'node:path'
299
+
300
+ import { defineKnipConfig } from '@evertondgn/solid-tooling/knip'
301
+
302
+ export default defineKnipConfig({
303
+ root: path.resolve(import.meta.dirname, '..'),
304
+ ignoreDependencies: ['server-only']
305
+ })
306
+ ```
307
+
308
+ Run it with `knip-bun --config tooling/knip.ts`, then with `--strict`. The
309
+ config lists:
310
+
311
+ - `src/entry-server.tsx`, `src/entry-client.tsx`, `src/middleware/index.ts`,
312
+ `env.ts` and `paraglide/runtime.js` as production entries. The Solid plugin
313
+ and the Paraglide runtime load them by path, and Knip does not read the Solid
314
+ plugin when `vite.config.ts` imports it with `import()`, as `./vite` does. An
315
+ application that imports `solid()` statically, with `start`, passes
316
+ `solidPluginImport: 'static'`: Knip's Solid plugin then finds the first four
317
+ itself, and listing them again would be a configuration hint.
318
+ - Every page and HTTP handler that `filesystem-routing` finds in `src/routes/`,
319
+ as production entries, and in `src/tests/fixtures/routes/` and
320
+ `src/tests/fixtures/e2e/routes/`, as test entries. The `filesystem-routing`
321
+ plugin of Knip is turned off, because it takes every file of `src/routes/` as
322
+ an entry and would hide unused helpers next to the routes.
323
+ - `src/**/*.{ts,tsx}` and `tooling/**/*.ts` as the project, with the tests out
324
+ of the production files.
325
+
326
+ The config turns on `treatConfigHintsAsErrors`, and Knip reports an entry or a
327
+ project pattern without files as a configuration hint. So each default above
328
+ enters only when the application has the file or the folder.
329
+
330
+ | Option | Default | Use |
331
+ | -------------------- | ----------------- | -------------------------------------------------------------- |
332
+ | `root` | required | The application folder |
333
+ | `configFile` | `tooling/knip.ts` | Path of the config file, relative to `root` |
334
+ | `entry` | none | More entries, such as a public API that only test fixtures use |
335
+ | `solidPluginImport` | `'lazy'` | `'static'` when `vite.config.ts` imports `solid()` statically |
336
+ | `ignoreBinaries` | none | Passed to Knip as is |
337
+ | `ignoreDependencies` | none | Passed to Knip as is |
338
+ | `ignoreIssues` | none | Passed to Knip, merged with the entry for `configFile` |
339
+ | `exclude` | none | Issue types to leave out, such as `catalogReferences` |
340
+
341
+ Knip adds the file given to `--config` to the entries in production mode, and
342
+ `--strict` only accepts `dependencies` there, so it reports this package as an
343
+ unlisted dependency of the config file. The config ignores `unlisted` issues in
344
+ `configFile` only: an import of this package from `src/` is still reported. An
345
+ application that keeps the config somewhere else passes its path in
346
+ `configFile`.
347
+
348
+ Nothing that depends on the application is a default. An ignore that matches
349
+ nothing is a configuration hint too, so `server-only` and the binaries of the
350
+ scripts are options. For a key without an option, wrap the function:
351
+
352
+ ```ts
353
+ const configure = defineKnipConfig({ root })
354
+
355
+ export default async function configureKnip() {
356
+ return { ...(await configure()), ignoreUnresolved: ['virtual:example'] }
357
+ }
358
+ ```
359
+
360
+ ## CSS lint
361
+
362
+ `checkCss(root)` lints `src/**/*.css` with Stylelint and the CSS policy of the
363
+ boilerplate, and checks the name of every CSS file in `src/` as
364
+ `project/css-filename` does for imports: `styles.module.css`, or `camelCase.css`
365
+ inside `src/theme/`. It returns the report and whether anything failed:
366
+
367
+ ```ts
368
+ // tooling/css/check.ts
369
+ import path from 'node:path'
370
+ import process from 'node:process'
371
+
372
+ import { checkCss } from '@evertondgn/solid-tooling/stylelint'
373
+
374
+ const { report, hasErrors } = await checkCss(
375
+ path.resolve(import.meta.dirname, '../..')
376
+ )
377
+
378
+ process.stdout.write(report)
379
+ if (hasErrors) process.exitCode = 1
380
+ ```
381
+
382
+ `defineStylelintConfig(root)` returns the Stylelint config that `checkCss` uses.
383
+ The policy:
384
+
385
+ - Classes use snake_case, nesting goes at most two levels deep, and adjacent
386
+ state selectors and media queries are nested (`stylelint-use-nesting`).
387
+ - Unknown or invalid properties, values, units, selectors, at-rules and media
388
+ features are errors, as are deprecated and unmatchable selectors.
389
+ - Outside `src/theme/`, colors, gradients, radii, shadows, font sizes and
390
+ weights and `z-index` come from tokens with `var()`, from the matching token
391
+ family. Literal colors, color functions and system colors are errors.
392
+ - Inside `src/theme/`, the token rules are off, so the theme can define the
393
+ values with `oklch()`, `oklab()`, `color-mix()`, `light-dark()` and system
394
+ colors. Hex, named colors and the `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`
395
+ and `color()` functions are still errors there.
396
+ - `var()` and animation names resolve against `src/theme/tokens/**/*.css` and
397
+ `src/theme/class/animation.css`. Stylelint stops the whole run when a
398
+ reference matches no file, so each one is passed only when it exists.
399
+ - An exception needs a `stylelint-disable` comment with the reason after `--`.
400
+
401
+ `stylelint` and `stylelint-use-nesting` are dependencies of this package. The
402
+ config passes the plugin by a path resolved from the package, because Stylelint
403
+ looks up a plugin name from the application and its working directory, where it
404
+ is not installed.
405
+
135
406
  ## Project layout the preset expects
136
407
 
137
408
  The rules match file paths relative to the folder where lint runs, so run it
@@ -275,6 +546,7 @@ overrides: [
275
546
  | `./fmt` | `fmt`: the formatter config |
276
547
  | `./tsconfig.base.json` | Compiler options, without application paths |
277
548
  | `./tsconfig.app.json` | The base plus the `types` and `include` of the boilerplate layout |
549
+ | `./vite` | `defineSolidApp(options)`, the whole `vite.config.ts` of an application |
278
550
  | `./vitest` | `solidTest(options)`, the `test` block of `vite.config.ts` |
279
551
  | `./playwright` | `definePlaywrightConfig(options)`, the Playwright config |
280
552
  | `./vitest-setup/browser` | Setup file of the browser project, loaded by `./vitest` |
@@ -284,11 +556,14 @@ overrides: [
284
556
  | `./architecture-plugin` | The `architecture` rule |
285
557
  | `./solid-plugin` | Re-export of `eslint-plugin-solid` |
286
558
  | `./css-filename` | `cssFilenameError(path)`, the CSS naming check, for a script outside lint |
559
+ | `./knip` | `defineKnipConfig(options)`, the Knip config |
560
+ | `./stylelint` | `checkCss(root)` and `defineStylelintConfig(root)`, the CSS lint |
287
561
 
288
- An application imports `./lint`, `./fmt`, `./vitest`, `./playwright` and one of
289
- the TypeScript configs. The plugin entries exist because Oxlint loads plugins by
290
- specifier, from the application; the setup and teardown entries are loaded by
291
- the test presets.
562
+ An application imports `./vite`, which brings `./lint`, `./fmt` and `./vitest`
563
+ along, `./playwright`, `./knip`, `./stylelint` and one of the TypeScript
564
+ configs. An application that only lints imports `./lint` and `./fmt`. The plugin
565
+ entries exist because Oxlint loads plugins by specifier, from the application;
566
+ the setup and teardown entries are loaded by the test presets.
292
567
 
293
568
  ## Formatter
294
569
 
@@ -348,6 +623,15 @@ break the `@/` imports of the end-to-end tests.
348
623
  (`.node.test`, `.dom.test`, `.browser.test`, `.e2e.test`), page tests in
349
624
  `src/tests/pages/`, Paraglide output in `paraglide/` and `.env.test` at the
350
625
  root.
626
+ - The Knip and CSS configs expect the same layout: pages in `src/routes/`,
627
+ fixture routes in `src/tests/fixtures/` and the theme in `src/theme/`.
628
+ - The Knip config ignores every unlisted dependency in its own file, not only
629
+ this package.
630
+ - `./vite` expects the same layout: its tasks run scripts in `tooling/`, and the
631
+ Markdown rules need the locale prefix of the Paraglide `urlPatterns` in every
632
+ URL. Nitro always uses the `vercel` preset. The plugin packages are pinned, so
633
+ a new version of Solid, Nitro, the router or the icons plugin needs a package
634
+ version tested with it.
351
635
 
352
636
  ## Versions
353
637
 
@@ -0,0 +1,9 @@
1
+ import { glob } from "node:fs/promises";
2
+ // src/has-match.ts
3
+ async function hasMatch({ root, pattern }) {
4
+ const matches = glob(pattern, { cwd: root });
5
+ const { done } = await matches.next();
6
+ await matches.return?.();
7
+ return !done;
8
+ }
9
+ export { hasMatch as t };
@@ -0,0 +1,12 @@
1
+ import { KnipConfiguration } from "knip";
2
+ type AppSettings = Pick<KnipConfiguration, 'ignoreBinaries' | 'ignoreDependencies' | 'ignoreIssues' | 'exclude'>;
3
+ type SolidPluginImport = 'lazy' | 'static';
4
+ type KnipOptions = AppSettings & {
5
+ root: string;
6
+ configFile?: string;
7
+ entry?: string[];
8
+ solidPluginImport?: SolidPluginImport;
9
+ };
10
+ type ConfigureKnip = () => Promise<KnipConfiguration>;
11
+ export declare function defineKnipConfig({ root, configFile, entry, solidPluginImport, ignoreBinaries, ignoreDependencies, ignoreIssues, exclude }: KnipOptions): ConfigureKnip;
12
+ export {};
package/dist/knip.mjs ADDED
@@ -0,0 +1,92 @@
1
+ import { t as hasMatch } from "./has-match-BITlj0EE.mjs";
2
+ import path from "node:path";
3
+ import { PageFileSystemRouter } from "filesystem-routing";
4
+ import { DEFAULT_EXTENSIONS } from "filesystem-routing/vite";
5
+ // src/knip/index.ts
6
+ const DEFAULT_CONFIG_FILE = "tooling/knip.ts";
7
+ const SOLID_ENTRIES = [
8
+ "src/entry-server.tsx",
9
+ "src/entry-client.tsx",
10
+ "src/middleware/index.ts",
11
+ "env.ts"
12
+ ];
13
+ const RUNTIME_ENTRIES = ["paraglide/runtime.js"];
14
+ const ROUTE_DIRECTORIES = [
15
+ {
16
+ directory: "src/routes",
17
+ isProduction: true
18
+ },
19
+ {
20
+ directory: "src/tests/fixtures/routes",
21
+ isProduction: false
22
+ },
23
+ {
24
+ directory: "src/tests/fixtures/e2e/routes",
25
+ isProduction: false
26
+ }
27
+ ];
28
+ const TOOLING_PROJECT = "tooling/**/*.ts";
29
+ function isModuleRef(value) {
30
+ return typeof value === "object" && value !== null && "src" in value && typeof value.src === "string";
31
+ }
32
+ function routeSources(route) {
33
+ return Object.entries(route).flatMap(([key, value]) => key.startsWith("$") && isModuleRef(value) ? [value.src] : []);
34
+ }
35
+ async function discoverRouteModules({ root, directory, isProduction }) {
36
+ const routes = await new PageFileSystemRouter({
37
+ dir: path.join(root, directory),
38
+ extensions: DEFAULT_EXTENSIONS,
39
+ httpMethods: true
40
+ }).getRoutes();
41
+ return [...new Set(routes.flatMap((route) => routeSources(route)))].map((source) => path.relative(root, source) + (isProduction ? "!" : ""));
42
+ }
43
+ async function conventionEntries({ root, patterns }) {
44
+ return (await Promise.all(patterns.map(async (pattern) => await hasMatch({
45
+ root,
46
+ pattern
47
+ }) ? [`${pattern}!`] : []))).flat();
48
+ }
49
+ function defineKnipConfig({ root, configFile = DEFAULT_CONFIG_FILE, entry = [], solidPluginImport = "lazy", ignoreBinaries, ignoreDependencies, ignoreIssues = {}, exclude }) {
50
+ const patterns = solidPluginImport === "static" ? RUNTIME_ENTRIES : [...SOLID_ENTRIES, ...RUNTIME_ENTRIES];
51
+ return async function configureKnip() {
52
+ const [conventions, routes, hasTooling] = await Promise.all([
53
+ conventionEntries({
54
+ root,
55
+ patterns
56
+ }),
57
+ Promise.all(ROUTE_DIRECTORIES.map((route) => discoverRouteModules({
58
+ root,
59
+ ...route
60
+ }))),
61
+ hasMatch({
62
+ root,
63
+ pattern: TOOLING_PROJECT
64
+ })
65
+ ]);
66
+ const configIssues = ignoreIssues[configFile] ?? [];
67
+ return {
68
+ entry: [.../* @__PURE__ */ new Set([
69
+ ...conventions,
70
+ ...entry,
71
+ ...routes.flat()
72
+ ])],
73
+ project: [
74
+ "src/**/*.{ts,tsx}!",
75
+ ...hasTooling ? [TOOLING_PROJECT] : [],
76
+ "!src/tests/**!",
77
+ "!src/**/__tests__/**!",
78
+ "!src/**/*.test.{ts,tsx}!"
79
+ ],
80
+ "filesystem-routing": false,
81
+ ...ignoreBinaries ? { ignoreBinaries } : {},
82
+ ...ignoreDependencies ? { ignoreDependencies } : {},
83
+ ignoreIssues: {
84
+ ...ignoreIssues,
85
+ [configFile]: [.../* @__PURE__ */ new Set([...configIssues, "unlisted"])]
86
+ },
87
+ ...exclude ? { exclude } : {},
88
+ treatConfigHintsAsErrors: true
89
+ };
90
+ };
91
+ }
92
+ export { defineKnipConfig };