@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.
package/dist/index.js CHANGED
@@ -514,52 +514,6 @@ async function getBrowser() {
514
514
  }
515
515
  return browserPromise;
516
516
  }
517
-
518
- // src/screenshot-service/utils/capture-semaphore/capture-semaphore.ts
519
- function createSemaphore(limit) {
520
- const max = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 1;
521
- let active = 0;
522
- const waiters = [];
523
- const acquire = () => {
524
- if (active < max) {
525
- active += 1;
526
- return Promise.resolve();
527
- }
528
- return new Promise((resolve) => {
529
- waiters.push(() => {
530
- active += 1;
531
- resolve();
532
- });
533
- });
534
- };
535
- const release = () => {
536
- active -= 1;
537
- const next = waiters.shift();
538
- if (next) next();
539
- };
540
- return {
541
- async run(fn) {
542
- await acquire();
543
- try {
544
- return await fn();
545
- } finally {
546
- release();
547
- }
548
- }
549
- };
550
- }
551
- var DEFAULT_CAPTURE_CONCURRENCY = 3;
552
- function resolveConcurrency(raw) {
553
- if (!raw) return DEFAULT_CAPTURE_CONCURRENCY;
554
- const parsed = Number.parseInt(raw, 10);
555
- if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_CAPTURE_CONCURRENCY;
556
- return parsed;
557
- }
558
- var captureSemaphore = createSemaphore(
559
- resolveConcurrency(process.env.ONLOOK_CAPTURE_CONCURRENCY)
560
- );
561
-
562
- // src/screenshot-service/utils/screenshot/screenshot.ts
563
517
  function classifyPreviewSnapshot(s) {
564
518
  if (s.text.includes("Sorry, something went wrong")) return "errored";
565
519
  if (s.text.includes("Couldn't find story")) return "errored";
@@ -630,89 +584,87 @@ function getScreenshotPath(storyId, theme) {
630
584
  return path8.join(storyDir, `${theme}.png`);
631
585
  }
632
586
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
633
- return captureSemaphore.run(async () => {
634
- const browser = await getBrowser();
635
- const context = await browser.newContext({
636
- viewport: { width, height },
637
- deviceScaleFactor: 2
638
- });
639
- const page = await context.newPage();
587
+ const browser = await getBrowser();
588
+ const context = await browser.newContext({
589
+ viewport: { width, height },
590
+ deviceScaleFactor: 2
591
+ });
592
+ const page = await context.newPage();
593
+ try {
594
+ const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
595
+ await page.goto(url, { timeout: loadTimeoutMs });
596
+ await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
597
+ await page.waitForLoadState("load", { timeout: loadTimeoutMs });
640
598
  try {
641
- const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
642
- await page.goto(url, { timeout: loadTimeoutMs });
643
- await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
644
- await page.waitForLoadState("load", { timeout: loadTimeoutMs });
645
- try {
646
- await page.waitForLoadState("networkidle", { timeout: 5e3 });
647
- } catch {
648
- }
649
- const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
650
- await page.evaluate(() => document.fonts.ready);
651
- try {
652
- await page.evaluate(async () => {
653
- const images = document.querySelectorAll("img");
654
- await Promise.all(
655
- Array.from(images).map((img) => {
656
- if (img.complete) return Promise.resolve();
657
- return new Promise((resolve) => {
658
- img.addEventListener("load", resolve);
659
- img.addEventListener("error", resolve);
660
- setTimeout(resolve, 3e3);
661
- });
662
- })
663
- );
664
- });
665
- } catch {
666
- }
667
- const contentBounds = await page.evaluate(() => {
668
- const root = document.querySelector("#storybook-root");
669
- if (!root) return null;
670
- const children = root.querySelectorAll("*");
671
- if (children.length === 0) return null;
672
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
673
- children.forEach((child) => {
674
- const rect = child.getBoundingClientRect();
675
- if (rect.width === 0 || rect.height === 0) return;
676
- minX = Math.min(minX, rect.left);
677
- minY = Math.min(minY, rect.top);
678
- maxX = Math.max(maxX, rect.right);
679
- maxY = Math.max(maxY, rect.bottom);
680
- });
681
- if (minX === Infinity) return null;
682
- return {
683
- x: minX,
684
- y: minY,
685
- width: maxX - minX,
686
- height: maxY - minY
687
- };
599
+ await page.waitForLoadState("networkidle", { timeout: 5e3 });
600
+ } catch {
601
+ }
602
+ const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
603
+ await page.evaluate(() => document.fonts.ready);
604
+ try {
605
+ await page.evaluate(async () => {
606
+ const images = document.querySelectorAll("img");
607
+ await Promise.all(
608
+ Array.from(images).map((img) => {
609
+ if (img.complete) return Promise.resolve();
610
+ return new Promise((resolve) => {
611
+ img.addEventListener("load", resolve);
612
+ img.addEventListener("error", resolve);
613
+ setTimeout(resolve, 3e3);
614
+ });
615
+ })
616
+ );
688
617
  });
689
- let screenshotBuffer;
690
- let resultBoundingBox = null;
691
- if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
692
- const PADDING = 20;
693
- const clippedWidth = Math.min(width, contentBounds.width + PADDING);
694
- const clippedHeight = Math.min(height, contentBounds.height + PADDING);
695
- resultBoundingBox = {
696
- width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
697
- height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight))
698
- };
699
- screenshotBuffer = await page.screenshot({
700
- type: "png",
701
- clip: {
702
- x: Math.max(0, contentBounds.x - 10),
703
- y: Math.max(0, contentBounds.y - 10),
704
- width: clippedWidth,
705
- height: clippedHeight
706
- }
707
- });
708
- } else {
709
- screenshotBuffer = await page.screenshot({ type: "png" });
710
- }
711
- return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
712
- } finally {
713
- await context.close();
618
+ } catch {
714
619
  }
715
- });
620
+ const contentBounds = await page.evaluate(() => {
621
+ const root = document.querySelector("#storybook-root");
622
+ if (!root) return null;
623
+ const children = root.querySelectorAll("*");
624
+ if (children.length === 0) return null;
625
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
626
+ children.forEach((child) => {
627
+ const rect = child.getBoundingClientRect();
628
+ if (rect.width === 0 || rect.height === 0) return;
629
+ minX = Math.min(minX, rect.left);
630
+ minY = Math.min(minY, rect.top);
631
+ maxX = Math.max(maxX, rect.right);
632
+ maxY = Math.max(maxY, rect.bottom);
633
+ });
634
+ if (minX === Infinity) return null;
635
+ return {
636
+ x: minX,
637
+ y: minY,
638
+ width: maxX - minX,
639
+ height: maxY - minY
640
+ };
641
+ });
642
+ let screenshotBuffer;
643
+ let resultBoundingBox = null;
644
+ if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
645
+ const PADDING = 20;
646
+ const clippedWidth = Math.min(width, contentBounds.width + PADDING);
647
+ const clippedHeight = Math.min(height, contentBounds.height + PADDING);
648
+ resultBoundingBox = {
649
+ width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
650
+ height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight))
651
+ };
652
+ screenshotBuffer = await page.screenshot({
653
+ type: "png",
654
+ clip: {
655
+ x: Math.max(0, contentBounds.x - 10),
656
+ y: Math.max(0, contentBounds.y - 10),
657
+ width: clippedWidth,
658
+ height: clippedHeight
659
+ }
660
+ });
661
+ } else {
662
+ screenshotBuffer = await page.screenshot({ type: "png" });
663
+ }
664
+ return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
665
+ } finally {
666
+ await context.close();
667
+ }
716
668
  }
