zelune 1.6.2 → 1.6.4

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/cli.mjs +13 -54
  3. package/dist/assets/CheckoutScreen-6IdrpKgm.js +229 -0
  4. package/dist/assets/HomeScreen-Dxe9L4I2.js +316 -0
  5. package/dist/assets/LoginScreen-Dp1awI4F.js +189 -0
  6. package/dist/assets/ProductDetailScreen-Wa5_nlRP.js +196 -0
  7. package/dist/assets/ProfileScreen-riAU6vVh.js +185 -0
  8. package/dist/assets/index-hEzd4JVy.js +44697 -0
  9. package/{index.html → dist/index.html} +3 -7
  10. package/mcp/server.mjs +1370 -34
  11. package/package.json +10 -17
  12. package/plugin.json +1 -1
  13. package/socket.yml +11 -0
  14. package/mcp/server.ts +0 -446
  15. package/plugins/viteReactNativeFallbackPlugin.ts +0 -519
  16. package/plugins/viteRnSourceInject.ts +0 -117
  17. package/plugins/viteVirtualScreensPlugin.ts +0 -216
  18. package/src/canvas/CanvasApp.tsx +0 -1655
  19. package/src/canvas/FlowArrowsLayer.tsx +0 -239
  20. package/src/canvas/InspectorPanel.tsx +0 -1494
  21. package/src/canvas/ScreenArtboard.tsx +0 -1491
  22. package/src/canvas/types.ts +0 -23
  23. package/src/main.tsx +0 -80
  24. package/src/runtime/MockNavigationContext.tsx +0 -1795
  25. package/src/runtime/ScreenErrorBoundary.tsx +0 -86
  26. package/src/runtime/nativewindShim.ts +0 -997
  27. package/src/runtime/reactNativeShim.ts +0 -240
  28. package/src/screens/CheckoutScreen.tsx +0 -229
  29. package/src/screens/HomeScreen.tsx +0 -288
  30. package/src/screens/LoginScreen.tsx +0 -176
  31. package/src/screens/ProductDetailScreen.tsx +0 -207
  32. package/src/screens/ProfileScreen.tsx +0 -172
  33. package/src/screens/index.ts +0 -1
  34. package/src/server/apiMiddleware.ts +0 -375
  35. package/src/server/astEngine.ts +0 -2553
  36. package/src/vite-env.d.ts +0 -7
  37. package/tsconfig.json +0 -23
  38. package/vite.config.ts +0 -340
  39. /package/{src/styles.css → dist/assets/index-C7nrS82N.css} +0 -0
package/mcp/server.mjs CHANGED
@@ -1,14 +1,13 @@
1
- import fsSync from "node:fs";
1
+ import fs$1 from "node:fs";
2
2
  import path from "node:path";
3
3
  import readline from "node:readline";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import fs from "node:fs/promises";
6
6
  import { parse } from "@babel/parser";
7
- import _traverse from "@babel/traverse";
8
7
  import _generate from "@babel/generator";
9
8
  import * as t from "@babel/types";
