envprobe 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.
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/bin/envcheck.js +68 -0
- package/package.json +49 -0
- package/src/analyzer.js +179 -0
- package/src/autocomplete.js +135 -0
- package/src/cache.js +114 -0
- package/src/cli.js +606 -0
- package/src/config.js +118 -0
- package/src/formatters/github.js +164 -0
- package/src/formatters/json.js +114 -0
- package/src/formatters/table.js +92 -0
- package/src/formatters/text.js +198 -0
- package/src/ignore.js +313 -0
- package/src/parser.js +119 -0
- package/src/plugins.js +138 -0
- package/src/progress.js +181 -0
- package/src/repl.js +416 -0
- package/src/scanner.js +182 -0
- package/src/scanners/go.js +89 -0
- package/src/scanners/javascript.js +93 -0
- package/src/scanners/python.js +97 -0
- package/src/scanners/ruby.js +90 -0
- package/src/scanners/rust.js +103 -0
- package/src/scanners/shell.js +125 -0
- package/src/security.js +411 -0
- package/src/suggestions.js +154 -0
- package/src/utils.js +57 -0
- package/src/watch.js +131 -0
package/src/progress.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress indicators and spinners for CLI operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simple spinner for long-running operations
|
|
7
|
+
*/
|
|
8
|
+
export class Spinner {
|
|
9
|
+
constructor(message = 'Processing...') {
|
|
10
|
+
this.message = message;
|
|
11
|
+
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
12
|
+
this.currentFrame = 0;
|
|
13
|
+
this.interval = null;
|
|
14
|
+
this.isSpinning = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
start() {
|
|
18
|
+
if (this.isSpinning) return;
|
|
19
|
+
|
|
20
|
+
this.isSpinning = true;
|
|
21
|
+
this.currentFrame = 0;
|
|
22
|
+
|
|
23
|
+
// Hide cursor
|
|
24
|
+
process.stdout.write('\x1B[?25l');
|
|
25
|
+
|
|
26
|
+
this.interval = setInterval(() => {
|
|
27
|
+
const frame = this.frames[this.currentFrame];
|
|
28
|
+
process.stdout.write(`\r${frame} ${this.message}`);
|
|
29
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
30
|
+
}, 80);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
update(message) {
|
|
34
|
+
this.message = message;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
succeed(message) {
|
|
38
|
+
this.stop();
|
|
39
|
+
process.stdout.write(`\r✓ ${message || this.message}\n`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fail(message) {
|
|
43
|
+
this.stop();
|
|
44
|
+
process.stdout.write(`\r✗ ${message || this.message}\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
warn(message) {
|
|
48
|
+
this.stop();
|
|
49
|
+
process.stdout.write(`\r⚠ ${message || this.message}\n`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
info(message) {
|
|
53
|
+
this.stop();
|
|
54
|
+
process.stdout.write(`\rℹ ${message || this.message}\n`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
stop() {
|
|
58
|
+
if (!this.isSpinning) return;
|
|
59
|
+
|
|
60
|
+
this.isSpinning = false;
|
|
61
|
+
|
|
62
|
+
if (this.interval) {
|
|
63
|
+
clearInterval(this.interval);
|
|
64
|
+
this.interval = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Clear line and show cursor
|
|
68
|
+
process.stdout.write('\r\x1B[K');
|
|
69
|
+
process.stdout.write('\x1B[?25h');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Progress bar for file scanning
|
|
75
|
+
*/
|
|
76
|
+
export class ProgressBar {
|
|
77
|
+
constructor(total, message = 'Progress') {
|
|
78
|
+
this.total = total;
|
|
79
|
+
this.current = 0;
|
|
80
|
+
this.message = message;
|
|
81
|
+
this.width = 40;
|
|
82
|
+
this.startTime = Date.now();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
update(current, message) {
|
|
86
|
+
this.current = current;
|
|
87
|
+
if (message) this.message = message;
|
|
88
|
+
this.render();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
increment(message) {
|
|
92
|
+
this.current++;
|
|
93
|
+
if (message) this.message = message;
|
|
94
|
+
this.render();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
render() {
|
|
98
|
+
const percentage = Math.min(100, Math.floor((this.current / this.total) * 100));
|
|
99
|
+
const filled = Math.floor((this.current / this.total) * this.width);
|
|
100
|
+
const empty = this.width - filled;
|
|
101
|
+
|
|
102
|
+
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
103
|
+
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
|
|
104
|
+
const rate = this.current / elapsed || 0;
|
|
105
|
+
const eta = this.current > 0 ? Math.floor((this.total - this.current) / rate) : 0;
|
|
106
|
+
|
|
107
|
+
process.stdout.write(
|
|
108
|
+
`\r${this.message}: [${bar}] ${percentage}% (${this.current}/${this.total}) ETA: ${eta}s`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
complete(message) {
|
|
113
|
+
this.current = this.total;
|
|
114
|
+
this.render();
|
|
115
|
+
process.stdout.write(`\n✓ ${message || 'Complete'}\n`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Multi-line progress display for concurrent operations
|
|
121
|
+
*/
|
|
122
|
+
export class MultiProgress {
|
|
123
|
+
constructor() {
|
|
124
|
+
this.tasks = new Map();
|
|
125
|
+
this.lineCount = 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
addTask(id, message) {
|
|
129
|
+
this.tasks.set(id, {
|
|
130
|
+
message,
|
|
131
|
+
status: 'pending',
|
|
132
|
+
spinner: 0,
|
|
133
|
+
});
|
|
134
|
+
this.render();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
updateTask(id, status, message) {
|
|
138
|
+
const task = this.tasks.get(id);
|
|
139
|
+
if (task) {
|
|
140
|
+
task.status = status;
|
|
141
|
+
if (message) task.message = message;
|
|
142
|
+
this.render();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
render() {
|
|
147
|
+
// Move cursor up to overwrite previous output
|
|
148
|
+
if (this.lineCount > 0) {
|
|
149
|
+
process.stdout.write(`\x1B[${this.lineCount}A`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const lines = [];
|
|
153
|
+
for (const [id, task] of this.tasks) {
|
|
154
|
+
const icon = this.getStatusIcon(task.status);
|
|
155
|
+
lines.push(`${icon} ${task.message}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.lineCount = lines.length;
|
|
159
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getStatusIcon(status) {
|
|
163
|
+
const icons = {
|
|
164
|
+
pending: '⋯',
|
|
165
|
+
running: '⠿',
|
|
166
|
+
success: '✓',
|
|
167
|
+
error: '✗',
|
|
168
|
+
warning: '⚠',
|
|
169
|
+
};
|
|
170
|
+
return icons[status] || '•';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
clear() {
|
|
174
|
+
if (this.lineCount > 0) {
|
|
175
|
+
process.stdout.write(`\x1B[${this.lineCount}A`);
|
|
176
|
+
process.stdout.write('\x1B[J');
|
|
177
|
+
}
|
|
178
|
+
this.tasks.clear();
|
|
179
|
+
this.lineCount = 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
package/src/repl.js
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
import { stdin as input, stdout as output } from 'process';
|
|
3
|
+
import { dirname } from 'path';
|
|
4
|
+
import { parseArguments, run } from './cli.js';
|
|
5
|
+
import { setupAutocomplete } from './autocomplete.js';
|
|
6
|
+
import { saveConfig, loadConfig } from './config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* REPL (Read-Eval-Print Loop) for interactive envcheck sessions
|
|
10
|
+
* Provides an interactive shell for running envcheck commands
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Session state management
|
|
15
|
+
*/
|
|
16
|
+
export class Session {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.history = [];
|
|
19
|
+
this.results = [];
|
|
20
|
+
this.config = {
|
|
21
|
+
path: '.',
|
|
22
|
+
envFile: '.env.example',
|
|
23
|
+
format: 'text',
|
|
24
|
+
failOn: 'none',
|
|
25
|
+
ignore: [],
|
|
26
|
+
noColor: false,
|
|
27
|
+
quiet: false,
|
|
28
|
+
};
|
|
29
|
+
this.startTime = Date.now();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
addCommand(command) {
|
|
33
|
+
this.history.push({
|
|
34
|
+
command,
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
addResult(result) {
|
|
40
|
+
this.results.push({
|
|
41
|
+
result,
|
|
42
|
+
timestamp: Date.now(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getConfig() {
|
|
47
|
+
return { ...this.config };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setConfig(key, value) {
|
|
51
|
+
if (key in this.config) {
|
|
52
|
+
this.config[key] = value;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getHistory() {
|
|
59
|
+
return [...this.history];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getResults() {
|
|
63
|
+
return [...this.results];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getDuration() {
|
|
67
|
+
return Date.now() - this.startTime;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
clear() {
|
|
71
|
+
this.history = [];
|
|
72
|
+
this.results = [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Command parser for REPL special commands
|
|
78
|
+
*/
|
|
79
|
+
export class CommandParser {
|
|
80
|
+
constructor(session) {
|
|
81
|
+
this.session = session;
|
|
82
|
+
this.commands = new Map([
|
|
83
|
+
['help', this.helpCommand.bind(this)],
|
|
84
|
+
['exit', this.exitCommand.bind(this)],
|
|
85
|
+
['quit', this.exitCommand.bind(this)],
|
|
86
|
+
['history', this.historyCommand.bind(this)],
|
|
87
|
+
['clear', this.clearCommand.bind(this)],
|
|
88
|
+
['config', this.configCommand.bind(this)],
|
|
89
|
+
['set', this.setCommand.bind(this)],
|
|
90
|
+
['get', this.getCommand.bind(this)],
|
|
91
|
+
['results', this.resultsCommand.bind(this)],
|
|
92
|
+
['last', this.lastCommand.bind(this)],
|
|
93
|
+
['watch', this.watchCommand.bind(this)],
|
|
94
|
+
['save', this.saveCommand.bind(this)],
|
|
95
|
+
['load', this.loadCommand.bind(this)],
|
|
96
|
+
['fix', this.fixCommand.bind(this)],
|
|
97
|
+
['suggest', this.suggestCommand.bind(this)],
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
isCommand(input) {
|
|
102
|
+
const trimmed = input.trim();
|
|
103
|
+
return trimmed.startsWith(':') || trimmed.startsWith('.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async execute(input) {
|
|
107
|
+
const trimmed = input.trim();
|
|
108
|
+
const prefix = trimmed[0];
|
|
109
|
+
const commandLine = trimmed.slice(1).trim();
|
|
110
|
+
const [command, ...args] = commandLine.split(/\s+/);
|
|
111
|
+
|
|
112
|
+
if (!this.commands.has(command)) {
|
|
113
|
+
return `Unknown command: ${command}. Type :help for available commands.`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return await this.commands.get(command)(args);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
helpCommand() {
|
|
120
|
+
return `
|
|
121
|
+
Available REPL Commands:
|
|
122
|
+
:help, .help Show this help message
|
|
123
|
+
:exit, :quit Exit the REPL
|
|
124
|
+
:history Show command history
|
|
125
|
+
:clear Clear history and results
|
|
126
|
+
:config Show current configuration
|
|
127
|
+
:set <key> <value> Set a configuration value
|
|
128
|
+
:get <key> Get a configuration value
|
|
129
|
+
:results Show all previous results
|
|
130
|
+
:last Show the last result
|
|
131
|
+
:watch Start watch mode
|
|
132
|
+
:save [file] Save current config to file
|
|
133
|
+
:load [file] Load config from file
|
|
134
|
+
:fix Auto-fix issues in .env.example
|
|
135
|
+
:suggest Show intelligent suggestions
|
|
136
|
+
|
|
137
|
+
Configuration Keys:
|
|
138
|
+
path, envFile, format, failOn, noColor, quiet, suggestions, progress
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
:set path ./src
|
|
142
|
+
:set format json
|
|
143
|
+
:get envFile
|
|
144
|
+
:save .envcheckrc.json
|
|
145
|
+
envcheck . --format json
|
|
146
|
+
. --fail-on missing
|
|
147
|
+
:fix
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
exitCommand() {
|
|
152
|
+
return { exit: true };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
historyCommand() {
|
|
156
|
+
const history = this.session.getHistory();
|
|
157
|
+
if (history.length === 0) {
|
|
158
|
+
return 'No command history.';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return history
|
|
162
|
+
.map((entry, index) => {
|
|
163
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
164
|
+
return `${index + 1}. [${time}] ${entry.command}`;
|
|
165
|
+
})
|
|
166
|
+
.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
clearCommand() {
|
|
170
|
+
this.session.clear();
|
|
171
|
+
return 'History and results cleared.';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
configCommand() {
|
|
175
|
+
const config = this.session.getConfig();
|
|
176
|
+
return Object.entries(config)
|
|
177
|
+
.map(([key, value]) => {
|
|
178
|
+
const displayValue = Array.isArray(value) ? `[${value.join(', ')}]` : value;
|
|
179
|
+
return `${key}: ${displayValue}`;
|
|
180
|
+
})
|
|
181
|
+
.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
setCommand(args) {
|
|
185
|
+
if (args.length < 2) {
|
|
186
|
+
return 'Usage: :set <key> <value>';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const [key, ...valueParts] = args;
|
|
190
|
+
const value = valueParts.join(' ');
|
|
191
|
+
|
|
192
|
+
// Handle special types
|
|
193
|
+
let parsedValue = value;
|
|
194
|
+
if (value === 'true') parsedValue = true;
|
|
195
|
+
else if (value === 'false') parsedValue = false;
|
|
196
|
+
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
|
197
|
+
|
|
198
|
+
if (this.session.setConfig(key, parsedValue)) {
|
|
199
|
+
return `Set ${key} = ${parsedValue}`;
|
|
200
|
+
} else {
|
|
201
|
+
return `Unknown configuration key: ${key}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
getCommand(args) {
|
|
206
|
+
if (args.length === 0) {
|
|
207
|
+
return 'Usage: :get <key>';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const key = args[0];
|
|
211
|
+
const config = this.session.getConfig();
|
|
212
|
+
|
|
213
|
+
if (key in config) {
|
|
214
|
+
const value = config[key];
|
|
215
|
+
const displayValue = Array.isArray(value) ? `[${value.join(', ')}]` : value;
|
|
216
|
+
return `${key}: ${displayValue}`;
|
|
217
|
+
} else {
|
|
218
|
+
return `Unknown configuration key: ${key}`;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
resultsCommand() {
|
|
223
|
+
const results = this.session.getResults();
|
|
224
|
+
if (results.length === 0) {
|
|
225
|
+
return 'No results yet.';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return results
|
|
229
|
+
.map((entry, index) => {
|
|
230
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
231
|
+
const summary = entry.result.summary || {};
|
|
232
|
+
return `${index + 1}. [${time}] Missing: ${summary.missingCount || 0}, Unused: ${summary.unusedCount || 0}, Undocumented: ${summary.undocumentedCount || 0}`;
|
|
233
|
+
})
|
|
234
|
+
.join('\n');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
lastCommand() {
|
|
238
|
+
const results = this.session.getResults();
|
|
239
|
+
if (results.length === 0) {
|
|
240
|
+
return 'No results yet.';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const last = results[results.length - 1];
|
|
244
|
+
return JSON.stringify(last.result, null, 2);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async watchCommand(args) {
|
|
248
|
+
return 'Watch mode not available in REPL. Use: envcheck . --watch';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async saveCommand(args) {
|
|
252
|
+
const filename = args[0] || '.envcheckrc.json';
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const config = this.session.getConfig();
|
|
256
|
+
const path = saveConfig(config, '.', filename);
|
|
257
|
+
return `Configuration saved to ${path}`;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
return `Failed to save config: ${error.message}`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async loadCommand(args) {
|
|
264
|
+
const filename = args[0];
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const config = loadConfig(filename ? dirname(filename) : '.');
|
|
268
|
+
|
|
269
|
+
if (!config) {
|
|
270
|
+
return 'No configuration file found';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Update session config
|
|
274
|
+
for (const [key, value] of Object.entries(config)) {
|
|
275
|
+
this.session.setConfig(key, value);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return 'Configuration loaded successfully';
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return `Failed to load config: ${error.message}`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async fixCommand(args) {
|
|
285
|
+
return 'Auto-fix: Run envcheck with --fix flag to update .env.example';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async suggestCommand(args) {
|
|
289
|
+
const results = this.session.getResults();
|
|
290
|
+
if (results.length === 0) {
|
|
291
|
+
return 'No results to analyze. Run a check first.';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const last = results[results.length - 1];
|
|
295
|
+
const { generateSuggestions } = await import('./suggestions.js');
|
|
296
|
+
const suggestions = generateSuggestions(last.result);
|
|
297
|
+
|
|
298
|
+
if (suggestions.length === 0) {
|
|
299
|
+
return 'No suggestions - everything looks good! ✨';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return suggestions
|
|
303
|
+
.map(s => {
|
|
304
|
+
const items = s.items.map(i => ` • ${i.suggestion}`).join('\n');
|
|
305
|
+
return `${s.message}\n${s.action}\n${items}`;
|
|
306
|
+
})
|
|
307
|
+
.join('\n\n');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Start the REPL
|
|
313
|
+
*/
|
|
314
|
+
export async function startREPL() {
|
|
315
|
+
const session = new Session();
|
|
316
|
+
const commandParser = new CommandParser(session);
|
|
317
|
+
|
|
318
|
+
const rl = createInterface({
|
|
319
|
+
input,
|
|
320
|
+
output,
|
|
321
|
+
prompt: 'envcheck> ',
|
|
322
|
+
historySize: 100,
|
|
323
|
+
completer: (line) => {
|
|
324
|
+
const { getCompletions } = require('./autocomplete.js');
|
|
325
|
+
const completions = getCompletions(line);
|
|
326
|
+
return [completions, line];
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
331
|
+
console.log('║ envcheck REPL - Interactive Environment Variable Checker ║');
|
|
332
|
+
console.log('╚════════════════════════════════════════════════════════════╝');
|
|
333
|
+
console.log('\n💡 Type :help for available commands, :exit to quit\n');
|
|
334
|
+
|
|
335
|
+
rl.prompt();
|
|
336
|
+
|
|
337
|
+
rl.on('line', async (line) => {
|
|
338
|
+
const input = line.trim();
|
|
339
|
+
|
|
340
|
+
if (!input) {
|
|
341
|
+
rl.prompt();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
session.addCommand(input);
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
// Check if it's a special command
|
|
349
|
+
if (commandParser.isCommand(input)) {
|
|
350
|
+
const result = await commandParser.execute(input);
|
|
351
|
+
|
|
352
|
+
if (result && typeof result === 'object' && result.exit) {
|
|
353
|
+
console.log('Goodbye!');
|
|
354
|
+
rl.close();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (result) {
|
|
359
|
+
console.log(result);
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
// Parse as envcheck command
|
|
363
|
+
let args;
|
|
364
|
+
|
|
365
|
+
// Handle shorthand: if line starts with '.', treat as 'envcheck .'
|
|
366
|
+
if (input.startsWith('.')) {
|
|
367
|
+
args = input.split(/\s+/);
|
|
368
|
+
} else if (input.startsWith('envcheck')) {
|
|
369
|
+
// Remove 'envcheck' prefix
|
|
370
|
+
args = input.slice('envcheck'.length).trim().split(/\s+/).filter(Boolean);
|
|
371
|
+
} else {
|
|
372
|
+
// Treat as arguments to envcheck
|
|
373
|
+
args = input.split(/\s+/);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Merge with session config
|
|
377
|
+
const config = session.getConfig();
|
|
378
|
+
const fullArgs = [
|
|
379
|
+
config.path,
|
|
380
|
+
'--env-file', config.envFile,
|
|
381
|
+
'--format', config.format,
|
|
382
|
+
'--fail-on', config.failOn,
|
|
383
|
+
...config.ignore.flatMap(pattern => ['--ignore', pattern]),
|
|
384
|
+
...(config.noColor ? ['--no-color'] : []),
|
|
385
|
+
...(config.quiet ? ['--quiet'] : []),
|
|
386
|
+
...args,
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
// Run the command
|
|
390
|
+
const exitCode = await run(fullArgs);
|
|
391
|
+
|
|
392
|
+
// Store result (we'd need to modify run() to return the actual result)
|
|
393
|
+
session.addResult({ exitCode });
|
|
394
|
+
|
|
395
|
+
if (exitCode === 0) {
|
|
396
|
+
console.log('\n✓ Check completed successfully');
|
|
397
|
+
} else if (exitCode === 1) {
|
|
398
|
+
console.log('\n✗ Validation failed');
|
|
399
|
+
} else {
|
|
400
|
+
console.log('\n✗ Error occurred');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(`Error: ${error.message}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
rl.prompt();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
rl.on('close', () => {
|
|
411
|
+
const duration = Math.round(session.getDuration() / 1000);
|
|
412
|
+
console.log(`\nSession duration: ${duration}s`);
|
|
413
|
+
console.log(`Commands executed: ${session.getHistory().length}`);
|
|
414
|
+
process.exit(0);
|
|
415
|
+
});
|
|
416
|
+
}
|