@weasel-js/forge 1.7.3 → 1.8.1

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 (46) hide show
  1. package/README.md +78 -0
  2. package/dist/{FrameController-RdGggdGA.d.ts → FrameController-BH8xJHZ8.d.ts} +36 -1
  3. package/dist/{chunk-NYBMOSSR.js → chunk-5N4YP5HS.js} +166 -10
  4. package/dist/chunk-5N4YP5HS.js.map +1 -0
  5. package/dist/chunk-CJX3W5HY.js +151 -0
  6. package/dist/chunk-CJX3W5HY.js.map +1 -0
  7. package/dist/{chunk-SEK6EKHO.js → chunk-GHTVXHNJ.js} +72 -10
  8. package/dist/chunk-GHTVXHNJ.js.map +1 -0
  9. package/dist/{chunk-KIFCYY4F.js → chunk-JQD3MVUQ.js} +4 -4
  10. package/dist/{chunk-KIFCYY4F.js.map → chunk-JQD3MVUQ.js.map} +1 -1
  11. package/dist/chunk-KJGXJD3U.js +15 -0
  12. package/dist/chunk-KJGXJD3U.js.map +1 -0
  13. package/dist/chunk-L5XM5VKB.js +47 -0
  14. package/dist/chunk-L5XM5VKB.js.map +1 -0
  15. package/dist/{chunk-YKN3IAEX.js → chunk-MGQWCPYE.js} +4 -24
  16. package/dist/chunk-MGQWCPYE.js.map +1 -0
  17. package/dist/{chunk-JNJGIUYU.js → chunk-NI5FYQHW.js} +8 -5
  18. package/dist/chunk-NI5FYQHW.js.map +1 -0
  19. package/dist/{config-BJIgUhiI.d.ts → config-DoLWwEb2.d.ts} +5 -1
  20. package/dist/csf/preview-api.js +2 -1
  21. package/dist/csf/preview-api.js.map +1 -1
  22. package/dist/frame/index.css +1 -0
  23. package/dist/frame/index.css.map +1 -1
  24. package/dist/frame/index.d.ts +2 -2
  25. package/dist/frame/index.js +5 -4
  26. package/dist/frame/index.js.map +1 -1
  27. package/dist/index.d.ts +79 -4
  28. package/dist/index.js +4 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/shell/index.css +14 -0
  31. package/dist/shell/index.css.map +1 -1
  32. package/dist/shell/index.d.ts +2 -2
  33. package/dist/shell/index.js +225 -56
  34. package/dist/shell/index.js.map +1 -1
  35. package/dist/test/index.d.ts +1 -1
  36. package/dist/test/index.js +6 -5
  37. package/dist/test/index.js.map +1 -1
  38. package/dist/vite/index.js +160 -16
  39. package/dist/vite/index.js.map +1 -1
  40. package/package.json +5 -5
  41. package/dist/chunk-JNJGIUYU.js.map +0 -1
  42. package/dist/chunk-NYBMOSSR.js.map +0 -1
  43. package/dist/chunk-SEK6EKHO.js.map +0 -1
  44. package/dist/chunk-Y3WZ2EWR.js +0 -15
  45. package/dist/chunk-Y3WZ2EWR.js.map +0 -1
  46. package/dist/chunk-YKN3IAEX.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { F as FrameSetup } from '../FrameController-RdGggdGA.js';
1
+ import { F as FrameSetup } from '../FrameController-BH8xJHZ8.js';
2
2
  import '@weasel-js/labkit/config';
3
3
  import 'react';
4
4
 
@@ -1,9 +1,10 @@
1
- import { StoryTrial } from '../chunk-SEK6EKHO.js';
2
- import '../chunk-JNJGIUYU.js';
3
- import { loadStories } from '../chunk-NYBMOSSR.js';
4
- import '../chunk-Y3WZ2EWR.js';
1
+ import { StoryTrial } from '../chunk-GHTVXHNJ.js';
2
+ import '../chunk-NI5FYQHW.js';
3
+ import { loadStories } from '../chunk-5N4YP5HS.js';
4
+ import '../chunk-L5XM5VKB.js';
5
5
  import '../chunk-HRGPFQBW.js';
6
- import '../chunk-YKN3IAEX.js';
6
+ import '../chunk-MGQWCPYE.js';
7
+ import '../chunk-KJGXJD3U.js';
7
8
  import { withValueAtPath } from '@weasel-js/labkit/config';
8
9
  import { useState, useEffect } from 'react';
9
10
  import { createRoot } from 'react-dom/client';
