@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 +119 -29
- package/dist/index.d.ts +1 -1
- package/dist/index.js +256 -53
- package/dist/preset/index.d.ts +1 -1
- package/dist/preset/index.js +260 -54
- package/dist/screenshot-service/index.js +77 -5
- package/dist/{storybook-onlook-plugin-CigILdDb.d.ts → storybook-onlook-plugin-B9Eo_OIq.d.ts} +1 -0
- package/package.json +1 -1
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",
|
|
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:
|
|
164
|
-
await page.waitForLoadState("domcontentloaded", { timeout:
|
|
165
|
-
await page.waitForLoadState("load", { timeout:
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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,
|
|
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-
|
|
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
|
|