rushdeploy 1.0.9 → 1.2.1
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/README.md +4 -3
- package/bin/index.js +215 -63
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,10 +17,11 @@ npm install -g rushdeploy
|
|
|
17
17
|
## Quickstart
|
|
18
18
|
|
|
19
19
|
### 1. Authenticate with your server
|
|
20
|
-
|
|
20
|
+
Run the login command to authenticate directly in your browser:
|
|
21
21
|
```bash
|
|
22
|
-
rushdeploy login
|
|
22
|
+
rushdeploy login
|
|
23
23
|
```
|
|
24
|
+
*(Or provide a token directly in headless/CI environments: `rushdeploy login <token>`)*
|
|
24
25
|
|
|
25
26
|
### 2. Check Workspace Status
|
|
26
27
|
```bash
|
|
@@ -38,7 +39,7 @@ rushdeploy deploy git https://github.com/my-username/my-react-app.git --branch m
|
|
|
38
39
|
|
|
39
40
|
## Commands Reference
|
|
40
41
|
|
|
41
|
-
* **`rushdeploy login
|
|
42
|
+
* **`rushdeploy login [token]`**: Authenticate via browser (or pass access token).
|
|
42
43
|
* **`rushdeploy logout`**: Clear local auth details.
|
|
43
44
|
* **`rushdeploy whoami`**: View details of the active user profile.
|
|
44
45
|
* **`rushdeploy status`**: Render resource metrics cards.
|
package/bin/index.js
CHANGED
|
@@ -7,13 +7,26 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const os = require('os');
|
|
10
|
+
let crypto;
|
|
11
|
+
try {
|
|
12
|
+
crypto = require('node:crypto');
|
|
13
|
+
} catch {
|
|
14
|
+
try {
|
|
15
|
+
crypto = require('crypto');
|
|
16
|
+
} catch {}
|
|
17
|
+
}
|
|
10
18
|
const http = require('http');
|
|
11
19
|
const https = require('https');
|
|
12
|
-
const { exec } = require('child_process');
|
|
20
|
+
const { exec, spawn } = require('child_process');
|
|
13
21
|
|
|
14
22
|
const CONFIG_DIR = path.join(os.homedir(), '.rushdeploy');
|
|
15
23
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
16
24
|
|
|
25
|
+
let CLI_VERSION = '1.2.1';
|
|
26
|
+
try {
|
|
27
|
+
CLI_VERSION = require('../package.json').version;
|
|
28
|
+
} catch {}
|
|
29
|
+
|
|
17
30
|
// Domain Hardcoding: Always default to rushdeploy.com
|
|
18
31
|
const DEFAULT_SERVER = 'https://rushdeploy.com';
|
|
19
32
|
|
|
@@ -92,8 +105,8 @@ const ui = {
|
|
|
92
105
|
},
|
|
93
106
|
|
|
94
107
|
box(fields, options = {}) {
|
|
95
|
-
const fieldPairs = Array.isArray(fields)
|
|
96
|
-
? fields
|
|
108
|
+
const fieldPairs = Array.isArray(fields)
|
|
109
|
+
? fields
|
|
97
110
|
: Object.entries(fields).map(([label, value]) => ({ label, value }));
|
|
98
111
|
|
|
99
112
|
const labelWidth = 14;
|
|
@@ -110,7 +123,7 @@ const ui = {
|
|
|
110
123
|
const innerWidth = Math.max(options.minWidth || 66, leftIndent + labelWidth + gap + maxValLen + rightMargin);
|
|
111
124
|
|
|
112
125
|
const topBorder = `┌${'─'.repeat(innerWidth)}┐`;
|
|
113
|
-
const emptyRow
|
|
126
|
+
const emptyRow = `│${' '.repeat(innerWidth)}│`;
|
|
114
127
|
const bottomBorder = `└${'─'.repeat(innerWidth)}┘`;
|
|
115
128
|
|
|
116
129
|
console.log(`\n${colors.gray}${topBorder}${colors.reset}`);
|
|
@@ -192,10 +205,18 @@ function loadConfig() {
|
|
|
192
205
|
function saveConfig(server, token) {
|
|
193
206
|
try {
|
|
194
207
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
195
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
208
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
196
209
|
}
|
|
197
210
|
const cleanServer = (server || DEFAULT_SERVER).replace(/\/$/, '');
|
|
198
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ server: cleanServer, token }, null, 2), 'utf8');
|
|
211
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ server: cleanServer, token }, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
212
|
+
try {
|
|
213
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
214
|
+
} catch (chmodErr) {
|
|
215
|
+
try {
|
|
216
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
217
|
+
} catch {}
|
|
218
|
+
throw chmodErr;
|
|
219
|
+
}
|
|
199
220
|
return true;
|
|
200
221
|
} catch (err) {
|
|
201
222
|
ui.stepError(`Error saving configuration: ${err.message}`);
|
|
@@ -210,8 +231,9 @@ function makeRequest(apiPath, method = 'GET', data = null) {
|
|
|
210
231
|
const isPlansPath = apiPath.endsWith('/subscriptions/plans') && method === 'GET';
|
|
211
232
|
|
|
212
233
|
if (!config && !isLoginPath && !isPlansPath) {
|
|
213
|
-
|
|
214
|
-
|
|
234
|
+
const err = new Error(`You are not logged in. Run 'rushdeploy login <token>' first.`);
|
|
235
|
+
err.statusCode = 401;
|
|
236
|
+
return reject(err);
|
|
215
237
|
}
|
|
216
238
|
|
|
217
239
|
const serverUrl = getServerUrl();
|
|
@@ -255,25 +277,27 @@ function makeRequest(apiPath, method = 'GET', data = null) {
|
|
|
255
277
|
} else if (jsonResponse && jsonResponse.detail) {
|
|
256
278
|
errorMsg = typeof jsonResponse.detail === 'object' ? jsonResponse.detail.message : jsonResponse.detail;
|
|
257
279
|
}
|
|
258
|
-
|
|
280
|
+
|
|
281
|
+
let formattedError;
|
|
259
282
|
if (res.statusCode === 401) {
|
|
260
|
-
|
|
261
|
-
console.log(` Please log in again using: ${colors.green}rushdeploy login <token>${colors.reset}`);
|
|
283
|
+
formattedError = `Unauthorized (401). Your token may have expired or been revoked.\n Please log in again using: ${colors.green}rushdeploy login <token>${colors.reset}`;
|
|
262
284
|
} else if (res.statusCode === 429) {
|
|
263
|
-
|
|
264
|
-
console.log(` Please wait a minute before running more CLI commands.`);
|
|
285
|
+
formattedError = `Rate Limit Exceeded (429): You are sending commands too quickly.\n Please wait a minute before running more CLI commands.`;
|
|
265
286
|
} else {
|
|
266
|
-
|
|
287
|
+
formattedError = `Request failed (${res.statusCode}): ${errorMsg}`;
|
|
267
288
|
}
|
|
268
|
-
|
|
289
|
+
const err = new Error(formattedError);
|
|
290
|
+
err.statusCode = res.statusCode;
|
|
291
|
+
err.response = jsonResponse;
|
|
292
|
+
reject(err);
|
|
269
293
|
}
|
|
270
294
|
});
|
|
271
295
|
});
|
|
272
296
|
|
|
273
297
|
req.on('error', (err) => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
298
|
+
const connErr = new Error(`Connection Error: Unable to connect to server at ${serverUrl} (${err.message})`);
|
|
299
|
+
connErr.cause = err;
|
|
300
|
+
reject(connErr);
|
|
277
301
|
});
|
|
278
302
|
|
|
279
303
|
if (data) {
|
|
@@ -312,24 +336,31 @@ function formatDate(isoStr) {
|
|
|
312
336
|
|
|
313
337
|
function openBrowser(url) {
|
|
314
338
|
let command;
|
|
339
|
+
let args = [];
|
|
315
340
|
switch (process.platform) {
|
|
316
341
|
case 'darwin':
|
|
317
|
-
command =
|
|
342
|
+
command = 'open';
|
|
343
|
+
args = [url];
|
|
318
344
|
break;
|
|
319
345
|
case 'win32':
|
|
320
|
-
command =
|
|
346
|
+
command = 'explorer.exe';
|
|
347
|
+
args = [url];
|
|
321
348
|
break;
|
|
322
349
|
default:
|
|
323
|
-
command =
|
|
350
|
+
command = 'xdg-open';
|
|
351
|
+
args = [url];
|
|
324
352
|
break;
|
|
325
353
|
}
|
|
326
|
-
|
|
327
|
-
|
|
354
|
+
try {
|
|
355
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
356
|
+
child.on('error', (err) => {
|
|
328
357
|
ui.stepError(`Failed to open browser: ${err.message}`);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
})
|
|
358
|
+
});
|
|
359
|
+
child.unref();
|
|
360
|
+
ui.step(`Opening browser: ${colors.brightCyan}${url}${colors.reset}`);
|
|
361
|
+
} catch (err) {
|
|
362
|
+
ui.stepError(`Failed to open browser: ${err.message}`);
|
|
363
|
+
}
|
|
333
364
|
}
|
|
334
365
|
|
|
335
366
|
function extractRepoName(url) {
|
|
@@ -342,19 +373,135 @@ function extractRepoName(url) {
|
|
|
342
373
|
}
|
|
343
374
|
}
|
|
344
375
|
|
|
345
|
-
|
|
376
|
+
function generateRandomState() {
|
|
377
|
+
if (crypto && typeof crypto.randomBytes === 'function') {
|
|
378
|
+
try {
|
|
379
|
+
return crypto.randomBytes(16).toString('hex');
|
|
380
|
+
} catch {}
|
|
381
|
+
}
|
|
382
|
+
if (typeof globalThis !== 'undefined' && globalThis.crypto?.getRandomValues) {
|
|
383
|
+
try {
|
|
384
|
+
const array = new Uint8Array(16);
|
|
385
|
+
globalThis.crypto.getRandomValues(array);
|
|
386
|
+
return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
387
|
+
} catch {}
|
|
388
|
+
}
|
|
389
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.crypto?.randomUUID === 'function') {
|
|
390
|
+
try {
|
|
391
|
+
return globalThis.crypto.randomUUID().replace(/-/g, '');
|
|
392
|
+
} catch {}
|
|
393
|
+
}
|
|
394
|
+
return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function loginViaBrowser(server) {
|
|
398
|
+
return new Promise((resolve, reject) => {
|
|
399
|
+
const state = generateRandomState();
|
|
400
|
+
|
|
401
|
+
const localServer = http.createServer((req, res) => {
|
|
402
|
+
try {
|
|
403
|
+
const parsed = new URL(req.url, 'http://127.0.0.1');
|
|
404
|
+
if (parsed.pathname === '/callback') {
|
|
405
|
+
const returnedToken = parsed.searchParams.get('token');
|
|
406
|
+
const returnedState = parsed.searchParams.get('state');
|
|
407
|
+
|
|
408
|
+
if (!returnedToken || returnedState !== state) {
|
|
409
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
410
|
+
res.end('<h1>Authentication Failed</h1><p>Invalid state or token parameter received.</p>');
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
415
|
+
res.end(`
|
|
416
|
+
<!DOCTYPE html>
|
|
417
|
+
<html>
|
|
418
|
+
<head>
|
|
419
|
+
<meta charset="utf-8">
|
|
420
|
+
<title>RushDeploy CLI - Authenticated</title>
|
|
421
|
+
<style>
|
|
422
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #09090b; color: #fff; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
|
423
|
+
.card { background: #18181b; border: 1px solid #27272a; padding: 36px; border-radius: 16px; text-align: center; max-width: 400px; box-shadow: 0 20px 40px rgba(0,0,0,0.6); }
|
|
424
|
+
.icon { width: 48px; height: 48px; background: rgba(16,185,129,0.15); color: #10b981; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 16px; }
|
|
425
|
+
h1 { color: #fafafa; font-size: 20px; font-weight: 600; margin: 0 0 8px 0; }
|
|
426
|
+
p { color: #a1a1aa; font-size: 13px; line-height: 1.5; margin: 0; }
|
|
427
|
+
</style>
|
|
428
|
+
</head>
|
|
429
|
+
<body>
|
|
430
|
+
<div class="card">
|
|
431
|
+
<div class="icon">✓</div>
|
|
432
|
+
<h1>Authentication Successful!</h1>
|
|
433
|
+
<p>Your CLI terminal has been successfully connected to RushDeploy. You can close this tab and return to your terminal.</p>
|
|
434
|
+
</div>
|
|
435
|
+
</body>
|
|
436
|
+
</html>
|
|
437
|
+
`);
|
|
438
|
+
|
|
439
|
+
if (timer) clearTimeout(timer);
|
|
440
|
+
localServer.close();
|
|
441
|
+
resolve(returnedToken);
|
|
442
|
+
} else {
|
|
443
|
+
res.writeHead(404);
|
|
444
|
+
res.end();
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
res.writeHead(500);
|
|
448
|
+
res.end('Internal server error');
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
let timer;
|
|
453
|
+
|
|
454
|
+
localServer.listen(0, '127.0.0.1', () => {
|
|
455
|
+
const address = localServer.address();
|
|
456
|
+
const port = address.port;
|
|
457
|
+
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
458
|
+
const authUrl = `${server}/auth/cli-auth?callback=${encodeURIComponent(callbackUrl)}&state=${state}&client=cli`;
|
|
459
|
+
|
|
460
|
+
ui.stepInfo(`Opening your browser to authenticate with RushDeploy...`);
|
|
461
|
+
console.log(` Waiting for authorization at:\n ${colors.brightCyan}${authUrl}${colors.reset}\n`);
|
|
462
|
+
|
|
463
|
+
openBrowser(authUrl);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// 2-minute timeout
|
|
467
|
+
timer = setTimeout(() => {
|
|
468
|
+
try {
|
|
469
|
+
localServer.close();
|
|
470
|
+
} catch { }
|
|
471
|
+
reject(new Error('Authentication timed out after 2 minutes'));
|
|
472
|
+
}, 120000);
|
|
473
|
+
if (timer.unref) {
|
|
474
|
+
timer.unref();
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function cmdLogin(tokenArg, serverArg) {
|
|
346
480
|
const server = serverArg || getServerUrl();
|
|
347
|
-
|
|
348
|
-
|
|
481
|
+
let token = tokenArg;
|
|
482
|
+
|
|
483
|
+
if (!token) {
|
|
484
|
+
ui.stepInfo(`Connecting to ${colors.brightWhite}${server}${colors.reset}...`);
|
|
485
|
+
try {
|
|
486
|
+
token = await loginViaBrowser(server);
|
|
487
|
+
} catch (err) {
|
|
488
|
+
ui.stepError(`Browser login failed: ${err.message}`);
|
|
489
|
+
console.log(` You can also log in manually using: ${colors.green}rushdeploy login <token>${colors.reset}`);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
ui.stepInfo(`Connecting to ${colors.brightWhite}${server}${colors.reset}...`);
|
|
494
|
+
}
|
|
495
|
+
|
|
349
496
|
saveConfig(server, token);
|
|
350
|
-
|
|
497
|
+
|
|
351
498
|
try {
|
|
352
499
|
ui.stepInfo(`Validating authentication token...`);
|
|
353
500
|
const res = await makeRequest('/auth/me');
|
|
354
501
|
if (res.success) {
|
|
355
502
|
const user = res.user;
|
|
356
503
|
ui.step(`Authentication successful!`);
|
|
357
|
-
|
|
504
|
+
|
|
358
505
|
ui.box([
|
|
359
506
|
{ label: 'Account', value: `${colors.bold}${user.name}${colors.reset} (${colors.gray}${user.email}${colors.reset})` },
|
|
360
507
|
{ label: 'Role', value: user.role.toUpperCase() },
|
|
@@ -364,7 +511,7 @@ async function cmdLogin(token, serverArg) {
|
|
|
364
511
|
}
|
|
365
512
|
} catch (err) {
|
|
366
513
|
if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
|
|
367
|
-
ui.stepError(`Authentication failed. Check your token or server URL
|
|
514
|
+
ui.stepError(`Authentication failed: ${err.message || 'Check your token or server URL.'}`);
|
|
368
515
|
process.exit(1);
|
|
369
516
|
}
|
|
370
517
|
}
|
|
@@ -421,7 +568,7 @@ async function cmdPlans() {
|
|
|
421
568
|
function drawCards(cards, cardsPerRow = 2) {
|
|
422
569
|
const cardWidth = 34;
|
|
423
570
|
const rows = [];
|
|
424
|
-
|
|
571
|
+
|
|
425
572
|
for (let i = 0; i < cards.length; i += cardsPerRow) {
|
|
426
573
|
rows.push(cards.slice(i, i + cardsPerRow));
|
|
427
574
|
}
|
|
@@ -440,19 +587,19 @@ function drawCards(cards, cardsPerRow = 2) {
|
|
|
440
587
|
|
|
441
588
|
const label = card.label || '';
|
|
442
589
|
let value = card.value !== undefined ? String(card.value) : '';
|
|
443
|
-
|
|
590
|
+
|
|
444
591
|
let valColor = colors.bold;
|
|
445
592
|
if (card.status === 'online') valColor = colors.green + colors.bold;
|
|
446
593
|
else if (card.status === 'offline') valColor = colors.red + colors.bold;
|
|
447
594
|
else if (card.status === 'warn') valColor = colors.yellow + colors.bold;
|
|
448
|
-
|
|
595
|
+
|
|
449
596
|
const valStr = `${valColor}${value}${colors.reset}`;
|
|
450
597
|
const rawVal = stripAnsi(value);
|
|
451
|
-
|
|
598
|
+
|
|
452
599
|
const textLen = label.length + rawVal.length;
|
|
453
600
|
const padLen = cardWidth - 6 - textLen;
|
|
454
601
|
const pad = ' '.repeat(Math.max(1, padLen));
|
|
455
|
-
|
|
602
|
+
|
|
456
603
|
labelValLine += space + `${colors.gray}│${colors.reset} ${colors.brightCyan}${label}${colors.reset}${pad}${valStr} ${colors.gray}│${colors.reset}`;
|
|
457
604
|
sepLine += space + colors.gray + '├' + '─'.repeat(cardWidth - 2) + '┤' + colors.reset;
|
|
458
605
|
|
|
@@ -460,7 +607,7 @@ function drawCards(cards, cardsPerRow = 2) {
|
|
|
460
607
|
const rawHint = stripAnsi(hint);
|
|
461
608
|
const hintPadLen = cardWidth - 6 - rawHint.length;
|
|
462
609
|
const hintPad = ' '.repeat(Math.max(0, hintPadLen));
|
|
463
|
-
|
|
610
|
+
|
|
464
611
|
hintLine += space + `${colors.gray}│${colors.reset} ${colors.gray}${hint}${colors.reset}${hintPad} ${colors.gray}│${colors.reset}`;
|
|
465
612
|
bottomBorder += space + colors.gray + '└' + '─'.repeat(cardWidth - 2) + '┘' + colors.reset;
|
|
466
613
|
});
|
|
@@ -499,7 +646,7 @@ async function cmdStatus() {
|
|
|
499
646
|
const byStatus = stats.projects_by_status || {};
|
|
500
647
|
const celery = stats.celery || {};
|
|
501
648
|
const workerOnline = (celery.workers ?? 0) > 0;
|
|
502
|
-
|
|
649
|
+
|
|
503
650
|
const activeBuildingProjects = (byStatus.building || byStatus.BUILDING || 0) + (byStatus.pending || byStatus.PENDING || 0);
|
|
504
651
|
const activeTasksCount = (celery.active && celery.active > 0) ? celery.active : activeBuildingProjects;
|
|
505
652
|
const pendingInQueue = stats.redis_queue_len ?? celery.queued ?? 0;
|
|
@@ -517,23 +664,23 @@ async function cmdStatus() {
|
|
|
517
664
|
const adminCards = [
|
|
518
665
|
{ label: 'Users', value: stats.users ?? 0, hint: 'Total registered users' },
|
|
519
666
|
{ label: 'Databases', value: stats.databases ?? 0, hint: 'Total workspace databases' },
|
|
520
|
-
{
|
|
521
|
-
label: 'Projects',
|
|
522
|
-
value: stats.projects ?? 0,
|
|
523
|
-
hint: `${colors.green}${runCount} run${colors.reset} · ${colors.yellow}${buildCount} build${colors.reset} · ${colors.red}${failCount} fail${colors.reset}`
|
|
667
|
+
{
|
|
668
|
+
label: 'Projects',
|
|
669
|
+
value: stats.projects ?? 0,
|
|
670
|
+
hint: `${colors.green}${runCount} run${colors.reset} · ${colors.yellow}${buildCount} build${colors.reset} · ${colors.red}${failCount} fail${colors.reset}`
|
|
524
671
|
},
|
|
525
|
-
{
|
|
526
|
-
label: 'App Containers',
|
|
527
|
-
value: stats.running_containers ?? 0,
|
|
672
|
+
{
|
|
673
|
+
label: 'App Containers',
|
|
674
|
+
value: stats.running_containers ?? 0,
|
|
528
675
|
status: stats.docker_available ? 'online' : 'offline',
|
|
529
676
|
hint: stats.docker_available ? 'Active running containers' : 'Engine offline'
|
|
530
677
|
},
|
|
531
678
|
{ label: 'CPU Usage', value: cpuVal, hint: 'Host CPU load' },
|
|
532
679
|
{ label: 'RAM Usage', value: ramVal, hint: 'Host memory allocation' },
|
|
533
680
|
{ label: 'Disk Usage', value: diskVal, hint: 'Host storage capacity' },
|
|
534
|
-
{
|
|
535
|
-
label: 'Task Queue',
|
|
536
|
-
value: workerOnline ? 'Online' : 'Offline',
|
|
681
|
+
{
|
|
682
|
+
label: 'Task Queue',
|
|
683
|
+
value: workerOnline ? 'Online' : 'Offline',
|
|
537
684
|
status: workerOnline ? 'online' : 'offline',
|
|
538
685
|
hint: `${activeTasksCount} active · ${pendingInQueue} pending`
|
|
539
686
|
}
|
|
@@ -835,14 +982,14 @@ async function resolveProjectId(idOrSlug) {
|
|
|
835
982
|
if (/^\d+$/.test(idOrSlug)) {
|
|
836
983
|
return parseInt(idOrSlug, 10);
|
|
837
984
|
}
|
|
838
|
-
|
|
985
|
+
|
|
839
986
|
const res = await makeRequest('/projects/');
|
|
840
987
|
const projects = res.projects || [];
|
|
841
988
|
const matched = projects.find(p => p.slug === idOrSlug || p.name.toLowerCase() === idOrSlug.toLowerCase());
|
|
842
989
|
if (matched) {
|
|
843
990
|
return matched.id;
|
|
844
991
|
}
|
|
845
|
-
|
|
992
|
+
|
|
846
993
|
ui.stepError(`No project found matching name or slug "${idOrSlug}".`);
|
|
847
994
|
process.exit(1);
|
|
848
995
|
}
|
|
@@ -912,7 +1059,7 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
|
|
|
912
1059
|
const stats = statsRes.stats;
|
|
913
1060
|
|
|
914
1061
|
ui.stepInfo(`Resource metrics for Project ${id}`);
|
|
915
|
-
|
|
1062
|
+
|
|
916
1063
|
const fields = [
|
|
917
1064
|
{ label: 'Current Status', value: ui.badge(info.status) }
|
|
918
1065
|
];
|
|
@@ -1193,13 +1340,13 @@ async function cmdWorkspace() {
|
|
|
1193
1340
|
|
|
1194
1341
|
function printHelp() {
|
|
1195
1342
|
console.log(`
|
|
1196
|
-
${colors.bold}${colors.brightWhite}RushDeploy CLI${colors.reset} ${colors.gray}
|
|
1343
|
+
${colors.bold}${colors.brightWhite}RushDeploy CLI${colors.reset} ${colors.gray}v${CLI_VERSION}${colors.reset} - Modern PaaS Command Line Interface
|
|
1197
1344
|
|
|
1198
1345
|
${colors.bold}USAGE:${colors.reset}
|
|
1199
1346
|
${colors.brightCyan}rushdeploy${colors.reset} <command> [arguments] [options]
|
|
1200
1347
|
|
|
1201
1348
|
${colors.bold}GLOBAL COMMANDS:${colors.reset}
|
|
1202
|
-
${colors.green}login
|
|
1349
|
+
${colors.green}login [token]${colors.reset} Authenticate with browser (or provide access token)
|
|
1203
1350
|
${colors.green}logout${colors.reset} Clear your stored CLI session token
|
|
1204
1351
|
${colors.green}whoami${colors.reset} Display active profile details
|
|
1205
1352
|
${colors.green}plans${colors.reset} List subscription plans and limits
|
|
@@ -1249,21 +1396,26 @@ async function main() {
|
|
|
1249
1396
|
process.exit(0);
|
|
1250
1397
|
}
|
|
1251
1398
|
|
|
1399
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
1400
|
+
console.log(CLI_VERSION);
|
|
1401
|
+
process.exit(0);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1252
1404
|
switch (command) {
|
|
1253
1405
|
case 'login': {
|
|
1254
|
-
|
|
1255
|
-
if (!token) {
|
|
1256
|
-
ui.stepError(`Please provide your access token.`);
|
|
1257
|
-
console.log(` Usage: ${colors.green}rushdeploy login <token> [--server <url>]${colors.reset}`);
|
|
1258
|
-
process.exit(1);
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1406
|
+
let token = null;
|
|
1261
1407
|
let server = null;
|
|
1408
|
+
|
|
1262
1409
|
const serverIndex = args.indexOf('--server');
|
|
1263
1410
|
if (serverIndex !== -1 && args[serverIndex + 1]) {
|
|
1264
1411
|
server = args[serverIndex + 1];
|
|
1265
1412
|
}
|
|
1266
|
-
|
|
1413
|
+
|
|
1414
|
+
// Check if user passed a token (first argument after 'login' that isn't --server or a flag)
|
|
1415
|
+
if (args[1] && !args[1].startsWith('--')) {
|
|
1416
|
+
token = args[1];
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1267
1419
|
await cmdLogin(token, server);
|
|
1268
1420
|
break;
|
|
1269
1421
|
}
|
package/package.json
CHANGED