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
|
@@ -11,7 +11,66 @@ import {
|
|
|
11
11
|
ScrollView as RNScrollView,
|
|
12
12
|
FlatList as RNFlatList,
|
|
13
13
|
} from 'react-native';
|
|
14
|
-
import { withNativeWind } from './nativewindShim';
|
|
14
|
+
import { withNativeWind, resolveTailwindClassNameToStyle } from './nativewindShim';
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
interface Window {
|
|
18
|
+
__ZELUNE_PUBLIC_ENV__?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof window !== 'undefined') {
|
|
23
|
+
const baseEnv: Record<string, string> = window.__ZELUNE_PUBLIC_ENV__ ?? {
|
|
24
|
+
NODE_ENV: 'development',
|
|
25
|
+
EXPO_PUBLIC_PLATFORM: 'web',
|
|
26
|
+
};
|
|
27
|
+
const envProxy = new Proxy(baseEnv, {
|
|
28
|
+
get(target, prop) {
|
|
29
|
+
if (typeof prop !== 'string') return Reflect.get(target, prop);
|
|
30
|
+
if (prop in target && target[prop] !== undefined && target[prop] !== '') {
|
|
31
|
+
return target[prop];
|
|
32
|
+
}
|
|
33
|
+
return 'zelune-preview';
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const createAssetStub = (): unknown => {
|
|
38
|
+
const fn = () => proxy;
|
|
39
|
+
const proxy: unknown = new Proxy(fn, {
|
|
40
|
+
get(_t, prop) {
|
|
41
|
+
if (prop === Symbol.toPrimitive) return (hint: string) => (hint === 'number' ? 0 : '');
|
|
42
|
+
if (prop === 'valueOf') return () => 0;
|
|
43
|
+
if (prop === 'toString') return () => '';
|
|
44
|
+
if (prop === 'uri' || prop === 'default') return '';
|
|
45
|
+
return proxy;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return proxy;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const win = window as unknown as Record<string, unknown>;
|
|
52
|
+
win.global = window;
|
|
53
|
+
win.__DEV__ = true;
|
|
54
|
+
if (!win.process || typeof win.process !== 'object') {
|
|
55
|
+
win.process = {
|
|
56
|
+
env: envProxy,
|
|
57
|
+
platform: 'browser',
|
|
58
|
+
version: '',
|
|
59
|
+
cwd: () => '/',
|
|
60
|
+
};
|
|
61
|
+
} else {
|
|
62
|
+
(win.process as Record<string, unknown>).env = envProxy;
|
|
63
|
+
}
|
|
64
|
+
const reqProp = ['req', 'uire'].join('');
|
|
65
|
+
if (typeof win[reqProp] !== 'function') {
|
|
66
|
+
win[reqProp] = (modId: unknown) => {
|
|
67
|
+
if (typeof modId === 'string' && /\.(png|jpe?g|gif|webp|svg|avif|bmp)$/i.test(modId)) {
|
|
68
|
+
return { uri: modId, default: modId };
|
|
69
|
+
}
|
|
70
|
+
return createAssetStub();
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
15
74
|
|
|
16
75
|
export interface NavigationController {
|
|
17
76
|
navigate: (targetRoute: string, params?: Record<string, unknown>) => void;
|
|
@@ -1221,6 +1280,53 @@ export const clsx = (...args: unknown[]) =>
|
|
|
1221
1280
|
export const cn = clsx;
|
|
1222
1281
|
export const twMerge = clsx;
|
|
1223
1282
|
|
|
1283
|
+
// NativeWind (v2 / v4) & twrnc Universal Shims
|
|
1284
|
+
export function styled<P extends Record<string, unknown>>(
|
|
1285
|
+
Component: React.ComponentType<P>,
|
|
1286
|
+
baseClassName?: string
|
|
1287
|
+
): React.ComponentType<P> {
|
|
1288
|
+
const Wrapped = withNativeWind(Component as unknown as React.ComponentType<Record<string, unknown>>);
|
|
1289
|
+
if (!baseClassName) return Wrapped as unknown as React.ComponentType<P>;
|
|
1290
|
+
return React.forwardRef<unknown, P>((props, ref) => {
|
|
1291
|
+
const combinedClass = [baseClassName, props?.className].filter(Boolean).join(' ');
|
|
1292
|
+
return React.createElement(Wrapped, { ...props, className: combinedClass, ref });
|
|
1293
|
+
}) as unknown as React.ComponentType<P>;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
export function cssInterop<P extends Record<string, unknown>>(
|
|
1297
|
+
Component: React.ComponentType<P>
|
|
1298
|
+
): React.ComponentType<P> {
|
|
1299
|
+
return withNativeWind(Component as unknown as React.ComponentType<Record<string, unknown>>) as unknown as React.ComponentType<P>;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
export const remapProps = cssInterop;
|
|
1303
|
+
|
|
1304
|
+
function twFn(stringsOrClass: TemplateStringsArray | string, ...values: unknown[]): Record<string, unknown> {
|
|
1305
|
+
if (typeof stringsOrClass === 'string') {
|
|
1306
|
+
return resolveTailwindClassNameToStyle(stringsOrClass) ?? {};
|
|
1307
|
+
}
|
|
1308
|
+
if (Array.isArray(stringsOrClass)) {
|
|
1309
|
+
const built = stringsOrClass.reduce(
|
|
1310
|
+
(acc, str, idx) => acc + str + (values[idx] !== undefined ? String(values[idx]) : ''),
|
|
1311
|
+
''
|
|
1312
|
+
);
|
|
1313
|
+
return resolveTailwindClassNameToStyle(built) ?? {};
|
|
1314
|
+
}
|
|
1315
|
+
return {};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
twFn.style = (...args: unknown[]) => {
|
|
1319
|
+
const mergedClass = args.filter((a): a is string => typeof a === 'string' && a.length > 0).join(' ');
|
|
1320
|
+
return resolveTailwindClassNameToStyle(mergedClass) ?? {};
|
|
1321
|
+
};
|
|
1322
|
+
twFn.color = (token: string) => {
|
|
1323
|
+
const s = resolveTailwindClassNameToStyle(`text-${token}`);
|
|
1324
|
+
return (s?.color as string | undefined) ?? '#0F172A';
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
export const tw = twFn;
|
|
1328
|
+
export const createTw = () => twFn;
|
|
1329
|
+
|
|
1224
1330
|
// React Hook Form Shims (react-hook-form)
|
|
1225
1331
|
export function useForm(config?: { defaultValues?: Record<string, unknown> }) {
|
|
1226
1332
|
const defaults = config?.defaultValues ?? {};
|
|
@@ -376,6 +376,7 @@ export function resolveTailwindClassNameToStyle(className?: string): Record<stri
|
|
|
376
376
|
(typeof window !== 'undefined' && window.__ZELUNE_TAILWIND_THEME__) || {};
|
|
377
377
|
const themeColors = (projectTheme.colors ?? {}) as Record<string, unknown>;
|
|
378
378
|
const themeFontSizes = (projectTheme.fontSize ?? {}) as Record<string, unknown>;
|
|
379
|
+
const themeFontFamilies = (projectTheme.fontFamily ?? {}) as Record<string, unknown>;
|
|
379
380
|
const themeRadii = (projectTheme.borderRadius ?? {}) as Record<string, unknown>;
|
|
380
381
|
const themeSpacing = (projectTheme.spacing ?? {}) as Record<string, unknown>;
|
|
381
382
|
|
|
@@ -808,6 +809,30 @@ export function resolveTailwindClassNameToStyle(className?: string): Record<stri
|
|
|
808
809
|
style.fontFamily = 'monospace';
|
|
809
810
|
continue;
|
|
810
811
|
}
|
|
812
|
+
if (token.startsWith('font-')) {
|
|
813
|
+
const fontKey = token.slice(5);
|
|
814
|
+
const customFontEntry = themeFontFamilies[fontKey];
|
|
815
|
+
if (customFontEntry !== undefined) {
|
|
816
|
+
const primaryFont = Array.isArray(customFontEntry)
|
|
817
|
+
? String(customFontEntry[0] ?? '')
|
|
818
|
+
: String(customFontEntry ?? '');
|
|
819
|
+
const lowerFont = primaryFont.toLowerCase();
|
|
820
|
+
if (lowerFont.includes('800') || lowerFont.includes('extrabold') || lowerFont.includes('black')) {
|
|
821
|
+
style.fontWeight = '800';
|
|
822
|
+
} else if (lowerFont.includes('700') || lowerFont.includes('bold')) {
|
|
823
|
+
style.fontWeight = lowerFont.includes('semibold') ? '600' : '700';
|
|
824
|
+
} else if (lowerFont.includes('600') || lowerFont.includes('semibold')) {
|
|
825
|
+
style.fontWeight = '600';
|
|
826
|
+
} else if (lowerFont.includes('500') || lowerFont.includes('medium')) {
|
|
827
|
+
style.fontWeight = '500';
|
|
828
|
+
} else if (lowerFont.includes('300') || lowerFont.includes('light')) {
|
|
829
|
+
style.fontWeight = '300';
|
|
830
|
+
} else {
|
|
831
|
+
style.fontWeight = '400';
|
|
832
|
+
}
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
811
836
|
|
|
812
837
|
// Letter spacing (tracking-*)
|
|
813
838
|
if (token === 'tracking-tighter') {
|
|
@@ -8,11 +8,17 @@ import {
|
|
|
8
8
|
inspectElement,
|
|
9
9
|
patchElement,
|
|
10
10
|
reorderElement,
|
|
11
|
+
insertElement,
|
|
12
|
+
duplicateElement,
|
|
13
|
+
deleteElement,
|
|
11
14
|
undoLastPatch,
|
|
12
15
|
redoLastPatch,
|
|
13
16
|
getHistoryStatus,
|
|
14
17
|
type PatchPayload,
|
|
15
18
|
type ReorderPayload,
|
|
19
|
+
type InsertElementPayload,
|
|
20
|
+
type DuplicateElementPayload,
|
|
21
|
+
type DeleteElementPayload,
|
|
16
22
|
} from './astEngine';
|
|
17
23
|
|
|
18
24
|
const MAX_BODY_BYTES = 1_000_000;
|
|
@@ -285,6 +291,48 @@ export function viteCanvasApiMiddleware(workspaceRoot: string): Plugin {
|
|
|
285
291
|
});
|
|
286
292
|
}
|
|
287
293
|
|
|
294
|
+
// POST /api/insert
|
|
295
|
+
if (req.method === 'POST' && pathname === '/api/insert') {
|
|
296
|
+
const body = await readJsonBody<InsertElementPayload>(req);
|
|
297
|
+
const result = await insertElement(workspaceRoot, body);
|
|
298
|
+
return sendJson(res, 200, {
|
|
299
|
+
ok: true,
|
|
300
|
+
element: result.element,
|
|
301
|
+
history: {
|
|
302
|
+
undoAvailable: result.undoAvailable,
|
|
303
|
+
redoAvailable: result.redoAvailable,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// POST /api/duplicate
|
|
309
|
+
if (req.method === 'POST' && pathname === '/api/duplicate') {
|
|
310
|
+
const body = await readJsonBody<DuplicateElementPayload>(req);
|
|
311
|
+
const result = await duplicateElement(workspaceRoot, body);
|
|
312
|
+
return sendJson(res, 200, {
|
|
313
|
+
ok: true,
|
|
314
|
+
element: result.element,
|
|
315
|
+
history: {
|
|
316
|
+
undoAvailable: result.undoAvailable,
|
|
317
|
+
redoAvailable: result.redoAvailable,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// POST /api/delete
|
|
323
|
+
if (req.method === 'POST' && pathname === '/api/delete') {
|
|
324
|
+
const body = await readJsonBody<DeleteElementPayload>(req);
|
|
325
|
+
const result = await deleteElement(workspaceRoot, body);
|
|
326
|
+
return sendJson(res, 200, {
|
|
327
|
+
ok: true,
|
|
328
|
+
deletedAstPath: result.deletedAstPath,
|
|
329
|
+
history: {
|
|
330
|
+
undoAvailable: result.undoAvailable,
|
|
331
|
+
redoAvailable: result.redoAvailable,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
288
336
|
// POST /api/undo
|
|
289
337
|
if (req.method === 'POST' && pathname === '/api/undo') {
|
|
290
338
|
const result = await undoLastPatch(workspaceRoot);
|