api-response-manager 2.5.2 → 2.5.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/README.md +1 -1
- package/bin/arm.js +7 -0
- package/commands/account.js +23 -11
- package/commands/login.js +25 -0
- package/commands/tunnel.js +86 -15
- package/package.json +1 -1
- package/utils/api.js +5 -0
package/README.md
CHANGED
package/bin/arm.js
CHANGED
|
@@ -38,6 +38,7 @@ program
|
|
|
38
38
|
.option('-e, --email <email>', 'Email address')
|
|
39
39
|
.option('-p, --password <password>', 'Password')
|
|
40
40
|
.option('--provider <provider>', 'OAuth provider (google, github, microsoft)')
|
|
41
|
+
.option('-t, --token <token>', 'API token for CI/CD automation')
|
|
41
42
|
.action(loginCommand);
|
|
42
43
|
|
|
43
44
|
// Logout command
|
|
@@ -58,6 +59,7 @@ program
|
|
|
58
59
|
.option('-p, --protocol <protocol>', 'Protocol (http, https, tcp, ws, wss)', 'http')
|
|
59
60
|
.option('--ssl', 'Enable SSL/HTTPS')
|
|
60
61
|
.option('-d, --domain <domain>', 'Custom domain')
|
|
62
|
+
.option('--json', 'Output in JSON format (for CI/CD automation)')
|
|
61
63
|
.action(tunnelCommand.start);
|
|
62
64
|
|
|
63
65
|
program
|
|
@@ -71,6 +73,11 @@ program
|
|
|
71
73
|
.argument('<tunnelId>', 'Tunnel ID to stop')
|
|
72
74
|
.action(tunnelCommand.stop);
|
|
73
75
|
|
|
76
|
+
program
|
|
77
|
+
.command('tunnel:stop-all')
|
|
78
|
+
.description('Stop all active tunnels')
|
|
79
|
+
.action(tunnelCommand.stopAll);
|
|
80
|
+
|
|
74
81
|
program
|
|
75
82
|
.command('tunnel:logs')
|
|
76
83
|
.description('View tunnel request logs')
|
package/commands/account.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const ora = require('ora');
|
|
3
3
|
const Table = require('cli-table3');
|
|
4
|
-
const
|
|
4
|
+
const api = require('../utils/api');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Get current user info
|
|
@@ -10,10 +10,14 @@ async function info() {
|
|
|
10
10
|
const spinner = ora('Fetching account info...').start();
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
-
const
|
|
14
|
-
|
|
13
|
+
const token = api.getToken();
|
|
14
|
+
if (!token) {
|
|
15
|
+
spinner.fail('Not authenticated');
|
|
16
|
+
console.error(chalk.red('Please run: arm login'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
15
19
|
|
|
16
|
-
const response = await api.get('/auth/me');
|
|
20
|
+
const response = await api.client.get('/auth/me');
|
|
17
21
|
const user = response.data.user;
|
|
18
22
|
|
|
19
23
|
spinner.stop();
|
|
@@ -52,10 +56,14 @@ async function features() {
|
|
|
52
56
|
const spinner = ora('Fetching feature access...').start();
|
|
53
57
|
|
|
54
58
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
59
|
+
const token = api.getToken();
|
|
60
|
+
if (!token) {
|
|
61
|
+
spinner.fail('Not authenticated');
|
|
62
|
+
console.error(chalk.red('Please run: arm login'));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
57
65
|
|
|
58
|
-
const response = await api.get('/auth/features');
|
|
66
|
+
const response = await api.client.get('/auth/features');
|
|
59
67
|
const { tier, features } = response.data;
|
|
60
68
|
|
|
61
69
|
spinner.stop();
|
|
@@ -102,12 +110,16 @@ async function usage() {
|
|
|
102
110
|
const spinner = ora('Fetching usage stats...').start();
|
|
103
111
|
|
|
104
112
|
try {
|
|
105
|
-
const
|
|
106
|
-
|
|
113
|
+
const token = api.getToken();
|
|
114
|
+
if (!token) {
|
|
115
|
+
spinner.fail('Not authenticated');
|
|
116
|
+
console.error(chalk.red('Please run: arm login'));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
107
119
|
|
|
108
120
|
const [userRes, featuresRes] = await Promise.all([
|
|
109
|
-
api.get('/auth/me'),
|
|
110
|
-
api.get('/auth/features')
|
|
121
|
+
api.client.get('/auth/me'),
|
|
122
|
+
api.client.get('/auth/features')
|
|
111
123
|
]);
|
|
112
124
|
|
|
113
125
|
const user = userRes.data.user;
|
package/commands/login.js
CHANGED
|
@@ -6,6 +6,31 @@ const api = require('../utils/api');
|
|
|
6
6
|
const config = require('../utils/config');
|
|
7
7
|
|
|
8
8
|
async function login(options) {
|
|
9
|
+
// Token-based login for CI/CD automation
|
|
10
|
+
if (options.token) {
|
|
11
|
+
const spinner = ora('Authenticating with token...').start();
|
|
12
|
+
try {
|
|
13
|
+
// Validate token by making a test API call
|
|
14
|
+
config.set('token', options.token);
|
|
15
|
+
const response = await api.getUser();
|
|
16
|
+
|
|
17
|
+
if (response && response.user) {
|
|
18
|
+
config.set('userId', response.user._id || response.user.id);
|
|
19
|
+
config.set('email', response.user.email);
|
|
20
|
+
spinner.succeed(chalk.green('Authenticated successfully!'));
|
|
21
|
+
console.log(chalk.gray(`\nLogged in as: ${response.user.email}\n`));
|
|
22
|
+
} else {
|
|
23
|
+
throw new Error('Invalid token');
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
config.delete('token');
|
|
27
|
+
spinner.fail(chalk.red('Authentication failed'));
|
|
28
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
9
34
|
console.log(chalk.blue.bold('\n🔐 API Response Manager - Login\n'));
|
|
10
35
|
|
|
11
36
|
// Check if social login is requested
|
package/commands/tunnel.js
CHANGED
|
@@ -8,14 +8,20 @@ const config = require('../utils/config');
|
|
|
8
8
|
|
|
9
9
|
// Start tunnel
|
|
10
10
|
async function start(port, options) {
|
|
11
|
-
|
|
11
|
+
const jsonOutput = options.json || false;
|
|
12
|
+
|
|
13
|
+
if (!jsonOutput) {
|
|
14
|
+
console.log(chalk.blue.bold('\n🚇 Starting Tunnel...\n'));
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
if (!port) {
|
|
14
18
|
port = config.get('defaultTunnelPort');
|
|
15
|
-
|
|
19
|
+
if (!jsonOutput) {
|
|
20
|
+
console.log(chalk.gray(`Using default port: ${port}`));
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
const spinner = ora('Creating tunnel...').start();
|
|
24
|
+
const spinner = jsonOutput ? null : ora('Creating tunnel...').start();
|
|
19
25
|
|
|
20
26
|
try {
|
|
21
27
|
const tunnelData = {
|
|
@@ -37,7 +43,22 @@ async function start(port, options) {
|
|
|
37
43
|
const response = await api.createTunnel(tunnelData);
|
|
38
44
|
const tunnel = response.tunnel;
|
|
39
45
|
|
|
40
|
-
spinner.succeed(chalk.green('Tunnel created successfully!'));
|
|
46
|
+
if (spinner) spinner.succeed(chalk.green('Tunnel created successfully!'));
|
|
47
|
+
|
|
48
|
+
// JSON output for automation/CI
|
|
49
|
+
if (jsonOutput) {
|
|
50
|
+
console.log(JSON.stringify({
|
|
51
|
+
success: true,
|
|
52
|
+
id: tunnel.id || tunnel._id,
|
|
53
|
+
subdomain: tunnel.subdomain,
|
|
54
|
+
publicUrl: tunnel.publicUrl,
|
|
55
|
+
localPort: tunnel.localPort,
|
|
56
|
+
protocol: tunnel.protocol || 'https'
|
|
57
|
+
}));
|
|
58
|
+
// For JSON mode, connect silently
|
|
59
|
+
await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort, true);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
41
62
|
|
|
42
63
|
// Safe string padding function
|
|
43
64
|
const safePadEnd = (str, length) => {
|
|
@@ -56,17 +77,30 @@ async function start(port, options) {
|
|
|
56
77
|
|
|
57
78
|
// Connect tunnel client
|
|
58
79
|
console.log(chalk.blue('Connecting tunnel client...\n'));
|
|
59
|
-
await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort);
|
|
80
|
+
await connectTunnelClient(tunnel.id || tunnel._id, tunnel.subdomain, tunnel.localPort, false);
|
|
60
81
|
|
|
61
82
|
} catch (error) {
|
|
62
|
-
spinner.fail(chalk.red('Failed to create tunnel'));
|
|
63
|
-
|
|
83
|
+
if (spinner) spinner.fail(chalk.red('Failed to create tunnel'));
|
|
84
|
+
|
|
85
|
+
const errorMsg = error.response?.data?.msg || error.message;
|
|
86
|
+
|
|
87
|
+
// Handle subdomain already taken error
|
|
88
|
+
if (error.response?.status === 409 && errorMsg.toLowerCase().includes('subdomain')) {
|
|
89
|
+
console.error(chalk.yellow(`\n⚠ ${errorMsg}`));
|
|
90
|
+
console.log(chalk.gray('\nOptions:'));
|
|
91
|
+
console.log(chalk.gray(' 1. Try again with a different subdomain: ') + chalk.cyan(`arm tunnel ${port} -s <new-subdomain>`));
|
|
92
|
+
console.log(chalk.gray(' 2. Let the system generate a unique subdomain: ') + chalk.cyan(`arm tunnel ${port}`));
|
|
93
|
+
console.log();
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.error(chalk.red(`\n✗ ${errorMsg}\n`));
|
|
64
98
|
process.exit(1);
|
|
65
99
|
}
|
|
66
100
|
}
|
|
67
101
|
|
|
68
102
|
// Connect tunnel client
|
|
69
|
-
async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
103
|
+
async function connectTunnelClient(tunnelId, subdomain, localPort, silent = false) {
|
|
70
104
|
const tunnelServerUrl = config.get('tunnelServerUrl') || 'ws://localhost:8080';
|
|
71
105
|
const token = api.getToken();
|
|
72
106
|
const userId = config.get('userId');
|
|
@@ -76,7 +110,7 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
|
76
110
|
let heartbeatInterval;
|
|
77
111
|
|
|
78
112
|
ws.on('open', () => {
|
|
79
|
-
console.log(chalk.green('✓ Connected to tunnel server'));
|
|
113
|
+
if (!silent) console.log(chalk.green('✓ Connected to tunnel server'));
|
|
80
114
|
|
|
81
115
|
ws.send(JSON.stringify({
|
|
82
116
|
type: 'register',
|
|
@@ -99,13 +133,17 @@ async function connectTunnelClient(tunnelId, subdomain, localPort) {
|
|
|
99
133
|
const message = JSON.parse(data.toString());
|
|
100
134
|
|
|
101
135
|
if (message.type === 'registered') {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
136
|
+
if (!silent) {
|
|
137
|
+
console.log(chalk.green.bold('\n🎉 Tunnel Active!\n'));
|
|
138
|
+
console.log(chalk.white('Your local server is now accessible at:'));
|
|
139
|
+
console.log(chalk.cyan.bold(` ${message.publicUrl}\n`));
|
|
140
|
+
console.log(chalk.gray('Press Ctrl+C to stop the tunnel\n'));
|
|
141
|
+
}
|
|
106
142
|
} else if (message.type === 'request') {
|
|
107
|
-
|
|
108
|
-
|
|
143
|
+
if (!silent) {
|
|
144
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
145
|
+
console.log(chalk.gray(`[${timestamp}]`), chalk.blue(message.method), chalk.white(message.path));
|
|
146
|
+
}
|
|
109
147
|
|
|
110
148
|
// Forward request to local server
|
|
111
149
|
try {
|
|
@@ -259,6 +297,38 @@ async function stop(tunnelId) {
|
|
|
259
297
|
}
|
|
260
298
|
}
|
|
261
299
|
|
|
300
|
+
// Stop all tunnels
|
|
301
|
+
async function stopAll() {
|
|
302
|
+
const spinner = ora('Stopping all tunnels...').start();
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const response = await api.getTunnels();
|
|
306
|
+
const tunnels = response.tunnels || [];
|
|
307
|
+
|
|
308
|
+
if (tunnels.length === 0) {
|
|
309
|
+
spinner.info(chalk.yellow('No active tunnels to stop'));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let stopped = 0;
|
|
314
|
+
for (const tunnel of tunnels) {
|
|
315
|
+
try {
|
|
316
|
+
await api.deleteTunnel(tunnel._id || tunnel.id);
|
|
317
|
+
stopped++;
|
|
318
|
+
} catch (e) {
|
|
319
|
+
// Continue stopping other tunnels
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
spinner.succeed(chalk.green(`Stopped ${stopped} tunnel(s)`));
|
|
324
|
+
console.log();
|
|
325
|
+
} catch (error) {
|
|
326
|
+
spinner.fail(chalk.red('Failed to stop tunnels'));
|
|
327
|
+
console.error(chalk.red(`\n✗ ${error.response?.data?.msg || error.message}\n`));
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
262
332
|
// View tunnel logs
|
|
263
333
|
async function logs(tunnelId, options) {
|
|
264
334
|
const spinner = ora('Fetching logs...').start();
|
|
@@ -437,6 +507,7 @@ module.exports = {
|
|
|
437
507
|
start,
|
|
438
508
|
list,
|
|
439
509
|
stop,
|
|
510
|
+
stopAll,
|
|
440
511
|
logs,
|
|
441
512
|
setDomain,
|
|
442
513
|
uploadSSL,
|
package/package.json
CHANGED
package/utils/api.js
CHANGED
|
@@ -59,6 +59,11 @@ class APIClient {
|
|
|
59
59
|
return response.data;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
async getUser() {
|
|
63
|
+
const response = await this.client.get('/auth/me');
|
|
64
|
+
return response.data;
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
// OAuth Device Flow for CLI
|
|
63
68
|
async requestDeviceCode(provider) {
|
|
64
69
|
const response = await this.client.post('/auth/device/code', { provider });
|