717
669
  async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
718
670
  try {
@@ -819,39 +771,7 @@ async function waitForIndexAndBroadcast(port = 6006) {
819
771
  });
820
772
  }
821
773
 
822
- // src/handlers/handleStoryFileChange/captureCoalescer/captureCoalescer.ts
823
- function createCaptureCoalescer(runCapture) {
824
- const inFlight2 = /* @__PURE__ */ new Map();
825
- const trailing = /* @__PURE__ */ new Set();
826
- const start = (storyId) => {
827
- const done = (async () => {
828
- try {
829
- await runCapture(storyId);
830
- } finally {
831
- inFlight2.delete(storyId);
832
- if (trailing.has(storyId)) {
833
- trailing.delete(storyId);
834
- await start(storyId);
835
- }
836
- }
837
- })();
838
- inFlight2.set(storyId, done);
839
- return done;
840
- };
841
- return {
842
- capture(storyId) {
843
- const existing = inFlight2.get(storyId);
844
- if (existing) {
845
- trailing.add(storyId);
846
- return existing;
847
- }
848
- return start(storyId);
849
- }
850
- };
851
- }
852
-
853
774
  // src/handlers/handleStoryFileChange/handleStoryFileChange.ts
854
- var STORYBOOK_URL = "http://localhost:6006";
855
775
  var cachedIndex = null;
