@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.
- package/README.md +13 -7
- package/dist/{FrameController-BQjKhdwk.d.ts → FrameController-UeTY7ubF.d.ts} +25 -1
- package/dist/{chunk-B3PPHJTS.js → chunk-AP3DRQLI.js} +16 -16
- package/dist/chunk-AP3DRQLI.js.map +1 -0
- package/dist/{chunk-MQSFPRF6.js → chunk-BIAEGWGJ.js} +37 -19
- package/dist/chunk-BIAEGWGJ.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-E2FHBDNQ.js} +16 -25
- package/dist/chunk-E2FHBDNQ.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-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 +41 -5
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +7 -6
- package/dist/shell/index.js +537 -334
- 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 +28 -1
- package/dist/vite/index.js +413 -79
- 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,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": () =>
|
|
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
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|