@vibecheckai/cli 3.2.1 โ 3.2.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/bin/runners/lib/cli-output.js +236 -210
- package/bin/runners/lib/scan-output.js +144 -822
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/terminal-ui.js +231 -733
- package/bin/runners/lib/unified-output.js +162 -158
- package/bin/runners/runShip.js +7 -8
- package/package.json +1 -1
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Output Utilities -
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* CLI Output Utilities - Enterprise Grade
|
|
3
|
+
* * Standardizes run lifecycle, artifact management, telemetry,
|
|
4
|
+
* * and JSON output for the Vibecheck suite.
|
|
5
|
+
* * Features:
|
|
6
|
+
* - ๐ก๏ธ Atomic file writes (no corruption on crash)
|
|
7
|
+
* - ๐ Automatic "latest" symlinking for easy debugging
|
|
8
|
+
* - ๐ต๏ธ Deep CI/Git context extraction
|
|
9
|
+
* - ๐ Beautiful Crash Reporting (matches UI style)
|
|
10
|
+
* - ๐งน Automatic Log Rotation
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
"use strict";
|
|
@@ -10,50 +15,151 @@
|
|
|
10
15
|
const fs = require("fs");
|
|
11
16
|
const path = require("path");
|
|
12
17
|
const crypto = require("crypto");
|
|
18
|
+
const os = require("os");
|
|
19
|
+
const childProcess = require("child_process");
|
|
20
|
+
const chalk = require("chalk"); // Added for premium error reporting
|
|
21
|
+
|
|
22
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
23
|
+
// CONFIGURATION
|
|
24
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
25
|
+
|
|
26
|
+
const CONFIG = {
|
|
27
|
+
DIR_NAME: ".vibecheck",
|
|
28
|
+
MAX_RETAINED_RUNS: 50, // Keep last 50 runs, delete older
|
|
29
|
+
WIDTH: 76,
|
|
30
|
+
};
|
|
13
31
|
|
|
14
32
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
15
33
|
// RUN ID GENERATION
|
|
16
34
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
17
35
|
|
|
18
36
|
/**
|
|
19
|
-
* Generate a unique run ID
|
|
37
|
+
* Generate a unique, time-sortable run ID
|
|
38
|
+
* Format: YYYY-MM-DD-HHmm-UUID
|
|
20
39
|
*/
|
|
21
40
|
function generateRunId() {
|
|
22
|
-
|
|
41
|
+
const date = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 16);
|
|
42
|
+
const uuid = crypto.randomUUID().slice(0, 8);
|
|
43
|
+
return `${date}-${uuid}`;
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
26
|
-
//
|
|
47
|
+
// CONTEXT GATHERING (Telemetry)
|
|
48
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
49
|
+
|
|
50
|
+
function getSystemContext() {
|
|
51
|
+
return {
|
|
52
|
+
platform: os.platform(),
|
|
53
|
+
arch: os.arch(),
|
|
54
|
+
node: process.version,
|
|
55
|
+
cpus: os.cpus().length,
|
|
56
|
+
memory: Math.round(os.totalmem() / 1024 / 1024 / 1024) + 'GB',
|
|
57
|
+
cwd: process.cwd(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getGitContext() {
|
|
62
|
+
try {
|
|
63
|
+
const commit = childProcess.execSync('git rev-parse --short HEAD', { stdio: 'pipe' }).toString().trim();
|
|
64
|
+
const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
|
|
65
|
+
const isDirty = childProcess.execSync('git status --porcelain', { stdio: 'pipe' }).toString().trim().length > 0;
|
|
66
|
+
return { commit, branch, isDirty };
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return null; // Not a git repo
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getCiContext() {
|
|
73
|
+
if (process.env.GITHUB_ACTIONS) return { provider: 'GitHub Actions', runId: process.env.GITHUB_RUN_ID };
|
|
74
|
+
if (process.env.GITLAB_CI) return { provider: 'GitLab CI', jobId: process.env.CI_JOB_ID };
|
|
75
|
+
if (process.env.JENKINS_URL) return { provider: 'Jenkins', buildUrl: process.env.BUILD_URL };
|
|
76
|
+
if (process.env.CI) return { provider: 'Generic CI' };
|
|
77
|
+
return { provider: null };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
81
|
+
// FILESYSTEM OPS (Atomic & Safe)
|
|
27
82
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
28
83
|
|
|
29
84
|
/**
|
|
30
|
-
*
|
|
85
|
+
* Atomic write to prevent file corruption if process dies mid-write
|
|
86
|
+
*/
|
|
87
|
+
function safeWriteJson(filePath, data) {
|
|
88
|
+
const tempPath = `${filePath}.tmp.${crypto.randomUUID().slice(0, 6)}`;
|
|
89
|
+
try {
|
|
90
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2));
|
|
91
|
+
fs.renameSync(tempPath, filePath); // Atomic operation
|
|
92
|
+
} catch (err) {
|
|
93
|
+
try { fs.unlinkSync(tempPath); } catch (e) {} // Cleanup
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function updateSymlink(target, linkPath) {
|
|
99
|
+
try {
|
|
100
|
+
if (fs.existsSync(linkPath)) fs.unlinkSync(linkPath);
|
|
101
|
+
// 'junction' type is required for Windows to not require Admin
|
|
102
|
+
fs.symlinkSync(target, linkPath, 'junction');
|
|
103
|
+
} catch (e) {
|
|
104
|
+
// Ignore symlink errors (often permissions)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Cleanup old runs to save disk space (Log Rotation)
|
|
31
110
|
*/
|
|
111
|
+
function rotateLogs(runsDir) {
|
|
112
|
+
try {
|
|
113
|
+
if (!fs.existsSync(runsDir)) return;
|
|
114
|
+
|
|
115
|
+
const runs = fs.readdirSync(runsDir)
|
|
116
|
+
.map(name => ({ name, path: path.join(runsDir, name) }))
|
|
117
|
+
.filter(item => fs.statSync(item.path).isDirectory() && item.name !== 'latest')
|
|
118
|
+
.sort((a, b) => b.name.localeCompare(a.name)); // Newest first
|
|
119
|
+
|
|
120
|
+
if (runs.length > CONFIG.MAX_RETAINED_RUNS) {
|
|
121
|
+
const toDelete = runs.slice(CONFIG.MAX_RETAINED_RUNS);
|
|
122
|
+
toDelete.forEach(run => fs.rmSync(run.path, { recursive: true, force: true }));
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
// Non-blocking cleanup failure
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
130
|
+
// OUTPUT DIRECTORY MANAGEMENT
|
|
131
|
+
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
132
|
+
|
|
32
133
|
function getRunPaths(runId, projectPath = process.cwd()) {
|
|
33
|
-
const
|
|
134
|
+
const baseDir = path.join(projectPath, CONFIG.DIR_NAME);
|
|
135
|
+
const runDir = path.join(baseDir, "runs", runId);
|
|
136
|
+
const latestLink = path.join(baseDir, "runs", "latest");
|
|
34
137
|
const artifactsDir = path.join(runDir, "artifacts");
|
|
35
138
|
|
|
36
139
|
// Ensure directories exist
|
|
37
|
-
|
|
38
|
-
|
|
140
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
141
|
+
|
|
142
|
+
// Update 'latest' symlink for easy debugging
|
|
143
|
+
updateSymlink(runDir, latestLink);
|
|
39
144
|
|
|
145
|
+
// Trigger cleanup async (don't block the CLI)
|
|
146
|
+
setTimeout(() => rotateLogs(path.join(baseDir, "runs")), 100).unref();
|
|
147
|
+
|
|
40
148
|
return {
|
|
41
149
|
runDir,
|
|
42
150
|
artifactsDir,
|
|
43
151
|
manifestPath: path.join(runDir, "manifest.json"),
|
|
44
152
|
commandPath: path.join(runDir, "command.json"),
|
|
153
|
+
crashPath: path.join(runDir, "crash.json"),
|
|
45
154
|
};
|
|
46
155
|
}
|
|
47
156
|
|
|
48
|
-
/**
|
|
49
|
-
* Save artifact to run directory
|
|
50
|
-
*/
|
|
51
157
|
function saveArtifact(runId, name, data, type = "json") {
|
|
52
158
|
const { artifactsDir } = getRunPaths(runId);
|
|
53
159
|
const artifactPath = path.join(artifactsDir, `${name}.${type}`);
|
|
54
160
|
|
|
55
161
|
if (type === "json") {
|
|
56
|
-
|
|
162
|
+
safeWriteJson(artifactPath, data);
|
|
57
163
|
} else {
|
|
58
164
|
fs.writeFileSync(artifactPath, data);
|
|
59
165
|
}
|
|
@@ -65,9 +171,6 @@ function saveArtifact(runId, name, data, type = "json") {
|
|
|
65
171
|
// JSON OUTPUT SCHEMA
|
|
66
172
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
67
173
|
|
|
68
|
-
/**
|
|
69
|
-
* Create standardized JSON output
|
|
70
|
-
*/
|
|
71
174
|
function createJsonOutput(options) {
|
|
72
175
|
const {
|
|
73
176
|
runId,
|
|
@@ -82,84 +185,71 @@ function createJsonOutput(options) {
|
|
|
82
185
|
artifacts = [],
|
|
83
186
|
} = options;
|
|
84
187
|
|
|
85
|
-
|
|
86
|
-
runId,
|
|
87
|
-
command,
|
|
88
|
-
timestamp: new Date().toISOString(),
|
|
89
|
-
duration: startTime ? Date.now() - new Date(startTime).getTime() : null,
|
|
90
|
-
exitCode,
|
|
91
|
-
verdict,
|
|
92
|
-
result,
|
|
188
|
+
return {
|
|
93
189
|
meta: {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
190
|
+
schemaVersion: "2.0.0",
|
|
191
|
+
generatedAt: new Date().toISOString(),
|
|
192
|
+
tool: { name: "vibecheck", version, tier },
|
|
193
|
+
system: getSystemContext(),
|
|
194
|
+
git: getGitContext(),
|
|
195
|
+
ci: getCiContext(),
|
|
196
|
+
},
|
|
197
|
+
run: {
|
|
198
|
+
id: runId,
|
|
199
|
+
command,
|
|
200
|
+
durationMs: startTime ? Date.now() - new Date(startTime).getTime() : 0,
|
|
201
|
+
exitCode,
|
|
202
|
+
verdict,
|
|
97
203
|
},
|
|
204
|
+
// The actual data payload
|
|
205
|
+
data: result,
|
|
206
|
+
// Links to files created
|
|
98
207
|
artifacts: artifacts.map(art => ({
|
|
99
208
|
type: art.type,
|
|
100
|
-
|
|
209
|
+
name: path.basename(art.path),
|
|
210
|
+
path: path.relative(projectPath, art.path), // Relative for portability
|
|
101
211
|
description: art.description,
|
|
102
212
|
})),
|
|
103
213
|
};
|
|
104
|
-
|
|
105
|
-
return output;
|
|
106
214
|
}
|
|
107
215
|
|
|
108
|
-
/**
|
|
109
|
-
* Write JSON output to file or stdout
|
|
110
|
-
*/
|
|
111
216
|
function writeJsonOutput(output, outputPath) {
|
|
112
217
|
const jsonString = JSON.stringify(output, null, 2);
|
|
113
218
|
|
|
114
219
|
if (outputPath) {
|
|
115
|
-
|
|
220
|
+
// If output is a file, write to it
|
|
221
|
+
safeWriteJson(outputPath, output);
|
|
116
222
|
return outputPath;
|
|
117
223
|
} else {
|
|
118
|
-
|
|
224
|
+
// If stdout, we must ensure we don't break pipe consumers
|
|
225
|
+
process.stdout.write(jsonString + '\n');
|
|
119
226
|
return null;
|
|
120
227
|
}
|
|
121
228
|
}
|
|
122
229
|
|
|
123
230
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
124
|
-
//
|
|
231
|
+
// VERDICT MAPPING
|
|
125
232
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
126
233
|
|
|
127
|
-
/**
|
|
128
|
-
* Map exit codes to verdict strings
|
|
129
|
-
*/
|
|
130
234
|
function exitCodeToVerdict(exitCode) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Map verdict to exit code
|
|
144
|
-
*/
|
|
145
|
-
function verdictToExitCode(verdict) {
|
|
146
|
-
switch (verdict?.toUpperCase()) {
|
|
147
|
-
case "SHIP": return 0;
|
|
148
|
-
case "WARN": return 1;
|
|
149
|
-
case "BLOCK": return 2;
|
|
150
|
-
case "FEATURE_DENIED": return 3;
|
|
151
|
-
case "INVALID_INPUT": return 4;
|
|
152
|
-
case "INTERNAL_ERROR": return 5;
|
|
153
|
-
default: return 5;
|
|
154
|
-
}
|
|
235
|
+
const map = {
|
|
236
|
+
0: "SHIP",
|
|
237
|
+
1: "WARN",
|
|
238
|
+
2: "BLOCK",
|
|
239
|
+
3: "FEATURE_DENIED",
|
|
240
|
+
4: "INVALID_INPUT",
|
|
241
|
+
5: "INTERNAL_ERROR"
|
|
242
|
+
};
|
|
243
|
+
return map[exitCode] || "UNKNOWN";
|
|
155
244
|
}
|
|
156
245
|
|
|
157
246
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
158
|
-
// COMMAND WRAPPER
|
|
247
|
+
// COMMAND WRAPPER (The Safety Net)
|
|
159
248
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
160
249
|
|
|
161
250
|
/**
|
|
162
|
-
* Wrap a command runner with
|
|
251
|
+
* Wrap a command runner with world-class output handling,
|
|
252
|
+
* telemetry, and crash protection.
|
|
163
253
|
*/
|
|
164
254
|
async function withStandardOutput(commandFn, options = {}) {
|
|
165
255
|
const {
|
|
@@ -168,104 +258,88 @@ async function withStandardOutput(commandFn, options = {}) {
|
|
|
168
258
|
runId = generateRunId(),
|
|
169
259
|
json = false,
|
|
170
260
|
output = null,
|
|
171
|
-
ci = false,
|
|
172
|
-
verbose = false,
|
|
173
261
|
tier = "free",
|
|
174
262
|
version = "1.0.0",
|
|
175
263
|
} = options;
|
|
176
264
|
|
|
177
|
-
// Initialize paths
|
|
178
265
|
const paths = getRunPaths(runId);
|
|
179
266
|
const artifacts = [];
|
|
180
267
|
|
|
268
|
+
// Artifact helper injected into the command
|
|
269
|
+
const artifactSaver = (name, data, type = 'json') => {
|
|
270
|
+
try {
|
|
271
|
+
const artifactPath = saveArtifact(runId, name, data, type);
|
|
272
|
+
artifacts.push({ type, path: artifactPath, description: name });
|
|
273
|
+
return artifactPath;
|
|
274
|
+
} catch (e) {
|
|
275
|
+
console.error(chalk.yellow(`โ ๏ธ Warning: Failed to save artifact ${name}: ${e.message}`));
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
181
280
|
try {
|
|
182
|
-
//
|
|
281
|
+
// Execute Command
|
|
183
282
|
const result = await commandFn({
|
|
184
283
|
runId,
|
|
185
284
|
paths,
|
|
186
|
-
saveArtifact:
|
|
187
|
-
const artifactPath = saveArtifact(runId, name, data, type);
|
|
188
|
-
artifacts.push({
|
|
189
|
-
type,
|
|
190
|
-
path: artifactPath,
|
|
191
|
-
description: `${name} artifact`,
|
|
192
|
-
});
|
|
193
|
-
return artifactPath;
|
|
194
|
-
},
|
|
285
|
+
saveArtifact: artifactSaver,
|
|
195
286
|
});
|
|
196
287
|
|
|
197
|
-
//
|
|
198
|
-
const exitCode = typeof result === "number" ? result : result.exitCode
|
|
288
|
+
// Normalize Result
|
|
289
|
+
const exitCode = typeof result === "number" ? result : (result.exitCode ?? 0);
|
|
199
290
|
const verdict = result.verdict || exitCodeToVerdict(exitCode);
|
|
200
|
-
|
|
201
|
-
|
|
291
|
+
const resultData = typeof result === "object" ? result : {};
|
|
292
|
+
|
|
293
|
+
const outputObj = createJsonOutput({
|
|
294
|
+
runId, command, startTime, exitCode, verdict,
|
|
295
|
+
result: resultData, tier, version, artifacts
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// 1. Always save to internal history
|
|
299
|
+
safeWriteJson(paths.commandPath, outputObj);
|
|
300
|
+
|
|
301
|
+
// 2. If JSON requested by user, print/write it
|
|
202
302
|
if (json) {
|
|
203
|
-
|
|
204
|
-
runId,
|
|
205
|
-
command,
|
|
206
|
-
startTime,
|
|
207
|
-
exitCode,
|
|
208
|
-
verdict,
|
|
209
|
-
result: typeof result === "object" ? result : {},
|
|
210
|
-
tier,
|
|
211
|
-
version,
|
|
212
|
-
artifacts,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
writeJsonOutput(jsonOutput, output);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Save command output to run directory
|
|
219
|
-
const commandOutput = {
|
|
220
|
-
exitCode,
|
|
221
|
-
verdict,
|
|
222
|
-
result: typeof result === "object" ? result : {},
|
|
223
|
-
artifacts,
|
|
224
|
-
};
|
|
225
|
-
fs.writeFileSync(paths.commandPath, JSON.stringify(commandOutput, null, 2));
|
|
226
|
-
|
|
227
|
-
// Update stable pointer
|
|
228
|
-
const lastPath = path.join(process.cwd(), ".vibecheck", "last", `${command}.json`);
|
|
229
|
-
if (!fs.existsSync(path.dirname(lastPath))) {
|
|
230
|
-
fs.mkdirSync(path.dirname(lastPath), { recursive: true });
|
|
303
|
+
writeJsonOutput(outputObj, output);
|
|
231
304
|
}
|
|
232
|
-
|
|
233
|
-
|
|
305
|
+
|
|
234
306
|
return exitCode;
|
|
307
|
+
|
|
235
308
|
} catch (error) {
|
|
236
|
-
//
|
|
309
|
+
// ๐ CRITICAL ERROR HANDLING
|
|
237
310
|
const exitCode = error.exitCode || 5;
|
|
238
311
|
const verdict = "INTERNAL_ERROR";
|
|
239
312
|
|
|
313
|
+
const errorData = {
|
|
314
|
+
message: error.message,
|
|
315
|
+
stack: error.stack,
|
|
316
|
+
code: error.code || 'UNKNOWN',
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const outputObj = createJsonOutput({
|
|
320
|
+
runId, command, startTime, exitCode, verdict,
|
|
321
|
+
result: { error: errorData }, tier, version, artifacts
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Save crash report to disk
|
|
325
|
+
safeWriteJson(paths.crashPath, outputObj);
|
|
326
|
+
safeWriteJson(paths.commandPath, outputObj);
|
|
327
|
+
|
|
240
328
|
if (json) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
version,
|
|
253
|
-
artifacts,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
writeJsonOutput(jsonOutput, output);
|
|
329
|
+
writeJsonOutput(outputObj, output);
|
|
330
|
+
} else {
|
|
331
|
+
// VISUAL CRASH REPORT
|
|
332
|
+
console.error('');
|
|
333
|
+
console.error(chalk.bgRed.white.bold(` ๐ SYSTEM ALERT: INTERNAL CRASH `));
|
|
334
|
+
console.error(chalk.red('โ'.repeat(CONFIG.WIDTH)));
|
|
335
|
+
console.error(`${chalk.bold('Error:')} ${error.message}`);
|
|
336
|
+
console.error(`${chalk.bold('Run ID:')} ${runId}`);
|
|
337
|
+
console.error(`${chalk.bold('Log:')} ${chalk.cyan(paths.crashPath)}`);
|
|
338
|
+
console.error(chalk.red('โ'.repeat(CONFIG.WIDTH)));
|
|
339
|
+
console.error('');
|
|
257
340
|
}
|
|
258
|
-
|
|
259
|
-
//
|
|
260
|
-
const commandOutput = {
|
|
261
|
-
exitCode,
|
|
262
|
-
verdict,
|
|
263
|
-
error: error.message,
|
|
264
|
-
artifacts,
|
|
265
|
-
};
|
|
266
|
-
fs.writeFileSync(paths.commandPath, JSON.stringify(commandOutput, null, 2));
|
|
267
|
-
|
|
268
|
-
throw error;
|
|
341
|
+
|
|
342
|
+
throw error; // Re-throw so the process actually exits with error
|
|
269
343
|
}
|
|
270
344
|
}
|
|
271
345
|
|
|
@@ -273,13 +347,10 @@ async function withStandardOutput(commandFn, options = {}) {
|
|
|
273
347
|
// STANDARD FLAGS PARSER
|
|
274
348
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
275
349
|
|
|
276
|
-
/**
|
|
277
|
-
* Parse standard CLI flags
|
|
278
|
-
*/
|
|
279
350
|
function parseStandardFlags(args) {
|
|
280
351
|
const flags = {
|
|
281
352
|
json: false,
|
|
282
|
-
ci:
|
|
353
|
+
ci: !!process.env.CI, // Auto-detect CI
|
|
283
354
|
path: process.cwd(),
|
|
284
355
|
output: null,
|
|
285
356
|
verbose: false,
|
|
@@ -287,74 +358,30 @@ function parseStandardFlags(args) {
|
|
|
287
358
|
help: false,
|
|
288
359
|
version: false,
|
|
289
360
|
debug: false,
|
|
290
|
-
quiet: false,
|
|
291
361
|
};
|
|
292
362
|
|
|
293
|
-
const parsed =
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
flags.help = true;
|
|
312
|
-
return false;
|
|
313
|
-
case "--version":
|
|
314
|
-
case "-V":
|
|
315
|
-
flags.version = true;
|
|
316
|
-
return false;
|
|
317
|
-
case "--debug":
|
|
318
|
-
case "-d":
|
|
319
|
-
flags.debug = true;
|
|
320
|
-
return false;
|
|
321
|
-
case "--quiet":
|
|
322
|
-
case "-q":
|
|
323
|
-
flags.quiet = true;
|
|
324
|
-
return false;
|
|
325
|
-
default:
|
|
326
|
-
if (arg.startsWith("--path=")) {
|
|
327
|
-
flags.path = arg.split("=")[1];
|
|
328
|
-
return false;
|
|
329
|
-
} else if (arg.startsWith("-p")) {
|
|
330
|
-
const idx = args.indexOf(arg);
|
|
331
|
-
if (idx < args.length - 1) {
|
|
332
|
-
flags.path = args[idx + 1];
|
|
333
|
-
args.splice(idx, 2);
|
|
334
|
-
return false;
|
|
335
|
-
}
|
|
336
|
-
} else if (arg.startsWith("--output=")) {
|
|
337
|
-
flags.output = arg.split("=")[1];
|
|
338
|
-
return false;
|
|
339
|
-
} else if (arg.startsWith("-o")) {
|
|
340
|
-
const idx = args.indexOf(arg);
|
|
341
|
-
if (idx < args.length - 1) {
|
|
342
|
-
flags.output = args[idx + 1];
|
|
343
|
-
args.splice(idx, 2);
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
});
|
|
363
|
+
const parsed = [];
|
|
364
|
+
|
|
365
|
+
for (let i = 0; i < args.length; i++) {
|
|
366
|
+
const arg = args[i];
|
|
367
|
+
|
|
368
|
+
if (arg === '--json' || arg === '-j') flags.json = true;
|
|
369
|
+
else if (arg === '--ci') flags.ci = true;
|
|
370
|
+
else if (arg === '--verbose' || arg === '-v') flags.verbose = true;
|
|
371
|
+
else if (arg === '--strict') flags.strict = true;
|
|
372
|
+
else if (arg === '--help' || arg === '-h') flags.help = true;
|
|
373
|
+
else if (arg === '--version' || arg === '-V') flags.version = true;
|
|
374
|
+
else if (arg === '--debug' || arg === '-d') flags.debug = true;
|
|
375
|
+
else if (arg.startsWith('--path=')) flags.path = arg.split('=')[1];
|
|
376
|
+
else if (arg === '-p' && args[i+1]) { flags.path = args[++i]; }
|
|
377
|
+
else if (arg.startsWith('--output=')) flags.output = arg.split('=')[1];
|
|
378
|
+
else if (arg === '-o' && args[i+1]) { flags.output = args[++i]; }
|
|
379
|
+
else parsed.push(arg);
|
|
380
|
+
}
|
|
350
381
|
|
|
351
382
|
return { flags, parsed };
|
|
352
383
|
}
|
|
353
384
|
|
|
354
|
-
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
355
|
-
// EXPORTS
|
|
356
|
-
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
357
|
-
|
|
358
385
|
module.exports = {
|
|
359
386
|
generateRunId,
|
|
360
387
|
getRunPaths,
|
|
@@ -362,7 +389,6 @@ module.exports = {
|
|
|
362
389
|
createJsonOutput,
|
|
363
390
|
writeJsonOutput,
|
|
364
391
|
exitCodeToVerdict,
|
|
365
|
-
verdictToExitCode,
|
|
366
392
|
withStandardOutput,
|
|
367
393
|
parseStandardFlags,
|
|
368
|
-
};
|
|
394
|
+
};
|