@agimon-ai/po-style-system-tooling 0.2.14-alpha.2 → 0.2.14-alpha.21

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.
@@ -39,19 +39,36 @@ let storybook_internal_csf_tools = require("storybook/internal/csf-tools");
39
39
  let glob = require("glob");
40
40
  let zod = require("zod");
41
41
  let playwright = require("playwright");
42
- let sharp = require("sharp");
43
- sharp = __toESM(sharp, 1);
42
+ let node_module = require("node:module");
44
43
  let _tailwindcss_vite = require("@tailwindcss/vite");
45
44
  _tailwindcss_vite = __toESM(_tailwindcss_vite, 1);
46
45
  let vite_plugin_singlefile = require("vite-plugin-singlefile");
47
- let node_module = require("node:module");
48
46
  let node_url = require("node:url");
49
47
  let _vitejs_plugin_react = require("@vitejs/plugin-react");
50
48
  _vitejs_plugin_react = __toESM(_vitejs_plugin_react, 1);
51
- let node_child_process = require("node:child_process");
52
- let _agimon_ai_foundation_exec = require("@agimon-ai/foundation-exec");
53
49
  let _modelcontextprotocol_server = require("@modelcontextprotocol/server");
54
50
  let _modelcontextprotocol_server_stdio = require("@modelcontextprotocol/server/stdio");
51
+ //#region src/container/types.ts
52
+ const STYLE_SYSTEM_TYPES = {
53
+ ListThemesTool: Symbol.for("StyleSystem:ListThemesTool"),
54
+ GetCSSClassesTool: Symbol.for("StyleSystem:GetCSSClassesTool"),
55
+ GetComponentVisualTool: Symbol.for("StyleSystem:GetComponentVisualTool"),
56
+ ListSharedComponentsTool: Symbol.for("StyleSystem:ListSharedComponentsTool"),
57
+ ListAppComponentsTool: Symbol.for("StyleSystem:ListAppComponentsTool"),
58
+ ThemeServiceFactory: Symbol.for("StyleSystem:ThemeServiceFactory"),
59
+ CSSClassesServiceFactory: Symbol.for("StyleSystem:CSSClassesServiceFactory"),
60
+ StoriesIndexService: Symbol.for("StyleSystem:StoriesIndexService"),
61
+ GetUiComponentService: Symbol.for("StyleSystem:GetUiComponentService"),
62
+ AppComponentsService: Symbol.for("StyleSystem:AppComponentsService"),
63
+ StoriesIndexFactory: Symbol.for("StyleSystem:StoriesIndexFactory"),
64
+ RendererFactory: Symbol.for("StyleSystem:RendererFactory"),
65
+ StyleSystemConfigLoader: Symbol.for("StyleSystem:StyleSystemConfigLoader"),
66
+ WorkspaceRoot: Symbol.for("StyleSystem:WorkspaceRoot"),
67
+ DefaultThemePath: Symbol.for("StyleSystem:DefaultThemePath"),
68
+ GetUiComponentServiceConfig: Symbol.for("StyleSystem:GetUiComponentServiceConfig"),
69
+ AppComponentsServiceConfig: Symbol.for("StyleSystem:AppComponentsServiceConfig")
70
+ };
71
+ //#endregion
55
72
  //#region src/services/ThemeService/BaseThemeService.ts
56
73
  /**
57
74
  * BaseThemeService
@@ -122,12 +139,23 @@ const logger = (0, pino.default)({
122
139
  mkdir: true,
123
140
  sync: true
124
141
  }));
125
- const log = {
126
- debug: (msg, ...args) => logger.debug({ args }, msg),
127
- info: (msg, ...args) => logger.info({ args }, msg),
128
- warn: (msg, ...args) => logger.warn({ args }, msg),
129
- error: (msg, ...args) => logger.error({ args }, msg)
130
- };
142
+ function createLog(directory) {
143
+ const target = directory === void 0 ? logger : (0, pino.default)({
144
+ level: "debug",
145
+ timestamp: pino.default.stdTimeFunctions.isoTime
146
+ }, pino.default.destination({
147
+ dest: node_path.join(directory, "style-system.log"),
148
+ mkdir: true,
149
+ sync: true
150
+ }));
151
+ return {
152
+ debug: (msg, ...args) => target.debug({ args }, msg),
153
+ info: (msg, ...args) => target.info({ args }, msg),
154
+ warn: (msg, ...args) => target.warn({ args }, msg),
155
+ error: (msg, ...args) => target.error({ args }, msg)
156
+ };
157
+ }
158
+ const log = createLog();
131
159
  //#endregion
132
160
  //#region src/utils/fsHelpers.ts
133
161
  /** True when candidate is root itself or inside it (sibling prefixes like `<root>-evil` are outside). */
