@stealthscale/vite-config-specimen 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +117 -2
- package/dist/cyclonedx/bom.json +1 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.mjs +235 -0
- package/package.json +60 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stealth Scale B.V.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @stealthscale/vite-config-specimen
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@stealthscale/vite-config-specimen` configures the three places a specimen is read from: the
|
|
4
|
+
package that holds one, the application that shows a catalogue of them, and the workspace root they
|
|
5
|
+
sit under.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add -D @stealthscale/vite-config-specimen
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package peers on `@stealthscale/vite-plugin-specimen`, `@stealthscale/vite-config`,
|
|
14
|
+
`@stealthscale/vite-config-core` and `vite`.
|
|
15
|
+
|
|
16
|
+
## A package that holds specimens
|
|
17
|
+
|
|
18
|
+
Add `specimen.layers()` to whichever tier the package already builds on.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import * as react from "@stealthscale/vite-config-react";
|
|
22
|
+
import * as specimen from "@stealthscale/vite-config-specimen";
|
|
23
|
+
import * as theme from "@stealthscale/vite-config-theme";
|
|
24
|
+
import { defineConfig } from "@stealthscale/vite-config/preset/web";
|
|
25
|
+
|
|
26
|
+
export default defineConfig(import.meta.dirname, {
|
|
27
|
+
extends: [react.layers(), theme.layers(), specimen.layers()],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
It stops counting `**/*.specimen.tsx` towards the package's coverage. A specimen declares a page for
|
|
32
|
+
the catalogue to draw rather than behaviour to assert, so it is an entry point in the way an
|
|
33
|
+
application's `main` is. A package that keeps its specimens elsewhere names its own globs:
|
|
34
|
+
`specimen.layers(["src/pages/**/*.specimen.tsx"])`.
|
|
35
|
+
|
|
36
|
+
The package's example spec renders each `**/*.example.tsx` file without pressing a control, so
|
|
37
|
+
`layers()` excludes those files from coverage too. Their event handlers would otherwise count as
|
|
38
|
+
uncovered functions.
|
|
39
|
+
|
|
40
|
+
## An application that shows a catalogue
|
|
41
|
+
|
|
42
|
+
Add `specimen.catalogue()` to whichever tier the application already builds on.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import * as react from "@stealthscale/vite-config-react";
|
|
46
|
+
import * as specimen from "@stealthscale/vite-config-specimen";
|
|
47
|
+
import { defineConfig } from "@stealthscale/vite-config/preset/app";
|
|
48
|
+
|
|
49
|
+
export default defineConfig(import.meta.dirname, {
|
|
50
|
+
extends: [
|
|
51
|
+
react.layers(),
|
|
52
|
+
specimen.catalogue({ patterns: ["../../components/*/src/**/*.specimen.tsx"] }),
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`patterns` has no default. A pattern resolves against the application root, and an application that
|
|
58
|
+
shows a catalogue of a workspace's components sits beside those components rather than above them.
|
|
59
|
+
|
|
60
|
+
State `props` to have each page carry what its components accept, resolved out of their types. Left
|
|
61
|
+
out, nothing loads a compiler. Every member of `Options` reaches the plugin as written, and the
|
|
62
|
+
plugin's README documents what a page then gets.
|
|
63
|
+
|
|
64
|
+
`catalogue()` adds the index plugin, and every specimen as an entry the dependency scan walks before
|
|
65
|
+
the server starts. A specimen is reached through a dynamic import the scan does not follow, so
|
|
66
|
+
without the entries the first page a reader opens re-optimises and reloads the catalogue.
|
|
67
|
+
|
|
68
|
+
Naming the entries stops Vite working the entries out for itself, which is why `**/*.html` is named
|
|
69
|
+
beside them.
|
|
70
|
+
|
|
71
|
+
## The stylesheet a catalogue needs
|
|
72
|
+
|
|
73
|
+
An application drawn on `@stealthscale/theme` states `static: "*"` in its `theme.config.ts`.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
export default { static: "*", themes: [graphite] } satisfies Application;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The compiler extracts a value written as a JSX literal and nothing it reads from a prop. A scene
|
|
80
|
+
draws its axis as `<Button variant={one}>`, so without this the compiler emits no rule for any value
|
|
81
|
+
a matrix draws, and the page renders every look, size and status identically.
|
|
82
|
+
|
|
83
|
+
Measured on the button: 21 recipe classes reach the page and 7 have rules until `static` is set. A
|
|
84
|
+
product application states nothing and ships only the rules its own source asks for.
|
|
85
|
+
|
|
86
|
+
## The workspace root
|
|
87
|
+
|
|
88
|
+
Add `specimen.workspace()` to the root configuration.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import * as specimen from "@stealthscale/vite-config-specimen";
|
|
92
|
+
|
|
93
|
+
import { defineConfig } from "./packages/vite-config/src/preset/workspace.ts";
|
|
94
|
+
|
|
95
|
+
export default defineConfig(import.meta.dirname, { extends: [specimen.workspace()] });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The linter runs from the workspace root and reads the root's configuration and no other, so a
|
|
99
|
+
relaxation stated in a package would compose, merge, and never be read.
|
|
100
|
+
|
|
101
|
+
Four rules come off `**/*.specimen.tsx`:
|
|
102
|
+
|
|
103
|
+
| Rule | Why |
|
|
104
|
+
| ------------------------------ | ---------------------------------------------------------------- |
|
|
105
|
+
| `no-default-export` | A specimen is read through its default export |
|
|
106
|
+
| Doc comments and the size caps | A specimen is as long as the scenes it takes to show a component |
|
|
107
|
+
| `react/only-export-components` | The default export is a page description, not a component |
|
|
108
|
+
| `react/no-multi-comp` | A scene is built from the components that arrange it |
|
|
109
|
+
|
|
110
|
+
Pass a list to cover different files: `workspace(["components/**/*.specimen.tsx"])`.
|
|
111
|
+
|
|
112
|
+
The root also excludes `**/*.example.tsx` from coverage and turns the doc comment rules off for it.
|
|
113
|
+
The catalogue shows an example file verbatim as the Source of a scene, so a doc comment would end up
|
|
114
|
+
in the code a consumer copies.
|
|
115
|
+
|
|
116
|
+
## Licence
|
|
117
|
+
|
|
118
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"http://cyclonedx.org/schema/bom-1.7.schema.json","bomFormat":"CycloneDX","specVersion":"1.7","version":1,"metadata":{"lifecycles":[{"phase":"build"}],"tools":{"components":[{"type":"application","name":"rolldown","version":"1.2.12"},{"type":"application","name":"vite","version":"8.3.3"},{"type":"library","name":"core","group":"@arethetypeswrong","version":"0.18.5","bom-ref":"pkg:npm/%40arethetypeswrong/core@0.18.5","author":"Andrew Branch","description":"The API behind arethetypeswrong.github.io","hashes":[{"alg":"SHA-512","content":"f72b63cc6c318e6f54cfb23d6af7dbb79be542deae93db914444b3263aabce797a58abc8e9dc184e8faf277534d96aeafe64778aa97f3f386f1e129d2480230d"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40arethetypeswrong/core@0.18.5","externalReferences":[{"url":"https://github.com/arethetypeswrong/arethetypeswrong.github.io.git#packages/core","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=20"}]},{"type":"library","name":"testing-config","group":"@stealthscale","version":"0.6.0","bom-ref":"pkg:npm/%40stealthscale/testing-config@0.6.0","description":"Lists every part of the house contract a config, plugin or library package breaks.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/testing-config@0.6.0","externalReferences":[{"url":"https://github.com/stealth-scale/scale/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/scale.git#packages/testing-config","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/scale/tree/main/packages/testing-config#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-core","group":"@stealthscale","version":"0.4.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-core@0.4.0","description":"Composes presets, contributions, removals and overrides into one Vite configuration.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-core@0.4.0","externalReferences":[{"url":"https://github.com/stealth-scale/scale/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/scale.git#packages/vite-config-core","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/scale/tree/main/packages/vite-config-core#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-typescript","group":"@stealthscale","version":"0.3.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-typescript@0.3.0","description":"Sets the TypeScript options every stealthscale package compiles with.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-typescript@0.3.0","externalReferences":[{"url":"https://github.com/stealth-scale/scale/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/scale.git#packages/vite-config-typescript","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/scale/tree/main/packages/vite-config-typescript#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config","group":"@stealthscale","version":"0.8.0","bom-ref":"pkg:npm/%40stealthscale/vite-config@0.8.0","description":"Configures a package from five Vite+ tiers and a named layer for every block.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config@0.8.0","externalReferences":[{"url":"https://github.com/stealth-scale/scale/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/scale.git#packages/vite-config","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/scale/tree/main/packages/vite-config#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-plugin-specimen","group":"@stealthscale","version":"0.2.1","bom-ref":"pkg:npm/%40stealthscale/vite-plugin-specimen@0.2.1","description":"Indexes specimen files from their source, so a catalogue lists every page without loading one.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-plugin-specimen@0.2.1","externalReferences":[{"url":"https://github.com/stealth-scale/scale/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/scale.git#packages/vite-plugin-specimen","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/scale/tree/main/packages/vite-plugin-specimen#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"node","group":"@types","version":"26.6.4","bom-ref":"pkg:npm/%40types/node@26.6.4","description":"TypeScript definitions for node","hashes":[{"alg":"SHA-512","content":"95d54f0c2ce3edfb1a199acb07436e1ee4ef25cb0d6accac040a8ca25aff720c6b2ddd716eac48af75c988f9c71c9502c63e6c345d6f9d18fd6969eb561e4972"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40types/node@26.6.4","externalReferences":[{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped.git#types/node","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"coverage-v8","group":"@vitest","version":"5.0.3","bom-ref":"pkg:npm/%40vitest/coverage-v8@5.0.3","author":"Anthony Fu <anthonyfu117@hotmail.com>","description":"V8 coverage provider for Vitest","hashes":[{"alg":"SHA-512","content":"fa496ccb3ec1bd3d6f094dbc6647f39acd486bbf170f4578d54dc552d45a6b74bebceaff117bcad4ee81bd50fb9754731239ba48e351d9ac9b3af0c37fdbb499"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40vitest/coverage-v8@5.0.3","externalReferences":[{"url":"https://github.com/vitest-dev/vitest/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"git+https://github.com/vitest-dev/vitest.git#packages/coverage-v8","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://vitest.dev/guide/coverage","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"vite-plus-core","group":"@voidzero-dev","version":"1.1.0","bom-ref":"pkg:npm/%40voidzero-dev/vite-plus-core@1.1.0","author":"VoidZero Inc.","description":"The Unified Toolchain for the Web","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40voidzero-dev/vite-plus-core@1.1.0","externalReferences":[{"url":"https://github.com/voidzero-dev/vite-plus/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"git+https://github.com/voidzero-dev/vite-plus.git#packages/core","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://viteplus.dev/guide","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":"^20.19.0 || ^22.18.0 || >=24.11.0"}]},{"type":"library","name":"typescript","version":"7.0.2","bom-ref":"pkg:npm/typescript@7.0.2","author":"Microsoft Corp.","description":"TypeScript is a language for application scale JavaScript development","hashes":[{"alg":"SHA-512","content":"f0561abbdea8dcd28e85b8ca8bfa8dbc6fd6e63873c646dd9b9b23f406d9ff94f9b16aa7de12602df1b1dbbb11299593bf2cc23fc74b4414dfe6d05349155434"}],"licenses":[{"license":{"id":"Apache-2.0"}}],"purl":"pkg:npm/typescript@7.0.2","externalReferences":[{"url":"https://github.com/microsoft/TypeScript/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"https://github.com/microsoft/TypeScript.git","type":"vcs","comment":"as detected from PackageJson property \"repository.url\""},{"url":"https://www.typescriptlang.org/","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=16.20.0"}]},{"type":"library","name":"vitest","version":"5.0.3","bom-ref":"pkg:npm/vitest@5.0.3","author":"Anthony Fu <anthonyfu117@hotmail.com>","description":"Next generation testing framework powered by Vite","hashes":[{"alg":"SHA-512","content":"c4cc3ded2deb8ddb63e5d9156adee30aca96069bdc86cdd196941cb54ab0243d0a904464e34bf3d9f27bee50f05bf57387fa64d7c788b5803392876149eb378d"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/vitest@5.0.3","externalReferences":[{"url":"https://github.com/vitest-dev/vitest/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"git+https://github.com/vitest-dev/vitest.git#packages/vitest","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://vitest.dev","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":"^22.12.0 || ^24.0.0 || >=26.0.0"}]}]},"component":{"type":"library","name":"vite-config-specimen","group":"@stealthscale","version":"0.2.1","bom-ref":"pkg:npm/%40stealthscale/vite-config-specimen@0.2.1","description":"Configures an application that shows a catalogue, and the workspace root the specimens sit under.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-specimen@0.2.1","externalReferences":[{"url":"https://github.com/stealth-scale/scale/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/scale.git#packages/vite-config-specimen","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/scale/tree/main/packages/vite-config-specimen#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}],"evidence":{"licenses":[{"license":{"name":"file: LICENSE","text":{"content":"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBTdGVhbHRoIFNjYWxlIEIuVi4KClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCg==","contentType":"text/plain","encoding":"base64"}}}]}},"supplier":{"name":"Stealth Scale B.V.","url":["https://stealthscale.io"]}},"components":[],"dependencies":[{"ref":"pkg:npm/%40stealthscale/vite-config-specimen@0.2.1"}]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Contribution, Layer } from "@stealthscale/vite-config-core";
|
|
2
|
+
import { Options } from "@stealthscale/vite-plugin-specimen";
|
|
3
|
+
//#region src/catalogue.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Returns the index layer and the crawl layers, in the order they run.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* An array and not a tier, because a catalogue is an ordinary application first: it picks the
|
|
9
|
+
* tier its framework calls for, and these layers extend it.
|
|
10
|
+
* @param stated - Where the specimens are. `Options` documents every member.
|
|
11
|
+
*/
|
|
12
|
+
export declare function catalogue(stated: Options): readonly Layer[];
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/crawled.d.ts
|
|
15
|
+
/**
|
|
16
|
+
* Appends every specimen, and the application's own HTML, to the dependency scan's entries.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* A specimen is reached from the index through a dynamic import of a file outside the project
|
|
20
|
+
* root, and the scan follows neither. Left out, the first page a reader opens discovers its
|
|
21
|
+
* dependencies, re-optimises, and reloads the whole catalogue. Naming the entries disables Vite's
|
|
22
|
+
* own inference, which is why the HTML is named again beside them.
|
|
23
|
+
* @param patterns - Where the specimens are, as the plugin's options state them.
|
|
24
|
+
*/
|
|
25
|
+
export declare function crawled(patterns: readonly string[]): readonly Contribution[];
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/indexed.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* Appends `specimens()` to the plugins of an application that shows a catalogue.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* The plugin package is loaded when the plugin is constructed and not when the layer is stated,
|
|
33
|
+
* so reading the configuration for its metadata loads neither the plugin nor the compiler
|
|
34
|
+
* behind it. Each composition constructs a plugin instance of its own.
|
|
35
|
+
* @param stated - Where the specimens are. `Options` documents every member.
|
|
36
|
+
*/
|
|
37
|
+
export declare function indexed(stated: Options): Contribution;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/layers.d.ts
|
|
40
|
+
/**
|
|
41
|
+
* Returns the coverage exclusions for the specimen and example files of a package.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* The function returns a layer list, not a tier, so a component package can add it to whichever
|
|
45
|
+
* tier its framework requires. Lint exemptions are declared at the workspace root through
|
|
46
|
+
* `workspace()`, because the linter reads only the root config.
|
|
47
|
+
* @param files - Specimen globs. Defaults to every `*.specimen.tsx` file in the package.
|
|
48
|
+
*/
|
|
49
|
+
export declare function layers(files?: readonly string[]): readonly Layer[];
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/workspace.d.ts
|
|
52
|
+
/**
|
|
53
|
+
* Returns the layers the workspace root declares for specimen and example files.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* The coverage exclusions duplicate the per-package ones, because the root run counts the files
|
|
57
|
+
* of every package. Specimen files are exempt from `no-default-export`, the doc comment rules,
|
|
58
|
+
* `react/only-export-components` and `react/no-multi-comp`. Example files are exempt from the doc
|
|
59
|
+
* comment rules and the dependency cap. Each layer records its reason in `because`.
|
|
60
|
+
* @param files - Specimen globs. Defaults to every `*.specimen.tsx` file in the workspace.
|
|
61
|
+
*/
|
|
62
|
+
export declare function workspace(files?: readonly string[]): readonly Layer[];
|
|
63
|
+
//#endregion
|
|
64
|
+
export type { Options };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { contribute, located, named } from "@stealthscale/vite-config-core";
|
|
2
|
+
import { deps, lint, test } from "@stealthscale/vite-config";
|
|
3
|
+
//#region src/specimens.ts
|
|
4
|
+
/**
|
|
5
|
+
* Names the files every layer of this package treats as specimens, and renames a contribution a
|
|
6
|
+
* layer borrows from the base configuration after this package.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The files the linter and the runner treat as specimens, unless a caller names others.
|
|
10
|
+
*/
|
|
11
|
+
const SPECIMENS = ["**/*.specimen.tsx"];
|
|
12
|
+
/**
|
|
13
|
+
* Renames a contribution borrowed from the base configuration, so its layer name identifies this
|
|
14
|
+
* package and the call a consumer wrote.
|
|
15
|
+
*
|
|
16
|
+
* @param contribution - The contribution as the base configuration built it.
|
|
17
|
+
* @param from - The base call's name, as it opens the contribution's name.
|
|
18
|
+
* @param to - This package's name for it.
|
|
19
|
+
* @returns The contribution under the new name.
|
|
20
|
+
*/
|
|
21
|
+
function renamed(contribution, from, to) {
|
|
22
|
+
return named(contribution.name.replace(from, to), contribution);
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/crawled.ts
|
|
26
|
+
/**
|
|
27
|
+
* Adds the specimens to the entries the dependency scan walks before a server starts serving.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Appends every specimen, and the application's own HTML, to the dependency scan's entries.
|
|
31
|
+
*
|
|
32
|
+
* @remarks
|
|
33
|
+
* A specimen is reached from the index through a dynamic import of a file outside the project
|
|
34
|
+
* root, and the scan follows neither. Left out, the first page a reader opens discovers its
|
|
35
|
+
* dependencies, re-optimises, and reloads the whole catalogue. Naming the entries disables Vite's
|
|
36
|
+
* own inference, which is why the HTML is named again beside them.
|
|
37
|
+
* @param patterns - Where the specimens are, as the plugin's options state them.
|
|
38
|
+
*/
|
|
39
|
+
function crawled(patterns) {
|
|
40
|
+
return deps.crawl({
|
|
41
|
+
because: "a specimen is reached through a dynamic import the dependency scan does not follow, so its dependencies would be discovered by the first page opened and cost a reload",
|
|
42
|
+
files: ["**/*.html", ...patterns]
|
|
43
|
+
}).map((contribution) => renamed(contribution, "deps.crawl", "specimen.crawled"));
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/loaded.ts
|
|
47
|
+
/**
|
|
48
|
+
* Defers loading the specimen plugin until a plugin is constructed, rather than when the
|
|
49
|
+
* configuration is read.
|
|
50
|
+
*
|
|
51
|
+
* @remarks
|
|
52
|
+
* Vite bundles a configuration file before running it, and the bundler resolves every import it
|
|
53
|
+
* can see, a dynamic import with a literal specifier included. If the plugin package has not
|
|
54
|
+
* been built yet, that resolution fails and the configuration cannot be read at all, not even to
|
|
55
|
+
* build the task graph that would build the package. Holding the specifier in a variable puts it
|
|
56
|
+
* out of the bundler's reach, so Node resolves it at call time, relative to this module wherever
|
|
57
|
+
* it ends up running from.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* The plugin package specifier, held in a variable so the bundler cannot resolve it statically.
|
|
61
|
+
*/
|
|
62
|
+
const PLUGIN = "@stealthscale/vite-plugin-specimen";
|
|
63
|
+
/**
|
|
64
|
+
* Imports the specimen plugin package.
|
|
65
|
+
*
|
|
66
|
+
* @throws {@link Error} When the plugin package has not been built yet.
|
|
67
|
+
*/
|
|
68
|
+
function loaded() {
|
|
69
|
+
return import(located(PLUGIN, import.meta.url));
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/indexed.ts
|
|
73
|
+
/**
|
|
74
|
+
* Adds the specimen plugin to the plugins a tier already built.
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* The configuration key the plugin joins.
|
|
78
|
+
*/
|
|
79
|
+
const AT = "plugins";
|
|
80
|
+
/**
|
|
81
|
+
* Appends `specimens()` to the plugins of an application that shows a catalogue.
|
|
82
|
+
*
|
|
83
|
+
* @remarks
|
|
84
|
+
* The plugin package is loaded when the plugin is constructed and not when the layer is stated,
|
|
85
|
+
* so reading the configuration for its metadata loads neither the plugin nor the compiler
|
|
86
|
+
* behind it. Each composition constructs a plugin instance of its own.
|
|
87
|
+
* @param stated - Where the specimens are. `Options` documents every member.
|
|
88
|
+
*/
|
|
89
|
+
function indexed(stated) {
|
|
90
|
+
return contribute({
|
|
91
|
+
at: AT,
|
|
92
|
+
because: "a catalogue lists every page before it loads one, which needs each specimen's metadata parsed out of the source rather than read off a module that has run",
|
|
93
|
+
itemOf: async () => (await loaded()).specimens(stated),
|
|
94
|
+
name: "specimen.indexed"
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/catalogue.ts
|
|
99
|
+
/**
|
|
100
|
+
* Returns the index layer and the crawl layers, in the order they run.
|
|
101
|
+
*
|
|
102
|
+
* @remarks
|
|
103
|
+
* An array and not a tier, because a catalogue is an ordinary application first: it picks the
|
|
104
|
+
* tier its framework calls for, and these layers extend it.
|
|
105
|
+
* @param stated - Where the specimens are. `Options` documents every member.
|
|
106
|
+
*/
|
|
107
|
+
function catalogue(stated) {
|
|
108
|
+
return [indexed(stated), ...crawled(stated.patterns)];
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/examples.ts
|
|
112
|
+
/**
|
|
113
|
+
* Exempts example files from coverage, from the doc comment rules and from the dependency cap.
|
|
114
|
+
*
|
|
115
|
+
* @remarks
|
|
116
|
+
* Example files are the code consumers copy, and the catalogue shows them verbatim, so doc
|
|
117
|
+
* comments would end up in the copied code. The example spec of each package renders its examples
|
|
118
|
+
* without interacting with them, so event handlers would count as uncovered functions. An example
|
|
119
|
+
* of a whole screen imports every package the screen is built from.
|
|
120
|
+
*/
|
|
121
|
+
/**
|
|
122
|
+
* Glob matching example files.
|
|
123
|
+
*/
|
|
124
|
+
const EXAMPLES = ["**/*.example.tsx"];
|
|
125
|
+
/**
|
|
126
|
+
* Returns the coverage exclusion for example files.
|
|
127
|
+
*
|
|
128
|
+
* @param files - Example globs. Defaults to {@link EXAMPLES}.
|
|
129
|
+
*/
|
|
130
|
+
function unmeasured(files = EXAMPLES) {
|
|
131
|
+
return test.omit({
|
|
132
|
+
because: "an example is the code a consumer copies, and its specification renders it without pressing a control",
|
|
133
|
+
files
|
|
134
|
+
}).map((contribution) => renamed(contribution, "test.omit", "specimen.example.uncounted"));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Returns the lint layer that disables the doc comment rules for example files.
|
|
138
|
+
*
|
|
139
|
+
* @param files - Example globs. Defaults to {@link EXAMPLES}.
|
|
140
|
+
*/
|
|
141
|
+
function undescribed(files = EXAMPLES) {
|
|
142
|
+
return named("specimen.example.undocumented", lint.undocumented(files));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Returns the lint layer that disables the dependency cap for example files.
|
|
146
|
+
*
|
|
147
|
+
* @remarks
|
|
148
|
+
* An example of an application shell renders a whole screen, and each region of the screen comes
|
|
149
|
+
* from its own package. Its dependency count is the number of packages the screen is built from,
|
|
150
|
+
* and one module per package would move the composition out of the file the catalogue shows.
|
|
151
|
+
* Only `import/max-dependencies` comes off.
|
|
152
|
+
* @param files - Example globs. Defaults to {@link EXAMPLES}.
|
|
153
|
+
*/
|
|
154
|
+
function uncapped(files = EXAMPLES) {
|
|
155
|
+
return named("specimen.example.composed", lint.relax({
|
|
156
|
+
because: "an example renders a whole screen from the packages it is built from, and the catalogue shows the one file",
|
|
157
|
+
files: [...files],
|
|
158
|
+
rules: { "import/max-dependencies": "off" }
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/uncounted.ts
|
|
163
|
+
/**
|
|
164
|
+
* Stops counting the specimens a package holds towards its coverage.
|
|
165
|
+
*/
|
|
166
|
+
/**
|
|
167
|
+
* Stops counting each specimen towards the coverage its package is held to.
|
|
168
|
+
*
|
|
169
|
+
* @remarks
|
|
170
|
+
* A specimen is an entry point the catalogue loads, like the `main` of an application, and it
|
|
171
|
+
* declares a page rather than behaviour to assert. Counted, every specimen would sit at nothing
|
|
172
|
+
* until somebody wrote a specification for a page description.
|
|
173
|
+
* @param files - Which files are specimens. Defaults to any `*.specimen.tsx` in the package.
|
|
174
|
+
*/
|
|
175
|
+
function uncounted(files = SPECIMENS) {
|
|
176
|
+
return test.omit({
|
|
177
|
+
because: "a specimen declares a page for the catalogue to draw rather than behaviour to assert, so it is an entry point in the way an application's main is",
|
|
178
|
+
files
|
|
179
|
+
}).map((contribution) => renamed(contribution, "test.omit", "specimen.uncounted"));
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/layers.ts
|
|
183
|
+
/**
|
|
184
|
+
* Returns the coverage exclusions for the specimen and example files of a package.
|
|
185
|
+
*
|
|
186
|
+
* @remarks
|
|
187
|
+
* The function returns a layer list, not a tier, so a component package can add it to whichever
|
|
188
|
+
* tier its framework requires. Lint exemptions are declared at the workspace root through
|
|
189
|
+
* `workspace()`, because the linter reads only the root config.
|
|
190
|
+
* @param files - Specimen globs. Defaults to every `*.specimen.tsx` file in the package.
|
|
191
|
+
*/
|
|
192
|
+
function layers(files = SPECIMENS) {
|
|
193
|
+
return [...uncounted(files), ...unmeasured()];
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/workspace.ts
|
|
197
|
+
/**
|
|
198
|
+
* Lint and coverage layers the workspace root declares for specimen and example files.
|
|
199
|
+
*
|
|
200
|
+
* @remarks
|
|
201
|
+
* The layers belong in the root config, not in the catalogue application. The linter reads only
|
|
202
|
+
* the root config, so a relaxation declared elsewhere is merged but never applied.
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* Returns the layers the workspace root declares for specimen and example files.
|
|
206
|
+
*
|
|
207
|
+
* @remarks
|
|
208
|
+
* The coverage exclusions duplicate the per-package ones, because the root run counts the files
|
|
209
|
+
* of every package. Specimen files are exempt from `no-default-export`, the doc comment rules,
|
|
210
|
+
* `react/only-export-components` and `react/no-multi-comp`. Example files are exempt from the doc
|
|
211
|
+
* comment rules and the dependency cap. Each layer records its reason in `because`.
|
|
212
|
+
* @param files - Specimen globs. Defaults to every `*.specimen.tsx` file in the workspace.
|
|
213
|
+
*/
|
|
214
|
+
function workspace(files = SPECIMENS) {
|
|
215
|
+
return [
|
|
216
|
+
...uncounted(files),
|
|
217
|
+
named("specimen.exported", lint.defaultExported(files)),
|
|
218
|
+
named("specimen.undocumented", lint.undocumented(files)),
|
|
219
|
+
named("specimen.described", lint.relax({
|
|
220
|
+
because: "a specimen's default export is a page description rather than a component, and the rule is a fast refresh convention that reads it as an anonymous one",
|
|
221
|
+
files: [...files],
|
|
222
|
+
rules: { "react/only-export-components": "off" }
|
|
223
|
+
})),
|
|
224
|
+
named("specimen.composed", lint.relax({
|
|
225
|
+
because: "a scene is built from the small components that arrange it, each drawn once beside the caption explaining it, and splitting them into files of their own puts every piece further from the picture it is part of",
|
|
226
|
+
files: [...files],
|
|
227
|
+
rules: { "react/no-multi-comp": "off" }
|
|
228
|
+
})),
|
|
229
|
+
...unmeasured(),
|
|
230
|
+
undescribed(),
|
|
231
|
+
uncapped()
|
|
232
|
+
];
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
export { catalogue, crawled, indexed, layers, workspace };
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stealthscale/vite-config-specimen",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Configures an application that shows a catalogue, and the workspace root the specimens sit under.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"catalogue",
|
|
7
|
+
"specimen",
|
|
8
|
+
"vite-config"
|
|
9
|
+
],
|
|
10
|
+
"homepage": "https://github.com/stealth-scale/scale/tree/main/packages/vite-config-specimen#readme",
|
|
11
|
+
"bugs": "https://github.com/stealth-scale/scale/issues",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/stealth-scale/scale.git",
|
|
16
|
+
"directory": "packages/vite-config-specimen"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"imports": {
|
|
26
|
+
"#*": "./src/*"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./dist/index.mjs",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@arethetypeswrong/core": "^0.18",
|
|
37
|
+
"@stealthscale/testing-config": "^0.6.0",
|
|
38
|
+
"@stealthscale/vite-config": "^0.8.0",
|
|
39
|
+
"@stealthscale/vite-config-core": "^0.4.0",
|
|
40
|
+
"@stealthscale/vite-config-typescript": "^0.3.0",
|
|
41
|
+
"@stealthscale/vite-plugin-specimen": "^0.2.1",
|
|
42
|
+
"@types/node": "^26.6",
|
|
43
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
44
|
+
"publint": "^0.3",
|
|
45
|
+
"typescript": "^7.0",
|
|
46
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
|
|
47
|
+
"vitest": "5.0.3"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@stealthscale/vite-config": "^0.8.0",
|
|
51
|
+
"@stealthscale/vite-config-core": "^0.4.0",
|
|
52
|
+
"@stealthscale/vite-plugin-specimen": "^0.2.1",
|
|
53
|
+
"vite": "^8.3"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=26.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "vp pack",
|
|
60
|
+
"check": "vp check",
|
|
61
|
+
"test": "vp test"
|
|
62
|
+
}
|
|
6
63
|
}
|