@aex.is/zero 0.1.3 → 0.1.5
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/dist/cli/prompts.js +163 -0
- package/dist/config/frameworks.js +2 -0
- package/dist/config/modules.js +56 -5
- package/dist/engine/scaffold.js +98 -96
- package/dist/engine/templates.js +603 -39
- package/dist/index.js +2 -9
- package/package.json +4 -9
- package/dist/ui/App.js +0 -75
- package/dist/ui/components/SelectList.js +0 -67
- package/dist/ui/screens/Confirm.js +0 -22
- package/dist/ui/screens/DomainPrompt.js +0 -11
- package/dist/ui/screens/FrameworkSelect.js +0 -12
- package/dist/ui/screens/Intro.js +0 -18
- package/dist/ui/screens/ModuleSelect.js +0 -12
- package/dist/ui/screens/NamePrompt.js +0 -18
package/dist/engine/templates.js
CHANGED
|
@@ -1,7 +1,125 @@
|
|
|
1
|
-
export
|
|
1
|
+
export function componentsJsonTemplate(globalsPath, tailwindConfigPath) {
|
|
2
|
+
return `{
|
|
3
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
4
|
+
"style": "default",
|
|
5
|
+
"rsc": true,
|
|
6
|
+
"tsx": true,
|
|
7
|
+
"tailwind": {
|
|
8
|
+
"config": "${tailwindConfigPath}",
|
|
9
|
+
"css": "${globalsPath}",
|
|
10
|
+
"baseColor": "slate",
|
|
11
|
+
"cssVariables": true
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
export function buildNextTemplateFiles(data) {
|
|
21
|
+
const base = data.basePath ? `${data.basePath}/` : '';
|
|
22
|
+
const envList = renderNextEnvList(data.envVars);
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
path: `${base}app/layout.tsx`,
|
|
26
|
+
content: nextLayoutTemplate(data.appName, data.domain)
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: `${base}app/page.tsx`,
|
|
30
|
+
content: nextHomeTemplate(data.appName, data.domain, envList)
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
path: `${base}app/about/page.tsx`,
|
|
34
|
+
content: nextRouteTemplate('About', 'A concise overview of your project.')
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: `${base}app/guide/page.tsx`,
|
|
38
|
+
content: nextRouteTemplate('Guide', 'Three routes are ready. Customize and ship.')
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: `${base}app/globals.css`,
|
|
42
|
+
content: nextGlobalsCss()
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
path: `${base}components/site-header.tsx`,
|
|
46
|
+
content: nextHeaderTemplate(data.appName)
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: `${base}components/site-footer.tsx`,
|
|
50
|
+
content: nextFooterTemplate(data.domain)
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
path: `${base}components/env-list.tsx`,
|
|
54
|
+
content: nextEnvListTemplate(envList)
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
path: `${base}lib/utils.ts`,
|
|
58
|
+
content: nextUtilsTemplate()
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
export function buildExpoTemplateFiles(data) {
|
|
63
|
+
const envItems = renderExpoEnvItems(data.envVars);
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
path: 'app/_layout.tsx',
|
|
67
|
+
content: expoLayoutTemplate()
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
path: 'app/index.tsx',
|
|
71
|
+
content: expoHomeTemplate(data.appName, data.domain, envItems)
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: 'app/about.tsx',
|
|
75
|
+
content: expoRouteTemplate('About', 'A concise overview of your project.')
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
path: 'app/guide.tsx',
|
|
79
|
+
content: expoRouteTemplate('Guide', 'Three routes are ready. Customize and ship.')
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
path: 'components/theme.ts',
|
|
83
|
+
content: expoThemeTemplate()
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: 'components/site-header.tsx',
|
|
87
|
+
content: expoHeaderTemplate(data.appName)
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
path: 'components/site-footer.tsx',
|
|
91
|
+
content: expoFooterTemplate(data.domain)
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
path: 'components/env-list.tsx',
|
|
95
|
+
content: expoEnvListTemplate(envItems)
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
path: 'components/page-shell.tsx',
|
|
99
|
+
content: expoPageShellTemplate()
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
path: 'tamagui.config.ts',
|
|
103
|
+
content: tamaguiConfigTemplate()
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
path: 'metro.config.js',
|
|
107
|
+
content: metroConfigTemplate()
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
path: 'babel.config.js',
|
|
111
|
+
content: babelConfigTemplate()
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
function nextLayoutTemplate(appName, domain) {
|
|
116
|
+
return `import type { ReactNode } from 'react';
|
|
117
|
+
import './globals.css';
|
|
118
|
+
import { SiteHeader } from '@/components/site-header';
|
|
119
|
+
import { SiteFooter } from '@/components/site-footer';
|
|
2
120
|
|
|
3
121
|
export const metadata = {
|
|
4
|
-
title: '
|
|
122
|
+
title: '${escapeTemplate(appName)}',
|
|
5
123
|
description: 'Scaffolded by Aexis Zero.'
|
|
6
124
|
};
|
|
7
125
|
|
|
@@ -12,74 +130,520 @@ export default function RootLayout({
|
|
|
12
130
|
}) {
|
|
13
131
|
return (
|
|
14
132
|
<html lang="en">
|
|
15
|
-
<body>
|
|
133
|
+
<body className="min-h-screen bg-[var(--bg)] text-[var(--fg)]">
|
|
134
|
+
<div className="flex min-h-screen flex-col">
|
|
135
|
+
<SiteHeader appName="${escapeTemplate(appName)}" />
|
|
136
|
+
<main className="flex-1">{children}</main>
|
|
137
|
+
<SiteFooter domain="${escapeTemplate(domain)}" />
|
|
138
|
+
</div>
|
|
139
|
+
</body>
|
|
16
140
|
</html>
|
|
17
141
|
);
|
|
18
142
|
}
|
|
19
143
|
`;
|
|
20
|
-
|
|
21
|
-
|
|
144
|
+
}
|
|
145
|
+
function nextHomeTemplate(appName, domain, envList) {
|
|
146
|
+
return `import { EnvList } from '@/components/env-list';
|
|
147
|
+
|
|
148
|
+
export default function Home() {
|
|
149
|
+
return (
|
|
150
|
+
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8 px-6 py-12">
|
|
151
|
+
<div className="flex flex-col gap-3">
|
|
152
|
+
<p className="text-xs uppercase tracking-[0.4em]">Hello World</p>
|
|
153
|
+
<h1 className="text-3xl font-thin">${escapeTemplate(appName)}</h1>
|
|
154
|
+
<p className="text-sm">
|
|
155
|
+
${escapeTemplate(domain) ? `Domain: ${escapeTemplate(domain)}` : 'No domain configured yet.'}
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
<div className="rounded-xl border border-[var(--fg)] p-6">
|
|
159
|
+
<h2 className="text-lg font-medium">Environment variables</h2>
|
|
160
|
+
<p className="text-sm">Set these in your <code className="rounded bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">.env</code>.</p>
|
|
161
|
+
<EnvList />
|
|
162
|
+
</div>
|
|
163
|
+
<div className="rounded-xl border border-[var(--fg)] p-6">
|
|
164
|
+
<h2 className="text-lg font-medium">Routes</h2>
|
|
165
|
+
<p className="text-sm">Explore <code className="rounded bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/about</code> and <code className="rounded bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/guide</code>.</p>
|
|
166
|
+
</div>
|
|
167
|
+
</section>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
function nextRouteTemplate(title, body) {
|
|
173
|
+
return `export default function Page() {
|
|
174
|
+
return (
|
|
175
|
+
<section className="mx-auto flex w-full max-w-3xl flex-col gap-4 px-6 py-12">
|
|
176
|
+
<h1 className="text-3xl font-thin">${title}</h1>
|
|
177
|
+
<p className="text-sm">${body}</p>
|
|
178
|
+
</section>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
function nextHeaderTemplate(appName) {
|
|
184
|
+
return `import Link from 'next/link';
|
|
185
|
+
|
|
186
|
+
const links = [
|
|
187
|
+
{ href: '/', label: 'Home' },
|
|
188
|
+
{ href: '/about', label: 'About' },
|
|
189
|
+
{ href: '/guide', label: 'Guide' }
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
export function SiteHeader({ appName }: { appName: string }) {
|
|
193
|
+
return (
|
|
194
|
+
<header className="border-b border-[var(--fg)]">
|
|
195
|
+
<div className="mx-auto flex w-full max-w-4xl items-center justify-between px-6 py-4">
|
|
196
|
+
<div className="flex items-baseline gap-3">
|
|
197
|
+
<span className="text-xl font-thin tracking-[0.3em]">ZER0</span>
|
|
198
|
+
<span className="text-xs uppercase tracking-[0.2em]">{appName}</span>
|
|
199
|
+
</div>
|
|
200
|
+
<nav className="hidden items-center gap-6 text-sm sm:flex">
|
|
201
|
+
{links.map((link) => (
|
|
202
|
+
<Link key={link.href} href={link.href} className="underline-offset-4 hover:underline">
|
|
203
|
+
{link.label}
|
|
204
|
+
</Link>
|
|
205
|
+
))}
|
|
206
|
+
</nav>
|
|
207
|
+
<details className="sm:hidden">
|
|
208
|
+
<summary className="cursor-pointer text-sm">Menu</summary>
|
|
209
|
+
<div className="mt-3 flex flex-col gap-3 text-sm">
|
|
210
|
+
{links.map((link) => (
|
|
211
|
+
<Link key={link.href} href={link.href} className="underline-offset-4 hover:underline">
|
|
212
|
+
{link.label}
|
|
213
|
+
</Link>
|
|
214
|
+
))}
|
|
215
|
+
</div>
|
|
216
|
+
</details>
|
|
217
|
+
</div>
|
|
218
|
+
</header>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
function nextFooterTemplate(domain) {
|
|
224
|
+
const domainLabel = escapeTemplate(domain).trim().length > 0
|
|
225
|
+
? `Domain: ${escapeTemplate(domain)}`
|
|
226
|
+
: 'Domain: not set';
|
|
227
|
+
return `export function SiteFooter({ domain }: { domain?: string }) {
|
|
228
|
+
return (
|
|
229
|
+
<footer className=\"border-t border-[var(--fg)]\">
|
|
230
|
+
<div className=\"mx-auto flex w-full max-w-4xl flex-col gap-2 px-6 py-4 text-xs\">
|
|
231
|
+
<span>${domainLabel}</span>
|
|
232
|
+
<span>Generated by Aexis Zero.</span>
|
|
233
|
+
</div>
|
|
234
|
+
</footer>
|
|
235
|
+
);
|
|
22
236
|
}
|
|
23
237
|
`;
|
|
24
|
-
|
|
238
|
+
}
|
|
239
|
+
function nextEnvListTemplate(envList) {
|
|
240
|
+
return `export function EnvList() {
|
|
241
|
+
return (
|
|
242
|
+
<div className="mt-4 flex flex-col gap-4">
|
|
243
|
+
${envList}
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
`;
|
|
248
|
+
}
|
|
249
|
+
function nextUtilsTemplate() {
|
|
250
|
+
return `import { clsx, type ClassValue } from 'clsx';
|
|
25
251
|
import { twMerge } from 'tailwind-merge';
|
|
26
252
|
|
|
27
253
|
export function cn(...inputs: ClassValue[]) {
|
|
28
254
|
return twMerge(clsx(inputs));
|
|
29
255
|
}
|
|
30
256
|
`;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
257
|
+
}
|
|
258
|
+
function nextGlobalsCss() {
|
|
259
|
+
return `@import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
|
260
|
+
|
|
261
|
+
@tailwind base;
|
|
262
|
+
@tailwind components;
|
|
263
|
+
@tailwind utilities;
|
|
264
|
+
|
|
265
|
+
:root {
|
|
266
|
+
--bg: #E7E5E4;
|
|
267
|
+
--fg: #1C1917;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@media (prefers-color-scheme: dark) {
|
|
271
|
+
:root {
|
|
272
|
+
--bg: #1C1917;
|
|
273
|
+
--fg: #E7E5E4;
|
|
45
274
|
}
|
|
46
275
|
}
|
|
47
|
-
`;
|
|
48
|
-
export const tamaguiConfigTemplate = `import { config } from '@tamagui/config/v3';
|
|
49
276
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const { withTamagui } = require('@tamagui/metro-plugin');
|
|
277
|
+
* {
|
|
278
|
+
border-color: var(--fg);
|
|
279
|
+
}
|
|
54
280
|
|
|
55
|
-
|
|
281
|
+
html,
|
|
282
|
+
body {
|
|
283
|
+
min-height: 100%;
|
|
284
|
+
background: var(--bg);
|
|
285
|
+
color: var(--fg);
|
|
286
|
+
font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
287
|
+
}
|
|
56
288
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
289
|
+
a {
|
|
290
|
+
color: inherit;
|
|
291
|
+
text-decoration: underline;
|
|
292
|
+
text-underline-offset: 4px;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
code {
|
|
296
|
+
font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
297
|
+
}
|
|
62
298
|
`;
|
|
63
|
-
|
|
64
|
-
|
|
299
|
+
}
|
|
300
|
+
function renderNextEnvList(envVars) {
|
|
301
|
+
if (envVars.length === 0) {
|
|
302
|
+
return '<p className="text-sm">No environment variables required.</p>';
|
|
303
|
+
}
|
|
304
|
+
return envVars
|
|
305
|
+
.map((item) => {
|
|
306
|
+
return `
|
|
307
|
+
<div className="rounded-lg border border-[var(--fg)] p-4">
|
|
308
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
309
|
+
<code className="rounded bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">${escapeTemplate(item.key)}</code>
|
|
310
|
+
<span className="text-sm">${escapeTemplate(item.description)}</span>
|
|
311
|
+
</div>
|
|
312
|
+
<a
|
|
313
|
+
className="mt-2 inline-flex text-sm underline underline-offset-4"
|
|
314
|
+
href="${escapeAttribute(item.url)}"
|
|
315
|
+
target="_blank"
|
|
316
|
+
rel="noreferrer"
|
|
317
|
+
>
|
|
318
|
+
Get keys →
|
|
319
|
+
</a>
|
|
320
|
+
</div>`;
|
|
321
|
+
})
|
|
322
|
+
.join('\n');
|
|
323
|
+
}
|
|
324
|
+
function expoLayoutTemplate() {
|
|
325
|
+
return `import { Stack } from 'expo-router';
|
|
326
|
+
import { TamaguiProvider, Theme } from 'tamagui';
|
|
327
|
+
import { useColorScheme } from 'react-native';
|
|
328
|
+
import { useFonts, GeistMono_100Thin } from '@expo-google-fonts/geist-mono';
|
|
65
329
|
import config from '../tamagui.config';
|
|
66
330
|
|
|
67
331
|
export default function RootLayout() {
|
|
332
|
+
const scheme = useColorScheme();
|
|
333
|
+
const [loaded] = useFonts({ GeistMono_100Thin });
|
|
334
|
+
|
|
335
|
+
if (!loaded) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
|
|
68
339
|
return (
|
|
69
340
|
<TamaguiProvider config={config}>
|
|
70
|
-
<
|
|
341
|
+
<Theme name={scheme === 'dark' ? 'dark' : 'light'}>
|
|
342
|
+
<Stack screenOptions={{ headerShown: false }} />
|
|
343
|
+
</Theme>
|
|
71
344
|
</TamaguiProvider>
|
|
72
345
|
);
|
|
73
346
|
}
|
|
74
347
|
`;
|
|
75
|
-
|
|
348
|
+
}
|
|
349
|
+
function expoHomeTemplate(appName, domain, envItems) {
|
|
350
|
+
return `import { Text, YStack } from 'tamagui';
|
|
351
|
+
import { PageShell } from '../components/page-shell';
|
|
352
|
+
import { EnvList } from '../components/env-list';
|
|
353
|
+
import { FONT_FAMILY, useThemeColors } from '../components/theme';
|
|
76
354
|
|
|
77
355
|
export default function Home() {
|
|
356
|
+
const { fg } = useThemeColors();
|
|
357
|
+
|
|
78
358
|
return (
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
359
|
+
<PageShell
|
|
360
|
+
title="${escapeTemplate(appName)}"
|
|
361
|
+
subtitle="${escapeTemplate(domain) ? `Domain: ${escapeTemplate(domain)}` : 'No domain configured yet.'}"
|
|
362
|
+
badge="Hello World"
|
|
363
|
+
>
|
|
364
|
+
<YStack borderWidth={1} borderColor={fg} padding="$4" borderRadius="$4" gap="$3">
|
|
365
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$4" color={fg}>
|
|
366
|
+
Environment variables
|
|
367
|
+
</Text>
|
|
368
|
+
<EnvList />
|
|
369
|
+
</YStack>
|
|
370
|
+
<YStack borderWidth={1} borderColor={fg} padding="$4" borderRadius="$4" gap="$3">
|
|
371
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$4" color={fg}>
|
|
372
|
+
Routes
|
|
373
|
+
</Text>
|
|
374
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$2" color={fg}>
|
|
375
|
+
Visit /about and /guide.
|
|
376
|
+
</Text>
|
|
377
|
+
</YStack>
|
|
378
|
+
</PageShell>
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
383
|
+
function expoRouteTemplate(title, body) {
|
|
384
|
+
return `import { PageShell } from '../components/page-shell';
|
|
385
|
+
|
|
386
|
+
export default function Page() {
|
|
387
|
+
return (
|
|
388
|
+
<PageShell title="${title}" subtitle="${body}">
|
|
389
|
+
<></>
|
|
390
|
+
</PageShell>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
function expoThemeTemplate() {
|
|
396
|
+
return `import { useColorScheme } from 'react-native';
|
|
397
|
+
|
|
398
|
+
export const COLORS = {
|
|
399
|
+
light: {
|
|
400
|
+
bg: '#E7E5E4',
|
|
401
|
+
fg: '#1C1917'
|
|
402
|
+
},
|
|
403
|
+
dark: {
|
|
404
|
+
bg: '#1C1917',
|
|
405
|
+
fg: '#E7E5E4'
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export const FONT_FAMILY = 'GeistMono_100Thin';
|
|
410
|
+
|
|
411
|
+
export function useThemeColors() {
|
|
412
|
+
const scheme = useColorScheme();
|
|
413
|
+
const mode = scheme === 'dark' ? 'dark' : 'light';
|
|
414
|
+
return {
|
|
415
|
+
mode,
|
|
416
|
+
bg: COLORS[mode].bg,
|
|
417
|
+
fg: COLORS[mode].fg
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
function expoHeaderTemplate(appName) {
|
|
423
|
+
return `import { useState } from 'react';
|
|
424
|
+
import { Link } from 'expo-router';
|
|
425
|
+
import { Button, Text, XStack, YStack } from 'tamagui';
|
|
426
|
+
import { FONT_FAMILY, useThemeColors } from './theme';
|
|
427
|
+
|
|
428
|
+
const links = [
|
|
429
|
+
{ href: '/', label: 'Home' },
|
|
430
|
+
{ href: '/about', label: 'About' },
|
|
431
|
+
{ href: '/guide', label: 'Guide' }
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
export function SiteHeader() {
|
|
435
|
+
const [open, setOpen] = useState(false);
|
|
436
|
+
const { bg, fg } = useThemeColors();
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<YStack backgroundColor={bg} paddingHorizontal="$5" paddingVertical="$4" borderBottomWidth={1} borderColor={fg}>
|
|
440
|
+
<XStack alignItems="center" justifyContent="space-between">
|
|
441
|
+
<XStack alignItems="center" gap="$3">
|
|
442
|
+
<Text fontFamily={FONT_FAMILY} fontWeight="100" fontSize="$6" color={fg}>
|
|
443
|
+
ZER0
|
|
444
|
+
</Text>
|
|
445
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$2" textTransform="uppercase" color={fg}>
|
|
446
|
+
${escapeTemplate(appName)}
|
|
447
|
+
</Text>
|
|
448
|
+
</XStack>
|
|
449
|
+
<Button
|
|
450
|
+
backgroundColor={fg}
|
|
451
|
+
color={bg}
|
|
452
|
+
fontFamily={FONT_FAMILY}
|
|
453
|
+
size="$2"
|
|
454
|
+
onPress={() => setOpen((prev) => !prev)}
|
|
455
|
+
>
|
|
456
|
+
Menu
|
|
457
|
+
</Button>
|
|
458
|
+
</XStack>
|
|
459
|
+
{open ? (
|
|
460
|
+
<YStack marginTop="$3" gap="$2">
|
|
461
|
+
{links.map((link) => (
|
|
462
|
+
<Link key={link.href} href={link.href} asChild>
|
|
463
|
+
<Text fontFamily={FONT_FAMILY} textDecorationLine="underline" color={fg}>
|
|
464
|
+
{link.label}
|
|
465
|
+
</Text>
|
|
466
|
+
</Link>
|
|
467
|
+
))}
|
|
468
|
+
</YStack>
|
|
469
|
+
) : null}
|
|
470
|
+
</YStack>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
`;
|
|
474
|
+
}
|
|
475
|
+
function expoFooterTemplate(domain) {
|
|
476
|
+
return `import { Text, YStack } from 'tamagui';
|
|
477
|
+
import { FONT_FAMILY, useThemeColors } from './theme';
|
|
478
|
+
|
|
479
|
+
export function SiteFooter() {
|
|
480
|
+
const { bg, fg } = useThemeColors();
|
|
481
|
+
|
|
482
|
+
return (
|
|
483
|
+
<YStack backgroundColor={bg} paddingHorizontal="$5" paddingVertical="$4" borderTopWidth={1} borderColor={fg}>
|
|
484
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$2" color={fg}>
|
|
485
|
+
${escapeTemplate(domain) ? `Domain: ${escapeTemplate(domain)}` : 'Domain: not set'}
|
|
486
|
+
</Text>
|
|
487
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$2" color={fg}>
|
|
488
|
+
Generated by Aexis Zero.
|
|
489
|
+
</Text>
|
|
82
490
|
</YStack>
|
|
83
491
|
);
|
|
84
492
|
}
|
|
85
493
|
`;
|
|
494
|
+
}
|
|
495
|
+
function expoEnvListTemplate(envItems) {
|
|
496
|
+
return `import { Linking } from 'react-native';
|
|
497
|
+
import { Text, YStack } from 'tamagui';
|
|
498
|
+
import { FONT_FAMILY, useThemeColors } from './theme';
|
|
499
|
+
|
|
500
|
+
const envItems = ${envItems};
|
|
501
|
+
|
|
502
|
+
export function EnvList() {
|
|
503
|
+
const { bg, fg } = useThemeColors();
|
|
504
|
+
|
|
505
|
+
if (envItems.length === 0) {
|
|
506
|
+
return (
|
|
507
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$2" color={fg}>
|
|
508
|
+
No environment variables required.
|
|
509
|
+
</Text>
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return (
|
|
514
|
+
<YStack gap="$3">
|
|
515
|
+
{envItems.map((item) => (
|
|
516
|
+
<YStack key={item.key} borderWidth={1} borderColor={fg} padding="$3" borderRadius="$4">
|
|
517
|
+
<Text fontFamily={FONT_FAMILY} color={fg}>{item.description}</Text>
|
|
518
|
+
<Text
|
|
519
|
+
fontFamily={FONT_FAMILY}
|
|
520
|
+
backgroundColor={fg}
|
|
521
|
+
color={bg}
|
|
522
|
+
paddingHorizontal="$2"
|
|
523
|
+
paddingVertical="$1"
|
|
524
|
+
borderRadius="$2"
|
|
525
|
+
marginTop="$2"
|
|
526
|
+
>
|
|
527
|
+
{item.key}
|
|
528
|
+
</Text>
|
|
529
|
+
<Text
|
|
530
|
+
fontFamily={FONT_FAMILY}
|
|
531
|
+
textDecorationLine="underline"
|
|
532
|
+
color={fg}
|
|
533
|
+
marginTop="$2"
|
|
534
|
+
onPress={() => Linking.openURL(item.url)}
|
|
535
|
+
>
|
|
536
|
+
Get keys →
|
|
537
|
+
</Text>
|
|
538
|
+
</YStack>
|
|
539
|
+
))}
|
|
540
|
+
</YStack>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
`;
|
|
544
|
+
}
|
|
545
|
+
function expoPageShellTemplate() {
|
|
546
|
+
return `import type { ReactNode } from 'react';
|
|
547
|
+
import { ScrollView, Text, YStack } from 'tamagui';
|
|
548
|
+
import { SiteHeader } from './site-header';
|
|
549
|
+
import { SiteFooter } from './site-footer';
|
|
550
|
+
import { FONT_FAMILY, useThemeColors } from './theme';
|
|
551
|
+
|
|
552
|
+
interface PageShellProps {
|
|
553
|
+
title: string;
|
|
554
|
+
subtitle: string;
|
|
555
|
+
badge?: string;
|
|
556
|
+
children: ReactNode;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export function PageShell({ title, subtitle, badge, children }: PageShellProps) {
|
|
560
|
+
const { bg, fg } = useThemeColors();
|
|
561
|
+
|
|
562
|
+
return (
|
|
563
|
+
<YStack flex={1} backgroundColor={bg}>
|
|
564
|
+
<SiteHeader />
|
|
565
|
+
<ScrollView contentContainerStyle={{ padding: 24 }}>
|
|
566
|
+
<YStack gap="$4">
|
|
567
|
+
{badge ? (
|
|
568
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$2" textTransform="uppercase" letterSpacing={2} color={fg}>
|
|
569
|
+
{badge}
|
|
570
|
+
</Text>
|
|
571
|
+
) : null}
|
|
572
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$7" color={fg}>
|
|
573
|
+
{title}
|
|
574
|
+
</Text>
|
|
575
|
+
<Text fontFamily={FONT_FAMILY} fontSize="$3" color={fg}>
|
|
576
|
+
{subtitle}
|
|
577
|
+
</Text>
|
|
578
|
+
{children}
|
|
579
|
+
</YStack>
|
|
580
|
+
</ScrollView>
|
|
581
|
+
<SiteFooter />
|
|
582
|
+
</YStack>
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
`;
|
|
586
|
+
}
|
|
587
|
+
function tamaguiConfigTemplate() {
|
|
588
|
+
return `import { config } from '@tamagui/config/v3';
|
|
589
|
+
|
|
590
|
+
export default config;
|
|
591
|
+
`;
|
|
592
|
+
}
|
|
593
|
+
function metroConfigTemplate() {
|
|
594
|
+
return `const { getDefaultConfig } = require('expo/metro-config');
|
|
595
|
+
const { withTamagui } = require('@tamagui/metro-plugin');
|
|
596
|
+
|
|
597
|
+
const config = getDefaultConfig(__dirname);
|
|
598
|
+
|
|
599
|
+
module.exports = withTamagui(config, {
|
|
600
|
+
components: ['tamagui'],
|
|
601
|
+
config: './tamagui.config.ts',
|
|
602
|
+
outputCSS: './tamagui-web.css'
|
|
603
|
+
});
|
|
604
|
+
`;
|
|
605
|
+
}
|
|
606
|
+
function babelConfigTemplate() {
|
|
607
|
+
return `module.exports = function (api) {
|
|
608
|
+
api.cache(true);
|
|
609
|
+
return {
|
|
610
|
+
presets: ['babel-preset-expo'],
|
|
611
|
+
plugins: [
|
|
612
|
+
'expo-router/babel',
|
|
613
|
+
[
|
|
614
|
+
'@tamagui/babel-plugin',
|
|
615
|
+
{
|
|
616
|
+
config: './tamagui.config.ts',
|
|
617
|
+
components: ['tamagui']
|
|
618
|
+
}
|
|
619
|
+
]
|
|
620
|
+
]
|
|
621
|
+
};
|
|
622
|
+
};
|
|
623
|
+
`;
|
|
624
|
+
}
|
|
625
|
+
function renderExpoEnvItems(envVars) {
|
|
626
|
+
if (envVars.length === 0) {
|
|
627
|
+
return '[]';
|
|
628
|
+
}
|
|
629
|
+
const items = envVars.map((item) => {
|
|
630
|
+
return `{
|
|
631
|
+
key: '${escapeTemplate(item.key)}',
|
|
632
|
+
description: '${escapeTemplate(item.description)}',
|
|
633
|
+
url: '${escapeTemplate(item.url)}'
|
|
634
|
+
}`;
|
|
635
|
+
});
|
|
636
|
+
return `[
|
|
637
|
+
${items.join(',\n ')}
|
|
638
|
+
]`;
|
|
639
|
+
}
|
|
640
|
+
function escapeTemplate(value) {
|
|
641
|
+
return value
|
|
642
|
+
.replace(/`/g, '\\`')
|
|
643
|
+
.replace(/\$/g, '\\$')
|
|
644
|
+
.replace(/"/g, '\\"')
|
|
645
|
+
.replace(/'/g, "\\'");
|
|
646
|
+
}
|
|
647
|
+
function escapeAttribute(value) {
|
|
648
|
+
return value.replace(/"/g, '"');
|
|
649
|
+
}
|