@onlook/storybook-plugin 0.4.0-beta.16 → 0.4.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +124 -78
- package/dist/index.d.ts +1 -1
- package/dist/index.js +454 -166
- package/dist/preset/index.d.ts +1 -1
- package/dist/preset/index.js +455 -166
- package/dist/screenshot-service/index.js +124 -78
- package/dist/{storybook-onlook-plugin-B9Eo_OIq.d.ts → storybook-onlook-plugin-BvrvMhRF.d.ts} +23 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -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";
|
|
@@ -584,87 +630,89 @@ function getScreenshotPath(storyId, theme) {
|
|
|
584
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 {
|
|
@@ -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();
|
|
@@ -793,44 +873,43 @@ function getStoriesForFile(filePath) {
|
|
|
793
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
|
|
@@ -1104,6 +1378,17 @@ 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
|
+
}
|
|
1107
1392
|
var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
|
|
1108
1393
|
function isStoryFile(file) {
|
|
1109
1394
|
return STORY_FILE_PATTERN.test(file);
|
|
@@ -1136,8 +1421,7 @@ function portFromArgv(argv) {
|
|
|
1136
1421
|
}
|
|
1137
1422
|
async function defaultFetchIndex(port) {
|
|
1138
1423
|
try {
|
|
1139
|
-
const res = await fetch(`http://localhost:${port}/index.json
|
|
1140
|
-
cache: "no-store",
|
|
1424
|
+
const res = await fetch(`http://localhost:${port}/index.json`, {
|
|
1141
1425
|
signal: AbortSignal.timeout(2e3)
|
|
1142
1426
|
});
|
|
1143
1427
|
if (!res.ok) return null;
|
|
@@ -1297,15 +1581,8 @@ function detectTailwindEntryCss(cwd) {
|
|
|
1297
1581
|
var serveMetadataAndScreenshots = (req, res, next) => {
|
|
1298
1582
|
if (req.url === "/onbook-index.json") {
|
|
1299
1583
|
console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
|
|
1300
|
-
const cacheBuster = Date.now();
|
|
1301
1584
|
console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
|
|
1302
|
-
fetch(
|
|
1303
|
-
cache: "no-store",
|
|
1304
|
-
headers: {
|
|
1305
|
-
"Cache-Control": "no-cache",
|
|
1306
|
-
Pragma: "no-cache"
|
|
1307
|
-
}
|
|
1308
|
-
}).then((response) => {
|
|
1585
|
+
fetch("http://localhost:6006/index.json").then((response) => {
|
|
1309
1586
|
console.log("[STORYBOOK_PLUGIN] Storybook index fetch response", {
|
|
1310
1587
|
status: response.status,
|
|
1311
1588
|
ok: response.ok,
|
|
@@ -1324,12 +1601,21 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1324
1601
|
entryCount: Object.keys(indexData.entries || {}).length,
|
|
1325
1602
|
hasMetadata: !!indexData.meta
|
|
1326
1603
|
});
|
|
1327
|
-
|
|
1604
|
+
const body = JSON.stringify(indexData);
|
|
1605
|
+
const { etag, notModified } = decideIndexResponse(
|
|
1606
|
+
body,
|
|
1607
|
+
req.headers["if-none-match"]
|
|
1608
|
+
);
|
|
1328
1609
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1329
|
-
res.setHeader("
|
|
1330
|
-
res.setHeader("
|
|
1331
|
-
|
|
1332
|
-
|
|
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);
|
|
1333
1619
|
}).catch((error) => {
|
|
1334
1620
|
console.error("[STORYBOOK_PLUGIN] Failed to fetch/extend index.json", {
|
|
1335
1621
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -1669,10 +1955,12 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1669
1955
|
handleHotUpdate: handleStoryFileChange
|
|
1670
1956
|
};
|
|
1671
1957
|
const envContainment = envContainmentPlugin(options.envContainment);
|
|
1958
|
+
const packageMock = packageMockPlugin(options.packageMock);
|
|
1672
1959
|
const moduleLoadCatchAll = options.moduleLoadCatchAll === false ? null : moduleLoadCatchAllPlugin();
|
|
1673
1960
|
return withDedupeApply(
|
|
1674
1961
|
[
|
|
1675
1962
|
...envContainment ? [envContainment] : [],
|
|
1963
|
+
...packageMock ? [packageMock] : [],
|
|
1676
1964
|
...moduleLoadCatchAll ? [moduleLoadCatchAll] : [],
|
|
1677
1965
|
componentLocPlugin(),
|
|
1678
1966
|
mainPlugin,
|