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/CHANGELOG.md +27 -0
- package/bin/cli.mjs +22 -42
- package/mcp/server.mjs +2228 -0
- package/mcp/server.ts +367 -117
- package/package.json +3 -5
- package/plugin.json +1 -1
- package/plugins/viteReactNativeFallbackPlugin.ts +2 -11
- package/src/canvas/CanvasApp.tsx +174 -3
- package/src/canvas/FlowArrowsLayer.tsx +84 -45
- package/src/canvas/InspectorPanel.tsx +529 -23
- package/src/runtime/MockNavigationContext.tsx +59 -0
- package/src/server/apiMiddleware.ts +48 -0
- package/src/server/astEngine.ts +1291 -33
- package/vite.config.ts +177 -115
package/vite.config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
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
|
|
38
|
-
return path.dirname(
|
|
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
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
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),
|