@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/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import
|
|
2
|
+
import path8, { dirname, join, relative } from 'path';
|
|
3
3
|
import crypto, { createHash } from 'crypto';
|
|
4
4
|
import fs5, { existsSync } from 'fs';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
@@ -61,7 +61,7 @@ function envContainmentPlugin(options) {
|
|
|
61
61
|
const resolveOutcomes = /* @__PURE__ */ new Map();
|
|
62
62
|
const roots = /* @__PURE__ */ new Set([normalizeSlashes(process.cwd())]);
|
|
63
63
|
const matchTarget = (absPath) => {
|
|
64
|
-
const norm = normalizeSlashes(
|
|
64
|
+
const norm = normalizeSlashes(path8.normalize(absPath));
|
|
65
65
|
for (const rel of byModule.keys()) {
|
|
66
66
|
for (const root of roots) {
|
|
67
67
|
const target = `${root}/${rel}`;
|
|
@@ -96,10 +96,10 @@ function envContainmentPlugin(options) {
|
|
|
96
96
|
if (source.startsWith("\0")) return null;
|
|
97
97
|
const cleaned = stripQuery(source);
|
|
98
98
|
let direct = null;
|
|
99
|
-
if (
|
|
99
|
+
if (path8.isAbsolute(cleaned)) {
|
|
100
100
|
direct = cleaned;
|
|
101
101
|
} else if (importer && (cleaned.startsWith("./") || cleaned.startsWith("../"))) {
|
|
102
|
-
direct =
|
|
102
|
+
direct = path8.resolve(path8.dirname(stripQuery(importer)), cleaned);
|
|
103
103
|
}
|
|
104
104
|
if (direct) {
|
|
105
105
|
const rel = matchTarget(direct);
|
|
@@ -108,7 +108,7 @@ function envContainmentPlugin(options) {
|
|
|
108
108
|
}
|
|
109
109
|
if (!specifierMightMatch(cleaned)) return null;
|
|
110
110
|
if (typeof this.resolve !== "function") return null;
|
|
111
|
-
const importerDir = importer ?
|
|
111
|
+
const importerDir = importer ? path8.dirname(stripQuery(importer)) : "";
|
|
112
112
|
const outcomeKey = `${importerDir}|${source}`;
|
|
113
113
|
const memoized = resolveOutcomes.get(outcomeKey);
|
|
114
114
|
if (memoized !== void 0) return memoized;
|
|
@@ -328,7 +328,7 @@ function componentLocPlugin(options = {}) {
|
|
|
328
328
|
sourceFilename: filepath
|
|
329
329
|
});
|
|
330
330
|
let mutated = false;
|
|
331
|
-
const relativePath =
|
|
331
|
+
const relativePath = path8.relative(root, filepath);
|
|
332
332
|
traverse(ast, {
|
|
333
333
|
JSXElement(nodePath) {
|
|
334
334
|
const opening = nodePath.node.openingElement;
|
|
@@ -387,9 +387,9 @@ function fastRefreshTolerantExportsPlugin() {
|
|
|
387
387
|
}
|
|
388
388
|
};
|
|
389
389
|
}
|
|
390
|
-
var CACHE_DIR =
|
|
391
|
-
var SCREENSHOTS_DIR =
|
|
392
|
-
var MANIFEST_PATH =
|
|
390
|
+
var CACHE_DIR = path8.join(process.cwd(), ".storybook-cache");
|
|
391
|
+
var SCREENSHOTS_DIR = path8.join(CACHE_DIR, "screenshots");
|
|
392
|
+
var MANIFEST_PATH = path8.join(CACHE_DIR, "manifest.json");
|
|
393
393
|
var VIEWPORT_WIDTH = 1920;
|
|
394
394
|
var VIEWPORT_HEIGHT = 1080;
|
|
395
395
|
var MIN_COMPONENT_WIDTH = 420;
|
|
@@ -514,6 +514,52 @@ async function getBrowser() {
|
|
|
514
514
|
}
|
|
515
515
|
return browserPromise;
|
|
516
516
|
}
|
|
517
|
+
|
|
518
|
+
// src/screenshot-service/utils/capture-semaphore/capture-semaphore.ts
|
|
519
|
+
function createSemaphore(limit) {
|
|
520
|
+
const max = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 1;
|
|
521
|
+
let active = 0;
|
|
522
|
+
const waiters = [];
|
|
523
|
+
const acquire = () => {
|
|
524
|
+
if (active < max) {
|
|
525
|
+
active += 1;
|
|
526
|
+
return Promise.resolve();
|
|
527
|
+
}
|
|
528
|
+
return new Promise((resolve) => {
|
|
529
|
+
waiters.push(() => {
|
|
530
|
+
active += 1;
|
|
531
|
+
resolve();
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
};
|
|
535
|
+
const release = () => {
|
|
536
|
+
active -= 1;
|
|
537
|
+
const next = waiters.shift();
|
|
538
|
+
if (next) next();
|
|
539
|
+
};
|
|
540
|
+
return {
|
|
541
|
+
async run(fn) {
|
|
542
|
+
await acquire();
|
|
543
|
+
try {
|
|
544
|
+
return await fn();
|
|
545
|
+
} finally {
|
|
546
|
+
release();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
var DEFAULT_CAPTURE_CONCURRENCY = 3;
|
|
552
|
+
function resolveConcurrency(raw) {
|
|
553
|
+
if (!raw) return DEFAULT_CAPTURE_CONCURRENCY;
|
|
554
|
+
const parsed = Number.parseInt(raw, 10);
|
|
555
|
+
if (!Number.isInteger(parsed) || parsed < 1) return DEFAULT_CAPTURE_CONCURRENCY;
|
|
556
|
+
return parsed;
|
|
557
|
+
}
|
|
558
|
+
var captureSemaphore = createSemaphore(
|
|
559
|
+
resolveConcurrency(process.env.ONLOOK_CAPTURE_CONCURRENCY)
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
// src/screenshot-service/utils/screenshot/screenshot.ts
|
|
517
563
|
function classifyPreviewSnapshot(s) {
|
|
518
564
|
if (s.text.includes("Sorry, something went wrong")) return "errored";
|
|
519
565
|
if (s.text.includes("Couldn't find story")) return "errored";
|
|
@@ -580,96 +626,98 @@ async function detectRenderState(page, opts = {}) {
|
|
|
580
626
|
}
|
|
581
627
|
}
|
|
582
628
|
function getScreenshotPath(storyId, theme) {
|
|
583
|
-
const storyDir =
|
|
584
|
-
return
|
|
629
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
630
|
+
return path8.join(storyDir, `${theme}.png`);
|
|
585
631
|
}
|
|
586
632
|
async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const page = await context.newPage();
|
|
593
|
-
try {
|
|
594
|
-
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
595
|
-
await page.goto(url, { timeout: loadTimeoutMs });
|
|
596
|
-
await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
|
|
597
|
-
await page.waitForLoadState("load", { timeout: loadTimeoutMs });
|
|
598
|
-
try {
|
|
599
|
-
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
600
|
-
} catch {
|
|
601
|
-
}
|
|
602
|
-
const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
|
|
603
|
-
await page.evaluate(() => document.fonts.ready);
|
|
604
|
-
try {
|
|
605
|
-
await page.evaluate(async () => {
|
|
606
|
-
const images = document.querySelectorAll("img");
|
|
607
|
-
await Promise.all(
|
|
608
|
-
Array.from(images).map((img) => {
|
|
609
|
-
if (img.complete) return Promise.resolve();
|
|
610
|
-
return new Promise((resolve) => {
|
|
611
|
-
img.addEventListener("load", resolve);
|
|
612
|
-
img.addEventListener("error", resolve);
|
|
613
|
-
setTimeout(resolve, 3e3);
|
|
614
|
-
});
|
|
615
|
-
})
|
|
616
|
-
);
|
|
617
|
-
});
|
|
618
|
-
} catch {
|
|
619
|
-
}
|
|
620
|
-
const contentBounds = await page.evaluate(() => {
|
|
621
|
-
const root = document.querySelector("#storybook-root");
|
|
622
|
-
if (!root) return null;
|
|
623
|
-
const children = root.querySelectorAll("*");
|
|
624
|
-
if (children.length === 0) return null;
|
|
625
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
626
|
-
children.forEach((child) => {
|
|
627
|
-
const rect = child.getBoundingClientRect();
|
|
628
|
-
if (rect.width === 0 || rect.height === 0) return;
|
|
629
|
-
minX = Math.min(minX, rect.left);
|
|
630
|
-
minY = Math.min(minY, rect.top);
|
|
631
|
-
maxX = Math.max(maxX, rect.right);
|
|
632
|
-
maxY = Math.max(maxY, rect.bottom);
|
|
633
|
-
});
|
|
634
|
-
if (minX === Infinity) return null;
|
|
635
|
-
return {
|
|
636
|
-
x: minX,
|
|
637
|
-
y: minY,
|
|
638
|
-
width: maxX - minX,
|
|
639
|
-
height: maxY - minY
|
|
640
|
-
};
|
|
633
|
+
return captureSemaphore.run(async () => {
|
|
634
|
+
const browser = await getBrowser();
|
|
635
|
+
const context = await browser.newContext({
|
|
636
|
+
viewport: { width, height },
|
|
637
|
+
deviceScaleFactor: 2
|
|
641
638
|
});
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
639
|
+
const page = await context.newPage();
|
|
640
|
+
try {
|
|
641
|
+
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
642
|
+
await page.goto(url, { timeout: loadTimeoutMs });
|
|
643
|
+
await page.waitForLoadState("domcontentloaded", { timeout: loadTimeoutMs });
|
|
644
|
+
await page.waitForLoadState("load", { timeout: loadTimeoutMs });
|
|
645
|
+
try {
|
|
646
|
+
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
647
|
+
} catch {
|
|
648
|
+
}
|
|
649
|
+
const renderState = await detectRenderState(page, { hardCapMs: renderTimeoutMs });
|
|
650
|
+
await page.evaluate(() => document.fonts.ready);
|
|
651
|
+
try {
|
|
652
|
+
await page.evaluate(async () => {
|
|
653
|
+
const images = document.querySelectorAll("img");
|
|
654
|
+
await Promise.all(
|
|
655
|
+
Array.from(images).map((img) => {
|
|
656
|
+
if (img.complete) return Promise.resolve();
|
|
657
|
+
return new Promise((resolve) => {
|
|
658
|
+
img.addEventListener("load", resolve);
|
|
659
|
+
img.addEventListener("error", resolve);
|
|
660
|
+
setTimeout(resolve, 3e3);
|
|
661
|
+
});
|
|
662
|
+
})
|
|
663
|
+
);
|
|
664
|
+
});
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
const contentBounds = await page.evaluate(() => {
|
|
668
|
+
const root = document.querySelector("#storybook-root");
|
|
669
|
+
if (!root) return null;
|
|
670
|
+
const children = root.querySelectorAll("*");
|
|
671
|
+
if (children.length === 0) return null;
|
|
672
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
673
|
+
children.forEach((child) => {
|
|
674
|
+
const rect = child.getBoundingClientRect();
|
|
675
|
+
if (rect.width === 0 || rect.height === 0) return;
|
|
676
|
+
minX = Math.min(minX, rect.left);
|
|
677
|
+
minY = Math.min(minY, rect.top);
|
|
678
|
+
maxX = Math.max(maxX, rect.right);
|
|
679
|
+
maxY = Math.max(maxY, rect.bottom);
|
|
680
|
+
});
|
|
681
|
+
if (minX === Infinity) return null;
|
|
682
|
+
return {
|
|
683
|
+
x: minX,
|
|
684
|
+
y: minY,
|
|
685
|
+
width: maxX - minX,
|
|
686
|
+
height: maxY - minY
|
|
687
|
+
};
|
|
660
688
|
});
|
|
661
|
-
|
|
662
|
-
|
|
689
|
+
let screenshotBuffer;
|
|
690
|
+
let resultBoundingBox = null;
|
|
691
|
+
if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
|
|
692
|
+
const PADDING = 20;
|
|
693
|
+
const clippedWidth = Math.min(width, contentBounds.width + PADDING);
|
|
694
|
+
const clippedHeight = Math.min(height, contentBounds.height + PADDING);
|
|
695
|
+
resultBoundingBox = {
|
|
696
|
+
width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
|
|
697
|
+
height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight))
|
|
698
|
+
};
|
|
699
|
+
screenshotBuffer = await page.screenshot({
|
|
700
|
+
type: "png",
|
|
701
|
+
clip: {
|
|
702
|
+
x: Math.max(0, contentBounds.x - 10),
|
|
703
|
+
y: Math.max(0, contentBounds.y - 10),
|
|
704
|
+
width: clippedWidth,
|
|
705
|
+
height: clippedHeight
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
} else {
|
|
709
|
+
screenshotBuffer = await page.screenshot({ type: "png" });
|
|
710
|
+
}
|
|
711
|
+
return { buffer: screenshotBuffer, boundingBox: resultBoundingBox, renderState };
|
|
712
|
+
} finally {
|
|
713
|
+
await context.close();
|
|
663
714
|
}
|
|
664
|
-
|
|
665
|
-
} finally {
|
|
666
|
-
await context.close();
|
|
667
|
-
}
|
|
715
|
+
});
|
|
668
716
|
}
|
|
669
717
|
async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
670
718
|
try {
|
|
671
719
|
ensureCacheDirectories();
|
|
672
|
-
const storyDir =
|
|
720
|
+
const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
|
|
673
721
|
if (!fs5.existsSync(storyDir)) {
|
|
674
722
|
fs5.mkdirSync(storyDir, { recursive: true });
|
|
675
723
|
}
|
|
@@ -771,7 +819,39 @@ async function waitForIndexAndBroadcast(port = 6006) {
|
|
|
771
819
|
});
|
|
772
820
|
}
|
|
773
821
|
|
|
822
|
+
// src/handlers/handleStoryFileChange/captureCoalescer/captureCoalescer.ts
|
|
823
|
+
function createCaptureCoalescer(runCapture) {
|
|
824
|
+
const inFlight2 = /* @__PURE__ */ new Map();
|
|
825
|
+
const trailing = /* @__PURE__ */ new Set();
|
|
826
|
+
const start = (storyId) => {
|
|
827
|
+
const done = (async () => {
|
|
828
|
+
try {
|
|
829
|
+
await runCapture(storyId);
|
|
830
|
+
} finally {
|
|
831
|
+
inFlight2.delete(storyId);
|
|
832
|
+
if (trailing.has(storyId)) {
|
|
833
|
+
trailing.delete(storyId);
|
|
834
|
+
await start(storyId);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
})();
|
|
838
|
+
inFlight2.set(storyId, done);
|
|
839
|
+
return done;
|
|
840
|
+
};
|
|
841
|
+
return {
|
|
842
|
+
capture(storyId) {
|
|
843
|
+
const existing = inFlight2.get(storyId);
|
|
844
|
+
if (existing) {
|
|
845
|
+
trailing.add(storyId);
|
|
846
|
+
return existing;
|
|
847
|
+
}
|
|
848
|
+
return start(storyId);
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
774
853
|
// src/handlers/handleStoryFileChange/handleStoryFileChange.ts
|
|
854
|
+
var STORYBOOK_URL = "http://localhost:6006";
|
|
775
855
|
var cachedIndex = null;
|
|
776
856
|
var indexFetchPromise = null;
|
|
777
857
|
var pendingFiles = /* @__PURE__ */ new Set();
|
|
@@ -790,47 +870,46 @@ async function fetchStorybookIndex() {
|
|
|
790
870
|
}
|
|
791
871
|
function getStoriesForFile(filePath) {
|
|
792
872
|
if (!cachedIndex) return [];
|
|
793
|
-
const fileName =
|
|
873
|
+
const fileName = path8.basename(filePath);
|
|
794
874
|
return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
|
|
795
875
|
}
|
|
876
|
+
var latestStoryFiles = /* @__PURE__ */ new Map();
|
|
877
|
+
async function captureStoryAndUpdateManifest(storyId) {
|
|
878
|
+
const [lightResult] = await Promise.all([
|
|
879
|
+
generateScreenshot(storyId, "light", STORYBOOK_URL),
|
|
880
|
+
generateScreenshot(storyId, "dark", STORYBOOK_URL)
|
|
881
|
+
]);
|
|
882
|
+
const boundingBox = lightResult?.boundingBox ?? void 0;
|
|
883
|
+
for (const file of latestStoryFiles.get(storyId) ?? []) {
|
|
884
|
+
const fileHash = computeFileHash(file);
|
|
885
|
+
updateManifest(storyId, file, fileHash, boundingBox);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
var captureCoalescer = createCaptureCoalescer(captureStoryAndUpdateManifest);
|
|
796
889
|
async function regenerateScreenshotsForFiles(files) {
|
|
797
890
|
await fetchStorybookIndex();
|
|
798
|
-
const
|
|
799
|
-
const fileToStories = /* @__PURE__ */ new Map();
|
|
891
|
+
const storyToFiles = /* @__PURE__ */ new Map();
|
|
800
892
|
for (const file of files) {
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
893
|
+
for (const storyId of getStoriesForFile(file)) {
|
|
894
|
+
const arr = storyToFiles.get(storyId) ?? [];
|
|
895
|
+
arr.push(file);
|
|
896
|
+
storyToFiles.set(storyId, arr);
|
|
805
897
|
}
|
|
806
898
|
}
|
|
807
|
-
if (
|
|
899
|
+
if (storyToFiles.size === 0) {
|
|
808
900
|
console.log("[Screenshots] No stories found for changed files");
|
|
809
901
|
return;
|
|
810
902
|
}
|
|
811
903
|
console.log(
|
|
812
|
-
`[Screenshots] Regenerating ${
|
|
904
|
+
`[Screenshots] Regenerating ${storyToFiles.size} stories from ${files.length} files`
|
|
813
905
|
);
|
|
814
|
-
const
|
|
815
|
-
|
|
906
|
+
for (const [storyId, storyFiles] of storyToFiles) {
|
|
907
|
+
latestStoryFiles.set(storyId, storyFiles);
|
|
908
|
+
}
|
|
816
909
|
await Promise.all(
|
|
817
|
-
Array.from(
|
|
818
|
-
const [lightResult, _darkResult] = await Promise.all([
|
|
819
|
-
generateScreenshot(storyId, "light", storybookUrl),
|
|
820
|
-
generateScreenshot(storyId, "dark", storybookUrl)
|
|
821
|
-
]);
|
|
822
|
-
if (lightResult) {
|
|
823
|
-
storyBoundingBoxes.set(storyId, lightResult.boundingBox);
|
|
824
|
-
}
|
|
825
|
-
})
|
|
910
|
+
Array.from(storyToFiles.keys()).map((storyId) => captureCoalescer.capture(storyId))
|
|
826
911
|
);
|
|
827
|
-
|
|
828
|
-
const fileHash = computeFileHash(file);
|
|
829
|
-
storyIds.forEach((storyId) => {
|
|
830
|
-
updateManifest(storyId, file, fileHash, storyBoundingBoxes.get(storyId));
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
console.log(`[Screenshots] \u2713 Regenerated ${allStoryIds.size} stories`);
|
|
912
|
+
console.log(`[Screenshots] \u2713 Regenerated ${storyToFiles.size} stories`);
|
|
834
913
|
}
|
|
835
914
|
function handleStoryFileChange({ file, modules }) {
|
|
836
915
|
if (file.endsWith(".stories.tsx") || file.endsWith(".stories.ts")) {
|
|
@@ -857,63 +936,258 @@ function handleStoryFileChange({ file, modules }) {
|
|
|
857
936
|
}
|
|
858
937
|
}
|
|
859
938
|
|
|
939
|
+
// src/package-mock/clerk-mock.ts
|
|
940
|
+
var VALID_IDENT_RE2 = /^[A-Za-z_$][\w$]*$/;
|
|
941
|
+
var RESERVED_HELPER_NAMES = /* @__PURE__ */ new Set([
|
|
942
|
+
"__user",
|
|
943
|
+
"__pass",
|
|
944
|
+
"__null",
|
|
945
|
+
"__clerk",
|
|
946
|
+
"__sessionId",
|
|
947
|
+
"__asyncNoop",
|
|
948
|
+
"__noop",
|
|
949
|
+
"createElement",
|
|
950
|
+
"Fragment"
|
|
951
|
+
]);
|
|
952
|
+
var BUILDERS = {
|
|
953
|
+
"@clerk/nextjs": buildClerkMockSource,
|
|
954
|
+
"@clerk/clerk-react": buildClerkMockSource
|
|
955
|
+
};
|
|
956
|
+
function isKnownMockPackage(pkg) {
|
|
957
|
+
return pkg in BUILDERS;
|
|
958
|
+
}
|
|
959
|
+
function buildPackageMockSource(pkg, usedExports) {
|
|
960
|
+
const build = BUILDERS[pkg];
|
|
961
|
+
if (!build) throw new Error(`no package mock for '${pkg}'`);
|
|
962
|
+
return build(usedExports);
|
|
963
|
+
}
|
|
964
|
+
var CLERK_KNOWN_HOOKS = {
|
|
965
|
+
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 })",
|
|
966
|
+
useUser: "({ isLoaded: true, isSignedIn: true, user: __user })",
|
|
967
|
+
useSession: '({ isLoaded: true, isSignedIn: true, session: { id: __sessionId, status: "active", user: __user } })',
|
|
968
|
+
useSessionList: "({ isLoaded: true, sessions: [], setActive: __asyncNoop })",
|
|
969
|
+
useClerk: "__clerk",
|
|
970
|
+
useOrganization: "({ isLoaded: true, organization: null, membership: null, memberships: undefined })",
|
|
971
|
+
useOrganizationList: "({ isLoaded: true, userMemberships: undefined, setActive: __asyncNoop })",
|
|
972
|
+
useSignIn: "({ isLoaded: true, signIn: undefined, setActive: __asyncNoop })",
|
|
973
|
+
useSignUp: "({ isLoaded: true, signUp: undefined, setActive: __asyncNoop })"
|
|
974
|
+
};
|
|
975
|
+
var CLERK_CHILDREN_COMPONENTS = /* @__PURE__ */ new Set([
|
|
976
|
+
"ClerkProvider",
|
|
977
|
+
"ClerkLoaded",
|
|
978
|
+
"SignedIn",
|
|
979
|
+
"Protect",
|
|
980
|
+
"SignInButton",
|
|
981
|
+
"SignUpButton",
|
|
982
|
+
"SignOutButton",
|
|
983
|
+
"SignInWithMetamaskButton"
|
|
984
|
+
]);
|
|
985
|
+
var CLERK_NULL_COMPONENTS = /* @__PURE__ */ new Set([
|
|
986
|
+
"SignedOut",
|
|
987
|
+
"ClerkLoading",
|
|
988
|
+
"RedirectToSignIn",
|
|
989
|
+
"RedirectToSignUp",
|
|
990
|
+
"RedirectToUserProfile",
|
|
991
|
+
"RedirectToOrganizationProfile",
|
|
992
|
+
"RedirectToCreateOrganization",
|
|
993
|
+
"AuthenticateWithRedirectCallback",
|
|
994
|
+
"UserButton",
|
|
995
|
+
"UserProfile",
|
|
996
|
+
"OrganizationSwitcher",
|
|
997
|
+
"OrganizationProfile",
|
|
998
|
+
"OrganizationList",
|
|
999
|
+
"CreateOrganization",
|
|
1000
|
+
"SignIn",
|
|
1001
|
+
"SignUp",
|
|
1002
|
+
"Waitlist",
|
|
1003
|
+
"GoogleOneTap"
|
|
1004
|
+
]);
|
|
1005
|
+
var CLERK_PREAMBLE = [
|
|
1006
|
+
"// Auto-generated by @onlook/storybook-plugin (package mock). Zero-backend,",
|
|
1007
|
+
"// signed-in Clerk stand-in so components that call useUser()/useAuth() render",
|
|
1008
|
+
"// authenticated content in the canvas without a publishable key, clerk-js, or",
|
|
1009
|
+
"// a network round-trip. Swap back to the real @clerk/* package for live data.",
|
|
1010
|
+
"import { createElement, Fragment } from 'react';",
|
|
1011
|
+
"const __asyncNoop = async () => {};",
|
|
1012
|
+
"const __noop = () => {};",
|
|
1013
|
+
// No `: any` param annotation: the virtual module is served extensionless
|
|
1014
|
+
// (see header) so Vite parses it as PLAIN JS — a TS type annotation here is a
|
|
1015
|
+
// runtime `Unexpected token ':'` that breaks every story importing the package.
|
|
1016
|
+
"const __pass = (props) => createElement(Fragment, null, props ? props.children : undefined);",
|
|
1017
|
+
"const __null = () => null;",
|
|
1018
|
+
"const __sessionId = 'sess_onlookMock';",
|
|
1019
|
+
"// A plausible signed-in Clerk user. Fields cover the common UserResource",
|
|
1020
|
+
"// surface components read at render; no image URL (avoids a network fetch).",
|
|
1021
|
+
"const __user = {",
|
|
1022
|
+
" id: 'user_onlookMock',",
|
|
1023
|
+
" firstName: 'Ada',",
|
|
1024
|
+
" lastName: 'Lovelace',",
|
|
1025
|
+
" fullName: 'Ada Lovelace',",
|
|
1026
|
+
" username: 'ada',",
|
|
1027
|
+
" imageUrl: '',",
|
|
1028
|
+
" hasImage: false,",
|
|
1029
|
+
" primaryEmailAddressId: 'idn_onlookMock',",
|
|
1030
|
+
" primaryEmailAddress: { id: 'idn_onlookMock', emailAddress: 'ada@example.com' },",
|
|
1031
|
+
" emailAddresses: [{ id: 'idn_onlookMock', emailAddress: 'ada@example.com' }],",
|
|
1032
|
+
" primaryPhoneNumber: null,",
|
|
1033
|
+
" phoneNumbers: [],",
|
|
1034
|
+
" externalAccounts: [],",
|
|
1035
|
+
" organizationMemberships: [],",
|
|
1036
|
+
" publicMetadata: {},",
|
|
1037
|
+
" unsafeMetadata: {},",
|
|
1038
|
+
" reload: __asyncNoop,",
|
|
1039
|
+
" update: __asyncNoop,",
|
|
1040
|
+
"};",
|
|
1041
|
+
"const __clerk = {",
|
|
1042
|
+
" loaded: true,",
|
|
1043
|
+
" user: __user,",
|
|
1044
|
+
" session: { id: __sessionId, status: 'active', user: __user },",
|
|
1045
|
+
" organization: null,",
|
|
1046
|
+
" openSignIn: __noop,",
|
|
1047
|
+
" openSignUp: __noop,",
|
|
1048
|
+
" openUserProfile: __noop,",
|
|
1049
|
+
" openOrganizationProfile: __noop,",
|
|
1050
|
+
" closeSignIn: __noop,",
|
|
1051
|
+
" closeSignUp: __noop,",
|
|
1052
|
+
" signOut: __asyncNoop,",
|
|
1053
|
+
" setActive: __asyncNoop,",
|
|
1054
|
+
"};"
|
|
1055
|
+
];
|
|
1056
|
+
function clerkExportLine(name) {
|
|
1057
|
+
if (name in CLERK_KNOWN_HOOKS) {
|
|
1058
|
+
return `export const ${name} = () => ${CLERK_KNOWN_HOOKS[name]};`;
|
|
1059
|
+
}
|
|
1060
|
+
if (CLERK_CHILDREN_COMPONENTS.has(name)) {
|
|
1061
|
+
return `export const ${name} = __pass;`;
|
|
1062
|
+
}
|
|
1063
|
+
if (CLERK_NULL_COMPONENTS.has(name)) {
|
|
1064
|
+
return `export const ${name} = __null;`;
|
|
1065
|
+
}
|
|
1066
|
+
if (/^use[A-Z]/.test(name)) {
|
|
1067
|
+
return `export const ${name} = () => ({ isLoaded: true, isSignedIn: true });`;
|
|
1068
|
+
}
|
|
1069
|
+
if (/^[A-Z]/.test(name)) {
|
|
1070
|
+
return `export const ${name} = __pass;`;
|
|
1071
|
+
}
|
|
1072
|
+
return `export const ${name} = undefined;`;
|
|
1073
|
+
}
|
|
1074
|
+
function buildClerkMockSource(usedExports) {
|
|
1075
|
+
const names = Array.from(
|
|
1076
|
+
new Set(
|
|
1077
|
+
usedExports.filter(
|
|
1078
|
+
(n) => VALID_IDENT_RE2.test(n) && n !== "default" && !RESERVED_HELPER_NAMES.has(n)
|
|
1079
|
+
)
|
|
1080
|
+
)
|
|
1081
|
+
).sort();
|
|
1082
|
+
const lines = [
|
|
1083
|
+
...CLERK_PREAMBLE,
|
|
1084
|
+
"",
|
|
1085
|
+
...names.map(clerkExportLine),
|
|
1086
|
+
"",
|
|
1087
|
+
"// Rarely used, but `import Clerk from ...` must not fail: an inert object.",
|
|
1088
|
+
"export default {};",
|
|
1089
|
+
""
|
|
1090
|
+
];
|
|
1091
|
+
return lines.join("\n");
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/package-mock/package-mock.ts
|
|
1095
|
+
var PACKAGE_MOCK_PLUGIN_NAME = "onlook-package-mock";
|
|
1096
|
+
var PACKAGE_MOCK_VIRTUAL_PREFIX = "\0onlook-package-mock:";
|
|
1097
|
+
function stripQuery2(id) {
|
|
1098
|
+
const q = id.indexOf("?");
|
|
1099
|
+
return q === -1 ? id : id.slice(0, q);
|
|
1100
|
+
}
|
|
1101
|
+
function packageMockPlugin(options) {
|
|
1102
|
+
const entries = (options?.packages ?? []).filter(
|
|
1103
|
+
(e) => e && typeof e.package === "string" && e.package.length > 0 && // Only known auth packages get a functional mock — an unknown package
|
|
1104
|
+
// has no source builder, so silently drop it (honest skip) rather than
|
|
1105
|
+
// intercept it into a broken module.
|
|
1106
|
+
isKnownMockPackage(e.package)
|
|
1107
|
+
);
|
|
1108
|
+
if (entries.length === 0) return null;
|
|
1109
|
+
const usedByPackage = /* @__PURE__ */ new Map();
|
|
1110
|
+
for (const e of entries) usedByPackage.set(e.package, e.usedExports ?? []);
|
|
1111
|
+
return {
|
|
1112
|
+
name: PACKAGE_MOCK_PLUGIN_NAME,
|
|
1113
|
+
// `pre` so we intercept the specifier before Vite's resolve pipeline (and
|
|
1114
|
+
// before tsconfig-paths / node resolution) claims it.
|
|
1115
|
+
enforce: "pre",
|
|
1116
|
+
resolveId(source) {
|
|
1117
|
+
if (source.startsWith("\0")) return null;
|
|
1118
|
+
const cleaned = stripQuery2(source);
|
|
1119
|
+
if (!usedByPackage.has(cleaned)) return null;
|
|
1120
|
+
return PACKAGE_MOCK_VIRTUAL_PREFIX + cleaned;
|
|
1121
|
+
},
|
|
1122
|
+
load(id) {
|
|
1123
|
+
if (!id.startsWith(PACKAGE_MOCK_VIRTUAL_PREFIX)) return null;
|
|
1124
|
+
const pkg = id.slice(PACKAGE_MOCK_VIRTUAL_PREFIX.length);
|
|
1125
|
+
const used = usedByPackage.get(pkg);
|
|
1126
|
+
if (used === void 0) return null;
|
|
1127
|
+
return buildPackageMockSource(pkg, used);
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
|
|
860
1132
|
// src/screenshot-service/utils/console-logs/console-logs.ts
|
|
861
1133
|
async function captureConsoleLogs(storyId, theme = "light", storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const page = await context.newPage();
|
|
867
|
-
const logs = [];
|
|
868
|
-
const pageErrors = [];
|
|
869
|
-
const failedRequests = [];
|
|
870
|
-
page.on("console", (msg) => {
|
|
871
|
-
const level = msg.type();
|
|
872
|
-
logs.push({
|
|
873
|
-
level: level === "warning" ? "warn" : level,
|
|
874
|
-
text: msg.text(),
|
|
875
|
-
timestamp: Date.now()
|
|
1134
|
+
return captureSemaphore.run(async () => {
|
|
1135
|
+
const browser = await getBrowser();
|
|
1136
|
+
const context = await browser.newContext({
|
|
1137
|
+
viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
|
|
876
1138
|
});
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
pageErrors
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1139
|
+
const page = await context.newPage();
|
|
1140
|
+
const logs = [];
|
|
1141
|
+
const pageErrors = [];
|
|
1142
|
+
const failedRequests = [];
|
|
1143
|
+
page.on("console", (msg) => {
|
|
1144
|
+
const level = msg.type();
|
|
1145
|
+
logs.push({
|
|
1146
|
+
level: level === "warning" ? "warn" : level,
|
|
1147
|
+
text: msg.text(),
|
|
1148
|
+
timestamp: Date.now()
|
|
1149
|
+
});
|
|
1150
|
+
});
|
|
1151
|
+
page.on("pageerror", (err) => {
|
|
1152
|
+
pageErrors.push(err.message);
|
|
1153
|
+
});
|
|
1154
|
+
page.on("response", (res) => {
|
|
1155
|
+
const status = res.status();
|
|
1156
|
+
if (status >= 400) {
|
|
1157
|
+
const req = res.request();
|
|
1158
|
+
failedRequests.push({
|
|
1159
|
+
url: res.url(),
|
|
1160
|
+
status,
|
|
1161
|
+
method: req.method(),
|
|
1162
|
+
resourceType: req.resourceType(),
|
|
1163
|
+
failure: null
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
page.on("requestfailed", (req) => {
|
|
885
1168
|
failedRequests.push({
|
|
886
|
-
url:
|
|
887
|
-
status,
|
|
1169
|
+
url: req.url(),
|
|
1170
|
+
status: null,
|
|
888
1171
|
method: req.method(),
|
|
889
1172
|
resourceType: req.resourceType(),
|
|
890
|
-
failure:
|
|
1173
|
+
failure: req.failure()?.errorText ?? "request failed"
|
|
891
1174
|
});
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
page.on("requestfailed", (req) => {
|
|
895
|
-
failedRequests.push({
|
|
896
|
-
url: req.url(),
|
|
897
|
-
status: null,
|
|
898
|
-
method: req.method(),
|
|
899
|
-
resourceType: req.resourceType(),
|
|
900
|
-
failure: req.failure()?.errorText ?? "request failed"
|
|
901
1175
|
});
|
|
902
|
-
|
|
903
|
-
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
904
|
-
try {
|
|
905
|
-
await page.goto(url, { timeout: timeoutMs });
|
|
906
|
-
await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
|
|
907
|
-
await page.waitForLoadState("load", { timeout: timeoutMs });
|
|
1176
|
+
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
908
1177
|
try {
|
|
909
|
-
await page.
|
|
910
|
-
|
|
1178
|
+
await page.goto(url, { timeout: timeoutMs });
|
|
1179
|
+
await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
|
|
1180
|
+
await page.waitForLoadState("load", { timeout: timeoutMs });
|
|
1181
|
+
try {
|
|
1182
|
+
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
1183
|
+
} catch {
|
|
1184
|
+
}
|
|
1185
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1186
|
+
return { logs, pageErrors, failedRequests, url };
|
|
1187
|
+
} finally {
|
|
1188
|
+
await context.close();
|
|
911
1189
|
}
|
|
912
|
-
|
|
913
|
-
return { logs, pageErrors, failedRequests, url };
|
|
914
|
-
} finally {
|
|
915
|
-
await context.close();
|
|
916
|
-
}
|
|
1190
|
+
});
|
|
917
1191
|
}
|
|
918
1192
|
|
|
919
1193
|
// src/soft-story-rerender/soft-story-rerender.ts
|
|
@@ -985,8 +1259,8 @@ function softStoryRerenderPlugin() {
|
|
|
985
1259
|
preview.onStoriesChanged({ importFn: newModule.importFn });
|
|
986
1260
|
}
|
|
987
1261
|
});`;
|
|
988
|
-
const out = code.replace(pattern, (_match, quote,
|
|
989
|
-
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${
|
|
1262
|
+
const out = code.replace(pattern, (_match, quote, path9) => {
|
|
1263
|
+
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path9}${quote}`);
|
|
990
1264
|
});
|
|
991
1265
|
return out === code ? null : { code: out, map: null };
|
|
992
1266
|
}
|
|
@@ -997,14 +1271,14 @@ function resolveTsconfigAliases(root) {
|
|
|
997
1271
|
try {
|
|
998
1272
|
const config = readTsconfigAliasConfig(root);
|
|
999
1273
|
if (!config) return [];
|
|
1000
|
-
const base =
|
|
1274
|
+
const base = path8.resolve(root, config.baseUrl ?? ".");
|
|
1001
1275
|
const aliases = [];
|
|
1002
1276
|
for (const [key, targets] of Object.entries(config.paths)) {
|
|
1003
1277
|
const target = targets[0];
|
|
1004
1278
|
if (!target) continue;
|
|
1005
1279
|
if (key.endsWith("/*") && target.endsWith("/*")) {
|
|
1006
1280
|
const findPrefix = key.slice(0, -1);
|
|
1007
|
-
const targetDir =
|
|
1281
|
+
const targetDir = path8.resolve(base, target.slice(0, -2));
|
|
1008
1282
|
aliases.push({
|
|
1009
1283
|
find: new RegExp(`^${escapeRegExp(findPrefix)}`),
|
|
1010
1284
|
replacement: `${targetDir}/`
|
|
@@ -1012,7 +1286,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1012
1286
|
} else if (!key.includes("*") && !target.includes("*")) {
|
|
1013
1287
|
aliases.push({
|
|
1014
1288
|
find: new RegExp(`^${escapeRegExp(key)}$`),
|
|
1015
|
-
replacement:
|
|
1289
|
+
replacement: path8.resolve(base, target)
|
|
1016
1290
|
});
|
|
1017
1291
|
}
|
|
1018
1292
|
}
|
|
@@ -1023,7 +1297,7 @@ function resolveTsconfigAliases(root) {
|
|
|
1023
1297
|
}
|
|
1024
1298
|
function readTsconfigAliasConfig(root) {
|
|
1025
1299
|
for (const candidate of TSCONFIG_CANDIDATES) {
|
|
1026
|
-
const abs =
|
|
1300
|
+
const abs = path8.join(root, candidate);
|
|
1027
1301
|
let text;
|
|
1028
1302
|
try {
|
|
1029
1303
|
text = fs5.readFileSync(abs, "utf-8");
|
|
@@ -1104,6 +1378,130 @@ function findGitRoot(startPath) {
|
|
|
1104
1378
|
}
|
|
1105
1379
|
return null;
|
|
1106
1380
|
}
|
|
1381
|
+
function ifNoneMatchHits(header, etag) {
|
|
1382
|
+
if (!header) return false;
|
|
1383
|
+
return header.split(",").some((raw) => {
|
|
1384
|
+
const token = raw.trim();
|
|
1385
|
+
return token === etag || token === `W/${etag}`;
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
function decideIndexResponse(body, ifNoneMatch) {
|
|
1389
|
+
const etag = `"${createHash("sha1").update(body).digest("hex")}"`;
|
|
1390
|
+
return { etag, notModified: ifNoneMatchHits(ifNoneMatch, etag) };
|
|
1391
|
+
}
|
|
1392
|
+
var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
|
|
1393
|
+
function isStoryFile(file) {
|
|
1394
|
+
return STORY_FILE_PATTERN.test(file);
|
|
1395
|
+
}
|
|
1396
|
+
function missingFromIndex(index, files) {
|
|
1397
|
+
const importPaths = [];
|
|
1398
|
+
for (const entry of Object.values(index.entries ?? {})) {
|
|
1399
|
+
if (typeof entry.importPath === "string") importPaths.push(entry.importPath);
|
|
1400
|
+
}
|
|
1401
|
+
return files.filter((file) => {
|
|
1402
|
+
const base = path8.basename(file);
|
|
1403
|
+
return !importPaths.some((importPath) => importPath.endsWith(base));
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
function portFromArgv(argv) {
|
|
1407
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1408
|
+
const arg = argv[i];
|
|
1409
|
+
if (arg === void 0) continue;
|
|
1410
|
+
let raw;
|
|
1411
|
+
if (arg === "-p" || arg === "--port") {
|
|
1412
|
+
raw = argv[i + 1];
|
|
1413
|
+
} else {
|
|
1414
|
+
raw = /^(?:-p|--port)=(\d+)$/.exec(arg)?.[1];
|
|
1415
|
+
}
|
|
1416
|
+
if (raw === void 0) continue;
|
|
1417
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1418
|
+
if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) return parsed;
|
|
1419
|
+
}
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
async function defaultFetchIndex(port) {
|
|
1423
|
+
try {
|
|
1424
|
+
const res = await fetch(`http://localhost:${port}/index.json`, {
|
|
1425
|
+
signal: AbortSignal.timeout(2e3)
|
|
1426
|
+
});
|
|
1427
|
+
if (!res.ok) return null;
|
|
1428
|
+
return await res.json();
|
|
1429
|
+
} catch {
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1434
|
+
function createStoryIndexBroadcaster({
|
|
1435
|
+
ports,
|
|
1436
|
+
notify,
|
|
1437
|
+
fetchIndex = defaultFetchIndex,
|
|
1438
|
+
debounceMs = 500,
|
|
1439
|
+
probeIntervalMs = 500,
|
|
1440
|
+
probeCeilingMs = 15e3
|
|
1441
|
+
}) {
|
|
1442
|
+
const candidatePorts = [...new Set(ports)];
|
|
1443
|
+
const pendingAdds = /* @__PURE__ */ new Set();
|
|
1444
|
+
let addDebounce = null;
|
|
1445
|
+
let unlinkDebounce = null;
|
|
1446
|
+
let probing = false;
|
|
1447
|
+
const scheduleProbe = () => {
|
|
1448
|
+
if (addDebounce) clearTimeout(addDebounce);
|
|
1449
|
+
addDebounce = setTimeout(() => {
|
|
1450
|
+
addDebounce = null;
|
|
1451
|
+
void probeThenNotify();
|
|
1452
|
+
}, debounceMs);
|
|
1453
|
+
};
|
|
1454
|
+
const probeThenNotify = async () => {
|
|
1455
|
+
if (probing) return;
|
|
1456
|
+
probing = true;
|
|
1457
|
+
const files = Array.from(pendingAdds);
|
|
1458
|
+
const deadline = Date.now() + probeCeilingMs;
|
|
1459
|
+
try {
|
|
1460
|
+
if (files.length === 0) return;
|
|
1461
|
+
while (Date.now() < deadline) {
|
|
1462
|
+
for (const candidate of candidatePorts) {
|
|
1463
|
+
const index = await fetchIndex(candidate);
|
|
1464
|
+
if (index && missingFromIndex(index, files).length === 0) {
|
|
1465
|
+
console.log(
|
|
1466
|
+
"[STORYBOOK_PLUGIN] Index caught up with new story files \u2014 broadcasting",
|
|
1467
|
+
{ files, port: candidate }
|
|
1468
|
+
);
|
|
1469
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1470
|
+
notify();
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
await sleep(probeIntervalMs);
|
|
1475
|
+
}
|
|
1476
|
+
console.warn("[STORYBOOK_PLUGIN] Index probe hit ceiling \u2014 broadcasting anyway", {
|
|
1477
|
+
files,
|
|
1478
|
+
probeCeilingMs
|
|
1479
|
+
});
|
|
1480
|
+
for (const file of files) pendingAdds.delete(file);
|
|
1481
|
+
notify();
|
|
1482
|
+
} finally {
|
|
1483
|
+
probing = false;
|
|
1484
|
+
if (pendingAdds.size > 0) scheduleProbe();
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
return {
|
|
1488
|
+
onStoryFileAdded(file) {
|
|
1489
|
+
if (!isStoryFile(file)) return;
|
|
1490
|
+
pendingAdds.add(file);
|
|
1491
|
+
scheduleProbe();
|
|
1492
|
+
},
|
|
1493
|
+
onStoryFileRemoved(file) {
|
|
1494
|
+
if (!isStoryFile(file)) return;
|
|
1495
|
+
pendingAdds.delete(file);
|
|
1496
|
+
if (unlinkDebounce) clearTimeout(unlinkDebounce);
|
|
1497
|
+
unlinkDebounce = setTimeout(() => {
|
|
1498
|
+
unlinkDebounce = null;
|
|
1499
|
+
console.log("[STORYBOOK_PLUGIN] Story file removed \u2014 broadcasting", { file });
|
|
1500
|
+
notify();
|
|
1501
|
+
}, debounceMs);
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1107
1505
|
|
|
1108
1506
|
// src/storybook-onlook-plugin.ts
|
|
1109
1507
|
function errorCode(error) {
|
|
@@ -1170,7 +1568,7 @@ var TAILWIND_ENTRY_CANDIDATES = [
|
|
|
1170
1568
|
var TAILWIND_DIRECTIVE_PATTERN = /@import\s+['"](?:tailwindcss|tailwindcss\/.+)['"]|@tailwind\s+(?:base|components|utilities)/;
|
|
1171
1569
|
function detectTailwindEntryCss(cwd) {
|
|
1172
1570
|
for (const rel of TAILWIND_ENTRY_CANDIDATES) {
|
|
1173
|
-
const abs =
|
|
1571
|
+
const abs = path8.join(cwd, rel);
|
|
1174
1572
|
try {
|
|
1175
1573
|
if (!fs5.existsSync(abs)) continue;
|
|
1176
1574
|
const head = fs5.readFileSync(abs, "utf-8").slice(0, 2048);
|
|
@@ -1183,15 +1581,8 @@ function detectTailwindEntryCss(cwd) {
|
|
|
1183
1581
|
var serveMetadataAndScreenshots = (req, res, next) => {
|
|
1184
1582
|
if (req.url === "/onbook-index.json") {
|
|
1185
1583
|
console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
|
|
1186
|
-
const cacheBuster = Date.now();
|
|
1187
1584
|
console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
|
|
1188
|
-
fetch(
|
|
1189
|
-
cache: "no-store",
|
|
1190
|
-
headers: {
|
|
1191
|
-
"Cache-Control": "no-cache",
|
|
1192
|
-
Pragma: "no-cache"
|
|
1193
|
-
}
|
|
1194
|
-
}).then((response) => {
|
|
1585
|
+
fetch("http://localhost:6006/index.json").then((response) => {
|
|
1195
1586
|
console.log("[STORYBOOK_PLUGIN] Storybook index fetch response", {
|
|
1196
1587
|
status: response.status,
|
|
1197
1588
|
ok: response.ok,
|
|
@@ -1210,12 +1601,21 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1210
1601
|
entryCount: Object.keys(indexData.entries || {}).length,
|
|
1211
1602
|
hasMetadata: !!indexData.meta
|
|
1212
1603
|
});
|
|
1213
|
-
|
|
1604
|
+
const body = JSON.stringify(indexData);
|
|
1605
|
+
const { etag, notModified } = decideIndexResponse(
|
|
1606
|
+
body,
|
|
1607
|
+
req.headers["if-none-match"]
|
|
1608
|
+
);
|
|
1214
1609
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1215
|
-
res.setHeader("
|
|
1216
|
-
res.setHeader("
|
|
1217
|
-
|
|
1218
|
-
|
|
1610
|
+
res.setHeader("ETag", etag);
|
|
1611
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1612
|
+
if (notModified) {
|
|
1613
|
+
res.statusCode = 304;
|
|
1614
|
+
res.end();
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
res.setHeader("Content-Type", "application/json");
|
|
1618
|
+
res.end(body);
|
|
1219
1619
|
}).catch((error) => {
|
|
1220
1620
|
console.error("[STORYBOOK_PLUGIN] Failed to fetch/extend index.json", {
|
|
1221
1621
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -1307,7 +1707,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1307
1707
|
return;
|
|
1308
1708
|
}
|
|
1309
1709
|
if (req.url?.startsWith("/screenshots/")) {
|
|
1310
|
-
const screenshotPath =
|
|
1710
|
+
const screenshotPath = path8.join(
|
|
1311
1711
|
process.cwd(),
|
|
1312
1712
|
".storybook-cache",
|
|
1313
1713
|
req.url.replace("/screenshots/", "screenshots/")
|
|
@@ -1328,7 +1728,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1328
1728
|
`[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
|
|
1329
1729
|
);
|
|
1330
1730
|
captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
|
|
1331
|
-
const storyDir =
|
|
1731
|
+
const storyDir = path8.join(
|
|
1332
1732
|
process.cwd(),
|
|
1333
1733
|
".storybook-cache",
|
|
1334
1734
|
"screenshots",
|
|
@@ -1447,7 +1847,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1447
1847
|
configureServer(server) {
|
|
1448
1848
|
console.log("[STORYBOOK_PLUGIN] Configuring server middleware");
|
|
1449
1849
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1450
|
-
const cachedManifestPath =
|
|
1850
|
+
const cachedManifestPath = path8.join(
|
|
1451
1851
|
process.cwd(),
|
|
1452
1852
|
".storybook-cache",
|
|
1453
1853
|
"manifest.json"
|
|
@@ -1466,7 +1866,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1466
1866
|
server.watcher.on("change", scheduleRefresh);
|
|
1467
1867
|
const storyGlobs = options.storyGlobs ?? ["**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)"];
|
|
1468
1868
|
const resolvedStoryGlobs = storyGlobs.map(
|
|
1469
|
-
(glob) =>
|
|
1869
|
+
(glob) => path8.isAbsolute(glob) ? glob : path8.join(process.cwd(), glob)
|
|
1470
1870
|
);
|
|
1471
1871
|
if (resolvedStoryGlobs.length > 0) {
|
|
1472
1872
|
server.watcher.add(resolvedStoryGlobs);
|
|
@@ -1474,10 +1874,36 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1474
1874
|
storyGlobs: resolvedStoryGlobs
|
|
1475
1875
|
});
|
|
1476
1876
|
}
|
|
1877
|
+
const probeCandidatePorts = [
|
|
1878
|
+
...new Set(
|
|
1879
|
+
[portFromArgv(process.argv), port, server.config.server.port].filter(
|
|
1880
|
+
(p) => typeof p === "number"
|
|
1881
|
+
)
|
|
1882
|
+
)
|
|
1883
|
+
];
|
|
1884
|
+
console.log("[STORYBOOK_PLUGIN] Story add/unlink index broadcast enabled", {
|
|
1885
|
+
probeCandidatePorts
|
|
1886
|
+
});
|
|
1887
|
+
const indexBroadcaster = createStoryIndexBroadcaster({
|
|
1888
|
+
ports: probeCandidatePorts,
|
|
1889
|
+
notify: notifyOnbookIndexChanged
|
|
1890
|
+
});
|
|
1891
|
+
let indexBroadcastWatcherReady = false;
|
|
1892
|
+
server.watcher.once("ready", () => {
|
|
1893
|
+
indexBroadcastWatcherReady = true;
|
|
1894
|
+
});
|
|
1895
|
+
server.watcher.on("add", (file) => {
|
|
1896
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1897
|
+
indexBroadcaster.onStoryFileAdded(file);
|
|
1898
|
+
});
|
|
1899
|
+
server.watcher.on("unlink", (file) => {
|
|
1900
|
+
if (!indexBroadcastWatcherReady) return;
|
|
1901
|
+
indexBroadcaster.onStoryFileRemoved(file);
|
|
1902
|
+
});
|
|
1477
1903
|
const tailwindEntryPath = (() => {
|
|
1478
1904
|
if (options.tailwindEntryCss === false) return null;
|
|
1479
1905
|
if (typeof options.tailwindEntryCss === "string") {
|
|
1480
|
-
return
|
|
1906
|
+
return path8.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path8.join(process.cwd(), options.tailwindEntryCss);
|
|
1481
1907
|
}
|
|
1482
1908
|
return detectTailwindEntryCss(process.cwd());
|
|
1483
1909
|
})();
|
|
@@ -1493,7 +1919,7 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1493
1919
|
});
|
|
1494
1920
|
server.watcher.on("add", (file) => {
|
|
1495
1921
|
if (!/\.tsx?$/.test(file)) return;
|
|
1496
|
-
const dir =
|
|
1922
|
+
const dir = path8.dirname(file);
|
|
1497
1923
|
const isNewDir = !seenDirs.has(dir);
|
|
1498
1924
|
seenDirs.add(dir);
|
|
1499
1925
|
if (!watcherReady || !isNewDir) return;
|
|
@@ -1524,15 +1950,17 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1524
1950
|
configurePreviewServer(server) {
|
|
1525
1951
|
console.log("[STORYBOOK_PLUGIN] Configuring preview server middleware");
|
|
1526
1952
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1527
|
-
refreshManifest(
|
|
1953
|
+
refreshManifest(path8.join(process.cwd(), ".storybook-cache", "manifest.json"));
|
|
1528
1954
|
},
|
|
1529
1955
|
handleHotUpdate: handleStoryFileChange
|
|
1530
1956
|
};
|
|
1531
1957
|
const envContainment = envContainmentPlugin(options.envContainment);
|
|
1958
|
+
const packageMock = packageMockPlugin(options.packageMock);
|
|
1532
1959
|
const moduleLoadCatchAll = options.moduleLoadCatchAll === false ? null : moduleLoadCatchAllPlugin();
|
|
1533
1960
|
return withDedupeApply(
|
|
1534
1961
|
[
|
|
1535
1962
|
...envContainment ? [envContainment] : [],
|
|
1963
|
+
...packageMock ? [packageMock] : [],
|
|
1536
1964
|
...moduleLoadCatchAll ? [moduleLoadCatchAll] : [],
|
|
1537
1965
|
componentLocPlugin(),
|
|
1538
1966
|
mainPlugin,
|