@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.
@@ -1,7 +1,125 @@
1
- export const nextLayoutTemplate = `import type { ReactNode } from 'react';
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: 'Aexis Zero App',
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>{children}</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
- export const nextPageTemplate = `export default function Home() {
21
- return <main />;
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
- export const shadcnUtilsTemplate = `import { clsx, type ClassValue } from 'clsx';
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
- export const componentsJsonTemplate = (globalsPath, tailwindConfigPath) => `{
32
- "$schema": "https://ui.shadcn.com/schema.json",
33
- "style": "default",
34
- "rsc": true,
35
- "tsx": true,
36
- "tailwind": {
37
- "config": "${tailwindConfigPath}",
38
- "css": "${globalsPath}",
39
- "baseColor": "slate",
40
- "cssVariables": true
41
- },
42
- "aliases": {
43
- "components": "@/components",
44
- "utils": "@/lib/utils"
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
- export default config;
51
- `;
52
- export const metroConfigTemplate = `const { getDefaultConfig } = require('expo/metro-config');
53
- const { withTamagui } = require('@tamagui/metro-plugin');
277
+ * {
278
+ border-color: var(--fg);
279
+ }
54
280
 
55
- const config = getDefaultConfig(__dirname);
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
- module.exports = withTamagui(config, {
58
- components: ['tamagui'],
59
- config: './tamagui.config.ts',
60
- outputCSS: './tamagui-web.css'
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
- export const expoLayoutTemplate = `import { Stack } from 'expo-router';
64
- import { TamaguiProvider } from 'tamagui';
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
- <Stack screenOptions={{ headerShown: false }} />
341
+ <Theme name={scheme === 'dark' ? 'dark' : 'light'}>
342
+ <Stack screenOptions={{ headerShown: false }} />
343
+ </Theme>
71
344
  </TamaguiProvider>
72
345
  );
73
346
  }
74
347
  `;
75
- export const expoIndexTemplate = `import { Button, H1, YStack } from 'tamagui';
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
- <YStack flex={1} alignItems=\"center\" justifyContent=\"center\" gap=\"$4\">
80
- <H1>Aexis Zero</H1>
81
- <Button>Start building</Button>
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, '&quot;');
649
+ }