zelune 1.6.3 → 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 +8 -0
  2. package/bin/cli.mjs +13 -53
  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 -1
  10. package/mcp/server.mjs +1324 -29
  11. package/package.json +10 -13
  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 -522
  16. package/plugins/viteRnSourceInject.ts +0 -105
  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 -1869
  25. package/src/runtime/ScreenErrorBoundary.tsx +0 -86
  26. package/src/runtime/nativewindShim.ts +0 -997
  27. package/src/runtime/reactNativeShim.ts +0 -1674
  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 -378
  35. package/src/server/astEngine.ts +0 -2624
  36. package/src/vite-env.d.ts +0 -7
  37. package/tsconfig.json +0 -23
  38. package/vite.config.ts +0 -339
  39. /package/{src/styles.css → dist/assets/index-C7nrS82N.css} +0 -0
package/mcp/server.mjs CHANGED
@@ -1,4 +1,4 @@
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";
@@ -6,7 +6,8 @@ import fs from "node:fs/promises";
6
6
  import { parse } from "@babel/parser";
7
7
  import _generate from "@babel/generator";
8
8
  import * as t from "@babel/types";
9
- const generate = _generate.default ?? _generate;
9
+ import http from "node:http";
10
+ const generate$1 = _generate.default ?? _generate;
10
11
  function getJsxTagName(nameNode) {
11
12
  if (t.isJSXIdentifier(nameNode)) {
12
13
  return nameNode.name;
@@ -99,7 +100,7 @@ function countScreenFilesInDirectory(screensDir, maxDepth = 4) {
99
100
  if (depth > maxDepth) return;
100
101
  let entries = [];
101
102
  try {
102
- entries = fsSync.readdirSync(curr, { withFileTypes: true });
103
+ entries = fs$1.readdirSync(curr, { withFileTypes: true });
103
104
  } catch {
104
105
  return;
105
106
  }
@@ -128,7 +129,7 @@ function hasDirectScreenFiles(dirPath) {
128
129
  for (const rel of SCREEN_DIR_CANDIDATES) {
129
130
  const full = path.join(dirPath, rel);
130
131
  try {
131
- if (fsSync.existsSync(full) && fsSync.statSync(full).isDirectory()) {
132
+ if (fs$1.existsSync(full) && fs$1.statSync(full).isDirectory()) {
132
133
  const count = countScreenFilesInDirectory(full);
133
134
  if (count > 0) {
134
135
  return { found: true, screenCount: count };
@@ -145,13 +146,13 @@ function scoreMobileCandidate(dirPath, screenCount) {
145
146
  if (/mobile|native|expo|app|client/.test(folderName)) {
146
147
  score += 10;
147
148
  }
148
- 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"))) {
149
150
  score += 25;
150
151
  }
151
152
  try {
152
153
  const pkgPath = path.join(dirPath, "package.json");
153
- if (fsSync.existsSync(pkgPath)) {
154
- const pkgRaw = fsSync.readFileSync(pkgPath, "utf-8");
154
+ if (fs$1.existsSync(pkgPath)) {
155
+ const pkgRaw = fs$1.readFileSync(pkgPath, "utf-8");
155
156
  if (pkgRaw.includes('"react-native"') || pkgRaw.includes('"expo"')) {
156
157
  score += 20;
157
158
  }
@@ -162,7 +163,7 @@ function scoreMobileCandidate(dirPath, screenCount) {
162
163
  }
163
164
  function findMobileAppRoot(rawRoot) {
164
165
  const normalizedRoot = path.resolve(rawRoot);
165
- if (!fsSync.existsSync(normalizedRoot)) {
166
+ if (!fs$1.existsSync(normalizedRoot)) {
166
167
  return normalizedRoot;
167
168
  }
168
169
  const direct = hasDirectScreenFiles(normalizedRoot);
@@ -175,7 +176,7 @@ function findMobileAppRoot(rawRoot) {
175
176
  if (depth > 2) return;
176
177
  let entries = [];
177
178
  try {
178
- entries = fsSync.readdirSync(parentDir, { withFileTypes: true });
179
+ entries = fs$1.readdirSync(parentDir, { withFileTypes: true });
179
180
  } catch {
180
181
  return;
181
182
  }
@@ -200,20 +201,20 @@ function findMobileAppRoot(rawRoot) {
200
201
  }
201
202
  function getActiveProjectRoot(fallbackRoot) {
202
203
  const envTarget = process.env.ANTIGRAVITY_TARGET_PROJECT;
203
- 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);
204
205
  return findMobileAppRoot(baseRoot);
205
206
  }
206
207
  function resolveScreensDirectory(projectRoot) {
207
208
  const normalizedRoot = findMobileAppRoot(projectRoot);
208
209
  for (const rel of SCREEN_DIR_CANDIDATES) {
209
210
  const full = path.join(normalizedRoot, rel);
210
- 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) {
211
212
  return { dirPath: full, relDir: rel };
212
213
  }
213
214
  }
214
215
  for (const rel of SCREEN_DIR_CANDIDATES) {
215
216
  const full = path.join(normalizedRoot, rel);
216
- if (fsSync.existsSync(full) && fsSync.statSync(full).isDirectory()) {
217
+ if (fs$1.existsSync(full) && fs$1.statSync(full).isDirectory()) {
217
218
  return { dirPath: full, relDir: rel };
218
219
  }
219
220
  }
@@ -225,7 +226,7 @@ function resolveScreensDirectory(projectRoot) {
225
226
  const FORBIDDEN_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
226
227
  function safeRealpath(targetPath) {
227
228
  try {
228
- return fsSync.realpathSync(targetPath);
229
+ return fs$1.realpathSync(targetPath);
229
230
  } catch {
230
231
  return path.resolve(targetPath);
231
232
  }
@@ -236,7 +237,7 @@ function resolveSafeScreenPath(workspaceRoot, relativeFile) {
236
237
  const resolved = path.resolve(normalizedRoot, cleanRel);
237
238
  const allowedDirs = ["src/screens", "screens", "app", "src/app"].map((d) => {
238
239
  const candidate = path.resolve(normalizedRoot, d);
239
- return fsSync.existsSync(candidate) ? safeRealpath(candidate) : candidate;
240
+ return fs$1.existsSync(candidate) ? safeRealpath(candidate) : candidate;
240
241
  });
241
242
  const isLexicallyAllowed = allowedDirs.some(
242
243
  (dir) => resolved.startsWith(dir + path.sep) || resolved === dir
@@ -249,8 +250,8 @@ function resolveSafeScreenPath(workspaceRoot, relativeFile) {
249
250
  if (!resolved.endsWith(".tsx") && !resolved.endsWith(".jsx")) {
250
251
  throw new Error(`Invalid file extension for "${relativeFile}". Only .tsx and .jsx are allowed.`);
251
252
  }
252
- if (fsSync.existsSync(resolved)) {
253
- const stat = fsSync.lstatSync(resolved);
253
+ if (fs$1.existsSync(resolved)) {
254
+ const stat = fs$1.lstatSync(resolved);
254
255
  if (stat.isSymbolicLink()) {
255
256
  throw new Error(
256
257
  `Security violation: Symbolic link "${relativeFile}" is not permitted inside screen directories`
@@ -365,7 +366,7 @@ function extractElementNavigationTarget(openingElement) {
365
366
  }
366
367
  if (t.isJSXExpressionContainer(attr.value)) {
367
368
  const expr = attr.value.expression;
368
- const codeStr = generate(expr).code;
369
+ const codeStr = generate$1(expr).code;
369
370
  const match = codeStr.match(/(?:navigate|push|replace|jumpTo)\(\s*['"`]([^'"`$]+)['"`]/);
370
371
  if (match) return normalizeRouteTarget(match[1]);
371
372
  const objMatch = codeStr.match(/(?:pathname|screen|route|name)\s*:\s*['"`]([^'"`$]+)['"`]/);
@@ -429,7 +430,7 @@ function discoverScreenFilesSync(workspaceRoot) {
429
430
  if (depth > 4) return;
430
431
  let dirents = [];
431
432
  try {
432
- dirents = fsSync.readdirSync(currDir, { withFileTypes: true });
433
+ dirents = fs$1.readdirSync(currDir, { withFileTypes: true });
433
434
  } catch {
434
435
  return;
435
436
  }
@@ -636,7 +637,7 @@ async function discoverScreens(workspaceRoot) {
636
637
  const extractTriggerLabelFromAncestry = (nodePath, fallbackTarget) => {
637
638
  const jsxParent = nodePath.findParent((p) => p.isJSXElement());
638
639
  if (jsxParent) {
639
- const rawCode = generate(jsxParent.node).code;
640
+ const rawCode = generate$1(jsxParent.node).code;
640
641
  const textMatches = Array.from(rawCode.matchAll(/>([^<>{}\n]+)</g)).map((m) => m[1].trim()).filter(Boolean);
641
642
  if (textMatches.length > 0) {
642
643
  return textMatches.slice(0, 2).join(" · ");
@@ -666,7 +667,7 @@ async function discoverScreens(workspaceRoot) {
666
667
  }
667
668
  }
668
669
  if (attrName === "onPress" && t.isJSXExpressionContainer(attr.value)) {
669
- const exprCode = generate(attr.value.expression).code;
670
+ const exprCode = generate$1(attr.value.expression).code;
670
671
  if (/\b(?:goBack|back|pop|popToTop)\s*\(/.test(exprCode)) {
671
672
  hasBackNavigation = true;
672
673
  backLine = line;
@@ -931,7 +932,7 @@ async function inspectElement(workspaceRoot, params) {
931
932
  const textInfo = inspectJsxTextChildren(element);
932
933
  const styleInfo = resolveElementStyles(opening, styleSheetInfo);
933
934
  const navigationTarget = extractElementNavigationTarget(opening);
934
- const snippetRaw = generate(element, { concise: false }).code;
935
+ const snippetRaw = generate$1(element, { concise: false }).code;
935
936
  const codeSnippet = snippetRaw.length > 600 ? `${snippetRaw.slice(0, 600)}
936
937
  /* ... */` : snippetRaw;
937
938
  return {
@@ -1065,7 +1066,7 @@ async function patchElement(workspaceRoot, payload) {
1065
1066
  upsertObjectProperty(targetObject, styleKey, styleVal);
1066
1067
  }
1067
1068
  }
1068
- const output = generate(ast, {
1069
+ const output = generate$1(ast, {
1069
1070
  retainLines: false,
1070
1071
  comments: true,
1071
1072
  jsescOption: { minimal: true }
@@ -1201,7 +1202,7 @@ async function reorderElement(workspaceRoot, payload) {
1201
1202
  }
1202
1203
  }
1203
1204
  });
1204
- const output = generate(ast, {
1205
+ const output = generate$1(ast, {
1205
1206
  retainLines: false,
1206
1207
  comments: true,
1207
1208
  jsescOption: { minimal: true }
@@ -1733,7 +1734,7 @@ async function insertElement(workspaceRoot, payload) {
1733
1734
  }
1734
1735
  }
1735
1736
  });
1736
- const output = generate(ast, {
1737
+ const output = generate$1(ast, {
1737
1738
  retainLines: false,
1738
1739
  comments: true,
1739
1740
  jsescOption: { minimal: true }
@@ -1810,7 +1811,7 @@ async function duplicateElement(workspaceRoot, payload) {
1810
1811
  }
1811
1812
  }
1812
1813
  });
1813
- const output = generate(ast, {
1814
+ const output = generate$1(ast, {
1814
1815
  retainLines: false,
1815
1816
  comments: true,
1816
1817
  jsescOption: { minimal: true }
@@ -1878,7 +1879,7 @@ async function deleteElement(workspaceRoot, payload) {
1878
1879
  throw new Error("Could not find element inside parent children.");
1879
1880
  }
1880
1881
  siblings.splice(pos, 1);
1881
- const output = generate(ast, {
1882
+ const output = generate$1(ast, {
1882
1883
  retainLines: false,
1883
1884
  comments: true,
1884
1885
  jsescOption: { minimal: true }
@@ -1903,6 +1904,1299 @@ async function deleteElement(workspaceRoot, payload) {
1903
1904
  };
1904
1905
  });
1905
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
+ }
1906
3200
  const __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
1907
3201
  const DEFAULT_WORKSPACE_ROOT = path.resolve(__dirname$1, "..");
1908
3202
  const MCP_TOOLS = [
@@ -2155,7 +3449,7 @@ async function handleMcpJsonRpcRequest(request, customWorkspaceRoot) {
2155
3449
  },
2156
3450
  serverInfo: {
2157
3451
  name: "zelune",
2158
- version: "1.6.3"
3452
+ version: "1.6.4"
2159
3453
  }
2160
3454
  }
2161
3455
  };
@@ -2254,7 +3548,7 @@ function startMcpServer(customWorkspaceRoot) {
2254
3548
  }
2255
3549
  const isDirectExecution = Boolean(process.argv[1]) && (() => {
2256
3550
  try {
2257
- return import.meta.url === pathToFileURL(fsSync.realpathSync(process.argv[1])).href;
3551
+ return import.meta.url === pathToFileURL(fs$1.realpathSync(process.argv[1])).href;
2258
3552
  } catch {
2259
3553
  return false;
2260
3554
  }
@@ -2265,5 +3559,6 @@ if (isDirectExecution) {
2265
3559
  export {
2266
3560
  MCP_TOOLS,
2267
3561
  handleMcpJsonRpcRequest,
2268
- startMcpServer
3562
+ startMcpServer,
3563
+ startStudioHttpServer
2269
3564
  };