@design.estate/wcctools 6.1.0 → 6.2.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.
Files changed (63) hide show
  1. package/changelog.md +20 -0
  2. package/dist_shell/bundle.js +405 -368
  3. package/dist_shell/bundle.js.map +1 -1
  4. package/dist_shell/bundle.js.third-party-notices.json +52 -0
  5. package/dist_shell/bundle.js.third-party-notices.txt +107 -1
  6. package/dist_shell/index.html +1 -1
  7. package/dist_ts/00_commitinfo_data.js +1 -1
  8. package/dist_ts/classes.bundlestatus.d.ts +36 -0
  9. package/dist_ts/classes.bundlestatus.js +110 -0
  10. package/dist_ts/classes.devapi.d.ts +3 -0
  11. package/dist_ts/classes.devapi.js +8 -1
  12. package/dist_ts/classes.devserver.d.ts +45 -10
  13. package/dist_ts/classes.devserver.js +123 -49
  14. package/dist_ts/cli.js +5 -2
  15. package/dist_ts/index.d.ts +0 -2
  16. package/dist_ts/index.js +1 -3
  17. package/dist_ts_interfaces/requests.d.ts +35 -0
  18. package/dist_ts_shell/bundlestatus.d.ts +15 -0
  19. package/dist_ts_shell/bundlestatus.js +64 -0
  20. package/dist_ts_shell/elements/wcc-contextmenu.d.ts +3 -0
  21. package/dist_ts_shell/elements/wcc-contextmenu.js +18 -3
  22. package/dist_ts_shell/elements/wcc-preview-frame.d.ts +6 -0
  23. package/dist_ts_shell/elements/wcc-preview-frame.js +50 -2
  24. package/dist_ts_shell/elements/wcc-shell.d.ts +15 -0
  25. package/dist_ts_shell/elements/wcc-shell.js +45 -3
  26. package/dist_ts_shell/plugins.d.ts +2 -1
  27. package/dist_ts_shell/plugins.js +3 -2
  28. package/dist_ts_web/00_commitinfo_data.js +1 -1
  29. package/package.json +5 -2
  30. package/readme.md +6 -2
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/classes.bundlestatus.ts +123 -0
  33. package/ts/classes.devapi.ts +9 -0
  34. package/ts/classes.devserver.ts +127 -47
  35. package/ts/cli.ts +4 -1
  36. package/ts/index.ts +0 -2
  37. package/ts_interfaces/bridge.ts +95 -0
  38. package/ts_interfaces/catalog.ts +37 -0
  39. package/ts_interfaces/index.ts +4 -0
  40. package/ts_interfaces/plugins.ts +3 -0
  41. package/ts_interfaces/requests.ts +65 -0
  42. package/ts_interfaces/standard.ts +103 -0
  43. package/ts_shared/index.ts +1 -0
  44. package/ts_shared/plugins.ts +3 -0
  45. package/ts_shared/previewroute.ts +58 -0
  46. package/ts_shell/bundlestatus.ts +68 -0
  47. package/ts_shell/elements/wcc-contextmenu.ts +306 -0
  48. package/ts_shell/elements/wcc-preview-frame.ts +167 -0
  49. package/ts_shell/elements/wcc-properties.ts +998 -0
  50. package/ts_shell/elements/wcc-record-button.ts +108 -0
  51. package/ts_shell/elements/wcc-recording-panel.ts +1005 -0
  52. package/ts_shell/elements/wcc-shell.ts +485 -0
  53. package/ts_shell/elements/wcc-sidebar.ts +1419 -0
  54. package/ts_shell/index.html +21 -0
  55. package/ts_shell/index.ts +4 -0
  56. package/ts_shell/plugins.ts +12 -0
  57. package/ts_shell/previewconnection.ts +103 -0
  58. package/ts_shell/services/recorder.service.ts +451 -0
  59. package/ts_shell/types/dom-mediacapture-stub/index.d.ts +12 -0
  60. package/ts_shell/types/dom-mediacapture-stub/package.json +6 -0
  61. package/ts_shell/types/dom-webcodecs-stub/index.d.ts +2 -0
  62. package/ts_shell/types/dom-webcodecs-stub/package.json +6 -0
  63. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -7,6 +7,11 @@ import { loadShellContent, shellPathPrefix } from './shellcontent.js';
7
7
  export interface IWccDevServerOptions {
8
8
  /** Port to listen on; overrides the tswatch configuration. 0 picks a free port. */
9
9
  port?: number;
10
+ /**
11
+ * The project directory: its tswatch configuration, package.json and catalog are served, and
12
+ * tswatch bundles and watches in it (default: `process.cwd()` when the server is constructed).
13
+ */
14
+ cwd?: string;
10
15
  }