856
776
  var indexFetchPromise = null;
857
777
  var pendingFiles = /* @__PURE__ */ new Set();
@@ -873,43 +793,44 @@ function getStoriesForFile(filePath) {
873
793
  const fileName = path8.basename(filePath);
874
794
  return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
875
795
  }
876
- var latestStoryFiles = /* @__PURE__ */ new Map();
877
- async function captureStoryAndUpdateManifest(storyId) {
878
- const [lightResult] = await Promise.all([
879
- generateScreenshot(storyId, "light", STORYBOOK_URL),
880
- generateScreenshot(storyId, "dark", STORYBOOK_URL)
881
- ]);
882
- const boundingBox = lightResult?.boundingBox ?? void 0;
883
- for (const file of latestStoryFiles.get(storyId) ?? []) {
884
- const fileHash = computeFileHash(file);
885
- updateManifest(storyId, file, fileHash, boundingBox);
886
- }
887
- }
888
- var captureCoalescer = createCaptureCoalescer(captureStoryAndUpdateManifest);
889
796
  async function regenerateScreenshotsForFiles(files) {
890
797
  await fetchStorybookIndex();
891
- const storyToFiles = /* @__PURE__ */ new Map();
798
+ const allStoryIds = /* @__PURE__ */ new Set();
799
+ const fileToStories = /* @__PURE__ */ new Map();
892
800
  for (const file of files) {
893
- for (const storyId of getStoriesForFile(file)) {
894
- const arr = storyToFiles.get(storyId) ?? [];
895
- arr.push(file);
896
- storyToFiles.set(storyId, arr);
801
+ const storyIds = getStoriesForFile(file);
802
+ fileToStories.set(file, storyIds);
803
+ for (const id of storyIds) {
804
+ allStoryIds.add(id);
897
805
  }
898
806
  }
899
- if (storyToFiles.size === 0) {
807
+ if (allStoryIds.size === 0) {
900
808
  console.log("[Screenshots] No stories found for changed files");
901
809
  return;
902
810
  }
903
811
  console.log(
904
- `[Screenshots] Regenerating ${storyToFiles.size} stories from ${files.length} files`
812
+ `[Screenshots] Regenerating ${allStoryIds.size} stories from ${files.length} files`
905
813
  );
906
- for (const [storyId, storyFiles] of storyToFiles) {
907
- latestStoryFiles.set(storyId, storyFiles);
908
- }
814
+ const storybookUrl = "http://localhost:6006";
815
+ const storyBoundingBoxes = /* @__PURE__ */ new Map();
909
816
  await Promise.all(
910
- Array.from(storyToFiles.keys()).map((storyId) => captureCoalescer.capture(storyId))
817
+ Array.from(allStoryIds).map(async (storyId) => {
818
+ const [lightResult, _darkResult] = await Promise.all([
819
+ generateScreenshot(storyId, "light", storybookUrl),
820
+ generateScreenshot(storyId, "dark", storybookUrl)
821
+ ]);
822
+ if (lightResult) {
823
+ storyBoundingBoxes.set(storyId, lightResult.boundingBox);
824
+ }
825
+ })
911
826
  );
912
- console.log(`[Screenshots] \u2713 Regenerated ${storyToFiles.size} stories`);
827
+ for (const [file, storyIds] of fileToStories) {
828
+ const fileHash = computeFileHash(file);
829
+ storyIds.forEach((storyId) => {
830
+ updateManifest(storyId, file, fileHash, storyBoundingBoxes.get(storyId));
831
+ });
832
+ }
833
+ console.log(`[Screenshots] \u2713 Regenerated ${allStoryIds.size} stories`);
913
834
  }
914
835
  function handleStoryFileChange({ file, modules }) {
915
836
  if (file.endsWith(".stories.tsx") || file.endsWith(".stories.ts")) {
@@ -1131,63 +1052,61 @@ function packageMockPlugin(options) {
1131
1052
 
1132
1053
  // src/screenshot-service/utils/console-logs/console-logs.ts
1133
1054
  async function captureConsoleLogs(storyId, theme = "light", storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
1134
- return captureSemaphore.run(async () => {
1135
- const browser = await getBrowser();
1136
- const context = await browser.newContext({
1137
- viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
1138
- });
1139
- const page = await context.newPage();
1140
- const logs = [];
1141
- const pageErrors = [];
1142
- const failedRequests = [];
1143
- page.on("console", (msg) => {
1144
- const level = msg.type();
1145
- logs.push({
1146
- level: level === "warning" ? "warn" : level,
1147
- text: msg.text(),
1148
- timestamp: Date.now()
1149
- });
1150
- });
1151
- page.on("pageerror", (err) => {
1152
- pageErrors.push(err.message);
1153
- });
1154
- page.on("response", (res) => {
1155
- const status = res.status();
1156
- if (status >= 400) {
1157
- const req = res.request();
1158
- failedRequests.push({
1159
- url: res.url(),
1160
- status,
1161
- method: req.method(),
1162
- resourceType: req.resourceType(),
1163
- failure: null
1164
- });
1165
- }
1055
+ const browser = await getBrowser();
1056
+ const context = await browser.newContext({
1057
+ viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
1058
+ });
1059
+ const page = await context.newPage();
1060
+ const logs = [];
1061
+ const pageErrors = [];
1062
+ const failedRequests = [];
1063
+ page.on("console", (msg) => {
1064
+ const level = msg.type();
1065
+ logs.push({
1066
+ level: level === "warning" ? "warn" : level,
1067
+ text: msg.text(),
1068
+ timestamp: Date.now()
1166
1069
  });
1167
- page.on("requestfailed", (req) => {
1070
+ });
1071
+ page.on("pageerror", (err) => {
1072
+ pageErrors.push(err.message);
1073
+ });
1074
+ page.on("response", (res) => {
1075
+ const status = res.status();
1076
+ if (status >= 400) {
1077
+ const req = res.request();
1168
1078
  failedRequests.push({
1169
- url: req.url(),
1170
- status: null,
1079
+ url: res.url(),
1080
+ status,
1171
1081
  method: req.method(),
1172
1082
  resourceType: req.resourceType(),
1173
- failure: req.failure()?.errorText ?? "request failed"
1083
+ failure: null
1174
1084
  });
1085
+ }
1086
+ });
1087
+ page.on("requestfailed", (req) => {
1088
+ failedRequests.push({
1089
+ url: req.url(),
1090
+ status: null,
1091
+ method: req.method(),
1092
+ resourceType: req.resourceType(),
1093
+ failure: req.failure()?.errorText ?? "request failed"
1175
1094
  });
1176
- const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
1095
+ });
1096
+ const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
1097
+ try {
1098
+ await page.goto(url, { timeout: timeoutMs });
1099
+ await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
1100
+ await page.waitForLoadState("load", { timeout: timeoutMs });
1177
1101
  try {
1178
- await page.goto(url, { timeout: timeoutMs });
1179
- await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
1180
- await page.waitForLoadState("load", { timeout: timeoutMs });
1181
- try {
1182
- await page.waitForLoadState("networkidle", { timeout: 5e3 });
1183
- } catch {
1184
- }
1185
- await new Promise((r) => setTimeout(r, 1e3));
1186
- return { logs, pageErrors, failedRequests, url };
1187
- } finally {
1188
- await context.close();
1102
+ await page.waitForLoadState("networkidle", { timeout: 5e3 });
1103
+ } catch {
1189
1104
  }
1190
- });
1105
+ await new Promise((r) => setTimeout(r, 1e3));
1106
+ return { logs, pageErrors, failedRequests, url };
1107
+ } finally {
1108
+ await context.close();
1109
+ }
1191
1110
  }
1192
1111
 
1193
1112
  // src/soft-story-rerender/soft-story-rerender.ts
@@ -1378,17 +1297,6 @@ function findGitRoot(startPath) {
1378
1297
  }
1379
1298
  return null;
1380
1299
  }
1381
- function ifNoneMatchHits(header, etag) {
1382
- if (!header) return false;
1383
- return header.split(",").some((raw) => {
1384
- const token = raw.trim();
1385
- return token === etag || token === `W/${etag}`;
1386
- });
1387
- }
1388
- function decideIndexResponse(body, ifNoneMatch) {
1389
- const etag = `"${createHash("sha1").update(body).digest("hex")}"`;
1390
- return { etag, notModified: ifNoneMatchHits(ifNoneMatch, etag) };
1391
- }
1392
1300
  var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
1393
1301
  function isStoryFile(file) {
1394
1302
  return STORY_FILE_PATTERN.test(file);
@@ -1421,7 +1329,8 @@ function portFromArgv(argv) {
1421
1329
  }
1422
1330
  async function defaultFetchIndex(port) {
1423
1331
  try {
1424
- const res = await fetch(`http://localhost:${port}/index.json`, {
1332
+ const res = await fetch(`http://localhost:${port}/index.json?_t=${Date.now()}`, {
1333
+ cache: "no-store",
1425
1334
  signal: AbortSignal.timeout(2e3)
1426
1335
  });
1427
1336
  if (!res.ok) return null;
@@ -1581,8 +1490,15 @@ function detectTailwindEntryCss(cwd) {
1581
1490
  var serveMetadataAndScreenshots = (req, res, next) => {
1582
1491
  if (req.url === "/onbook-index.json") {
1583
1492
  console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
1493
+ const cacheBuster = Date.now();
1584
1494
  console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
1585
- fetch("http://localhost:6006/index.json").then((response) => {
1495
+ fetch(`http://localhost:6006/index.json?_t=${cacheBuster}`, {
1496
+ cache: "no-store",
1497
+ headers: {
1498
+ "Cache-Control": "no-cache",
1499
+ Pragma: "no-cache"
1500
+ }
1501
+ }).then((response) => {
1586
1502
  console.log("[STORYBOOK_PLUGIN] Storybook index fetch response", {
1587
1503
  status: response.status,
1588
1504
  ok: response.ok,
@@ -1601,21 +1517,12 @@ var serveMetadataAndScreenshots = (req, res, next) => {
1601
1517
  entryCount: Object.keys(indexData.entries || {}).length,
1602
1518
  hasMetadata: !!indexData.meta
1603
1519
  });
1604
- const body = JSON.stringify(indexData);
1605
- const { etag, notModified } = decideIndexResponse(
1606
- body,
1607
- req.headers["if-none-match"]
1608
- );
1609
- res.setHeader("Access-Control-Allow-Origin", "*");
1610
- res.setHeader("ETag", etag);
1611
- res.setHeader("Cache-Control", "no-cache");
1612
- if (notModified) {
1613
- res.statusCode = 304;
1614
- res.end();
1615
- return;
1616
- }
1617
1520
  res.setHeader("Content-Type", "application/json");
1618
- res.end(body);
1521
+ res.setHeader("Access-Control-Allow-Origin", "*");
1522
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1523
+ res.setHeader("Pragma", "no-cache");
1524
+ res.setHeader("Expires", "0");
1525
+ res.end(JSON.stringify(indexData));
1619
1526
  }).catch((error) => {
1620
1527
  console.error("[STORYBOOK_PLUGIN] Failed to fetch/extend index.json", {
1621
1528
  error: error instanceof Error ? error.message : String(error),
@@ -1,4 +1,4 @@
1
- import { O as OnlookPluginOptions } from '../storybook-onlook-plugin-BvrvMhRF.js';
1
+ import { O as OnlookPluginOptions } from '../storybook-onlook-plugin-J4qj72mN.js';
2
2
  import 'vite';
3
3
 
4
4
  declare function pluginOptionsFromPresetOptions(options: Record<string, unknown> | undefined): OnlookPluginOptions;