@design.estate/wcctools 5.1.0 → 6.1.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/.smartconfig.json +7 -3
- package/changelog.md +28 -0
- package/cli.js +3 -0
- package/dist_shell/bundle.js +2970 -0
- package/dist_shell/bundle.js.map +1 -0
- package/dist_shell/bundle.js.third-party-notices.json +1722 -0
- package/dist_shell/bundle.js.third-party-notices.txt +4195 -0
- package/dist_shell/index.html +21 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.devapi.d.ts +11 -0
- package/dist_ts/classes.devapi.js +21 -0
- package/dist_ts/classes.devserver.d.ts +49 -0
- package/dist_ts/classes.devserver.js +142 -0
- package/dist_ts/cli.d.ts +6 -0
- package/dist_ts/cli.js +51 -0
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -0
- package/dist_ts/paths.d.ts +4 -0
- package/dist_ts/paths.js +6 -0
- package/dist_ts/plugins.d.ts +20 -0
- package/dist_ts/plugins.js +27 -0
- package/dist_ts/shellcontent.d.ts +9 -0
- package/dist_ts/shellcontent.js +42 -0
- package/dist_ts/standards/baselines.d.ts +27 -0
- package/dist_ts/standards/baselines.js +83 -0
- package/dist_ts/standards/checker.d.ts +21 -0
- package/dist_ts/standards/checker.js +88 -0
- package/dist_ts/standards/classes.gitrepo.d.ts +13 -0
- package/dist_ts/standards/classes.gitrepo.js +43 -0
- package/dist_ts/standards/classes.sourceedits.d.ts +15 -0
- package/dist_ts/standards/classes.sourceedits.js +54 -0
- package/dist_ts/standards/classes.standardfixer.d.ts +40 -0
- package/dist_ts/standards/classes.standardfixer.js +210 -0
- package/dist_ts/standards/classes.standardproject.d.ts +80 -0
- package/dist_ts/standards/classes.standardproject.js +195 -0
- package/dist_ts/standards/classes.standardtree.d.ts +56 -0
- package/dist_ts/standards/classes.standardtree.js +207 -0
- package/dist_ts/standards/cli.d.ts +7 -0
- package/dist_ts/standards/cli.js +89 -0
- package/dist_ts/standards/config.d.ts +33 -0
- package/dist_ts/standards/config.js +139 -0
- package/dist_ts/standards/errors.d.ts +9 -0
- package/dist_ts/standards/errors.js +17 -0
- package/dist_ts/standards/helpers.ast.d.ts +56 -0
- package/dist_ts/standards/helpers.ast.js +172 -0
- package/dist_ts/standards/helpers.paths.d.ts +36 -0
- package/dist_ts/standards/helpers.paths.js +86 -0
- package/dist_ts/standards/index.d.ts +14 -0
- package/dist_ts/standards/index.js +15 -0
- package/dist_ts/standards/output.d.ts +7 -0
- package/dist_ts/standards/output.js +57 -0
- package/dist_ts/standards/ratchet.d.ts +15 -0
- package/dist_ts/standards/ratchet.js +45 -0
- package/dist_ts/standards/rule.d.ts +50 -0
- package/dist_ts/standards/rule.js +9 -0
- package/dist_ts/standards/rules/barrel.element-index.d.ts +7 -0
- package/dist_ts/standards/rules/barrel.element-index.js +108 -0
- package/dist_ts/standards/rules/barrel.no-demo-export.d.ts +3 -0
- package/dist_ts/standards/rules/barrel.no-demo-export.js +29 -0
- package/dist_ts/standards/rules/demo.fixture-not-exported.d.ts +3 -0
- package/dist_ts/standards/rules/demo.fixture-not-exported.js +29 -0
- package/dist_ts/standards/rules/demo.groups.d.ts +6 -0
- package/dist_ts/standards/rules/demo.groups.js +72 -0
- package/dist_ts/standards/rules/demo.required.d.ts +3 -0
- package/dist_ts/standards/rules/demo.required.js +48 -0
- package/dist_ts/standards/rules/demo.separate-file.d.ts +3 -0
- package/dist_ts/standards/rules/demo.separate-file.js +38 -0
- package/dist_ts/standards/rules/demo.shape.d.ts +3 -0
- package/dist_ts/standards/rules/demo.shape.js +38 -0
- package/dist_ts/standards/rules/docs.component-index.d.ts +8 -0
- package/dist_ts/standards/rules/docs.component-index.js +84 -0
- package/dist_ts/standards/rules/element.exported.d.ts +3 -0
- package/dist_ts/standards/rules/element.exported.js +33 -0
- package/dist_ts/standards/rules/element.literal-tag.d.ts +3 -0
- package/dist_ts/standards/rules/element.literal-tag.js +30 -0
- package/dist_ts/standards/rules/element.reachable.d.ts +3 -0
- package/dist_ts/standards/rules/element.reachable.js +48 -0
- package/dist_ts/standards/rules/element.tag-map.d.ts +3 -0
- package/dist_ts/standards/rules/element.tag-map.js +83 -0
- package/dist_ts/standards/rules/i18n.literals.d.ts +3 -0
- package/dist_ts/standards/rules/i18n.literals.js +52 -0
- package/dist_ts/standards/rules/index.d.ts +12 -0
- package/dist_ts/standards/rules/index.js +83 -0
- package/dist_ts/standards/rules/layout.element-folder.d.ts +3 -0
- package/dist_ts/standards/rules/layout.element-folder.js +47 -0
- package/dist_ts/standards/rules/layout.entry-file-name.d.ts +3 -0
- package/dist_ts/standards/rules/layout.entry-file-name.js +71 -0
- package/dist_ts/standards/rules/layout.entry.d.ts +3 -0
- package/dist_ts/standards/rules/layout.entry.js +38 -0
- package/dist_ts/standards/rules/layout.group-folder.d.ts +3 -0
- package/dist_ts/standards/rules/layout.group-folder.js +22 -0
- package/dist_ts/standards/rules/layout.shared-00.d.ts +3 -0
- package/dist_ts/standards/rules/layout.shared-00.js +36 -0
- package/dist_ts/standards/rules/naming.class-matches-tag.d.ts +3 -0
- package/dist_ts/standards/rules/naming.class-matches-tag.js +26 -0
- package/dist_ts/standards/rules/naming.lowercase-files.d.ts +3 -0
- package/dist_ts/standards/rules/naming.lowercase-files.js +26 -0
- package/dist_ts/standards/rules/naming.tag-prefix.d.ts +3 -0
- package/dist_ts/standards/rules/naming.tag-prefix.js +23 -0
- package/dist_ts/standards/rules/props.accessor.d.ts +3 -0
- package/dist_ts/standards/rules/props.accessor.js +35 -0
- package/dist_ts/standards/rules/props.object-no-attribute.d.ts +3 -0
- package/dist_ts/standards/rules/props.object-no-attribute.js +42 -0
- package/dist_ts/standards/rules/props.reflect-primitive.d.ts +5 -0
- package/dist_ts/standards/rules/props.reflect-primitive.js +41 -0
- package/dist_ts/standards/rules/props.typed.d.ts +6 -0
- package/dist_ts/standards/rules/props.typed.js +64 -0
- package/dist_ts/standards/rules/shared.d.ts +38 -0
- package/dist_ts/standards/rules/shared.js +103 -0
- package/dist_ts/standards/rules/style.bdtheme.d.ts +5 -0
- package/dist_ts/standards/rules/style.bdtheme.js +28 -0
- package/dist_ts_interfaces/bridge.d.ts +88 -0
- package/dist_ts_interfaces/bridge.js +2 -0
- package/dist_ts_interfaces/catalog.d.ts +32 -0
- package/dist_ts_interfaces/catalog.js +6 -0
- package/dist_ts_interfaces/index.d.ts +4 -0
- package/dist_ts_interfaces/index.js +5 -0
- package/dist_ts_interfaces/plugins.d.ts +2 -0
- package/dist_ts_interfaces/plugins.js +3 -0
- package/dist_ts_interfaces/requests.d.ts +15 -0
- package/dist_ts_interfaces/requests.js +2 -0
- package/dist_ts_interfaces/standard.d.ts +93 -0
- package/dist_ts_interfaces/standard.js +6 -0
- package/dist_ts_shared/index.d.ts +1 -0
- package/dist_ts_shared/index.js +2 -0
- package/dist_ts_shared/plugins.d.ts +2 -0
- package/dist_ts_shared/plugins.js +3 -0
- package/dist_ts_shared/previewroute.d.ts +18 -0
- package/dist_ts_shared/previewroute.js +47 -0
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.js +1 -1
- package/dist_ts_shell/elements/wcc-preview-frame.d.ts +23 -0
- package/dist_ts_shell/elements/wcc-preview-frame.js +197 -0
- package/dist_ts_shell/elements/wcc-properties.d.ts +49 -0
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-properties.js +149 -216
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.js +1 -1
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.d.ts +2 -2
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-recording-panel.js +12 -16
- package/dist_ts_shell/elements/wcc-shell.d.ts +87 -0
- package/dist_ts_shell/elements/wcc-shell.js +524 -0
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.d.ts +12 -10
- package/{dist_ts_web → dist_ts_shell}/elements/wcc-sidebar.js +137 -183
- package/dist_ts_shell/index.d.ts +4 -0
- package/dist_ts_shell/index.js +5 -0
- package/dist_ts_shell/plugins.d.ts +4 -0
- package/dist_ts_shell/plugins.js +5 -0
- package/dist_ts_shell/previewconnection.d.ts +30 -0
- package/dist_ts_shell/previewconnection.js +86 -0
- package/dist_ts_shell/services/recorder.service.js +353 -0
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/catalog.d.ts +45 -0
- package/dist_ts_web/catalog.js +153 -0
- package/dist_ts_web/elementproperties.d.ts +15 -0
- package/dist_ts_web/elementproperties.js +89 -0
- package/dist_ts_web/elements/wcc-preview.d.ts +19 -0
- package/dist_ts_web/elements/wcc-preview.js +164 -0
- package/dist_ts_web/elements/wcc-standalone-notice.d.ts +14 -0
- package/dist_ts_web/elements/wcc-standalone-notice.js +110 -0
- package/dist_ts_web/elements/wcctools.helpers.d.ts +2 -6
- package/dist_ts_web/elements/wcctools.helpers.js +1 -7
- package/dist_ts_web/index.d.ts +2 -5
- package/dist_ts_web/index.js +38 -17
- package/dist_ts_web/previewbridge.d.ts +39 -0
- package/dist_ts_web/previewbridge.js +153 -0
- package/dist_ts_web/renderedtree.d.ts +5 -0
- package/dist_ts_web/renderedtree.js +37 -0
- package/dist_ts_web/wcctools.interfaces.d.ts +3 -6
- package/dist_ts_web/wcctools.interfaces.js +1 -1
- package/dist_ts_web/wcctools.plugins.d.ts +3 -1
- package/dist_ts_web/wcctools.plugins.js +4 -2
- package/package.json +17 -7
- package/readme.md +93 -28
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.devapi.ts +25 -0
- package/ts/classes.devserver.ts +161 -0
- package/ts/cli.ts +53 -0
- package/ts/index.ts +3 -0
- package/ts/paths.ts +7 -0
- package/ts/plugins.ts +37 -0
- package/ts/shellcontent.ts +42 -0
- package/ts/standards/baselines.ts +100 -0
- package/ts/standards/checker.ts +114 -0
- package/ts/standards/classes.gitrepo.ts +48 -0
- package/ts/standards/classes.sourceedits.ts +58 -0
- package/ts/standards/classes.standardfixer.ts +234 -0
- package/ts/standards/classes.standardproject.ts +250 -0
- package/ts/standards/classes.standardtree.ts +220 -0
- package/ts/standards/cli.ts +86 -0
- package/ts/standards/config.ts +160 -0
- package/ts/standards/errors.ts +18 -0
- package/ts/standards/helpers.ast.ts +208 -0
- package/ts/standards/helpers.paths.ts +119 -0
- package/ts/standards/index.ts +22 -0
- package/ts/standards/output.ts +65 -0
- package/ts/standards/ratchet.ts +63 -0
- package/ts/standards/rule.ts +58 -0
- package/ts/standards/rules/barrel.element-index.ts +112 -0
- package/ts/standards/rules/barrel.no-demo-export.ts +30 -0
- package/ts/standards/rules/demo.fixture-not-exported.ts +30 -0
- package/ts/standards/rules/demo.groups.ts +74 -0
- package/ts/standards/rules/demo.required.ts +49 -0
- package/ts/standards/rules/demo.separate-file.ts +40 -0
- package/ts/standards/rules/demo.shape.ts +42 -0
- package/ts/standards/rules/docs.component-index.ts +88 -0
- package/ts/standards/rules/element.exported.ts +35 -0
- package/ts/standards/rules/element.literal-tag.ts +30 -0
- package/ts/standards/rules/element.reachable.ts +49 -0
- package/ts/standards/rules/element.tag-map.ts +87 -0
- package/ts/standards/rules/i18n.literals.ts +58 -0
- package/ts/standards/rules/index.ts +87 -0
- package/ts/standards/rules/layout.element-folder.ts +49 -0
- package/ts/standards/rules/layout.entry-file-name.ts +73 -0
- package/ts/standards/rules/layout.entry.ts +40 -0
- package/ts/standards/rules/layout.group-folder.ts +23 -0
- package/ts/standards/rules/layout.shared-00.ts +37 -0
- package/ts/standards/rules/naming.class-matches-tag.ts +27 -0
- package/ts/standards/rules/naming.lowercase-files.ts +27 -0
- package/ts/standards/rules/naming.tag-prefix.ts +24 -0
- package/ts/standards/rules/props.accessor.ts +37 -0
- package/ts/standards/rules/props.object-no-attribute.ts +45 -0
- package/ts/standards/rules/props.reflect-primitive.ts +44 -0
- package/ts/standards/rules/props.typed.ts +70 -0
- package/ts/standards/rules/shared.ts +127 -0
- package/ts/standards/rules/style.bdtheme.ts +31 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/catalog.ts +194 -0
- package/ts_web/elementproperties.ts +96 -0
- package/ts_web/elements/wcc-preview.ts +111 -0
- package/ts_web/elements/wcc-standalone-notice.ts +65 -0
- package/ts_web/elements/wcctools.helpers.ts +2 -10
- package/ts_web/index.ts +38 -19
- package/ts_web/previewbridge.ts +189 -0
- package/ts_web/readme.md +26 -85
- package/ts_web/renderedtree.ts +43 -0
- package/ts_web/wcctools.interfaces.ts +4 -7
- package/ts_web/wcctools.plugins.ts +6 -1
- package/dist_bundle/bundle.js +0 -92419
- package/dist_bundle/bundle.js.map +0 -1
- package/dist_ts_web/elements/wcc-dashboard.d.ts +0 -119
- package/dist_ts_web/elements/wcc-dashboard.js +0 -700
- package/dist_ts_web/elements/wcc-frame.d.ts +0 -22
- package/dist_ts_web/elements/wcc-frame.js +0 -256
- package/dist_ts_web/elements/wcc-properties.d.ts +0 -43
- package/dist_ts_web/pages/index.d.ts +0 -1
- package/dist_ts_web/pages/index.js +0 -2
- package/dist_ts_web/services/recorder.service.js +0 -353
- package/ts_web/elements/wcc-contextmenu.ts +0 -291
- package/ts_web/elements/wcc-dashboard.ts +0 -672
- package/ts_web/elements/wcc-frame.ts +0 -189
- package/ts_web/elements/wcc-properties.ts +0 -1089
- package/ts_web/elements/wcc-record-button.ts +0 -108
- package/ts_web/elements/wcc-recording-panel.ts +0 -1010
- package/ts_web/elements/wcc-sidebar.ts +0 -1470
- package/ts_web/pages/index.ts +0 -1
- package/ts_web/services/recorder.service.ts +0 -451
- package/ts_web/types/dom-mediacapture-stub/index.d.ts +0 -12
- package/ts_web/types/dom-mediacapture-stub/package.json +0 -6
- package/ts_web/types/dom-webcodecs-stub/index.d.ts +0 -2
- package/ts_web/types/dom-webcodecs-stub/package.json +0 -6
- /package/{dist_ts_web → dist_ts_shell}/elements/wcc-contextmenu.d.ts +0 -0
- /package/{dist_ts_web → dist_ts_shell}/elements/wcc-record-button.d.ts +0 -0
- /package/{dist_ts_web → dist_ts_shell}/services/recorder.service.d.ts +0 -0
package/readme.md
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
`@design.estate/wcctools` provides a comprehensive development environment for web components, featuring:
|
|
8
8
|
|
|
9
|
+
- 🖥️ **`wcctools dev`** — One command bundles your catalogue with live reload and serves it in the wcctools shell
|
|
9
10
|
- 🎨 **Interactive Component Catalogue** — Live preview with customizable sidebar sections
|
|
10
11
|
- 🔧 **Real-time Property Editing** — Modify component props on the fly with auto-detected editors
|
|
11
12
|
- 🌓 **Theme Switching** — Test light/dark modes instantly
|
|
12
|
-
- 📱 **Responsive Viewport Testing** — Phone, phablet, tablet, and desktop views
|
|
13
|
+
- 📱 **Responsive Viewport Testing** — Phone, phablet, tablet, and desktop views in a frame of the exact width, so width media queries respond
|
|
13
14
|
- 🎬 **Screen Recording** — Record component demos with audio, trimming, and MP4/WebM export
|
|
14
15
|
- 🧪 **Advanced Demo Tools** — Post-render hooks for interactive testing
|
|
15
16
|
- 📂 **Section-based Organization** — Group components into custom sections with filtering and sorting
|
|
@@ -45,6 +46,16 @@ import { setupWccTools } from '@design.estate/wcctools';
|
|
|
45
46
|
|
|
46
47
|
A catalogue still on a 4.x or older release also follows the 5.0.0 changes: [`setupWccTools()` takes only the config object](#migration-from-setupwcctoolselements-pages) and [`DeesDemoWrapper` moved to `@design.estate/dees-element/demotools`](#-demo-tools).
|
|
47
48
|
|
|
49
|
+
### Migrating to 6.0.0
|
|
50
|
+
|
|
51
|
+
6.0.0 splits the catalogue into two documents. The **shell** (sidebar, properties panel, recorder) is prebuilt into the package and served by the new `wcctools dev` command; your catalogue bundle runs as the **preview** document in a same-origin iframe that is exactly as wide as the selected viewport. Your `html/index.ts` and `html/index.html` stay as they are.
|
|
52
|
+
|
|
53
|
+
1. Keep `@design.estate/wcctools` as a devDependency at `^6.0.0`.
|
|
54
|
+
2. Replace the `watch` script: `"watch": "wcctools dev"` (it reads the same `@git.zone/tswatch` configuration from `.smartconfig.json`, or uses tswatch's `element` preset).
|
|
55
|
+
3. Open the address `wcctools dev` prints (`http://localhost:<port>/wcctools/`).
|
|
56
|
+
|
|
57
|
+
Opened outside `wcctools dev` (for example from plain `tswatch`), the catalogue bundle shows a notice that names the command instead of the catalogue UI. `RecorderService`, `WccRecordButton` and `WccRecordingPanel` are no longer exported: recording lives in the shell.
|
|
58
|
+
|
|
48
59
|
## Quick Start
|
|
49
60
|
|
|
50
61
|
### 1. Create Your Component
|
|
@@ -98,9 +109,8 @@ export class MyButton extends DeesElement {
|
|
|
98
109
|
### 2. Set Up Your Catalogue
|
|
99
110
|
|
|
100
111
|
```typescript
|
|
101
|
-
//
|
|
112
|
+
// html/index.ts
|
|
102
113
|
import { setupWccTools } from '@design.estate/wcctools';
|
|
103
|
-
import { html } from 'lit';
|
|
104
114
|
|
|
105
115
|
// Import your components
|
|
106
116
|
import * as elements from './components/index.js';
|
|
@@ -133,19 +143,30 @@ setupWccTools({
|
|
|
133
143
|
|
|
134
144
|
### 3. Create an HTML Entry Point
|
|
135
145
|
|
|
146
|
+
`wcctools dev` bundles `html/index.ts` to `dist_watch/bundle.js` and serves `html/index.html` as the preview document:
|
|
147
|
+
|
|
136
148
|
```html
|
|
137
149
|
<!DOCTYPE html>
|
|
138
150
|
<html>
|
|
139
151
|
<head>
|
|
140
152
|
<title>Component Catalogue</title>
|
|
141
153
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
154
|
+
<script type="module" src="/bundle.js"></script>
|
|
142
155
|
</head>
|
|
143
156
|
<body style="margin: 0; padding: 0;">
|
|
144
|
-
<script type="module" src="./catalogue.js"></script>
|
|
145
157
|
</body>
|
|
146
158
|
</html>
|
|
147
159
|
```
|
|
148
160
|
|
|
161
|
+
### 4. Run the Catalogue
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
pnpm exec wcctools dev # port from the tswatch configuration, else 3002
|
|
165
|
+
pnpm exec wcctools dev --port 0 # any free port
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
It prints the shell's address, for example `wcctools dev: http://localhost:3002/wcctools/`. Saving a source file rebuilds the bundle and reloads only the preview frame; the shell keeps its state. `Ctrl+C` stops the server and the watchers.
|
|
169
|
+
|
|
149
170
|
## 📂 Sections Configuration
|
|
150
171
|
|
|
151
172
|
The sections-based API gives you full control over how components are organized in the sidebar.
|
|
@@ -258,7 +279,9 @@ Test your components across different screen sizes:
|
|
|
258
279
|
- **Tablet** — 1024px width
|
|
259
280
|
- **Desktop** — Available preview width
|
|
260
281
|
|
|
261
|
-
|
|
282
|
+
The preview document runs in an iframe of the selected width, so both width `@media` queries and `wccToolsViewport` container queries respond to it.
|
|
283
|
+
|
|
284
|
+
On browser viewports up to 600px wide, the catalogue sidebar moves above the preview and the property controls wrap into a compact bottom toolbar. Selecting phone, phablet, or tablet still gives the preview frame its exact target width; wider targets scroll inside the frame instead of widening the document.
|
|
262
285
|
|
|
263
286
|
### 🌓 Theme Support
|
|
264
287
|
|
|
@@ -371,7 +394,7 @@ public static demo = async () => {
|
|
|
371
394
|
|
|
372
395
|
### 🎯 Container Queries
|
|
373
396
|
|
|
374
|
-
Components can respond to their container size using the `wccToolsViewport` container:
|
|
397
|
+
Components can respond to their container size using the `wccToolsViewport` container, which wraps every rendered demo in the preview document:
|
|
375
398
|
|
|
376
399
|
```typescript
|
|
377
400
|
public static styles = [
|
|
@@ -442,13 +465,13 @@ The sidebar is an ARIA tree with a single tab stop (the selected entry, or the l
|
|
|
442
465
|
| `Enter` / `Space` | Open the entry (or toggle a header) |
|
|
443
466
|
| `ContextMenu` / `Shift+F10` | Open the entry's context menu (pin, show in group) at the entry; `↑` / `↓` / `Home` / `End` move, `Enter` / `Space` act, `Esc` / `Tab` close — focus returns to the entry |
|
|
444
467
|
|
|
445
|
-
The sidebar resize handle is a focusable separator: `←` / `→` resize by 10 px (`Shift` for 50 px), `Home` / `End` jump to the minimum / maximum width. Theme and viewport controls are toggle buttons (`aria-pressed`), and `Esc` leaves the native viewport.
|
|
468
|
+
The sidebar resize handle is a focusable separator: `←` / `→` resize by 10 px (`Shift` for 50 px), `Home` / `End` jump to the minimum / maximum width. Theme and viewport controls are toggle buttons (`aria-pressed`), and `Esc` leaves the native viewport, also while the focus is inside the preview.
|
|
446
469
|
|
|
447
470
|
Rendered demos keep their state while you search, pin, resize the sidebar, or switch theme or viewport; only selecting another entry or demo renders a new one.
|
|
448
471
|
|
|
449
472
|
## URL Routing
|
|
450
473
|
|
|
451
|
-
The
|
|
474
|
+
The shell uses URL routing for deep linking:
|
|
452
475
|
|
|
453
476
|
```
|
|
454
477
|
/wcctools-route/:sectionName/:itemName/:demoIndex/:viewport/:theme
|
|
@@ -459,11 +482,63 @@ Examples:
|
|
|
459
482
|
/wcctools-route/Pages/home/0/desktop/dark
|
|
460
483
|
```
|
|
461
484
|
|
|
485
|
+
The preview document alone renders one demo from its own route, for tools that capture a single element:
|
|
486
|
+
|
|
487
|
+
```
|
|
488
|
+
/wcctools-preview?section=Elements&item=my-button&demo=0&theme=dark
|
|
489
|
+
```
|
|
490
|
+
|
|
462
491
|
## API Reference
|
|
463
492
|
|
|
493
|
+
### `wcctools dev [--port <port>]`
|
|
494
|
+
|
|
495
|
+
Bundles and watches the catalogue as the project's `@git.zone/tswatch` configuration describes (or tswatch's `element` preset) and serves on one port:
|
|
496
|
+
|
|
497
|
+
| Path | Serves |
|
|
498
|
+
|------|--------|
|
|
499
|
+
| `/` | Redirects to `/wcctools/` |
|
|
500
|
+
| `/wcctools/`, `/wcctools-route/...` | The shell |
|
|
501
|
+
| `/wcctools/typedrequest` | The shell's typed API |
|
|
502
|
+
| every other path | The catalogue's serve directory (default `dist_watch/`), with live reload for the preview document |
|
|
503
|
+
|
|
504
|
+
`--port` overrides the configured port; `0` picks a free port.
|
|
505
|
+
|
|
506
|
+
Every `wcctools` command exits with code 2 on a usage error (an unknown command or option, a missing or invalid option value) and with 1 on any other failure.
|
|
507
|
+
|
|
508
|
+
### `wcctools check` and `wcctools fix`
|
|
509
|
+
|
|
510
|
+
Check a catalogue against the wcctools component standard ([docs/standard.md](https://code.foss.global/design.estate/wcctools/src/branch/main/docs/standard.md), a draft: rule ids, severities and defaults may still change until it is declared stable): file layout, naming, element registration, barrels, demos and the attribute surface of public properties, plus the theme, wording and component-index ratchets of the dees-catalog family. Both commands run in the repository root and read `.smartconfig.json`:
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
{
|
|
514
|
+
"@design.estate/wcctools": {
|
|
515
|
+
"standard": {
|
|
516
|
+
"profile": "base",
|
|
517
|
+
"tagPrefix": "my"
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
`profile` is `base` or `dees`; `tagPrefix` is the tag prefix without its hyphen. Exceptions go into `standard.allowlist` (rule id → `'<file>#<member>'` → `'<category>: <reason>'`), accepted error counts of an existing codebase into `standard.baselines` (rule id → file → count, only shrinking).
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
wcctools check # findings as text; exit 0 ok, 1 failing errors, 2 configuration or usage error
|
|
527
|
+
wcctools check --json # one IStandardReport on stdout
|
|
528
|
+
wcctools check --verbose # also info findings, covered findings and baseline slack
|
|
529
|
+
wcctools check --init-baseline # record the current error counts (only while no baselines exist)
|
|
530
|
+
wcctools check --update-baseline # lower the baselines to the current counts; refuses to raise any
|
|
531
|
+
|
|
532
|
+
wcctools fix --dry-run # show the moves and edits without writing
|
|
533
|
+
wcctools fix # apply every automatic fix
|
|
534
|
+
wcctools fix --rule props/reflect-primitive # also opt-in rules, one rule at a time
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
`wcctools fix` needs a clean work tree and never commits; it validates every path it would write before writing anything and refuses (exit code 2) a plan that leaves the repository root. It edits source text by syntax-node spans, so formatting and comments stay as they are; it moves files with `git mv` and rewrites the relative imports that pointed at them across the repository, and it checks again at the end. Review the result with `git diff` and commit it yourself.
|
|
538
|
+
|
|
464
539
|
### `setupWccTools(config)`
|
|
465
540
|
|
|
466
|
-
|
|
541
|
+
Sets up the catalogue's preview document with sections configuration. The shell reads a data-only description of the sections from it; element classes and template factories never leave the preview document.
|
|
467
542
|
|
|
468
543
|
```typescript
|
|
469
544
|
interface IWccSection {
|
|
@@ -496,26 +571,16 @@ The wrapper provides full DOM API access:
|
|
|
496
571
|
- `wrapper.querySelectorAll()` — Find multiple elements
|
|
497
572
|
- `wrapper.children` — Access child elements directly
|
|
498
573
|
|
|
499
|
-
###
|
|
574
|
+
### `@design.estate/wcctools/interfaces`
|
|
500
575
|
|
|
501
|
-
|
|
576
|
+
The contracts between the shell, the preview document and the dev server, for tools that drive a preview: the catalogue manifest (`IWccCatalogManifest`), the preview bridge the preview document installs as `window.wccPreview` (`IWccPreviewBridge`: render a demo, switch the theme, read and edit the rendered element's properties) and the dev server's typed requests, and the component standard's configuration and reports (`IStandardConfig`, `IStandardReport`, `IStandardFixResult`).
|
|
502
577
|
|
|
503
578
|
```typescript
|
|
504
|
-
import {
|
|
505
|
-
|
|
506
|
-
const recorder = new RecorderService({
|
|
507
|
-
onDurationUpdate: (duration) => console.log(`${duration}s`),
|
|
508
|
-
onRecordingComplete: (blob) => console.log('Recording done!', blob),
|
|
509
|
-
onAudioLevelUpdate: (level) => console.log(`Audio: ${level}%`),
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// Record (always captures as WebM internally)
|
|
513
|
-
await recorder.startRecording({ mode: 'viewport' });
|
|
514
|
-
// ... later
|
|
515
|
-
recorder.stopRecording();
|
|
579
|
+
import type { IWccPreviewBridge } from '@design.estate/wcctools/interfaces';
|
|
516
580
|
|
|
517
|
-
|
|
518
|
-
|
|
581
|
+
const bridge: IWccPreviewBridge = previewFrame.contentWindow.wccPreview;
|
|
582
|
+
await bridge.render({ sectionName: 'Elements', itemName: 'MyButton', demoIndex: 0 });
|
|
583
|
+
const properties = await bridge.getProperties();
|
|
519
584
|
```
|
|
520
585
|
|
|
521
586
|
## Project Structure
|
|
@@ -533,10 +598,10 @@ my-component-library/
|
|
|
533
598
|
│ ├── pages/ # Documentation pages
|
|
534
599
|
│ │ ├── home.ts
|
|
535
600
|
│ │ └── index.ts
|
|
536
|
-
│ └── catalogue.ts # WCC Tools setup
|
|
537
601
|
├── html/
|
|
538
|
-
│
|
|
539
|
-
└──
|
|
602
|
+
│ ├── index.ts # setupWccTools: the catalogue
|
|
603
|
+
│ └── index.html # the preview document
|
|
604
|
+
└── package.json # "watch": "wcctools dev"
|
|
540
605
|
```
|
|
541
606
|
|
|
542
607
|
## Browser Support
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autocreated commitinfo by @push.rocks/commitinfo
|
|
3
|
+
*/
|
|
4
|
+
export const commitinfo = {
|
|
5
|
+
name: '@design.estate/wcctools',
|
|
6
|
+
version: '6.1.0',
|
|
7
|
+
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The typed API the shell reaches on `/wcctools/typedrequest`. Later features register their
|
|
5
|
+
* handlers on `typedrouter`.
|
|
6
|
+
*/
|
|
7
|
+
export class WccDevApi {
|
|
8
|
+
public readonly typedrouter = new plugins.typedrequest.TypedRouter();
|
|
9
|
+
private serverInfo: plugins.interfaces.IWccDevServerInfo | null = null;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.typedrouter.addTypedHandler(
|
|
13
|
+
new plugins.typedrequest.TypedHandler<plugins.interfaces.IReq_GetDevServerInfo>('getDevServerInfo', async () => {
|
|
14
|
+
if (!this.serverInfo) {
|
|
15
|
+
throw new Error('The dev server has not started.');
|
|
16
|
+
}
|
|
17
|
+
return { ...this.serverInfo };
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public setServerInfo(infoArg: plugins.interfaces.IWccDevServerInfo) {
|
|
23
|
+
this.serverInfo = { ...infoArg };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import * as paths from './paths.js';
|
|
3
|
+
import { commitinfo } from './00_commitinfo_data.js';
|
|
4
|
+
import { WccDevApi } from './classes.devapi.js';
|
|
5
|
+
import { loadShellContent, shellPathPrefix } from './shellcontent.js';
|
|
6
|
+
|
|
7
|
+
export interface IWccDevServerOptions {
|
|
8
|
+
/** Port to listen on; overrides the tswatch configuration. 0 picks a free port. */
|
|
9
|
+
port?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IWccDevServerAddress {
|
|
13
|
+
port: number;
|
|
14
|
+
/** The shell's address. */
|
|
15
|
+
url: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** tswatch's default port and serve directory, used when the configuration names none. */
|
|
19
|
+
const defaultPort = 3002;
|
|
20
|
+
const defaultServeDir = './dist_watch/';
|
|
21
|
+
|
|
22
|
+
/** The development headers tswatch serves catalogs with: a cross-origin isolated document. */
|
|
23
|
+
const securityHeaders: plugins.typedserver.ISecurityHeaders = {
|
|
24
|
+
crossOriginOpenerPolicy: 'same-origin',
|
|
25
|
+
crossOriginEmbedderPolicy: 'require-corp',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const acceptAnyHostname = () => true;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* `wcctools dev`: bundles the catalog of the current project with tswatch, as its
|
|
32
|
+
* `@git.zone/tswatch` configuration (or the `element` preset) describes, and serves on one port:
|
|
33
|
+
*
|
|
34
|
+
* - the shell on `/wcctools/` (`/` redirects there) and its routes `/wcctools-route/...`,
|
|
35
|
+
* - the typed API on `/wcctools/typedrequest`,
|
|
36
|
+
* - the catalog's preview document and bundle from the tswatch serve directory on every other
|
|
37
|
+
* path, with live reload that reloads only the preview document.
|
|
38
|
+
*
|
|
39
|
+
* tswatch resolves its paths against the process's working directory, so the server serves the
|
|
40
|
+
* project the process runs in.
|
|
41
|
+
*/
|
|
42
|
+
export class WccDevServer {
|
|
43
|
+
/** The typed API on `/wcctools/typedrequest`; register further handlers before `start()`. */
|
|
44
|
+
public readonly api = new WccDevApi();
|
|
45
|
+
private tsWatch: plugins.tswatch.TsWatch | null = null;
|
|
46
|
+
private typedServer: plugins.typedserver.TypedServer | null = null;
|
|
47
|
+
private readonly smartExit = new plugins.smartexit.SmartExit({ silent: true });
|
|
48
|
+
|
|
49
|
+
constructor(private readonly options: IWccDevServerOptions = {}) {
|
|
50
|
+
this.smartExit.addCleanupFunction(() => this.stop());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public async start(): Promise<IWccDevServerAddress> {
|
|
54
|
+
if (this.typedServer) {
|
|
55
|
+
throw new Error('wcctools: the dev server is already started.');
|
|
56
|
+
}
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
const configHandler = new plugins.tswatch.ConfigHandler(cwd);
|
|
59
|
+
const config = configHandler.loadConfig() ?? configHandler.getPreset('element');
|
|
60
|
+
const serveDir = plugins.path.resolve(cwd, config.server?.serveDir ?? defaultServeDir);
|
|
61
|
+
const port = this.options.port ?? config.server?.port ?? defaultPort;
|
|
62
|
+
|
|
63
|
+
// The preview surface watches the serve directory, which must exist before the first bundle
|
|
64
|
+
await plugins.fs.mkdir(serveDir, { recursive: true });
|
|
65
|
+
this.api.setServerInfo({
|
|
66
|
+
wcctoolsVersion: commitinfo.version,
|
|
67
|
+
projectName: await this.readProjectName(cwd),
|
|
68
|
+
});
|
|
69
|
+
const typedServer = new plugins.typedserver.TypedServer({
|
|
70
|
+
cors: false,
|
|
71
|
+
port,
|
|
72
|
+
surfaces: [
|
|
73
|
+
await this.createShellSurface(),
|
|
74
|
+
this.createPreviewSurface(serveDir),
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
this.typedServer = typedServer;
|
|
78
|
+
|
|
79
|
+
// Bundling and watching stay tswatch's; its own server stays off
|
|
80
|
+
this.tsWatch = new plugins.tswatch.TsWatch({ ...config, server: { enabled: false } });
|
|
81
|
+
try {
|
|
82
|
+
await this.tsWatch.start();
|
|
83
|
+
await typedServer.start();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
await this.stop();
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
const boundPort = typedServer.listeningPort ?? port;
|
|
89
|
+
return { port: boundPort, url: `http://localhost:${boundPort}${shellPathPrefix}/` };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Stops the server, then tswatch's bundling and watching. */
|
|
93
|
+
public async stop(): Promise<void> {
|
|
94
|
+
const typedServer = this.typedServer;
|
|
95
|
+
const tsWatch = this.tsWatch;
|
|
96
|
+
this.typedServer = null;
|
|
97
|
+
this.tsWatch = null;
|
|
98
|
+
this.smartExit.deregister();
|
|
99
|
+
await typedServer?.stop();
|
|
100
|
+
await tsWatch?.stop();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The shell: the prebuilt shell bundle and the typed API.
|
|
105
|
+
*
|
|
106
|
+
* Seam for side-effecting APIs (assistant, capture, standards fixes): before any handler with
|
|
107
|
+
* side effects is registered here, add a `websocketAdmission`/`requestAdmission` that accepts
|
|
108
|
+
* only the shell's own origin, and keep surface mode's strict authority validation.
|
|
109
|
+
*/
|
|
110
|
+
private async createShellSurface(): Promise<plugins.typedserver.ITypedServerSurface> {
|
|
111
|
+
return {
|
|
112
|
+
name: 'shell',
|
|
113
|
+
match: {
|
|
114
|
+
hostnameResolver: acceptAnyHostname,
|
|
115
|
+
pathPrefixes: [shellPathPrefix, '/wcctools-route'],
|
|
116
|
+
},
|
|
117
|
+
bundledContent: await loadShellContent(paths.distShellDir),
|
|
118
|
+
spaFallback: true,
|
|
119
|
+
httpTypedRouter: this.api.typedrouter,
|
|
120
|
+
typedRequestPath: `${shellPathPrefix}/typedrequest`,
|
|
121
|
+
noCache: true,
|
|
122
|
+
securityHeaders,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The preview: the catalog's own document and bundle from the tswatch serve directory, on
|
|
128
|
+
* the root paths it was written for. Its live reload client is injected into its document
|
|
129
|
+
* only, so a rebuilt bundle reloads the preview frame and never the shell.
|
|
130
|
+
*/
|
|
131
|
+
private createPreviewSurface(serveDirArg: string): plugins.typedserver.ITypedServerSurface {
|
|
132
|
+
return {
|
|
133
|
+
name: 'preview',
|
|
134
|
+
match: {
|
|
135
|
+
hostnameResolver: acceptAnyHostname,
|
|
136
|
+
pathPrefixes: ['/'],
|
|
137
|
+
},
|
|
138
|
+
serveDir: serveDirArg,
|
|
139
|
+
spaFallback: true,
|
|
140
|
+
injectReload: true,
|
|
141
|
+
watch: true,
|
|
142
|
+
// Lets the reload client get pushed changes instead of polling
|
|
143
|
+
websocketTypedRouter: new plugins.typedrequest.TypedRouter(),
|
|
144
|
+
// As tswatch serves catalogs
|
|
145
|
+
cors: true,
|
|
146
|
+
httpHandler: async (contextArg) => {
|
|
147
|
+
if (contextArg.path === '/' && (contextArg.method === 'GET' || contextArg.method === 'HEAD')) {
|
|
148
|
+
return new Response(null, { status: 302, headers: { Location: `${shellPathPrefix}/` } });
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
},
|
|
152
|
+
noCache: true,
|
|
153
|
+
securityHeaders,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async readProjectName(cwdArg: string): Promise<string> {
|
|
158
|
+
const packageJson = JSON.parse(await plugins.fs.readFile(plugins.path.join(cwdArg, 'package.json'), 'utf8')) as { name?: string };
|
|
159
|
+
return packageJson.name ?? plugins.path.basename(cwdArg);
|
|
160
|
+
}
|
|
161
|
+
}
|
package/ts/cli.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { commitinfo } from './00_commitinfo_data.js';
|
|
3
|
+
import { WccDevServer } from './classes.devserver.js';
|
|
4
|
+
import { StandardConfigError, StandardUsageError, registerStandardCommands } from './standards/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Registers the wcctools commands on the given console.
|
|
8
|
+
*/
|
|
9
|
+
export const registerCommands = (out: plugins.smartconsole.SmartConsole): void => {
|
|
10
|
+
out.cli.configure({
|
|
11
|
+
name: 'wcctools',
|
|
12
|
+
version: commitinfo.version,
|
|
13
|
+
description: 'Develop a web component catalog in the wcctools shell',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
out.cli.command({
|
|
17
|
+
name: 'dev',
|
|
18
|
+
description: 'Bundle the catalog with live reload and serve it in the wcctools shell',
|
|
19
|
+
options: {
|
|
20
|
+
port: {
|
|
21
|
+
type: 'number',
|
|
22
|
+
description: 'Port to listen on (0 picks a free port); overrides the tswatch configuration',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}, async ({ options }) => {
|
|
26
|
+
const devServer = new WccDevServer({ port: options.port });
|
|
27
|
+
const address = await devServer.start();
|
|
28
|
+
await out.log(`wcctools dev: ${address.url}`);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
registerStandardCommands(out);
|
|
32
|
+
|
|
33
|
+
out.cli.default({}, async () => {
|
|
34
|
+
await out.log(out.cli.renderHelp());
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const runCli = async () => {
|
|
39
|
+
const out = new plugins.smartconsole.SmartConsole();
|
|
40
|
+
try {
|
|
41
|
+
registerCommands(out);
|
|
42
|
+
await out.cli.run();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
await out.error(error instanceof Error ? error.message : String(error));
|
|
45
|
+
// Configuration and usage errors exit with 2, every other failure with 1.
|
|
46
|
+
const usageError = error instanceof plugins.smartconsole.CliUsageError
|
|
47
|
+
|| error instanceof StandardConfigError
|
|
48
|
+
|| error instanceof StandardUsageError;
|
|
49
|
+
process.exitCode = usageError ? 2 : 1;
|
|
50
|
+
} finally {
|
|
51
|
+
await out.dispose();
|
|
52
|
+
}
|
|
53
|
+
};
|
package/ts/index.ts
ADDED
package/ts/paths.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
/** The root of the installed wcctools package. */
|
|
4
|
+
export const packageDir = plugins.path.resolve(plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url)), '..');
|
|
5
|
+
|
|
6
|
+
/** The prebuilt shell bundle the package ships. */
|
|
7
|
+
export const distShellDir = plugins.path.join(packageDir, 'dist_shell');
|
package/ts/plugins.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// node native scope
|
|
2
|
+
import * as childProcess from 'node:child_process';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import * as fsSync from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import * as url from 'node:url';
|
|
7
|
+
|
|
8
|
+
export { childProcess, fs, fsSync, path, url };
|
|
9
|
+
|
|
10
|
+
// @api.global scope
|
|
11
|
+
import * as typedrequest from '@api.global/typedrequest';
|
|
12
|
+
import * as typedserver from '@api.global/typedserver';
|
|
13
|
+
|
|
14
|
+
export { typedrequest, typedserver };
|
|
15
|
+
|
|
16
|
+
// @git.zone scope
|
|
17
|
+
import * as tswatch from '@git.zone/tswatch';
|
|
18
|
+
|
|
19
|
+
export { tswatch };
|
|
20
|
+
|
|
21
|
+
// @push.rocks scope
|
|
22
|
+
import * as smartconfig from '@push.rocks/smartconfig';
|
|
23
|
+
import * as smartconsole from '@push.rocks/smartconsole';
|
|
24
|
+
import * as smartexit from '@push.rocks/smartexit';
|
|
25
|
+
|
|
26
|
+
export { smartconfig, smartconsole, smartexit };
|
|
27
|
+
|
|
28
|
+
// third party scope
|
|
29
|
+
import typescript from 'typescript';
|
|
30
|
+
|
|
31
|
+
export { typescript };
|
|
32
|
+
|
|
33
|
+
// project scope
|
|
34
|
+
import * as interfaces from '../ts_interfaces/index.js';
|
|
35
|
+
import * as shared from '../ts_shared/index.js';
|
|
36
|
+
|
|
37
|
+
export { interfaces, shared };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
/** The path below which the shell surface serves the shell bundle. */
|
|
4
|
+
export const shellPathPrefix = '/wcctools';
|
|
5
|
+
|
|
6
|
+
const collectFiles = async (dirArg: string, relativeDirArg = ''): Promise<string[]> => {
|
|
7
|
+
const entries = await plugins.fs.readdir(plugins.path.join(dirArg, relativeDirArg), { withFileTypes: true });
|
|
8
|
+
const files: string[] = [];
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const relativePath = relativeDirArg ? `${relativeDirArg}/${entry.name}` : entry.name;
|
|
11
|
+
if (entry.isDirectory()) {
|
|
12
|
+
files.push(...await collectFiles(dirArg, relativePath));
|
|
13
|
+
} else if (entry.isFile()) {
|
|
14
|
+
files.push(relativePath);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return files;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The prebuilt shell bundle as in-memory content of the shell surface. A surface serves files
|
|
22
|
+
* by their full request path, so every file is keyed below `wcctools/`; the shell page is
|
|
23
|
+
* also keyed as `index.html`, which the surface's SPA fallback answers `/wcctools-route/...` with.
|
|
24
|
+
*/
|
|
25
|
+
export const loadShellContent = async (distShellDirArg: string): Promise<plugins.typedserver.IBundledContentItem[]> => {
|
|
26
|
+
const indexPath = plugins.path.join(distShellDirArg, 'index.html');
|
|
27
|
+
try {
|
|
28
|
+
await plugins.fs.access(indexPath);
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error(`wcctools: the shell bundle is missing (${indexPath}); build the package with "pnpm build".`);
|
|
31
|
+
}
|
|
32
|
+
const prefix = shellPathPrefix.slice(1);
|
|
33
|
+
const content: plugins.typedserver.IBundledContentItem[] = [];
|
|
34
|
+
for (const relativePath of await collectFiles(distShellDirArg)) {
|
|
35
|
+
const contentBase64 = (await plugins.fs.readFile(plugins.path.join(distShellDirArg, relativePath))).toString('base64');
|
|
36
|
+
content.push({ path: `${prefix}/${relativePath}`, contentBase64 });
|
|
37
|
+
if (relativePath === 'index.html') {
|
|
38
|
+
content.push({ path: 'index.html', contentBase64 });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return content;
|
|
42
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { allowlistStaleRuleId } from './rules/index.js';
|
|
3
|
+
|
|
4
|
+
type IStandardConfig = plugins.interfaces.IStandardConfig;
|
|
5
|
+
type IStandardReport = plugins.interfaces.IStandardReport;
|
|
6
|
+
type TBaselines = NonNullable<IStandardConfig['baselines']>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Baseline arithmetic of the ratchet. Pure functions: persisting the result goes through
|
|
10
|
+
* IStandardConfigWriter (see config.ts).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Per rule and file, the error findings a baseline would have to accept (allowlisted findings excluded). */
|
|
14
|
+
const errorCounts = (reportArg: IStandardReport): TBaselines => {
|
|
15
|
+
const counts: TBaselines = {};
|
|
16
|
+
for (const finding of reportArg.findings) {
|
|
17
|
+
if (finding.severity !== 'error' || finding.covered === 'allowlist' || finding.ruleId === allowlistStaleRuleId) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
counts[finding.ruleId] ??= {};
|
|
21
|
+
counts[finding.ruleId][finding.file] = (counts[finding.ruleId][finding.file] ?? 0) + 1;
|
|
22
|
+
}
|
|
23
|
+
return counts;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const sortBaselines = (baselinesArg: TBaselines): TBaselines => {
|
|
27
|
+
const sorted: TBaselines = {};
|
|
28
|
+
for (const ruleId of Object.keys(baselinesArg).sort()) {
|
|
29
|
+
const files = Object.keys(baselinesArg[ruleId]).sort();
|
|
30
|
+
if (files.length > 0) {
|
|
31
|
+
sorted[ruleId] = Object.fromEntries(files.map((fileArg) => [fileArg, baselinesArg[ruleId][fileArg]]));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return sorted;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** `--init-baseline`: the current error counts of every rule. Only valid while the configuration has no baselines. */
|
|
38
|
+
export const initialBaselines = (reportArg: IStandardReport): TBaselines => sortBaselines(errorCounts(reportArg));
|
|
39
|
+
|
|
40
|
+
export interface IBaselineRaise {
|
|
41
|
+
ruleId: string;
|
|
42
|
+
file: string;
|
|
43
|
+
baseline: number;
|
|
44
|
+
current: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `--update-baseline`: lowers every count to the current one and removes entries that reach 0.
|
|
49
|
+
* `raised` lists every file whose count would have to grow, including files without an entry;
|
|
50
|
+
* when it is not empty the update is refused.
|
|
51
|
+
*/
|
|
52
|
+
export const updatedBaselines = (reportArg: IStandardReport, baselinesArg: TBaselines): { baselines: TBaselines; raised: IBaselineRaise[] } => {
|
|
53
|
+
const current = errorCounts(reportArg);
|
|
54
|
+
const raised: IBaselineRaise[] = [];
|
|
55
|
+
const baselines: TBaselines = {};
|
|
56
|
+
for (const [ruleId, counts] of Object.entries(baselinesArg)) {
|
|
57
|
+
baselines[ruleId] = {};
|
|
58
|
+
for (const [file, baseline] of Object.entries(counts)) {
|
|
59
|
+
const count = current[ruleId]?.[file] ?? 0;
|
|
60
|
+
if (count > baseline) {
|
|
61
|
+
raised.push({ ruleId, file, baseline, current: count });
|
|
62
|
+
} else if (count > 0) {
|
|
63
|
+
baselines[ruleId][file] = count;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const [ruleId, counts] of Object.entries(current)) {
|
|
68
|
+
for (const [file, count] of Object.entries(counts)) {
|
|
69
|
+
if (baselinesArg[ruleId]?.[file] === undefined) {
|
|
70
|
+
raised.push({ ruleId, file, baseline: 0, current: count });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { baselines: sortBaselines(baselines), raised };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** The configuration with allowlist and baseline file paths moved along with `wcctools fix` moves. */
|
|
78
|
+
export const rekeyStandardConfig = (configArg: IStandardConfig, movesArg: readonly plugins.interfaces.IStandardFileMove[]): { config: IStandardConfig; changed: string[] } => {
|
|
79
|
+
const moved = new Map(movesArg.map((moveArg) => [moveArg.from, moveArg.to]));
|
|
80
|
+
const changed: string[] = [];
|
|
81
|
+
const rekey = (keyArg: string): string => {
|
|
82
|
+
const [file, ...member] = keyArg.split('#');
|
|
83
|
+
const target = moved.get(file);
|
|
84
|
+
if (!target) {
|
|
85
|
+
return keyArg;
|
|
86
|
+
}
|
|
87
|
+
changed.push(keyArg);
|
|
88
|
+
return [target, ...member].join('#');
|
|
89
|
+
};
|
|
90
|
+
const config: IStandardConfig = { ...configArg };
|
|
91
|
+
if (configArg.allowlist) {
|
|
92
|
+
config.allowlist = Object.fromEntries(Object.entries(configArg.allowlist).map(([ruleId, entries]) =>
|
|
93
|
+
[ruleId, Object.fromEntries(Object.entries(entries).map(([key, value]) => [rekey(key), value]))]));
|
|
94
|
+
}
|
|
95
|
+
if (configArg.baselines) {
|
|
96
|
+
config.baselines = Object.fromEntries(Object.entries(configArg.baselines).map(([ruleId, counts]) =>
|
|
97
|
+
[ruleId, Object.fromEntries(Object.entries(counts).map(([file, count]) => [rekey(file), count]))]));
|
|
98
|
+
}
|
|
99
|
+
return { config, changed };
|
|
100
|
+
};
|