@onlook/storybook-plugin 0.4.0-beta.15 → 0.4.0-beta.16

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'node:module';
2
- import path7, { dirname, join, relative } from 'path';
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(path7.normalize(absPath));
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 (path7.isAbsolute(cleaned)) {
99
+ if (path8.isAbsolute(cleaned)) {
100
100
  direct = cleaned;
101
101
  } else if (importer && (cleaned.startsWith("./") || cleaned.startsWith("../"))) {
102
- direct = path7.resolve(path7.dirname(stripQuery(importer)), cleaned);
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 ? path7.dirname(stripQuery(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 = path7.relative(root, filepath);
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 = path7.join(process.cwd(), ".storybook-cache");
391
- var SCREENSHOTS_DIR = path7.join(CACHE_DIR, "screenshots");
392
- var MANIFEST_PATH = path7.join(CACHE_DIR, "manifest.json");
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;
@@ -580,8 +580,8 @@ async function detectRenderState(page, opts = {}) {
580
580
  }
581
581
  }
582
582
  function getScreenshotPath(storyId, theme) {
583
- const storyDir = path7.join(SCREENSHOTS_DIR, storyId);
584
- return path7.join(storyDir, `${theme}.png`);
583
+ const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
584
+ return path8.join(storyDir, `${theme}.png`);
585
585
  }
586
586
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
587
587
  const browser = await getBrowser();
@@ -669,7 +669,7 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
669
669
  async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
670
670
  try {
671
671
  ensureCacheDirectories();
672
- const storyDir = path7.join(SCREENSHOTS_DIR, storyId);
672
+ const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
673
673
  if (!fs5.existsSync(storyDir)) {
674
674
  fs5.mkdirSync(storyDir, { recursive: true });
675
675
  }
@@ -790,7 +790,7 @@ async function fetchStorybookIndex() {
790
790
  }
791
791
  function getStoriesForFile(filePath) {
792
792
  if (!cachedIndex) return [];
793
- const fileName = path7.basename(filePath);
793
+ const fileName = path8.basename(filePath);
794
794
  return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
795
795
  }
796
796
  async function regenerateScreenshotsForFiles(files) {
@@ -985,8 +985,8 @@ function softStoryRerenderPlugin() {
985
985
  preview.onStoriesChanged({ importFn: newModule.importFn });
986
986
  }
987
987
  });`;
988
- const out = code.replace(pattern, (_match, quote, path8) => {
989
- return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path8}${quote}`);
988
+ const out = code.replace(pattern, (_match, quote, path9) => {
989
+ return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path9}${quote}`);
990
990
  });
991
991
  return out === code ? null : { code: out, map: null };
992
992
  }
@@ -997,14 +997,14 @@ function resolveTsconfigAliases(root) {
997
997
  try {
998
998
  const config = readTsconfigAliasConfig(root);
999
999
  if (!config) return [];
1000
- const base = path7.resolve(root, config.baseUrl ?? ".");
1000
+ const base = path8.resolve(root, config.baseUrl ?? ".");
1001
1001
  const aliases = [];
1002
1002
  for (const [key, targets] of Object.entries(config.paths)) {
1003
1003
  const target = targets[0];
1004
1004
  if (!target) continue;
1005
1005
  if (key.endsWith("/*") && target.endsWith("/*")) {
1006
1006
  const findPrefix = key.slice(0, -1);
1007
- const targetDir = path7.resolve(base, target.slice(0, -2));
1007
+ const targetDir = path8.resolve(base, target.slice(0, -2));
1008
1008
  aliases.push({
1009
1009
  find: new RegExp(`^${escapeRegExp(findPrefix)}`),
1010
1010
  replacement: `${targetDir}/`
@@ -1012,7 +1012,7 @@ function resolveTsconfigAliases(root) {
1012
1012
  } else if (!key.includes("*") && !target.includes("*")) {
1013
1013
  aliases.push({
1014
1014
  find: new RegExp(`^${escapeRegExp(key)}$`),
1015
- replacement: path7.resolve(base, target)
1015
+ replacement: path8.resolve(base, target)
1016
1016
  });
1017
1017
  }
1018
1018
  }
@@ -1023,7 +1023,7 @@ function resolveTsconfigAliases(root) {
1023
1023
  }
1024
1024
  function readTsconfigAliasConfig(root) {
1025
1025
  for (const candidate of TSCONFIG_CANDIDATES) {
1026
- const abs = path7.join(root, candidate);
1026
+ const abs = path8.join(root, candidate);
1027
1027
  let text;
1028
1028
  try {
1029
1029
  text = fs5.readFileSync(abs, "utf-8");
@@ -1104,6 +1104,120 @@ function findGitRoot(startPath) {
1104
1104
  }
1105
1105
  return null;
1106
1106
  }
1107
+ var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
1108
+ function isStoryFile(file) {
1109
+ return STORY_FILE_PATTERN.test(file);
1110
+ }
1111
+ function missingFromIndex(index, files) {
1112
+ const importPaths = [];
1113
+ for (const entry of Object.values(index.entries ?? {})) {
1114
+ if (typeof entry.importPath === "string") importPaths.push(entry.importPath);
1115
+ }
1116
+ return files.filter((file) => {
1117
+ const base = path8.basename(file);
1118
+ return !importPaths.some((importPath) => importPath.endsWith(base));
1119
+ });
1120
+ }
1121
+ function portFromArgv(argv) {
1122
+ for (let i = 0; i < argv.length; i += 1) {
1123
+ const arg = argv[i];
1124
+ if (arg === void 0) continue;
1125
+ let raw;
1126
+ if (arg === "-p" || arg === "--port") {
1127
+ raw = argv[i + 1];
1128
+ } else {
1129
+ raw = /^(?:-p|--port)=(\d+)$/.exec(arg)?.[1];
1130
+ }
1131
+ if (raw === void 0) continue;
1132
+ const parsed = Number.parseInt(raw, 10);
1133
+ if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) return parsed;
1134
+ }
1135
+ return null;
1136
+ }
1137
+ async function defaultFetchIndex(port) {
1138
+ try {
1139
+ const res = await fetch(`http://localhost:${port}/index.json?_t=${Date.now()}`, {
1140
+ cache: "no-store",
1141
+ signal: AbortSignal.timeout(2e3)
1142
+ });
1143
+ if (!res.ok) return null;
1144
+ return await res.json();
1145
+ } catch {
1146
+ return null;
1147
+ }
1148
+ }
1149
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1150
+ function createStoryIndexBroadcaster({
1151
+ ports,
1152
+ notify,
1153
+ fetchIndex = defaultFetchIndex,
1154
+ debounceMs = 500,
1155
+ probeIntervalMs = 500,
1156
+ probeCeilingMs = 15e3
1157
+ }) {
1158
+ const candidatePorts = [...new Set(ports)];
1159
+ const pendingAdds = /* @__PURE__ */ new Set();
1160
+ let addDebounce = null;
1161
+ let unlinkDebounce = null;
1162
+ let probing = false;
1163
+ const scheduleProbe = () => {
1164
+ if (addDebounce) clearTimeout(addDebounce);
1165
+ addDebounce = setTimeout(() => {
1166
+ addDebounce = null;
1167
+ void probeThenNotify();
1168
+ }, debounceMs);
1169
+ };
1170
+ const probeThenNotify = async () => {
1171
+ if (probing) return;
1172
+ probing = true;
1173
+ const files = Array.from(pendingAdds);
1174
+ const deadline = Date.now() + probeCeilingMs;
1175
+ try {
1176
+ if (files.length === 0) return;
1177
+ while (Date.now() < deadline) {
1178
+ for (const candidate of candidatePorts) {
1179
+ const index = await fetchIndex(candidate);
1180
+ if (index && missingFromIndex(index, files).length === 0) {
1181
+ console.log(
1182
+ "[STORYBOOK_PLUGIN] Index caught up with new story files \u2014 broadcasting",
1183
+ { files, port: candidate }
1184
+ );
1185
+ for (const file of files) pendingAdds.delete(file);
1186
+ notify();
1187
+ return;
1188
+ }
1189
+ }
1190
+ await sleep(probeIntervalMs);
1191
+ }
1192
+ console.warn("[STORYBOOK_PLUGIN] Index probe hit ceiling \u2014 broadcasting anyway", {
1193
+ files,
1194
+ probeCeilingMs
1195
+ });
1196
+ for (const file of files) pendingAdds.delete(file);
1197
+ notify();
1198
+ } finally {
1199
+ probing = false;
1200
+ if (pendingAdds.size > 0) scheduleProbe();
1201
+ }
1202
+ };
1203
+ return {
1204
+ onStoryFileAdded(file) {
1205
+ if (!isStoryFile(file)) return;
1206
+ pendingAdds.add(file);
1207
+ scheduleProbe();
1208
+ },
1209
+ onStoryFileRemoved(file) {
1210
+ if (!isStoryFile(file)) return;
1211
+ pendingAdds.delete(file);
1212
+ if (unlinkDebounce) clearTimeout(unlinkDebounce);
1213
+ unlinkDebounce = setTimeout(() => {
1214
+ unlinkDebounce = null;
1215
+ console.log("[STORYBOOK_PLUGIN] Story file removed \u2014 broadcasting", { file });
1216
+ notify();
1217
+ }, debounceMs);
1218
+ }
1219
+ };
1220
+ }
1107
1221
 
