@onlook/storybook-plugin 0.4.0-beta.16 → 0.4.0-beta.17
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 +124 -78
- package/dist/index.d.ts +1 -1
- package/dist/index.js +454 -166
- package/dist/preset/index.d.ts +1 -1
- package/dist/preset/index.js +455 -166
- package/dist/screenshot-service/index.js +124 -78
- package/dist/{storybook-onlook-plugin-B9Eo_OIq.d.ts → storybook-onlook-plugin-BvrvMhRF.d.ts} +23 -0
- package/package.json +3 -2
|
@@ -151,6 +151,50 @@ async function closeBrowser() {
|
|
|
151
151
|
var ONLOOK_RENDER_STATE_ATTR = "data-onlook-render-state";
|
|
152
152
|
var ONLOOK_CONTAINED_CARD_ATTR = "data-onlook-contained-card";
|
|
153
153
|
|
|
154
|
+
// src/screenshot-service/utils/capture-semaphore/capture-semaphore.ts
|
|
155
|
+
function createSemaphore(limit) {
|
|
156
|
+
const max = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 1;
|
|
157
|
+
let active = 0;
|
|
158
|
+
const waiters = [];
|
|
159
|
+
const acquire = () => {
|
|
160
|
+
if (active < max) {
|
|
161
|
+
active += 1;
|
|
162
|
+
return Promise.resolve();
|
|
163
|
+
}
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
waiters.push(() => {
|
|
166
|
+
active += 1;
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
const release = () => {
|
|
172
|
+
active -= 1;
|
|
173
|
+
const next = waiters.shift();
|
|
174
|
+
if (next) next();
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
async run(fn) {
|
|
178
|
+
await acquire();
|
|
179
|
+
try {
|
|
180
|
+
return await fn();
|
|
181
|
+
} finally {
|
|
182
|
+
release();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
var DEFAULT_CAPTURE_CONCURRENCY = 3;
|
|
188
|
+
function resolveConcurrency(raw) {
|
|
189
|
+
if (!raw) return DEFAULT_CAPTURE_CONCURRENCY;
|
|
190
|
+
const parsed = Number.parseInt(raw, 10);
|
|
191
|
+
if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_CAPTURE_CONCURRENCY;
|
|
192
|
+
return parsed;
|
|
193
|
+
}
|
|
194
|
+
var captureSemaphore = createSemaphore(
|
|
195
|
+
resolveConcurrency(process.env.ONLOOK_CAPTURE_CONCURRENCY)
|
|
196
|
+
);
|
|
197
|
+
|
|
154
198
|
// src/screenshot-service/utils/screenshot/screenshot.ts
|
|
155
199
|
function classifyPreviewSnapshot(s) {
|
|
156
200
|
if (s.text.includes("Sorry, something went wrong")) return "errored";
|
|
@@ -226,87 +270,89 @@ function screenshotExists(storyId, theme) {
|
|
|
226
270
|
return fs.existsSync(screenshotPath);
|
|
227
271
|
}
|
|
228
272
|
async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const page = await context.newPage();
|
|
235
|
-
try {
|
|
236
|
-
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
237
|
-
await page.goto(url, { timeout: loadTimeoutMs });
|
|
238
|
-
await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
|
|
239
|
-
await page.waitForLoadState("load", { timeout: loadTimeoutMs });
|
|
240
|
-
try {
|
|
241
|
-
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
242
|
-
} catch {
|
|
243
|
-
}
|
|
244
|
-
const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
|
|
245
|
-
await page.evaluate(() => document.fonts.ready);
|
|
246
|
-
try {
|
|
247
|
-
await page.evaluate(async () => {
|
|
248
|
-
const images = document.querySelectorAll("img");
|
|
249
|
-
await Promise.all(
|
|
250
|
-
Array.from(images).map((img) => {
|
|
251
|
-
if (img.complete) return Promise.resolve();
|
|
252
|
-
return new Promise((resolve) => {
|
|
253
|
-
img.addEventListener("load", resolve);
|
|
254
|
-
img.addEventListener("error", resolve);
|
|
255
|
-
setTimeout(resolve, 3e3);
|
|
256
|
-
});
|
|
257
|
-
})
|
|
258
|
-
);
|
|
259
|
-
});
|
|
260
|
-
} catch {
|
|
261
|
-
}
|
|
262
|
-
const contentBounds = await page.evaluate(() => {
|
|
263
|
-
const root = document.querySelector("#storybook-root");
|
|
264
|
-
if (!root) return null;
|
|
265
|
-
const children = root.querySelectorAll("*");
|
|
266
|
-
if (children.length === 0) return null;
|
|
267
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
268
|
-
children.forEach((child) => {
|
|
269
|
-
const rect = child.getBoundingClientRect();
|
|
270
|
-
if (rect.width === 0 || rect.height === 0) return;
|
|
271
|
-
minX = Math.min(minX, rect.left);
|
|
272
|
-
minY = Math.min(minY, rect.top);
|
|
273
|
-
maxX = Math.max(maxX, rect.right);
|
|
274
|
-
maxY = Math.max(maxY, rect.bottom);
|
|
275
|
-
});
|
|
276
|
-
if (minX === Infinity) return null;
|
|
277
|
-
return {
|
|
278
|
-
x: minX,
|
|
279
|
-
y: minY,
|
|
280
|
-
width: maxX - minX,
|
|
281
|
-
height: maxY - minY
|
|
282
|
-
};
|
|
273
|
+
return captureSemaphore.run(async () => {
|
|
274
|
+
const browser = await getBrowser();
|
|
275
|
+
const context = await browser.newContext({
|
|
276
|
+
viewport: { width, height },
|
|
277
|
+
deviceScaleFactor: 2
|
|
283
278
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
279
|
+
const page = await context.newPage();
|
|
280
|
+
try {
|
|
281
|
+
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
282
|
+
await page.goto(url, { timeout: loadTimeoutMs });
|
|
283
|
+
await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
|
|
284
|
+
await page.waitForLoadState("load", { timeout: loadTimeoutMs });
|
|
285
|
+
try {
|
|
286
|
+
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
|
|
290
|
+
await page.evaluate(() => document.fonts.ready);
|
|
291
|
+
try {
|
|
292
|
+
await page.evaluate(async () => {
|
|
293
|
+
const images = document.querySelectorAll("img");
|
|
294
|
+
await Promise.all(
|
|
295
|
+
Array.from(images).map((img) => {
|
|
296
|
+
if (img.complete) return Promise.resolve();
|
|
297
|
+
return new Promise((resolve) => {
|
|
298
|
+
img.addEventListener("load", resolve);
|
|
299
|
+
img.addEventListener("error", resolve);
|
|
300
|
+
setTimeout(resolve, 3e3);
|
|
301
|
+
});
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
});
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
const contentBounds = await page.evaluate(() => {
|
|
308
|
+
const root = document.querySelector("#storybook-root");
|
|
309
|
+
if (!root) return null;
|
|
310
|
+
const children = root.querySelectorAll("*");
|
|
311
|
+
if (children.length === 0) return null;
|
|
312
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
313
|
+
children.forEach((child) => {
|
|
314
|
+
const rect = child.getBoundingClientRect();
|
|
315
|
+
if (rect.width === 0 || rect.height === 0) return;
|
|
316
|
+
minX = Math.min(minX, rect.left);
|
|
317
|
+
minY = Math.min(minY, rect.top);
|
|
318
|
+
maxX = Math.max(maxX, rect.right);
|
|
319
|
+
maxY = Math.max(maxY, rect.bottom);
|
|
320
|
+
});
|
|
321
|
+
if (minX === Infinity) return null;
|
|
322
|
+
return {
|
|
323
|
+
x: minX,
|
|
324
|
+
y: minY,
|
|
325
|
+
width: maxX - minX,
|
|
326
|
+
height: maxY - minY
|
|
327
|
+
};
|
|
302
328
|
});
|
|
303
|
-
|
|
304
|
-
|
|
329
|
+
let screenshotBuffer;
|
|
330
|
+
let resultBoundingBox = null;
|
|
331
|
+
if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
|
|
332
|
+
const PADDING = 20;
|
|
333
|
+
const clippedWidth = Math.min(width, contentBounds.width + PADDING);
|
|
334
|
+
const clippedHeight = Math.min(height, contentBounds.height + PADDING);
|
|
335
|
+
resultBoundingBox = {
|
|
336
|
+
width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
|
|
337
|
+
height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight))
|
|
338
|
+
};
|
|
339
|
+
screenshotBuffer = await page.screenshot({
|
|
340
|
+
type: "png",
|
|
341
|
+
clip: {
|
|
342
|
+
x: Math.max(0, contentBounds.x - 10),
|
|
343
|
+
y: Math.max(0, contentBounds.y - 10),
|
|
344
|
+
width: clippedWidth,
|
|
345
|
+
height: clippedHeight
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
} else {
|
|
349
|
+
screenshotBuffer = await page.screenshot({ type: "png" });
|
|
350
|
+
}
|
|
351
|
+
return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
|
|
352
|
+
} finally {
|
|
353
|
+
await context.close();
|
|
305
354
|
}
|
|
306
|
-
|
|
307
|
-
} finally {
|
|
308
|
-
await context.close();
|
|
309
|
-
}
|
|
355
|
+
});
|
|
310
356
|
}
|
|
311
357
|
async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
312
358
|
try {
|
package/dist/{storybook-onlook-plugin-B9Eo_OIq.d.ts → storybook-onlook-plugin-BvrvMhRF.d.ts}
RENAMED
|
@@ -42,6 +42,28 @@ type EnvContainmentOptions = {
|
|
|
42
42
|
*/
|
|
43
43
|
declare function envContainmentPlugin(options: EnvContainmentOptions | undefined): Plugin | null;
|
|
44
44
|
|
|
45
|
+
/** One package to replace with a functional zero-backend mock. */
|
|
46
|
+
interface PackageMockEntry {
|
|
47
|
+
/** Bare npm specifier to intercept, e.g. `@clerk/nextjs`. */
|
|
48
|
+
package: string;
|
|
49
|
+
/**
|
|
50
|
+
* Named exports the customer actually imports from the package (the union
|
|
51
|
+
* across story-reachable component sources). The generated mock provides a
|
|
52
|
+
* stand-in for each so no `import { X } from '@clerk/nextjs'` produces a Vite
|
|
53
|
+
* "no matching export" build error.
|
|
54
|
+
*/
|
|
55
|
+
usedExports: string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The `packageMock` plugin-options surface. Written by the preflight engine into
|
|
59
|
+
* `.storybook/main.*` (`storybookOnlookPlugin({ packageMock })`). Mirrored
|
|
60
|
+
* structurally as `PackageMockValue` in
|
|
61
|
+
* `packages/preflight/src/checks/storybook/patch.ts` — keep the shapes in sync.
|
|
62
|
+
*/
|
|
63
|
+
interface PackageMockOptions {
|
|
64
|
+
packages?: PackageMockEntry[];
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
type OnlookPluginOptions = {
|
|
46
68
|
/** Storybook port (default: 6006) */
|
|
47
69
|
port?: number;
|
|
@@ -54,6 +76,7 @@ type OnlookPluginOptions = {
|
|
|
54
76
|
storyGlobs?: string[];
|
|
55
77
|
tailwindEntryCss?: string | false;
|
|
56
78
|
envContainment?: EnvContainmentOptions;
|
|
79
|
+
packageMock?: PackageMockOptions;
|
|
57
80
|
moduleLoadCatchAll?: boolean;
|
|
58
81
|
resolveAliases?: boolean;
|
|
59
82
|
};
|
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.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"onlook-storybook": "./dist/cli/index.js"
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "rm -rf dist && tsup",
|
|
37
37
|
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
|
38
|
+
"test": "bun test",
|
|
38
39
|
"typecheck": "tsc --noEmit",
|
|
39
40
|
"prepublishOnly": "bun run build && bun scripts/prepublish.ts",
|
|
40
41
|
"postpublish": "bun scripts/postpublish.ts",
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"storybook": "^10.1.11"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"@onbook/tsconfig": "
|
|
56
|
+
"@onbook/tsconfig": "0.1.0",
|
|
56
57
|
"@types/babel__generator": "^7.6.8",
|
|
57
58
|
"@types/babel__traverse": "^7.20.6",
|
|
58
59
|
"@types/node": "^22.15.32",
|