10
- _traverse.default ?? _traverse;
11
- _generate.default ?? _generate;
9
+ import http from "node:http";
10
+ const generate$1 = _generate.default ?? _generate;
12
11
  function getJsxTagName(nameNode) {
13
12
  if (t.isJSXIdentifier(nameNode)) {
14
13
  return nameNode.name;
@@ -21,8 +20,51 @@ function getJsxTagName(nameNode) {
21
20
  }
22
21
  return "Unknown";
23
22
  }
24
- const traverse = _traverse.default ?? _traverse;
25
- const generate = _generate.default ?? _generate;
23
+ function traverse(rootNode, visitors) {
24
+ function createPath(node, parentPath) {
25
+ return {
26
+ node,
27
+ parentPath,
28
+ isJSXElement() {
29
+ return t.isJSXElement(node);
30
+ },
31
+ isJSXFragment() {
32
+ return t.isJSXFragment(node);
33
+ },
34
+ findParent(predicate) {
35
+ let curr = parentPath;
36
+ while (curr) {
37
+ if (predicate(curr)) return curr;
38
+ curr = curr.parentPath;
39
+ }
40
+ return null;
41
+ }
42
+ };
43
+ }
44
+ function visit(node, parentPath) {
45
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
46
+ const typedNode = node;
47
+ const pathObj = createPath(typedNode, parentPath);
48
+ const handler = visitors[typedNode.type];
49
+ if (typeof handler === "function") {
50
+ handler(pathObj);
51
+ }
52
+ const keys = t.VISITOR_KEYS[typedNode.type];
53
+ if (!keys) return;
54
+ const rec = typedNode;
55
+ for (const key of keys) {
56
+ const child = rec[key];
57
+ if (Array.isArray(child)) {
58
+ for (let i = 0; i < child.length; i++) {
59
+ if (child[i]) visit(child[i], pathObj);
60
+ }
61
+ } else if (child && typeof child === "object") {
62
+ visit(child, pathObj);
63
+ }
64
+ }
65
+ }
66
+ visit(rootNode, null);
67
+ }
26
68
  const MAX_HISTORY = 50;
27
69
  const undoStack = [];
28
70
  const redoStack = [];
@@ -58,7 +100,7 @@ function countScreenFilesInDirectory(screensDir, maxDepth = 4) {
58
100
  if (depth > maxDepth) return;
59
101
  let entries = [];
60
102
  try {
61
- entries = fsSync.readdirSync(curr, { withFileTypes: true });
103
+ entries = fs$1.readdirSync(curr, { withFileTypes: true });
62
104
  } catch {
63
105
  return;
64
106
  }
@@ -87,7 +129,7 @@ function hasDirectScreenFiles(dirPath) {
87
129
  for (const rel of SCREEN_DIR_CANDIDATES) {
88
130
  const full = path.join(dirPath, rel);
89
131
  try {
90
- if (fsSync.existsSync(full) && fsSync.statSync(full).isDirectory()) {
132
+ if (fs$1.existsSync(full) && fs$1.statSync(full).isDirectory()) {
91
133
  const count = countScreenFilesInDirectory(full);
92
134
  if (count > 0) {
93
135
  return { found: true, screenCount: count };
@@ -104,13 +146,13 @@ function scoreMobileCandidate(dirPath, screenCount) {
104
146
  if (/mobile|native|expo|app|client/.test(folderName)) {
105
147
  score += 10;
106
148
  }
107
- if (fsSync.existsSync(path.join(dirPath, "app.json")) || fsSync.existsSync(path.join(dirPath, "eas.json"))) {
149
+ if (fs$1.existsSync(path.join(dirPath, "app.json")) || fs$1.existsSync(path.join(dirPath, "eas.json"))) {
108
150
  score += 25;
109
151
  }
110
152
  try {
111
153
  const pkgPath = path.join(dirPath, "package.json");
112
- if (fsSync.existsSync(pkgPath)) {
113
- const pkgRaw = fsSync.readFileSync(pkgPath, "utf-8");
154
+ if (fs$1.existsSync(pkgPath)) {
155
+ const pkgRaw = fs$1.readFileSync(pkgPath, "utf-8");
114
156
  if (pkgRaw.includes('"react-native"') || pkgRaw.includes('"expo"')) {
115
157
  score += 20;
116
158
  }
@@ -121,7 +163,7 @@ function scoreMobileCandidate(dirPath, screenCount) {
121
163
  }
122
164
  function findMobileAppRoot(rawRoot) {
123
165
  const normalizedRoot = path.resolve(rawRoot);
124
- if (!fsSync.existsSync(normalizedRoot)) {
166
+ if (!fs$1.existsSync(normalizedRoot)) {
125
167
  return normalizedRoot;
126
168
  }
127
169
  const direct = hasDirectScreenFiles(normalizedRoot);
@@ -134,7 +176,7 @@ function findMobileAppRoot(rawRoot) {
134
176
  if (depth > 2) return;
135
177
  let entries = [];
136
178
  try {
137
- entries = fsSync.readdirSync(parentDir, { withFileTypes: true });
179
+ entries = fs$1.readdirSync(parentDir, { withFileTypes: true });
138
180
  } catch {
139
181
  return;
140
182
  }
@@ -159,20 +201,20 @@ function findMobileAppRoot(rawRoot) {
159
201
  }
160
202
  function getActiveProjectRoot(fallbackRoot) {
161
203
  const envTarget = process.env.ANTIGRAVITY_TARGET_PROJECT;
162
- const baseRoot = envTarget && fsSync.existsSync(envTarget) ? path.resolve(envTarget) : path.resolve(fallbackRoot);
204
+ const baseRoot = envTarget && fs$1.existsSync(envTarget) ? path.resolve(envTarget) : path.resolve(fallbackRoot);
163
205
  return findMobileAppRoot(baseRoot);
164
206
  }
165
207
  function resolveScreensDirectory(projectRoot) {
166
208
  const normalizedRoot = findMobileAppRoot(projectRoot);
167
209
  for (const rel of SCREEN_DIR_CANDIDATES) {
168
210
  const full = path.join(normalizedRoot, rel);
169
- if (fsSync.existsSync(full) && fsSync.statSync(full).isDirectory() && countScreenFilesInDirectory(full) > 0) {
211
+ if (fs$1.existsSync(full) && fs$1.statSync(full).isDirectory() && countScreenFilesInDirectory(full) > 0) {
170
212
  return { dirPath: full, relDir: rel };
171
213
  }
172
214
  }
173
215
  for (const rel of SCREEN_DIR_CANDIDATES) {
174
216
  const full = path.join(normalizedRoot, rel);
175
- if (fsSync.existsSync(full) && fsSync.statSync(full).isDirectory()) {
217
+ if (fs$1.existsSync(full) && fs$1.statSync(full).isDirectory()) {
176
218
  return { dirPath: full, relDir: rel };
177
219
  }
178
220
  }
@@ -184,7 +226,7 @@ function resolveScreensDirectory(projectRoot) {
184
226
  const FORBIDDEN_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
185
227
  function safeRealpath(targetPath) {
186
228
  try {
187
- return fsSync.realpathSync(targetPath);
229
+ return fs$1.realpathSync(targetPath);
188
230
  } catch {
189
231
  return path.resolve(targetPath);
190
232
  }
@@ -195,7 +237,7 @@ function resolveSafeScreenPath(workspaceRoot, relativeFile) {
195
237
  const resolved = path.resolve(normalizedRoot, cleanRel);
196
238
  const allowedDirs = ["src/screens", "screens", "app", "src/app"].map((d) => {
197
239
  const candidate = path.resolve(normalizedRoot, d);
198
- return fsSync.existsSync(candidate) ? safeRealpath(candidate) : candidate;
240
+ return fs$1.existsSync(candidate) ? safeRealpath(candidate) : candidate;
199
241
  });
200
242
  const isLexicallyAllowed = allowedDirs.some(
201
243
  (dir) => resolved.startsWith(dir + path.sep) || resolved === dir
@@ -208,8 +250,8 @@ function resolveSafeScreenPath(workspaceRoot, relativeFile) {
208
250
  if (!resolved.endsWith(".tsx") && !resolved.endsWith(".jsx")) {
209
251
  throw new Error(`Invalid file extension for "${relativeFile}". Only .tsx and .jsx are allowed.`);
210
252
  }
211
- if (fsSync.existsSync(resolved)) {
212
- const stat = fsSync.lstatSync(resolved);
253
+ if (fs$1.existsSync(resolved)) {
254
+ const stat = fs$1.lstatSync(resolved);
213
255
  if (stat.isSymbolicLink()) {
214
256
  throw new Error(
215
257
  `Security violation: Symbolic link "${relativeFile}" is not permitted inside screen directories`
@@ -324,7 +366,7 @@ function extractElementNavigationTarget(openingElement) {
324
366
  }
325
367
  if (t.isJSXExpressionContainer(attr.value)) {
326
368
  const expr = attr.value.expression;
327
- const codeStr = generate(expr).code;
369
+ const codeStr = generate$1(expr).code;
328
370
  const match = codeStr.match(/(?:navigate|push|replace|jumpTo)\(\s*['"`]([^'"`$]+)['"`]/);
329
371
  if (match) return normalizeRouteTarget(match[1]);
330
372
  const objMatch = codeStr.match(/(?:pathname|screen|route|name)\s*:\s*['"`]([^'"`$]+)['"`]/);
@@ -388,7 +430,7 @@ function discoverScreenFilesSync(workspaceRoot) {
388
430
  if (depth > 4) return;
389
431
  let dirents = [];
390
432
  try {
391
- dirents = fsSync.readdirSync(currDir, { withFileTypes: true });
433
+ dirents = fs$1.readdirSync(currDir, { withFileTypes: true });
392
434
  } catch {
393
435
  return;
394
436
  }
@@ -519,7 +561,7 @@ function extractRouteStringsFromAstNode(node, depth = 0) {
519
561
  function resolveCandidateToScreenId(rawCandidate, screenFiles, currentScreenId, allowUnmatchedFallback = false) {
520
562
  const trimmed = rawCandidate.trim();
521
563
  if (!trimmed || trimmed.length > 80) return null;
522
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("mailto:") || trimmed.startsWith("tel:") || trimmed.startsWith("#") || /\.(png|jpg|jpeg|svg|webp|gif|json|css)$/i.test(trimmed)) {
564
+ if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || trimmed.startsWith("#") || /\.(png|jpg|jpeg|svg|webp|gif|json|css)$/i.test(trimmed)) {
523
565
  return null;
524
566
  }
525
567
  const normalized = normalizeRouteTarget(trimmed);
@@ -595,7 +637,7 @@ async function discoverScreens(workspaceRoot) {
595
637
  const extractTriggerLabelFromAncestry = (nodePath, fallbackTarget) => {
596
638
  const jsxParent = nodePath.findParent((p) => p.isJSXElement());
597
639
  if (jsxParent) {
598
- const rawCode = generate(jsxParent.node).code;
640
+ const rawCode = generate$1(jsxParent.node).code;
599
641
  const textMatches = Array.from(rawCode.matchAll(/>([^<>{}\n]+)</g)).map((m) => m[1].trim()).filter(Boolean);
600
642
  if (textMatches.length > 0) {
601
643
  return textMatches.slice(0, 2).join(" · ");
@@ -625,7 +667,7 @@ async function discoverScreens(workspaceRoot) {
625
667
  }
626
668
  }
627
669
  if (attrName === "onPress" && t.isJSXExpressionContainer(attr.value)) {
628
- const exprCode = generate(attr.value.expression).code;
670
+ const exprCode = generate$1(attr.value.expression).code;
629
671
  if (/\b(?:goBack|back|pop|popToTop)\s*\(/.test(exprCode)) {
630
672
  hasBackNavigation = true;
631
673
  backLine = line;
@@ -890,7 +932,7 @@ async function inspectElement(workspaceRoot, params) {
890
932
  const textInfo = inspectJsxTextChildren(element);
891
933
  const styleInfo = resolveElementStyles(opening, styleSheetInfo);
892
934
  const navigationTarget = extractElementNavigationTarget(opening);
893
- const snippetRaw = generate(element, { concise: false }).code;
935
+ const snippetRaw = generate$1(element, { concise: false }).code;
894
936
  const codeSnippet = snippetRaw.length > 600 ? `${snippetRaw.slice(0, 600)}
895
937
  /* ... */` : snippetRaw;
896
938
  return {
@@ -1024,7 +1066,7 @@ async function patchElement(workspaceRoot, payload) {
1024
1066
  upsertObjectProperty(targetObject, styleKey, styleVal);
1025
1067
  }
1026
1068
  }
1027
- const output = generate(ast, {
1069
+ const output = generate$1(ast, {
1028
1070
  retainLines: false,
1029
1071
  comments: true,
1030
1072
  jsescOption: { minimal: true }
@@ -1160,7 +1202,7 @@ async function reorderElement(workspaceRoot, payload) {
1160
1202
  }
1161
1203
  }
1162
1204
  });
1163
- const output = generate(ast, {
1205
+ const output = generate$1(ast, {
1164
1206
  retainLines: false,
1165
1207
  comments: true,
1166
1208
  jsescOption: { minimal: true }
@@ -1692,7 +1734,7 @@ async function insertElement(workspaceRoot, payload) {
1692
1734
  }
1693
1735
  }
1694
1736
  });
1695
- const output = generate(ast, {
1737
+ const output = generate$1(ast, {
1696
1738
  retainLines: false,
1697
1739
  comments: true,
1698
1740
  jsescOption: { minimal: true }
@@ -1769,7 +1811,7 @@ async function duplicateElement(workspaceRoot, payload) {
1769
1811
  }
1770
1812
  }
1771
1813
  });
1772
- const output = generate(ast, {
1814
+ const output = generate$1(ast, {
1773
1815
  retainLines: false,
1774
1816
  comments: true,
1775
1817
  jsescOption: { minimal: true }
@@ -1837,7 +1879,7 @@ async function deleteElement(workspaceRoot, payload) {
1837
1879
  throw new Error("Could not find element inside parent children.");
1838
1880
  }
1839
1881
  siblings.splice(pos, 1);
1840
- const output = generate(ast, {
1882
+ const output = generate$1(ast, {
1841
1883
  retainLines: false,
1842
1884
  comments: true,
1843
1885
  jsescOption: { minimal: true }
@@ -1862,6 +1904,1299 @@ async function deleteElement(workspaceRoot, payload) {
1862
1904
  };
1863
1905
  });
1864
1906
  }
1907
+ async function undoLastPatch(workspaceRoot) {
1908
+ const entry = undoStack.pop();
1909
+ if (!entry) {
1910
+ return {
1911
+ restoredFile: null,
1912
+ description: null,
1913
+ undoAvailable: 0,
1914
+ redoAvailable: redoStack.length
1915
+ };
1916
+ }
1917
+ const fullPath = resolveSafeScreenPath(workspaceRoot, entry.file);
1918
+ await withFileLock(fullPath, async () => {
1919
+ await fs.writeFile(fullPath, entry.previousContent, "utf-8");
1920
+ });
1921
+ redoStack.push(entry);
1922
+ return {
1923
+ restoredFile: entry.file,
1924
+ description: entry.description,
1925
+ undoAvailable: undoStack.length,
1926
+ redoAvailable: redoStack.length
1927
+ };
1928
+ }
1929
+ async function redoLastPatch(workspaceRoot) {
1930
+ const entry = redoStack.pop();
1931
+ if (!entry) {
1932
+ return {
1933
+ restoredFile: null,
1934
+ description: null,
1935
+ undoAvailable: undoStack.length,
1936
+ redoAvailable: 0
1937
+ };
1938
+ }
1939
+ const fullPath = resolveSafeScreenPath(workspaceRoot, entry.file);
1940
+ await withFileLock(fullPath, async () => {
1941
+ await fs.writeFile(fullPath, entry.nextContent, "utf-8");
1942
+ });
1943
+ undoStack.push(entry);
1944
+ return {
1945
+ restoredFile: entry.file,
1946
+ description: entry.description,
1947
+ undoAvailable: undoStack.length,
1948
+ redoAvailable: redoStack.length
1949
+ };
1950
+ }
1951
+ function getHistoryStatus() {
1952
+ return {
1953
+ undoAvailable: undoStack.length,
1954
+ redoAvailable: redoStack.length
1955
+ };
1956
+ }
1957
+ const MAX_BODY_BYTES = 1e6;
1958
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]", "::1"]);
1959
+ const FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1960
+ function isLoopbackHostHeader(hostHeader) {
1961
+ if (!hostHeader) return false;
1962
+ const trimmed = hostHeader.trim().toLowerCase();
1963
+ if (trimmed.startsWith("[")) {
1964
+ const closeIdx = trimmed.indexOf("]");
1965
+ if (closeIdx === -1) return false;
1966
+ const ipv6Host = trimmed.slice(0, closeIdx + 1);
1967
+ return LOOPBACK_HOSTS.has(ipv6Host);
1968
+ }
1969
+ const hostname = trimmed.split(":")[0];
1970
+ return LOOPBACK_HOSTS.has(hostname);
1971
+ }
1972
+ function verifyApiRequestSecurity(req) {
1973
+ const host = typeof req.headers.host === "string" ? req.headers.host : void 0;
1974
+ if (!isLoopbackHostHeader(host)) {
1975
+ return {
1976
+ allowed: false,
1977
+ reason: "Forbidden Host header: Local API only accepts loopback connections (127.0.0.1, localhost, [::1])"
1978
+ };
1979
+ }
1980
+ const secFetchSite = typeof req.headers["sec-fetch-site"] === "string" ? req.headers["sec-fetch-site"].toLowerCase() : void 0;
1981
+ if (secFetchSite && secFetchSite !== "same-origin" && secFetchSite !== "none") {
1982
+ return {
1983
+ allowed: false,
1984
+ reason: `Forbidden cross-site request (Sec-Fetch-Site: ${secFetchSite})`
1985
+ };
1986
+ }
1987
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : void 0;
1988
+ if (origin) {
1989
+ try {
1990
+ const parsedOrigin = new URL(origin);
1991
+ if (!LOOPBACK_HOSTS.has(parsedOrigin.hostname.toLowerCase())) {
1992
+ return {
1993
+ allowed: false,
1994
+ reason: `Forbidden cross-origin request from ${origin}`
1995
+ };
1996
+ }
1997
+ } catch {
1998
+ return {
1999
+ allowed: false,
2000
+ reason: "Malformed Origin header"
2001
+ };
2002
+ }
2003
+ }
2004
+ if (req.method === "POST") {
2005
+ const contentType = typeof req.headers["content-type"] === "string" ? req.headers["content-type"].toLowerCase() : "";
2006
+ if (!contentType.startsWith("application/json")) {
2007
+ return {
2008
+ allowed: false,
2009
+ reason: "POST requests to /api/* must specify Content-Type: application/json"
2010
+ };
2011
+ }
2012
+ }
2013
+ return { allowed: true };
2014
+ }
2015
+ function sanitizeLayoutPayload(input) {
2016
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
2017
+ throw new Error("Invalid layout payload: expected an object map of screen coordinates");
2018
+ }
2019
+ const sanitized = /* @__PURE__ */ Object.create(null);
2020
+ const entries = Object.entries(input);
2021
+ if (entries.length > 200) {
2022
+ throw new Error("Invalid layout payload: too many screen entries");
2023
+ }
2024
+ for (const [screenId, rawBox] of entries) {
2025
+ if (!screenId || FORBIDDEN_KEYS.has(screenId) || screenId.length > 120) {
2026
+ throw new Error(`Invalid screen key in layout payload: "${screenId}"`);
2027
+ }
2028
+ if (!rawBox || typeof rawBox !== "object" || Array.isArray(rawBox)) {
2029
+ throw new Error(`Invalid layout coordinates for screen "${screenId}"`);
2030
+ }
2031
+ const box = rawBox;
2032
+ const x = Number(box.x);
2033
+ const y = Number(box.y);
2034
+ const width = Number(box.width);
2035
+ const height = Number(box.height);
2036
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(width) || !Number.isFinite(height) || Math.abs(x) > 1e5 || Math.abs(y) > 1e5 || width < 100 || width > 5e3 || height < 100 || height > 5e3) {
2037
+ throw new Error(`Out-of-bounds layout coordinates for screen "${screenId}"`);
2038
+ }
2039
+ sanitized[screenId] = {
2040
+ x: Math.round(x),
2041
+ y: Math.round(y),
2042
+ width: Math.round(width),
2043
+ height: Math.round(height)
2044
+ };
2045
+ }
2046
+ return sanitized;
2047
+ }
2048
+ async function readJsonBody(req) {
2049
+ return new Promise((resolve, reject) => {
2050
+ let raw = "";
2051
+ let byteCount = 0;
2052
+ let aborted = false;
2053
+ req.on("data", (chunk) => {
2054
+ if (aborted) return;
2055
+ const chunkBytes = typeof chunk === "string" ? Buffer.byteLength(chunk, "utf-8") : chunk.length;
2056
+ byteCount += chunkBytes;
2057
+ if (byteCount > MAX_BODY_BYTES) {
2058
+ aborted = true;
2059
+ req.destroy();
2060
+ reject(new Error("Request payload too large"));
2061
+ return;
2062
+ }
2063
+ raw += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
2064
+ });
2065
+ req.on("end", () => {
2066
+ if (aborted) return;
2067
+ try {
2068
+ resolve(raw ? JSON.parse(raw) : {});
2069
+ } catch (err) {
2070
+ reject(err);
2071
+ }
2072
+ });
2073
+ req.on("error", (err) => {
2074
+ if (!aborted) {
2075
+ aborted = true;
2076
+ reject(err);
2077
+ }
2078
+ });
2079
+ });
2080
+ }
2081
+ const generate = _generate.default ?? _generate;
2082
+ const ALLOWED_SCREEN_PREFIXES = ["src/screens/", "screens/", "app/", "src/app/"];
2083
+ const RESOLVABLE_EXTENSIONS = [
2084
+ ".web.tsx",
2085
+ ".web.ts",
2086
+ ".web.jsx",
2087
+ ".web.js",
2088
+ ".tsx",
2089
+ ".ts",
2090
+ ".jsx",
2091
+ ".js",
2092
+ ".json",
2093
+ ".css"
2094
+ ];
2095
+ function stripJsonCommentsAndTrailingCommas(input) {
2096
+ const withoutComments = input.replace(
2097
+ /("(?:\\.|[^"\\])*")|\/\*[\s\S]*?\*\/|\/\/.*/g,
2098
+ (_match, quotedString) => quotedString ?? ""
2099
+ );
2100
+ return withoutComments.replace(/,\s*([}\]])/g, "$1");
2101
+ }
2102
+ function tryResolveLocalModule(candidateBase) {
2103
+ try {
2104
+ const cleanBase = candidateBase.split("?")[0];
2105
+ if (fs$1.existsSync(cleanBase) && fs$1.statSync(cleanBase).isFile()) {
2106
+ return cleanBase;
2107
+ }
2108
+ for (const ext of RESOLVABLE_EXTENSIONS) {
2109
+ const withExt = cleanBase + ext;
2110
+ if (fs$1.existsSync(withExt) && fs$1.statSync(withExt).isFile()) {
2111
+ return withExt;
2112
+ }
2113
+ }
2114
+ if (fs$1.existsSync(cleanBase) && fs$1.statSync(cleanBase).isDirectory()) {
2115
+ for (const ext of RESOLVABLE_EXTENSIONS) {
2116
+ const indexFile = path.join(cleanBase, `index${ext}`);
2117
+ if (fs$1.existsSync(indexFile) && fs$1.statSync(indexFile).isFile()) {
2118
+ return indexFile;
2119
+ }
2120
+ }
2121
+ }
2122
+ } catch {
2123
+ }
2124
+ return null;
2125
+ }
2126
+ function loadTsconfigAliases(roots) {
2127
+ var _a, _b;
2128
+ const mappings = [];
2129
+ for (const rootDir of roots) {
2130
+ try {
2131
+ const tsconfigPath = path.join(rootDir, "tsconfig.json");
2132
+ if (!fs$1.existsSync(tsconfigPath)) continue;
2133
+ const raw = fs$1.readFileSync(tsconfigPath, "utf-8");
2134
+ const parsed = JSON.parse(stripJsonCommentsAndTrailingCommas(raw));
2135
+ const baseUrl = ((_a = parsed.compilerOptions) == null ? void 0 : _a.baseUrl) ?? ".";
2136
+ const paths = ((_b = parsed.compilerOptions) == null ? void 0 : _b.paths) ?? {};
2137
+ for (const [pattern, targets] of Object.entries(paths)) {
2138
+ if (!Array.isArray(targets)) continue;
2139
+ const cleanPrefix = pattern.replace(/\/\*$/, "");
2140
+ if (!cleanPrefix) continue;
2141
+ for (const target of targets) {
2142
+ const cleanTarget = target.replace(/\/\*$/, "");
2143
+ const resolvedDir = path.resolve(rootDir, baseUrl, cleanTarget);
2144
+ mappings.push({ prefix: cleanPrefix, targetDir: resolvedDir });
2145
+ }
2146
+ }
2147
+ } catch {
2148
+ }
2149
+ }
2150
+ return mappings;
2151
+ }
2152
+ function cleanJsxText(rawText) {
2153
+ const lines = rawText.split(/\r\n|\n|\r/);
2154
+ let lastNonEmptyLine = 0;
2155
+ for (let i = 0; i < lines.length; i++) {
2156
+ if (/[^ \t]/.test(lines[i])) {
2157
+ lastNonEmptyLine = i;
2158
+ }
2159
+ }
2160
+ let str = "";
2161
+ for (let i = 0; i < lines.length; i++) {
2162
+ const line = lines[i];
2163
+ const isFirstLine = i === 0;
2164
+ const isLastLine = i === lines.length - 1;
2165
+ const isLastNonEmptyLine = i === lastNonEmptyLine;
2166
+ let trimmedLine = line.replace(/\t/g, " ");
2167
+ if (!isFirstLine) {
2168
+ trimmedLine = trimmedLine.replace(/^[ ]+/, "");
2169
+ }
2170
+ if (!isLastLine) {
2171
+ trimmedLine = trimmedLine.replace(/[ ]+$/, "");
2172
+ }
2173
+ if (trimmedLine) {
2174
+ if (!isLastNonEmptyLine) {
2175
+ trimmedLine += " ";
2176
+ }
2177
+ str += trimmedLine;
2178
+ }
2179
+ }
2180
+ return str.length > 0 ? str : null;
2181
+ }
2182
+ function convertJsxElementNameToExpr(nameNode) {
2183
+ if (t.isJSXIdentifier(nameNode)) {
2184
+ if (nameNode.name === "this") {
2185
+ return t.thisExpression();
2186
+ }
2187
+ if (/^[a-z]/.test(nameNode.name) || nameNode.name.includes("-")) {
2188
+ return t.stringLiteral(nameNode.name);
2189
+ }
2190
+ return t.identifier(nameNode.name);
2191
+ }
2192
+ if (t.isJSXMemberExpression(nameNode)) {
2193
+ return t.memberExpression(
2194
+ convertJsxElementNameToExpr(nameNode.object),
2195
+ t.identifier(nameNode.property.name)
2196
+ );
2197
+ }
2198
+ if (t.isJSXNamespacedName(nameNode)) {
2199
+ return t.stringLiteral(`${nameNode.namespace.name}:${nameNode.name.name}`);
2200
+ }
2201
+ return t.stringLiteral("div");
2202
+ }
2203
+ function convertJsxAttributesToExpr(attributes) {
2204
+ if (attributes.length === 0) {
2205
+ return t.nullLiteral();
2206
+ }
2207
+ const props = [];
2208
+ for (const attr of attributes) {
2209
+ if (t.isJSXSpreadAttribute(attr)) {
2210
+ props.push(t.spreadElement(attr.argument));
2211
+ continue;
2212
+ }
2213
+ const rawKey = t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
2214
+ const keyNode = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(rawKey) ? t.identifier(rawKey) : t.stringLiteral(rawKey);
2215
+ let valExpr;
2216
+ if (attr.value === null || attr.value === void 0) {
2217
+ valExpr = t.booleanLiteral(true);
2218
+ } else if (t.isJSXExpressionContainer(attr.value)) {
2219
+ if (t.isJSXEmptyExpression(attr.value.expression)) {
2220
+ valExpr = t.booleanLiteral(true);
2221
+ } else {
2222
+ valExpr = attr.value.expression;
2223
+ }
2224
+ } else if (t.isJSXElement(attr.value) || t.isJSXFragment(attr.value)) {
2225
+ valExpr = transformJsxNode(attr.value);
2226
+ } else {
2227
+ valExpr = attr.value;
2228
+ }
2229
+ props.push(t.objectProperty(keyNode, valExpr));
2230
+ }
2231
+ return t.objectExpression(props);
2232
+ }
2233
+ function convertJsxChildrenToExprs(children) {
2234
+ const out = [];
2235
+ for (const child of children) {
2236
+ if (t.isJSXText(child)) {
2237
+ const cleaned = cleanJsxText(child.value);
2238
+ if (cleaned !== null) {
2239
+ out.push(t.stringLiteral(cleaned));
2240
+ }
2241
+ } else if (t.isJSXExpressionContainer(child)) {
2242
+ if (!t.isJSXEmptyExpression(child.expression)) {
2243
+ out.push(child.expression);
2244
+ }
2245
+ } else if (t.isJSXSpreadChild(child)) {
2246
+ out.push(child.expression);
2247
+ } else if (t.isJSXElement(child) || t.isJSXFragment(child)) {
2248
+ out.push(transformJsxNode(child));
2249
+ }
2250
+ }
2251
+ return out;
2252
+ }
2253
+ function transformJsxNode(node) {
2254
+ const createElMember = t.memberExpression(
2255
+ t.identifier("React"),
2256
+ t.identifier("createElement")
2257
+ );
2258
+ if (t.isJSXFragment(node)) {
2259
+ const fragMember = t.memberExpression(
2260
+ t.identifier("React"),
2261
+ t.identifier("Fragment")
2262
+ );
2263
+ const children2 = convertJsxChildrenToExprs(node.children);
2264
+ return t.callExpression(createElMember, [fragMember, t.nullLiteral(), ...children2]);
2265
+ }
2266
+ const tagExpr = convertJsxElementNameToExpr(node.openingElement.name);
2267
+ const propsExpr = convertJsxAttributesToExpr(node.openingElement.attributes);
2268
+ const children = convertJsxChildrenToExprs(node.children);
2269
+ return t.callExpression(createElMember, [tagExpr, propsExpr, ...children]);
2270
+ }
2271
+ function convertTsEnumToVar(enumNode) {
2272
+ const props = [];
2273
+ let nextNumericValue = 0;
2274
+ for (const member of enumNode.members) {
2275
+ const keyNode = t.isIdentifier(member.id) ? t.identifier(member.id.name) : t.stringLiteral(member.id.value);
2276
+ let initExpr;
2277
+ if (member.initializer) {
2278
+ initExpr = member.initializer;
2279
+ if (t.isNumericLiteral(member.initializer)) {
2280
+ nextNumericValue = member.initializer.value + 1;
2281
+ }
2282
+ } else {
2283
+ initExpr = t.numericLiteral(nextNumericValue++);
2284
+ }
2285
+ props.push(t.objectProperty(keyNode, initExpr));
2286
+ }
2287
+ return t.variableDeclaration("const", [
2288
+ t.variableDeclarator(t.identifier(enumNode.id.name), t.objectExpression(props))
2289
+ ]);
2290
+ }
2291
+ function stripTypeScriptAndTransformJsxAst(node) {
2292
+ var _a;
2293
+ if (!node || typeof node !== "object") return node;
2294
+ if (Array.isArray(node)) {
2295
+ const nextArr = [];
2296
+ for (let i = 0; i < node.length; i++) {
2297
+ const item = node[i];
2298
+ if (!item) continue;
2299
+ if (item.type === "TSInterfaceDeclaration" || item.type === "TSTypeAliasDeclaration" || item.type === "TSDeclareFunction" || item.type === "TSDeclareMethod" || item.type === "TSModuleDeclaration" || item.type === "TSIndexSignature") {
2300
+ continue;
2301
+ }
2302
+ if (item.type === "ImportDeclaration" && item.importKind === "type") {
2303
+ continue;
2304
+ }
2305
+ if (item.type === "ExportNamedDeclaration") {
2306
+ if (item.exportKind === "type") continue;
2307
+ if (item.declaration && (item.declaration.type === "TSInterfaceDeclaration" || item.declaration.type === "TSTypeAliasDeclaration" || item.declaration.type === "TSDeclareFunction" || item.declaration.type === "TSModuleDeclaration")) {
2308
+ continue;
2309
+ }
2310
+ }
2311
+ const transformed = stripTypeScriptAndTransformJsxAst(item);
2312
+ if (transformed !== null && transformed !== void 0) {
2313
+ nextArr.push(transformed);
2314
+ }
2315
+ }
2316
+ return nextArr;
2317
+ }
2318
+ while (node && (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression" || node.type === "TSInstantiationExpression")) {
2319
+ node = node.expression;
2320
+ }
2321
+ if (!node || typeof node !== "object") return node;
2322
+ if (node.type === "TSEnumDeclaration") {
2323
+ return convertTsEnumToVar(node);
2324
+ }
2325
+ if (node.type === "ExportNamedDeclaration" && ((_a = node.declaration) == null ? void 0 : _a.type) === "TSEnumDeclaration") {
2326
+ node.declaration = convertTsEnumToVar(node.declaration);
2327
+ }
2328
+ if (node.type === "ImportDeclaration" && Array.isArray(node.specifiers)) {
2329
+ const origCount = node.specifiers.length;
2330
+ node.specifiers = node.specifiers.filter(
2331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2332
+ (s) => s.importKind !== "type"
2333
+ );
2334
+ if (origCount > 0 && node.specifiers.length === 0) {
2335
+ return null;
2336
+ }
2337
+ }
2338
+ if (node.type === "ExportNamedDeclaration" && Array.isArray(node.specifiers)) {
2339
+ node.specifiers = node.specifiers.filter(
2340
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2341
+ (s) => s.exportKind !== "type"
2342
+ );
2343
+ if (!node.declaration && node.specifiers.length === 0) {
2344
+ return null;
2345
+ }
2346
+ }
2347
+ delete node.typeAnnotation;
2348
+ delete node.returnType;
2349
+ delete node.typeParameters;
2350
+ delete node.typeArguments;
2351
+ delete node.implements;
2352
+ delete node.superTypeParameters;
2353
+ delete node.definite;
2354
+ delete node.accessibility;
2355
+ delete node.readonly;
2356
+ delete node.declare;
2357
+ delete node.override;
2358
+ delete node.optional;
2359
+ if (node.type === "TSParameterProperty") {
2360
+ return stripTypeScriptAndTransformJsxAst(node.parameter);
2361
+ }
2362
+ for (const key of Object.keys(node)) {
2363
+ if (key === "loc" || key === "comments" || key === "leadingComments" || key === "trailingComments") {
2364
+ continue;
2365
+ }
2366
+ const val = node[key];
2367
+ if (val && typeof val === "object") {
2368
+ node[key] = stripTypeScriptAndTransformJsxAst(val);
2369
+ }
2370
+ }
2371
+ if (node.type === "JSXElement" || node.type === "JSXFragment") {
2372
+ return transformJsxNode(node);
2373
+ }
2374
+ return node;
2375
+ }
2376
+ function injectScreenAgyAttributes(ast, relPath) {
2377
+ let jsxIndex = 0;
2378
+ let hasJsx = false;
2379
+ function visit(node) {
2380
+ var _a, _b;
2381
+ if (!node || typeof node !== "object" || typeof node.type !== "string") return;
2382
+ const typed = node;
2383
+ if (typed.type === "JSXFragment") {
2384
+ hasJsx = true;
2385
+ } else if (typed.type === "JSXOpeningElement") {
2386
+ hasJsx = true;
2387
+ const tagName = getJsxTagName(typed.name);
2388
+ if (tagName !== "Fragment" && tagName !== "React.Fragment" && tagName !== "") {
2389
+ const hasAgyAttr = typed.attributes.some(
2390
+ (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: "data-agy-ast-path" })
2391
+ );
2392
+ if (!hasAgyAttr) {
2393
+ const currentIndex = String(jsxIndex++);
2394
+ const line = String(((_a = typed.loc) == null ? void 0 : _a.start.line) ?? 1);
2395
+ const col = String(((_b = typed.loc) == null ? void 0 : _b.start.column) ?? 0);
2396
+ const dataSetProps = [
2397
+ t.objectProperty(t.identifier("agyFile"), t.stringLiteral(relPath)),
2398
+ t.objectProperty(t.identifier("agyLine"), t.stringLiteral(line)),
2399
+ t.objectProperty(t.identifier("agyCol"), t.stringLiteral(col)),
2400
+ t.objectProperty(t.identifier("agyAstPath"), t.stringLiteral(currentIndex)),
2401
+ t.objectProperty(t.identifier("agyTag"), t.stringLiteral(tagName))
2402
+ ];
2403
+ typed.attributes.push(
2404
+ t.jsxAttribute(
2405
+ t.jsxIdentifier("dataSet"),
2406
+ t.jsxExpressionContainer(t.objectExpression(dataSetProps))
2407
+ ),
2408
+ t.jsxAttribute(t.jsxIdentifier("data-agy-file"), t.stringLiteral(relPath)),
2409
+ t.jsxAttribute(t.jsxIdentifier("data-agy-line"), t.stringLiteral(line)),
2410
+ t.jsxAttribute(t.jsxIdentifier("data-agy-col"), t.stringLiteral(col)),
2411
+ t.jsxAttribute(t.jsxIdentifier("data-agy-ast-path"), t.stringLiteral(currentIndex)),
2412
+ t.jsxAttribute(t.jsxIdentifier("data-agy-tag"), t.stringLiteral(tagName))
2413
+ );
2414
+ }
2415
+ }
2416
+ }
2417
+ const keys = t.VISITOR_KEYS[typed.type];
2418
+ if (!keys) return;
2419
+ const rec = typed;
2420
+ for (const key of keys) {
2421
+ const child = rec[key];
2422
+ if (Array.isArray(child)) {
2423
+ for (let i = 0; i < child.length; i++) {
2424
+ if (child[i]) visit(child[i]);
2425
+ }
2426
+ } else if (child && typeof child === "object") {
2427
+ visit(child);
2428
+ }
2429
+ }
2430
+ }
2431
+ visit(ast);
2432
+ return hasJsx;
2433
+ }
2434
+ function compileModuleForBrowser(options) {
2435
+ const { filePath, targetProjectRoot, workspaceRoot = targetProjectRoot } = options;
2436
+ const cleanFilePath = filePath.split("?")[0];
2437
+ const ext = path.extname(cleanFilePath).toLowerCase();
2438
+ if (ext === ".css") {
2439
+ return "export default {};\n";
2440
+ }
2441
+ if (ext === ".json") {
2442
+ const rawJson = fs$1.readFileSync(cleanFilePath, "utf-8");
2443
+ return `export default ${rawJson};
2444
+ `;
2445
+ }
2446
+ if (/\.(png|jpe?g|gif|webp|svg|avif|bmp)$/i.test(ext)) {
2447
+ const assetUrl = `/@zelune/asset?file=${encodeURIComponent(cleanFilePath)}`;
2448
+ return `export default { uri: ${JSON.stringify(assetUrl)} };
2449
+ `;
2450
+ }
2451
+ const code = fs$1.readFileSync(cleanFilePath, "utf-8");
2452
+ const ast = parse(code, {
2453
+ sourceType: "module",
2454
+ plugins: ["typescript", "jsx", "decorators-legacy"],
2455
+ errorRecovery: true
2456
+ });
2457
+ const normalizedTargetRoot = path.resolve(targetProjectRoot);
2458
+ const relPath = path.relative(normalizedTargetRoot, path.resolve(cleanFilePath)).replace(/\\/g, "/");
2459
+ const isScreenFile = ALLOWED_SCREEN_PREFIXES.some((prefix) => relPath.startsWith(prefix));
2460
+ const hadJsx = isScreenFile ? injectScreenAgyAttributes(ast, relPath) : true;
2461
+ const uniqueRoots = Array.from(
2462
+ /* @__PURE__ */ new Set([path.resolve(targetProjectRoot), path.resolve(workspaceRoot)])
2463
+ );
2464
+ const tsconfigAliases = loadTsconfigAliases(uniqueRoots);
2465
+ function resolveAlias(specifier) {
2466
+ for (const mapping of tsconfigAliases) {
2467
+ if (specifier === mapping.prefix) {
2468
+ const found = tryResolveLocalModule(mapping.targetDir);
2469
+ if (found) return found;
2470
+ } else if (specifier.startsWith(mapping.prefix + "/")) {
2471
+ const rel = specifier.slice(mapping.prefix.length + 1);
2472
+ const found = tryResolveLocalModule(path.join(mapping.targetDir, rel));
2473
+ if (found) return found;
2474
+ }
2475
+ }
2476
+ if (specifier.startsWith("@/") || specifier.startsWith("~/")) {
2477
+ const rel = specifier.slice(2);
2478
+ for (const r of uniqueRoots) {
2479
+ const direct = tryResolveLocalModule(path.join(r, rel));
2480
+ if (direct) return direct;
2481
+ const inSrc = tryResolveLocalModule(path.join(r, "src", rel));
2482
+ if (inSrc) return inSrc;
2483
+ }
2484
+ }
2485
+ return null;
2486
+ }
2487
+ function rewriteSpecifier(specifier, requestedExports) {
2488
+ if (specifier === "react" || specifier === "react/jsx-runtime" || specifier === "react/jsx-dev-runtime") {
2489
+ return "/@zelune/shim/react.js";
2490
+ }
2491
+ if (specifier === "react-dom" || specifier === "react-dom/client") {
2492
+ return "/@zelune/shim/react-dom.js";
2493
+ }
2494
+ if (specifier === "react-native" || specifier === "react-native-web") {
2495
+ return "/@zelune/shim/react-native.js";
2496
+ }
2497
+ if (specifier === "lucide-react" || specifier === "lucide-react-native") {
2498
+ return `/@zelune/shim/lucide.js?exports=${encodeURIComponent(requestedExports.join(","))}`;
2499
+ }
2500
+ if (specifier.startsWith(".")) {
2501
+ const candidate = path.resolve(path.dirname(cleanFilePath), specifier);
2502
+ const resolved = tryResolveLocalModule(candidate);
2503
+ if (resolved) {
2504
+ return `/@zelune/module?file=${encodeURIComponent(resolved)}`;
2505
+ }
2506
+ return `/@zelune/shim/stub.js?pkg=${encodeURIComponent(specifier)}&exports=${encodeURIComponent(
2507
+ requestedExports.join(",")
2508
+ )}`;
2509
+ }
2510
+ if (specifier.startsWith("@/") || specifier.startsWith("~/") || tsconfigAliases.some((m) => specifier === m.prefix || specifier.startsWith(m.prefix + "/"))) {
2511
+ const resolved = resolveAlias(specifier);
2512
+ if (resolved) {
2513
+ return `/@zelune/module?file=${encodeURIComponent(resolved)}`;
2514
+ }
2515
+ return `/@zelune/shim/stub.js?pkg=${encodeURIComponent(specifier)}&exports=${encodeURIComponent(
2516
+ requestedExports.join(",")
2517
+ )}`;
2518
+ }
2519
+ return `/@zelune/shim/stub.js?pkg=${encodeURIComponent(specifier)}&exports=${encodeURIComponent(
2520
+ requestedExports.join(",")
2521
+ )}`;
2522
+ }
2523
+ stripTypeScriptAndTransformJsxAst(ast);
2524
+ let hasReactIdentifierInScope = false;
2525
+ for (const stmt of ast.program.body) {
2526
+ if (stmt.type === "ImportDeclaration") {
2527
+ const requested = [];
2528
+ for (const spec of stmt.specifiers) {
2529
+ if (spec.local.name === "React") {
2530
+ hasReactIdentifierInScope = true;
2531
+ }
2532
+ if (spec.type === "ImportSpecifier") {
2533
+ const importedName = spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value;
2534
+ if (importedName && importedName !== "default") {
2535
+ requested.push(importedName);
2536
+ }
2537
+ }
2538
+ }
2539
+ stmt.source.value = rewriteSpecifier(stmt.source.value, requested);
2540
+ } else if ((stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportAllDeclaration") && stmt.source) {
2541
+ const requested = [];
2542
+ if (stmt.type === "ExportNamedDeclaration") {
2543
+ for (const spec of stmt.specifiers) {
2544
+ if (spec.type === "ExportSpecifier") {
2545
+ const localName = spec.local.type === "Identifier" ? spec.local.name : spec.local.value;
2546
+ const exportedName = spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value;
2547
+ if (localName && localName !== "default") requested.push(localName);
2548
+ if (exportedName && exportedName !== "default") requested.push(exportedName);
2549
+ }
2550
+ }
2551
+ }
2552
+ stmt.source.value = rewriteSpecifier(stmt.source.value, requested);
2553
+ }
2554
+ }
2555
+ if (hadJsx && !hasReactIdentifierInScope) {
2556
+ ast.program.body.unshift(
2557
+ t.importDeclaration(
2558
+ [t.importDefaultSpecifier(t.identifier("React"))],
2559
+ t.stringLiteral("/@zelune/shim/react.js")
2560
+ )
2561
+ );
2562
+ }
2563
+ const out = generate(ast, {
2564
+ retainLines: false,
2565
+ compact: false
2566
+ });
2567
+ return out.code;
2568
+ }
2569
+ function extractStaticAstValue(node, scope, depth = 0) {
2570
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2571
+ if (!node || depth > 12) return void 0;
2572
+ switch (node.type) {
2573
+ case "StringLiteral":
2574
+ case "NumericLiteral":
2575
+ case "BooleanLiteral":
2576
+ return node.value;
2577
+ case "NullLiteral":
2578
+ return null;
2579
+ case "UnaryExpression": {
2580
+ const arg = extractStaticAstValue(node.argument, scope, depth + 1);
2581
+ if (typeof arg === "number") {
2582
+ if (node.operator === "-") return -arg;
2583
+ if (node.operator === "+") return +arg;
2584
+ }
2585
+ return void 0;
2586
+ }
2587
+ case "TemplateLiteral": {
2588
+ if (Array.isArray(node.expressions) && node.expressions.length === 0) {
2589
+ return ((_c = (_b = (_a = node.quasis) == null ? void 0 : _a[0]) == null ? void 0 : _b.value) == null ? void 0 : _c.cooked) ?? ((_f = (_e = (_d = node.quasis) == null ? void 0 : _d[0]) == null ? void 0 : _e.value) == null ? void 0 : _f.raw) ?? "";
2590
+ }
2591
+ return void 0;
2592
+ }
2593
+ case "ArrayExpression": {
2594
+ const items = [];
2595
+ for (const el of node.elements ?? []) {
2596
+ if (!el) continue;
2597
+ if (el.type === "SpreadElement") {
2598
+ const spreadVal = extractStaticAstValue(el.argument, scope, depth + 1);
2599
+ if (Array.isArray(spreadVal)) items.push(...spreadVal);
2600
+ } else {
2601
+ const val = extractStaticAstValue(el, scope, depth + 1);
2602
+ if (val !== void 0) items.push(val);
2603
+ }
2604
+ }
2605
+ return items;
2606
+ }
2607
+ case "ObjectExpression": {
2608
+ const result = {};
2609
+ for (const prop of node.properties ?? []) {
2610
+ if (prop.type === "SpreadElement") {
2611
+ const spreadObj = extractStaticAstValue(prop.argument, scope, depth + 1);
2612
+ if (spreadObj && typeof spreadObj === "object" && !Array.isArray(spreadObj)) {
2613
+ Object.assign(result, spreadObj);
2614
+ }
2615
+ continue;
2616
+ }
2617
+ if (prop.type !== "ObjectProperty" || prop.computed) continue;
2618
+ const key = ((_g = prop.key) == null ? void 0 : _g.type) === "Identifier" ? prop.key.name : ((_h = prop.key) == null ? void 0 : _h.type) === "StringLiteral" || ((_i = prop.key) == null ? void 0 : _i.type) === "NumericLiteral" ? String(prop.key.value) : null;
2619
+ if (!key) continue;
2620
+ const val = extractStaticAstValue(prop.value, scope, depth + 1);
2621
+ if (val !== void 0) {
2622
+ result[key] = val;
2623
+ }
2624
+ }
2625
+ return result;
2626
+ }
2627
+ case "TSAsExpression":
2628
+ case "TSSatisfiesExpression":
2629
+ case "TSTypeAssertion":
2630
+ case "ParenthesizedExpression":
2631
+ return extractStaticAstValue(node.expression, scope, depth + 1);
2632
+ case "Identifier":
2633
+ return scope.get(node.name);
2634
+ default:
2635
+ return void 0;
2636
+ }
2637
+ }
2638
+ function parseTailwindConfigSource(raw) {
2639
+ try {
2640
+ const ast = parse(raw, {
2641
+ sourceType: "unambiguous",
2642
+ plugins: ["typescript", "jsx"],
2643
+ errorRecovery: true
2644
+ });
2645
+ const scope = /* @__PURE__ */ new Map();
2646
+ let exportedConfig;
2647
+ for (const stmt of ast.program.body) {
2648
+ if (stmt.type === "VariableDeclaration") {
2649
+ for (const decl of stmt.declarations) {
2650
+ if (decl.id.type === "Identifier" && decl.init) {
2651
+ const val = extractStaticAstValue(decl.init, scope);
2652
+ if (val !== void 0) {
2653
+ scope.set(decl.id.name, val);
2654
+ }
2655
+ }
2656
+ }
2657
+ } else if (stmt.type === "ExportDefaultDeclaration") {
2658
+ exportedConfig = extractStaticAstValue(stmt.declaration, scope);
2659
+ } else if (stmt.type === "ExpressionStatement" && stmt.expression.type === "AssignmentExpression") {
2660
+ const left = stmt.expression.left;
2661
+ if (left.type === "MemberExpression" && left.object.type === "Identifier" && left.property.type === "Identifier" && (left.object.name === "module" && left.property.name === "exports" || left.object.name === "exports" && left.property.name === "default")) {
2662
+ exportedConfig = extractStaticAstValue(stmt.expression.right, scope);
2663
+ }
2664
+ }
2665
+ }
2666
+ const cfg = exportedConfig;
2667
+ if ((cfg == null ? void 0 : cfg.theme) && typeof cfg.theme === "object") {
2668
+ const baseTheme = { ...cfg.theme };
2669
+ const extendTheme = cfg.theme.extend && typeof cfg.theme.extend === "object" ? cfg.theme.extend : {};
2670
+ delete baseTheme.extend;
2671
+ const merged = { ...baseTheme };
2672
+ for (const [k, v] of Object.entries(extendTheme)) {
2673
+ if (v && typeof v === "object" && !Array.isArray(v)) {
2674
+ merged[k] = {
2675
+ ...baseTheme[k] ?? {},
2676
+ ...v
2677
+ };
2678
+ } else {
2679
+ merged[k] = v;
2680
+ }
2681
+ }
2682
+ return merged;
2683
+ }
2684
+ } catch {
2685
+ }
2686
+ return {};
2687
+ }
2688
+ function loadProjectTailwindTheme(roots) {
2689
+ const candidates = [
2690
+ "tailwind.config.js",
2691
+ "tailwind.config.cjs",
2692
+ "tailwind.config.mjs",
2693
+ "tailwind.config.ts"
2694
+ ];
2695
+ const uniqueRoots = Array.from(new Set(roots.map((r) => path.resolve(r))));
2696
+ for (const rootDir of uniqueRoots) {
2697
+ for (const fileName of candidates) {
2698
+ const fullPath = path.join(rootDir, fileName);
2699
+ try {
2700
+ if (!fs$1.existsSync(fullPath)) continue;
2701
+ const raw = fs$1.readFileSync(fullPath, "utf-8");
2702
+ const parsedTheme = parseTailwindConfigSource(raw);
2703
+ if (Object.keys(parsedTheme).length > 0) {
2704
+ return parsedTheme;
2705
+ }
2706
+ } catch {
2707
+ }
2708
+ }
2709
+ }
2710
+ return {};
2711
+ }
2712
+ const MIME_TYPES = {
2713
+ ".html": "text/html; charset=utf-8",
2714
+ ".js": "application/javascript; charset=utf-8",
2715
+ ".mjs": "application/javascript; charset=utf-8",
2716
+ ".css": "text/css; charset=utf-8",
2717
+ ".json": "application/json; charset=utf-8",
2718
+ ".svg": "image/svg+xml",
2719
+ ".png": "image/png",
2720
+ ".jpg": "image/jpeg",
2721
+ ".jpeg": "image/jpeg",
2722
+ ".webp": "image/webp",
2723
+ ".gif": "image/gif",
2724
+ ".ico": "image/x-icon"
2725
+ };
2726
+ const REACT_EXPORTS = [
2727
+ "Children",
2728
+ "Component",
2729
+ "Fragment",
2730
+ "Profiler",
2731
+ "PureComponent",
2732
+ "StrictMode",
2733
+ "Suspense",
2734
+ "cloneElement",
2735
+ "createContext",
2736
+ "createElement",
2737
+ "createFactory",
2738
+ "createRef",
2739
+ "forwardRef",
2740
+ "isValidElement",
2741
+ "lazy",
2742
+ "memo",
2743
+ "startTransition",
2744
+ "useCallback",
2745
+ "useContext",
2746
+ "useDebugValue",
2747
+ "useDeferredValue",
2748
+ "useEffect",
2749
+ "useId",
2750
+ "useImperativeHandle",
2751
+ "useInsertionEffect",
2752
+ "useLayoutEffect",
2753
+ "useMemo",
2754
+ "useReducer",
2755
+ "useRef",
2756
+ "useState",
2757
+ "useSyncExternalStore",
2758
+ "useTransition",
2759
+ "version"
2760
+ ];
2761
+ const REACT_NATIVE_EXPORTS = [
2762
+ "View",
2763
+ "Text",
2764
+ "Pressable",
2765
+ "TouchableOpacity",
2766
+ "TouchableHighlight",
2767
+ "TouchableWithoutFeedback",
2768
+ "TouchableNativeFeedback",
2769
+ "ScrollView",
2770
+ "FlatList",
2771
+ "SectionList",
2772
+ "VirtualizedList",
2773
+ "KeyboardAvoidingView",
2774
+ "TextInput",
2775
+ "Image",
2776
+ "ImageBackground",
2777
+ "SafeAreaView",
2778
+ "ActivityIndicator",
2779
+ "Switch",
2780
+ "Modal",
2781
+ "Button",
2782
+ "RefreshControl",
2783
+ "StatusBar",
2784
+ "StyleSheet",
2785
+ "Platform",
2786
+ "Dimensions",
2787
+ "useWindowDimensions",
2788
+ "PixelRatio",
2789
+ "Animated",
2790
+ "useAnimatedValue",
2791
+ "Easing",
2792
+ "Alert",
2793
+ "Linking",
2794
+ "Share",
2795
+ "Vibration",
2796
+ "Keyboard",
2797
+ "BackHandler",
2798
+ "AppState",
2799
+ "Appearance",
2800
+ "useColorScheme",
2801
+ "PanResponder",
2802
+ "LayoutAnimation",
2803
+ "UIManager",
2804
+ "InteractionManager",
2805
+ "NativeEventEmitter",
2806
+ "DeviceEventEmitter",
2807
+ "NativeAppEventEmitter",
2808
+ "NativeModules",
2809
+ "I18nManager",
2810
+ "AccessibilityInfo",
2811
+ "LogBox",
2812
+ "YellowBox",
2813
+ "findNodeHandle",
2814
+ "processColor",
2815
+ "PermissionsAndroid",
2816
+ "ToastAndroid",
2817
+ "ActionSheetIOS",
2818
+ "InputAccessoryView",
2819
+ "DrawerLayoutAndroid",
2820
+ "TurboModuleRegistry",
2821
+ "requireNativeComponent",
2822
+ "codegenNativeComponent",
2823
+ "PlatformColor",
2824
+ "DynamicColorIOS",
2825
+ "DevSettings",
2826
+ "Systrace",
2827
+ "Networking",
2828
+ "Settings"
2829
+ ];
2830
+ function sendJson(res, status, payload) {
2831
+ res.statusCode = status;
2832
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
2833
+ res.setHeader("X-Content-Type-Options", "nosniff");
2834
+ res.end(JSON.stringify(payload));
2835
+ }
2836
+ function sendJs(res, code) {
2837
+ res.statusCode = 200;
2838
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
2839
+ res.setHeader("Cache-Control", "no-store");
2840
+ res.setHeader("X-Content-Type-Options", "nosniff");
2841
+ res.end(code);
2842
+ }
2843
+ function buildReactShimModule() {
2844
+ const lines = [
2845
+ `const R = window.__ZELUNE_RUNTIME__.React;`,
2846
+ `export default R;`,
2847
+ `export const jsx = R.createElement;`,
2848
+ `export const jsxs = R.createElement;`,
2849
+ `export const jsxDEV = R.createElement;`
2850
+ ];
2851
+ for (const name of REACT_EXPORTS) {
2852
+ lines.push(`export const ${name} = R.${name};`);
2853
+ }
2854
+ return lines.join("\n");
2855
+ }
2856
+ function buildReactDomShimModule() {
2857
+ return [
2858
+ `const RD = window.__ZELUNE_RUNTIME__.ReactDOM;`,
2859
+ `export default RD;`,
2860
+ `export const createRoot = RD.createRoot;`,
2861
+ `export const hydrateRoot = RD.hydrateRoot;`,
2862
+ `export const flushSync = RD.flushSync;`,
2863
+ `export const createPortal = RD.createPortal;`
2864
+ ].join("\n");
2865
+ }
2866
+ function buildReactNativeShimModule() {
2867
+ const lines = [
2868
+ `const RN = window.__ZELUNE_RUNTIME__.ReactNative;`,
2869
+ `export default RN.default || RN;`
2870
+ ];
2871
+ for (const name of REACT_NATIVE_EXPORTS) {
2872
+ lines.push(`export const ${name} = RN.${name};`);
2873
+ }
2874
+ return lines.join("\n");
2875
+ }
2876
+ function buildLucideShimModule(requestedNames) {
2877
+ const lines = [
2878
+ `const L = window.__ZELUNE_RUNTIME__.LucideIcons || {};`,
2879
+ `const M = window.__ZELUNE_RUNTIME__.MockNav || {};`,
2880
+ `export default L;`
2881
+ ];
2882
+ const unique = Array.from(
2883
+ new Set(
2884
+ requestedNames.map((s) => s.trim()).filter((s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s) && s !== "default")
2885
+ )
2886
+ );
2887
+ for (const name of unique) {
2888
+ lines.push(
2889
+ `export const ${name} = L[${JSON.stringify(name)}] || M.createVectorIconComponent(${JSON.stringify(name)});`
2890
+ );
2891
+ }
2892
+ return lines.join("\n");
2893
+ }
2894
+ function buildUniversalStubModule(requestedNames) {
2895
+ const lines = [
2896
+ `const React = window.__ZELUNE_RUNTIME__.React;`,
2897
+ `const RN = window.__ZELUNE_RUNTIME__.ReactNative;`,
2898
+ `const M = window.__ZELUNE_RUNTIME__.MockNav;`,
2899
+ `export default M.default || M;`,
2900
+ `const COMPOUND_SUB_NAMES = ['Item', 'Header', 'Footer', 'Title', 'Content', 'Trigger', 'Portal', 'Overlay', 'Close', 'Action', 'Cancel', 'Description', 'Label', 'Group', 'Separator', 'Value', 'Icon', 'Viewport', 'Root', 'Screen', 'Navigator', 'Provider', 'Consumer'];`,
2901
+ `function createSubStub(displayName) {`,
2902
+ ` const Sub = React.forwardRef(function SubComp(props, ref) {`,
2903
+ ` const content = typeof props?.children === 'function' ? props.children(M.SAFE_CHAINABLE_PROXY) : props?.children;`,
2904
+ ` return React.createElement(RN.View, { style: props?.style, className: props?.className, ref }, content);`,
2905
+ ` });`,
2906
+ ` Sub.displayName = displayName;`,
2907
+ ` return Sub;`,
2908
+ `}`,
2909
+ `function createComponentStub(displayName) {`,
2910
+ ` const Comp = React.forwardRef(function StubComp(props, ref) {`,
2911
+ ` if (typeof props?.render === 'function') {`,
2912
+ ` return props.render({`,
2913
+ ` field: { name: props?.name ?? '', value: props?.defaultValue ?? '', onChange: () => undefined, onBlur: () => undefined, ref: () => undefined },`,
2914
+ ` fieldState: { invalid: false, isTouched: false, isDirty: false, error: undefined },`,
2915
+ ` formState: { errors: {}, isSubmitting: false, isValid: true },`,
2916
+ ` });`,
2917
+ ` }`,
2918
+ ` const content = typeof props?.children === 'function' ? props.children(M.SAFE_CHAINABLE_PROXY) : props?.children;`,
2919
+ ` return React.createElement(RN.View, { style: props?.style, className: props?.className, ref }, content);`,
2920
+ ` });`,
2921
+ ` Comp.displayName = displayName;`,
2922
+ ` for (const sub of COMPOUND_SUB_NAMES) {`,
2923
+ ` Comp[sub] = createSubStub(displayName + '.' + sub);`,
2924
+ ` }`,
2925
+ ` return Comp;`,
2926
+ `}`,
2927
+ `function resolveExport(name) {`,
2928
+ ` if (name in M) return M[name];`,
2929
+ ` if (M.default && name in M.default) return M.default[name];`,
2930
+ ` if (/^use[A-Z]/.test(name)) return () => M.SAFE_CHAINABLE_PROXY;`,
2931
+ ` if (/^[A-Z]/.test(name)) return createComponentStub(name);`,
2932
+ ` return M.SAFE_CHAINABLE_PROXY;`,
2933
+ `}`
2934
+ ];
2935
+ const unique = Array.from(
2936
+ new Set(
2937
+ requestedNames.map((s) => s.trim()).filter((s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s) && s !== "default")
2938
+ )
2939
+ );
2940
+ for (const name of unique) {
2941
+ lines.push(`export const ${name} = resolveExport(${JSON.stringify(name)});`);
2942
+ }
2943
+ return lines.join("\n");
2944
+ }
2945
+ async function resolveConnectedProjectName(workspaceRoot) {
2946
+ var _a;
2947
+ const activeRoot = getActiveProjectRoot(workspaceRoot);
2948
+ const dirName = path.basename(activeRoot);
2949
+ try {
2950
+ const appJsonRaw = await fs.readFile(path.join(activeRoot, "app.json"), "utf-8");
2951
+ const appJson = JSON.parse(appJsonRaw);
2952
+ if ((_a = appJson == null ? void 0 : appJson.expo) == null ? void 0 : _a.name) return appJson.expo.name;
2953
+ if (appJson == null ? void 0 : appJson.name) return appJson.name;
2954
+ } catch {
2955
+ }
2956
+ try {
2957
+ const pkgRaw = await fs.readFile(path.join(activeRoot, "package.json"), "utf-8");
2958
+ const pkg = JSON.parse(pkgRaw);
2959
+ if ((pkg == null ? void 0 : pkg.name) && pkg.name.toLowerCase() !== "zelune") {
2960
+ return pkg.name;
2961
+ }
2962
+ } catch {
2963
+ }
2964
+ return dirName;
2965
+ }
2966
+ async function startStudioHttpServer(options) {
2967
+ const { packageRoot, targetProjectRoot, workspaceRoot } = options;
2968
+ const distDir = path.resolve(packageRoot, "dist");
2969
+ const allowedRoots = Array.from(
2970
+ /* @__PURE__ */ new Set([
2971
+ path.resolve(packageRoot),
2972
+ path.resolve(targetProjectRoot),
2973
+ path.resolve(workspaceRoot)
2974
+ ])
2975
+ );
2976
+ function isPathAllowed(candidatePath) {
2977
+ const resolved = path.resolve(candidatePath);
2978
+ return allowedRoots.some((root) => resolved === root || resolved.startsWith(root + path.sep));
2979
+ }
2980
+ const requestHandler = async (req, res) => {
2981
+ const rawUrl = req.url ?? "/";
2982
+ const qIdx = rawUrl.indexOf("?");
2983
+ const pathname = qIdx === -1 ? rawUrl : rawUrl.slice(0, qIdx);
2984
+ const searchParams = new URLSearchParams(qIdx === -1 ? "" : rawUrl.slice(qIdx + 1));
2985
+ if (pathname.startsWith("/api/")) {
2986
+ const securityCheck = verifyApiRequestSecurity(req);
2987
+ if (!securityCheck.allowed) {
2988
+ return sendJson(res, 403, { ok: false, error: securityCheck.reason });
2989
+ }
2990
+ const activeRoot = getActiveProjectRoot(targetProjectRoot);
2991
+ const layoutFilePath = path.resolve(activeRoot, ".canvas-layout.json");
2992
+ try {
2993
+ if (req.method === "GET" && pathname === "/api/screens") {
2994
+ const [screens, projectName] = await Promise.all([
2995
+ discoverScreens(targetProjectRoot),
2996
+ resolveConnectedProjectName(targetProjectRoot)
2997
+ ]);
2998
+ const history = getHistoryStatus();
2999
+ return sendJson(res, 200, { ok: true, projectName, screens, history });
3000
+ }
3001
+ if (req.method === "GET" && pathname === "/api/inspect") {
3002
+ const file = searchParams.get("file") ?? "";
3003
+ const astPath = searchParams.get("astPath") ?? "";
3004
+ const lineParam = searchParams.get("line");
3005
+ const line = lineParam ? Number.parseInt(lineParam, 10) : void 0;
3006
+ const element = await inspectElement(targetProjectRoot, { file, astPath, line });
3007
+ const history = getHistoryStatus();
3008
+ return sendJson(res, 200, { ok: true, element, history });
3009
+ }
3010
+ if (req.method === "POST" && pathname === "/api/patch") {
3011
+ const body = await readJsonBody(req);
3012
+ const result = await patchElement(targetProjectRoot, body);
3013
+ return sendJson(res, 200, {
3014
+ ok: true,
3015
+ element: result.element,
3016
+ history: {
3017
+ undoAvailable: result.undoAvailable,
3018
+ redoAvailable: result.redoAvailable
3019
+ }
3020
+ });
3021
+ }
3022
+ if (req.method === "POST" && pathname === "/api/reorder") {
3023
+ const body = await readJsonBody(req);
3024
+ const result = await reorderElement(targetProjectRoot, body);
3025
+ return sendJson(res, 200, {
3026
+ ok: true,
3027
+ element: result.element,
3028
+ history: {
3029
+ undoAvailable: result.undoAvailable,
3030
+ redoAvailable: result.redoAvailable
3031
+ }
3032
+ });
3033
+ }
3034
+ if (req.method === "POST" && pathname === "/api/insert") {
3035
+ const body = await readJsonBody(req);
3036
+ const result = await insertElement(targetProjectRoot, body);
3037
+ return sendJson(res, 200, {
3038
+ ok: true,
3039
+ element: result.element,
3040
+ history: {
3041
+ undoAvailable: result.undoAvailable,
3042
+ redoAvailable: result.redoAvailable
3043
+ }
3044
+ });
3045
+ }
3046
+ if (req.method === "POST" && pathname === "/api/duplicate") {
3047
+ const body = await readJsonBody(req);
3048
+ const result = await duplicateElement(targetProjectRoot, body);
3049
+ return sendJson(res, 200, {
3050
+ ok: true,
3051
+ element: result.element,
3052
+ history: {
3053
+ undoAvailable: result.undoAvailable,
3054
+ redoAvailable: result.redoAvailable
3055
+ }
3056
+ });
3057
+ }
3058
+ if (req.method === "POST" && pathname === "/api/delete") {
3059
+ const body = await readJsonBody(req);
3060
+ const result = await deleteElement(targetProjectRoot, body);
3061
+ return sendJson(res, 200, {
3062
+ ok: true,
3063
+ deletedAstPath: result.deletedAstPath,
3064
+ history: {
3065
+ undoAvailable: result.undoAvailable,
3066
+ redoAvailable: result.redoAvailable
3067
+ }
3068
+ });
3069
+ }
3070
+ if (req.method === "POST" && pathname === "/api/undo") {
3071
+ const result = await undoLastPatch(targetProjectRoot);
3072
+ return sendJson(res, 200, { ok: true, ...result });
3073
+ }
3074
+ if (req.method === "POST" && pathname === "/api/redo") {
3075
+ const result = await redoLastPatch(targetProjectRoot);
3076
+ return sendJson(res, 200, { ok: true, ...result });
3077
+ }
3078
+ if (req.method === "GET" && pathname === "/api/layout") {
3079
+ try {
3080
+ const raw = await fs.readFile(layoutFilePath, "utf-8");
3081
+ const validated = sanitizeLayoutPayload(JSON.parse(raw));
3082
+ return sendJson(res, 200, { ok: true, layout: validated });
3083
+ } catch {
3084
+ return sendJson(res, 200, { ok: true, layout: null });
3085
+ }
3086
+ }
3087
+ if (req.method === "POST" && pathname === "/api/layout") {
3088
+ const body = await readJsonBody(req);
3089
+ const validated = sanitizeLayoutPayload(body);
3090
+ await fs.writeFile(layoutFilePath, JSON.stringify(validated, null, 2), "utf-8");
3091
+ return sendJson(res, 200, { ok: true });
3092
+ }
3093
+ return sendJson(res, 404, { ok: false, error: `Unknown API route: ${pathname}` });
3094
+ } catch (err) {
3095
+ const message = err instanceof Error ? err.message : String(err);
3096
+ return sendJson(res, 400, { ok: false, error: message });
3097
+ }
3098
+ }
3099
+ if (pathname === "/@zelune/shim/react.js") {
3100
+ return sendJs(res, buildReactShimModule());
3101
+ }
3102
+ if (pathname === "/@zelune/shim/react-dom.js") {
3103
+ return sendJs(res, buildReactDomShimModule());
3104
+ }
3105
+ if (pathname === "/@zelune/shim/react-native.js") {
3106
+ return sendJs(res, buildReactNativeShimModule());
3107
+ }
3108
+ if (pathname === "/@zelune/shim/lucide.js") {
3109
+ const exportsParam = searchParams.get("exports") ?? "";
3110
+ return sendJs(res, buildLucideShimModule(exportsParam.split(",")));
3111
+ }
3112
+ if (pathname === "/@zelune/shim/stub.js") {
3113
+ const exportsParam = searchParams.get("exports") ?? "";
3114
+ return sendJs(res, buildUniversalStubModule(exportsParam.split(",")));
3115
+ }
3116
+ if (pathname === "/@zelune/module") {
3117
+ const fileParam = searchParams.get("file") ?? "";
3118
+ if (!fileParam || !isPathAllowed(fileParam) || !fs$1.existsSync(fileParam)) {
3119
+ res.statusCode = 404;
3120
+ return res.end("// Module not found\nexport default {};\n");
3121
+ }
3122
+ try {
3123
+ const compiled = compileModuleForBrowser({
3124
+ filePath: fileParam,
3125
+ targetProjectRoot,
3126
+ workspaceRoot
3127
+ });
3128
+ return sendJs(res, compiled);
3129
+ } catch (err) {
3130
+ const msg = err instanceof Error ? err.message : String(err);
3131
+ res.statusCode = 500;
3132
+ return res.end(`throw new Error(${JSON.stringify(msg)});
3133
+ `);
3134
+ }
3135
+ }
3136
+ if (pathname === "/@zelune/asset") {
3137
+ const fileParam = searchParams.get("file") ?? "";
3138
+ if (!fileParam || !isPathAllowed(fileParam) || !fs$1.existsSync(fileParam)) {
3139
+ res.statusCode = 404;
3140
+ return res.end("Not found");
3141
+ }
3142
+ const ext = path.extname(fileParam).toLowerCase();
3143
+ res.statusCode = 200;
3144
+ res.setHeader("Content-Type", MIME_TYPES[ext] ?? "application/octet-stream");
3145
+ return fs$1.createReadStream(fileParam).pipe(res);
3146
+ }
3147
+ if (pathname === "/" || pathname === "/index.html") {
3148
+ const indexHtmlPath = path.join(distDir, "index.html");
3149
+ if (!fs$1.existsSync(indexHtmlPath)) {
3150
+ res.statusCode = 500;
3151
+ return res.end("Zelune dist/index.html not found.");
3152
+ }
3153
+ const rawHtml = await fs.readFile(indexHtmlPath, "utf-8");
3154
+ const tailwindTheme = loadProjectTailwindTheme([targetProjectRoot, workspaceRoot]);
3155
+ const discoveredManifest = discoverScreenFilesSync(targetProjectRoot);
3156
+ const runtimeScript = `<script>window.__ZELUNE_TAILWIND_THEME__ = ${JSON.stringify(
3157
+ tailwindTheme
3158
+ )}; window.__ZELUNE_SCREEN_MANIFEST__ = ${JSON.stringify(discoveredManifest)};<\/script>`;
3159
+ const injectedHtml = rawHtml.replace("<head>", `<head>
3160
+ ${runtimeScript}`);
3161
+ res.statusCode = 200;
3162
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
3163
+ res.setHeader("Cache-Control", "no-store");
3164
+ return res.end(injectedHtml);
3165
+ }
3166
+ const relativeAsset = pathname.replace(/^\/+/, "");
3167
+ const assetCandidate = path.resolve(distDir, relativeAsset);
3168
+ if (assetCandidate.startsWith(distDir + path.sep) && fs$1.existsSync(assetCandidate) && fs$1.statSync(assetCandidate).isFile()) {
3169
+ const ext = path.extname(assetCandidate).toLowerCase();
3170
+ res.statusCode = 200;
3171
+ res.setHeader("Content-Type", MIME_TYPES[ext] ?? "application/octet-stream");
3172
+ return fs$1.createReadStream(assetCandidate).pipe(res);
3173
+ }
3174
+ res.statusCode = 404;
3175
+ res.end("Not found");
3176
+ };
3177
+ const basePort = options.port ?? 5173;
3178
+ for (let offset = 0; offset < 25; offset++) {
3179
+ const candidatePort = basePort + offset;
3180
+ try {
3181
+ const srv = await new Promise((resolve, reject) => {
3182
+ const s = http.createServer(requestHandler);
3183
+ s.once("error", reject);
3184
+ s.listen(candidatePort, "127.0.0.1", () => {
3185
+ s.removeListener("error", reject);
3186
+ resolve(s);
3187
+ });
3188
+ });
3189
+ return { server: srv, port: candidatePort };
3190
+ } catch (err) {
3191
+ const code = err == null ? void 0 : err.code;
3192
+ if (code === "EADDRINUSE" && !options.port) {
3193
+ continue;
3194
+ }
3195
+ throw err;
3196
+ }
3197
+ }
3198
+ throw new Error(`Unable to bind Studio HTTP server starting from port ${basePort}`);
3199
+ }
1865
3200
  const __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
1866
3201
  const DEFAULT_WORKSPACE_ROOT = path.resolve(__dirname$1, "..");
1867
3202
  const MCP_TOOLS = [
@@ -2114,7 +3449,7 @@ async function handleMcpJsonRpcRequest(request, customWorkspaceRoot) {
2114
3449
  },
2115
3450
  serverInfo: {
2116
3451
  name: "zelune",
2117
- version: "1.6.1"
3452
+ version: "1.6.4"
2118
3453
  }
2119
3454
  }
2120
3455
  };
@@ -2213,7 +3548,7 @@ function startMcpServer(customWorkspaceRoot) {
2213
3548
  }
2214
3549
  const isDirectExecution = Boolean(process.argv[1]) && (() => {
2215
3550
  try {
2216
- return import.meta.url === pathToFileURL(fsSync.realpathSync(process.argv[1])).href;
3551
+ return import.meta.url === pathToFileURL(fs$1.realpathSync(process.argv[1])).href;
2217
3552
  } catch {
2218
3553
  return false;
2219
3554
  }
@@ -2224,5 +3559,6 @@ if (isDirectExecution) {
2224
3559
  export {
2225
3560
  MCP_TOOLS,
2226
3561
  handleMcpJsonRpcRequest,
2227
- startMcpServer
3562
+ startMcpServer,
3563
+ startStudioHttpServer
2228
3564
  };