create-fleetbo-project 1.2.38 → 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.
- package/install-react-template.js +165 -176
- package/package.json +1 -1
- package/recov.js +208 -68
|
@@ -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('
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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/\${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
|
|
242
|
-
|
|
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
|
-
|
|
351
|
-
`REACT_APP_FLEETBO_DB_KEY=${keys.fleetboDBKey}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
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
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
buildDir = 'build';
|
|
66
|
-
}
|
|
77
|
+
const importLine = \`import \${pageName} from './pages/\${pageName}';\`;
|
|
78
|
+
const routeLine = \`<Route path="/\${pageName.toLowerCase()}" element=<\${pageName} /> />\`;
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
console.error(\`\\n❌ Error: Build folder '\${buildDir}' not found.\`);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
80
|
+
let injected = false;
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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] 🚀
|
|
175
|
-
console.log(\'[Fleetbo] 👉 Your studio is ready. You can now open the link above.\
|
|
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 });
|