@weasel-js/forge 1.9.4 → 1.10.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 (48) hide show
  1. package/README.md +13 -7
  2. package/dist/{FrameController-BQjKhdwk.d.ts → FrameController-UeTY7ubF.d.ts} +25 -1
  3. package/dist/{chunk-B3PPHJTS.js → chunk-AP3DRQLI.js} +16 -16
  4. package/dist/chunk-AP3DRQLI.js.map +1 -0
  5. package/dist/{chunk-MQSFPRF6.js → chunk-BIAEGWGJ.js} +37 -19
  6. package/dist/chunk-BIAEGWGJ.js.map +1 -0
  7. package/dist/{chunk-JHL4ERDA.js → chunk-DFKCLZKS.js} +3 -3
  8. package/dist/chunk-DFKCLZKS.js.map +1 -0
  9. package/dist/{chunk-FRUA5WJF.js → chunk-DKWHGSBZ.js} +3 -3
  10. package/dist/{chunk-FRUA5WJF.js.map → chunk-DKWHGSBZ.js.map} +1 -1
  11. package/dist/{chunk-ZZCCIXEJ.js → chunk-E2FHBDNQ.js} +16 -25
  12. package/dist/chunk-E2FHBDNQ.js.map +1 -0
  13. package/dist/{chunk-TXLWQFF6.js → chunk-FL2MUPUL.js} +2 -2
  14. package/dist/chunk-FL2MUPUL.js.map +1 -0
  15. package/dist/{chunk-5KI5YSQ5.js → chunk-GB6PRJET.js} +3 -3
  16. package/dist/{chunk-5KI5YSQ5.js.map → chunk-GB6PRJET.js.map} +1 -1
  17. package/dist/{chunk-HRGPFQBW.js → chunk-ZAWXMUGZ.js} +6 -3
  18. package/dist/chunk-ZAWXMUGZ.js.map +1 -0
  19. package/dist/{config-Dfu-NKxd.d.ts → config-Cz7ZyGF-.d.ts} +5 -5
  20. package/dist/csf/preview-api.d.ts +5 -2
  21. package/dist/csf/preview-api.js +2 -2
  22. package/dist/csf/preview-api.js.map +1 -1
  23. package/dist/frame/index.css +19 -2
  24. package/dist/frame/index.css.map +1 -1
  25. package/dist/frame/index.d.ts +2 -2
  26. package/dist/frame/index.js +5 -5
  27. package/dist/frame/index.js.map +1 -1
  28. package/dist/index.d.ts +4 -4
  29. package/dist/index.js +3 -3
  30. package/dist/shell/index.css +41 -5
  31. package/dist/shell/index.css.map +1 -1
  32. package/dist/shell/index.d.ts +7 -6
  33. package/dist/shell/index.js +537 -334
  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 -6
  37. package/dist/test/play.d.ts +3 -5
  38. package/dist/test/play.js.map +1 -1
  39. package/dist/vite/index.d.ts +28 -1
  40. package/dist/vite/index.js +413 -79
  41. package/dist/vite/index.js.map +1 -1
  42. package/package.json +10 -5
  43. package/dist/chunk-B3PPHJTS.js.map +0 -1
  44. package/dist/chunk-HRGPFQBW.js.map +0 -1
  45. package/dist/chunk-JHL4ERDA.js.map +0 -1
  46. package/dist/chunk-MQSFPRF6.js.map +0 -1
  47. package/dist/chunk-TXLWQFF6.js.map +0 -1
  48. package/dist/chunk-ZZCCIXEJ.js.map +0 -1
@@ -1,10 +1,12 @@
1
1
  import { DEFAULT_TITLE } from '../chunk-5DXUNLV2.js';
2
2
  import { isGallery } from '../chunk-WEQSLPKT.js';
3
- import { storyNameFromExport, storyId } from '../chunk-HRGPFQBW.js';
4
- import { readFileSync, existsSync, mkdirSync, writeFileSync, globSync, statSync } from 'fs';
5
- import { resolve, sep, relative, dirname, join, basename, matchesGlob } from 'path';
3
+ import { PROP_ARG_TYPES_EXPORT, storyNameFromExport, storyId } from '../chunk-ZAWXMUGZ.js';
4
+ import { readFileSync, mkdtempSync, existsSync, mkdirSync, writeFileSync, globSync, statSync, watch, rmSync } from 'fs';
5
+ import { resolve, sep, relative, join, dirname, basename, matchesGlob, posix } from 'path';
6
6
  import { parse } from '@babel/parser';
7
+ import { execFile } from 'child_process';
7
8
  import { fileURLToPath } from 'url';
9
+ import { tmpdir } from 'os';
8
10
 
9
11
  // src/vite/html.ts
