novatec-cli 3.0.1 → 3.0.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/lib/create.js +106 -195
- package/lib/doctor.js +1 -7
- package/lib/generators/backend.js +203 -132
- package/lib/generators/deploy.js +17 -9
- package/lib/generators/frontend.js +8 -57
- package/lib/prompts.js +54 -130
- package/lib/utils.js +12 -10
- package/package.json +1 -1
package/lib/create.js
CHANGED
|
@@ -2,154 +2,99 @@ import path from 'path';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import fse from 'fs-extra';
|
|
4
4
|
import boxen from 'boxen';
|
|
5
|
-
import {
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
6
|
import { askProjectOptions } from './prompts.js';
|
|
7
7
|
import { generateFrontend } from './generators/frontend.js';
|
|
8
8
|
import { generateBackend } from './generators/backend.js';
|
|
9
|
-
import { generateBusiness } from './generators/business.js';
|
|
10
|
-
import { generateDocker } from './generators/docker.js';
|
|
11
|
-
import { generateSecurity } from './generators/security.js';
|
|
12
|
-
import { generateDeploy } from './generators/deploy.js';
|
|
13
9
|
import { isInstalled } from './utils.js';
|
|
14
10
|
|
|
15
|
-
function bar(cur, tot, label) {
|
|
16
|
-
const w = 28, f = Math.round((cur / tot) * w);
|
|
17
|
-
const pct = Math.round((cur / tot) * 100);
|
|
18
|
-
process.stdout.write(`\r [${'█'.repeat(f)}${'░'.repeat(w - f)}] ${chalk.white(pct + '%')} ${chalk.gray(label.substring(0, 30))} `);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
11
|
export async function runCreate(nameArg, options) {
|
|
22
12
|
const t0 = Date.now();
|
|
23
13
|
const config = await askProjectOptions(nameArg, options);
|
|
24
14
|
const root = path.resolve(process.cwd(), config.name);
|
|
25
15
|
await fse.ensureDir(root);
|
|
26
16
|
|
|
27
|
-
const extras = config.extras || [];
|
|
28
17
|
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
29
18
|
const pm = config.pm || 'npm';
|
|
30
19
|
|
|
31
|
-
// Calcular pasos
|
|
32
|
-
const steps = ['Frontend', 'Backend', 'README', 'Env files',
|
|
33
|
-
config.mode === 'business' && 'Business mode',
|
|
34
|
-
extras.includes('docker') && 'Docker',
|
|
35
|
-
extras.includes('security') && 'Seguridad',
|
|
36
|
-
extras.includes('ci') && 'GitHub Actions',
|
|
37
|
-
extras.includes('eslint') && 'ESLint',
|
|
38
|
-
extras.includes('testing') && 'Testing',
|
|
39
|
-
extras.includes('swagger') && 'Swagger',
|
|
40
|
-
extras.includes('logs') && 'Logs',
|
|
41
|
-
extras.includes('seo') && 'SEO',
|
|
42
|
-
extras.includes('git') && 'Git',
|
|
43
|
-
config.install && 'Instalando deps',
|
|
44
|
-
].filter(Boolean);
|
|
45
|
-
|
|
46
20
|
console.log();
|
|
47
|
-
let step = 0;
|
|
48
|
-
const tick = (label) => { step++; bar(step, steps.length, label); };
|
|
49
21
|
|
|
50
22
|
// 1. Frontend
|
|
51
|
-
|
|
23
|
+
console.log(chalk.white(' [1/4] Generando frontend...'));
|
|
52
24
|
await generateFrontend(config, root);
|
|
53
|
-
await connectFrontendToBackend(config, root);
|
|
54
25
|
|
|
55
|
-
// 2. Backend
|
|
56
|
-
|
|
26
|
+
// 2. Backend
|
|
27
|
+
console.log(chalk.white(' [2/4] Generando backend...'));
|
|
57
28
|
await generateBackend(config, root);
|
|
58
|
-
await applyArchitecture(config, root);
|
|
59
29
|
|
|
60
|
-
// 3.
|
|
61
|
-
|
|
62
|
-
await
|
|
30
|
+
// 3. Arquitectura
|
|
31
|
+
console.log(chalk.white(' [3/4] Aplicando arquitectura...'));
|
|
32
|
+
await applyArchitecture(config, root);
|
|
63
33
|
|
|
64
|
-
// 4. .env
|
|
65
|
-
|
|
34
|
+
// 4. Archivos base (.env, README)
|
|
35
|
+
console.log(chalk.white(' [4/4] Generando archivos base...'));
|
|
66
36
|
await generateEnv(config, root);
|
|
37
|
+
await generateReadme(config, root);
|
|
67
38
|
|
|
68
|
-
// 5.
|
|
69
|
-
if (config.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (extras.includes('docker')) {
|
|
76
|
-
tick('Docker...');
|
|
77
|
-
await generateDocker(config, root);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 7. Seguridad
|
|
81
|
-
if (extras.includes('security')) {
|
|
82
|
-
tick('Seguridad...');
|
|
83
|
-
await generateSecurity(config, root);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 8. GitHub Actions
|
|
87
|
-
if (extras.includes('ci')) {
|
|
88
|
-
tick('GitHub Actions...');
|
|
89
|
-
await generateDeploy(config, root);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 9. ESLint
|
|
93
|
-
if (extras.includes('eslint')) {
|
|
94
|
-
tick('ESLint...');
|
|
95
|
-
await setupEslint(root);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 10. Testing
|
|
99
|
-
if (extras.includes('testing')) {
|
|
100
|
-
tick('Testing...');
|
|
101
|
-
await setupTesting(root);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 11. Swagger (ya incluido en security/backend, solo notificar)
|
|
105
|
-
if (extras.includes('swagger')) {
|
|
106
|
-
tick('Swagger...');
|
|
107
|
-
// generado en generateSecurity
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 12. Logs
|
|
111
|
-
if (extras.includes('logs')) {
|
|
112
|
-
tick('Logs...');
|
|
113
|
-
await setupLogs(config, root);
|
|
114
|
-
}
|
|
39
|
+
// 5. Instalar dependencias
|
|
40
|
+
if (config.install) {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(chalk.white(' Instalando dependencias...'));
|
|
43
|
+
const feDir = path.join(root, 'frontend');
|
|
44
|
+
const beDir = path.join(root, 'backend');
|
|
45
|
+
const installCmd = pm === 'yarn' ? 'yarn' : `${pm} install`;
|
|
115
46
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
47
|
+
if (await fse.pathExists(path.join(feDir, 'package.json'))) {
|
|
48
|
+
try {
|
|
49
|
+
console.log(chalk.gray(` → frontend (${pm} install)`));
|
|
50
|
+
execSync(installCmd, { cwd: feDir, stdio: 'inherit' });
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.log(chalk.yellow(' ⚠ Error instalando frontend: ' + e.message));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
121
55
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
56
|
+
if (!isPython && await fse.pathExists(path.join(beDir, 'package.json'))) {
|
|
57
|
+
try {
|
|
58
|
+
console.log(chalk.gray(` → backend (${pm} install)`));
|
|
59
|
+
execSync(installCmd, { cwd: beDir, stdio: 'inherit' });
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.log(chalk.yellow(' ⚠ Error instalando backend: ' + e.message));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
130
64
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
65
|
+
if (isPython && await fse.pathExists(path.join(beDir, 'requirements.txt'))) {
|
|
66
|
+
if (isInstalled('pip')) {
|
|
67
|
+
try {
|
|
68
|
+
console.log(chalk.gray(' → backend (pip install -r requirements.txt)'));
|
|
69
|
+
execSync('pip install -r requirements.txt', { cwd: beDir, stdio: 'inherit' });
|
|
70
|
+
} catch (e) {
|
|
71
|
+
console.log(chalk.yellow(' ⚠ Error instalando backend Python: ' + e.message));
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
console.log(chalk.yellow(' ⚠ pip no encontrado. Ejecuta manualmente: pip install -r requirements.txt'));
|
|
75
|
+
}
|
|
140
76
|
}
|
|
141
77
|
}
|
|
142
78
|
|
|
143
|
-
bar(steps.length, steps.length, 'Completado');
|
|
144
|
-
console.log('\n');
|
|
145
|
-
|
|
146
79
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
147
|
-
const beCmd = { express:'npm run dev', nestjs:'npm run start:dev', fastify:'npm run dev', hono:'npm run dev', fastapi:'uvicorn main:app --reload', django:'python manage.py runserver', flask:'python run.py', spring:'mvn spring-boot:run', deno:'deno task dev', gin:'go run main.go' };
|
|
148
80
|
|
|
81
|
+
const beCmd = {
|
|
82
|
+
express: 'npm run dev',
|
|
83
|
+
nestjs: 'npm run start:dev',
|
|
84
|
+
fastify: 'npm run dev',
|
|
85
|
+
hono: 'npm run dev',
|
|
86
|
+
fastapi: 'uvicorn main:app --reload',
|
|
87
|
+
django: 'python manage.py runserver',
|
|
88
|
+
flask: 'python run.py',
|
|
89
|
+
spring: 'mvn spring-boot:run',
|
|
90
|
+
deno: 'deno task dev',
|
|
91
|
+
gin: 'go run main.go',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
console.log();
|
|
149
95
|
console.log(boxen(
|
|
150
96
|
chalk.bold.white(' ✔ PROYECTO LISTO — NovaTec CLI\n\n') +
|
|
151
97
|
chalk.gray(' Nombre │ ') + chalk.white(config.name) + '\n' +
|
|
152
|
-
chalk.gray(' Modo │ ') + chalk.white(config.mode || 'estándar') + '\n' +
|
|
153
98
|
chalk.gray(' Frontend │ ') + chalk.white(config.frontend) + (config.typescript ? chalk.gray(' + TypeScript') : '') + '\n' +
|
|
154
99
|
chalk.gray(' Backend │ ') + chalk.white(config.backend) + '\n' +
|
|
155
100
|
chalk.gray(' Base datos │ ') + chalk.white(config.db || 'ninguna') + '\n' +
|
|
@@ -157,49 +102,20 @@ export async function runCreate(nameArg, options) {
|
|
|
157
102
|
chalk.gray(' Tiempo │ ') + chalk.white(elapsed + 's') + '\n\n' +
|
|
158
103
|
chalk.gray(' ─────────────────────────────────────────────\n\n') +
|
|
159
104
|
chalk.white(' # Frontend\n') +
|
|
160
|
-
chalk.gray(` cd ${config.name}/frontend\n`) +
|
|
161
|
-
chalk.gray(` ${pm} ${pm === 'yarn' ? '' : 'run '}dev\n\n`) +
|
|
105
|
+
chalk.gray(` cd ${config.name}/frontend && ${pm} run dev\n\n`) +
|
|
162
106
|
chalk.white(' # Backend\n') +
|
|
163
|
-
chalk.gray(` cd ${config.name}/backend
|
|
164
|
-
chalk.gray(` ${beCmd[config.backend] || pm + ' run dev'}\n`) +
|
|
165
|
-
(config.mode === 'business' ? '\n' + chalk.gray(' # Dashboard → http://localhost:5173/dashboard\n') + chalk.gray(' # API Docs → http://localhost:3001/api-docs') : ''),
|
|
107
|
+
chalk.gray(` cd ${config.name}/backend && ${beCmd[config.backend] || pm + ' run dev'}`),
|
|
166
108
|
{ padding: 1, margin: { top: 1, left: 2 }, borderStyle: 'round', borderColor: 'white', dimBorder: true }
|
|
167
109
|
));
|
|
168
110
|
|
|
169
111
|
// Abrir VS Code
|
|
170
112
|
if (isInstalled('code')) {
|
|
171
|
-
try {
|
|
113
|
+
try { execSync(`code "${root}"`, { stdio: 'ignore' }); } catch {}
|
|
172
114
|
console.log(' ' + chalk.gray('VS Code abierto ✔'));
|
|
173
115
|
}
|
|
174
116
|
console.log();
|
|
175
117
|
}
|
|
176
118
|
|
|
177
|
-
// ── CONEXIÓN FE → BE ──────────────────────────────────────────────────────────
|
|
178
|
-
async function connectFrontendToBackend(config, root) {
|
|
179
|
-
const feDir = path.join(root, 'frontend');
|
|
180
|
-
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
181
|
-
const bePort = isPython ? '8000' : '3001';
|
|
182
|
-
|
|
183
|
-
// Archivo de API client
|
|
184
|
-
const apiClient = config.db === 'supabase'
|
|
185
|
-
? `import { createClient } from '@supabase/supabase-js';\n\nexport const supabase = createClient(\n import.meta.env.VITE_SUPABASE_URL,\n import.meta.env.VITE_SUPABASE_ANON_KEY\n);\n\nexport const api = {\n get: (url) => fetch(import.meta.env.VITE_API_URL + url).then(r => r.json()),\n post: (url, data) => fetch(import.meta.env.VITE_API_URL + url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()),\n put: (url, data) => fetch(import.meta.env.VITE_API_URL + url, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(r => r.json()),\n delete: (url) => fetch(import.meta.env.VITE_API_URL + url, { method: 'DELETE' }).then(r => r.json()),\n};\n`
|
|
186
|
-
: `const BASE = import.meta.env.VITE_API_URL || 'http://localhost:${bePort}';\n\nconst headers = () => ({\n 'Content-Type': 'application/json',\n ...(localStorage.getItem('token') ? { Authorization: \`Bearer \${localStorage.getItem('token')}\` } : {}),\n});\n\nexport const api = {\n get: (url) => fetch(BASE + url, { headers: headers() }).then(r => r.json()),\n post: (url, data) => fetch(BASE + url, { method: 'POST', headers: headers(), body: JSON.stringify(data) }).then(r => r.json()),\n put: (url, data) => fetch(BASE + url, { method: 'PUT', headers: headers(), body: JSON.stringify(data) }).then(r => r.json()),\n delete: (url) => fetch(BASE + url, { method: 'DELETE', headers: headers() }).then(r => r.json()),\n};\n`;
|
|
187
|
-
|
|
188
|
-
const srcDir = path.join(feDir, 'src', 'lib');
|
|
189
|
-
await fse.ensureDir(srcDir);
|
|
190
|
-
await fse.writeFile(path.join(srcDir, 'api.js'), apiClient);
|
|
191
|
-
|
|
192
|
-
// Proxy en vite.config si aplica
|
|
193
|
-
const viteConfig = path.join(feDir, 'vite.config.js');
|
|
194
|
-
if (await fse.pathExists(viteConfig)) {
|
|
195
|
-
const content = await fse.readFile(viteConfig, 'utf8');
|
|
196
|
-
if (!content.includes('proxy')) {
|
|
197
|
-
const proxy = `\n server: {\n proxy: {\n '/api': {\n target: 'http://localhost:${bePort}',\n changeOrigin: true,\n },\n },\n },`;
|
|
198
|
-
await fse.writeFile(viteConfig, content.replace('plugins:', proxy + '\n plugins:'));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
119
|
// ── ARQUITECTURA ──────────────────────────────────────────────────────────────
|
|
204
120
|
async function applyArchitecture(config, root) {
|
|
205
121
|
const beDir = path.join(root, 'backend', 'src');
|
|
@@ -217,72 +133,67 @@ async function applyArchitecture(config, root) {
|
|
|
217
133
|
}
|
|
218
134
|
}
|
|
219
135
|
|
|
220
|
-
// ── ENV
|
|
136
|
+
// ── .ENV ──────────────────────────────────────────────────────────────────────
|
|
221
137
|
async function generateEnv(config, root) {
|
|
222
138
|
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
139
|
+
const bePort = isPython ? '8000' : '3001';
|
|
140
|
+
|
|
223
141
|
const dbUrl = {
|
|
224
142
|
postgres: 'postgresql://user:password@localhost:5432/mydb',
|
|
225
143
|
mysql: 'mysql://user:password@localhost:3306/mydb',
|
|
226
144
|
mongo: 'mongodb://localhost:27017/mydb',
|
|
227
145
|
sqlite: 'file:./dev.db',
|
|
228
|
-
supabase:
|
|
146
|
+
supabase: 'https://your-project.supabase.co',
|
|
229
147
|
}[config.db] || '';
|
|
230
148
|
|
|
231
|
-
const feEnv = `VITE_API_URL=http://localhost:${
|
|
232
|
-
|
|
149
|
+
const feEnv = `VITE_API_URL=http://localhost:${bePort}\n`;
|
|
150
|
+
const beEnv = isPython
|
|
151
|
+
? `DATABASE_URL=${dbUrl}\nSECRET_KEY=change_me_in_production\nDEBUG=True\n`
|
|
152
|
+
: `PORT=3001\nNODE_ENV=development\nJWT_SECRET=change_me_in_production\n${dbUrl ? 'DATABASE_URL=' + dbUrl + '\n' : ''}`;
|
|
233
153
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
(config.db === 'supabase' ? `SUPABASE_URL=${config.supabaseUrl || ''}\nSUPABASE_KEY=${config.supabaseKey || ''}\n` : '') +
|
|
237
|
-
(config.extras?.includes('stripe') ? 'STRIPE_SECRET_KEY=sk_test_...\nSTRIPE_WEBHOOK_SECRET=whsec_...\n' : '');
|
|
154
|
+
const feDir = path.join(root, 'frontend');
|
|
155
|
+
const beDir = path.join(root, 'backend');
|
|
238
156
|
|
|
239
|
-
await fse.
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
157
|
+
if (await fse.pathExists(feDir)) {
|
|
158
|
+
await fse.writeFile(path.join(feDir, '.env'), feEnv);
|
|
159
|
+
await fse.writeFile(path.join(feDir, '.env.example'), feEnv);
|
|
160
|
+
}
|
|
161
|
+
if (await fse.pathExists(beDir)) {
|
|
162
|
+
await fse.writeFile(path.join(beDir, '.env'), beEnv);
|
|
163
|
+
await fse.writeFile(path.join(beDir, '.env.example'), beEnv);
|
|
244
164
|
}
|
|
245
165
|
}
|
|
246
166
|
|
|
247
167
|
// ── README ────────────────────────────────────────────────────────────────────
|
|
248
168
|
async function generateReadme(config, root) {
|
|
249
169
|
const ts = config.typescript ? ' + TypeScript' : '';
|
|
250
|
-
const content =
|
|
170
|
+
const content =
|
|
171
|
+
`# ${config.name}
|
|
172
|
+
|
|
173
|
+
> Generado con [NovaTec CLI](https://www.npmjs.com/package/novatec-cli)
|
|
174
|
+
|
|
175
|
+
## Stack
|
|
176
|
+
|
|
177
|
+
| Capa | Tecnología |
|
|
178
|
+
|------|------------|
|
|
179
|
+
| Frontend | **${config.frontend}**${ts} |
|
|
180
|
+
| Backend | **${config.backend}** |
|
|
181
|
+
| Base de datos | **${config.db || 'N/A'}** |
|
|
182
|
+
| Arquitectura | **${config.architecture || 'mvc'}** |
|
|
183
|
+
|
|
184
|
+
## Inicio rápido
|
|
185
|
+
|
|
186
|
+
\`\`\`bash
|
|
187
|
+
# Frontend
|
|
188
|
+
cd ${config.name}/frontend
|
|
189
|
+
npm install
|
|
190
|
+
npm run dev
|
|
191
|
+
|
|
192
|
+
# Backend
|
|
193
|
+
cd ${config.name}/backend
|
|
194
|
+
npm install
|
|
195
|
+
npm run dev
|
|
196
|
+
\`\`\`
|
|
197
|
+
`;
|
|
251
198
|
await fse.writeFile(path.join(root, 'README.md'), content);
|
|
252
199
|
}
|
|
253
|
-
|
|
254
|
-
// ── ESLINT ────────────────────────────────────────────────────────────────────
|
|
255
|
-
async function setupEslint(root) {
|
|
256
|
-
const fe = path.join(root, 'frontend');
|
|
257
|
-
await fse.writeFile(path.join(fe, '.eslintrc.json'), JSON.stringify({ env: { browser: true, es2021: true }, extends: ['eslint:recommended'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, rules: { 'no-unused-vars': 'warn' } }, null, 2));
|
|
258
|
-
await fse.writeFile(path.join(fe, '.prettierrc'), JSON.stringify({ semi: true, singleQuote: true, tabWidth: 2, trailingComma: 'es5' }, null, 2));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// ── TESTING ───────────────────────────────────────────────────────────────────
|
|
262
|
-
async function setupTesting(root) {
|
|
263
|
-
const testDir = path.join(root, 'frontend', 'src', '__tests__');
|
|
264
|
-
await fse.ensureDir(testDir);
|
|
265
|
-
await fse.writeFile(path.join(testDir, 'app.test.js'), `import { describe, it, expect } from 'vitest';\ndescribe('App', () => { it('works', () => expect(1 + 1).toBe(2)); });\n`);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ── LOGS ──────────────────────────────────────────────────────────────────────
|
|
269
|
-
async function setupLogs(config, root) {
|
|
270
|
-
const isPython = ['fastapi', 'django', 'flask'].includes(config.backend);
|
|
271
|
-
if (isPython) return;
|
|
272
|
-
const loggerFile = `import winston from 'winston';\n\nexport const logger = winston.createLogger({\n level: process.env.LOG_LEVEL || 'info',\n format: winston.format.combine(\n winston.format.timestamp(),\n winston.format.colorize(),\n winston.format.printf(({ timestamp, level, message }) => \`\${timestamp} [\${level}]: \${message}\`)\n ),\n transports: [\n new winston.transports.Console(),\n new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),\n new winston.transports.File({ filename: 'logs/combined.log' }),\n ],\n});\n`;
|
|
273
|
-
const logDir = path.join(root, 'backend', 'src', 'utils');
|
|
274
|
-
await fse.ensureDir(logDir);
|
|
275
|
-
await fse.writeFile(path.join(logDir, 'logger.js'), loggerFile);
|
|
276
|
-
await fse.ensureDir(path.join(root, 'backend', 'logs'));
|
|
277
|
-
await fse.writeFile(path.join(root, 'backend', 'logs', '.gitkeep'), '');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// ── SEO ───────────────────────────────────────────────────────────────────────
|
|
281
|
-
async function setupSEO(config, root) {
|
|
282
|
-
const publicDir = path.join(root, 'frontend', 'public');
|
|
283
|
-
await fse.ensureDir(publicDir);
|
|
284
|
-
await fse.writeFile(path.join(publicDir, 'robots.txt'), `User-agent: *\nAllow: /\nSitemap: https://yourdomain.com/sitemap.xml\n`);
|
|
285
|
-
await fse.writeFile(path.join(publicDir, 'sitemap.xml'), `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url><loc>https://yourdomain.com/</loc><priority>1.0</priority></url>\n</urlset>\n`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const GITIGNORE = `node_modules/\ndist/\n.next/\n.nuxt/\n.svelte-kit/\n.env\n*.log\nlogs/\n__pycache__/\n.venv/\n*.pyc\ntarget/\n*.class\n.DS_Store\n`;
|
package/lib/doctor.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import boxen from 'boxen';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
function check(cmd, args = ['--version']) {
|
|
6
|
-
const r = spawnSync(cmd, args, { stdio: 'pipe', shell: true, encoding: 'utf8' });
|
|
7
|
-
if (r.status !== 0) return null;
|
|
8
|
-
return (r.stdout || r.stderr || '').trim().split('\n')[0];
|
|
9
|
-
}
|
|
3
|
+
import { execSync } from 'child_process';
|
|
10
4
|
|
|
11
5
|
function getVersion(cmd) {
|
|
12
6
|
try { return execSync(`${cmd} --version`, { encoding: 'utf8', stdio: 'pipe' }).trim().split('\n')[0]; } catch { return null; }
|