cloud-ide-cide 2.0.34
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/buildAllProjects.js +131 -0
- package/buildProject.js +209 -0
- package/buildWorkspace.js +225 -0
- package/cideShell.js +1292 -0
- package/cli.js +521 -0
- package/createProject.js +71 -0
- package/deployer/node/upload-api.js +265 -0
- package/deployer/php/setup.php +332 -0
- package/deployer/php/upload-ui.php +294 -0
- package/package.json +53 -0
- package/publishPackage.js +969 -0
- package/resolveNgProjectName.js +94 -0
- package/serverInit.js +665 -0
- package/startProject.js +57 -0
- package/uploadProject.js +727 -0
- package/watchLinkProject.js +40 -0
package/serverInit.js
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cide server-init — scaffold .cide/ folder, install listener, manage Node background process.
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* cide server-init — interactive setup (scaffold + install + start for Node)
|
|
6
|
+
* cide server-init --type php|node — non-interactive type
|
|
7
|
+
* cide server-init --path /var/www — non-interactive path
|
|
8
|
+
* cide server-init --start — start the Node listener (background)
|
|
9
|
+
* cide server-init --stop — stop the Node listener
|
|
10
|
+
* cide server-init --status — check if Node listener is running
|
|
11
|
+
* cide server-init --restart — restart the Node listener
|
|
12
|
+
*
|
|
13
|
+
* Shell:
|
|
14
|
+
* server init — interactive setup
|
|
15
|
+
* server start — start Node listener
|
|
16
|
+
* server stop — stop Node listener
|
|
17
|
+
* server status — check listener status
|
|
18
|
+
* server restart — restart listener
|
|
19
|
+
*
|
|
20
|
+
* Server folder structure:
|
|
21
|
+
*
|
|
22
|
+
* {server_root}/
|
|
23
|
+
* .cide/
|
|
24
|
+
* config.json ← token, server type, pid, port
|
|
25
|
+
* deployments.json ← current + history per app
|
|
26
|
+
* scripts/ ← listener script + deps
|
|
27
|
+
* releases/{app}/{ver}/ ← extracted code per version
|
|
28
|
+
* backups/{app}/{ver}.zip
|
|
29
|
+
* logs/
|
|
30
|
+
* deploy.log ← upload/rollback audit log
|
|
31
|
+
* server.log ← Node listener stdout/stderr
|
|
32
|
+
* server.pid ← PID of running Node listener
|
|
33
|
+
* current/{app} ← symlink → active release
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const fs = require('fs');
|
|
37
|
+
const path = require('path');
|
|
38
|
+
const readline = require('readline');
|
|
39
|
+
const crypto = require('crypto');
|
|
40
|
+
const { execSync, spawn } = require('child_process');
|
|
41
|
+
|
|
42
|
+
const PHP_SCRIPT_NAME = 'upload-ui.php';
|
|
43
|
+
const NODE_SCRIPT_NAME = 'upload-api.js';
|
|
44
|
+
|
|
45
|
+
function getDeployerSourceDir() {
|
|
46
|
+
// Scripts are bundled inside the CLI package at deployer/
|
|
47
|
+
const bundledPath = path.join(__dirname, 'deployer');
|
|
48
|
+
if (fs.existsSync(bundledPath)) return bundledPath;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function askQuestion(rl, question) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function generateToken() {
|
|
59
|
+
return crypto.randomBytes(32).toString('hex');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ensureDir(dir) {
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function safeReadJson(p) {
|
|
67
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find cide.json in cwd or parent directories.
|
|
72
|
+
*/
|
|
73
|
+
function findCideJson() {
|
|
74
|
+
let d = path.resolve(process.cwd());
|
|
75
|
+
for (let i = 0; i < 10; i++) {
|
|
76
|
+
const p = path.join(d, 'cide.json');
|
|
77
|
+
if (fs.existsSync(p)) return p;
|
|
78
|
+
const parent = path.dirname(d);
|
|
79
|
+
if (parent === d) break;
|
|
80
|
+
d = parent;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Auto-detect server type from cide.json in cwd or parent directories.
|
|
87
|
+
*/
|
|
88
|
+
function detectServerType() {
|
|
89
|
+
let d = path.resolve(process.cwd());
|
|
90
|
+
for (let i = 0; i < 10; i++) {
|
|
91
|
+
const cj = safeReadJson(path.join(d, 'cide.json'));
|
|
92
|
+
if (cj) {
|
|
93
|
+
const tmpl = (cj.templete || cj.template || '').toLowerCase();
|
|
94
|
+
if (tmpl === 'angular' || tmpl === 'react') return { type: 'php', template: tmpl, name: cj.name };
|
|
95
|
+
if (tmpl === 'node') return { type: 'node', template: tmpl, name: cj.name };
|
|
96
|
+
}
|
|
97
|
+
const parent = path.dirname(d);
|
|
98
|
+
if (parent === d) break;
|
|
99
|
+
d = parent;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
105
|
+
// ── Node listener process management ──────────────────────────────────────────
|
|
106
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find the .cide/ folder — walk up from cwd looking for .cide/config.json.
|
|
110
|
+
*/
|
|
111
|
+
function findCideDir() {
|
|
112
|
+
let d = path.resolve(process.cwd());
|
|
113
|
+
for (let i = 0; i < 10; i++) {
|
|
114
|
+
const candidate = path.join(d, '.cide', 'config.json');
|
|
115
|
+
if (fs.existsSync(candidate)) return path.join(d, '.cide');
|
|
116
|
+
const parent = path.dirname(d);
|
|
117
|
+
if (parent === d) break;
|
|
118
|
+
d = parent;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getPidFile(cideDir) {
|
|
124
|
+
return path.join(cideDir, 'logs', 'server.pid');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getServerLog(cideDir) {
|
|
128
|
+
return path.join(cideDir, 'logs', 'server.log');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function readPid(cideDir) {
|
|
132
|
+
const pidFile = getPidFile(cideDir);
|
|
133
|
+
try {
|
|
134
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
135
|
+
return isNaN(pid) ? null : pid;
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isProcessRunning(pid) {
|
|
142
|
+
try {
|
|
143
|
+
process.kill(pid, 0); // signal 0 = just check if alive
|
|
144
|
+
return true;
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function startNodeListener(cideDir) {
|
|
151
|
+
const config = safeReadJson(path.join(cideDir, 'config.json'));
|
|
152
|
+
if (!config) {
|
|
153
|
+
console.error(' No config.json found. Run: cide server-init');
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
if (config.serverType !== 'node') {
|
|
157
|
+
console.error(' This is a PHP server — no background process needed.');
|
|
158
|
+
console.error(' PHP runs via Apache/Nginx automatically.');
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check if already running
|
|
163
|
+
const existingPid = readPid(cideDir);
|
|
164
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
165
|
+
console.log(` Listener already running (PID: ${existingPid})`);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const scriptsDir = config.paths?.scripts || path.join(cideDir, 'scripts');
|
|
170
|
+
const scriptPath = path.join(scriptsDir, NODE_SCRIPT_NAME);
|
|
171
|
+
|
|
172
|
+
if (!fs.existsSync(scriptPath)) {
|
|
173
|
+
console.error(` Listener script not found: ${scriptPath}`);
|
|
174
|
+
console.error(' Run: cide server-init');
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Check node_modules
|
|
179
|
+
if (!fs.existsSync(path.join(scriptsDir, 'node_modules'))) {
|
|
180
|
+
console.log(' Installing dependencies...');
|
|
181
|
+
try {
|
|
182
|
+
execSync('npm install', { cwd: scriptsDir, stdio: 'inherit', shell: true });
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error(' npm install failed:', e.message);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const logFile = getServerLog(cideDir);
|
|
190
|
+
ensureDir(path.dirname(logFile));
|
|
191
|
+
const out = fs.openSync(logFile, 'a');
|
|
192
|
+
const err = fs.openSync(logFile, 'a');
|
|
193
|
+
|
|
194
|
+
const port = config.port || 4500;
|
|
195
|
+
const env = {
|
|
196
|
+
...process.env,
|
|
197
|
+
CIDE_UPLOAD_TOKEN: config.token || '',
|
|
198
|
+
CIDE_UPLOAD_PORT: String(port),
|
|
199
|
+
CIDE_UPLOAD_BASE_PATH: cideDir,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const child = spawn('node', [scriptPath], {
|
|
203
|
+
cwd: scriptsDir,
|
|
204
|
+
env,
|
|
205
|
+
detached: true,
|
|
206
|
+
stdio: ['ignore', out, err],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
child.unref();
|
|
210
|
+
|
|
211
|
+
// Save PID
|
|
212
|
+
const pidFile = getPidFile(cideDir);
|
|
213
|
+
fs.writeFileSync(pidFile, String(child.pid));
|
|
214
|
+
|
|
215
|
+
console.log(` Listener started (PID: ${child.pid}, port: ${port})`);
|
|
216
|
+
console.log(` Log: ${logFile}`);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function stopNodeListener(cideDir) {
|
|
221
|
+
const pid = readPid(cideDir);
|
|
222
|
+
if (!pid) {
|
|
223
|
+
console.log(' No listener PID found. Not running.');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!isProcessRunning(pid)) {
|
|
228
|
+
console.log(` PID ${pid} is not running (stale). Cleaning up.`);
|
|
229
|
+
try { fs.unlinkSync(getPidFile(cideDir)); } catch {}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
process.kill(pid, 'SIGTERM');
|
|
235
|
+
console.log(` Listener stopped (PID: ${pid})`);
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.error(` Failed to stop PID ${pid}: ${e.message}`);
|
|
238
|
+
|
|
239
|
+
// Try harder on Windows
|
|
240
|
+
try {
|
|
241
|
+
execSync(`taskkill /PID ${pid} /F`, { stdio: 'ignore', shell: true });
|
|
242
|
+
console.log(` Force-killed PID ${pid}`);
|
|
243
|
+
} catch {
|
|
244
|
+
console.error(` Could not kill PID ${pid}. Stop it manually.`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try { fs.unlinkSync(getPidFile(cideDir)); } catch {}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function listenerStatus(cideDir) {
|
|
252
|
+
const config = safeReadJson(path.join(cideDir, 'config.json'));
|
|
253
|
+
if (!config) {
|
|
254
|
+
console.log(' No .cide/config.json found.');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log(` Server type : ${config.serverType}`);
|
|
260
|
+
console.log(` Server root : ${config.serverRoot}`);
|
|
261
|
+
console.log(` Port : ${config.port || 4500}`);
|
|
262
|
+
|
|
263
|
+
if (config.serverType === 'php') {
|
|
264
|
+
console.log(' Status : PHP runs via Apache/Nginx (no background process)');
|
|
265
|
+
} else {
|
|
266
|
+
const pid = readPid(cideDir);
|
|
267
|
+
if (pid && isProcessRunning(pid)) {
|
|
268
|
+
console.log(` Status : Running (PID: ${pid})`);
|
|
269
|
+
} else {
|
|
270
|
+
console.log(' Status : Stopped');
|
|
271
|
+
if (pid) {
|
|
272
|
+
console.log(` (Stale PID file: ${pid})`);
|
|
273
|
+
try { fs.unlinkSync(getPidFile(cideDir)); } catch {}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
console.log(` Log : ${getServerLog(cideDir)}`);
|
|
277
|
+
}
|
|
278
|
+
console.log('');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
282
|
+
// ── Main server-init ──────────────────────────────────────────────────────────
|
|
283
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
284
|
+
|
|
285
|
+
async function serverInit(opts = {}) {
|
|
286
|
+
// ── Sub-commands: start / stop / status / restart ─────────────────────
|
|
287
|
+
if (opts.start || opts.stop || opts.status || opts.restart) {
|
|
288
|
+
const cideDir = findCideDir();
|
|
289
|
+
if (!cideDir) {
|
|
290
|
+
console.error(' No .cide/ folder found. Run: cide server-init first.');
|
|
291
|
+
process.exitCode = 1;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (opts.status) {
|
|
296
|
+
listenerStatus(cideDir);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (opts.stop) {
|
|
300
|
+
stopNodeListener(cideDir);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (opts.restart) {
|
|
304
|
+
stopNodeListener(cideDir);
|
|
305
|
+
startNodeListener(cideDir);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (opts.start) {
|
|
309
|
+
startNodeListener(cideDir);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── Full init flow ────────────────────────────────────────────────────
|
|
316
|
+
const deployerDir = getDeployerSourceDir();
|
|
317
|
+
if (!deployerDir) {
|
|
318
|
+
console.error('Could not find deployer scripts. The CLI package is missing the deployer/ folder.');
|
|
319
|
+
process.exitCode = 1;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const rl = opts.readlineInterface || readline.createInterface({
|
|
324
|
+
input: process.stdin,
|
|
325
|
+
output: process.stdout,
|
|
326
|
+
});
|
|
327
|
+
const ownRl = !opts.readlineInterface;
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
// ── Step 1: Server type ─────────────────────────────────────────────
|
|
331
|
+
console.log('');
|
|
332
|
+
console.log(' Cloud IDE — Server Init');
|
|
333
|
+
console.log(' ─────────────────────────────────────────');
|
|
334
|
+
console.log(' Which server are you setting up?');
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log(' [1] PHP server (for UI — Angular/React apps served by Apache/Nginx)');
|
|
337
|
+
console.log(' [2] Node server (for API — Node.js apps served by Express/PM2)');
|
|
338
|
+
console.log('');
|
|
339
|
+
|
|
340
|
+
let serverType = opts.type || '';
|
|
341
|
+
if (!serverType) {
|
|
342
|
+
const answer = await askQuestion(rl, ' Select (1 or 2): ');
|
|
343
|
+
if (answer === '1' || answer.toLowerCase() === 'php') {
|
|
344
|
+
serverType = 'php';
|
|
345
|
+
} else if (answer === '2' || answer.toLowerCase() === 'node') {
|
|
346
|
+
serverType = 'node';
|
|
347
|
+
} else {
|
|
348
|
+
console.error(` Invalid selection: "${answer}"`);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
354
|
+
// PHP flow — generate setup.php, user pastes on server, CLI calls it
|
|
355
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
356
|
+
if (serverType === 'php') {
|
|
357
|
+
const setupSrc = path.join(deployerDir, 'php', 'setup.php');
|
|
358
|
+
if (!fs.existsSync(setupSrc)) {
|
|
359
|
+
console.error(' setup.php not found in CLI package.');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Save setup.php locally
|
|
364
|
+
const outputPath = path.join(process.cwd(), 'setup.php');
|
|
365
|
+
fs.copyFileSync(setupSrc, outputPath);
|
|
366
|
+
|
|
367
|
+
console.log('');
|
|
368
|
+
console.log(` setup.php generated at: ${outputPath}`);
|
|
369
|
+
console.log('');
|
|
370
|
+
console.log(' Paste this file on your PHP server root');
|
|
371
|
+
console.log(' (the folder where your site is served from)');
|
|
372
|
+
console.log('');
|
|
373
|
+
|
|
374
|
+
// Wait for user to paste it
|
|
375
|
+
const pasted = await askQuestion(rl, ' Did you paste setup.php on the server? (yes/no): ');
|
|
376
|
+
if (pasted.toLowerCase() !== 'yes' && pasted.toLowerCase() !== 'y') {
|
|
377
|
+
console.log(' Setup paused. Paste setup.php on the server and run cide server-init again.');
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Ask for the server URL where they pasted it
|
|
382
|
+
const serverUrl = await askQuestion(rl, ' Server URL where you pasted (e.g. https://ui.example.com): ');
|
|
383
|
+
if (!serverUrl) {
|
|
384
|
+
console.error(' Server URL is required.');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Call setup.php?token=CREATE on the server
|
|
389
|
+
const setupUrl = `${serverUrl.replace(/\/+$/, '')}/setup.php?token=CREATE`;
|
|
390
|
+
console.log(`\n Calling: ${setupUrl}`);
|
|
391
|
+
|
|
392
|
+
let setupResult;
|
|
393
|
+
try {
|
|
394
|
+
const axios = require('axios');
|
|
395
|
+
const resp = await axios.get(setupUrl, { timeout: 30000 });
|
|
396
|
+
setupResult = resp.data;
|
|
397
|
+
} catch (err) {
|
|
398
|
+
const msg = err.response?.data?.message || err.message;
|
|
399
|
+
console.error(`\n Failed to reach setup.php: ${msg}`);
|
|
400
|
+
console.error(' Make sure the file is accessible and try again.');
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (setupResult.status !== 'SUCCESS') {
|
|
405
|
+
console.error(`\n Setup failed: ${setupResult.message || JSON.stringify(setupResult)}`);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log(' Server setup complete!');
|
|
410
|
+
console.log(` Token: ${setupResult.token}`);
|
|
411
|
+
console.log('');
|
|
412
|
+
|
|
413
|
+
// Ask for build path and app code
|
|
414
|
+
const buildPath = await askQuestion(rl, ' Build path (e.g. dist/cloud-ide-lms-ui/browser): ');
|
|
415
|
+
const appCode = await askQuestion(rl, ' App code (e.g. cloud-ide-lms-ui): ');
|
|
416
|
+
const serverName = await askQuestion(rl, ' Server name (e.g. Production, Staging): ');
|
|
417
|
+
|
|
418
|
+
// Build the upload listener URL from setup response
|
|
419
|
+
const scriptUrl = `${serverUrl.replace(/\/+$/, '')}/.cide/scripts/upload-ui.php`;
|
|
420
|
+
|
|
421
|
+
// Auto-read environment.ts and host-manager.json
|
|
422
|
+
const cideJsonPath = findCideJson();
|
|
423
|
+
let envBlock = {};
|
|
424
|
+
let hostsBlock = {};
|
|
425
|
+
if (cideJsonPath) {
|
|
426
|
+
const projectDir = path.dirname(cideJsonPath);
|
|
427
|
+
const { findEnvFile, findHostManagerFile, parseExistingEnv } = require('./uploadProject');
|
|
428
|
+
|
|
429
|
+
const envFile = findEnvFile(projectDir);
|
|
430
|
+
if (envFile) {
|
|
431
|
+
envBlock = parseExistingEnv(fs.readFileSync(envFile, 'utf8'));
|
|
432
|
+
console.log(`\n Read environment from: ${envFile}`);
|
|
433
|
+
Object.entries(envBlock).forEach(([k, v]) => console.log(` ${k}: ${v}`));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const hostFile = findHostManagerFile(projectDir);
|
|
437
|
+
if (hostFile) {
|
|
438
|
+
try {
|
|
439
|
+
const hostJson = JSON.parse(fs.readFileSync(hostFile, 'utf8'));
|
|
440
|
+
if (Array.isArray(hostJson.hosts)) {
|
|
441
|
+
for (const h of hostJson.hosts) {
|
|
442
|
+
if (h.url) hostsBlock[h.url] = h.replace || '';
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
console.log(` Read hosts from: ${hostFile}`);
|
|
446
|
+
Object.entries(hostsBlock).forEach(([k, v]) => console.log(` ${k} → ${v}`));
|
|
447
|
+
} catch { /* skip */ }
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (Object.keys(envBlock).length || Object.keys(hostsBlock).length) {
|
|
451
|
+
console.log(' (Edit "env" and "hosts" in cide.json to set server-specific values)\n');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Update local cide.json
|
|
456
|
+
if (cideJsonPath) {
|
|
457
|
+
const cj = safeReadJson(cideJsonPath);
|
|
458
|
+
if (cj) {
|
|
459
|
+
const newEntry = {
|
|
460
|
+
name: serverName || 'Production',
|
|
461
|
+
server_url: scriptUrl,
|
|
462
|
+
build_path: buildPath || 'dist',
|
|
463
|
+
app_code: appCode || cj.name || '',
|
|
464
|
+
token: setupResult.token,
|
|
465
|
+
env: envBlock,
|
|
466
|
+
hosts: hostsBlock,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Normalize existing upload to array
|
|
470
|
+
let uploadArr = [];
|
|
471
|
+
if (Array.isArray(cj.upload)) {
|
|
472
|
+
uploadArr = cj.upload;
|
|
473
|
+
} else if (cj.upload && cj.upload.server_url) {
|
|
474
|
+
uploadArr = [cj.upload];
|
|
475
|
+
}
|
|
476
|
+
uploadArr.push(newEntry);
|
|
477
|
+
cj.upload = uploadArr;
|
|
478
|
+
|
|
479
|
+
fs.writeFileSync(cideJsonPath, JSON.stringify(cj, null, 4) + '\n');
|
|
480
|
+
console.log(`\n Updated: ${cideJsonPath}`);
|
|
481
|
+
console.log(` Added server: ${serverName || 'Production'} → ${scriptUrl}`);
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
console.log('\n No cide.json found in current project.');
|
|
485
|
+
console.log(' Add this to your cide.json upload array:');
|
|
486
|
+
console.log(JSON.stringify({
|
|
487
|
+
name: serverName || 'Production',
|
|
488
|
+
server_url: scriptUrl,
|
|
489
|
+
build_path: buildPath || 'dist',
|
|
490
|
+
app_code: appCode || 'your-app',
|
|
491
|
+
token: setupResult.token,
|
|
492
|
+
}, null, 4));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
console.log('');
|
|
496
|
+
if (setupResult.setupFileDeleted) {
|
|
497
|
+
console.log(' setup.php auto-deleted from server.');
|
|
498
|
+
} else {
|
|
499
|
+
console.log(' Note: Could not auto-delete setup.php. Remove it manually from the server.');
|
|
500
|
+
}
|
|
501
|
+
console.log('');
|
|
502
|
+
console.log(' Ready! Run: cide upload');
|
|
503
|
+
console.log('');
|
|
504
|
+
|
|
505
|
+
// Clean up local setup.php
|
|
506
|
+
try { fs.unlinkSync(outputPath); } catch {}
|
|
507
|
+
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
512
|
+
// Node flow — scaffold locally, install, auto-start
|
|
513
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
514
|
+
let serverRoot = opts.path || '';
|
|
515
|
+
if (!serverRoot) {
|
|
516
|
+
serverRoot = await askQuestion(rl, ` Server root directory [${process.cwd()}]: `);
|
|
517
|
+
if (!serverRoot) serverRoot = process.cwd();
|
|
518
|
+
}
|
|
519
|
+
serverRoot = path.resolve(serverRoot);
|
|
520
|
+
|
|
521
|
+
let port = 4500;
|
|
522
|
+
const portAnswer = await askQuestion(rl, ' Listener port [4500]: ');
|
|
523
|
+
if (portAnswer) {
|
|
524
|
+
const p = parseInt(portAnswer, 10);
|
|
525
|
+
if (!isNaN(p) && p > 0 && p < 65536) port = p;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const cideDir = path.join(serverRoot, '.cide');
|
|
529
|
+
const scriptsDir = path.join(cideDir, 'scripts');
|
|
530
|
+
const releasesDir = path.join(cideDir, 'releases');
|
|
531
|
+
const backupsDir = path.join(cideDir, 'backups');
|
|
532
|
+
const logsDir = path.join(cideDir, 'logs');
|
|
533
|
+
const currentDir = path.join(serverRoot, 'current');
|
|
534
|
+
|
|
535
|
+
ensureDir(scriptsDir);
|
|
536
|
+
ensureDir(releasesDir);
|
|
537
|
+
ensureDir(backupsDir);
|
|
538
|
+
ensureDir(logsDir);
|
|
539
|
+
ensureDir(currentDir);
|
|
540
|
+
|
|
541
|
+
const configPath = path.join(cideDir, 'config.json');
|
|
542
|
+
const existingConfig = safeReadJson(configPath);
|
|
543
|
+
const token = existingConfig?.token || generateToken();
|
|
544
|
+
|
|
545
|
+
const config = {
|
|
546
|
+
serverType,
|
|
547
|
+
token,
|
|
548
|
+
port,
|
|
549
|
+
serverRoot,
|
|
550
|
+
createdAt: existingConfig?.createdAt || new Date().toISOString(),
|
|
551
|
+
updatedAt: new Date().toISOString(),
|
|
552
|
+
paths: {
|
|
553
|
+
scripts: scriptsDir,
|
|
554
|
+
releases: releasesDir,
|
|
555
|
+
backups: backupsDir,
|
|
556
|
+
logs: logsDir,
|
|
557
|
+
current: currentDir,
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
561
|
+
|
|
562
|
+
const deploymentsPath = path.join(cideDir, 'deployments.json');
|
|
563
|
+
if (!fs.existsSync(deploymentsPath)) {
|
|
564
|
+
fs.writeFileSync(deploymentsPath, JSON.stringify({ apps: {} }, null, 2) + '\n');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const logPath = path.join(logsDir, 'deploy.log');
|
|
568
|
+
if (!fs.existsSync(logPath)) {
|
|
569
|
+
fs.writeFileSync(logPath, `# Cloud IDE deploy log — created ${new Date().toISOString()}\n`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
{
|
|
573
|
+
const src = path.join(deployerDir, 'node', NODE_SCRIPT_NAME);
|
|
574
|
+
if (!fs.existsSync(src)) {
|
|
575
|
+
console.error(` Source script not found: ${src}`);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
fs.copyFileSync(src, path.join(scriptsDir, NODE_SCRIPT_NAME));
|
|
579
|
+
|
|
580
|
+
// package.json
|
|
581
|
+
const pkgPath = path.join(scriptsDir, 'package.json');
|
|
582
|
+
if (!fs.existsSync(pkgPath)) {
|
|
583
|
+
fs.writeFileSync(pkgPath, JSON.stringify({
|
|
584
|
+
name: 'cide-upload-api',
|
|
585
|
+
version: '1.0.0',
|
|
586
|
+
private: true,
|
|
587
|
+
scripts: { start: `node ${NODE_SCRIPT_NAME}` },
|
|
588
|
+
dependencies: {
|
|
589
|
+
express: '^4.18.0',
|
|
590
|
+
multer: '^1.4.5-lts.1',
|
|
591
|
+
'adm-zip': '^0.5.10',
|
|
592
|
+
},
|
|
593
|
+
}, null, 2) + '\n');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// npm install
|
|
597
|
+
console.log('\n Installing Node dependencies...');
|
|
598
|
+
try {
|
|
599
|
+
execSync('npm install', { cwd: scriptsDir, stdio: 'inherit', shell: true });
|
|
600
|
+
} catch (e) {
|
|
601
|
+
console.error(' npm install failed. Run manually: cd ' + scriptsDir + ' && npm install');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Auto-start the listener
|
|
605
|
+
console.log('');
|
|
606
|
+
startNodeListener(cideDir);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ── Print Node summary ─────────────────────────────────────────────
|
|
610
|
+
console.log('');
|
|
611
|
+
console.log(' ─── Node Setup Complete ───');
|
|
612
|
+
console.log('');
|
|
613
|
+
console.log(' Server folder layout:');
|
|
614
|
+
console.log(` ${serverRoot}/`);
|
|
615
|
+
console.log(` ├── .cide/`);
|
|
616
|
+
console.log(` │ ├── config.json ← token, type, port`);
|
|
617
|
+
console.log(` │ ├── deployments.json ← current + history per app`);
|
|
618
|
+
console.log(` │ ├── scripts/ ← listener script`);
|
|
619
|
+
console.log(` │ ├── releases/{app}/{ver}/ ← extracted code`);
|
|
620
|
+
console.log(` │ ├── backups/{app}/{ver}.zip ← upload backups`);
|
|
621
|
+
console.log(` │ └── logs/`);
|
|
622
|
+
console.log(` │ ├── deploy.log ← deploy audit log`);
|
|
623
|
+
console.log(` │ ├── server.log ← listener output`);
|
|
624
|
+
console.log(` │ └── server.pid ← listener PID`);
|
|
625
|
+
console.log(` └── current/{app}/ ← symlink → active release`);
|
|
626
|
+
|
|
627
|
+
console.log('');
|
|
628
|
+
console.log(` Token: ${token}`);
|
|
629
|
+
if (existingConfig?.token === token) {
|
|
630
|
+
console.log(' (Existing token preserved)');
|
|
631
|
+
} else {
|
|
632
|
+
console.log(' (Save this token — it will not be shown again)');
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
console.log('');
|
|
636
|
+
console.log(' Node listener is running in background.');
|
|
637
|
+
console.log('');
|
|
638
|
+
console.log(' Manage it with:');
|
|
639
|
+
console.log(' cide server-init --status ← check if running');
|
|
640
|
+
console.log(' cide server-init --stop ← stop listener');
|
|
641
|
+
console.log(' cide server-init --start ← start listener');
|
|
642
|
+
console.log(' cide server-init --restart ← restart listener');
|
|
643
|
+
|
|
644
|
+
console.log('');
|
|
645
|
+
console.log(' In your project cide.json, add to the "upload" array:');
|
|
646
|
+
console.log(' "upload": [');
|
|
647
|
+
console.log(' {');
|
|
648
|
+
console.log(` "name": "Production API",`);
|
|
649
|
+
console.log(` "server_url": "https://your-server.com:${port}/upload",`);
|
|
650
|
+
console.log(' "build_path": "dist/your-project-name",');
|
|
651
|
+
console.log(' "app_code": "your-project-name",');
|
|
652
|
+
console.log(` "token": "${token}"`);
|
|
653
|
+
console.log(' }');
|
|
654
|
+
console.log(' ]');
|
|
655
|
+
console.log('');
|
|
656
|
+
} finally {
|
|
657
|
+
if (ownRl) rl.close();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
module.exports = serverInit;
|
|
662
|
+
module.exports.findCideDir = findCideDir;
|
|
663
|
+
module.exports.startNodeListener = startNodeListener;
|
|
664
|
+
module.exports.stopNodeListener = stopNodeListener;
|
|
665
|
+
module.exports.listenerStatus = listenerStatus;
|