rushdeploy 1.0.8 → 1.2.0

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 (3) hide show
  1. package/README.md +4 -3
  2. package/bin/index.js +145 -35
  3. 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
- Generate a CLI token in Settings inside the Web Dashboard and log in:
20
+ Run the login command to authenticate directly in your browser:
21
21
  ```bash
22
- rushdeploy login <token>
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 <token>`**: Authenticate CLI session.
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
@@ -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.
@@ -9,7 +9,7 @@ const path = require('path');
9
9
  const os = require('os');
10
10
  const http = require('http');
11
11
  const https = require('https');
12
- const { exec } = require('child_process');
12
+ const { exec, spawn } = require('child_process');
13
13
 
14
14
  const CONFIG_DIR = path.join(os.homedir(), '.rushdeploy');
15
15
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
@@ -210,8 +210,9 @@ function makeRequest(apiPath, method = 'GET', data = null) {
210
210
  const isPlansPath = apiPath.endsWith('/subscriptions/plans') && method === 'GET';
211
211
 
212
212
  if (!config && !isLoginPath && !isPlansPath) {
213
- ui.stepError(`You are not logged in. Run 'rushdeploy login <token>' first.`);
214
- process.exit(1);
213
+ const err = new Error(`You are not logged in. Run 'rushdeploy login <token>' first.`);
214
+ err.statusCode = 401;
215
+ return reject(err);
215
216
  }
216
217
 
217
218
  const serverUrl = getServerUrl();
@@ -255,25 +256,27 @@ function makeRequest(apiPath, method = 'GET', data = null) {
255
256
  } else if (jsonResponse && jsonResponse.detail) {
256
257
  errorMsg = typeof jsonResponse.detail === 'object' ? jsonResponse.detail.message : jsonResponse.detail;
257
258
  }
258
-
259
+
260
+ let formattedError;
259
261
  if (res.statusCode === 401) {
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
+ 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
263
  } 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.`);
264
+ formattedError = `Rate Limit Exceeded (429): You are sending commands too quickly.\n Please wait a minute before running more CLI commands.`;
265
265
  } else {
266
- ui.stepError(`Error (${res.statusCode}): ${errorMsg}`);
266
+ formattedError = `Request failed (${res.statusCode}): ${errorMsg}`;
267
267
  }
268
- process.exit(1);
268
+ const err = new Error(formattedError);
269
+ err.statusCode = res.statusCode;
270
+ err.response = jsonResponse;
271
+ reject(err);
269
272
  }
270
273
  });
271
274
  });
272
275
 
273
276
  req.on('error', (err) => {
274
- ui.stepError(`Connection Error: Unable to connect to server at ${serverUrl}`);
275
- console.log(` Details: ${colors.gray}${err.message}${colors.reset}`);
276
- process.exit(1);
277
+ const connErr = new Error(`Connection Error: Unable to connect to server at ${serverUrl} (${err.message})`);
278
+ connErr.cause = err;
279
+ reject(connErr);
277
280
  });
278
281
 
279
282
  if (data) {
@@ -312,24 +315,31 @@ function formatDate(isoStr) {
312
315
 
313
316
  function openBrowser(url) {
314
317
  let command;
318
+ let args = [];
315
319
  switch (process.platform) {
316
320
  case 'darwin':
317
- command = `open "${url}"`;
321
+ command = 'open';
322
+ args = [url];
318
323
  break;
319
324
  case 'win32':
320
- command = `start "" "${url}"`;
325
+ command = 'explorer.exe';
326
+ args = [url];
321
327
  break;
322
328
  default:
323
- command = `xdg-open "${url}"`;
329
+ command = 'xdg-open';
330
+ args = [url];
324
331
  break;
325
332
  }
326
- exec(command, (err) => {
327
- if (err) {
333
+ try {
334
+ const child = spawn(command, args, { stdio: 'ignore', detached: true });
335
+ child.on('error', (err) => {
328
336
  ui.stepError(`Failed to open browser: ${err.message}`);
329
- } else {
330
- ui.step(`Opening browser: ${colors.brightCyan}${url}${colors.reset}`);
331
- }
332
- });
337
+ });
338
+ child.unref();
339
+ ui.step(`Opening browser: ${colors.brightCyan}${url}${colors.reset}`);
340
+ } catch (err) {
341
+ ui.stepError(`Failed to open browser: ${err.message}`);
342
+ }
333
343
  }
334
344
 
335
345
  function extractRepoName(url) {
@@ -342,9 +352,104 @@ function extractRepoName(url) {
342
352
  }
343
353
  }
344
354
 
345
- async function cmdLogin(token, serverArg) {
355
+ function loginViaBrowser(server) {
356
+ return new Promise((resolve, reject) => {
357
+ const state = crypto.randomBytes(16).toString('hex');
358
+
359
+ const localServer = http.createServer((req, res) => {
360
+ try {
361
+ const parsed = new URL(req.url, 'http://127.0.0.1');
362
+ if (parsed.pathname === '/callback') {
363
+ const returnedToken = parsed.searchParams.get('token');
364
+ const returnedState = parsed.searchParams.get('state');
365
+
366
+ if (!returnedToken || returnedState !== state) {
367
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
368
+ res.end('<h1>Authentication Failed</h1><p>Invalid state or token parameter received.</p>');
369
+ return;
370
+ }
371
+
372
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
373
+ res.end(`
374
+ <!DOCTYPE html>
375
+ <html>
376
+ <head>
377
+ <meta charset="utf-8">
378
+ <title>RushDeploy CLI - Authenticated</title>
379
+ <style>
380
+ 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; }
381
+ .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); }
382
+ .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; }
383
+ h1 { color: #fafafa; font-size: 20px; font-weight: 600; margin: 0 0 8px 0; }
384
+ p { color: #a1a1aa; font-size: 13px; line-height: 1.5; margin: 0; }
385
+ </style>
386
+ </head>
387
+ <body>
388
+ <div class="card">
389
+ <div class="icon">&#10003;</div>
390
+ <h1>Authentication Successful!</h1>
391
+ <p>Your CLI terminal has been successfully connected to RushDeploy. You can close this tab and return to your terminal.</p>
392
+ </div>
393
+ </body>
394
+ </html>
395
+ `);
396
+
397
+ if (timer) clearTimeout(timer);
398
+ localServer.close();
399
+ resolve(returnedToken);
400
+ } else {
401
+ res.writeHead(404);
402
+ res.end();
403
+ }
404
+ } catch (err) {
405
+ res.writeHead(500);
406
+ res.end('Internal server error');
407
+ }
408
+ });
409
+
410
+ let timer;
411
+
412
+ localServer.listen(0, '127.0.0.1', () => {
413
+ const address = localServer.address();
414
+ const port = address.port;
415
+ const callbackUrl = `http://127.0.0.1:${port}/callback`;
416
+ const authUrl = `${server}/auth/cli-auth?callback=${encodeURIComponent(callbackUrl)}&state=${state}&client=cli`;
417
+
418
+ ui.stepInfo(`Opening your browser to authenticate with RushDeploy...`);
419
+ console.log(` Waiting for authorization at:\n ${colors.brightCyan}${authUrl}${colors.reset}\n`);
420
+
421
+ openBrowser(authUrl);
422
+ });
423
+
424
+ // 2-minute timeout
425
+ timer = setTimeout(() => {
426
+ try {
427
+ localServer.close();
428
+ } catch {}
429
+ reject(new Error('Authentication timed out after 2 minutes'));
430
+ }, 120000);
431
+ if (timer.unref) {
432
+ timer.unref();
433
+ }
434
+ });
435
+ }
436
+
437
+ async function cmdLogin(tokenArg, serverArg) {
346
438
  const server = serverArg || getServerUrl();
347
- ui.stepInfo(`Connecting to ${colors.brightWhite}${server}${colors.reset}...`);
439
+ let token = tokenArg;
440
+
441
+ if (!token) {
442
+ ui.stepInfo(`Connecting to ${colors.brightWhite}${server}${colors.reset}...`);
443
+ try {
444
+ token = await loginViaBrowser(server);
445
+ } catch (err) {
446
+ ui.stepError(`Browser login failed: ${err.message}`);
447
+ console.log(` You can also log in manually using: ${colors.green}rushdeploy login <token>${colors.reset}`);
448
+ process.exit(1);
449
+ }
450
+ } else {
451
+ ui.stepInfo(`Connecting to ${colors.brightWhite}${server}${colors.reset}...`);
452
+ }
348
453
 
349
454
  saveConfig(server, token);
350
455
 
@@ -364,7 +469,7 @@ async function cmdLogin(token, serverArg) {
364
469
  }
365
470
  } catch (err) {
366
471
  if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
367
- ui.stepError(`Authentication failed. Check your token or server URL.`);
472
+ ui.stepError(`Authentication failed: ${err.message || 'Check your token or server URL.'}`);
368
473
  process.exit(1);
369
474
  }