@@ -275,9 +303,9 @@ var CSSThemeService = class extends BaseThemeService {
275
303
  * Creates a new CSSThemeService instance
276
304
  * @param config - Theme service configuration with themePath or cssFiles
277
305
  */
278
- constructor(config) {
306
+ constructor(config, workspaceRoot) {
279
307
  super(config);
280
- this.monorepoRoot = TemplatesManagerService.getWorkspaceRootSync();
308
+ this.monorepoRoot = workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync();
281
309
  }
282
310
  /**
283
311
  * Get the source identifier for this service
@@ -393,6 +421,18 @@ const DEFAULT_THEME_SERVICE_CONFIG = {
393
421
  customServicePath: void 0
394
422
  };
395
423
  //#endregion
424
+ //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorateMetadata.js
425
+ function __decorateMetadata(k, v) {
426
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
427
+ }
428
+ //#endregion
429
+ //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorateParam.js
430
+ function __decorateParam(paramIndex, decorator) {
431
+ return function(target, key) {
432
+ decorator(target, key, paramIndex);
433
+ };
434
+ }
435
+ //#endregion
396
436
  //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorate.js
397
437
  function __decorate(decorators, target, key, desc) {
398
438
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -419,6 +459,10 @@ function __decorate(decorators, target, key, desc) {
419
459
  * - Hardcoded service paths
420
460
  */
421
461
  let ThemeServiceFactory = class ThemeServiceFactory {
462
+ workspaceRoot;
463
+ constructor(workspaceRoot) {
464
+ this.workspaceRoot = workspaceRoot;
465
+ }
422
466
  /**
423
467
  * Create a theme service based on configuration.
424
468
  *
@@ -436,7 +480,7 @@ let ThemeServiceFactory = class ThemeServiceFactory {
436
480
  ...config
437
481
  };
438
482
  if (resolvedConfig.customServicePath) return this.loadCustomService(resolvedConfig);
439
- return new CSSThemeService(resolvedConfig);
483
+ return new CSSThemeService(resolvedConfig, this.workspaceRoot);
440
484
  }
441
485
  /**
442
486
  * Load a custom theme service from user-specified path.
@@ -463,7 +507,12 @@ let ThemeServiceFactory = class ThemeServiceFactory {
463
507
  }
464
508
  }
465
509
  };
466
- ThemeServiceFactory = __decorate([(0, inversify.injectable)()], ThemeServiceFactory);
510
+ ThemeServiceFactory = __decorate([
511
+ (0, inversify.injectable)(),
512
+ __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.WorkspaceRoot)),
513
+ __decorateParam(0, (0, inversify.optional)()),
514
+ __decorateMetadata("design:paramtypes", [String])
515
+ ], ThemeServiceFactory);
467
516
  //#endregion
468
517
  //#region src/services/CssClasses/BaseCSSClassesService.ts
469
518
  /**
@@ -791,6 +840,10 @@ const VALID_SERVICE_EXTENSIONS$1 = [
791
840
  ".cjs"
792
841
  ];
793
842
  let CSSClassesServiceFactory = class CSSClassesServiceFactory {
843
+ workspaceRoot;
844
+ constructor(workspaceRoot) {
845
+ this.workspaceRoot = workspaceRoot;
846
+ }
794
847
  /**
795
848
  * Create a CSS classes service based on configuration.
796
849
  *
@@ -831,7 +884,7 @@ let CSSClassesServiceFactory = class CSSClassesServiceFactory {
831
884
  async loadCustomService(config) {
832
885
  const servicePath = config.customServicePath;
833
886
  if (!servicePath) throw new Error("customServicePath is required for custom service loading");
834
- const monorepoRoot = TemplatesManagerService.getWorkspaceRootSync();
887
+ const monorepoRoot = this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync();
835
888
  const resolvedPath = node_path.default.resolve(monorepoRoot, servicePath);
836
889
  const normalizedWorkspaceRoot = node_path.default.resolve(monorepoRoot);
837
890
  if (!resolvedPath.startsWith(normalizedWorkspaceRoot + node_path.default.sep)) throw new Error(`Security error: customServicePath "${servicePath}" resolves outside workspace root`);
@@ -852,12 +905,12 @@ let CSSClassesServiceFactory = class CSSClassesServiceFactory {
852
905
  }
853
906
  }
854
907
  };
855
- CSSClassesServiceFactory = __decorate([(0, inversify.injectable)()], CSSClassesServiceFactory);
856
- //#endregion
857
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorateMetadata.js
858
- function __decorateMetadata(k, v) {
859
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
860
- }
908
+ CSSClassesServiceFactory = __decorate([
909
+ (0, inversify.injectable)(),
910
+ __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.WorkspaceRoot)),
911
+ __decorateParam(0, (0, inversify.optional)()),
912
+ __decorateMetadata("design:paramtypes", [String])
913
+ ], CSSClassesServiceFactory);
861
914
  //#endregion
862
915
  //#region src/services/StoriesIndexService/StoriesIndexService.ts
863
916
  /**
@@ -912,6 +965,7 @@ let StoriesIndexService = class StoriesIndexService {
912
965
  */
913
966
  async initialize() {
914
967
  if (this.initialized && this.lastInitResult) return this.lastInitResult;
968
+ if (this.config.workspaceRoot) this.monorepoRoot = await node_fs.promises.realpath(this.monorepoRoot);
915
969
  log.info("[StoriesIndexService] Initializing story index...");
916
970
  const storyFiles = await this.findStoryFiles();
917
971
  log.info(`[StoriesIndexService] Found ${storyFiles.length} story files`);
@@ -983,6 +1037,7 @@ let StoriesIndexService = class StoriesIndexService {
983
1037
  * parse is shared by content hash across service instances and concurrent rules.
984
1038
  */
985
1039
  async indexStoryFile(filePath) {
1040
+ await this.assertContainedStory(filePath);
986
1041
  const content = await node_fs.promises.readFile(filePath, "utf-8");
987
1042
  const fileHash = this.hashContent(content);
988
1043
  const cached = storyParseCache.get(filePath);
@@ -1009,12 +1064,19 @@ let StoriesIndexService = class StoriesIndexService {
1009
1064
  });
1010
1065
  if (component) this.storeComponent(component);
1011
1066
  }
1067
+ async assertContainedStory(filePath) {
1068
+ if (!this.config.workspaceRoot) return;
1069
+ const [root, story] = await Promise.all([node_fs.promises.realpath(this.monorepoRoot), node_fs.promises.realpath(filePath)]);
1070
+ if (!isContained(root, story)) throw new Error("Story must remain inside the configured workspace root.");
1071
+ }
1012
1072
  storeComponent(component) {
1013
1073
  this.componentIndex.set(component.title, component);
1014
1074
  this.componentFileIndex.set(node_path.default.resolve(component.filePath), component);
1015
1075
  }
1016
1076
  parseStoryFile(filePath, content, fileHash) {
1017
- const csf = (0, storybook_internal_csf_tools.loadCsf)(content, {
1077
+ const alias = /^export\s+default\s+([A-Za-z_$][\w$]*)\s*;?\s*$/m.exec(content)?.[1];
1078
+ const parsable = alias ? content.replace(new RegExp(`^export\\s+(const|let|var)\\s+(${alias.replaceAll("$", "\\$")})\\b`, "m"), "$1 $2") : content;
1079
+ const csf = (0, storybook_internal_csf_tools.loadCsf)(parsable, {
1018
1080
  fileName: filePath,
1019
1081
  makeTitle: (title) => title
1020
1082
  });
@@ -1108,6 +1170,7 @@ let StoriesIndexService = class StoriesIndexService {
1108
1170
  * @returns True if file was updated, false if unchanged
1109
1171
  */
1110
1172
  async refreshFile(filePath) {
1173
+ await this.assertContainedStory(filePath);
1111
1174
  const content = await node_fs.promises.readFile(filePath, "utf-8");
1112
1175
  const newHash = this.hashContent(content);
1113
1176
  const existingComponent = Array.from(this.componentIndex.values()).find((c) => c.filePath === filePath);
@@ -1147,27 +1210,6 @@ let StoriesIndexService = class StoriesIndexService {
1147
1210
  };
1148
1211
  StoriesIndexService = __decorate([(0, inversify.injectable)(), __decorateMetadata("design:paramtypes", [Object])], StoriesIndexService);
1149
1212
  //#endregion
1150
- //#region src/container/types.ts
1151
- const STYLE_SYSTEM_TYPES = {
1152
- ListThemesTool: Symbol.for("StyleSystem:ListThemesTool"),
1153
- GetCSSClassesTool: Symbol.for("StyleSystem:GetCSSClassesTool"),
1154
- GetComponentVisualTool: Symbol.for("StyleSystem:GetComponentVisualTool"),
1155
- ListSharedComponentsTool: Symbol.for("StyleSystem:ListSharedComponentsTool"),
1156
- ListAppComponentsTool: Symbol.for("StyleSystem:ListAppComponentsTool"),
1157
- PolishComponentTool: Symbol.for("StyleSystem:PolishComponentTool"),
1158
- ThemeServiceFactory: Symbol.for("StyleSystem:ThemeServiceFactory"),
1159
- CSSClassesServiceFactory: Symbol.for("StyleSystem:CSSClassesServiceFactory"),
1160
- StoriesIndexService: Symbol.for("StyleSystem:StoriesIndexService"),
1161
- GetUiComponentService: Symbol.for("StyleSystem:GetUiComponentService"),
1162
- AppComponentsService: Symbol.for("StyleSystem:AppComponentsService"),
1163
- StoriesIndexFactory: Symbol.for("StyleSystem:StoriesIndexFactory"),
1164
- RendererFactory: Symbol.for("StyleSystem:RendererFactory"),
1165
- StyleSystemConfigLoader: Symbol.for("StyleSystem:StyleSystemConfigLoader"),
1166
- DefaultThemePath: Symbol.for("StyleSystem:DefaultThemePath"),
1167
- GetUiComponentServiceConfig: Symbol.for("StyleSystem:GetUiComponentServiceConfig"),
1168
- AppComponentsServiceConfig: Symbol.for("StyleSystem:AppComponentsServiceConfig")
1169
- };
1170
- //#endregion
1171
1213
  //#region src/config/types.ts
1172
1214
  /**
1173
1215
  * Style system configuration types.
@@ -1333,9 +1375,9 @@ const EXPO_APP_CONFIG_FILE = "app.json";
1333
1375
  * values, and warns when it is the one that resolves.
1334
1376
  */
1335
1377
  async function resolveStyleSystemThemePath(input) {
1336
- const hostRoot = TemplatesManagerService.getWorkspaceRootSync();
1378
+ const hostRoot = input.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync();
1337
1379
  const resolvedAppPath = input.appPath ? node_path.default.isAbsolute(input.appPath) ? node_path.default.normalize(input.appPath) : node_path.default.join(hostRoot, input.appPath) : hostRoot;
1338
- const monorepoRoot = input.appPath ? TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath) : hostRoot;
1380
+ const monorepoRoot = input.workspaceRoot ?? (input.appPath ? TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath) : hostRoot);
1339
1381
  const workspaceCandidate = node_path.default.join(monorepoRoot, input.themePath);
1340
1382
  const candidates = node_path.default.isAbsolute(input.themePath) ? [input.themePath] : [workspaceCandidate, node_path.default.resolve(resolvedAppPath, input.themePath)];
1341
1383
  const uniqueCandidates = [...new Set(candidates)];
@@ -1385,19 +1427,23 @@ function isNotFound(error) {
1385
1427
  return error instanceof Error && "code" in error && error.code === "ENOENT";
1386
1428
  }
1387
1429
  let StyleSystemConfigLoader = class StyleSystemConfigLoader {
1430
+ workspaceRoot;
1388
1431
  /**
1389
1432
  * Promises rather than values, so concurrent resolves de-duplicate. Several
1390
1433
  * callers hit config from inside Promise.all/allSettled.
1391
1434
  */
1392
1435
  rootPromises = /* @__PURE__ */ new Map();
1393
1436
  projectCache = /* @__PURE__ */ new Map();
1437
+ constructor(workspaceRoot) {
1438
+ this.workspaceRoot = workspaceRoot;
1439
+ }
1394
1440
  /** Drop all cached config state. */
1395
1441
  invalidate() {
1396
1442
  this.rootPromises.clear();
1397
1443
  this.projectCache.clear();
1398
1444
  }
1399
1445
  /** Load and cache the workspace root config. */
1400
- async loadRoot(workspaceRoot = TemplatesManagerService.getWorkspaceRootSync()) {
1446
+ async loadRoot(workspaceRoot = this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync()) {
1401
1447
  let pending = this.rootPromises.get(workspaceRoot);
1402
1448
  if (!pending) {
1403
1449
  pending = this.readRoot(workspaceRoot);
@@ -1413,8 +1459,8 @@ let StyleSystemConfigLoader = class StyleSystemConfigLoader {
1413
1459
  /** Resolve the merged configuration for a project. */
1414
1460
  async resolveProject(appPath) {
1415
1461
  if (!appPath || typeof appPath !== "string") throw new Error("appPath is required and must be a non-empty string");
1416
- const resolvedAppPath = node_path.default.isAbsolute(appPath) ? node_path.default.normalize(appPath) : node_path.default.join(TemplatesManagerService.getWorkspaceRootSync(), appPath);
1417
- const workspaceRoot = TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath);
1462
+ const resolvedAppPath = node_path.default.isAbsolute(appPath) ? node_path.default.normalize(appPath) : node_path.default.join(this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync(), appPath);
1463
+ const workspaceRoot = this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath);
1418
1464
  const cached = this.projectCache.get(resolvedAppPath);
1419
1465
  if (cached) return cached;
1420
1466
  const pending = this.readProject(resolvedAppPath, workspaceRoot);
@@ -1481,7 +1527,12 @@ let StyleSystemConfigLoader = class StyleSystemConfigLoader {
1481
1527
  };
1482
1528
  }
1483
1529
  };
1484
- StyleSystemConfigLoader = __decorate([(0, inversify.injectable)()], StyleSystemConfigLoader);
1530
+ StyleSystemConfigLoader = __decorate([
1531
+ (0, inversify.injectable)(),
1532
+ __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.WorkspaceRoot)),
1533
+ __decorateParam(0, (0, inversify.optional)()),
1534
+ __decorateMetadata("design:paramtypes", [String])
1535
+ ], StyleSystemConfigLoader);
1485
1536
  //#endregion
1486
1537
  //#region src/config/index.ts
1487
1538
  /**
@@ -1579,6 +1630,17 @@ const browserLaunchConfigs = [
1579
1630
  ];
1580
1631
  const STORY_ROOT_SELECTOR = "#root";
1581
1632
  const VITE_ERROR_OVERLAY_SELECTOR = "vite-error-overlay";
1633
+ const STORY_MOUNT_TIMEOUT_MS = 1e4;
1634
+ /**
1635
+ * Runs in the page (this package has no DOM types): resolves once web fonts and every image the
1636
+ * story rendered have loaded (or failed), then two frames have painted.
1637
+ */
1638
+ const FONTS_IMAGES_AND_TWO_FRAMES = `Promise.all([
1639
+ document.fonts.ready,
1640
+ ...Array.from(document.images, (image) =>
1641
+ image.complete ? null : new Promise((resolve) => { image.addEventListener('load', resolve, { once: true }); image.addEventListener('error', resolve, { once: true }); }),
1642
+ ),
1643
+ ]).then(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))))`;
1582
1644
  /**
1583
1645
  * Attempts to launch a browser, trying multiple configurations in order of preference.
1584
1646
  * @returns Launched browser instance
@@ -1600,26 +1662,39 @@ async function launchBrowserWithFallback() {
1600
1662
  throw new Error(`No browser available. Tried:\n${errors.join("\n")}\n\nPlease install Chrome, or run 'npx playwright install chromium'`);
1601
1663
  }
1602
1664
  async function takeScreenshot(options) {
1603
- const { url, output, width = 1280, height = 800, fullPage = false, browser: browserName = "chromium", waitTime = 1e3, darkMode = false, mobile = false, generateThumbnail = false, thumbnailWidth = 400, thumbnailQuality = 80, base64 = false } = options;
1665
+ const { url, output, width = 1280, height = 800, fullPage = false, browser: browserName = "chromium", waitTime = 1e3, darkMode = false, mobile = false, generateThumbnail = false, thumbnailWidth = 400, thumbnailQuality = 80, base64 = false, sharedBrowser } = options;
1604
1666
  let browser = null;
1667
+ let context = null;
1668
+ let release;
1605
1669
  try {
1606
- if (browserName === "chromium") browser = await launchBrowserWithFallback();
1670
+ if (sharedBrowser) {
1671
+ const borrowed = await sharedBrowser();
1672
+ if ("browser" in borrowed) {
1673
+ browser = borrowed.browser;
1674
+ release = () => borrowed.release();
1675
+ } else browser = borrowed;
1676
+ } else if (browserName === "chromium") browser = await launchBrowserWithFallback();
1607
1677
  else {
1608
1678
  const browserType = browsers[browserName];
1609
1679
  if (!browserType) throw new Error(`Unsupported browser: ${browserName}`);
1610
1680
  browser = await browserType.launch({ headless: true });
1611
1681
  }
1612
- const page = await (await browser.newContext({
1682
+ context = await browser.newContext({
1613
1683
  viewport: {
1614
1684
  width,
1615
1685
  height
1616
1686
  },
1617
1687
  colorScheme: darkMode ? "dark" : "light",
1618
1688
  isMobile: mobile
1619
- })).newPage();
1689
+ });
1690
+ const page = await context.newPage();
1620
1691
  const runtimeErrors = [];
1621
- page.on("pageerror", (error) => runtimeErrors.push(error.stack ?? error.message));
1622
- await page.goto(url, { waitUntil: "networkidle" });
1692
+ page.on("pageerror", (error) => runtimeErrors.push(error.stack || error.message || String(error)));
1693
+ await page.goto(url, { waitUntil: "load" });
1694
+ if (await page.waitForSelector(`${STORY_ROOT_SELECTOR} > *, ${VITE_ERROR_OVERLAY_SELECTOR}`, {
1695
+ state: "attached",
1696
+ timeout: STORY_MOUNT_TIMEOUT_MS
1697
+ }).then(() => true).catch(() => false)) await page.evaluate(FONTS_IMAGES_AND_TWO_FRAMES);
1623
1698
  if (waitTime > 0) await page.waitForTimeout(waitTime);
1624
1699
  const hasViteErrorOverlay = await page.locator(VITE_ERROR_OVERLAY_SELECTOR).count();
1625
1700
  const renderedChildCount = await page.locator(`${STORY_ROOT_SELECTOR} > *`).count();
@@ -1631,7 +1706,27 @@ async function takeScreenshot(options) {
1631
1706
  path: output,
1632
1707
  fullPage
1633
1708
  });
1634
- const screenshotMetadata = await (0, sharp.default)(screenshotBuffer).metadata();
1709
+ const screenshotMetadata = await page.evaluate(`(async () => {
1710
+ const bytes = Uint8Array.from(atob(${JSON.stringify(screenshotBuffer.toString("base64"))}), (char) => char.charCodeAt(0));
1711
+ const image = await createImageBitmap(new Blob([bytes]));
1712
+ try {
1713
+ const result = { width: image.width, height: image.height };
1714
+ if (${generateThumbnail}) {
1715
+ const width = ${JSON.stringify(thumbnailWidth)};
1716
+ const quality = ${JSON.stringify(thumbnailQuality)};
1717
+ if (!Number.isSafeInteger(width) || width <= 0 || !Number.isInteger(quality) || quality < 1 || quality > 100)
1718
+ throw new Error('Invalid thumbnail width or quality.');
1719
+ const canvas = document.createElement('canvas');
1720
+ canvas.width = width;
1721
+ canvas.height = Math.max(1, Math.round(image.height * width / image.width));
1722
+ canvas.getContext('2d').drawImage(image, 0, 0, canvas.width, canvas.height);
1723
+ result.thumbnail = canvas.toDataURL('image/jpeg', quality / 100).split(',')[1];
1724
+ }
1725
+ return result;
1726
+ } finally {
1727
+ image.close();
1728
+ }
1729
+ })()`);
1635
1730
  const result = {
1636
1731
  imagePath: output,
1637
1732
  width: screenshotMetadata.width ?? width,
@@ -1640,13 +1735,18 @@ async function takeScreenshot(options) {
1640
1735
  if (generateThumbnail) {
1641
1736
  const ext = node_path.default.extname(output);
1642
1737
  const thumbnailPath = output.replace(ext, `-thumb${ext}`);
1643
- await (0, sharp.default)(screenshotBuffer).resize(thumbnailWidth).jpeg({ quality: thumbnailQuality }).toFile(thumbnailPath.replace(ext, ".jpg"));
1738
+ await (0, node_fs_promises.writeFile)(thumbnailPath.replace(ext, ".jpg"), Buffer.from(screenshotMetadata.thumbnail, "base64"));
1644
1739
  result.thumbnailPath = thumbnailPath.replace(ext, ".jpg");
1645
1740
  }
1646
1741
  if (base64) result.base64 = screenshotBuffer.toString("base64");
1647
1742
  return result;
1648
1743
  } finally {
1649
- if (browser) await browser.close();
1744
+ try {
1745
+ if (sharedBrowser) await context?.close();
1746
+ else if (browser) await browser.close();
1747
+ } finally {
1748
+ release?.();
1749
+ }
1650
1750
  }
1651
1751
  }
1652
1752
  //#endregion
@@ -1896,6 +1996,37 @@ function createStoryEntryPlugin(getStoryConfig, generateCode) {
1896
1996
  }
1897
1997
  };
1898
1998
  }
1999
+ /** Where a package the app depends on is installed, resolved from the app rather than from this tool. */
2000
+ function appPackageDir(appPath, packageName) {
2001
+ return node_path.default.dirname((0, node_module.createRequire)(node_path.default.join(appPath, "package.json")).resolve(`${packageName}/package.json`));
2002
+ }
2003
+ /**
2004
+ * React and react-dom are peers of this tool: every story bundle takes the app's own pair, so the
2005
+ * story, the generated entry and any workspace package it imports share one React that matches its
2006
+ * react-dom. A pair resolved from the tool fails in the browser with React error #527.
2007
+ */
2008
+ function appReactAliases(appPath) {
2009
+ const reactDir = appPackageDir(appPath, "react");
2010
+ const reactDomDir = appPackageDir(appPath, "react-dom");
2011
+ return [
2012
+ {
2013
+ find: /^react$/,
2014
+ replacement: reactDir
2015
+ },
2016
+ {
2017
+ find: /^react\/(.*)$/,
2018
+ replacement: node_path.default.join(reactDir, "$1")
2019
+ },
2020
+ {
2021
+ find: /^react-dom$/,
2022
+ replacement: reactDomDir
2023
+ },
2024
+ {
2025
+ find: /^react-dom\/(.*)$/,
2026
+ replacement: node_path.default.join(reactDomDir, "$1")
2027
+ }
2028
+ ];
2029
+ }
1899
2030
  /**
1900
2031
  * Helper to create a Vite plugin that resolves the module specifiers TanStack Start's
1901
2032
  * `tanstackStart()` plugin normally provides to inert client-safe stubs, so stories in TanStack
@@ -1936,6 +2067,7 @@ function createTanStackStartStubPlugin() {
1936
2067
  * ```
1937
2068
  */
1938
2069
  var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerService {
2070
+ workspaceRoot;
1939
2071
  static instance = null;
1940
2072
  server = null;
1941
2073
  monorepoRoot;
@@ -1952,9 +2084,10 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
1952
2084
  * Use getInstance() for singleton access.
1953
2085
  * @param config - Service configuration options
1954
2086
  */
1955
- constructor(config = {}) {
2087
+ constructor(config = {}, workspaceRoot) {
1956
2088
  super(config);
1957
- this.monorepoRoot = TemplatesManagerService.getWorkspaceRootSync();
2089
+ this.workspaceRoot = workspaceRoot;
2090
+ this.monorepoRoot = workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync();
1958
2091
  }
1959
2092
  /**
1960
2093
  * Get the singleton instance of ViteReactBundlerService.
@@ -2055,6 +2188,7 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2055
2188
  */
2056
2189
  async doStartDevServer(resolvedAppPath) {
2057
2190
  const tmpDir = node_path.default.join(resolvedAppPath, TMP_DIR_NAME);
2191
+ let createdServer;
2058
2192
  try {
2059
2193
  await node_fs.promises.mkdir(tmpDir, { recursive: true });
2060
2194
  const { createServer } = await import("vite");
@@ -2069,6 +2203,33 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2069
2203
  (0, _tailwindcss_vite.default)(),
2070
2204
  createTanStackStartStubPlugin(),
2071
2205
  ...this.getAdditionalVitePlugins(resolvedAppPath),
2206
+ {
2207
+ name: "style-system-preview-html",
2208
+ configureServer: (server) => {
2209
+ server.middlewares.use(async (req, res, next) => {
2210
+ const url = req.url || "/";
2211
+ const match = url.match(PREVIEW_ROUTE_PATTERN);
2212
+ if (match) {
2213
+ const storyId = match[1];
2214
+ const config = this.storyConfigs.get(storyId);
2215
+ if (config) try {
2216
+ const htmlTemplate = this.generateHtmlTemplate(`${VIRTUAL_STORY_ENTRY_SCRIPT_PREFIX}${storyId}`, config.darkMode);
2217
+ const transformedHtml = await server.transformIndexHtml(url, htmlTemplate);
2218
+ res.statusCode = HTTP_STATUS_OK;
2219
+ res.setHeader(CONTENT_TYPE_HEADER, TEXT_HTML_CONTENT_TYPE);
2220
+ res.end(transformedHtml);
2221
+ return;
2222
+ } catch (e) {
2223
+ const err = e;
2224
+ log.error(`[ViteMiddleware] Error serving preview: ${err.message}`);
2225
+ next(err);
2226
+ return;
2227
+ }
2228
+ }
2229
+ next();
2230
+ });
2231
+ }
2232
+ },
2072
2233
  createStoryEntryPlugin((id) => this.storyConfigs.get(id), (opts) => this.generateEntryFile(opts))
2073
2234
  ],
2074
2235
  resolve: this.getViteResolveConfig(resolvedAppPath),
@@ -2080,28 +2241,7 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2080
2241
  open: false
2081
2242
  }
2082
2243
  });
2083
- this.server.middlewares.use(async (req, res, next) => {
2084
- const url = req.url || "/";
2085
- const match = url.match(PREVIEW_ROUTE_PATTERN);
2086
- if (match) {
2087
- const storyId = match[1];
2088
- const config = this.storyConfigs.get(storyId);
2089
- if (config) try {
2090
- const htmlTemplate = this.generateHtmlTemplate(`${VIRTUAL_STORY_ENTRY_SCRIPT_PREFIX}${storyId}`, config.darkMode);
2091
- const transformedHtml = await this.server?.transformIndexHtml(url, htmlTemplate);
2092
- res.statusCode = HTTP_STATUS_OK;
2093
- res.setHeader(CONTENT_TYPE_HEADER, TEXT_HTML_CONTENT_TYPE);
2094
- res.end(transformedHtml);
2095
- return;
2096
- } catch (e) {
2097
- const err = e;
2098
- log.error(`[ViteMiddleware] Error serving preview: ${err.message}`);
2099
- next(err);
2100
- return;
2101
- }
2102
- }
2103
- next();
2104
- });
2244
+ createdServer = this.server;
2105
2245
  await this.server.listen();
2106
2246
  const address = this.server.httpServer?.address();
2107
2247
  if (!address || typeof address === "string") throw new Error("Failed to start Vite dev server. Ensure no other process is using the port.");
@@ -2116,6 +2256,15 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2116
2256
  port
2117
2257
  };
2118
2258
  } catch (error) {
2259
+ try {
2260
+ await createdServer?.close();
2261
+ } catch (closeError) {
2262
+ log.warn(`[ViteReactBundlerService] Failed to close server after startup failure: ${String(closeError)}`);
2263
+ }
2264
+ this.server = null;
2265
+ this.serverUrl = null;
2266
+ this.serverPort = null;
2267
+ this.currentAppPath = null;
2119
2268
  const err = /* @__PURE__ */ new Error(`Failed to start dev server for ${resolvedAppPath}: ${error instanceof Error ? error.message : String(error)}`);
2120
2269
  err.cause = error;
2121
2270
  throw err;
@@ -2134,7 +2283,7 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2134
2283
  validateComponentPath(componentPath);
2135
2284
  validateArgsSize(args);
2136
2285
  const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(this.monorepoRoot, appPath);
2137
- validateCssFiles(cssFiles, TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath));
2286
+ validateCssFiles(cssFiles, this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath));
2138
2287
  if (this.currentAppPath !== resolvedAppPath) throw new Error(`Dev server is running for ${this.currentAppPath} but requested ${resolvedAppPath}.`);
2139
2288
  const tmpDir = node_path.default.join(resolvedAppPath, TMP_DIR_NAME);
2140
2289
  try {
@@ -2182,7 +2331,7 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2182
2331
  validateComponentPath(componentPath);
2183
2332
  validateArgsSize(args);
2184
2333
  const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(this.monorepoRoot, appPath);
2185
- validateCssFiles(cssFiles, TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath));
2334
+ validateCssFiles(cssFiles, this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync(resolvedAppPath));
2186
2335
  const tmpDir = node_path.default.join(resolvedAppPath, TMP_DIR_NAME);
2187
2336
  try {
2188
2337
  await node_fs.promises.mkdir(tmpDir, { recursive: true });
@@ -2208,9 +2357,10 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2208
2357
  * Subclasses can override to add platform-specific aliases (e.g., react-native-web).
2209
2358
  */
2210
2359
  getViteResolveConfig(appPath) {
2211
- const workspaceRoot = TemplatesManagerService.getWorkspaceRootSync(appPath);
2360
+ const workspaceRoot = this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync(appPath);
2212
2361
  const frontendPackagePath = (packageDir, sourcePath = "index.ts") => node_path.default.join(workspaceRoot, WORKSPACE_PACKAGE_DIR_PREFIX, "frontend", packageDir, APP_SOURCE_DIR$1, sourcePath);
2213
2362
  return { alias: [
2363
+ ...appReactAliases(appPath),
2214
2364
  {
2215
2365
  find: APP_SOURCE_ALIAS_PATTERN,
2216
2366
  replacement: `${node_path.default.join(appPath, APP_SOURCE_DIR$1)}/`
@@ -2317,7 +2467,7 @@ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerS
2317
2467
  */
2318
2468
  async generateWrapperCss(appPath, cssFiles) {
2319
2469
  const resolvedCssFiles = cssFiles.length > 0 ? cssFiles : await this.getDefaultCssFiles(appPath);
2320
- const workspaceRoot = TemplatesManagerService.getWorkspaceRootSync(appPath);
2470
+ const workspaceRoot = this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync(appPath);
2321
2471
  const cssImportStatements = resolvedCssFiles.map((cssFile) => {
2322
2472
  if (node_path.default.isAbsolute(cssFile)) return `@import '${cssFile}';`;
2323
2473
  if (cssFile.startsWith(PACKAGE_IMPORT_PREFIX) || cssFile.startsWith(TAILWIND_IMPORT_PREFIX)) return `@import '${cssFile}';`;
@@ -2539,20 +2689,16 @@ mount();
2539
2689
  *
2540
2690
  * Extends ViteReactBundlerService with React Native → web aliases
2541
2691
  * so native-ui components can be rendered in a browser via Playwright.
2542
- * Uses React Native Web and the app's real Unistyles web runtime.
2692
+ * Uses the app's own React Native Web and Unistyles web runtime.
2543
2693
  *
2544
- * All shim paths resolve relative to this package — no monorepo-root assumptions.
2694
+ * Shim paths resolve relative to this package; React, react-dom and
2695
+ * react-native-web are peers and always resolve from the app.
2545
2696
  */
2546
2697
  const __dirname$1 = (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
2547
- const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
2548
- const REACT_PACKAGE = "react";
2549
- const REACT_DOM_PACKAGE = "react-dom";
2550
2698
  const REACT_NATIVE_PACKAGE = "react-native";
2551
2699
  const REACT_NATIVE_WEB_PACKAGE = "react-native-web";
2552
- const REACT_NATIVE_INDEX_SUBPATH = "react-native/index.js";
2553
2700
  const REACT_NATIVE_UNISTYLES_PACKAGE = "react-native-unistyles";
2554
2701
  const REACT_NATIVE_UNISTYLES_BABEL_PLUGIN = "react-native-unistyles/plugin";
2555
- const REACT_NATIVE_REANIMATED_PACKAGE = "react-native-reanimated";
2556
2702
  const REACT_NATIVE_REANIMATED_BABEL_PLUGIN = "react-native-reanimated/plugin";
2557
2703
  const BABEL_CORE_PACKAGE = "@babel/core";
2558
2704
  const BABEL_PLUGIN_SYNTAX_JSX = "@babel/plugin-syntax-jsx";
@@ -2566,8 +2712,7 @@ const APP_SOURCE_ALIAS = "@";
2566
2712
  const APP_SOURCE_DIR = "src";
2567
2713
  const VITE_PLUGIN_ENFORCE_PRE = "pre";
2568
2714
  const VITE_REACT_NATIVE_BUNDLER_ID = "vite-react-native";
2569
- const REACT_NATIVE_REDIRECT_PLUGIN_NAME = "vite-redirect-react-native";
2570
- const REACT_NATIVE_REANIMATED_REDIRECT_PLUGIN_NAME = "vite-redirect-react-native-reanimated";
2715
+ const REACT_NATIVE_OPTIMIZE_DEPS_PLUGIN_NAME = "vite-react-native-optimize-deps";
2571
2716
  const EXPO_ICONS_REDIRECT_PLUGIN_NAME = "vite-redirect-expo-vector-icons";
2572
2717
  const NATIVE_BABEL_TRANSFORM_PLUGIN_NAME = "vite-react-native-babel";
2573
2718
  const EXPO_VECTOR_ICONS_RESOLVED_MODULE_PREFIX = `\0virtual:style-system-expo-vector-icons`;
@@ -2578,10 +2723,8 @@ const EXPO_VECTOR_ICONS_SHIM_FILE = "expo-vector-icons.ts";
2578
2723
  const EXPO_SYMBOLS_SHIM_FILE = "expo-symbols.ts";
2579
2724
  const REQUIRED_REACT_NATIVE_SHIM_FILES = [EXPO_VECTOR_ICONS_SHIM_FILE, EXPO_SYMBOLS_SHIM_FILE];
2580
2725
  const PACKAGE_JSON_SUBPATH = "package.json";
2581
- const REACT_NATIVE_WEB_DIST_INDEX = "dist/index.js";
2582
2726
  const REACT_NATIVE_UNISTYLES_WEB_INDEX = "lib/module/index.js";
2583
2727
  const REACT_NATIVE_UNISTYLES_NATIVE_COMPONENTS = "lib/module/components/native";
2584
- const REACT_NATIVE_REANIMATED_WEB_INDEX = "lib/module/index.js";
2585
2728
  const EXPO_VECTOR_ICONS_GLYPHMAP_DIR = "build/vendor/react-native-vector-icons/glyphmaps";
2586
2729
  const EXPO_VECTOR_ICONS_FONT_DIR = "build/vendor/react-native-vector-icons/Fonts";
2587
2730
  const GLYPHMAP_EXTENSION = ".json";
@@ -2600,6 +2743,8 @@ const REACT_NATIVE_DEV_GLOBAL = "__DEV__";
2600
2743
  const REACT_NATIVE_DEV_VALUE = "false";
2601
2744
  const REACT_NATIVE_GLOBAL = "global";
2602
2745
  const REACT_NATIVE_GLOBAL_VALUE = "globalThis";
2746
+ const EXPO_OS_DEFINE = "process.env.EXPO_OS";
2747
+ const EXPO_OS_WEB_VALUE = JSON.stringify("web");
2603
2748
  const VITE_RESOLVE_EXTENSIONS = [
2604
2749
  ".web.tsx",
2605
2750
  ".web.ts",
@@ -2622,18 +2767,13 @@ const SHIMS_DIR_CANDIDATES = [
2622
2767
  node_path.default.resolve(__dirname$1, "../src/shims")
2623
2768
  ];
2624
2769
  const SHIMS_DIR = resolveShimsDir();
2625
- const REACT_NATIVE_WEB_DIR = resolvePackageDir(REACT_NATIVE_WEB_PACKAGE);
2626
2770
  function resolveExistingDir(candidates, label, isValidDir = () => true) {
2627
2771
  const existing = candidates.find((candidate) => (0, node_fs.existsSync)(candidate) && isValidDir(candidate));
2628
2772
  if (existing) return existing;
2629
2773
  throw new Error(`Could not resolve ${label}. Tried: ${candidates.join(", ")}`);
2630
2774
  }
2631
- function resolvePackageDir(packageName) {
2632
- return (0, node_path.dirname)(require$1.resolve(`${packageName}/${PACKAGE_JSON_SUBPATH}`));
2633
- }
2634
2775
  function resolvePackageDirFrom(fromPath, packageName) {
2635
- const fromRequire = (0, node_module.createRequire)(node_path.default.join(fromPath, PACKAGE_JSON_SUBPATH));
2636
- return (0, node_path.dirname)(fromRequire.resolve(`${packageName}/${PACKAGE_JSON_SUBPATH}`));
2776
+ return appPackageDir(fromPath, packageName);
2637
2777
  }
2638
2778
  function resolvePackageSubpathFrom(fromPath, subpath) {
2639
2779
  return (0, node_module.createRequire)(node_path.default.join(fromPath, PACKAGE_JSON_SUBPATH)).resolve(subpath);
@@ -2728,6 +2868,7 @@ function createNativeBabelTransformPlugin(appPath) {
2728
2868
  name: NATIVE_BABEL_TRANSFORM_PLUGIN_NAME,
2729
2869
  enforce: VITE_PLUGIN_ENFORCE_PRE,
2730
2870
  async transform(code, id) {
2871
+ id = id.split(VITE_QUERY_SEPARATOR)[0] ?? id;
2731
2872
  const isFlowModule = toViteImportPath(id).includes("/@react-native-segmented-control/segmented-control/");
2732
2873
  const isSupportedNodeModule = isFlowModule || id.includes(REACT_NATIVE_MARKDOWN_DISPLAY_PATH_SEGMENT);
2733
2874
  if (id.includes(NODE_MODULES_PATH_SEGMENT) && !isSupportedNodeModule || !BABEL_TRANSFORM_EXTENSION_PATTERN.test(id)) return null;
@@ -2869,24 +3010,20 @@ var ViteReactNativeBundlerService = class ViteReactNativeBundlerService extends
2869
3010
  return VITE_REACT_NATIVE_BUNDLER_ID;
2870
3011
  }
2871
3012
  getAdditionalVitePlugins(appPath) {
2872
- const reactNativeWebEntry = node_path.default.join(REACT_NATIVE_WEB_DIR, REACT_NATIVE_WEB_DIST_INDEX);
2873
- const reactNativeReanimatedEntry = appPath ? node_path.default.join(resolvePackageDirFrom(appPath, REACT_NATIVE_REANIMATED_PACKAGE), REACT_NATIVE_REANIMATED_WEB_INDEX) : null;
2874
3013
  return [
2875
3014
  {
2876
- name: REACT_NATIVE_REDIRECT_PLUGIN_NAME,
2877
- enforce: VITE_PLUGIN_ENFORCE_PRE,
2878
- resolveId(source) {
2879
- if (source === REACT_NATIVE_PACKAGE || source === REACT_NATIVE_INDEX_SUBPATH) return reactNativeWebEntry;
2880
- return null;
2881
- }
2882
- },
2883
- {
2884
- name: REACT_NATIVE_REANIMATED_REDIRECT_PLUGIN_NAME,
2885
- enforce: VITE_PLUGIN_ENFORCE_PRE,
2886
- resolveId(source) {
2887
- if (source === REACT_NATIVE_REANIMATED_PACKAGE) return reactNativeReanimatedEntry;
2888
- return null;
2889
- }
3015
+ name: REACT_NATIVE_OPTIMIZE_DEPS_PLUGIN_NAME,
3016
+ config: (config) => ({ optimizeDeps: {
3017
+ rolldownOptions: { resolve: { extensions: config.resolve?.extensions } },
3018
+ exclude: [
3019
+ "@react-native-segmented-control/segmented-control",
3020
+ SAFE_AREA_CONTEXT_PACKAGE,
3021
+ "expo-haptics",
3022
+ "expo-modules-core",
3023
+ "react-native-gesture-handler"
3024
+ ],
3025
+ include: ["react-native-gesture-handler > hoist-non-react-statics", "react-native-gesture-handler > invariant"]
3026
+ } })
2890
3027
  },
2891
3028
  createExpoTypeDeclarationStubPlugin(),
2892
3029
  createSvgComponentPlugin(),
@@ -2922,7 +3059,8 @@ const styleSystemSafeAreaMetrics = {
2922
3059
  getViteDefineConfig() {
2923
3060
  return {
2924
3061
  [REACT_NATIVE_DEV_GLOBAL]: REACT_NATIVE_DEV_VALUE,
2925
- [REACT_NATIVE_GLOBAL]: REACT_NATIVE_GLOBAL_VALUE
3062
+ [REACT_NATIVE_GLOBAL]: REACT_NATIVE_GLOBAL_VALUE,
3063
+ [EXPO_OS_DEFINE]: EXPO_OS_WEB_VALUE
2926
3064
  };
2927
3065
  }
2928
3066
  generatePreviewStyles() {
@@ -2947,38 +3085,18 @@ const styleSystemSafeAreaMetrics = {
2947
3085
  getViteResolveConfig(appPath) {
2948
3086
  const reactNativeUnistylesDir = resolvePackageDirFrom(appPath, REACT_NATIVE_UNISTYLES_PACKAGE);
2949
3087
  const reactNativeUnistylesEntry = node_path.default.join(reactNativeUnistylesDir, REACT_NATIVE_UNISTYLES_WEB_INDEX);
2950
- const reactDir = resolvePackageDirFrom(appPath, REACT_PACKAGE);
2951
- const reactDomDir = resolvePackageDirFrom(appPath, REACT_DOM_PACKAGE);
3088
+ const reactNativeWebDir = resolvePackageDirFrom(appPath, REACT_NATIVE_WEB_PACKAGE);
2952
3089
  return {
2953
3090
  extensions: [...VITE_RESOLVE_EXTENSIONS],
2954
3091
  alias: [
2955
- {
2956
- find: /^react$/,
2957
- replacement: reactDir
2958
- },
2959
- {
2960
- find: /^react\/(.*)$/,
2961
- replacement: node_path.default.join(reactDir, "$1")
2962
- },
2963
- {
2964
- find: /^react-dom$/,
2965
- replacement: reactDomDir
2966
- },
2967
- {
2968
- find: /^react-dom\/(.*)$/,
2969
- replacement: node_path.default.join(reactDomDir, "$1")
2970
- },
3092
+ ...appReactAliases(appPath),
2971
3093
  {
2972
3094
  find: APP_SOURCE_ALIAS,
2973
3095
  replacement: node_path.default.join(appPath, APP_SOURCE_DIR)
2974
3096
  },
2975
3097
  {
2976
- find: REACT_NATIVE_PACKAGE,
2977
- replacement: REACT_NATIVE_WEB_DIR
2978
- },
2979
- {
2980
- find: REACT_NATIVE_INDEX_SUBPATH,
2981
- replacement: node_path.default.join(REACT_NATIVE_WEB_DIR, REACT_NATIVE_WEB_DIST_INDEX)
3098
+ find: /^react-native$/,
3099
+ replacement: reactNativeWebDir
2982
3100
  },
2983
3101
  {
2984
3102
  find: /^react-native-unistyles\/components\/native\/(.+)$/,
@@ -3041,8 +3159,8 @@ const VALID_SERVICE_EXTENSIONS = [
3041
3159
  * await bundler.startDevServer('apps/my-app');
3042
3160
  * ```
3043
3161
  */
3044
- function createDefaultBundlerService() {
3045
- return ViteReactBundlerService.getInstance();
3162
+ function createDefaultBundlerService(workspaceRoot) {
3163
+ return workspaceRoot ? new ViteReactBundlerService({}, workspaceRoot) : ViteReactBundlerService.getInstance();
3046
3164
  }
3047
3165
  /**
3048
3166
  * Registry of available bundler service factories.
@@ -3077,13 +3195,17 @@ bundlerRegistry.set("vite-react-native", () => ViteReactNativeBundlerService.get
3077
3195
  * const bundler = getBundlerService('unknown'); // Returns ViteReactBundlerService
3078
3196
  * ```
3079
3197
  */
3080
- function getBundlerService(key) {
3198
+ function getBundlerService(key, workspaceRoot) {
3199
+ if (workspaceRoot) {
3200
+ if (key === "vite-react-native") return new ViteReactNativeBundlerService({}, workspaceRoot);
3201
+ if (key === "vite-react") return new ViteReactBundlerService({}, workspaceRoot);
3202
+ }
3081
3203
  const factory = bundlerRegistry.get(key);
3082
3204
  if (factory) return factory();
3083
- return createDefaultBundlerService();
3205
+ return createDefaultBundlerService(workspaceRoot);
3084
3206
  }
3085
3207
  /** Cached bundler service instance loaded from config */
3086
- let cachedBundlerService = null;
3208
+ let cachedDefaultBundlerService = null;
3087
3209
  /**
3088
3210
  * Get bundler service based on the workspace style-system.config.yaml.
3089
3211
  *
@@ -3107,25 +3229,31 @@ let cachedBundlerService = null;
3107
3229
  * await bundler.startDevServer('apps/my-app');
3108
3230
  * ```
3109
3231
  */
3110
- async function getBundlerServiceFromConfig() {
3111
- if (cachedBundlerService) return cachedBundlerService;
3112
- const config = await getBundlerConfig();
3232
+ async function getBundlerServiceFromConfig(options) {
3233
+ if (options) return loadBundlerServiceFromConfig(options);
3234
+ cachedDefaultBundlerService ??= await loadBundlerServiceFromConfig();
3235
+ return cachedDefaultBundlerService;
3236
+ }
3237
+ async function loadBundlerServiceFromConfig(options) {
3238
+ let cachedBundlerService = null;
3239
+ const fallback = options?.defaultFactory ?? (() => createDefaultBundlerService(options?.workspaceRoot));
3240
+ const config = options ? (await (options.configLoader ?? new StyleSystemConfigLoader(options.workspaceRoot)).loadRoot()).bundler : await getBundlerConfig();
3113
3241
  if (!config?.customService) {
3114
- cachedBundlerService = createDefaultBundlerService();
3242
+ cachedBundlerService = fallback();
3115
3243
  return cachedBundlerService;
3116
3244
  }
3117
- const monorepoRoot = TemplatesManagerService.getWorkspaceRootSync();
3245
+ const monorepoRoot = options?.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync();
3118
3246
  const customServicePath = node_path.default.resolve(monorepoRoot, config.customService);
3119
3247
  const normalizedWorkspaceRoot = node_path.default.resolve(monorepoRoot);
3120
3248
  if (!customServicePath.startsWith(normalizedWorkspaceRoot + node_path.default.sep)) {
3121
3249
  log.error(`[BundlerServiceFactory] Security error: customService path "${config.customService}" resolves outside workspace root`);
3122
- cachedBundlerService = createDefaultBundlerService();
3250
+ cachedBundlerService = fallback();
3123
3251
  return cachedBundlerService;
3124
3252
  }
3125
3253
  const ext = node_path.default.extname(customServicePath).toLowerCase();
3126
3254
  if (!VALID_SERVICE_EXTENSIONS.includes(ext)) {
3127
3255
  log.error(`[BundlerServiceFactory] Invalid file extension "${ext}" for customService. Expected one of: ${VALID_SERVICE_EXTENSIONS.join(", ")}`);
3128
- cachedBundlerService = createDefaultBundlerService();
3256
+ cachedBundlerService = fallback();
3129
3257
  return cachedBundlerService;
3130
3258
  }
3131
3259
  try {
@@ -3157,7 +3285,7 @@ async function getBundlerServiceFromConfig() {
3157
3285
  const message = error instanceof Error ? error.message : String(error);
3158
3286
  log.error(`[BundlerServiceFactory] Failed to load custom bundler service: ${message}`);
3159
3287
  log.info("[BundlerServiceFactory] Falling back to default ViteReactBundlerService");
3160
- cachedBundlerService = createDefaultBundlerService();
3288
+ cachedBundlerService = fallback();
3161
3289
  return cachedBundlerService;
3162
3290
  }
3163
3291
  }
@@ -3202,8 +3330,8 @@ var ThemeService = class {
3202
3330
  * Creates a new ThemeService instance
3203
3331
  * @param config - Design system configuration
3204
3332
  */
3205
- constructor(config) {
3206
- this.monorepoRoot = TemplatesManagerService.getWorkspaceRootSync();
3333
+ constructor(config, workspaceRoot) {
3334
+ this.monorepoRoot = workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync();
3207
3335
  this.config = config;
3208
3336
  if (this.config.themeProvider) log.info(`[ThemeService] Using theme provider: ${this.config.themeProvider}`);
3209
3337
  log.info(`[ThemeService] Design system type: ${this.config.type}`);
@@ -3366,7 +3494,8 @@ export { WrappedComponent };
3366
3494
  */
3367
3495
  const DEFAULT_RENDER_WIDTH = 1280;
3368
3496
  const DEFAULT_RENDER_HEIGHT = 800;
3369
- const SCREENSHOT_WAIT_TIME_MS = 2e3;
3497
+ /** Settle time after the readiness check in takeScreenshot (mount, fonts, two frames). */
3498
+ const SCREENSHOT_WAIT_TIME_MS = 100;
3370
3499
  const DEFAULT_CLEANUP_AGE_MS = 36e5;
3371
3500
  const COMPONENT_FILE_PREFIX = "component-";
3372
3501
  const FILE_URL_PREFIX = "file://";
@@ -3406,6 +3535,9 @@ var ComponentRendererService = class ComponentRendererService {
3406
3535
  themeService;
3407
3536
  appPath;
3408
3537
  bundlerFactory;
3538
+ log;
3539
+ preserveOutput;
3540
+ sharedBrowser;
3409
3541
  /**
3410
3542
  * Creates a new ComponentRendererService instance
3411
3543
  * @param designSystemConfig - Design system configuration
@@ -3415,8 +3547,12 @@ var ComponentRendererService = class ComponentRendererService {
3415
3547
  constructor(designSystemConfig, appPath, options = {}) {
3416
3548
  if (!appPath) throw new Error("appPath is required for ComponentRendererService");
3417
3549
  this.appPath = appPath;
3418
- this.tmpDir = node_path.default.join(node_os.default.tmpdir(), "style-system");
3419
- this.themeService = new ThemeService(designSystemConfig);
3550
+ this.sharedBrowser = options.sharedBrowser;
3551
+ this.tmpDir = options.temporaryDirectory ?? node_path.default.join(node_os.default.tmpdir(), "style-system");
3552
+ this.preserveOutput = options.temporaryDirectory !== void 0;
3553
+ if (options.temporaryDirectory !== void 0 && !node_path.default.isAbsolute(options.temporaryDirectory)) throw new Error("Renderer temporary directory must be absolute.");
3554
+ this.log = log;
3555
+ this.themeService = new ThemeService(designSystemConfig, options.workspaceRoot);
3420
3556
  if (options.bundlerFactory) this.bundlerFactory = options.bundlerFactory;
3421
3557
  else if (designSystemConfig.bundler) this.bundlerFactory = () => getBundlerService(designSystemConfig.bundler);
3422
3558
  else this.bundlerFactory = createDefaultBundlerService;
@@ -3439,17 +3575,25 @@ var ComponentRendererService = class ComponentRendererService {
3439
3575
  async renderComponent(componentInfo, options = {}) {
3440
3576
  const { storyName = componentInfo.stories[0] || "Default", args = {}, darkMode = false, width = DEFAULT_RENDER_WIDTH, height = DEFAULT_RENDER_HEIGHT } = options;
3441
3577
  try {
3442
- log.info(`[ComponentRendererService] Rendering ${componentInfo.title} - ${storyName}`);
3443
- await node_fs.promises.mkdir(this.tmpDir, { recursive: true });
3578
+ await node_fs.promises.mkdir(this.tmpDir, {
3579
+ recursive: true,
3580
+ mode: 448
3581
+ });
3582
+ if (this.preserveOutput) {
3583
+ const info = await node_fs.promises.lstat(this.tmpDir);
3584
+ if (!info.isDirectory() || info.isSymbolicLink() || await node_fs.promises.realpath(this.tmpDir) !== this.tmpDir) throw new Error("Unsafe renderer temporary directory.");
3585
+ if (this.log === log) this.log = createLog(this.tmpDir);
3586
+ }
3587
+ this.log.info(`[ComponentRendererService] Rendering ${componentInfo.title} - ${storyName}`);
3444
3588
  const designSystemConfig = this.themeService.getConfig();
3445
- log.info(`[ComponentRendererService] Using theme provider: ${designSystemConfig.themeProvider}`);
3446
- log.info(`[ComponentRendererService] Design system type: ${designSystemConfig.type}`);
3447
- if (!await this.themeService.validateThemeProvider()) log.warn(`[ComponentRendererService] Theme provider path may not exist: ${designSystemConfig.themeProvider}`);
3589
+ this.log.info(`[ComponentRendererService] Using theme provider: ${designSystemConfig.themeProvider}`);
3590
+ this.log.info(`[ComponentRendererService] Design system type: ${designSystemConfig.type}`);
3591
+ if (!await this.themeService.validateThemeProvider()) this.log.warn(`[ComponentRendererService] Theme provider path may not exist: ${designSystemConfig.themeProvider}`);
3448
3592
  const bundlerService = this.getBundlerService();
3449
3593
  let componentUrl;
3450
3594
  let htmlFilePath;
3451
3595
  if (bundlerService.isServerRunning()) {
3452
- log.info("[ComponentRendererService] Using dev server for fast rendering");
3596
+ this.log.info("[ComponentRendererService] Using dev server for fast rendering");
3453
3597
  try {
3454
3598
  const result = await bundlerService.serveComponent({
3455
3599
  componentPath: componentInfo.filePath,
@@ -3464,9 +3608,9 @@ var ComponentRendererService = class ComponentRendererService {
3464
3608
  });
3465
3609
  componentUrl = result.url;
3466
3610
  htmlFilePath = result.htmlFilePath;
3467
- log.info(`[ComponentRendererService] Component served at: ${componentUrl}`);
3611
+ this.log.info(`[ComponentRendererService] Component served at: ${componentUrl}`);
3468
3612
  } catch (devServerError) {
3469
- log.warn(`[ComponentRendererService] Dev server failed, falling back to static build: ${devServerError instanceof Error ? devServerError.message : String(devServerError)}`);
3613
+ this.log.warn(`[ComponentRendererService] Dev server failed, falling back to static build: ${devServerError instanceof Error ? devServerError.message : String(devServerError)}`);
3470
3614
  const result = await bundlerService.prerenderComponent({
3471
3615
  componentPath: componentInfo.filePath,
3472
3616
  storyName,
@@ -3480,10 +3624,10 @@ var ComponentRendererService = class ComponentRendererService {
3480
3624
  });
3481
3625
  componentUrl = `${FILE_URL_PREFIX}${result.htmlFilePath}`;
3482
3626
  htmlFilePath = result.htmlFilePath;
3483
- log.info(`[ComponentRendererService] HTML built to: ${htmlFilePath}`);
3627
+ this.log.info(`[ComponentRendererService] HTML built to: ${htmlFilePath}`);
3484
3628
  }
3485
3629
  } else {
3486
- log.info("[ComponentRendererService] No dev server running, using static build");
3630
+ this.log.info("[ComponentRendererService] No dev server running, using static build");
3487
3631
  const result = await bundlerService.prerenderComponent({
3488
3632
  componentPath: componentInfo.filePath,
3489
3633
  storyName,
@@ -3497,7 +3641,7 @@ var ComponentRendererService = class ComponentRendererService {
3497
3641
  });
3498
3642
  componentUrl = `${FILE_URL_PREFIX}${result.htmlFilePath}`;
3499
3643
  htmlFilePath = result.htmlFilePath;
3500
- log.info(`[ComponentRendererService] HTML built to: ${htmlFilePath}`);
3644
+ this.log.info(`[ComponentRendererService] HTML built to: ${htmlFilePath}`);
3501
3645
  }
3502
3646
  const timestamp = Date.now();
3503
3647
  const imageName = `${COMPONENT_FILE_PREFIX}${componentInfo.title.split("/").pop()}-${timestamp}-${(0, node_crypto.randomUUID)()}.png`;
@@ -3507,20 +3651,21 @@ var ComponentRendererService = class ComponentRendererService {
3507
3651
  output: imagePath,
3508
3652
  width,
3509
3653
  height,
3510
- fullPage: true,
3654
+ fullPage: false,
3511
3655
  browser: "chromium",
3512
3656
  waitTime: SCREENSHOT_WAIT_TIME_MS,
3513
3657
  darkMode,
3514
3658
  mobile: false,
3515
3659
  generateThumbnail: false,
3516
- base64: false
3660
+ base64: false,
3661
+ sharedBrowser: this.sharedBrowser
3517
3662
  });
3518
- log.info(`[ComponentRendererService] Screenshot saved to: ${imagePath}`);
3663
+ this.log.info(`[ComponentRendererService] Screenshot saved to: ${imagePath}`);
3519
3664
  let html = "";
3520
3665
  if (htmlFilePath) {
3521
3666
  html = await node_fs.promises.readFile(htmlFilePath, "utf-8");
3522
- log.info(`[ComponentRendererService] HTML file kept at: ${htmlFilePath}`);
3523
- } else log.warn("[ComponentRendererService] No HTML file path available, returning empty HTML content");
3667
+ this.log.info(`[ComponentRendererService] HTML file kept at: ${htmlFilePath}`);
3668
+ } else this.log.warn("[ComponentRendererService] No HTML file path available, returning empty HTML content");
3524
3669
  return {
3525
3670
  imagePath,
3526
3671
  html,
@@ -3563,20 +3708,20 @@ var ComponentRendererService = class ComponentRendererService {
3563
3708
  };
3564
3709
  }));
3565
3710
  for (const result of statResults) if (result.status === "fulfilled") componentFiles.push(result.value);
3566
- else log.warn("[ComponentRendererService] Failed to inspect component file during cleanup:", result.reason);
3711
+ else this.log.warn("[ComponentRendererService] Failed to inspect component file during cleanup:", result.reason);
3567
3712
  const oldFiles = componentFiles.filter((f) => now - f.mtime > olderThanMs);
3568
- await Promise.all(oldFiles.map((file) => node_fs.promises.unlink(file.path).catch((err) => log.warn("[ComponentRendererService] Failed to delete file:", file.path, err))));
3713
+ await Promise.all(oldFiles.map((file) => node_fs.promises.unlink(file.path).catch((err) => this.log.warn("[ComponentRendererService] Failed to delete file:", file.path, err))));
3569
3714
  let deletedCount = oldFiles.length;
3570
3715
  const remainingFiles = componentFiles.filter((f) => now - f.mtime <= olderThanMs);
3571
3716
  if (remainingFiles.length > keepCount) {
3572
3717
  remainingFiles.sort((a, b) => a.mtime - b.mtime);
3573
3718
  const toDelete = remainingFiles.slice(0, remainingFiles.length - keepCount);
3574
- await Promise.all(toDelete.map((file) => node_fs.promises.unlink(file.path).catch((err) => log.warn("[ComponentRendererService] Failed to delete file:", file.path, err))));
3719
+ await Promise.all(toDelete.map((file) => node_fs.promises.unlink(file.path).catch((err) => this.log.warn("[ComponentRendererService] Failed to delete file:", file.path, err))));
3575
3720
  deletedCount += toDelete.length;
3576
3721
  }
3577
- if (deletedCount > 0) log.info(`[ComponentRendererService] Cleaned up ${deletedCount} temp files`);
3722
+ if (deletedCount > 0) this.log.info(`[ComponentRendererService] Cleaned up ${deletedCount} temp files`);
3578
3723
  } catch (error) {
3579
- log.error("[ComponentRendererService] Cleanup error:", error);
3724
+ this.log.error("[ComponentRendererService] Cleanup error:", error);
3580
3725
  }
3581
3726
  }
3582
3727
  /**
@@ -3586,11 +3731,11 @@ var ComponentRendererService = class ComponentRendererService {
3586
3731
  */
3587
3732
  async dispose() {
3588
3733
  try {
3589
- await this.cleanup(void 0, ComponentRendererService.DISPOSE_KEEP_COUNT);
3734
+ if (!this.preserveOutput) await this.cleanup(void 0, ComponentRendererService.DISPOSE_KEEP_COUNT);
3590
3735
  const bundlerService = this.getBundlerService();
3591
3736
  if (!bundlerService.isServerRunning()) await bundlerService.cleanup();
3592
3737
  } catch (error) {
3593
- log.error("[ComponentRendererService] Dispose error:", error);
3738
+ this.log.error("[ComponentRendererService] Dispose error:", error);
3594
3739
  }
3595
3740
  }
3596
3741
  };
@@ -3606,13 +3751,6 @@ const DEFAULT_GET_UI_COMPONENT_CONFIG = {
3606
3751
  defaultHeight: 800
3607
3752
  };
3608
3753
  //#endregion
3609
- //#region \0@oxc-project+runtime@0.153.0/helpers/esm/decorateParam.js
3610
- function __decorateParam(paramIndex, decorator) {
3611
- return function(target, key) {
3612
- decorator(target, key, paramIndex);
3613
- };
3614
- }
3615
- //#endregion
3616
3754
  //#region src/services/GetUiComponentService/GetUiComponentService.ts
3617
3755
  /**
3618
3756
  * GetUiComponentService
@@ -3637,6 +3775,7 @@ const NATIVE_DEFAULT_VIEWPORT = {
3637
3775
  height: 852
3638
3776
  };
3639
3777
  let GetUiComponentService = class GetUiComponentService {
3778
+ configLoader;
3640
3779
  config;
3641
3780
  storiesIndexFactory;
3642
3781
  rendererFactory;
@@ -3646,7 +3785,8 @@ let GetUiComponentService = class GetUiComponentService {
3646
3785
  * @param storiesIndexFactory - Factory for creating StoriesIndexService (for DI/testing)
3647
3786
  * @param rendererFactory - Factory for creating ComponentRendererService (for DI/testing)
3648
3787
  */
3649
- constructor(config = {}, storiesIndexFactory = () => new StoriesIndexService(), rendererFactory = (c, p) => new ComponentRendererService(c, p)) {
3788
+ constructor(config = {}, storiesIndexFactory = () => new StoriesIndexService(), rendererFactory = (c, p) => new ComponentRendererService(c, p), configLoader) {
3789
+ this.configLoader = configLoader;
3650
3790
  this.config = {
3651
3791
  ...DEFAULT_GET_UI_COMPONENT_CONFIG,
3652
3792
  ...config
@@ -3678,7 +3818,7 @@ let GetUiComponentService = class GetUiComponentService {
3678
3818
  const storiesIndex = this.storiesIndexFactory();
3679
3819
  let designSystemConfig;
3680
3820
  try {
3681
- [, designSystemConfig] = await Promise.all([storiesIndex.initialize(), getAppDesignSystemConfig(appPath)]);
3821
+ [, designSystemConfig] = await Promise.all([storiesIndex.initialize(), this.configLoader ? this.configLoader.resolveProject(appPath).then((resolved) => resolved.config) : getAppDesignSystemConfig(appPath)]);
3682
3822
  } catch (error) {
3683
3823
  throw new Error(`Failed to initialize stories index or design system config: ${error instanceof Error ? error.message : String(error)}`);
3684
3824
  }
@@ -3778,10 +3918,13 @@ GetUiComponentService = __decorate([
3778
3918
  __decorateParam(1, (0, inversify.optional)()),
3779
3919
  __decorateParam(2, (0, inversify.inject)(STYLE_SYSTEM_TYPES.RendererFactory)),
3780
3920
  __decorateParam(2, (0, inversify.optional)()),
3921
+ __decorateParam(3, (0, inversify.inject)(STYLE_SYSTEM_TYPES.StyleSystemConfigLoader)),
3922
+ __decorateParam(3, (0, inversify.optional)()),
3781
3923
  __decorateMetadata("design:paramtypes", [
3782
3924
  Object,
3783
3925
  Object,
3784
- Object
3926
+ Object,
3927
+ typeof StyleSystemConfigLoader === "undefined" ? Object : StyleSystemConfigLoader
3785
3928
  ])
3786
3929
  ], GetUiComponentService);
3787
3930
  //#endregion
@@ -3810,12 +3953,14 @@ const DEFAULT_APP_COMPONENTS_CONFIG = { pageSize: 50 };
3810
3953
  * - Direct tool implementation (services should be tool-agnostic)
3811
3954
  */
3812
3955
  let AppComponentsService = class AppComponentsService {
3956
+ workspaceRoot;
3813
3957
  config;
3814
3958
  /**
3815
3959
  * Creates a new AppComponentsService instance.
3816
3960
  * @param config - Service configuration options
3817
3961
  */
3818
- constructor(config = {}) {
3962
+ constructor(config = {}, workspaceRoot) {
3963
+ this.workspaceRoot = workspaceRoot;
3819
3964
  this.config = {
3820
3965
  ...DEFAULT_APP_COMPONENTS_CONFIG,
3821
3966
  ...config
@@ -3832,14 +3977,14 @@ let AppComponentsService = class AppComponentsService {
3832
3977
  if (input.cursor !== void 0 && typeof input.cursor !== "string") throw new Error("cursor must be a string");
3833
3978
  const { appPath, cursor } = input;
3834
3979
  const { offset } = cursor ? this.decodeCursor(cursor) : { offset: 0 };
3835
- const monorepoRoot = TemplatesManagerService.getWorkspaceRootSync();
3980
+ const monorepoRoot = this.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync();
3836
3981
  const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(monorepoRoot, appPath);
3837
3982
  try {
3838
3983
  await node_fs.promises.access(resolvedAppPath);
3839
3984
  } catch {
3840
3985
  throw new Error(`App path does not exist: ${resolvedAppPath}`);
3841
3986
  }
3842
- const storiesIndex = new StoriesIndexService();
3987
+ const storiesIndex = new StoriesIndexService({ workspaceRoot: monorepoRoot });
3843
3988
  const [appName, workspaceDependencies, packageMap] = await Promise.all([
3844
3989
  this.getAppName(resolvedAppPath),
3845
3990
  this.getWorkspaceDependencies(resolvedAppPath),
@@ -4042,18 +4187,42 @@ AppComponentsService = __decorate([
4042
4187
  (0, inversify.injectable)(),
4043
4188
  __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.AppComponentsServiceConfig)),
4044
4189
  __decorateParam(0, (0, inversify.optional)()),
4045
- __decorateMetadata("design:paramtypes", [Object])
4190
+ __decorateParam(1, (0, inversify.inject)(STYLE_SYSTEM_TYPES.WorkspaceRoot)),
4191
+ __decorateParam(1, (0, inversify.optional)()),
4192
+ __decorateMetadata("design:paramtypes", [Object, String])
4046
4193
  ], AppComponentsService);
4047
4194
  //#endregion
4048
4195
  //#region src/tools/ListThemesTool.ts
4196
+ /**
4197
+ * ListThemesTool
4198
+ *
4199
+ * DESIGN PATTERNS:
4200
+ * - Tool pattern with getDefinition() and execute() methods
4201
+ * - Factory pattern for service creation
4202
+ * - JSON Schema validation for inputs
4203
+ *
4204
+ * CODING STANDARDS:
4205
+ * - Implement Tool interface from ../types
4206
+ * - Use TOOL_NAME constant with snake_case (e.g., 'list_themes')
4207
+ * - Return CallToolResult with content array
4208
+ * - Handle errors with isError flag
4209
+ * - Delegate complex logic to services
4210
+ *
4211
+ * AVOID:
4212
+ * - Complex business logic in execute method
4213
+ * - Unhandled promise rejections
4214
+ * - Missing input validation
4215
+ */
4049
4216
  var _ListThemesTool;
4050
4217
  let ListThemesTool = class ListThemesTool {
4051
4218
  static {
4052
4219
  _ListThemesTool = this;
4053
4220
  }
4221
+ configLoader;
4054
4222
  static TOOL_NAME = "list_themes";
4055
4223
  serviceFactory;
4056
- constructor(serviceFactory) {
4224
+ constructor(serviceFactory, configLoader) {
4225
+ this.configLoader = configLoader;
4057
4226
  this.serviceFactory = serviceFactory;
4058
4227
  }
4059
4228
  getDefinition() {
@@ -4075,12 +4244,13 @@ let ListThemesTool = class ListThemesTool {
4075
4244
  let themePath;
4076
4245
  let cssFiles;
4077
4246
  if (input.appPath) {
4078
- const appConfig = await getAppDesignSystemConfig(input.appPath);
4247
+ const appConfig = this.configLoader ? (await this.configLoader.resolveProject(input.appPath)).config : await getAppDesignSystemConfig(input.appPath);
4079
4248
  themePath = appConfig.themePath ? await resolveStyleSystemThemePath({
4080
4249
  appPath: input.appPath,
4081
- themePath: appConfig.themePath
4250
+ themePath: appConfig.themePath,
4251
+ workspaceRoot: this.configLoader?.workspaceRoot
4082
4252
  }) : void 0;
4083
- cssFiles = themePath ? void 0 : appConfig.cssFiles;
4253
+ cssFiles = themePath ? void 0 : appConfig.cssFiles?.map((file) => node_path.default.resolve(this.configLoader?.workspaceRoot ?? TemplatesManagerService.getWorkspaceRootSync(), input.appPath, file));
4084
4254
  }
4085
4255
  const result = await (await this.serviceFactory.createService({
4086
4256
  themePath,
@@ -4104,7 +4274,9 @@ let ListThemesTool = class ListThemesTool {
4104
4274
  ListThemesTool = _ListThemesTool = __decorate([
4105
4275
  (0, inversify.injectable)(),
4106
4276
  __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.ThemeServiceFactory)),
4107
- __decorateMetadata("design:paramtypes", [typeof ThemeServiceFactory === "undefined" ? Object : ThemeServiceFactory])
4277
+ __decorateParam(1, (0, inversify.inject)(STYLE_SYSTEM_TYPES.StyleSystemConfigLoader)),
4278
+ __decorateParam(1, (0, inversify.optional)()),
4279
+ __decorateMetadata("design:paramtypes", [typeof ThemeServiceFactory === "undefined" ? Object : ThemeServiceFactory, typeof StyleSystemConfigLoader === "undefined" ? Object : StyleSystemConfigLoader])
4108
4280
  ], ListThemesTool);
4109
4281
  //#endregion
4110
4282
  //#region src/tools/GetCSSClassesTool.ts
@@ -4131,6 +4303,7 @@ let GetCSSClassesTool = class GetCSSClassesTool {
4131
4303
  static {
4132
4304
  _GetCSSClassesTool = this;
4133
4305
  }
4306
+ configLoader;
4134
4307
  static TOOL_NAME = "get_css_classes";
4135
4308
  static CSS_REUSE_INSTRUCTION = "IMPORTANT: Always reuse these existing CSS classes from the theme as much as possible instead of creating custom styles. This ensures design consistency and reduces CSS bloat.";
4136
4309
  serviceFactory;
@@ -4140,7 +4313,8 @@ let GetCSSClassesTool = class GetCSSClassesTool {
4140
4313
  * @param serviceFactory - Injected CSSClassesServiceFactory
4141
4314
  * @param defaultThemePath - Default path to theme CSS file (relative to workspace root)
4142
4315
  */
4143
- constructor(serviceFactory, defaultThemePath) {
4316
+ constructor(serviceFactory, defaultThemePath, configLoader) {
4317
+ this.configLoader = configLoader;
4144
4318
  this.serviceFactory = serviceFactory;
4145
4319
  this.defaultThemePath = defaultThemePath;
4146
4320
  }
@@ -4185,7 +4359,7 @@ let GetCSSClassesTool = class GetCSSClassesTool {
4185
4359
  const category = input.category || "all";
4186
4360
  if (!isValidCategory(category)) throw new Error(`Invalid category: '${category}'. Must be one of: ${VALID_CATEGORIES.join(", ")}`);
4187
4361
  const themePath = await this.resolveThemePath(input.appPath);
4188
- const workspaceConfig = await getGetCssClassesConfig();
4362
+ const workspaceConfig = this.configLoader ? (await this.configLoader.loadRoot()).getCssClasses : await getGetCssClassesConfig();
4189
4363
  const result = await (await this.serviceFactory.createService({ customServicePath: workspaceConfig?.customService })).extractClasses(category, themePath);
4190
4364
  return { content: [{
4191
4365
  type: "text",
@@ -4214,13 +4388,17 @@ let GetCSSClassesTool = class GetCSSClassesTool {
4214
4388
  */
4215
4389
  async resolveThemePath(appPath) {
4216
4390
  if (appPath) {
4217
- const config = await getAppDesignSystemConfig(appPath);
4391
+ const config = this.configLoader ? (await this.configLoader.resolveProject(appPath)).config : await getAppDesignSystemConfig(appPath);
4218
4392
  if (config.themePath) return await resolveStyleSystemThemePath({
4219
4393
  appPath,
4220
- themePath: config.themePath
4394
+ themePath: config.themePath,
4395
+ workspaceRoot: this.configLoader?.workspaceRoot
4221
4396
  });
4222
4397
  }
4223
- if (this.defaultThemePath) return await resolveStyleSystemThemePath({ themePath: this.defaultThemePath });
4398
+ if (this.defaultThemePath) return await resolveStyleSystemThemePath({
4399
+ themePath: this.defaultThemePath,
4400
+ workspaceRoot: this.configLoader?.workspaceRoot
4401
+ });
4224
4402
  throw new Error("No theme CSS path configured. Pass --theme-path, or set themePath in the target app style-system.config.yaml.");
4225
4403
  }
4226
4404
  };
@@ -4229,156 +4407,15 @@ GetCSSClassesTool = _GetCSSClassesTool = __decorate([
4229
4407
  __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.CSSClassesServiceFactory)),
4230
4408
  __decorateParam(1, (0, inversify.inject)(STYLE_SYSTEM_TYPES.DefaultThemePath)),
4231
4409
  __decorateParam(1, (0, inversify.optional)()),
4232
- __decorateMetadata("design:paramtypes", [typeof CSSClassesServiceFactory === "undefined" ? Object : CSSClassesServiceFactory, String])
4410
+ __decorateParam(2, (0, inversify.inject)(STYLE_SYSTEM_TYPES.StyleSystemConfigLoader)),
4411
+ __decorateParam(2, (0, inversify.optional)()),
4412
+ __decorateMetadata("design:paramtypes", [
4413
+ typeof CSSClassesServiceFactory === "undefined" ? Object : CSSClassesServiceFactory,
4414
+ String,
4415
+ typeof StyleSystemConfigLoader === "undefined" ? Object : StyleSystemConfigLoader
4416
+ ])
4233
4417
  ], GetCSSClassesTool);
4234
4418
  //#endregion
4235
- //#region src/services/AntigravityCliService.ts
4236
- const DEFAULT_MODEL = "gemini-3.6-flash-high";
4237
- const DEFAULT_TIMEOUT = 18e5;
4238
- const MAX_BUFFER = 104857600;
4239
- const FAILURE_SNIPPET_LENGTH = 400;
4240
- const ANTIGRAVITY_CLI_COMMAND = "agy";
4241
- const ANTIGRAVITY_PROVIDER_NAME = "antigravity";
4242
- const ANTIGRAVITY_UNAVAILABLE_MESSAGE = "Antigravity CLI not found. Run `agy install` to configure it.";
4243
- function sanitizePrompt(raw) {
4244
- return raw.replace(/\u0000/g, "").replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, "").replace(/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "").replace(/[\u0001-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F\uFEFF]/g, "");
4245
- }
4246
- function tailSnippet(value) {
4247
- const trimmed = value.trim();
4248
- if (!trimmed) return "";
4249
- return trimmed.length <= FAILURE_SNIPPET_LENGTH ? trimmed : `...${trimmed.slice(-400)}`;
4250
- }
4251
- var AntigravityCliService = class {
4252
- isAvailable() {
4253
- try {
4254
- (0, node_child_process.execSync)(`${ANTIGRAVITY_CLI_COMMAND} --version`, {
4255
- stdio: "ignore",
4256
- timeout: 5e3,
4257
- env: process.env
4258
- });
4259
- return true;
4260
- } catch {
4261
- return false;
4262
- }
4263
- }
4264
- async edit(params) {
4265
- if (!this.isAvailable()) throw new Error(ANTIGRAVITY_UNAVAILABLE_MESSAGE);
4266
- const workspaceRoot = TemplatesManagerService.getWorkspaceRootSync();
4267
- const model = params.model ?? DEFAULT_MODEL;
4268
- const timeout = params.timeout ?? DEFAULT_TIMEOUT;
4269
- let fullPrompt = sanitizePrompt(params.prompt);
4270
- if (params.systemPrompt) fullPrompt = `${sanitizePrompt(params.systemPrompt)}\n\n${fullPrompt}`;
4271
- if (params.screenshotPaths?.length) fullPrompt = `${params.screenshotPaths.map((path) => `@${(0, node_path.basename)(path)}`).join(" ")}\n\n${fullPrompt}`;
4272
- const args = [
4273
- "--new-project",
4274
- "--dangerously-skip-permissions",
4275
- "--print",
4276
- "--print-timeout",
4277
- `${Math.ceil(timeout / 1e3)}s`,
4278
- "--model",
4279
- model,
4280
- fullPrompt
4281
- ];
4282
- return {
4283
- content: (await this.execAntigravity(args, workspaceRoot, model, timeout)).trim(),
4284
- model
4285
- };
4286
- }
4287
- execAntigravity(args, cwd, model, timeout) {
4288
- return new Promise((resolve, reject) => {
4289
- const chunks = [];
4290
- let totalBytes = 0;
4291
- let settled = false;
4292
- let stderrContent = "";
4293
- const registeredArgs = [...args];
4294
- registeredArgs[registeredArgs.length - 1] = "<prompt>";
4295
- const child = (0, node_child_process.spawn)(ANTIGRAVITY_CLI_COMMAND, args, {
4296
- stdio: [
4297
- "ignore",
4298
- "pipe",
4299
- "pipe"
4300
- ],
4301
- env: {
4302
- ...process.env,
4303
- OTEL_SERVICE_NAME: ANTIGRAVITY_PROVIDER_NAME
4304
- },
4305
- cwd
4306
- });
4307
- let releaseChild;
4308
- let registrationPromise;
4309
- if (child.pid) registrationPromise = (0, _agimon_ai_foundation_exec.registerManagedProcess)({
4310
- repositoryPath: cwd,
4311
- serviceName: `style-system-antigravity-${child.pid}`,
4312
- serviceType: "tool",
4313
- environment: process.env["NODE_ENV"] ?? "development",
4314
- pid: child.pid,
4315
- command: ANTIGRAVITY_CLI_COMMAND,
4316
- args: registeredArgs,
4317
- metadata: {
4318
- model,
4319
- parentPid: process.pid
4320
- },
4321
- killOnRelease: true,
4322
- releasePortOnRelease: false
4323
- }).then((release) => {
4324
- releaseChild = release;
4325
- }).catch((error) => {
4326
- log.debug(`[AntigravityCliService] Process registration failed: ${error instanceof Error ? error.message : String(error)}`);
4327
- });
4328
- const finalize = async (callback) => {
4329
- try {
4330
- await registrationPromise;
4331
- await releaseChild?.();
4332
- } catch (error) {
4333
- log.debug(`[AntigravityCliService] Process cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
4334
- } finally {
4335
- callback();
4336
- }
4337
- };
4338
- const timer = setTimeout(() => {
4339
- if (settled) return;
4340
- settled = true;
4341
- child.kill("SIGTERM");
4342
- finalize(() => reject(/* @__PURE__ */ new Error(`Antigravity CLI timed out after ${timeout}ms.`)));
4343
- }, timeout);
4344
- child.stdout.on("data", (chunk) => {
4345
- totalBytes += chunk.length;
4346
- if (totalBytes > MAX_BUFFER) {
4347
- if (!settled) {
4348
- settled = true;
4349
- child.kill("SIGTERM");
4350
- finalize(() => reject(/* @__PURE__ */ new Error("Antigravity CLI output exceeded maximum buffer size.")));
4351
- }
4352
- return;
4353
- }
4354
- chunks.push(chunk);
4355
- });
4356
- child.stderr.on("data", (chunk) => {
4357
- stderrContent += chunk.toString();
4358
- });
4359
- child.on("error", (error) => {
4360
- clearTimeout(timer);
4361
- if (settled) return;
4362
- settled = true;
4363
- const message = error.code === "ENOENT" ? ANTIGRAVITY_UNAVAILABLE_MESSAGE : `Failed to invoke Antigravity CLI: ${error.message}`;
4364
- finalize(() => reject(new Error(message)));
4365
- });
4366
- child.on("close", (code) => {
4367
- clearTimeout(timer);
4368
- if (settled) return;
4369
- settled = true;
4370
- const stdoutContent = Buffer.concat(chunks).toString("utf-8");
4371
- if (code !== 0) {
4372
- const detail = tailSnippet(stderrContent) || tailSnippet(stdoutContent) || `exit code ${code}`;
4373
- finalize(() => reject(new Error(detail)));
4374
- return;
4375
- }
4376
- finalize(() => resolve(stdoutContent));
4377
- });
4378
- });
4379
- }
4380
- };
4381
- //#endregion
4382
4419
  //#region src/tools/GetComponentVisualTool.ts
4383
4420
  var _GetComponentVisualTool;
4384
4421
  /**
@@ -4507,11 +4544,13 @@ let ListSharedComponentsTool = class ListSharedComponentsTool {
4507
4544
  static {
4508
4545
  _ListSharedComponentsTool = this;
4509
4546
  }
4547
+ configLoader;
4510
4548
  static TOOL_NAME = "list_shared_components";
4511
4549
  static PAGE_SIZE = 50;
4512
4550
  static COMPONENT_REUSE_INSTRUCTION = "IMPORTANT: Before creating new components, check if a similar component already exists in the list above. Always reuse existing shared components to maintain consistency and reduce code duplication.";
4513
4551
  storiesIndexFactory;
4514
- constructor(storiesIndexFactory) {
4552
+ constructor(storiesIndexFactory, configLoader) {
4553
+ this.configLoader = configLoader;
4515
4554
  this.storiesIndexFactory = storiesIndexFactory;
4516
4555
  }
4517
4556
  /**
@@ -4570,7 +4609,7 @@ let ListSharedComponentsTool = class ListSharedComponentsTool {
4570
4609
  const { cursor, tags: inputTags } = input;
4571
4610
  const { offset } = cursor ? this.decodeCursor(cursor) : { offset: 0 };
4572
4611
  const storiesIndex = this.storiesIndexFactory();
4573
- const [, defaultTags] = await Promise.all([storiesIndex.initialize(), getSharedComponentTags()]);
4612
+ const [, defaultTags] = await Promise.all([storiesIndex.initialize(), this.configLoader ? this.configLoader.loadRoot().then((root) => root.sharedComponentTags ?? DEFAULT_SHARED_COMPONENT_TAGS) : getSharedComponentTags()]);
4574
4613
  const availableTags = storiesIndex.getAllTags();
4575
4614
  const filterTags = inputTags !== void 0 ? inputTags : defaultTags;
4576
4615
  const allComponentNames = storiesIndex.getComponentsByTags(filterTags.length > 0 ? filterTags : void 0).map((component) => component.title.split("/").pop() || component.title).filter((name, index, self) => self.indexOf(name) === index).sort();
@@ -4608,7 +4647,9 @@ let ListSharedComponentsTool = class ListSharedComponentsTool {
4608
4647
  ListSharedComponentsTool = _ListSharedComponentsTool = __decorate([
4609
4648
  (0, inversify.injectable)(),
4610
4649
  __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.StoriesIndexFactory)),
4611
- __decorateMetadata("design:paramtypes", [Object])
4650
+ __decorateParam(1, (0, inversify.inject)(STYLE_SYSTEM_TYPES.StyleSystemConfigLoader)),
4651
+ __decorateParam(1, (0, inversify.optional)()),
4652
+ __decorateMetadata("design:paramtypes", [Object, typeof StyleSystemConfigLoader === "undefined" ? Object : StyleSystemConfigLoader])
4612
4653
  ], ListSharedComponentsTool);
4613
4654
  //#endregion
4614
4655
  //#region src/tools/ListAppComponentsTool.ts
@@ -4681,309 +4722,31 @@ ListAppComponentsTool = _ListAppComponentsTool = __decorate([
4681
4722
  __decorateMetadata("design:paramtypes", [typeof AppComponentsService === "undefined" ? Object : AppComponentsService])
4682
4723
  ], ListAppComponentsTool);
4683
4724
  //#endregion
4684
- //#region src/tools/PolishComponentTool.ts
4685
- /**
4686
- * PolishComponentTool
4687
- *
4688
- * DESIGN PATTERNS:
4689
- * - Tool pattern with getDefinition() and execute() methods
4690
- * - Service delegation for business logic
4691
- * - JSON Schema validation for inputs
4692
- *
4693
- * CODING STANDARDS:
4694
- * - Implement Tool interface from ../types
4695
- * - Use TOOL_NAME constant with snake_case
4696
- * - Return CallToolResult with content array
4697
- * - Handle errors with isError flag
4698
- * - Delegate complex logic to services
4699
- *
4700
- * AVOID:
4701
- * - Complex business logic in execute method
4702
- * - Unhandled promise rejections
4703
- * - Missing input validation
4704
- */
4705
- var _PolishComponentTool;
4706
- let PolishComponentTool = class PolishComponentTool {
4707
- static {
4708
- _PolishComponentTool = this;
4709
- }
4710
- static TOOL_NAME = "polish_component";
4711
- antigravityService;
4712
- getUiComponentService;
4713
- cssClassesServiceFactory;
4714
- storiesIndexFactory;
4715
- constructor(getUiComponentService, cssClassesServiceFactory, storiesIndexFactory) {
4716
- this.antigravityService = new AntigravityCliService();
4717
- this.getUiComponentService = getUiComponentService;
4718
- this.cssClassesServiceFactory = cssClassesServiceFactory;
4719
- this.storiesIndexFactory = storiesIndexFactory;
4720
- }
4721
- getDefinition() {
4722
- return {
4723
- name: _PolishComponentTool.TOOL_NAME,
4724
- description: "Polish a UI component by running Antigravity to directly edit the component file. Takes a .tsx file path, finds the collocated stories file for visual context, gathers design principles, design tokens, and shared components, then runs Antigravity to edit in place.",
4725
- inputSchema: {
4726
- type: "object",
4727
- properties: {
4728
- filePath: {
4729
- type: "string",
4730
- minLength: 1,
4731
- description: "Path to the component .tsx file to polish (e.g., \"packages/frontend/web-atoms/src/components/Button/index.tsx\")"
4732
- },
4733
- instructions: {
4734
- type: "string",
4735
- description: "Specific polishing instructions (e.g., \"improve spacing\", \"add hover states\", \"fix dark mode\")"
4736
- }
4737
- },
4738
- required: ["filePath"],
4739
- additionalProperties: false
4740
- }
4741
- };
4742
- }
4743
- async execute(input) {
4744
- try {
4745
- if (!this.antigravityService.isAvailable()) return {
4746
- content: [{
4747
- type: "text",
4748
- text: "Error: Antigravity CLI is not installed. Run `agy install` to configure it."
4749
- }],
4750
- isError: true
4751
- };
4752
- const workspaceRoot = TemplatesManagerService.getWorkspaceRootSync();
4753
- const { instructions } = input;
4754
- const filePath = node_path.default.isAbsolute(input.filePath) ? input.filePath : node_path.default.join(workspaceRoot, input.filePath);
4755
- const componentDir = node_path.default.dirname(filePath);
4756
- const storyFile = await this.findStoriesFile(componentDir);
4757
- if (!storyFile) return {
4758
- content: [{
4759
- type: "text",
4760
- text: `Error: No .stories.tsx file found in ${componentDir}`
4761
- }],
4762
- isError: true
4763
- };
4764
- const componentName = node_path.default.basename(componentDir);
4765
- const appPath = await this.findAppPath(filePath, workspaceRoot);
4766
- const dsConfig = appPath ? await getAppDesignSystemConfig(appPath).catch((error) => {
4767
- log.debug(`[PolishComponentTool] Could not load design system config for ${appPath}: ${error instanceof Error ? error.message : String(error)}`);
4768
- return null;
4769
- }) : null;
4770
- const platform = this.detectPlatform(dsConfig);
4771
- log.info(`[PolishComponentTool] Polishing ${filePath}, stories: ${storyFile}, appPath: ${appPath}, platform: ${platform}`);
4772
- const [componentVisual, designPrinciples, sharedComponents, componentSource, designTokens] = await Promise.allSettled([
4773
- appPath ? this.getComponentVisual(componentName, appPath) : Promise.reject(/* @__PURE__ */ new Error("No appPath")),
4774
- appPath ? this.getDesignPrinciples(appPath) : Promise.reject(/* @__PURE__ */ new Error("No appPath")),
4775
- this.getSharedComponents(),
4776
- node_fs.promises.readFile(filePath, "utf-8"),
4777
- this.getDesignTokens(platform, dsConfig, appPath)
4778
- ]);
4779
- const contextSections = [];
4780
- if (componentSource.status === "fulfilled") contextSections.push(`## Component Source\n\nFile: ${filePath}\n\n\`\`\`tsx\n${componentSource.value}\n\`\`\``);
4781
- const storyContent = await node_fs.promises.readFile(storyFile, "utf-8").catch((error) => {
4782
- log.debug(`[PolishComponentTool] Could not read stories file ${storyFile}: ${error instanceof Error ? error.message : String(error)}`);
4783
- return null;
4784
- });
4785
- if (storyContent) contextSections.push(`## Stories File\n\nFile: ${storyFile}\n\n\`\`\`tsx\n${storyContent}\n\`\`\``);
4786
- if (designPrinciples.status === "fulfilled" && designPrinciples.value.length > 0) {
4787
- const principlesText = designPrinciples.value.map((p) => `### ${p.variant}\n${js_yaml.dump(p.content, {
4788
- indent: 2,
4789
- lineWidth: 120
4790
- })}`).join("\n");
4791
- contextSections.push(`## Design Principles\n\n${principlesText}`);
4792
- }
4793
- if (designTokens.status === "fulfilled") contextSections.push(designTokens.value);
4794
- if (sharedComponents.status === "fulfilled") contextSections.push(`## Available Shared Components\n\nReuse these existing components where possible:\n${sharedComponents.value}`);
4795
- const systemPrompt = this.buildSystemPrompt(platform);
4796
- const userPrompt = [
4797
- `# Polish Component`,
4798
- "",
4799
- instructions ? `## Instructions\n\n${instructions}\n` : "",
4800
- ...contextSections,
4801
- "",
4802
- `Edit the component file at: ${filePath}`,
4803
- "Polish this component following the design system. Edit the files directly."
4804
- ].filter(Boolean).join("\n");
4805
- const screenshotPaths = componentVisual.status === "fulfilled" ? [componentVisual.value.imagePath] : [];
4806
- log.info(`[PolishComponentTool] Running Antigravity to edit component...`);
4807
- const response = await this.antigravityService.edit({
4808
- prompt: userPrompt,
4809
- systemPrompt,
4810
- screenshotPaths
4811
- });
4812
- log.info(`[PolishComponentTool] Antigravity finished editing (model: ${response.model})`);
4813
- const reviewPrompt = [
4814
- "\n## Review the changes",
4815
- "",
4816
- "Antigravity has edited the component files directly. Review the result with `pnpm exec style-system get-ui-component`:",
4817
- ` - componentName: "${componentName}"`,
4818
- appPath ? ` - appPath: "${appPath}"` : "",
4819
- "",
4820
- `Component file: ${filePath}`,
4821
- `Stories file: ${storyFile}`
4822
- ].filter(Boolean).join("\n");
4823
- return { content: [{
4824
- type: "text",
4825
- text: response.content
4826
- }, {
4827
- type: "text",
4828
- text: reviewPrompt
4829
- }] };
4830
- } catch (error) {
4831
- return {
4832
- content: [{
4833
- type: "text",
4834
- text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
4835
- }],
4836
- isError: true
4837
- };
4838
- }
4839
- }
4840
- detectPlatform(config) {
4841
- if (config?.bundler?.includes("native")) return "native";
4842
- return "web";
4843
- }
4844
- buildSystemPrompt(platform) {
4845
- const base = [
4846
- "You are a senior frontend designer and developer.",
4847
- "You polish UI components to improve their visual quality, consistency, and adherence to the design system.",
4848
- "You receive the component source code, a screenshot of its current rendering, the design principles, available design tokens, and shared components.",
4849
- "Your job is to directly edit the component files to improve them.",
4850
- "",
4851
- "CRITICAL RULES:"
4852
- ];
4853
- if (platform === "native") base.push("- This is a React Native component using react-native-unistyles v3 for styling.", "- ONLY use theme tokens from the UnistylesTheme interface listed in the \"Unistyles Theme Tokens\" section.", "- Access tokens via StyleSheet.create((theme) => ({...})) — e.g., theme.colors.primary, theme.spacing.md, theme.borderRadius.lg, theme.fontSize.base.", "- NEVER use hardcoded color hex values, pixel numbers, or inline styles when a theme token exists.", "- If a semantic value is not available in the theme tokens, use the closest match or leave it unchanged.");
4854
- else base.push("- ONLY use CSS classes listed in the \"Available CSS Classes\" section. NEVER use raw Tailwind color/spacing classes (e.g., green-500, red-600, p-4) that are not in the provided design tokens.", "- If a semantic color or spacing value is not available in the design tokens, use the closest match from the provided list or leave it unchanged. Do NOT invent new color values.");
4855
- base.push("- Reuse shared components instead of creating custom markup", "- Follow the design principles strictly", "- Edit the files directly — do not just suggest changes", "- Explain what you changed after editing");
4856
- if (platform === "native") base.push("", "VIOLATION EXAMPLES (DO NOT DO THIS):", "- { color: \"#22c55e\" } → hardcoded hex, use theme.colors.success instead", "- { padding: 16 } → hardcoded pixel, use theme.spacing.md instead", "- { borderRadius: 8 } → hardcoded pixel, use theme.borderRadius.lg instead");
4857
- else base.push("", "VIOLATION EXAMPLES (DO NOT DO THIS):", "- bg-green-500/10 text-green-700 → raw colors, not from design tokens", "- border-red-600/30 → raw color, not from design tokens", "- Instead use semantic classes like bg-destructive, text-destructive-foreground, bg-accent, etc.");
4858
- return base.join("\n");
4859
- }
4860
- /**
4861
- * Read design tokens from config.themePath.
4862
- * For web: extracts Tailwind CSS classes. For native: reads the TypeScript types file.
4863
- */
4864
- async getDesignTokens(platform, config, appPath) {
4865
- if (!config?.themePath) return `No themePath configured in style-system.config.yaml.`;
4866
- const themePath = await resolveStyleSystemThemePath({
4867
- appPath,
4868
- themePath: config.themePath
4869
- });
4870
- if (platform === "native") return `## Unistyles Theme Tokens\n\nUse these tokens via \`StyleSheet.create((theme) => ({...}))\`:\n\n\`\`\`typescript\n${await node_fs.promises.readFile(themePath, "utf-8")}\n\`\`\``;
4871
- const toolkitConfig = await getGetCssClassesConfig();
4872
- const result = await (await this.cssClassesServiceFactory.createService({ customServicePath: toolkitConfig?.customService })).extractClasses("all", themePath);
4873
- return `## Available CSS Classes (Design Tokens)\n\n${JSON.stringify(result, null, 2)}`;
4874
- }
4875
- async findStoriesFile(dir) {
4876
- for (const pattern of [
4877
- "*.stories.tsx",
4878
- "*.stories.ts",
4879
- "*.stories.jsx",
4880
- "*.stories.js"
4881
- ]) {
4882
- const matches = await (0, glob.glob)(pattern, {
4883
- cwd: dir,
4884
- absolute: true
4885
- });
4886
- if (matches.length > 0) return matches[0];
4887
- }
4888
- return null;
4889
- }
4890
- /**
4891
- * Find the app that owns a file, for style-system context.
4892
- * Only apps declaring their own style-system.config.yaml are eligible.
4893
- */
4894
- async findAppPath(filePath, workspaceRoot) {
4895
- const parts = node_path.default.relative(workspaceRoot, filePath).split(node_path.default.sep);
4896
- if (parts[0] === "apps" && parts.length > 1) {
4897
- const appPath = node_path.default.join(parts[0], parts[1]);
4898
- if (await hasStyleSystemProjectConfig(appPath)) return appPath;
4899
- log.debug(`[PolishComponentTool] ${appPath} has no style-system config`);
4900
- }
4901
- const appsDir = node_path.default.join(workspaceRoot, "apps");
4902
- let candidates;
4903
- try {
4904
- candidates = (await node_fs.promises.readdir(appsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory() && entry.name.endsWith("-app")).map((entry) => entry.name).sort();
4905
- } catch (error) {
4906
- log.debug(`[PolishComponentTool] Could not scan apps dir: ${error instanceof Error ? error.message : String(error)}`);
4907
- return null;
4908
- }
4909
- for (const name of candidates) {
4910
- const appPath = `apps/${name}`;
4911
- if (await hasStyleSystemProjectConfig(appPath)) return appPath;
4912
- log.debug(`[PolishComponentTool] ${appPath} has no style-system config`);
4913
- }
4914
- return null;
4915
- }
4916
- async getComponentVisual(componentName, appPath) {
4917
- return { imagePath: (await this.getUiComponentService.getComponent({
4918
- componentName,
4919
- appPath,
4920
- darkMode: true
4921
- })).imagePath };
4922
- }
4923
- async getDesignPrinciples(appPath) {
4924
- const workspaceRoot = TemplatesManagerService.getWorkspaceRootSync();
4925
- const product = node_path.default.basename(appPath).replace(/-app$|-native-app$|-mcp-app$|-e2e$/, "");
4926
- const principlesDir = node_path.default.join(workspaceRoot, "docs", "architecture", product, "design-principles");
4927
- const results = [];
4928
- try {
4929
- const files = await node_fs.promises.readdir(principlesDir);
4930
- for (const file of files) {
4931
- if (!file.endsWith(".yaml")) continue;
4932
- try {
4933
- const content = await node_fs.promises.readFile(node_path.default.join(principlesDir, file), "utf-8");
4934
- const parsed = js_yaml.load(content);
4935
- const variant = file.replace(".yaml", "");
4936
- results.push({
4937
- variant,
4938
- content: parsed
4939
- });
4940
- } catch (error) {
4941
- log.debug(`[PolishComponentTool] Skipping unreadable design principle ${file}: ${error instanceof Error ? error.message : String(error)}`);
4942
- }
4943
- }
4944
- } catch (error) {
4945
- log.debug(`[PolishComponentTool] No design principles at ${principlesDir}: ${error instanceof Error ? error.message : String(error)}`);
4946
- }
4947
- return results;
4948
- }
4949
- async getSharedComponents() {
4950
- const storiesIndex = this.storiesIndexFactory();
4951
- const [, defaultTags] = await Promise.all([storiesIndex.initialize(), getSharedComponentTags()]);
4952
- return storiesIndex.getComponentsByTags(defaultTags.length > 0 ? defaultTags : void 0).map((c) => c.title.split("/").pop() || c.title).filter((name, index, self) => self.indexOf(name) === index).sort().map((n) => `- ${n}`).join("\n");
4953
- }
4954
- };
4955
- PolishComponentTool = _PolishComponentTool = __decorate([
4956
- (0, inversify.injectable)(),
4957
- __decorateParam(0, (0, inversify.inject)(STYLE_SYSTEM_TYPES.GetUiComponentService)),
4958
- __decorateParam(1, (0, inversify.inject)(STYLE_SYSTEM_TYPES.CSSClassesServiceFactory)),
4959
- __decorateParam(2, (0, inversify.inject)(STYLE_SYSTEM_TYPES.StoriesIndexFactory)),
4960
- __decorateMetadata("design:paramtypes", [
4961
- typeof GetUiComponentService === "undefined" ? Object : GetUiComponentService,
4962
- typeof CSSClassesServiceFactory === "undefined" ? Object : CSSClassesServiceFactory,
4963
- Object
4964
- ])
4965
- ], PolishComponentTool);
4966
- //#endregion
4967
4725
  //#region src/container/module.ts
4968
4726
  function createStyleSystemModule(optionsInput = {}) {
4969
4727
  return new inversify.ContainerModule((options) => {
4728
+ options.bind(STYLE_SYSTEM_TYPES.WorkspaceRoot).toConstantValue(optionsInput.workspaceRoot);
4970
4729
  options.bind(STYLE_SYSTEM_TYPES.StyleSystemConfigLoader).to(StyleSystemConfigLoader).inSingletonScope();
4971
4730
  options.bind(STYLE_SYSTEM_TYPES.DefaultThemePath).toConstantValue(optionsInput.defaultThemePath);
4972
4731
  options.bind(STYLE_SYSTEM_TYPES.GetUiComponentServiceConfig).toConstantValue(DEFAULT_GET_UI_COMPONENT_CONFIG);
4973
4732
  options.bind(STYLE_SYSTEM_TYPES.AppComponentsServiceConfig).toConstantValue(DEFAULT_APP_COMPONENTS_CONFIG);
4974
4733
  options.bind(STYLE_SYSTEM_TYPES.ThemeServiceFactory).to(ThemeServiceFactory).inSingletonScope();
4975
4734
  options.bind(STYLE_SYSTEM_TYPES.CSSClassesServiceFactory).to(CSSClassesServiceFactory).inSingletonScope();
4976
- options.bind(STYLE_SYSTEM_TYPES.StoriesIndexService).to(StoriesIndexService).inTransientScope();
4735
+ options.bind(STYLE_SYSTEM_TYPES.StoriesIndexService).toDynamicValue(() => new StoriesIndexService({ workspaceRoot: optionsInput.workspaceRoot }));
4977
4736
  options.bind(STYLE_SYSTEM_TYPES.AppComponentsService).to(AppComponentsService).inTransientScope();
4978
4737
  options.bind(STYLE_SYSTEM_TYPES.GetUiComponentService).to(GetUiComponentService).inSingletonScope();
4979
- options.bind(STYLE_SYSTEM_TYPES.StoriesIndexFactory).toConstantValue(() => new StoriesIndexService());
4980
- options.bind(STYLE_SYSTEM_TYPES.RendererFactory).toConstantValue((config, appPath) => new ComponentRendererService(config, appPath));
4738
+ options.bind(STYLE_SYSTEM_TYPES.StoriesIndexFactory).toConstantValue(() => new StoriesIndexService({ workspaceRoot: optionsInput.workspaceRoot }));
4739
+ options.bind(STYLE_SYSTEM_TYPES.RendererFactory).toConstantValue((config, appPath) => new ComponentRendererService(config, appPath, {
4740
+ workspaceRoot: optionsInput.workspaceRoot,
4741
+ temporaryDirectory: optionsInput.temporaryDirectory,
4742
+ sharedBrowser: optionsInput.sharedBrowser,
4743
+ ...optionsInput.bundlerFactory ? { bundlerFactory: () => optionsInput.bundlerFactory(config) } : {}
4744
+ }));
4981
4745
  options.bind(STYLE_SYSTEM_TYPES.ListThemesTool).to(ListThemesTool).inSingletonScope();
4982
4746
  options.bind(STYLE_SYSTEM_TYPES.GetCSSClassesTool).to(GetCSSClassesTool).inSingletonScope();
4983
4747
  options.bind(STYLE_SYSTEM_TYPES.GetComponentVisualTool).to(GetComponentVisualTool).inSingletonScope();
4984
4748
  options.bind(STYLE_SYSTEM_TYPES.ListSharedComponentsTool).to(ListSharedComponentsTool).inSingletonScope();
4985
4749
  options.bind(STYLE_SYSTEM_TYPES.ListAppComponentsTool).to(ListAppComponentsTool).inSingletonScope();
4986
- options.bind(STYLE_SYSTEM_TYPES.PolishComponentTool).to(PolishComponentTool).inSingletonScope();
4987
4750
  });
4988
4751
  }
4989
4752
  //#endregion
@@ -4995,7 +4758,7 @@ function createContainer(options) {
4995
4758
  }
4996
4759
  //#endregion
4997
4760
  //#region package.json
4998
- var version = "0.2.14-alpha.2";
4761
+ var version = "0.2.14-alpha.21";
4999
4762
  //#endregion
5000
4763
  //#region src/metadata.ts
5001
4764
  const STYLE_SYSTEM_CLI_NAME = "style-system";
@@ -5029,7 +4792,6 @@ function createServer(containerOrThemePath) {
5029
4792
  const getComponentVisualTool = container.get(STYLE_SYSTEM_TYPES.GetComponentVisualTool);
5030
4793
  const listSharedComponentsTool = container.get(STYLE_SYSTEM_TYPES.ListSharedComponentsTool);
5031
4794
  const listAppComponentsTool = container.get(STYLE_SYSTEM_TYPES.ListAppComponentsTool);
5032
- const polishComponentTool = container.get(STYLE_SYSTEM_TYPES.PolishComponentTool);
5033
4795
  server.setRequestHandler("tools/list", async () => {
5034
4796
  return { tools: [
5035
4797
  withCapabilities(listThemesTool.getDefinition(), [
@@ -5057,12 +4819,6 @@ function createServer(containerOrThemePath) {
5057
4819
  "components",
5058
4820
  "app-components",
5059
4821
  "component-discovery"
5060
- ]),
5061
- withCapabilities(polishComponentTool.getDefinition(), [
5062
- "components",
5063
- "design-review",
5064
- "polish",
5065
- "antigravity"
5066
4822
  ])
5067
4823
  ].map((tool) => {
5068
4824
  if (tool.inputSchema.type !== "object") throw new Error(`Invalid schema for ${tool.name}`);
@@ -5079,8 +4835,7 @@ function createServer(containerOrThemePath) {
5079
4835
  getCSSClassesTool,
5080
4836
  getComponentVisualTool,
5081
4837
  listSharedComponentsTool,
5082
- listAppComponentsTool,
5083
- polishComponentTool
4838
+ listAppComponentsTool
5084
4839
  ].find((candidate) => candidate.getDefinition().name === name);
5085
4840
  if (!tool) return {
5086
4841
  content: [{
@@ -5333,10 +5088,10 @@ Object.defineProperty(exports, "ListThemesTool", {
5333
5088
  return ListThemesTool;
5334
5089
  }
5335
5090
  });
5336
- Object.defineProperty(exports, "PolishComponentTool", {
5091
+ Object.defineProperty(exports, "MAX_STYLE_SYSTEM_VIEWPORT_DIMENSION", {
5337
5092
  enumerable: true,
5338
5093
  get: function() {
5339
- return PolishComponentTool;
5094
+ return MAX_STYLE_SYSTEM_VIEWPORT_DIMENSION;
5340
5095
  }
5341
5096
  });
5342
5097
  Object.defineProperty(exports, "STYLE_SYSTEM_CLI_NAME", {
@@ -5399,6 +5154,12 @@ Object.defineProperty(exports, "ViteReactBundlerService", {
5399
5154
  return ViteReactBundlerService;
5400
5155
  }
5401
5156
  });
5157
+ Object.defineProperty(exports, "ViteReactNativeBundlerService", {
5158
+ enumerable: true,
5159
+ get: function() {
5160
+ return ViteReactNativeBundlerService;
5161
+ }
5162
+ });
5402
5163
  Object.defineProperty(exports, "__toESM", {
5403
5164
  enumerable: true,
5404
5165
  get: function() {
@@ -5477,6 +5238,12 @@ Object.defineProperty(exports, "isContained", {
5477
5238
  return isContained;
5478
5239
  }
5479
5240
  });
5241
+ Object.defineProperty(exports, "launchBrowserWithFallback", {
5242
+ enumerable: true,
5243
+ get: function() {
5244
+ return launchBrowserWithFallback;
5245
+ }
5246
+ });
5480
5247
  Object.defineProperty(exports, "log", {
5481
5248
  enumerable: true,
5482
5249
  get: function() {