cloud-ide-cide 2.0.36 → 2.0.38
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/deployer/node/upload-api.js +72 -34
- package/package.json +1 -1
- package/serverInit.js +36 -0
- package/uploadProject.js +38 -1
|
@@ -121,63 +121,99 @@ function stopApp(appCode) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function startApp(appCode) {
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
const appDir = path.join(SERVER_ROOT, appCode);
|
|
126
|
+
const entryFile = path.join(appDir, 'server.js');
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
if (!fs.existsSync(entryFile)) {
|
|
129
|
+
return resolve({ started: false, reason: `Entry file not found: ${entryFile}` });
|
|
130
|
+
}
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
// Stop existing process first
|
|
133
|
+
stopApp(appCode);
|
|
133
134
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
135
|
+
const logFile = getAppLogFile(appCode);
|
|
136
|
+
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
137
|
+
|
|
138
|
+
// Truncate log so we capture only this run's output
|
|
139
|
+
fs.writeFileSync(logFile, '');
|
|
140
|
+
|
|
141
|
+
const out = fs.openSync(logFile, 'a');
|
|
142
|
+
const err = fs.openSync(logFile, 'a');
|
|
143
|
+
|
|
144
|
+
const child = spawn('node', [entryFile], {
|
|
145
|
+
cwd: appDir,
|
|
146
|
+
detached: true,
|
|
147
|
+
stdio: ['ignore', out, err],
|
|
148
|
+
env: { ...process.env, NODE_ENV: 'production' },
|
|
149
|
+
});
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
child.unref();
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
// Save PID
|
|
154
|
+
fs.writeFileSync(getAppPidFile(appCode), String(child.pid));
|
|
155
|
+
appendLog(`START app=${appCode} pid=${child.pid} entry=${entryFile}`);
|
|
151
156
|
|
|
152
|
-
|
|
157
|
+
// Wait 3 seconds then check if process is still alive & read its output
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
const alive = isProcessRunning(child.pid);
|
|
160
|
+
let output = '';
|
|
161
|
+
try { output = fs.readFileSync(logFile, 'utf8').slice(-2000); } catch {}
|
|
162
|
+
|
|
163
|
+
if (!alive) {
|
|
164
|
+
try { fs.unlinkSync(getAppPidFile(appCode)); } catch {}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
resolve({
|
|
168
|
+
started: alive,
|
|
169
|
+
pid: alive ? child.pid : null,
|
|
170
|
+
logFile,
|
|
171
|
+
output,
|
|
172
|
+
reason: alive ? null : 'Process exited within 3s (see output)',
|
|
173
|
+
});
|
|
174
|
+
}, 3000);
|
|
175
|
+
});
|
|
153
176
|
}
|
|
154
177
|
|
|
155
|
-
function
|
|
178
|
+
function cleanDir(dir) {
|
|
179
|
+
if (!fs.existsSync(dir)) return;
|
|
180
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
181
|
+
// preserve node_modules to speed up npm install
|
|
182
|
+
if (entry === 'node_modules') continue;
|
|
183
|
+
const fullPath = path.join(dir, entry);
|
|
184
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function installAndStart(appCode) {
|
|
156
189
|
const appDir = path.join(SERVER_ROOT, appCode);
|
|
157
190
|
const steps = [];
|
|
191
|
+
let npmOutput = '';
|
|
158
192
|
|
|
159
193
|
// npm install if package.json exists
|
|
160
194
|
const pkgPath = path.join(appDir, 'package.json');
|
|
161
195
|
if (fs.existsSync(pkgPath)) {
|
|
162
196
|
try {
|
|
163
|
-
execSync('npm install --production', { cwd: appDir,
|
|
197
|
+
const out = execSync('npm install --production 2>&1', { cwd: appDir, encoding: 'utf8', shell: true, timeout: 120000 });
|
|
198
|
+
npmOutput = (out || '').slice(-1000);
|
|
164
199
|
steps.push('npm install: ok');
|
|
165
200
|
appendLog(`INSTALL app=${appCode} npm install ok`);
|
|
166
201
|
} catch (e) {
|
|
167
|
-
|
|
202
|
+
npmOutput = (e.stdout || '').slice(-500) + '\n' + (e.stderr || '').slice(-500);
|
|
203
|
+
steps.push(`npm install: failed`);
|
|
168
204
|
appendLog(`INSTALL app=${appCode} npm install FAILED: ${e.message}`);
|
|
169
205
|
}
|
|
170
206
|
}
|
|
171
207
|
|
|
172
208
|
// Start the app
|
|
173
|
-
const result = startApp(appCode);
|
|
209
|
+
const result = await startApp(appCode);
|
|
174
210
|
if (result.started) {
|
|
175
211
|
steps.push(`started: pid ${result.pid}`);
|
|
176
212
|
} else {
|
|
177
213
|
steps.push(`start failed: ${result.reason}`);
|
|
178
214
|
}
|
|
179
215
|
|
|
180
|
-
return { steps, ...result };
|
|
216
|
+
return { steps, npmOutput, ...result };
|
|
181
217
|
}
|
|
182
218
|
|
|
183
219
|
// ── Auth middleware ───────────────────────────────────────────────────────────
|
|
@@ -196,7 +232,7 @@ function authGuard(req, res, next) {
|
|
|
196
232
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
197
233
|
// ── Upload ────────────────────────────────────────────────────────────────────
|
|
198
234
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
199
|
-
app.post('/upload', authGuard, upload.single('file'), (req, res) => {
|
|
235
|
+
app.post('/upload', authGuard, upload.single('file'), async (req, res) => {
|
|
200
236
|
try {
|
|
201
237
|
if (!req.file) {
|
|
202
238
|
return res.status(400).json({ status: 'FAILED', message: 'No file uploaded.' });
|
|
@@ -222,14 +258,15 @@ app.post('/upload', authGuard, upload.single('file'), (req, res) => {
|
|
|
222
258
|
const releaseZipPath = path.join(releaseDir, `${version}.zip`);
|
|
223
259
|
fs.copyFileSync(req.file.path, releaseZipPath);
|
|
224
260
|
|
|
225
|
-
// ──
|
|
261
|
+
// ── Clean old files & extract to {server_root}/{appCode}/ ─────────────
|
|
226
262
|
fs.mkdirSync(appDir, { recursive: true });
|
|
263
|
+
cleanDir(appDir);
|
|
227
264
|
const zip = new AdmZip(req.file.path);
|
|
228
265
|
zip.extractAllTo(appDir, true);
|
|
229
266
|
fs.unlinkSync(req.file.path);
|
|
230
267
|
|
|
231
268
|
// ── Install dependencies & start the app ─────────────────────────────
|
|
232
|
-
const appResult = installAndStart(appCode);
|
|
269
|
+
const appResult = await installAndStart(appCode);
|
|
233
270
|
|
|
234
271
|
// ── Track state ──────────────────────────────────────────────────────
|
|
235
272
|
const deployments = loadDeployments();
|
|
@@ -298,7 +335,7 @@ app.get('/history', authGuard, (req, res) => {
|
|
|
298
335
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
299
336
|
// ── Rollback ──────────────────────────────────────────────────────────────────
|
|
300
337
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
301
|
-
app.post('/rollback', authGuard, (req, res) => {
|
|
338
|
+
app.post('/rollback', authGuard, async (req, res) => {
|
|
302
339
|
try {
|
|
303
340
|
const appCode = (req.body.appCode || '').replace(/[^a-zA-Z0-9\-_]/g, '');
|
|
304
341
|
const version = (req.body.version || '').replace(/[^a-zA-Z0-9\.\+\-_]/g, '');
|
|
@@ -316,13 +353,14 @@ app.post('/rollback', authGuard, (req, res) => {
|
|
|
316
353
|
const deployments = loadDeployments();
|
|
317
354
|
const previousVersion = deployments.apps?.[appCode]?.current?.version || null;
|
|
318
355
|
|
|
319
|
-
//
|
|
356
|
+
// Clean old files & re-extract the zip
|
|
320
357
|
fs.mkdirSync(appDir, { recursive: true });
|
|
358
|
+
cleanDir(appDir);
|
|
321
359
|
const zip = new AdmZip(releaseZipPath);
|
|
322
360
|
zip.extractAllTo(appDir, true);
|
|
323
361
|
|
|
324
362
|
// Install dependencies & restart the app
|
|
325
|
-
const appResult = installAndStart(appCode);
|
|
363
|
+
const appResult = await installAndStart(appCode);
|
|
326
364
|
|
|
327
365
|
const record = {
|
|
328
366
|
version,
|
package/package.json
CHANGED
package/serverInit.js
CHANGED
|
@@ -256,6 +256,7 @@ function listenerStatus(cideDir) {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
console.log('');
|
|
259
|
+
console.log(' ── Upload Listener ──────────────────────────');
|
|
259
260
|
console.log(` Server type : ${config.serverType}`);
|
|
260
261
|
console.log(` Server root : ${config.serverRoot}`);
|
|
261
262
|
console.log(` Port : ${config.port || 4500}`);
|
|
@@ -275,6 +276,41 @@ function listenerStatus(cideDir) {
|
|
|
275
276
|
}
|
|
276
277
|
console.log(` Log : ${getServerLog(cideDir)}`);
|
|
277
278
|
}
|
|
279
|
+
|
|
280
|
+
// ── Show deployed apps status ─────────────────────────────────────────
|
|
281
|
+
const deployments = safeReadJson(path.join(cideDir, 'deployments.json'));
|
|
282
|
+
if (deployments && deployments.apps && Object.keys(deployments.apps).length > 0) {
|
|
283
|
+
console.log('');
|
|
284
|
+
console.log(' ── Deployed Apps ────────────────────────────');
|
|
285
|
+
|
|
286
|
+
for (const [appCode, appData] of Object.entries(deployments.apps)) {
|
|
287
|
+
const pidFile = path.join(cideDir, 'logs', `${appCode}.pid`);
|
|
288
|
+
let appPid = null;
|
|
289
|
+
try { appPid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10); } catch {}
|
|
290
|
+
if (isNaN(appPid)) appPid = null;
|
|
291
|
+
|
|
292
|
+
const appRunning = appPid ? isProcessRunning(appPid) : false;
|
|
293
|
+
const current = appData.current || {};
|
|
294
|
+
const appLogFile = path.join(cideDir, 'logs', `${appCode}.log`);
|
|
295
|
+
|
|
296
|
+
console.log('');
|
|
297
|
+
console.log(` App : ${appCode}`);
|
|
298
|
+
console.log(` Path : ${current.appDir || 'N/A'}`);
|
|
299
|
+
console.log(` Version : ${current.version || 'N/A'}`);
|
|
300
|
+
console.log(` Deployed : ${current.deployedAt || 'N/A'}`);
|
|
301
|
+
if (appRunning) {
|
|
302
|
+
console.log(` Status : Running (PID: ${appPid})`);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(' Status : Stopped');
|
|
305
|
+
if (appPid) {
|
|
306
|
+
try { fs.unlinkSync(pidFile); } catch {}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
console.log(` Log : ${appLogFile}`);
|
|
310
|
+
console.log(` Releases : ${appData.history ? appData.history.length : 0}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
278
314
|
console.log('');
|
|
279
315
|
}
|
|
280
316
|
|
package/uploadProject.js
CHANGED
|
@@ -629,6 +629,17 @@ async function uploadProject(opts = {}) {
|
|
|
629
629
|
console.log(' Rollback successful!');
|
|
630
630
|
console.log(` Now active : ${result.rolledBackTo}`);
|
|
631
631
|
console.log(` Previous : ${result.previousVersion}`);
|
|
632
|
+
if (result.appProcess) {
|
|
633
|
+
if (result.appProcess.started) {
|
|
634
|
+
console.log(` App : Running (PID: ${result.appProcess.pid})`);
|
|
635
|
+
} else {
|
|
636
|
+
console.log(` App : Failed to start`);
|
|
637
|
+
if (result.appProcess.reason) console.log(` Reason : ${result.appProcess.reason}`);
|
|
638
|
+
}
|
|
639
|
+
if (result.appProcess.steps) {
|
|
640
|
+
result.appProcess.steps.forEach(s => console.log(` Step : ${s}`));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
632
643
|
console.log('');
|
|
633
644
|
} else {
|
|
634
645
|
console.error(` Rollback failed: ${result.message || JSON.stringify(result)}`);
|
|
@@ -704,7 +715,33 @@ async function uploadProject(opts = {}) {
|
|
|
704
715
|
console.log(` App : ${result.appCode}`);
|
|
705
716
|
console.log(` Server : ${label}`);
|
|
706
717
|
if (result.previousVersion) console.log(` Previous : ${result.previousVersion}`);
|
|
707
|
-
if (result.
|
|
718
|
+
if (result.appDir) console.log(` Path : ${result.appDir}`);
|
|
719
|
+
else if (result.releasePath) console.log(` Path : ${result.releasePath}`);
|
|
720
|
+
if (result.appProcess) {
|
|
721
|
+
console.log('');
|
|
722
|
+
if (result.appProcess.started) {
|
|
723
|
+
console.log(` App : Running (PID: ${result.appProcess.pid})`);
|
|
724
|
+
if (result.appProcess.logFile) console.log(` App Log : ${result.appProcess.logFile}`);
|
|
725
|
+
} else {
|
|
726
|
+
console.log(` App : Failed to start`);
|
|
727
|
+
if (result.appProcess.reason) console.log(` Reason : ${result.appProcess.reason}`);
|
|
728
|
+
}
|
|
729
|
+
if (result.appProcess.steps) {
|
|
730
|
+
result.appProcess.steps.forEach(s => console.log(` Step : ${s}`));
|
|
731
|
+
}
|
|
732
|
+
if (result.appProcess.output) {
|
|
733
|
+
console.log('');
|
|
734
|
+
console.log(' ── App Console Output ──────────────────────');
|
|
735
|
+
console.log(result.appProcess.output.trim().split('\n').map(l => ` ${l}`).join('\n'));
|
|
736
|
+
console.log(' ─────────────────────────────────────────────');
|
|
737
|
+
}
|
|
738
|
+
if (result.appProcess.npmOutput) {
|
|
739
|
+
console.log('');
|
|
740
|
+
console.log(' ── npm install Output ──────────────────────');
|
|
741
|
+
console.log(result.appProcess.npmOutput.trim().split('\n').map(l => ` ${l}`).join('\n'));
|
|
742
|
+
console.log(' ─────────────────────────────────────────────');
|
|
743
|
+
}
|
|
744
|
+
}
|
|
708
745
|
console.log('');
|
|
709
746
|
} else {
|
|
710
747
|
console.error(` Upload failed: ${result.message || JSON.stringify(result)}`);
|