securenow 5.2.1 → 5.3.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/cli/apps.js +167 -0
- package/cli/auth.js +208 -0
- package/cli/client.js +113 -0
- package/cli/config.js +111 -0
- package/cli/init.js +100 -0
- package/cli/monitor.js +458 -0
- package/cli/security.js +630 -0
- package/cli/ui.js +312 -0
- package/cli.js +357 -235
- package/nextjs.js +48 -6
- package/package.json +3 -1
package/cli/ui.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
|
|
5
|
+
const NO_COLOR = !!process.env.NO_COLOR || !process.stdout.isTTY;
|
|
6
|
+
|
|
7
|
+
const codes = {
|
|
8
|
+
reset: '\x1b[0m',
|
|
9
|
+
bold: '\x1b[1m',
|
|
10
|
+
dim: '\x1b[2m',
|
|
11
|
+
italic: '\x1b[3m',
|
|
12
|
+
underline: '\x1b[4m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
magenta: '\x1b[35m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
white: '\x1b[37m',
|
|
20
|
+
gray: '\x1b[90m',
|
|
21
|
+
bgRed: '\x1b[41m',
|
|
22
|
+
bgGreen: '\x1b[42m',
|
|
23
|
+
bgYellow: '\x1b[43m',
|
|
24
|
+
bgBlue: '\x1b[44m',
|
|
25
|
+
bgCyan: '\x1b[46m',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function style(code, text) {
|
|
29
|
+
if (NO_COLOR) return text;
|
|
30
|
+
return `${code}${text}${codes.reset}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const c = {
|
|
34
|
+
bold: (s) => style(codes.bold, s),
|
|
35
|
+
dim: (s) => style(codes.dim, s),
|
|
36
|
+
italic: (s) => style(codes.italic, s),
|
|
37
|
+
underline: (s) => style(codes.underline, s),
|
|
38
|
+
red: (s) => style(codes.red, s),
|
|
39
|
+
green: (s) => style(codes.green, s),
|
|
40
|
+
yellow: (s) => style(codes.yellow, s),
|
|
41
|
+
blue: (s) => style(codes.blue, s),
|
|
42
|
+
magenta: (s) => style(codes.magenta, s),
|
|
43
|
+
cyan: (s) => style(codes.cyan, s),
|
|
44
|
+
white: (s) => style(codes.white, s),
|
|
45
|
+
gray: (s) => style(codes.gray, s),
|
|
46
|
+
success: (s) => style(codes.green, s),
|
|
47
|
+
error: (s) => style(codes.red, s),
|
|
48
|
+
warn: (s) => style(codes.yellow, s),
|
|
49
|
+
info: (s) => style(codes.cyan, s),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function stripAnsi(str) {
|
|
53
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function visibleLength(str) {
|
|
57
|
+
return stripAnsi(str).length;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function padEnd(str, len) {
|
|
61
|
+
const visible = visibleLength(str);
|
|
62
|
+
if (visible >= len) return str;
|
|
63
|
+
return str + ' '.repeat(len - visible);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function table(headers, rows, opts = {}) {
|
|
67
|
+
if (!rows.length) {
|
|
68
|
+
console.log(c.dim(' No results found.'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const maxWidth = process.stdout.columns || 120;
|
|
73
|
+
const colWidths = headers.map((h, i) => {
|
|
74
|
+
const headerLen = visibleLength(String(h));
|
|
75
|
+
const maxCell = rows.reduce((max, row) => {
|
|
76
|
+
const cell = String(row[i] ?? '');
|
|
77
|
+
return Math.max(max, visibleLength(cell));
|
|
78
|
+
}, 0);
|
|
79
|
+
return Math.min(Math.max(headerLen, maxCell), opts.maxColWidth || 60);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const totalWidth = colWidths.reduce((a, b) => a + b, 0) + (colWidths.length - 1) * 3;
|
|
83
|
+
if (totalWidth > maxWidth && colWidths.length > 1) {
|
|
84
|
+
const lastIdx = colWidths.length - 1;
|
|
85
|
+
const excess = totalWidth - maxWidth;
|
|
86
|
+
colWidths[lastIdx] = Math.max(10, colWidths[lastIdx] - excess);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const headerLine = headers.map((h, i) => padEnd(c.bold(String(h)), colWidths[i])).join(' ');
|
|
90
|
+
const separator = colWidths.map(w => c.dim('─'.repeat(w))).join('───');
|
|
91
|
+
|
|
92
|
+
console.log(` ${headerLine}`);
|
|
93
|
+
console.log(` ${separator}`);
|
|
94
|
+
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
const line = row.map((cell, i) => {
|
|
97
|
+
let s = String(cell ?? '');
|
|
98
|
+
if (visibleLength(s) > colWidths[i]) {
|
|
99
|
+
s = stripAnsi(s).slice(0, colWidths[i] - 1) + '…';
|
|
100
|
+
}
|
|
101
|
+
return padEnd(s, colWidths[i]);
|
|
102
|
+
}).join(' ');
|
|
103
|
+
console.log(` ${line}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function keyValue(pairs) {
|
|
108
|
+
const maxKey = pairs.reduce((max, [k]) => Math.max(max, k.length), 0);
|
|
109
|
+
for (const [key, value] of pairs) {
|
|
110
|
+
console.log(` ${c.bold(key.padEnd(maxKey))} ${value ?? c.dim('—')}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function heading(text) {
|
|
115
|
+
console.log(`\n${c.bold(c.cyan(text))}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function subheading(text) {
|
|
119
|
+
console.log(`\n ${c.bold(text)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function success(msg) {
|
|
123
|
+
console.log(`${c.green('✓')} ${msg}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function error(msg) {
|
|
127
|
+
console.error(`${c.red('✗')} ${msg}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function warn(msg) {
|
|
131
|
+
console.log(`${c.yellow('!')} ${msg}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function info(msg) {
|
|
135
|
+
console.log(`${c.cyan('ℹ')} ${msg}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function spinner(message) {
|
|
139
|
+
if (!process.stderr.isTTY) {
|
|
140
|
+
process.stderr.write(` ${message}...\n`);
|
|
141
|
+
return {
|
|
142
|
+
update() {},
|
|
143
|
+
stop(msg) { if (msg) process.stderr.write(` ${msg}\n`); },
|
|
144
|
+
fail(msg) { if (msg) process.stderr.write(` ${msg}\n`); },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
149
|
+
let i = 0;
|
|
150
|
+
let currentMsg = message;
|
|
151
|
+
const interval = setInterval(() => {
|
|
152
|
+
const frame = c.cyan(frames[i++ % frames.length]);
|
|
153
|
+
process.stderr.write(`\r ${frame} ${currentMsg}`);
|
|
154
|
+
}, 80);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
update(msg) { currentMsg = msg; },
|
|
158
|
+
stop(finalMsg) {
|
|
159
|
+
clearInterval(interval);
|
|
160
|
+
process.stderr.write(`\r ${c.green('✓')} ${finalMsg || currentMsg}\n`);
|
|
161
|
+
},
|
|
162
|
+
fail(finalMsg) {
|
|
163
|
+
clearInterval(interval);
|
|
164
|
+
process.stderr.write(`\r ${c.red('✗')} ${finalMsg || currentMsg}\n`);
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function prompt(question, opts = {}) {
|
|
170
|
+
const rl = readline.createInterface({
|
|
171
|
+
input: process.stdin,
|
|
172
|
+
output: process.stderr,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
const q = opts.secret ? question : ` ${c.cyan('?')} ${question} `;
|
|
177
|
+
rl.question(q, (answer) => {
|
|
178
|
+
rl.close();
|
|
179
|
+
resolve(answer.trim());
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (opts.secret) {
|
|
183
|
+
const onData = (char) => {
|
|
184
|
+
const code = char.toString();
|
|
185
|
+
if (code === '\n' || code === '\r' || code === '\u0004') return;
|
|
186
|
+
process.stderr.write('*');
|
|
187
|
+
};
|
|
188
|
+
process.stdin.on('data', onData);
|
|
189
|
+
rl.once('close', () => process.stdin.removeListener('data', onData));
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function confirm(question, defaultYes = false) {
|
|
195
|
+
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
196
|
+
const answer = await prompt(`${question} ${c.dim(`(${hint})`)}`);
|
|
197
|
+
if (!answer) return defaultYes;
|
|
198
|
+
return answer.toLowerCase().startsWith('y');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function select(question, choices) {
|
|
202
|
+
console.log(`\n ${c.cyan('?')} ${question}\n`);
|
|
203
|
+
choices.forEach((choice, i) => {
|
|
204
|
+
console.log(` ${c.bold(String(i + 1))} ${choice.label || choice}`);
|
|
205
|
+
});
|
|
206
|
+
console.log('');
|
|
207
|
+
const answer = await prompt('Enter number');
|
|
208
|
+
const idx = parseInt(answer, 10) - 1;
|
|
209
|
+
if (idx < 0 || idx >= choices.length) {
|
|
210
|
+
error('Invalid selection');
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
return choices[idx].value ?? choices[idx];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function json(data) {
|
|
217
|
+
console.log(JSON.stringify(data, null, 2));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function hr() {
|
|
221
|
+
const width = Math.min(process.stdout.columns || 80, 80);
|
|
222
|
+
console.log(c.dim('─'.repeat(width)));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function timeAgo(dateStr) {
|
|
226
|
+
if (!dateStr) return '—';
|
|
227
|
+
const diff = Date.now() - new Date(dateStr).getTime();
|
|
228
|
+
const seconds = Math.floor(diff / 1000);
|
|
229
|
+
if (seconds < 60) return 'just now';
|
|
230
|
+
const minutes = Math.floor(seconds / 60);
|
|
231
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
232
|
+
const hours = Math.floor(minutes / 60);
|
|
233
|
+
if (hours < 24) return `${hours}h ago`;
|
|
234
|
+
const days = Math.floor(hours / 24);
|
|
235
|
+
if (days < 30) return `${days}d ago`;
|
|
236
|
+
return new Date(dateStr).toLocaleDateString();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function truncate(str, len = 50) {
|
|
240
|
+
if (!str) return '';
|
|
241
|
+
str = String(str);
|
|
242
|
+
if (str.length <= len) return str;
|
|
243
|
+
return str.slice(0, len - 1) + '…';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function statusBadge(status) {
|
|
247
|
+
const map = {
|
|
248
|
+
active: c.green('● active'),
|
|
249
|
+
inactive: c.dim('○ inactive'),
|
|
250
|
+
healthy: c.green('● healthy'),
|
|
251
|
+
unhealthy: c.red('● unhealthy'),
|
|
252
|
+
warning: c.yellow('● warning'),
|
|
253
|
+
open: c.red('● open'),
|
|
254
|
+
resolved: c.green('● resolved'),
|
|
255
|
+
closed: c.dim('● closed'),
|
|
256
|
+
acknowledged: c.yellow('● ack'),
|
|
257
|
+
blocked: c.red('■ blocked'),
|
|
258
|
+
trusted: c.green('■ trusted'),
|
|
259
|
+
enabled: c.green('● enabled'),
|
|
260
|
+
disabled: c.dim('○ disabled'),
|
|
261
|
+
critical: c.red('▲ critical'),
|
|
262
|
+
high: c.red('▲ high'),
|
|
263
|
+
medium: c.yellow('▲ medium'),
|
|
264
|
+
low: c.blue('▲ low'),
|
|
265
|
+
info: c.cyan('▲ info'),
|
|
266
|
+
read: c.dim('○ read'),
|
|
267
|
+
unread: c.cyan('● unread'),
|
|
268
|
+
};
|
|
269
|
+
return map[(status || '').toLowerCase()] || status;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function httpStatusColor(code) {
|
|
273
|
+
code = parseInt(code, 10);
|
|
274
|
+
if (code < 300) return c.green(code);
|
|
275
|
+
if (code < 400) return c.cyan(code);
|
|
276
|
+
if (code < 500) return c.yellow(code);
|
|
277
|
+
return c.red(code);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function durationColor(ms) {
|
|
281
|
+
ms = parseFloat(ms);
|
|
282
|
+
if (isNaN(ms)) return '—';
|
|
283
|
+
if (ms < 100) return c.green(`${ms.toFixed(0)}ms`);
|
|
284
|
+
if (ms < 500) return c.yellow(`${ms.toFixed(0)}ms`);
|
|
285
|
+
if (ms < 1000) return c.red(`${ms.toFixed(0)}ms`);
|
|
286
|
+
return c.red(`${(ms / 1000).toFixed(2)}s`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = {
|
|
290
|
+
c,
|
|
291
|
+
NO_COLOR,
|
|
292
|
+
stripAnsi,
|
|
293
|
+
table,
|
|
294
|
+
keyValue,
|
|
295
|
+
heading,
|
|
296
|
+
subheading,
|
|
297
|
+
success,
|
|
298
|
+
error,
|
|
299
|
+
warn,
|
|
300
|
+
info,
|
|
301
|
+
spinner,
|
|
302
|
+
prompt,
|
|
303
|
+
confirm,
|
|
304
|
+
select,
|
|
305
|
+
json,
|
|
306
|
+
hr,
|
|
307
|
+
timeAgo,
|
|
308
|
+
truncate,
|
|
309
|
+
statusBadge,
|
|
310
|
+
httpStatusColor,
|
|
311
|
+
durationColor,
|
|
312
|
+
};
|