rushdeploy 1.2.0 → 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.
Files changed (2) hide show
  1. package/bin/index.js +77 -35
  2. package/package.json +1 -1
package/bin/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * RushDeploy CLI
4
4
  * A zero-dependency Node.js CLI utility to manage your RushDeploy account.
@@ -7,6 +7,14 @@
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
20
  const { exec, spawn } = require('child_process');
@@ -14,6 +22,11 @@ const { exec, spawn } = require('child_process');
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 = `│${' '.repeat(innerWidth)}│`;
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}`);
@@ -352,9 +373,30 @@ function extractRepoName(url) {
352
373
  }
353
374
  }
354
375
 
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
+
355
397
  function loginViaBrowser(server) {
356
398
  return new Promise((resolve, reject) => {
357
- const state = crypto.randomBytes(16).toString('hex');
399
+ const state = generateRandomState();
358
400
 
359
401
  const localServer = http.createServer((req, res) => {
360
402
  try {
@@ -425,7 +467,7 @@ function loginViaBrowser(server) {
425
467
  timer = setTimeout(() => {
426
468
  try {
427
469
  localServer.close();
428
- } catch {}
470
+ } catch { }
429
471
  reject(new Error('Authentication timed out after 2 minutes'));
430
472
  }, 120000);
431
473
  if (timer.unref) {
@@ -450,16 +492,16 @@ async function cmdLogin(tokenArg, serverArg) {
450
492
  } else {
451
493
  ui.stepInfo(`Connecting to ${colors.brightWhite}${server}${colors.reset}...`);
452
494
  }
453
-
495
+
454
496
  saveConfig(server, token);
455
-
497
+
456
498
  try {
457
499
  ui.stepInfo(`Validating authentication token...`);
458
500
  const res = await makeRequest('/auth/me');
459
501
  if (res.success) {
460
502
  const user = res.user;
461
503
  ui.step(`Authentication successful!`);
462
-
504
+
463
505
  ui.box([
464
506
  { label: 'Account', value: `${colors.bold}${user.name}${colors.reset} (${colors.gray}${user.email}${colors.reset})` },
465
507
  { label: 'Role', value: user.role.toUpperCase() },
@@ -526,7 +568,7 @@ async function cmdPlans() {
526
568
  function drawCards(cards, cardsPerRow = 2) {
527
569
  const cardWidth = 34;
528
570
  const rows = [];
529
-
571
+
530
572
  for (let i = 0; i < cards.length; i += cardsPerRow) {
531
573
  rows.push(cards.slice(i, i + cardsPerRow));
532
574
  }
@@ -545,19 +587,19 @@ function drawCards(cards, cardsPerRow = 2) {
545
587
 
546
588
  const label = card.label || '';
547
589
  let value = card.value !== undefined ? String(card.value) : '';
548
-
590
+
549
591
  let valColor = colors.bold;
550
592
  if (card.status === 'online') valColor = colors.green + colors.bold;
551
593
  else if (card.status === 'offline') valColor = colors.red + colors.bold;
552
594
  else if (card.status === 'warn') valColor = colors.yellow + colors.bold;
553
-
595
+
554
596
  const valStr = `${valColor}${value}${colors.reset}`;
555
597
  const rawVal = stripAnsi(value);
556
-
598
+
557
599
  const textLen = label.length + rawVal.length;
558
600
  const padLen = cardWidth - 6 - textLen;
559
601
  const pad = ' '.repeat(Math.max(1, padLen));
560
-
602
+
561
603
  labelValLine += space + `${colors.gray}│${colors.reset} ${colors.brightCyan}${label}${colors.reset}${pad}${valStr} ${colors.gray}│${colors.reset}`;
562
604
  sepLine += space + colors.gray + '├' + '─'.repeat(cardWidth - 2) + '┤' + colors.reset;
563
605
 
@@ -565,7 +607,7 @@ function drawCards(cards, cardsPerRow = 2) {
565
607
  const rawHint = stripAnsi(hint);
566
608
  const hintPadLen = cardWidth - 6 - rawHint.length;
567
609
  const hintPad = ' '.repeat(Math.max(0, hintPadLen));
568
-
610
+
569
611
  hintLine += space + `${colors.gray}│${colors.reset} ${colors.gray}${hint}${colors.reset}${hintPad} ${colors.gray}│${colors.reset}`;
570
612
  bottomBorder += space + colors.gray + '└' + '─'.repeat(cardWidth - 2) + '┘' + colors.reset;
571
613
  });
@@ -604,7 +646,7 @@ async function cmdStatus() {
604
646
  const byStatus = stats.projects_by_status || {};
605
647
  const celery = stats.celery || {};
606
648
  const workerOnline = (celery.workers ?? 0) > 0;
607
-
649
+
608
650
  const activeBuildingProjects = (byStatus.building || byStatus.BUILDING || 0) + (byStatus.pending || byStatus.PENDING || 0);
609
651
  const activeTasksCount = (celery.active && celery.active > 0) ? celery.active : activeBuildingProjects;
610
652
  const pendingInQueue = stats.redis_queue_len ?? celery.queued ?? 0;
@@ -622,23 +664,23 @@ async function cmdStatus() {
622
664
  const adminCards = [
623
665
  { label: 'Users', value: stats.users ?? 0, hint: 'Total registered users' },
624
666
  { label: 'Databases', value: stats.databases ?? 0, hint: 'Total workspace databases' },
625
- {
626
- label: 'Projects',
627
- value: stats.projects ?? 0,
628
- 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}`
629
671
  },
