@onlook/storybook-plugin 0.4.0-beta.5 → 0.4.0-beta.7
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/README.md +1 -1
- package/dist/cli/index.js +83 -33
- package/dist/index.d.ts +8 -14
- package/dist/index.js +97413 -1241
- package/dist/screenshot-service/index.d.ts +0 -6
- package/dist/screenshot-service/index.js +83 -34
- package/dist/screenshot-service/utils/browser/index.js +2 -1
- package/package.json +5 -9
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Configures Vite HMR for E2B sandboxes (WSS protocol, port 443 routing).
|
|
|
44
44
|
|
|
45
45
|
### CORS
|
|
46
46
|
|
|
47
|
-
Pre-configured for `https://app.onlook.
|
|
47
|
+
Pre-configured for `https://app.onlook.com`, `localhost:3000`, and `localhost:6006`.
|
|
48
48
|
|
|
49
49
|
## Options
|
|
50
50
|
|
package/dist/cli/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { command, string, boolean, run } from '@drizzle-team/brocli';
|
|
3
4
|
import { spawn } from 'child_process';
|
|
4
5
|
import fs, { readFileSync, existsSync } from 'fs';
|
|
@@ -6,6 +7,7 @@ import path, { resolve, join, dirname } from 'path';
|
|
|
6
7
|
import crypto from 'crypto';
|
|
7
8
|
import { chromium } from 'playwright';
|
|
8
9
|
|
|
10
|
+
globalThis.require = createRequire(import.meta.url);
|
|
9
11
|
var CACHE_DIR = path.join(process.cwd(), ".storybook-cache");
|
|
10
12
|
var SCREENSHOTS_DIR = path.join(CACHE_DIR, "screenshots");
|
|
11
13
|
var MANIFEST_PATH = path.join(CACHE_DIR, "manifest.json");
|
|
@@ -190,6 +192,33 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
|
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
// src/screenshot-service/screenshot-service.ts
|
|
195
|
+
var StoryTimeoutError = class extends Error {
|
|
196
|
+
constructor(storyId, timeoutMs) {
|
|
197
|
+
super(`Story ${storyId} timed out after ${timeoutMs / 1e3}s`);
|
|
198
|
+
this.storyId = storyId;
|
|
199
|
+
this.timeoutMs = timeoutMs;
|
|
200
|
+
this.name = "StoryTimeoutError";
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
async function captureBothThemes(storyId, storybookUrl, timeoutMs, storyTimeoutMs) {
|
|
204
|
+
let timer;
|
|
205
|
+
try {
|
|
206
|
+
return await Promise.race([
|
|
207
|
+
Promise.all([
|
|
208
|
+
generateScreenshot(storyId, "light", storybookUrl, timeoutMs),
|
|
209
|
+
generateScreenshot(storyId, "dark", storybookUrl, timeoutMs)
|
|
210
|
+
]),
|
|
211
|
+
new Promise((_, reject) => {
|
|
212
|
+
timer = setTimeout(
|
|
213
|
+
() => reject(new StoryTimeoutError(storyId, storyTimeoutMs)),
|
|
214
|
+
storyTimeoutMs
|
|
215
|
+
);
|
|
216
|
+
})
|
|
217
|
+
]);
|
|
218
|
+
} finally {
|
|
219
|
+
if (timer) clearTimeout(timer);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
193
222
|
async function generateAllScreenshots(stories, storybookUrl = "http://localhost:6006", concurrency = 10, offset = 0, total, skipExisting = false, timeoutMs = 3e4) {
|
|
194
223
|
const displayTotal = total ?? offset + stories.length;
|
|
195
224
|
console.log(
|
|
@@ -200,53 +229,74 @@ async function generateAllScreenshots(stories, storybookUrl = "http://localhost:
|
|
|
200
229
|
for (let i = 0; i < stories.length; i += BATCH_SIZE) {
|
|
201
230
|
batches.push(stories.slice(i, i + BATCH_SIZE));
|
|
202
231
|
}
|
|
232
|
+
const storyTimeout = timeoutMs * 2 + 1e4;
|
|
203
233
|
let completed = 0;
|
|
204
234
|
for (const batch of batches) {
|
|
205
235
|
await Promise.all(
|
|
206
236
|
batch.map(async (story) => {
|
|
207
237
|
if (skipExisting && screenshotExists(story.id, "light") && screenshotExists(story.id, "dark")) {
|
|
208
238
|
completed++;
|
|
209
|
-
const
|
|
210
|
-
console.log(`[${
|
|
239
|
+
const absoluteIndex2 = offset + completed;
|
|
240
|
+
console.log(`[${absoluteIndex2}/${displayTotal}] Skipped (exists) ${story.id}`);
|
|
211
241
|
return;
|
|
212
242
|
}
|
|
213
|
-
|
|
214
|
-
let
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
let lightResult = null;
|
|
244
|
+
let darkResult = null;
|
|
245
|
+
let lastError;
|
|
246
|
+
const startedAt = Date.now();
|
|
247
|
+
let attempts = 0;
|
|
248
|
+
const maxAttempts = 2;
|
|
249
|
+
while (attempts < maxAttempts) {
|
|
250
|
+
attempts++;
|
|
251
|
+
try {
|
|
252
|
+
[lightResult, darkResult] = await captureBothThemes(
|
|
253
|
+
story.id,
|
|
254
|
+
storybookUrl,
|
|
255
|
+
timeoutMs,
|
|
256
|
+
storyTimeout
|
|
257
|
+
);
|
|
258
|
+
lastError = void 0;
|
|
259
|
+
break;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
lastError = error;
|
|
262
|
+
if (!(error instanceof StoryTimeoutError) || attempts >= maxAttempts) {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
console.warn(
|
|
266
|
+
`[screenshot] Retrying ${story.id} after timeout (attempt ${attempts + 1}/${maxAttempts})`
|
|
267
|
+
);
|
|
237
268
|
}
|
|
238
|
-
|
|
239
|
-
|
|
269
|
+
}
|
|
270
|
+
completed++;
|
|
271
|
+
const absoluteIndex = offset + completed;
|
|
272
|
+
const durationMs = Date.now() - startedAt;
|
|
273
|
+
if (lightResult && darkResult) {
|
|
274
|
+
const fileHash = computeFileHash(story.importPath);
|
|
275
|
+
updateManifest(story.id, story.importPath, fileHash, lightResult.boundingBox);
|
|
240
276
|
console.log(
|
|
241
|
-
`[${absoluteIndex}/${displayTotal}] Generated screenshots for ${story.id}
|
|
277
|
+
`[${absoluteIndex}/${displayTotal}] Generated screenshots for ${story.id}`,
|
|
278
|
+
{ storyId: story.id, attempts, durationMs, outcome: "ok" }
|
|
242
279
|
);
|
|
243
|
-
}
|
|
244
|
-
completed++;
|
|
245
|
-
const absoluteIndex = offset + completed;
|
|
280
|
+
} else if (lastError) {
|
|
246
281
|
console.error(
|
|
247
282
|
`[${absoluteIndex}/${displayTotal}] \u26A0\uFE0F Failed ${story.id}:`,
|
|
248
|
-
|
|
283
|
+
lastError instanceof Error ? lastError.message : lastError,
|
|
284
|
+
{
|
|
285
|
+
storyId: story.id,
|
|
286
|
+
attempts,
|
|
287
|
+
durationMs,
|
|
288
|
+
outcome: lastError instanceof StoryTimeoutError ? "timeout" : "error"
|
|
289
|
+
}
|
|
249
290
|
);
|
|
291
|
+
} else {
|
|
292
|
+
console.warn(`[${absoluteIndex}/${displayTotal}] Partial result ${story.id}`, {
|
|
293
|
+
storyId: story.id,
|
|
294
|
+
attempts,
|
|
295
|
+
durationMs,
|
|
296
|
+
outcome: "partial",
|
|
297
|
+
hasLight: !!lightResult,
|
|
298
|
+
hasDark: !!darkResult
|
|
299
|
+
});
|
|
250
300
|
}
|
|
251
301
|
})
|
|
252
302
|
);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import { PluginOption } from 'vite';
|
|
2
|
+
import { Indexer } from 'storybook/internal/types';
|
|
2
3
|
|
|
3
|
-
/** Folder name for auto-generated stories (created next to each component) */
|
|
4
|
-
declare const AUTO_STORIES_FOLDER = ".onlook-stories";
|
|
5
4
|
type OnlookPluginOptions = {
|
|
6
5
|
/** Storybook port (default: 6006) */
|
|
7
6
|
port?: number;
|
|
8
7
|
/** Additional allowed origins for CORS (merged with defaults) */
|
|
9
8
|
allowedOrigins?: string[];
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
autoStories?: string[] | false;
|
|
16
|
-
/**
|
|
17
|
-
* Glob patterns to exclude from auto-story generation.
|
|
18
|
-
* (default: stories, tests, and node_modules)
|
|
19
|
-
*/
|
|
20
|
-
autoStoriesIgnore?: string[];
|
|
9
|
+
hmr?: {
|
|
10
|
+
protocol?: 'ws' | 'wss';
|
|
11
|
+
clientPort?: number;
|
|
12
|
+
};
|
|
21
13
|
};
|
|
22
14
|
declare function storybookOnlookPlugin(options?: OnlookPluginOptions): PluginOption[];
|
|
23
15
|
|
|
24
|
-
|
|
16
|
+
declare const tolerantCsfIndexer: Indexer;
|
|
17
|
+
|
|
18
|
+
export { type OnlookPluginOptions, storybookOnlookPlugin, tolerantCsfIndexer };
|