@weasel-js/forge 1.13.0 → 1.14.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 (51) hide show
  1. package/README.md +1 -1
  2. package/dist/{FrameController-DgfVaENb.d.ts → FrameController-BM6xuEfY.d.ts} +6 -10
  3. package/dist/{chunk-7XJNVJB5.js → chunk-B26DWEYT.js} +3 -3
  4. package/dist/{chunk-7XJNVJB5.js.map → chunk-B26DWEYT.js.map} +1 -1
  5. package/dist/{chunk-3BAM6M26.js → chunk-FPF2JSWB.js} +3 -3
  6. package/dist/chunk-FPF2JSWB.js.map +1 -0
  7. package/dist/{chunk-ORMMZ5PV.js → chunk-MRYXJTDO.js} +7 -3
  8. package/dist/chunk-MRYXJTDO.js.map +1 -0
  9. package/dist/{chunk-GHPMNT2A.js → chunk-PGLOYXDP.js} +184 -184
  10. package/dist/chunk-PGLOYXDP.js.map +1 -0
  11. package/dist/{chunk-WFT5IOHX.js → chunk-PPB7VP6R.js} +5 -4
  12. package/dist/chunk-PPB7VP6R.js.map +1 -0
  13. package/dist/{chunk-COT4I347.js → chunk-SFKDHXVE.js} +26 -4
  14. package/dist/chunk-SFKDHXVE.js.map +1 -0
  15. package/dist/chunk-STK2AZH2.js +11 -0
  16. package/dist/chunk-STK2AZH2.js.map +1 -0
  17. package/dist/chunk-TT4ZOYB6.js +143 -0
  18. package/dist/chunk-TT4ZOYB6.js.map +1 -0
  19. package/dist/{chunk-XIBKYSRU.js → chunk-UDEUT7AG.js} +124 -80
  20. package/dist/chunk-UDEUT7AG.js.map +1 -0
  21. package/dist/{config-6fWf87Tt.d.ts → config-DOjWyIui.d.ts} +1 -1
  22. package/dist/csf/preview-api.js +2 -2
  23. package/dist/frame/index.css +0 -2
  24. package/dist/frame/index.css.map +1 -1
  25. package/dist/frame/index.d.ts +4 -4
  26. package/dist/frame/index.js +9 -6
  27. package/dist/frame/index.js.map +1 -1
  28. package/dist/index.d.ts +11 -5
  29. package/dist/index.js +4 -4
  30. package/dist/{mountFrame-Bs7u1ro-.d.ts → mountFrame-CRPAjg1M.d.ts} +1 -1
  31. package/dist/shell/index.css +164 -147
  32. package/dist/shell/index.css.map +1 -1
  33. package/dist/shell/index.d.ts +23 -5
  34. package/dist/shell/index.js +454 -318
  35. package/dist/shell/index.js.map +1 -1
  36. package/dist/test/index.d.ts +1 -1
  37. package/dist/test/index.js +8 -10
  38. package/dist/test/index.js.map +1 -1
  39. package/dist/vite/index.js +118 -30
  40. package/dist/vite/index.js.map +1 -1
  41. package/package.json +11 -9
  42. package/dist/chunk-2ZBZRBHI.js +0 -10
  43. package/dist/chunk-2ZBZRBHI.js.map +0 -1
  44. package/dist/chunk-3BAM6M26.js.map +0 -1
  45. package/dist/chunk-COT4I347.js.map +0 -1
  46. package/dist/chunk-GHPMNT2A.js.map +0 -1
  47. package/dist/chunk-L5XM5VKB.js +0 -47
  48. package/dist/chunk-L5XM5VKB.js.map +0 -1
  49. package/dist/chunk-ORMMZ5PV.js.map +0 -1
  50. package/dist/chunk-WFT5IOHX.js.map +0 -1
  51. package/dist/chunk-XIBKYSRU.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { F as FrameSetup } from '../FrameController-DgfVaENb.js';