630
- {
631
- label: 'App Containers',
632
- value: stats.running_containers ?? 0,
672
+ {
673
+ label: 'App Containers',
674
+ value: stats.running_containers ?? 0,
633
675
  status: stats.docker_available ? 'online' : 'offline',
634
676
  hint: stats.docker_available ? 'Active running containers' : 'Engine offline'
635
677
  },
636
678
  { label: 'CPU Usage', value: cpuVal, hint: 'Host CPU load' },
637
679
  { label: 'RAM Usage', value: ramVal, hint: 'Host memory allocation' },
638
680
  { label: 'Disk Usage', value: diskVal, hint: 'Host storage capacity' },
639
- {
640
- label: 'Task Queue',
641
- value: workerOnline ? 'Online' : 'Offline',
681
+ {
682
+ label: 'Task Queue',
683
+ value: workerOnline ? 'Online' : 'Offline',
642
684
  status: workerOnline ? 'online' : 'offline',
643
685
  hint: `${activeTasksCount} active · ${pendingInQueue} pending`
644
686
  }
@@ -940,14 +982,14 @@ async function resolveProjectId(idOrSlug) {
940
982
  if (/^\d+$/.test(idOrSlug)) {
941
983
  return parseInt(idOrSlug, 10);
942
984
  }
943
-
985
+
944
986
  const res = await makeRequest('/projects/');
945
987
  const projects = res.projects || [];
946
988
  const matched = projects.find(p => p.slug === idOrSlug || p.name.toLowerCase() === idOrSlug.toLowerCase());
947
989
  if (matched) {
948
990
  return matched.id;
949
991
  }
950
-
992
+
951
993
  ui.stepError(`No project found matching name or slug "${idOrSlug}".`);
952
994
  process.exit(1);
953
995
  }
@@ -1017,7 +1059,7 @@ async function cmdProject(idOrSlug, subcommand, subArgs) {
1017
1059
  const stats = statsRes.stats;
1018
1060
 
1019
1061
  ui.stepInfo(`Resource metrics for Project ${id}`);
1020
-
1062
+
1021
1063
  const fields = [
1022
1064
  { label: 'Current Status', value: ui.badge(info.status) }
1023
1065
  ];
@@ -1298,7 +1340,7 @@ async function cmdWorkspace() {
1298
1340
 
1299
1341
  function printHelp() {
1300
1342
  console.log(`
1301
- ${colors.bold}${colors.brightWhite}RushDeploy CLI${colors.reset} ${colors.gray}v1.1.0${colors.reset} - Modern PaaS Command Line Interface
1343
+ ${colors.bold}${colors.brightWhite}RushDeploy CLI${colors.reset} ${colors.gray}v${CLI_VERSION}${colors.reset} - Modern PaaS Command Line Interface
1302
1344
 
1303
1345
  ${colors.bold}USAGE:${colors.reset}
1304
1346
  ${colors.brightCyan}rushdeploy${colors.reset} <command> [arguments] [options]
@@ -1355,7 +1397,7 @@ async function main() {
1355
1397
  }
1356
1398
 
1357
1399
  if (command === '--version' || command === '-v' || command === 'version') {
1358
- console.log('1.1.0');
1400
+ console.log(CLI_VERSION);
1359
1401
  process.exit(0);
1360
1402
  }
1361
1403
 
@@ -1363,7 +1405,7 @@ async function main() {
1363
1405
  case 'login': {
1364
1406
  let token = null;
1365
1407
  let server = null;
1366
-
1408
+
1367
1409
  const serverIndex = args.indexOf('--server');
1368
1410
  if (serverIndex !== -1 && args[serverIndex + 1]) {
1369
1411
  server = args[serverIndex + 1];
@@ -1373,7 +1415,7 @@ async function main() {
1373
1415
  if (args[1] && !args[1].startsWith('--')) {
1374
1416
  token = args[1];
1375
1417
  }
1376
-
1418
+
1377
1419
  await cmdLogin(token, server);
1378
1420
  break;
1379
1421
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rushdeploy",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLI tool to manage your self-hosted RushDeploy PaaS account.",
5
5
  "main": "bin/index.js",
6
6
  "bin": {