@@ -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 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\n/** Options for `runStory`. */\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"]}
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\n/** Options for `runStory`. */\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"]}
@@ -1,6 +1,6 @@
1
1
  import { isGallery } from '../chunk-WEQSLPKT.js';
2
2
  import { storyNameFromExport, storyId } from '../chunk-HRGPFQBW.js';
3
- import { existsSync, readFileSync, mkdirSync, writeFileSync, globSync, statSync } from 'fs';
3
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, globSync, statSync } from 'fs';
4
4
  import { resolve, sep, relative, matchesGlob, dirname, join, basename } from 'path';
5
5
  import { parse } from '@babel/parser';
6
6
  import { fileURLToPath } from 'url';
@@ -344,17 +344,63 @@ function namesIn(node) {
344
344
  walk(node);
345
345
  return out;
346
346
  }
347
- function indexFile(code, file, autoTitle2) {
347
+ function indexFile(code, file, autoTitle2, wrappers = /* @__PURE__ */ new Set()) {
348
348
  try {
349
- const plugins = ["typescript", "decorators-legacy", .../\.[jt]sx$/.test(file) ? ["jsx"] : []];
350
- return indexProgram(parse(code, { sourceType: "module", plugins }).program, file, autoTitle2);
349
+ return indexProgram(parseFile(code, file), file, autoTitle2, wrappers);
351
350
  } catch (err) {
352
351
  throw new Error(`${file}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
353
352
  }
354
353
  }
355
- function indexProgram(program, file, autoTitle2) {
354
+ function foreignCallees(code, file) {
355
+ const program = parseFile(code, file);
356
+ const { bindings } = scan(program, /* @__PURE__ */ new Set());
357
+ const imported = /* @__PURE__ */ new Map();
358
+ const exported = [];
359
+ for (const stmt of program.body) {
360
+ if (stmt.type === "ImportDeclaration" && stmt.importKind !== "type" && stmt.source.value !== "@weasel-js/forge") {
361
+ for (const spec of stmt.specifiers) {
362
+ if (spec.type === "ImportNamespaceSpecifier" || spec.type === "ImportSpecifier" && spec.importKind === "type") continue;
363
+ const name = spec.type === "ImportDefaultSpecifier" ? "default" : exportedName(spec.imported);
364
+ imported.set(spec.local.name, { local: spec.local.name, spec: stmt.source.value, imported: name });
365
+ }
366
+ }
367
+ if (stmt.type === "ExportDefaultDeclaration") exported.push(stmt.declaration);
368
+ if (stmt.type !== "ExportNamedDeclaration" || stmt.exportKind === "type") continue;
369
+ if (stmt.declaration?.type === "VariableDeclaration") exported.push(...stmt.declaration.declarations.map((d) => d.init));
370
+ if (stmt.source) continue;
371
+ for (const spec of stmt.specifiers) if (spec.type === "ExportSpecifier") exported.push(bindings.get(exportedName(spec.local)));
372
+ }
373
+ const found = /* @__PURE__ */ new Map();
374
+ for (const node of exported) {
375
+ const hit = imported.get(calleeOf(node ?? void 0, bindings) ?? "");
376
+ if (hit) found.set(hit.local, hit);
377
+ }
378
+ return [...found.values()];
379
+ }
380
+ function parseFile(code, file) {
381
+ const plugins = ["typescript", "decorators-legacy", .../\.[jt]sx$/.test(file) ? ["jsx"] : []];
382
+ return parse(code, { sourceType: "module", plugins }).program;
383
+ }
384
+ function calleeOf(node, bindings, depth = 0) {
385
+ if (!node || depth > 16) return void 0;
386
+ switch (node.type) {
387
+ case "TSSatisfiesExpression":
388
+ case "TSAsExpression":
389
+ case "TSTypeAssertion":
390
+ case "TSNonNullExpression":
391
+ case "ParenthesizedExpression":
392
+ return calleeOf(node.expression, bindings, depth + 1);
393
+ case "CallExpression":
394
+ return node.callee.type === "Identifier" ? node.callee.name : void 0;
395
+ case "Identifier":
396
+ return calleeOf(bindings.get(node.name), bindings, depth + 1);
397
+ default:
398
+ return void 0;
399
+ }
400
+ }
401
+ function scan(program, extra) {
356
402
  const bindings = /* @__PURE__ */ new Map();
357
- const wrappers = /* @__PURE__ */ new Set();
403
+ const wrappers = new Set(extra);
358
404
  for (const stmt of program.body) {
359
405
  if (stmt.type === "ImportDeclaration" && stmt.source.value === "@weasel-js/forge") {
360
406
  for (const spec of stmt.specifiers) {
@@ -368,6 +414,10 @@ function indexProgram(program, file, autoTitle2) {
368
414
  for (const d of decl.declarations) if (d.id.type === "Identifier" && d.init) bindings.set(d.id.name, d.init);
369
415
  }
370
416
  }
417
+ return { bindings, wrappers };
418
+ }
419
+ function indexProgram(program, file, autoTitle2, extra) {
420
+ const { bindings, wrappers } = scan(program, extra);
371
421
  let metaNode;
372
422
  let metaDoc;
373
423
  const exported = [];
@@ -536,6 +586,67 @@ function storybookShims() {
536
586
  };
537
587
  }
538
588
 
589
+ // src/vite/wrappers.ts
590
+ function createWrapperResolver(read) {
591
+ const modules = /* @__PURE__ */ new Map();
592
+ const info = (file) => {
593
+ if (!modules.has(file)) {
594
+ let parsed = null;
595
+ try {
596
+ parsed = moduleInfo(read(file), file);
597
+ } catch {
598
+ parsed = null;
599
+ }
600
+ modules.set(file, parsed);
601
+ }
602
+ return modules.get(file) ?? null;
603
+ };
604
+ return async (file, callees, resolve4) => {
605
+ const reads = /* @__PURE__ */ new Set();
606
+ const wrappers = /* @__PURE__ */ new Set();
607
+ if (callees.length === 0) return { wrappers, reads };
608
+ const target = async (spec, importer) => {
609
+ const id = (await resolve4(spec, importer).catch(() => null))?.split("?")[0];
610
+ return id && !id.startsWith("\0") ? id : null;
611
+ };
612
+ const declaring = async (at, name, depth = 0) => {
613
+ reads.add(at);
614
+ const mod = info(at);
615
+ if (!mod || depth > 32) return null;
616
+ const exp = mod.exports.get(name);
617
+ if (exp?.kind === "local") {
618
+ const imp = mod.imports.get(exp.local);
619
+ if (!imp) return `${at}#${exp.local}`;
620
+ if (imp.imported === "*") return null;
621
+ const next = await target(imp.spec, at);
622
+ return next ? declaring(next, imp.imported, depth + 1) : null;
623
+ }
624
+ if (exp?.kind === "from") {
625
+ const next = await target(exp.spec, at);
626
+ return next ? declaring(next, exp.imported, depth + 1) : null;
627
+ }
628
+ if (name === "default") return null;
629
+ for (const spec of mod.stars) {
630
+ const next = await target(spec, at);
631
+ const found = next ? await declaring(next, name, depth + 1) : null;
632
+ if (found) return found;
633
+ }
634
+ return null;
635
+ };
636
+ const forge2 = await target("@weasel-js/forge", file);
637
+ if (!forge2) return { wrappers, reads };
638
+ const own = new Set(
639
+ (await Promise.all(["meta", "story"].map((name) => declaring(forge2, name)))).filter((key) => key !== null)
640
+ );
641
+ for (const callee of callees) {
642
+ const from = await target(callee.spec, file);
643
+ const key = from ? await declaring(from, callee.imported) : null;
644
+ if (key && own.has(key)) wrappers.add(callee.local);
645
+ }
646
+ return { wrappers, reads };
647
+ };
648
+ }
649
+
539
650
  // src/vite/plugin.ts
