create-wizze-app 0.1.2 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +17 -1
  2. package/dist/index.js +739 -61
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,23 @@
2
2
 
3
3
  Gerador oficial de projetos do Wizze Framework.
4
4
 
5
- Uso:
5
+ ## Uso
6
6
 
7
7
  - `create-wizze-app <nome>`
8
8
  - `create-wizze-app <nome> --template <blank|tabs|ecommerce|delivery|social|dashboard-mobile|marketplace|fintech|education|health|restaurant>`
9
+
10
+ ## O que é gerado
11
+
12
+ - Estrutura universal com suporte a Android, iOS e Web.
13
+ - `src/app` com rotas por arquivo (Expo Router).
14
+ - `src/components`, `src/constants` e `src/hooks`.
15
+ - `app.json` configurado para Expo.
16
+ - `wizze.config.ts` com metadados do projeto Wizze Go.
17
+
18
+ ## Scripts iniciais
19
+
20
+ - `npm run start`
21
+ - `npm run android`
22
+ - `npm run ios`
23
+ - `npm run web`
24
+ - `npm run prebuild` (gera pastas nativas `android/` e `ios/`)
package/dist/index.js CHANGED
@@ -1,8 +1,5 @@
1
- import { copyFile, mkdir, readdir, writeFile } from 'node:fs/promises';
2
- import { fileURLToPath } from 'node:url';
1
+ import { mkdir, readdir, writeFile } from 'node:fs/promises';
3
2
  import { join } from 'node:path';
