@onlook/storybook-plugin 0.4.0-beta.17 → 0.4.0-beta.18

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.
@@ -394,52 +394,6 @@ async function getBrowser() {
394
394
  }
395
395
  return browserPromise;
396
396
  }
397
-
398
- // src/screenshot-service/utils/capture-semaphore/capture-semaphore.ts
399
- function createSemaphore(limit) {
400
- const max = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 1;
401
- let active = 0;
402
- const waiters = [];
403
- const acquire = () => {
404
- if (active < max) {
405
- active += 1;
406
- return Promise.resolve();
407
- }
408
- return new Promise((resolve) => {
409
- waiters.push(() => {
410
- active += 1;
411
- resolve();
412
- });
413
- });
414
- };
415
- const release = () => {
416
- active -= 1;
417
- const next = waiters.shift();
418
- if (next) next();
419
- };
420
- return {
421
- async run(fn) {
422
- await acquire();
423
- try {
424
- return await fn();
425
- } finally {
426
- release();
427
- }
428
- }
429
- };
430
- }
431
- var DEFAULT_CAPTURE_CONCURRENCY = 3;
432
- function resolveConcurrency(raw) {
433
- if (!raw) return DEFAULT_CAPTURE_CONCURRENCY;
434
- const parsed = Number.parseInt(raw, 10);
435
- if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_CAPTURE_CONCURRENCY;
436
- return parsed;
437
- }
438
- var captureSemaphore = createSemaphore(
439
- resolveConcurrency(process.env.ONLOOK_CAPTURE_CONCURRENCY)
440
- );
441
-
442
- // src/screenshot-service/utils/screenshot/screenshot.ts
443
397
  function classifyPreviewSnapshot(s) {
444
398
  if (s.text.includes("Sorry, something went wrong")) return "errored";
445
399
  if (s.text.includes("Couldn't find story")) return "errored";
@@ -510,89 +464,87 @@ function getScreenshotPath(storyId, theme) {
510
464
  return path8.join(storyDir, `${theme}.png`);
511
465
  }
512
466
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
513
- return captureSemaphore.run(async () => {
514
- const browser = await getBrowser();
515
- const context = await browser.newContext({
516
- viewport: { width, height },
517
- deviceScaleFactor: 2
518
- });
519
- const page = await context.newPage();
467
+ const browser = await getBrowser();
468
+ const context = await browser.newContext({
469
+ viewport: { width, height },
470
+ deviceScaleFactor: 2
471
+ });
472
+ const page = await context.newPage();
473
+ try {
474
+ const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
475
+ await page.goto(url, { timeout: loadTimeoutMs });
476
+ await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
477
+ await page.waitForLoadState("load", { timeout: loadTimeoutMs });
520
478
  try {
521
- const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
522
- await page.goto(url, { timeout: loadTimeoutMs });
523
- await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
524
- await page.waitForLoadState("load", { timeout: loadTimeoutMs });
525
- try {
526
- await page.waitForLoadState("networkidle", { timeout: 5e3 });
527
- } catch {
528
- }
529
- const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
530
- await page.evaluate(() => document.fonts.ready);
531
- try {
532
- await page.evaluate(async () => {
533
- const images = document.querySelectorAll("img");
534
- await Promise.all(
535
- Array.from(images).map((img) => {
536
- if (img.complete) return Promise.resolve();
537
- return new Promise((resolve) => {
538
- img.addEventListener("load", resolve);
539
- img.addEventListener("error", resolve);
540
- setTimeout(resolve, 3e3);
541
- });
542
- })
543
- );
544
- });
545
- } catch {
546
- }
547
- const contentBounds = await page.evaluate(() => {
548
- const root = document.querySelector("#storybook-root");
549
- if (!root) return null;
550
- const children = root.querySelectorAll("*");
551
- if (children.length === 0) return null;
552
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
553
- children.forEach((child) => {
554
- const rect = child.getBoundingClientRect();
555
- if (rect.width === 0 || rect.height === 0) return;
556
- minX = Math.min(minX, rect.left);
557
- minY = Math.min(minY, rect.top);
558
- maxX = Math.max(maxX, rect.right);
559
- maxY = Math.max(maxY, rect.bottom);
560
- });
561
- if (minX === Infinity) return null;
562
- return {
563
- x: minX,
564
- y: minY,
565
- width: maxX - minX,
566
- height: maxY - minY
567
- };
479
+ await page.waitForLoadState("networkidle", { timeout: 5e3 });
480
+ } catch {
481
+ }
482
+ const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
483
+ await page.evaluate(() => document.fonts.ready);
484
+ try {
485
+ await page.evaluate(async () => {
486
+ const images = document.querySelectorAll("img");
487
+ await Promise.all(
488
+ Array.from(images).map((img) => {
489
+ if (img.complete) return Promise.resolve();
490
+ return new Promise((resolve) => {
491
+ img.addEventListener("load", resolve);
492
+ img.addEventListener("error", resolve);
493
+ setTimeout(resolve, 3e3);
494
+ });
495
+ })
496
+ );
568
497
  });
569
- let screenshotBuffer;
570
- let resultBoundingBox = null;
571
- if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
572
- const PADDING = 20;
573
- const clippedWidth = Math.min(width, contentBounds.width + PADDING);
574
- const clippedHeight = Math.min(height, contentBounds.height + PADDING);
575
- resultBoundingBox = {
576
- width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
577
- height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight))
578
- };
579
- screenshotBuffer = await page.screenshot({
580
- type: "png",
581
- clip: {
582
- x: Math.max(0, contentBounds.x - 10),
583
- y: Math.max(0, contentBounds.y - 10),
584
- width: clippedWidth,
585
- height: clippedHeight
586
- }
587
- });
588
- } else {
589
- screenshotBuffer = await page.screenshot({ type: "png" });
590
- }
591
- return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
592
- } finally {
593
- await context.close();
498
+ } catch {
594
499
  }
595
- });
500
+ const contentBounds = await page.evaluate(() => {
501
+ const root = document.querySelector("#storybook-root");
502
+ if (!root) return null;
503
+ const children = root.querySelectorAll("*");
504
+ if (children.length === 0) return null;
505
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
506
+ children.forEach((child) => {
507
+ const rect = child.getBoundingClientRect();
508
+ if (rect.width === 0 || rect.height === 0) return;
509
+ minX = Math.min(minX, rect.left);
510
+ minY = Math.min(minY, rect.top);
511
+ maxX = Math.max(maxX, rect.right);
512
+ maxY = Math.max(maxY, rect.bottom);
513
+ });
514
+ if (minX === Infinity) return null;
515
+ return {
516
+ x: minX,
517
+ y: minY,
518
+ width: maxX - minX,
519
+ height: maxY - minY
520
+ };
521
+ });
522
+ let screenshotBuffer;
523
+ let resultBoundingBox = null;
524
+ if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
525
+ const PADDING = 20;
526
+ const clippedWidth = Math.min(width, contentBounds.width + PADDING);
527
+ const clippedHeight = Math.min(height, contentBounds.height + PADDING);
528
+ resultBoundingBox = {
529
+ width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
530
+ height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight))
531
+ };
532
+ screenshotBuffer = await page.screenshot({
533
+ type: "png",
534
+ clip: {
535
+ x: Math.max(0, contentBounds.x - 10),
536
+ y: Math.max(0, contentBounds.y - 10),
537
+ width: clippedWidth,
538
+ height: clippedHeight
539
+ }
540
+ });
541
+ } else {
542
+ screenshotBuffer = await page.screenshot({ type: "png" });
543
+ }
544
+ return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
545
+ } finally {
546
+ await context.close();
547
+ }
596
548
  }