11
16
 
12
17
  export interface IWccDevServerAddress {
@@ -28,84 +33,159 @@ const securityHeaders: plugins.typedserver.ISecurityHeaders = {
28
33
  const acceptAnyHostname = () => true;
29
34
 
30
35
  /**
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:
36
+ * `wcctools dev`: bundles the catalog of a project with tswatch, as its `@git.zone/tswatch`
37
+ * configuration (or the `element` preset) describes, and serves on one port:
33
38
  *
34
39
  * - the shell on `/wcctools/` (`/` redirects there) and its routes `/wcctools-route/...`,
35
- * - the typed API on `/wcctools/typedrequest`,
40
+ * - the typed API on `/wcctools/typedrequest`, including the bundles' status,
36
41
  * - the catalog's preview document and bundle from the tswatch serve directory on every other
37
42
  * path, with live reload that reloads only the preview document.
38
43
  *
39
- * tswatch resolves its paths against the process's working directory, so the server serves the
40
- * project the process runs in.
44
+ * The server owns no process lifecycle: while it runs, a process shutdown through smartexit's
45
+ * `ProcessLifecycle` stops it. The entry point installs that lifecycle with
46
+ * `getShutdownTimeoutMs()`.
41
47
  */
42
48
  export class WccDevServer {
43
49
  /** The typed API on `/wcctools/typedrequest`; register further handlers before `start()`. */
44
50
  public readonly api = new WccDevApi();
45
- private tsWatch: plugins.tswatch.TsWatch | null = null;
51
+ /** The project directory everything resolves against. */
52
+ public readonly cwd: string;
53
+ /** The project's tswatch configuration, or the `element` preset; read once, on construction. */
54
+ private readonly config: plugins.tswatch.ITswatchConfig;
55
+ /** Bundling and watching stay tswatch's; its own server stays off. */
56
+ private readonly tsWatch: plugins.tswatch.TsWatch;
46
57
  private typedServer: plugins.typedserver.TypedServer | null = null;
47
- private readonly smartExit = new plugins.smartexit.SmartExit({ silent: true });
58
+ /** Stops the server on a process shutdown; registered by start(), released by stop(). */
59
+ private smartExit: plugins.smartexit.SmartExit | null = null;
60
+ private unsubscribeBundleEvents: (() => void) | null = null;
61
+ private running = false;
62
+ private starting: Promise<IWccDevServerAddress> | null = null;
63
+ private stopping: Promise<void> | null = null;
48
64
 
49
65
  constructor(private readonly options: IWccDevServerOptions = {}) {
50
- this.smartExit.addCleanupFunction(() => this.stop());
66
+ this.cwd = plugins.path.resolve(options.cwd ?? process.cwd());
67
+ const configHandler = new plugins.tswatch.ConfigHandler(this.cwd);
68
+ this.config = configHandler.loadConfig() ?? this.getElementPreset(configHandler);
69
+ this.tsWatch = new plugins.tswatch.TsWatch({ ...this.config, server: { enabled: false } }, { cwd: this.cwd });
51
70
  }
52
71
 
72
+ /**
73
+ * The shutdown deadline the server needs, tswatch's: the entry point installs smartexit's
74
+ * `ProcessLifecycle` with it, so every watcher command keeps its full grace period.
75
+ */
76
+ public getShutdownTimeoutMs(): number {
77
+ return this.tsWatch.getShutdownTimeoutMs();
78
+ }
79
+
80
+ /**
81
+ * Bundles the catalog, starts the watchers and serves. Refused while the server runs or stops;
82
+ * the server starts again after stop().
83
+ */
53
84
  public async start(): Promise<IWccDevServerAddress> {
54
- if (this.typedServer) {
55
- throw new Error('wcctools: the dev server is already started.');
85
+ if (this.running || this.stopping) {
86
+ throw new Error('wcctools: the dev server is already started or still stopping.');
87
+ }
88
+ this.running = true;
89
+ // A process shutdown stops the server from here on, also while it is still starting
90
+ const smartExit = new plugins.smartexit.SmartExit({ silent: true });
91
+ smartExit.addCleanupFunction(() => this.stop());
92
+ this.smartExit = smartExit;
93
+ const starting = this.startServing();
94
+ this.starting = starting;
95
+ try {
96
+ const address = await starting;
97
+ if (this.stopping) {
98
+ throw new Error('wcctools: the dev server was stopped while starting.');
99
+ }
100
+ return address;
101
+ } catch (error) {
102
+ // A start that failed halfway releases what it opened
103
+ await this.stop();
104
+ throw error;
105
+ } finally {
106
+ if (this.starting === starting) {
107
+ this.starting = null;
108
+ }
56
109
  }
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;
110
+ }
111
+
112
+ /**
113
+ * Stops the server and tswatch's bundling and watching, and releases everything start()
114
+ * registered. A stop during start() waits for the start to settle first; concurrent and
115
+ * repeated calls share one stop.
116
+ */
117
+ public stop(): Promise<void> {
118
+ this.stopping ??= this.release().finally(() => {
119
+ this.stopping = null;
120
+ });
121
+ return this.stopping;
122
+ }
123
+
124
+ private async release(): Promise<void> {
125
+ await this.starting?.catch(() => undefined);
126
+ const smartExit = this.smartExit;
127
+ const typedServer = this.typedServer;
128
+ const unsubscribeBundleEvents = this.unsubscribeBundleEvents;
129
+ this.smartExit = null;
130
+ this.typedServer = null;
131
+ this.unsubscribeBundleEvents = null;
132
+ smartExit?.deregister();
133
+ // Status requests waiting for a change are refused now, so none holds the server's stop, and
134
+ // requests until the server stops listening are refused too: the shell backs off
135
+ this.api.bundleStatus.close();
136
+ try {
137
+ await typedServer?.stop();
138
+ } finally {
139
+ try {
140
+ await this.tsWatch.stop();
141
+ } finally {
142
+ unsubscribeBundleEvents?.();
143
+ this.running = false;
144
+ }
145
+ }
146
+ }
147
+
148
+ private async startServing(): Promise<IWccDevServerAddress> {
149
+ const serveDir = plugins.path.resolve(this.cwd, this.config.server?.serveDir ?? defaultServeDir);
150
+ const port = this.options.port ?? this.config.server?.port ?? defaultPort;
62
151
 
63
152
  // The preview surface watches the serve directory, which must exist before the first bundle
64
153
  await plugins.fs.mkdir(serveDir, { recursive: true });
65
154
  this.api.setServerInfo({
66
155
  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
- ],
156
+ projectName: await this.readProjectName(),
76
157
  });
158
+ const surfaces = [await this.createShellSurface(), this.createPreviewSurface(serveDir)];
159
+ const typedServer = new plugins.typedserver.TypedServer({ cors: false, port, surfaces });
77
160
  this.typedServer = typedServer;
78
161
 
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
- }
162
+ this.api.bundleStatus.open();
163
+ const bundleStatus = this.api.bundleStatus;
164
+ this.unsubscribeBundleEvents = this.tsWatch.onBundleEvent((eventArg) => bundleStatus.record(eventArg));
165
+ // The initial bundles run here; a failed one is reported in the bundle status
166
+ await this.tsWatch.start();
167
+ await typedServer.start();
88
168
  const boundPort = typedServer.listeningPort ?? port;
89
169
  return { port: boundPort, url: `http://localhost:${boundPort}${shellPathPrefix}/` };
90
170
  }
91
171
 
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();
172
+ private getElementPreset(configHandlerArg: plugins.tswatch.ConfigHandler): plugins.tswatch.ITswatchConfig {
173
+ const preset = configHandlerArg.getPreset('element');
174
+ if (!preset) {
175
+ throw new Error('wcctools: tswatch provides no `element` preset.');
176
+ }
177
+ return preset;
101
178
  }
102
179
 
103
180
  /**
104
181
  * The shell: the prebuilt shell bundle and the typed API.
105
182
  *
106
183
  * 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.
184
+ * side effects is registered here, the server binds to localhost by default (another interface
185
+ * only on explicit request), validates the Host header against the addresses it serves instead
186
+ * of accepting any hostname, and admits typedsocket connections and typed requests only from the
187
+ * shell's own origin (a `websocketAdmission`/`requestAdmission`), keeping surface mode's strict
188
+ * authority validation. Until then every handler here is read-only.
109
189
  */
110
190
  private async createShellSurface(): Promise<plugins.typedserver.ITypedServerSurface> {
111
191
  return {
@@ -154,8 +234,8 @@ export class WccDevServer {
154
234
  };
155
235
  }
156
236
 
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);
237
+ private async readProjectName(): Promise<string> {
238
+ const packageJson = JSON.parse(await plugins.fs.readFile(plugins.path.join(this.cwd, 'package.json'), 'utf8')) as { name?: string };
239
+ return packageJson.name ?? plugins.path.basename(this.cwd);
160
240
  }
161
241
  }
package/ts/cli.ts CHANGED
@@ -23,7 +23,10 @@ export const registerCommands = (out: plugins.smartconsole.SmartConsole): void =
23
23
  },
