@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/preset/index.js
CHANGED
|
@@ -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";
|
|
@@ -464,87 +510,89 @@ function getScreenshotPath(storyId, theme) {
|
|
|
464
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 {
|
|
@@ -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();
|
|
@@ -673,44 +753,43 @@ function getStoriesForFile(filePath) {
|
|
|
673
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
|
|
@@ -1100,6 +1374,17 @@ 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
|
+
}
|
|
1103
1388
|
var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
|
|
1104
1389
|
function isStoryFile(file) {
|
|
1105
1390
|
return STORY_FILE_PATTERN.test(file);
|
|
@@ -1132,8 +1417,7 @@ function portFromArgv(argv) {
|
|
|
1132
1417
|
}
|
|
1133
1418
|
async function defaultFetchIndex(port) {
|
|
1134
1419
|
try {
|
|
1135
|
-
const res = await fetch(`http://localhost:${port}/index.json
|
|
1136
|
-
cache: "no-store",
|
|
1420
|
+
const res = await fetch(`http://localhost:${port}/index.json`, {
|
|
1137
1421
|
signal: AbortSignal.timeout(2e3)
|
|
1138
1422
|
});
|
|
1139
1423
|
if (!res.ok) return null;
|
|
@@ -1293,15 +1577,8 @@ function detectTailwindEntryCss(cwd) {
|
|
|
1293
1577
|
var serveMetadataAndScreenshots = (req, res, next) => {
|
|
1294
1578
|
if (req.url === "/onbook-index.json") {
|
|
1295
1579
|
console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
|
|
1296
|
-
const cacheBuster = Date.now();
|
|
1297
1580
|
console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
|
|
1298
|
-
fetch(
|
|
1299
|
-
cache: "no-store",
|
|
1300
|
-
headers: {
|
|
1301
|
-
"Cache-Control": "no-cache",
|
|
1302
|
-
Pragma: "no-cache"
|
|
1303
|
-
}
|
|
1304
|
-
}).then((response) => {
|
|
1581
|
+
fetch("http://localhost:6006/index.json").then((response) => {
|
|
1305
1582
|
console.log("[STORYBOOK_PLUGIN] Storybook index fetch response", {
|
|
1306
1583
|
status: response.status,
|
|
1307
1584
|
ok: response.ok,
|
|
@@ -1320,12 +1597,21 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1320
1597
|
entryCount: Object.keys(indexData.entries || {}).length,
|
|
1321
1598
|
hasMetadata: !!indexData.meta
|
|
1322
1599
|
});
|
|
1323
|
-
|
|
1600
|
+
const body = JSON.stringify(indexData);
|
|
1601
|
+
const { etag, notModified } = decideIndexResponse(
|
|
1602
|
+
body,
|
|
1603
|
+
req.headers["if-none-match"]
|
|
1604
|
+
);
|
|
1324
1605
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1325
|
-
res.setHeader("
|
|
1326
|
-
res.setHeader("
|
|
1327
|
-
|
|
1328
|
-
|
|
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);
|
|
1329
1615
|
}).catch((error) => {
|
|
1330
1616
|
console.error("[STORYBOOK_PLUGIN] Failed to fetch/extend index.json", {
|
|
1331
1617
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -1665,10 +1951,12 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1665
1951
|
handleHotUpdate: handleStoryFileChange
|
|
1666
1952
|
};
|
|
1667
1953
|
const envContainment = envContainmentPlugin(options.envContainment);
|
|
1954
|
+
const packageMock = packageMockPlugin(options.packageMock);
|
|
1668
1955
|
const moduleLoadCatchAll = options.moduleLoadCatchAll === false ? null : moduleLoadCatchAllPlugin();
|
|
1669
1956
|
return withDedupeApply(
|
|
1670
1957
|
[
|
|
1671
1958
|
...envContainment ? [envContainment] : [],
|
|
1959
|
+
...packageMock ? [packageMock] : [],
|
|
1672
1960
|
...moduleLoadCatchAll ? [moduleLoadCatchAll] : [],
|
|
1673
1961
|
componentLocPlugin(),
|
|
1674
1962
|
mainPlugin,
|
|
@@ -1749,6 +2037,7 @@ var OPTION_KEY_MAP = {
|
|
|
1749
2037
|
storyGlobs: true,
|
|
1750
2038
|
tailwindEntryCss: true,
|
|
1751
2039
|
envContainment: true,
|
|
2040
|
+
packageMock: true,
|
|
1752
2041
|
moduleLoadCatchAll: true,
|
|
1753
2042
|
resolveAliases: true
|
|
1754
2043
|
};
|