@evertondgn/solid-tooling 0.3.0 → 0.5.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,12 @@
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, the Vite+ application config, the commitlint config and the
5
+ Git hooks for SolidJS 2 applications built with
6
+ [Vite+](https://viteplus.dev/guide/).
5
7
 
6
- The package contains the lint, format, TypeScript and test setup of
8
+ The package contains the lint, format, TypeScript, test, build, dead code, CSS
9
+ lint, commit message and Git hook setup of
7
10
  [boilerplate_solidjs](https://github.com/everton-dgn/boilerplate_solidjs), a
8
11
  SolidJS application template. Applications created from it install the package
9
12
  instead of copying the files, so a rule change reaches them through a version
@@ -24,15 +27,26 @@ exact versions the package is tested with. Install the versions listed in its
24
27
  `peerDependencies`. `typescript` is not a peer dependency: the package is tested
25
28
  with the version in its `devDependencies`.
26
29
 
27
- `eslint-plugin-solid` comes with the package, so the application does not
28
- declare it.
30
+ `eslint-plugin-solid`, `stylelint` and `stylelint-use-nesting` come with the
31
+ package, so the application does not declare them.
29
32
 
30
- The test entries need the test tools the application already runs:
33
+ Some entries need tools the application already runs:
31
34
 
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 |
35
+ | Entry | The application installs |
36
+ | ---------------- | ------------------------------------------------------------------------------- |
37
+ | `./vitest` | `@vitest/browser-playwright`, `@vitest/coverage-v8`, `happy-dom`, `playwright` |
38
+ | `./playwright` | `@playwright/test`, an optional peer dependency pinned like the others |
39
+ | `./knip` | `knip` and `filesystem-routing`, optional peer dependencies pinned the same way |
40
+ | `./commitlint` | `@commitlint/cli`, an optional peer dependency pinned the same way |
41
+ | `./lefthook.yml` | `lefthook`, an optional peer dependency pinned the same way |
42
+
43
+ `./vite` also needs the test tools of `./vitest`, and loads four plugin
44
+ packages: `@solidjs/vite-plugin`, `nitro`, `filesystem-routing` and
45
+ `unplugin-icons`. They are optional peer dependencies pinned like the others.
46
+ Install the versions listed in `peerDependencies` as direct dependencies of the
47
+ application: Bun applies `overrides` and `patchedDependencies` only from the
48
+ root `package.json`, and the Knip plugin of `unplugin-icons` only turns on with
49
+ a direct dependency.
36
50
 
37
51
  ## Install
38
52
 
@@ -86,12 +100,20 @@ projects:
86
100
  | `dom` | `src/**/*.dom.test.{ts,tsx}` | happy-dom at `http://localhost/en` |
87
101
  | `browser` | `src/**/*.browser.test.{ts,tsx}` | Chromium through Playwright, with the CSS loaded |
88
102
 
103
+ `defineSolidApp` from `./vite` already calls `solidTest`. An application that
104
+ builds its own Vite config calls it directly:
105
+
89
106
  ```ts
90
107
  // vite.config.ts
91
108
  import { solidTest } from '@evertondgn/solid-tooling/vitest'
109
+ import solid from '@solidjs/vite-plugin'
110
+ import { defineConfig } from 'vite-plus'
92
111
 
93
112
  export default defineConfig({
94
- test: solidTest({ root: import.meta.dirname, plugins: componentPlugins })
113
+ test: solidTest({
114
+ root: import.meta.dirname,
115
+ plugins: () => [solid({ serverFunctions: true })]
116
+ })
95
117
  })
96
118
  ```
97
119
 
@@ -106,7 +128,8 @@ before each browser test.
106
128
  The `node` project does not load the Solid and routing plugins, so a plugin of
107
129
  the preset serves `server-only`, `virtual:file-routes` and
108
130
  `virtual:solid-server-function-handler`. Knip does not know `server-only`, a
109
- virtual module of `@solidjs/vite-plugin`: list it in `ignoreDependencies`.
131
+ virtual module of `@solidjs/vite-plugin`: pass it in the `ignoreDependencies` of
132
+ [`defineKnipConfig`](#knip).
110
133
 
111
134
  `definePlaywrightConfig` builds the Playwright config from the application root:
112
135
 
@@ -132,6 +155,307 @@ Each serial suite gets its own project: `markdown` runs with one worker,
132
155
  `serverLogs` runs last as the teardown of the others, and `publicationLoad` runs
133
156
  alone, against two servers, with `TEST_PUBLICATION_LOAD=true`.
134
157
 
158
+ ## Application config
159
+
160
+ `defineSolidApp` returns the whole `vite.config.ts` of a boilerplate
161
+ application: the Solid, routing, icons and Nitro plugins, the headers of the
162
+ static files, the build, CSS, server and preview blocks, the `vp run` tasks, and
163
+ `lint`, `fmt` and `test` from the presets above.
164
+
165
+ ```ts
166
+ // vite.config.ts
167
+ import { defineSolidApp } from '@evertondgn/solid-tooling/vite'
168
+
169
+ import { SECURITY_HEADERS } from './src/middleware/securityHeaders/constants.ts'
170
+
171
+ export default defineSolidApp({
172
+ root: import.meta.dirname,
173
+ middleware: './src/middleware/index.ts',
174
+ securityHeaders: SECURITY_HEADERS,
175
+ staticPaths: ['/favicon/**']
176
+ })
177
+ ```
178
+
179
+ | Option | Default | Use |
180
+ | ----------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
181
+ | `root` | required | Application folder. `.env`, `.env.test`, `project.inlang/settings.json` and the icon folders resolve from it |
182
+ | `securityHeaders` | required | Values of `strict-transport-security`, `x-content-type-options` and `cross-origin-resource-policy` for the static files |
183
+ | `middleware` | none | `start.middleware` of the Solid plugin. It has to sit inside the application folder |
184
+ | `serverSetup` | `serverFunctions: true` | `serverFunctions.serverSetup` of the Solid plugin |
185
+ | `e2e` | production paths | `{ middleware, routes }` for `--mode e2e`: the middleware chain and the route folder of the fixtures, without route types |
186
+ | `icons` | none | Own `unplugin-icons` collections, as name to folder |
187
+ | `publicAssets` | none | `publicAssets` of Nitro. Each entry needs a `baseURL`, which also gets the static headers |
188
+ | `staticPaths` | none | Other static paths that get the headers, such as `/favicon/**` |
189
+ | `routeRules` | none | More Nitro route rules, applied after the static ones. A rule with the same path replaces the static one |
190
+ | `extend` | none | `(context) => ({ plugins, nitroModules })`, for plugins and Nitro modules the application still owns |
191
+ | `tasks` | none | Adds or replaces `vp run` tasks by name |
192
+ | `workspace` | none | `{ dependsOn, inputs }` for an application inside a monorepo, whose `bun.lock`, `bunfig.toml` and package builds sit at the root |
193
+
194
+ The preset fixes the rest:
195
+
196
+ - Plugins, in this order: the `extend` plugins, `unplugin-icons` (Solid
197
+ compiler, `aria-hidden` on every icon), `@solidjs/vite-plugin` with SSR and
198
+ server functions, `filesystem-routing` with HTTP methods and the route types
199
+ in `src/@types/routes.d.ts`, and Nitro with the `vercel` preset. The `dom` and
200
+ `browser` test projects get the `extend` plugins, the icons, Solid with server
201
+ functions only and the routes. The `test` mode loads no application plugin and
202
+ reads `.env.test` into `process.env`.
203
+ - The static headers go to `/assets/**`, each `publicAssets` folder, the
204
+ `staticPaths`, `/<locale>/**.md` for each locale of
205
+ `project.inlang/settings.json`, `/llms.txt`, `/llms-full.txt`, `/sitemap.xml`,
206
+ `/sitemaps/pages/**` and `/_markdown/static.json`. The static files leave the
207
+ CDN without going through the middleware, so these rules give them the headers
208
+ the middleware writes on the other responses.
209
+ - `build.manifest` only in the `e2e` mode, which the `TEST_PREBUILT` check of
210
+ `./playwright` relies on, and no comments in the output.
211
+ - Lightning CSS, with CSS Modules classes named `[content-hash]_[local]`.
212
+ - `resolve.tsconfigPaths`, and the dev server and the preview on the `HOST` and
213
+ `PORT` of `.env`. The dev server forwards the browser console to the terminal.
214
+
215
+ The plugins are imported inside `lazyPlugins`, so `vp lint`, `vp fmt` and
216
+ `vp check` read the config without loading them.
217
+
218
+ Nitro compiles the route rules with the `preMerge` of h3, which refuses rules
219
+ that overlap in part, such as `/pt/blog/**` next to `/pt/**.md`, and falls back
220
+ to its simple mode, with the same result and a warning. `defineSolidApp` throws
221
+ when a `publicAssets` entry has no `baseURL`, or one with a trailing slash, and
222
+ when a static path does not start with `/`. Build, dev and preview fail to load
223
+ the plugins when a `publicAssets` folder or a static path sits inside a locale
224
+ folder.
225
+
226
+ ### `extend`
227
+
228
+ `extend` runs inside `lazyPlugins`: once for the application in build, dev and
229
+ preview (`target: 'app'`), and once for each test project with a Vite server
230
+ (`target: 'test'`). Every load of the config calls it again, so a build also
231
+ calls it for the two test projects, whose plugins Vite does not use; keep side
232
+ effects out of it. It never runs while Vite+ only reads the config. Return new
233
+ plugin instances on each call. The `plugins` go before the fixed ones, and the
234
+ `nitroModules` go to Nitro; the `test` target ignores them. One call returns
235
+ both, so a value that a plugin and a module share, such as a build id, stays the
236
+ same.
237
+
238
+ ```ts
239
+ export default defineSolidApp({
240
+ root: import.meta.dirname,
241
+ securityHeaders: SECURITY_HEADERS,
242
+ async extend() {
243
+ const { i18nPlugin } = await import('./tooling/i18n/i18nPlugin/index.ts')
244
+ return { plugins: [i18nPlugin()] }
245
+ }
246
+ })
247
+ ```
248
+
249
+ ### Tasks
250
+
251
+ | Task | Command |
252
+ | ------------------- | ------------------------------------------------------------------------------------------------------- |
253
+ | `i18n-generate` | `bun tooling/i18n/generate.ts` |
254
+ | `lint-css` | `bun tooling/css/check.ts` |
255
+ | `dead-code-project` | `knip-bun --config tooling/knip.ts`, then again with `--strict` |
256
+ | `lint-project` | `vp lint`, after `lint-css` |
257
+ | `check-project` | `vp fmt --check && vp lint`, after `lint-css` |
258
+ | `typecheck-node` | `tsc --project tsconfig.node.json` |
259
+ | `test-tooling` | `node --test` over `tooling/**/__tests__` except `tooling/release/`, three workers on CI, with no cache |
260
+
261
+ The commands run the scripts of the boilerplate layout. An application without
262
+ one of them replaces the task through `tasks`. Knip reads task commands only
263
+ from a `run.tasks` block written in `vite.config.ts`, so it does not see these:
264
+ list the scripts they run as Knip entries.
265
+
266
+ With `workspace`, the cache inputs read `bun.lock`, `bunfig.toml` and the
267
+ `inputs` from the workspace root, and `lint-css`, `dead-code-project` and
268
+ `typecheck-node` depend on the tasks in `dependsOn`:
269
+
270
+ ```ts
271
+ workspace: {
272
+ dependsOn: ['@evertondgn/solid-tooling#build'],
273
+ inputs: ['packages/solid-tooling/dist/**']
274
+ }
275
+ ```
276
+
277
+ ### Changing the result
278
+
279
+ Wrap the result in `defineConfig` and merge what the application needs. Vite+
280
+ removes the plugins it injected before it injects them again, so they are not
281
+ duplicated:
282
+
283
+ ```ts
284
+ import { defineSolidApp } from '@evertondgn/solid-tooling/vite'
285
+ import { defineConfig, mergeConfig } from 'vite-plus'
286
+
287
+ const app = defineSolidApp({ root: import.meta.dirname, securityHeaders })
288
+
289
+ export default defineConfig(env =>
290
+ mergeConfig(app(env), { server: { strictPort: true } })
291
+ )
292
+ ```
293
+
294
+ ## Knip
295
+
296
+ `defineKnipConfig` returns the Knip config of the application as a function, the
297
+ form Knip accepts as the default export of its config file:
298
+
299
+ ```ts
300
+ // tooling/knip.ts
301
+ import path from 'node:path'
302
+
303
+ import { defineKnipConfig } from '@evertondgn/solid-tooling/knip'
304
+
305
+ export default defineKnipConfig({
306
+ root: path.resolve(import.meta.dirname, '..'),
307
+ ignoreDependencies: ['server-only']
308
+ })
309
+ ```
310
+
311
+ Run it with `knip-bun --config tooling/knip.ts`, then with `--strict`. The
312
+ config lists:
313
+
314
+ - `src/entry-server.tsx`, `src/entry-client.tsx`, `src/middleware/index.ts`,
315
+ `env.ts` and `paraglide/runtime.js` as production entries. The Solid plugin
316
+ and the Paraglide runtime load them by path, and Knip does not read the Solid
317
+ plugin when `vite.config.ts` imports it with `import()`, as `./vite` does. An
318
+ application that imports `solid()` statically, with `start`, passes
319
+ `solidPluginImport: 'static'`: Knip's Solid plugin then finds the first four
320
+ itself, and listing them again would be a configuration hint.
321
+ - Every page and HTTP handler that `filesystem-routing` finds in `src/routes/`,
322
+ as production entries, and in `src/tests/fixtures/routes/` and
323
+ `src/tests/fixtures/e2e/routes/`, as test entries. The `filesystem-routing`
324
+ plugin of Knip is turned off, because it takes every file of `src/routes/` as
325
+ an entry and would hide unused helpers next to the routes.
326
+ - `src/**/*.{ts,tsx}` and `tooling/**/*.ts` as the project, with the tests out
327
+ of the production files.
328
+
329
+ The config turns on `treatConfigHintsAsErrors`, and Knip reports an entry or a
330
+ project pattern without files as a configuration hint. So each default above
331
+ enters only when the application has the file or the folder.
332
+
333
+ | Option | Default | Use |
334
+ | -------------------- | ----------------- | -------------------------------------------------------------- |
335
+ | `root` | required | The application folder |
336
+ | `configFile` | `tooling/knip.ts` | Path of the config file, relative to `root` |
337
+ | `entry` | none | More entries, such as a public API that only test fixtures use |
338
+ | `solidPluginImport` | `'lazy'` | `'static'` when `vite.config.ts` imports `solid()` statically |
339
+ | `ignoreBinaries` | none | Passed to Knip as is |
340
+ | `ignoreDependencies` | none | Passed to Knip as is |
341
+ | `ignoreIssues` | none | Passed to Knip, merged with the entry for `configFile` |
342
+ | `exclude` | none | Issue types to leave out, such as `catalogReferences` |
343
+
344
+ Knip adds the file given to `--config` to the entries in production mode, and
345
+ `--strict` only accepts `dependencies` there, so it reports this package as an
346
+ unlisted dependency of the config file. The config ignores `unlisted` issues in
347
+ `configFile` only: an import of this package from `src/` is still reported. An
348
+ application that keeps the config somewhere else passes its path in
349
+ `configFile`.
350
+
351
+ Nothing that depends on the application is a default. An ignore that matches
352
+ nothing is a configuration hint too, so `server-only` and the binaries of the
353
+ scripts are options. For a key without an option, wrap the function:
354
+
355
+ ```ts
356
+ const configure = defineKnipConfig({ root })
357
+
358
+ export default async function configureKnip() {
359
+ return { ...(await configure()), ignoreUnresolved: ['virtual:example'] }
360
+ }
361
+ ```
362
+
363
+ ## CSS lint
364
+
365
+ `checkCss(root)` lints `src/**/*.css` with Stylelint and the CSS policy of the
366
+ boilerplate, and checks the name of every CSS file in `src/` as
367
+ `project/css-filename` does for imports: `styles.module.css`, or `camelCase.css`
368
+ inside `src/theme/`. It returns the report and whether anything failed:
369
+
370
+ ```ts
371
+ // tooling/css/check.ts
372
+ import path from 'node:path'
373
+ import process from 'node:process'
374
+
375
+ import { checkCss } from '@evertondgn/solid-tooling/stylelint'
376
+
377
+ const { report, hasErrors } = await checkCss(
378
+ path.resolve(import.meta.dirname, '../..')
379
+ )
380
+
381
+ process.stdout.write(report)
382
+ if (hasErrors) process.exitCode = 1
383
+ ```
384
+
385
+ `defineStylelintConfig(root)` returns the Stylelint config that `checkCss` uses.
386
+ The policy:
387
+
388
+ - Classes use snake_case, nesting goes at most two levels deep, and adjacent
389
+ state selectors and media queries are nested (`stylelint-use-nesting`).
390
+ - Unknown or invalid properties, values, units, selectors, at-rules and media
391
+ features are errors, as are deprecated and unmatchable selectors.
392
+ - Outside `src/theme/`, colors, gradients, radii, shadows, font sizes and
393
+ weights and `z-index` come from tokens with `var()`, from the matching token
394
+ family. Literal colors, color functions and system colors are errors.
395
+ - Inside `src/theme/`, the token rules are off, so the theme can define the
396
+ values with `oklch()`, `oklab()`, `color-mix()`, `light-dark()` and system
397
+ colors. Hex, named colors and the `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`
398
+ and `color()` functions are still errors there.
399
+ - `var()` and animation names resolve against `src/theme/tokens/**/*.css` and
400
+ `src/theme/class/animation.css`. Stylelint stops the whole run when a
401
+ reference matches no file, so each one is passed only when it exists.
402
+ - An exception needs a `stylelint-disable` comment with the reason after `--`.
403
+
404
+ `stylelint` and `stylelint-use-nesting` are dependencies of this package. The
405
+ config passes the plugin by a path resolved from the package, because Stylelint
406
+ looks up a plugin name from the application and its working directory, where it
407
+ is not installed.
408
+
409
+ ## Commit messages and Git hooks
410
+
411
+ `./commitlint` is the commitlint config: the Conventional Commits rules of
412
+ `@commitlint/config-conventional`, with the header limited to 100 characters, a
413
+ blank line required before the footer, and a warning for body and footer lines
414
+ over 100 characters. The application installs `@commitlint/cli` and points its
415
+ config to the package:
416
+
417
+ ```json
418
+ // .commitlintrc
419
+ { "extends": ["@evertondgn/solid-tooling/commitlint"] }
420
+ ```
421
+
422
+ `./lefthook.yml` holds the Git hooks. The application installs `lefthook`,
423
+ allows its install script and extends the file from its own `.lefthook.yml`:
424
+
425
+ ```json
426
+ // package.json
427
+ { "trustedDependencies": ["lefthook"] }
428
+ ```
429
+
430
+ ```yaml
431
+ # .lefthook.yml
432
+ extends:
433
+ - node_modules/@evertondgn/solid-tooling/lefthook.yml
434
+ ```
435
+
436
+ | Hook | Jobs |
437
+ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
438
+ | `commit-msg` | `commitlint` on the message |
439
+ | `pre-commit` | `check:fix` on the staged files, which are staged again after the fix, and `scan:secrets --staged` when `kingfisher` is installed. Skipped in merge and rebase |
440
+ | `pre-push` | `check:ci`, `typecheck:node` and `test:ci` in order, then `test:tooling` and `dead-code` together. The first failure stops the rest |
441
+
442
+ The jobs call `package.json` scripts by name, so the application defines
443
+ `check:fix`, `scan:secrets`, `check:ci`, `typecheck:node`, `test:ci`,
444
+ `test:tooling` and `dead-code`.
445
+
446
+ Lefthook applies the files in `extends` over `.lefthook.yml`, so:
447
+
448
+ - A job added in `.lefthook.yml` runs before the shared jobs of the same hook.
449
+ - A job declared there with the name of a shared job does not change it.
450
+ - To change or skip a shared job, declare a job with the same name in
451
+ `lefthook-local.yml`, which Lefthook applies last, also inside a group. The
452
+ rest keeps its order, and new jobs in that file run after the shared ones.
453
+ Commit the file to change the job for everyone, or keep it out of Git to
454
+ change it on one machine.
455
+
456
+ Create `.lefthook.yml` before the first install. Without it, the install script
457
+ of Lefthook writes a `lefthook.yml` of its own, which Lefthook reads first.
458
+
135
459
  ## Project layout the preset expects
136
460
 
137
461
  The rules match file paths relative to the folder where lint runs, so run it
@@ -275,6 +599,7 @@ overrides: [
275
599
  | `./fmt` | `fmt`: the formatter config |
276
600
  | `./tsconfig.base.json` | Compiler options, without application paths |
277
601
  | `./tsconfig.app.json` | The base plus the `types` and `include` of the boilerplate layout |
602
+ | `./vite` | `defineSolidApp(options)`, the whole `vite.config.ts` of an application |
278
603
  | `./vitest` | `solidTest(options)`, the `test` block of `vite.config.ts` |
279
604
  | `./playwright` | `definePlaywrightConfig(options)`, the Playwright config |
280
605
  | `./vitest-setup/browser` | Setup file of the browser project, loaded by `./vitest` |
@@ -284,11 +609,17 @@ overrides: [
284
609
  | `./architecture-plugin` | The `architecture` rule |
285
610
  | `./solid-plugin` | Re-export of `eslint-plugin-solid` |
286
611
  | `./css-filename` | `cssFilenameError(path)`, the CSS naming check, for a script outside lint |
287
-
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.
612
+ | `./knip` | `defineKnipConfig(options)`, the Knip config |
613
+ | `./stylelint` | `checkCss(root)` and `defineStylelintConfig(root)`, the CSS lint |
614
+ | `./commitlint` | The commitlint config |
615
+ | `./lefthook.yml` | The Git hooks, extended from the application's `.lefthook.yml` |
616
+
617
+ An application imports `./vite`, which brings `./lint`, `./fmt` and `./vitest`
618
+ along, `./playwright`, `./knip`, `./stylelint` and one of the TypeScript
619
+ configs, and extends `./commitlint` and `./lefthook.yml`. An application that
620
+ only lints imports `./lint` and `./fmt`. The plugin entries exist because Oxlint
621
+ loads plugins by specifier, from the application; the setup and teardown entries
622
+ are loaded by the test presets.
292
623
 
293
624
  ## Formatter
294
625
 
@@ -348,6 +679,15 @@ break the `@/` imports of the end-to-end tests.
348
679
  (`.node.test`, `.dom.test`, `.browser.test`, `.e2e.test`), page tests in
349
680
  `src/tests/pages/`, Paraglide output in `paraglide/` and `.env.test` at the
350
681
  root.
682
+ - The Knip and CSS configs expect the same layout: pages in `src/routes/`,
683
+ fixture routes in `src/tests/fixtures/` and the theme in `src/theme/`.
684
+ - The Knip config ignores every unlisted dependency in its own file, not only
685
+ this package.
686
+ - `./vite` expects the same layout: its tasks run scripts in `tooling/`, and the
687
+ Markdown rules need the locale prefix of the Paraglide `urlPatterns` in every
688
+ URL. Nitro always uses the `vercel` preset. The plugin packages are pinned, so
689
+ a new version of Solid, Nitro, the router or the icons plugin needs a package
690
+ version tested with it.
351
691
 
352
692
  ## Versions
353
693
 
@@ -0,0 +1,3 @@
1
+ type CommitlintConfig = Readonly<Record<string, unknown>>;
2
+ declare const commitlint: CommitlintConfig;
3
+ export { commitlint as default };
@@ -0,0 +1,28 @@
1
+ import conventional from "@commitlint/config-conventional";
2
+ // src/commitlint/index.ts
3
+ const WARNING = 1;
4
+ const ERROR = 2;
5
+ const MAX_LINE_LENGTH = 100;
6
+ const commitlint = {
7
+ ...conventional,
8
+ rules: {
9
+ ...conventional.rules,
10
+ "header-max-length": [
11
+ ERROR,
12
+ "always",
13
+ MAX_LINE_LENGTH
14
+ ],
15
+ "body-max-line-length": [
16
+ WARNING,
17
+ "always",
18
+ MAX_LINE_LENGTH
19
+ ],
20
+ "footer-leading-blank": [ERROR, "always"],
21
+ "footer-max-line-length": [
22
+ WARNING,
23
+ "always",
24
+ MAX_LINE_LENGTH
25
+ ]
26
+ }
27
+ };
28
+ export { commitlint as default };
@@ -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 };