@weasel-js/forge 1.9.4 → 1.10.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 +12 -4
- package/dist/{FrameController-BQjKhdwk.d.ts → FrameController-UeTY7ubF.d.ts} +25 -1
- package/dist/{chunk-B3PPHJTS.js → chunk-4NM3RTYK.js} +14 -14
- package/dist/chunk-4NM3RTYK.js.map +1 -0
- package/dist/{chunk-JHL4ERDA.js → chunk-DFKCLZKS.js} +3 -3
- package/dist/chunk-DFKCLZKS.js.map +1 -0
- package/dist/{chunk-FRUA5WJF.js → chunk-DKWHGSBZ.js} +3 -3
- package/dist/{chunk-FRUA5WJF.js.map → chunk-DKWHGSBZ.js.map} +1 -1
- package/dist/{chunk-ZZCCIXEJ.js → chunk-ENXU34PI.js} +16 -25
- package/dist/chunk-ENXU34PI.js.map +1 -0
- package/dist/{chunk-TXLWQFF6.js → chunk-FL2MUPUL.js} +2 -2
- package/dist/chunk-FL2MUPUL.js.map +1 -0
- package/dist/{chunk-5KI5YSQ5.js → chunk-GB6PRJET.js} +3 -3
- package/dist/{chunk-5KI5YSQ5.js.map → chunk-GB6PRJET.js.map} +1 -1
- package/dist/{chunk-MQSFPRF6.js → chunk-V4RI7GWD.js} +37 -19
- package/dist/chunk-V4RI7GWD.js.map +1 -0
- package/dist/{chunk-HRGPFQBW.js → chunk-ZAWXMUGZ.js} +6 -3
- package/dist/chunk-ZAWXMUGZ.js.map +1 -0
- package/dist/{config-Dfu-NKxd.d.ts → config-Cz7ZyGF-.d.ts} +5 -5
- package/dist/csf/preview-api.d.ts +5 -2
- package/dist/csf/preview-api.js +2 -2
- package/dist/csf/preview-api.js.map +1 -1
- package/dist/frame/index.css +19 -2
- package/dist/frame/index.css.map +1 -1
- package/dist/frame/index.d.ts +2 -2
- package/dist/frame/index.js +5 -5
- package/dist/frame/index.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/shell/index.css +36 -5
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +7 -6
- package/dist/shell/index.js +398 -274
- package/dist/shell/index.js.map +1 -1
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +6 -6
- package/dist/test/play.d.ts +3 -5
- package/dist/test/play.js.map +1 -1
- package/dist/vite/index.d.ts +16 -1
- package/dist/vite/index.js +403 -78
- package/dist/vite/index.js.map +1 -1
- package/package.json +10 -5
- package/dist/chunk-B3PPHJTS.js.map +0 -1
- package/dist/chunk-HRGPFQBW.js.map +0 -1
- package/dist/chunk-JHL4ERDA.js.map +0 -1
- package/dist/chunk-MQSFPRF6.js.map +0 -1
- package/dist/chunk-TXLWQFF6.js.map +0 -1
- package/dist/chunk-ZZCCIXEJ.js.map +0 -1
package/dist/vite/index.js
CHANGED
|
@@ -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-
|
|
4
|
-
import { readFileSync, existsSync, mkdirSync, writeFileSync, globSync, statSync } from 'fs';
|
|
5
|
-
import { resolve, sep, relative,
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -79,7 +81,7 @@ function createDepGraph({ root, read }) {
|
|
|
79
81
|
reset() {
|
|
80
82
|
resolutions.clear();
|
|
81
83
|
},
|
|
82
|
-
async build(entries,
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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 (
|
|
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
|
|
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,
|
|
806
|
-
const trace = tracer(
|
|
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,
|
|
834
|
-
const resolution = await decide(file, foreignCallees(code, file),
|
|
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,57 @@ 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 = `[forge] 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
|
+
}
|
|
888
1257
|
|
|
889
1258
|
// src/vite/plugin.ts
|
|
890
1259
|
var PREFIX = "virtual:forge/";
|
|
891
1260
|
var MODULES = /* @__PURE__ */ new Set(["index.js", "importers.js", "deps.js", "frame-config.js", "shell-config.js", "shell-entry.js", "frame-entry.js"]);
|
|
892
1261
|
var SCRIPT2 = /\.[cm]?[jt]sx?$/;
|
|
893
|
-
var PAGES2 = { "/": "shell-entry.js", "/index.html": "shell-entry.js", "/frame.html": "frame-entry.js" };
|
|
894
1262
|
function forge(options) {
|
|
895
1263
|
let root = process.cwd();
|
|
896
1264
|
let base = "/";
|
|
@@ -949,6 +1317,17 @@ function forge(options) {
|
|
|
949
1317
|
}
|
|
950
1318
|
};
|
|
951
1319
|
const depGraph = () => deps ??= createDepGraph({ root, read: (file) => readFileSync(file, "utf8") });
|
|
1320
|
+
let history = null;
|
|
1321
|
+
async function graphOf(resolveImport) {
|
|
1322
|
+
const graph = await depGraph().build(index(), resolveImport);
|
|
1323
|
+
const sources = Object.values(graph).flatMap((entry) => entry.source === null ? [] : [entry.source]);
|
|
1324
|
+
const histories = await (history ??= createHistoryReader(root)).read(sources);
|
|
1325
|
+
for (const entry of Object.values(graph)) {
|
|
1326
|
+
const found = entry.source === null ? void 0 : histories[entry.source];
|
|
1327
|
+
if (found) entry.history = found;
|
|
1328
|
+
}
|
|
1329
|
+
return graph;
|
|
1330
|
+
}
|
|
952
1331
|
const titleUnder = (at) => {
|
|
953
1332
|
if (!options.shellConfig) return void 0;
|
|
954
1333
|
const file = resolve(at, options.shellConfig);
|
|
@@ -972,50 +1351,8 @@ ${lines.join("\n")}
|
|
|
972
1351
|
},
|
|
973
1352
|
"frame-config.js": () => configModule(options.frameConfig),
|
|
974
1353
|
"shell-config.js": () => configModule(options.shellConfig),
|
|
975
|
-
"shell-entry.js": () =>
|
|
976
|
-
|
|
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
|
-
`
|
|
1354
|
+
"shell-entry.js": () => shellEntry(base, options.stories),
|
|
1355
|
+
"frame-entry.js": () => frameEntry((options.frameStyles ?? []).map((path) => resolve(root, path)))
|
|
1019
1356
|
};
|
|
1020
1357
|
const invalidate = (server, names) => {
|
|
1021
1358
|
for (const env of Object.values(server.environments)) {
|
|
@@ -1027,9 +1364,9 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
|
1027
1364
|
};
|
|
1028
1365
|
function redeps(server) {
|
|
1029
1366
|
if (depsSent === null || depsPending) return;
|
|
1030
|
-
const
|
|
1367
|
+
const resolve5 = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
|
|
1031
1368
|
depsPending = Promise.resolve().then(async () => {
|
|
1032
|
-
const graph = await
|
|
1369
|
+
const graph = await graphOf(resolve5);
|
|
1033
1370
|
const json = JSON.stringify(graph);
|
|
1034
1371
|
if (json === depsSent) return;
|
|
1035
1372
|
depsSent = json;
|
|
@@ -1084,6 +1421,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
|
1084
1421
|
}
|
|
1085
1422
|
return [
|
|
1086
1423
|
...options.storybookShims === false ? [] : [storybookShims()],
|
|
1424
|
+
...options.propControls === false ? [] : [propControls(() => [...files().keys()], options.propControls === true ? void 0 : options.propControls)],
|
|
1087
1425
|
{
|
|
1088
1426
|
name: "weaselforge",
|
|
1089
1427
|
config(config, { command }) {
|
|
@@ -1111,6 +1449,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
|
1111
1449
|
wrappersOf.clear();
|
|
1112
1450
|
settledAll = null;
|
|
1113
1451
|
deps = null;
|
|
1452
|
+
history = null;
|
|
1114
1453
|
depsSent = null;
|
|
1115
1454
|
},
|
|
1116
1455
|
resolveId(id) {
|
|
@@ -1124,7 +1463,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
|
1124
1463
|
await settledAll;
|
|
1125
1464
|
}
|
|
1126
1465
|
if (name !== "deps.js") return modules[name]?.();
|
|
1127
|
-
const graph = await
|
|
1466
|
+
const graph = await graphOf(async (spec, importer) => (await this.resolve(spec, importer))?.id ?? null);
|
|
1128
1467
|
depsSent = JSON.stringify(graph);
|
|
1129
1468
|
return `export default ${depsSent};
|
|
1130
1469
|
`;
|
|
@@ -1135,6 +1474,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
|
1135
1474
|
},
|
|
1136
1475
|
configureServer(server) {
|
|
1137
1476
|
server.watcher.add([...files().keys()]);
|
|
1477
|
+
void warnWhenWatchIsSilent(server);
|
|
1138
1478
|
for (const event of ["add", "change", "unlink"]) {
|
|
1139
1479
|
server.watcher.on(event, (path) => {
|
|
1140
1480
|
const file = resolve(path);
|
|
@@ -1146,22 +1486,7 @@ import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
|
1146
1486
|
}, logError);
|
|
1147
1487
|
});
|
|
1148
1488
|
}
|
|
1149
|
-
server
|
|
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
|
-
});
|
|
1489
|
+
servePages(server, base, () => titleUnder(root));
|
|
1165
1490
|
}
|
|
1166
1491
|
}
|
|
1167
1492
|
];
|