540
651
  var PREFIX = "virtual:forge/";
541
652
  var MODULES = /* @__PURE__ */ new Set(["index.js", "importers.js", "deps.js", "frame-config.js", "shell-config.js", "shell-entry.js", "frame-entry.js"]);
@@ -548,6 +659,8 @@ function forge(options) {
548
659
  let deps = null;
549
660
  let depsSent = null;
550
661
  let depsPending = null;
662
+ const wrappersOf = /* @__PURE__ */ new Map();
663
+ let settledAll = null;
551
664
  const glob = () => {
552
665
  const files2 = options.stories.flatMap(
553
666
  (pattern) => globSync(pattern, { cwd: root, exclude: (name) => basename(String(name)) === "node_modules" }).map((f) => resolve(root, f)).filter((f) => statSync(f).isFile()).sort()
@@ -558,7 +671,7 @@ function forge(options) {
558
671
  const path = relative(root, file).split(sep).join("/");
559
672
  return !path.split("/").includes("node_modules") && options.stories.some((pattern) => matchesGlob(path, pattern));
560
673
  };
561
- const read = (file) => indexFile(readFileSync(file, "utf8"), file, autoTitle(file, root, options.stories));
674
+ const read = (file) => indexFile(readFileSync(file, "utf8"), file, autoTitle(file, root, options.stories), wrappersOf.get(file)?.wrappers);
562
675
  const logError = (err) => logger?.error(`[forge] ${err instanceof Error ? err.message : String(err)}`);
563
676
  const files = () => {
564
677
  byFile ??= new Map(
@@ -574,6 +687,27 @@ function forge(options) {
574
687
  return byFile;
575
688
  };
576
689
  const index = () => [...files().values()].flat();
690
+ const settle = async (targets, resolveImport) => {
691
+ const decide = createWrapperResolver((file) => readFileSync(file, "utf8"));
692
+ const current = files();
693
+ for (const file of targets) {
694
+ if (!current.has(file)) continue;
695
+ let next;
696
+ try {
697
+ next = await decide(file, foreignCallees(readFileSync(file, "utf8"), file), resolveImport);
698
+ } catch {
699
+ continue;
700
+ }
701
+ const before = wrappersOf.get(file)?.wrappers ?? /* @__PURE__ */ new Set();
702
+ wrappersOf.set(file, next);
703
+ if (before.size === next.wrappers.size && [...before].every((name) => next.wrappers.has(name))) continue;
704
+ try {
705
+ current.set(file, read(file));
706
+ } catch (err) {
707
+ logError(err);
708
+ }
709
+ }
710
+ };
577
711
  const depGraph = () => deps ??= createDepGraph({ root, read: (file) => readFileSync(file, "utf8") });
578
712
  const configModule = (path) => path ? `export { default } from ${JSON.stringify(resolve(root, path))};
579
713
  ` : "export default {};\n";
@@ -654,23 +788,26 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
654
788
  depsPending = null;
655
789
  });
656
790
  }
657
- function reindex(server, file, event) {
791
+ async function reindex(server, file, event) {
658
792
  const current = files();
659
793
  if (event === "change" && current.has(file)) server.ws.send({ type: "custom", event: "forge:story", data: { file } });
660
- if (event === "change" && !current.has(file)) return false;
661
- if (event === "add" && !matches(file)) return false;
662
- if (event === "unlink" && !current.has(file)) return false;
794
+ const own = event === "add" ? matches(file) : current.has(file);
795
+ const through = [...wrappersOf].filter(([story, { reads }]) => story !== file && reads.has(file)).map(([story]) => story);
796
+ if (!own && through.length === 0) return false;
663
797
  const before = JSON.stringify(index());
664
- if (event === "unlink") {
798
+ if (own && event === "unlink") {
665
799
  current.delete(file);
666
- } else {
800
+ wrappersOf.delete(file);
801
+ } else if (own) {
667
802
  try {
668
803
  current.set(file, read(file));
669
804
  } catch (err) {
670
805
  logError(err);
671
- return false;
806
+ if (through.length === 0) return false;
672
807
  }
673
808
  }
809
+ const resolveImport = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
810
+ await settle([...own && event !== "unlink" ? [file] : [], ...through], resolveImport);
674
811
  const next = index();
675
812
  if (JSON.stringify(next) === before) return false;
676
813
  invalidate(server, ["index.js", "importers.js"]);
@@ -703,6 +840,8 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
703
840
  base = config.base;
704
841
  logger = config.logger;
705
842
  byFile = null;
843
+ wrappersOf.clear();
844
+ settledAll = null;
706
845
  deps = null;
707
846
  depsSent = null;
708
847
  },
@@ -712,6 +851,10 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
712
851
  async load(id) {
713
852
  if (!id.startsWith(`\0${PREFIX}`)) return void 0;
714
853
  const name = id.slice(PREFIX.length + 1);
854
+ if (name === "index.js") {
855
+ settledAll ??= settle([...files().keys()], async (spec, importer) => (await this.resolve(spec, importer))?.id ?? null);
856
+ await settledAll;
857
+ }
715
858
  if (name !== "deps.js") return modules[name]?.();
716
859
  const graph = await depGraph().build(index(), async (spec, importer) => (await this.resolve(spec, importer))?.id ?? null);
717
860
  depsSent = JSON.stringify(graph);
@@ -723,11 +866,12 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
723
866
  for (const event of ["add", "change", "unlink"]) {
724
867
  server.watcher.on(event, (path) => {
725
868
  const file = resolve(path);
726
- const indexed = reindex(server, file, event);
727
869
  const added = event === "add" && /\.[cm]?[jt]sx?$/.test(file) && !file.split(sep).includes("node_modules");
728
870
  if (added) depGraph().reset();
729
871
  const read2 = added || event !== "add" && depGraph().invalidate(file);
730
- if (indexed || read2) redeps(server);
872
+ reindex(server, file, event).then((indexed) => {
873
+ if (indexed || read2) redeps(server);
874
+ }, logError);
731
875
  });
732
876
  }
733
877
  server.middlewares.use((req, res, next) => {