1
+ import { F as FrameSetup } from '../FrameController-BM6xuEfY.js';
2
2
  import '@weasel-js/icons';
3
3
  import '@weasel-js/labkit/config';
4
4
  import 'react';
@@ -1,10 +1,10 @@
1
- import { StoryTrial } from '../chunk-XIBKYSRU.js';
2
- import '../chunk-3BAM6M26.js';
3
- import { loadStories } from '../chunk-GHPMNT2A.js';
4
- import '../chunk-L5XM5VKB.js';
1
+ import { StoryTrial } from '../chunk-UDEUT7AG.js';
2
+ import '../chunk-FPF2JSWB.js';
3
+ import { loadStories } from '../chunk-PGLOYXDP.js';
4
+ import '../chunk-TT4ZOYB6.js';
5
5
  import '../chunk-5WS67HMU.js';
6
- import '../chunk-7XJNVJB5.js';
7
- import '../chunk-2ZBZRBHI.js';
6
+ import '../chunk-B26DWEYT.js';
7
+ import '../chunk-STK2AZH2.js';
8
8
  import { withValueAtPath } from '@weasel-js/labkit/config';
9
9
  import { useState, useEffect } from 'react';
10
10
  import { createRoot } from 'react-dom/client';
@@ -42,10 +42,8 @@ async function runStory(mod, exportName, file, title, { setup = {} } = {}) {
42
42
  }
43
43
  if (!story) throw new Error(`load fault: ${file} has no story export "${exportName}"`);
44
44
  const container = document.createElement("div");
45
- if (story.viewport) {
46
- container.style.width = `${story.viewport.width}px`;
47
- container.style.height = `${story.viewport.height}px`;
48
- }
45
+ if (story.viewport) container.style.width = `${story.viewport.width}px`;
46
+ container.style.height = story.viewport ? `${story.viewport.height}px` : "100vh";
49
47
  document.body.append(container);
50
48
  let root;
