zelune 1.5.7 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/vite.config.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { defineConfig } from 'vite';
1
+ import { parse } from '@babel/parser';
2
+ import { build, defineConfig, loadEnv } from 'vite';
2
3
  import react from '@vitejs/plugin-react';
3
4
  import fs from 'node:fs';
4
- import { createRequire } from 'node:module';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { viteRnSourceInject } from './plugins/viteRnSourceInject';
@@ -11,7 +11,6 @@ import { viteCanvasApiMiddleware } from './src/server/apiMiddleware';
11
11
  import { findMobileAppRoot } from './src/server/astEngine';
12
12
 
13
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
- const requireFromZelune = createRequire(import.meta.url);
15
14
 
16
15
  const rawTargetRoot = process.env.ANTIGRAVITY_TARGET_PROJECT
17
16
  ? path.resolve(process.env.ANTIGRAVITY_TARGET_PROJECT)
@@ -34,8 +33,8 @@ function safeRealpath(targetPath: string): string {
34
33
 
35
34
  function resolvePackageRootDir(pkgName: string): string {
36
35
  try {
37
- const pkgJsonPath = requireFromZelune.resolve(`${pkgName}/package.json`);
38
- return path.dirname(pkgJsonPath);
36
+ const pkgJsonUrl = import.meta.resolve(`${pkgName}/package.json`);
37
+ return path.dirname(fileURLToPath(pkgJsonUrl));
39
38
  } catch {
40
39
  return pkgName;
41
40
  }
@@ -46,55 +45,169 @@ const resolvedReactDomDir = resolvePackageRootDir('react-dom');
46
45
  const resolvedRnWebDir = resolvePackageRootDir('react-native-web');
47
46
  const resolvedLucideDir = resolvePackageRootDir('lucide-react');
48
47
 
49
- /**
50
- * Loads only safe client-public environment variables (EXPO_PUBLIC_*, NEXT_PUBLIC_*, VITE_*)
51
- * from the target project's .env files so top-level SDK initializers in mobile screens work.
52
- */
53
48
  function loadPublicProjectEnv(roots: string[]): Record<string, string> {
54
49
  const publicEnv: Record<string, string> = {
55
50
  NODE_ENV: 'development',
56
51
  EXPO_PUBLIC_PLATFORM: 'web',
57
52
  };
58
- const envFiles = ['.env', '.env.development', '.env.local'];
59
53
  const uniqueRoots = Array.from(new Set(roots.map((r) => path.resolve(r))));
60
54
 
61
55
  for (const rootDir of uniqueRoots) {
62
- for (const fileName of envFiles) {
63
- const fullPath = path.join(rootDir, fileName);
64
- try {
65
- if (!fs.existsSync(fullPath)) continue;
66
- const raw = fs.readFileSync(fullPath, 'utf-8');
67
- for (const line of raw.split(/\r?\n/)) {
68
- const trimmed = line.trim();
69
- if (!trimmed || trimmed.startsWith('#')) continue;
70
- const eqIdx = trimmed.indexOf('=');
71
- if (eqIdx <= 0) continue;
72
- const key = trimmed.slice(0, eqIdx).trim();
73
- if (
74
- !key.startsWith('EXPO_PUBLIC_') &&
75
- !key.startsWith('NEXT_PUBLIC_') &&
76
- !key.startsWith('VITE_')
77
- ) {
78
- continue;
79
- }
80
- let val = trimmed.slice(eqIdx + 1).trim();
81
- if (
82
- (val.startsWith('"') && val.endsWith('"')) ||
83
- (val.startsWith("'") && val.endsWith("'"))
84
- ) {
85
- val = val.slice(1, -1);
56
+ try {
57
+ const loaded = loadEnv('development', rootDir, ['EXPO_PUBLIC_', 'NEXT_PUBLIC_', 'VITE_']);
58
+ Object.assign(publicEnv, loaded);
59
+ } catch {
60
+ // Ignore unreadable env directories
61
+ }
62
+ }
63
+ return publicEnv;
64
+ }
65
+
66
+ const publicProjectEnv = loadPublicProjectEnv([workspaceRoot, rawTargetRoot, targetProjectRoot]);
67
+
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ function extractStaticAstValue(node: any, scope: Map<string, unknown>, depth = 0): unknown {
70
+ if (!node || depth > 12) return undefined;
71
+
72
+ switch (node.type) {
73
+ case 'StringLiteral':
74
+ case 'NumericLiteral':
75
+ case 'BooleanLiteral':
76
+ return node.value;
77
+ case 'NullLiteral':
78
+ return null;
79
+ case 'UnaryExpression': {
80
+ const arg = extractStaticAstValue(node.argument, scope, depth + 1);
81
+ if (typeof arg === 'number') {
82
+ if (node.operator === '-') return -arg;
83
+ if (node.operator === '+') return +arg;
84
+ }
85
+ return undefined;
86
+ }
87
+ case 'TemplateLiteral': {
88
+ if (Array.isArray(node.expressions) && node.expressions.length === 0) {
89
+ return node.quasis?.[0]?.value?.cooked ?? node.quasis?.[0]?.value?.raw ?? '';
90
+ }
91
+ return undefined;
92
+ }
93
+ case 'ArrayExpression': {
94
+ const items: unknown[] = [];
95
+ for (const el of node.elements ?? []) {
96
+ if (!el) continue;
97
+ if (el.type === 'SpreadElement') {
98
+ const spreadVal = extractStaticAstValue(el.argument, scope, depth + 1);
99
+ if (Array.isArray(spreadVal)) items.push(...spreadVal);
100
+ } else {
101
+ const val = extractStaticAstValue(el, scope, depth + 1);
102
+ if (val !== undefined) items.push(val);
103
+ }
104
+ }
105
+ return items;
106
+ }
107
+ case 'ObjectExpression': {
108
+ const result: Record<string, unknown> = {};
109
+ for (const prop of node.properties ?? []) {
110
+ if (prop.type === 'SpreadElement') {
111
+ const spreadObj = extractStaticAstValue(prop.argument, scope, depth + 1);
112
+ if (spreadObj && typeof spreadObj === 'object' && !Array.isArray(spreadObj)) {
113
+ Object.assign(result, spreadObj);
86
114
  }
87
- publicEnv[key] = val;
115
+ continue;
116
+ }
117
+ if (prop.type !== 'ObjectProperty' || prop.computed) continue;
118
+ const key =
119
+ prop.key?.type === 'Identifier'
120
+ ? prop.key.name
121
+ : prop.key?.type === 'StringLiteral' || prop.key?.type === 'NumericLiteral'
122
+ ? String(prop.key.value)
123
+ : null;
124
+ if (!key) continue;
125
+ const val = extractStaticAstValue(prop.value, scope, depth + 1);
126
+ if (val !== undefined) {
127
+ result[key] = val;
88
128
  }
89
- } catch {
90
- // Ignore unreadable .env files
91
129
  }
130
+ return result;
92
131
  }
132
+ case 'TSAsExpression':
133
+ case 'TSSatisfiesExpression':
134
+ case 'TSTypeAssertion':
135
+ case 'ParenthesizedExpression':
136
+ return extractStaticAstValue(node.expression, scope, depth + 1);
137
+ case 'Identifier':
138
+ return scope.get(node.name);
139
+ default:
140
+ return undefined;
93
141
  }
94
- return publicEnv;
95
142
  }
96
143
 
97
- const publicProjectEnv = loadPublicProjectEnv([workspaceRoot, rawTargetRoot, targetProjectRoot]);
144
+ export function parseTailwindConfigSource(raw: string): Record<string, unknown> {
145
+ try {
146
+ const ast = parse(raw, {
147
+ sourceType: 'unambiguous',
148
+ plugins: ['typescript', 'jsx'],
149
+ errorRecovery: true,
150
+ });
151
+
152
+ const scope = new Map<string, unknown>();
153
+ let exportedConfig: unknown;
154
+
155
+ for (const stmt of ast.program.body) {
156
+ if (stmt.type === 'VariableDeclaration') {
157
+ for (const decl of stmt.declarations) {
158
+ if (decl.id.type === 'Identifier' && decl.init) {
159
+ const val = extractStaticAstValue(decl.init, scope);
160
+ if (val !== undefined) {
161
+ scope.set(decl.id.name, val);
162
+ }
163
+ }
164
+ }
165
+ } else if (stmt.type === 'ExportDefaultDeclaration') {
166
+ exportedConfig = extractStaticAstValue(stmt.declaration, scope);
167
+ } else if (
168
+ stmt.type === 'ExpressionStatement' &&
169
+ stmt.expression.type === 'AssignmentExpression'
170
+ ) {
171
+ const left = stmt.expression.left;
172
+ if (
173
+ left.type === 'MemberExpression' &&
174
+ left.object.type === 'Identifier' &&
175
+ left.property.type === 'Identifier' &&
176
+ ((left.object.name === 'module' && left.property.name === 'exports') ||
177
+ (left.object.name === 'exports' && left.property.name === 'default'))
178
+ ) {
179
+ exportedConfig = extractStaticAstValue(stmt.expression.right, scope);
180
+ }
181
+ }
182
+ }
183
+
184
+ const cfg = exportedConfig as
185
+ | { theme?: Record<string, unknown> & { extend?: Record<string, unknown> } }
186
+ | undefined;
187
+
188
+ if (cfg?.theme && typeof cfg.theme === 'object') {
189
+ const baseTheme = { ...cfg.theme };
190
+ const extendTheme =
191
+ cfg.theme.extend && typeof cfg.theme.extend === 'object' ? cfg.theme.extend : {};
192
+ delete baseTheme.extend;
193
+ const merged: Record<string, unknown> = { ...baseTheme };
194
+ for (const [k, v] of Object.entries(extendTheme)) {
195
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
196
+ merged[k] = {
197
+ ...((baseTheme[k] as Record<string, unknown>) ?? {}),
198
+ ...(v as Record<string, unknown>),
199
+ };
200
+ } else {
201
+ merged[k] = v;
202
+ }
203
+ }
204
+ return merged;
205
+ }
206
+ } catch {
207
+ // Ignore tailwind config parse errors
208
+ }
209
+ return {};
210
+ }
98
211
 
99
212
  function loadProjectTailwindTheme(roots: string[]): Record<string, unknown> {
100
213
  const candidates = [
@@ -110,39 +223,12 @@ function loadProjectTailwindTheme(roots: string[]): Record<string, unknown> {
110
223
  try {
111
224
  if (!fs.existsSync(fullPath)) continue;
112
225
  const raw = fs.readFileSync(fullPath, 'utf-8');
113
- const cleaned = raw
114
- .replace(/^import\s+.*$/gm, '')
115
- .replace(/\b(const|let|var)\s+([A-Za-z0-9_$]+)\s*:\s*[^=]+=/g, '$1 $2 =')
116
- .replace(/\bsatisfies\s+[A-Za-z0-9_$.<>]+/g, '')
117
- .replace(/\bas\s+const\b/g, '')
118
- .replace(/export\s+default\s+/, 'module.exports = ');
119
- const mod = { exports: {} as Record<string, unknown> };
120
- const fakeRequire = () => ({});
121
- const fn = new Function('module', 'exports', 'require', cleaned);
122
- fn(mod, mod.exports, fakeRequire);
123
- const cfg = mod.exports as {
124
- theme?: Record<string, unknown> & { extend?: Record<string, unknown> };
125
- };
126
- if (cfg?.theme && typeof cfg.theme === 'object') {
127
- const baseTheme = { ...cfg.theme };
128
- const extendTheme =
129
- cfg.theme.extend && typeof cfg.theme.extend === 'object' ? cfg.theme.extend : {};
130
- delete baseTheme.extend;
131
- const merged: Record<string, unknown> = { ...baseTheme };
132
- for (const [k, v] of Object.entries(extendTheme)) {
133
- if (v && typeof v === 'object' && !Array.isArray(v)) {
134
- merged[k] = {
135
- ...((baseTheme[k] as Record<string, unknown>) ?? {}),
136
- ...(v as Record<string, unknown>),
137
- };
138
- } else {
139
- merged[k] = v;
140
- }
141
- }
142
- return merged;
226
+ const parsedTheme = parseTailwindConfigSource(raw);
227
+ if (Object.keys(parsedTheme).length > 0) {
228
+ return parsedTheme;
143
229
  }
144
230
  } catch {
145
- // Ignore tailwind config parse errors
231
+ // Ignore unreadable tailwind config files
146
232
  }
147
233
  }
148
234
  }
@@ -175,53 +261,29 @@ export default defineConfig({
175
261
  transformIndexHtml(html) {
176
262
  const serializedEnv = JSON.stringify(publicProjectEnv);
177
263
  const serializedTailwindTheme = JSON.stringify(projectTailwindTheme);
178
- const envScript = `<script>
179
- (function() {
180
- window.__ZELUNE_TAILWIND_THEME__ = ${serializedTailwindTheme};
181
- var baseEnv = ${serializedEnv};
182
- var envProxy = new Proxy(baseEnv, {
183
- get: function(target, prop) {
184
- if (typeof prop !== 'string') return target[prop];
185
- if (prop in target && target[prop] !== undefined && target[prop] !== '') {
186
- return target[prop];
187
- }
188
- var upper = prop.toUpperCase();
189
- if (upper.includes('URL') || upper.includes('ENDPOINT') || upper.includes('HOST')) {
190
- return 'https://placeholder.example.com';
191
- }
192
- if (upper.includes('KEY') || upper.includes('TOKEN') || upper.includes('ID') || upper.includes('SECRET')) {
193
- return 'placeholder-public-token';
194
- }
195
- return '';
196
- }
197
- });
198
- function createRequireProxy() {
199
- var fn = function() { return proxy; };
200
- var proxy = new Proxy(fn, {
201
- get: function(_t, prop) {
202
- if (prop === Symbol.toPrimitive) return function(hint) { return hint === 'number' ? 0 : ''; };
203
- if (prop === 'valueOf') return function() { return 0; };
204
- if (prop === 'toString') return function() { return ''; };
205
- if (prop === 'uri' || prop === 'default') return '';
206
- return proxy;
207
- }
208
- });
209
- return proxy;
210
- }
211
- window.global = window;
212
- window.__DEV__ = true;
213
- window.process = window.process || { env: envProxy, platform: 'browser', version: '', cwd: function() { return '/'; } };
214
- window.process.env = envProxy;
215
- window.require = window.require || function(modId) {
216
- if (typeof modId === 'string' && /\\.(png|jpe?g|gif|webp|svg|avif|bmp)$/i.test(modId)) {
217
- return { uri: modId, default: modId };
218
- }
219
- return createRequireProxy();
220
- };
221
- })();
222
- </script>`;
264
+ const envScript = `<script>window.__ZELUNE_TAILWIND_THEME__ = ${serializedTailwindTheme}; window.__ZELUNE_PUBLIC_ENV__ = ${serializedEnv};</script>`;
223
265
  return html.replace('<head>', `<head>\n ${envScript}`);
224
266
  },
267
+ async closeBundle() {
268
+ await build({
269
+ configFile: false,
270
+ css: { postcss: {} },
271
+ logLevel: 'silent',
272
+ build: {
273
+ ssr: path.resolve(__dirname, 'mcp/server.ts'),
274
+ outDir: path.resolve(__dirname, 'mcp'),
275
+ emptyOutDir: false,
276
+ minify: false,
277
+ rollupOptions: {
278
+ external: [/^node:/, /^@babel\//],
279
+ output: {
280
+ format: 'es',
281
+ entryFileNames: 'server.mjs',
282
+ },
283
+ },
284
+ },
285
+ });
286
+ },
225
287
  },
226
288
  viteReactNativeFallbackPlugin(targetProjectRoot, mockNavShimPath, workspaceRoot),
227
289
  viteVirtualScreensPlugin(targetProjectRoot),