@onlook/storybook-plugin 0.4.0-beta.14 → 0.4.0-beta.16
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/dist/cli/index.js
CHANGED
|
@@ -63,6 +63,9 @@ var execFileAsync = promisify(execFile);
|
|
|
63
63
|
var require2 = createRequire$1(import.meta.url);
|
|
64
64
|
var browserPromise = null;
|
|
65
65
|
var installPromise = null;
|
|
66
|
+
var ChromiumUnavailableError = class extends Error {
|
|
67
|
+
code = "CHROMIUM_UNAVAILABLE";
|
|
68
|
+
};
|
|
66
69
|
var isMissingExecutableError = (err) => {
|
|
67
70
|
const msg = err instanceof Error ? err.message : String(err);
|
|
68
71
|
return msg.includes("Executable doesn't exist") || msg.includes("Please run the following command to download new browsers") || // Older playwright revisions phrase the version-mismatch case differently.
|
|
@@ -95,7 +98,9 @@ async function installChromium() {
|
|
|
95
98
|
} catch (err) {
|
|
96
99
|
const detail = err instanceof Error ? err.message : String(err);
|
|
97
100
|
console.error(`[STORYBOOK_PLUGIN] chromium install FAILED: ${detail}`);
|
|
98
|
-
throw new
|
|
101
|
+
throw new ChromiumUnavailableError(
|
|
102
|
+
`Playwright chromium install failed: ${detail}`
|
|
103
|
+
);
|
|
99
104
|
} finally {
|
|
100
105
|
installPromise = null;
|
|
101
106
|
}
|
|
@@ -117,7 +122,7 @@ async function launchWithSelfHeal() {
|
|
|
117
122
|
console.error(
|
|
118
123
|
`[STORYBOOK_PLUGIN] launch still failing after install \u2014 likely path or libs issue: ${detail}`
|
|
119
124
|
);
|
|
120
|
-
throw new
|
|
125
|
+
throw new ChromiumUnavailableError(
|
|
121
126
|
`Playwright chromium is unavailable in this sandbox (install ran but launch still failed): ${detail}`
|
|
122
127
|
);
|
|
123
128
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import
|
|
2
|
+
import path8, { dirname, join, relative } from 'path';
|
|
3
3
|
import crypto, { createHash } from 'crypto';
|
|
4
4
|
import fs5, { existsSync } from 'fs';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
@@ -61,7 +61,7 @@ function envContainmentPlugin(options) {
|
|
|
61
61
|
const resolveOutcomes = /* @__PURE__ */ new Map();
|
|
62
62
|
const roots = /* @__PURE__ */ new Set([normalizeSlashes(process.cwd())]);
|
|
63
63
|
const matchTarget = (absPath) => {
|
|
64
|
-
const norm = normalizeSlashes(
|
|
64
|
+
const norm = normalizeSlashes(path8.normalize(absPath));
|
|
65
65
|
for (const rel of byModule.keys()) {
|
|
66
66
|
for (const root of roots) {
|
|
67
67
|
const target = `${root}/${rel}`;
|
|
@@ -96,10 +96,10 @@ function envContainmentPlugin(options) {
|
|
|
96
96
|
if (source.startsWith("\0")) return null;
|
|
97
97
|
const cleaned = stripQuery(source);
|
|
98
98
|
let direct = null;
|
|
99
|
-
if (
|
|
99
|
+
if (path8.isAbsolute(cleaned)) {
|
|
100
100
|
direct = cleaned;
|
|
101
101
|
} else if (importer && (cleaned.startsWith("./") || cleaned.startsWith("../"))) {
|
|
102
|
-
direct =
|
|
102
|
+
direct = path8.resolve(path8.dirname(stripQuery(importer)), cleaned);
|
|
103
103
|
}
|
|
104
104
|
if (direct) {
|
|
105
105
|
const rel = matchTarget(direct);
|
|
@@ -108,7 +108,7 @@ function envContainmentPlugin(options) {
|
|
|
108
108
|
}
|
|
109
109
|
if (!specifierMightMatch(cleaned)) return null;
|
|
110
110
|
if (typeof this.resolve !== "function") return null;
|
|
111
|
-
const importerDir = importer ?
|
|
111
|
+
const importerDir = importer ? path8.dirname(stripQuery(importer)) : "";
|
|
112
112
|
const outcomeKey = `${importerDir}|${source}`;
|
|
113
113
|
const memoized = resolveOutcomes.get(outcomeKey);
|
|
114
114
|
if (memoized !== void 0) return memoized;
|
|
@@ -328,7 +328,7 @@ function componentLocPlugin(options = {}) {
|
|
|
328
328
|
sourceFilename: filepath
|
|
329
329
|
});
|
|
330
330
|
let mutated = false;
|
|
331
|
-
const relativePath =
|
|
331
|
+
const relativePath = path8.relative(root, filepath);
|
|
332
332
|
traverse(ast, {
|
|
333
333
|
JSXElement(nodePath) {
|
|
334
334
|
const opening = nodePath.node.openingElement;
|
|
@@ -387,9 +387,9 @@ function fastRefreshTolerantExportsPlugin() {
|
|
|
387
387
|
}
|
|
388
388
|
};
|
|
389
389
|
}
|
|
390
|
-
var CACHE_DIR =
|
|
391
|
-
var SCREENSHOTS_DIR =
|
|
392
|
-
var MANIFEST_PATH =
|
|
390
|
+
var CACHE_DIR = path8.join(process.cwd(), ".storybook-cache");
|
|
391
|
+
var SCREENSHOTS_DIR = path8.join(CACHE_DIR, "screenshots");
|
|
392
|
+
var MANIFEST_PATH = path8.join(CACHE_DIR, "manifest.json");
|
|
393
393
|
var VIEWPORT_WIDTH = 1920;
|
|
394
394
|
var VIEWPORT_HEIGHT = 1080;
|
|
395
395
|
var MIN_COMPONENT_WIDTH = 420;
|
|
@@ -440,6 +440,9 @@ var execFileAsync = promisify(execFile);
|
|
|
440
440
|
var require2 = createRequire$1(import.meta.url);
|
|
441
441
|
var browserPromise = null;
|
|
442
442
|
var installPromise = null;
|
|
443
|
+
var ChromiumUnavailableError = class extends Error {
|
|
444
|
+
code = "CHROMIUM_UNAVAILABLE";
|
|
445
|
+
};
|
|
443
446
|
var isMissingExecutableError = (err) => {
|
|
444
447
|
const msg = err instanceof Error ? err.message : String(err);
|
|
445
448
|
return msg.includes("Executable doesn't exist") || msg.includes("Please run the following command to download new browsers") || // Older playwright revisions phrase the version-mismatch case differently.
|
|
@@ -472,7 +475,9 @@ async function installChromium() {
|
|
|
472
475
|
} catch (err) {
|
|
473
476
|
const detail = err instanceof Error ? err.message : String(err);
|
|
474
477
|
console.error(`[STORYBOOK_PLUGIN] chromium install FAILED: ${detail}`);
|
|
475
|
-
throw new
|
|
478
|
+
throw new ChromiumUnavailableError(
|
|
479
|
+
`Playwright chromium install failed: ${detail}`
|
|
480
|
+
);
|
|
476
481
|
} finally {
|
|
477
482
|
installPromise = null;
|
|
478
483
|
}
|
|
@@ -494,7 +499,7 @@ async function launchWithSelfHeal() {
|
|
|
494
499
|
console.error(
|
|
495
500
|
`[STORYBOOK_PLUGIN] launch still failing after install \u2014 likely path or libs issue: ${detail}`
|
|
496
501
|
);
|
|
497
|
-
throw new
|
|
502
|
+
throw new ChromiumUnavailableError(
|
|
498
503
|
`Playwright chromium is unavailable in this sandbox (install ran but launch still failed): ${detail}`
|
|
499
504
|
);
|
|
500
505
|
}
|
|
@@ -575,8 +580,8 @@ async function detectRenderState(page, opts = {}) {
|
|
|
575
580
|
}
|
|
576
581
|
}
|
|
577
582
|
function getScreenshotPath(storyId, theme) {
|
|
578
|
-
const storyDir =
|
|
579
|
-
return
|
|
583
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
584
|
+
return path8.join(storyDir, `${theme}.png`);
|
|
580
585
|
}
|
|
581
586
|
async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
|
|
582
587
|
const browser = await getBrowser();
|
|
@@ -664,7 +669,7 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
|
|
|
664
669
|
async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
665
670
|
try {
|
|
666
671
|
ensureCacheDirectories();
|
|
667
|
-
const storyDir =
|
|
672
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
668
673
|
if (!fs5.existsSync(storyDir)) {
|
|
669
674
|
fs5.mkdirSync(storyDir, { recursive: true });
|
|
670
675
|
}
|
|
@@ -785,7 +790,7 @@ async function fetchStorybookIndex() {
|
|
|
785
790
|
}
|
|
786
791
|
function getStoriesForFile(filePath) {
|
|
787
792
|
if (!cachedIndex) return [];
|
|
788
|
-
const fileName =
|
|
793
|
+
const fileName = path8.basename(filePath);
|
|
789
794
|
return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
|
|
790
795
|
}
|
|
791
796
|
async function regenerateScreenshotsForFiles(files) {
|
|
@@ -980,8 +985,8 @@ function softStoryRerenderPlugin() {
|
|
|
980
985
|
preview.onStoriesChanged({ importFn: newModule.importFn });
|
|
981
986
|
}
|
|
982
987
|
});`;
|
|
983
|
-
const out = code.replace(pattern, (_match, quote,
|
|
984
|
-
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${
|
|
988
|
+
const out = code.replace(pattern, (_match, quote, path9) => {
|
|
989
|
+
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path9}${quote}`);
|
|
985
990
|
});
|
|
986
991
|
return out === code ? null : { code: out, map: null };
|
|
987
992
|
}
|
|
@@ -992,14 +997,14 @@ function resolveTsconfigAliases(root) {
|
|
|
992
997
|
try {
|
|
993
998
|
const config = readTsconfigAliasConfig(root);
|
|
994
999
|
if (!config) return [];
|
|
995
|
-
const base =
|
|
1000
|
+
const base = path8.resolve(root, config.baseUrl ?? ".");
|
|
996
1001
|
const aliases = [];
|
|
997
1002
|
for (const [key, targets] of Object.entries(config.paths)) {
|
|
998
1003
|
const target = targets[0];
|
|
999
1004
|
if (!target) continue;
|
|
1000
1005
|
if (key.endsWith("/*") && target.endsWith("/*")) {
|
|
1001
1006
|
const findPrefix = key.slice(0, -1);
|
|
1002
|
-
const targetDir =
|
|
1007
|
+
const targetDir = path8.resolve(base, target.slice(0, -2));
|
|
1003
1008
|
aliases.push({
|
|
1004
1009
|
find: new RegExp(`^${escapeRegExp(findPrefix)}`),
|
|
1005
1010
|
replacement: `${targetDir}/`
|
|
@@ -1007,7 +1012,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1007
1012
|
} else if (!key.includes("*") && !target.includes("*")) {
|
|
1008
1013
|
aliases.push({
|
|
1009
1014
|
find: new RegExp(`^${escapeRegExp(key)}$`),
|
|
1010
|
-
replacement:
|
|
1015
|
+
replacement: path8.resolve(base, target)
|
|
1011
1016
|
});
|
|
1012
1017
|
}
|
|
1013
1018
|
}
|
|
@@ -1018,7 +1023,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1018
1023
|
}
|
|
1019
1024
|
function readTsconfigAliasConfig(root) {
|
|
1020
1025
|
for (const candidate of TSCONFIG_CANDIDATES) {
|
|
1021
|
-
const abs =
|
|
1026
|
+
const abs = path8.join(root, candidate);
|
|
1022
1027
|
let text;
|
|
1023
1028
|
try {
|
|
1024
1029
|
text = fs5.readFileSync(abs, "utf-8");
|
|
@@ -1099,8 +1104,128 @@ function findGitRoot(startPath) {
|
|
|
1099
1104
|
}
|
|
1100
1105
|
return null;
|
|
1101
1106
|
}
|
|
1107
|
+
var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
|
|
1108
|
+
function isStoryFile(file) {
|
|
1109
|
+
return STORY_FILE_PATTERN.test(file);
|
|
1110
|
+
}
|
|
1111
|
+
function missingFromIndex(index, files) {
|
|
1112
|
+
const importPaths = [];
|
|
1113
|
+
for (const entry of Object.values(index.entries ?? {})) {
|
|
1114
|
+
if (typeof entry.importPath === "string") importPaths.push(entry.importPath);
|
|
1115
|
+
}
|
|
1116
|
+
return files.filter((file) => {
|
|
1117
|
+
const base = path8.basename(file);
|
|
1118
|
+
return !importPaths.some((importPath) => importPath.endsWith(base));
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
function portFromArgv(argv) {
|
|
1122
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1123
|
+
const arg = argv[i];
|
|
1124
|
+
if (arg === void 0) continue;
|
|
1125
|
+
let raw;
|
|
1126
|
+
if (arg === "-p" || arg === "--port") {
|
|
1127
|
+
raw = argv[i + 1];
|
|
1128
|
+
} else {
|
|
1129
|
+
raw = /^(?:-p|--port)=(\d+)$/.exec(arg)?.[1];
|
|
1130
|
+
}
|
|
1131
|
+
if (raw === void 0) continue;
|
|
1132
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1133
|
+
if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) return parsed;
|
|
1134
|
+
}
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
async function defaultFetchIndex(port) {
|
|
1138
|
+
try {
|
|
1139
|
+
const res = await fetch(`http://localhost:${port}/index.json?_t=${Date.now()}`, {
|
|
1140
|
+
cache: "no-store",
|
|
1141
|
+
signal: AbortSignal.timeout(2e3)
|
|
1142
|
+
});
|
|
1143
|
+
if (!res.ok) return null;
|
|
1144
|
+
return await res.json();
|
|
1145
|
+
} catch {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1150
|
+
function createStoryIndexBroadcaster({
|
|
1151
|
+
ports,
|
|
1152
|
+
notify,
|
|
1153
|
+
fetchIndex = defaultFetchIndex,
|
|
1154
|
+
debounceMs = 500,
|
|
1155
|
+
probeIntervalMs = 500,
|
|
1156
|
+
probeCeilingMs = 15e3
|
|
1157
|
+
}) {
|
|
1158
|
+
const candidatePorts = [...new Set(ports)];
|
|
1159
|
+
const pendingAdds = /* @__PURE__ */ new Set();
|
|
1160
|
+
let addDebounce = null;
|
|
1161
|
+
let unlinkDebounce = null;
|
|
1162
|
+
let probing = false;
|
|
1163
|
+
const scheduleProbe = () => {
|
|
1164
|
+
if (addDebounce) clearTimeout(addDebounce);
|
|
1165
|
+
addDebounce = setTimeout(() => {
|
|
1166
|
+
addDebounce = null;
|
|
1167
|
+
void probeThenNotify();
|
|
1168
|
+
}, debounceMs);
|
|
1169
|
+
};
|
|
1170
|
+
const probeThenNotify = async () => {
|
|
1171
|
+
if (probing) return;
|
|
1172
|
+
probing = true;
|
|
1173
|
+
const files = Array.from(pendingAdds);
|
|
1174
|
+
const deadline = Date.now() + probeCeilingMs;
|
|
1175
|
+
try {
|
|
1176
|
+
if (files.length === 0) return;
|
|
1177
|
+
while (Date.now() < deadline) {
|
|
1178
|
+
for (const candidate of candidatePorts) {
|
|
1179
|
+
const index = await fetchIndex(candidate);
|
|
1180
|
+
if (index && missingFromIndex(index, files).length === 0) {
|
|
1181
|
+
console.log(
|
|
1182
|
+
"[STORYBOOK_PLUGIN] Index caught up with new story files \u2014 broadcasting",
|
|
1183
|
+
{ files, port: candidate }
|
|
1184
|
+
);
|
|
1185
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1186
|
+
notify();
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
await sleep(probeIntervalMs);
|
|
1191
|
+
}
|
|
1192
|
+
console.warn("[STORYBOOK_PLUGIN] Index probe hit ceiling \u2014 broadcasting anyway", {
|
|
1193
|
+
files,
|
|
1194
|
+
probeCeilingMs
|
|
1195
|
+
});
|
|
1196
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1197
|
+
notify();
|
|
1198
|
+
} finally {
|
|
1199
|
+
probing = false;
|
|
1200
|
+
if (pendingAdds.size > 0) scheduleProbe();
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
return {
|
|
1204
|
+
onStoryFileAdded(file) {
|
|
1205
|
+
if (!isStoryFile(file)) return;
|
|
1206
|
+
pendingAdds.add(file);
|
|
1207
|
+
scheduleProbe();
|
|
1208
|
+
},
|
|
1209
|
+
onStoryFileRemoved(file) {
|
|
1210
|
+
if (!isStoryFile(file)) return;
|
|
1211
|
+
pendingAdds.delete(file);
|
|
1212
|
+
if (unlinkDebounce) clearTimeout(unlinkDebounce);
|
|
1213
|
+
unlinkDebounce = setTimeout(() => {
|
|
1214
|
+
unlinkDebounce = null;
|
|
1215
|
+
console.log("[STORYBOOK_PLUGIN] Story file removed \u2014 broadcasting", { file });
|
|
1216
|
+
notify();
|
|
1217
|
+
}, debounceMs);
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1102
1221
|
|
|
1103
1222
|
// src/storybook-onlook-plugin.ts
|
|
1223
|
+
function errorCode(error) {
|
|
1224
|
+
if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
|
|
1225
|
+
return error.code;
|
|
1226
|
+
}
|
|
1227
|
+
return void 0;
|
|
1228
|
+
}
|
|
1104
1229
|
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
1105
1230
|
var storybookDir = join(__dirname$1, "..");
|
|
1106
1231
|
var gitRoot = findGitRoot(storybookDir);
|
|
@@ -1159,7 +1284,7 @@ var TAILWIND_ENTRY_CANDIDATES = [
|
|
|
1159
1284
|
var TAILWIND_DIRECTIVE_PATTERN = /@import\s+['"](?:tailwindcss|tailwindcss\/.+)['"]|@tailwind\s+(?:base|components|utilities)/;
|
|
1160
1285
|
function detectTailwindEntryCss(cwd) {
|
|
1161
1286
|
for (const rel of TAILWIND_ENTRY_CANDIDATES) {
|
|
1162
|
-
const abs =
|
|
1287
|
+
const abs = path8.join(cwd, rel);
|
|
1163
1288
|
try {
|
|
1164
1289
|
if (!fs5.existsSync(abs)) continue;
|
|
1165
1290
|
const head = fs5.readFileSync(abs, "utf-8").slice(0, 2048);
|
|
@@ -1246,11 +1371,13 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1246
1371
|
res.end(buffer);
|
|
1247
1372
|
}).catch((error) => {
|
|
1248
1373
|
console.error("Screenshot capture error:", error);
|
|
1374
|
+
const code = errorCode(error);
|
|
1249
1375
|
res.statusCode = 500;
|
|
1250
1376
|
res.setHeader("Content-Type", "application/json");
|
|
1251
1377
|
res.end(
|
|
1252
1378
|
JSON.stringify({
|
|
1253
1379
|
error: "Failed to capture screenshot",
|
|
1380
|
+
...code ? { code } : {},
|
|
1254
1381
|
details: String(error)
|
|
1255
1382
|
})
|
|
1256
1383
|
);
|
|
@@ -1280,11 +1407,13 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1280
1407
|
res.end(JSON.stringify(result));
|
|
1281
1408
|
}).catch((error) => {
|
|
1282
1409
|
console.error("Console log capture error:", error);
|
|
1410
|
+
const code = errorCode(error);
|
|
1283
1411
|
res.statusCode = 500;
|
|
1284
1412
|
res.setHeader("Content-Type", "application/json");
|
|
1285
1413
|
res.end(
|
|
1286
1414
|
JSON.stringify({
|
|
1287
1415
|
error: "Failed to capture console logs",
|
|
1416
|
+
...code ? { code } : {},
|
|
1288
1417
|
details: String(error)
|
|
1289
1418
|
})
|
|
1290
1419
|
);
|
|
@@ -1292,7 +1421,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1292
1421
|
return;
|
|
1293
1422
|
}
|
|
1294
1423
|
if (req.url?.startsWith("/screenshots/")) {
|
|
1295
|
-
const screenshotPath =
|
|
1424
|
+
const screenshotPath = path8.join(
|
|
1296
1425
|
process.cwd(),
|
|
1297
1426
|
".storybook-cache",
|
|
1298
1427
|
req.url.replace("/screenshots/", "screenshots/")
|
|
@@ -1313,7 +1442,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1313
1442
|
`[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
|
|
1314
1443
|
);
|
|
1315
1444
|
captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
|
|
1316
|
-
const storyDir =
|
|
1445
|
+
const storyDir = path8.join(
|
|
1317
1446
|
process.cwd(),
|
|
1318
1447
|
".storybook-cache",
|
|
1319
1448
|
"screenshots",
|
|
@@ -1332,6 +1461,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1332
1461
|
`[STORYBOOK_PLUGIN] Failed to generate screenshot: ${storyId}/${theme}`,
|
|
1333
1462
|
error
|
|
1334
1463
|
);
|
|
1464
|
+
const code = errorCode(error);
|
|
1335
1465
|
res.statusCode = 500;
|
|
1336
1466
|
res.setHeader("Content-Type", "application/json");
|
|
1337
1467
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -1339,6 +1469,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1339
1469
|
res.end(
|
|
1340
1470
|
JSON.stringify({
|
|
1341
1471
|
error: "Failed to generate screenshot",
|
|
1472
|
+
...code ? { code } : {},
|
|
1342
1473
|
details: String(error)
|
|
1343
1474
|
})
|
|
1344
1475
|
);
|
|
@@ -1430,7 +1561,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1430
1561
|
configureServer(server) {
|
|
1431
1562
|
console.log("[STORYBOOK_PLUGIN] Configuring server middleware");
|
|
1432
1563
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1433
|
-
const cachedManifestPath =
|
|
1564
|
+
const cachedManifestPath = path8.join(
|
|
1434
1565
|
process.cwd(),
|
|
1435
1566
|
".storybook-cache",
|
|
1436
1567
|
"manifest.json"
|
|
@@ -1449,7 +1580,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1449
1580
|
server.watcher.on("change", scheduleRefresh);
|
|
1450
1581
|
const storyGlobs = options.storyGlobs ?? ["**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)"];
|
|
1451
1582
|
const resolvedStoryGlobs = storyGlobs.map(
|
|
1452
|
-
(glob) =>
|
|
1583
|
+
(glob) => path8.isAbsolute(glob) ? glob : path8.join(process.cwd(), glob)
|
|
1453
1584
|
);
|
|
1454
1585
|
if (resolvedStoryGlobs.length > 0) {
|
|
1455
1586
|
server.watcher.add(resolvedStoryGlobs);
|
|
@@ -1457,10 +1588,36 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1457
1588
|
storyGlobs: resolvedStoryGlobs
|
|
1458
1589
|
});
|
|
1459
1590
|
}
|
|
1591
|
+
const probeCandidatePorts = [
|
|
1592
|
+
...new Set(
|
|
1593
|
+
[portFromArgv(process.argv), port, server.config.server.port].filter(
|
|
1594
|
+
(p) => typeof p === "number"
|
|
1595
|
+
)
|
|
1596
|
+
)
|
|
1597
|
+
];
|
|
1598
|
+
console.log("[STORYBOOK_PLUGIN] Story add/unlink index broadcast enabled", {
|
|
1599
|
+
probeCandidatePorts
|
|
1600
|
+
});
|
|
1601
|
+
const indexBroadcaster = createStoryIndexBroadcaster({
|
|
1602
|
+
ports: probeCandidatePorts,
|
|
1603
|
+
notify: notifyOnbookIndexChanged
|
|
1604
|
+
});
|
|
1605
|
+
let indexBroadcastWatcherReady = false;
|
|
1606
|
+
server.watcher.once("ready", () => {
|
|
1607
|
+
indexBroadcastWatcherReady = true;
|
|
1608
|
+
});
|
|
1609
|
+
server.watcher.on("add", (file) => {
|
|
1610
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1611
|
+
indexBroadcaster.onStoryFileAdded(file);
|
|
1612
|
+
});
|
|
1613
|
+
server.watcher.on("unlink", (file) => {
|
|
1614
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1615
|
+
indexBroadcaster.onStoryFileRemoved(file);
|
|
1616
|
+
});
|
|
1460
1617
|
const tailwindEntryPath = (() => {
|
|
1461
1618
|
if (options.tailwindEntryCss === false) return null;
|
|
1462
1619
|
if (typeof options.tailwindEntryCss === "string") {
|
|
1463
|
-
return
|
|
1620
|
+
return path8.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path8.join(process.cwd(), options.tailwindEntryCss);
|
|
1464
1621
|
}
|
|
1465
1622
|
return detectTailwindEntryCss(process.cwd());
|
|
1466
1623
|
})();
|
|
@@ -1476,7 +1633,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1476
1633
|
});
|
|
1477
1634
|
server.watcher.on("add", (file) => {
|
|
1478
1635
|
if (!/\.tsx?$/.test(file)) return;
|
|
1479
|
-
const dir =
|
|
1636
|
+
const dir = path8.dirname(file);
|
|
1480
1637
|
const isNewDir = !seenDirs.has(dir);
|
|
1481
1638
|
seenDirs.add(dir);
|
|
1482
1639
|
if (!watcherReady || !isNewDir) return;
|
|
@@ -1507,7 +1664,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1507
1664
|
configurePreviewServer(server) {
|
|
1508
1665
|
console.log("[STORYBOOK_PLUGIN] Configuring preview server middleware");
|
|
1509
1666
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1510
|
-
refreshManifest(
|
|
1667
|
+
refreshManifest(path8.join(process.cwd(), ".storybook-cache", "manifest.json"));
|
|
1511
1668
|
},
|
|
1512
1669
|
handleHotUpdate: handleStoryFileChange
|
|
1513
1670
|
};
|
package/dist/preset/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import crypto, { createHash } from 'crypto';
|
|
3
3
|
import fs5, { existsSync } from 'fs';
|
|
4
|
-
import
|
|
4
|
+
import path8, { dirname, join, relative } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import generateModule from '@babel/generator';
|
|
7
7
|
import { parse } from '@babel/parser';
|
|
@@ -45,7 +45,7 @@ function componentLocPlugin(options = {}) {
|
|
|
45
45
|
sourceFilename: filepath
|
|
46
46
|
});
|
|
47
47
|
let mutated = false;
|
|
48
|
-
const relativePath =
|
|
48
|
+
const relativePath = path8.relative(root, filepath);
|
|
49
49
|
traverse(ast, {
|
|
50
50
|
JSXElement(nodePath) {
|
|
51
51
|
const opening = nodePath.node.openingElement;
|
|
@@ -117,7 +117,7 @@ function envContainmentPlugin(options) {
|
|
|
117
117
|
const resolveOutcomes = /* @__PURE__ */ new Map();
|
|
118
118
|
const roots = /* @__PURE__ */ new Set([normalizeSlashes(process.cwd())]);
|
|
119
119
|
const matchTarget = (absPath) => {
|
|
120
|
-
const norm = normalizeSlashes(
|
|
120
|
+
const norm = normalizeSlashes(path8.normalize(absPath));
|
|
121
121
|
for (const rel of byModule.keys()) {
|
|
122
122
|
for (const root of roots) {
|
|
123
123
|
const target = `${root}/${rel}`;
|
|
@@ -152,10 +152,10 @@ function envContainmentPlugin(options) {
|
|
|
152
152
|
if (source.startsWith("\0")) return null;
|
|
153
153
|
const cleaned = stripQuery(source);
|
|
154
154
|
let direct = null;
|
|
155
|
-
if (
|
|
155
|
+
if (path8.isAbsolute(cleaned)) {
|
|
156
156
|
direct = cleaned;
|
|
157
157
|
} else if (importer && (cleaned.startsWith("./") || cleaned.startsWith("../"))) {
|
|
158
|
-
direct =
|
|
158
|
+
direct = path8.resolve(path8.dirname(stripQuery(importer)), cleaned);
|
|
159
159
|
}
|
|
160
160
|
if (direct) {
|
|
161
161
|
const rel = matchTarget(direct);
|
|
@@ -164,7 +164,7 @@ function envContainmentPlugin(options) {
|
|
|
164
164
|
}
|
|
165
165
|
if (!specifierMightMatch(cleaned)) return null;
|
|
166
166
|
if (typeof this.resolve !== "function") return null;
|
|
167
|
-
const importerDir = importer ?
|
|
167
|
+
const importerDir = importer ? path8.dirname(stripQuery(importer)) : "";
|
|
168
168
|
const outcomeKey = `${importerDir}|${source}`;
|
|
169
169
|
const memoized = resolveOutcomes.get(outcomeKey);
|
|
170
170
|
if (memoized !== void 0) return memoized;
|
|
@@ -267,9 +267,9 @@ function fastRefreshTolerantExportsPlugin() {
|
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
|
-
var CACHE_DIR =
|
|
271
|
-
var SCREENSHOTS_DIR =
|
|
272
|
-
var MANIFEST_PATH =
|
|
270
|
+
var CACHE_DIR = path8.join(process.cwd(), ".storybook-cache");
|
|
271
|
+
var SCREENSHOTS_DIR = path8.join(CACHE_DIR, "screenshots");
|
|
272
|
+
var MANIFEST_PATH = path8.join(CACHE_DIR, "manifest.json");
|
|
273
273
|
var VIEWPORT_WIDTH = 1920;
|
|
274
274
|
var VIEWPORT_HEIGHT = 1080;
|
|
275
275
|
var MIN_COMPONENT_WIDTH = 420;
|
|
@@ -320,6 +320,9 @@ var execFileAsync = promisify(execFile);
|
|
|
320
320
|
var require2 = createRequire$1(import.meta.url);
|
|
321
321
|
var browserPromise = null;
|
|
322
322
|
var installPromise = null;
|
|
323
|
+
var ChromiumUnavailableError = class extends Error {
|
|
324
|
+
code = "CHROMIUM_UNAVAILABLE";
|
|
325
|
+
};
|
|
323
326
|
var isMissingExecutableError = (err) => {
|
|
324
327
|
const msg = err instanceof Error ? err.message : String(err);
|
|
325
328
|
return msg.includes("Executable doesn't exist") || msg.includes("Please run the following command to download new browsers") || // Older playwright revisions phrase the version-mismatch case differently.
|
|
@@ -352,7 +355,9 @@ async function installChromium() {
|
|
|
352
355
|
} catch (err) {
|
|
353
356
|
const detail = err instanceof Error ? err.message : String(err);
|
|
354
357
|
console.error(`[STORYBOOK_PLUGIN] chromium install FAILED: ${detail}`);
|
|
355
|
-
throw new
|
|
358
|
+
throw new ChromiumUnavailableError(
|
|
359
|
+
`Playwright chromium install failed: ${detail}`
|
|
360
|
+
);
|
|
356
361
|
} finally {
|
|
357
362
|
installPromise = null;
|
|
358
363
|
}
|
|
@@ -374,7 +379,7 @@ async function launchWithSelfHeal() {
|
|
|
374
379
|
console.error(
|
|
375
380
|
`[STORYBOOK_PLUGIN] launch still failing after install \u2014 likely path or libs issue: ${detail}`
|
|
376
381
|
);
|
|
377
|
-
throw new
|
|
382
|
+
throw new ChromiumUnavailableError(
|
|
378
383
|
`Playwright chromium is unavailable in this sandbox (install ran but launch still failed): ${detail}`
|
|
379
384
|
);
|
|
380
385
|
}
|
|
@@ -455,8 +460,8 @@ async function detectRenderState(page, opts = {}) {
|
|
|
455
460
|
}
|
|
456
461
|
}
|
|
457
462
|
function getScreenshotPath(storyId, theme) {
|
|
458
|
-
const storyDir =
|
|
459
|
-
return
|
|
463
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
464
|
+
return path8.join(storyDir, `${theme}.png`);
|
|
460
465
|
}
|
|
461
466
|
async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
|
|
462
467
|
const browser = await getBrowser();
|
|
@@ -544,7 +549,7 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
|
|
|
544
549
|
async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
545
550
|
try {
|
|
546
551
|
ensureCacheDirectories();
|
|
547
|
-
const storyDir =
|
|
552
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
548
553
|
if (!fs5.existsSync(storyDir)) {
|
|
549
554
|
fs5.mkdirSync(storyDir, { recursive: true });
|
|
550
555
|
}
|
|
@@ -665,7 +670,7 @@ async function fetchStorybookIndex() {
|
|
|
665
670
|
}
|
|
666
671
|
function getStoriesForFile(filePath) {
|
|
667
672
|
if (!cachedIndex) return [];
|
|
668
|
-
const fileName =
|
|
673
|
+
const fileName = path8.basename(filePath);
|
|
669
674
|
return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
|
|
670
675
|
}
|
|
671
676
|
async function regenerateScreenshotsForFiles(files) {
|
|
@@ -976,8 +981,8 @@ function softStoryRerenderPlugin() {
|
|
|
976
981
|
preview.onStoriesChanged({ importFn: newModule.importFn });
|
|
977
982
|
}
|
|
978
983
|
});`;
|
|
979
|
-
const out = code.replace(pattern, (_match, quote,
|
|
980
|
-
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${
|
|
984
|
+
const out = code.replace(pattern, (_match, quote, path9) => {
|
|
985
|
+
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path9}${quote}`);
|
|
981
986
|
});
|
|
982
987
|
return out === code ? null : { code: out, map: null };
|
|
983
988
|
}
|
|
@@ -988,14 +993,14 @@ function resolveTsconfigAliases(root) {
|
|
|
988
993
|
try {
|
|
989
994
|
const config = readTsconfigAliasConfig(root);
|
|
990
995
|
if (!config) return [];
|
|
991
|
-
const base =
|
|
996
|
+
const base = path8.resolve(root, config.baseUrl ?? ".");
|
|
992
997
|
const aliases = [];
|
|
993
998
|
for (const [key, targets] of Object.entries(config.paths)) {
|
|
994
999
|
const target = targets[0];
|
|
995
1000
|
if (!target) continue;
|
|
996
1001
|
if (key.endsWith("/*") && target.endsWith("/*")) {
|
|
997
1002
|
const findPrefix = key.slice(0, -1);
|
|
998
|
-
const targetDir =
|
|
1003
|
+
const targetDir = path8.resolve(base, target.slice(0, -2));
|
|
999
1004
|
aliases.push({
|
|
1000
1005
|
find: new RegExp(`^${escapeRegExp(findPrefix)}`),
|
|
1001
1006
|
replacement: `${targetDir}/`
|
|
@@ -1003,7 +1008,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1003
1008
|
} else if (!key.includes("*") && !target.includes("*")) {
|
|
1004
1009
|
aliases.push({
|
|
1005
1010
|
find: new RegExp(`^${escapeRegExp(key)}$`),
|
|
1006
|
-
replacement:
|
|
1011
|
+
replacement: path8.resolve(base, target)
|
|
1007
1012
|
});
|
|
1008
1013
|
}
|
|
1009
1014
|
}
|
|
@@ -1014,7 +1019,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1014
1019
|
}
|
|
1015
1020
|
function readTsconfigAliasConfig(root) {
|
|
1016
1021
|
for (const candidate of TSCONFIG_CANDIDATES) {
|
|
1017
|
-
const abs =
|
|
1022
|
+
const abs = path8.join(root, candidate);
|
|
1018
1023
|
let text;
|
|
1019
1024
|
try {
|
|
1020
1025
|
text = fs5.readFileSync(abs, "utf-8");
|
|
@@ -1095,8 +1100,128 @@ function findGitRoot(startPath) {
|
|
|
1095
1100
|
}
|
|
1096
1101
|
return null;
|
|
1097
1102
|
}
|
|
1103
|
+
var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
|
|
1104
|
+
function isStoryFile(file) {
|
|
1105
|
+
return STORY_FILE_PATTERN.test(file);
|
|
1106
|
+
}
|
|
1107
|
+
function missingFromIndex(index, files) {
|
|
1108
|
+
const importPaths = [];
|
|
1109
|
+
for (const entry of Object.values(index.entries ?? {})) {
|
|
1110
|
+
if (typeof entry.importPath === "string") importPaths.push(entry.importPath);
|
|
1111
|
+
}
|
|
1112
|
+
return files.filter((file) => {
|
|
1113
|
+
const base = path8.basename(file);
|
|
1114
|
+
return !importPaths.some((importPath) => importPath.endsWith(base));
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
function portFromArgv(argv) {
|
|
1118
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1119
|
+
const arg = argv[i];
|
|
1120
|
+
if (arg === void 0) continue;
|
|
1121
|
+
let raw;
|
|
1122
|
+
if (arg === "-p" || arg === "--port") {
|
|
1123
|
+
raw = argv[i + 1];
|
|
1124
|
+
} else {
|
|
1125
|
+
raw = /^(?:-p|--port)=(\d+)$/.exec(arg)?.[1];
|
|
1126
|
+
}
|
|
1127
|
+
if (raw === void 0) continue;
|
|
1128
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1129
|
+
if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) return parsed;
|
|
1130
|
+
}
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
async function defaultFetchIndex(port) {
|
|
1134
|
+
try {
|
|
1135
|
+
const res = await fetch(`http://localhost:${port}/index.json?_t=${Date.now()}`, {
|
|
1136
|
+
cache: "no-store",
|
|
1137
|
+
signal: AbortSignal.timeout(2e3)
|
|
1138
|
+
});
|
|
1139
|
+
if (!res.ok) return null;
|
|
1140
|
+
return await res.json();
|
|
1141
|
+
} catch {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1146
|
+
function createStoryIndexBroadcaster({
|
|
1147
|
+
ports,
|
|
1148
|
+
notify,
|
|
1149
|
+
fetchIndex = defaultFetchIndex,
|
|
1150
|
+
debounceMs = 500,
|
|
1151
|
+
probeIntervalMs = 500,
|
|
1152
|
+
probeCeilingMs = 15e3
|
|
1153
|
+
}) {
|
|
1154
|
+
const candidatePorts = [...new Set(ports)];
|
|
1155
|
+
const pendingAdds = /* @__PURE__ */ new Set();
|
|
1156
|
+
let addDebounce = null;
|
|
1157
|
+
let unlinkDebounce = null;
|
|
1158
|
+
let probing = false;
|
|
1159
|
+
const scheduleProbe = () => {
|
|
1160
|
+
if (addDebounce) clearTimeout(addDebounce);
|
|
1161
|
+
addDebounce = setTimeout(() => {
|
|
1162
|
+
addDebounce = null;
|
|
1163
|
+
void probeThenNotify();
|
|
1164
|
+
}, debounceMs);
|
|
1165
|
+
};
|
|
1166
|
+
const probeThenNotify = async () => {
|
|
1167
|
+
if (probing) return;
|
|
1168
|
+
probing = true;
|
|
1169
|
+
const files = Array.from(pendingAdds);
|
|
1170
|
+
const deadline = Date.now() + probeCeilingMs;
|
|
1171
|
+
try {
|
|
1172
|
+
if (files.length === 0) return;
|
|
1173
|
+
while (Date.now() < deadline) {
|
|
1174
|
+
for (const candidate of candidatePorts) {
|
|
1175
|
+
const index = await fetchIndex(candidate);
|
|
1176
|
+
if (index && missingFromIndex(index, files).length === 0) {
|
|
1177
|
+
console.log(
|
|
1178
|
+
"[STORYBOOK_PLUGIN] Index caught up with new story files \u2014 broadcasting",
|
|
1179
|
+
{ files, port: candidate }
|
|
1180
|
+
);
|
|
1181
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1182
|
+
notify();
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
await sleep(probeIntervalMs);
|
|
1187
|
+
}
|
|
1188
|
+
console.warn("[STORYBOOK_PLUGIN] Index probe hit ceiling \u2014 broadcasting anyway", {
|
|
1189
|
+
files,
|
|
1190
|
+
probeCeilingMs
|
|
1191
|
+
});
|
|
1192
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1193
|
+
notify();
|
|
1194
|
+
} finally {
|
|
1195
|
+
probing = false;
|
|
1196
|
+
if (pendingAdds.size > 0) scheduleProbe();
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
return {
|
|
1200
|
+
onStoryFileAdded(file) {
|
|
1201
|
+
if (!isStoryFile(file)) return;
|
|
1202
|
+
pendingAdds.add(file);
|
|
1203
|
+
scheduleProbe();
|
|
1204
|
+
},
|
|
1205
|
+
onStoryFileRemoved(file) {
|
|
1206
|
+
if (!isStoryFile(file)) return;
|
|
1207
|
+
pendingAdds.delete(file);
|
|
1208
|
+
if (unlinkDebounce) clearTimeout(unlinkDebounce);
|
|
1209
|
+
unlinkDebounce = setTimeout(() => {
|
|
1210
|
+
unlinkDebounce = null;
|
|
1211
|
+
console.log("[STORYBOOK_PLUGIN] Story file removed \u2014 broadcasting", { file });
|
|
1212
|
+
notify();
|
|
1213
|
+
}, debounceMs);
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1098
1217
|
|
|
1099
1218
|
// src/storybook-onlook-plugin.ts
|
|
1219
|
+
function errorCode(error) {
|
|
1220
|
+
if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
|
|
1221
|
+
return error.code;
|
|
1222
|
+
}
|
|
1223
|
+
return void 0;
|
|
1224
|
+
}
|
|
1100
1225
|
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
1101
1226
|
var storybookDir = join(__dirname$1, "..");
|
|
1102
1227
|
var gitRoot = findGitRoot(storybookDir);
|
|
@@ -1155,7 +1280,7 @@ var TAILWIND_ENTRY_CANDIDATES = [
|
|
|
1155
1280
|
var TAILWIND_DIRECTIVE_PATTERN = /@import\s+['"](?:tailwindcss|tailwindcss\/.+)['"]|@tailwind\s+(?:base|components|utilities)/;
|
|
1156
1281
|
function detectTailwindEntryCss(cwd) {
|
|
1157
1282
|
for (const rel of TAILWIND_ENTRY_CANDIDATES) {
|
|
1158
|
-
const abs =
|
|
1283
|
+
const abs = path8.join(cwd, rel);
|
|
1159
1284
|
try {
|
|
1160
1285
|
if (!fs5.existsSync(abs)) continue;
|
|
1161
1286
|
const head = fs5.readFileSync(abs, "utf-8").slice(0, 2048);
|
|
@@ -1242,11 +1367,13 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1242
1367
|
res.end(buffer);
|
|
1243
1368
|
}).catch((error) => {
|
|
1244
1369
|
console.error("Screenshot capture error:", error);
|
|
1370
|
+
const code = errorCode(error);
|
|
1245
1371
|
res.statusCode = 500;
|
|
1246
1372
|
res.setHeader("Content-Type", "application/json");
|
|
1247
1373
|
res.end(
|
|
1248
1374
|
JSON.stringify({
|
|
1249
1375
|
error: "Failed to capture screenshot",
|
|
1376
|
+
...code ? { code } : {},
|
|
1250
1377
|
details: String(error)
|
|
1251
1378
|
})
|
|
1252
1379
|
);
|
|
@@ -1276,11 +1403,13 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1276
1403
|
res.end(JSON.stringify(result));
|
|
1277
1404
|
}).catch((error) => {
|
|
1278
1405
|
console.error("Console log capture error:", error);
|
|
1406
|
+
const code = errorCode(error);
|
|
1279
1407
|
res.statusCode = 500;
|
|
1280
1408
|
res.setHeader("Content-Type", "application/json");
|
|
1281
1409
|
res.end(
|
|
1282
1410
|
JSON.stringify({
|
|
1283
1411
|
error: "Failed to capture console logs",
|
|
1412
|
+
...code ? { code } : {},
|
|
1284
1413
|
details: String(error)
|
|
1285
1414
|
})
|
|
1286
1415
|
);
|
|
@@ -1288,7 +1417,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1288
1417
|
return;
|
|
1289
1418
|
}
|
|
1290
1419
|
if (req.url?.startsWith("/screenshots/")) {
|
|
1291
|
-
const screenshotPath =
|
|
1420
|
+
const screenshotPath = path8.join(
|
|
1292
1421
|
process.cwd(),
|
|
1293
1422
|
".storybook-cache",
|
|
1294
1423
|
req.url.replace("/screenshots/", "screenshots/")
|
|
@@ -1309,7 +1438,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1309
1438
|
`[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
|
|
1310
1439
|
);
|
|
1311
1440
|
captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
|
|
1312
|
-
const storyDir =
|
|
1441
|
+
const storyDir = path8.join(
|
|
1313
1442
|
process.cwd(),
|
|
1314
1443
|
".storybook-cache",
|
|
1315
1444
|
"screenshots",
|
|
@@ -1328,6 +1457,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1328
1457
|
`[STORYBOOK_PLUGIN] Failed to generate screenshot: ${storyId}/${theme}`,
|
|
1329
1458
|
error
|
|
1330
1459
|
);
|
|
1460
|
+
const code = errorCode(error);
|
|
1331
1461
|
res.statusCode = 500;
|
|
1332
1462
|
res.setHeader("Content-Type", "application/json");
|
|
1333
1463
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -1335,6 +1465,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1335
1465
|
res.end(
|
|
1336
1466
|
JSON.stringify({
|
|
1337
1467
|
error: "Failed to generate screenshot",
|
|
1468
|
+
...code ? { code } : {},
|
|
1338
1469
|
details: String(error)
|
|
1339
1470
|
})
|
|
1340
1471
|
);
|
|
@@ -1426,7 +1557,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1426
1557
|
configureServer(server) {
|
|
1427
1558
|
console.log("[STORYBOOK_PLUGIN] Configuring server middleware");
|
|
1428
1559
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1429
|
-
const cachedManifestPath =
|
|
1560
|
+
const cachedManifestPath = path8.join(
|
|
1430
1561
|
process.cwd(),
|
|
1431
1562
|
".storybook-cache",
|
|
1432
1563
|
"manifest.json"
|
|
@@ -1445,7 +1576,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1445
1576
|
server.watcher.on("change", scheduleRefresh);
|
|
1446
1577
|
const storyGlobs = options.storyGlobs ?? ["**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)"];
|
|
1447
1578
|
const resolvedStoryGlobs = storyGlobs.map(
|
|
1448
|
-
(glob) =>
|
|
1579
|
+
(glob) => path8.isAbsolute(glob) ? glob : path8.join(process.cwd(), glob)
|
|
1449
1580
|
);
|
|
1450
1581
|
if (resolvedStoryGlobs.length > 0) {
|
|
1451
1582
|
server.watcher.add(resolvedStoryGlobs);
|
|
@@ -1453,10 +1584,36 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1453
1584
|
storyGlobs: resolvedStoryGlobs
|
|
1454
1585
|
});
|
|
1455
1586
|
}
|
|
1587
|
+
const probeCandidatePorts = [
|
|
1588
|
+
...new Set(
|
|
1589
|
+
[portFromArgv(process.argv), port, server.config.server.port].filter(
|
|
1590
|
+
(p) => typeof p === "number"
|
|
1591
|
+
)
|
|
1592
|
+
)
|
|
1593
|
+
];
|
|
1594
|
+
console.log("[STORYBOOK_PLUGIN] Story add/unlink index broadcast enabled", {
|
|
1595
|
+
probeCandidatePorts
|
|
1596
|
+
});
|
|
1597
|
+
const indexBroadcaster = createStoryIndexBroadcaster({
|
|
1598
|
+
ports: probeCandidatePorts,
|
|
1599
|
+
notify: notifyOnbookIndexChanged
|
|
1600
|
+
});
|
|
1601
|
+
let indexBroadcastWatcherReady = false;
|
|
1602
|
+
server.watcher.once("ready", () => {
|
|
1603
|
+
indexBroadcastWatcherReady = true;
|
|
1604
|
+
});
|
|
1605
|
+
server.watcher.on("add", (file) => {
|
|
1606
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1607
|
+
indexBroadcaster.onStoryFileAdded(file);
|
|
1608
|
+
});
|
|
1609
|
+
server.watcher.on("unlink", (file) => {
|
|
1610
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1611
|
+
indexBroadcaster.onStoryFileRemoved(file);
|
|
1612
|
+
});
|
|
1456
1613
|
const tailwindEntryPath = (() => {
|
|
1457
1614
|
if (options.tailwindEntryCss === false) return null;
|
|
1458
1615
|
if (typeof options.tailwindEntryCss === "string") {
|
|
1459
|
-
return
|
|
1616
|
+
return path8.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path8.join(process.cwd(), options.tailwindEntryCss);
|
|
1460
1617
|
}
|
|
1461
1618
|
return detectTailwindEntryCss(process.cwd());
|
|
1462
1619
|
})();
|
|
@@ -1472,7 +1629,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1472
1629
|
});
|
|
1473
1630
|
server.watcher.on("add", (file) => {
|
|
1474
1631
|
if (!/\.tsx?$/.test(file)) return;
|
|
1475
|
-
const dir =
|
|
1632
|
+
const dir = path8.dirname(file);
|
|
1476
1633
|
const isNewDir = !seenDirs.has(dir);
|
|
1477
1634
|
seenDirs.add(dir);
|
|
1478
1635
|
if (!watcherReady || !isNewDir) return;
|
|
@@ -1503,7 +1660,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1503
1660
|
configurePreviewServer(server) {
|
|
1504
1661
|
console.log("[STORYBOOK_PLUGIN] Configuring preview server middleware");
|
|
1505
1662
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1506
|
-
refreshManifest(
|
|
1663
|
+
refreshManifest(path8.join(process.cwd(), ".storybook-cache", "manifest.json"));
|
|
1507
1664
|
},
|
|
1508
1665
|
handleHotUpdate: handleStoryFileChange
|
|
1509
1666
|
};
|
|
@@ -61,6 +61,9 @@ var execFileAsync = promisify(execFile);
|
|
|
61
61
|
var require2 = createRequire$1(import.meta.url);
|
|
62
62
|
var browserPromise = null;
|
|
63
63
|
var installPromise = null;
|
|
64
|
+
var ChromiumUnavailableError = class extends Error {
|
|
65
|
+
code = "CHROMIUM_UNAVAILABLE";
|
|
66
|
+
};
|
|
64
67
|
var isMissingExecutableError = (err) => {
|
|
65
68
|
const msg = err instanceof Error ? err.message : String(err);
|
|
66
69
|
return msg.includes("Executable doesn't exist") || msg.includes("Please run the following command to download new browsers") || // Older playwright revisions phrase the version-mismatch case differently.
|
|
@@ -93,7 +96,9 @@ async function installChromium() {
|
|
|
93
96
|
} catch (err) {
|
|
94
97
|
const detail = err instanceof Error ? err.message : String(err);
|
|
95
98
|
console.error(`[STORYBOOK_PLUGIN] chromium install FAILED: ${detail}`);
|
|
96
|
-
throw new
|
|
99
|
+
throw new ChromiumUnavailableError(
|
|
100
|
+
`Playwright chromium install failed: ${detail}`
|
|
101
|
+
);
|
|
97
102
|
} finally {
|
|
98
103
|
installPromise = null;
|
|
99
104
|
}
|
|
@@ -115,7 +120,7 @@ async function launchWithSelfHeal() {
|
|
|
115
120
|
console.error(
|
|
116
121
|
`[STORYBOOK_PLUGIN] launch still failing after install \u2014 likely path or libs issue: ${detail}`
|
|
117
122
|
);
|
|
118
|
-
throw new
|
|
123
|
+
throw new ChromiumUnavailableError(
|
|
119
124
|
`Playwright chromium is unavailable in this sandbox (install ran but launch still failed): ${detail}`
|
|
120
125
|
);
|
|
121
126
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { Browser } from 'playwright';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Terminal "this sandbox cannot launch chromium" failure (ONL-1429/ONL-1602).
|
|
5
|
+
* The `code` property rides the plugin's HTTP 5xx JSON error bodies
|
|
6
|
+
* (storybook-onlook-plugin.ts) so consumers — the agent runner's canvas
|
|
7
|
+
* tools — can classify the failure as tool_unavailable via a contract WE own,
|
|
8
|
+
* instead of regex-parsing Playwright's error prose (which reworded across
|
|
9
|
+
* versions and broke the detection twice: ONL-1085 → ONL-1364).
|
|
10
|
+
*/
|
|
11
|
+
declare class ChromiumUnavailableError extends Error {
|
|
12
|
+
readonly code = "CHROMIUM_UNAVAILABLE";
|
|
13
|
+
}
|
|
3
14
|
declare function getBrowser(): Promise<Browser>;
|
|
4
15
|
declare function closeBrowser(): Promise<void>;
|
|
5
16
|
|
|
6
|
-
export { closeBrowser, getBrowser };
|
|
17
|
+
export { ChromiumUnavailableError, closeBrowser, getBrowser };
|
|
@@ -10,6 +10,9 @@ var execFileAsync = promisify(execFile);
|
|
|
10
10
|
var require2 = createRequire$1(import.meta.url);
|
|
11
11
|
var browserPromise = null;
|
|
12
12
|
var installPromise = null;
|
|
13
|
+
var ChromiumUnavailableError = class extends Error {
|
|
14
|
+
code = "CHROMIUM_UNAVAILABLE";
|
|
15
|
+
};
|
|
13
16
|
var isMissingExecutableError = (err) => {
|
|
14
17
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15
18
|
return msg.includes("Executable doesn't exist") || msg.includes("Please run the following command to download new browsers") || // Older playwright revisions phrase the version-mismatch case differently.
|
|
@@ -42,7 +45,9 @@ async function installChromium() {
|
|
|
42
45
|
} catch (err) {
|
|
43
46
|
const detail = err instanceof Error ? err.message : String(err);
|
|
44
47
|
console.error(`[STORYBOOK_PLUGIN] chromium install FAILED: ${detail}`);
|
|
45
|
-
throw new
|
|
48
|
+
throw new ChromiumUnavailableError(
|
|
49
|
+
`Playwright chromium install failed: ${detail}`
|
|
50
|
+
);
|
|
46
51
|
} finally {
|
|
47
52
|
installPromise = null;
|
|
48
53
|
}
|
|
@@ -64,7 +69,7 @@ async function launchWithSelfHeal() {
|
|
|
64
69
|
console.error(
|
|
65
70
|
`[STORYBOOK_PLUGIN] launch still failing after install \u2014 likely path or libs issue: ${detail}`
|
|
66
71
|
);
|
|
67
|
-
throw new
|
|
72
|
+
throw new ChromiumUnavailableError(
|
|
68
73
|
`Playwright chromium is unavailable in this sandbox (install ran but launch still failed): ${detail}`
|
|
69
74
|
);
|
|
70
75
|
}
|
|
@@ -91,4 +96,4 @@ async function closeBrowser() {
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
|
-
export { closeBrowser, getBrowser };
|
|
99
|
+
export { ChromiumUnavailableError, closeBrowser, getBrowser };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlook/storybook-plugin",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"onlook-storybook": "./dist/cli/index.js"
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"@babel/traverse": "^7.26.9",
|
|
48
48
|
"@babel/types": "^7.29.0",
|
|
49
49
|
"@drizzle-team/brocli": "^0.11.0",
|
|
50
|
-
"playwright": "
|
|
51
|
-
"playwright-core": "
|
|
50
|
+
"playwright": "1.57.0",
|
|
51
|
+
"playwright-core": "1.57.0",
|
|
52
52
|
"storybook": "^10.1.11"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@onbook/tsconfig": "
|
|
55
|
+
"@onbook/tsconfig": "workspace:*",
|
|
56
56
|
"@types/babel__generator": "^7.6.8",
|
|
57
57
|
"@types/babel__traverse": "^7.20.6",
|
|
58
58
|
"@types/node": "^22.15.32",
|