@design.estate/wcctools 6.1.0 → 7.0.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/changelog.md +41 -0
- package/dist_shell/bundle.js +405 -368
- package/dist_shell/bundle.js.map +1 -1
- package/dist_shell/bundle.js.third-party-notices.json +52 -0
- package/dist_shell/bundle.js.third-party-notices.txt +107 -1
- package/dist_shell/index.html +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/capture/classes.capturebrowser.d.ts +65 -0
- package/dist_ts/capture/classes.capturebrowser.js +251 -0
- package/dist_ts/capture/classes.captureservice.d.ts +39 -0
- package/dist_ts/capture/classes.captureservice.js +119 -0
- package/dist_ts/capture/classes.screencast.d.ts +47 -0
- package/dist_ts/capture/classes.screencast.js +127 -0
- package/dist_ts/capture/errors.d.ts +14 -0
- package/dist_ts/capture/errors.js +23 -0
- package/dist_ts/capture/images.d.ts +3 -0
- package/dist_ts/capture/images.js +58 -0
- package/dist_ts/capture/index.d.ts +4 -0
- package/dist_ts/capture/index.js +5 -0
- package/dist_ts/capture/interaction.d.ts +11 -0
- package/dist_ts/capture/interaction.js +138 -0
- package/dist_ts/capture/navigationguard.d.ts +44 -0
- package/dist_ts/capture/navigationguard.js +129 -0
- package/dist_ts/capture/previewpage.d.ts +29 -0
- package/dist_ts/capture/previewpage.js +96 -0
- package/dist_ts/capture/request.d.ts +69 -0
- package/dist_ts/capture/request.js +239 -0
- package/dist_ts/classes.bundlestatus.d.ts +36 -0
- package/dist_ts/classes.bundlestatus.js +110 -0
- package/dist_ts/classes.devapi.d.ts +3 -0
- package/dist_ts/classes.devapi.js +8 -1
- package/dist_ts/classes.devserver.d.ts +65 -14
- package/dist_ts/classes.devserver.js +157 -52
- package/dist_ts/classes.hostpolicy.d.ts +63 -0
- package/dist_ts/classes.hostpolicy.js +171 -0
- package/dist_ts/cli.js +28 -3
- package/dist_ts/cli.screenshot.d.ts +6 -0
- package/dist_ts/cli.screenshot.js +89 -0
- package/dist_ts/index.d.ts +0 -2
- package/dist_ts/index.js +1 -3
- package/dist_ts/plugins.d.ts +6 -2
- package/dist_ts/plugins.js +8 -3
- package/dist_ts_interfaces/capture.d.ts +151 -0
- package/dist_ts_interfaces/capture.js +2 -0
- package/dist_ts_interfaces/index.d.ts +1 -0
- package/dist_ts_interfaces/index.js +2 -1
- package/dist_ts_interfaces/requests.d.ts +59 -0
- package/dist_ts_shell/bundlestatus.d.ts +15 -0
- package/dist_ts_shell/bundlestatus.js +64 -0
- package/dist_ts_shell/elements/wcc-contextmenu.d.ts +3 -0
- package/dist_ts_shell/elements/wcc-contextmenu.js +18 -3
- package/dist_ts_shell/elements/wcc-preview-frame.d.ts +6 -0
- package/dist_ts_shell/elements/wcc-preview-frame.js +50 -2
- package/dist_ts_shell/elements/wcc-recording-panel.d.ts +9 -0
- package/dist_ts_shell/elements/wcc-recording-panel.js +12 -1
- package/dist_ts_shell/elements/wcc-shell.d.ts +15 -0
- package/dist_ts_shell/elements/wcc-shell.js +45 -3
- package/dist_ts_shell/plugins.d.ts +2 -1
- package/dist_ts_shell/plugins.js +3 -2
- package/dist_ts_shell/services/framesampler.service.d.ts +23 -0
- package/dist_ts_shell/services/framesampler.service.js +101 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +6 -2
- package/readme.md +108 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/capture/classes.capturebrowser.ts +290 -0
- package/ts/capture/classes.captureservice.ts +152 -0
- package/ts/capture/classes.screencast.ts +152 -0
- package/ts/capture/errors.ts +25 -0
- package/ts/capture/images.ts +61 -0
- package/ts/capture/index.ts +4 -0
- package/ts/capture/interaction.ts +146 -0
- package/ts/capture/navigationguard.ts +147 -0
- package/ts/capture/previewpage.ts +130 -0
- package/ts/capture/request.ts +304 -0
- package/ts/classes.bundlestatus.ts +123 -0
- package/ts/classes.devapi.ts +9 -0
- package/ts/classes.devserver.ts +172 -50
- package/ts/classes.hostpolicy.ts +201 -0
- package/ts/cli.screenshot.ts +95 -0
- package/ts/cli.ts +28 -2
- package/ts/index.ts +0 -2
- package/ts/plugins.ts +9 -2
- package/ts_interfaces/bridge.ts +95 -0
- package/ts_interfaces/capture.ts +144 -0
- package/ts_interfaces/catalog.ts +37 -0
- package/ts_interfaces/index.ts +5 -0
- package/ts_interfaces/plugins.ts +3 -0
- package/ts_interfaces/requests.ts +104 -0
- package/ts_interfaces/standard.ts +103 -0
- package/ts_shared/index.ts +1 -0
- package/ts_shared/plugins.ts +3 -0
- package/ts_shared/previewroute.ts +58 -0
- package/ts_shell/bundlestatus.ts +68 -0
- package/ts_shell/elements/wcc-contextmenu.ts +306 -0
- package/ts_shell/elements/wcc-preview-frame.ts +167 -0
- package/ts_shell/elements/wcc-properties.ts +998 -0
- package/ts_shell/elements/wcc-record-button.ts +108 -0
- package/ts_shell/elements/wcc-recording-panel.ts +1017 -0
- package/ts_shell/elements/wcc-shell.ts +485 -0
- package/ts_shell/elements/wcc-sidebar.ts +1419 -0
- package/ts_shell/index.html +21 -0
- package/ts_shell/index.ts +4 -0
- package/ts_shell/plugins.ts +12 -0
- package/ts_shell/previewconnection.ts +103 -0
- package/ts_shell/services/framesampler.service.ts +112 -0
- package/ts_shell/services/recorder.service.ts +451 -0
- package/ts_shell/types/dom-mediacapture-stub/index.d.ts +12 -0
- package/ts_shell/types/dom-mediacapture-stub/package.json +6 -0
- package/ts_shell/types/dom-webcodecs-stub/index.d.ts +2 -0
- package/ts_shell/types/dom-webcodecs-stub/package.json +6 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
package/ts/plugins.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import * as childProcess from 'node:child_process';
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
4
|
import * as fsSync from 'node:fs';
|
|
5
|
+
import * as os from 'node:os';
|
|
5
6
|
import * as path from 'node:path';
|
|
6
7
|
import * as url from 'node:url';
|
|
7
8
|
|
|
8
|
-
export { childProcess, fs, fsSync, path, url };
|
|
9
|
+
export { childProcess, fs, fsSync, os, path, url };
|
|
9
10
|
|
|
10
11
|
// @api.global scope
|
|
11
12
|
import * as typedrequest from '@api.global/typedrequest';
|
|
@@ -13,17 +14,23 @@ import * as typedserver from '@api.global/typedserver';
|
|
|
13
14
|
|
|
14
15
|
export { typedrequest, typedserver };
|
|
15
16
|
|
|
17
|
+
// @design.estate scope
|
|
18
|
+
import * as deesDomtools from '@design.estate/dees-domtools';
|
|
19
|
+
|
|
20
|
+
export { deesDomtools };
|
|
21
|
+
|
|
16
22
|
// @git.zone scope
|
|
17
23
|
import * as tswatch from '@git.zone/tswatch';
|
|
18
24
|
|
|
19
25
|
export { tswatch };
|
|
20
26
|
|
|
21
27
|
// @push.rocks scope
|
|
28
|
+
import * as smartbrowser from '@push.rocks/smartbrowser/automation';
|
|
22
29
|
import * as smartconfig from '@push.rocks/smartconfig';
|
|
23
30
|
import * as smartconsole from '@push.rocks/smartconsole';
|
|
24
31
|
import * as smartexit from '@push.rocks/smartexit';
|
|
25
32
|
|
|
26
|
-
export { smartconfig, smartconsole, smartexit };
|
|
33
|
+
export { smartbrowser, smartconfig, smartconsole, smartexit };
|
|
27
34
|
|
|
28
35
|
// third party scope
|
|
29
36
|
import typescript from 'typescript';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { IWccCatalogManifest } from './catalog.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The bridge between the shell and the preview document. The preview runtime installs it as
|
|
5
|
+
* `window.wccPreview` in the preview document; the shell reaches it through the same-origin
|
|
6
|
+
* iframe. Every argument and result is plain, structured-cloneable data: values never carry
|
|
7
|
+
* objects of the other document's realm.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type TWccTheme = 'dark' | 'bright';
|
|
11
|
+
|
|
12
|
+
/** One demo (or page) of the catalog. */
|
|
13
|
+
export interface IWccSelection {
|
|
14
|
+
sectionName: string;
|
|
15
|
+
itemName: string;
|
|
16
|
+
/** 0-based; always 0 for a page. */
|
|
17
|
+
demoIndex: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** What the preview shows instead of a demo. */
|
|
21
|
+
export interface IWccEmptyState {
|
|
22
|
+
title: string;
|
|
23
|
+
detail: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type TWccEmptyReason =
|
|
27
|
+
| 'nothing-selected'
|
|
28
|
+
| 'no-demo'
|
|
29
|
+
| 'ignored'
|
|
30
|
+
| 'missing'
|
|
31
|
+
| 'no-such-demo'
|
|
32
|
+
| 'failed';
|
|
33
|
+
|
|
34
|
+
export type TWccRenderOutcome =
|
|
35
|
+
| { kind: 'rendered'; type: 'element' | 'page' }
|
|
36
|
+
| { kind: 'empty'; reason: TWccEmptyReason; emptyState: IWccEmptyState };
|
|
37
|
+
|
|
38
|
+
export type TWccPropertyKind = 'String' | 'Number' | 'Boolean' | 'Enum' | 'Object' | 'Array';
|
|
39
|
+
|
|
40
|
+
/** A scalar property value the properties panel can show and set directly. */
|
|
41
|
+
export type TWccPropertyScalar = string | number | boolean | null;
|
|
42
|
+
|
|
43
|
+
export interface IWccPropertyDescriptor {
|
|
44
|
+
name: string;
|
|
45
|
+
kind: TWccPropertyKind;
|
|
46
|
+
/** The values of an `Enum` property. */
|
|
47
|
+
enumValues?: string[];
|
|
48
|
+
/** The current value of a String, Number, Boolean or Enum property. */
|
|
49
|
+
value?: TWccPropertyScalar;
|
|
50
|
+
/** The current value of an Object or Array property, as formatted JSON. */
|
|
51
|
+
json?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type TWccPropertyListing =
|
|
55
|
+
| { kind: 'properties'; elementName: string; properties: IWccPropertyDescriptor[] }
|
|
56
|
+
| { kind: 'unavailable'; reason: string };
|
|
57
|
+
|
|
58
|
+
export type TWccPropertyEditResult = { ok: true } | { ok: false; error: string };
|
|
59
|
+
|
|
60
|
+
export interface IWccRenderedEvent {
|
|
61
|
+
type: 'rendered';
|
|
62
|
+
selection: IWccSelection | null;
|
|
63
|
+
outcome: TWccRenderOutcome;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type TWccPreviewEvent = IWccRenderedEvent;
|
|
67
|
+
|
|
68
|
+
export interface IWccPreviewBridge {
|
|
69
|
+
getManifest(): IWccCatalogManifest;
|
|
70
|
+
/** The selection rendered last, or null before the first render. */
|
|
71
|
+
getSelection(): IWccSelection | null;
|
|
72
|
+
getTheme(): TWccTheme;
|
|
73
|
+
/** Render a demo or page into the preview document, without reloading it. */
|
|
74
|
+
render(selectionArg: IWccSelection): Promise<TWccRenderOutcome>;
|
|
75
|
+
setTheme(themeArg: TWccTheme): Promise<void>;
|
|
76
|
+
/** The editable properties of the selected element's rendered instance, once it has rendered. */
|
|
77
|
+
getProperties(): Promise<TWccPropertyListing>;
|
|
78
|
+
setProperty(nameArg: string, valueArg: TWccPropertyScalar): TWccPropertyEditResult;
|
|
79
|
+
/** Parse JSON in the preview document and assign it to an Object or Array property. */
|
|
80
|
+
setPropertyJson(nameArg: string, jsonArg: string): TWccPropertyEditResult;
|
|
81
|
+
/** The rendered instance of the selected element, for same-origin tooling. */
|
|
82
|
+
findRenderedInstance(): HTMLElement | null;
|
|
83
|
+
subscribe(listenerArg: (eventArg: TWccPreviewEvent) => void): () => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The message a preview document posts to its parent once its bridge is installed. */
|
|
87
|
+
export interface IWccPreviewReadyMessage {
|
|
88
|
+
type: 'wcctools-preview-ready';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
declare global {
|
|
92
|
+
interface Window {
|
|
93
|
+
wccPreview?: IWccPreviewBridge;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { IWccSelection, TWccTheme } from './bridge.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Captures of the catalog's demos: screenshots and interaction frames the `wcctools dev` server
|
|
5
|
+
* takes in a headless browser, and frames the shell samples from a recording.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The named capture widths: the dees-domtools breakpoints. */
|
|
9
|
+
export type TWccCaptureViewportPreset = 'phone' | 'phablet' | 'tablet' | 'desktop';
|
|
10
|
+
|
|
11
|
+
/** A named capture width, or a width in CSS pixels. */
|
|
12
|
+
export type TWccCaptureViewport = TWccCaptureViewportPreset | number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What a capture shows:
|
|
16
|
+
* - `element`: the rendered instance of the selected element (the first one its demo renders),
|
|
17
|
+
* - `viewport`: the visible preview at the capture's width and height,
|
|
18
|
+
* - `fullpage`: the whole preview document.
|
|
19
|
+
*/
|
|
20
|
+
export type TWccCaptureFraming = 'element' | 'viewport' | 'fullpage';
|
|
21
|
+
|
|
22
|
+
export type TWccImageMimeType = 'image/png' | 'image/jpeg';
|
|
23
|
+
|
|
24
|
+
/** An encoded image. */
|
|
25
|
+
export interface IWccImage {
|
|
26
|
+
mimeType: TWccImageMimeType;
|
|
27
|
+
/** Width in device pixels. */
|
|
28
|
+
width: number;
|
|
29
|
+
/** Height in device pixels. */
|
|
30
|
+
height: number;
|
|
31
|
+
dataBase64: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A frame of a recording or of an observed interaction. */
|
|
35
|
+
export interface IWccTimedFrame extends IWccImage {
|
|
36
|
+
/** Milliseconds since the start of the recording or observation. */
|
|
37
|
+
timestampMs: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The catalog entry a capture renders. `itemName` is an entry's name or its element's tag;
|
|
42
|
+
* without `sectionName` it must name exactly one entry across all sections.
|
|
43
|
+
*/
|
|
44
|
+
export interface IWccCaptureSubject {
|
|
45
|
+
sectionName?: string;
|
|
46
|
+
itemName: string;
|
|
47
|
+
/** 0-based demo index; default 0. */
|
|
48
|
+
demoIndex?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** How the headless preview is set up for a capture. */
|
|
52
|
+
export interface IWccCaptureView {
|
|
53
|
+
/** Default `desktop`. */
|
|
54
|
+
viewport?: TWccCaptureViewport;
|
|
55
|
+
/** Height of the preview window in CSS pixels; default 800. */
|
|
56
|
+
height?: number;
|
|
57
|
+
/** Default `dark`. */
|
|
58
|
+
theme?: TWccTheme;
|
|
59
|
+
/** Device pixel ratio, 1 or 2; default 1. */
|
|
60
|
+
scale?: 1 | 2;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface IWccScreenshotRequest extends IWccCaptureView {
|
|
64
|
+
subject: IWccCaptureSubject;
|
|
65
|
+
/** Default `element` for element demos and `fullpage` for pages. */
|
|
66
|
+
framing?: TWccCaptureFraming;
|
|
67
|
+
/** Default `png`. */
|
|
68
|
+
format?: 'png' | 'jpeg';
|
|
69
|
+
/** JPEG quality from 1 to 100; default 80. */
|
|
70
|
+
quality?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface IWccScreenshotResult {
|
|
74
|
+
/** The demo the subject resolved to. */
|
|
75
|
+
selection: IWccSelection;
|
|
76
|
+
framing: TWccCaptureFraming;
|
|
77
|
+
image: IWccImage;
|
|
78
|
+
/** True when a full-page capture was cut at the maximum height. */
|
|
79
|
+
truncated: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* One step of a scripted interaction. A selector is a CSS selector matched in the preview
|
|
84
|
+
* document and in every open shadow root below it; the first match in document order is used,
|
|
85
|
+
* once it is visible and enabled.
|
|
86
|
+
*/
|
|
87
|
+
export type TWccInteractionStep =
|
|
88
|
+
| { action: 'click'; selector: string }
|
|
89
|
+
| { action: 'hover'; selector: string }
|
|
90
|
+
/** Replaces the value of an input, textarea, select or contenteditable element. */
|
|
91
|
+
| { action: 'fill'; selector: string; text: string }
|
|
92
|
+
/** Presses a key (`Enter`, `Tab`, `a`, …), on the matched element when a selector is given. */
|
|
93
|
+
| { action: 'press'; key: string; selector?: string }
|
|
94
|
+
/** Waits until a match is visible, or until none is (`hidden`). */
|
|
95
|
+
| { action: 'waitFor'; selector: string; state?: 'visible' | 'hidden' }
|
|
96
|
+
/** Observes for a fixed time, for example an animation. */
|
|
97
|
+
| { action: 'wait'; ms: number };
|
|
98
|
+
|
|
99
|
+
export interface IWccInteractionRequest extends IWccCaptureView {
|
|
100
|
+
subject: IWccCaptureSubject;
|
|
101
|
+
steps: TWccInteractionStep[];
|
|
102
|
+
/** How long to keep observing after the last step, in milliseconds; default 500. */
|
|
103
|
+
observeMs?: number;
|
|
104
|
+
/** The most frames to return, spread over the observation; default 12. */
|
|
105
|
+
maxFrames?: number;
|
|
106
|
+
/** JPEG quality of the frames from 1 to 100; default 70. */
|
|
107
|
+
frameQuality?: number;
|
|
108
|
+
/** Also take a screenshot once the steps ran, framed as a screenshot request would be. */
|
|
109
|
+
finalScreenshot?: { framing?: TWccCaptureFraming; format?: 'png' | 'jpeg'; quality?: number };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface IWccInteractionStepResult {
|
|
113
|
+
index: number;
|
|
114
|
+
ok: boolean;
|
|
115
|
+
/** Why the step failed; the steps after a failed one do not run. */
|
|
116
|
+
error?: string;
|
|
117
|
+
/** Milliseconds since the start of the observation. */
|
|
118
|
+
startMs: number;
|
|
119
|
+
endMs: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface IWccInteractionResult {
|
|
123
|
+
selection: IWccSelection;
|
|
124
|
+
/** Viewport frames in time order, timestamped from the start of the observation. */
|
|
125
|
+
frames: IWccTimedFrame[];
|
|
126
|
+
steps: IWccInteractionStepResult[];
|
|
127
|
+
/** True when every step ran. */
|
|
128
|
+
completed: boolean;
|
|
129
|
+
finalImage?: IWccImage;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** How frames are sampled from a recording. */
|
|
133
|
+
export interface IWccFrameSamplingOptions {
|
|
134
|
+
/** Number of frames, spread evenly over the range; default 8, at most 32. */
|
|
135
|
+
count?: number;
|
|
136
|
+
/** Start of the range in seconds; default the start of the recording. */
|
|
137
|
+
startS?: number;
|
|
138
|
+
/** End of the range in seconds; default the end of the recording. */
|
|
139
|
+
endS?: number;
|
|
140
|
+
/** Frames wider than this are scaled down; default 960. */
|
|
141
|
+
maxWidth?: number;
|
|
142
|
+
/** JPEG quality from 0 to 1; default 0.8. */
|
|
143
|
+
quality?: number;
|
|
144
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data-only description of a catalog that the preview runtime hands to the shell.
|
|
3
|
+
* Element classes and template factories stay in the preview document.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** How the entries of a section are rendered: element demos or page factories. */
|
|
7
|
+
export type TWccSectionType = 'elements' | 'pages';
|
|
8
|
+
|
|
9
|
+
/** A navigable catalog entry. */
|
|
10
|
+
export interface IWccManifestEntry {
|
|
11
|
+
name: string;
|
|
12
|
+
/** The registered tag of an element; null for pages and unregistered element classes. */
|
|
13
|
+
tagName: string | null;
|
|
14
|
+
/** Number of demos (always 1 for a page). */
|
|
15
|
+
demoCount: number;
|
|
16
|
+
demoGroups: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A registered element without a usable demo; listed, never navigable. */
|
|
20
|
+
export interface IWccManifestEntryWithoutDemo {
|
|
21
|
+
name: string;
|
|
22
|
+
tagName: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IWccManifestSection {
|
|
26
|
+
name: string;
|
|
27
|
+
type: TWccSectionType;
|
|
28
|
+
icon?: string;
|
|
29
|
+
collapsed?: boolean;
|
|
30
|
+
/** Classified, filtered and sorted as the section configures it. */
|
|
31
|
+
entries: IWccManifestEntry[];
|
|
32
|
+
entriesWithoutDemo: IWccManifestEntryWithoutDemo[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface IWccCatalogManifest {
|
|
36
|
+
sections: IWccManifestSection[];
|
|
37
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import type {
|
|
3
|
+
IWccInteractionRequest,
|
|
4
|
+
IWccInteractionResult,
|
|
5
|
+
IWccScreenshotRequest,
|
|
6
|
+
IWccScreenshotResult,
|
|
7
|
+
} from './capture.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Typed requests the shell sends to the `wcctools dev` server on `/wcctools/typedrequest`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface IWccDevServerInfo {
|
|
14
|
+
/** Version of the wcctools package that serves the shell. */
|
|
15
|
+
wcctoolsVersion: string;
|
|
16
|
+
/** Name of the served project's package. */
|
|
17
|
+
projectName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IReq_GetDevServerInfo
|
|
21
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
22
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
23
|
+
IReq_GetDevServerInfo
|
|
24
|
+
> {
|
|
25
|
+
method: 'getDevServerInfo';
|
|
26
|
+
request: {};
|
|
27
|
+
response: IWccDevServerInfo;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The state of a catalog bundle: its latest run started, finished or failed. */
|
|
31
|
+
export type TWccBundleState = 'started' | 'finished' | 'failed';
|
|
32
|
+
|
|
33
|
+
/** The status of one catalog bundle the dev server builds with tswatch. */
|
|
34
|
+
export interface IWccBundleStatus {
|
|
35
|
+
/** The bundle's name in the tswatch configuration. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** The latest run: `started` while it runs, then `finished` or `failed`. */
|
|
38
|
+
state: TWccBundleState;
|
|
39
|
+
/** Milliseconds the latest completed run took; absent before a run completed. */
|
|
40
|
+
durationMs?: number;
|
|
41
|
+
/**
|
|
42
|
+
* The bundler's error message when the latest completed run failed. It stays while the next
|
|
43
|
+
* run is in progress, so a failure is reported until a run finishes.
|
|
44
|
+
*/
|
|
45
|
+
errorMessage?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The status of every catalog bundle, at a revision that changes with every bundle event. */
|
|
49
|
+
export interface IWccBundleStatusSnapshot {
|
|
50
|
+
revision: number;
|
|
51
|
+
bundles: IWccBundleStatus[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reads the bundle status. With `afterRevision`, the server answers once the status differs from
|
|
56
|
+
* that revision, or with the unchanged status after its wait limit, so a client follows the
|
|
57
|
+
* status with one request at a time. While the server is not serving (it is stopping), the request
|
|
58
|
+
* is refused with a typed error.
|
|
59
|
+
*/
|
|
60
|
+
export interface IReq_GetBundleStatus
|
|
61
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
62
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
63
|
+
IReq_GetBundleStatus
|
|
64
|
+
> {
|
|
65
|
+
method: 'getBundleStatus';
|
|
66
|
+
request: {
|
|
67
|
+
/** The revision the client knows; omitted: answer at once. */
|
|
68
|
+
afterRevision?: number;
|
|
69
|
+
};
|
|
70
|
+
response: IWccBundleStatusSnapshot;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Screenshots one demo in the dev server's headless browser. A capture waits while a catalog
|
|
75
|
+
* bundle builds, and is refused with a typed error when a bundle failed, when the subject does not
|
|
76
|
+
* resolve to a rendered demo, when the browser is busy with other captures, when the demo
|
|
77
|
+
* navigates away from the preview, or on its deadline of 30 seconds.
|
|
78
|
+
*/
|
|
79
|
+
export interface IReq_CaptureScreenshot
|
|
80
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
81
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
82
|
+
IReq_CaptureScreenshot
|
|
83
|
+
> {
|
|
84
|
+
method: 'captureScreenshot';
|
|
85
|
+
request: IWccScreenshotRequest;
|
|
86
|
+
response: IWccScreenshotResult;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Renders one demo in the dev server's headless browser, runs the scripted steps on it and
|
|
91
|
+
* returns the frames it observed meanwhile. A failed step ends the script and is reported in the
|
|
92
|
+
* result, as is a step that started a navigation away from the preview, which the browser blocked;
|
|
93
|
+
* refusals are those of `captureScreenshot`. Its deadline is 135 seconds, and `TypedRequest.fire()`
|
|
94
|
+
* gives up after 60 by default: fire it with a `timeoutMs` above the deadline.
|
|
95
|
+
*/
|
|
96
|
+
export interface IReq_CaptureInteraction
|
|
97
|
+
extends plugins.typedrequestInterfaces.implementsTR<
|
|
98
|
+
plugins.typedrequestInterfaces.ITypedRequest,
|
|
99
|
+
IReq_CaptureInteraction
|
|
100
|
+
> {
|
|
101
|
+
method: 'captureInteraction';
|
|
102
|
+
request: IWccInteractionRequest;
|
|
103
|
+
response: IWccInteractionResult;
|
|
104
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wcctools component standard (docs/standard.md): its configuration in `.smartconfig.json`
|
|
3
|
+
* and the reports `wcctools check` and `wcctools fix` produce.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Rule set a repository adopts. */
|
|
7
|
+
export type TStandardProfile = 'base' | 'dees';
|
|
8
|
+
|
|
9
|
+
/** A rule id of the form `<area>/<name>`, e.g. `props/reflect-primitive`. */
|
|
10
|
+
export type TStandardRuleId = string;
|
|
11
|
+
|
|
12
|
+
export type TStandardSeverity = 'error' | 'warn' | 'info';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What `wcctools fix` does for a rule:
|
|
16
|
+
* - `auto`: fixes it on every run;
|
|
17
|
+
* - `opt-in`: fixes it only when the rule is named with `--rule`;
|
|
18
|
+
* - `none`: reports only.
|
|
19
|
+
*/
|
|
20
|
+
export type TStandardFixMode = 'auto' | 'opt-in' | 'none';
|
|
21
|
+
|
|
22
|
+
/** `.smartconfig.json` → `@design.estate/wcctools` → `standard`. */
|
|
23
|
+
export interface IStandardConfig {
|
|
24
|
+
/** Rule set; 'base' when absent. */
|
|
25
|
+
profile?: TStandardProfile;
|
|
26
|
+
/** Tag prefix without the trailing hyphen, e.g. 'dees'. */
|
|
27
|
+
tagPrefix: string;
|
|
28
|
+
/** dees profile: display name per group area, e.g. { harness: 'Agent Chat' }. */
|
|
29
|
+
groupNames?: Record<string, string>;
|
|
30
|
+
/** Exact exceptions: rule id → '<file>#<member>' → '<category>: <reason>'. */
|
|
31
|
+
allowlist?: Record<TStandardRuleId, Record<string, string>>;
|
|
32
|
+
/** Accepted counts of error findings: rule id → file → count. Only shrinks. */
|
|
33
|
+
baselines?: Record<TStandardRuleId, Record<string, number>>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `.smartconfig.json` → `@design.estate/wcctools`. */
|
|
37
|
+
export interface IWccToolsConfig {
|
|
38
|
+
standard?: IStandardConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface IStandardFinding {
|
|
42
|
+
ruleId: TStandardRuleId;
|
|
43
|
+
severity: TStandardSeverity;
|
|
44
|
+
/** Path relative to the repository root, '/' separators. */
|
|
45
|
+
file: string;
|
|
46
|
+
/** 1-based. */
|
|
47
|
+
line: number;
|
|
48
|
+
/** 1-based. */
|
|
49
|
+
column: number;
|
|
50
|
+
message: string;
|
|
51
|
+
/** True when `wcctools fix` resolves this finding (for opt-in rules: with --rule). */
|
|
52
|
+
fixable: boolean;
|
|
53
|
+
/** Set when the finding is covered by the allowlist or a baseline and does not fail check. */
|
|
54
|
+
covered?: 'allowlist' | 'baseline';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A file whose finding count is below its baseline: a candidate for lowering the baseline. */
|
|
58
|
+
export interface IStandardBaselineSlack {
|
|
59
|
+
ruleId: TStandardRuleId;
|
|
60
|
+
file: string;
|
|
61
|
+
baseline: number;
|
|
62
|
+
current: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface IStandardReport {
|
|
66
|
+
profile: TStandardProfile;
|
|
67
|
+
findings: IStandardFinding[];
|
|
68
|
+
/** Files whose count is below their baseline: candidates for --update-baseline. */
|
|
69
|
+
baselineSlack: IStandardBaselineSlack[];
|
|
70
|
+
/** True when no error finding remains outside the allowlist and baselines. */
|
|
71
|
+
ok: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A rule as it applies in one profile. */
|
|
75
|
+
export interface IStandardRuleInfo {
|
|
76
|
+
id: TStandardRuleId;
|
|
77
|
+
severity: TStandardSeverity;
|
|
78
|
+
fixMode: TStandardFixMode;
|
|
79
|
+
/** Categories an allowlist entry for this rule may use; empty when the rule takes no allowlist. */
|
|
80
|
+
allowlistCategories: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface IStandardFileMove {
|
|
84
|
+
from: string;
|
|
85
|
+
to: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The changes a `wcctools fix` run makes (or, with --dry-run, would make). */
|
|
89
|
+
export interface IStandardFixPlan {
|
|
90
|
+
/** `git mv` operations, from the original path to the final path. */
|
|
91
|
+
moves: IStandardFileMove[];
|
|
92
|
+
/** New files, by final path. */
|
|
93
|
+
created: string[];
|
|
94
|
+
/** Changed existing files, by final path, with the rules that changed them. */
|
|
95
|
+
edited: Array<{ file: string; ruleIds: TStandardRuleId[] }>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface IStandardFixResult {
|
|
99
|
+
dryRun: boolean;
|
|
100
|
+
plan: IStandardFixPlan;
|
|
101
|
+
/** The check after the fix (with --dry-run: as the fix would leave the repository). */
|
|
102
|
+
remaining: IStandardReport;
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './previewroute.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The address of a preview document: the path the `wcctools dev` server serves it on, and
|
|
5
|
+
* the query that names what it renders. The shell loads it into its iframe; headless tools
|
|
6
|
+
* load it directly to render one demo alone.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** The path `wcctools dev` serves the preview document on. */
|
|
10
|
+
export const previewRoutePath = '/wcctools-preview';
|
|
11
|
+
|
|
12
|
+
export interface IWccPreviewRoute {
|
|
13
|
+
selection: plugins.interfaces.IWccSelection | null;
|
|
14
|
+
theme: plugins.interfaces.TWccTheme;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const queryKeys = {
|
|
18
|
+
section: 'section',
|
|
19
|
+
item: 'item',
|
|
20
|
+
demo: 'demo',
|
|
21
|
+
theme: 'theme',
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
/** The query string (with leading `?`) of a preview route. */
|
|
25
|
+
export const formatPreviewQuery = (routeArg: IWccPreviewRoute): string => {
|
|
26
|
+
const params = new URLSearchParams();
|
|
27
|
+
if (routeArg.selection) {
|
|
28
|
+
params.set(queryKeys.section, routeArg.selection.sectionName);
|
|
29
|
+
params.set(queryKeys.item, routeArg.selection.itemName);
|
|
30
|
+
params.set(queryKeys.demo, String(routeArg.selection.demoIndex));
|
|
31
|
+
}
|
|
32
|
+
params.set(queryKeys.theme, routeArg.theme);
|
|
33
|
+
return `?${params.toString()}`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Read a preview route from a query string. Missing or invalid parts read as no selection and the dark theme. */
|
|
37
|
+
export const parsePreviewQuery = (searchArg: string): IWccPreviewRoute => {
|
|
38
|
+
const params = new URLSearchParams(searchArg);
|
|
39
|
+
const theme: plugins.interfaces.TWccTheme = params.get(queryKeys.theme) === 'bright' ? 'bright' : 'dark';
|
|
40
|
+
const sectionName = params.get(queryKeys.section);
|
|
41
|
+
const itemName = params.get(queryKeys.item);
|
|
42
|
+
if (!sectionName || !itemName) {
|
|
43
|
+
return { selection: null, theme };
|
|
44
|
+
}
|
|
45
|
+
const demoIndex = Number.parseInt(params.get(queryKeys.demo) ?? '0', 10);
|
|
46
|
+
return {
|
|
47
|
+
selection: { sectionName, itemName, demoIndex: Number.isInteger(demoIndex) && demoIndex >= 0 ? demoIndex : 0 },
|
|
48
|
+
theme,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const previewReadyMessage: plugins.interfaces.IWccPreviewReadyMessage = { type: 'wcctools-preview-ready' };
|
|
53
|
+
|
|
54
|
+
export const isPreviewReadyMessage = (dataArg: unknown): dataArg is plugins.interfaces.IWccPreviewReadyMessage => {
|
|
55
|
+
return typeof dataArg === 'object'
|
|
56
|
+
&& dataArg !== null
|
|
57
|
+
&& (dataArg as { type?: unknown }).type === previewReadyMessage.type;
|
|
58
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
/** The first and the longest pause before a failed status request is sent again. */
|
|
4
|
+
const retryDelayMinMs = 1_000;
|
|
5
|
+
const retryDelayMaxMs = 10_000;
|
|
6
|
+
|
|
7
|
+
/** Resolves after `msArg`, or at once when the signal aborts. */
|
|
8
|
+
const pause = (msArg: number, signalArg: AbortSignal): Promise<void> =>
|
|
9
|
+
new Promise((resolve) => {
|
|
10
|
+
const done = () => {
|
|
11
|
+
clearTimeout(timer);
|
|
12
|
+
signalArg.removeEventListener('abort', done);
|
|
13
|
+
resolve();
|
|
14
|
+
};
|
|
15
|
+
const timer = setTimeout(done, msArg);
|
|
16
|
+
signalArg.addEventListener('abort', done, { once: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Follows the dev server's bundle status: one `getBundleStatus` request at a time, each answered
|
|
21
|
+
* when the status changes. A failed request (the server restarting, for example) is sent again
|
|
22
|
+
* after a growing pause. `dispose()` aborts the request in flight and ends the loop.
|
|
23
|
+
*/
|
|
24
|
+
export class WccBundleStatusClient {
|
|
25
|
+
private abortController: AbortController | null = null;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly apiPath: string,
|
|
29
|
+
private readonly onStatus: (snapshotArg: plugins.interfaces.IWccBundleStatusSnapshot) => void,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
public start() {
|
|
33
|
+
if (this.abortController) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const abortController = new AbortController();
|
|
37
|
+
this.abortController = abortController;
|
|
38
|
+
void this.follow(abortController.signal);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public dispose() {
|
|
42
|
+
this.abortController?.abort();
|
|
43
|
+
this.abortController = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private async follow(signalArg: AbortSignal) {
|
|
47
|
+
const request = new plugins.typedrequest.TypedRequest<plugins.interfaces.IReq_GetBundleStatus>(this.apiPath, 'getBundleStatus');
|
|
48
|
+
let revision: number | undefined;
|
|
49
|
+
let retryDelay = retryDelayMinMs;
|
|
50
|
+
while (!signalArg.aborted) {
|
|
51
|
+
try {
|
|
52
|
+
const snapshot = await request.fire({ afterRevision: revision }, { abortSignal: signalArg });
|
|
53
|
+
retryDelay = retryDelayMinMs;
|
|
54
|
+
if (!signalArg.aborted && snapshot.revision !== revision) {
|
|
55
|
+
revision = snapshot.revision;
|
|
56
|
+
this.onStatus(snapshot);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (signalArg.aborted) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
console.warn('wcctools: reading the bundle status failed; retrying', error);
|
|
63
|
+
await pause(retryDelay, signalArg);
|
|
64
|
+
retryDelay = Math.min(retryDelay * 2, retryDelayMaxMs);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|