@onlook/storybook-plugin 0.4.0-beta.10 → 0.4.0-beta.12

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
@@ -2,9 +2,9 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { command, string, boolean, run } from '@drizzle-team/brocli';
4
4
  import { execFile, spawn } from 'child_process';
5
- import fs, { readFileSync, existsSync } from 'fs';
6
5
  import path, { resolve, join, dirname } from 'path';
7
6
  import crypto from 'crypto';
7
+ import fs, { existsSync, readFileSync } from 'fs';
8
8
  import { createRequire as createRequire$1 } from 'module';
9
9
  import { promisify } from 'util';
10
10
  import { chromium } from 'playwright';
@@ -143,6 +143,77 @@ async function closeBrowser() {
143
143
  console.error("Error closing browser:", error);
144
144
  }
145
145
  }
146
+
147
+ // src/containment-contract/containment-contract.ts
148
+ var ONLOOK_RENDER_STATE_ATTR = "data-onlook-render-state";
149
+ var ONLOOK_CONTAINED_CARD_ATTR = "data-onlook-contained-card";
150
+
151
+ // src/screenshot-service/utils/screenshot/screenshot.ts
152
+ function classifyPreviewSnapshot(s) {
153
+ if (s.text.includes("Sorry, something went wrong")) return "errored";
154
+ if (s.text.includes("Couldn't find story")) return "errored";
155
+ if (s.hasContainedCard) return "contained";
156
+ if (s.renderStateAttr === "contained") return "contained";
157
+ if (s.renderStateAttr === "rendered") return "rendered";
158
+ if (s.text.length > 0) return "rendered";
159
+ if (s.hasSizedElement) return "rendered";
160
+ return "pending";
161
+ }
162
+ async function detectRenderState(page, opts = {}) {
163
+ const pollMs = opts.pollMs ?? 250;
164
+ const settleMs = opts.settleMs ?? 4e3;
165
+ const blankStablePolls = opts.blankStablePolls ?? 6;
166
+ const hardCapMs = opts.hardCapMs ?? 2e4;
167
+ const now = opts.now ?? Date.now;
168
+ const start = now();
169
+ let emptyStreak = 0;
170
+ const snapshot = () => page.evaluate(
171
+ ({ stateAttr, cardAttr }) => {
172
+ const root = document.getElementById("storybook-root") ?? document.body;
173
+ const text = (root?.textContent ?? "").trim();
174
+ let hasSizedElement = false;
175
+ if (root) {
176
+ const all = root.querySelectorAll("*");
177
+ for (let i = 0; i < all.length; i++) {
178
+ const rect = all[i]?.getBoundingClientRect();
179
+ if (rect && rect.width > 0 && rect.height > 0) {
180
+ hasSizedElement = true;
181
+ break;
182
+ }
183
+ }
184
+ }
185
+ return {
186
+ text,
187
+ hasContainedCard: Boolean(root?.querySelector(`[${cardAttr}]`)),
188
+ renderStateAttr: root?.getAttribute(stateAttr) ?? null,
189
+ hasSizedElement
190
+ };
191
+ },
192
+ { stateAttr: ONLOOK_RENDER_STATE_ATTR, cardAttr: ONLOOK_CONTAINED_CARD_ATTR }
193
+ );
194
+ while (true) {
195
+ let state;
196
+ let threw = false;
197
+ try {
198
+ state = classifyPreviewSnapshot(await snapshot());
199
+ } catch {
200
+ state = "pending";
201
+ threw = true;
202
+ }
203
+ if (state === "rendered" || state === "contained" || state === "errored") {
204
+ return state;
205
+ }
206
+ const elapsed = now() - start;
207
+ if (threw) {
208
+ emptyStreak = 0;
209
+ } else if (elapsed >= settleMs) {
210
+ emptyStreak += 1;
211
+ if (emptyStreak >= blankStablePolls) return "blank";
212
+ }
213
+ if (elapsed >= hardCapMs) return "timedOut";
214
+ await page.waitForTimeout(pollMs);
215
+ }
216
+ }
146
217
  function getScreenshotPath(storyId, theme) {
147
218
  const storyDir = path.join(SCREENSHOTS_DIR, storyId);
148
219
  return path.join(storyDir, `${theme}.png`);
@@ -151,7 +222,7 @@ function screenshotExists(storyId, theme) {
151
222
  const screenshotPath = getScreenshotPath(storyId, theme);
152
223
  return fs.existsSync(screenshotPath);
153
224
  }
154
- async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
225
+ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
155
226
  const browser = await getBrowser();
156
227
  const context = await browser.newContext({
157
228
  viewport: { width, height },
@@ -160,13 +231,14 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
160
231
  const page = await context.newPage();
161
232
  try {
162
233
  const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
163
- await page.goto(url, { timeout: timeoutMs });
164
- await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
165
- await page.waitForLoadState("load", { timeout: timeoutMs });
234
+ await page.goto(url, { timeout: loadTimeoutMs });
235
+ await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
236
+ await page.waitForLoadState("load", { timeout: loadTimeoutMs });
166
237
  try {
167
238
  await page.waitForLoadState("networkidle", { timeout: 5e3 });
168
239
  } catch {
169
240
  }
241
+ const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
170
242
  await page.evaluate(() => document.fonts.ready);
171
243
  try {
172
244
  await page.evaluate(async () => {
@@ -228,7 +300,7 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
228
300
  } else {
229
301
  screenshotBuffer = await page.screenshot({ type: "png" });
230
302
  }
231
- return { buffer: screenshotBuffer, boundingBox: resultBoundingBox };
303
+ return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
232
304
  } finally {
233
305
  await context.close();
234
306
  }
@@ -431,36 +503,54 @@ function detectPackageManager(repoRoot) {
431
503
  }
432
504
  return "npm";
433
505
  }
506
+ function getStorybookScript(storybookDir) {
507
+ const packageJsonPath = join(storybookDir, "package.json");
508
+ if (!existsSync(packageJsonPath)) {
509
+ return null;
510
+ }
511
+ try {
512
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
513
+ return packageJson.scripts?.storybook ?? null;
514
+ } catch {
515
+ return null;
516
+ }
517
+ }
434
518
 
435
519
  // src/cli/generate-screenshots/generate-screenshots.ts
520
+ function execStorybookCommand(pm) {
521
+ const devFlags = ["dev", "-p", "6006", "--no-open"];
522
+ switch (pm) {
523
+ case "pnpm":
524
+ return { command: "pnpm", args: ["exec", "storybook", ...devFlags] };
525
+ case "yarn":
526
+ return { command: "yarn", args: ["exec", "storybook", ...devFlags] };
527
+ case "bun":
528
+ return { command: "bunx", args: ["storybook", ...devFlags] };
529
+ default:
530
+ return { command: "npx", args: ["storybook", ...devFlags] };
531
+ }
532
+ }
436
533
  function getStorybookCommand(storybookDir) {
437
534
  const repoRoot = findRepoRoot(storybookDir);
438
535
  const pm = repoRoot ? detectPackageManager(repoRoot) : "npm";
439
536
  console.log(
440
537
  `\u{1F4E6} Detected package manager: ${pm}${repoRoot ? ` (from ${repoRoot})` : ""}`
441
538
  );
442
- let extraArgs = [];
443
- try {
444
- const pkgPath = resolve(storybookDir, "package.json");
445
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
446
- const script = pkg.scripts?.storybook ?? "";
447
- const hasPort = script.includes("-p ") || script.includes("--port");
448
- const hasNoOpen = script.includes("--no-open");
449
- if (!hasPort || !hasNoOpen) {
450
- const needsSeparator = pm === "npm" || pm === "pnpm";
451
- const flags = [];
452
- if (!hasPort) flags.push("-p", "6006");
453
- if (!hasNoOpen) flags.push("--no-open");
454
- extraArgs = needsSeparator && flags.length > 0 ? ["--", ...flags] : flags;
455
- }
456
- } catch {
457
- const separator = pm === "npm" || pm === "pnpm" ? ["--"] : [];
458
- extraArgs = [...separator, "-p", "6006", "--no-open"];
539
+ const memberScript = getStorybookScript(storybookDir);
540
+ const rootScript = !memberScript && repoRoot && repoRoot !== storybookDir ? getStorybookScript(repoRoot) : null;
541
+ const [script, scriptDir] = memberScript ? [memberScript, storybookDir] : rootScript && repoRoot ? [rootScript, repoRoot] : [null, storybookDir];
542
+ if (!script) {
543
+ console.log("\u26A0\uFE0F No `storybook` script found; invoking the binary directly");
544
+ return { ...execStorybookCommand(pm), cwd: storybookDir };
459
545
  }
460
- return {
461
- command: pm,
462
- args: ["run", "storybook", ...extraArgs]
463
- };
546
+ const hasPort = script.includes("-p ") || script.includes("--port");
547
+ const hasNoOpen = script.includes("--no-open");
548
+ const flags = [];
549
+ if (!hasPort) flags.push("-p", "6006");
550
+ if (!hasNoOpen) flags.push("--no-open");
551
+ const needsSeparator = pm === "npm" || pm === "pnpm";
552
+ const extraArgs = needsSeparator && flags.length > 0 ? ["--", ...flags] : flags;
553
+ return { command: pm, args: ["run", "storybook", ...extraArgs], cwd: scriptDir };
464
554
  }
465
555
  async function isStorybookRunning(url) {
466
556
  try {
@@ -575,8 +665,8 @@ async function generateScreenshots(options = {}) {
575
665
  if (alreadyRunning) {
576
666
  console.log("\u2705 Storybook is already running");
577
667
  } else {
578
- const { command: command2, args } = getStorybookCommand(storybookDir);
579
- storybookProcess = await startStorybook(command2, args, storybookDir);
668
+ const { command: command2, args, cwd } = getStorybookCommand(storybookDir);
669
+ storybookProcess = await startStorybook(command2, args, cwd);
580
670
  weStartedStorybook = true;
581
671
  console.log("\u23F3 Waiting for Storybook to finish compiling...");
582
672
  await waitForStorybookReady(url);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { E as EnvContainmentOptions, a as EnvContainmentSubstitution, O as OnlookPluginOptions, e as envContainmentPlugin, s as storybookOnlookPlugin } from './storybook-onlook-plugin-CigILdDb.js';
1
+ export { E as EnvContainmentOptions, a as EnvContainmentSubstitution, O as OnlookPluginOptions, e as envContainmentPlugin, s as storybookOnlookPlugin } from './storybook-onlook-plugin-B9Eo_OIq.js';
2
2
  import { Plugin } from 'vite';
3
3
  import { Indexer } from 'storybook/internal/types';
4
4