@weasel-js/forge 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -10
- package/dist/{FrameController-BPfCt4PJ.d.ts → FrameController-BspC9Zr5.d.ts} +67 -4
- package/dist/chunk-A4JOEGYQ.js +325 -0
- package/dist/chunk-A4JOEGYQ.js.map +1 -0
- package/dist/{chunk-2MADHLRA.js → chunk-FWPBKKYB.js} +58 -231
- package/dist/chunk-FWPBKKYB.js.map +1 -0
- package/dist/{chunk-6SRQIHJQ.js → chunk-HRGPFQBW.js} +3 -3
- package/dist/{chunk-6SRQIHJQ.js.map → chunk-HRGPFQBW.js.map} +1 -1
- package/dist/chunk-PO5JLVYT.js +318 -0
- package/dist/chunk-PO5JLVYT.js.map +1 -0
- package/dist/{chunk-TLO6UHF2.js → chunk-VEKEBILL.js} +17 -4
- package/dist/chunk-VEKEBILL.js.map +1 -0
- package/dist/{config-CrLpfSq9.d.ts → config-DU_WuORS.d.ts} +7 -1
- package/dist/csf/preview-api.js +1 -2
- package/dist/csf/preview-api.js.map +1 -1
- package/dist/frame/index.css +102 -0
- package/dist/frame/index.css.map +1 -1
- package/dist/frame/index.d.ts +11 -3
- package/dist/frame/index.js +365 -5
- package/dist/frame/index.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js.map +1 -1
- package/dist/shell/index.css +202 -13
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +76 -12
- package/dist/shell/index.js +1075 -187
- package/dist/shell/index.js.map +1 -1
- package/dist/test/index.d.ts +3 -3
- package/dist/test/index.js +67 -72
- package/dist/test/index.js.map +1 -1
- package/dist/vite/index.js +68 -11
- package/dist/vite/index.js.map +1 -1
- package/package.json +5 -4
- package/dist/chunk-2JDYOBWM.js +0 -118
- package/dist/chunk-2JDYOBWM.js.map +0 -1
- package/dist/chunk-2MADHLRA.js.map +0 -1
- package/dist/chunk-FTFR2YPM.js +0 -13
- package/dist/chunk-FTFR2YPM.js.map +0 -1
- package/dist/chunk-TLO6UHF2.js.map +0 -1
package/dist/test/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as FrameSetup } from '../FrameController-
|
|
1
|
+
import { F as FrameSetup } from '../FrameController-BspC9Zr5.js';
|
|
2
2
|
import '@weasel-js/labkit/config';
|
|
3
3
|
import 'react';
|
|
4
4
|
|
|
@@ -9,8 +9,8 @@ interface RunStoryOptions {
|
|
|
9
9
|
viewport?: (width: number, height: number) => Promise<void>;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Renders one story the way the workshop
|
|
13
|
-
* Throws on
|
|
12
|
+
* Renders one story in the test page the way the workshop does — defaults, no state, no globals — then plays it.
|
|
13
|
+
* Throws on a render the story's boundary caught, or a play function that fails.
|
|
14
14
|
*/
|
|
15
15
|
declare function runStory(mod: Record<string, unknown>, exportName: string, file: string, autoTitle: string, { setup, viewport }?: RunStoryOptions): Promise<void>;
|
|
16
16
|
|
package/dist/test/index.js
CHANGED
|
@@ -1,72 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StoryTrial } from '../chunk-A4JOEGYQ.js';
|
|
2
|
+
import { loadStories } from '../chunk-FWPBKKYB.js';
|
|
2
3
|
import '../chunk-2GXKNXTJ.js';
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import
|
|
5
|
-
import '
|
|
6
|
-
import '
|
|
4
|
+
import '../chunk-HRGPFQBW.js';
|
|
5
|
+
import '../chunk-VEKEBILL.js';
|
|
6
|
+
import { withValueAtPath } from '@weasel-js/labkit/config';
|
|
7
|
+
import { useState, useEffect } from 'react';
|
|
8
|
+
import { createRoot } from 'react-dom/client';
|
|
9
|
+
import { jsx } from 'react/jsx-runtime';
|
|
7
10
|
|
|
8
|
-
// src/test/runStory.ts
|
|
9
11
|
function phased(phase, error) {
|
|
10
12
|
const message = error instanceof Error ? error.message : String(error);
|
|
11
13
|
const out = new Error(`${phase} fault: ${message}`, { cause: error });
|
|
12
14
|
if (error instanceof Error && error.stack) out.stack = `${phase} fault: ${error.stack}`;
|
|
13
15
|
return out;
|
|
14
16
|
}
|
|
15
|
-
function inbox(channel) {
|
|
16
|
-
const received = [];
|
|
17
|
-
let failure = null;
|
|
18
|
-
const wake = /* @__PURE__ */ new Set();
|
|
19
|
-
const off = channel.on((msg) => {
|
|
20
|
-
if (msg.type === "fault" && !failure) {
|
|
21
|
-
failure = new Error(`${msg.phase} fault: ${msg.message}`);
|
|
22
|
-
if (msg.stack) failure.stack = `${msg.phase} fault: ${msg.stack}`;
|
|
23
|
-
}
|
|
24
|
-
received.push(msg);
|
|
25
|
-
for (const fn of [...wake]) fn();
|
|
26
|
-
});
|
|
27
|
-
const check = () => {
|
|
28
|
-
if (failure) throw failure;
|
|
29
|
-
};
|
|
30
|
-
const next = (type) => new Promise((resolve, reject) => {
|
|
31
|
-
const poll = () => {
|
|
32
|
-
if (failure) {
|
|
33
|
-
wake.delete(poll);
|
|
34
|
-
reject(failure);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const at = received.findIndex((m) => m.type === type);
|
|
38
|
-
if (at === -1) return;
|
|
39
|
-
wake.delete(poll);
|
|
40
|
-
resolve(received.splice(at, 1)[0]);
|
|
41
|
-
};
|
|
42
|
-
wake.add(poll);
|
|
43
|
-
poll();
|
|
44
|
-
});
|
|
45
|
-
const fault = () => new Promise((resolve) => {
|
|
46
|
-
const done = () => {
|
|
47
|
-
clearTimeout(timer);
|
|
48
|
-
wake.delete(onMessage);
|
|
49
|
-
resolve(failure);
|
|
50
|
-
};
|
|
51
|
-
const onMessage = () => {
|
|
52
|
-
if (failure) done();
|
|
53
|
-
};
|
|
54
|
-
const timer = setTimeout(done, 0);
|
|
55
|
-
wake.add(onMessage);
|
|
56
|
-
onMessage();
|
|
57
|
-
});
|
|
58
|
-
return { next, check, fault, close: off };
|
|
59
|
-
}
|
|
60
17
|
var settle = () => new Promise((resolve) => {
|
|
61
18
|
requestAnimationFrame(() => setTimeout(resolve, 0));
|
|
62
19
|
});
|
|
20
|
+
function TestTrial({ story, setup, hostRef, onConfig, onRendered, onError }) {
|
|
21
|
+
const [config, setConfigState] = useState(() => story.config.defaults());
|
|
22
|
+
const [state, setState] = useState(null);
|
|
23
|
+
useEffect(() => onConfig(config), [config, onConfig]);
|
|
24
|
+
const ctx = {
|
|
25
|
+
config,
|
|
26
|
+
state,
|
|
27
|
+
setConfig: (path, value) => setConfigState((prev) => withValueAtPath(prev, path, value)),
|
|
28
|
+
setState
|
|
29
|
+
};
|
|
30
|
+
return /* @__PURE__ */ jsx(StoryTrial, { story, setup, ctx, hostRef, onRendered, onError });
|
|
31
|
+
}
|
|
63
32
|
var leftover = null;
|
|
64
33
|
var pageSize = null;
|
|
65
|
-
async function runStory(mod, exportName, file, autoTitle, { setup, viewport } = {}) {
|
|
34
|
+
async function runStory(mod, exportName, file, autoTitle, { setup = {}, viewport } = {}) {
|
|
66
35
|
leftover?.();
|
|
67
36
|
let story;
|
|
68
37
|
try {
|
|
69
|
-
story = loadStories(mod, autoTitle, setup
|
|
38
|
+
story = loadStories(mod, autoTitle, setup.parameters).find((s) => s.exportName === exportName);
|
|
70
39
|
} catch (error) {
|
|
71
40
|
throw phased("load", error);
|
|
72
41
|
}
|
|
@@ -76,49 +45,75 @@ async function runStory(mod, exportName, file, autoTitle, { setup, viewport } =
|
|
|
76
45
|
if (viewport && (window.innerWidth !== size.width || window.innerHeight !== size.height)) {
|
|
77
46
|
await viewport(size.width, size.height);
|
|
78
47
|
}
|
|
79
|
-
const { port1, port2 } = new MessageChannel();
|
|
80
|
-
const shell = openChannel(port1);
|
|
81
|
-
const frame = openChannel(port2);
|
|
82
|
-
const messages = inbox(shell);
|
|
83
48
|
const container = document.createElement("div");
|
|
84
49
|
document.body.append(container);
|
|
85
|
-
let
|
|
50
|
+
let root;
|
|
86
51
|
const cleanup = () => {
|
|
87
52
|
if (leftover === cleanup) leftover = null;
|
|
88
53
|
let thrown = null;
|
|
89
54
|
try {
|
|
90
|
-
|
|
91
|
-
stopFrame?.();
|
|
55
|
+
root?.unmount();
|
|
92
56
|
} catch (error) {
|
|
93
57
|
thrown = { error };
|
|
94
58
|
} finally {
|
|
95
|
-
|
|
96
|
-
shell.close();
|
|
97
|
-
frame.close();
|
|
59
|
+
root = void 0;
|
|
98
60
|
container.remove();
|
|
99
61
|
}
|
|
100
62
|
return thrown;
|
|
101
63
|
};
|
|
102
64
|
leftover = cleanup;
|
|
65
|
+
const hostRef = { current: null };
|
|
66
|
+
let config;
|
|
67
|
+
let renderError = null;
|
|
68
|
+
let markRendered = () => {
|
|
69
|
+
};
|
|
70
|
+
const rendered = new Promise((resolve) => {
|
|
71
|
+
markRendered = resolve;
|
|
72
|
+
});
|
|
73
|
+
const fail = (error) => {
|
|
74
|
+
renderError ??= { error };
|
|
75
|
+
markRendered();
|
|
76
|
+
};
|
|
77
|
+
const checkRender = () => {
|
|
78
|
+
if (renderError) throw phased("render", renderError.error);
|
|
79
|
+
};
|
|
103
80
|
let failure = null;
|
|
104
81
|
try {
|
|
105
82
|
try {
|
|
106
|
-
|
|
83
|
+
await setup.prepare?.(story);
|
|
84
|
+
root = createRoot(container, { onUncaughtError: fail });
|
|
85
|
+
root.render(
|
|
86
|
+
/* @__PURE__ */ jsx(
|
|
87
|
+
TestTrial,
|
|
88
|
+
{
|
|
89
|
+
story,
|
|
90
|
+
setup,
|
|
91
|
+
hostRef,
|
|
92
|
+
onConfig: (next) => {
|
|
93
|
+
config = next;
|
|
94
|
+
},
|
|
95
|
+
onRendered: markRendered,
|
|
96
|
+
onError: fail
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
);
|
|
107
100
|
} catch (error) {
|
|
108
101
|
throw phased("mount", error);
|
|
109
102
|
}
|
|
110
|
-
await
|
|
111
|
-
shell.send({ type: "init", config: story.config.defaults(), state: null, globals: {} });
|
|
112
|
-
await messages.next("answers");
|
|
103
|
+
await rendered;
|
|
113
104
|
await settle();
|
|
114
|
-
|
|
105
|
+
checkRender();
|
|
106
|
+
const host = hostRef.current;
|
|
107
|
+
if (!host) throw new Error("render fault: the story host never mounted");
|
|
115
108
|
if (story.play) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
109
|
+
try {
|
|
110
|
+
await story.play({ canvasElement: host, config, globals: {} });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
throw phased("play", error);
|
|
113
|
+
}
|
|
119
114
|
await settle();
|
|
115
|
+
checkRender();
|
|
120
116
|
}
|
|
121
|
-
messages.check();
|
|
122
117
|
} catch (error) {
|
|
123
118
|
failure = { error };
|
|
124
119
|
}
|
package/dist/test/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/test/runStory.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { type FrameSetup, startFrame } from '../frame/FrameController';\nimport { loadStories } from '../frame/mountFrame';\nimport { type Channel, openChannel } from '../protocol/channel';\nimport type { FromFrame, ToFrame, Viewport } from '../protocol/messages';\n\ntype Received<T extends FromFrame['type']> = Extract<FromFrame, { type: T }>;\n\n/** Prefixes `error` with the phase it came from, keeping its stack. */\nfunction phased(phase: string, error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n const out = new Error(`${phase} fault: ${message}`, { cause: error });\n if (error instanceof Error && error.stack) out.stack = `${phase} fault: ${error.stack}`;\n return out;\n}\n\nfunction inbox(channel: Channel<FromFrame, ToFrame>) {\n const received: FromFrame[] = [];\n let failure: Error | null = null;\n const wake = new Set<() => void>();\n const off = channel.on((msg) => {\n if (msg.type === 'fault' && !failure) {\n failure = new Error(`${msg.phase} fault: ${msg.message}`);\n if (msg.stack) failure.stack = `${msg.phase} fault: ${msg.stack}`;\n }\n received.push(msg);\n for (const fn of [...wake]) fn();\n });\n\n const check = () => {\n if (failure) throw failure;\n };\n\n const next = <T extends FromFrame['type']>(type: T): Promise<Received<T>> =>\n new Promise((resolve, reject) => {\n const poll = () => {\n if (failure) {\n wake.delete(poll);\n reject(failure);\n return;\n }\n const at = received.findIndex((m) => m.type === type);\n if (at === -1) return;\n wake.delete(poll);\n resolve(received.splice(at, 1)[0] as Received<T>);\n };\n wake.add(poll);\n poll();\n });\n\n /** The first fault, if one arrives by the next macrotask. */\n const fault = (): Promise<Error | null> =>\n new Promise((resolve) => {\n const done = () => {\n clearTimeout(timer);\n wake.delete(onMessage);\n resolve(failure);\n };\n const onMessage = () => {\n if (failure) done();\n };\n const timer = setTimeout(done, 0);\n wake.add(onMessage);\n onMessage();\n });\n\n return { next, check, fault, close: off };\n}\n\nconst settle = () =>\n new Promise<void>((resolve) => {\n requestAnimationFrame(() => setTimeout(resolve, 0));\n });\n\nexport interface RunStoryOptions {\n /** The frame config (`frameConfig`). */\n setup?: FrameSetup;\n /** Resizes the page, for a story that names a viewport. Without it, stories run at whatever size the page is. */\n viewport?: (width: number, height: number) => Promise<void>;\n}\n\n/** Stops the story a timed-out test left mounted. */\nlet leftover: (() => unknown) | null = null;\n/** The page's size before any story resized it; a story with no viewport runs at this size. */\nlet pageSize: Viewport | null = null;\n\n/**\n * Renders one story the way the workshop's frame does — defaults, no state, no globals — then plays it.\n * Throws on any fault the frame reports, or a play function that fails.\n */\nexport async function runStory(\n mod: Record<string, unknown>,\n exportName: string,\n file: string,\n autoTitle: string,\n { setup, viewport }: RunStoryOptions = {},\n): Promise<void> {\n leftover?.();\n let story: ReturnType<typeof loadStories>[number] | undefined;\n try {\n story = loadStories(mod, autoTitle, setup?.parameters).find((s) => s.exportName === exportName);\n } catch (error) {\n throw phased('load', error);\n }\n if (!story) throw new Error(`load fault: ${file} has no story export \"${exportName}\"`);\n\n pageSize ??= { width: window.innerWidth, height: window.innerHeight };\n const size = story.viewport ?? pageSize;\n if (viewport && (window.innerWidth !== size.width || window.innerHeight !== size.height)) {\n await viewport(size.width, size.height);\n }\n\n const { port1, port2 } = new MessageChannel();\n const shell = openChannel<FromFrame, ToFrame>(port1);\n const frame = openChannel<ToFrame, FromFrame>(port2);\n const messages = inbox(shell);\n const container = document.createElement('div');\n document.body.append(container);\n let stopFrame: (() => void) | undefined;\n /** Returns what `stopFrame` threw, having released everything else regardless. */\n const cleanup = (): { error: unknown } | null => {\n if (leftover === cleanup) leftover = null;\n let thrown: { error: unknown } | null = null;\n try {\n messages.close();\n stopFrame?.();\n } catch (error) {\n thrown = { error };\n } finally {\n stopFrame = undefined;\n shell.close();\n frame.close();\n container.remove();\n }\n return thrown;\n };\n leftover = cleanup;\n\n let failure: { error: unknown } | null = null;\n try {\n try {\n stopFrame = startFrame({ story, channel: frame, container, setup });\n } catch (error) {\n throw phased('mount', error);\n }\n await messages.next('ready');\n shell.send({ type: 'init', config: story.config.defaults(), state: null, globals: {} });\n await messages.next('answers');\n await settle();\n messages.check();\n if (story.play) {\n shell.send({ type: 'play' });\n const played = await messages.next('played');\n if (!played.ok) throw (await messages.fault()) ?? new Error(`play fault: ${played.message ?? 'failed'}`);\n await settle();\n }\n messages.check();\n } catch (error) {\n failure = { error };\n }\n const cleanupError = cleanup();\n if (failure) throw failure.error;\n if (cleanupError) throw phased('cleanup', cleanupError.error);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/test/runStory.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { withValueAtPath } from '@weasel-js/labkit/config';\nimport { type RefObject, useEffect, useState } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\nimport type { FrameSetup } from '../frame/FrameController';\nimport type { Viewport } from '../protocol/messages';\nimport { StoryTrial } from '../shell/StoryTrial';\nimport { loadStories } from '../story/load';\nimport type { LoadedStory } from '../story/types';\n\n/** Prefixes `error` with the phase it came from, keeping its stack. */\nfunction phased(phase: string, error: unknown): Error {\n const message = error instanceof Error ? error.message : String(error);\n const out = new Error(`${phase} fault: ${message}`, { cause: error });\n if (error instanceof Error && error.stack) out.stack = `${phase} fault: ${error.stack}`;\n return out;\n}\n\nconst settle = () =>\n new Promise<void>((resolve) => {\n requestAnimationFrame(() => setTimeout(resolve, 0));\n });\n\ninterface TestTrialProps {\n story: LoadedStory;\n setup: FrameSetup;\n hostRef: RefObject<HTMLElement | null>;\n /** The config in force after each commit, so `play` reads what the story last wrote. */\n onConfig: (config: unknown) => void;\n onRendered: () => void;\n onError: (error: unknown) => void;\n}\n\n/** The trial a story test runs in: its config and state, held the way a lab trial holds them. */\nfunction TestTrial({ story, setup, hostRef, onConfig, onRendered, onError }: TestTrialProps) {\n const [config, setConfigState] = useState<unknown>(() => story.config.defaults());\n const [state, setState] = useState<unknown>(null);\n useEffect(() => onConfig(config), [config, onConfig]);\n const ctx = {\n config,\n state,\n setConfig: (path: string, value: unknown) => setConfigState((prev: unknown) => withValueAtPath(prev, path, value)),\n setState,\n };\n return <StoryTrial story={story} setup={setup} ctx={ctx} hostRef={hostRef} onRendered={onRendered} onError={onError} />;\n}\n\nexport interface RunStoryOptions {\n /** The frame config (`frameConfig`). */\n setup?: FrameSetup;\n /** Resizes the page, for a story that names a viewport. Without it, stories run at whatever size the page is. */\n viewport?: (width: number, height: number) => Promise<void>;\n}\n\n/** Stops the story a timed-out test left mounted. */\nlet leftover: (() => unknown) | null = null;\n/** The page's size before any story resized it; a story with no viewport runs at this size. */\nlet pageSize: Viewport | null = null;\n\n/**\n * Renders one story in the test page the way the workshop does — defaults, no state, no globals — then plays it.\n * Throws on a render the story's boundary caught, or a play function that fails.\n */\nexport async function runStory(\n mod: Record<string, unknown>,\n exportName: string,\n file: string,\n autoTitle: string,\n { setup = {}, viewport }: RunStoryOptions = {},\n): Promise<void> {\n leftover?.();\n let story: LoadedStory | undefined;\n try {\n story = loadStories(mod, autoTitle, setup.parameters).find((s) => s.exportName === exportName);\n } catch (error) {\n throw phased('load', error);\n }\n if (!story) throw new Error(`load fault: ${file} has no story export \"${exportName}\"`);\n\n pageSize ??= { width: window.innerWidth, height: window.innerHeight };\n const size = story.viewport ?? pageSize;\n if (viewport && (window.innerWidth !== size.width || window.innerHeight !== size.height)) {\n await viewport(size.width, size.height);\n }\n\n const container = document.createElement('div');\n document.body.append(container);\n let root: Root | undefined;\n /** Returns what unmounting threw, having released everything else regardless. */\n const cleanup = (): { error: unknown } | null => {\n if (leftover === cleanup) leftover = null;\n let thrown: { error: unknown } | null = null;\n try {\n root?.unmount();\n } catch (error) {\n thrown = { error };\n } finally {\n root = undefined;\n container.remove();\n }\n return thrown;\n };\n leftover = cleanup;\n\n const hostRef: RefObject<HTMLElement | null> = { current: null };\n let config: unknown;\n let renderError: { error: unknown } | null = null;\n let markRendered: () => void = () => {};\n const rendered = new Promise<void>((resolve) => {\n markRendered = resolve;\n });\n const fail = (error: unknown) => {\n renderError ??= { error };\n // An error the boundary could not catch leaves nothing to announce `rendered`; the check below throws instead.\n markRendered();\n };\n const checkRender = () => {\n if (renderError) throw phased('render', renderError.error);\n };\n\n let failure: { error: unknown } | null = null;\n try {\n try {\n await setup.prepare?.(story);\n root = createRoot(container, { onUncaughtError: fail });\n root.render(\n <TestTrial\n story={story}\n setup={setup}\n hostRef={hostRef}\n onConfig={(next) => {\n config = next;\n }}\n onRendered={markRendered}\n onError={fail}\n />,\n );\n } catch (error) {\n throw phased('mount', error);\n }\n await rendered;\n await settle();\n checkRender();\n const host = hostRef.current;\n if (!host) throw new Error('render fault: the story host never mounted');\n if (story.play) {\n try {\n await story.play({ canvasElement: host, config, globals: {} });\n } catch (error) {\n throw phased('play', error);\n }\n await settle();\n checkRender();\n }\n } catch (error) {\n failure = { error };\n }\n const cleanupError = cleanup();\n if (failure) throw failure.error;\n if (cleanupError) throw phased('cleanup', cleanupError.error);\n}\n"]}
|
package/dist/vite/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { storyNameFromExport, storyId } from '../chunk-
|
|
2
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, globSync } from 'fs';
|
|
1
|
+
import { storyNameFromExport, storyId } from '../chunk-HRGPFQBW.js';
|
|
2
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, globSync, statSync } from 'fs';
|
|
3
3
|
import { resolve, relative, sep, matchesGlob, dirname, join, basename } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { parse } from '@babel/parser';
|
|
@@ -99,24 +99,31 @@ function indexProgram(program, file, autoTitle2) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
let metaNode;
|
|
102
|
+
let metaDoc;
|
|
102
103
|
const exported = [];
|
|
103
104
|
for (const stmt of program.body) {
|
|
104
|
-
if (stmt.type === "ExportDefaultDeclaration")
|
|
105
|
+
if (stmt.type === "ExportDefaultDeclaration") {
|
|
106
|
+
metaNode = stmt.declaration;
|
|
107
|
+
metaDoc = docOf(stmt);
|
|
108
|
+
}
|
|
105
109
|
if (stmt.type !== "ExportNamedDeclaration" || stmt.exportKind === "type") continue;
|
|
110
|
+
const doc = docOf(stmt);
|
|
106
111
|
const decl = stmt.declaration;
|
|
107
112
|
if (decl?.type === "VariableDeclaration") {
|
|
108
113
|
for (const d of decl.declarations) {
|
|
109
|
-
if (d.id.type === "Identifier") exported.push({ name: d.id.name, node: d.init ?? void 0 });
|
|
114
|
+
if (d.id.type === "Identifier") exported.push({ name: d.id.name, node: d.init ?? void 0, doc: docOf(d) ?? doc });
|
|
110
115
|
}
|
|
111
116
|
} else if (decl?.type === "FunctionDeclaration" && decl.id) {
|
|
112
|
-
exported.push({ name: decl.id.name, node: void 0 });
|
|
117
|
+
exported.push({ name: decl.id.name, node: void 0, doc });
|
|
113
118
|
}
|
|
114
119
|
for (const spec of stmt.specifiers) {
|
|
115
120
|
if (spec.type !== "ExportSpecifier" || spec.exportKind === "type") continue;
|
|
116
121
|
const name = exportedName(spec.exported);
|
|
117
122
|
const node = stmt.source ? void 0 : bindings.get(spec.local.name);
|
|
118
|
-
if (name === "default")
|
|
119
|
-
|
|
123
|
+
if (name === "default") {
|
|
124
|
+
metaNode = node;
|
|
125
|
+
metaDoc ??= doc;
|
|
126
|
+
} else exported.push({ name, node, doc });
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
if (!metaNode && !program.body.some(isDefaultExport)) return [];
|
|
@@ -124,21 +131,50 @@ function indexProgram(program, file, autoTitle2) {
|
|
|
124
131
|
const title = stringProp(meta, "title") ?? autoTitle2;
|
|
125
132
|
const include = storyFilter(meta, "includeStories");
|
|
126
133
|
const exclude = storyFilter(meta, "excludeStories");
|
|
127
|
-
|
|
134
|
+
const componentNode = prop(meta, "component");
|
|
135
|
+
const componentName = componentNode?.type === "Identifier" ? componentNode.name : void 0;
|
|
136
|
+
const metaIsolate = isolateOf(meta, bindings, wrappers, "default");
|
|
137
|
+
return exported.filter(({ name }) => name !== "__namedExportsOrder").filter(({ name }) => (!include || include(name)) && (!exclude || !exclude(name))).map(({ name, node, doc }) => {
|
|
128
138
|
const spec = objectOf(node, bindings, wrappers);
|
|
139
|
+
const isolate = isolateOf(spec, bindings, wrappers, name) ?? metaIsolate;
|
|
129
140
|
return {
|
|
130
141
|
id: storyId(title, name),
|
|
131
142
|
title,
|
|
132
143
|
name: stringProp(spec, "name") ?? stringProp(spec, "storyName") ?? storyNameFromExport(name),
|
|
133
144
|
exportName: name,
|
|
134
|
-
file
|
|
145
|
+
file,
|
|
146
|
+
...doc === void 0 ? {} : { description: doc },
|
|
147
|
+
...metaDoc === void 0 ? {} : { componentDescription: metaDoc },
|
|
148
|
+
...componentName === void 0 ? {} : { componentName },
|
|
149
|
+
...isolate === void 0 ? {} : { isolate }
|
|
135
150
|
};
|
|
136
151
|
});
|
|
137
152
|
}
|
|
153
|
+
function isolateOf(obj, bindings, wrappers, exportName) {
|
|
154
|
+
const parameters = objectOf(prop(obj, "parameters"), bindings, wrappers);
|
|
155
|
+
const forge2 = objectOf(prop(parameters, "forge"), bindings, wrappers);
|
|
156
|
+
const value = prop(obj, "isolate") ?? prop(forge2, "isolate");
|
|
157
|
+
if (value === void 0) return void 0;
|
|
158
|
+
const literal = stringLiteral(value);
|
|
159
|
+
if (literal === void 0) throw new Error(`${exportName}: isolate must be a string literal`);
|
|
160
|
+
return literal;
|
|
161
|
+
}
|
|
162
|
+
function stringLiteral(node) {
|
|
163
|
+
if (node.type === "StringLiteral") return node.value;
|
|
164
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis.map((q) => q.value.cooked ?? q.value.raw).join("");
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
138
167
|
function isDefaultExport(stmt) {
|
|
139
168
|
if (stmt.type === "ExportDefaultDeclaration") return true;
|
|
140
169
|
return stmt.type === "ExportNamedDeclaration" && stmt.specifiers.some((s) => s.type === "ExportSpecifier" && exportedName(s.exported) === "default");
|
|
141
170
|
}
|
|
171
|
+
function docOf(node) {
|
|
172
|
+
const comments = node.leadingComments;
|
|
173
|
+
const last = comments?.[comments.length - 1];
|
|
174
|
+
if (last?.type !== "CommentBlock" || !last.value.startsWith("*")) return void 0;
|
|
175
|
+
const text = last.value.slice(1).split("\n").map((line) => line.replace(/^\s*\*/, "").trim()).join("\n").trim();
|
|
176
|
+
return text || void 0;
|
|
177
|
+
}
|
|
142
178
|
function exportedName(node) {
|
|
143
179
|
return node.type === "Identifier" ? node.name : node.value;
|
|
144
180
|
}
|
|
@@ -224,7 +260,7 @@ function forge(options) {
|
|
|
224
260
|
let logger;
|
|
225
261
|
const glob = () => {
|
|
226
262
|
const files2 = options.stories.flatMap(
|
|
227
|
-
(pattern) => globSync(pattern, { cwd: root, exclude: (name) => basename(String(name)) === "node_modules" }).map((f) => resolve(root, f)).sort()
|
|
263
|
+
(pattern) => globSync(pattern, { cwd: root, exclude: (name) => basename(String(name)) === "node_modules" }).map((f) => resolve(root, f)).filter((f) => statSync(f).isFile()).sort()
|
|
228
264
|
);
|
|
229
265
|
return [...new Set(files2)];
|
|
230
266
|
};
|
|
@@ -266,15 +302,29 @@ ${lines.join("\n")}
|
|
|
266
302
|
import '@weasel-js/labkit/styles.css';
|
|
267
303
|
import '@weasel-js/forge/shell.css';
|
|
268
304
|
import index from 'virtual:forge/index.js';
|
|
305
|
+
import importers from 'virtual:forge/importers.js';
|
|
269
306
|
import config from 'virtual:forge/shell-config.js';
|
|
307
|
+
import setup from 'virtual:forge/frame-config.js';
|
|
270
308
|
|
|
271
309
|
const workshop = mountWorkshop({
|
|
272
310
|
index,
|
|
311
|
+
importers,
|
|
312
|
+
setup,
|
|
273
313
|
config,
|
|
274
314
|
frameUrl: ${JSON.stringify(`${base}frame.html`)},
|
|
275
315
|
stories: ${JSON.stringify(options.stories)},
|
|
276
316
|
});
|
|
277
317
|
import.meta.hot?.on('forge:index', (next) => workshop.setIndex(next));
|
|
318
|
+
// A story edit reaches here as an update of the importers module, whose fresh import() URLs carry the new
|
|
319
|
+
// timestamps; the file it names arrives just before, so the reload waits for the importers that can fetch it.
|
|
320
|
+
const stale = new Set();
|
|
321
|
+
import.meta.hot?.on('forge:story', ({ file }) => stale.add(file));
|
|
322
|
+
import.meta.hot?.accept('virtual:forge/importers.js', (next) => {
|
|
323
|
+
if (!next) return;
|
|
324
|
+
workshop.setImporters(next.default);
|
|
325
|
+
for (const file of stale) workshop.reloadStory(file);
|
|
326
|
+
stale.clear();
|
|
327
|
+
});
|
|
278
328
|
`,
|
|
279
329
|
"frame-entry.js": () => `import { mountFrame } from '@weasel-js/forge/frame';
|
|
280
330
|
import '@weasel-js/forge/frame.css';
|
|
@@ -283,10 +333,14 @@ import importers from 'virtual:forge/importers.js';
|
|
|
283
333
|
import setup from 'virtual:forge/frame-config.js';
|
|
284
334
|
|
|
285
335
|
mountFrame({ index, importers, setup });
|
|
336
|
+
// The importers module is shared with the workshop page, which accepts a story edit in place; a frame document
|
|
337
|
+
// showing the old module reloads instead, and without this the edit would dead-end here and reload every page.
|
|
338
|
+
import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
286
339
|
`
|
|
287
340
|
};
|
|
288
341
|
function reindex(server, file, event) {
|
|
289
342
|
const current = files();
|
|
343
|
+
if (event === "change" && current.has(file)) server.ws.send({ type: "custom", event: "forge:story", data: { file } });
|
|
290
344
|
if (event === "change" && !current.has(file)) return;
|
|
291
345
|
if (event === "add" && !matches(file)) return;
|
|
292
346
|
if (event === "unlink" && !current.has(file)) return;
|
|
@@ -398,6 +452,8 @@ function forgeTest(options) {
|
|
|
398
452
|
`import { test as __forge_test } from "vitest";`,
|
|
399
453
|
`import { page as __forge_page } from "vitest/browser";`,
|
|
400
454
|
`import { runStory as __forge_runStory } from "@weasel-js/forge/test";`,
|
|
455
|
+
// shell.css draws the story host; frame.css still holds the boundary's fault rule.
|
|
456
|
+
`import "@weasel-js/forge/shell.css";`,
|
|
401
457
|
`import "@weasel-js/forge/frame.css";`,
|
|
402
458
|
...options.frameConfig ? [`import __forge_frame_config from ${q(resolve(root, options.frameConfig))};`] : [],
|
|
403
459
|
`const __forge_file = ${q(id)};`,
|
|
@@ -408,8 +464,9 @@ function forgeTest(options) {
|
|
|
408
464
|
" viewport: (width, height) => __forge_page.viewport(width, height),",
|
|
409
465
|
"};",
|
|
410
466
|
"const __forge_run = async (exportName) => __forge_runStory(await import(/* @vite-ignore */ import.meta.url), exportName, __forge_file, __forge_title, __forge_options);",
|
|
467
|
+
// An isolated story asks for a document of its own, which the test page is not; its reason goes in the name.
|
|
411
468
|
...entries.map(
|
|
412
|
-
(entry) => `__forge_test(${q(entry.name)}, () => __forge_run(${q(entry.exportName)}), __forge_timeout);`
|
|
469
|
+
(entry) => entry.isolate === void 0 ? `__forge_test(${q(entry.name)}, () => __forge_run(${q(entry.exportName)}), __forge_timeout);` : `__forge_test.skip(${q(`${entry.name} (isolated: ${entry.isolate})`)}, () => {});`
|
|
413
470
|
)
|
|
414
471
|
];
|
|
415
472
|
return { code: `${code}
|