@onlook/storybook-plugin 0.4.0-beta.15 → 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 +619 -191
- package/dist/preset/index.d.ts +1 -1
- package/dist/preset/index.js +620 -191
- 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 +2 -1
package/dist/preset/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import crypto, { createHash } from 'crypto';
|
|
3
3
|
import fs5, { existsSync } from 'fs';
|
|
4
|
-
import
|
|
4
|
+
import path8, { dirname, join, relative } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import generateModule from '@babel/generator';
|
|
7
7
|
import { parse } from '@babel/parser';
|
|
@@ -45,7 +45,7 @@ function componentLocPlugin(options = {}) {
|
|
|
45
45
|
sourceFilename: filepath
|
|
46
46
|
});
|
|
47
47
|
let mutated = false;
|
|
48
|
-
const relativePath =
|
|
48
|
+
const relativePath = path8.relative(root, filepath);
|
|
49
49
|
traverse(ast, {
|
|
50
50
|
JSXElement(nodePath) {
|
|
51
51
|
const opening = nodePath.node.openingElement;
|
|
@@ -117,7 +117,7 @@ function envContainmentPlugin(options) {
|
|
|
117
117
|
const resolveOutcomes = /* @__PURE__ */ new Map();
|
|
118
118
|
const roots = /* @__PURE__ */ new Set([normalizeSlashes(process.cwd())]);
|
|
119
119
|
const matchTarget = (absPath) => {
|
|
120
|
-
const norm = normalizeSlashes(
|
|
120
|
+
const norm = normalizeSlashes(path8.normalize(absPath));
|
|
121
121
|
for (const rel of byModule.keys()) {
|
|
122
122
|
for (const root of roots) {
|
|
123
123
|
const target = `${root}/${rel}`;
|
|
@@ -152,10 +152,10 @@ function envContainmentPlugin(options) {
|
|
|
152
152
|
if (source.startsWith("\0")) return null;
|
|
153
153
|
const cleaned = stripQuery(source);
|
|
154
154
|
let direct = null;
|
|
155
|
-
if (
|
|
155
|
+
if (path8.isAbsolute(cleaned)) {
|
|
156
156
|
direct = cleaned;
|
|
157
157
|
} else if (importer && (cleaned.startsWith("./") || cleaned.startsWith("../"))) {
|
|
158
|
-
direct =
|
|
158
|
+
direct = path8.resolve(path8.dirname(stripQuery(importer)), cleaned);
|
|
159
159
|
}
|
|
160
160
|
if (direct) {
|
|
161
161
|
const rel = matchTarget(direct);
|
|
@@ -164,7 +164,7 @@ function envContainmentPlugin(options) {
|
|
|
164
164
|
}
|
|
165
165
|
if (!specifierMightMatch(cleaned)) return null;
|
|
166
166
|
if (typeof this.resolve !== "function") return null;
|
|
167
|
-
const importerDir = importer ?
|
|
167
|
+
const importerDir = importer ? path8.dirname(stripQuery(importer)) : "";
|
|
168
168
|
const outcomeKey = `${importerDir}|${source}`;
|
|
169
169
|
const memoized = resolveOutcomes.get(outcomeKey);
|
|
170
170
|
if (memoized !== void 0) return memoized;
|
|
@@ -267,9 +267,9 @@ function fastRefreshTolerantExportsPlugin() {
|
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
|
-
var CACHE_DIR =
|
|
271
|
-
var SCREENSHOTS_DIR =
|
|
272
|
-
var MANIFEST_PATH =
|
|
270
|
+
var CACHE_DIR = path8.join(process.cwd(), ".storybook-cache");
|
|
271
|
+
var SCREENSHOTS_DIR = path8.join(CACHE_DIR, "screenshots");
|
|
272
|
+
var MANIFEST_PATH = path8.join(CACHE_DIR, "manifest.json");
|
|
273
273
|
var VIEWPORT_WIDTH = 1920;
|
|
274
274
|
var VIEWPORT_HEIGHT = 1080;
|
|
275
275
|
var MIN_COMPONENT_WIDTH = 420;
|
|
@@ -394,6 +394,52 @@ async function getBrowser() {
|
|
|
394
394
|
}
|
|
395
395
|
return browserPromise;
|
|
396
396
|
}
|
|
397
|
+
|
|
398
|
+
// src/screenshot-service/utils/capture-semaphore/capture-semaphore.ts
|
|
399
|
+
function createSemaphore(limit) {
|
|
400
|
+
const max = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 1;
|
|
401
|
+
let active = 0;
|
|
402
|
+
const waiters = [];
|
|
403
|
+
const acquire = () => {
|
|
404
|
+
if (active < max) {
|
|
405
|
+
active += 1;
|
|
406
|
+
return Promise.resolve();
|
|
407
|
+
}
|
|
408
|
+
return new Promise((resolve) => {
|
|
409
|
+
waiters.push(() => {
|
|
410
|
+
active += 1;
|
|
411
|
+
resolve();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
const release = () => {
|
|
416
|
+
active -= 1;
|
|
417
|
+
const next = waiters.shift();
|
|
418
|
+
if (next) next();
|
|
419
|
+
};
|
|
420
|
+
return {
|
|
421
|
+
async run(fn) {
|
|
422
|
+
await acquire();
|
|
423
|
+
try {
|
|
424
|
+
return await fn();
|
|
425
|
+
} finally {
|
|
426
|
+
release();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
var DEFAULT_CAPTURE_CONCURRENCY = 3;
|
|
432
|
+
function resolveConcurrency(raw) {
|
|
433
|
+
if (!raw) return DEFAULT_CAPTURE_CONCURRENCY;
|
|
434
|
+
const parsed = Number.parseInt(raw, 10);
|
|
435
|
+
if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_CAPTURE_CONCURRENCY;
|
|
436
|
+
return parsed;
|
|
437
|
+
}
|
|
438
|
+
var captureSemaphore = createSemaphore(
|
|
439
|
+
resolveConcurrency(process.env.ONLOOK_CAPTURE_CONCURRENCY)
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
// src/screenshot-service/utils/screenshot/screenshot.ts
|
|
397
443
|
function classifyPreviewSnapshot(s) {
|
|
398
444
|
if (s.text.includes("Sorry, something went wrong")) return "errored";
|
|
399
445
|
if (s.text.includes("Couldn't find story")) return "errored";
|
|
@@ -460,96 +506,98 @@ async function detectRenderState(page, opts = {}) {
|
|
|
460
506
|
}
|
|
461
507
|
}
|
|
462
508
|
function getScreenshotPath(storyId, theme) {
|
|
463
|
-
const storyDir =
|
|
464
|
-
return
|
|
509
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
510
|
+
return path8.join(storyDir, `${theme}.png`);
|
|
465
511
|
}
|
|
466
512
|
async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const page = await context.newPage();
|
|
473
|
-
try {
|
|
474
|
-
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
475
|
-
await page.goto(url, { timeout: loadTimeoutMs });
|
|
476
|
-
await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
|
|
477
|
-
await page.waitForLoadState("load", { timeout: loadTimeoutMs });
|
|
478
|
-
try {
|
|
479
|
-
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
480
|
-
} catch {
|
|
481
|
-
}
|
|
482
|
-
const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
|
|
483
|
-
await page.evaluate(() => document.fonts.ready);
|
|
484
|
-
try {
|
|
485
|
-
await page.evaluate(async () => {
|
|
486
|
-
const images = document.querySelectorAll("img");
|
|
487
|
-
await Promise.all(
|
|
488
|
-
Array.from(images).map((img) => {
|
|
489
|
-
if (img.complete) return Promise.resolve();
|
|
490
|
-
return new Promise((resolve) => {
|
|
491
|
-
img.addEventListener("load", resolve);
|
|
492
|
-
img.addEventListener("error", resolve);
|
|
493
|
-
setTimeout(resolve, 3e3);
|
|
494
|
-
});
|
|
495
|
-
})
|
|
496
|
-
);
|
|
497
|
-
});
|
|
498
|
-
} catch {
|
|
499
|
-
}
|
|
500
|
-
const contentBounds = await page.evaluate(() => {
|
|
501
|
-
const root = document.querySelector("#storybook-root");
|
|
502
|
-
if (!root) return null;
|
|
503
|
-
const children = root.querySelectorAll("*");
|
|
504
|
-
if (children.length === 0) return null;
|
|
505
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
506
|
-
children.forEach((child) => {
|
|
507
|
-
const rect = child.getBoundingClientRect();
|
|
508
|
-
if (rect.width === 0 || rect.height === 0) return;
|
|
509
|
-
minX = Math.min(minX, rect.left);
|
|
510
|
-
minY = Math.min(minY, rect.top);
|
|
511
|
-
maxX = Math.max(maxX, rect.right);
|
|
512
|
-
maxY = Math.max(maxY, rect.bottom);
|
|
513
|
-
});
|
|
514
|
-
if (minX === Infinity) return null;
|
|
515
|
-
return {
|
|
516
|
-
x: minX,
|
|
517
|
-
y: minY,
|
|
518
|
-
width: maxX - minX,
|
|
519
|
-
height: maxY - minY
|
|
520
|
-
};
|
|
513
|
+
return captureSemaphore.run(async () => {
|
|
514
|
+
const browser = await getBrowser();
|
|
515
|
+
const context = await browser.newContext({
|
|
516
|
+
viewport: { width, height },
|
|
517
|
+
deviceScaleFactor: 2
|
|
521
518
|
});
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
519
|
+
const page = await context.newPage();
|
|
520
|
+
try {
|
|
521
|
+
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
522
|
+
await page.goto(url, { timeout: loadTimeoutMs });
|
|
523
|
+
await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
|
|
524
|
+
await page.waitForLoadState("load", { timeout: loadTimeoutMs });
|
|
525
|
+
try {
|
|
526
|
+
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
|
|
530
|
+
await page.evaluate(() => document.fonts.ready);
|
|
531
|
+
try {
|
|
532
|
+
await page.evaluate(async () => {
|
|
533
|
+
const images = document.querySelectorAll("img");
|
|
534
|
+
await Promise.all(
|
|
535
|
+
Array.from(images).map((img) => {
|
|
536
|
+
if (img.complete) return Promise.resolve();
|
|
537
|
+
return new Promise((resolve) => {
|
|
538
|
+
img.addEventListener("load", resolve);
|
|
539
|
+
img.addEventListener("error", resolve);
|
|
540
|
+
setTimeout(resolve, 3e3);
|
|
541
|
+
});
|
|
542
|
+
})
|
|
543
|
+
);
|
|
544
|
+
});
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
const contentBounds = await page.evaluate(() => {
|
|
548
|
+
const root = document.querySelector("#storybook-root");
|
|
549
|
+
if (!root) return null;
|
|
550
|
+
const children = root.querySelectorAll("*");
|
|
551
|
+
if (children.length === 0) return null;
|
|
552
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
553
|
+
children.forEach((child) => {
|
|
554
|
+
const rect = child.getBoundingClientRect();
|
|
555
|
+
if (rect.width === 0 || rect.height === 0) return;
|
|
556
|
+
minX = Math.min(minX, rect.left);
|
|
557
|
+
minY = Math.min(minY, rect.top);
|
|
558
|
+
maxX = Math.max(maxX, rect.right);
|
|
559
|
+
maxY = Math.max(maxY, rect.bottom);
|
|
560
|
+
});
|
|
561
|
+
if (minX === Infinity) return null;
|
|
562
|
+
return {
|
|
563
|
+
x: minX,
|
|
564
|
+
y: minY,
|
|
565
|
+
width: maxX - minX,
|
|
566
|
+
height: maxY - minY
|
|
567
|
+
};
|
|
540
568
|
});
|
|
541
|
-
|
|
542
|
-
|
|
569
|
+
let screenshotBuffer;
|
|
570
|
+
let resultBoundingBox = null;
|
|
571
|
+
if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
|
|
572
|
+
const PADDING = 20;
|
|
573
|
+
const clippedWidth = Math.min(width, contentBounds.width + PADDING);
|
|
574
|
+
const clippedHeight = Math.min(height, contentBounds.height + PADDING);
|
|
575
|
+
resultBoundingBox = {
|
|
576
|
+
width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
|
|
577
|
+
height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight))
|
|
578
|
+
};
|
|
579
|
+
screenshotBuffer = await page.screenshot({
|
|
580
|
+
type: "png",
|
|
581
|
+
clip: {
|
|
582
|
+
x: Math.max(0, contentBounds.x - 10),
|
|
583
|
+
y: Math.max(0, contentBounds.y - 10),
|
|
584
|
+
width: clippedWidth,
|
|
585
|
+
height: clippedHeight
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
} else {
|
|
589
|
+
screenshotBuffer = await page.screenshot({ type: "png" });
|
|
590
|
+
}
|
|
591
|
+
return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
|
|
592
|
+
} finally {
|
|
593
|
+
await context.close();
|
|
543
594
|
}
|
|
544
|
-
|
|
545
|
-
} finally {
|
|
546
|
-
await context.close();
|
|
547
|
-
}
|
|
595
|
+
});
|
|
548
596
|
}
|
|
549
597
|
async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
550
598
|
try {
|
|
551
599
|
ensureCacheDirectories();
|
|
552
|
-
const storyDir =
|
|
600
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
553
601
|
if (!fs5.existsSync(storyDir)) {
|
|
554
602
|
fs5.mkdirSync(storyDir, { recursive: true });
|
|
555
603
|
}
|
|
@@ -651,7 +699,39 @@ async function waitForIndexAndBroadcast(port = 6006) {
|
|
|
651
699
|
});
|
|
652
700
|
}
|
|
653
701
|
|
|
702
|
+
// src/handlers/handleStoryFileChange/captureCoalescer/captureCoalescer.ts
|
|
703
|
+
function createCaptureCoalescer(runCapture) {
|
|
704
|
+
const inFlight3 = /* @__PURE__ */ new Map();
|
|
705
|
+
const trailing2 = /* @__PURE__ */ new Set();
|
|
706
|
+
const start = (storyId) => {
|
|
707
|
+
const done = (async () => {
|
|
708
|
+
try {
|
|
709
|
+
await runCapture(storyId);
|
|
710
|
+
} finally {
|
|
711
|
+
inFlight3.delete(storyId);
|
|
712
|
+
if (trailing2.has(storyId)) {
|
|
713
|
+
trailing2.delete(storyId);
|
|
714
|
+
await start(storyId);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
})();
|
|
718
|
+
inFlight3.set(storyId, done);
|
|
719
|
+
return done;
|
|
720
|
+
};
|
|
721
|
+
return {
|
|
722
|
+
capture(storyId) {
|
|
723
|
+
const existing = inFlight3.get(storyId);
|
|
724
|
+
if (existing) {
|
|
725
|
+
trailing2.add(storyId);
|
|
726
|
+
return existing;
|
|
727
|
+
}
|
|
728
|
+
return start(storyId);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
654
733
|
// src/handlers/handleStoryFileChange/handleStoryFileChange.ts
|
|
734
|
+
var STORYBOOK_URL = "http://localhost:6006";
|
|
655
735
|
var cachedIndex = null;
|
|
656
736
|
var indexFetchPromise = null;
|
|
657
737
|
var pendingFiles = /* @__PURE__ */ new Set();
|
|
@@ -670,47 +750,46 @@ async function fetchStorybookIndex() {
|
|
|
670
750
|
}
|
|
671
751
|
function getStoriesForFile(filePath) {
|
|
672
752
|
if (!cachedIndex) return [];
|
|
673
|
-
const fileName =
|
|
753
|
+
const fileName = path8.basename(filePath);
|
|
674
754
|
return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
|
|
675
755
|
}
|
|
756
|
+
var latestStoryFiles = /* @__PURE__ */ new Map();
|
|
757
|
+
async function captureStoryAndUpdateManifest(storyId) {
|
|
758
|
+
const [lightResult] = await Promise.all([
|
|
759
|
+
generateScreenshot(storyId, "light", STORYBOOK_URL),
|
|
760
|
+
generateScreenshot(storyId, "dark", STORYBOOK_URL)
|
|
761
|
+
]);
|
|
762
|
+
const boundingBox = lightResult?.boundingBox ?? void 0;
|
|
763
|
+
for (const file of latestStoryFiles.get(storyId) ?? []) {
|
|
764
|
+
const fileHash = computeFileHash(file);
|
|
765
|
+
updateManifest(storyId, file, fileHash, boundingBox);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
var captureCoalescer = createCaptureCoalescer(captureStoryAndUpdateManifest);
|
|
676
769
|
async function regenerateScreenshotsForFiles(files) {
|
|
677
770
|
await fetchStorybookIndex();
|
|
678
|
-
const
|
|
679
|
-
const fileToStories = /* @__PURE__ */ new Map();
|
|
771
|
+
const storyToFiles = /* @__PURE__ */ new Map();
|
|
680
772
|
for (const file of files) {
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
773
|
+
for (const storyId of getStoriesForFile(file)) {
|
|
774
|
+
const arr = storyToFiles.get(storyId) ?? [];
|
|
775
|
+
arr.push(file);
|
|
776
|
+
storyToFiles.set(storyId, arr);
|
|
685
777
|
}
|
|
686
778
|
}
|
|
687
|
-
if (
|
|
779
|
+
if (storyToFiles.size === 0) {
|
|
688
780
|
console.log("[Screenshots] No stories found for changed files");
|
|
689
781
|
return;
|
|
690
782
|
}
|
|
691
783
|
console.log(
|
|
692
|
-
`[Screenshots] Regenerating ${
|
|
784
|
+
`[Screenshots] Regenerating ${storyToFiles.size} stories from ${files.length} files`
|
|
693
785
|
);
|
|
694
|
-
const
|
|
695
|
-
|
|
786
|
+
for (const [storyId, storyFiles] of storyToFiles) {
|
|
787
|
+
latestStoryFiles.set(storyId, storyFiles);
|
|
788
|
+
}
|
|
696
789
|
await Promise.all(
|
|
697
|
-
Array.from(
|
|
698
|
-
const [lightResult, _darkResult] = await Promise.all([
|
|
699
|
-
generateScreenshot(storyId, "light", storybookUrl),
|
|
700
|
-
generateScreenshot(storyId, "dark", storybookUrl)
|
|
701
|
-
]);
|
|
702
|
-
if (lightResult) {
|
|
703
|
-
storyBoundingBoxes.set(storyId, lightResult.boundingBox);
|
|
704
|
-
}
|
|
705
|
-
})
|
|
790
|
+
Array.from(storyToFiles.keys()).map((storyId) => captureCoalescer.capture(storyId))
|
|
706
791
|
);
|
|
707
|
-
|
|
708
|
-
const fileHash = computeFileHash(file);
|
|
709
|
-
storyIds.forEach((storyId) => {
|
|
710
|
-
updateManifest(storyId, file, fileHash, storyBoundingBoxes.get(storyId));
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
console.log(`[Screenshots] \u2713 Regenerated ${allStoryIds.size} stories`);
|
|
792
|
+
console.log(`[Screenshots] \u2713 Regenerated ${storyToFiles.size} stories`);
|
|
714
793
|
}
|
|
715
794
|
function handleStoryFileChange({ file, modules }) {
|
|
716
795
|
if (file.endsWith(".stories.tsx") || file.endsWith(".stories.ts")) {
|
|
@@ -853,63 +932,258 @@ function moduleLoadCatchAllPlugin() {
|
|
|
853
932
|
};
|
|
854
933
|
}
|
|
855
934
|
|
|
935
|
+
// src/package-mock/clerk-mock.ts
|
|
936
|
+
var VALID_IDENT_RE2 = /^[A-Za-z_$][\w$]*$/;
|
|
937
|
+
var RESERVED_HELPER_NAMES = /* @__PURE__ */ new Set([
|
|
938
|
+
"__user",
|
|
939
|
+
"__pass",
|
|
940
|
+
"__null",
|
|
941
|
+
"__clerk",
|
|
942
|
+
"__sessionId",
|
|
943
|
+
"__asyncNoop",
|
|
944
|
+
"__noop",
|
|
945
|
+
"createElement",
|
|
946
|
+
"Fragment"
|
|
947
|
+
]);
|
|
948
|
+
var BUILDERS = {
|
|
949
|
+
"@clerk/nextjs": buildClerkMockSource,
|
|
950
|
+
"@clerk/clerk-react": buildClerkMockSource
|
|
951
|
+
};
|
|
952
|
+
function isKnownMockPackage(pkg) {
|
|
953
|
+
return pkg in BUILDERS;
|
|
954
|
+
}
|
|
955
|
+
function buildPackageMockSource(pkg, usedExports) {
|
|
956
|
+
const build = BUILDERS[pkg];
|
|
957
|
+
if (!build) throw new Error(`no package mock for '${pkg}'`);
|
|
958
|
+
return build(usedExports);
|
|
959
|
+
}
|
|
960
|
+
var CLERK_KNOWN_HOOKS = {
|
|
961
|
+
useAuth: "({ isLoaded: true, isSignedIn: true, userId: __user.id, sessionId: __sessionId, actor: null, orgId: null, orgRole: null, orgSlug: null, has: () => true, signOut: __asyncNoop, getToken: async () => null })",
|
|
962
|
+
useUser: "({ isLoaded: true, isSignedIn: true, user: __user })",
|
|
963
|
+
useSession: '({ isLoaded: true, isSignedIn: true, session: { id: __sessionId, status: "active", user: __user } })',
|
|
964
|
+
useSessionList: "({ isLoaded: true, sessions: [], setActive: __asyncNoop })",
|
|
965
|
+
useClerk: "__clerk",
|
|
966
|
+
useOrganization: "({ isLoaded: true, organization: null, membership: null, memberships: undefined })",
|
|
967
|
+
useOrganizationList: "({ isLoaded: true, userMemberships: undefined, setActive: __asyncNoop })",
|
|
968
|
+
useSignIn: "({ isLoaded: true, signIn: undefined, setActive: __asyncNoop })",
|
|
969
|
+
useSignUp: "({ isLoaded: true, signUp: undefined, setActive: __asyncNoop })"
|
|
970
|
+
};
|
|
971
|
+
var CLERK_CHILDREN_COMPONENTS = /* @__PURE__ */ new Set([
|
|
972
|
+
"ClerkProvider",
|
|
973
|
+
"ClerkLoaded",
|
|
974
|
+
"SignedIn",
|
|
975
|
+
"Protect",
|
|
976
|
+
"SignInButton",
|
|
977
|
+
"SignUpButton",
|
|
978
|
+
"SignOutButton",
|
|
979
|
+
"SignInWithMetamaskButton"
|
|
980
|
+
]);
|
|
981
|
+
var CLERK_NULL_COMPONENTS = /* @__PURE__ */ new Set([
|
|
982
|
+
"SignedOut",
|
|
983
|
+
"ClerkLoading",
|
|
984
|
+
"RedirectToSignIn",
|
|
985
|
+
"RedirectToSignUp",
|
|
986
|
+
"RedirectToUserProfile",
|
|
987
|
+
"RedirectToOrganizationProfile",
|
|
988
|
+
"RedirectToCreateOrganization",
|
|
989
|
+
"AuthenticateWithRedirectCallback",
|
|
990
|
+
"UserButton",
|
|
991
|
+
"UserProfile",
|
|
992
|
+
"OrganizationSwitcher",
|
|
993
|
+
"OrganizationProfile",
|
|
994
|
+
"OrganizationList",
|
|
995
|
+
"CreateOrganization",
|
|
996
|
+
"SignIn",
|
|
997
|
+
"SignUp",
|
|
998
|
+
"Waitlist",
|
|
999
|
+
"GoogleOneTap"
|
|
1000
|
+
]);
|
|
1001
|
+
var CLERK_PREAMBLE = [
|
|
1002
|
+
"// Auto-generated by @onlook/storybook-plugin (package mock). Zero-backend,",
|
|
1003
|
+
"// signed-in Clerk stand-in so components that call useUser()/useAuth() render",
|
|
1004
|
+
"// authenticated content in the canvas without a publishable key, clerk-js, or",
|
|
1005
|
+
"// a network round-trip. Swap back to the real @clerk/* package for live data.",
|
|
1006
|
+
"import { createElement, Fragment } from 'react';",
|
|
1007
|
+
"const __asyncNoop = async () => {};",
|
|
1008
|
+
"const __noop = () => {};",
|
|
1009
|
+
// No `: any` param annotation: the virtual module is served extensionless
|
|
1010
|
+
// (see header) so Vite parses it as PLAIN JS — a TS type annotation here is a
|
|
1011
|
+
// runtime `Unexpected token ':'` that breaks every story importing the package.
|
|
1012
|
+
"const __pass = (props) => createElement(Fragment, null, props ? props.children : undefined);",
|
|
1013
|
+
"const __null = () => null;",
|
|
1014
|
+
"const __sessionId = 'sess_onlookMock';",
|
|
1015
|
+
"// A plausible signed-in Clerk user. Fields cover the common UserResource",
|
|
1016
|
+
"// surface components read at render; no image URL (avoids a network fetch).",
|
|
1017
|
+
"const __user = {",
|
|
1018
|
+
" id: 'user_onlookMock',",
|
|
1019
|
+
" firstName: 'Ada',",
|
|
1020
|
+
" lastName: 'Lovelace',",
|
|
1021
|
+
" fullName: 'Ada Lovelace',",
|
|
1022
|
+
" username: 'ada',",
|
|
1023
|
+
" imageUrl: '',",
|
|
1024
|
+
" hasImage: false,",
|
|
1025
|
+
" primaryEmailAddressId: 'idn_onlookMock',",
|
|
1026
|
+
" primaryEmailAddress: { id: 'idn_onlookMock', emailAddress: 'ada@example.com' },",
|
|
1027
|
+
" emailAddresses: [{ id: 'idn_onlookMock', emailAddress: 'ada@example.com' }],",
|
|
1028
|
+
" primaryPhoneNumber: null,",
|
|
1029
|
+
" phoneNumbers: [],",
|
|
1030
|
+
" externalAccounts: [],",
|
|
1031
|
+
" organizationMemberships: [],",
|
|
1032
|
+
" publicMetadata: {},",
|
|
1033
|
+
" unsafeMetadata: {},",
|
|
1034
|
+
" reload: __asyncNoop,",
|
|
1035
|
+
" update: __asyncNoop,",
|
|
1036
|
+
"};",
|
|
1037
|
+
"const __clerk = {",
|
|
1038
|
+
" loaded: true,",
|
|
1039
|
+
" user: __user,",
|
|
1040
|
+
" session: { id: __sessionId, status: 'active', user: __user },",
|
|
1041
|
+
" organization: null,",
|
|
1042
|
+
" openSignIn: __noop,",
|
|
1043
|
+
" openSignUp: __noop,",
|
|
1044
|
+
" openUserProfile: __noop,",
|
|
1045
|
+
" openOrganizationProfile: __noop,",
|
|
1046
|
+
" closeSignIn: __noop,",
|
|
1047
|
+
" closeSignUp: __noop,",
|
|
1048
|
+
" signOut: __asyncNoop,",
|
|
1049
|
+
" setActive: __asyncNoop,",
|
|
1050
|
+
"};"
|
|
1051
|
+
];
|
|
1052
|
+
function clerkExportLine(name) {
|
|
1053
|
+
if (name in CLERK_KNOWN_HOOKS) {
|
|
1054
|
+
return `export const ${name} = () => ${CLERK_KNOWN_HOOKS[name]};`;
|
|
1055
|
+
}
|
|
1056
|
+
if (CLERK_CHILDREN_COMPONENTS.has(name)) {
|
|
1057
|
+
return `export const ${name} = __pass;`;
|
|
1058
|
+
}
|
|
1059
|
+
if (CLERK_NULL_COMPONENTS.has(name)) {
|
|
1060
|
+
return `export const ${name} = __null;`;
|
|
1061
|
+
}
|
|
1062
|
+
if (/^use[A-Z]/.test(name)) {
|
|
1063
|
+
return `export const ${name} = () => ({ isLoaded: true, isSignedIn: true });`;
|
|
1064
|
+
}
|
|
1065
|
+
if (/^[A-Z]/.test(name)) {
|
|
1066
|
+
return `export const ${name} = __pass;`;
|
|
1067
|
+
}
|
|
1068
|
+
return `export const ${name} = undefined;`;
|
|
1069
|
+
}
|
|
1070
|
+
function buildClerkMockSource(usedExports) {
|
|
1071
|
+
const names = Array.from(
|
|
1072
|
+
new Set(
|
|
1073
|
+
usedExports.filter(
|
|
1074
|
+
(n) => VALID_IDENT_RE2.test(n) && n !== "default" && !RESERVED_HELPER_NAMES.has(n)
|
|
1075
|
+
)
|
|
1076
|
+
)
|
|
1077
|
+
).sort();
|
|
1078
|
+
const lines = [
|
|
1079
|
+
...CLERK_PREAMBLE,
|
|
1080
|
+
"",
|
|
1081
|
+
...names.map(clerkExportLine),
|
|
1082
|
+
"",
|
|
1083
|
+
"// Rarely used, but `import Clerk from ...` must not fail: an inert object.",
|
|
1084
|
+
"export default {};",
|
|
1085
|
+
""
|
|
1086
|
+
];
|
|
1087
|
+
return lines.join("\n");
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/package-mock/package-mock.ts
|
|
1091
|
+
var PACKAGE_MOCK_PLUGIN_NAME = "onlook-package-mock";
|
|
1092
|
+
var PACKAGE_MOCK_VIRTUAL_PREFIX = "\0onlook-package-mock:";
|
|
1093
|
+
function stripQuery2(id) {
|
|
1094
|
+
const q = id.indexOf("?");
|
|
1095
|
+
return q === -1 ? id : id.slice(0, q);
|
|
1096
|
+
}
|
|
1097
|
+
function packageMockPlugin(options) {
|
|
1098
|
+
const entries = (options?.packages ?? []).filter(
|
|
1099
|
+
(e) => e && typeof e.package === "string" && e.package.length > 0 && // Only known auth packages get a functional mock — an unknown package
|
|
1100
|
+
// has no source builder, so silently drop it (honest skip) rather than
|
|
1101
|
+
// intercept it into a broken module.
|
|
1102
|
+
isKnownMockPackage(e.package)
|
|
1103
|
+
);
|
|
1104
|
+
if (entries.length === 0) return null;
|
|
1105
|
+
const usedByPackage = /* @__PURE__ */ new Map();
|
|
1106
|
+
for (const e of entries) usedByPackage.set(e.package, e.usedExports ?? []);
|
|
1107
|
+
return {
|
|
1108
|
+
name: PACKAGE_MOCK_PLUGIN_NAME,
|
|
1109
|
+
// `pre` so we intercept the specifier before Vite's resolve pipeline (and
|
|
1110
|
+
// before tsconfig-paths / node resolution) claims it.
|
|
1111
|
+
enforce: "pre",
|
|
1112
|
+
resolveId(source) {
|
|
1113
|
+
if (source.startsWith("\0")) return null;
|
|
1114
|
+
const cleaned = stripQuery2(source);
|
|
1115
|
+
if (!usedByPackage.has(cleaned)) return null;
|
|
1116
|
+
return PACKAGE_MOCK_VIRTUAL_PREFIX + cleaned;
|
|
1117
|
+
},
|
|
1118
|
+
load(id) {
|
|
1119
|
+
if (!id.startsWith(PACKAGE_MOCK_VIRTUAL_PREFIX)) return null;
|
|
1120
|
+
const pkg = id.slice(PACKAGE_MOCK_VIRTUAL_PREFIX.length);
|
|
1121
|
+
const used = usedByPackage.get(pkg);
|
|
1122
|
+
if (used === void 0) return null;
|
|
1123
|
+
return buildPackageMockSource(pkg, used);
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
|
|
856
1128
|
// src/screenshot-service/utils/console-logs/console-logs.ts
|
|
857
1129
|
async function captureConsoleLogs(storyId, theme = "light", storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const page = await context.newPage();
|
|
863
|
-
const logs = [];
|
|
864
|
-
const pageErrors = [];
|
|
865
|
-
const failedRequests = [];
|
|
866
|
-
page.on("console", (msg) => {
|
|
867
|
-
const level = msg.type();
|
|
868
|
-
logs.push({
|
|
869
|
-
level: level === "warning" ? "warn" : level,
|
|
870
|
-
text: msg.text(),
|
|
871
|
-
timestamp: Date.now()
|
|
1130
|
+
return captureSemaphore.run(async () => {
|
|
1131
|
+
const browser = await getBrowser();
|
|
1132
|
+
const context = await browser.newContext({
|
|
1133
|
+
viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
|
|
872
1134
|
});
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
pageErrors
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1135
|
+
const page = await context.newPage();
|
|
1136
|
+
const logs = [];
|
|
1137
|
+
const pageErrors = [];
|
|
1138
|
+
const failedRequests = [];
|
|
1139
|
+
page.on("console", (msg) => {
|
|
1140
|
+
const level = msg.type();
|
|
1141
|
+
logs.push({
|
|
1142
|
+
level: level === "warning" ? "warn" : level,
|
|
1143
|
+
text: msg.text(),
|
|
1144
|
+
timestamp: Date.now()
|
|
1145
|
+
});
|
|
1146
|
+
});
|
|
1147
|
+
page.on("pageerror", (err) => {
|
|
1148
|
+
pageErrors.push(err.message);
|
|
1149
|
+
});
|
|
1150
|
+
page.on("response", (res) => {
|
|
1151
|
+
const status = res.status();
|
|
1152
|
+
if (status >= 400) {
|
|
1153
|
+
const req = res.request();
|
|
1154
|
+
failedRequests.push({
|
|
1155
|
+
url: res.url(),
|
|
1156
|
+
status,
|
|
1157
|
+
method: req.method(),
|
|
1158
|
+
resourceType: req.resourceType(),
|
|
1159
|
+
failure: null
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
page.on("requestfailed", (req) => {
|
|
881
1164
|
failedRequests.push({
|
|
882
|
-
url:
|
|
883
|
-
status,
|
|
1165
|
+
url: req.url(),
|
|
1166
|
+
status: null,
|
|
884
1167
|
method: req.method(),
|
|
885
1168
|
resourceType: req.resourceType(),
|
|
886
|
-
failure:
|
|
1169
|
+
failure: req.failure()?.errorText ?? "request failed"
|
|
887
1170
|
});
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
page.on("requestfailed", (req) => {
|
|
891
|
-
failedRequests.push({
|
|
892
|
-
url: req.url(),
|
|
893
|
-
status: null,
|
|
894
|
-
method: req.method(),
|
|
895
|
-
resourceType: req.resourceType(),
|
|
896
|
-
failure: req.failure()?.errorText ?? "request failed"
|
|
897
1171
|
});
|
|
898
|
-
|
|
899
|
-
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
900
|
-
try {
|
|
901
|
-
await page.goto(url, { timeout: timeoutMs });
|
|
902
|
-
await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
|
|
903
|
-
await page.waitForLoadState("load", { timeout: timeoutMs });
|
|
1172
|
+
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
904
1173
|
try {
|
|
905
|
-
await page.
|
|
906
|
-
|
|
1174
|
+
await page.goto(url, { timeout: timeoutMs });
|
|
1175
|
+
await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
|
|
1176
|
+
await page.waitForLoadState("load", { timeout: timeoutMs });
|
|
1177
|
+
try {
|
|
1178
|
+
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1182
|
+
return { logs, pageErrors, failedRequests, url };
|
|
1183
|
+
} finally {
|
|
1184
|
+
await context.close();
|
|
907
1185
|
}
|
|
908
|
-
|
|
909
|
-
return { logs, pageErrors, failedRequests, url };
|
|
910
|
-
} finally {
|
|
911
|
-
await context.close();
|
|
912
|
-
}
|
|
1186
|
+
});
|
|
913
1187
|
}
|
|
914
1188
|
|
|
915
1189
|
// src/soft-story-rerender/soft-story-rerender.ts
|
|
@@ -981,8 +1255,8 @@ function softStoryRerenderPlugin() {
|
|
|
981
1255
|
preview.onStoriesChanged({ importFn: newModule.importFn });
|
|
982
1256
|
}
|
|
983
1257
|
});`;
|
|
984
|
-
const out = code.replace(pattern, (_match, quote,
|
|
985
|
-
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${
|
|
1258
|
+
const out = code.replace(pattern, (_match, quote, path9) => {
|
|
1259
|
+
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path9}${quote}`);
|
|
986
1260
|
});
|
|
987
1261
|
return out === code ? null : { code: out, map: null };
|
|
988
1262
|
}
|
|
@@ -993,14 +1267,14 @@ function resolveTsconfigAliases(root) {
|
|
|
993
1267
|
try {
|
|
994
1268
|
const config = readTsconfigAliasConfig(root);
|
|
995
1269
|
if (!config) return [];
|
|
996
|
-
const base =
|
|
1270
|
+
const base = path8.resolve(root, config.baseUrl ?? ".");
|
|
997
1271
|
const aliases = [];
|
|
998
1272
|
for (const [key, targets] of Object.entries(config.paths)) {
|
|
999
1273
|
const target = targets[0];
|
|
1000
1274
|
if (!target) continue;
|
|
1001
1275
|
if (key.endsWith("/*") && target.endsWith("/*")) {
|
|
1002
1276
|
const findPrefix = key.slice(0, -1);
|
|
1003
|
-
const targetDir =
|
|
1277
|
+
const targetDir = path8.resolve(base, target.slice(0, -2));
|
|
1004
1278
|
aliases.push({
|
|
1005
1279
|
find: new RegExp(`^${escapeRegExp(findPrefix)}`),
|
|
1006
1280
|
replacement: `${targetDir}/`
|
|
@@ -1008,7 +1282,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1008
1282
|
} else if (!key.includes("*") && !target.includes("*")) {
|
|
1009
1283
|
aliases.push({
|
|
1010
1284
|
find: new RegExp(`^${escapeRegExp(key)}$`),
|
|
1011
|
-
replacement:
|
|
1285
|
+
replacement: path8.resolve(base, target)
|
|
1012
1286
|
});
|
|
1013
1287
|
}
|
|
1014
1288
|
}
|
|
@@ -1019,7 +1293,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1019
1293
|
}
|
|
1020
1294
|
function readTsconfigAliasConfig(root) {
|
|
1021
1295
|
for (const candidate of TSCONFIG_CANDIDATES) {
|
|
1022
|
-
const abs =
|
|
1296
|
+
const abs = path8.join(root, candidate);
|
|
1023
1297
|
let text;
|
|
1024
1298
|
try {
|
|
1025
1299
|
text = fs5.readFileSync(abs, "utf-8");
|
|
@@ -1100,6 +1374,130 @@ function findGitRoot(startPath) {
|
|
|
1100
1374
|
}
|
|
1101
1375
|
return null;
|
|
1102
1376
|
}
|
|
1377
|
+
function ifNoneMatchHits(header, etag) {
|
|
1378
|
+
if (!header) return false;
|
|
1379
|
+
return header.split(",").some((raw) => {
|
|
1380
|
+
const token = raw.trim();
|
|
1381
|
+
return token === etag || token === `W/${etag}`;
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
function decideIndexResponse(body, ifNoneMatch) {
|
|
1385
|
+
const etag = `"${createHash("sha1").update(body).digest("hex")}"`;
|
|
1386
|
+
return { etag, notModified: ifNoneMatchHits(ifNoneMatch, etag) };
|
|
1387
|
+
}
|
|
1388
|
+
var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
|
|
1389
|
+
function isStoryFile(file) {
|
|
1390
|
+
return STORY_FILE_PATTERN.test(file);
|
|
1391
|
+
}
|
|
1392
|
+
function missingFromIndex(index, files) {
|
|
1393
|
+
const importPaths = [];
|
|
1394
|
+
for (const entry of Object.values(index.entries ?? {})) {
|
|
1395
|
+
if (typeof entry.importPath === "string") importPaths.push(entry.importPath);
|
|
1396
|
+
}
|
|
1397
|
+
return files.filter((file) => {
|
|
1398
|
+
const base = path8.basename(file);
|
|
1399
|
+
return !importPaths.some((importPath) => importPath.endsWith(base));
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
function portFromArgv(argv) {
|
|
1403
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1404
|
+
const arg = argv[i];
|
|
1405
|
+
if (arg === void 0) continue;
|
|
1406
|
+
let raw;
|
|
1407
|
+
if (arg === "-p" || arg === "--port") {
|
|
1408
|
+
raw = argv[i + 1];
|
|
1409
|
+
} else {
|
|
1410
|
+
raw = /^(?:-p|--port)=(\d+)$/.exec(arg)?.[1];
|
|
1411
|
+
}
|
|
1412
|
+
if (raw === void 0) continue;
|
|
1413
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1414
|
+
if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) return parsed;
|
|
1415
|
+
}
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
async function defaultFetchIndex(port) {
|
|
1419
|
+
try {
|
|
1420
|
+
const res = await fetch(`http://localhost:${port}/index.json`, {
|
|
1421
|
+
signal: AbortSignal.timeout(2e3)
|
|
1422
|
+
});
|
|
1423
|
+
if (!res.ok) return null;
|
|
1424
|
+
return await res.json();
|
|
1425
|
+
} catch {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1430
|
+
function createStoryIndexBroadcaster({
|
|
1431
|
+
ports,
|
|
1432
|
+
notify,
|
|
1433
|
+
fetchIndex = defaultFetchIndex,
|
|
1434
|
+
debounceMs = 500,
|
|
1435
|
+
probeIntervalMs = 500,
|
|
1436
|
+
probeCeilingMs = 15e3
|
|
1437
|
+
}) {
|
|
1438
|
+
const candidatePorts = [...new Set(ports)];
|
|
1439
|
+
const pendingAdds = /* @__PURE__ */ new Set();
|
|
1440
|
+
let addDebounce = null;
|
|
1441
|
+
let unlinkDebounce = null;
|
|
1442
|
+
let probing = false;
|
|
1443
|
+
const scheduleProbe = () => {
|
|
1444
|
+
if (addDebounce) clearTimeout(addDebounce);
|
|
1445
|
+
addDebounce = setTimeout(() => {
|
|
1446
|
+
addDebounce = null;
|
|
1447
|
+
void probeThenNotify();
|
|
1448
|
+
}, debounceMs);
|
|
1449
|
+
};
|
|
1450
|
+
const probeThenNotify = async () => {
|
|
1451
|
+
if (probing) return;
|
|
1452
|
+
probing = true;
|
|
1453
|
+
const files = Array.from(pendingAdds);
|
|
1454
|
+
const deadline = Date.now() + probeCeilingMs;
|
|
1455
|
+
try {
|
|
1456
|
+
if (files.length === 0) return;
|
|
1457
|
+
while (Date.now() < deadline) {
|
|
1458
|
+
for (const candidate of candidatePorts) {
|
|
1459
|
+
const index = await fetchIndex(candidate);
|
|
1460
|
+
if (index && missingFromIndex(index, files).length === 0) {
|
|
1461
|
+
console.log(
|
|
1462
|
+
"[STORYBOOK_PLUGIN] Index caught up with new story files \u2014 broadcasting",
|
|
1463
|
+
{ files, port: candidate }
|
|
1464
|
+
);
|
|
1465
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1466
|
+
notify();
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
await sleep(probeIntervalMs);
|
|
1471
|
+
}
|
|
1472
|
+
console.warn("[STORYBOOK_PLUGIN] Index probe hit ceiling \u2014 broadcasting anyway", {
|
|
1473
|
+
files,
|
|
1474
|
+
probeCeilingMs
|
|
1475
|
+
});
|
|
1476
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1477
|
+
notify();
|
|
1478
|
+
} finally {
|
|
1479
|
+
probing = false;
|
|
1480
|
+
if (pendingAdds.size > 0) scheduleProbe();
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
return {
|
|
1484
|
+
onStoryFileAdded(file) {
|
|
1485
|
+
if (!isStoryFile(file)) return;
|
|
1486
|
+
pendingAdds.add(file);
|
|
1487
|
+
scheduleProbe();
|
|
1488
|
+
},
|
|
1489
|
+
onStoryFileRemoved(file) {
|
|
1490
|
+
if (!isStoryFile(file)) return;
|
|
1491
|
+
pendingAdds.delete(file);
|
|
1492
|
+
if (unlinkDebounce) clearTimeout(unlinkDebounce);
|
|
1493
|
+
unlinkDebounce = setTimeout(() => {
|
|
1494
|
+
unlinkDebounce = null;
|
|
1495
|
+
console.log("[STORYBOOK_PLUGIN] Story file removed \u2014 broadcasting", { file });
|
|
1496
|
+
notify();
|
|
1497
|
+
}, debounceMs);
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1103
1501
|
|
|
1104
1502
|
// src/storybook-onlook-plugin.ts
|
|
1105
1503
|
function errorCode(error) {
|
|
@@ -1166,7 +1564,7 @@ var TAILWIND_ENTRY_CANDIDATES = [
|
|
|
1166
1564
|
var TAILWIND_DIRECTIVE_PATTERN = /@import\s+['"](?:tailwindcss|tailwindcss\/.+)['"]|@tailwind\s+(?:base|components|utilities)/;
|
|
1167
1565
|
function detectTailwindEntryCss(cwd) {
|
|
1168
1566
|
for (const rel of TAILWIND_ENTRY_CANDIDATES) {
|
|
1169
|
-
const abs =
|
|
1567
|
+
const abs = path8.join(cwd, rel);
|
|
1170
1568
|
try {
|
|
1171
1569
|
if (!fs5.existsSync(abs)) continue;
|
|
1172
1570
|
const head = fs5.readFileSync(abs, "utf-8").slice(0, 2048);
|
|
@@ -1179,15 +1577,8 @@ function detectTailwindEntryCss(cwd) {
|
|
|
1179
1577
|
var serveMetadataAndScreenshots = (req, res, next) => {
|
|
1180
1578
|
if (req.url === "/onbook-index.json") {
|
|
1181
1579
|
console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
|
|
1182
|
-
const cacheBuster = Date.now();
|
|
1183
1580
|
console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
|
|
1184
|
-
fetch(
|
|
1185
|
-
cache: "no-store",
|
|
1186
|
-
headers: {
|
|
1187
|
-
"Cache-Control": "no-cache",
|
|
1188
|
-
Pragma: "no-cache"
|
|
1189
|
-
}
|
|
1190
|
-
}).then((response) => {
|
|
1581
|
+
fetch("http://localhost:6006/index.json").then((response) => {
|
|
1191
1582
|
console.log("[STORYBOOK_PLUGIN] Storybook index fetch response", {
|
|
1192
1583
|
status: response.status,
|
|
1193
1584
|
ok: response.ok,
|
|
@@ -1206,12 +1597,21 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1206
1597
|
entryCount: Object.keys(indexData.entries || {}).length,
|
|
1207
1598
|
hasMetadata: !!indexData.meta
|
|
1208
1599
|
});
|
|
1209
|
-
|
|
1600
|
+
const body = JSON.stringify(indexData);
|
|
1601
|
+
const { etag, notModified } = decideIndexResponse(
|
|
1602
|
+
body,
|
|
1603
|
+
req.headers["if-none-match"]
|
|
1604
|
+
);
|
|
1210
1605
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1211
|
-
res.setHeader("
|
|
1212
|
-
res.setHeader("
|
|
1213
|
-
|
|
1214
|
-
|
|
1606
|
+
res.setHeader("ETag", etag);
|
|
1607
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1608
|
+
if (notModified) {
|
|
1609
|
+
res.statusCode = 304;
|
|
1610
|
+
res.end();
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
res.setHeader("Content-Type", "application/json");
|
|
1614
|
+
res.end(body);
|
|
1215
1615
|
}).catch((error) => {
|
|
1216
1616
|
console.error("[STORYBOOK_PLUGIN] Failed to fetch/extend index.json", {
|
|
1217
1617
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -1303,7 +1703,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1303
1703
|
return;
|
|
1304
1704
|
}
|
|
1305
1705
|
if (req.url?.startsWith("/screenshots/")) {
|
|
1306
|
-
const screenshotPath =
|
|
1706
|
+
const screenshotPath = path8.join(
|
|
1307
1707
|
process.cwd(),
|
|
1308
1708
|
".storybook-cache",
|
|
1309
1709
|
req.url.replace("/screenshots/", "screenshots/")
|
|
@@ -1324,7 +1724,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1324
1724
|
`[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
|
|
1325
1725
|
);
|
|
1326
1726
|
captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
|
|
1327
|
-
const storyDir =
|
|
1727
|
+
const storyDir = path8.join(
|
|
1328
1728
|
process.cwd(),
|
|
1329
1729
|
".storybook-cache",
|
|
1330
1730
|
"screenshots",
|
|
@@ -1443,7 +1843,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1443
1843
|
configureServer(server) {
|
|
1444
1844
|
console.log("[STORYBOOK_PLUGIN] Configuring server middleware");
|
|
1445
1845
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1446
|
-
const cachedManifestPath =
|
|
1846
|
+
const cachedManifestPath = path8.join(
|
|
1447
1847
|
process.cwd(),
|
|
1448
1848
|
".storybook-cache",
|
|
1449
1849
|
"manifest.json"
|
|
@@ -1462,7 +1862,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1462
1862
|
server.watcher.on("change", scheduleRefresh);
|
|
1463
1863
|
const storyGlobs = options.storyGlobs ?? ["**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)"];
|
|
1464
1864
|
const resolvedStoryGlobs = storyGlobs.map(
|
|
1465
|
-
(glob) =>
|
|
1865
|
+
(glob) => path8.isAbsolute(glob) ? glob : path8.join(process.cwd(), glob)
|
|
1466
1866
|
);
|
|
1467
1867
|
if (resolvedStoryGlobs.length > 0) {
|
|
1468
1868
|
server.watcher.add(resolvedStoryGlobs);
|
|
@@ -1470,10 +1870,36 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1470
1870
|
storyGlobs: resolvedStoryGlobs
|
|
1471
1871
|
});
|
|
1472
1872
|
}
|
|
1873
|
+
const probeCandidatePorts = [
|
|
1874
|
+
...new Set(
|
|
1875
|
+
[portFromArgv(process.argv), port, server.config.server.port].filter(
|
|
1876
|
+
(p) => typeof p === "number"
|
|
1877
|
+
)
|
|
1878
|
+
)
|
|
1879
|
+
];
|
|
1880
|
+
console.log("[STORYBOOK_PLUGIN] Story add/unlink index broadcast enabled", {
|
|
1881
|
+
probeCandidatePorts
|
|
1882
|
+
});
|
|
1883
|
+
const indexBroadcaster = createStoryIndexBroadcaster({
|
|
1884
|
+
ports: probeCandidatePorts,
|
|
1885
|
+
notify: notifyOnbookIndexChanged
|
|
1886
|
+
});
|
|
1887
|
+
let indexBroadcastWatcherReady = false;
|
|
1888
|
+
server.watcher.once("ready", () => {
|
|
1889
|
+
indexBroadcastWatcherReady = true;
|
|
1890
|
+
});
|
|
1891
|
+
server.watcher.on("add", (file) => {
|
|
1892
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1893
|
+
indexBroadcaster.onStoryFileAdded(file);
|
|
1894
|
+
});
|
|
1895
|
+
server.watcher.on("unlink", (file) => {
|
|
1896
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1897
|
+
indexBroadcaster.onStoryFileRemoved(file);
|
|
1898
|
+
});
|
|
1473
1899
|
const tailwindEntryPath = (() => {
|
|
1474
1900
|
if (options.tailwindEntryCss === false) return null;
|
|
1475
1901
|
if (typeof options.tailwindEntryCss === "string") {
|
|
1476
|
-
return
|
|
1902
|
+
return path8.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path8.join(process.cwd(), options.tailwindEntryCss);
|
|
1477
1903
|
}
|
|
1478
1904
|
return detectTailwindEntryCss(process.cwd());
|
|
1479
1905
|
})();
|
|
@@ -1489,7 +1915,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1489
1915
|
});
|
|
1490
1916
|
server.watcher.on("add", (file) => {
|
|
1491
1917
|
if (!/\.tsx?$/.test(file)) return;
|
|
1492
|
-
const dir =
|
|
1918
|
+
const dir = path8.dirname(file);
|
|
1493
1919
|
const isNewDir = !seenDirs.has(dir);
|
|
1494
1920
|
seenDirs.add(dir);
|
|
1495
1921
|
if (!watcherReady || !isNewDir) return;
|
|
@@ -1520,15 +1946,17 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1520
1946
|
configurePreviewServer(server) {
|
|
1521
1947
|
console.log("[STORYBOOK_PLUGIN] Configuring preview server middleware");
|
|
1522
1948
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1523
|
-
refreshManifest(
|
|
1949
|
+
refreshManifest(path8.join(process.cwd(), ".storybook-cache", "manifest.json"));
|
|
1524
1950
|
},
|
|
1525
1951
|
handleHotUpdate: handleStoryFileChange
|
|
1526
1952
|
};
|
|
1527
1953
|
const envContainment = envContainmentPlugin(options.envContainment);
|
|
1954
|
+
const packageMock = packageMockPlugin(options.packageMock);
|
|
1528
1955
|
const moduleLoadCatchAll = options.moduleLoadCatchAll === false ? null : moduleLoadCatchAllPlugin();
|
|
1529
1956
|
return withDedupeApply(
|
|
1530
1957
|
[
|
|
1531
1958
|
...envContainment ? [envContainment] : [],
|
|
1959
|
+
...packageMock ? [packageMock] : [],
|
|
1532
1960
|
...moduleLoadCatchAll ? [moduleLoadCatchAll] : [],
|
|
1533
1961
|
componentLocPlugin(),
|
|
1534
1962
|
mainPlugin,
|
|
@@ -1609,6 +2037,7 @@ var OPTION_KEY_MAP = {
|
|
|
1609
2037
|
storyGlobs: true,
|
|
1610
2038
|
tailwindEntryCss: true,
|
|
1611
2039
|
envContainment: true,
|
|
2040
|
+
packageMock: true,
|
|
1612
2041
|
moduleLoadCatchAll: true,
|
|
1613
2042
|
resolveAliases: true
|
|
1614
2043
|
};
|