597
549
  async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
598
550
  try {
@@ -699,39 +651,7 @@ async function waitForIndexAndBroadcast(port = 6006) {
699
651
  });
700
652
  }
701
653
 
702
- // src/handlers/handleStoryFileChange/captureCoalescer/captureCoalescer.ts
703
- function createCaptureCoalescer(runCapture) {
704
- const inFlight3 = /* @__PURE__ */ new Map();
705
- const trailing2 = /* @__PURE__ */ new Set();
706
- const start = (storyId) => {
707
- const done = (async () => {
708
- try {
709
- await runCapture(storyId);
710
- } finally {
711
- inFlight3.delete(storyId);
712
- if (trailing2.has(storyId)) {
713
- trailing2.delete(storyId);
714
- await start(storyId);
715
- }
716
- }
717
- })();
718
- inFlight3.set(storyId, done);
719
- return done;
720
- };
721
- return {
722
- capture(storyId) {
723
- const existing = inFlight3.get(storyId);
724
- if (existing) {
725
- trailing2.add(storyId);
726
- return existing;
727
- }
728
- return start(storyId);
729
- }
730
- };
731
- }
732
-
733
654
  // src/handlers/handleStoryFileChange/handleStoryFileChange.ts
734
- var STORYBOOK_URL = "http://localhost:6006";
735
655
  var cachedIndex = null;
