create-fleetbo-project 1.2.37 → 1.2.39

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.
@@ -11,8 +11,8 @@ const branchName = 'master';
11
11
  const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/${branchName}.tar.gz`;
12
12
  const bootstrapUrl = 'https://us-central1-myapp-259bf.cloudfunctions.net/bootstrapProject';
13
13
 
14
- const CLI_SCRIPT_CONTENT = `#!/usr/bin/env node
15
14
 
15
+ const CLI_SCRIPT_CONTENT = `#!/usr/bin/env node
16
16
  const { spawn, execSync } = require('child_process');
17
17
  const fs = require('fs');
18
18
  const path = require('path');
@@ -20,9 +20,12 @@ const axios = require('axios');
20
20
  const dotenv = require('dotenv');
21
21
  const os = require('os');
22
22
  const archiver = require('archiver');
23
+ const readline = require('readline');
23
24
 
24
25
  const CLOUD_ENGINE_URL = "https://us-central1-myapp-259bf.cloudfunctions.net/uploadLogicBundle";
25
26
  const UPDATE_NETWORK_URL = 'https://us-central1-myapp-259bf.cloudfunctions.net/updateDeveloperNetwork';
27
+ const ALEX_ENGINE_URL = "https://us-central1-myapp-259bf.cloudfunctions.net/generateNativeModule";
28
+ const APP_JS_PATH = path.join(process.cwd(), 'src/App.js');
26
29
  const PORT = 3000;
27
30
 
28
31
  const args = process.argv.slice(2);
@@ -30,7 +33,7 @@ const command = args[0];
30
33
 
31
34
  const envPath = path.join(process.cwd(), '.env');
32
35
  if (!fs.existsSync(envPath)) {
33
- console.error('❌ Error: Configuration file (.env) not found.');
36
+ console.error('\\\\x1b[31m%s\\\\x1b[0m', '❌ Error: Configuration file (.env) not found.');
34
37
  process.exit(1);
35
38
  }
36
39
  dotenv.config({ path: envPath });
@@ -39,96 +42,157 @@ const projectId = process.env.REACT_APP_ENTERPRISE_ID;
39
42
  const keyApp = process.env.REACT_KEY_APP;
40
43
  const testerEmail = process.env.REACT_APP_TESTER_EMAIL;
41
44
 
45
+ const checkGitSecurity = () => {
46
+ const gitDir = path.join(process.cwd(), '.git');
47
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
48
+ if (fs.existsSync(gitDir)) {
49
+ if (!fs.existsSync(gitignorePath)) {
50
+ console.error('\\\\n\\\\x1b[31m🚨 SECURITY ALERT:\\\\x1b[0m .git detected but no .gitignore found.');
51
+ process.exit(1);
52
+ }
53
+ const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
54
+ if (!gitignoreContent.includes('.env')) {
55
+ console.error('\\\\n\\\\x1b[31m🚨 CRITICAL RISK:\\\\x1b[0m .env is NOT ignored by Git.');
56
+ process.exit(1);
57
+ }
58
+ }
59
+ };
60
+
42
61
  if (!projectId) {
43
- console.error('\\n❌ Error: Project ID is missing.');
44
- console.error('👉 Please check your .env file.');
45
- console.error(' It must contain: REACT_APP_ENTERPRISE_ID=your_project_id\\n');
62
+ console.error('\\\\n❌ Error: Project ID missing in .env.\\\\n');
46
63
  process.exit(1);
47
64
  }
48
65
 
49
- if (command === 'deploy') {
50
- (async () => {
51
- console.log('\\n\\x1b[36m%s\\x1b[0m', '⚡ FLEETBO CLOUD ENGINE v1.0');
52
- console.log('\\x1b[32m%s\\x1b[0m', 'Target Runtime:', keyApp);
53
- console.log('------------------------------------------------');
66
+ const injectRouteIntoAppJs = (pageName) => {
67
+ if (!fs.existsSync(APP_JS_PATH)) return false;
68
+ let content = fs.readFileSync(APP_JS_PATH, 'utf8');
69
+ if (!content.includes('// FLEETBO_IMPORTS') || !content.includes('{/* FLEETBO_ROUTES */}')) return false;
54
70
 
55
- console.log('🔨 \\x1b[33mCompiling local assets...\\x1b[0m');
56
- try {
57
- execSync('npm run build', { stdio: 'inherit' });
58
- } catch (e) {
59
- console.error('\\n❌ Build failed. Fix errors and try again.');
60
- process.exit(1);
61
- }
71
+ const importLine = \`import \\\${pageName} from './pages/\\\${pageName}';\`;
72
+ const routeLine = \`<Route path="/\\\${pageName.toLowerCase()}" element=<\\\${pageName} /> />\`;
62
73
 
63
- let buildDir = 'dist';
64
- if (!fs.existsSync(path.join(process.cwd(), 'dist')) && fs.existsSync(path.join(process.cwd(), 'build'))) {
65
- buildDir = 'build';
66
- }
74
+ let injected = false;
75
+ if (!content.includes(importLine) && !content.includes(\`import \\\${pageName} from\`)) {
76
+ content = content.replace('// FLEETBO_IMPORTS', \`\\\${importLine}\\\\n// FLEETBO_IMPORTS\`);
77
+ injected = true;
78
+ }
79
+ if (!content.includes(routeLine) && !content.includes(\`path="/\\\${pageName.toLowerCase()}"\`)) {
80
+ const anchor = content.includes('{/* FLEETBO_ROUTES */}') ? '{/* FLEETBO_ROUTES */}' : '// FLEETBO_ROUTES';
81
+ content = content.replace(anchor, \`\\\${routeLine}\\\\n \\\${anchor}\`);
82
+ injected = true;
83
+ }
84
+ if (injected) fs.writeFileSync(APP_JS_PATH, content);
85
+ return injected;
86
+ };
87
+
88
+ const showEnergyTransfer = async () => {
89
+ const width = 30;
90
+ for (let i = 0; i <= width; i++) {
91
+ const dots = "█".repeat(i); const empty = "░".repeat(width - i);
92
+ process.stdout.write(\`\\\\r \\\\x1b[32m⚡ Alex Energy Sync:\\\\x1b[0m [\\\${dots}\\\${empty}] \\\${Math.round((i / width) * 100)}%\`);
93
+ await new Promise(r => setTimeout(r, 45));
94
+ }
95
+ process.stdout.write('\\\\n');
96
+ };
67
97
 
68
- if (!fs.existsSync(path.join(process.cwd(), buildDir))) {
69
- console.error(\`\\n❌ Error: Build folder '\${buildDir}' not found.\`);
70
- process.exit(1);
71
- }
98
+ if (command === 'alex') {
99
+ checkGitSecurity();
100
+ const initialPrompt = args.slice(1).join(' ');
101
+
102
+ const processAlexRequest = async (prompt) => {
103
+ process.stdout.write('\\\\n\\\\x1b[33m🧠 Alex is architecting...\\\\x1b[0m\\\\r');
104
+ try {
105
+ const result = await axios.post(ALEX_ENGINE_URL, { prompt, projectType: 'android' }, {
106
+ headers: { 'x-project-id': projectId }
107
+ });
108
+ const aiData = result.data.data || result.data;
109
+ process.stdout.write(' '.repeat(30) + '\\\\r');
110
+
111
+ if (aiData.status === 'success' && aiData.moduleData) {
112
+ const { fileName, code, mockFileName, mockCode } = aiData.moduleData;
113
+ const writeFile = (dir, name, content) => {
114
+ const fullPath = path.join(process.cwd(), dir);
115
+ const filePath = path.join(fullPath, name);
116
+ if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
117
+
118
+ if (fs.existsSync(filePath)) {
119
+ const backupPath = filePath.replace(/(\\\\.jsx|\\\\.kt|\\\\.swift)$/, '.original\\\$1');
120
+ fs.copyFileSync(filePath, backupPath);
121
+ fs.writeFileSync(filePath, content);
122
+ console.log(\` ✅ \\\\x1b[33m[Updated]\\\\x1b[0m \\\${dir}\\\${name} \\\\x1b[90m(Backup created)\\\\x1b[0m\`);
123
+ } else {
124
+ fs.writeFileSync(filePath, content);
125
+ console.log(\` ✅ \\\\x1b[32m[Created]\\\\x1b[0m \\\${dir}\\\${name}\`);
126
+ }
127
+ };
128
+ console.log('');
129
+ if (code && fileName && (fileName.endsWith('.kt') || fileName.endsWith('.swift'))) {
130
+ writeFile(fileName.endsWith('.kt') ? 'public/native/android/' : 'public/native/ios/', fileName, code);
131
+ }
132
+ if (mockCode && mockFileName) {
133
+ writeFile('src/pages/mocks/', mockFileName, mockCode);
134
+ writeFile('src/pages/mocks/', 'Quick.js', mockCode);
135
+ }
136
+ if (code && fileName && fileName.endsWith('.jsx') && !mockCode) {
137
+ const pageName = fileName.replace('.jsx', '');
138
+ writeFile('src/pages/', fileName, code);
139
+ injectRouteIntoAppJs(pageName);
140
+ }
141
+ console.log('\\\\n\\\\x1b[32mAlex ❯\\\\x1b[0m ' + aiData.message);
142
+ }
143
+ } catch (error) { console.error('\\\\n\\\\x1b[31m❌ Alex Error:\\\\x1b[0m ' + error.message); }
144
+ };
72
145
 
73
- console.log('\\n📦 \\x1b[33mProcessing Neural Logic...\\x1b[0m');
74
- console.log(\` Detected stack: React (\${buildDir})\`);
146
+ const startAlexSession = async () => {
147
+ process.stdout.write('\\\\n\\\\x1b[33m🛡️ Alex is checking runtime state...\\\\x1b[0m\\\\r');
148
+ try {
149
+ const validation = await axios.post(ALEX_ENGINE_URL, { validateProject: true, checkNetwork: true, projectKey: keyApp, testerEmail: testerEmail }, { headers: { 'x-project-id': projectId } });
150
+ if (!validation.data.exists || !validation.data.isRunning) {
151
+ console.error('\\\\n\\\\x1b[31m⚠️ ENGINE OFFLINE:\\\\x1b[0m Alex needs a live Uplink.');
152
+ process.exit(1);
153
+ }
154
+ process.stdout.write(' '.repeat(60) + '\\\\r');
155
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '\\\\x1b[32mAlex ❯ \\\\x1b[0m' });
156
+ console.log('\\\\n\\\\x1b[32m🤖 Alex is now online.\\\\x1b[0m Pilot safely.');
157
+ rl.prompt();
158
+ rl.on('line', async (line) => {
159
+ if (['exit', 'quit', 'bye'].includes(line.trim().toLowerCase())) { rl.close(); return; }
160
+ if (line.trim()) await processAlexRequest(line.trim());
161
+ rl.prompt();
162
+ }).on('close', () => { console.log('\\\\n👋 Alex going offline.'); process.exit(0); });
163
+ } catch (error) { process.exit(1); }
164
+ };
165
+ if (!initialPrompt || initialPrompt === '?') startAlexSession();
166
+ else processAlexRequest(initialPrompt);
167
+ return;
168
+ }
75
169
 
170
+ if (command === 'deploy') {
171
+ checkGitSecurity();
172
+ (async () => {
173
+ console.log('\\\\n\\\\x1b[36m⚡ FLEETBO CLOUD ENGINE\\\\x1b[0m');
76
174
  try {
175
+ execSync('npm run build', { stdio: 'inherit' });
176
+ let buildDir = fs.existsSync(path.join(process.cwd(), 'dist')) ? 'dist' : 'build';
77
177
  const zipBuffer = await new Promise((resolve, reject) => {
78
- const chunks = [];
79
- const archive = archiver('zip', { zlib: { level: 9 } });
80
-
81
- archive.on('data', (chunk) => chunks.push(chunk));
82
- archive.on('error', (err) => reject(err));
178
+ const chunks = []; const archive = archiver('zip', { zlib: { level: 9 } });
179
+ archive.on('data', chunk => chunks.push(chunk));
83
180
  archive.on('end', () => resolve(Buffer.concat(chunks)));
84
-
85
181
  archive.directory(path.join(process.cwd(), buildDir), false);
86
182
  archive.finalize();
87
183
  });
88
-
89
- console.log('☁️ \\x1b[33mEstablishing Uplink to Switch-Engine...\\x1b[0m');
90
-
91
- const response = await axios.post(CLOUD_ENGINE_URL, zipBuffer, {
92
- headers: {
93
- 'Content-Type': 'application/zip',
94
- 'x-project-id': projectId,
95
- 'Content-Length': zipBuffer.length
96
- },
97
- maxContentLength: Infinity,
98
- maxBodyLength: Infinity
99
- });
100
-
101
- console.log('------------------------------------------------');
102
- console.log('✅ \\x1b[1m\\x1b[32mDEPLOYMENT SUCCESSFUL\\x1b[0m');
103
- console.log(\` Build ID: \${response.data.buildId}\`);
104
- console.log(\` Payload: \${(zipBuffer.length / 1024 / 1024).toFixed(2)} MB\`);
105
- console.log('\\n📱 \\x1b[36mNative Runtime has been hot-swapped.\\x1b[0m');
106
- console.log(' Your app is live.\\n');
107
-
108
- } catch (error) {
109
- console.error('\\n❌ \\x1b[31mCRITICAL FAILURE\\x1b[0m');
110
- if (error.response) {
111
- console.error(\` Server responded with \${error.response.status}: "\${error.response.statusText}".\`);
112
- console.error(\` Details: \${JSON.stringify(error.response.data)}\`);
113
- } else {
114
- console.error(\` \${error.message}\`);
115
- }
116
- process.exit(1);
117
- }
184
+ console.log('\\\\n📦 \\\\x1b[33mPreparing Neural Logic for Uplink...\\\\x1b[0m');
185
+ await showEnergyTransfer();
186
+ await axios.post(CLOUD_ENGINE_URL, zipBuffer, { headers: { 'Content-Type': 'application/zip', 'x-project-id': projectId } });
187
+ console.log('\\\\n✅ \\\\x1b[1mDEPLOYMENT SUCCESSFUL\\\\x1b[0m | \\\\x1b[32mAlex ❯\\\\x1b[0m Runtime updated.');
188
+ } catch (error) { process.exit(1); }
118
189
  })();
119
190
  return;
120
191
  }
121
192
 
122
193
  const GENERATOR_COMMANDS = ['page', 'g', 'generate', 'android', 'ios'];
123
-
124
194
  if (GENERATOR_COMMANDS.includes(command)) {
125
- try {
126
- require('./page.js');
127
- } catch (error) {
128
- console.error('\\x1b[31m%s\\x1b[0m', ' CRITICAL ERROR in Fleetbo Generator:');
129
- console.error(error.message);
130
- process.exit(1);
131
- }
195
+ try { require('./page.js'); } catch (error) { process.exit(1); }
132
196
  return;
133
197
  }
134
198
 
@@ -137,10 +201,8 @@ const NULL_DEV = process.platform === 'win32' ? '>nul 2>&1' : '2>/dev/null';
137
201
  function killProcessOnPort(port) {
138
202
  try {
139
203
  if (process.platform !== 'win32') {
140
- const pid = execSync(\`lsof -ti:\${port} \${NULL_DEV}\`).toString().trim();
141
- if (pid) {
142
- execSync(\`kill -9 \${pid.split('\\n').join(' ')} \${NULL_DEV}\`);
143
- }
204
+ const pid = execSync(\`lsof -ti:\\\${port} \\\${NULL_DEV}\`).toString().trim();
205
+ if (pid) { execSync(\`kill -9 \\\${pid.split('\\\\n').join(' ')} \\\${NULL_DEV}\`); }
144
206
  }
145
207
  } catch (e) {}
146
208
  }
@@ -152,114 +214,49 @@ function killNetworkService() {
152
214
  } catch (e) {}
153
215
  }
154
216
 
155
- // Variable de verrouillage globale
156
217
  let isExiting = false;
157
-
158
218
  async function cleanupAndExit(code = 0) {
159
- // Si on est déjà en train de sortir, on ignore les autres signaux
160
219
  if (isExiting) return;
161
220
  isExiting = true;
162
-
163
- console.log('\n[Fleetbo] 🛑 Stopping development environment...');
164
-
165
221
  try {
166
- // Nettoyage de l'Uplink dans Firestore
167
- await axios.post(UPDATE_NETWORK_URL, {
168
- keyApp: keyApp,
169
- networkUrl: '',
170
- tester: testerEmail
171
- });
172
- console.log('[Fleetbo] 🧊 Uplink cleaned successfully.');
173
- } catch (e) {
174
- // On ne bloque pas si le réseau échoue à la fermeture
175
- }
176
-
222
+ await axios.post(UPDATE_NETWORK_URL, { keyApp: keyApp, networkUrl: '', tester: testerEmail });
223
+ } catch (e) {}
177
224
  killNetworkService();
178
225
  killProcessOnPort(PORT);
179
226
  process.exit(code);
180
227
  }
181
- // Les écouteurs restent les mêmes, mais la fonction gère maintenant le verrou
182
228
  process.on('SIGINT', () => cleanupAndExit(0));
183
229
  process.on('SIGTERM', () => cleanupAndExit(0));
184
230
 
185
231
  async function syncFirebase(keyApp, networkUrl, testerEmail) {
186
232
  try {
187
- await axios.post(UPDATE_NETWORK_URL, {
188
- keyApp,
189
- networkUrl,
190
- tester: testerEmail
191
- });
192
- console.log('\\n[Fleetbo] ---------------------------------------------------\\n');
193
- console.log(\`[Fleetbo] ✅ Uplink Status: Online for \${testerEmail}\`);
194
- console.log(\`[Fleetbo] 🚀 Fleetbo Studio: https://fleetbo.io/studio/view/\${keyApp}\`);
195
- console.log(\'[Fleetbo] 👉 Your studio is ready. You can now open the link above.\');
196
- console.log('\\n[Fleetbo] ---------------------------------------------------\\n');
197
- } catch (err) {
198
- console.error(\`[Fleetbo] ⚠️ Sync Error: \${err.message}\`);
199
- }
233
+ await axios.post(UPDATE_NETWORK_URL, { keyApp, networkUrl, tester: testerEmail });
234
+ console.log(\`\\\\n[Fleetbo] ✅ Uplink Status: Online for \\\${testerEmail}\`);
235
+ console.log(\`[Fleetbo] 🚀 Fleetbo Studio: https://fleetbo.io/studio/\\\${keyApp}\`);
236
+ } catch (err) { console.error(\`[Fleetbo] ⚠️ Sync Error: \\\${err.message}\`); }
200
237
  }
201
238
 
202
239
  async function runDevEnvironment() {
203
- console.log(\`[Fleetbo] 🛡️ Initializing Developer Environment on \${os.platform()}...\`);
204
-
205
240
  killNetworkService();
206
241
  killProcessOnPort(PORT);
207
-
208
- if (!testerEmail) {
209
- console.error('Error: REACT_APP_TESTER_EMAIL missing in .env');
210
- process.exit(1);
211
- }
212
-
213
- console.log(\`[Fleetbo] 📦 Starting Local Server...\`);
214
242
  const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
215
-
216
- const devServer = spawn(npmCmd, ['start'], {
217
- stdio: ['ignore', 'pipe', 'pipe'],
218
- shell: true,
219
- env: { ...process.env, BROWSER: 'none', PORT: PORT.toString() }
220
- });
221
-
222
- devServer.stdout.pipe(process.stdout);
223
- devServer.stderr.pipe(process.stderr);
224
-
225
- let connectionStarted = false;
226
-
243
+ const devServer = spawn(npmCmd, ['start'], { stdio: ['ignore', 'pipe', 'pipe'], shell: true, env: { ...process.env, BROWSER: 'none', PORT: PORT.toString() } });
227
244
  devServer.stdout.on('data', (data) => {
228
245
  const output = data.toString();
229
-
230
- if (!connectionStarted && (output.includes('Local:') || output.includes('Compiled successfully'))) {
231
- connectionStarted = true;
232
- console.log(\`\\n[Fleetbo] 🔗 Local Server Ready. Establishing Secure Uplink...\`);
233
- console.log(\`\\n[Fleetbo] 🛑 STOP! DO NOT open the Studio yet.\`);
234
- console.log(\`\\n[Fleetbo] ⏳ Establishing Secure Uplink... Please wait...\`);
235
-
236
-
246
+ if (output.includes('Local:') || output.includes('Compiled successfully')) {
237
247
  const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
238
-
239
- const uplink = spawn(npxCmd, ['cloudflared', 'tunnel', '--url', \`http://localhost:\${PORT}\`], { shell: true });
248
+ const uplink = spawn(npxCmd, ['cloudflared', 'tunnel', '--url', \`http://localhost:\\\${PORT}\`], { shell: true });
240
249
  uplink.stderr.on('data', (chunk) => {
241
- const log = chunk.toString();
242
- const match = log.match(/https:\\/\\/[a-zA-Z0-9-]+\\.trycloudflare\\.com/);
243
-
244
- if (match) {
245
- const url = match[0];
246
- if (global.currentUrl !== url) {
247
- global.currentUrl = url;
248
- syncFirebase(keyApp, url, testerEmail);
249
- }
250
- }
251
- });
252
-
253
- uplink.on('error', (err) => {
254
- console.error("[Fleetbo] ❌ Uplink Error. Network component missing.");
250
+ const match = chunk.toString().match(/https:\\\\/\\\\/[a-zA-Z0-9-]+\\\\.trycloudflare\\\\.com/);
251
+ if (match) syncFirebase(keyApp, match[0], testerEmail);
255
252
  });
256
253
  }
257
254
  });
258
255
  }
259
-
260
256
  runDevEnvironment();
261
257
  `;
