zelune 1.5.6 → 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 +34 -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 +107 -1
- package/src/runtime/nativewindShim.ts +25 -0
- package/src/server/apiMiddleware.ts +48 -0
- package/src/server/astEngine.ts +1291 -33
- package/vite.config.ts +177 -112
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,36 +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
|
-
const mod = { exports: {} as Record<string, unknown> };
|
|
117
|
-
const fakeRequire = () => ({});
|
|
118
|
-
const fn = new Function('module', 'exports', 'require', cleaned);
|
|
119
|
-
fn(mod, mod.exports, fakeRequire);
|
|
120
|
-
const cfg = mod.exports as {
|
|
121
|
-
theme?: Record<string, unknown> & { extend?: Record<string, unknown> };
|
|
122
|
-
};
|
|
123
|
-
if (cfg?.theme && typeof cfg.theme === 'object') {
|
|
124
|
-
const baseTheme = { ...cfg.theme };
|
|
125
|
-
const extendTheme =
|
|
126
|
-
cfg.theme.extend && typeof cfg.theme.extend === 'object' ? cfg.theme.extend : {};
|
|
127
|
-
delete baseTheme.extend;
|
|
128
|
-
const merged: Record<string, unknown> = { ...baseTheme };
|
|
129
|
-
for (const [k, v] of Object.entries(extendTheme)) {
|
|
130
|
-
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
131
|
-
merged[k] = {
|
|
132
|
-
...((baseTheme[k] as Record<string, unknown>) ?? {}),
|
|
133
|
-
...(v as Record<string, unknown>),
|
|
134
|
-
};
|
|
135
|
-
} else {
|
|
136
|
-
merged[k] = v;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return merged;
|
|
226
|
+
const parsedTheme = parseTailwindConfigSource(raw);
|
|
227
|
+
if (Object.keys(parsedTheme).length > 0) {
|
|
228
|
+
return parsedTheme;
|
|
140
229
|
}
|
|
141
230
|
} catch {
|
|
142
|
-
// Ignore tailwind config
|
|
231
|
+
// Ignore unreadable tailwind config files
|
|
143
232
|
}
|
|
144
233
|
}
|
|
145
234
|
}
|
|
@@ -172,53 +261,29 @@ export default defineConfig({
|
|
|
172
261
|
transformIndexHtml(html) {
|
|
173
262
|
const serializedEnv = JSON.stringify(publicProjectEnv);
|
|
174
263
|
const serializedTailwindTheme = JSON.stringify(projectTailwindTheme);
|
|
175
|
-
const envScript = `<script>
|
|
176
|
-
(function() {
|
|
177
|
-
window.__ZELUNE_TAILWIND_THEME__ = ${serializedTailwindTheme};
|
|
178
|
-
var baseEnv = ${serializedEnv};
|
|
179
|
-
var envProxy = new Proxy(baseEnv, {
|
|
180
|
-
get: function(target, prop) {
|
|
181
|
-
if (typeof prop !== 'string') return target[prop];
|
|
182
|
-
if (prop in target && target[prop] !== undefined && target[prop] !== '') {
|
|
183
|
-
return target[prop];
|
|
184
|
-
}
|
|
185
|
-
var upper = prop.toUpperCase();
|
|
186
|
-
if (upper.includes('URL') || upper.includes('ENDPOINT') || upper.includes('HOST')) {
|
|
187
|
-
return 'https://placeholder.example.com';
|
|
188
|
-
}
|
|
189
|
-
if (upper.includes('KEY') || upper.includes('TOKEN') || upper.includes('ID') || upper.includes('SECRET')) {
|
|
190
|
-
return 'placeholder-public-token';
|
|
191
|
-
}
|
|
192
|
-
return '';
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
function createRequireProxy() {
|
|
196
|
-
var fn = function() { return proxy; };
|
|
197
|
-
var proxy = new Proxy(fn, {
|
|
198
|
-
get: function(_t, prop) {
|
|
199
|
-
if (prop === Symbol.toPrimitive) return function(hint) { return hint === 'number' ? 0 : ''; };
|
|
200
|
-
if (prop === 'valueOf') return function() { return 0; };
|
|
201
|
-
if (prop === 'toString') return function() { return ''; };
|
|
202
|
-
if (prop === 'uri' || prop === 'default') return '';
|
|
203
|
-
return proxy;
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
return proxy;
|
|
207
|
-
}
|
|
208
|
-
window.global = window;
|
|
209
|
-
window.__DEV__ = true;
|
|
210
|
-
window.process = window.process || { env: envProxy, platform: 'browser', version: '', cwd: function() { return '/'; } };
|
|
211
|
-
window.process.env = envProxy;
|
|
212
|
-
window.require = window.require || function(modId) {
|
|
213
|
-
if (typeof modId === 'string' && /\\.(png|jpe?g|gif|webp|svg|avif|bmp)$/i.test(modId)) {
|
|
214
|
-
return { uri: modId, default: modId };
|
|
215
|
-
}
|
|
216
|
-
return createRequireProxy();
|
|
217
|
-
};
|
|
218
|
-
})();
|
|
219
|
-
</script>`;
|
|
264
|
+
const envScript = `<script>window.__ZELUNE_TAILWIND_THEME__ = ${serializedTailwindTheme}; window.__ZELUNE_PUBLIC_ENV__ = ${serializedEnv};</script>`;
|
|
220
265
|
return html.replace('<head>', `<head>\n ${envScript}`);
|
|
221
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
|
+
},
|
|
222
287
|
},
|
|
223
288
|
viteReactNativeFallbackPlugin(targetProjectRoot, mockNavShimPath, workspaceRoot),
|
|
224
289
|
viteVirtualScreensPlugin(targetProjectRoot),
|