create-fleetbo-project 1.2.30 → 1.2.31
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/install-react-template.js +1 -13
- package/package.json +1 -1
- package/recov.js +296 -0
|
@@ -5,14 +5,12 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const https = require('https');
|
|
7
7
|
|
|
8
|
-
// --- Configuration ---
|
|
9
8
|
const repoOwner = 'FleetFleetbo';
|
|
10
9
|
const repoName = 'dev.fleetbo.io';
|
|
11
10
|
const branchName = 'master';
|
|
12
11
|
const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/${branchName}.tar.gz`;
|
|
13
12
|
const bootstrapUrl = 'https://us-central1-myapp-259bf.cloudfunctions.net/bootstrapProject';
|
|
14
13
|
|
|
15
|
-
// --- LE CONTENU DU CLI (Version avec Email Dynamique) ---
|
|
16
14
|
const CLI_SCRIPT_CONTENT = `#!/usr/bin/env node
|
|
17
15
|
|
|
18
16
|
const { spawn, execSync } = require('child_process');
|
|
@@ -60,7 +58,7 @@ function killProcessOnPort(port) {
|
|
|
60
58
|
function killNetworkService() {
|
|
61
59
|
try {
|
|
62
60
|
const cmd = process.platform === 'win32' ? 'taskkill /IM cloudflared.exe /F' : 'pkill cloudflared';
|
|
63
|
-
execSync(cmd + ' ' + NULL_DEV);
|
|
61
|
+
execSync(cmd + ' ' + NULL_DEV);
|
|
64
62
|
} catch (e) {}
|
|
65
63
|
}
|
|
66
64
|
|
|
@@ -93,7 +91,6 @@ async function syncFirebase(keyApp, networkUrl, testerEmail) {
|
|
|
93
91
|
async function runDevEnvironment() {
|
|
94
92
|
console.log(\`[Fleetbo] 🛡️ Initializing Developer Environment on \${os.platform()}...\`);
|
|
95
93
|
|
|
96
|
-
// 1. NETTOYAGE PRÉVENTIF
|
|
97
94
|
killNetworkService();
|
|
98
95
|
killProcessOnPort(PORT);
|
|
99
96
|
|
|
@@ -111,7 +108,6 @@ async function runDevEnvironment() {
|
|
|
111
108
|
process.exit(1);
|
|
112
109
|
}
|
|
113
110
|
|
|
114
|
-
// 2. DÉMARRAGE DU SERVEUR LOCAL
|
|
115
111
|
console.log(\`[Fleetbo] 📦 Starting Local Server...\`);
|
|
116
112
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
117
113
|
|
|
@@ -133,11 +129,9 @@ async function runDevEnvironment() {
|
|
|
133
129
|
connectionStarted = true;
|
|
134
130
|
console.log(\`\\n[Fleetbo] 🔗 Local Server Ready. Establishing Secure Uplink...\`);
|
|
135
131
|
|
|
136
|
-
// 3. DÉMARRAGE DE LA LIAISON (UPLINK)
|
|
137
132
|
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
138
133
|
const uplink = spawn(npxCmd, ['cloudflared', 'tunnel', '--url', \`http://localhost:\${PORT}\`], { shell: true });
|
|
139
134
|
|
|
140
|
-
// Capture de l'URL (Silencieux)
|
|
141
135
|
uplink.stderr.on('data', (chunk) => {
|
|
142
136
|
const log = chunk.toString();
|
|
143
137
|
|
|
@@ -162,7 +156,6 @@ async function runDevEnvironment() {
|
|
|
162
156
|
runDevEnvironment();
|
|
163
157
|
`;
|
|
164
158
|
|
|
165
|
-
// --- Analyse des Arguments ---
|
|
166
159
|
const args = process.argv.slice(2);
|
|
167
160
|
const projectNameArg = args.find(arg => !arg.startsWith('--'));
|
|
168
161
|
const tokenArg = args.find(arg => arg.startsWith('--token='));
|
|
@@ -179,7 +172,6 @@ if (!projectNameArg || !bootstrapTokenArg || !userEmailArg) {
|
|
|
179
172
|
const projectName = projectNameArg;
|
|
180
173
|
const projectDir = path.join(process.cwd(), projectName);
|
|
181
174
|
|
|
182
|
-
// --- Fonctions Utilitaires ---
|
|
183
175
|
function fetchProjectKeys(token) {
|
|
184
176
|
return new Promise((resolve, reject) => {
|
|
185
177
|
const postData = JSON.stringify({ token });
|
|
@@ -226,7 +218,6 @@ function downloadEngine(url, dest) {
|
|
|
226
218
|
});
|
|
227
219
|
}
|
|
228
220
|
|
|
229
|
-
// --- Fonction Principale ---
|
|
230
221
|
async function setupProject() {
|
|
231
222
|
console.log(`\n⚡ Initializing Fleetbo Framework for "${projectName}"...`);
|
|
232
223
|
|
|
@@ -261,19 +252,16 @@ async function setupProject() {
|
|
|
261
252
|
`;
|
|
262
253
|
|
|
263
254
|
fs.writeFileSync(path.join(projectDir, '.env'), envContent, 'utf8');
|
|
264
|
-
// On s'assure que le dossier scripts existe (normalement oui via le tar.gz, mais sécurité d'abord)
|
|
265
255
|
const scriptsDir = path.join(projectDir, 'scripts');
|
|
266
256
|
if (!fs.existsSync(scriptsDir)) {
|
|
267
257
|
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
268
258
|
}
|
|
269
259
|
fs.writeFileSync(path.join(scriptsDir, 'cli.js'), CLI_SCRIPT_CONTENT, 'utf8');
|
|
270
|
-
// On rend le script exécutable (Optionnel mais propre sur Mac/Linux)
|
|
271
260
|
try { fs.chmodSync(path.join(scriptsDir, 'cli.js'), '755'); } catch (e) {}
|
|
272
261
|
|
|
273
262
|
console.log(' [5/6] 📚 Installing dependencies...');
|
|
274
263
|
execSync('npm install', { stdio: 'inherit' });
|
|
275
264
|
|
|
276
|
-
// Installation silencieuse
|
|
277
265
|
execSync('npm install cloudflared dotenv axios --save-dev', { stdio: 'ignore' });
|
|
278
266
|
|
|
279
267
|
console.log(' [6/6] ✨ Finalizing setup...');
|
package/package.json
CHANGED
package/recov.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
|
|
8
|
+
// --- Configuration ---
|
|
9
|
+
const repoOwner = 'FleetFleetbo';
|
|
10
|
+
const repoName = 'dev.fleetbo.io';
|
|
11
|
+
const branchName = 'master';
|
|
12
|
+
const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/${branchName}.tar.gz`;
|
|
13
|
+
const bootstrapUrl = 'https://us-central1-myapp-259bf.cloudfunctions.net/bootstrapProject';
|
|
14
|
+
|
|
15
|
+
// --- LE CONTENU DU CLI (Version avec Email Dynamique) ---
|
|
16
|
+
const CLI_SCRIPT_CONTENT = `#!/usr/bin/env node
|
|
17
|
+
|
|
18
|
+
const { spawn, execSync } = require('child_process');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const axios = require('axios');
|
|
22
|
+
const dotenv = require('dotenv');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
const command = args[0];
|
|
27
|
+
const GENERATOR_COMMANDS = ['page', 'g', 'generate'];
|
|
28
|
+
if (GENERATOR_COMMANDS.includes(command)) {
|
|
29
|
+
try {
|
|
30
|
+
require('./page.js');
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('\\x1b[31m%s\\x1b[0m', ' CRITICAL ERROR in Fleetbo Generator:');
|
|
33
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
34
|
+
console.error(". The generator script (scripts/page.js) is missing.");
|
|
35
|
+
console.error(" Please update your Fleetbo SDK.");
|
|
36
|
+
} else {
|
|
37
|
+
console.error(error.message);
|
|
38
|
+
}
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const UPDATE_NETWORK_URL = 'https://us-central1-myapp-259bf.cloudfunctions.net/updateDeveloperNetwork';
|
|
45
|
+
const PORT = 3000;
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
const NULL_DEV = process.platform === 'win32' ? '>nul 2>&1' : '2>/dev/null';
|
|
49
|
+
function killProcessOnPort(port) {
|
|
50
|
+
try {
|
|
51
|
+
if (process.platform !== 'win32') {
|
|
52
|
+
const pid = execSync(\`lsof -ti:\${port} \${NULL_DEV}\`).toString().trim();
|
|
53
|
+
if (pid) {
|
|
54
|
+
execSync(\`kill -9 \${pid.split('\\n').join(' ')} \${NULL_DEV}\`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function killNetworkService() {
|
|
61
|
+
try {
|
|
62
|
+
const cmd = process.platform === 'win32' ? 'taskkill /IM cloudflared.exe /F' : 'pkill cloudflared';
|
|
63
|
+
execSync(cmd + ' ' + NULL_DEV); // Utilisation de la variable adaptée
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function cleanupAndExit(code = 0) {
|
|
68
|
+
console.log('\\n[Fleetbo] 🛑 Stopping development environment...');
|
|
69
|
+
killNetworkService();
|
|
70
|
+
killProcessOnPort(PORT);
|
|
71
|
+
process.exit(code);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
process.on('SIGINT', () => cleanupAndExit(0));
|
|
75
|
+
process.on('SIGTERM', () => cleanupAndExit(0));
|
|
76
|
+
|
|
77
|
+
async function syncFirebase(keyApp, networkUrl, testerEmail) {
|
|
78
|
+
try {
|
|
79
|
+
await axios.post(UPDATE_NETWORK_URL, {
|
|
80
|
+
keyApp,
|
|
81
|
+
networkUrl,
|
|
82
|
+
tester: testerEmail
|
|
83
|
+
});
|
|
84
|
+
console.log('\\n[Fleetbo] ---------------------------------------------------');
|
|
85
|
+
console.log(\`[Fleetbo] ✅ Uplink Status: Online for \${testerEmail}\`);
|
|
86
|
+
console.log(\`[Fleetbo] 🚀 Simulator: https://fleetbo.io/studio/view/\${keyApp}\`);
|
|
87
|
+
console.log('[Fleetbo] ---------------------------------------------------\\n');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(\`[Fleetbo] ⚠️ Sync Error: \${err.message}\`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function runDevEnvironment() {
|
|
94
|
+
console.log(\`[Fleetbo] 🛡️ Initializing Developer Environment on \${os.platform()}...\`);
|
|
95
|
+
|
|
96
|
+
// 1. NETTOYAGE PRÉVENTIF
|
|
97
|
+
killNetworkService();
|
|
98
|
+
killProcessOnPort(PORT);
|
|
99
|
+
|
|
100
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
101
|
+
if (!fs.existsSync(envPath)) {
|
|
102
|
+
console.error('Error: Configuration file not found.');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
dotenv.config({ path: envPath });
|
|
106
|
+
const keyApp = process.env.REACT_KEY_APP;
|
|
107
|
+
const testerEmail = process.env.REACT_APP_TESTER_EMAIL;
|
|
108
|
+
|
|
109
|
+
if (!testerEmail) {
|
|
110
|
+
console.error('Error: REACT_APP_TESTER_EMAIL missing in .env');
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 2. DÉMARRAGE DU SERVEUR LOCAL
|
|
115
|
+
console.log(\`[Fleetbo] 📦 Starting Local Server...\`);
|
|
116
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
117
|
+
|
|
118
|
+
const devServer = spawn(npmCmd, ['start'], {
|
|
119
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
120
|
+
shell: true,
|
|
121
|
+
env: { ...process.env, BROWSER: 'none', PORT: PORT.toString() }
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
devServer.stdout.pipe(process.stdout);
|
|
125
|
+
devServer.stderr.pipe(process.stderr);
|
|
126
|
+
|
|
127
|
+
let connectionStarted = false;
|
|
128
|
+
|
|
129
|
+
devServer.stdout.on('data', (data) => {
|
|
130
|
+
const output = data.toString();
|
|
131
|
+
|
|
132
|
+
if (!connectionStarted && (output.includes('Local:') || output.includes('Compiled successfully'))) {
|
|
133
|
+
connectionStarted = true;
|
|
134
|
+
console.log(\`\\n[Fleetbo] 🔗 Local Server Ready. Establishing Secure Uplink...\`);
|
|
135
|
+
|
|
136
|
+
// 3. DÉMARRAGE DE LA LIAISON (UPLINK)
|
|
137
|
+
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
138
|
+
const uplink = spawn(npxCmd, ['cloudflared', 'tunnel', '--url', \`http://localhost:\${PORT}\`], { shell: true });
|
|
139
|
+
|
|
140
|
+
// Capture de l'URL (Silencieux)
|
|
141
|
+
uplink.stderr.on('data', (chunk) => {
|
|
142
|
+
const log = chunk.toString();
|
|
143
|
+
|
|
144
|
+
const match = log.match(/https:\\/\\/[a-zA-Z0-9-]+\\.trycloudflare\\.com/);
|
|
145
|
+
|
|
146
|
+
if (match) {
|
|
147
|
+
const url = match[0];
|
|
148
|
+
if (global.currentUrl !== url) {
|
|
149
|
+
global.currentUrl = url;
|
|
150
|
+
syncFirebase(keyApp, url, testerEmail);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
uplink.on('error', (err) => {
|
|
156
|
+
console.error("[Fleetbo] ❌ Uplink Error. Network component missing.");
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
runDevEnvironment();
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
// --- Analyse des Arguments ---
|
|
166
|
+
const args = process.argv.slice(2);
|
|
167
|
+
const projectNameArg = args.find(arg => !arg.startsWith('--'));
|
|
168
|
+
const tokenArg = args.find(arg => arg.startsWith('--token='));
|
|
169
|
+
const emailArg = args.find(arg => arg.startsWith('--email='));
|
|
170
|
+
|
|
171
|
+
const bootstrapTokenArg = tokenArg ? tokenArg.split('=')[1] : null;
|
|
172
|
+
const userEmailArg = emailArg ? emailArg.split('=')[1] : null;
|
|
173
|
+
|
|
174
|
+
if (!projectNameArg || !bootstrapTokenArg || !userEmailArg) {
|
|
175
|
+
console.error('\n ❌ Usage: npx create-fleetbo-project <name> --token=<token> --email=<email>');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const projectName = projectNameArg;
|
|
180
|
+
const projectDir = path.join(process.cwd(), projectName);
|
|
181
|
+
|
|
182
|
+
// --- Fonctions Utilitaires ---
|
|
183
|
+
function fetchProjectKeys(token) {
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const postData = JSON.stringify({ token });
|
|
186
|
+
const uri = new URL(bootstrapUrl);
|
|
187
|
+
const options = {
|
|
188
|
+
hostname: uri.hostname, path: uri.pathname, method: 'POST',
|
|
189
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) }
|
|
190
|
+
};
|
|
191
|
+
const req = https.request(options, (res) => {
|
|
192
|
+
let data = '';
|
|
193
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
194
|
+
res.on('end', () => {
|
|
195
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
196
|
+
try { resolve(JSON.parse(data)); } catch (e) { reject(new Error('Invalid response')); }
|
|
197
|
+
} else { reject(new Error(`Server error ${res.statusCode}`)); }
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
req.on('error', (e) => reject(e));
|
|
201
|
+
req.write(postData);
|
|
202
|
+
req.end();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function downloadEngine(url, dest) {
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
const uri = new URL(url);
|
|
209
|
+
const options = {
|
|
210
|
+
hostname: uri.hostname, path: uri.pathname + uri.search, method: 'GET',
|
|
211
|
+
headers: { 'User-Agent': 'Fleetbo-CLI-Installer', 'Accept': '*/*' }
|
|
212
|
+
};
|
|
213
|
+
const request = https.get(options, (response) => {
|
|
214
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
215
|
+
if (!response.headers.location) { reject(new Error("Redirect error")); return; }
|
|
216
|
+
downloadEngine(response.headers.location, dest).then(resolve).catch(reject);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (response.statusCode !== 200) { reject(new Error(`Status: ${response.statusCode}`)); return; }
|
|
220
|
+
const file = fs.createWriteStream(dest);
|
|
221
|
+
response.pipe(file);
|
|
222
|
+
file.on('finish', () => file.close(resolve));
|
|
223
|
+
file.on('error', (err) => { fs.unlink(dest, () => {}); reject(err); });
|
|
224
|
+
});
|
|
225
|
+
request.on('error', (err) => reject(err));
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// --- Fonction Principale ---
|
|
230
|
+
async function setupProject() {
|
|
231
|
+
console.log(`\n⚡ Initializing Fleetbo Framework for "${projectName}"...`);
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
if (fs.existsSync(projectDir)) throw new Error(`Directory "${projectName}" already exists.`);
|
|
235
|
+
fs.mkdirSync(projectDir);
|
|
236
|
+
|
|
237
|
+
console.log(' [1/6] 📥 Downloading Fleetbo Core Engine...');
|
|
238
|
+
const archivePath = path.join(projectDir, 'engine.tar.gz');
|
|
239
|
+
await downloadEngine(archiveUrl, archivePath);
|
|
240
|
+
|
|
241
|
+
console.log(' [2/6] 📦 Scaffolding project structure...');
|
|
242
|
+
try {
|
|
243
|
+
execSync(`tar -xf "${archivePath}" -C "${projectDir}" --strip-components=1`, { stdio: 'ignore' });
|
|
244
|
+
fs.unlinkSync(archivePath);
|
|
245
|
+
} catch (e) { throw new Error("Extract failed."); }
|
|
246
|
+
|
|
247
|
+
process.chdir(projectDir);
|
|
248
|
+
|
|
249
|
+
console.log(' [3/6] 🔑 Authenticating with Fleetbo Cloud...');
|
|
250
|
+
const keys = await fetchProjectKeys(bootstrapTokenArg);
|
|
251
|
+
if (!keys.enterpriseId) throw new Error("Invalid keys.");
|
|
252
|
+
|
|
253
|
+
console.log(' [4/6] ⚙️ Configuring environment & CLI...');
|
|
254
|
+
|
|
255
|
+
const envContent = `REACT_APP_FLEETBO_DB_KEY=${keys.fleetboDBKey}
|
|
256
|
+
REACT_APP_ENTERPRISE_ID=${keys.enterpriseId}
|
|
257
|
+
REACT_KEY_APP=${projectName}
|
|
258
|
+
REACT_APP_TESTER_EMAIL=${userEmailArg}
|
|
259
|
+
DANGEROUSLY_DISABLE_HOST_CHECK=true
|
|
260
|
+
WDS_SOCKET_PORT=0
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
fs.writeFileSync(path.join(projectDir, '.env'), envContent, 'utf8');
|
|
264
|
+
// On s'assure que le dossier scripts existe (normalement oui via le tar.gz, mais sécurité d'abord)
|
|
265
|
+
const scriptsDir = path.join(projectDir, 'scripts');
|
|
266
|
+
if (!fs.existsSync(scriptsDir)) {
|
|
267
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
268
|
+
}
|
|
269
|
+
fs.writeFileSync(path.join(scriptsDir, 'cli.js'), CLI_SCRIPT_CONTENT, 'utf8');
|
|
270
|
+
// On rend le script exécutable (Optionnel mais propre sur Mac/Linux)
|
|
271
|
+
try { fs.chmodSync(path.join(scriptsDir, 'cli.js'), '755'); } catch (e) {}
|
|
272
|
+
|
|
273
|
+
console.log(' [5/6] 📚 Installing dependencies...');
|
|
274
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
275
|
+
|
|
276
|
+
// Installation silencieuse
|
|
277
|
+
execSync('npm install cloudflared dotenv axios --save-dev', { stdio: 'ignore' });
|
|
278
|
+
|
|
279
|
+
console.log(' [6/6] ✨ Finalizing setup...');
|
|
280
|
+
const packageJsonPath = path.join(projectDir, 'package.json');
|
|
281
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
282
|
+
packageJson.name = projectName;
|
|
283
|
+
packageJson.scripts = { ...packageJson.scripts, "fleetbo": "node scripts/cli.js", "dev": "node scripts/cli.js" };
|
|
284
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
|
|
285
|
+
|
|
286
|
+
console.log('\n [Fleetbo] ✅ Project successfully created!');
|
|
287
|
+
console.log(`\n👉 Run: cd ${projectName} && npm run fleetbo`);
|
|
288
|
+
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error('\n❌ Setup failed:', error.message);
|
|
291
|
+
if (fs.existsSync(projectDir)) try { fs.rmSync(projectDir, { recursive: true, force: true }); } catch(e){}
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
setupProject();
|