262
258
 
259
+
263
260
  const args = process.argv.slice(2);
264
261
  const projectNameArg = args.find(arg => !arg.startsWith('--'));
265
262
  const tokenArg = args.find(arg => arg.startsWith('--token='));
@@ -345,48 +342,40 @@ async function setupProject() {
345
342
  const keys = await fetchProjectKeys(bootstrapTokenArg);
346
343
  if (!keys.enterpriseId) throw new Error("Invalid keys.");
347
344
 
348
- console.log(' [4/6] ⚙️ Configuring environment & CLI...');
345
+ console.log(' [4.1/6] ⚙️ Configuring environment & CLI...');
349
346
 
350
- const envContent =
351
- `REACT_APP_FLEETBO_DB_KEY=${keys.fleetboDBKey}
352
- REACT_APP_ENTERPRISE_ID=${keys.enterpriseId}
353
- REACT_KEY_APP=${projectName}
354
- REACT_APP_TESTER_EMAIL=${userEmailArg}
355
- DANGEROUSLY_DISABLE_HOST_CHECK=true
356
- WDS_SOCKET_PORT=0
357
- `;
347
+ // Correction indentation .env
348
+ const envContent = `REACT_APP_FLEETBO_DB_KEY=${keys.fleetboDBKey}
349
+ REACT_APP_ENTERPRISE_ID=${keys.enterpriseId}
350
+ REACT_KEY_APP=${projectName}
351
+ REACT_APP_TESTER_EMAIL=${userEmailArg}
352
+ DANGEROUSLY_DISABLE_HOST_CHECK=true
353
+ WDS_SOCKET_PORT=0`;
358
354
 
359
355
  fs.writeFileSync(path.join(projectDir, '.env'), envContent, 'utf8');
356
+
357
+ console.log(' [4.2/6] 🛡️ Securing environment (adding .gitignore)...');
358
+ 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`;
359
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), gitignoreContent, 'utf8');
360
+
360
361
  const scriptsDir = path.join(projectDir, 'scripts');
361
- if (!fs.existsSync(scriptsDir)) {
362
- fs.mkdirSync(scriptsDir, { recursive: true });
363
- }
362
+ if (!fs.existsSync(scriptsDir)) fs.mkdirSync(scriptsDir, { recursive: true });
364
363
  fs.writeFileSync(path.join(scriptsDir, 'cli.js'), CLI_SCRIPT_CONTENT, 'utf8');
365
364
  try { fs.chmodSync(path.join(scriptsDir, 'cli.js'), '755'); } catch (e) {}
366
365
 
367
366
  console.log(' [5/6] 📚 Installing dependencies...');
368
367
  execSync('npm install', { stdio: 'inherit' });
369
-
370
368
  execSync('npm install cloudflared dotenv axios archiver --save-dev', { stdio: 'ignore' });
371
369
 
372
370
  console.log(' [6/6] ✨ Finalizing setup...');
373
371
  const packageJsonPath = path.join(projectDir, 'package.json');
374
372
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
375
-
376
373
  packageJson.name = projectName;
377
-
378
- packageJson.scripts = {
379
- ...packageJson.scripts,
380
- "fleetbo": "node scripts/cli.js",
381
- "dev": "node scripts/cli.js"
382
- };
383
-
374
+ packageJson.scripts = { ...packageJson.scripts, "fleetbo": "node scripts/cli.js", "dev": "node scripts/cli.js" };
384
375
  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
385
376
 
386
377
  console.log('\n [Fleetbo] ✅ Project successfully created!');
387
- console.log(`\n👉 Run: cd ${projectName}`);
388
- console.log(`👉 Start Dev: npm run fleetbo`);
389
- console.log(`👉 Deploy App: npm run fleetbo deploy`);
378
+ console.log(`\n👉 Run: cd ${projectName} && npm run fleetbo`);
390
379
 
391
380
  } catch (error) {
392
381
  console.error('\n❌ Setup failed:', error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fleetbo-project",
3
- "version": "1.2.37",
3
+ "version": "1.2.39",
4
4
  "description": "Creates a new Fleetbo project.",
5
5
  "main": "install-react-template.js",
6
6
  "bin": {
package/recov.js CHANGED
@@ -11,6 +11,7 @@ const branchName = 'master';
11
11
  const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/${branchName}.tar.gz`;
12
12
  const bootstrapUrl = 'https://us-central1-myapp-259bf.cloudfunctions.net/bootstrapProject';
13
13
 
14
+
14
15
  const CLI_SCRIPT_CONTENT = `#!/usr/bin/env node
15
16
 
16
17
  const { spawn, execSync } = require('child_process');
@@ -20,9 +21,12 @@ const axios = require('axios');
20
21
  const dotenv = require('dotenv');
21
22
  const os = require('os');
22
23
  const archiver = require('archiver');
24
+ const readline = require('readline');
23
25
 
24
26
  const CLOUD_ENGINE_URL = "https://us-central1-myapp-259bf.cloudfunctions.net/uploadLogicBundle";
25
27
  const UPDATE_NETWORK_URL = 'https://us-central1-myapp-259bf.cloudfunctions.net/updateDeveloperNetwork';
28
+ const ALEX_ENGINE_URL = "https://us-central1-myapp-259bf.cloudfunctions.net/generateNativeModule";
29
+ const APP_JS_PATH = path.join(process.cwd(), 'src/App.js');
26
30
  const PORT = 3000;
27
31
 
28
32
  const args = process.argv.slice(2);
@@ -30,7 +34,7 @@ const command = args[0];
30
34
 
31
35
  const envPath = path.join(process.cwd(), '.env');
32
36
  if (!fs.existsSync(envPath)) {
33
- console.error('❌ Error: Configuration file (.env) not found.');
37
+ console.error('\\x1b[31m%s\\x1b[0m', '❌ Error: Configuration file (.env) not found.');
34
38
  process.exit(1);
35
39
  }
36
40
  dotenv.config({ path: envPath });
@@ -39,82 +43,172 @@ const projectId = process.env.REACT_APP_ENTERPRISE_ID;
39
43
  const keyApp = process.env.REACT_KEY_APP;
40
44
  const testerEmail = process.env.REACT_APP_TESTER_EMAIL;
41
45
 
46
+ // 🛡️ SÉCURITÉ 1 : PROTECTION DES SECRETS .ENV
47
+ const checkGitSecurity = () => {
48
+ const gitDir = path.join(process.cwd(), '.git');
49
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
50
+ if (fs.existsSync(gitDir)) {
51
+ if (!fs.existsSync(gitignorePath)) {
52
+ console.error('\\n\\x1b[31m🚨 SECURITY ALERT:\\x1b[0m .git detected but no .gitignore found.');
53
+ process.exit(1);
54
+ }
55
+ const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
56
+ if (!gitignoreContent.includes('.env')) {
57
+ console.error('\\n\\x1b[31m🚨 CRITICAL RISK:\\x1b[0m .env is NOT ignored by Git.');
58
+ process.exit(1);
59
+ }
60
+ }
61
+ };
62
+
63
+ // 🛡️ SÉCURITÉ 2 : VÉRIFICATION PROJET
42
64
  if (!projectId) {
43
- console.error('\\n❌ Error: Project ID is missing.');
44
- console.error('👉 Please check your .env file.');
45
- console.error(' It must contain: REACT_APP_ENTERPRISE_ID=your_project_id\\n');
65
+ console.error('\\n❌ Error: Project ID missing in .env.\\n');
46
66
  process.exit(1);
47
67
  }
48
68
 
49
- if (command === 'deploy') {
50
- (async () => {
51
- console.log('\\n\\x1b[36m%s\\x1b[0m', '⚡ FLEETBO CLOUD ENGINE v1.0');
52
- console.log('\\x1b[32m%s\\x1b[0m', 'Target Runtime:', keyApp);
53
- console.log('------------------------------------------------');
69
+ // 📡 SMART ROUTING : INJECTION SANS DOUBLONS
70
+ const injectRouteIntoAppJs = (pageName) => {
71
+ if (!fs.existsSync(APP_JS_PATH)) return false;
72
+ let content = fs.readFileSync(APP_JS_PATH, 'utf8');
54
73
 
55
- console.log('🔨 \\x1b[33mCompiling local assets...\\x1b[0m');
56
- try {
57
- execSync('npm run build', { stdio: 'inherit' });
58
- } catch (e) {
59
- console.error('\\n❌ Build failed. Fix errors and try again.');
60
- process.exit(1);
61
- }
74
+ // Vérification des ancres Fleetbo
75
+ if (!content.includes('// FLEETBO_IMPORTS') || !content.includes('{/* FLEETBO_ROUTES */}')) return false;
62
76
 
63
- let buildDir = 'dist';
64
- if (!fs.existsSync(path.join(process.cwd(), 'dist')) && fs.existsSync(path.join(process.cwd(), 'build'))) {
65
- buildDir = 'build';
66
- }
77
+ const importLine = \`import \${pageName} from './pages/\${pageName}';\`;
78
+ const routeLine = \`<Route path="/\${pageName.toLowerCase()}" element=<\${pageName} /> />\`;
67
79
 
68
- if (!fs.existsSync(path.join(process.cwd(), buildDir))) {
69
- console.error(\`\\n❌ Error: Build folder '\${buildDir}' not found.\`);
70
- process.exit(1);
71
- }
80
+ let injected = false;
72
81
 
73
- console.log('\\n📦 \\x1b[33mProcessing Neural Logic...\\x1b[0m');
74
- console.log(\` Detected stack: React (\${buildDir})\`);
82
+ // 1. Vérifie si l'import existe déjà
83
+ if (!content.includes(importLine) && !content.includes(\`import \${pageName} from\`) ) {
84
+ content = content.replace('// FLEETBO_IMPORTS', \`\${importLine}\\n// FLEETBO_IMPORTS\`);
85
+ injected = true;
86
+ }
75
87
 
88
+ // 2. Vérifie si la route existe déjà
89
+ if (!content.includes(routeLine) && !content.includes(\`path="/\${pageName.toLowerCase()}"\`) ) {
90
+ content = content.replace('{/* FLEETBO_ROUTES */}', \`\${routeLine}\\n {/* FLEETBO_ROUTES */}\`);
91
+ injected = true;
92
+ }
93
+
94
+ if (injected) fs.writeFileSync(APP_JS_PATH, content);
95
+ return injected;
96
+ };
97
+
98
+ // ⚡ ANIMATION : ALEX ENERGY TRANSFER
99
+ const showEnergyTransfer = async () => {
100
+ const width = 30;
101
+ for (let i = 0; i <= width; i++) {
102
+ const dots = "█".repeat(i); const empty = "░".repeat(width - i);
103
+ process.stdout.write(\`\\r \\x1b[32m⚡ Alex Energy Sync:\\x1b[0m [\${dots}\${empty}] \${Math.round((i / width) * 100)}%\`);
104
+ await new Promise(r => setTimeout(r, 45));
105
+ }
106
+ process.stdout.write('\\n');
107
+ };
108
+
109
+ // ==========================================
110
+ // 🤖 COMMANDE: ALEX (ARCHITECTE SMART)
111
+ // ==========================================
112
+ if (command === 'alex') {
113
+ checkGitSecurity();
114
+ const initialPrompt = args.slice(1).join(' ');
115
+
116
+ const processAlexRequest = async (prompt) => {
117
+ process.stdout.write('\\n\\x1b[33m🧠 Alex is architecting...\\x1b[0m\\r');
76
118
  try {
77
- const zipBuffer = await new Promise((resolve, reject) => {
78
- const chunks = [];
79
- const archive = archiver('zip', { zlib: { level: 9 } });
119
+ const result = await axios.post(ALEX_ENGINE_URL, { prompt, projectType: 'android' }, {
120
+ headers: { 'x-project-id': projectId }
121
+ });
122
+ const aiData = result.data.data || result.data;
123
+ process.stdout.write(' '.repeat(30) + '\\r');
124
+
125
+ if (aiData.status === 'success' && aiData.moduleData) {
126
+ const { fileName, code, mockFileName, mockCode } = aiData.moduleData;
127
+ // 🛡️ ALEX SAFEGUARD : ÉCRITURE AVEC BACKUP
128
+ const writeFile = (dir, name, content) => {
129
+ const fullPath = path.join(process.cwd(), dir);
130
+ const filePath = path.join(fullPath, name);
131
+ if (!fs.existsSync(fullPath)) fs.mkdirSync(fullPath, { recursive: true });
132
+
133
+ if (fs.existsSync(filePath)) {
134
+ const backupPath = filePath.replace(/(\\.jsx|\\.kt|\\.swift)$/, '.original$1');
135
+ fs.copyFileSync(filePath, backupPath);
136
+ fs.writeFileSync(filePath, content);
137
+ console.log(\` ✅ \\x1b[33m[Updated]\\x1b[0m \${dir}\${name} \\x1b[90m(Backup created)\\x1b[0m\`);
138
+ } else {
139
+ fs.writeFileSync(filePath, content);
140
+ console.log(\` ✅ \\x1b[32m[Created]\\x1b[0m \${dir}\${name}\`);
141
+ }
142
+ };
80
143
 
81
- archive.on('data', (chunk) => chunks.push(chunk));
82
- archive.on('error', (err) => reject(err));
83
- archive.on('end', () => resolve(Buffer.concat(chunks)));
144
+ console.log('');
145
+ // Natif
146
+ if (code && fileName && (fileName.endsWith('.kt') || fileName.endsWith('.swift'))) {
147
+ writeFile(fileName.endsWith('.kt') ? 'public/native/android/' : 'public/native/ios/', fileName, code);
148
+ }
149
+ // Mocks
150
+ if (mockCode && mockFileName) {
151
+ writeFile('src/pages/mocks/', mockFileName, mockCode);
152
+ writeFile('src/pages/mocks/', 'Quick.js', mockCode);
153
+ }
154
+ // React Page + Smart Route
155
+ if (code && fileName && fileName.endsWith('.jsx') && !mockCode) {
156
+ const pageName = fileName.replace('.jsx', '');
157
+ writeFile('src/pages/', fileName, code);
158
+ const linked = injectRouteIntoAppJs(pageName);
159
+ if (linked) console.log(\` \\x1b[35m📦 [Linked]\\x1b[0m App.js wired with Route /\${pageName.toLowerCase()}\`);
160
+ }
161
+ console.log('\\n\\x1b[32mAlex ❯\\x1b[0m ' + aiData.message);
162
+ }
163
+ } catch (error) { console.error('\\n\\x1b[31m❌ Alex Error:\\x1b[0m ' + error.message); }
164
+ };
84
165
 
166
+ const startAlexSession = async () => {
167
+ process.stdout.write('\\x1b[33m🛡️ Alex is checking runtime state...\\x1b[0m\\r');
168
+ try {
169
+ const validation = await axios.post(ALEX_ENGINE_URL, { validateProject: true, checkNetwork: true, projectKey: keyApp, testerEmail: testerEmail }, { headers: { 'x-project-id': projectId } });
170
+ if (!validation.data.exists || !validation.data.isRunning) {
171
+ console.error('\\n\\x1b[31m⚠️ ENGINE OFFLINE:\\x1b[0m Alex needs a live Uplink.');
172
+ process.exit(1);
173
+ }
174
+ process.stdout.write(' '.repeat(60) + '\\r');
175
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '\\x1b[32mAlex ❯ \\x1b[0m' });
176
+ console.log('\\n\\x1b[32m🤖 Alex is now online.\\x1b[0m Pilot safely.');
177
+ rl.prompt();
178
+ rl.on('line', async (line) => {
179
+ if (['exit', 'quit', 'bye'].includes(line.trim().toLowerCase())) { rl.close(); return; }
180
+ if (line.trim()) await processAlexRequest(line.trim());
181
+ rl.prompt();
182
+ }).on('close', () => { console.log('\\n👋 Alex going offline.'); process.exit(0); });
183
+ } catch (error) { process.exit(1); }
184
+ };
185
+ if (!initialPrompt || initialPrompt === '?') startAlexSession();
186
+ else processAlexRequest(initialPrompt);
187
+ return;
188
+ }
189
+
190
+ // ==========================================
191
+ // 🚀 COMMANDE: DEPLOY (ANIMÉ)
192
+ // ==========================================
193
+ if (command === 'deploy') {
194
+ checkGitSecurity();
195
+ (async () => {
196
+ console.log('\\n\\x1b[36m⚡ FLEETBO CLOUD ENGINE\\x1b[0m');
197
+ try {
198
+ execSync('npm run build', { stdio: 'inherit' });
199
+ let buildDir = fs.existsSync(path.join(process.cwd(), 'dist')) ? 'dist' : 'build';
200
+ const zipBuffer = await new Promise((resolve, reject) => {
201
+ const chunks = []; const archive = archiver('zip', { zlib: { level: 9 } });
202
+ archive.on('data', chunk => chunks.push(chunk));
203
+ archive.on('end', () => resolve(Buffer.concat(chunks)));
85
204
  archive.directory(path.join(process.cwd(), buildDir), false);
86
205
  archive.finalize();
87
206
  });
88
-
89
- console.log('☁️ \\x1b[33mEstablishing Uplink to Switch-Engine...\\x1b[0m');
90
-
91
- const response = await axios.post(CLOUD_ENGINE_URL, zipBuffer, {
92
- headers: {
93
- 'Content-Type': 'application/zip',
94
- 'x-project-id': projectId,
95
- 'Content-Length': zipBuffer.length
96
- },
97
- maxContentLength: Infinity,
98
- maxBodyLength: Infinity
99
- });
100
-
101
- console.log('------------------------------------------------');
102
- console.log('✅ \\x1b[1m\\x1b[32mDEPLOYMENT SUCCESSFUL\\x1b[0m');
103
- console.log(\` Build ID: \${response.data.buildId}\`);
104
- console.log(\` Payload: \${(zipBuffer.length / 1024 / 1024).toFixed(2)} MB\`);
105
- console.log('\\n📱 \\x1b[36mNative Runtime has been hot-swapped.\\x1b[0m');
106
- console.log(' Your app is live.\\n');
107
-
108
- } catch (error) {
109
- console.error('\\n❌ \\x1b[31mCRITICAL FAILURE\\x1b[0m');
110
- if (error.response) {
111
- console.error(\` Server responded with \${error.response.status}: "\${error.response.statusText}".\`);
112
- console.error(\` Details: \${JSON.stringify(error.response.data)}\`);
113
- } else {
114
- console.error(\` \${error.message}\`);
115
- }
116
- process.exit(1);
117
- }
207
+ console.log('\\n📦 \\x1b[33mPreparing Neural Logic for Uplink...\\x1b[0m');
208
+ await showEnergyTransfer();
209
+ await axios.post(CLOUD_ENGINE_URL, zipBuffer, { headers: { 'Content-Type': 'application/zip', 'x-project-id': projectId } });
210
+ console.log('\\n✅ \\x1b[1mDEPLOYMENT SUCCESSFUL\\x1b[0m | \\x1b[32mAlex ❯\\x1b[0m Runtime updated.');
211
+ } catch (error) { process.exit(1); }
118
212
  })();
119
213
  return;
120
214
  }
@@ -152,13 +246,33 @@ function killNetworkService() {
152
246
  } catch (e) {}
153
247
  }
154
248
 
249
+ // Variable de verrouillage globale
250
+ let isExiting = false;
251
+
155
252
  async function cleanupAndExit(code = 0) {
156
- console.log('\\n[Fleetbo] 🛑 Stopping development environment...');
253
+ // Si on est déjà en train de sortir, on ignore les autres signaux
254
+ if (isExiting) return;
255
+ isExiting = true;
256
+
257
+ console.log('\n[Fleetbo] 🛑 Stopping development environment...');
258
+
259
+ try {
260
+ // Nettoyage de l'Uplink dans Firestore
261
+ await axios.post(UPDATE_NETWORK_URL, {
262
+ keyApp: keyApp,
263
+ networkUrl: '',
264
+ tester: testerEmail
265
+ });
266
+ console.log('[Fleetbo] 🧊 Uplink cleaned successfully.');
267
+ } catch (e) {
268
+ // On ne bloque pas si le réseau échoue à la fermeture
269
+ }
270
+
157
271
  killNetworkService();
158
272
  killProcessOnPort(PORT);
159
273
  process.exit(code);
160
274
  }
161
-
275
+ // Les écouteurs restent les mêmes, mais la fonction gère maintenant le verrou
162
276
  process.on('SIGINT', () => cleanupAndExit(0));
163
277
  process.on('SIGTERM', () => cleanupAndExit(0));
164
278
 
@@ -171,9 +285,9 @@ async function syncFirebase(keyApp, networkUrl, testerEmail) {
171
285
  });
172
286
  console.log('\\n[Fleetbo] ---------------------------------------------------\\n');
173
287
  console.log(\`[Fleetbo] ✅ Uplink Status: Online for \${testerEmail}\`);
174
- console.log(\`[Fleetbo] 🚀 Simulator: https://fleetbo.io/studio/view/\${keyApp}\`);
175
- console.log(\'[Fleetbo] 👉 Your studio is ready. You can now open the link above.\n');
176
- console.log('[Fleetbo] ---------------------------------------------------\\n');
288
+ console.log(\`[Fleetbo] 🚀 Fleetbo Studio: https://fleetbo.io/studio/\${keyApp}\`);
289
+ console.log(\'[Fleetbo] 👉 Your studio is ready. You can now open the link above.\');
290
+ console.log('\\n[Fleetbo] ---------------------------------------------------\\n');
177
291
  } catch (err) {
178
292
  console.error(\`[Fleetbo] ⚠️ Sync Error: \${err.message}\`);
179
293
  }
@@ -325,7 +439,7 @@ async function setupProject() {
325
439
  const keys = await fetchProjectKeys(bootstrapTokenArg);
326
440
  if (!keys.enterpriseId) throw new Error("Invalid keys.");
327
441
 
328
- console.log(' [4/6] ⚙️ Configuring environment & CLI...');
442
+ console.log(' [4.1/6] ⚙️ Configuring environment & CLI...');
329
443
 
330
444
  const envContent =
331
445
  `REACT_APP_FLEETBO_DB_KEY=${keys.fleetboDBKey}
@@ -337,6 +451,32 @@ async function setupProject() {
337
451
  `;
338
452
 
339
453
  fs.writeFileSync(path.join(projectDir, '.env'), envContent, 'utf8');
454
+
455
+ console.log(' [4.2/6] 🛡️ Securing environment (adding .gitignore)...');
456
+
457
+ const gitignorePath = path.join(projectDir, '.gitignore');
458
+ const gitignoreContent = `# Fleetbo Security
459
+ .env
460
+ .env.local
461
+ node_modules/
462
+ dist/
463
+ build/
464
+ .DS_Store
465
+ npm-debug.log*
466
+ yarn-debug.log*
467
+ yarn-error.log*
468
+ `;
469
+
470
+ // On crée le fichier ou on ajoute .env s'il existe déjà
471
+ if (fs.existsSync(gitignorePath)) {
472
+ let currentGitignore = fs.readFileSync(gitignorePath, 'utf8');
473
+ if (!currentGitignore.includes('.env')) {
474
+ fs.appendFileSync(gitignorePath, '\\n# Fleetbo Secrets\\n.env\\n');
475
+ }
476
+ } else {
477
+ fs.writeFileSync(gitignorePath, gitignoreContent, 'utf8');
478
+ }
479
+
340
480
  const scriptsDir = path.join(projectDir, 'scripts');
341
481
  if (!fs.existsSync(scriptsDir)) {
342
482
  fs.mkdirSync(scriptsDir, { recursive: true });