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.
- package/README.md +4 -3
- package/bin/index.js +145 -35
- 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
|
@@ -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
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
266
|
+
formattedError = `Request failed (${res.statusCode}): ${errorMsg}`;
|
|
267
267
|
}
|
|
268
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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 =
|
|
321
|
+
command = 'open';
|
|
322
|
+
args = [url];
|
|
318
323
|
break;
|
|
319
324
|
case 'win32':
|
|
320
|
-
command =
|
|
325
|
+
command = 'explorer.exe';
|
|
326
|
+
args = [url];
|
|
321
327
|
break;
|
|
322
328
|
default:
|
|
323
|
-
command =
|
|
329
|
+
command = 'xdg-open';
|
|
330
|
+
args = [url];
|
|
324
331
|
break;
|
|
325
332
|
}
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
}
|
|
330
|
-
|
|
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
|
-
|
|
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">✓</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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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