rushdeploy 1.0.1 → 1.0.3
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/bin/index.js +478 -256
- package/package.json +6 -2
package/bin/index.js
CHANGED
|
@@ -28,18 +28,158 @@ function getServerUrl() {
|
|
|
28
28
|
return DEFAULT_SERVER;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
// ANSI terminal color
|
|
31
|
+
// ANSI terminal color tokens & UI utilities
|
|
32
32
|
const colors = {
|
|
33
33
|
reset: '\x1b[0m',
|
|
34
34
|
bold: '\x1b[1m',
|
|
35
|
+
dim: '\x1b[2m',
|
|
36
|
+
italic: '\x1b[3m',
|
|
37
|
+
underline: '\x1b[4m',
|
|
38
|
+
gray: '\x1b[90m',
|
|
39
|
+
white: '\x1b[37m',
|
|
40
|
+
brightWhite: '\x1b[97m',
|
|
35
41
|
green: '\x1b[32m',
|
|
42
|
+
brightGreen: '\x1b[92m',
|
|
36
43
|
red: '\x1b[31m',
|
|
37
|
-
|
|
44
|
+
brightRed: '\x1b[91m',
|
|
38
45
|
yellow: '\x1b[33m',
|
|
46
|
+
brightYellow: '\x1b[93m',
|
|
47
|
+
cyan: '\x1b[36m',
|
|
48
|
+
brightCyan: '\x1b[96m',
|
|
39
49
|
blue: '\x1b[34m',
|
|
40
50
|
magenta: '\x1b[35m'
|
|
41
51
|
};
|
|
42
52
|
|
|
53
|
+
function stripAnsi(str) {
|
|
54
|
+
return String(str || '').replace(/\x1b\[[0-9;]*m/g, '');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ui = {
|
|
58
|
+
step(msg) {
|
|
59
|
+
console.log(`${colors.green}✓${colors.reset} ${msg}`);
|
|
60
|
+
},
|
|
61
|
+
stepInfo(msg) {
|
|
62
|
+
console.log(`${colors.cyan}❯${colors.reset} ${msg}`);
|
|
63
|
+
},
|
|
64
|
+
stepWarn(msg) {
|
|
65
|
+
console.log(`${colors.yellow}⚠${colors.reset} ${msg}`);
|
|
66
|
+
},
|
|
67
|
+
stepError(msg) {
|
|
68
|
+
console.log(`${colors.red}✗${colors.reset} ${msg}`);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
badge(statusStr) {
|
|
72
|
+
if (!statusStr) return '';
|
|
73
|
+
const upper = String(statusStr).toUpperCase();
|
|
74
|
+
if (['RUNNING', 'ONLINE', 'ACTIVE', 'SUCCESS', 'AUTHENTICATED', 'OK'].includes(upper)) {
|
|
75
|
+
return `${colors.green}${colors.bold}${upper}${colors.reset}`;
|
|
76
|
+
}
|
|
77
|
+
if (['BUILDING', 'PENDING', 'QUEUED', 'STARTING', 'ON', 'WARN'].includes(upper)) {
|
|
78
|
+
return `${colors.yellow}${colors.bold}${upper}${colors.reset}`;
|
|
79
|
+
}
|
|
80
|
+
if (['FAILED', 'OFFLINE', 'STOPPED', 'OFF', 'ERROR'].includes(upper)) {
|
|
81
|
+
return `${colors.red}${colors.bold}${upper}${colors.reset}`;
|
|
82
|
+
}
|
|
83
|
+
return `${colors.cyan}${colors.bold}${upper}${colors.reset}`;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
progressBar(percent, width = 16) {
|
|
87
|
+
const pct = Math.max(0, Math.min(100, parseFloat(percent) || 0));
|
|
88
|
+
const filled = Math.round((pct / 100) * width);
|
|
89
|
+
const empty = width - filled;
|
|
90
|
+
const bar = `${colors.green}${'█'.repeat(filled)}${colors.gray}${'░'.repeat(empty)}${colors.reset}`;
|
|
91
|
+
return `${bar} ${pct.toFixed(1)}%`;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
box(fields, options = {}) {
|
|
95
|
+
const fieldPairs = Array.isArray(fields)
|
|
96
|
+
? fields
|
|
97
|
+
: Object.entries(fields).map(([label, value]) => ({ label, value }));
|
|
98
|
+
|
|
99
|
+
const labelWidth = 14;
|
|
100
|
+
const leftIndent = 3;
|
|
101
|
+
const rightMargin = 3;
|
|
102
|
+
const gap = 2;
|
|
103
|
+
|
|
104
|
+
let maxValLen = 30;
|
|
105
|
+
fieldPairs.forEach(f => {
|
|
106
|
+
const rawVal = stripAnsi(f.value !== undefined ? String(f.value) : '');
|
|
107
|
+
if (rawVal.length > maxValLen) maxValLen = rawVal.length;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const innerWidth = Math.max(options.minWidth || 66, leftIndent + labelWidth + gap + maxValLen + rightMargin);
|
|
111
|
+
|
|
112
|
+
const topBorder = `┌${'─'.repeat(innerWidth)}┐`;
|
|
113
|
+
const emptyRow = `│${' '.repeat(innerWidth)}│`;
|
|
114
|
+
const bottomBorder = `└${'─'.repeat(innerWidth)}┘`;
|
|
115
|
+
|
|
116
|
+
console.log(`\n${colors.gray}${topBorder}${colors.reset}`);
|
|
117
|
+
console.log(`${colors.gray}${emptyRow}${colors.reset}`);
|
|
118
|
+
|
|
119
|
+
fieldPairs.forEach(f => {
|
|
120
|
+
const label = f.label || '';
|
|
121
|
+
const valStr = f.value !== undefined ? String(f.value) : '';
|
|
122
|
+
const rawVal = stripAnsi(valStr);
|
|
123
|
+
|
|
124
|
+
const labelPadded = label.padEnd(labelWidth);
|
|
125
|
+
const padLen = innerWidth - (leftIndent + labelWidth + gap + rawVal.length + rightMargin);
|
|
126
|
+
const padRight = ' '.repeat(Math.max(0, padLen));
|
|
127
|
+
|
|
128
|
+
console.log(
|
|
129
|
+
`${colors.gray}│${colors.reset}` +
|
|
130
|
+
' '.repeat(leftIndent) +
|
|
131
|
+
`${colors.gray}${labelPadded}${colors.reset}` +
|
|
132
|
+
' '.repeat(gap) +
|
|
133
|
+
`${valStr}` +
|
|
134
|
+
padRight +
|
|
135
|
+
' '.repeat(rightMargin) +
|
|
136
|
+
`${colors.gray}│${colors.reset}`
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(`${colors.gray}${emptyRow}${colors.reset}`);
|
|
141
|
+
console.log(`${colors.gray}${bottomBorder}${colors.reset}\n`);
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
table(headers, rows) {
|
|
147
|
+
if (!rows || rows.length === 0) return;
|
|
148
|
+
|
|
149
|
+
const colWidths = headers.map(h => h.label.length);
|
|
150
|
+
rows.forEach(row => {
|
|
151
|
+
row.forEach((cell, i) => {
|
|
152
|
+
const raw = stripAnsi(cell !== undefined ? String(cell) : '');
|
|
153
|
+
if (raw.length > (colWidths[i] || 0)) {
|
|
154
|
+
colWidths[i] = raw.length;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const headerLine = headers.map((h, i) => {
|
|
160
|
+
const label = h.label.toUpperCase();
|
|
161
|
+
return colors.gray + colors.bold + label.padEnd(colWidths[i] + (h.pad || 3)) + colors.reset;
|
|
162
|
+
}).join('');
|
|
163
|
+
|
|
164
|
+
const totalLen = colWidths.reduce((a, b) => a + b, 0) + (headers.length * 3);
|
|
165
|
+
const divider = colors.gray + '─'.repeat(Math.max(64, totalLen)) + colors.reset;
|
|
166
|
+
|
|
167
|
+
console.log(`\n${headerLine}`);
|
|
168
|
+
console.log(divider);
|
|
169
|
+
|
|
170
|
+
rows.forEach(row => {
|
|
171
|
+
const rowLine = row.map((cell, i) => {
|
|
172
|
+
const str = cell !== undefined ? String(cell) : '';
|
|
173
|
+
const raw = stripAnsi(str);
|
|
174
|
+
const padLen = (colWidths[i] || 0) + (headers[i]?.pad || 3) - raw.length;
|
|
175
|
+
return str + ' '.repeat(Math.max(0, padLen));
|
|
176
|
+
}).join('');
|
|
177
|
+
console.log(rowLine);
|
|
178
|
+
});
|
|
179
|
+
console.log();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
43
183
|
function loadConfig() {
|
|
44
184
|
if (!fs.existsSync(CONFIG_FILE)) return null;
|
|
45
185
|
try {
|
|
@@ -58,7 +198,7 @@ function saveConfig(server, token) {
|
|
|
58
198
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ server: cleanServer, token }, null, 2), 'utf8');
|
|
59
199
|
return true;
|
|
60
200
|
} catch (err) {
|
|
61
|
-
|
|
201
|
+
ui.stepError(`Error saving configuration: ${err.message}`);
|
|
62
202
|
return false;
|
|
63
203
|
}
|
|
64
204
|
}
|
|
@@ -70,7 +210,7 @@ function makeRequest(apiPath, method = 'GET', data = null) {
|
|
|
70
210
|
const isPlansPath = apiPath.endsWith('/subscriptions/plans') && method === 'GET';
|
|
71
211
|
|
|
72
212
|
if (!config && !isLoginPath && !isPlansPath) {
|
|
73
|
-
|
|
213
|
+
ui.stepError(`You are not logged in. Run 'rushdeploy login <token>' first.`);
|
|
74
214
|
process.exit(1);
|
|
75
215
|
}
|
|
76
216
|
|
|
@@ -117,10 +257,13 @@ function makeRequest(apiPath, method = 'GET', data = null) {
|
|
|
117
257
|
}
|
|
118
258
|
|
|
119
259
|
if (res.statusCode === 401) {
|
|
120
|
-
|
|
121
|
-
console.
|
|
260
|
+
ui.stepError(`Unauthorized (401). Your token may have expired or been revoked.`);
|
|
261
|
+
console.log(` Please log in again using: ${colors.green}rushdeploy login <token>${colors.reset}`);
|
|
262
|
+
} else if (res.statusCode === 429) {
|
|
263
|
+
ui.stepError(`Rate Limit Exceeded (429): You are sending commands too quickly.`);
|
|
264
|
+
console.log(` Please wait a minute before running more CLI commands.`);
|
|
122
265
|
} else {
|
|
123
|
-
|
|
266
|
+
ui.stepError(`Error (${res.statusCode}): ${errorMsg}`);
|
|
124
267
|
}
|
|
125
268
|
process.exit(1);
|
|
126
269
|
}
|
|
@@ -128,8 +271,8 @@ function makeRequest(apiPath, method = 'GET', data = null) {
|
|
|
128
271
|
});
|
|
129
272
|
|
|
130
273
|
req.on('error', (err) => {
|
|
131
|
-
|
|
132
|
-
console.
|
|
274
|
+
ui.stepError(`Connection Error: Unable to connect to server at ${serverUrl}`);
|
|
275
|
+
console.log(` Details: ${colors.gray}${err.message}${colors.reset}`);
|
|
133
276
|
process.exit(1);
|
|
134
277
|
});
|
|
135
278
|
|
|
@@ -149,6 +292,14 @@ function formatBytes(bytes) {
|
|
|
149
292
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
150
293
|
}
|
|
151
294
|
|
|
295
|
+
function formatMemory(val) {
|
|
296
|
+
if (val === undefined || val === null || isNaN(val)) return '0 B';
|
|
297
|
+
const num = Number(val);
|
|
298
|
+
if (num === 0) return '0 B';
|
|
299
|
+
const bytes = num < 1000000 ? num * 1024 * 1024 : num;
|
|
300
|
+
return formatBytes(bytes);
|
|
301
|
+
}
|
|
302
|
+
|
|
152
303
|
function formatDate(isoStr) {
|
|
153
304
|
if (!isoStr) return 'Never';
|
|
154
305
|
try {
|
|
@@ -174,9 +325,9 @@ function openBrowser(url) {
|
|
|
174
325
|
}
|
|
175
326
|
exec(command, (err) => {
|
|
176
327
|
if (err) {
|
|
177
|
-
|
|
328
|
+
ui.stepError(`Failed to open browser: ${err.message}`);
|
|
178
329
|
} else {
|
|
179
|
-
|
|
330
|
+
ui.step(`Opening browser: ${colors.brightCyan}${url}${colors.reset}`);
|
|
180
331
|
}
|
|
181
332
|
});
|
|
182
333
|
}
|
|
@@ -192,22 +343,28 @@ function extractRepoName(url) {
|
|
|
192
343
|
}
|
|
193
344
|
|
|
194
345
|
async function cmdLogin(token, serverArg) {
|
|
195
|
-
const server = serverArg ||
|
|
196
|
-
|
|
346
|
+
const server = serverArg || getServerUrl();
|
|
347
|
+
ui.stepInfo(`Connecting to ${colors.brightWhite}${server}${colors.reset}...`);
|
|
197
348
|
|
|
198
349
|
saveConfig(server, token);
|
|
199
350
|
|
|
200
351
|
try {
|
|
352
|
+
ui.stepInfo(`Validating authentication token...`);
|
|
201
353
|
const res = await makeRequest('/auth/me');
|
|
202
354
|
if (res.success) {
|
|
203
355
|
const user = res.user;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
356
|
+
ui.step(`Authentication successful!`);
|
|
357
|
+
|
|
358
|
+
ui.box([
|
|
359
|
+
{ label: 'Account', value: `${colors.bold}${user.name}${colors.reset} (${colors.gray}${user.email}${colors.reset})` },
|
|
360
|
+
{ label: 'Role', value: user.role.toUpperCase() },
|
|
361
|
+
{ label: 'Status', value: ui.badge('AUTHENTICATED') },
|
|
362
|
+
{ label: 'Server', value: colors.gray + server + colors.reset }
|
|
363
|
+
]);
|
|
207
364
|
}
|
|
208
365
|
} catch (err) {
|
|
209
366
|
if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
|
|
210
|
-
|
|
367
|
+
ui.stepError(`Authentication failed. Check your token or server URL.`);
|
|
211
368
|
process.exit(1);
|
|
212
369
|
}
|
|
213
370
|
}
|
|
@@ -215,56 +372,52 @@ async function cmdLogin(token, serverArg) {
|
|
|
215
372
|
function cmdLogout() {
|
|
216
373
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
217
374
|
fs.unlinkSync(CONFIG_FILE);
|
|
218
|
-
|
|
375
|
+
ui.step(`Successfully logged out. Local token configuration cleared.`);
|
|
219
376
|
} else {
|
|
220
|
-
|
|
377
|
+
ui.stepInfo(`You are already logged out.`);
|
|
221
378
|
}
|
|
222
379
|
}
|
|
223
380
|
|
|
224
381
|
async function cmdWhoami() {
|
|
225
382
|
const config = loadConfig();
|
|
226
383
|
if (!config) {
|
|
227
|
-
|
|
384
|
+
ui.stepWarn(`Not logged in. Run 'rushdeploy login <token>' to log in.`);
|
|
228
385
|
process.exit(0);
|
|
229
386
|
}
|
|
230
387
|
|
|
231
388
|
const res = await makeRequest('/auth/me');
|
|
232
389
|
const user = res.user;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
390
|
+
ui.box([
|
|
391
|
+
{ label: 'Name', value: colors.bold + user.name + colors.reset },
|
|
392
|
+
{ label: 'Email', value: user.email },
|
|
393
|
+
{ label: 'Role', value: user.role.toUpperCase() },
|
|
394
|
+
{ label: 'Server', value: colors.gray + getServerUrl() + colors.reset }
|
|
395
|
+
], { title: 'USER PROFILE' });
|
|
239
396
|
}
|
|
240
397
|
|
|
241
398
|
async function cmdPlans() {
|
|
399
|
+
ui.stepInfo(`Fetching RushDeploy hosting plans...`);
|
|
242
400
|
const res = await makeRequest('/subscriptions/plans');
|
|
243
401
|
const plans = res.plans || [];
|
|
244
402
|
|
|
245
|
-
console.log(`\n${colors.cyan}${colors.bold}====================================================${colors.reset}`);
|
|
246
|
-
console.log(`${colors.bold} RUSHDEPLOY HOSTING PLANS ${colors.reset}`);
|
|
247
|
-
console.log(`${colors.cyan}${colors.bold}====================================================${colors.reset}`);
|
|
248
|
-
|
|
249
403
|
plans.forEach(p => {
|
|
250
404
|
const price = p.base_price_monthly ? `$${p.base_price_monthly}/mo` : 'Free';
|
|
251
|
-
|
|
252
|
-
|
|
405
|
+
const fields = [
|
|
406
|
+
{ label: 'Monthly Price', value: colors.green + colors.bold + price + colors.reset },
|
|
407
|
+
{ label: 'Description', value: colors.gray + (p.description || 'N/A') + colors.reset }
|
|
408
|
+
];
|
|
253
409
|
if (p.limits) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
410
|
+
fields.push(
|
|
411
|
+
{ label: 'Projects Limit', value: String(p.limits.max_projects || 'Unlimited') },
|
|
412
|
+
{ label: 'Deployments', value: String(p.limits.max_deployments || 'Unlimited') },
|
|
413
|
+
{ label: 'Bandwidth', value: p.limits.max_bandwidth_bytes ? formatBytes(p.limits.max_bandwidth_bytes) : 'Unlimited' },
|
|
414
|
+
{ label: 'Build Minutes', value: p.limits.max_build_minutes ? `${p.limits.max_build_minutes} mins` : 'Unlimited' }
|
|
415
|
+
);
|
|
259
416
|
}
|
|
260
|
-
|
|
417
|
+
ui.box(fields, { title: p.name.toUpperCase() });
|
|
261
418
|
});
|
|
262
419
|
}
|
|
263
420
|
|
|
264
|
-
function stripAnsi(str) {
|
|
265
|
-
return String(str).replace(/\x1b\[[0-9;]*m/g, '');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
421
|
function drawCards(cards, cardsPerRow = 2) {
|
|
269
422
|
const cardWidth = 34;
|
|
270
423
|
const rows = [];
|
|
@@ -283,7 +436,7 @@ function drawCards(cards, cardsPerRow = 2) {
|
|
|
283
436
|
row.forEach((card, idx) => {
|
|
284
437
|
const space = idx > 0 ? ' ' : ''; // Gap between cards
|
|
285
438
|
|
|
286
|
-
topBorder += space + '┌' + '─'.repeat(cardWidth - 2) + '┐';
|
|
439
|
+
topBorder += space + colors.gray + '┌' + '─'.repeat(cardWidth - 2) + '┐' + colors.reset;
|
|
287
440
|
|
|
288
441
|
const label = card.label || '';
|
|
289
442
|
let value = card.value !== undefined ? String(card.value) : '';
|
|
@@ -297,19 +450,19 @@ function drawCards(cards, cardsPerRow = 2) {
|
|
|
297
450
|
const rawVal = stripAnsi(value);
|
|
298
451
|
|
|
299
452
|
const textLen = label.length + rawVal.length;
|
|
300
|
-
const padLen = cardWidth - 6 - textLen;
|
|
453
|
+
const padLen = cardWidth - 6 - textLen;
|
|
301
454
|
const pad = ' '.repeat(Math.max(1, padLen));
|
|
302
455
|
|
|
303
|
-
labelValLine += space +
|
|
304
|
-
sepLine += space + '├' + '─'.repeat(cardWidth - 2) + '┤';
|
|
456
|
+
labelValLine += space + `${colors.gray}│${colors.reset} ${colors.brightCyan}${label}${colors.reset}${pad}${valStr} ${colors.gray}│${colors.reset}`;
|
|
457
|
+
sepLine += space + colors.gray + '├' + '─'.repeat(cardWidth - 2) + '┤' + colors.reset;
|
|
305
458
|
|
|
306
459
|
const hint = card.hint || '';
|
|
307
460
|
const rawHint = stripAnsi(hint);
|
|
308
461
|
const hintPadLen = cardWidth - 6 - rawHint.length;
|
|
309
462
|
const hintPad = ' '.repeat(Math.max(0, hintPadLen));
|
|
310
463
|
|
|
311
|
-
hintLine += space +
|
|
312
|
-
bottomBorder += space + '└' + '─'.repeat(cardWidth - 2) + '┘';
|
|
464
|
+
hintLine += space + `${colors.gray}│${colors.reset} ${colors.gray}${hint}${colors.reset}${hintPad} ${colors.gray}│${colors.reset}`;
|
|
465
|
+
bottomBorder += space + colors.gray + '└' + '─'.repeat(cardWidth - 2) + '┘' + colors.reset;
|
|
313
466
|
});
|
|
314
467
|
|
|
315
468
|
console.log(topBorder);
|
|
@@ -334,7 +487,7 @@ async function cmdStatus() {
|
|
|
334
487
|
|
|
335
488
|
if (user.role === 'super_admin') {
|
|
336
489
|
try {
|
|
337
|
-
|
|
490
|
+
ui.stepInfo(`Fetching system metrics (Admin overview)...`);
|
|
338
491
|
const [overviewRes, serverRes] = await Promise.all([
|
|
339
492
|
makeRequest('/admin/overview').catch(() => null),
|
|
340
493
|
makeRequest('/admin/server').catch(() => null)
|
|
@@ -359,9 +512,7 @@ async function cmdStatus() {
|
|
|
359
512
|
const buildCount = activeBuildingProjects;
|
|
360
513
|
const failCount = byStatus.failed || byStatus.FAILED || 0;
|
|
361
514
|
|
|
362
|
-
|
|
363
|
-
console.log(`${colors.bold} RUSHDEPLOY SYSTEM STATUS (ADMIN) ${colors.reset}`);
|
|
364
|
-
console.log(`${colors.cyan}${colors.bold}========================================================================${colors.reset}\n`);
|
|
515
|
+
ui.step(`System Overview metrics retrieved`);
|
|
365
516
|
|
|
366
517
|
const adminCards = [
|
|
367
518
|
{ label: 'Users', value: stats.users ?? 0, hint: 'Total registered users' },
|
|
@@ -406,9 +557,7 @@ async function cmdStatus() {
|
|
|
406
557
|
const bwLimit = limits.max_bandwidth_bytes ? formatBytes(limits.max_bandwidth_bytes) : 'Unlimited';
|
|
407
558
|
const buildLimit = limits.max_build_minutes ? `${limits.max_build_minutes} mins` : 'Unlimited';
|
|
408
559
|
|
|
409
|
-
|
|
410
|
-
console.log(`${colors.bold} RUSHDEPLOY ACCOUNT STATUS (WORKSPACE) ${colors.reset}`);
|
|
411
|
-
console.log(`${colors.cyan}${colors.bold}========================================================================${colors.reset}\n`);
|
|
560
|
+
ui.step(`Workspace metrics retrieved`);
|
|
412
561
|
|
|
413
562
|
const workspaceCards = [
|
|
414
563
|
{ label: 'Workspace', value: workspace.name, hint: `Tier Plan: ${workspace.tier.toUpperCase()}` },
|
|
@@ -419,62 +568,76 @@ async function cmdStatus() {
|
|
|
419
568
|
|
|
420
569
|
drawCards(workspaceCards, 2);
|
|
421
570
|
} else {
|
|
422
|
-
|
|
571
|
+
ui.stepError(`Could not retrieve status details.`);
|
|
423
572
|
}
|
|
424
573
|
}
|
|
425
574
|
|
|
426
575
|
async function cmdProjects() {
|
|
576
|
+
ui.stepInfo(`Fetching projects list...`);
|
|
427
577
|
const res = await makeRequest('/projects/');
|
|
428
578
|
const projects = res.projects || [];
|
|
429
579
|
|
|
430
580
|
if (projects.length === 0) {
|
|
431
|
-
|
|
581
|
+
ui.stepInfo('No projects found in active workspace.');
|
|
432
582
|
return;
|
|
433
583
|
}
|
|
434
584
|
|
|
435
|
-
|
|
436
|
-
console.log('-'.repeat(80));
|
|
437
|
-
projects.forEach((p) => {
|
|
438
|
-
let statusColor = colors.reset;
|
|
439
|
-
if (p.status === 'running') statusColor = colors.green;
|
|
440
|
-
if (p.status === 'failed') statusColor = colors.red;
|
|
441
|
-
if (p.status === 'pending') statusColor = colors.yellow;
|
|
585
|
+
ui.step(`Retrieved ${projects.length} project(s)`);
|
|
442
586
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
587
|
+
const headers = [
|
|
588
|
+
{ label: 'ID', pad: 4 },
|
|
589
|
+
{ label: 'Project Name', pad: 4 },
|
|
590
|
+
{ label: 'Status', pad: 4 },
|
|
591
|
+
{ label: 'Public URL', pad: 2 }
|
|
592
|
+
];
|
|
593
|
+
|
|
594
|
+
const rows = projects.map(p => [
|
|
595
|
+
String(p.id),
|
|
596
|
+
colors.bold + p.name + colors.reset,
|
|
597
|
+
ui.badge(p.status),
|
|
598
|
+
colors.brightCyan + p.url + colors.reset
|
|
599
|
+
]);
|
|
600
|
+
|
|
601
|
+
ui.table(headers, rows);
|
|
446
602
|
}
|
|
447
603
|
|
|
448
604
|
async function cmdRepos() {
|
|
449
605
|
try {
|
|
606
|
+
ui.stepInfo(`Fetching connected GitHub repositories...`);
|
|
450
607
|
const res = await makeRequest('/auth/github/repos');
|
|
451
608
|
const repos = res.repos || [];
|
|
452
609
|
|
|
453
610
|
if (repos.length === 0) {
|
|
454
|
-
|
|
611
|
+
ui.stepInfo('No GitHub repositories found. Try connecting to GitHub first.');
|
|
455
612
|
return;
|
|
456
613
|
}
|
|
457
614
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
615
|
+
ui.step(`Found ${repos.length} repository/repositories`);
|
|
616
|
+
|
|
617
|
+
const headers = [
|
|
618
|
+
{ label: 'Repository Name', pad: 4 },
|
|
619
|
+
{ label: 'Default Branch', pad: 4 },
|
|
620
|
+
{ label: 'Visibility', pad: 2 }
|
|
621
|
+
];
|
|
622
|
+
|
|
623
|
+
const rows = repos.map(r => [
|
|
624
|
+
colors.bold + r.full_name + colors.reset,
|
|
625
|
+
colors.gray + String(r.default_branch || 'main') + colors.reset,
|
|
626
|
+
r.private ? ui.badge('PRIVATE') : ui.badge('PUBLIC')
|
|
627
|
+
]);
|
|
628
|
+
|
|
629
|
+
ui.table(headers, rows);
|
|
465
630
|
} catch (err) {
|
|
466
|
-
|
|
467
|
-
console.error(`${colors.yellow}Notice: Please connect your GitHub account via Settings in the Web Dashboard first.${colors.reset}`);
|
|
631
|
+
ui.stepWarn(`Please connect your GitHub account via Settings in the Web Dashboard first.`);
|
|
468
632
|
}
|
|
469
633
|
}
|
|
470
634
|
|
|
471
635
|
async function cmdDeploy(type, args) {
|
|
472
636
|
if (type === 'git') {
|
|
473
|
-
// Deploy from URI: deploy git <repo_url> [--branch <branch>] [--name <name>] [--type <type>]
|
|
474
637
|
const repoUrl = args[0];
|
|
475
638
|
if (!repoUrl) {
|
|
476
|
-
|
|
477
|
-
console.log(
|
|
639
|
+
ui.stepError(`Please specify the Git repository URL.`);
|
|
640
|
+
console.log(` Usage: ${colors.green}rushdeploy deploy git <repo_url> [--branch <branch>] [--name <name>] [--type <type>]${colors.reset}`);
|
|
478
641
|
process.exit(1);
|
|
479
642
|
}
|
|
480
643
|
|
|
@@ -491,7 +654,10 @@ async function cmdDeploy(type, args) {
|
|
|
491
654
|
const typeIdx = args.indexOf('--type');
|
|
492
655
|
if (typeIdx !== -1 && args[typeIdx + 1]) ptype = args[typeIdx + 1];
|
|
493
656
|
|
|
494
|
-
|
|
657
|
+
ui.step(`Connecting to repository`);
|
|
658
|
+
ui.step(`Creating project ${name}`);
|
|
659
|
+
ui.step(`Triggering deployment pipeline for ${branch} branch`);
|
|
660
|
+
|
|
495
661
|
const res = await makeRequest('/projects/deploy', 'POST', {
|
|
496
662
|
name,
|
|
497
663
|
repo_url: repoUrl,
|
|
@@ -500,18 +666,22 @@ async function cmdDeploy(type, args) {
|
|
|
500
666
|
});
|
|
501
667
|
|
|
502
668
|
if (res.success) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
669
|
+
ui.step(`Deployment queued successfully!`);
|
|
670
|
+
ui.box([
|
|
671
|
+
{ label: 'Project', value: colors.bold + name + colors.reset },
|
|
672
|
+
{ label: 'State', value: ui.badge('QUEUED') },
|
|
673
|
+
{ label: 'Project ID', value: String(res.project_id) },
|
|
674
|
+
{ label: 'Deployment ID', value: String(res.deployment_id) },
|
|
675
|
+
{ label: 'Repository', value: colors.gray + repoUrl + colors.reset },
|
|
676
|
+
{ label: 'Public URL', value: colors.brightCyan + colors.bold + res.url + colors.reset }
|
|
677
|
+
]);
|
|
507
678
|
}
|
|
508
679
|
|
|
509
680
|
} else if (type === 'repo') {
|
|
510
|
-
// Deploy from Github Account: deploy repo <owner/repo> [--branch <branch>] [--name <name>] [--type <type>]
|
|
511
681
|
const ownerRepo = args[0];
|
|
512
682
|
if (!ownerRepo || !ownerRepo.includes('/')) {
|
|
513
|
-
|
|
514
|
-
console.log(
|
|
683
|
+
ui.stepError(`Please specify the repository in 'owner/repo' format.`);
|
|
684
|
+
console.log(` Usage: ${colors.green}rushdeploy deploy repo <owner/repo> [--branch <branch>] [--name <name>] [--type <type>]${colors.reset}`);
|
|
515
685
|
process.exit(1);
|
|
516
686
|
}
|
|
517
687
|
|
|
@@ -529,7 +699,10 @@ async function cmdDeploy(type, args) {
|
|
|
529
699
|
const typeIdx = args.indexOf('--type');
|
|
530
700
|
if (typeIdx !== -1 && args[typeIdx + 1]) ptype = args[typeIdx + 1];
|
|
531
701
|
|
|
532
|
-
|
|
702
|
+
ui.step(`Connecting to GitHub repository ${ownerRepo}`);
|
|
703
|
+
ui.step(`Creating project ${name}`);
|
|
704
|
+
ui.step(`Triggering deployment pipeline for ${branch} branch`);
|
|
705
|
+
|
|
533
706
|
const res = await makeRequest('/projects/deploy', 'POST', {
|
|
534
707
|
name,
|
|
535
708
|
repo_url: repoUrl,
|
|
@@ -538,24 +711,28 @@ async function cmdDeploy(type, args) {
|
|
|
538
711
|
});
|
|
539
712
|
|
|
540
713
|
if (res.success) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
714
|
+
ui.step(`Deployment queued successfully!`);
|
|
715
|
+
ui.box([
|
|
716
|
+
{ label: 'Project', value: colors.bold + name + colors.reset },
|
|
717
|
+
{ label: 'State', value: ui.badge('QUEUED') },
|
|
718
|
+
{ label: 'Project ID', value: String(res.project_id) },
|
|
719
|
+
{ label: 'Deployment ID', value: String(res.deployment_id) },
|
|
720
|
+
{ label: 'Repository', value: colors.gray + ownerRepo + colors.reset },
|
|
721
|
+
{ label: 'Public URL', value: colors.brightCyan + colors.bold + res.url + colors.reset }
|
|
722
|
+
]);
|
|
545
723
|
}
|
|
546
724
|
|
|
547
725
|
} else if (type === 'wordpress') {
|
|
548
|
-
// Deploy wordpress <name> --email <email> [--title <title>] [--user <user>] [--password <pass>]
|
|
549
726
|
const name = args[0];
|
|
550
727
|
if (!name) {
|
|
551
|
-
|
|
552
|
-
console.log(
|
|
728
|
+
ui.stepError(`Please specify the site/project name.`);
|
|
729
|
+
console.log(` Usage: ${colors.green}rushdeploy deploy wordpress <name> --email <email> [options]${colors.reset}`);
|
|
553
730
|
process.exit(1);
|
|
554
731
|
}
|
|
555
732
|
|
|
556
733
|
const emailIdx = args.indexOf('--email');
|
|
557
734
|
if (emailIdx === -1 || !args[emailIdx + 1]) {
|
|
558
|
-
|
|
735
|
+
ui.stepError(`--email <email> option is required for WordPress deployment.`);
|
|
559
736
|
process.exit(1);
|
|
560
737
|
}
|
|
561
738
|
const adminEmail = args[emailIdx + 1];
|
|
@@ -572,7 +749,10 @@ async function cmdDeploy(type, args) {
|
|
|
572
749
|
const passIdx = args.indexOf('--password');
|
|
573
750
|
if (passIdx !== -1 && args[passIdx + 1]) adminPassword = args[passIdx + 1];
|
|
574
751
|
|
|
575
|
-
|
|
752
|
+
ui.step(`Preparing WordPress environment for ${name}`);
|
|
753
|
+
ui.step(`Configuring database and web server container`);
|
|
754
|
+
ui.step(`Starting WordPress auto-installation`);
|
|
755
|
+
|
|
576
756
|
const res = await makeRequest('/projects/wordpress', 'POST', {
|
|
577
757
|
name,
|
|
578
758
|
admin_email: adminEmail,
|
|
@@ -582,18 +762,23 @@ async function cmdDeploy(type, args) {
|
|
|
582
762
|
});
|
|
583
763
|
|
|
584
764
|
if (res.success) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
765
|
+
ui.step(`WordPress deployment started successfully!`);
|
|
766
|
+
ui.box([
|
|
767
|
+
{ label: 'Site Name', value: colors.bold + name + colors.reset },
|
|
768
|
+
{ label: 'State', value: ui.badge('BUILDING') },
|
|
769
|
+
{ label: 'Admin Email', value: adminEmail },
|
|
770
|
+
{ label: 'Project ID', value: String(res.project_id) },
|
|
771
|
+
{ label: 'Deployment ID', value: String(res.deployment_id) },
|
|
772
|
+
{ label: 'Public URL', value: colors.brightCyan + colors.bold + res.url + colors.reset }
|
|
773
|
+
]);
|
|
589
774
|
}
|
|
590
775
|
|
|
591
776
|
} else {
|
|
592
|
-
|
|
593
|
-
console.log(
|
|
594
|
-
console.log(
|
|
595
|
-
console.log(
|
|
596
|
-
console.log(
|
|
777
|
+
ui.stepError(`Invalid deployment source type. Must be 'git', 'repo', or 'wordpress'.`);
|
|
778
|
+
console.log(`\nUsage:`);
|
|
779
|
+
console.log(` rushdeploy deploy git <repo_url> [options]`);
|
|
780
|
+
console.log(` rushdeploy deploy repo <owner/repo> [options]`);
|
|
781
|
+
console.log(` rushdeploy deploy wordpress <name> --email <email> [options]`);
|
|
597
782
|
process.exit(1);
|
|
598
783
|
}
|
|
599
784
|
}
|
|
@@ -610,7 +795,7 @@ async function resolveProjectId(idOrSlug) {
|
|
|
610
795
|
return matched.id;
|
|
611
796
|
}
|
|
612
797
|
|
|
613
|
-
|
|
798
|
+
ui.stepError(`No project found matching name or slug "${idOrSlug}".`);
|
|
614
799
|
process.exit(1);
|
|
615
800
|
}
|
|
616
801
|
|
|
@@ -619,27 +804,27 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
619
804
|
|
|
620
805
|
switch (subcommand) {
|
|
621
806
|
case 'start': {
|
|
622
|
-
|
|
807
|
+
ui.stepInfo(`Starting project ${id}...`);
|
|
623
808
|
const res = await makeRequest(`/projects/${id}/start`, 'POST');
|
|
624
809
|
if (res.success) {
|
|
625
|
-
|
|
810
|
+
ui.step(`Project container started successfully.`);
|
|
626
811
|
}
|
|
627
812
|
break;
|
|
628
813
|
}
|
|
629
814
|
case 'stop': {
|
|
630
|
-
|
|
815
|
+
ui.stepInfo(`Stopping project ${id}...`);
|
|
631
816
|
const res = await makeRequest(`/projects/${id}/stop`, 'POST');
|
|
632
817
|
if (res.success) {
|
|
633
|
-
|
|
818
|
+
ui.step(`Project container stopped successfully.`);
|
|
634
819
|
}
|
|
635
820
|
break;
|
|
636
821
|
}
|
|
637
822
|
case 'redeploy': {
|
|
638
|
-
|
|
823
|
+
ui.stepInfo(`Triggering project redeployment...`);
|
|
639
824
|
const res = await makeRequest(`/projects/${id}/redeploy`, 'POST');
|
|
640
825
|
if (res.success) {
|
|
641
|
-
|
|
642
|
-
|
|
826
|
+
ui.step(`Redeployment triggered successfully.`);
|
|
827
|
+
ui.stepInfo(`Deployment ID: ${res.deployment_id}`);
|
|
643
828
|
}
|
|
644
829
|
break;
|
|
645
830
|
}
|
|
@@ -655,48 +840,62 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
655
840
|
const info = await makeRequest(`/projects/${id}/status`);
|
|
656
841
|
const stats = statsRes.stats;
|
|
657
842
|
|
|
658
|
-
|
|
659
|
-
console.log(`${colors.bold} RESOURCE METRICS: PROJECT ${id} ${colors.reset}`);
|
|
660
|
-
console.log(`${colors.cyan}${colors.bold}====================================================${colors.reset}`);
|
|
661
|
-
console.log(`Current Status: ${info.status.toUpperCase()}`);
|
|
843
|
+
ui.stepInfo(`Resource metrics for Project ${id}`);
|
|
662
844
|
|
|
845
|
+
const fields = [
|
|
846
|
+
{ label: 'Current Status', value: ui.badge(info.status) }
|
|
847
|
+
];
|
|
848
|
+
|
|
663
849
|
if (stats) {
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
const memPct = stats.memory_limit ? ((stats.memory_usage / stats.memory_limit) * 100).toFixed(2) : '0.00';
|
|
850
|
+
const cpuPct = stats.cpu_usage !== undefined ? stats.cpu_usage : 0;
|
|
851
|
+
const rawUsage = stats.memory_usage ?? 0;
|
|
852
|
+
const rawLimit = stats.memory_limit || stats.memory_limit_mb || 0;
|
|
668
853
|
|
|
669
|
-
|
|
670
|
-
|
|
854
|
+
const rawUsageMb = rawUsage < 1000000 ? rawUsage : rawUsage / (1024 * 1024);
|
|
855
|
+
const rawLimitMb = rawLimit > 0 ? (rawLimit < 1000000 ? rawLimit : rawLimit / (1024 * 1024)) : 0;
|
|
856
|
+
|
|
857
|
+
const memUsed = formatMemory(rawUsageMb);
|
|
858
|
+
const memLimit = rawLimitMb > 0 ? formatMemory(rawLimitMb) : 'Unlimited';
|
|
859
|
+
const memPct = rawLimitMb > 0 ? (rawUsageMb / rawLimitMb) * 100 : (stats.memory_percent || 0);
|
|
860
|
+
|
|
861
|
+
fields.push(
|
|
862
|
+
{ label: 'CPU Load', value: ui.progressBar(cpuPct) },
|
|
863
|
+
{ label: 'Memory Load', value: `${memUsed} / ${memLimit} (${ui.progressBar(memPct)})` }
|
|
864
|
+
);
|
|
671
865
|
} else {
|
|
672
|
-
|
|
866
|
+
fields.push({ label: 'Metrics Status', value: colors.gray + 'Container metrics inactive or stopped' + colors.reset });
|
|
673
867
|
}
|
|
674
|
-
|
|
868
|
+
|
|
869
|
+
ui.box(fields, { title: `METRICS: PROJECT ${id}` });
|
|
675
870
|
break;
|
|
676
871
|
}
|
|
677
872
|
case 'actions': {
|
|
873
|
+
ui.stepInfo(`Fetching recent deployment actions for Project ${id}...`);
|
|
678
874
|
const res = await makeRequest(`/projects/${id}`);
|
|
679
875
|
const deployments = res.deployments || [];
|
|
680
876
|
|
|
681
877
|
if (deployments.length === 0) {
|
|
682
|
-
|
|
878
|
+
ui.stepInfo('No recent deployments found.');
|
|
683
879
|
return;
|
|
684
880
|
}
|
|
685
881
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
882
|
+
const headers = [
|
|
883
|
+
{ label: 'ID', pad: 4 },
|
|
884
|
+
{ label: 'Status', pad: 4 },
|
|
885
|
+
{ label: 'Branch', pad: 4 },
|
|
886
|
+
{ label: 'Duration', pad: 4 },
|
|
887
|
+
{ label: 'Created At', pad: 2 }
|
|
888
|
+
];
|
|
889
|
+
|
|
890
|
+
const rows = deployments.slice(0, 10).map(d => [
|
|
891
|
+
String(d.id),
|
|
892
|
+
ui.badge(d.status),
|
|
893
|
+
colors.gray + (d.branch || 'main') + colors.reset,
|
|
894
|
+
d.duration_ms ? `${(d.duration_ms / 1000).toFixed(1)}s` : 'N/A',
|
|
895
|
+
formatDate(d.created_at)
|
|
896
|
+
]);
|
|
897
|
+
|
|
898
|
+
ui.table(headers, rows);
|
|
700
899
|
break;
|
|
701
900
|
}
|
|
702
901
|
case 'autoscale': {
|
|
@@ -704,11 +903,11 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
704
903
|
if (!enableArg || (enableArg !== 'on' && enableArg !== 'off')) {
|
|
705
904
|
const res = await makeRequest(`/projects/${id}`);
|
|
706
905
|
const p = res.project;
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
906
|
+
ui.box([
|
|
907
|
+
{ label: 'Autoscale State', value: p.autoscale_enabled ? ui.badge('ON') : ui.badge('OFF') },
|
|
908
|
+
{ label: 'Min/Max Replicas', value: `${p.min_replicas} (min) / ${p.max_replicas} (max)` },
|
|
909
|
+
{ label: 'CPU Trigger', value: `${p.cpu_threshold_percent}% CPU load` }
|
|
910
|
+
], { title: `AUTOSCALE: PROJECT ${id}` });
|
|
712
911
|
return;
|
|
713
912
|
}
|
|
714
913
|
|
|
@@ -726,7 +925,7 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
726
925
|
const cpuIdx = subArgs.indexOf('--cpu');
|
|
727
926
|
if (cpuIdx !== -1 && subArgs[cpuIdx + 1]) cpu_threshold_percent = parseFloat(subArgs[cpuIdx + 1]);
|
|
728
927
|
|
|
729
|
-
|
|
928
|
+
ui.stepInfo(`Updating autoscale settings...`);
|
|
730
929
|
const res = await makeRequest(`/projects/${id}/autoscale`, 'PUT', {
|
|
731
930
|
autoscale_enabled: enabled,
|
|
732
931
|
min_replicas,
|
|
@@ -735,10 +934,12 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
735
934
|
});
|
|
736
935
|
|
|
737
936
|
if (res.success) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
937
|
+
ui.step(`Autoscale configuration updated successfully.`);
|
|
938
|
+
ui.box([
|
|
939
|
+
{ label: 'Autoscale State', value: res.autoscale.autoscale_enabled ? ui.badge('ON') : ui.badge('OFF') },
|
|
940
|
+
{ label: 'Replica Range', value: `${res.autoscale.min_replicas} - ${res.autoscale.max_replicas}` },
|
|
941
|
+
{ label: 'Trigger Threshold', value: `${res.autoscale.cpu_threshold_percent}% CPU` }
|
|
942
|
+
]);
|
|
742
943
|
}
|
|
743
944
|
break;
|
|
744
945
|
}
|
|
@@ -746,21 +947,21 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
746
947
|
const enableArg = subArgs[0];
|
|
747
948
|
if (!enableArg || (enableArg !== 'on' && enableArg !== 'off')) {
|
|
748
949
|
const res = await makeRequest(`/projects/${id}`);
|
|
749
|
-
|
|
950
|
+
ui.stepInfo(`Auto-deploy is currently: ${res.project.auto_deploy ? ui.badge('ON') : ui.badge('OFF')}`);
|
|
750
951
|
return;
|
|
751
952
|
}
|
|
752
953
|
|
|
753
954
|
if (enableArg === 'on') {
|
|
754
|
-
|
|
955
|
+
ui.stepInfo(`Enabling auto-deploy (webhook configuration)...`);
|
|
755
956
|
const res = await makeRequest(`/projects/${id}/auto-deploy/enable`, 'POST');
|
|
756
957
|
if (res.success) {
|
|
757
|
-
|
|
958
|
+
ui.step(`GitHub Auto-Deploy enabled.`);
|
|
758
959
|
}
|
|
759
960
|
} else {
|
|
760
|
-
|
|
961
|
+
ui.stepInfo(`Disabling auto-deploy...`);
|
|
761
962
|
const res = await makeRequest(`/projects/${id}/auto-deploy/disable`, 'POST');
|
|
762
963
|
if (res.success) {
|
|
763
|
-
|
|
964
|
+
ui.step(`GitHub Auto-Deploy disabled.`);
|
|
764
965
|
}
|
|
765
966
|
}
|
|
766
967
|
break;
|
|
@@ -774,12 +975,12 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
774
975
|
|
|
775
976
|
const res = await makeRequest(`/projects/${id}/logs`);
|
|
776
977
|
const logLines = res.lines || [];
|
|
777
|
-
|
|
778
|
-
console.log('
|
|
978
|
+
ui.stepInfo(`Showing last ${Math.min(linesLimit, logLines.length)} log lines for project ${id}:`);
|
|
979
|
+
console.log(colors.gray + '─'.repeat(80) + colors.reset);
|
|
779
980
|
logLines.slice(-linesLimit).forEach(ln => {
|
|
780
981
|
let levelColor = colors.reset;
|
|
781
982
|
if (ln.level === 'error') levelColor = colors.red;
|
|
782
|
-
console.log(`[${formatDate(ln.timestamp)}] ${levelColor}${ln.message}${colors.reset}`);
|
|
983
|
+
console.log(`[${colors.gray}${formatDate(ln.timestamp)}${colors.reset}] ${levelColor}${ln.message}${colors.reset}`);
|
|
783
984
|
});
|
|
784
985
|
console.log();
|
|
785
986
|
break;
|
|
@@ -790,16 +991,20 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
790
991
|
if (!action) {
|
|
791
992
|
const res = await makeRequest(`/projects/${id}/env`);
|
|
792
993
|
const env = res.env || {};
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
994
|
+
ui.stepInfo(`Environment Variables for Project ${id}`);
|
|
995
|
+
const headers = [
|
|
996
|
+
{ label: 'Key', pad: 4 },
|
|
997
|
+
{ label: 'Value', pad: 2 }
|
|
998
|
+
];
|
|
999
|
+
const rows = Object.entries(env).map(([k, v]) => [
|
|
1000
|
+
colors.green + colors.bold + k + colors.reset,
|
|
1001
|
+
colors.gray + String(v) + colors.reset
|
|
1002
|
+
]);
|
|
1003
|
+
ui.table(headers, rows);
|
|
799
1004
|
} else if (action === 'set') {
|
|
800
1005
|
const setPairs = subArgs.slice(1);
|
|
801
1006
|
if (setPairs.length === 0) {
|
|
802
|
-
|
|
1007
|
+
ui.stepError(`Please specify KEY=VALUE pairs to set.`);
|
|
803
1008
|
process.exit(1);
|
|
804
1009
|
}
|
|
805
1010
|
|
|
@@ -815,13 +1020,13 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
815
1020
|
|
|
816
1021
|
const res = await makeRequest(`/projects/${id}/env`, 'PUT', { env });
|
|
817
1022
|
if (res.success) {
|
|
818
|
-
|
|
819
|
-
|
|
1023
|
+
ui.step(`Environment variables updated successfully.`);
|
|
1024
|
+
ui.stepInfo(`Run 'rushdeploy project ${idOrSlug} redeploy' to apply changes.`);
|
|
820
1025
|
}
|
|
821
1026
|
} else if (action === 'remove') {
|
|
822
1027
|
const removeKeys = subArgs.slice(1);
|
|
823
1028
|
if (removeKeys.length === 0) {
|
|
824
|
-
|
|
1029
|
+
ui.stepError(`Please specify keys to remove.`);
|
|
825
1030
|
process.exit(1);
|
|
826
1031
|
}
|
|
827
1032
|
|
|
@@ -834,113 +1039,130 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
834
1039
|
|
|
835
1040
|
const res = await makeRequest(`/projects/${id}/env`, 'PUT', { env });
|
|
836
1041
|
if (res.success) {
|
|
837
|
-
|
|
838
|
-
|
|
1042
|
+
ui.step(`Environment variables updated successfully.`);
|
|
1043
|
+
ui.stepInfo(`Run 'rushdeploy project ${idOrSlug} redeploy' to apply changes.`);
|
|
839
1044
|
}
|
|
840
1045
|
} else {
|
|
841
|
-
|
|
1046
|
+
ui.stepError(`Unknown env subcommand: "${action}". Use "set" or "remove".`);
|
|
842
1047
|
}
|
|
843
1048
|
break;
|
|
844
1049
|
}
|
|
845
1050
|
default: {
|
|
846
|
-
const res = await
|
|
1051
|
+
const [res, statsRes] = await Promise.all([
|
|
1052
|
+
makeRequest(`/projects/${id}`),
|
|
1053
|
+
makeRequest(`/projects/${id}/stats`).catch(() => null)
|
|
1054
|
+
]);
|
|
847
1055
|
const p = res.project;
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1056
|
+
const stats = statsRes ? statsRes.stats : null;
|
|
1057
|
+
|
|
1058
|
+
const fields = [
|
|
1059
|
+
{ label: 'Project Name', value: colors.bold + p.name + colors.reset },
|
|
1060
|
+
{ label: 'ID / Slug', value: `${p.id} (${p.slug})` },
|
|
1061
|
+
{ label: 'State', value: ui.badge(p.status) },
|
|
1062
|
+
{ label: 'Public URL', value: colors.brightCyan + colors.bold + p.url + colors.reset },
|
|
1063
|
+
{ label: 'Repository', value: colors.gray + `${p.repo_url} (${p.branch})` + colors.reset }
|
|
1064
|
+
];
|
|
1065
|
+
|
|
1066
|
+
if (stats && p.status === 'running') {
|
|
1067
|
+
const cpuPct = stats.cpu_usage !== undefined ? stats.cpu_usage : 0;
|
|
1068
|
+
const rawUsage = stats.memory_usage ?? 0;
|
|
1069
|
+
const rawLimit = stats.memory_limit || stats.memory_limit_mb || p.memory_mb || 0;
|
|
1070
|
+
|
|
1071
|
+
const rawUsageMb = rawUsage < 1000000 ? rawUsage : rawUsage / (1024 * 1024);
|
|
1072
|
+
const rawLimitMb = rawLimit > 0 ? (rawLimit < 1000000 ? rawLimit : rawLimit / (1024 * 1024)) : (p.memory_mb || 512);
|
|
1073
|
+
|
|
1074
|
+
const memUsed = formatMemory(rawUsageMb);
|
|
1075
|
+
const memLimit = formatMemory(rawLimitMb);
|
|
1076
|
+
const memPct = rawLimitMb > 0 ? (rawUsageMb / rawLimitMb) * 100 : (stats.memory_percent || 0);
|
|
1077
|
+
|
|
1078
|
+
fields.push(
|
|
1079
|
+
{ label: 'CPU Usage', value: ui.progressBar(cpuPct) },
|
|
1080
|
+
{ label: 'Memory Usage', value: `${memUsed} / ${memLimit} (${ui.progressBar(memPct)})` }
|
|
1081
|
+
);
|
|
1082
|
+
} else {
|
|
1083
|
+
fields.push(
|
|
1084
|
+
{ label: 'Resource Alloc', value: `${p.memory_mb} MB RAM · ${p.cpu_cores} Core(s)` }
|
|
1085
|
+
);
|
|
864
1086
|
}
|
|
865
|
-
|
|
866
|
-
|
|
1087
|
+
|
|
1088
|
+
fields.push(
|
|
1089
|
+
{ label: 'Autoscale', value: p.autoscale_enabled ? `${ui.badge('ENABLED')} (${p.min_replicas}-${p.max_replicas} reps)` : ui.badge('DISABLED') },
|
|
1090
|
+
{ label: 'Created At', value: formatDate(p.created_at) }
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
ui.box(fields, { title: p.name.toUpperCase() });
|
|
867
1094
|
}
|
|
868
1095
|
}
|
|
869
1096
|
}
|
|
870
1097
|
|
|
871
1098
|
async function cmdWorkspace() {
|
|
1099
|
+
ui.stepInfo(`Fetching workspace quotas & usage metrics...`);
|
|
872
1100
|
const wsRes = await makeRequest('/workspaces/current');
|
|
873
1101
|
const workspace = wsRes.workspace;
|
|
874
1102
|
const usage = wsRes.usage;
|
|
875
1103
|
const limits = wsRes.limits;
|
|
876
1104
|
|
|
877
|
-
console.log(`\n${colors.cyan}${colors.bold}====================================================${colors.reset}`);
|
|
878
|
-
console.log(`${colors.bold} WORKSPACE QUOTAS & METRICS ${colors.reset}`);
|
|
879
|
-
console.log(`${colors.cyan}${colors.bold}====================================================${colors.reset}`);
|
|
880
|
-
console.log(`Workspace: ${workspace.name} (${workspace.slug})`);
|
|
881
|
-
console.log(`Billing Period: ${usage.billing_period}`);
|
|
882
|
-
console.log(`Tier: ${colors.magenta}${workspace.tier.toUpperCase()}${colors.reset}`);
|
|
883
|
-
console.log(`----------------------------------------------------`);
|
|
884
|
-
console.log(`Resource Usage:`);
|
|
885
|
-
|
|
886
1105
|
const projLimit = limits.max_projects || 'Unlimited';
|
|
887
1106
|
const depLimit = limits.max_deployments || 'Unlimited';
|
|
888
1107
|
const bwLimit = limits.max_bandwidth_bytes ? formatBytes(limits.max_bandwidth_bytes) : 'Unlimited';
|
|
889
1108
|
const buildLimit = limits.max_build_minutes ? `${limits.max_build_minutes} mins` : 'Unlimited';
|
|
890
1109
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1110
|
+
ui.step(`Retrieved workspace quotas`);
|
|
1111
|
+
|
|
1112
|
+
ui.box([
|
|
1113
|
+
{ label: 'Workspace', value: `${colors.bold}${workspace.name}${colors.reset} (${workspace.slug})` },
|
|
1114
|
+
{ label: 'Tier Plan', value: colors.magenta + colors.bold + workspace.tier.toUpperCase() + colors.reset },
|
|
1115
|
+
{ label: 'Billing Period', value: colors.gray + usage.billing_period + colors.reset },
|
|
1116
|
+
{ label: 'Projects Slot', value: `${usage.projects_count} / ${projLimit}` },
|
|
1117
|
+
{ label: 'Deployments', value: `${usage.deployments_count} / ${depLimit}` },
|
|
1118
|
+
{ label: 'Bandwidth Used', value: `${formatBytes(usage.bandwidth_bytes)} / ${bwLimit}` },
|
|
1119
|
+
{ label: 'Build Duration', value: `${(usage.build_seconds / 60).toFixed(1)} mins / ${buildLimit}` }
|
|
1120
|
+
], { title: 'WORKSPACE QUOTAS' });
|
|
896
1121
|
}
|
|
897
1122
|
|
|
898
1123
|
function printHelp() {
|
|
899
1124
|
console.log(`
|
|
900
|
-
${colors.bold}RushDeploy CLI${colors.reset} - Command Line Interface
|
|
901
|
-
|
|
902
|
-
${colors.bold}
|
|
903
|
-
rushdeploy <command> [arguments]
|
|
904
|
-
|
|
905
|
-
${colors.bold}
|
|
906
|
-
${colors.green}login <token>${colors.reset}
|
|
907
|
-
|
|
908
|
-
${colors.green}
|
|
909
|
-
${colors.green}
|
|
910
|
-
${colors.green}
|
|
911
|
-
${colors.green}
|
|
912
|
-
${colors.green}
|
|
913
|
-
${colors.green}
|
|
914
|
-
${colors.green}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
${colors.
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
${colors.
|
|
926
|
-
${colors.green}project <slug|id> start${colors.reset}
|
|
927
|
-
${colors.green}project <slug|id> stop${colors.reset}
|
|
928
|
-
${colors.green}project <slug|id> redeploy${colors.reset}
|
|
929
|
-
${colors.green}project <slug|id> visit${colors.reset}
|
|
930
|
-
${colors.green}project <slug|id> stats${colors.reset}
|
|
931
|
-
${colors.green}project <slug|id>
|
|
932
|
-
${colors.green}project <slug|id>
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
${colors.
|
|
937
|
-
${colors.green}project <slug|id>
|
|
938
|
-
${colors.green}project <slug|id>
|
|
939
|
-
|
|
940
|
-
${colors.green}project <slug|id>
|
|
941
|
-
${colors.green}project <slug|id> env${colors.reset} List environment variables.
|
|
942
|
-
${colors.green}project <slug|id> env set KEY=VAL [K2=V2]${colors.reset} Set environment variables.
|
|
943
|
-
${colors.green}project <slug|id> env remove KEY [KEY2]${colors.reset} Remove environment variables.
|
|
1125
|
+
${colors.bold}${colors.brightWhite}RushDeploy CLI${colors.reset} ${colors.gray}v1.0.2${colors.reset} - Modern PaaS Command Line Interface
|
|
1126
|
+
|
|
1127
|
+
${colors.bold}USAGE:${colors.reset}
|
|
1128
|
+
${colors.brightCyan}rushdeploy${colors.reset} <command> [arguments] [options]
|
|
1129
|
+
|
|
1130
|
+
${colors.bold}GLOBAL COMMANDS:${colors.reset}
|
|
1131
|
+
${colors.green}login <token>${colors.reset} Authenticate with your access token
|
|
1132
|
+
${colors.green}logout${colors.reset} Clear your stored CLI session token
|
|
1133
|
+
${colors.green}whoami${colors.reset} Display active profile details
|
|
1134
|
+
${colors.green}plans${colors.reset} List subscription plans and limits
|
|
1135
|
+
${colors.green}status${colors.reset} Show system & workspace status metrics
|
|
1136
|
+
${colors.green}projects${colors.reset} List all projects in current workspace
|
|
1137
|
+
${colors.green}repos${colors.reset} List repositories from connected GitHub account
|
|
1138
|
+
${colors.green}workspace${colors.reset} Display workspace resource usage & quotas
|
|
1139
|
+
${colors.green}help${colors.reset} Display this help manual
|
|
1140
|
+
|
|
1141
|
+
${colors.bold}DEPLOYMENT COMMANDS:${colors.reset}
|
|
1142
|
+
${colors.green}deploy git <repo_url>${colors.reset} Deploy a project from a Git repository URL
|
|
1143
|
+
Options: ${colors.gray}--name <name> --branch <branch> --type <type>${colors.reset}
|
|
1144
|
+
${colors.green}deploy repo <owner/repo>${colors.reset} Deploy from connected GitHub account
|
|
1145
|
+
Options: ${colors.gray}--name <name> --branch <branch> --type <type>${colors.reset}
|
|
1146
|
+
${colors.green}deploy wordpress <name>${colors.reset} Deploy a new one-click WordPress site
|
|
1147
|
+
Options: ${colors.gray}--email <e> [--title <t>] [--user <u>] [--password <p>]${colors.reset}
|
|
1148
|
+
|
|
1149
|
+
${colors.bold}PROJECT CONTROL:${colors.reset}
|
|
1150
|
+
${colors.green}project <slug|id>${colors.reset} Show project overview
|
|
1151
|
+
${colors.green}project <slug|id> start${colors.reset} Start project container
|
|
1152
|
+
${colors.green}project <slug|id> stop${colors.reset} Stop project container
|
|
1153
|
+
${colors.green}project <slug|id> redeploy${colors.reset} Rebuild and redeploy project
|
|
1154
|
+
${colors.green}project <slug|id> visit${colors.reset} Open project in default browser
|
|
1155
|
+
${colors.green}project <slug|id> stats${colors.reset} Show container CPU & RAM metrics meter
|
|
1156
|
+
${colors.green}project <slug|id> actions${colors.reset} Show recent deployment history
|
|
1157
|
+
${colors.green}project <slug|id> logs${colors.reset} View project container logs (${colors.gray}--lines <N>${colors.reset})
|
|
1158
|
+
|
|
1159
|
+
${colors.bold}PROJECT CONFIGURATION:${colors.reset}
|
|
1160
|
+
${colors.green}project <slug|id> autoscale${colors.reset} Show auto-scaling configuration
|
|
1161
|
+
${colors.green}project <slug|id> autoscale <on|off>${colors.reset} Configure auto-scaling limits (${colors.gray}--min <N> --max <N> --cpu <%>${colors.reset})
|
|
1162
|
+
${colors.green}project <slug|id> autodeploy <on|off>${colors.reset} Toggle auto-deploy webhooks
|
|
1163
|
+
${colors.green}project <slug|id> env${colors.reset} List environment variables
|
|
1164
|
+
${colors.green}project <slug|id> env set K=V${colors.reset} Set environment variable(s)
|
|
1165
|
+
${colors.green}project <slug|id> env remove K${colors.reset} Remove environment variable(s)
|
|
944
1166
|
`);
|
|
945
1167
|
}
|
|
946
1168
|
|
|
@@ -957,8 +1179,8 @@ async function main() {
|
|
|
957
1179
|
case 'login': {
|
|
958
1180
|
const token = args[1];
|
|
959
1181
|
if (!token) {
|
|
960
|
-
|
|
961
|
-
console.log(
|
|
1182
|
+
ui.stepError(`Please provide your access token.`);
|
|
1183
|
+
console.log(` Usage: ${colors.green}rushdeploy login <token> [--server <url>]${colors.reset}`);
|
|
962
1184
|
process.exit(1);
|
|
963
1185
|
}
|
|
964
1186
|
|
|
@@ -998,8 +1220,8 @@ async function main() {
|
|
|
998
1220
|
case 'project': {
|
|
999
1221
|
const idOrSlug = args[1];
|
|
1000
1222
|
if (!idOrSlug) {
|
|
1001
|
-
|
|
1002
|
-
console.log(
|
|
1223
|
+
ui.stepError(`Please provide a project ID or slug.`);
|
|
1224
|
+
console.log(` Usage: ${colors.green}rushdeploy project <slug|id> [subcommand] [arguments]${colors.reset}`);
|
|
1003
1225
|
process.exit(1);
|
|
1004
1226
|
}
|
|
1005
1227
|
const subcommand = args[2];
|
|
@@ -1011,13 +1233,13 @@ async function main() {
|
|
|
1011
1233
|
await cmdWorkspace();
|
|
1012
1234
|
break;
|
|
1013
1235
|
default:
|
|
1014
|
-
|
|
1236
|
+
ui.stepError(`Unknown command: "${command}"`);
|
|
1015
1237
|
printHelp();
|
|
1016
1238
|
process.exit(1);
|
|
1017
1239
|
}
|
|
1018
1240
|
}
|
|
1019
1241
|
|
|
1020
1242
|
main().catch(err => {
|
|
1021
|
-
|
|
1243
|
+
ui.stepError(`Fatal Error: ${err.message}`);
|
|
1022
1244
|
process.exit(1);
|
|
1023
1245
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rushdeploy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "CLI tool to manage your self-hosted RushDeploy PaaS account.",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,5 +16,9 @@
|
|
|
16
16
|
"cli"
|
|
17
17
|
],
|
|
18
18
|
"author": "RushDeploy Team",
|
|
19
|
-
"license": "MIT"
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"README.md"
|
|
23
|
+
]
|
|
20
24
|
}
|