736
656
  var indexFetchPromise = null;
737
657
  var pendingFiles = /* @__PURE__ */ new Set();
@@ -753,43 +673,44 @@ function getStoriesForFile(filePath) {
753
673
  const fileName = path8.basename(filePath);
754
674
  return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
755
675
  }
756
- var latestStoryFiles = /* @__PURE__ */ new Map();
757
- async function captureStoryAndUpdateManifest(storyId) {
758
- const [lightResult] = await Promise.all([
759
- generateScreenshot(storyId, "light", STORYBOOK_URL),
760
- generateScreenshot(storyId, "dark", STORYBOOK_URL)
761
- ]);
762
- const boundingBox = lightResult?.boundingBox ?? void 0;
763
- for (const file of latestStoryFiles.get(storyId) ?? []) {
764
- const fileHash = computeFileHash(file);
765
- updateManifest(storyId, file, fileHash, boundingBox);
766
- }
767
- }
768
- var captureCoalescer = createCaptureCoalescer(captureStoryAndUpdateManifest);
769
676
  async function regenerateScreenshotsForFiles(files) {
770
677
  await fetchStorybookIndex();
771
- const storyToFiles = /* @__PURE__ */ new Map();
678
+ const allStoryIds = /* @__PURE__ */ new Set();
679
+ const fileToStories = /* @__PURE__ */ new Map();
772
680
  for (const file of files) {
773
- for (const storyId of getStoriesForFile(file)) {
774
- const arr = storyToFiles.get(storyId) ?? [];
775
- arr.push(file);
776
- storyToFiles.set(storyId, arr);
681
+ const storyIds = getStoriesForFile(file);
682
+ fileToStories.set(file, storyIds);
683
+ for (const id of storyIds) {
684
+ allStoryIds.add(id);
777
685
  }
778
686
  }
779
- if (storyToFiles.size === 0) {
687
+ if (allStoryIds.size === 0) {
780
688
  console.log("[Screenshots] No stories found for changed files");
781
689
  return;
782
690
  }
783
691
  console.log(
784
- `[Screenshots] Regenerating ${storyToFiles.size} stories from ${files.length} files`
692
+ `[Screenshots] Regenerating ${allStoryIds.size} stories from ${files.length} files`
785
693
  );
786
- for (const [storyId, storyFiles] of storyToFiles) {
787
- latestStoryFiles.set(storyId, storyFiles);
788
- }
694
+ const storybookUrl = "http://localhost:6006";
695
+ const storyBoundingBoxes = /* @__PURE__ */ new Map();
789
696
  await Promise.all(
790
- Array.from(storyToFiles.keys()).map((storyId) => captureCoalescer.capture(storyId))
697
+ Array.from(allStoryIds).map(async (storyId) => {
698
+ const [lightResult, _darkResult] = await Promise.all([
699
+ generateScreenshot(storyId, "light", storybookUrl),
700
+ generateScreenshot(storyId, "dark", storybookUrl)
701
+ ]);
702
+ if (lightResult) {
703
+ storyBoundingBoxes.set(storyId, lightResult.boundingBox);
704
+ }
705
+ })
791
706
  );
792
- console.log(`[Screenshots] \u2713 Regenerated ${storyToFiles.size} stories`);
707
+ for (const [file, storyIds] of fileToStories) {
708
+ const fileHash = computeFileHash(file);
709
+ storyIds.forEach((storyId) => {
710
+ updateManifest(storyId, file, fileHash, storyBoundingBoxes.get(storyId));
711
+ });
712
+ }
713
+ console.log(`[Screenshots] \u2713 Regenerated ${allStoryIds.size} stories`);
793
714
  }
794
715
  function handleStoryFileChange({ file, modules }) {
795
716
  if (file.endsWith(".stories.tsx") || file.endsWith(".stories.ts")) {
@@ -1127,63 +1048,61 @@ function packageMockPlugin(options) {
1127
1048
 
1128
1049
  // src/screenshot-service/utils/console-logs/console-logs.ts
1129
1050
  async function captureConsoleLogs(storyId, theme = "light", storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
1130
- return captureSemaphore.run(async () => {
1131
- const browser = await getBrowser();
1132
- const context = await browser.newContext({
1133
- viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
1134
- });
1135
- const page = await context.newPage();
1136
- const logs = [];
1137
- const pageErrors = [];
1138
- const failedRequests = [];
1139
- page.on("console", (msg) => {
1140
- const level = msg.type();
1141
- logs.push({
1142
- level: level === "warning" ? "warn" : level,
1143
- text: msg.text(),
1144
- timestamp: Date.now()
1145
- });
1146
- });
1147
- page.on("pageerror", (err) => {
1148
- pageErrors.push(err.message);
1149
- });
1150
- page.on("response", (res) => {
1151
- const status = res.status();
1152
- if (status >= 400) {
1153
- const req = res.request();
1154
- failedRequests.push({
1155
- url: res.url(),
1156
- status,
1157
- method: req.method(),
1158
- resourceType: req.resourceType(),
1159
- failure: null
1160
- });
1161
- }
1051
+ const browser = await getBrowser();
1052
+ const context = await browser.newContext({
1053
+ viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
1054
+ });
1055
+ const page = await context.newPage();
1056
+ const logs = [];
1057
+ const pageErrors = [];
1058
+ const failedRequests = [];
1059
+ page.on("console", (msg) => {
1060
+ const level = msg.type();
1061
+ logs.push({
1062
+ level: level === "warning" ? "warn" : level,
1063
+ text: msg.text(),
1064
+ timestamp: Date.now()
1162
1065
  });
1163
- page.on("requestfailed", (req) => {
1066
+ });
1067
+ page.on("pageerror", (err) => {
1068
+ pageErrors.push(err.message);
1069
+ });
1070
+ page.on("response", (res) => {
1071
+ const status = res.status();
1072
+ if (status >= 400) {
1073
+ const req = res.request();
1164
1074
  failedRequests.push({
1165
- url: req.url(),
1166
- status: null,
1075
+ url: res.url(),
1076
+ status,
1167
1077
  method: req.method(),
1168
1078
  resourceType: req.resourceType(),
1169
- failure: req.failure()?.errorText ?? "request failed"
1079
+ failure: null
1170
1080
  });
1081
+ }
1082
+ });
1083
+ page.on("requestfailed", (req) => {
1084
+ failedRequests.push({
1085
+ url: req.url(),
1086
+ status: null,
1087
+ method: req.method(),
1088
+ resourceType: req.resourceType(),
1089
+ failure: req.failure()?.errorText ?? "request failed"
1171
1090
  });
1172
- const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
1091
+ });
1092
+ const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
1093
+ try {
1094
+ await page.goto(url, { timeout: timeoutMs });
1095
+ await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
1096
+ await page.waitForLoadState("load", { timeout: timeoutMs });
1173
1097
  try {
1174
- await page.goto(url, { timeout: timeoutMs });
1175
- await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
1176
- await page.waitForLoadState("load", { timeout: timeoutMs });
1177
- try {
1178
- await page.waitForLoadState("networkidle", { timeout: 5e3 });
1179
- } catch {
1180
- }
1181
- await new Promise((r) => setTimeout(r, 1e3));
1182
- return { logs, pageErrors, failedRequests, url };
1183
- } finally {
1184
- await context.close();
1098
+ await page.waitForLoadState("networkidle", { timeout: 5e3 });
1099
+ } catch {
1185
1100
  }
1186
- });
1101
+ await new Promise((r) => setTimeout(r, 1e3));
1102
+ return { logs, pageErrors, failedRequests, url };
1103
+ } finally {
1104
+ await context.close();
1105
+ }
1187
1106
  }
1188
1107
 
1189
1108
  // src/soft-story-rerender/soft-story-rerender.ts
@@ -1374,17 +1293,6 @@ function findGitRoot(startPath) {
1374
1293
  }
1375
1294
  return null;
1376
1295
  }
1377
- function ifNoneMatchHits(header, etag) {
1378
- if (!header) return false;
1379
- return header.split(",").some((raw) => {
1380
- const token = raw.trim();
1381
- return token === etag || token === `W/${etag}`;
1382
- });
1383
- }
1384
- function decideIndexResponse(body, ifNoneMatch) {
1385
- const etag = `"${createHash("sha1").update(body).digest("hex")}"`;
1386
- return { etag, notModified: ifNoneMatchHits(ifNoneMatch, etag) };
1387
- }
1388
1296
  var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
1389
1297
  function isStoryFile(file) {
1390
1298
  return STORY_FILE_PATTERN.test(file);
@@ -1417,7 +1325,8 @@ function portFromArgv(argv) {
1417
1325
  }
1418
1326
  async function defaultFetchIndex(port) {
1419
1327
  try {
1420
- const res = await fetch(`http://localhost:${port}/index.json`, {
1328
+ const res = await fetch(`http://localhost:${port}/index.json?_t=${Date.now()}`, {
1329
+ cache: "no-store",
1421
1330
  signal: AbortSignal.timeout(2e3)
1422
1331
  });
1423
1332
  if (!res.ok) return null;
@@ -1577,8 +1486,15 @@ function detectTailwindEntryCss(cwd) {
1577
1486
  var serveMetadataAndScreenshots = (req, res, next) => {
1578
1487
  if (req.url === "/onbook-index.json") {
1579
1488
  console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
1489
+ const cacheBuster = Date.now();
1580
1490
  console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
1581
- fetch("http://localhost:6006/index.json").then((response) => {
1491
+ fetch(`http://localhost:6006/index.json?_t=${cacheBuster}`, {
1492
+ cache: "no-store",
1493
+ headers: {
1494
+ "Cache-Control": "no-cache",
1495
+ Pragma: "no-cache"
1496
+ }
1497
+ }).then((response) => {
1582
1498
  console.log("[STORYBOOK_PLUGIN] Storybook index fetch response", {
1583
1499
  status: response.status,
1584
1500
  ok: response.ok,
@@ -1597,21 +1513,12 @@ var serveMetadataAndScreenshots = (req, res, next) => {
1597
1513
  entryCount: Object.keys(indexData.entries || {}).length,
1598
1514
  hasMetadata: !!indexData.meta
1599
1515
  });
1600
- const body = JSON.stringify(indexData);
1601
- const { etag, notModified } = decideIndexResponse(
1602
- body,
1603
- req.headers["if-none-match"]
1604
- );
1605
- res.setHeader("Access-Control-Allow-Origin", "*");
1606
- res.setHeader("ETag", etag);
1607
- res.setHeader("Cache-Control", "no-cache");
1608
- if (notModified) {
1609
- res.statusCode = 304;
1610
- res.end();
1611
- return;
1612
- }
1613
1516
  res.setHeader("Content-Type", "application/json");
1614
- res.end(body);
1517
+ res.setHeader("Access-Control-Allow-Origin", "*");
1518
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1519
+ res.setHeader("Pragma", "no-cache");
1520
+ res.setHeader("Expires", "0");
1521
+ res.end(JSON.stringify(indexData));
1615
1522
  }).catch((error) => {
1616
1523
  console.error("[STORYBOOK_PLUGIN] Failed to fetch/extend index.json", {
1617
1524
  error: error instanceof Error ? error.message : String(error),