@chengyixu/envshow 1.0.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 (2) hide show
  1. package/envshow.js +224 -0
  2. package/package.json +31 -0
package/envshow.js ADDED
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { env, stdout, exit, stderr } = process;
4
+
5
+ const HELP = `envshow — pretty-print environment variables
6
+
7
+ Usage:
8
+ envshow [options]
9
+ echo "KEY=val" | envshow --stdin Merge stdin .env lines into output
10
+
11
+ Options:
12
+ --filter, -f <pattern> Only show keys matching pattern (case-insensitive substring)
13
+ --mask, -m Mask values: show first 4 + last 4 characters
14
+ --json, -j Output as JSON
15
+ --keys, -k Show only keys, no values
16
+ --no-sort, --sort -s Sort alphabetically by key (default: --sort)
17
+ --stdin, -i Read KEY=value lines from stdin and merge into env
18
+ --prefix, -p <prefix> Only show keys starting with this prefix
19
+ --help, -h Show this help
20
+ --version, -v Show version
21
+
22
+ Examples:
23
+ envshow All env vars, sorted
24
+ envshow -f NODE Only keys containing "NODE"
25
+ envshow -p AWS_ Only keys starting with "AWS_"
26
+ envshow -mj Masked values as JSON
27
+ envshow -k -f PATH Just keys matching PATH (handy for checking)
28
+ cat .env | envshow --stdin Merge .env file into current env and display
29
+ envshow --stdin < .env Same as above
30
+ envshow -f proxy -m Find proxy settings with masked values
31
+ `;
32
+
33
+ const VERSION = '1.0.0';
34
+
35
+ function parseArgs(argv) {
36
+ const flags = {
37
+ filter: null,
38
+ mask: false,
39
+ json: false,
40
+ keysOnly: false,
41
+ sort: true,
42
+ stdin: false,
43
+ prefix: null,
44
+ help: false,
45
+ version: false,
46
+ };
47
+
48
+ // Expand combined short flags: -mj -> -m -j
49
+ const expanded = [];
50
+ for (let i = 2; i < argv.length; i++) {
51
+ const arg = argv[i];
52
+ if (arg.length > 2 && arg.startsWith('-') && !arg.startsWith('--') && arg[1] !== 'f' && arg[1] !== 'p') {
53
+ // Split -mj into -m -j (but not -f or -p since they take values)
54
+ for (let j = 1; j < arg.length; j++) {
55
+ expanded.push('-' + arg[j]);
56
+ }
57
+ } else {
58
+ expanded.push(arg);
59
+ }
60
+ }
61
+
62
+ for (let i = 0; i < expanded.length; i++) {
63
+ const arg = expanded[i];
64
+ switch (arg) {
65
+ case '--filter': case '-f':
66
+ flags.filter = expanded[++i];
67
+ if (!flags.filter || flags.filter.startsWith('-')) { stderr.write('envshow: --filter requires a pattern\n'); exit(1); }
68
+ break;
69
+ case '--mask': case '-m':
70
+ flags.mask = true;
71
+ break;
72
+ case '--json': case '-j':
73
+ flags.json = true;
74
+ break;
75
+ case '--keys': case '-k':
76
+ flags.keysOnly = true;
77
+ break;
78
+ case '--sort': case '-s':
79
+ flags.sort = true;
80
+ break;
81
+ case '--no-sort':
82
+ flags.sort = false;
83
+ break;
84
+ case '--stdin': case '-i':
85
+ flags.stdin = true;
86
+ break;
87
+ case '--prefix': case '-p':
88
+ flags.prefix = expanded[++i];
89
+ if (!flags.prefix || flags.prefix.startsWith('-')) { stderr.write('envshow: --prefix requires a value\n'); exit(1); }
90
+ break;
91
+ case '--help': case '-h':
92
+ flags.help = true;
93
+ break;
94
+ case '--version': case '-v':
95
+ flags.version = true;
96
+ break;
97
+ default:
98
+ if (arg.startsWith('-')) {
99
+ stderr.write(`envshow: unknown option '${arg}'\n`);
100
+ exit(1);
101
+ }
102
+ }
103
+ }
104
+
105
+ return flags;
106
+ }
107
+
108
+ function maskValue(val) {
109
+ if (val.length <= 8) return '*'.repeat(val.length);
110
+ return val.slice(0, 4) + '*'.repeat(Math.max(val.length - 8, 1)) + val.slice(-4);
111
+ }
112
+
113
+ function readStdin() {
114
+ return new Promise((resolve) => {
115
+ if (process.stdin.isTTY) return resolve('');
116
+ let data = '';
117
+ process.stdin.setEncoding('utf8');
118
+ process.stdin.on('data', (chunk) => { data += chunk; });
119
+ process.stdin.on('end', () => resolve(data));
120
+ // If nothing comes in after 100ms, resolve
121
+ setTimeout(() => resolve(data), 100);
122
+ });
123
+ }
124
+
125
+ function parseEnvLines(input) {
126
+ const result = {};
127
+ for (const line of input.split('\n')) {
128
+ const trimmed = line.trim();
129
+ if (!trimmed || trimmed.startsWith('#')) continue;
130
+ const eqIdx = trimmed.indexOf('=');
131
+ if (eqIdx === -1) continue;
132
+ const key = trimmed.slice(0, eqIdx).trim();
133
+ let val = trimmed.slice(eqIdx + 1).trim();
134
+ // Strip surrounding quotes
135
+ if ((val.startsWith('"') && val.endsWith('"')) ||
136
+ (val.startsWith("'") && val.endsWith("'"))) {
137
+ val = val.slice(1, -1);
138
+ }
139
+ if (key) result[key] = val;
140
+ }
141
+ return result;
142
+ }
143
+
144
+ async function main() {
145
+ const flags = parseArgs(process.argv);
146
+
147
+ if (flags.help) {
148
+ stdout.write(HELP);
149
+ exit(0);
150
+ }
151
+
152
+ if (flags.version) {
153
+ stdout.write(`envshow v${VERSION}\n`);
154
+ exit(0);
155
+ }
156
+
157
+ let entries = Object.entries(env);
158
+
159
+ // Merge stdin if requested
160
+ if (flags.stdin) {
161
+ const stdinData = await readStdin();
162
+ if (stdinData) {
163
+ const parsed = parseEnvLines(stdinData);
164
+ const merged = { ...env, ...parsed };
165
+ entries = Object.entries(merged);
166
+ }
167
+ }
168
+
169
+ // Filter by prefix
170
+ if (flags.prefix) {
171
+ const pfx = flags.prefix.toUpperCase();
172
+ entries = entries.filter(([k]) => k.toUpperCase().startsWith(pfx));
173
+ }
174
+
175
+ // Filter by substring
176
+ if (flags.filter) {
177
+ const f = flags.filter.toUpperCase();
178
+ entries = entries.filter(([k]) => k.toUpperCase().includes(f));
179
+ }
180
+
181
+ // Sort
182
+ if (flags.sort) {
183
+ entries.sort(([a], [b]) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
184
+ }
185
+
186
+ // JSON output
187
+ if (flags.json) {
188
+ const obj = {};
189
+ for (const [k, v] of entries) {
190
+ obj[k] = flags.keysOnly ? '' : (flags.mask ? maskValue(v) : v);
191
+ }
192
+ stdout.write(JSON.stringify(obj, null, 2) + '\n');
193
+ return;
194
+ }
195
+
196
+ // Find max key length for alignment
197
+ const maxKeyLen = entries.length > 0
198
+ ? Math.max(...entries.map(([k]) => k.length))
199
+ : 0;
200
+
201
+ // Terminal color support (basic check)
202
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
203
+
204
+ const CYAN = useColor ? '\x1b[36m' : '';
205
+ const YELLOW = useColor ? '\x1b[33m' : '';
206
+ const DIM = useColor ? '\x1b[2m' : '';
207
+ const RESET = useColor ? '\x1b[0m' : '';
208
+
209
+ for (const [key, val] of entries) {
210
+ if (flags.keysOnly) {
211
+ stdout.write(`${CYAN}${key}${RESET}\n`);
212
+ } else {
213
+ const displayVal = flags.mask ? maskValue(val) : val;
214
+ const valColor = flags.mask ? YELLOW : '';
215
+ const pad = ' '.repeat(Math.max(maxKeyLen - key.length, 0));
216
+ stdout.write(`${CYAN}${key}${RESET}${pad} ${DIM}=${RESET} ${valColor}${displayVal}${RESET}\n`);
217
+ }
218
+ }
219
+ }
220
+
221
+ main().catch((err) => {
222
+ stderr.write(`envshow: ${err.message}\n`);
223
+ exit(1);
224
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@chengyixu/envshow",
3
+ "version": "1.0.0",
4
+ "description": "Pretty-print environment variables with sorting, filtering, masking, and JSON output. Zero dependencies.",
5
+ "main": "envshow.js",
6
+ "bin": {
7
+ "envshow": "envshow.js"
8
+ },
9
+ "files": [
10
+ "envshow.js"
11
+ ],
12
+ "keywords": [
13
+ "env",
14
+ "environment",
15
+ "variables",
16
+ "pretty-print",
17
+ "cli",
18
+ "dotenv",
19
+ "debug",
20
+ "devtools"
21
+ ],
22
+ "author": "chengyixu",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/chengyixu/envshow.git"
27
+ },
28
+ "engines": {
29
+ "node": ">=14"
30
+ }
31
+ }