51
49
  const cleanup = () => {
@@ -1 +1 @@
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 { 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\n/** Options for `runStory`. */\nexport interface RunStoryOptions {\n /** The frame config (`frameConfig`). */\n setup?: FrameSetup;\n}\n\n/** Stops the story a timed-out test left mounted. */\nlet leftover: (() => unknown) | 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 /** The file's index entry title. */\n title: string,\n { setup = {} }: RunStoryOptions = {},\n): Promise<void> {\n leftover?.();\n let story: LoadedStory | undefined;\n try {\n story = loadStories(mod, title, 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 const container = document.createElement('div');\n // The workshop gives a story's viewport to its stage, a box of that size, not to the window.\n if (story.viewport) {\n container.style.width = `${story.viewport.width}px`;\n container.style.height = `${story.viewport.height}px`;\n }\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"]}
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 { 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\n/** Options for `runStory`. */\nexport interface RunStoryOptions {\n /** The frame config (`frameConfig`). */\n setup?: FrameSetup;\n}\n\n/** Stops the story a timed-out test left mounted. */\nlet leftover: (() => unknown) | 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 /** The file's index entry title. */\n title: string,\n { setup = {} }: RunStoryOptions = {},\n): Promise<void> {\n leftover?.();\n let story: LoadedStory | undefined;\n try {\n story = loadStories(mod, title, 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 const container = document.createElement('div');\n // The workshop gives a story's viewport to its stage, a box of that size, not to the window.\n if (story.viewport) container.style.width = `${story.viewport.width}px`;\n // Without one the stage still fills the workshop's preview: an auto height lays a filling story out 0 tall.\n container.style.height = story.viewport ? `${story.viewport.height}px` : '100vh';\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"]}
@@ -1,9 +1,9 @@
1
- import { CAPTURE_PAGES, DEFAULT_TITLE, CAPTURE_PATH, parsePayload, messageOf } from '../chunk-ORMMZ5PV.js';
1
+ import { CAPTURE_PAGES, DEFAULT_TITLE, ISSUES_PATH, CAPTURE_PATH, issueLabel, parsePayload, messageOf } from '../chunk-MRYXJTDO.js';
2
2
  import { byComponent } from '../chunk-RVJHUY7W.js';
3
3
  import { isGallery } from '../chunk-VUPN73JP.js';
4
4
  import { PROP_ARG_TYPES_EXPORT, storyNameFromExport, storyId } from '../chunk-5WS67HMU.js';
5
5
  import { readFileSync, mkdtempSync, existsSync, mkdirSync, writeFileSync, globSync, statSync, watch, rmSync } from 'fs';
6
- import { resolve, sep, relative, join, dirname, basename, matchesGlob, posix } from 'path';
6
+ import { resolve, relative, sep, join, dirname, basename, matchesGlob, posix } from 'path';
7
7
  import { parse } from '@babel/parser';
8
8
  import { execFile } from 'child_process';
9
9
  import { fileURLToPath } from 'url';
@@ -1048,7 +1048,7 @@ function controlFor(reading, type, written) {
1048
1048
  const { TypeFlags } = reading.tsm;
1049
1049
  const parts = type.isUnion() ? type.types : [type];
1050
1050
  if (parts.every((part) => part.flags & TypeFlags.BooleanLike)) return { control: { type: "boolean" } };
1051
- if (parts.some((part) => part.flags & TypeFlags.String)) return { control: { type: "text" } };
1051
+ if (textLike(reading, parts)) return { control: { type: "text" } };
1052
1052
  if (type.flags & TypeFlags.Number) return { control: { type: "number" } };
1053
1053
  const options = literalsOf(reading, parts);
1054
1054
  if (options.length > 1) {
@@ -1060,6 +1060,9 @@ function controlFor(reading, type, written) {
1060
1060
  return shape && shape.name !== "enum" && !(shape.name in SCALARS) ? { control: "object", type: shape } : null;
1061
1061
  }
1062
1062
  var SCALARS = { boolean: true, string: true, number: true };
1063
+ function textLike({ tsm }, parts) {
1064
+ return parts.some((part) => part.flags & tsm.TypeFlags.String) && !parts.some((part) => part.flags & tsm.TypeFlags.Object);
1065
+ }
1063
1066
  function literalsOf({ tsm }, parts) {
1064
1067
  return parts.filter((part) => part.flags & tsm.TypeFlags.StringLiteral).map((part) => part.value);
1065
1068
  }
@@ -1072,7 +1075,7 @@ function shapeOf(reading, type, depth) {
1072
1075
  const parts = whole.isUnion() ? whole.types : [whole];
1073
1076
  if (parts.length === 0) return null;
1074
1077
  if (parts.every((part) => part.flags & TypeFlags.BooleanLike)) return { name: "boolean" };
1075
- if (parts.some((part) => part.flags & TypeFlags.String)) return { name: "string" };
1078
+ if (textLike(reading, parts)) return { name: "string" };
1076
1079
  if (whole.flags & TypeFlags.Number) return { name: "number" };
1077
1080
  const literals = literalsOf(reading, parts);
1078
1081
  if (literals.length === parts.length || literals.length > 1) return { name: "enum", value: literals };
@@ -1166,6 +1169,86 @@ export const ${PROP_ARG_TYPES_EXPORT} = ${JSON.stringify(table.argTypes)};
1166
1169
  }
1167
1170
  };
1168
1171
  }
1172
+ var runGh = (args, cwd) => new Promise((done) => {
1173
+ execFile(
1174
+ "gh",
1175
+ [...args],
1176
+ { cwd, maxBuffer: 16 << 20, env: { ...process.env, GH_PROMPT_DISABLED: "1" } },
1177
+ (error, stdout, stderr) => {
1178
+ if (!error) return done({ stdout });
1179
+ if (error.code === "ENOENT") return done({ missing: true });
1180
+ done({ failed: stderr.trim() || error.message, code: typeof error.code === "number" ? error.code : null });
1181
+ }
1182
+ );
1183
+ });
1184
+ var SCP = /^(?:[^@\s/]+@)?([\w.-]+):(?!\/)([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/;
1185
+ var URL_FORM = /^(?:ssh|git|https?):\/\/(?:[^@/\s]+@)?([\w.-]+)(?::\d+)?\/([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/;
1186
+ function repoOf(remote) {
1187
+ const match = SCP.exec(remote.trim()) ?? URL_FORM.exec(remote.trim());
1188
+ if (!match) return null;
1189
+ const [, rawHost = "", owner = "", name = ""] = match;
1190
+ const host = rawHost === "github.com" || rawHost.endsWith(".github.com") ? "github.com" : rawHost;
1191
+ const slug = `${owner}/${name}`;
1192
+ return { slug, url: `https://${host}/${slug}`, ghRepo: host === "github.com" ? slug : `${host}/${slug}` };
1193
+ }
1194
+ var attempt = async (run) => {
1195
+ try {
1196
+ return (await run()).trim() || null;
1197
+ } catch {
1198
+ return null;
1199
+ }
1200
+ };
1201
+ var GH_AUTH_EXIT = 4;
1202
+ var json = (res, status, body2) => {
1203
+ res.statusCode = status;
1204
+ res.setHeader("Content-Type", "application/json");
1205
+ res.end(JSON.stringify(body2));
1206
+ };
1207
+ function serveIssues(server, base, root, { gh = runGh, git = runGit } = {}) {
1208
+ const answer = async (component) => {
1209
+ const cwd = root();
1210
+ const [remote, head, commit] = await Promise.all([
1211
+ attempt(() => git(["remote", "get-url", "origin"], cwd)),
1212
+ attempt(() => git(["rev-parse", "--abbrev-ref", "HEAD"], cwd)),
1213
+ attempt(() => git(["rev-parse", "HEAD"], cwd))
1214
+ ]);
1215
+ const found = remote === null ? null : repoOf(remote);
1216
+ const context = {
1217
+ repo: found && { slug: found.slug, url: found.url },
1218
+ branch: head === "HEAD" ? null : head,
1219
+ commit
1220
+ };
1221
+ if (!found) return { status: 200, body: { ...context, unavailable: "This checkout\u2019s origin is not a GitHub repository." } };
1222
+ const fields = "number,title,url,createdAt";
1223
+ const result = await gh(
1224
+ ["issue", "list", "-R", found.ghRepo, "--label", issueLabel(component), "--state", "open", "--json", fields],
1225
+ cwd
1226
+ );
1227
+ if ("missing" in result) return { status: 200, body: { ...context, unavailable: "The GitHub CLI (gh) is not installed." } };
1228
+ if ("failed" in result) {
1229
+ if (result.code === GH_AUTH_EXIT || /gh auth login/.test(result.failed)) {
1230
+ return { status: 200, body: { ...context, unavailable: "The GitHub CLI (gh) is not logged in. Run gh auth login." } };
1231
+ }
1232
+ return { status: 502, body: { ...context, error: result.failed } };
1233
+ }
1234
+ let listed;
1235
+ try {
1236
+ listed = JSON.parse(result.stdout);
1237
+ } catch {
1238
+ return { status: 502, body: { ...context, error: "gh answered something that is not JSON" } };
1239
+ }
1240
+ const issues = listed.map(({ number, title, url, createdAt }) => ({ number, title, url, opened: createdAt }));
1241
+ return { status: 200, body: { ...context, issues } };
1242
+ };
1243
+ server.middlewares.use((req, res, next) => {
1244
+ const url = new URL(req.url ?? "/", "http://forge.invalid");
1245
+ if (url.pathname !== `${base}${ISSUES_PATH}`) return next();
1246
+ if (req.method !== "GET") return next();
1247
+ const component = url.searchParams.get("component")?.trim();
1248
+ if (!component) return json(res, 400, { error: "name a component: ?component=<name>" });
1249
+ answer(component).then(({ status, body: body2 }) => json(res, status, body2), next);
1250
+ });
1251
+ }
1169
1252
  var IDLE_CLOSE_MS = 12e4;
1170
1253
  var SHOT_DEADLINE_MS = 2e4;
1171
1254
  var QUEUE_CAP = 8;
@@ -1388,7 +1471,7 @@ function ownOrigin(origin, req, protocol, server) {
1388
1471
  const printed = [...server.resolvedUrls?.local ?? [], ...server.resolvedUrls?.network ?? []];
1389
1472
  return printed.some((u) => new URL(u).origin === url.origin);
1390
1473
  }
1391
- var json = (res, status, body2) => {
1474
+ var json2 = (res, status, body2) => {
1392
1475
  res.statusCode = status;
1393
1476
  res.setHeader("Content-Type", "application/json");
1394
1477
  res.end(JSON.stringify(body2));
@@ -1426,37 +1509,37 @@ function serveCapture(server, base, allowOrigins = () => []) {
1426
1509
  const capture = async (req, res) => {
1427
1510
  const origin = req.headers.origin;
1428
1511
  if (origin !== void 0 && !ownOrigin(origin, req, protocol, server)) {
1429
- return json(res, 403, { error: "a capture is only taken for this server\u2019s own pages" });
1512
+ return json2(res, 403, { error: "a capture is only taken for this server\u2019s own pages" });
1430
1513
  }
1431
1514
  const type = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
1432
- if (type !== "application/json") return json(res, 415, { error: "a capture payload is sent as application/json" });
1515
+ if (type !== "application/json") return json2(res, 415, { error: "a capture payload is sent as application/json" });
1433
1516
  const body2 = await readBody(req);
1434
1517
  if (body2 === null) {
1435
1518
  res.setHeader("Connection", "close");
1436
- json(res, 413, { error: "capture payload is too large" });
1519
+ json2(res, 413, { error: "capture payload is too large" });
1437
1520
  return void res.once("finish", () => req.destroy());
1438
1521
  }
1439
1522
  let parsed;
1440
1523
  try {
1441
1524
  parsed = JSON.parse(body2);
1442
1525
  } catch {
1443
- return json(res, 400, { error: "capture payload is not JSON" });
1526
+ return json2(res, 400, { error: "capture payload is not JSON" });
1444
1527
  }
1445
1528
  const payload = parsePayload(parsed);
1446
- if ("error" in payload) return json(res, 400, payload);
1529
+ if ("error" in payload) return json2(res, 400, payload);
1447
1530
  const key = keyOf2(payload);
1448
1531
  let png = cache.get(key);
1449
1532
  if (!png) {
1450
1533
  const result = await browser();
1451
- if ("unavailable" in result) return json(res, 503, { available: false, reason: result.unavailable });
1534
+ if ("unavailable" in result) return json2(res, 503, { available: false, reason: result.unavailable });
1452
1535
  const page = `${socketOrigin(req, protocol)}${base}${payload.isolate ? CAPTURE_PAGES.frame : CAPTURE_PAGES.shell}`;
1453
1536
  const at = revision;
1454
1537
  try {
1455
1538
  png = await result.capture(page, payload, allowOrigins());
1456
1539
  } catch (err) {
1457
1540
  const message = messageOf(err);
1458
- if (message === QUEUE_FULL) return json(res, 503, { available: false, reason: QUEUE_FULL });
1459
- return json(res, 500, { error: message });
1541
+ if (message === QUEUE_FULL) return json2(res, 503, { available: false, reason: QUEUE_FULL });
1542
+ return json2(res, 500, { error: message });
1460
1543
  }
1461
1544
  if (at === revision) cache.set(key, png);
1462
1545
  }
@@ -1466,7 +1549,7 @@ function serveCapture(server, base, allowOrigins = () => []) {
1466
1549
  };
1467
1550
  const status = async (res) => {
1468
1551
  const result = await captureAvailability();
1469
- json(res, 200, "unavailable" in result ? { available: false, reason: result.unavailable } : { available: true });
1552
+ json2(res, 200, "unavailable" in result ? { available: false, reason: result.unavailable } : { available: true });
1470
1553
  };
1471
1554
  server.middlewares.use((req, res, next) => {
1472
1555
  if (req.url?.split("?")[0] !== `${base}${CAPTURE_PATH}`) return next();
@@ -1641,6 +1724,7 @@ const workshop = mountWorkshop({
1641
1724
  config,
1642
1725
  frameUrl: ${JSON.stringify(`${base}frame.html`)},
1643
1726
  captureBase: ${JSON.stringify(base)},
1727
+ issuesBase: ${JSON.stringify(base)},
1644
1728
  stories: ${JSON.stringify(stories)},
1645
1729
  });
1646
1730
  // Read from source on first request, so the graph costs nothing until the page is up.
@@ -1757,6 +1841,21 @@ function watchProbe({ probe } = {}) {
1757
1841
 
1758
1842
  // src/vite/plugin.ts
1759
1843
  var SCRIPT2 = /\.[cm]?[jt]sx?$/;
1844
+ function watchSources(server, { files, depGraph, reindex, redeps, logError }) {
1845
+ server.watcher.add([...files]);
1846
+ void warnWhenWatchIsSilent(server);
1847
+ for (const event of ["add", "change", "unlink"]) {
1848
+ server.watcher.on(event, (path) => {
1849
+ const file = resolve(path);
1850
+ const added = event === "add" && SCRIPT2.test(file) && !file.split(sep).includes("node_modules");
1851
+ if (added) depGraph().reset();
1852
+ const read = added || event !== "add" && depGraph().invalidate(file);
1853
+ reindex(server, file, event).then((indexed) => {
1854
+ if (indexed || read) redeps(server);
1855
+ }, logError);
1856
+ });
1857
+ }
1858
+ }
1760
1859
  function forge(options) {
1761
1860
  let root = process.cwd();
1762
1861
  let base = "/";
@@ -1842,9 +1941,9 @@ function forge(options) {
1842
1941
  const resolve6 = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
1843
1942
  depsPending = Promise.resolve().then(async () => {
1844
1943
  const graph = await graphOf(resolve6);
1845
- const json2 = JSON.stringify(graph);
1846
- if (json2 === depsSent) return;
1847
- depsSent = json2;
1944
+ const json3 = JSON.stringify(graph);
1945
+ if (json3 === depsSent) return;
1946
+ depsSent = json3;
1848
1947
  invalidate(server, ["deps.js"]);
1849
1948
  server.ws.send({ type: "custom", event: "forge:deps", data: graph });
1850
1949
  }).catch(logError).finally(() => {
@@ -1949,20 +2048,9 @@ function forge(options) {
1949
2048
  for (const file of storiesReaching(modules2)) server.ws.send({ type: "custom", event: "forge:story", data: { file } });
1950
2049
  },
1951
2050
  configureServer(server) {
1952
- server.watcher.add([...files().keys()]);
1953
- void warnWhenWatchIsSilent(server);
1954
- for (const event of ["add", "change", "unlink"]) {
1955
- server.watcher.on(event, (path) => {
1956
- const file = resolve(path);
1957
- const added = event === "add" && SCRIPT2.test(file) && !file.split(sep).includes("node_modules");
1958
- if (added) depGraph().reset();
1959
- const read2 = added || event !== "add" && depGraph().invalidate(file);
1960
- reindex(server, file, event).then((indexed) => {
1961
- if (indexed || read2) redeps(server);
1962
- }, logError);
1963
- });
1964
- }
2051
+ watchSources(server, { files: [...files().keys()], depGraph, reindex, redeps, logError });
1965
2052
  capture = servePages(server, base, () => shellUnder(root));
2053
+ serveIssues(server, base, () => root);
1966
2054
  },
1967
2055
  closeServer: () => capture?.close()
1968
2056
  }