24
24
  },
25
25
  }, async ({ options }) => {
26
- const devServer = new WccDevServer({ port: options.port });
26
+ const devServer = new WccDevServer({ port: options.port, cwd: process.cwd() });
27
+ // The entry point owns process signals: SIGINT and SIGTERM run every registered cleanup, the
28
+ // dev server's stop among them, once within tswatch's shutdown deadline, then exit.
29
+ plugins.smartexit.ProcessLifecycle.install({ shutdownTimeoutMs: devServer.getShutdownTimeoutMs() });
27
30
  const address = await devServer.start();
28
31
  await out.log(`wcctools dev: ${address.url}`);
29
32
  });
package/ts/index.ts CHANGED
@@ -1,3 +1 @@
1
1
  export { runCli } from './cli.js';
2
- export { WccDevServer, type IWccDevServerOptions, type IWccDevServerAddress } from './classes.devserver.js';
3
- export { WccDevApi } from './classes.devapi.js';
@@ -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,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,4 @@
1
+ export * from './catalog.js';
2
+ export * from './bridge.js';
3
+ export * from './requests.js';
4
+ export * from './standard.js';
@@ -0,0 +1,3 @@
1
+ import * as typedrequestInterfaces from '@api.global/typedrequest-interfaces';
2
+
3
+ export { typedrequestInterfaces };
@@ -0,0 +1,65 @@
1
+ import * as plugins from './plugins.js';
2
+
3
+ /**
4
+ * Typed requests the shell sends to the `wcctools dev` server on `/wcctools/typedrequest`.
5
+ */
6
+
7
+ export interface IWccDevServerInfo {
8
+ /** Version of the wcctools package that serves the shell. */
9
+ wcctoolsVersion: string;
10
+ /** Name of the served project's package. */
11
+ projectName: string;
12
+ }
13
+
14
+ export interface IReq_GetDevServerInfo
15
+ extends plugins.typedrequestInterfaces.implementsTR<
16
+ plugins.typedrequestInterfaces.ITypedRequest,
17
+ IReq_GetDevServerInfo
18
+ > {
19
+ method: 'getDevServerInfo';
20
+ request: {};
21
+ response: IWccDevServerInfo;
22
+ }
23
+
24
+ /** The state of a catalog bundle: its latest run started, finished or failed. */
25
+ export type TWccBundleState = 'started' | 'finished' | 'failed';
26
+
27
+ /** The status of one catalog bundle the dev server builds with tswatch. */
28
+ export interface IWccBundleStatus {
29
+ /** The bundle's name in the tswatch configuration. */
30
+ name: string;
31
+ /** The latest run: `started` while it runs, then `finished` or `failed`. */
32
+ state: TWccBundleState;
33
+ /** Milliseconds the latest completed run took; absent before a run completed. */
34
+ durationMs?: number;
35
+ /**
36
+ * The bundler's error message when the latest completed run failed. It stays while the next
37
+ * run is in progress, so a failure is reported until a run finishes.
38
+ */
39
+ errorMessage?: string;
40
+ }
41
+
42
+ /** The status of every catalog bundle, at a revision that changes with every bundle event. */
43
+ export interface IWccBundleStatusSnapshot {
44
+ revision: number;
45
+ bundles: IWccBundleStatus[];
46
+ }
47
+
48
+ /**
49
+ * Reads the bundle status. With `afterRevision`, the server answers once the status differs from
50
+ * that revision, or with the unchanged status after its wait limit, so a client follows the
51
+ * status with one request at a time. While the server is not serving (it is stopping), the request
52
+ * is refused with a typed error.
53
+ */
54
+ export interface IReq_GetBundleStatus
55
+ extends plugins.typedrequestInterfaces.implementsTR<
56
+ plugins.typedrequestInterfaces.ITypedRequest,
57
+ IReq_GetBundleStatus
58
+ > {
59
+ method: 'getBundleStatus';
60
+ request: {
61
+ /** The revision the client knows; omitted: answer at once. */
62
+ afterRevision?: number;
63
+ };
64
+ response: IWccBundleStatusSnapshot;
65
+ }
@@ -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,3 @@
1
+ import * as interfaces from '../ts_interfaces/index.js';
2
+
3
+ export { interfaces };
@@ -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
+ };