@aex.is/zero 0.1.16 → 0.1.17

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.
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,24 +1,5 @@
1
- const nextBaseEnv = [
2
- {
3
- key: 'RESEND_API_KEY',
4
- description: 'Resend API key',
5
- url: 'https://resend.com'
6
- },
7
- {
8
- key: 'CONTACT_FROM_EMAIL',
9
- description: 'Verified sender email address'
10
- },
11
- {
12
- key: 'CONTACT_TO_EMAIL',
13
- description: 'Destination email address'
14
- }
15
- ];
16
- const expoBaseEnv = [
17
- {
18
- key: 'EXPO_PUBLIC_CONTACT_ENDPOINT',
19
- description: 'Contact API endpoint (e.g. https://yourdomain.com/api/contact)'
20
- }
21
- ];
1
+ const nextBaseEnv = [];
2
+ const expoBaseEnv = [];
22
3
  export function getBaseEnvHelp(framework) {
23
4
  return framework === 'nextjs' ? nextBaseEnv : expoBaseEnv;
24
5
  }
@@ -7,7 +7,6 @@ export const frameworks = [
7
7
  'class-variance-authority',
8
8
  'clsx',
9
9
  'lucide-react',
10
- 'resend',
11
10
  'tailwind-merge',
12
11
  'tailwindcss-animate',
13
12
  '@radix-ui/react-slot'
@@ -93,6 +93,43 @@ export const modules = [
93
93
  nextjs: ['stripe'],
94
94
  expo: ['stripe']
95
95
  }
96
+ },
97
+ {
98
+ id: 'email',
99
+ label: 'Email',
100
+ description: 'Contact form email via Resend.',
101
+ connect: {
102
+ label: 'Connect to Resend',
103
+ url: 'https://resend.com/api-keys'
104
+ },
105
+ envVars: [
106
+ {
107
+ key: 'RESEND_API_KEY',
108
+ description: 'Resend API key',
109
+ url: 'https://resend.com/api-keys',
110
+ frameworks: ['nextjs']
111
+ },
112
+ {
113
+ key: 'CONTACT_FROM_EMAIL',
114
+ description: 'Verified sender email address',
115
+ url: 'https://resend.com/domains',
116
+ frameworks: ['nextjs']
117
+ },
118
+ {
119
+ key: 'CONTACT_TO_EMAIL',
120
+ description: 'Destination email address',
121
+ frameworks: ['nextjs']
122
+ },
123
+ {
124
+ key: 'EXPO_PUBLIC_CONTACT_ENDPOINT',
125
+ description: 'Contact API endpoint (e.g. https://yourdomain.com/api/contact)',
126
+ frameworks: ['expo']
127
+ }
128
+ ],
129
+ packages: {
130
+ nextjs: ['resend'],
131
+ expo: []
132
+ }
96
133
  }
97
134
  ];
98
135
  export function getModuleDefinition(id) {
@@ -112,21 +149,27 @@ export function getModulePackages(moduleIds, framework) {
112
149
  }
113
150
  return Array.from(packages).sort();
114
151
  }
