@evertondgn/solid-tooling 0.1.0 → 0.3.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 +332 -25
- package/dist/architecture-plugin.mjs +1 -1
- package/dist/backend-plugin.mjs +3 -3
- package/dist/{constants-DwOmJ9No.mjs → constants-Jqvtbvxu.mjs} +1 -1
- package/dist/css-filename.mjs +2 -2
- package/dist/lint.mjs +7 -7
- package/dist/playwright-teardown.d.mts +1 -0
- package/dist/playwright-teardown.mjs +23 -0
- package/dist/playwright.d.mts +18 -0
- package/dist/playwright.mjs +158 -0
- package/dist/project-plugin.mjs +11 -11
- package/dist/vitest-setup/browser.d.mts +1 -0
- package/dist/vitest-setup/browser.mjs +5 -0
- package/dist/vitest.d.mts +7 -0
- package/dist/vitest.mjs +129 -0
- package/package.json +20 -8
- package/tsconfig.app.json +21 -0
package/README.md
CHANGED
|
@@ -1,21 +1,57 @@
|
|
|
1
1
|
# @evertondgn/solid-tooling
|
|
2
2
|
|
|
3
|
-
Lint,
|
|
4
|
-
|
|
5
|
-
`node_modules`, e o Vite+ carrega a configuração em Node.
|
|
3
|
+
Lint preset, formatter config, TypeScript configs and test configs for SolidJS 2
|
|
4
|
+
applications built with [Vite+](https://viteplus.dev/guide/).
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
The package contains the lint, format, TypeScript and test setup of
|
|
7
|
+
[boilerplate_solidjs](https://github.com/everton-dgn/boilerplate_solidjs), a
|
|
8
|
+
SolidJS application template. Applications created from it install the package
|
|
9
|
+
instead of copying the files, so a rule change reaches them through a version
|
|
10
|
+
update.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
The application installs three packages next to this one:
|
|
15
|
+
|
|
16
|
+
| Package | Why |
|
|
17
|
+
| ---------------------- | ----------------------------------------------------------- |
|
|
18
|
+
| `vite-plus` | Runs lint and format, and provides the Oxlint plugin API |
|
|
19
|
+
| `@inlang/paraglide-js` | The preset registers its lint plugin, resolved from the app |
|
|
20
|
+
| `typescript` | The preset turns on type-aware linting and type checking |
|
|
21
|
+
|
|
22
|
+
`vite-plus` and `@inlang/paraglide-js` are peer dependencies, pinned to the
|
|
23
|
+
exact versions the package is tested with. Install the versions listed in its
|
|
24
|
+
`peerDependencies`. `typescript` is not a peer dependency: the package is tested
|
|
25
|
+
with the version in its `devDependencies`.
|
|
26
|
+
|
|
27
|
+
`eslint-plugin-solid` comes with the package, so the application does not
|
|
28
|
+
declare it.
|
|
29
|
+
|
|
30
|
+
The test entries need the test tools the application already runs:
|
|
31
|
+
|
|
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 |
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
`vp` is the Vite+ CLI. An application built with Vite+ already has `vite-plus`
|
|
40
|
+
and `typescript`, so it only adds this package:
|
|
8
41
|
|
|
9
42
|
```bash
|
|
10
43
|
vp add -D @evertondgn/solid-tooling
|
|
11
44
|
```
|
|
12
45
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Paraglide precisa acompanhar a versão que ela usa. O `eslint-plugin-solid` vem
|
|
16
|
-
como dependência do pacote e dispensa declaração.
|
|
46
|
+
An application without `@inlang/paraglide-js` adds it too, in the version the
|
|
47
|
+
package asks for:
|
|
17
48
|
|
|
18
|
-
|
|
49
|
+
```bash
|
|
50
|
+
npm view @evertondgn/solid-tooling peerDependencies
|
|
51
|
+
vp add -D @inlang/paraglide-js@<version>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
19
55
|
|
|
20
56
|
```ts
|
|
21
57
|
// vite.config.ts
|
|
@@ -29,25 +65,296 @@ export default defineConfig({ fmt, lint })
|
|
|
29
65
|
```json
|
|
30
66
|
// tsconfig.json
|
|
31
67
|
{
|
|
32
|
-
"extends": "@evertondgn/solid-tooling/tsconfig.base.json"
|
|
68
|
+
"extends": "@evertondgn/solid-tooling/tsconfig.base.json",
|
|
69
|
+
"compilerOptions": {
|
|
70
|
+
"paths": { "@/*": ["./src/*"] }
|
|
71
|
+
},
|
|
72
|
+
"include": ["src", "vite.config.ts"]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`vp check`, `vp lint` and `vp fmt` read both configs from `vite.config.ts`.
|
|
77
|
+
|
|
78
|
+
## Tests
|
|
79
|
+
|
|
80
|
+
`solidTest` returns the `test` block of `vite.config.ts`, with three Vitest
|
|
81
|
+
projects:
|
|
82
|
+
|
|
83
|
+
| Project | Files | Environment |
|
|
84
|
+
| --------- | -------------------------------- | ------------------------------------------------ |
|
|
85
|
+
| `node` | `src/**/*.node.test.{ts,tsx}` | Node, with stand-ins for the build-only modules |
|
|
86
|
+
| `dom` | `src/**/*.dom.test.{ts,tsx}` | happy-dom at `http://localhost/en` |
|
|
87
|
+
| `browser` | `src/**/*.browser.test.{ts,tsx}` | Chromium through Playwright, with the CSS loaded |
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// vite.config.ts
|
|
91
|
+
import { solidTest } from '@evertondgn/solid-tooling/vitest'
|
|
92
|
+
|
|
93
|
+
export default defineConfig({
|
|
94
|
+
test: solidTest({ root: import.meta.dirname, plugins: componentPlugins })
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`plugins` is a function that returns the Vite plugins the `dom` and `browser`
|
|
99
|
+
projects need to compile components: Solid, routes, icons and i18n. The preset
|
|
100
|
+
calls it once per project, because each project starts its own Vite server and a
|
|
101
|
+
plugin instance keeps the state of one server. The preset also sets the coverage
|
|
102
|
+
thresholds, the reporters and the workers on CI, passes `BASE_URL_TEST`, `HOST`
|
|
103
|
+
and `PORT` to the tests when they are set, and resets the Paraglide locale
|
|
104
|
+
before each browser test.
|
|
105
|
+
|
|
106
|
+
The `node` project does not load the Solid and routing plugins, so a plugin of
|
|
107
|
+
the preset serves `server-only`, `virtual:file-routes` and
|
|
108
|
+
`virtual:solid-server-function-handler`. Knip does not know `server-only`, a
|
|
109
|
+
virtual module of `@solidjs/vite-plugin`: list it in `ignoreDependencies`.
|
|
110
|
+
|
|
111
|
+
`definePlaywrightConfig` builds the Playwright config from the application root:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// playwright.config.ts
|
|
115
|
+
import { definePlaywrightConfig } from '@evertondgn/solid-tooling/playwright'
|
|
116
|
+
|
|
117
|
+
export default definePlaywrightConfig({ root: import.meta.dirname })
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
It reads `BASE_URL_TEST` from `.env.test`, builds the application (`e2e` mode
|
|
121
|
+
for the fixture routes, `production` with `TEST_PRODUCTION_ROUTES=true`), starts
|
|
122
|
+
the preview, and checks the server log for private markers when the suite ends.
|
|
123
|
+
`TEST_PREBUILT=true` reuses an existing production build.
|
|
124
|
+
|
|
125
|
+
| Option | Default | Use |
|
|
126
|
+
| ---------------- | ----------------- | ---------------------------------------------------------------------- |
|
|
127
|
+
| `testDir` | `src/tests/pages` | Folder of the `*.e2e.test.ts` files |
|
|
128
|
+
| `fixtureServers` | none | Extra servers for the fixture routes, as `{ command, url }` |
|
|
129
|
+
| `suites` | none | Globs of serial suites: `serverLogs`, `markdown` and `publicationLoad` |
|
|
130
|
+
|
|
131
|
+
Each serial suite gets its own project: `markdown` runs with one worker,
|
|
132
|
+
`serverLogs` runs last as the teardown of the others, and `publicationLoad` runs
|
|
133
|
+
alone, against two servers, with `TEST_PUBLICATION_LOAD=true`.
|
|
134
|
+
|
|
135
|
+
## Project layout the preset expects
|
|
136
|
+
|
|
137
|
+
The rules match file paths relative to the folder where lint runs, so run it
|
|
138
|
+
from the application root. They know these locations:
|
|
139
|
+
|
|
140
|
+
| Path | Meaning |
|
|
141
|
+
| --------------------------------------------- | -------------------------------------------------------- |
|
|
142
|
+
| `src/` | Application code. The `@/` alias points here |
|
|
143
|
+
| `src/routes/` | Pages. Each page may have a `seo.ts` module |
|
|
144
|
+
| `src/components/{atoms,molecules,organisms}/` | UI layers |
|
|
145
|
+
| `src/primitives/` | Shared primitives |
|
|
146
|
+
| `src/infra/server/` | Backend adapters and server error handling |
|
|
147
|
+
| `src/theme/` | Global CSS |
|
|
148
|
+
| `src/tests/pages/<Page>/` | End-to-end tests, one folder per page |
|
|
149
|
+
| `paraglide/` | Generated i18n modules. The `@paraglide/` alias |
|
|
150
|
+
| `tooling/` | Project scripts and their tests, written for `node:test` |
|
|
151
|
+
|
|
152
|
+
An application with another layout can still use the preset, but it has to
|
|
153
|
+
override the rules that do not fit. See
|
|
154
|
+
[Changing the preset](#changing-the-preset).
|
|
155
|
+
|
|
156
|
+
## What the lint preset turns on
|
|
157
|
+
|
|
158
|
+
- Oxlint plugins: `import`, `jsx-a11y`, `node`, `oxc`, `promise`, `typescript`,
|
|
159
|
+
`unicorn` and `vitest`.
|
|
160
|
+
- JavaScript plugins: `solid`, `paraglide`, `vite-plus` and the three plugins of
|
|
161
|
+
this package (`project`, `backend`, `architecture`).
|
|
162
|
+
- Type-aware rules and type checking (`typeAware`, `typeCheck`).
|
|
163
|
+
- Warnings fail the run (`denyWarnings`), and an unused disable directive is an
|
|
164
|
+
error.
|
|
165
|
+
|
|
166
|
+
Every rule is listed by name in the preset. No Oxlint category is enabled, so an
|
|
167
|
+
Oxlint update does not add rules on its own.
|
|
168
|
+
|
|
169
|
+
### Rules of this package
|
|
170
|
+
|
|
171
|
+
| Rule | What it enforces |
|
|
172
|
+
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| `project/css-modules-import` | A CSS Module is imported as the default binding `S` |
|
|
174
|
+
| `project/css-filename` | A CSS file imported from `src/` is named `styles.module.css`, or `camelCase.css` inside `src/theme/` |
|
|
175
|
+
| `project/test-filename` | A test file sits in `__tests__` next to its module and is named after it, plus the environment: `Button/__tests__/Button.dom.test.tsx` |
|
|
176
|
+
| `project/no-export-list` | Exports sit on the declaration. `export { a, b }` is only allowed with `from` |
|
|
177
|
+
| `project/seo-module` | A route `seo.ts` exports only `seo`. It has no relative value import, no value import from `src/routes/`, no computed `import()` and no `import.meta.glob` |
|
|
178
|
+
| `backend/static-solid-imports` | No `import()` of `@solidjs/web` or its subpaths |
|
|
179
|
+
| `backend/static-render-integrity-imports` | No `import()` of the `renderIntegrity` module |
|
|
180
|
+
| `architecture/layer-imports` | Lower layers do not import upper ones, and production code does not import tests or `tooling/` |
|
|
181
|
+
|
|
182
|
+
These rules read import specifiers as text. They resolve relative paths and the
|
|
183
|
+
`@/`, `@paraglide/` and `/src/` prefixes. An `import()` with a computed path and
|
|
184
|
+
any other alias are not checked, except in `seo.ts`, where a computed `import()`
|
|
185
|
+
is an error.
|
|
186
|
+
|
|
187
|
+
`architecture/layer-imports` checks these directions:
|
|
188
|
+
|
|
189
|
+
- The base layer (`src/@types`, `constants`, `helpers`, `data`, `infra`,
|
|
190
|
+
`theme`, `i18n` and `paraglide/`) does not import primitives, components,
|
|
191
|
+
routes or entry files.
|
|
192
|
+
- `src/primitives` does not import components, routes or entry files.
|
|
193
|
+
- Atoms do not import molecules or organisms, and molecules do not import
|
|
194
|
+
organisms.
|
|
195
|
+
- No production file imports a test or `tooling/`.
|
|
196
|
+
|
|
197
|
+
The entry files are `App`, `Document`, `entry-server`, `entry-client`, `router`
|
|
198
|
+
and `middleware`, at the root of `src/`.
|
|
199
|
+
|
|
200
|
+
`project/test-filename` accepts these names:
|
|
201
|
+
|
|
202
|
+
- `Name.<environment>.test.ts(x)` in `src/`, where the environment is `node`,
|
|
203
|
+
`dom`, `browser` or `e2e`.
|
|
204
|
+
- `Name.test-d.ts(x)` in `src/`, for type tests.
|
|
205
|
+
- `Name.test.ts` in `tooling/`.
|
|
206
|
+
|
|
207
|
+
`Name` is the folder that holds `__tests__`. A qualifier may follow it:
|
|
208
|
+
`Button.focus.dom.test.tsx`. End-to-end tests are the exception: they live in
|
|
209
|
+
`src/tests/pages/<Page>/` and are named after the page.
|
|
210
|
+
|
|
211
|
+
### Backend restrictions
|
|
212
|
+
|
|
213
|
+
Inside `src/`, the preset also restricts what code can reach:
|
|
214
|
+
|
|
215
|
+
- `console` is not allowed. `src/infra/server/logServerFailure/index.ts` is the
|
|
216
|
+
only module that may call `console.error`.
|
|
217
|
+
- `fetch`, `XMLHttpRequest`, `WebSocket` and `EventSource` are not allowed.
|
|
218
|
+
`src/infra/server/requestJson/index.ts` is the only module that may call
|
|
219
|
+
`fetch`.
|
|
220
|
+
- The Node transports (`http`, `https`, `http2`, `net`, `tls`, `undici`) cannot
|
|
221
|
+
be imported.
|
|
222
|
+
- `markSafeError` and `SAFE_ERROR` can only be imported in
|
|
223
|
+
`src/infra/server/publicErrors/index.ts`.
|
|
224
|
+
- `configureServerErrors` from `@solidjs/web`, the
|
|
225
|
+
`@solidjs/web/server-functions` subpath and `observeRenderFailures` from the
|
|
226
|
+
application's `renderIntegrity` module can only be imported in
|
|
227
|
+
`src/infra/server/configureServerErrors/index.ts`.
|
|
228
|
+
- A relative import goes up at most three levels. Use `@/` beyond that.
|
|
229
|
+
|
|
230
|
+
Test files and `.d.ts` files are exempt from all of these except the relative
|
|
231
|
+
import limit.
|
|
232
|
+
|
|
233
|
+
## Changing the preset
|
|
234
|
+
|
|
235
|
+
`lint` and `fmt` are plain objects. Spread them and replace what the application
|
|
236
|
+
needs:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
// vite.config.ts
|
|
240
|
+
import { fmt } from '@evertondgn/solid-tooling/fmt'
|
|
241
|
+
import { lint } from '@evertondgn/solid-tooling/lint'
|
|
242
|
+
import { defineConfig } from 'vite-plus'
|
|
243
|
+
|
|
244
|
+
export default defineConfig({
|
|
245
|
+
fmt,
|
|
246
|
+
lint: {
|
|
247
|
+
...lint,
|
|
248
|
+
ignorePatterns: [...(lint.ignorePatterns ?? []), 'generated/**'],
|
|
249
|
+
rules: { ...lint.rules, 'eslint/max-params': 'off' }
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Do not replace `lint.jsPlugins`. It registers the JavaScript plugins listed
|
|
255
|
+
above, and a rule whose plugin is missing from it fails to resolve.
|
|
256
|
+
|
|
257
|
+
To turn a rule off for some files only, add an entry to `lint.overrides` and
|
|
258
|
+
keep the existing ones:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
overrides: [
|
|
262
|
+
...(lint.overrides ?? []),
|
|
263
|
+
{
|
|
264
|
+
files: ['src/tests/pages/Checkout/**'],
|
|
265
|
+
rules: { 'project/test-filename': 'off' }
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Entry points
|
|
271
|
+
|
|
272
|
+
| Entry | Content |
|
|
273
|
+
| ------------------------ | ------------------------------------------------------------------------- |
|
|
274
|
+
| `./lint` | `lint`: the lint preset, with the plugins below registered |
|
|
275
|
+
| `./fmt` | `fmt`: the formatter config |
|
|
276
|
+
| `./tsconfig.base.json` | Compiler options, without application paths |
|
|
277
|
+
| `./tsconfig.app.json` | The base plus the `types` and `include` of the boilerplate layout |
|
|
278
|
+
| `./vitest` | `solidTest(options)`, the `test` block of `vite.config.ts` |
|
|
279
|
+
| `./playwright` | `definePlaywrightConfig(options)`, the Playwright config |
|
|
280
|
+
| `./vitest-setup/browser` | Setup file of the browser project, loaded by `./vitest` |
|
|
281
|
+
| `./playwright-teardown` | Server log check, loaded by `./playwright` |
|
|
282
|
+
| `./project-plugin` | The `project` rules |
|
|
283
|
+
| `./backend-plugin` | The `backend` rules |
|
|
284
|
+
| `./architecture-plugin` | The `architecture` rule |
|
|
285
|
+
| `./solid-plugin` | Re-export of `eslint-plugin-solid` |
|
|
286
|
+
| `./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.
|
|
292
|
+
|
|
293
|
+
## Formatter
|
|
294
|
+
|
|
295
|
+
`fmt` sets 80 columns, single quotes, no semicolons, no trailing commas, wrapped
|
|
296
|
+
prose in Markdown and sorted imports. It ignores `public/`, `src/assets/`,
|
|
297
|
+
generated route types, SVG and lock files.
|
|
298
|
+
|
|
299
|
+
## TypeScript base
|
|
300
|
+
|
|
301
|
+
`tsconfig.base.json` targets ES2024 with bundler resolution, keeps JSX for the
|
|
302
|
+
Solid compiler (`jsxImportSource: @solidjs/web`), emits nothing and turns on the
|
|
303
|
+
unused code and unchecked index checks. It has no `paths`, `include` or `types`.
|
|
304
|
+
|
|
305
|
+
`tsconfig.app.json` extends the base with the `types` of a boilerplate
|
|
306
|
+
application and an `include` resolved from the application folder with
|
|
307
|
+
`${configDir}`, which needs TypeScript 5.5 or later. Each entry of `types` comes
|
|
308
|
+
from a package the application installs:
|
|
309
|
+
|
|
310
|
+
| Entry of `types` | Package |
|
|
311
|
+
| --------------------------------------- | --------------------------- |
|
|
312
|
+
| `node` | `@types/node` |
|
|
313
|
+
| `vite/client` | `vite` |
|
|
314
|
+
| `vite-plus/test/globals` | `vite-plus` |
|
|
315
|
+
| `@testing-library/jest-dom/vitest` | `@testing-library/jest-dom` |
|
|
316
|
+
| `@solidjs/vite-plugin/boundary-modules` | `@solidjs/vite-plugin` |
|
|
317
|
+
| `@solidjs/vite-plugin/virtual-modules` | `@solidjs/vite-plugin` |
|
|
318
|
+
|
|
319
|
+
The application still declares its `paths`:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"extends": "@evertondgn/solid-tooling/tsconfig.app.json",
|
|
324
|
+
"compilerOptions": {
|
|
325
|
+
"paths": { "@/*": ["./src/*"], "@paraglide/*": ["./paraglide/*"] }
|
|
326
|
+
}
|
|
33
327
|
}
|
|
34
328
|
```
|
|
35
329
|
|
|
36
|
-
|
|
330
|
+
Playwright reads the `tsconfig.json` of the test files with its own loader,
|
|
331
|
+
which does not expand `${configDir}`, so paths declared in the package would
|
|
332
|
+
break the `@/` imports of the end-to-end tests.
|
|
333
|
+
|
|
334
|
+
## Known limits
|
|
335
|
+
|
|
336
|
+
- The package ships compiled JavaScript. Node refuses to run TypeScript from
|
|
337
|
+
`node_modules`, and Vite+ loads `vite.config.ts` in Node.
|
|
338
|
+
- `project/test-filename` has no options. Test folders without a module, such as
|
|
339
|
+
the page tests in `src/tests/pages/`, are listed inside the package with the
|
|
340
|
+
names used in boilerplate_solidjs. For any other folder of that kind, turn the
|
|
341
|
+
rule off in `lint.overrides`, as shown in
|
|
342
|
+
[Changing the preset](#changing-the-preset).
|
|
343
|
+
- The Paraglide plugin is always registered, so `@inlang/paraglide-js` has to be
|
|
344
|
+
installed even in an application without translations.
|
|
345
|
+
- Some rule messages point to `docs/architecture.md` and `AGENTS.md`. Those
|
|
346
|
+
files belong to the application created from boilerplate_solidjs.
|
|
347
|
+
- The test presets expect the boilerplate layout: tests named by suffix
|
|
348
|
+
(`.node.test`, `.dom.test`, `.browser.test`, `.e2e.test`), page tests in
|
|
349
|
+
`src/tests/pages/`, Paraglide output in `paraglide/` and `.env.test` at the
|
|
350
|
+
root.
|
|
351
|
+
|
|
352
|
+
## Versions
|
|
37
353
|
|
|
38
|
-
|
|
354
|
+
The package follows semantic versioning. Below 1.0.0, a breaking change raises
|
|
355
|
+
the minor version. Each release is listed in the
|
|
356
|
+
[changelog](https://github.com/everton-dgn/solidjs_base/blob/main/packages/solid-tooling/CHANGELOG.md).
|
|
39
357
|
|
|
40
|
-
|
|
41
|
-
| ----------------------- | ----------------------------------------------------------- |
|
|
42
|
-
| `./lint` | Preset de lint, com os plugins abaixo já registrados |
|
|
43
|
-
| `./fmt` | Configuração do formatador |
|
|
44
|
-
| `./project-plugin` | Regras de exports, nomes de arquivos de teste e de CSS, SEO |
|
|
45
|
-
| `./backend-plugin` | Imports estáticos das APIs privilegiadas do servidor |
|
|
46
|
-
| `./architecture-plugin` | Dependências entre camadas |
|
|
47
|
-
| `./solid-plugin` | Reexport do `eslint-plugin-solid` |
|
|
48
|
-
| `./css-filename` | Regra de nome de arquivo CSS, para o script de lint de CSS |
|
|
49
|
-
| `./tsconfig.base.json` | Opções do compilador sem caminhos da aplicação |
|
|
358
|
+
## License
|
|
50
359
|
|
|
51
|
-
|
|
52
|
-
código em `src/`, ferramentas em `tooling/` e as camadas descritas no guia de
|
|
53
|
-
arquitetura da aplicação.
|
|
360
|
+
MIT
|
|
@@ -41,7 +41,7 @@ const layerImports = defineRule({
|
|
|
41
41
|
meta: {
|
|
42
42
|
type: "problem",
|
|
43
43
|
schema: [],
|
|
44
|
-
messages: { forbidden: "
|
|
44
|
+
messages: { forbidden: "Layer {{from}} must not depend on {{to}} ({{target}}). See the layer rules in docs/architecture.md." }
|
|
45
45
|
},
|
|
46
46
|
create(context) {
|
|
47
47
|
const from = layer(path.relative(context.cwd, context.filename).split(path.sep).join("/"));
|
package/dist/backend-plugin.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as readStaticSpecifier } from "./import-specifier-C7HWhMXB.mjs";
|
|
2
|
-
import { n as RENDER_INTEGRITY_MODULE } from "./constants-
|
|
2
|
+
import { n as RENDER_INTEGRITY_MODULE } from "./constants-Jqvtbvxu.mjs";
|
|
3
3
|
import { definePlugin, defineRule } from "vite-plus/lint/plugins";
|
|
4
4
|
// src/backendPolicy/plugin.ts
|
|
5
5
|
const RENDER_INTEGRITY = new RegExp(RENDER_INTEGRITY_MODULE, "u");
|
|
@@ -26,11 +26,11 @@ const backendPlugin = definePlugin({
|
|
|
26
26
|
rules: {
|
|
27
27
|
"static-solid-imports": staticImportRule({
|
|
28
28
|
matches: (specifier) => specifier === "@solidjs/web" || specifier.startsWith("@solidjs/web/"),
|
|
29
|
-
message: "Use import
|
|
29
|
+
message: "Use a static import for @solidjs/web and its subpaths: the import restrictions on markSafeError and configureServerErrors cannot check import()."
|
|
30
30
|
}),
|
|
31
31
|
"static-render-integrity-imports": staticImportRule({
|
|
32
32
|
matches: (specifier) => RENDER_INTEGRITY.test(specifier),
|
|
33
|
-
message: "Use import
|
|
33
|
+
message: "Use a static import for renderIntegrity: the import restriction on observeRenderFailures cannot check import()."
|
|
34
34
|
})
|
|
35
35
|
}
|
|
36
36
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/backendPolicy/constants.ts
|
|
2
2
|
const RELATIVE_IMPORT_RESTRICTION = {
|
|
3
3
|
regex: String.raw`^(\.\./){4,}`,
|
|
4
|
-
message: "
|
|
4
|
+
message: "Relative imports may go up at most three levels. Use the @/ alias for more distant paths."
|
|
5
5
|
};
|
|
6
6
|
const RENDER_INTEGRITY_MODULE = String.raw`(?:^|/)renderIntegrity(?:/index(?:\.ts)?)?$`;
|
|
7
7
|
export { RENDER_INTEGRITY_MODULE as n, RELATIVE_IMPORT_RESTRICTION as t };
|
package/dist/css-filename.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
function cssFilenameError(file) {
|
|
6
6
|
const normalized = file.split("\\").join("/");
|
|
7
7
|
const basename = path.posix.basename(normalized);
|
|
8
|
-
if (normalized.startsWith("src/theme/")) return /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/u.test(basename.slice(0, -4)) ? void 0 : "Use
|
|
9
|
-
return basename === "styles.module.css" ? void 0 : "Use styles.module.css
|
|
8
|
+
if (normalized.startsWith("src/theme/")) return /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/u.test(basename.slice(0, -4)) ? void 0 : "Use camelCaseName.css inside src/theme/.";
|
|
9
|
+
return basename === "styles.module.css" ? void 0 : "Use styles.module.css outside src/theme/, including in routes.";
|
|
10
10
|
}
|
|
11
11
|
const cssFilename = defineRule({
|
|
12
12
|
meta: {
|
package/dist/lint.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as RENDER_INTEGRITY_MODULE, t as RELATIVE_IMPORT_RESTRICTION } from "./constants-
|
|
1
|
+
import { n as RENDER_INTEGRITY_MODULE, t as RELATIVE_IMPORT_RESTRICTION } from "./constants-Jqvtbvxu.mjs";
|
|
2
2
|
// src/backendPolicy/index.ts
|
|
3
3
|
const NETWORK = [
|
|
4
4
|
"fetch",
|
|
@@ -16,21 +16,21 @@ const GLOBALS = [
|
|
|
16
16
|
const SAFE_ERRORS = {
|
|
17
17
|
name: "@solidjs/web",
|
|
18
18
|
importNames: ["markSafeError", "SAFE_ERROR"],
|
|
19
|
-
message: "Use createPublicError;
|
|
19
|
+
message: "Use createPublicError; only publicErrors may mark an error as public."
|
|
20
20
|
};
|
|
21
21
|
const SERVER_ERROR_HOOK = {
|
|
22
22
|
name: "@solidjs/web",
|
|
23
23
|
importNames: ["configureServerErrors"],
|
|
24
|
-
message: "
|
|
24
|
+
message: "Only src/infra/server/configureServerErrors/index.ts may import this: server error handling is configured in that module."
|
|
25
25
|
};
|
|
26
26
|
const SERVER_CONFIG = ["@solidjs/web/server-functions", "@solidjs/web/server-functions/server"].map((name) => ({
|
|
27
27
|
name,
|
|
28
|
-
message: "
|
|
28
|
+
message: "Only src/infra/server/configureServerErrors/index.ts may import this: server error handling is configured in that module."
|
|
29
29
|
}));
|
|
30
30
|
const RENDER_OBSERVER = {
|
|
31
31
|
regex: RENDER_INTEGRITY_MODULE,
|
|
32
32
|
importNames: ["observeRenderFailures"],
|
|
33
|
-
message: "
|
|
33
|
+
message: "Only configureServerErrors may declare that render failures are observed."
|
|
34
34
|
};
|
|
35
35
|
const TRANSPORTS = [
|
|
36
36
|
"http",
|
|
@@ -41,7 +41,7 @@ const TRANSPORTS = [
|
|
|
41
41
|
"undici"
|
|
42
42
|
].flatMap((name) => [name, `node:${name}`]).map((name) => ({
|
|
43
43
|
name,
|
|
44
|
-
message: "
|
|
44
|
+
message: "Node transports cannot be imported in src/. Use requestJson for HTTP, or an adapter in src/infra/server/ wrapped in protectServerOperation."
|
|
45
45
|
}));
|
|
46
46
|
function imports({ paths, patterns = [] }) {
|
|
47
47
|
return { "eslint/no-restricted-imports": ["error", {
|
|
@@ -59,7 +59,7 @@ function globals({ names, properties = names }) {
|
|
|
59
59
|
"eslint/no-restricted-properties": ["error", ...GLOBALS.flatMap((object) => properties.map((property) => ({
|
|
60
60
|
object,
|
|
61
61
|
property,
|
|
62
|
-
message: property === "console" ? "
|
|
62
|
+
message: property === "console" ? "Log failures only through logServerFailure." : "Call the backend through requestJson, or through an adapter in src/infra/server/ wrapped in protectServerOperation."
|
|
63
63
|
})))]
|
|
64
64
|
};
|
|
65
65
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function checkServerLog(): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { env } from "node:process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { stripVTControlCharacters } from "node:util";
|
|
4
|
+
// src/playwright/teardown.ts
|
|
5
|
+
const PRIVATE_MARKER = /PRIVATE_[A-Z0-9_]+/u;
|
|
6
|
+
async function checkLog({ file, baseUrl }) {
|
|
7
|
+
if (!file || !baseUrl) throw new Error("Server log file is not configured");
|
|
8
|
+
const log = stripVTControlCharacters(await readFile(file, "utf8"));
|
|
9
|
+
const { host } = new URL(baseUrl);
|
|
10
|
+
if (!log.includes(host)) throw new Error(`Server log does not show the preview startup: ${file}`);
|
|
11
|
+
if (PRIVATE_MARKER.test(log)) throw new Error(`Server log contains a private marker: ${file}`);
|
|
12
|
+
}
|
|
13
|
+
async function checkServerLog() {
|
|
14
|
+
await checkLog({
|
|
15
|
+
file: env.SERVER_LOG_FILE,
|
|
16
|
+
baseUrl: env.BASE_URL_TEST
|
|
17
|
+
});
|
|
18
|
+
if (env.TEST_PUBLICATION_LOAD === "true") await checkLog({
|
|
19
|
+
file: env.SERVER_LOG_SECONDARY,
|
|
20
|
+
baseUrl: env.PUBLICATION_SECONDARY_URL
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export { checkServerLog as default };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PlaywrightTestConfig } from "@playwright/test";
|
|
2
|
+
type FixtureServer = {
|
|
3
|
+
command: string;
|
|
4
|
+
url: string;
|
|
5
|
+
};
|
|
6
|
+
type SerialSuites = {
|
|
7
|
+
serverLogs?: string;
|
|
8
|
+
markdown?: string;
|
|
9
|
+
publicationLoad?: string;
|
|
10
|
+
};
|
|
11
|
+
type PlaywrightOptions = {
|
|
12
|
+
root: string;
|
|
13
|
+
testDir?: string;
|
|
14
|
+
fixtureServers?: FixtureServer[];
|
|
15
|
+
suites?: SerialSuites;
|
|
16
|
+
};
|
|
17
|
+
export declare function definePlaywrightConfig({ root, testDir, fixtureServers, suites }: PlaywrightOptions): PlaywrightTestConfig;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { env, loadEnvFile } from "node:process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
5
|
+
import { loadEnv } from "vite-plus";
|
|
6
|
+
// src/playwright/index.ts
|
|
7
|
+
const STARTUP_TIMEOUT = 12e4;
|
|
8
|
+
const SECONDARY_PORT_OFFSET = 2;
|
|
9
|
+
const MAX_PORT = 65535;
|
|
10
|
+
const CI_WORKERS = 2;
|
|
11
|
+
const SECONDARY_LOG = "test-results/server-publication-secondary.log";
|
|
12
|
+
const PREBUILT_GUARD = "if [ -e .vercel/output/static/.vite ]; then echo 'TEST_PREBUILT found an e2e build; run vp run build first' >&2; exit 1; fi && ";
|
|
13
|
+
function readRunMode(suites) {
|
|
14
|
+
const mode = {
|
|
15
|
+
isProduction: env.TEST_PRODUCTION_ROUTES === "true",
|
|
16
|
+
isPublicationLoad: env.TEST_PUBLICATION_LOAD === "true",
|
|
17
|
+
shouldBuild: env.TEST_PREBUILT !== "true"
|
|
18
|
+
};
|
|
19
|
+
if (mode.isPublicationLoad && mode.isProduction) throw new Error("TEST_PUBLICATION_LOAD requires fixture routes");
|
|
20
|
+
if (mode.isPublicationLoad && !suites.publicationLoad) throw new Error("TEST_PUBLICATION_LOAD requires a publicationLoad suite");
|
|
21
|
+
if (!mode.shouldBuild && !mode.isProduction) throw new Error("TEST_PREBUILT requires production routes");
|
|
22
|
+
return mode;
|
|
23
|
+
}
|
|
24
|
+
function readBaseUrl(mode) {
|
|
25
|
+
if (!env.BASE_URL_TEST) throw new Error(".env.test must define BASE_URL_TEST");
|
|
26
|
+
const baseUrl = new URL(env.BASE_URL_TEST);
|
|
27
|
+
if (!baseUrl.port) throw new Error("BASE_URL_TEST must include an explicit port");
|
|
28
|
+
if (mode.isPublicationLoad && Number(baseUrl.port) + SECONDARY_PORT_OFFSET > MAX_PORT) throw new Error("BASE_URL_TEST leaves no port for the secondary server");
|
|
29
|
+
return env.BASE_URL_TEST;
|
|
30
|
+
}
|
|
31
|
+
function readWorkers(mode) {
|
|
32
|
+
if (mode.isPublicationLoad) return 1;
|
|
33
|
+
return env.CI ? CI_WORKERS : void 0;
|
|
34
|
+
}
|
|
35
|
+
function secondaryUrl(baseUrl) {
|
|
36
|
+
const url = new URL(baseUrl);
|
|
37
|
+
url.port = String(Number(url.port) + SECONDARY_PORT_OFFSET);
|
|
38
|
+
return url;
|
|
39
|
+
}
|
|
40
|
+
function buildProjects({ mode, suites }) {
|
|
41
|
+
if (mode.isPublicationLoad) return [{
|
|
42
|
+
name: "publication-load",
|
|
43
|
+
testMatch: suites.publicationLoad
|
|
44
|
+
}];
|
|
45
|
+
if (mode.isProduction) return [{ name: "chromium" }];
|
|
46
|
+
const teardown = suites.serverLogs ? { teardown: "server-logs" } : {};
|
|
47
|
+
const projects = [{
|
|
48
|
+
name: "chromium",
|
|
49
|
+
testIgnore: ["**/*.production.e2e.test.ts", ...[
|
|
50
|
+
suites.serverLogs,
|
|
51
|
+
suites.publicationLoad,
|
|
52
|
+
suites.markdown
|
|
53
|
+
].filter((suite) => suite !== void 0)],
|
|
54
|
+
...teardown
|
|
55
|
+
}];
|
|
56
|
+
if (suites.markdown) projects.push({
|
|
57
|
+
name: "markdown",
|
|
58
|
+
testMatch: suites.markdown,
|
|
59
|
+
fullyParallel: false,
|
|
60
|
+
workers: 1,
|
|
61
|
+
...teardown
|
|
62
|
+
});
|
|
63
|
+
if (suites.serverLogs) projects.push({
|
|
64
|
+
name: "server-logs",
|
|
65
|
+
testMatch: suites.serverLogs,
|
|
66
|
+
fullyParallel: false
|
|
67
|
+
});
|
|
68
|
+
return projects;
|
|
69
|
+
}
|
|
70
|
+
function buildServers({ root, mode, fixtureServers, baseUrl, serverLog }) {
|
|
71
|
+
const buildMode = mode.isProduction ? "production" : "e2e";
|
|
72
|
+
const build = mode.shouldBuild ? `vp run build --mode ${buildMode} && ` : PREBUILT_GUARD;
|
|
73
|
+
const shared = {
|
|
74
|
+
cwd: root,
|
|
75
|
+
reuseExistingServer: false
|
|
76
|
+
};
|
|
77
|
+
const server = {
|
|
78
|
+
...shared,
|
|
79
|
+
timeout: STARTUP_TIMEOUT
|
|
80
|
+
};
|
|
81
|
+
const fixtures = mode.isProduction ? [] : fixtureServers.map((fixture) => ({
|
|
82
|
+
...fixture,
|
|
83
|
+
...shared
|
|
84
|
+
}));
|
|
85
|
+
const { hostname, port } = new URL(baseUrl);
|
|
86
|
+
const secondary = secondaryUrl(baseUrl);
|
|
87
|
+
return [
|
|
88
|
+
...fixtures,
|
|
89
|
+
{
|
|
90
|
+
...server,
|
|
91
|
+
command: `${build}mkdir -p test-results && vp run start --strictPort > ${serverLog} 2>&1`,
|
|
92
|
+
env: {
|
|
93
|
+
HOST: hostname,
|
|
94
|
+
PORT: port,
|
|
95
|
+
NODE_ENV: "production"
|
|
96
|
+
},
|
|
97
|
+
url: baseUrl
|
|
98
|
+
},
|
|
99
|
+
...mode.isPublicationLoad ? [{
|
|
100
|
+
...server,
|
|
101
|
+
command: `vp run start --strictPort > ${SECONDARY_LOG} 2>&1`,
|
|
102
|
+
env: {
|
|
103
|
+
HOST: hostname,
|
|
104
|
+
PORT: secondary.port,
|
|
105
|
+
NODE_ENV: "production"
|
|
106
|
+
},
|
|
107
|
+
url: secondary.href
|
|
108
|
+
}] : []
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
function definePlaywrightConfig({ root, testDir = "src/tests/pages", fixtureServers = [], suites = {} }) {
|
|
112
|
+
loadEnvFile(path.join(root, ".env.test"));
|
|
113
|
+
const mode = readRunMode(suites);
|
|
114
|
+
const buildMode = mode.isProduction ? "production" : "e2e";
|
|
115
|
+
Object.assign(env, loadEnv(buildMode, root, "VITE_"));
|
|
116
|
+
const baseUrl = readBaseUrl(mode);
|
|
117
|
+
const serverLog = `test-results/server-${buildMode}.log`;
|
|
118
|
+
env.SERVER_LOG_FILE = path.join(root, serverLog);
|
|
119
|
+
if (mode.isPublicationLoad) {
|
|
120
|
+
env.PUBLICATION_SECONDARY_URL = secondaryUrl(baseUrl).href;
|
|
121
|
+
env.SERVER_LOG_SECONDARY = path.join(root, SECONDARY_LOG);
|
|
122
|
+
}
|
|
123
|
+
const reportFolder = mode.isProduction ? "production" : "e2e";
|
|
124
|
+
return defineConfig({
|
|
125
|
+
testDir: path.join(root, testDir),
|
|
126
|
+
testMatch: mode.isProduction ? "**/*.production.e2e.test.ts" : "**/*.e2e.test.ts",
|
|
127
|
+
outputDir: path.join(root, mode.isProduction ? "test-results/e2e-production" : "test-results/e2e"),
|
|
128
|
+
globalTeardown: fileURLToPath(new URL("playwright-teardown.mjs", import.meta.url)),
|
|
129
|
+
fullyParallel: !mode.isPublicationLoad,
|
|
130
|
+
forbidOnly: true,
|
|
131
|
+
workers: readWorkers(mode),
|
|
132
|
+
reporter: [["list"], ["html", {
|
|
133
|
+
outputFolder: path.join(root, "playwright-report", reportFolder),
|
|
134
|
+
open: "never",
|
|
135
|
+
onlyFailures: Boolean(env.CI)
|
|
136
|
+
}]],
|
|
137
|
+
use: {
|
|
138
|
+
...devices["Desktop Chrome"],
|
|
139
|
+
channel: env.CI ? "chrome" : void 0,
|
|
140
|
+
baseURL: baseUrl,
|
|
141
|
+
locale: "en-US",
|
|
142
|
+
screenshot: "only-on-failure",
|
|
143
|
+
trace: "retain-on-failure"
|
|
144
|
+
},
|
|
145
|
+
projects: buildProjects({
|
|
146
|
+
mode,
|
|
147
|
+
suites
|
|
148
|
+
}),
|
|
149
|
+
webServer: buildServers({
|
|
150
|
+
root,
|
|
151
|
+
mode,
|
|
152
|
+
fixtureServers,
|
|
153
|
+
baseUrl,
|
|
154
|
+
serverLog
|
|
155
|
+
})
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
export { definePlaywrightConfig };
|
package/dist/project-plugin.mjs
CHANGED
|
@@ -7,7 +7,7 @@ const noExportList = defineRule({
|
|
|
7
7
|
meta: {
|
|
8
8
|
type: "suggestion",
|
|
9
9
|
schema: [],
|
|
10
|
-
messages: { inlineExport: "
|
|
10
|
+
messages: { inlineExport: "Export on the declaration itself (export function, export const, export type) instead of using export { ... }. An export list is only allowed in a re-export with from." }
|
|
11
11
|
},
|
|
12
12
|
create(context) {
|
|
13
13
|
return { ExportNamedDeclaration(node) {
|
|
@@ -32,10 +32,10 @@ const seoModule = defineRule({
|
|
|
32
32
|
type: "problem",
|
|
33
33
|
schema: [],
|
|
34
34
|
messages: {
|
|
35
|
-
onlySeo: "
|
|
36
|
-
localImport: "
|
|
37
|
-
computedImport: "
|
|
38
|
-
globImport: "
|
|
35
|
+
onlySeo: "seo.ts may export only seo. The route manifest loads the route export of every page in the initial bundle, and seo.ts goes with it, so keep the page body in index.tsx (see the page modules section of AGENTS.md).",
|
|
36
|
+
localImport: "seo.ts must not import values from relative paths or from src/routes/: whatever it imports goes into the initial bundle. Use import type, or keep the code in index.tsx (see the page modules section of AGENTS.md).",
|
|
37
|
+
computedImport: "seo.ts must not use import() with a computed path: the rule cannot tell whether the target is local. Keep the page body in index.tsx (see the page modules section of AGENTS.md).",
|
|
38
|
+
globImport: "seo.ts must not use import.meta.glob: Vite turns it into imports of every module the pattern matches. Keep the page body in index.tsx (see the page modules section of AGENTS.md)."
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
create(context) {
|
|
@@ -155,13 +155,13 @@ function testFilenameError(file) {
|
|
|
155
155
|
});
|
|
156
156
|
const candidate = dotted ? `${DOTTED_SUBJECT}${basename.slice(dotted.length)}` : basename;
|
|
157
157
|
const match = normalized.startsWith("tooling/") ? /^(?<subject>[^.]+)(?<qualifiers>(?:\.[a-z][a-zA-Z0-9]*)*)\.test\.ts$/u.exec(candidate) : /^(?<subject>[^.]+)(?<qualifiers>(?:\.[a-z][a-zA-Z0-9]*)*)(?:\.(?<environment>node|dom|browser|e2e)\.test|\.test-d)\.tsx?$/u.exec(candidate);
|
|
158
|
-
if (!match?.groups) return "
|
|
158
|
+
if (!match?.groups) return "In src/, name the test Name[.qualifier].<node|dom|browser|e2e>.test.ts(x), or Name[.qualifier].test-d.ts(x) for a type test. In tooling/, use Name[.qualifier].test.ts.";
|
|
159
159
|
const { environment } = match.groups;
|
|
160
160
|
const subject = dotted ?? match.groups.subject;
|
|
161
|
-
if (inPages && environment !== "e2e") return "
|
|
162
|
-
if (environment === "e2e" && (!inPages || !subjects[directory])) return "
|
|
163
|
-
if (!expected) return "
|
|
164
|
-
if (!subject || !expected.includes(subject)) return `Use
|
|
161
|
+
if (inPages && environment !== "e2e") return "Page tests must use the e2e environment.";
|
|
162
|
+
if (environment === "e2e" && (!inPages || !subjects[directory])) return "Place e2e tests in src/tests/pages/<Page>/. The accepted pages are listed in @evertondgn/solid-tooling; for another page, turn off project/test-filename for its folder in lint.overrides.";
|
|
163
|
+
if (!expected) return "Place the test in a __tests__ folder inside the module folder, as in Button/__tests__/Button.dom.test.tsx. For a test folder without a module, turn off project/test-filename for its files in lint.overrides.";
|
|
164
|
+
if (!subject || !expected.includes(subject)) return `Use the exact name of the subject under test: ${expected.join(", ")}. Qualifiers use a dot and camelCase.`;
|
|
165
165
|
}
|
|
166
166
|
const testFilename = defineRule({
|
|
167
167
|
meta: {
|
|
@@ -183,7 +183,7 @@ const cssModulesImport = defineRule({
|
|
|
183
183
|
meta: {
|
|
184
184
|
type: "suggestion",
|
|
185
185
|
schema: [],
|
|
186
|
-
messages: { useS: "
|
|
186
|
+
messages: { useS: "Import CSS Modules as S: import S from \"./styles.module.css\"." }
|
|
187
187
|
},
|
|
188
188
|
create(context) {
|
|
189
189
|
return { ImportDeclaration(node) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/vitest.mjs
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { env } from "node:process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { playwright } from "vite-plus/test/browser-playwright";
|
|
5
|
+
// src/vitest/index.ts
|
|
6
|
+
const CI_WORKERS = 2;
|
|
7
|
+
const STUB_PREFIX = "\0solid-tooling-stub:";
|
|
8
|
+
const NODE_STUBS = /* @__PURE__ */ new Map([
|
|
9
|
+
["server-only", "export default undefined"],
|
|
10
|
+
["virtual:file-routes", "export const pageRoutes = []\nexport default []"],
|
|
11
|
+
["virtual:solid-server-function-handler", "export const endpoint = '/_server'"]
|
|
12
|
+
]);
|
|
13
|
+
const TEST_ENV_KEYS = [
|
|
14
|
+
"BASE_URL_TEST",
|
|
15
|
+
"HOST",
|
|
16
|
+
"PORT"
|
|
17
|
+
];
|
|
18
|
+
function packageFile(name) {
|
|
19
|
+
return fileURLToPath(new URL(name, import.meta.url));
|
|
20
|
+
}
|
|
21
|
+
function nodeStubs() {
|
|
22
|
+
return {
|
|
23
|
+
name: "solid-tooling:node-stubs",
|
|
24
|
+
enforce: "pre",
|
|
25
|
+
resolveId: (id) => NODE_STUBS.has(id) ? `${STUB_PREFIX}${id}` : void 0,
|
|
26
|
+
load: (id) => id.startsWith(STUB_PREFIX) ? NODE_STUBS.get(id.slice(20)) : void 0
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function readTestEnv() {
|
|
30
|
+
return Object.fromEntries(TEST_ENV_KEYS.flatMap((key) => {
|
|
31
|
+
const value = env[key];
|
|
32
|
+
return value === void 0 ? [] : [[key, value]];
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function solidTest({ root, plugins }) {
|
|
36
|
+
return {
|
|
37
|
+
pool: "threads",
|
|
38
|
+
maxWorkers: env.CI ? CI_WORKERS : void 0,
|
|
39
|
+
css: false,
|
|
40
|
+
globals: true,
|
|
41
|
+
restoreMocks: true,
|
|
42
|
+
unstubGlobals: true,
|
|
43
|
+
unstubEnvs: true,
|
|
44
|
+
expect: {
|
|
45
|
+
requireAssertions: true,
|
|
46
|
+
poll: { interval: 10 }
|
|
47
|
+
},
|
|
48
|
+
env: readTestEnv(),
|
|
49
|
+
coverage: {
|
|
50
|
+
provider: "v8",
|
|
51
|
+
reporter: ["text", "html"],
|
|
52
|
+
thresholds: {
|
|
53
|
+
lines: 80,
|
|
54
|
+
functions: 80,
|
|
55
|
+
branches: 75,
|
|
56
|
+
statements: 80
|
|
57
|
+
},
|
|
58
|
+
include: ["src/**/*.{ts,tsx}"],
|
|
59
|
+
exclude: [
|
|
60
|
+
"src/**/*.d.ts",
|
|
61
|
+
"src/**/{constants,types,@types}/**",
|
|
62
|
+
"src/**/{constants,types}.{ts,tsx}",
|
|
63
|
+
"src/**/__tests__/**",
|
|
64
|
+
"src/tests/**",
|
|
65
|
+
"src/App.tsx",
|
|
66
|
+
"src/Document.tsx",
|
|
67
|
+
"src/entry-server.tsx",
|
|
68
|
+
"src/entry-client.tsx",
|
|
69
|
+
"src/router.ts"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
reporters: env.GITHUB_ACTIONS ? ["default", "github-actions"] : ["verbose"],
|
|
73
|
+
projects: [
|
|
74
|
+
{
|
|
75
|
+
plugins: [nodeStubs()],
|
|
76
|
+
test: {
|
|
77
|
+
name: {
|
|
78
|
+
label: "node",
|
|
79
|
+
color: "cyan"
|
|
80
|
+
},
|
|
81
|
+
environment: "node",
|
|
82
|
+
include: ["src/**/*.node.test.{ts,tsx}"]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
plugins: plugins(),
|
|
87
|
+
test: {
|
|
88
|
+
name: {
|
|
89
|
+
label: "dom",
|
|
90
|
+
color: "magenta"
|
|
91
|
+
},
|
|
92
|
+
environment: "happy-dom",
|
|
93
|
+
environmentOptions: { happyDOM: { url: "http://localhost/en" } },
|
|
94
|
+
include: ["src/**/*.dom.test.{ts,tsx}"]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
plugins: plugins(),
|
|
99
|
+
resolve: { alias: [{
|
|
100
|
+
find: /^@paraglide\//u,
|
|
101
|
+
replacement: `${path.join(root, "paraglide")}/`
|
|
102
|
+
}] },
|
|
103
|
+
test: {
|
|
104
|
+
css: true,
|
|
105
|
+
name: {
|
|
106
|
+
label: "browser",
|
|
107
|
+
color: "yellow"
|
|
108
|
+
},
|
|
109
|
+
setupFiles: [packageFile("vitest-setup/browser.mjs")],
|
|
110
|
+
include: ["src/**/*.browser.test.{ts,tsx}"],
|
|
111
|
+
browser: {
|
|
112
|
+
enabled: true,
|
|
113
|
+
headless: true,
|
|
114
|
+
trace: {
|
|
115
|
+
mode: "retain-on-failure",
|
|
116
|
+
tracesDir: path.join(root, "test-results/browser-traces")
|
|
117
|
+
},
|
|
118
|
+
provider: playwright({
|
|
119
|
+
contextOptions: { locale: "en-US" },
|
|
120
|
+
launchOptions: env.CI ? { channel: "chrome" } : {}
|
|
121
|
+
}),
|
|
122
|
+
instances: [{ browser: "chromium" }]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export { solidTest };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evertondgn/solid-tooling",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Lint, formatter and
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Lint, formatter, TypeScript and test presets for SolidJS applications built with Vite+.",
|
|
5
5
|
"author": "Éverton Toffanetto",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"type": "module",
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
|
-
"tsconfig.base.json"
|
|
22
|
+
"tsconfig.base.json",
|
|
23
|
+
"tsconfig.app.json"
|
|
23
24
|
],
|
|
24
25
|
"exports": {
|
|
25
26
|
"./architecture-plugin": "./dist/architecture-plugin.mjs",
|
|
@@ -27,10 +28,15 @@
|
|
|
27
28
|
"./css-filename": "./dist/css-filename.mjs",
|
|
28
29
|
"./fmt": "./dist/fmt.mjs",
|
|
29
30
|
"./lint": "./dist/lint.mjs",
|
|
31
|
+
"./playwright": "./dist/playwright.mjs",
|
|
32
|
+
"./playwright-teardown": "./dist/playwright-teardown.mjs",
|
|
30
33
|
"./project-plugin": "./dist/project-plugin.mjs",
|
|
31
34
|
"./solid-plugin": "./dist/solid-plugin.mjs",
|
|
35
|
+
"./vitest": "./dist/vitest.mjs",
|
|
36
|
+
"./vitest-setup/browser": "./dist/vitest-setup/browser.mjs",
|
|
32
37
|
"./package.json": "./package.json",
|
|
33
|
-
"./tsconfig.base.json": "./tsconfig.base.json"
|
|
38
|
+
"./tsconfig.base.json": "./tsconfig.base.json",
|
|
39
|
+
"./tsconfig.app.json": "./tsconfig.app.json"
|
|
34
40
|
},
|
|
35
41
|
"publishConfig": {
|
|
36
42
|
"access": "public"
|
|
@@ -39,21 +45,27 @@
|
|
|
39
45
|
"build": "vp pack",
|
|
40
46
|
"dev": "vp pack --watch",
|
|
41
47
|
"test": "vp run test-rules",
|
|
42
|
-
"test:tarball": "vp run test-tarball"
|
|
43
|
-
"prepublishOnly": "vp pack"
|
|
48
|
+
"test:tarball": "vp run test-tarball"
|
|
44
49
|
},
|
|
45
50
|
"dependencies": {
|
|
46
51
|
"eslint-plugin-solid": "0.19.0"
|
|
47
52
|
},
|
|
48
53
|
"peerDependencies": {
|
|
49
54
|
"@inlang/paraglide-js": "3.2.2",
|
|
50
|
-
"vite-plus": "1.6.0"
|
|
55
|
+
"vite-plus": "1.6.0",
|
|
56
|
+
"@playwright/test": "1.65.0"
|
|
51
57
|
},
|
|
52
58
|
"devDependencies": {
|
|
53
59
|
"@inlang/paraglide-js": "3.2.2",
|
|
54
60
|
"@types/node": "24.19.2",
|
|
55
61
|
"typescript": "7.0.2",
|
|
56
62
|
"valibot": "1.5.0",
|
|
57
|
-
"vite-plus": "1.6.0"
|
|
63
|
+
"vite-plus": "1.6.0",
|
|
64
|
+
"@playwright/test": "1.65.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@playwright/test": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
58
70
|
}
|
|
59
71
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"types": [
|
|
5
|
+
"node",
|
|
6
|
+
"vite/client",
|
|
7
|
+
"vite-plus/test/globals",
|
|
8
|
+
"@testing-library/jest-dom/vitest",
|
|
9
|
+
"@solidjs/vite-plugin/boundary-modules",
|
|
10
|
+
"@solidjs/vite-plugin/virtual-modules"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"${configDir}/src",
|
|
15
|
+
"${configDir}/env.ts",
|
|
16
|
+
"${configDir}/solid-env.d.ts",
|
|
17
|
+
"${configDir}/vite.config.ts",
|
|
18
|
+
"${configDir}/playwright.config.ts",
|
|
19
|
+
"${configDir}/tooling/**/*.ts"
|
|
20
|
+
]
|
|
21
|
+
}
|