10
12
  var escaped = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -79,7 +81,7 @@ function createDepGraph({ root, read }) {
79
81
  reset() {
80
82
  resolutions.clear();
81
83
  },
82
- async build(entries, resolve4) {
84
+ async build(entries, resolve5) {
83
85
  const resolveFrom = (spec, importer) => {
84
86
  let byImporter = resolutions.get(importer);
85
87
  if (!byImporter) {
@@ -88,7 +90,7 @@ function createDepGraph({ root, read }) {
88
90
  }
89
91
  let pending = byImporter.get(spec);
90
92
  if (!pending) {
91
- pending = resolve4(spec, importer).then(
93
+ pending = resolve5(spec, importer).then(
92
94
  (id) => {
93
95
  const file = id?.split("?")[0];
94
96
  return file && !file.startsWith("\0") && SCRIPT.test(file) && !file.split(/[\\/]/).includes("node_modules") ? file : null;
@@ -318,6 +320,92 @@ function namesIn(node) {
318
320
  walk(node);
319
321
  return out;
320
322
  }
323
+ var runGit = (args, cwd) => new Promise((done, fail) => {
324
+ execFile(
325
+ "git",
326
+ ["-c", "core.quotepath=off", ...args],
327
+ { cwd, maxBuffer: 1 << 30 },
328
+ (error, stdout) => error ? fail(error) : done(stdout)
329
+ );
330
+ });
331
+ var LOG_FORMAT = "--format=%x00%H%x09%aI";
332
+ function fileDates(log, paths) {
333
+ const out = /* @__PURE__ */ new Map();
334
+ const names = new Map(paths.map((path) => [path, path]));
335
+ let commit = "";
336
+ let date = "";
337
+ for (const line of log.split("\n")) {
338
+ if (names.size === 0) break;
339
+ if (line.startsWith("\0")) {
340
+ [commit = "", date = ""] = line.slice(1).split(" ");
341
+ continue;
342
+ }
343
+ const [status, ...files] = line.split(" ");
344
+ const name = files.at(-1);
345
+ const path = name === void 0 ? void 0 : names.get(name);
346
+ if (!status || name === void 0 || path === void 0) continue;
347
+ out.set(path, { created: date, modified: out.get(path)?.modified ?? date, commit });
348
+ const renamed = status.startsWith("R") ? files[0] : void 0;
349
+ if (status === "A" || renamed !== void 0) names.delete(name);
350
+ if (renamed !== void 0) names.set(renamed, path);
351
+ }
352
+ return out;
353
+ }
354
+ function versionOf(named) {
355
+ const tag = named.trim().split(/[~^]/)[0] ?? "";
356
+ return /(?:^v?|@)(\d+\.\d+\.\d+[-+.\w]*)$/.exec(tag)?.[1] ?? null;
357
+ }
358
+ function packageOf(file, top) {
359
+ for (let dir = dirname(file); dir.startsWith(top); dir = dirname(dir)) {
360
+ const manifest = join(dir, "package.json");
361
+ if (existsSync(manifest)) {
362
+ try {
363
+ const name = JSON.parse(readFileSync(manifest, "utf8")).name;
364
+ if (typeof name === "string") return name;
365
+ } catch {
366
+ }
367
+ }
368
+ if (dir === top) break;
369
+ }
370
+ return null;
371
+ }
372
+ function createHistoryReader(root, git = runGit) {
373
+ let cached = null;
374
+ async function load(sources) {
375
+ const [top = "", prefix = ""] = (await git(["rev-parse", "--show-toplevel", "--show-prefix"], root)).split("\n");
376
+ const inRepo = new Map(sources.map((source) => [posix.normalize(posix.join(prefix, source)), source]));
377
+ const log = await git(["log", "--name-status", "-M", LOG_FORMAT], top);
378
+ const dates = fileDates(log, [...inRepo.keys()]);
379
+ const byPackage = /* @__PURE__ */ new Map();
380
+ for (const path of dates.keys()) {
381
+ const name = packageOf(resolve(top, path), top);
382
+ byPackage.set(name, [...byPackage.get(name) ?? [], path]);
383
+ }
384
+ const history = {};
385
+ for (const [name, paths] of byPackage) {
386
+ const refs = ["v[0-9]*", "[0-9]*", ...name === null ? [] : [`${name}@[0-9]*`]].map((ref) => `--refs=${ref}`);
387
+ const commits = paths.map((path) => dates.get(path).commit);
388
+ const named = (await git(["name-rev", "--tags", "--name-only", ...refs, ...commits], top)).split("\n");
389
+ paths.forEach((path, i) => {
390
+ const { created, modified } = dates.get(path);
391
+ history[inRepo.get(path)] = { created, modified, introduced: versionOf(named[i] ?? "") };
392
+ });
393
+ }
394
+ return history;
395
+ }
396
+ return {
397
+ async read(sources) {
398
+ try {
399
+ const head = (await git(["rev-parse", "HEAD"], root)).trim();
400
+ const key = [...sources].sort().join("\n");
401
+ if (cached?.head !== head || cached.sources !== key) cached = { head, sources: key, history: await load(sources) };
402
+ return cached.history;
403
+ } catch {
404
+ return {};
405
+ }
406
+ }
407
+ };
408
+ }
321
409
  function parseFile(code, file) {
322
410
  const plugins = ["typescript", "decorators-legacy", .../\.[jt]sx$/.test(file) ? ["jsx"] : []];
323
411
  return parse(code, { sourceType: "module", plugins }).program;
@@ -647,7 +735,7 @@ function storyGlobOf(path, stories) {
647
735
  }
648
736
 
649
737
  // src/vite/autoTitle.ts
650
- var posix = (path) => path.split(sep).join("/");
738
+ var posix2 = (path) => path.split(sep).join("/");
651
739
  var dotted = (path) => /^\.{1,2}([/\\]|$)/.test(path) ? path : `./${path}`;
652
740
  function globDirectory(pattern) {
653
741
  const segments = pattern.split("/");
@@ -666,11 +754,11 @@ function titleParts(parts) {
666
754
  }
667
755
  var titleFrom = (importPath, directory) => titleParts(importPath.replace(directory, "").split("/").filter(Boolean)).join("/");
668
756
  function autoTitle(file, root, stories) {
669
- const path = posix(relative(root, file));
757
+ const path = posix2(relative(root, file));
670
758
  const importPath = dotted(path);
671
759
  const pattern = storyGlobOf(path, stories);
672
760
  if (pattern === void 0) return titleFrom(importPath, ".");
673
- const directory = dotted(posix(relative(root, resolve(root, globDirectory(pattern))))).replace(/\/$/, "");
761
+ const directory = dotted(posix2(relative(root, resolve(root, globDirectory(pattern))))).replace(/\/$/, "");
674
762
  return titleFrom(importPath, directory);
675
763
  }
676
764
 
@@ -754,13 +842,13 @@ function createTracer(read) {
754
842
  }
755
843
  return modules.get(file) ?? null;
756
844
  };
757
- return (resolve4) => {
845
+ return (resolve5) => {
758
846
  const trace = {
759
847
  reads: /* @__PURE__ */ new Set(),
760
848
  unresolved: false,
761
849
  info,
762
850
  async target(spec, importer) {
763
- const id = (await resolve4(spec, importer).catch(() => null))?.split("?")[0];
851
+ const id = (await resolve5(spec, importer).catch(() => null))?.split("?")[0];
764
852
  if (!id) trace.unresolved = true;
765
853
  return id && !id.startsWith("\0") ? id : null;
766
854
  },
@@ -802,8 +890,8 @@ function sameResolution(a, b) {
802
890
  }
803
891
  function createWrapperResolver(read) {
804
892
  const tracer = createTracer(read);
805
- return async (file, callees, resolve4) => {
806
- const trace = tracer(resolve4);
893
+ return async (file, callees, resolve5) => {
894
+ const trace = tracer(resolve5);
807
895
  const wrappers = /* @__PURE__ */ new Set();
808
896
  const direct = [...trace.info(file)?.imports.values() ?? []].some((imp) => imp.spec === "@weasel-js/forge");
809
897
  if (callees.length === 0 && !direct) return { wrappers, foreign: NO_FOREIGN, reads: trace.reads, unresolved: false };
@@ -830,8 +918,8 @@ function indexStory(code, file, source, resolved) {
830
918
  const foreign = resolved ? foreignFrom(resolved.foreign) : void 0;
831
919
  return indexFile(code, file, autoTitle(file, source.root, source.stories), resolved?.wrappers, foreign);
832
920
  }
833
- async function indexStoryResolved(code, file, source, resolve4, decide = createWrapperResolver((path) => readFileSync(path, "utf8"))) {
834
- const resolution = await decide(file, foreignCallees(code, file), resolve4);
921
+ async function indexStoryResolved(code, file, source, resolve5, decide = createWrapperResolver((path) => readFileSync(path, "utf8"))) {
922
+ const resolution = await decide(file, foreignCallees(code, file), resolve5);
835
923
  return { entries: indexStory(code, file, source, resolution), resolution };
836
924
  }
837
925
  function ownEntries(realms) {
@@ -847,6 +935,241 @@ function ownEntries(realms) {
847
935
  return [];
848
936
  }
849
937
 
938
+ // src/vite/entries.ts
939
+ function shellEntry(base, stories) {
940
+ return `import { mountWorkshop } from '@weasel-js/forge/shell';
941
+ import '@weasel-js/labkit/styles.css';
942
+ import '@weasel-js/forge/shell.css';
943
+ import index from 'virtual:forge/index.js';
944
+ import importers from 'virtual:forge/importers.js';
945
+ import config from 'virtual:forge/shell-config.js';
946
+ import setup from 'virtual:forge/frame-config.js';
947
+
948
+ const workshop = mountWorkshop({
949
+ index,
950
+ importers,
951
+ setup,
952
+ config,
953
+ frameUrl: ${JSON.stringify(`${base}frame.html`)},
954
+ stories: ${JSON.stringify(stories)},
955
+ });
956
+ import.meta.hot?.on('forge:index', (next) => workshop.setIndex(next));
957
+ // Read from source on first request, so the graph costs nothing until the page is up.
958
+ import('virtual:forge/deps.js').then((m) => workshop.setDependencies(m.default));
959
+ import.meta.hot?.on('forge:deps', (next) => workshop.setDependencies(next));
960
+ // An edit to a story file, or to a module one imports, reaches here as an update of the importers module, whose
961
+ // fresh import() URLs carry the new timestamps; the files it reaches arrive just before, so the reload waits for
962
+ // the importers that can fetch them.
963
+ const stale = new Set();
964
+ import.meta.hot?.on('forge:story', ({ file }) => stale.add(file));
965
+ import.meta.hot?.accept('virtual:forge/importers.js', (next) => {
966
+ if (!next) return;
967
+ workshop.setImporters(next.default);
968
+ for (const file of stale) workshop.reloadStory(file);
969
+ stale.clear();
970
+ });
971
+ `;
972
+ }
973
+ function frameEntry(styles) {
974
+ return `import { mountFrame } from '@weasel-js/forge/frame';
975
+ import '@weasel-js/forge/frame.css';
976
+ ${styles.map((path) => `import ${JSON.stringify(path)};
977
+ `).join("")}import index from 'virtual:forge/index.js';
978
+ import importers from 'virtual:forge/importers.js';
979
+ import setup from 'virtual:forge/frame-config.js';
980
+
981
+ mountFrame({ index, importers, setup });
982
+ // The importers module is shared with the workshop page, which accepts a story edit in place; a frame document
983
+ // showing the old module reloads instead, and without this the edit would dead-end here and reload every page.
984
+ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
985
+ `;
986
+ }
987
+ var PLATFORM = /[\\/]node_modules[\\/](@types[\\/]react(-dom)?|typescript)[\\/]/;
988
+ async function createPropReader(root, stories, { skip } = {}) {
989
+ const skipped = (file) => PLATFORM.test(file) || skip?.(file) === true;
990
+ let tsm;
991
+ try {
992
+ tsm = (await import('typescript')).default;
993
+ } catch {
994
+ return null;
995
+ }
996
+ const { sys } = tsm;
997
+ const configFile = tsm.findConfigFile(root, sys.fileExists);
998
+ const read = configFile ? tsm.readConfigFile(configFile, sys.readFile) : void 0;
999
+ const parsed = read?.config ? tsm.parseJsonConfigFileContent(read.config, { ...sys, readDirectory: () => [] }, dirname(configFile)) : void 0;
1000
+ const options = {
1001
+ jsx: tsm.JsxEmit.ReactJSX,
1002
+ moduleResolution: tsm.ModuleResolutionKind.Bundler,
1003
+ module: tsm.ModuleKind.ESNext,
1004
+ target: tsm.ScriptTarget.ESNext,
1005
+ strict: true,
1006
+ ...parsed?.options,
1007
+ allowJs: true,
1008
+ noEmit: true,
1009
+ skipLibCheck: true
1010
+ };
1011
+ const host = {
1012
+ getScriptFileNames: () => [...stories()],
1013
+ getScriptVersion: (file) => String(sys.getModifiedTime?.(file)?.getTime() ?? 0),
1014
+ getScriptSnapshot: (file) => {
1015
+ const text = sys.readFile(file);
1016
+ return text === void 0 ? void 0 : tsm.ScriptSnapshot.fromString(text);
1017
+ },
1018
+ getCurrentDirectory: () => root,
1019
+ getCompilationSettings: () => options,
1020
+ getDefaultLibFileName: tsm.getDefaultLibFilePath,
1021
+ fileExists: sys.fileExists,
1022
+ readFile: sys.readFile,
1023
+ readDirectory: sys.readDirectory,
1024
+ directoryExists: sys.directoryExists,
1025
+ getDirectories: sys.getDirectories
1026
+ };
1027
+ const service = tsm.createLanguageService(host, tsm.createDocumentRegistry());
1028
+ return {
1029
+ read(file) {
1030
+ const program = service.getProgram();
1031
+ const source = program?.getSourceFile(file);
1032
+ if (!program || !source) return null;
1033
+ const checker = program.getTypeChecker();
1034
+ const props = componentProps(tsm, checker, source);
1035
+ return props ? propTable(tsm, checker, props, source, skipped) : null;
1036
+ }
1037
+ };
1038
+ }
1039
+ function componentProps(tsm, checker, source) {
1040
+ const module = checker.getSymbolAtLocation(source);
1041
+ const exported = module && checker.tryGetMemberInModuleExports("default", module);
1042
+ if (!exported) return null;
1043
+ const meta = checker.getTypeOfSymbolAtLocation(
1044
+ exported.flags & tsm.SymbolFlags.Alias ? checker.getAliasedSymbol(exported) : exported,
1045
+ source
1046
+ );
1047
+ const named = meta.getProperty("component");
1048
+ if (!named) return null;
1049
+ const component = checker.getNonNullableType(checker.getTypeOfSymbolAtLocation(named, source));
1050
+ for (const part of component.isUnion() ? component.types : [component]) {
1051
+ const [call] = part.getCallSignatures();
1052
+ const [param] = call?.getParameters() ?? [];
1053
+ if (call) return param ? checker.getTypeOfSymbolAtLocation(param, source) : null;
1054
+ const instance = part.getConstructSignatures()[0]?.getReturnType();
1055
+ const held = instance?.getProperty("props");
1056
+ if (held) return checker.getTypeOfSymbolAtLocation(held, source);
1057
+ }
1058
+ return null;
1059
+ }
1060
+ function propTable(tsm, checker, props, at, skipped) {
1061
+ const argTypes = {};
1062
+ const sources = /* @__PURE__ */ new Set();
1063
+ for (const prop2 of checker.getPropertiesOfType(checker.getApparentType(props))) {
1064
+ const files = (prop2.getDeclarations() ?? []).map((declaration) => declaration.getSourceFile().fileName);
1065
+ if (files.length > 0 && files.every(skipped)) continue;
1066
+ const argType = controlFor(
1067
+ tsm,
1068
+ checker.getNonNullableType(checker.getTypeOfSymbolAtLocation(prop2, at)),
1069
+ () => writtenOrder(tsm, checker, prop2)
1070
+ );
1071
+ if (!argType) continue;
1072
+ const description = tsm.displayPartsToString(prop2.getDocumentationComment(checker)).replace(/\s+/g, " ").trim();
1073
+ argTypes[prop2.getName()] = description ? { ...argType, description } : argType;
1074
+ for (const file of files) if (!/[\\/]node_modules[\\/]/.test(file)) sources.add(file);
1075
+ }
1076
+ return { argTypes, sources: [...sources] };
1077
+ }
1078
+ function controlFor(tsm, type, written) {
1079
+ const { TypeFlags } = tsm;
1080
+ const parts = type.isUnion() ? type.types : [type];
1081
+ const every = (flags) => parts.every((part) => part.flags & flags);
1082
+ if (every(TypeFlags.BooleanLike)) return { control: { type: "boolean" } };
1083
+ if (type.flags & TypeFlags.String) return { control: { type: "text" } };
1084
+ if (type.flags & TypeFlags.Number) return { control: { type: "number" } };
1085
+ if (parts.length > 1 && every(TypeFlags.StringLiteral)) {
1086
+ const options = parts.map((part) => part.value);
1087
+ const order = written();
1088
+ const same = order?.length === options.length && options.every((option) => order.includes(option));
1089
+ return { options: same ? order : options };
1090
+ }
1091
+ return null;
1092
+ }
1093
+ function writtenOrder(tsm, checker, prop2) {
1094
+ const [declaration, ...others] = prop2.getDeclarations() ?? [];
1095
+ if (!declaration || others.length > 0) return null;
1096
+ if (!tsm.isPropertySignature(declaration) && !tsm.isPropertyDeclaration(declaration)) return null;
1097
+ const seen = /* @__PURE__ */ new Set();
1098
+ const literals = (node) => {
1099
+ if (!node || seen.has(node)) return null;
1100
+ seen.add(node);
1101
+ if (tsm.isParenthesizedTypeNode(node)) return literals(node.type);
1102
+ if (tsm.isLiteralTypeNode(node)) {
1103
+ if (node.literal.kind === tsm.SyntaxKind.NullKeyword) return [];
1104
+ return tsm.isStringLiteralLike(node.literal) ? [node.literal.text] : null;
1105
+ }
1106
+ if (node.kind === tsm.SyntaxKind.UndefinedKeyword) return [];
1107
+ if (tsm.isUnionTypeNode(node)) {
1108
+ const parts = node.types.map(literals);
1109
+ return parts.every((part) => part !== null) ? [...new Set(parts.flat())] : null;
1110
+ }
1111
+ if (tsm.isTypeReferenceNode(node) && !node.typeArguments) {
1112
+ const named = checker.getSymbolAtLocation(node.typeName);
1113
+ const target = named && named.flags & tsm.SymbolFlags.Alias ? checker.getAliasedSymbol(named) : named;
1114
+ const [alias, ...rest] = target?.getDeclarations() ?? [];
1115
+ if (alias && rest.length === 0 && tsm.isTypeAliasDeclaration(alias) && !alias.typeParameters) {
1116
+ return literals(alias.type);
1117
+ }
1118
+ }
1119
+ return null;
1120
+ };
1121
+ return literals(declaration.type);
1122
+ }
1123
+
1124
+ // src/vite/propControls.ts
1125
+ function propControls(stories, options = {}) {
1126
+ let root = process.cwd();
1127
+ let reader = null;
1128
+ return {
1129
+ name: "weaselforge:prop-controls",
1130
+ configResolved(config) {
1131
+ root = config.root;
1132
+ reader = null;
1133
+ },
1134
+ async transform(code, id) {
1135
+ if (!stories().includes(id)) return void 0;
1136
+ const props = await (reader ??= createPropReader(root, stories, options));
1137
+ let table = null;
1138
+ try {
1139
+ table = props?.read(id) ?? null;
1140
+ } catch (err) {
1141
+ this.warn(`could not read the props for ${id}: ${err instanceof Error ? err.message : String(err)}`);
1142
+ }
1143
+ if (!table || Object.keys(table.argTypes).length === 0) return void 0;
1144
+ for (const source of table.sources) this.addWatchFile(source);
1145
+ return { code: `${code}
1146
+ export const ${PROP_ARG_TYPES_EXPORT} = ${JSON.stringify(table.argTypes)};
1147
+ `, map: null };
1148
+ }
1149
+ };
1150
+ }
1151
+
1152
+ // src/vite/servePages.ts
1153
+ var PAGES2 = { "/": "shell-entry.js", "/index.html": "shell-entry.js", "/frame.html": "frame-entry.js" };
1154
+ function servePages(server, base, title) {
1155
+ server.middlewares.use((req, res, next) => {
1156
+ const url = req.url ?? "/";
1157
+ const path = url.split("?")[0] ?? "/";
1158
+ const prefix = base.endsWith("/") ? base.slice(0, -1) : base;
1159
+ const page = path === prefix ? "/" : path.startsWith(`${prefix}/`) ? path.slice(prefix.length) : null;
1160
+ const entry = req.method === "GET" && page !== null ? PAGES2[page] : void 0;
1161
+ if (!entry) return next();
1162
+ server.transformIndexHtml(url, html(entry, "serve", title())).then(
1163
+ (doc) => {
1164
+ res.statusCode = 200;
1165
+ res.setHeader("Content-Type", "text/html");
1166
+ res.end(doc);
1167
+ },
1168
+ (err) => next(err)
1169
+ );
1170
+ });
1171
+ }
1172
+
850
1173
  // src/vite/shellTitle.ts
851
1174
  function shellTitle(code, file) {
852
1175
  let program;
@@ -885,12 +1208,66 @@ function storybookShims() {
885
1208
  }
886
1209
  };
887
1210
  }
1211
+ var WAIT_MS = 5e3;
1212
+ var REWRITE_MS = 250;
1213
+ var watchDir = (dir, onEvent) => {
1214
+ const watcher = watch(dir, onEvent);
1215
+ watcher.on("error", () => {
1216
+ });
1217
+ watcher.unref();
1218
+ return () => watcher.close();
1219
+ };
1220
+ function fileEventsArrive({ waitMs = WAIT_MS, watch = watchDir } = {}) {
1221
+ const dir = mkdtempSync(join(tmpdir(), "forge-watch-"));
1222
+ return new Promise((resolve5) => {
1223
+ let settled2 = false;
1224
+ let writes = 0;
1225
+ let stop;
1226
+ let rewrite;
1227
+ let deadline;
1228
+ const settle = (arrived) => {
1229
+ if (settled2) return;
1230
+ settled2 = true;
1231
+ clearInterval(rewrite);
1232
+ clearTimeout(deadline);
1233
+ stop?.();
1234
+ rmSync(dir, { recursive: true, force: true });
1235
+ resolve5(arrived);
1236
+ };
1237
+ const write = () => writeFileSync(join(dir, "probe"), String(writes++));
1238
+ try {
1239
+ stop = watch(dir, () => settle(true));
1240
+ write();
1241
+ } catch {
1242
+ settle(false);
1243
+ return;
1244
+ }
1245
+ rewrite = setInterval(write, REWRITE_MS);
1246
+ deadline = setTimeout(() => settle(false), waitMs);
1247
+ rewrite.unref();
1248
+ deadline.unref();
1249
+ });
1250
+ }
1251
+ var SILENT = `[watch] a file written ${WAIT_MS / 1e3} s ago has not been reported by the OS file watcher, so an edit will not reach the page until file events arrive again. \`server.watch: { usePolling: true }\` serves edits without them. On macOS, look for \`fseventsd\` held at 100% CPU.`;
1252
+ async function warnWhenWatchIsSilent(server, probe = fileEventsArrive) {
1253
+ const { watch } = server.config.server;
1254
+ if (watch === null || watch?.usePolling) return;
1255
+ if (!await probe()) server.config.logger.warn(SILENT);
1256
+ }
1257
+ function watchProbe({ probe } = {}) {
1258
+ return {
1259
+ name: "weasel-watch-probe",
1260
+ apply: "serve",
1261
+ configureServer(server) {
1262
+ void warnWhenWatchIsSilent(server, probe);
1263
+ }
1264
+ };
1265
+ }
888
1266
 
889
1267
  // src/vite/plugin.ts
890
1268
  var PREFIX = "virtual:forge/";
891
1269
  var MODULES = /* @__PURE__ */ new Set(["index.js", "importers.js", "deps.js", "frame-config.js", "shell-config.js", "shell-entry.js", "frame-entry.js"]);
892
1270
  var SCRIPT2 = /\.[cm]?[jt]sx?$/;
893
- var PAGES2 = { "/": "shell-entry.js", "/index.html": "shell-entry.js", "/frame.html": "frame-entry.js" };
894
1271
  function forge(options) {
895
1272
  let root = process.cwd();
896
1273
  let base = "/";
@@ -949,6 +1326,17 @@ function forge(options) {
949
1326
  }
950
1327
  };
951
1328
  const depGraph = () => deps ??= createDepGraph({ root, read: (file) => readFileSync(file, "utf8") });
1329
+ let history = null;
1330
+ async function graphOf(resolveImport) {
1331
+ const graph = await depGraph().build(index(), resolveImport);
1332
+ const sources = Object.values(graph).flatMap((entry) => entry.source === null ? [] : [entry.source]);
1333
+ const histories = await (history ??= createHistoryReader(root)).read(sources);
1334
+ for (const entry of Object.values(graph)) {
1335
+ const found = entry.source === null ? void 0 : histories[entry.source];
1336
+ if (found) entry.history = found;
1337
+ }
1338
+ return graph;
1339
+ }
952
1340
  const titleUnder = (at) => {
953
1341
  if (!options.shellConfig) return void 0;
954
1342
  const file = resolve(at, options.shellConfig);
@@ -972,50 +1360,8 @@ ${lines.join("\n")}
972
1360
  },
973
1361
  "frame-config.js": () => configModule(options.frameConfig),
974
1362
  "shell-config.js": () => configModule(options.shellConfig),
975
- "shell-entry.js": () => `import { mountWorkshop } from '@weasel-js/forge/shell';
976
- import '@weasel-js/labkit/styles.css';
977
- import '@weasel-js/forge/shell.css';
978
- import index from 'virtual:forge/index.js';
979
- import importers from 'virtual:forge/importers.js';
980
- import config from 'virtual:forge/shell-config.js';
981
- import setup from 'virtual:forge/frame-config.js';
982
-
983
- const workshop = mountWorkshop({
984
- index,
985
- importers,
986
- setup,
987
- config,
988
- frameUrl: ${JSON.stringify(`${base}frame.html`)},
989
- stories: ${JSON.stringify(options.stories)},
990
- });
991
- import.meta.hot?.on('forge:index', (next) => workshop.setIndex(next));
992
- // Read from source on first request, so the graph costs nothing until the page is up.
993
- import('virtual:forge/deps.js').then((m) => workshop.setDependencies(m.default));
994
- import.meta.hot?.on('forge:deps', (next) => workshop.setDependencies(next));
995
- // An edit to a story file, or to a module one imports, reaches here as an update of the importers module, whose
996
- // fresh import() URLs carry the new timestamps; the files it reaches arrive just before, so the reload waits for
997
- // the importers that can fetch them.
998
- const stale = new Set();
999
- import.meta.hot?.on('forge:story', ({ file }) => stale.add(file));
1000
- import.meta.hot?.accept('virtual:forge/importers.js', (next) => {
1001
- if (!next) return;
1002
- workshop.setImporters(next.default);
1003
- for (const file of stale) workshop.reloadStory(file);
1004
- stale.clear();
1005
- });
1006
- `,
1007
- "frame-entry.js": () => `import { mountFrame } from '@weasel-js/forge/frame';
1008
- import '@weasel-js/forge/frame.css';
1009
- ${(options.frameStyles ?? []).map((path) => `import ${JSON.stringify(resolve(root, path))};
1010
- `).join("")}import index from 'virtual:forge/index.js';
1011
- import importers from 'virtual:forge/importers.js';
1012
- import setup from 'virtual:forge/frame-config.js';
1013
-
1014
- mountFrame({ index, importers, setup });
1015
- // The importers module is shared with the workshop page, which accepts a story edit in place; a frame document
1016
- // showing the old module reloads instead, and without this the edit would dead-end here and reload every page.
1017
- import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
1018
- `
1363
+ "shell-entry.js": () => shellEntry(base, options.stories),
1364
+ "frame-entry.js": () => frameEntry((options.frameStyles ?? []).map((path) => resolve(root, path)))
1019
1365
  };
1020
1366
  const invalidate = (server, names) => {
1021
1367
  for (const env of Object.values(server.environments)) {
@@ -1027,9 +1373,9 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
1027
1373
  };
1028
1374
  function redeps(server) {
1029
1375
  if (depsSent === null || depsPending) return;
1030
- const resolve4 = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
1376
+ const resolve5 = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
1031
1377
  depsPending = Promise.resolve().then(async () => {
1032
- const graph = await depGraph().build(index(), resolve4);
1378
+ const graph = await graphOf(resolve5);
1033
1379
  const json = JSON.stringify(graph);
1034
1380
  if (json === depsSent) return;
1035
1381
  depsSent = json;
@@ -1084,6 +1430,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
1084
1430
  }
1085
1431
  return [
1086
1432
  ...options.storybookShims === false ? [] : [storybookShims()],
1433
+ ...options.propControls === false ? [] : [propControls(() => [...files().keys()], options.propControls === true ? void 0 : options.propControls)],
1087
1434
  {
1088
1435
  name: "weaselforge",
1089
1436
  config(config, { command }) {
@@ -1111,6 +1458,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
1111
1458
  wrappersOf.clear();
1112
1459
  settledAll = null;
1113
1460
  deps = null;
1461
+ history = null;
1114
1462
  depsSent = null;
1115
1463
  },
1116
1464
  resolveId(id) {
@@ -1124,7 +1472,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
1124
1472
  await settledAll;
1125
1473
  }
1126
1474
  if (name !== "deps.js") return modules[name]?.();
1127
- const graph = await depGraph().build(index(), async (spec, importer) => (await this.resolve(spec, importer))?.id ?? null);
1475
+ const graph = await graphOf(async (spec, importer) => (await this.resolve(spec, importer))?.id ?? null);
1128
1476
  depsSent = JSON.stringify(graph);
1129
1477
  return `export default ${depsSent};
1130
1478
  `;
@@ -1135,6 +1483,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
1135
1483
  },
1136
1484
  configureServer(server) {
1137
1485
  server.watcher.add([...files().keys()]);
1486
+ void warnWhenWatchIsSilent(server);
1138
1487
  for (const event of ["add", "change", "unlink"]) {
1139
1488
  server.watcher.on(event, (path) => {
1140
1489
  const file = resolve(path);
@@ -1146,22 +1495,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
1146
1495
  }, logError);
1147
1496
  });
1148
1497
  }
1149
- server.middlewares.use((req, res, next) => {
1150
- const url = req.url ?? "/";
1151
- const path = url.split("?")[0] ?? "/";
1152
- const prefix = base.endsWith("/") ? base.slice(0, -1) : base;
1153
- const page = path === prefix ? "/" : path.startsWith(`${prefix}/`) ? path.slice(prefix.length) : null;
1154
- const entry = req.method === "GET" && page !== null ? PAGES2[page] : void 0;
1155
- if (!entry) return next();
1156
- server.transformIndexHtml(url, html(entry, "serve", titleUnder(root))).then(
1157
- (doc) => {
1158
- res.statusCode = 200;
1159
- res.setHeader("Content-Type", "text/html");
1160
- res.end(doc);
1161
- },
1162
- (err) => next(err)
1163
- );
1164
- });
1498
+ servePages(server, base, () => titleUnder(root));
1165
1499
  }
1166
1500
  }
1167
1501
  ];
@@ -1233,6 +1567,6 @@ ${lines.join("\n")}
1233
1567
  ];
1234
1568
  }
1235
1569
 
1236
- export { forge, forgeTest };
1570
+ export { forge, forgeTest, watchProbe };
1237
1571
  //# sourceMappingURL=index.js.map
1238
1572
  //# sourceMappingURL=index.js.map