@weasel-js/forge 1.11.1 → 1.13.0-rc.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 +110 -5
- package/dist/{FrameController-Cj_i-Rdk.d.ts → FrameController-Tv3jrHyT.d.ts} +3 -3
- package/dist/{chunk-JTCUIJPZ.js → chunk-35DOPXHZ.js} +119 -10
- package/dist/chunk-35DOPXHZ.js.map +1 -0
- package/dist/{chunk-7BQPHMA2.js → chunk-3BAM6M26.js} +2 -2
- package/dist/chunk-3BAM6M26.js.map +1 -0
- package/dist/{chunk-ZAWXMUGZ.js → chunk-5WS67HMU.js} +8 -3
- package/dist/chunk-5WS67HMU.js.map +1 -0
- package/dist/{chunk-BKPDKUDN.js → chunk-BUXUWAI4.js} +45 -60
- package/dist/chunk-BUXUWAI4.js.map +1 -0
- package/dist/chunk-ORMMZ5PV.js +59 -0
- package/dist/chunk-ORMMZ5PV.js.map +1 -0
- package/dist/chunk-RKQ277P5.js +501 -0
- package/dist/chunk-RKQ277P5.js.map +1 -0
- package/dist/chunk-STF6NQ5N.js +60 -0
- package/dist/chunk-STF6NQ5N.js.map +1 -0
- package/dist/chunk-VUPN73JP.js +16 -0
- package/dist/chunk-VUPN73JP.js.map +1 -0
- package/dist/{config-DN6IaNf6.d.ts → config-Bd6FVlh7.d.ts} +22 -5
- package/dist/frame/index.css +37 -0
- package/dist/frame/index.css.map +1 -1
- package/dist/frame/index.d.ts +62 -29
- package/dist/frame/index.js +138 -8
- package/dist/frame/index.js.map +1 -1
- package/dist/index.d.ts +28 -5
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mountFrame-FdJDb3TB.d.ts +35 -0
- package/dist/shell/index.css +147 -2
- package/dist/shell/index.css.map +1 -1
- package/dist/shell/index.d.ts +44 -8
- package/dist/shell/index.js +1614 -277
- package/dist/shell/index.js.map +1 -1
- package/dist/test/index.css +58 -0
- package/dist/test/index.css.map +1 -0
- package/dist/test/index.d.ts +1 -1
- package/dist/test/index.js +4 -4
- package/dist/vite/index.js +571 -121
- package/dist/vite/index.js.map +1 -1
- package/package.json +13 -8
- package/dist/chunk-5DXUNLV2.js +0 -6
- package/dist/chunk-5DXUNLV2.js.map +0 -1
- package/dist/chunk-7BQPHMA2.js.map +0 -1
- package/dist/chunk-BKPDKUDN.js.map +0 -1
- package/dist/chunk-FZNABWJC.js +0 -282
- package/dist/chunk-FZNABWJC.js.map +0 -1
- package/dist/chunk-JTCUIJPZ.js.map +0 -1
- package/dist/chunk-WEQSLPKT.js +0 -9
- package/dist/chunk-WEQSLPKT.js.map +0 -1
- package/dist/chunk-ZAWXMUGZ.js.map +0 -1
package/dist/vite/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { DEFAULT_TITLE } from '../chunk-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { CAPTURE_PAGES, DEFAULT_TITLE, CAPTURE_PATH, parsePayload, messageOf } from '../chunk-ORMMZ5PV.js';
|
|
2
|
+
import { byComponent } from '../chunk-STF6NQ5N.js';
|
|
3
|
+
import { isGallery } from '../chunk-VUPN73JP.js';
|
|
4
|
+
import { PROP_ARG_TYPES_EXPORT, storyNameFromExport, storyId } from '../chunk-5WS67HMU.js';
|
|
4
5
|
import { readFileSync, mkdtempSync, existsSync, mkdirSync, writeFileSync, globSync, statSync, watch, rmSync } from 'fs';
|
|
5
6
|
import { resolve, sep, relative, join, dirname, basename, matchesGlob, posix } from 'path';
|
|
6
7
|
import { parse } from '@babel/parser';
|
|
7
8
|
import { execFile } from 'child_process';
|
|
8
9
|
import { fileURLToPath } from 'url';
|
|
10
|
+
import { createHash } from 'crypto';
|
|
11
|
+
import { stableStringify } from '@weasel-js/core/json';
|
|
9
12
|
import { tmpdir } from 'os';
|
|
10
13
|
|
|
11
14
|
// src/vite/html.ts
|
|
@@ -81,7 +84,7 @@ function createDepGraph({ root, read }) {
|
|
|
81
84
|
reset() {
|
|
82
85
|
resolutions.clear();
|
|
83
86
|
},
|
|
84
|
-
async build(entries,
|
|
87
|
+
async build(entries, resolve6) {
|
|
85
88
|
const resolveFrom = (spec, importer) => {
|
|
86
89
|
let byImporter = resolutions.get(importer);
|
|
87
90
|
if (!byImporter) {
|
|
@@ -90,7 +93,7 @@ function createDepGraph({ root, read }) {
|
|
|
90
93
|
}
|
|
91
94
|
let pending = byImporter.get(spec);
|
|
92
95
|
if (!pending) {
|
|
93
|
-
pending =
|
|
96
|
+
pending = resolve6(spec, importer).then(
|
|
94
97
|
(id) => {
|
|
95
98
|
const file = id?.split("?")[0];
|
|
96
99
|
return file && !file.startsWith("\0") && SCRIPT.test(file) && !file.split(/[\\/]/).includes("node_modules") ? file : null;
|
|
@@ -853,13 +856,13 @@ function createTracer(read) {
|
|
|
853
856
|
}
|
|
854
857
|
return modules.get(file) ?? null;
|
|
855
858
|
};
|
|
856
|
-
return (
|
|
859
|
+
return (resolve6) => {
|
|
857
860
|
const trace = {
|
|
858
861
|
reads: /* @__PURE__ */ new Set(),
|
|
859
862
|
unresolved: false,
|
|
860
863
|
info,
|
|
861
864
|
async target(spec, importer) {
|
|
862
|
-
const id = (await
|
|
865
|
+
const id = (await resolve6(spec, importer).catch(() => null))?.split("?")[0];
|
|
863
866
|
if (!id) trace.unresolved = true;
|
|
864
867
|
return id && !id.startsWith("\0") ? id : null;
|
|
865
868
|
},
|
|
@@ -901,8 +904,8 @@ function sameResolution(a, b) {
|
|
|
901
904
|
}
|
|
902
905
|
function createWrapperResolver(read) {
|
|
903
906
|
const tracer = createTracer(read);
|
|
904
|
-
return async (file, callees,
|
|
905
|
-
const trace = tracer(
|
|
907
|
+
return async (file, callees, resolve6) => {
|
|
908
|
+
const trace = tracer(resolve6);
|
|
906
909
|
const wrappers = /* @__PURE__ */ new Set();
|
|
907
910
|
const direct = [...trace.info(file)?.imports.values() ?? []].some((imp) => imp.spec === "@weasel-js/forge");
|
|
908
911
|
if (callees.length === 0 && !direct) return { wrappers, foreign: NO_FOREIGN, reads: trace.reads, unresolved: false };
|
|
@@ -929,8 +932,8 @@ function indexStory(code, file, source, resolved) {
|
|
|
929
932
|
const foreign = resolved ? foreignFrom(resolved.foreign) : void 0;
|
|
930
933
|
return indexFile(code, file, autoTitle(file, source.root, source.stories), resolved?.wrappers, foreign);
|
|
931
934
|
}
|
|
932
|
-
async function indexStoryResolved(code, file, source,
|
|
933
|
-
const resolution = await decide(file, foreignCallees(code, file),
|
|
935
|
+
async function indexStoryResolved(code, file, source, resolve6, decide = createWrapperResolver((path) => readFileSync(path, "utf8"))) {
|
|
936
|
+
const resolution = await decide(file, foreignCallees(code, file), resolve6);
|
|
934
937
|
return { entries: indexStory(code, file, source, resolution), resolution };
|
|
935
938
|
}
|
|
936
939
|
function ownEntries(realms) {
|
|
@@ -945,56 +948,6 @@ function ownEntries(realms) {
|
|
|
945
948
|
}
|
|
946
949
|
return [];
|
|
947
950
|
}
|
|
948
|
-
|
|
949
|
-
// src/vite/entries.ts
|
|
950
|
-
function shellEntry(base, stories) {
|
|
951
|
-
return `import { mountWorkshop } from '@weasel-js/forge/shell';
|
|
952
|
-
import '@weasel-js/labkit/styles.css';
|
|
953
|
-
import '@weasel-js/forge/shell.css';
|
|
954
|
-
import index from 'virtual:forge/index.js';
|
|
955
|
-
import importers from 'virtual:forge/importers.js';
|
|
956
|
-
import config from 'virtual:forge/shell-config.js';
|
|
957
|
-
import setup from 'virtual:forge/frame-config.js';
|
|
958
|
-
|
|
959
|
-
const workshop = mountWorkshop({
|
|
960
|
-
index,
|
|
961
|
-
importers,
|
|
962
|
-
setup,
|
|
963
|
-
config,
|
|
964
|
-
frameUrl: ${JSON.stringify(`${base}frame.html`)},
|
|
965
|
-
stories: ${JSON.stringify(stories)},
|
|
966
|
-
});
|
|
967
|
-
import.meta.hot?.on('forge:index', (next) => workshop.setIndex(next));
|
|
968
|
-
// Read from source on first request, so the graph costs nothing until the page is up.
|
|
969
|
-
import('virtual:forge/deps.js').then((m) => workshop.setDependencies(m.default));
|
|
970
|
-
import.meta.hot?.on('forge:deps', (next) => workshop.setDependencies(next));
|
|
971
|
-
// An edit to a story file, or to a module one imports, reaches here as an update of the importers module, whose
|
|
972
|
-
// fresh import() URLs carry the new timestamps; the files it reaches arrive just before, so the reload waits for
|
|
973
|
-
// the importers that can fetch them.
|
|
974
|
-
const stale = new Set();
|
|
975
|
-
import.meta.hot?.on('forge:story', ({ file }) => stale.add(file));
|
|
976
|
-
import.meta.hot?.accept('virtual:forge/importers.js', (next) => {
|
|
977
|
-
if (!next) return;
|
|
978
|
-
workshop.setImporters(next.default);
|
|
979
|
-
for (const file of stale) workshop.reloadStory(file);
|
|
980
|
-
stale.clear();
|
|
981
|
-
});
|
|
982
|
-
`;
|
|
983
|
-
}
|
|
984
|
-
function frameEntry(styles) {
|
|
985
|
-
return `import { mountFrame } from '@weasel-js/forge/frame';
|
|
986
|
-
import '@weasel-js/forge/frame.css';
|
|
987
|
-
${styles.map((path) => `import ${JSON.stringify(path)};
|
|
988
|
-
`).join("")}import index from 'virtual:forge/index.js';
|
|
989
|
-
import importers from 'virtual:forge/importers.js';
|
|
990
|
-
import setup from 'virtual:forge/frame-config.js';
|
|
991
|
-
|
|
992
|
-
mountFrame({ index, importers, setup });
|
|
993
|
-
// The importers module is shared with the workshop page, which accepts a story edit in place; a frame document
|
|
994
|
-
// showing the old module reloads instead, and without this the edit would dead-end here and reload every page.
|
|
995
|
-
import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
996
|
-
`;
|
|
997
|
-
}
|
|
998
951
|
var PLATFORM = /[\\/]node_modules[\\/](@types[\\/]react(-dom)?|typescript)[\\/]/;
|
|
999
952
|
async function createPropReader(root, stories, { skip } = {}) {
|
|
1000
953
|
const skipped = (file) => PLATFORM.test(file) || skip?.(file) === true;
|
|
@@ -1070,36 +1023,89 @@ function componentProps(tsm, checker, source) {
|
|
|
1070
1023
|
}
|
|
1071
1024
|
function propTable(tsm, checker, props, at, skipped) {
|
|
1072
1025
|
const argTypes = {};
|
|
1073
|
-
const
|
|
1026
|
+
const reading = { tsm, checker, at, skipped, sources: /* @__PURE__ */ new Set() };
|
|
1074
1027
|
for (const prop2 of checker.getPropertiesOfType(checker.getApparentType(props))) {
|
|
1075
|
-
const files = (prop2
|
|
1028
|
+
const files = declaredIn(prop2);
|
|
1076
1029
|
if (files.length > 0 && files.every(skipped)) continue;
|
|
1077
1030
|
const argType = controlFor(
|
|
1078
|
-
|
|
1031
|
+
reading,
|
|
1079
1032
|
checker.getNonNullableType(checker.getTypeOfSymbolAtLocation(prop2, at)),
|
|
1080
1033
|
() => writtenOrder(tsm, checker, prop2)
|
|
1081
1034
|
);
|
|
1082
1035
|
if (!argType) continue;
|
|
1083
1036
|
const description = tsm.displayPartsToString(prop2.getDocumentationComment(checker)).replace(/\s+/g, " ").trim();
|
|
1084
1037
|
argTypes[prop2.getName()] = description ? { ...argType, description } : argType;
|
|
1085
|
-
|
|
1038
|
+
record(reading, files);
|
|
1086
1039
|
}
|
|
1087
|
-
return { argTypes, sources: [...sources] };
|
|
1040
|
+
return { argTypes, sources: [...reading.sources] };
|
|
1088
1041
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1042
|
+
var declaredIn = (symbol) => (symbol.getDeclarations() ?? []).map((declaration) => declaration.getSourceFile().fileName);
|
|
1043
|
+
var IN_PACKAGES = /[\\/]node_modules[\\/]/;
|
|
1044
|
+
function record({ sources }, files) {
|
|
1045
|
+
for (const file of files) if (!IN_PACKAGES.test(file)) sources.add(file);
|
|
1046
|
+
}
|
|
1047
|
+
function controlFor(reading, type, written) {
|
|
1048
|
+
const { TypeFlags } = reading.tsm;
|
|
1091
1049
|
const parts = type.isUnion() ? type.types : [type];
|
|
1092
|
-
|
|
1093
|
-
if (
|
|
1094
|
-
if (type.flags & TypeFlags.String) return { control: { type: "text" } };
|
|
1050
|
+
if (parts.every((part) => part.flags & TypeFlags.BooleanLike)) return { control: { type: "boolean" } };
|
|
1051
|
+
if (parts.some((part) => part.flags & TypeFlags.String)) return { control: { type: "text" } };
|
|
1095
1052
|
if (type.flags & TypeFlags.Number) return { control: { type: "number" } };
|
|
1096
|
-
|
|
1097
|
-
|
|
1053
|
+
const options = literalsOf(reading, parts);
|
|
1054
|
+
if (options.length > 1) {
|
|
1098
1055
|
const order = written();
|
|
1099
1056
|
const same = order?.length === options.length && options.every((option) => order.includes(option));
|
|
1100
1057
|
return { options: same ? order : options };
|
|
1101
1058
|
}
|
|
1102
|
-
|
|
1059
|
+
const shape = shapeOf(reading, type, 0);
|
|
1060
|
+
return shape && shape.name !== "enum" && !(shape.name in SCALARS) ? { control: "object", type: shape } : null;
|
|
1061
|
+
}
|
|
1062
|
+
var SCALARS = { boolean: true, string: true, number: true };
|
|
1063
|
+
function literalsOf({ tsm }, parts) {
|
|
1064
|
+
return parts.filter((part) => part.flags & tsm.TypeFlags.StringLiteral).map((part) => part.value);
|
|
1065
|
+
}
|
|
1066
|
+
var MAX_DEPTH2 = 4;
|
|
1067
|
+
function shapeOf(reading, type, depth) {
|
|
1068
|
+
const { tsm, checker } = reading;
|
|
1069
|
+
const { TypeFlags } = tsm;
|
|
1070
|
+
if (depth > MAX_DEPTH2) return null;
|
|
1071
|
+
const whole = checker.getNonNullableType(type);
|
|
1072
|
+
const parts = whole.isUnion() ? whole.types : [whole];
|
|
1073
|
+
if (parts.length === 0) return null;
|
|
1074
|
+
if (parts.every((part) => part.flags & TypeFlags.BooleanLike)) return { name: "boolean" };
|
|
1075
|
+
if (parts.some((part) => part.flags & TypeFlags.String)) return { name: "string" };
|
|
1076
|
+
if (whole.flags & TypeFlags.Number) return { name: "number" };
|
|
1077
|
+
const literals = literalsOf(reading, parts);
|
|
1078
|
+
if (literals.length === parts.length || literals.length > 1) return { name: "enum", value: literals };
|
|
1079
|
+
if (parts.length > 1) {
|
|
1080
|
+
const members = parts.map((part) => shapeOf(reading, part, depth + 1));
|
|
1081
|
+
return members.every((member) => member?.name === "object") ? { name: "union", value: members } : null;
|
|
1082
|
+
}
|
|
1083
|
+
if (checker.isArrayType(whole)) {
|
|
1084
|
+
const [item] = checker.getTypeArguments(whole);
|
|
1085
|
+
const shape = item && shapeOf(reading, item, depth + 1);
|
|
1086
|
+
return shape ? { name: "array", value: shape } : null;
|
|
1087
|
+
}
|
|
1088
|
+
if (checker.isTupleType(whole)) {
|
|
1089
|
+
const items = checker.getTypeArguments(whole).map((item) => shapeOf(reading, item, depth + 1));
|
|
1090
|
+
const [first] = items;
|
|
1091
|
+
const same = first && items.every((item) => JSON.stringify(item) === JSON.stringify(first));
|
|
1092
|
+
return same ? { name: "array", value: first } : null;
|
|
1093
|
+
}
|
|
1094
|
+
if (!(whole.flags & (TypeFlags.Object | TypeFlags.Intersection))) return null;
|
|
1095
|
+
if (whole.getCallSignatures().length > 0 || whole.getConstructSignatures().length > 0) return null;
|
|
1096
|
+
if (checker.getIndexInfosOfType(whole).length > 0) return null;
|
|
1097
|
+
const declared = (whole.getSymbol() ?? whole.aliasSymbol)?.getDeclarations() ?? [];
|
|
1098
|
+
if (declared.some((declaration) => IN_PACKAGES.test(declaration.getSourceFile().fileName))) return null;
|
|
1099
|
+
const fields = {};
|
|
1100
|
+
for (const prop2 of checker.getPropertiesOfType(whole)) {
|
|
1101
|
+
const files = declaredIn(prop2);
|
|
1102
|
+
if (files.some((file) => IN_PACKAGES.test(file) || reading.skipped(file))) return null;
|
|
1103
|
+
const field = shapeOf(reading, checker.getTypeOfSymbolAtLocation(prop2, reading.at), depth + 1);
|
|
1104
|
+
if (field) fields[prop2.getName()] = field;
|
|
1105
|
+
else if (!(prop2.flags & tsm.SymbolFlags.Optional)) return null;
|
|
1106
|
+
record(reading, files);
|
|
1107
|
+
}
|
|
1108
|
+
return Object.keys(fields).length > 0 ? { name: "object", value: fields } : null;
|
|
1103
1109
|
}
|
|
1104
1110
|
function writtenOrder(tsm, checker, prop2) {
|
|
1105
1111
|
const [declaration, ...others] = prop2.getDeclarations() ?? [];
|
|
@@ -1126,6 +1132,7 @@ function writtenOrder(tsm, checker, prop2) {
|
|
|
1126
1132
|
if (alias && rest.length === 0 && tsm.isTypeAliasDeclaration(alias) && !alias.typeParameters) {
|
|
1127
1133
|
return literals(alias.type);
|
|
1128
1134
|
}
|
|
1135
|
+
if (target && target.flags & (tsm.SymbolFlags.Interface | tsm.SymbolFlags.Class)) return [];
|
|
1129
1136
|
}
|
|
1130
1137
|
return null;
|
|
1131
1138
|
};
|
|
@@ -1159,10 +1166,339 @@ export const ${PROP_ARG_TYPES_EXPORT} = ${JSON.stringify(table.argTypes)};
|
|
|
1159
1166
|
}
|
|
1160
1167
|
};
|
|
1161
1168
|
}
|
|
1169
|
+
var IDLE_CLOSE_MS = 12e4;
|
|
1170
|
+
var SHOT_DEADLINE_MS = 2e4;
|
|
1171
|
+
var QUEUE_CAP = 8;
|
|
1172
|
+
var QUEUE_FULL = "capture queue is full";
|
|
1173
|
+
var INSTALL_PLAYWRIGHT = "install playwright";
|
|
1174
|
+
var INSTALL_CHROMIUM = "install Chromium for Playwright: npx playwright install chromium";
|
|
1175
|
+
var loadPlaywright = () => import('playwright');
|
|
1176
|
+
async function captureAvailability(load = loadPlaywright) {
|
|
1177
|
+
let playwright;
|
|
1178
|
+
try {
|
|
1179
|
+
playwright = await load();
|
|
1180
|
+
} catch {
|
|
1181
|
+
return { unavailable: INSTALL_PLAYWRIGHT };
|
|
1182
|
+
}
|
|
1183
|
+
let executable;
|
|
1184
|
+
try {
|
|
1185
|
+
executable = playwright.chromium.executablePath();
|
|
1186
|
+
} catch (err) {
|
|
1187
|
+
return { unavailable: messageOf(err) };
|
|
1188
|
+
}
|
|
1189
|
+
return existsSync(executable) ? { available: true } : { unavailable: INSTALL_CHROMIUM };
|
|
1190
|
+
}
|
|
1191
|
+
var WIRE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding"]);
|
|
1192
|
+
async function passThrough(route, fetchImpl) {
|
|
1193
|
+
const request = route.request();
|
|
1194
|
+
const method = request.method();
|
|
1195
|
+
if (method !== "GET" && method !== "HEAD") return route.abort();
|
|
1196
|
+
try {
|
|
1197
|
+
const response = await fetchImpl(request.url(), { method, headers: request.headers(), redirect: "manual" });
|
|
1198
|
+
await route.fulfill({
|
|
1199
|
+
status: response.status,
|
|
1200
|
+
headers: Object.fromEntries([...response.headers].filter(([name]) => !WIRE_HEADERS.has(name))),
|
|
1201
|
+
body: Buffer.from(await response.arrayBuffer())
|
|
1202
|
+
});
|
|
1203
|
+
} catch {
|
|
1204
|
+
await route.abort();
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
async function guard(page, url, allow, fetchImpl) {
|
|
1208
|
+
const own = new URL(url);
|
|
1209
|
+
const secure = own.protocol === "https:";
|
|
1210
|
+
const reach = new Set(allow);
|
|
1211
|
+
await page.route("**/*", (route) => {
|
|
1212
|
+
const origin = new URL(route.request().url()).origin;
|
|
1213
|
+
if (origin === own.origin) return route.continue();
|
|
1214
|
+
if (!reach.has(origin)) return route.abort();
|
|
1215
|
+
return secure ? passThrough(route, fetchImpl) : route.continue();
|
|
1216
|
+
});
|
|
1217
|
+
const ownSocket = `${secure ? "wss:" : "ws:"}//${own.host}`;
|
|
1218
|
+
await page.routeWebSocket(
|
|
1219
|
+
(u) => `${u.protocol}//${u.host}` !== ownSocket,
|
|
1220
|
+
(ws) => ws.close()
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
async function launchCaptureBrowser(load = loadPlaywright, fetchImpl = fetch) {
|
|
1224
|
+
let playwright;
|
|
1225
|
+
try {
|
|
1226
|
+
playwright = await load();
|
|
1227
|
+
} catch {
|
|
1228
|
+
return { unavailable: INSTALL_PLAYWRIGHT };
|
|
1229
|
+
}
|
|
1230
|
+
let chromium;
|
|
1231
|
+
try {
|
|
1232
|
+
chromium = Promise.resolve(await playwright.chromium.launch({ headless: true }));
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
return { unavailable: messageOf(err) };
|
|
1235
|
+
}
|
|
1236
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
1237
|
+
let idle;
|
|
1238
|
+
let outstanding = 0;
|
|
1239
|
+
let tail = Promise.resolve();
|
|
1240
|
+
const shut = async () => {
|
|
1241
|
+
clearTimeout(idle);
|
|
1242
|
+
const closing = chromium;
|
|
1243
|
+
chromium = null;
|
|
1244
|
+
contexts.clear();
|
|
1245
|
+
await closing?.then((b) => b.close(), () => {
|
|
1246
|
+
});
|
|
1247
|
+
};
|
|
1248
|
+
const arm = () => {
|
|
1249
|
+
clearTimeout(idle);
|
|
1250
|
+
idle = setTimeout(() => void shut(), IDLE_CLOSE_MS);
|
|
1251
|
+
idle.unref?.();
|
|
1252
|
+
};
|
|
1253
|
+
const browser = () => {
|
|
1254
|
+
if (!chromium) {
|
|
1255
|
+
const launching = playwright.chromium.launch({ headless: true });
|
|
1256
|
+
chromium = launching;
|
|
1257
|
+
launching.catch(() => {
|
|
1258
|
+
if (chromium === launching) chromium = null;
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
return chromium;
|
|
1262
|
+
};
|
|
1263
|
+
const contextFor = (dpr, secure) => {
|
|
1264
|
+
const key = `${dpr}:${secure}`;
|
|
1265
|
+
let context = contexts.get(key);
|
|
1266
|
+
if (!context) {
|
|
1267
|
+
const making = browser().then(
|
|
1268
|
+
(b) => b.newContext({ deviceScaleFactor: dpr, ignoreHTTPSErrors: secure, serviceWorkers: "block" })
|
|
1269
|
+
);
|
|
1270
|
+
context = making;
|
|
1271
|
+
contexts.set(key, making);
|
|
1272
|
+
making.catch(() => {
|
|
1273
|
+
if (contexts.get(key) === making) contexts.delete(key);
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
return context;
|
|
1277
|
+
};
|
|
1278
|
+
const render = async (page, payload, url, allow) => {
|
|
1279
|
+
await guard(page, url, allow, fetchImpl);
|
|
1280
|
+
await page.setViewportSize({ width: Math.ceil(payload.box.width), height: Math.ceil(payload.box.height) });
|
|
1281
|
+
await page.emulateMedia({ colorScheme: payload.colorScheme });
|
|
1282
|
+
await page.goto(url);
|
|
1283
|
+
await page.waitForFunction(() => window.__forgeCapture !== void 0);
|
|
1284
|
+
await page.evaluate((p) => window.__forgeCapture.render(p), payload);
|
|
1285
|
+
return page.locator("[data-fg-capture]").screenshot({ type: "png" });
|
|
1286
|
+
};
|
|
1287
|
+
const shoot = async (url, payload, allow) => {
|
|
1288
|
+
let page = null;
|
|
1289
|
+
let late = false;
|
|
1290
|
+
let timer;
|
|
1291
|
+
const deadline = new Promise((_, reject) => {
|
|
1292
|
+
timer = setTimeout(() => reject(new Error(`capture took longer than ${SHOT_DEADLINE_MS / 1e3} s`)), SHOT_DEADLINE_MS);
|
|
1293
|
+
});
|
|
1294
|
+
const work = (async () => {
|
|
1295
|
+
const opened = await (await contextFor(payload.dpr, url.startsWith("https:"))).newPage();
|
|
1296
|
+
page = opened;
|
|
1297
|
+
if (late) throw new Error("capture timed out");
|
|
1298
|
+
return render(opened, payload, url, allow);
|
|
1299
|
+
})();
|
|
1300
|
+
try {
|
|
1301
|
+
return await Promise.race([work, deadline]);
|
|
1302
|
+
} finally {
|
|
1303
|
+
late = true;
|
|
1304
|
+
clearTimeout(timer);
|
|
1305
|
+
await page?.close().catch(() => {
|
|
1306
|
+
});
|
|
1307
|
+
void work.catch(() => page?.close().catch(() => {
|
|
1308
|
+
}));
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
arm();
|
|
1312
|
+
return {
|
|
1313
|
+
capture(url, payload, allow = []) {
|
|
1314
|
+
if (outstanding >= QUEUE_CAP) return Promise.reject(new Error(QUEUE_FULL));
|
|
1315
|
+
outstanding += 1;
|
|
1316
|
+
clearTimeout(idle);
|
|
1317
|
+
const run = tail.then(() => shoot(url, payload, allow));
|
|
1318
|
+
tail = run.catch(() => {
|
|
1319
|
+
});
|
|
1320
|
+
return run.finally(() => {
|
|
1321
|
+
outstanding -= 1;
|
|
1322
|
+
if (outstanding === 0) arm();
|
|
1323
|
+
});
|
|
1324
|
+
},
|
|
1325
|
+
close: shut
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// src/vite/capture/cache.ts
|
|
1330
|
+
function createCaptureCache(limits = { entries: 64, bytes: 64 * 1024 * 1024 }) {
|
|
1331
|
+
const held = /* @__PURE__ */ new Map();
|
|
1332
|
+
let bytes = 0;
|
|
1333
|
+
const drop = (key) => {
|
|
1334
|
+
const png = held.get(key);
|
|
1335
|
+
if (!png) return;
|
|
1336
|
+
held.delete(key);
|
|
1337
|
+
bytes -= png.length;
|
|
1338
|
+
};
|
|
1339
|
+
return {
|
|
1340
|
+
get(key) {
|
|
1341
|
+
const png = held.get(key);
|
|
1342
|
+
if (!png) return void 0;
|
|
1343
|
+
held.delete(key);
|
|
1344
|
+
held.set(key, png);
|
|
1345
|
+
return png;
|
|
1346
|
+
},
|
|
1347
|
+
set(key, png) {
|
|
1348
|
+
drop(key);
|
|
1349
|
+
if (png.length > limits.bytes) return;
|
|
1350
|
+
held.set(key, png);
|
|
1351
|
+
bytes += png.length;
|
|
1352
|
+
for (const oldest of held.keys()) {
|
|
1353
|
+
if (held.size <= limits.entries && bytes <= limits.bytes) break;
|
|
1354
|
+
drop(oldest);
|
|
1355
|
+
}
|
|
1356
|
+
},
|
|
1357
|
+
clear() {
|
|
1358
|
+
held.clear();
|
|
1359
|
+
bytes = 0;
|
|
1360
|
+
},
|
|
1361
|
+
get size() {
|
|
1362
|
+
return held.size;
|
|
1363
|
+
},
|
|
1364
|
+
get bytes() {
|
|
1365
|
+
return bytes;
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// src/vite/capture/endpoint.ts
|
|
1371
|
+
var MAX_BODY = 16 * 1024 * 1024;
|
|
1372
|
+
var keyOf2 = (payload) => createHash("sha1").update(stableStringify(payload)).digest("hex");
|
|
1373
|
+
var urlHost = (address) => {
|
|
1374
|
+
const v4 = address.replace(/^::ffff:(?=\d+\.\d+\.\d+\.\d+$)/, "");
|
|
1375
|
+
return v4.includes(":") ? `[${v4.replace("%", "%25")}]` : v4;
|
|
1376
|
+
};
|
|
1377
|
+
var socketOrigin = (req, protocol) => `${protocol}://${urlHost(req.socket.localAddress ?? "127.0.0.1")}:${req.socket.localPort}`;
|
|
1378
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1379
|
+
function ownOrigin(origin, req, protocol, server) {
|
|
1380
|
+
let url;
|
|
1381
|
+
try {
|
|
1382
|
+
url = new URL(origin);
|
|
1383
|
+
} catch {
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
if (url.origin === socketOrigin(req, protocol)) return true;
|
|
1387
|
+
if (url.protocol === `${protocol}:` && LOOPBACK.has(url.hostname) && url.port === String(req.socket.localPort)) return true;
|
|
1388
|
+
const printed = [...server.resolvedUrls?.local ?? [], ...server.resolvedUrls?.network ?? []];
|
|
1389
|
+
return printed.some((u) => new URL(u).origin === url.origin);
|
|
1390
|
+
}
|
|
1391
|
+
var json = (res, status, body2) => {
|
|
1392
|
+
res.statusCode = status;
|
|
1393
|
+
res.setHeader("Content-Type", "application/json");
|
|
1394
|
+
res.end(JSON.stringify(body2));
|
|
1395
|
+
};
|
|
1396
|
+
function readBody(req) {
|
|
1397
|
+
return new Promise((resolve6, reject) => {
|
|
1398
|
+
const chunks = [];
|
|
1399
|
+
let size = 0;
|
|
1400
|
+
req.on("data", (chunk) => {
|
|
1401
|
+
size += chunk.length;
|
|
1402
|
+
if (size <= MAX_BODY) return void chunks.push(chunk);
|
|
1403
|
+
req.pause();
|
|
1404
|
+
req.removeAllListeners("data");
|
|
1405
|
+
resolve6(null);
|
|
1406
|
+
});
|
|
1407
|
+
req.on("end", () => resolve6(Buffer.concat(chunks).toString("utf8")));
|
|
1408
|
+
req.on("error", reject);
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
function serveCapture(server, base, allowOrigins = () => []) {
|
|
1412
|
+
const cache = createCaptureCache();
|
|
1413
|
+
let launched = null;
|
|
1414
|
+
let revision = 0;
|
|
1415
|
+
const browser = () => {
|
|
1416
|
+
if (!launched) {
|
|
1417
|
+
const launching = launchCaptureBrowser();
|
|
1418
|
+
launched = launching;
|
|
1419
|
+
void launching.then((result) => {
|
|
1420
|
+
if ("unavailable" in result && launched === launching) launched = null;
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
return launched;
|
|
1424
|
+
};
|
|
1425
|
+
const protocol = server.config.server.https ? "https" : "http";
|
|
1426
|
+
const capture = async (req, res) => {
|
|
1427
|
+
const origin = req.headers.origin;
|
|
1428
|
+
if (origin !== void 0 && !ownOrigin(origin, req, protocol, server)) {
|
|
1429
|
+
return json(res, 403, { error: "a capture is only taken for this server\u2019s own pages" });
|
|
1430
|
+
}
|
|
1431
|
+
const type = (req.headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase();
|
|
1432
|
+
if (type !== "application/json") return json(res, 415, { error: "a capture payload is sent as application/json" });
|
|
1433
|
+
const body2 = await readBody(req);
|
|
1434
|
+
if (body2 === null) {
|
|
1435
|
+
res.setHeader("Connection", "close");
|
|
1436
|
+
json(res, 413, { error: "capture payload is too large" });
|
|
1437
|
+
return void res.once("finish", () => req.destroy());
|
|
1438
|
+
}
|
|
1439
|
+
let parsed;
|
|
1440
|
+
try {
|
|
1441
|
+
parsed = JSON.parse(body2);
|
|
1442
|
+
} catch {
|
|
1443
|
+
return json(res, 400, { error: "capture payload is not JSON" });
|
|
1444
|
+
}
|
|
1445
|
+
const payload = parsePayload(parsed);
|
|
1446
|
+
if ("error" in payload) return json(res, 400, payload);
|
|
1447
|
+
const key = keyOf2(payload);
|
|
1448
|
+
let png = cache.get(key);
|
|
1449
|
+
if (!png) {
|
|
1450
|
+
const result = await browser();
|
|
1451
|
+
if ("unavailable" in result) return json(res, 503, { available: false, reason: result.unavailable });
|
|
1452
|
+
const page = `${socketOrigin(req, protocol)}${base}${payload.isolate ? CAPTURE_PAGES.frame : CAPTURE_PAGES.shell}`;
|
|
1453
|
+
const at = revision;
|
|
1454
|
+
try {
|
|
1455
|
+
png = await result.capture(page, payload, allowOrigins());
|
|
1456
|
+
} catch (err) {
|
|
1457
|
+
const message = messageOf(err);
|
|
1458
|
+
if (message === QUEUE_FULL) return json(res, 503, { available: false, reason: QUEUE_FULL });
|
|
1459
|
+
return json(res, 500, { error: message });
|
|
1460
|
+
}
|
|
1461
|
+
if (at === revision) cache.set(key, png);
|
|
1462
|
+
}
|
|
1463
|
+
res.statusCode = 200;
|
|
1464
|
+
res.setHeader("Content-Type", "image/png");
|
|
1465
|
+
res.end(png);
|
|
1466
|
+
};
|
|
1467
|
+
const status = async (res) => {
|
|
1468
|
+
const result = await captureAvailability();
|
|
1469
|
+
json(res, 200, "unavailable" in result ? { available: false, reason: result.unavailable } : { available: true });
|
|
1470
|
+
};
|
|
1471
|
+
server.middlewares.use((req, res, next) => {
|
|
1472
|
+
if (req.url?.split("?")[0] !== `${base}${CAPTURE_PATH}`) return next();
|
|
1473
|
+
if (req.method === "GET") return void status(res).catch(next);
|
|
1474
|
+
if (req.method === "POST") return void capture(req, res).catch(next);
|
|
1475
|
+
next();
|
|
1476
|
+
});
|
|
1477
|
+
return {
|
|
1478
|
+
revise() {
|
|
1479
|
+
revision += 1;
|
|
1480
|
+
cache.clear();
|
|
1481
|
+
},
|
|
1482
|
+
async close() {
|
|
1483
|
+
revision += 1;
|
|
1484
|
+
cache.clear();
|
|
1485
|
+
const closing = launched;
|
|
1486
|
+
launched = null;
|
|
1487
|
+
const result = await closing;
|
|
1488
|
+
if (result && !("unavailable" in result)) await result.close();
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1162
1492
|
|
|
1163
1493
|
// src/vite/servePages.ts
|
|
1164
|
-
var PAGES2 = {
|
|
1165
|
-
|
|
1494
|
+
var PAGES2 = {
|
|
1495
|
+
"/": "shell-entry.js",
|
|
1496
|
+
"/index.html": "shell-entry.js",
|
|
1497
|
+
"/frame.html": "frame-entry.js",
|
|
1498
|
+
[`/${CAPTURE_PAGES.shell}`]: "capture-entry.js",
|
|
1499
|
+
[`/${CAPTURE_PAGES.frame}`]: "capture-frame-entry.js"
|
|
1500
|
+
};
|
|
1501
|
+
function servePages(server, base, shell) {
|
|
1166
1502
|
server.middlewares.use((req, res, next) => {
|
|
1167
1503
|
const url = req.url ?? "/";
|
|
1168
1504
|
const path = url.split("?")[0] ?? "/";
|
|
@@ -1170,7 +1506,7 @@ function servePages(server, base, title) {
|
|
|
1170
1506
|
const page = path === prefix ? "/" : path.startsWith(`${prefix}/`) ? path.slice(prefix.length) : null;
|
|
1171
1507
|
const entry = req.method === "GET" && page !== null ? PAGES2[page] : void 0;
|
|
1172
1508
|
if (!entry) return next();
|
|
1173
|
-
server.transformIndexHtml(url, html(entry, "serve",
|
|
1509
|
+
server.transformIndexHtml(url, html(entry, "serve", shell().title)).then(
|
|
1174
1510
|
(doc) => {
|
|
1175
1511
|
res.statusCode = 200;
|
|
1176
1512
|
res.setHeader("Content-Type", "text/html");
|
|
@@ -1179,15 +1515,14 @@ function servePages(server, base, title) {
|
|
|
1179
1515
|
(err) => next(err)
|
|
1180
1516
|
);
|
|
1181
1517
|
});
|
|
1518
|
+
return serveCapture(server, base, () => shell().allowOrigins);
|
|
1182
1519
|
}
|
|
1183
|
-
|
|
1184
|
-
// src/vite/shellTitle.ts
|
|
1185
|
-
function shellTitle(code, file) {
|
|
1520
|
+
function configOf(code, file) {
|
|
1186
1521
|
let program;
|
|
1187
1522
|
try {
|
|
1188
1523
|
program = parseFile(code, file);
|
|
1189
1524
|
} catch {
|
|
1190
|
-
return
|
|
1525
|
+
return null;
|
|
1191
1526
|
}
|
|
1192
1527
|
const bindings = /* @__PURE__ */ new Map();
|
|
1193
1528
|
let config;
|
|
@@ -1202,7 +1537,46 @@ function shellTitle(code, file) {
|
|
|
1202
1537
|
}
|
|
1203
1538
|
}
|
|
1204
1539
|
const wrapper = calleeOf(config, bindings);
|
|
1205
|
-
return
|
|
1540
|
+
return { config: objectOf(config, bindings, new Set(wrapper ? [wrapper] : [])), bindings };
|
|
1541
|
+
}
|
|
1542
|
+
function shellTitle(code, file) {
|
|
1543
|
+
const read = configOf(code, file);
|
|
1544
|
+
return read ? stringOf(prop(read.config, "title"), read.bindings) : void 0;
|
|
1545
|
+
}
|
|
1546
|
+
function arrayOf(node, bindings, depth = 0) {
|
|
1547
|
+
if (!node || depth > 16) return null;
|
|
1548
|
+
if (node.type === "ArrayExpression") return node;
|
|
1549
|
+
if (node.type === "Identifier") return arrayOf(bindings.get(node.name), bindings, depth + 1);
|
|
1550
|
+
if ("expression" in node && (node.type.startsWith("TS") || node.type === "ParenthesizedExpression")) {
|
|
1551
|
+
return arrayOf(node.expression, bindings, depth + 1);
|
|
1552
|
+
}
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
var originOf = (url) => {
|
|
1556
|
+
if (url === void 0) return [];
|
|
1557
|
+
try {
|
|
1558
|
+
const { origin } = new URL(url);
|
|
1559
|
+
return origin === "null" ? [] : [origin];
|
|
1560
|
+
} catch {
|
|
1561
|
+
return [];
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
function shellAllowOrigins(code, file) {
|
|
1565
|
+
const read = configOf(code, file);
|
|
1566
|
+
if (!read) return [];
|
|
1567
|
+
const capture = objectOf(prop(read.config, "capture"), read.bindings, /* @__PURE__ */ new Set());
|
|
1568
|
+
const list = arrayOf(prop(capture, "allowOrigins"), read.bindings);
|
|
1569
|
+
return (list?.elements ?? []).flatMap((el) => el && el.type !== "SpreadElement" ? originOf(stringOf(el, read.bindings)) : []);
|
|
1570
|
+
}
|
|
1571
|
+
function readShellFields(file) {
|
|
1572
|
+
let code;
|
|
1573
|
+
try {
|
|
1574
|
+
if (file === void 0) throw new Error("no shell config");
|
|
1575
|
+
code = readFileSync(file, "utf8");
|
|
1576
|
+
} catch {
|
|
1577
|
+
return { title: void 0, allowOrigins: [] };
|
|
1578
|
+
}
|
|
1579
|
+
return { title: shellTitle(code, file), allowOrigins: shellAllowOrigins(code, file) };
|
|
1206
1580
|
}
|
|
1207
1581
|
var SHIMS = [
|
|
1208
1582
|
[/^@?storybook\/preview-api$/, "@weasel-js/forge/preview-api"],
|
|
@@ -1219,6 +1593,105 @@ function storybookShims() {
|
|
|
1219
1593
|
}
|
|
1220
1594
|
};
|
|
1221
1595
|
}
|
|
1596
|
+
|
|
1597
|
+
// src/vite/capture/entries.ts
|
|
1598
|
+
var body = (mount) => `import index from 'virtual:forge/index.js';
|
|
1599
|
+
import importers from 'virtual:forge/importers.js';
|
|
1600
|
+
import setup from 'virtual:forge/frame-config.js';
|
|
1601
|
+
|
|
1602
|
+
${mount}
|
|
1603
|
+
// The endpoint drops its captures on any edit; a page loaded before one would render the old module.
|
|
1604
|
+
import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
1605
|
+
`;
|
|
1606
|
+
function captureEntry() {
|
|
1607
|
+
return `import { mountCapture } from '@weasel-js/forge/frame';
|
|
1608
|
+
import '@weasel-js/labkit/styles.css';
|
|
1609
|
+
import '@weasel-js/forge/shell.css';
|
|
1610
|
+
import config from 'virtual:forge/shell-config.js';
|
|
1611
|
+
${body("mountCapture({ index, importers, setup, labTheme: config.labTheme });")}`;
|
|
1612
|
+
}
|
|
1613
|
+
function captureFrameEntry(styles) {
|
|
1614
|
+
return `import { mountCapture } from '@weasel-js/forge/frame';
|
|
1615
|
+
import '@weasel-js/forge/frame.css';
|
|
1616
|
+
${styles.map((path) => `import ${JSON.stringify(path)};
|
|
1617
|
+
`).join("")}${body("mountCapture({ index, importers, setup });")}`;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// src/vite/entries.ts
|
|
1621
|
+
function shellEntry(base, stories) {
|
|
1622
|
+
return `import { followEdits, mountWorkshop } from '@weasel-js/forge/shell';
|
|
1623
|
+
import '@weasel-js/labkit/styles.css';
|
|
1624
|
+
import '@weasel-js/forge/shell.css';
|
|
1625
|
+
import index from 'virtual:forge/index.js';
|
|
1626
|
+
import importers from 'virtual:forge/importers.js';
|
|
1627
|
+
import config from 'virtual:forge/shell-config.js';
|
|
1628
|
+
import setup from 'virtual:forge/frame-config.js';
|
|
1629
|
+
|
|
1630
|
+
const workshop = mountWorkshop({
|
|
1631
|
+
index,
|
|
1632
|
+
importers,
|
|
1633
|
+
setup,
|
|
1634
|
+
config,
|
|
1635
|
+
frameUrl: ${JSON.stringify(`${base}frame.html`)},
|
|
1636
|
+
captureBase: ${JSON.stringify(base)},
|
|
1637
|
+
stories: ${JSON.stringify(stories)},
|
|
1638
|
+
});
|
|
1639
|
+
// Read from source on first request, so the graph costs nothing until the page is up.
|
|
1640
|
+
import('virtual:forge/deps.js').then((m) => workshop.setDependencies(m.default));
|
|
1641
|
+
const edits = followEdits(import.meta.hot, workshop);
|
|
1642
|
+
import.meta.hot?.accept('virtual:forge/importers.js', (next) => edits.importers(next?.default));
|
|
1643
|
+
`;
|
|
1644
|
+
}
|
|
1645
|
+
function frameEntry(styles) {
|
|
1646
|
+
return `import { mountFrame } from '@weasel-js/forge/frame';
|
|
1647
|
+
import '@weasel-js/forge/frame.css';
|
|
1648
|
+
${styles.map((path) => `import ${JSON.stringify(path)};
|
|
1649
|
+
`).join("")}import index from 'virtual:forge/index.js';
|
|
1650
|
+
import importers from 'virtual:forge/importers.js';
|
|
1651
|
+
import setup from 'virtual:forge/frame-config.js';
|
|
1652
|
+
|
|
1653
|
+
mountFrame({ index, importers, setup });
|
|
1654
|
+
// The importers module is shared with the workshop page, which accepts a story edit in place; a frame document
|
|
1655
|
+
// showing the old module reloads instead, and without this the edit would dead-end here and reload every page.
|
|
1656
|
+
import.meta.hot?.accept('virtual:forge/importers.js', () => location.reload());
|
|
1657
|
+
`;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// src/vite/virtualModules.ts
|
|
1661
|
+
var PREFIX = "virtual:forge/";
|
|
1662
|
+
var MODULES = /* @__PURE__ */ new Set([
|
|
1663
|
+
"index.js",
|
|
1664
|
+
"importers.js",
|
|
1665
|
+
"deps.js",
|
|
1666
|
+
"frame-config.js",
|
|
1667
|
+
"shell-config.js",
|
|
1668
|
+
"shell-entry.js",
|
|
1669
|
+
"frame-entry.js",
|
|
1670
|
+
"capture-entry.js",
|
|
1671
|
+
"capture-frame-entry.js"
|
|
1672
|
+
]);
|
|
1673
|
+
function virtualModules(options, src) {
|
|
1674
|
+
const configModule = (path) => path ? `export { default } from ${JSON.stringify(resolve(src.root(), path))};
|
|
1675
|
+
` : "export default {};\n";
|
|
1676
|
+
const frameStyles = () => (options.frameStyles ?? []).map((path) => resolve(src.root(), path));
|
|
1677
|
+
return {
|
|
1678
|
+
"index.js": () => `export default ${JSON.stringify(src.index())};
|
|
1679
|
+
`,
|
|
1680
|
+
"importers.js": () => {
|
|
1681
|
+
const lines = [...src.files()].filter(([, entries]) => entries.length > 0).map(([file]) => ` ${JSON.stringify(file)}: () => import(${JSON.stringify(file)}),`);
|
|
1682
|
+
return `export default {
|
|
1683
|
+
${lines.join("\n")}
|
|
1684
|
+
};
|
|
1685
|
+
`;
|
|
1686
|
+
},
|
|
1687
|
+
"frame-config.js": () => configModule(options.frameConfig),
|
|
1688
|
+
"shell-config.js": () => configModule(options.shellConfig),
|
|
1689
|
+
"shell-entry.js": () => shellEntry(src.base(), options.stories),
|
|
1690
|
+
"frame-entry.js": () => frameEntry(frameStyles()),
|
|
1691
|
+
"capture-entry.js": () => captureEntry(),
|
|
1692
|
+
"capture-frame-entry.js": () => captureFrameEntry(frameStyles())
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1222
1695
|
var WAIT_MS = 5e3;
|
|
1223
1696
|
var REWRITE_MS = 250;
|
|
1224
1697
|
var watchDir = (dir, onEvent) => {
|
|
@@ -1230,7 +1703,7 @@ var watchDir = (dir, onEvent) => {
|
|
|
1230
1703
|
};
|
|
1231
1704
|
function fileEventsArrive({ waitMs = WAIT_MS, watch = watchDir } = {}) {
|
|
1232
1705
|
const dir = mkdtempSync(join(tmpdir(), "forge-watch-"));
|
|
1233
|
-
return new Promise((
|
|
1706
|
+
return new Promise((resolve6) => {
|
|
1234
1707
|
let settled2 = false;
|
|
1235
1708
|
let writes = 0;
|
|
1236
1709
|
let stop;
|
|
@@ -1243,7 +1716,7 @@ function fileEventsArrive({ waitMs = WAIT_MS, watch = watchDir } = {}) {
|
|
|
1243
1716
|
clearTimeout(deadline);
|
|
1244
1717
|
stop?.();
|
|
1245
1718
|
rmSync(dir, { recursive: true, force: true });
|
|
1246
|
-
|
|
1719
|
+
resolve6(arrived);
|
|
1247
1720
|
};
|
|
1248
1721
|
const write = () => writeFileSync(join(dir, "probe"), String(writes++));
|
|
1249
1722
|
try {
|
|
@@ -1276,8 +1749,6 @@ function watchProbe({ probe } = {}) {
|
|
|
1276
1749
|
}
|
|
1277
1750
|
|
|
1278
1751
|
// src/vite/plugin.ts
|
|
1279
|
-
var PREFIX = "virtual:forge/";
|
|
1280
|
-
var MODULES = /* @__PURE__ */ new Set(["index.js", "importers.js", "deps.js", "frame-config.js", "shell-config.js", "shell-entry.js", "frame-entry.js"]);
|
|
1281
1752
|
var SCRIPT2 = /\.[cm]?[jt]sx?$/;
|
|
1282
1753
|
function forge(options) {
|
|
1283
1754
|
let root = process.cwd();
|
|
@@ -1314,7 +1785,7 @@ function forge(options) {
|
|
|
1314
1785
|
);
|
|
1315
1786
|
return byFile;
|
|
1316
1787
|
};
|
|
1317
|
-
const index = () => [...files().values()].flat();
|
|
1788
|
+
const index = () => byComponent([...files().values()].flat());
|
|
1318
1789
|
const settle = async (targets, resolveImport) => {
|
|
1319
1790
|
const decide = createWrapperResolver((file) => readFileSync(file, "utf8"));
|
|
1320
1791
|
const current = files();
|
|
@@ -1348,32 +1819,9 @@ function forge(options) {
|
|
|
1348
1819
|
}
|
|
1349
1820
|
return graph;
|
|
1350
1821
|
}
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
try {
|
|
1355
|
-
return shellTitle(readFileSync(file, "utf8"), file);
|
|
1356
|
-
} catch {
|
|
1357
|
-
return void 0;
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
const configModule = (path) => path ? `export { default } from ${JSON.stringify(resolve(root, path))};
|
|
1361
|
-
` : "export default {};\n";
|
|
1362
|
-
const modules = {
|
|
1363
|
-
"index.js": () => `export default ${JSON.stringify(index())};
|
|
1364
|
-
`,
|
|
1365
|
-
"importers.js": () => {
|
|
1366
|
-
const lines = [...files()].filter(([, entries]) => entries.length > 0).map(([file]) => ` ${JSON.stringify(file)}: () => import(${JSON.stringify(file)}),`);
|
|
1367
|
-
return `export default {
|
|
1368
|
-
${lines.join("\n")}
|
|
1369
|
-
};
|
|
1370
|
-
`;
|
|
1371
|
-
},
|
|
1372
|
-
"frame-config.js": () => configModule(options.frameConfig),
|
|
1373
|
-
"shell-config.js": () => configModule(options.shellConfig),
|
|
1374
|
-
"shell-entry.js": () => shellEntry(base, options.stories),
|
|
1375
|
-
"frame-entry.js": () => frameEntry((options.frameStyles ?? []).map((path) => resolve(root, path)))
|
|
1376
|
-
};
|
|
1822
|
+
const shellUnder = (at) => readShellFields(options.shellConfig ? resolve(at, options.shellConfig) : void 0);
|
|
1823
|
+
const modules = virtualModules(options, { index, files, root: () => root, base: () => base });
|
|
1824
|
+
let capture = null;
|
|
1377
1825
|
const invalidate = (server, names) => {
|
|
1378
1826
|
for (const env of Object.values(server.environments)) {
|
|
1379
1827
|
for (const name of names) {
|
|
@@ -1384,12 +1832,12 @@ ${lines.join("\n")}
|
|
|
1384
1832
|
};
|
|
1385
1833
|
function redeps(server) {
|
|
1386
1834
|
if (depsSent === null || depsPending) return;
|
|
1387
|
-
const
|
|
1835
|
+
const resolve6 = async (spec, importer) => (await server.environments.client.pluginContainer.resolveId(spec, importer))?.id ?? null;
|
|
1388
1836
|
depsPending = Promise.resolve().then(async () => {
|
|
1389
|
-
const graph = await graphOf(
|
|
1390
|
-
const
|
|
1391
|
-
if (
|
|
1392
|
-
depsSent =
|
|
1837
|
+
const graph = await graphOf(resolve6);
|
|
1838
|
+
const json2 = JSON.stringify(graph);
|
|
1839
|
+
if (json2 === depsSent) return;
|
|
1840
|
+
depsSent = json2;
|
|
1393
1841
|
invalidate(server, ["deps.js"]);
|
|
1394
1842
|
server.ws.send({ type: "custom", event: "forge:deps", data: graph });
|
|
1395
1843
|
}).catch(logError).finally(() => {
|
|
@@ -1453,7 +1901,7 @@ ${lines.join("\n")}
|
|
|
1453
1901
|
];
|
|
1454
1902
|
const optimizeDeps = { entries };
|
|
1455
1903
|
if (command !== "build") return { optimizeDeps };
|
|
1456
|
-
return { optimizeDeps, build: { rolldownOptions: { input: writePages(at,
|
|
1904
|
+
return { optimizeDeps, build: { rolldownOptions: { input: writePages(at, shellUnder(at).title) } } };
|
|
1457
1905
|
},
|
|
1458
1906
|
generateBundle: {
|
|
1459
1907
|
order: "post",
|
|
@@ -1490,6 +1938,7 @@ ${lines.join("\n")}
|
|
|
1490
1938
|
},
|
|
1491
1939
|
hotUpdate({ modules: modules2, server }) {
|
|
1492
1940
|
if (this.environment.name !== "client") return;
|
|
1941
|
+
capture?.revise();
|
|
1493
1942
|
for (const file of storiesReaching(modules2)) server.ws.send({ type: "custom", event: "forge:story", data: { file } });
|
|
1494
1943
|
},
|
|
1495
1944
|
configureServer(server) {
|
|
@@ -1506,8 +1955,9 @@ ${lines.join("\n")}
|
|
|
1506
1955
|
}, logError);
|
|
1507
1956
|
});
|
|
1508
1957
|
}
|
|
1509
|
-
servePages(server, base, () =>
|
|
1510
|
-
}
|
|
1958
|
+
capture = servePages(server, base, () => shellUnder(root));
|
|
1959
|
+
},
|
|
1960
|
+
closeServer: () => capture?.close()
|
|
1511
1961
|
}
|
|
1512
1962
|
];
|
|
1513
1963
|
}
|