@onlook/storybook-plugin 0.4.0-beta.7 → 0.4.0-beta.9

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
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { command, string, boolean, run } from '@drizzle-team/brocli';
4
- import { spawn } from 'child_process';
4
+ import { execFile, spawn } from 'child_process';
5
5
  import fs, { readFileSync, existsSync } from 'fs';
6
6
  import path, { resolve, join, dirname } from 'path';
7
7
  import crypto from 'crypto';
8
+ import { createRequire as createRequire$1 } from 'module';
9
+ import { promisify } from 'util';
8
10
  import { chromium } from 'playwright';
9
11
 
10
12
  globalThis.require = createRequire(import.meta.url);
@@ -57,24 +59,84 @@ function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
57
59
  };
58
60
  saveManifest(manifest);
59
61
  }
60
- var browser = null;
62
+ var execFileAsync = promisify(execFile);
63
+ var require2 = createRequire$1(import.meta.url);
64
+ var browserPromise = null;
65
+ var installPromise = null;
66
+ var isMissingExecutableError = (err) => {
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ return msg.includes("Executable doesn't exist") || msg.includes("Please run the following command to download new browsers") || // Older playwright revisions phrase the version-mismatch case differently.
69
+ /chromium-\d+\/chrome-linux\/chrome/i.test(msg);
70
+ };
71
+ async function installChromium() {
72
+ if (!installPromise) {
73
+ installPromise = (async () => {
74
+ const cliPath = require2.resolve("playwright-core/cli.js");
75
+ console.log(
76
+ `[STORYBOOK_PLUGIN] chromium binary missing \u2014 installing via ${cliPath}`
77
+ );
78
+ try {
79
+ const { stdout, stderr } = await execFileAsync(
80
+ process.execPath,
81
+ [cliPath, "install", "--force", "chromium"],
82
+ { timeout: 5 * 60 * 1e3, maxBuffer: 32 * 1024 * 1024 }
83
+ );
84
+ if (stdout)
85
+ console.log(`[STORYBOOK_PLUGIN] playwright install: ${stdout.slice(-1e3)}`);
86
+ if (stderr)
87
+ console.log(
88
+ `[STORYBOOK_PLUGIN] playwright install stderr: ${stderr.slice(-1e3)}`
89
+ );
90
+ console.log("[STORYBOOK_PLUGIN] chromium installed");
91
+ } catch (err) {
92
+ const detail = err instanceof Error ? err.message : String(err);
93
+ console.error(`[STORYBOOK_PLUGIN] chromium install FAILED: ${detail}`);
94
+ throw new Error(`Playwright chromium install failed: ${detail}`);
95
+ } finally {
96
+ installPromise = null;
97
+ }
98
+ })();
99
+ }
100
+ return installPromise;
101
+ }
102
+ async function launchWithSelfHeal() {
103
+ try {
104
+ return await chromium.launch({ headless: true });
105
+ } catch (err) {
106
+ if (!isMissingExecutableError(err)) throw err;
107
+ console.log("[STORYBOOK_PLUGIN] launch failed, attempting self-heal install");
108
+ await installChromium();
109
+ try {
110
+ return await chromium.launch({ headless: true });
111
+ } catch (postInstallErr) {
112
+ const detail = postInstallErr instanceof Error ? postInstallErr.message : String(postInstallErr);
113
+ console.error(
114
+ `[STORYBOOK_PLUGIN] launch still failing after install \u2014 likely path or libs issue: ${detail}`
115
+ );
116
+ throw new Error(
117
+ `Playwright chromium is unavailable in this sandbox (install ran but launch still failed): ${detail}`
118
+ );
119
+ }
120
+ }
121
+ }
61
122
  async function getBrowser() {
62
- if (!browser) {
63
- browser = await chromium.launch({
64
- headless: true
123
+ if (!browserPromise) {
124
+ browserPromise = launchWithSelfHeal().catch((err) => {
125
+ browserPromise = null;
126
+ throw err;
65
127
  });
66
128
  }
67
- return browser;
129
+ return browserPromise;
68
130
  }
69
131
  async function closeBrowser() {
70
- if (browser) {
71
- try {
72
- await browser.close();
73
- } catch (error) {
74
- console.error("Error closing browser:", error);
75
- } finally {
76
- browser = null;
77
- }
132
+ const pending = browserPromise;
133
+ if (!pending) return;
134
+ browserPromise = null;
135
+ try {
136
+ const b = await pending;
137
+ await b.close();
138
+ } catch (error) {
139
+ console.error("Error closing browser:", error);
78
140
  }
79
141
  }
80
142
  function getScreenshotPath(storyId, theme) {
@@ -86,8 +148,8 @@ function screenshotExists(storyId, theme) {
86
148
  return fs.existsSync(screenshotPath);
87
149
  }
88
150
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
89
- const browser2 = await getBrowser();
90
- const context = await browser2.newContext({
151
+ const browser = await getBrowser();
152
+ const context = await browser.newContext({
91
153
  viewport: { width, height },
92
154
  deviceScaleFactor: 2
93
155
  });
@@ -192,6 +254,34 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
192
254
  }
193
255
 
194
256
  // src/screenshot-service/screenshot-service.ts
257
+ var ONBOOK_PROXY_TOKEN = process.env.ONBOOK_PROXY_TOKEN ?? null;
258
+ var ONBOOK_BROADCAST_URL = process.env.ONBOOK_BROADCAST_URL ?? null;
259
+ var PROGRESS_TIMEOUT_MS = 3e3;
260
+ async function broadcastScreenshotProgress(completed, total) {
261
+ if (!ONBOOK_PROXY_TOKEN || !ONBOOK_BROADCAST_URL) return;
262
+ const percent = total > 0 ? Math.min(100, Math.floor(completed / total * 100)) : null;
263
+ try {
264
+ await fetch(ONBOOK_BROADCAST_URL, {
265
+ method: "POST",
266
+ headers: {
267
+ Authorization: `Bearer ${ONBOOK_PROXY_TOKEN}`,
268
+ "Content-Type": "application/json"
269
+ },
270
+ body: JSON.stringify({
271
+ event: {
272
+ type: "SANDBOX_PHASE_PROGRESS",
273
+ phase: "generating_screenshots",
274
+ completed,
275
+ total,
276
+ percent,
277
+ label: "Capturing stories"
278
+ }
279
+ }),
280
+ signal: AbortSignal.timeout(PROGRESS_TIMEOUT_MS)
281
+ });
282
+ } catch {
283
+ }
284
+ }
195
285
  var StoryTimeoutError = class extends Error {
196
286
  constructor(storyId, timeoutMs) {
197
287
  super(`Story ${storyId} timed out after ${timeoutMs / 1e3}s`);
@@ -231,6 +321,7 @@ async function generateAllScreenshots(stories, storybookUrl = "http://localhost:
231
321
  }
232
322
  const storyTimeout = timeoutMs * 2 + 1e4;
233
323
  let completed = 0;
324
+ void broadcastScreenshotProgress(0, displayTotal);
234
325
  for (const batch of batches) {
235
326
  await Promise.all(
236
327
  batch.map(async (story) => {
@@ -300,8 +391,10 @@ async function generateAllScreenshots(stories, storybookUrl = "http://localhost:
300
391
  }
301
392
  })
302
393
  );
394
+ void broadcastScreenshotProgress(offset + completed, displayTotal);
303
395
  }
304
396
  await closeBrowser();
397
+ void broadcastScreenshotProgress(displayTotal, displayTotal);
305
398
  console.log("Screenshot generation complete!");
306
399
  }
307
400
  var LOCKFILES = {
@@ -452,8 +545,8 @@ async function startStorybook(command2, args, storybookDir) {
452
545
  }
453
546
  async function warmupStorybook(url, firstStoryId) {
454
547
  console.log("\u{1F525} Warming up Storybook...");
455
- const browser2 = await getBrowser();
456
- const context = await browser2.newContext();
548
+ const browser = await getBrowser();
549
+ const context = await browser.newContext();
457
550
  const page = await context.newPage();
458
551
  try {
459
552
  const warmupUrl = `${url}/iframe.html?id=${firstStoryId}&viewMode=story`;
@@ -559,5 +652,5 @@ var generateScreenshotsCommand = command({
559
652
  });
560
653
  run([generateScreenshotsCommand], {
561
654
  name: "onlook-storybook",
562
- description: "Storybook plugin for Onbook - generate screenshots and more"
655
+ description: "Storybook plugin for Onlook - generate screenshots and more"
563
656
  });
package/dist/index.d.ts CHANGED
@@ -1,18 +1,97 @@
1
- import { PluginOption } from 'vite';
1
+ export { E as EnvContainmentOptions, a as EnvContainmentSubstitution, O as OnlookPluginOptions, e as envContainmentPlugin, s as storybookOnlookPlugin } from './storybook-onlook-plugin-CigILdDb.js';
2
+ import { Plugin } from 'vite';
2
3
  import { Indexer } from 'storybook/internal/types';
3
4
 
4
- type OnlookPluginOptions = {
5
- /** Storybook port (default: 6006) */
6
- port?: number;
7
- /** Additional allowed origins for CORS (merged with defaults) */
8
- allowedOrigins?: string[];
9
- hmr?: {
10
- protocol?: 'ws' | 'wss';
11
- clientPort?: number;
12
- };
5
+ /** Advertised + embedded in every event payload. Mirrored CLI-side (U8). */
6
+ declare const ONLOOK_CONTAINMENT_CONTRACT_VERSION = 1;
7
+ /**
8
+ * Global registry the env-containment stand-ins populate at evaluation time:
9
+ * `Record<modulePath, ContainedModuleMarker>`.
10
+ */
11
+ declare const ONLOOK_CONTAINED_MODULES_GLOBAL = "__ONLOOK_CONTAINED_MODULES__";
12
+ /** Property carrying the marker on stand-in exports and thrown errors. */
13
+ declare const ONLOOK_CONTAINED_PROP = "__onlookContained";
14
+ /** Marker attached to every contained export, thrown error, and the global registry. */
15
+ type ContainedModuleMarker = {
16
+ modulePath: string;
17
+ reason: 'env-validation';
18
+ envKeys: string[];
19
+ schemaKind?: string;
13
20
  };
14
- declare function storybookOnlookPlugin(options?: OnlookPluginOptions): PluginOption[];
21
+ /**
22
+ * Handshake global, set by the preview annotations (in `beforeAll` and again
23
+ * defensively when the decorator first runs):
24
+ * `globalThis.__ONLOOK_CONTAINMENT_CONTRACT__ = { version: 1 }`.
25
+ */
26
+ declare const ONLOOK_CONTAINMENT_CONTRACT_GLOBAL = "__ONLOOK_CONTAINMENT_CONTRACT__";
27
+ type OnlookContainmentContract = {
28
+ version: number;
29
+ };
30
+ /**
31
+ * Emitted when the error-boundary decorator catches a render-time throw and
32
+ * renders the "needs setup" card in place of the story.
33
+ */
34
+ declare const ONLOOK_CONTAINED_EVENT = "onlook/contained";
35
+ /**
36
+ * Emitted once per story mount, after the boundary's children committed
37
+ * without throwing. The verifier must key `rendered` on THIS (plus absence of
38
+ * Storybook error events) — never on `STORY_RENDERED` alone, which Storybook
39
+ * fires even after its own boundary catches.
40
+ */
41
+ declare const ONLOOK_RENDERED_EVENT = "onlook/rendered";
42
+ /**
43
+ * Tag-for-verifier re-emit of errors the boundary cannot catch (loader /
44
+ * `beforeEach` / module-load throws surface as Storybook's
45
+ * `storyThrewException` / `storyErrored` channel events, outside the React
46
+ * render). These remain overlay-not-card; no DOM repaint is attempted.
47
+ */
48
+ declare const ONLOOK_UNCONTAINED_ERROR_EVENT = "onlook/uncontained-error";
49
+ type OnlookContainedPayload = {
50
+ storyId: string;
51
+ contractVersion: number;
52
+ /** Trimmed failure summary (error message). */
53
+ message: string;
54
+ /** Present when the failure is attributed to a contained module (U4). */
55
+ module?: ContainedModuleMarker;
56
+ };
57
+ type OnlookRenderedPayload = {
58
+ storyId: string;
59
+ contractVersion: number;
60
+ };
61
+ type OnlookUncontainedErrorPayload = {
62
+ /** Best-effort (read from the preview iframe URL); absent when unknown. */
63
+ storyId?: string;
64
+ contractVersion: number;
65
+ message: string;
66
+ /** Which Storybook channel event this re-emits. */
67
+ source: 'storyThrewException' | 'storyErrored';
68
+ };
69
+ /**
70
+ * Render-state attribute, value `'rendered' | 'contained'`. Set on
71
+ * `#storybook-root` (falling back to `document.body` when absent) after each
72
+ * boundary commit, and ALSO carried on the card's own wrapper element when
73
+ * contained. Cleared on boundary unmount (story switch), so a stale value
74
+ * never leaks into the next story's mount — verifiers should match it
75
+ * together with `data-onlook-story-id`.
76
+ */
77
+ declare const ONLOOK_RENDER_STATE_ATTR = "data-onlook-render-state";
78
+ /** Companion attribute carrying the story id the render state belongs to. */
79
+ declare const ONLOOK_RENDER_STORY_ATTR = "data-onlook-story-id";
80
+ /** Present (empty value) on the "needs setup" card's wrapper element. */
81
+ declare const ONLOOK_CONTAINED_CARD_ATTR = "data-onlook-contained-card";
82
+ type OnlookRenderState = 'rendered' | 'contained';
83
+
84
+ /** Virtual stories module id as builder-vite declares it (SB_VIRTUAL_FILES). */
85
+ declare const STORYBOOK_VIRTUAL_STORIES_ID = "virtual:/@storybook/builder-vite/storybook-stories.js";
86
+ /** Rollup-resolved form (`getResolvedVirtualModuleId` prepends `\0`). */
87
+ declare const RESOLVED_STORYBOOK_VIRTUAL_STORIES_ID = "\0virtual:/@storybook/builder-vite/storybook-stories.js";
88
+ declare const MODULE_LOAD_CATCH_ALL_PLUGIN_NAME = "onlook-module-load-catch-all";
89
+ /**
90
+ * The Vite plugin. Registered by `storybookOnlookPlugin()` unless the
91
+ * `moduleLoadCatchAll` option is `false`.
92
+ */
93
+ declare function moduleLoadCatchAllPlugin(): Plugin;
15
94
 
16
95
  declare const tolerantCsfIndexer: Indexer;
17
96
 
18
- export { type OnlookPluginOptions, storybookOnlookPlugin, tolerantCsfIndexer };
97
+ export { type ContainedModuleMarker, MODULE_LOAD_CATCH_ALL_PLUGIN_NAME, ONLOOK_CONTAINED_CARD_ATTR, ONLOOK_CONTAINED_EVENT, ONLOOK_CONTAINED_MODULES_GLOBAL, ONLOOK_CONTAINED_PROP, ONLOOK_CONTAINMENT_CONTRACT_GLOBAL, ONLOOK_CONTAINMENT_CONTRACT_VERSION, ONLOOK_RENDERED_EVENT, ONLOOK_RENDER_STATE_ATTR, ONLOOK_RENDER_STORY_ATTR, ONLOOK_UNCONTAINED_ERROR_EVENT, type OnlookContainedPayload, type OnlookContainmentContract, type OnlookRenderState, type OnlookRenderedPayload, type OnlookUncontainedErrorPayload, RESOLVED_STORYBOOK_VIRTUAL_STORIES_ID, STORYBOOK_VIRTUAL_STORIES_ID, moduleLoadCatchAllPlugin, tolerantCsfIndexer };