forged-cli 0.3.0 → 0.3.2
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/bin/forged.js +7 -0
- package/package.json +2 -2
- package/src/commands/gen.js +102 -0
- package/src/utils/trustedPublishers.js +28 -0
package/bin/forged.js
CHANGED
|
@@ -12,6 +12,7 @@ const [,, command, ...args] = process.argv;
|
|
|
12
12
|
const commands = {
|
|
13
13
|
readme: 'Generate a README.md for the current project',
|
|
14
14
|
scan: 'Audit dependencies for typosquats, bad scripts, and suspicious binaries',
|
|
15
|
+
gen: 'Generate passwords, secrets, PINs, and UUIDs',
|
|
15
16
|
init: 'Scaffold a new dev environment',
|
|
16
17
|
new: 'Create a new project with GitHub setup',
|
|
17
18
|
install: 'Install Forged into an existing shell config',
|
|
@@ -42,6 +43,12 @@ if (command === 'readme') {
|
|
|
42
43
|
process.exit(0);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
if (command === 'gen') {
|
|
47
|
+
const { genCommand } = await import('../src/commands/gen.js');
|
|
48
|
+
await genCommand(args);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
if (command === 'scan') {
|
|
46
53
|
const { scanCommand } = await import('../src/commands/scan.js');
|
|
47
54
|
const { resolve } = await import('path');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forged-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A complete dev environment toolkit — shell, workflows, and project scaffolding in one install.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"bin": {
|
|
25
|
-
"forged": "
|
|
25
|
+
"forged": "bin/forged.js"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=18"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from 'crypto';
|
|
2
|
+
|
|
3
|
+
const green = '\x1b[32m';
|
|
4
|
+
const yellow = '\x1b[33m';
|
|
5
|
+
const red = '\x1b[31m';
|
|
6
|
+
const blue = '\x1b[34m';
|
|
7
|
+
const bold = '\x1b[1m';
|
|
8
|
+
const reset = '\x1b[0m';
|
|
9
|
+
const dim = '\x1b[2m';
|
|
10
|
+
|
|
11
|
+
const UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
12
|
+
const LOWER = 'abcdefghijklmnopqrstuvwxyz';
|
|
13
|
+
const DIGITS = '0123456789';
|
|
14
|
+
const SYMBOLS = '!@#$%^&*()-_=+[]{}|;:,.<>?';
|
|
15
|
+
const SAFE = '-_';
|
|
16
|
+
|
|
17
|
+
function randomChar(charset) {
|
|
18
|
+
return charset[randomBytes(1)[0] % charset.length];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function generatePassword(length, safe = false) {
|
|
22
|
+
const symbols = safe ? SAFE : SYMBOLS;
|
|
23
|
+
const charset = UPPER + LOWER + DIGITS + symbols;
|
|
24
|
+
const required = [
|
|
25
|
+
randomChar(UPPER),
|
|
26
|
+
randomChar(LOWER),
|
|
27
|
+
randomChar(DIGITS),
|
|
28
|
+
randomChar(symbols),
|
|
29
|
+
];
|
|
30
|
+
const rest = Array.from({ length: length - required.length }, () => randomChar(charset));
|
|
31
|
+
const all = [...required, ...rest];
|
|
32
|
+
for (let i = all.length - 1; i > 0; i--) {
|
|
33
|
+
const j = randomBytes(1)[0] % (i + 1);
|
|
34
|
+
[all[i], all[j]] = [all[j], all[i]];
|
|
35
|
+
}
|
|
36
|
+
return all.join('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function colorize(password) {
|
|
40
|
+
return password.split('').map(char => {
|
|
41
|
+
if (/[0-9]/.test(char)) return `${yellow}${char}${reset}`;
|
|
42
|
+
if (/[^a-zA-Z0-9]/.test(char)) return `${red}${char}${reset}`;
|
|
43
|
+
return char;
|
|
44
|
+
}).join('');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function generatePin(length) {
|
|
48
|
+
return Array.from({ length }, () => randomBytes(1)[0] % 10).join('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function genCommand(args) {
|
|
52
|
+
const sub = args[0];
|
|
53
|
+
const safe = args.includes('--safe') || args.includes('--ascii');
|
|
54
|
+
const lenArg = args.find(a => /^(-l=|--length=)/.test(a));
|
|
55
|
+
const length = lenArg ? parseInt(lenArg.split('=')[1]) : null;
|
|
56
|
+
|
|
57
|
+
if (!sub || sub === 'help') {
|
|
58
|
+
console.log(`
|
|
59
|
+
${bold}forged gen${reset} — generate secure credentials
|
|
60
|
+
|
|
61
|
+
${dim}Usage:${reset}
|
|
62
|
+
forged gen pass Secure password ${dim}(default 20 chars)${reset}
|
|
63
|
+
forged gen pass --safe ASCII-only, no symbols — safe for routers
|
|
64
|
+
forged gen secret 32-byte hex — JWT secrets, API keys
|
|
65
|
+
forged gen pin 6-digit numeric PIN
|
|
66
|
+
forged gen uuid UUID v4
|
|
67
|
+
|
|
68
|
+
${dim}Options:${reset}
|
|
69
|
+
--length=N, -l=N Custom length (pass, pin)
|
|
70
|
+
--safe, --ascii Letters + numbers only
|
|
71
|
+
`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (sub === 'pass') {
|
|
76
|
+
const len = length ?? 20;
|
|
77
|
+
if (len < 4) { console.log(` ${red}Length must be at least 4${reset}`); return; }
|
|
78
|
+
const pass = generatePassword(len, safe);
|
|
79
|
+
console.log(`\n ${bold}Password${reset} ${colorize(pass)}`);
|
|
80
|
+
if (safe) console.log(` ${dim}ASCII-safe — router/device friendly${reset}`);
|
|
81
|
+
console.log();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (sub === 'secret') {
|
|
86
|
+
console.log(`\n ${bold}Secret${reset} ${green}${randomBytes(32).toString('hex')}${reset}\n`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (sub === 'pin') {
|
|
91
|
+
const len = length ?? 6;
|
|
92
|
+
console.log(`\n ${bold}PIN${reset} ${yellow}${generatePin(len)}${reset}\n`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (sub === 'uuid') {
|
|
97
|
+
console.log(`\n ${bold}UUID${reset} ${blue}${randomUUID()}${reset}\n`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(` ${red}Unknown:${reset} '${sub}' — run ${bold}forged gen help${reset}`);
|
|
102
|
+
}
|
|
@@ -15,6 +15,34 @@ export const TRUSTED_COMMUNITY = new Set([
|
|
|
15
15
|
'ulisesgascon', 'formidablelabs',
|
|
16
16
|
'blakeembrey', 'wesleytodd', 'danez', 'domenic', 'panva', 'joshuakgoldberg',
|
|
17
17
|
'matteo.collina', 'rvagg',
|
|
18
|
+
// React-Bootstrap / @restart team
|
|
19
|
+
'kytsang', 'monastic.panic',
|
|
20
|
+
// Chart.js core
|
|
21
|
+
'etimberg',
|
|
22
|
+
// commander contributor
|
|
23
|
+
'abetomo',
|
|
24
|
+
// isomorphic-fetch (Financial Times official org)
|
|
25
|
+
'financial-times',
|
|
26
|
+
// webpack core team
|
|
27
|
+
'evilebottnawi',
|
|
28
|
+
// mass transfer from shinnn — all legitimate
|
|
29
|
+
'stevemao',
|
|
30
|
+
// TC39 delegate, took over ESLint packages from nzakas
|
|
31
|
+
'michaelficarra',
|
|
32
|
+
// raf maintainer
|
|
33
|
+
'cmtegner',
|
|
34
|
+
// known React developer, react-text-mask
|
|
35
|
+
'browniefed',
|
|
36
|
+
// perfect-scrollbar current maintainer
|
|
37
|
+
'mattonit',
|
|
38
|
+
// Mapbox internal npm account rotation
|
|
39
|
+
'mapbox-npm-01', 'mapbox-npm-03',
|
|
40
|
+
// Prettier automated release bot (replaced fisker)
|
|
41
|
+
'prettier-bot',
|
|
42
|
+
// Sequelize current maintainer (replaced sdepold, original creator)
|
|
43
|
+
'wikirik',
|
|
44
|
+
// Meta/React Native core team (replaced zertosh on v8-compile-cache)
|
|
45
|
+
'yungsters',
|
|
18
46
|
]);
|
|
19
47
|
|
|
20
48
|
export function classifyPublisherChange(from, to) {
|