create-fleetbo-project 1.2.85 → 1.2.87

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.
@@ -1,572 +1,213 @@
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
- const repoOwner = 'FleetFleetbo';
8
- const repoName = 'dev.fleetbo.io';
9
- const branchName = 'master';
10
- const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/${branchName}.tar.gz`;
11
- const bootstrapUrl = 'https://us-central1-myapp-259bf.cloudfunctions.net/bootstrapProject';
12
- const CLI_SCRIPT_CONTENT = `#!/usr/bin/env node
13
- const { spawn, execSync } = require('child_process');
14
- const fs = require('fs');
15
- const path = require('path');
16
- const axios = require('axios');
17
- const dotenv = require('dotenv');
18
- const os = require('os');
19
- const archiver = require('archiver');
20
- const readline = require('readline');
21
- const ANDROID_BUILD_URL = "https://fandroidbuild-jqycakhlxa-uc.a.run.app";
22
- const IOS_BUILD_URL = "https://fiosbuild-jqycakhlxa-uc.a.run.app";
23
- const UPDATE_NETWORK_URL = 'https://updatedevelopernetwork-jqycakhlxa-uc.a.run.app';
24
- const ALEX_ENGINE_URL = "https://generatenativemodule-jqycakhlxa-uc.a.run.app";
25
- const INJECT_DEPS_URL = "https://savegeneratedfile-jqycakhlxa-uc.a.run.app";
26
- const APP_JS_PATH = path.join(process.cwd(), 'src/App.js');
27
- const PORT = 3000;
28
- let uplinkProcess = null;
29
- const args = process.argv.slice(2);
30
- const command = args[0];
31
- process.env.DOTENV_SILENT = 'true';
32
- const envPath = path.join(process.cwd(), '.env');
33
- if (!fs.existsSync(envPath)) {
34
- console.error('\\x1b[31m%s\\x1b[0m', ' Error: Configuration file (.env) not found.');
35
- process.exit(1);
36
- }
37
- dotenv.config({ path: envPath, quiet: true });
38
- const projectId = process.env.REACT_APP_ENTERPRISE_ID;
39
- const keyApp = process.env.REACT_KEY_APP;
40
- const testerEmail = process.env.REACT_APP_TESTER_EMAIL;
41
- const wrapText = (text, maxWidth) => {
42
- if (!text) return "";
43
- const rawLines = text.split('\\n');
44
- let formattedLines = [];
45
- rawLines.forEach(line => {
46
- if (line.trim().length === 0) {
47
- formattedLines.push("");
48
- return;
49
- }
50
- const isSpecialFormat = /^[\\s]*[-*•\\d]/.test(line) || line.startsWith(" ");
51
- if (isSpecialFormat) {
52
- formattedLines.push(line);
53
- } else {
54
- const words = line.split(" ");
55
- let currentLine = words[0];
56
- for (let i = 1; i < words.length; i++) {
57
- if (currentLine.length + 1 + words[i].length <= maxWidth) {
58
- currentLine += " " + words[i];
59
- } else {
60
- formattedLines.push(currentLine);
61
- currentLine = words[i];
62
- }
63
- }
64
- formattedLines.push(currentLine);
65
- }
66
- });
67
- return formattedLines.join('\\n ');
68
- };
69
- const checkGitSecurity = () => {
70
- const gitDir = path.join(process.cwd(), '.git');
71
- const gitignorePath = path.join(process.cwd(), '.gitignore');
72
- if (fs.existsSync(gitDir)) {
73
- if (!fs.existsSync(gitignorePath)) {
74
- console.error('\\n\\x1b[31m🚨 SECURITY ALERT:\\x1b[0m .git detected but no .gitignore found.');
75
- process.exit(1);
76
- }
77
- const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
78
- if (!gitignoreContent.includes('.env')) {
79
- console.error('\\n\\x1b[31m🚨 CRITICAL RISK:\\x1b[0m .env is NOT ignored by Git.');
80
- process.exit(1);
81
- }
82
- }
83
- };
84
- if (!projectId) {
85
- console.error('\\n Error: Project ID missing in .env.\\n');
86
- process.exit(1);
87
- }
88
- const injectRouteIntoAppJs = (moduleName, subPath = '') => {
89
- const appJsPath = path.join(process.cwd(), 'src', 'App.js');
90
- if (!fs.existsSync(appJsPath)) {
91
- console.error(\` \\x1b[31m[Safety Stop]\\x1b[0m App.js missing.\`);
92
- return false;
93
- }
94
-
95
- let content = fs.readFileSync(appJsPath, 'utf8');
96
- const importAnchor = '// FLEETBO_MORE_IMPORTS';
97
- const routeAnchor = '{/* FLEETBO_DYNAMIC ROUTES */}';
98
-
99
- if (!content.includes(importAnchor) || !content.includes(routeAnchor)) {
100
- console.log(\` \\x1b[33m[Skipped]\\x1b[0m Anchors missing in App.js. Manual injection required.\`);
101
- return false;
102
- }
103
-
104
- const cleanSubPath = subPath ? \`\${subPath}/\` : '';
105
- const importLine = \`import \${moduleName} from './app/\${cleanSubPath}\${moduleName}';\`;
106
- const routeLine = \`<Route path="/\${cleanSubPath}\${moduleName.toLowerCase()}" element={<\${moduleName} />} />\`;
107
-
108
- let modified = false;
109
-
110
- if (!content.includes(importLine)) {
111
- content = content.replace(importAnchor, \`\${importLine}\\n\${importAnchor}\`);
112
- modified = true;
113
- }
114
- if (!content.includes(routeLine)) {
115
- content = content.replace(routeAnchor, \`\${routeLine}\\n \${routeAnchor}\`);
116
- modified = true;
117
- }
118
- if (modified) {
119
- fs.writeFileSync(appJsPath, content);
120
- console.log(\` \\x1b[32m[Routed]\\x1b[0m \${moduleName} injected into App.js safely.\`);
121
- }
122
-
123
- return modified;
124
- };
125
- const showEnergyTransfer = async () => {
126
- const width = 30;
127
- for (let i = 0; i <= width; i++) {
128
- const dots = "█".repeat(i); const empty = "░".repeat(width - i);
129
- process.stdout.write(\`\\r \\x1b[32m⚡ Alex Energy Sync:\\x1b[0m [\${dots}\${empty}] \${Math.round((i / width) * 100)}%\`);
130
- await new Promise(r => setTimeout(r, 45));
131
- }
132
- process.stdout.write('\\n');
133
- };
134
- if (command === 'alex') {
135
- checkGitSecurity();
136
- const initialPrompt = args.slice(1).join(' ');
137
- const processAlexRequest = async (prompt) => {
138
- if (prompt.length > 300) {
139
- console.log('\\n\\x1b[31m⛔ [Alex Safety] Request too long (' + prompt.length + '/300 chars).\\x1b[0m');
140
- console.log('\\x1b[90mAlex prefers concise instructions. Please summarize.\\x1b[0m');
141
- return;
142
- }
143
- process.stdout.write('\\x1b[33m🧠 Alex is thinking...\\x1b[0m');
144
- try {
145
- const result = await axios.post(ALEX_ENGINE_URL, { prompt, projectType: 'android' }, {
146
- headers: { 'x-project-id': projectId }
147
- });
148
- const aiData = result.data;
149
- process.stdout.write('\\r' + ' '.repeat(50) + '\\r');
150
- if (aiData.status === 'quota_exceeded') {
151
- console.log(\`\\n\\x1b[31m⛔ ARCHITECT QUOTA REACHED:\\x1b[0m \${aiData.message}\`);
152
- return;
153
- }
154
- if (aiData.status === 'success' || aiData.status === 'message' || aiData.status === 'complex_refusal') {
155
- console.log('');
156
- const rawMsg = aiData.message || "I'm ready.";
157
- const formattedMsg = wrapText(rawMsg, 85);
158
- console.log('\\x1b[32mAlex ❯\\x1b[0m '+formattedMsg);
159
-
160
- if (aiData.remainingConsultations !== undefined) {
161
- const remaining = aiData.remainingConsultations;
162
- const limit = aiData.consultationLimit || 7;
163
- const tierLabel = aiData.tier === 'senior' ? 'SENIOR' : aiData.tier === 'expert' ? 'EXPERT' : 'JUNIOR';
164
- const percent = Math.round((remaining / limit) * 100);
165
- const energyColor = percent > 20 ? '\\x1b[32m' : '\\x1b[31m';
166
- console.log(\`\\x1b[36m⚡ Architect Fuel:\\x1b[0m \${energyColor}\${percent}%\\x1b[0m (\${remaining}/\${limit} instructions left) [\${tierLabel}]\`);
167
- console.log('');
168
- }
169
- }
170
- if (aiData.status === 'success' && aiData.moduleData) {
171
- const { fileName, code, mockFileName, mockCode, moduleName, instructions, config_offload } = aiData.moduleData;
172
- console.log(\` \\x1b[90m Architecting: \${moduleName}\\x1b[0m\`);
173
- const writeFile = (dir, name, content) => {
174
- const fullPath = path.join(process.cwd(), dir);
175
- const filePath = path.join(fullPath, name);
176
- if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
177
- fs.writeFileSync(filePath, content);
178
- console.log(\` \\x1b[32m[Written]\\x1b[0m \${dir}\${name}\`);
179
- };
180
- if (instructions && Array.isArray(instructions) && instructions.length > 0) {
181
- console.log('\\n\\x1b[33m--- GUIDE (MCI) ---\\x1b[0m');
182
- instructions.forEach(line => {
183
- if (typeof line === 'string') {
184
- const formattedLine = line.replace(/ACTION|CAPTURE|PERSPECTIVE/g, '\\x1b[1m$&\\x1b[0m');
185
- console.log(\` \${formattedLine}\`);
186
- }
187
- });
188
- }
189
- if (code && fileName) {
190
- const folder = fileName.endsWith('.kt') ? 'public/native/android/' : 'src/app/';
191
- writeFile(folder, fileName, code);
192
- if (fileName.endsWith('.jsx')) injectRouteIntoAppJs(fileName.replace('.jsx', ''));
193
- }
194
- if (mockCode && mockFileName) {
195
- const pageName = mockFileName.replace('.jsx', '');
196
- writeFile('src/app/mocks/', mockFileName, mockCode);
197
- const injected = injectRouteIntoAppJs(pageName, 'mocks');
198
- if (injected) {
199
- console.log(\` \\x1b[32m[Routed]\\x1b[0m App.js -> /mocks/\${pageName.toLowerCase()}\`);
200
- }
201
- }
202
- if (config_offload && (config_offload.dependencies?.length > 0 || config_offload.permissions?.length > 0)) {
203
- process.stdout.write(\` \\x1b[33m[Cloud Inject]\\x1b[0m Syncing \${config_offload.dependencies.length} libs to Factory...\`);
204
- try {
205
- await axios.post(INJECT_DEPS_URL, {
206
- projectId: projectId,
207
- fileData: {
208
- path: fileName,
209
- config_offload: config_offload
210
- }
211
- });
212
- process.stdout.write(\` \\x1b[32mOK\\x1b[0m\\n\`);
213
- } catch (err) {
214
- process.stdout.write(\` \\x1b[31mFAILED\\x1b[0m\\n\`);
215
- console.error(\` ⚠️ Config sync failed: \${err.message}\`);
216
- }
217
- }
218
- }
219
- } catch (error) {
220
- process.stdout.write('\\r' + ' '.repeat(50) + '\\r');
221
- console.error('\\n\\x1b[31m Alex Error:\\x1b[0m ' + (error.response?.data?.message || error.message));
222
- }
223
- };
224
- const startAlexSession = async () => {
225
- process.stdout.write('\\x1b[33m🛡️ Alex is checking runtime state...\\x1b[0m\\r');
226
- let attempts = 0;
227
- const maxAttempts = 5;
228
- let isReady = false;
229
- let dynamicUsername = 'Pilot';
230
- while (attempts < maxAttempts && !isReady) {
231
- try {
232
- const validation = await axios.post(ALEX_ENGINE_URL, {
233
- prompt: "ping", validateProject: true, checkNetwork: true, projectKey: keyApp
234
- }, { headers: { 'x-project-id': projectId }, timeout: 5000 });
235
-
236
- if (validation.data?.isRunning) { isReady = true; dynamicUsername = validation.data.username || 'Pilot'; break; }
237
- attempts++;
238
- if (attempts < maxAttempts) await new Promise(r => setTimeout(r, 2000));
239
- } catch (error) {
240
- attempts++;
241
- await new Promise(r => setTimeout(r, 2000));
242
- }
243
- }
244
- if (!isReady) {
245
- console.error('\\n\\x1b[31m⚠️ ENGINE OFFLINE:\\x1b[0m Start Fleetbo runtime first: "npm run fleetbo" ');
246
- console.error(\`\\x1b[90m(Ensure you are running the runtime for project: \${keyApp})\\x1b[0m\`);
247
- process.exit(1);
248
- }
249
- process.stdout.write(' '.repeat(60) + '\\r');
250
- console.log('\\n\\x1b[32m🤖 Alex is now online.\\x1b[0m');
251
- console.log('\\x1b[32mAlex ❯\\x1b[0m Infrastructure online. I am ready to forge. What module are we architecting today, Pilot?');
252
- console.log('');
253
- const rl = readline.createInterface({
254
- input: process.stdin,
255
- output: process.stdout,
256
- prompt: \`\\x1b[34m\${dynamicUsername} ❯ \\x1b[0m\`
257
- });
258
- process.stdout.write('\\n\\x1b[F');
259
- rl.prompt();
260
- rl.on('line', async (line) => {
261
- if (['exit', 'quit'].includes(line.trim().toLowerCase())) {
262
- console.log('\\n\\x1b[90m Alex session closed.\\x1b[0m');
263
- rl.close();
264
- return;
265
- }
266
- if (line.trim()) {
267
- await processAlexRequest(line.trim());
268
- console.log('');
269
- }
270
- process.stdout.write('\\n\\x1b[F');
271
- rl.prompt();
272
- }).on('close', () => {
273
- process.exit(0);
274
- });
275
- };
276
-
277
- if (!initialPrompt || initialPrompt === '?') startAlexSession();
278
- else processAlexRequest(initialPrompt);
279
- return;
280
- }
281
- if (command === 'android' || command === 'ios') {
282
- checkGitSecurity();
283
- const platform = command;
284
- const nativeDir = platform === 'android' ? 'public/native/android/' : 'public/native/ios/';
285
- const extension = platform === 'android' ? '.kt' : '.swift';
286
- const fullPath = path.join(process.cwd(), nativeDir);
287
- let hasNativeFiles = false;
288
- if (fs.existsSync(fullPath)) {
289
- const files = fs.readdirSync(fullPath);
290
- hasNativeFiles = files.some(file => file.endsWith(extension));
291
- }
292
- if (!hasNativeFiles) {
293
- console.log(\`\\n\\x1b[31m⚠️ ENGINE INCOMPLETE:\\x1b[0m No native blueprints detected for \\x1b[1m\${platform.toUpperCase()}\\x1b[0m.\`);
294
- console.log(\`\\x1b[90mAlex must architect at least one \${extension} module before deployment.\\x1b[0m\\n\`);
295
- process.exit(1);
296
- }
297
- const targetUrl = platform === 'android' ? ANDROID_BUILD_URL : IOS_BUILD_URL;
298
- (async () => {
299
- console.log(\`\\n\\x1b[36m⚡ FLEETBO \${platform.toUpperCase()} UPLINK\\x1b[0m\`);
300
- try {
301
- execSync('npm run build', { stdio: 'inherit' });
302
- let buildDir = fs.existsSync(path.join(process.cwd(), 'dist')) ? 'dist' : 'build';
303
- const zipBuffer = await new Promise((resolve, reject) => {
304
- const chunks = []; const archive = archiver('zip', { zlib: { level: 9 } });
305
- archive.on('data', chunk => chunks.push(chunk));
306
- archive.on('end', () => resolve(Buffer.concat(chunks)));
307
- archive.directory(path.join(process.cwd(), buildDir), false);
308
- archive.finalize();
309
- });
310
- console.log(\`\\n\\x1b[33mSyncing \${platform} logic bundle...\\x1b[0m\`);
311
- await showEnergyTransfer();
312
-
313
- const res = await axios.post(targetUrl, zipBuffer, {
314
- headers: { 'Content-Type': 'application/zip', 'x-project-id': projectId }
315
- });
316
-
317
- if (res.data.success) {
318
- console.log(\`\\n\\x1b[1m\${platform.toUpperCase()} DEPLOYED\\x1b[0m | \\x1b[32mAlex ❯\\x1b[0m Runtime updated.\`);
319
- }
320
- } catch (error) {
321
- console.error(\`\\n\\x1b[31m Build Error:\\x1b[0m \${error.response?.data?.error || error.message}\`);
322
- process.exit(1);
323
- }
324
- })();
325
- return;
326
- }
327
- const GENERATOR_COMMANDS = ['page', 'g', 'generate', 'android', 'ios'];
328
- if (GENERATOR_COMMANDS.includes(command)) {
329
- try { require('./page.js'); } catch (e) { console.error(e.message); process.exit(1); }
330
- return;
331
- }
332
- const NULL_DEV = process.platform === 'win32' ? '>nul 2>&1' : '2>/dev/null';
333
- function killProcessOnPort(port) {
334
- try {
335
- if (process.platform !== 'win32') {
336
- const pid = execSync(\`lsof -ti:\${port} \${NULL_DEV}\`).toString().trim();
337
- if (pid) execSync(\`kill -9 \${pid.split('\\n').join(' ')} \${NULL_DEV}\`);
338
- }
339
- } catch (e) {}
340
- }
341
- const killNetworkService = () => {
342
- if (uplinkProcess) {
343
- try {
344
- uplinkProcess.kill('SIGINT');
345
- console.log('[Fleetbo] Engine closed.');
346
- } catch (e) {
347
- console.error('[Fleetbo] Error closing tunnel:', e.message);
348
- }
349
- }
350
- };
351
- let isExiting = false;
352
- async function cleanupAndExit(code = 0) {
353
- if (isExiting) return;
354
- isExiting = true;
355
- console.log('\\n\\x1b[33m[Fleetbo] 🛑 Stopping environment & Cleaning Uplink...\\x1b[0m');
356
- try {
357
- await axios.post(UPDATE_NETWORK_URL, { keyApp, networkUrl: '', tester: testerEmail });
358
- console.log('\\x1b[32m[Fleetbo] Network status reset to offline.\\x1b[0m');
359
- } catch (e) {
360
- console.error('[Fleetbo] Network cleanup warning:', e.message);
361
- }
362
- killNetworkService();
363
- killProcessOnPort(PORT);
364
- console.log('[Fleetbo] Bye.');
365
- process.exit(code);
366
- }
367
- process.on('SIGINT', () => cleanupAndExit(0));
368
- process.on('SIGTERM', () => cleanupAndExit(0));
369
- async function syncFirebase(keyApp, networkUrl, testerEmail) {
370
- try {
371
- await axios.post(UPDATE_NETWORK_URL, { keyApp, networkUrl, tester: testerEmail });
372
- console.log('\\n\\x1b[32mEngine started successfully\\x1b[0m');
373
- console.log(\`\\n\\x1b[32m[Fleetbo] \\x1b[0m -------------------------------------------------------------\`);
374
- console.log('\\x1b[32m[Fleetbo] \\x1b[1mGO GO GO ! FLEETBO STUDIO IS READY\\x1b[0m');
375
- console.log('\\x1b[32m[Fleetbo] You can now start coding and previewing in Studio. 🚀');
376
- console.log(\`\\x1b[32m[Fleetbo] \\x1b[0m -------------------------------------------------------------\`);
377
- console.log(\`\\x1b[34mPilot Instruction ❯\\x1b[0m Switch to your Fleetbo Cockpit tab to begin.\\n\`);
378
- } catch (err) {
379
- console.error(\`[Fleetbo] Sync Error: \${err.message}\`);
380
- }
381
- }
382
- async function runDevEnvironment() {
383
- console.log(\`[Fleetbo] 🛡️ Initializing Dev Environment...\`);
384
- killNetworkService();
385
- killProcessOnPort(PORT);
386
-
387
- if (!testerEmail) { console.error('Error: REACT_APP_TESTER_EMAIL missing'); process.exit(1); }
388
-
389
- const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
390
- const devServer = spawn(npmCmd, ['start'], {
391
- stdio: ['ignore', 'pipe', 'pipe'],
392
- shell: true,
393
- env: {
394
- ...process.env,
395
- BROWSER: 'none',
396
- PORT: PORT.toString(),
397
- DANGEROUSLY_DISABLE_HOST_CHECK: 'true',
398
- HOST: '0.0.0.0',
399
- WDS_SOCKET_HOST: 'localhost',
400
- WDS_SOCKET_PORT: PORT.toString()
401
- }
402
- });
403
-
404
- devServer.stdout.pipe(process.stdout);
405
- devServer.stderr.pipe(process.stderr);
406
-
407
- let connectionStarted = false;
408
- devServer.stdout.on('data', (data) => {
409
- const output = data.toString();
410
-
411
- if (!connectionStarted && (output.includes('Local:') || output.includes('Compiled successfully'))) {
412
- connectionStarted = true;
413
-
414
- console.log('\\n[Fleetbo] ---------------------------------------------------');
415
- console.log(\`[Fleetbo] 🔗 Establishing Secure Uplink...\`);
416
- console.log(\`[Fleetbo] ⏳ Please wait for the green message...\`);
417
- console.log('[Fleetbo] ---------------------------------------------------');
418
-
419
- const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
420
- uplinkProcess = spawn(npxCmd, [
421
- 'cloudflared',
422
- 'tunnel',
423
- '--url', \`http://127.0.0.1:\${PORT}\`,
424
- '--http-host-header', \`127.0.0.1:\${PORT}\`
425
- ], { shell: true });
426
-
427
- uplinkProcess.stderr.on('data', (chunk) => {
428
- const text = chunk.toString();
429
- const match = text.match(/https:\\/\\/[a-zA-Z0-9-]+\\.trycloudflare\\.com/);
430
- if (match) syncFirebase(process.env.REACT_KEY_APP, match[0], process.env.REACT_APP_TESTER_EMAIL);
431
- });
432
- }
433
- });
434
- }
435
-
436
- runDevEnvironment();
437
- `;
438
- const args = process.argv.slice(2);
439
- const projectNameArg = args.find(arg => !arg.startsWith('--'));
440
- const tokenArg = args.find(arg => arg.startsWith('--token='));
441
- const emailArg = args.find(arg => arg.startsWith('--email='));
442
- const bootstrapTokenArg = tokenArg ? tokenArg.split('=')[1] : null;
443
- const userEmailArg = emailArg ? emailArg.split('=')[1] : null;
444
- if (!projectNameArg || !bootstrapTokenArg || !userEmailArg) {
445
- console.error('\n Usage: npx create-fleetbo-project <name> --token=<token> --email=<email>');
446
- process.exit(1);
447
- }
448
- const projectName = projectNameArg;
449
- const projectDir = path.join(process.cwd(), projectName);
450
-
451
- function fetchProjectKeys(token) {
452
- return new Promise((resolve, reject) => {
453
- const postData = JSON.stringify({ token });
454
- const uri = new URL(bootstrapUrl);
455
- const options = {
456
- hostname: uri.hostname, path: uri.pathname, method: 'POST',
457
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) }
458
- };
459
- const req = https.request(options, (res) => {
460
- let data = '';
461
- res.on('data', (chunk) => { data += chunk; });
462
- res.on('end', () => {
463
- if (res.statusCode >= 200 && res.statusCode < 300) {
464
- try { resolve(JSON.parse(data)); } catch (e) { reject(new Error('Invalid response')); }
465
- } else { reject(new Error(`Server error ${res.statusCode}`)); }
466
- });
467
- });
468
- req.on('error', (e) => reject(e));
469
- req.write(postData);
470
- req.end();
471
- });
472
- }
473
- function downloadEngine(url, dest) {
474
- return new Promise((resolve, reject) => {
475
- const uri = new URL(url);
476
- const options = {
477
- hostname: uri.hostname, path: uri.pathname + uri.search, method: 'GET',
478
- headers: { 'User-Agent': 'Fleetbo-CLI-Installer', 'Accept': '*/*' }
479
- };
480
- const request = https.get(options, (response) => {
481
- if (response.statusCode === 301 || response.statusCode === 302) {
482
- if (!response.headers.location) { reject(new Error("Redirect error")); return; }
483
- downloadEngine(response.headers.location, dest).then(resolve).catch(reject);
484
- return;
485
- }
486
- if (response.statusCode !== 200) { reject(new Error(`Status: ${response.statusCode}`)); return; }
487
- const file = fs.createWriteStream(dest);
488
- response.pipe(file);
489
- file.on('finish', () => file.close(resolve));
490
- file.on('error', (err) => { fs.unlink(dest, () => {}); reject(err); });
491
- });
492
- request.on('error', (err) => reject(err));
493
- });
494
- }
495
- async function setupProject() {
496
- console.log(`\n ⚡Initializing Fleetbo Framework for "${projectName}"...`);
497
-
498
- try {
499
- if (fs.existsSync(projectDir)) throw new Error(`Directory "${projectName}" already exists.`);
500
- fs.mkdirSync(projectDir);
501
-
502
- console.log(' [1/7] Downloading Fleetbo Core Engine...');
503
- const archivePath = path.join(projectDir, 'engine.tar.gz');
504
- await downloadEngine(archiveUrl, archivePath);
505
-
506
- console.log(' [2/7] Scaffolding project structure...');
507
- try {
508
- execSync(`tar -xf "${archivePath}" -C "${projectDir}" --strip-components=1`, { stdio: 'ignore' });
509
- fs.unlinkSync(archivePath);
510
- } catch (e) { throw new Error("Extract failed."); }
511
-
512
- process.chdir(projectDir);
513
-
514
- console.log(' [2.5/7] Standardizing Architecture (src/app)...');
515
- const oldPath = path.join(projectDir, 'src/pages');
516
- const newPath = path.join(projectDir, 'src/app');
517
-
518
- if (fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
519
- try {
520
- fs.renameSync(oldPath, newPath);
521
- const appJsPath = path.join(projectDir, 'src/App.js');
522
- if (fs.existsSync(appJsPath)) {
523
- let appContent = fs.readFileSync(appJsPath, 'utf8');
524
- appContent = appContent.replace(/from "\.\/pages\//g, 'from "./app/');
525
- appContent = appContent.replace(/from '\.\/pages\//g, "from './app/");
526
- fs.writeFileSync(appJsPath, appContent);
527
- }
528
- } catch (err) {
529
- console.warn(' Architecture migration warning:', err.message);
530
- }
531
- }
532
-
533
- console.log(' [3/7] Authenticating with Fleetbo Cloud...');
534
- const keys = await fetchProjectKeys(bootstrapTokenArg);
535
- if (!keys.enterpriseId) throw new Error("Invalid keys.");
536
-
537
- console.log(' [4/7] Configuring environment & CLI...');
538
- const envContent = `REACT_APP_FLEETBO_DB_KEY=${keys.fleetboDBKey}
539
- REACT_APP_ENTERPRISE_ID=${keys.enterpriseId}
540
- REACT_KEY_APP=${projectName}
541
- REACT_APP_TESTER_EMAIL=${userEmailArg}
542
- DANGEROUSLY_DISABLE_HOST_CHECK=true
543
- WDS_SOCKET_PORT=0`;
544
-
545
- fs.writeFileSync(path.join(projectDir, '.env'), envContent, 'utf8');
546
- console.log(' [5/7] Securing environment (adding .gitignore)...');
547
- const gitignoreContent = `# Fleetbo Security\n.env\n.env.local\nnode_modules/\ndist/\nbuild/\n.DS_Store\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n`;
548
- fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent, 'utf8');
549
- const scriptsDir = path.join(projectDir, 'scripts');
550
- if (!fs.existsSync(scriptsDir)) fs.mkdirSync(scriptsDir, { recursive: true });
551
- fs.writeFileSync(path.join(scriptsDir, 'cli.js'), CLI_SCRIPT_CONTENT, 'utf8');
552
- try { fs.chmodSync(path.join(scriptsDir, 'cli.js'), '755'); } catch (e) {}
553
- console.log(' [6/7] Installing dependencies...');
554
- execSync('npm install', { stdio: 'inherit' });
555
- execSync('npm install cloudflared dotenv axios archiver --save-dev', { stdio: 'ignore' });
556
- console.log(' [7/7] Finalizing setup...');
557
- const packageJsonPath = path.join(projectDir, 'package.json');
558
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
559
- packageJson.name = projectName;
560
- packageJson.scripts = { ...packageJson.scripts, "fleetbo": "node scripts/cli.js", "dev": "node scripts/cli.js" };
561
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
562
- console.log('\n \x1b[32m [Fleetbo] Project successfully created!\x1b[0m');
563
- console.log(`\n Run: cd ${projectName} && npm run fleetbo`);
564
- console.log('\n \x1b[32m [Fleetbo] To start architecting with Alex, run: npm run fleetbo alex!\x1b[0m');
565
- console.log('');
566
- } catch (error) {
567
- console.error('\n Setup failed:', error.message);
568
- if (fs.existsSync(projectDir)) try { fs.rmSync(projectDir, { recursive: true, force: true }); } catch(e){}
569
- process.exit(1);
570
- }
571
- }
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
+ const repoOwner = 'FleetFleetbo';
9
+ const repoName = 'dev.fleetbo.io';
10
+ const branchName = 'master';
11
+ const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/${branchName}.tar.gz`;
12
+ const bootstrapUrl = 'https://us-central1-myapp-259bf.cloudfunctions.net/bootstrapProject';
13
+
14
+ const args = process.argv.slice(2);
15
+ const projectNameArg = args.find(arg => !arg.startsWith('--'));
16
+ const tokenArg = args.find(arg => arg.startsWith('--token='));
17
+ const emailArg = args.find(arg => arg.startsWith('--email='));
18
+ const bootstrapTokenArg = tokenArg ? tokenArg.split('=')[1] : null;
19
+ const userEmailArg = emailArg ? emailArg.split('=')[1] : null;
20
+
21
+ if (!projectNameArg || !bootstrapTokenArg || !userEmailArg) {
22
+ console.error('\n Usage: npx create-fleetbo-project <name> --token=<token> --email=<email>');
23
+ process.exit(1);
24
+ }
25
+
26
+ const projectName = projectNameArg;
27
+ const projectDir = path.join(process.cwd(), projectName);
28
+
29
+ function fetchProjectKeys(token) {
30
+ return new Promise((resolve, reject) => {
31
+ const postData = JSON.stringify({ token });
32
+ const uri = new URL(bootstrapUrl);
33
+ const options = {
34
+ hostname: uri.hostname,
35
+ path: uri.pathname,
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Content-Length': Buffer.byteLength(postData)
40
+ }
41
+ };
42
+ const req = https.request(options, (res) => {
43
+ let data = '';
44
+ res.on('data', (chunk) => { data += chunk; });
45
+ res.on('end', () => {
46
+ if (res.statusCode >= 200 && res.statusCode < 300) {
47
+ try { resolve(JSON.parse(data)); }
48
+ catch (e) { reject(new Error('Invalid response')); }
49
+ } else {
50
+ reject(new Error(`Server error ${res.statusCode}`));
51
+ }
52
+ });
53
+ });
54
+ req.on('error', (e) => reject(e));
55
+ req.write(postData);
56
+ req.end();
57
+ });
58
+ }
59
+
60
+ function downloadEngine(url, dest) {
61
+ return new Promise((resolve, reject) => {
62
+ const uri = new URL(url);
63
+ const options = {
64
+ hostname: uri.hostname,
65
+ path: uri.pathname + uri.search,
66
+ method: 'GET',
67
+ headers: {
68
+ 'User-Agent': 'Fleetbo-CLI-Installer',
69
+ 'Accept': '*/*'
70
+ }
71
+ };
72
+ const request = https.get(options, (response) => {
73
+ if (response.statusCode === 301 || response.statusCode === 302) {
74
+ if (!response.headers.location) {
75
+ reject(new Error("Redirect error"));
76
+ return;
77
+ }
78
+ downloadEngine(response.headers.location, dest).then(resolve).catch(reject);
79
+ return;
80
+ }
81
+ if (response.statusCode !== 200) {
82
+ reject(new Error(`Status: ${response.statusCode}`));
83
+ return;
84
+ }
85
+ const file = fs.createWriteStream(dest);
86
+ response.pipe(file);
87
+ file.on('finish', () => file.close(resolve));
88
+ file.on('error', (err) => {
89
+ fs.unlink(dest, () => {});
90
+ reject(err);
91
+ });
92
+ });
93
+ request.on('error', (err) => reject(err));
94
+ });
95
+ }
96
+
97
+ async function setupProject() {
98
+ console.log(`\n ⚡ Initializing Fleetbo Framework for "${projectName}"...`);
99
+
100
+ try {
101
+ if (fs.existsSync(projectDir)) {
102
+ throw new Error(`Directory "${projectName}" already exists.`);
103
+ }
104
+ fs.mkdirSync(projectDir);
105
+
106
+ console.log(' [1/8] Downloading Fleetbo Core Engine...');
107
+ const archivePath = path.join(projectDir, 'engine.tar.gz');
108
+ await downloadEngine(archiveUrl, archivePath);
109
+
110
+ console.log(' [2/8] Scaffolding project structure...');
111
+ try {
112
+ execSync(`tar -xf "${archivePath}" -C "${projectDir}" --strip-components=1`, { stdio: 'ignore' });
113
+ fs.unlinkSync(archivePath);
114
+ } catch (e) {
115
+ throw new Error("Extract failed.");
116
+ }
117
+
118
+ process.chdir(projectDir);
119
+
120
+ console.log(' [3/8] Standardizing Architecture (src/app)...');
121
+ const oldPath = path.join(projectDir, 'src/pages');
122
+ const newPath = path.join(projectDir, 'src/app');
123
+
124
+ if (fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
125
+ try {
126
+ fs.renameSync(oldPath, newPath);
127
+ const appJsPath = path.join(projectDir, 'src/App.js');
128
+ if (fs.existsSync(appJsPath)) {
129
+ let appContent = fs.readFileSync(appJsPath, 'utf8');
130
+ appContent = appContent.replace(/from "\.\/pages\//g, 'from "./app/');
131
+ appContent = appContent.replace(/from '\.\/pages\//g, "from './app/");
132
+ fs.writeFileSync(appJsPath, appContent);
133
+ }
134
+ } catch (err) {
135
+ console.warn(' Architecture migration warning:', err.message);
136
+ }
137
+ }
138
+
139
+ console.log(' [4/8] Authenticating with Fleetbo Cloud...');
140
+ const keys = await fetchProjectKeys(bootstrapTokenArg);
141
+ if (!keys.enterpriseId) throw new Error("Invalid keys.");
142
+
143
+ console.log(' [5/8] Configuring environment...');
144
+ const envContent = `REACT_APP_FLEETBO_DB_KEY=${keys.fleetboDBKey}
145
+ REACT_APP_ENTERPRISE_ID=${keys.enterpriseId}
146
+ REACT_KEY_APP=${projectName}
147
+ REACT_APP_TESTER_EMAIL=${userEmailArg}
148
+ DANGEROUSLY_DISABLE_HOST_CHECK=true
149
+ WDS_SOCKET_PORT=0`;
150
+
151
+ fs.writeFileSync(path.join(projectDir, '.env'), envContent, 'utf8');
152
+
153
+ console.log(' [6/8] Securing environment (adding .gitignore)...');
154
+ const gitignoreContent = `# Fleetbo Security
155
+ .env
156
+ .env.local
157
+ node_modules/
158
+ dist/
159
+ build/
160
+ .DS_Store
161
+ npm-debug.log*
162
+ yarn-debug.log*
163
+ yarn-error.log*
164
+ `;
165
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent, 'utf8');
166
+
167
+ console.log(' [7/8] Installing dependencies...');
168
+ execSync('npm install', { stdio: 'inherit' });
169
+
170
+ // Installer cloudflared comme devDependency (nécessaire pour le tunnel)
171
+ execSync('npm install cloudflared --save-dev', { stdio: 'ignore' });
172
+
173
+ console.log(' [8/8] Finalizing setup...');
174
+ const packageJsonPath = path.join(projectDir, 'package.json');
175
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
176
+ packageJson.name = projectName;
177
+
178
+ // Configuration pour utiliser le package NPM centralisé
179
+ packageJson.scripts = {
180
+ ...packageJson.scripts,
181
+ "fleetbo": "npx fleetbo-cockpit-cli@latest",
182
+ "dev": "npx fleetbo-cockpit-cli@latest"
183
+ };
184
+
185
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
186
+
187
+ // Supprimer le dossier scripts local (car on utilise le CLI npm)
188
+ const scriptsDir = path.join(projectDir, 'scripts');
189
+ if (fs.existsSync(scriptsDir)) {
190
+ try {
191
+ fs.rmSync(scriptsDir, { recursive: true, force: true });
192
+ } catch (e) {
193
+ // Ignore si ça échoue
194
+ }
195
+ }
196
+
197
+ console.log('\n \x1b[32m✓ [Fleetbo] Project successfully created!\x1b[0m');
198
+ console.log(`\n Run: cd ${projectName} && npm run fleetbo`);
199
+ console.log('\n \x1b[32m[Fleetbo] To start architecting with Alex, run: npm run fleetbo alex\x1b[0m');
200
+ console.log('');
201
+
202
+ } catch (error) {
203
+ console.error('\n Setup failed:', error.message);
204
+ if (fs.existsSync(projectDir)) {
205
+ try {
206
+ fs.rmSync(projectDir, { recursive: true, force: true });
207
+ } catch(e) {}
208
+ }
209
+ process.exit(1);
210
+ }
211
+ }
212
+
572
213
  setupProject();