1108
1222
  // src/storybook-onlook-plugin.ts
1109
1223
  function errorCode(error) {
@@ -1170,7 +1284,7 @@ var TAILWIND_ENTRY_CANDIDATES = [
1170
1284
  var TAILWIND_DIRECTIVE_PATTERN = /@import\s+['"](?:tailwindcss|tailwindcss\/.+)['"]|@tailwind\s+(?:base|components|utilities)/;
1171
1285
  function detectTailwindEntryCss(cwd) {
1172
1286
  for (const rel of TAILWIND_ENTRY_CANDIDATES) {
1173
- const abs = path7.join(cwd, rel);
1287
+ const abs = path8.join(cwd, rel);
1174
1288
  try {
1175
1289
  if (!fs5.existsSync(abs)) continue;
1176
1290
  const head = fs5.readFileSync(abs, "utf-8").slice(0, 2048);
@@ -1307,7 +1421,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
1307
1421
  return;
1308
1422
  }
1309
1423
  if (req.url?.startsWith("/screenshots/")) {
1310
- const screenshotPath = path7.join(
1424
+ const screenshotPath = path8.join(
1311
1425
  process.cwd(),
1312
1426
  ".storybook-cache",
1313
1427
  req.url.replace("/screenshots/", "screenshots/")
@@ -1328,7 +1442,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
1328
1442
  `[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
1329
1443
  );
1330
1444
  captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
1331
- const storyDir = path7.join(
1445
+ const storyDir = path8.join(
1332
1446
  process.cwd(),
1333
1447
  ".storybook-cache",
1334
1448
  "screenshots",
@@ -1447,7 +1561,7 @@ function storybookOnlookPlugin(options = {}) {
1447
1561
  configureServer(server) {
1448
1562
  console.log("[STORYBOOK_PLUGIN] Configuring server middleware");
1449
1563
  server.middlewares.use(serveMetadataAndScreenshots);
1450
- const cachedManifestPath = path7.join(
1564
+ const cachedManifestPath = path8.join(
1451
1565
  process.cwd(),
1452
1566
  ".storybook-cache",
1453
1567
  "manifest.json"
@@ -1466,7 +1580,7 @@ function storybookOnlookPlugin(options = {}) {
1466
1580
  server.watcher.on("change", scheduleRefresh);
1467
1581
  const storyGlobs = options.storyGlobs ?? ["**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)"];
1468
1582
  const resolvedStoryGlobs = storyGlobs.map(
1469
- (glob) => path7.isAbsolute(glob) ? glob : path7.join(process.cwd(), glob)
1583
+ (glob) => path8.isAbsolute(glob) ? glob : path8.join(process.cwd(), glob)
1470
1584
  );
1471
1585
  if (resolvedStoryGlobs.length > 0) {
1472
1586
  server.watcher.add(resolvedStoryGlobs);
@@ -1474,10 +1588,36 @@ function storybookOnlookPlugin(options = {}) {
1474
1588
  storyGlobs: resolvedStoryGlobs
1475
1589
  });
1476
1590
  }
1591
+ const probeCandidatePorts = [
1592
+ ...new Set(
1593
+ [portFromArgv(process.argv), port, server.config.server.port].filter(
1594
+ (p) => typeof p === "number"
1595
+ )
1596
+ )
1597
+ ];
1598
+ console.log("[STORYBOOK_PLUGIN] Story add/unlink index broadcast enabled", {
1599
+ probeCandidatePorts
1600
+ });
1601
+ const indexBroadcaster = createStoryIndexBroadcaster({
1602
+ ports: probeCandidatePorts,
1603
+ notify: notifyOnbookIndexChanged
1604
+ });
1605
+ let indexBroadcastWatcherReady = false;
1606
+ server.watcher.once("ready", () => {
1607
+ indexBroadcastWatcherReady = true;
1608
+ });
1609
+ server.watcher.on("add", (file) => {
1610
+ if (!indexBroadcastWatcherReady) return;
1611
+ indexBroadcaster.onStoryFileAdded(file);
1612
+ });
1613
+ server.watcher.on("unlink", (file) => {
1614
+ if (!indexBroadcastWatcherReady) return;
1615
+ indexBroadcaster.onStoryFileRemoved(file);
1616
+ });
1477
1617
  const tailwindEntryPath = (() => {
1478
1618
  if (options.tailwindEntryCss === false) return null;
1479
1619
  if (typeof options.tailwindEntryCss === "string") {
1480
- return path7.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path7.join(process.cwd(), options.tailwindEntryCss);
1620
+ return path8.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path8.join(process.cwd(), options.tailwindEntryCss);
1481
1621
  }
1482
1622
  return detectTailwindEntryCss(process.cwd());
1483
1623
  })();
@@ -1493,7 +1633,7 @@ function storybookOnlookPlugin(options = {}) {
1493
1633
  });
1494
1634
  server.watcher.on("add", (file) => {
1495
1635
  if (!/\.tsx?$/.test(file)) return;
1496
- const dir = path7.dirname(file);
1636
+ const dir = path8.dirname(file);
1497
1637
  const isNewDir = !seenDirs.has(dir);
1498
1638
  seenDirs.add(dir);
1499
1639
  if (!watcherReady || !isNewDir) return;
@@ -1524,7 +1664,7 @@ function storybookOnlookPlugin(options = {}) {
1524
1664
  configurePreviewServer(server) {
1525
1665
  console.log("[STORYBOOK_PLUGIN] Configuring preview server middleware");
1526
1666
  server.middlewares.use(serveMetadataAndScreenshots);
1527
- refreshManifest(path7.join(process.cwd(), ".storybook-cache", "manifest.json"));
1667
+ refreshManifest(path8.join(process.cwd(), ".storybook-cache", "manifest.json"));
1528
1668
  },
1529
1669
  handleHotUpdate: handleStoryFileChange
1530
1670
  };
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import crypto, { createHash } from 'crypto';
3
3
  import fs5, { existsSync } from 'fs';
4
- import path7, { dirname, join, relative } from 'path';
4
+ import path8, { dirname, join, relative } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import generateModule from '@babel/generator';
7
7
  import { parse } from '@babel/parser';
@@ -45,7 +45,7 @@ function componentLocPlugin(options = {}) {
45
45
  sourceFilename: filepath
46
46
  });
47
47
  let mutated = false;
48
- const relativePath = path7.relative(root, filepath);
48
+ const relativePath = path8.relative(root, filepath);
49
49
  traverse(ast, {
50
50
  JSXElement(nodePath) {
51
51
  const opening = nodePath.node.openingElement;
@@ -117,7 +117,7 @@ function envContainmentPlugin(options) {
117
117
  const resolveOutcomes = /* @__PURE__ */ new Map();
118
118
  const roots = /* @__PURE__ */ new Set([normalizeSlashes(process.cwd())]);
119
119
  const matchTarget = (absPath) => {
120
- const norm = normalizeSlashes(path7.normalize(absPath));
120
+ const norm = normalizeSlashes(path8.normalize(absPath));
121
121
  for (const rel of byModule.keys()) {
122
122
  for (const root of roots) {
123
123
  const target = `${root}/${rel}`;
@@ -152,10 +152,10 @@ function envContainmentPlugin(options) {
152
152
  if (source.startsWith("\0")) return null;
153
153
  const cleaned = stripQuery(source);
154
154
  let direct = null;
155
- if (path7.isAbsolute(cleaned)) {
155
+ if (path8.isAbsolute(cleaned)) {
156
156
  direct = cleaned;
157
157
  } else if (importer && (cleaned.startsWith("./") || cleaned.startsWith("../"))) {
158
- direct = path7.resolve(path7.dirname(stripQuery(importer)), cleaned);
158
+ direct = path8.resolve(path8.dirname(stripQuery(importer)), cleaned);
159
159
  }
160
160
  if (direct) {
161
161
  const rel = matchTarget(direct);
@@ -164,7 +164,7 @@ function envContainmentPlugin(options) {
164
164
  }
165
165
  if (!specifierMightMatch(cleaned)) return null;
166
166
  if (typeof this.resolve !== "function") return null;
167
- const importerDir = importer ? path7.dirname(stripQuery(importer)) : "";
167
+ const importerDir = importer ? path8.dirname(stripQuery(importer)) : "";
168
168
  const outcomeKey = `${importerDir}|${source}`;
169
169
  const memoized = resolveOutcomes.get(outcomeKey);
170
170
  if (memoized !== void 0) return memoized;
@@ -267,9 +267,9 @@ function fastRefreshTolerantExportsPlugin() {
267
267
  }
268
268
  };
269
269
  }
270
- var CACHE_DIR = path7.join(process.cwd(), ".storybook-cache");
271
- var SCREENSHOTS_DIR = path7.join(CACHE_DIR, "screenshots");
272
- var MANIFEST_PATH = path7.join(CACHE_DIR, "manifest.json");
270
+ var CACHE_DIR = path8.join(process.cwd(), ".storybook-cache");
271
+ var SCREENSHOTS_DIR = path8.join(CACHE_DIR, "screenshots");
272
+ var MANIFEST_PATH = path8.join(CACHE_DIR, "manifest.json");
273
273
  var VIEWPORT_WIDTH = 1920;
274
274
  var VIEWPORT_HEIGHT = 1080;
275
275
  var MIN_COMPONENT_WIDTH = 420;
@@ -460,8 +460,8 @@ async function detectRenderState(page, opts = {}) {
460
460
  }
461
461
  }
462
462
  function getScreenshotPath(storyId, theme) {
463
- const storyDir = path7.join(SCREENSHOTS_DIR, storyId);
464
- return path7.join(storyDir, `${theme}.png`);
463
+ const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
464
+ return path8.join(storyDir, `${theme}.png`);
465
465
  }
466
466
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", loadTimeoutMs = 9e4, renderTimeoutMs = 2e4) {
467
467
  const browser = await getBrowser();
@@ -549,7 +549,7 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
549
549
  async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
550
550
  try {
551
551
  ensureCacheDirectories();
552
- const storyDir = path7.join(SCREENSHOTS_DIR, storyId);
552
+ const storyDir = path8.join(SCREENSHOTS_DIR, storyId);
553
553
  if (!fs5.existsSync(storyDir)) {
554
554
  fs5.mkdirSync(storyDir, { recursive: true });
555
555
  }
@@ -670,7 +670,7 @@ async function fetchStorybookIndex() {
670
670
  }
671
671
  function getStoriesForFile(filePath) {
672
672
  if (!cachedIndex) return [];
673
- const fileName = path7.basename(filePath);
673
+ const fileName = path8.basename(filePath);
674
674
  return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
675
675
  }
676
676
  async function regenerateScreenshotsForFiles(files) {
@@ -981,8 +981,8 @@ function softStoryRerenderPlugin() {
981
981
  preview.onStoriesChanged({ importFn: newModule.importFn });
982
982
  }
983
983
  });`;
984
- const out = code.replace(pattern, (_match, quote, path8) => {
985
- return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path8}${quote}`);
984
+ const out = code.replace(pattern, (_match, quote, path9) => {
985
+ return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path9}${quote}`);
986
986
  });
987
987
  return out === code ? null : { code: out, map: null };
988
988
  }
@@ -993,14 +993,14 @@ function resolveTsconfigAliases(root) {
993
993
  try {
994
994
  const config = readTsconfigAliasConfig(root);
995
995
  if (!config) return [];
996
- const base = path7.resolve(root, config.baseUrl ?? ".");
996
+ const base = path8.resolve(root, config.baseUrl ?? ".");
997
997
  const aliases = [];
998
998
  for (const [key, targets] of Object.entries(config.paths)) {
999
999
  const target = targets[0];
1000
1000
  if (!target) continue;
1001
1001
  if (key.endsWith("/*") && target.endsWith("/*")) {
1002
1002
  const findPrefix = key.slice(0, -1);
1003
- const targetDir = path7.resolve(base, target.slice(0, -2));
1003
+ const targetDir = path8.resolve(base, target.slice(0, -2));
1004
1004
  aliases.push({
1005
1005
  find: new RegExp(`^${escapeRegExp(findPrefix)}`),
1006
1006
  replacement: `${targetDir}/`
@@ -1008,7 +1008,7 @@ function resolveTsconfigAliases(root) {
1008
1008
  } else if (!key.includes("*") && !target.includes("*")) {
1009
1009
  aliases.push({
1010
1010
  find: new RegExp(`^${escapeRegExp(key)}$`),
1011
- replacement: path7.resolve(base, target)
1011
+ replacement: path8.resolve(base, target)
1012
1012
  });
1013
1013
  }
1014
1014
  }
@@ -1019,7 +1019,7 @@ function resolveTsconfigAliases(root) {
1019
1019
  }
1020
1020
  function readTsconfigAliasConfig(root) {
1021
1021
  for (const candidate of TSCONFIG_CANDIDATES) {
1022
- const abs = path7.join(root, candidate);
1022
+ const abs = path8.join(root, candidate);
1023
1023
  let text;
1024
1024
  try {
1025
1025
  text = fs5.readFileSync(abs, "utf-8");
@@ -1100,6 +1100,120 @@ function findGitRoot(startPath) {
1100
1100
  }
1101
1101
  return null;
1102
1102
  }
1103
+ var STORY_FILE_PATTERN = /\.stories\.(js|jsx|mjs|ts|tsx|mdx)$/;
1104
+ function isStoryFile(file) {
1105
+ return STORY_FILE_PATTERN.test(file);
1106
+ }
1107
+ function missingFromIndex(index, files) {
1108
+ const importPaths = [];
1109
+ for (const entry of Object.values(index.entries ?? {})) {
1110
+ if (typeof entry.importPath === "string") importPaths.push(entry.importPath);
1111
+ }
1112
+ return files.filter((file) => {
1113
+ const base = path8.basename(file);
1114
+ return !importPaths.some((importPath) => importPath.endsWith(base));
1115
+ });
1116
+ }
1117
+ function portFromArgv(argv) {
1118
+ for (let i = 0; i < argv.length; i += 1) {
1119
+ const arg = argv[i];
1120
+ if (arg === void 0) continue;
1121
+ let raw;
1122
+ if (arg === "-p" || arg === "--port") {
1123
+ raw = argv[i + 1];
1124
+ } else {
1125
+ raw = /^(?:-p|--port)=(\d+)$/.exec(arg)?.[1];
1126
+ }
1127
+ if (raw === void 0) continue;
1128
+ const parsed = Number.parseInt(raw, 10);
1129
+ if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) return parsed;
1130
+ }
1131
+ return null;
1132
+ }
1133
+ async function defaultFetchIndex(port) {
1134
+ try {
1135
+ const res = await fetch(`http://localhost:${port}/index.json?_t=${Date.now()}`, {
1136
+ cache: "no-store",
1137
+ signal: AbortSignal.timeout(2e3)
1138
+ });
1139
+ if (!res.ok) return null;
1140
+ return await res.json();
1141
+ } catch {
1142
+ return null;
1143
+ }
1144
+ }
1145
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1146
+ function createStoryIndexBroadcaster({
1147
+ ports,
1148
+ notify,
1149
+ fetchIndex = defaultFetchIndex,
1150
+ debounceMs = 500,
1151
+ probeIntervalMs = 500,
1152
+ probeCeilingMs = 15e3
1153
+ }) {
1154
+ const candidatePorts = [...new Set(ports)];
1155
+ const pendingAdds = /* @__PURE__ */ new Set();
1156
+ let addDebounce = null;
1157
+ let unlinkDebounce = null;
1158
+ let probing = false;
1159
+ const scheduleProbe = () => {
1160
+ if (addDebounce) clearTimeout(addDebounce);
1161
+ addDebounce = setTimeout(() => {
1162
+ addDebounce = null;
1163
+ void probeThenNotify();
1164
+ }, debounceMs);
1165
+ };
1166
+ const probeThenNotify = async () => {
1167
+ if (probing) return;
1168
+ probing = true;
1169
+ const files = Array.from(pendingAdds);
1170
+ const deadline = Date.now() + probeCeilingMs;
1171
+ try {
1172
+ if (files.length === 0) return;
1173
+ while (Date.now() < deadline) {
1174
+ for (const candidate of candidatePorts) {
1175
+ const index = await fetchIndex(candidate);
1176
+ if (index && missingFromIndex(index, files).length === 0) {
1177
+ console.log(
1178
+ "[STORYBOOK_PLUGIN] Index caught up with new story files \u2014 broadcasting",
1179
+ { files, port: candidate }
1180
+ );
1181
+ for (const file of files) pendingAdds.delete(file);
1182
+ notify();
1183
+ return;
1184
+ }
1185
+ }
1186
+ await sleep(probeIntervalMs);
1187
+ }
1188
+ console.warn("[STORYBOOK_PLUGIN] Index probe hit ceiling \u2014 broadcasting anyway", {
1189
+ files,
1190
+ probeCeilingMs
1191
+ });
1192
+ for (const file of files) pendingAdds.delete(file);
1193
+ notify();
1194
+ } finally {
1195
+ probing = false;
1196
+ if (pendingAdds.size > 0) scheduleProbe();
1197
+ }
1198
+ };
1199
+ return {
1200
+ onStoryFileAdded(file) {
1201
+ if (!isStoryFile(file)) return;
1202
+ pendingAdds.add(file);
1203
+ scheduleProbe();
1204
+ },
1205
+ onStoryFileRemoved(file) {
1206
+ if (!isStoryFile(file)) return;
1207
+ pendingAdds.delete(file);
1208
+ if (unlinkDebounce) clearTimeout(unlinkDebounce);
1209
+ unlinkDebounce = setTimeout(() => {
1210
+ unlinkDebounce = null;
1211
+ console.log("[STORYBOOK_PLUGIN] Story file removed \u2014 broadcasting", { file });
1212
+ notify();
1213
+ }, debounceMs);
1214
+ }
1215
+ };
1216
+ }
1103
1217
 
1104
1218
  // src/storybook-onlook-plugin.ts
1105
1219
  function errorCode(error) {
@@ -1166,7 +1280,7 @@ var TAILWIND_ENTRY_CANDIDATES = [
1166
1280
  var TAILWIND_DIRECTIVE_PATTERN = /@import\s+['"](?:tailwindcss|tailwindcss\/.+)['"]|@tailwind\s+(?:base|components|utilities)/;
1167
1281
  function detectTailwindEntryCss(cwd) {
1168
1282
  for (const rel of TAILWIND_ENTRY_CANDIDATES) {
1169
- const abs = path7.join(cwd, rel);
1283
+ const abs = path8.join(cwd, rel);
1170
1284
  try {
1171
1285
  if (!fs5.existsSync(abs)) continue;
1172
1286
  const head = fs5.readFileSync(abs, "utf-8").slice(0, 2048);
@@ -1303,7 +1417,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
1303
1417
  return;
1304
1418
  }
1305
1419
  if (req.url?.startsWith("/screenshots/")) {
1306
- const screenshotPath = path7.join(
1420
+ const screenshotPath = path8.join(
1307
1421
  process.cwd(),
1308
1422
  ".storybook-cache",
1309
1423
  req.url.replace("/screenshots/", "screenshots/")
@@ -1324,7 +1438,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
1324
1438
  `[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
1325
1439
  );
1326
1440
  captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
1327
- const storyDir = path7.join(
1441
+ const storyDir = path8.join(
1328
1442
  process.cwd(),
1329
1443
  ".storybook-cache",
1330
1444
  "screenshots",
@@ -1443,7 +1557,7 @@ function storybookOnlookPlugin(options = {}) {
1443
1557
  configureServer(server) {
1444
1558
  console.log("[STORYBOOK_PLUGIN] Configuring server middleware");
1445
1559
  server.middlewares.use(serveMetadataAndScreenshots);
1446
- const cachedManifestPath = path7.join(
1560
+ const cachedManifestPath = path8.join(
1447
1561
  process.cwd(),
1448
1562
  ".storybook-cache",
1449
1563
  "manifest.json"
@@ -1462,7 +1576,7 @@ function storybookOnlookPlugin(options = {}) {
1462
1576
  server.watcher.on("change", scheduleRefresh);
1463
1577
  const storyGlobs = options.storyGlobs ?? ["**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)"];
1464
1578
  const resolvedStoryGlobs = storyGlobs.map(
1465
- (glob) => path7.isAbsolute(glob) ? glob : path7.join(process.cwd(), glob)
1579
+ (glob) => path8.isAbsolute(glob) ? glob : path8.join(process.cwd(), glob)
1466
1580
  );
1467
1581
  if (resolvedStoryGlobs.length > 0) {
1468
1582
  server.watcher.add(resolvedStoryGlobs);
@@ -1470,10 +1584,36 @@ function storybookOnlookPlugin(options = {}) {
1470
1584
  storyGlobs: resolvedStoryGlobs
1471
1585
  });
1472
1586
  }
1587
+ const probeCandidatePorts = [
1588
+ ...new Set(
1589
+ [portFromArgv(process.argv), port, server.config.server.port].filter(
1590
+ (p) => typeof p === "number"
1591
+ )
1592
+ )
1593
+ ];
1594
+ console.log("[STORYBOOK_PLUGIN] Story add/unlink index broadcast enabled", {
1595
+ probeCandidatePorts
1596
+ });
1597
+ const indexBroadcaster = createStoryIndexBroadcaster({
1598
+ ports: probeCandidatePorts,
1599
+ notify: notifyOnbookIndexChanged
1600
+ });
1601
+ let indexBroadcastWatcherReady = false;
1602
+ server.watcher.once("ready", () => {
1603
+ indexBroadcastWatcherReady = true;
1604
+ });
1605
+ server.watcher.on("add", (file) => {
1606
+ if (!indexBroadcastWatcherReady) return;
1607
+ indexBroadcaster.onStoryFileAdded(file);
1608
+ });
1609
+ server.watcher.on("unlink", (file) => {
1610
+ if (!indexBroadcastWatcherReady) return;
1611
+ indexBroadcaster.onStoryFileRemoved(file);
1612
+ });
1473
1613
  const tailwindEntryPath = (() => {
1474
1614
  if (options.tailwindEntryCss === false) return null;
1475
1615
  if (typeof options.tailwindEntryCss === "string") {
1476
- return path7.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path7.join(process.cwd(), options.tailwindEntryCss);
1616
+ return path8.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path8.join(process.cwd(), options.tailwindEntryCss);
1477
1617
  }
1478
1618
  return detectTailwindEntryCss(process.cwd());
1479
1619
  })();
@@ -1489,7 +1629,7 @@ function storybookOnlookPlugin(options = {}) {
1489
1629
  });
1490
1630
  server.watcher.on("add", (file) => {
1491
1631
  if (!/\.tsx?$/.test(file)) return;
1492
- const dir = path7.dirname(file);
1632
+ const dir = path8.dirname(file);
1493
1633
  const isNewDir = !seenDirs.has(dir);
1494
1634
  seenDirs.add(dir);
1495
1635
  if (!watcherReady || !isNewDir) return;
@@ -1520,7 +1660,7 @@ function storybookOnlookPlugin(options = {}) {
1520
1660
  configurePreviewServer(server) {
1521
1661
  console.log("[STORYBOOK_PLUGIN] Configuring preview server middleware");
1522
1662
  server.middlewares.use(serveMetadataAndScreenshots);
1523
- refreshManifest(path7.join(process.cwd(), ".storybook-cache", "manifest.json"));
1663
+ refreshManifest(path8.join(process.cwd(), ".storybook-cache", "manifest.json"));
1524
1664
  },
1525
1665
  handleHotUpdate: handleStoryFileChange
1526
1666
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlook/storybook-plugin",
3
- "version": "0.4.0-beta.15",
3
+ "version": "0.4.0-beta.16",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "onlook-storybook": "./dist/cli/index.js"
@@ -52,7 +52,7 @@
52
52
  "storybook": "^10.1.11"
53
53
  },
54
54
  "devDependencies": {
55
- "@onbook/tsconfig": "0.1.0",
55
+ "@onbook/tsconfig": "workspace:*",
56
56
  "@types/babel__generator": "^7.6.8",
57
57
  "@types/babel__traverse": "^7.20.6",
58
58
  "@types/node": "^22.15.32",