115
- export function getModuleEnvVars(moduleIds) {
152
+ export function getModuleEnvVars(moduleIds, framework) {
116
153
  const envVars = new Set();
117
154
  for (const id of moduleIds) {
118
155
  const module = getModuleDefinition(id);
119
156
  for (const envVar of module.envVars) {
157
+ if (!supportsFramework(envVar, framework)) {
158
+ continue;
159
+ }
120
160
  envVars.add(envVar.key);
121
161
  }
122
162
  }
123
163
  return Array.from(envVars).sort();
124
164
  }
125
- export function getModuleEnvHelp(moduleIds) {
165
+ export function getModuleEnvHelp(moduleIds, framework) {
126
166
  const map = new Map();
127
167
  for (const id of moduleIds) {
128
168
  const module = getModuleDefinition(id);
129
169
  for (const envVar of module.envVars) {
170
+ if (!supportsFramework(envVar, framework)) {
171
+ continue;
172
+ }
130
173
  if (!map.has(envVar.key)) {
131
174
  map.set(envVar.key, envVar);
132
175
  }
@@ -152,3 +195,9 @@ export function getModuleConnections(moduleIds) {
152
195
  }
153
196
  return connections;
154
197
  }
198
+ function supportsFramework(envVar, framework) {
199
+ if (!envVar.frameworks || envVar.frameworks.length === 0) {
200
+ return true;
201
+ }
202
+ return envVar.frameworks.includes(framework);
203
+ }
@@ -3,7 +3,7 @@ import { promises as fs } from 'fs';
3
3
  import { getBaseEnvVars } from '../config/base-env.js';
4
4
  import { getModuleEnvVars } from '../config/modules.js';
5
5
  export async function writeEnvExample(moduleIds, framework, targetDir) {
6
- const envVars = [...getBaseEnvVars(framework), ...getModuleEnvVars(moduleIds)];
6
+ const envVars = [...getBaseEnvVars(framework), ...getModuleEnvVars(moduleIds, framework)];
7
7
  const unique = Array.from(new Set(envVars));
8
8
  const lines = unique.map((key) => `${key}=`);
9
9
  const content = lines.length > 0 ? `${lines.join('\n')}\n` : '';
@@ -151,14 +151,16 @@ async function applyNextTemplates(config, targetDir) {
151
151
  const srcAppDir = path.join(targetDir, 'src', 'app');
152
152
  const usesSrcDir = await pathExists(srcAppDir);
153
153
  const basePath = usesSrcDir ? 'src' : '';
154
- const envHelp = mergeEnvHelp(getBaseEnvHelp(config.framework), getModuleEnvHelp(config.modules));
154
+ const envHelp = mergeEnvHelp(getBaseEnvHelp(config.framework), getModuleEnvHelp(config.modules, config.framework));
155
155
  const connections = getModuleConnections(config.modules);
156
+ const includeContact = config.modules.includes('email');
156
157
  const files = buildNextTemplateFiles({
157
158
  appName: config.appName,
158
159
  domain: config.domain,
159
160
  envVars: envHelp,
160
161
  connections,
161
- basePath
162
+ basePath,
163
+ includeContact
162
164
  });
163
165
  await writeTemplateFiles(targetDir, files);
164
166
  const globalsPath = usesSrcDir ? 'src/app/globals.css' : 'app/globals.css';
@@ -169,14 +171,16 @@ async function applyNextTemplates(config, targetDir) {
169
171
  await ensurePackageName(targetDir, config.appName);
170
172
  }
171
173
  async function applyExpoTemplates(config, targetDir) {
172
- const envHelp = mergeEnvHelp(getBaseEnvHelp(config.framework), getModuleEnvHelp(config.modules));
174
+ const envHelp = mergeEnvHelp(getBaseEnvHelp(config.framework), getModuleEnvHelp(config.modules, config.framework));
173
175
  const connections = getModuleConnections(config.modules);
176
+ const includeContact = config.modules.includes('email');
174
177
  const files = buildExpoTemplateFiles({
175
178
  appName: config.appName,
176
179
  domain: config.domain,
177
180
  envVars: envHelp,
178
181
  connections,
179
- basePath: ''
182
+ basePath: '',
183
+ includeContact
180
184
  });
181
185
  await writeTemplateFiles(targetDir, files);
182
186
  await ensureExpoConfig(targetDir, config.appName);
@@ -21,14 +21,15 @@ export function buildNextTemplateFiles(data) {
21
21
  const base = data.basePath ? `${data.basePath}/` : '';
22
22
  const envList = renderNextEnvList(data.envVars);
23
23
  const connectionSection = renderNextConnectionSection(data.connections);
24
- return [
24
+ const includeContact = data.includeContact;
25
+ const files = [
25
26
  {
26
27
  path: `${base}app/layout.tsx`,
27
28
  content: nextLayoutTemplate(data.appName, data.domain)
28
29
  },
29
30
  {
30
31
  path: `${base}app/page.tsx`,
31
- content: nextHomeTemplate(data.appName, data.domain, envList, connectionSection)
32
+ content: nextHomeTemplate(data.appName, data.domain, envList, connectionSection, includeContact)
32
33
  },
33
34
  {
34
35
  path: `${base}app/about/page.tsx`,
@@ -38,30 +39,18 @@ export function buildNextTemplateFiles(data) {
38
39
  path: `${base}app/guide/page.tsx`,
39
40
  content: nextRouteTemplate('Guide', 'Three routes are ready. Customize and ship.')
40
41
  },
41
- {
42
- path: `${base}app/contact/page.tsx`,
43
- content: nextContactPageTemplate()
44
- },
45
- {
46
- path: `${base}app/api/contact/route.ts`,
47
- content: nextContactRouteTemplate(data.appName)
48
- },
49
42
  {
50
43
  path: `${base}app/globals.css`,
51
44
  content: nextGlobalsCss()
52
45
  },
53
46
  {
54
47
  path: `${base}components/site-header.tsx`,
55
- content: nextHeaderTemplate(data.appName)
48
+ content: nextHeaderTemplate(data.appName, includeContact)
56
49
  },
57
50
  {
58
51
  path: `${base}components/site-footer.tsx`,
59
52
  content: nextFooterTemplate(data.domain)
60
53
  },
61
- {
62
- path: `${base}components/contact-form.tsx`,
63
- content: nextContactFormTemplate()
64
- },
65
54
  {
66
55
  path: `${base}components/connection-guide.tsx`,
67
56
  content: nextConnectionGuideTemplate(data.connections)
@@ -75,22 +64,34 @@ export function buildNextTemplateFiles(data) {
75
64
  content: nextUtilsTemplate()
76
65
  }
77
66
  ];
67
+ if (includeContact) {
68
+ files.splice(4, 0, {
69
+ path: `${base}app/contact/page.tsx`,
70
+ content: nextContactPageTemplate()
71
+ });
72
+ files.splice(5, 0, {
73
+ path: `${base}app/api/contact/route.ts`,
74
+ content: nextContactRouteTemplate(data.appName)
75
+ });
76
+ files.push({
77
+ path: `${base}components/contact-form.tsx`,
78
+ content: nextContactFormTemplate()
79
+ });
80
+ }
81
+ return files;
78
82
  }
79
83
  export function buildExpoTemplateFiles(data) {
80
84
  const envItems = renderExpoEnvItems(data.envVars);
81
85
  const connectionItems = renderConnectionItems(data.connections);
82
- return [
86
+ const includeContact = data.includeContact;
87
+ const files = [
83
88
  {
84
89
  path: 'app/_layout.tsx',
85
90
  content: expoLayoutTemplate()
86
91
  },
87
92
  {
88
93
  path: 'app/index.tsx',
89
- content: expoHomeTemplate(data.appName, data.domain, envItems)
90
- },
91
- {
92
- path: 'app/contact.tsx',
93
- content: expoContactTemplate()
94
+ content: expoHomeTemplate(data.appName, data.domain, envItems, includeContact)
94
95
  },
95
96
  {
96
97
  path: 'app/about.tsx',
@@ -106,7 +107,7 @@ export function buildExpoTemplateFiles(data) {
106
107
  },
107
108
  {
108
109
  path: 'components/site-header.tsx',
109
- content: expoHeaderTemplate(data.appName)
110
+ content: expoHeaderTemplate(data.appName, includeContact)
110
111
  },
111
112
  {
112
113
  path: 'components/site-footer.tsx',
@@ -120,10 +121,6 @@ export function buildExpoTemplateFiles(data) {
120
121
  path: 'components/connection-guide.tsx',
121
122
  content: expoConnectionGuideTemplate(connectionItems)
122
123
  },
123
- {
124
- path: 'components/contact-form.tsx',
125
- content: expoContactFormTemplate()
126
- },
127
124
  {
128
125
  path: 'components/page-shell.tsx',
129
126
  content: expoPageShellTemplate()
@@ -141,6 +138,17 @@ export function buildExpoTemplateFiles(data) {
141
138
  content: babelConfigTemplate()
142
139
  }
143
140
  ];
141
+ if (includeContact) {
142
+ files.splice(2, 0, {
143
+ path: 'app/contact.tsx',
144
+ content: expoContactTemplate()
145
+ });
146
+ files.push({
147
+ path: 'components/contact-form.tsx',
148
+ content: expoContactFormTemplate()
149
+ });
150
+ }
151
+ return files;
144
152
  }
145
153
  function nextLayoutTemplate(appName, domain) {
146
154
  const description = `${escapeTemplate(appName)} starter crafted by Aexis Zero.`;
@@ -189,10 +197,17 @@ export default function RootLayout({
189
197
  }
190
198
  `;
191
199
  }
192
- function nextHomeTemplate(appName, domain, envList, connectionSection) {
200
+ function nextHomeTemplate(appName, domain, envList, connectionSection, includeContact) {
201
+ const contactImport = includeContact ? "import { ContactForm } from '@/components/contact-form';\n" : '';
202
+ const contactSection = includeContact ? '\n <ContactForm />' : '';
203
+ const routeList = includeContact
204
+ ? `Explore <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/about</code>,{' '}
205
+ <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/guide</code>, and{' '}
206
+ <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/contact</code>.`
207
+ : `Explore <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/about</code> and{' '}
208
+ <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/guide</code>.`;
193
209
  return `import { EnvList } from '@/components/env-list';
194
- import { ContactForm } from '@/components/contact-form';
195
- import { ConnectionGuide } from '@/components/connection-guide';
210
+ ${contactImport}import { ConnectionGuide } from '@/components/connection-guide';
196
211
 
197
212
  export const metadata = {
198
213
  title: 'Home',
@@ -220,12 +235,10 @@ export default function Home() {
220
235
  <div className="flex flex-col gap-2">
221
236
  <h2 className="text-base font-bold">Routes</h2>
222
237
  <p className="text-base">
223
- Explore <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/about</code>,{' '}
224
- <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/guide</code>, and{' '}
225
- <code className="bg-[var(--fg)] px-2 py-1 text-[var(--bg)]">/contact</code>.
238
+ ${routeList}
226
239
  </p>
227
240
  </div>
228
- <ContactForm />
241
+ ${contactSection}
229
242
  </section>
230
243
  );
231
244
  }
@@ -247,14 +260,14 @@ export default function Page() {
247
260
  }
248
261
  `;
249
262
  }
250
- function nextHeaderTemplate(appName) {
263
+ function nextHeaderTemplate(appName, includeContact) {
264
+ const contactLink = includeContact ? "\n { href: '/contact', label: 'Contact' }" : '';
251
265
  return `import Link from 'next/link';
252
266
 
253
267
  const links = [
254
268
  { href: '/', label: 'Home' },
255
269
  { href: '/about', label: 'About' },
256
- { href: '/guide', label: 'Guide' },
257
- { href: '/contact', label: 'Contact' }
270
+ { href: '/guide', label: 'Guide' },${contactLink}
258
271
  ];
259
272
 
260
273
  export function SiteHeader({ appName }: { appName: string }) {
@@ -654,13 +667,16 @@ export default function RootLayout() {
654
667
  }
655
668
  `;
656
669
  }
657
- function expoHomeTemplate(appName, domain, envItems) {
670
+ function expoHomeTemplate(appName, domain, envItems, includeContact) {
671
+ const contactImport = includeContact ? "import { ContactForm } from '../components/contact-form';\n" : '';
672
+ const contactSection = includeContact ? '\n <ContactForm />' : '';
673
+ const routeLine = includeContact ? 'Visit /about, /guide, and /contact.' : 'Visit /about and /guide.';
658
674
  return `import { Head } from 'expo-router/head';
659
675
  import { Text, YStack } from 'tamagui';
660
676
  import { PageShell } from '../components/page-shell';
661
677
  import { EnvList } from '../components/env-list';
662
678
  import { ConnectionGuide } from '../components/connection-guide';
663
- import { ContactForm } from '../components/contact-form';
679
+ ${contactImport}
664
680
  import { FONT_BOLD, FONT_REGULAR, FONT_SIZE, useThemeColors } from '../components/theme';
665
681
 
666
682
  export default function Home() {
@@ -691,10 +707,10 @@ export default function Home() {
691
707
  Routes
692
708
  </Text>
693
709
  <Text fontFamily={FONT_REGULAR} fontSize={FONT_SIZE} color={fg}>
694
- Visit /about, /guide, and /contact.
710
+ ${routeLine}
695
711
  </Text>
696
712
  </YStack>
697
- <ContactForm />
713
+ ${contactSection}
698
714
  </PageShell>
699
715
  );
700
716
  }
@@ -770,7 +786,8 @@ export function useThemeColors() {
770
786
  }
771
787
  `;
772
788
  }
773
- function expoHeaderTemplate(appName) {
789
+ function expoHeaderTemplate(appName, includeContact) {
790
+ const contactLink = includeContact ? "\n { href: '/contact', label: 'Contact' }" : '';
774
791
  return `import { useState } from 'react';
775
792
  import { Link } from 'expo-router';
776
793
  import { Button, Text, XStack, YStack } from 'tamagui';
@@ -779,8 +796,7 @@ import { FONT_BOLD, FONT_REGULAR, FONT_SIZE, useThemeColors } from './theme';
779
796
  const links = [
780
797
  { href: '/', label: 'Home' },
781
798
  { href: '/about', label: 'About' },
782
- { href: '/guide', label: 'Guide' },
783
- { href: '/contact', label: 'Contact' }
799
+ { href: '/guide', label: 'Guide' },${contactLink}
784
800
  ];
785
801
 
786
802
  export function SiteHeader() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aex.is/zero",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "Aexis Zero scaffolding CLI",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",