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.
- package/README.md +17 -1
- package/dist/index.js +739 -61
- 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 {
|
|
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 =
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
153
|
+
version: '1.0.0',
|
|
55
154
|
scripts: {
|
|
56
|
-
|
|
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
|
|
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
|
|
272
|
+
function buildTsConfigSource() {
|
|
78
273
|
return [
|
|
79
|
-
'
|
|
80
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
}
|