370
475
  }
@@ -1193,13 +1298,13 @@ async function cmdWorkspace() {
1193
1298
 
1194
1299
  function printHelp() {
1195
1300
  console.log(`
1196
- ${colors.bold}${colors.brightWhite}RushDeploy CLI${colors.reset} ${colors.gray}v1.0.8${colors.reset} - Modern PaaS Command Line Interface
1301
+ ${colors.bold}${colors.brightWhite}RushDeploy CLI${colors.reset} ${colors.gray}v1.1.0${colors.reset} - Modern PaaS Command Line Interface
1197
1302
 
1198
1303
  ${colors.bold}USAGE:${colors.reset}
1199
1304
  ${colors.brightCyan}rushdeploy${colors.reset} <command> [arguments] [options]
1200
1305
 
1201
1306
  ${colors.bold}GLOBAL COMMANDS:${colors.reset}
1202
- ${colors.green}login <token>${colors.reset} Authenticate with your access token
1307
+ ${colors.green}login [token]${colors.reset} Authenticate with browser (or provide access token)
1203
1308
  ${colors.green}logout${colors.reset} Clear your stored CLI session token
1204
1309
  ${colors.green}whoami${colors.reset} Display active profile details
1205
1310
  ${colors.green}plans${colors.reset} List subscription plans and limits
@@ -1249,20 +1354,25 @@ async function main() {
1249
1354
  process.exit(0);
1250
1355
  }
1251
1356
 
1357
+ if (command === '--version' || command === '-v' || command === 'version') {
1358
+ console.log('1.1.0');
1359
+ process.exit(0);
1360
+ }
1361
+
1252
1362
  switch (command) {
1253
1363
  case 'login': {
1254
- const token = args[1];
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
-
1364
+ let token = null;
1261
1365
  let server = null;
1366
+
1262
1367
  const serverIndex = args.indexOf('--server');
1263
1368
  if (serverIndex !== -1 && args[serverIndex + 1]) {
1264
1369
  server = args[serverIndex + 1];
1265
1370
  }
1371
+
1372
+ // Check if user passed a token (first argument after 'login' that isn't --server or a flag)
1373
+ if (args[1] && !args[1].startsWith('--')) {
1374
+ token = args[1];
1375
+ }
1266
1376
 
1267
1377
  await cmdLogin(token, server);
1268
1378
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rushdeploy",
3
- "version": "1.0.8",
3
+ "version": "1.2.0",
4
4
  "description": "CLI tool to manage your self-hosted RushDeploy PaaS account.",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
@@ -21,4 +21,4 @@
21
21
  "bin",
22
22
  "README.md"
23
23
  ]
24
- }
24
+ }