4
- import { access } from 'node:fs/promises';
5
- import { resolveTemplate } from './template-resolver.js';
6
3
  const SUPPORTED_TEMPLATES = new Set([
7
4
  'blank',
8
5
  'dashboard-mobile',
@@ -16,6 +13,74 @@ const SUPPORTED_TEMPLATES = new Set([
16
13
  'social',
17
14
  'tabs',
18
15
  ]);
16
+ const TEMPLATE_META = {
17
+ blank: {
18
+ title: 'Wizze Go Base',
19
+ subtitle: 'Estrutura inicial para Android, iOS e Web com Expo Router.',
20
+ accentColor: '#1d4ed8',
21
+ features: ['Roteamento por arquivos', 'Tabs prontas', 'Scripts Android/iOS/Web'],
22
+ },
23
+ 'dashboard-mobile': {
24
+ title: 'Dashboard Mobile',
25
+ subtitle: 'Painel operacional com foco em KPIs, ambientes e releases.',
26
+ accentColor: '#0369a1',
27
+ features: ['Visão de KPIs', 'Fluxo de releases', 'Status por ambiente'],
28
+ },
29
+ delivery: {
30
+ title: 'Delivery App',
31
+ subtitle: 'Base para pedidos, rastreio de entregas e operação de cozinha.',
32
+ accentColor: '#b45309',
33
+ features: ['Pedidos em tempo real', 'Status de entrega', 'Operação por etapas'],
34
+ },
35
+ ecommerce: {
36
+ title: 'E-commerce App',
37
+ subtitle: 'Base para catálogo, carrinho e checkout mobile/web.',
38
+ accentColor: '#0f766e',
39
+ features: ['Catálogo de produtos', 'Carrinho de compras', 'Fluxo de checkout'],
40
+ },
41
+ education: {
42
+ title: 'Education App',
43
+ subtitle: 'Estrutura para trilhas de conteúdo, aulas e progresso.',
44
+ accentColor: '#4338ca',
45
+ features: ['Trilhas e módulos', 'Acompanhamento de progresso', 'Área do aluno'],
46
+ },
47
+ fintech: {
48
+ title: 'Fintech App',
49
+ subtitle: 'Base para contas, transações e visão financeira.',
50
+ accentColor: '#0f766e',
51
+ features: ['Extrato e saldo', 'Movimentações', 'Painel financeiro'],
52
+ },
53
+ health: {
54
+ title: 'Health App',
55
+ subtitle: 'Estrutura inicial para agenda, prontuário e acompanhamento.',
56
+ accentColor: '#0e7490',
57
+ features: ['Agenda clínica', 'Histórico de atendimentos', 'Painel de acompanhamento'],
58
+ },
59
+ marketplace: {
60
+ title: 'Marketplace App',
61
+ subtitle: 'Base para vendedores, produtos e pedidos multi-loja.',
62
+ accentColor: '#9333ea',
63
+ features: ['Múltiplos vendedores', 'Gestão de pedidos', 'Catálogo compartilhado'],
64
+ },
65
+ restaurant: {
66
+ title: 'Restaurant App',
67
+ subtitle: 'Estrutura para salão, balcão e integração com impressoras.',
68
+ accentColor: '#b91c1c',
69
+ features: ['Gestão de mesas', 'Pedidos de cozinha', 'Suporte SUNMI'],
70
+ },
71
+ social: {
72
+ title: 'Social App',
73
+ subtitle: 'Base para feed, interações e perfil de usuário.',
74
+ accentColor: '#db2777',
75
+ features: ['Feed de conteúdo', 'Interações sociais', 'Perfil e preferências'],
76
+ },
77
+ tabs: {
78
+ title: 'Tabs App',
79
+ subtitle: 'Layout focado em navegação por abas para evolução rápida.',
80
+ accentColor: '#2563eb',
81
+ features: ['Navegação em abas', 'Páginas modulares', 'Base para expansão rápida'],
82
+ },
83
+ };
19
84
  export async function createWizzeApp(input) {
20
85
  const rawName = input.name.trim();
21
86
  if (!rawName) {
@@ -24,7 +89,7 @@ export async function createWizzeApp(input) {
24
89
  if (rawName.includes('/') || rawName.includes('\\') || rawName === '.' || rawName === '..') {
25
90
  throw new Error('Nome de aplicativo inválido. Não use separadores de diretório.');
26
91
  }
27
- const template = resolveTemplate(input.template).trim();
92
+ const template = (input.template ?? 'blank').trim();
28
93
  if (!SUPPORTED_TEMPLATES.has(template)) {
29
94
  throw new Error(`Template inválido: ${template}`);
30
95
  }
@@ -34,30 +99,160 @@ export async function createWizzeApp(input) {
34
99
  }
35
100
  const appDir = join(process.cwd(), slug);
36
101
  await mkdir(appDir, { recursive: true });
37
- const templateDir = await resolveTemplateDir(template);
38
- if (templateDir) {
39
- await copyDirectory(templateDir, appDir);
102
+ await ensureDirectoryIsEmpty(appDir);
103
+ const displayName = toTitleCase(slug);
104
+ const templateMeta = TEMPLATE_META[template] ?? TEMPLATE_META.blank;
105
+ await createBaseProjectStructure(appDir);
106
+ await writeAssetPlaceholders(appDir);
107
+ await writeProjectFiles({ appDir, slug, displayName, template, templateMeta });
108
+ }
109
+ async function ensureDirectoryIsEmpty(appDir) {
110
+ const files = await readdir(appDir);
111
+ if (files.length > 0) {
112
+ throw new Error('A pasta do aplicativo já existe e não está vazia. Escolha outro nome.');
40
113
  }
41
- else {
42
- await mkdir(join(appDir, 'src'), { recursive: true });
114
+ }
115
+ async function createBaseProjectStructure(appDir) {
116
+ const directories = [
117
+ 'assets/expo.icon',
118
+ 'assets/images',
119
+ 'scripts',
120
+ 'src/app/(tabs)',
121
+ 'src/components',
122
+ 'src/constants',
123
+ 'src/hooks',
124
+ ];
125
+ for (const dir of directories) {
126
+ await mkdir(join(appDir, dir), { recursive: true });
43
127
  }
44
- const configPayload = {
45
- name: toTitleCase(slug),
46
- slug,
47
- version: '1.0.0',
48
- template
49
- };
50
- await writeFile(join(appDir, 'wizze.config.ts'), `export default ${JSON.stringify(configPayload, null, 2)};\n`, 'utf8');
128
+ }
129
+ async function writeAssetPlaceholders(appDir) {
130
+ // 1x1 PNG (sem transparência) para evitar referências quebradas no app.json inicial.
131
+ const pngBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADUlEQVR42mP8z8BQDwAF/gL+3xaQaQAAAABJRU5ErkJggg==';
132
+ const pngBuffer = Buffer.from(pngBase64, 'base64');
133
+ const files = [
134
+ 'assets/images/icon.png',
135
+ 'assets/images/favicon.png',
136
+ 'assets/images/splash-icon.png',
137
+ 'assets/images/android-icon-foreground.png',
138
+ 'assets/images/android-icon-background.png',
139
+ 'assets/images/android-icon-monochrome.png',
140
+ ];
141
+ for (const file of files) {
142
+ await writeFile(join(appDir, file), pngBuffer);
143
+ }
144
+ await writeFile(join(appDir, 'assets/expo.icon/icon.json'), `${JSON.stringify({ images: [] }, null, 2)}\n`, 'utf8');
145
+ }
146
+ async function writeProjectFiles(input) {
147
+ const { appDir, slug, displayName, template, templateMeta } = input;
148
+ const scheme = slug.replace(/-/g, '');
51
149
  const packagePayload = {
52
150
  name: slug,
151
+ main: 'expo-router/entry',
53
152
  private: true,
54
- version: '0.0.1',
153
+ version: '1.0.0',
55
154
  scripts: {
56
- dev: 'echo "Projeto Wizze criado. Configure o runtime para desenvolvimento."'
57
- }
155
+ start: 'expo start',
156
+ android: 'expo start --android',
157
+ ios: 'expo start --ios',
158
+ web: 'expo start --web',
159
+ prebuild: 'expo prebuild',
160
+ lint: 'expo lint',
161
+ 'reset-project': 'node ./scripts/reset-project.js',
162
+ },
163
+ dependencies: {
164
+ '@expo/vector-icons': '^15.0.3',
165
+ '@react-navigation/bottom-tabs': '^7.15.5',
166
+ '@react-navigation/native': '^7.1.33',
167
+ expo: '~55.0.24',
168
+ 'expo-constants': '~55.0.16',
169
+ 'expo-font': '~55.0.7',
170
+ 'expo-linking': '~55.0.15',
171
+ 'expo-router': '~55.0.14',
172
+ 'expo-splash-screen': '~55.0.21',
173
+ 'expo-status-bar': '~55.0.6',
174
+ 'expo-system-ui': '~55.0.18',
175
+ react: '19.2.0',
176
+ 'react-dom': '19.2.0',
177
+ 'react-native': '0.83.6',
178
+ 'react-native-gesture-handler': '~2.30.0',
179
+ 'react-native-safe-area-context': '~5.6.2',
180
+ 'react-native-screens': '~4.23.0',
181
+ 'react-native-web': '~0.21.0',
182
+ },
183
+ devDependencies: {
184
+ '@types/react': '~19.2.2',
185
+ typescript: '~5.9.2',
186
+ },
187
+ };
188
+ const appConfigPayload = {
189
+ expo: {
190
+ name: displayName,
191
+ slug,
192
+ version: '1.0.0',
193
+ orientation: 'portrait',
194
+ icon: './assets/images/icon.png',
195
+ scheme,
196
+ userInterfaceStyle: 'automatic',
197
+ ios: {
198
+ icon: './assets/expo.icon',
199
+ },
200
+ android: {
201
+ adaptiveIcon: {
202
+ backgroundColor: '#E6F4FE',
203
+ foregroundImage: './assets/images/android-icon-foreground.png',
204
+ backgroundImage: './assets/images/android-icon-background.png',
205
+ monochromeImage: './assets/images/android-icon-monochrome.png',
206
+ },
207
+ predictiveBackGestureEnabled: false,
208
+ },
209
+ web: {
210
+ output: 'static',
211
+ favicon: './assets/images/favicon.png',
212
+ },
213
+ plugins: [
214
+ 'expo-router',
215
+ [
216
+ 'expo-splash-screen',
217
+ {
218
+ backgroundColor: templateMeta.accentColor,
219
+ android: {
220
+ image: './assets/images/splash-icon.png',
221
+ imageWidth: 76,
222
+ },
223
+ },
224
+ ],
225
+ ],
226
+ experiments: {
227
+ typedRoutes: true,
228
+ reactCompiler: true,
229
+ },
230
+ },
231
+ };
232
+ const wizzeConfigPayload = {
233
+ name: displayName,
234
+ slug,
235
+ version: '1.0.0',
236
+ template,
237
+ platforms: ['android', 'ios', 'web'],
58
238
  };
59
239
  await writeFile(join(appDir, 'package.json'), `${JSON.stringify(packagePayload, null, 2)}\n`, 'utf8');
60
- await ensureTemplateAppSource(appDir, template);
240
+ await writeFile(join(appDir, 'app.json'), `${JSON.stringify(appConfigPayload, null, 2)}\n`, 'utf8');
241
+ await writeFile(join(appDir, 'tsconfig.json'), buildTsConfigSource(), 'utf8');
242
+ await writeFile(join(appDir, '.gitignore'), buildGitIgnoreSource(), 'utf8');
243
+ await writeFile(join(appDir, 'README.md'), buildReadmeSource(slug, template, templateMeta), 'utf8');
244
+ await writeFile(join(appDir, 'wizze.config.ts'), `export default ${JSON.stringify(wizzeConfigPayload, null, 2)};\n`, 'utf8');
245
+ await writeFile(join(appDir, 'scripts', 'reset-project.js'), buildResetProjectScript(), 'utf8');
246
+ await writeFile(join(appDir, 'src', 'app', '_layout.tsx'), buildRootLayoutSource(), 'utf8');
247
+ await writeFile(join(appDir, 'src', 'app', '(tabs)', '_layout.tsx'), buildTabLayoutSource(), 'utf8');
248
+ await writeFile(join(appDir, 'src', 'app', '(tabs)', 'index.tsx'), buildHomeScreenSource(displayName, templateMeta), 'utf8');
249
+ await writeFile(join(appDir, 'src', 'app', '(tabs)', 'projects.tsx'), buildProjectsScreenSource(templateMeta), 'utf8');
250
+ await writeFile(join(appDir, 'src', 'app', '(tabs)', 'builds.tsx'), buildBuildsScreenSource(templateMeta), 'utf8');
251
+ await writeFile(join(appDir, 'src', 'components', 'feature-card.tsx'), buildFeatureCardSource(), 'utf8');
252
+ await writeFile(join(appDir, 'src', 'constants', 'theme.ts'), buildThemeSource(templateMeta.accentColor), 'utf8');
253
+ await writeFile(join(appDir, 'src', 'hooks', 'use-color-scheme.ts'), "export { useColorScheme } from 'react-native';\n", 'utf8');
254
+ await writeFile(join(appDir, 'src', 'hooks', 'use-color-scheme.web.ts'), buildUseColorSchemeWebSource(), 'utf8');
255
+ await writeFile(join(appDir, 'src', 'hooks', 'use-theme.ts'), buildUseThemeSource(), 'utf8');
61
256
  }
62
257
  function toSlug(value) {
63
258
  return value
@@ -74,50 +269,533 @@ function toTitleCase(value) {
74
269
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
75
270
  .join(' ');
76
271
  }
77
- function buildAppTemplateSource(template) {
272
+ function buildTsConfigSource() {
78
273
  return [
79
- 'export function App() {',
80
- ` return 'Wizze template: ${template}';`,
274
+ '{',
275
+ ' "extends": "expo/tsconfig.base",',
276
+ ' "compilerOptions": {',
277
+ ' "strict": true,',
278
+ ' "paths": {',
279
+ ' "@/*": [',
280
+ ' "./src/*"',
281
+ ' ],',
282
+ ' "@/assets/*": [',
283
+ ' "./assets/*"',
284
+ ' ]',
285
+ ' }',
286
+ ' },',
287
+ ' "include": [',
288
+ ' "**/*.ts",',
289
+ ' "**/*.tsx",',
290
+ ' ".expo/types/**/*.ts",',
291
+ ' "expo-env.d.ts"',
292
+ ' ]',
81
293
  '}',
82
- ''
294
+ '',
83
295
  ].join('\n');
84
296
  }
85
- async function ensureTemplateAppSource(appDir, template) {
86
- const tsPath = join(appDir, 'src', 'App.ts');
87
- const tsxPath = join(appDir, 'src', 'App.tsx');
88
- if (!(await pathExists(tsPath)) && !(await pathExists(tsxPath))) {
89
- await writeFile(tsPath, buildAppTemplateSource(template), 'utf8');
90
- }
297
+ function buildGitIgnoreSource() {
298
+ return [
299
+ 'node_modules/',
300
+ '',
301
+ '# Expo',
302
+ '.expo/',
303
+ 'dist/',
304
+ 'web-build/',
305
+ 'expo-env.d.ts',
306
+ '',
307
+ '# Native artifacts',
308
+ '.kotlin/',
309
+ '*.orig.*',
310
+ '*.jks',
311
+ '*.p8',
312
+ '*.p12',
313
+ '*.key',
314
+ '*.mobileprovision',
315
+ '',
316
+ '# Metro',
317
+ '.metro-health-check*',
318
+ '',
319
+ '# Logs',
320
+ 'npm-debug.*',
321
+ 'yarn-debug.*',
322
+ 'yarn-error.*',
323
+ '',
324
+ '# macOS',
325
+ '.DS_Store',
326
+ '*.pem',
327
+ '',
328
+ '# Local env',
329
+ '.env*.local',
330
+ '',
331
+ '# TypeScript',
332
+ '*.tsbuildinfo',
333
+ '',
334
+ '# Generated native folders',
335
+ '/ios',
336
+ '/android',
337
+ '',
338
+ ].join('\n');
91
339
  }
92
- async function resolveTemplateDir(template) {
93
- const currentFilePath = fileURLToPath(import.meta.url);
94
- const rootTemplates = join(currentFilePath, '..', '..', '..', '..', 'templates', template);
95
- if (await pathExists(rootTemplates)) {
96
- return rootTemplates;
97
- }
98
- return null;
99
- }
100
- async function copyDirectory(sourceDir, targetDir) {
101
- const entries = await readdir(sourceDir, { withFileTypes: true });
102
- for (const entry of entries) {
103
- const sourcePath = join(sourceDir, entry.name);
104
- const targetPath = join(targetDir, entry.name);
105
- if (entry.isDirectory()) {
106
- await mkdir(targetPath, { recursive: true });
107
- await copyDirectory(sourcePath, targetPath);
108
- continue;
109
- }
110
- if (entry.isFile()) {
111
- await copyFile(sourcePath, targetPath);
112
- }
113
- }
340
+ function buildReadmeSource(slug, template, templateMeta) {
341
+ return [
342
+ `# ${slug}`,
343
+ '',
344
+ `Projeto Wizze Go criado com template \`${template}\`.`,
345
+ '',
346
+ `## ${templateMeta.title}`,
347
+ templateMeta.subtitle,
348
+ '',
349
+ '## Como rodar',
350
+ '',
351
+ '```bash',
352
+ 'npm install',
353
+ 'npm run start',
354
+ '```',
355
+ '',
356
+ 'Comandos por plataforma:',
357
+ '',
358
+ '- `npm run android`',
359
+ '- `npm run ios`',
360
+ '- `npm run web`',
361
+ '',
362
+ 'Para gerar estruturas nativas (`android/` e `ios/`):',
363
+ '',
364
+ '```bash',
365
+ 'npm run prebuild',
366
+ '```',
367
+ '',
368
+ '## Estrutura',
369
+ '',
370
+ '- `src/app`: rotas e telas',
371
+ '- `src/components`: componentes reutilizáveis',
372
+ '- `src/constants`: tokens e tema',
373
+ '- `src/hooks`: hooks de suporte',
374
+ '- `assets`: ícones e imagens do app',
375
+ '- `app.json`: configuração Expo (Android, iOS e Web)',
376
+ '- `wizze.config.ts`: configuração do projeto Wizze Go',
377
+ '',
378
+ '## Recursos iniciais',
379
+ '',
380
+ ...templateMeta.features.map((feature) => `- ${feature}`),
381
+ '',
382
+ ].join('\n');
114
383
  }
115
- async function pathExists(path) {
116
- try {
117
- await access(path);
118
- return true;
119
- }
120
- catch {
121
- return false;
122
- }
384
+ function buildResetProjectScript() {
385
+ return [
386
+ '#!/usr/bin/env node',
387
+ '',
388
+ "const fs = require('fs');",
389
+ "const path = require('path');",
390
+ '',
391
+ 'const root = process.cwd();',
392
+ "const srcAppDir = path.join(root, 'src', 'app');",
393
+ '',
394
+ 'async function main() {',
395
+ ' await fs.promises.rm(srcAppDir, { recursive: true, force: true });',
396
+ ' await fs.promises.mkdir(path.join(srcAppDir, "(tabs)"), { recursive: true });',
397
+ '',
398
+ ' const rootLayout = [',
399
+ " \"import { Stack } from 'expo-router';\"",
400
+ " \"\"",
401
+ " \"export default function RootLayout() {\"",
402
+ " \" return <Stack />;\"",
403
+ " \"}\"",
404
+ " \"\"",
405
+ " ].join('\\n');",
406
+ '',
407
+ ' const index = [',
408
+ " \"import { StyleSheet, Text, View } from 'react-native';\"",
409
+ " \"\"",
410
+ " \"export default function Home() {\"",
411
+ " \" return (\"",
412
+ " \" <View style={styles.container}>\"",
413
+ " \" <Text>Edite src/app/(tabs)/index.tsx para começar.</Text>\"",
414
+ " \" </View>\"",
415
+ " \" );\"",
416
+ " \"}\"",
417
+ " \"\"",
418
+ " \"const styles = StyleSheet.create({\"",
419
+ " \" container: {\"",
420
+ " \" flex: 1,\"",
421
+ " \" alignItems: 'center',\"",
422
+ " \" justifyContent: 'center',\"",
423
+ " \" padding: 24,\"",
424
+ " \" },\"",
425
+ " \"});\"",
426
+ " \"\"",
427
+ " ].join('\\n');",
428
+ '',
429
+ " await fs.promises.writeFile(path.join(srcAppDir, '_layout.tsx'), rootLayout, 'utf8');",
430
+ " await fs.promises.writeFile(path.join(srcAppDir, '(tabs)', 'index.tsx'), index, 'utf8');",
431
+ " console.log('Projeto resetado com sucesso.');",
432
+ '}',
433
+ '',
434
+ "main().catch((error) => {",
435
+ " console.error('Falha ao resetar projeto:', error.message);",
436
+ ' process.exit(1);',
437
+ '});',
438
+ '',
439
+ ].join('\n');
440
+ }
441
+ function buildRootLayoutSource() {
442
+ return [
443
+ "import { Stack } from 'expo-router';",
444
+ "import React from 'react';",
445
+ '',
446
+ 'export default function RootLayout() {',
447
+ ' return (',
448
+ ' <Stack>',
449
+ " <Stack.Screen name=\"(tabs)\" options={{ headerShown: false }} />",
450
+ ' </Stack>',
451
+ ' );',
452
+ '}',
453
+ '',
454
+ ].join('\n');
455
+ }
456
+ function buildTabLayoutSource() {
457
+ return [
458
+ "import FontAwesome from '@expo/vector-icons/FontAwesome';",
459
+ "import { Tabs } from 'expo-router';",
460
+ "import React from 'react';",
461
+ '',
462
+ "import { useTheme } from '@/hooks/use-theme';",
463
+ '',
464
+ 'export default function TabLayout() {',
465
+ ' const theme = useTheme();',
466
+ '',
467
+ ' return (',
468
+ ' <Tabs',
469
+ ' screenOptions={{',
470
+ ' tabBarActiveTintColor: theme.colors.primary,',
471
+ ' headerStyle: {',
472
+ ' backgroundColor: theme.colors.surface,',
473
+ ' },',
474
+ ' headerTitleStyle: {',
475
+ ' color: theme.colors.text,',
476
+ ' fontWeight: \"700\",',
477
+ ' },',
478
+ ' tabBarStyle: {',
479
+ ' backgroundColor: theme.colors.surface,',
480
+ ' },',
481
+ ' }}>',
482
+ ' <Tabs.Screen',
483
+ ' name="index"',
484
+ ' options={{',
485
+ ' title: "Início",',
486
+ ' tabBarIcon: ({ color }) => <FontAwesome name="home" size={20} color={color} />,',
487
+ ' }}',
488
+ ' />',
489
+ ' <Tabs.Screen',
490
+ ' name="projects"',
491
+ ' options={{',
492
+ ' title: "Projetos",',
493
+ ' tabBarIcon: ({ color }) => <FontAwesome name="folder-open" size={20} color={color} />,',
494
+ ' }}',
495
+ ' />',
496
+ ' <Tabs.Screen',
497
+ ' name="builds"',
498
+ ' options={{',
499
+ ' title: "Builds",',
500
+ ' tabBarIcon: ({ color }) => <FontAwesome name="rocket" size={20} color={color} />,',
501
+ ' }}',
502
+ ' />',
503
+ ' </Tabs>',
504
+ ' );',
505
+ '}',
506
+ '',
507
+ ].join('\n');
508
+ }
509
+ function buildHomeScreenSource(displayName, templateMeta) {
510
+ const features = templateMeta.features
511
+ .map((feature) => {
512
+ const value = JSON.stringify(feature);
513
+ return ` <FeatureCard key=${value} label=${value} />`;
514
+ })
515
+ .join('\n');
516
+ return [
517
+ "import { StyleSheet, Text, View } from 'react-native';",
518
+ '',
519
+ "import { FeatureCard } from '@/components/feature-card';",
520
+ "import { useTheme } from '@/hooks/use-theme';",
521
+ '',
522
+ 'export default function HomeScreen() {',
523
+ ' const theme = useTheme();',
524
+ '',
525
+ ' return (',
526
+ ' <View style={[styles.container, { backgroundColor: theme.colors.background }]}>',
527
+ ' <View style={styles.copy}>',
528
+ ` <Text style={[styles.kicker, { color: theme.colors.primary }]}>${templateMeta.title}</Text>`,
529
+ ` <Text style={[styles.title, { color: theme.colors.text }]}>{${JSON.stringify(displayName)}}</Text>`,
530
+ ` <Text style={[styles.subtitle, { color: theme.colors.textSecondary }]}>{${JSON.stringify(templateMeta.subtitle)}}</Text>`,
531
+ ' </View>',
532
+ ' <View style={styles.featureList}>',
533
+ features,
534
+ ' </View>',
535
+ ' </View>',
536
+ ' );',
537
+ '}',
538
+ '',
539
+ 'const styles = StyleSheet.create({',
540
+ ' container: {',
541
+ ' flex: 1,',
542
+ ' paddingHorizontal: 20,',
543
+ ' paddingVertical: 22,',
544
+ ' gap: 16,',
545
+ ' },',
546
+ ' copy: {',
547
+ ' gap: 8,',
548
+ ' },',
549
+ ' kicker: {',
550
+ ' fontSize: 12,',
551
+ ' fontWeight: "700",',
552
+ ' textTransform: "uppercase",',
553
+ ' letterSpacing: 1.1,',
554
+ ' },',
555
+ ' title: {',
556
+ ' fontSize: 28,',
557
+ ' fontWeight: "800",',
558
+ ' letterSpacing: -0.4,',
559
+ ' },',
560
+ ' subtitle: {',
561
+ ' fontSize: 15,',
562
+ ' lineHeight: 21,',
563
+ ' },',
564
+ ' featureList: {',
565
+ ' gap: 10,',
566
+ ' },',
567
+ '});',
568
+ '',
569
+ ].join('\n');
570
+ }
571
+ function buildProjectsScreenSource(templateMeta) {
572
+ return [
573
+ "import { StyleSheet, Text, View } from 'react-native';",
574
+ '',
575
+ "import { useTheme } from '@/hooks/use-theme';",
576
+ '',
577
+ 'const EXAMPLE_PROJECTS = [',
578
+ " 'mobile-app-main',",
579
+ " 'painel-operacional',",
580
+ " 'site-marketing',",
581
+ '];',
582
+ '',
583
+ 'export default function ProjectsScreen() {',
584
+ ' const theme = useTheme();',
585
+ '',
586
+ ' return (',
587
+ ' <View style={[styles.container, { backgroundColor: theme.colors.background }]}>',
588
+ ` <Text style={[styles.heading, { color: theme.colors.text }]}>Projetos - ${templateMeta.title}</Text>`,
589
+ ' <Text style={[styles.caption, { color: theme.colors.textSecondary }]}>',
590
+ ' Use esta área para listar apps por ambiente, cliente e canal de release.',
591
+ ' </Text>',
592
+ ' <View style={styles.list}>',
593
+ ' {EXAMPLE_PROJECTS.map((item) => (',
594
+ ' <View',
595
+ ' key={item}',
596
+ ' style={[styles.row, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}>',
597
+ ' <Text style={[styles.rowText, { color: theme.colors.text }]}>{item}</Text>',
598
+ ' </View>',
599
+ ' ))}',
600
+ ' </View>',
601
+ ' </View>',
602
+ ' );',
603
+ '}',
604
+ '',
605
+ 'const styles = StyleSheet.create({',
606
+ ' container: {',
607
+ ' flex: 1,',
608
+ ' padding: 20,',
609
+ ' gap: 12,',
610
+ ' },',
611
+ ' heading: {',
612
+ ' fontSize: 22,',
613
+ ' fontWeight: "800",',
614
+ ' },',
615
+ ' caption: {',
616
+ ' fontSize: 14,',
617
+ ' lineHeight: 20,',
618
+ ' },',
619
+ ' list: {',
620
+ ' gap: 10,',
621
+ ' marginTop: 6,',
622
+ ' },',
623
+ ' row: {',
624
+ ' borderWidth: 1,',
625
+ ' borderRadius: 12,',
626
+ ' paddingHorizontal: 12,',
627
+ ' paddingVertical: 10,',
628
+ ' },',
629
+ ' rowText: {',
630
+ ' fontSize: 14,',
631
+ ' fontWeight: "600",',
632
+ ' },',
633
+ '});',
634
+ '',
635
+ ].join('\n');
636
+ }
637
+ function buildBuildsScreenSource(templateMeta) {
638
+ return [
639
+ "import { StyleSheet, Text, View } from 'react-native';",
640
+ '',
641
+ "import { useTheme } from '@/hooks/use-theme';",
642
+ '',
643
+ 'const SAMPLE_BUILDS = [',
644
+ " { id: 'build-2401', platform: 'android', status: 'success' },",
645
+ " { id: 'build-2402', platform: 'ios', status: 'running' },",
646
+ " { id: 'build-2403', platform: 'web', status: 'queued' },",
647
+ '];',
648
+ '',
649
+ 'export default function BuildsScreen() {',
650
+ ' const theme = useTheme();',
651
+ '',
652
+ ' return (',
653
+ ' <View style={[styles.container, { backgroundColor: theme.colors.background }]}>',
654
+ ' <Text style={[styles.heading, { color: theme.colors.text }]}>Build Pipeline</Text>',
655
+ ` <Text style={[styles.caption, { color: theme.colors.textSecondary }]}>Template ativo: ${templateMeta.title}</Text>`,
656
+ ' <View style={styles.list}>',
657
+ ' {SAMPLE_BUILDS.map((build) => (',
658
+ ' <View',
659
+ ' key={build.id}',
660
+ ' style={[styles.row, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}>',
661
+ ' <Text style={[styles.rowTitle, { color: theme.colors.text }]}>{build.id}</Text>',
662
+ ' <Text style={[styles.rowMeta, { color: theme.colors.textSecondary }]}>',
663
+ ' {build.platform} • {build.status}',
664
+ ' </Text>',
665
+ ' </View>',
666
+ ' ))}',
667
+ ' </View>',
668
+ ' </View>',
669
+ ' );',
670
+ '}',
671
+ '',
672
+ 'const styles = StyleSheet.create({',
673
+ ' container: {',
674
+ ' flex: 1,',
675
+ ' padding: 20,',
676
+ ' gap: 12,',
677
+ ' },',
678
+ ' heading: {',
679
+ ' fontSize: 22,',
680
+ ' fontWeight: "800",',
681
+ ' },',
682
+ ' caption: {',
683
+ ' fontSize: 14,',
684
+ ' },',
685
+ ' list: {',
686
+ ' marginTop: 8,',
687
+ ' gap: 10,',
688
+ ' },',
689
+ ' row: {',
690
+ ' borderWidth: 1,',
691
+ ' borderRadius: 12,',
692
+ ' paddingHorizontal: 12,',
693
+ ' paddingVertical: 10,',
694
+ ' gap: 4,',
695
+ ' },',
696
+ ' rowTitle: {',
697
+ ' fontSize: 14,',
698
+ ' fontWeight: "700",',
699
+ ' },',
700
+ ' rowMeta: {',
701
+ ' fontSize: 13,',
702
+ ' },',
703
+ '});',
704
+ '',
705
+ ].join('\n');
706
+ }
707
+ function buildFeatureCardSource() {
708
+ return [
709
+ "import { StyleSheet, Text, View } from 'react-native';",
710
+ '',
711
+ "import { useTheme } from '@/hooks/use-theme';",
712
+ '',
713
+ 'type FeatureCardProps = {',
714
+ ' label: string;',
715
+ '};',
716
+ '',
717
+ 'export function FeatureCard({ label }: FeatureCardProps) {',
718
+ ' const theme = useTheme();',
719
+ '',
720
+ ' return (',
721
+ ' <View style={[styles.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}>',
722
+ ' <Text style={[styles.text, { color: theme.colors.text }]}>{label}</Text>',
723
+ ' </View>',
724
+ ' );',
725
+ '}',
726
+ '',
727
+ 'const styles = StyleSheet.create({',
728
+ ' card: {',
729
+ ' borderWidth: 1,',
730
+ ' borderRadius: 12,',
731
+ ' paddingHorizontal: 12,',
732
+ ' paddingVertical: 10,',
733
+ ' },',
734
+ ' text: {',
735
+ ' fontSize: 14,',
736
+ ' fontWeight: "600",',
737
+ ' },',
738
+ '});',
739
+ '',
740
+ ].join('\n');
741
+ }
742
+ function buildThemeSource(accentColor) {
743
+ return [
744
+ `const primary = ${JSON.stringify(accentColor)};`,
745
+ '',
746
+ 'export const Colors = {',
747
+ ' light: {',
748
+ " primary,",
749
+ " background: '#f8fafc',",
750
+ " surface: '#ffffff',",
751
+ " border: '#dbe2ea',",
752
+ " text: '#0f172a',",
753
+ " textSecondary: '#475569',",
754
+ ' },',
755
+ ' dark: {',
756
+ " primary: '#60a5fa',",
757
+ " background: '#0f172a',",
758
+ " surface: '#1e293b',",
759
+ " border: '#334155',",
760
+ " text: '#f8fafc',",
761
+ " textSecondary: '#cbd5e1',",
762
+ ' },',
763
+ '};',
764
+ '',
765
+ 'export type ThemeName = keyof typeof Colors;',
766
+ '',
767
+ ].join('\n');
768
+ }
769
+ function buildUseColorSchemeWebSource() {
770
+ return [
771
+ "import { useEffect, useState } from 'react';",
772
+ "import { useColorScheme as useNativeColorScheme } from 'react-native';",
773
+ '',
774
+ 'export function useColorScheme() {',
775
+ ' const [hydrated, setHydrated] = useState(false);',
776
+ '',
777
+ ' useEffect(() => {',
778
+ ' setHydrated(true);',
779
+ ' }, []);',
780
+ '',
781
+ ' const colorScheme = useNativeColorScheme();',
782
+ " return hydrated ? colorScheme : 'light';",
783
+ '}',
784
+ '',
785
+ ].join('\n');
786
+ }
787
+ function buildUseThemeSource() {
788
+ return [
789
+ "import { Colors } from '@/constants/theme';",
790
+ "import { useColorScheme } from '@/hooks/use-color-scheme';",
791
+ '',
792
+ 'export function useTheme() {',
793
+ " const scheme = useColorScheme() === 'dark' ? 'dark' : 'light';",
794
+ ' return {',
795
+ ' name: scheme,',
796
+ ' colors: Colors[scheme],',
797
+ ' };',
798
+ '}',
799
+ '',
800
+ ].join('\n');
123
801
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-wizze-app",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Gerador oficial de projetos do Wizze Framework.",
5
5
  "author": "Master Dev (Taliton Silva)",
6
6
  "license": "MIT",