kodevu 0.1.55 → 0.1.57
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/README.md +2 -1
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/index.js +45 -21
- package/src/logger.js +130 -25
- package/src/review-runner.js +67 -12
- package/src/shell.js +34 -12
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ npx kodevu .
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Review reports are saved to `~/.kodevu/` by default.
|
|
24
|
+
Console output is intentionally concise by default; detailed execution logs are written to `~/.kodevu/logs/`.
|
|
24
25
|
|
|
25
26
|
## Usage
|
|
26
27
|
|
|
@@ -44,7 +45,7 @@ npx kodevu [target] [options]
|
|
|
44
45
|
- `--openai-model`: Model used when `--reviewer openai` (default: `gpt-5-mini`).
|
|
45
46
|
- `--openai-org`: Optional OpenAI organization ID.
|
|
46
47
|
- `--openai-project`: Optional OpenAI project ID.
|
|
47
|
-
- `--debug, -d`:
|
|
48
|
+
- `--debug, -d`: Show extra debug information on the console.
|
|
48
49
|
- `--version, -V`: Print the current version and exit.
|
|
49
50
|
|
|
50
51
|
> [!IMPORTANT]
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -373,7 +373,7 @@ Options:
|
|
|
373
373
|
--openai-model Model used when reviewer=openai (default: gpt-5-mini)
|
|
374
374
|
--openai-org Optional OpenAI organization ID
|
|
375
375
|
--openai-project Optional OpenAI project ID
|
|
376
|
-
--debug, -d
|
|
376
|
+
--debug, -d Show extra debug information on the console
|
|
377
377
|
--help, -h Show help
|
|
378
378
|
--version, -V Show version
|
|
379
379
|
|
package/src/index.js
CHANGED
|
@@ -30,33 +30,57 @@ try {
|
|
|
30
30
|
|
|
31
31
|
if (config.reviewerWasAutoSelected) {
|
|
32
32
|
logger.info(
|
|
33
|
-
`Reviewer "auto" selected ${config.reviewer}${config.reviewerCommandPath ? ` (${config.reviewerCommandPath})` : ""}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (config.debug) {
|
|
38
|
-
logger.debug(
|
|
39
|
-
`Resolved config: ${JSON.stringify({
|
|
33
|
+
`Reviewer "auto" selected ${config.reviewer}${config.reviewerCommandPath ? ` (${config.reviewerCommandPath})` : ""}.`,
|
|
34
|
+
{
|
|
35
|
+
scope: "session",
|
|
40
36
|
reviewer: config.reviewer,
|
|
41
|
-
reviewerCommandPath: config.reviewerCommandPath,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
openaiModel: config.openaiModel,
|
|
45
|
-
openaiOrganization: config.openaiOrganization,
|
|
46
|
-
openaiProject: config.openaiProject,
|
|
47
|
-
target: config.target,
|
|
48
|
-
outputDir: config.outputDir,
|
|
49
|
-
lang: config.lang,
|
|
50
|
-
debug: config.debug
|
|
51
|
-
})}`
|
|
37
|
+
reviewerCommandPath: config.reviewerCommandPath || "",
|
|
38
|
+
console: true
|
|
39
|
+
}
|
|
52
40
|
);
|
|
53
41
|
}
|
|
54
42
|
|
|
55
|
-
logger.
|
|
43
|
+
logger.debug("Resolved config", {
|
|
44
|
+
scope: "session",
|
|
45
|
+
reviewer: config.reviewer,
|
|
46
|
+
reviewerCommandPath: config.reviewerCommandPath || "",
|
|
47
|
+
reviewerWasAutoSelected: config.reviewerWasAutoSelected || false,
|
|
48
|
+
openaiBaseUrl: config.openaiBaseUrl,
|
|
49
|
+
openaiModel: config.openaiModel,
|
|
50
|
+
openaiOrganization: config.openaiOrganization || "",
|
|
51
|
+
openaiProject: config.openaiProject || "",
|
|
52
|
+
target: config.target,
|
|
53
|
+
outputDir: config.outputDir,
|
|
54
|
+
lang: config.lang,
|
|
55
|
+
resolvedLang: config.resolvedLang,
|
|
56
|
+
debug: config.debug,
|
|
57
|
+
outputFormats: config.outputFormats,
|
|
58
|
+
mode: config.uncommitted ? "uncommitted" : config.rev ? "rev" : "last",
|
|
59
|
+
rev: config.rev || "",
|
|
60
|
+
last: config.last || 0,
|
|
61
|
+
console: "debug"
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
logger.info("Session started", {
|
|
65
|
+
scope: "session",
|
|
66
|
+
target: config.target,
|
|
67
|
+
reviewer: config.reviewer,
|
|
68
|
+
outputDir: config.outputDir,
|
|
69
|
+
mode: config.uncommitted ? "uncommitted" : config.rev ? "rev" : "last",
|
|
70
|
+
rev: config.rev || "",
|
|
71
|
+
last: config.last || 0
|
|
72
|
+
});
|
|
56
73
|
await runReviewCycle(config);
|
|
57
|
-
logger.info("Session completed successfully
|
|
74
|
+
logger.info("Session completed successfully", {
|
|
75
|
+
scope: "session",
|
|
76
|
+
target: config.target,
|
|
77
|
+
reviewer: config.reviewer
|
|
78
|
+
});
|
|
58
79
|
} catch (error) {
|
|
59
|
-
logger.error("Session failed
|
|
80
|
+
logger.error("Session failed", error, {
|
|
81
|
+
scope: "session",
|
|
82
|
+
console: true
|
|
83
|
+
});
|
|
60
84
|
process.exitCode = 1;
|
|
61
85
|
}
|
|
62
86
|
process.exit(process.exitCode || 0);
|
package/src/logger.js
CHANGED
|
@@ -2,16 +2,87 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { formatDate } from "./utils.js";
|
|
4
4
|
|
|
5
|
+
function formatValue(value) {
|
|
6
|
+
if (value == null) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (typeof value === "string") {
|
|
11
|
+
return value.trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
15
|
+
return String(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
return JSON.stringify(value);
|
|
20
|
+
} catch {
|
|
21
|
+
return String(value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatMeta(meta = {}) {
|
|
26
|
+
const parts = [];
|
|
27
|
+
|
|
28
|
+
for (const [key, rawValue] of Object.entries(meta)) {
|
|
29
|
+
if (rawValue == null || rawValue === "") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const value = formatValue(rawValue);
|
|
34
|
+
if (!value) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof rawValue === "string") {
|
|
39
|
+
parts.push(`${key}=${JSON.stringify(value)}`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof rawValue === "number" || typeof rawValue === "boolean") {
|
|
44
|
+
parts.push(`${key}=${String(rawValue)}`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
parts.push(`${key}=${value}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return parts.join(" ");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatErrorDetails(error) {
|
|
55
|
+
if (!error) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (error instanceof Error) {
|
|
60
|
+
return error.stack || error.message || String(error);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof error === "string") {
|
|
64
|
+
return error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(error, null, 2);
|
|
69
|
+
} catch {
|
|
70
|
+
return String(error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
5
74
|
class Logger {
|
|
6
75
|
constructor() {
|
|
7
76
|
this.config = null;
|
|
8
77
|
this.logFile = null;
|
|
78
|
+
this.sessionId = null;
|
|
9
79
|
this.initialized = false;
|
|
10
80
|
}
|
|
11
81
|
|
|
12
82
|
init(config) {
|
|
13
83
|
if (this.initialized) return;
|
|
14
84
|
this.config = config;
|
|
85
|
+
this.sessionId = this._createSessionId();
|
|
15
86
|
|
|
16
87
|
if (config.logsDir) {
|
|
17
88
|
try {
|
|
@@ -30,33 +101,36 @@ class Logger {
|
|
|
30
101
|
}
|
|
31
102
|
}
|
|
32
103
|
|
|
33
|
-
info(message) {
|
|
34
|
-
this._log("INFO", message);
|
|
104
|
+
info(message, meta) {
|
|
105
|
+
this._log("INFO", message, meta);
|
|
35
106
|
}
|
|
36
107
|
|
|
37
|
-
warn(message) {
|
|
38
|
-
this._log("WARN", message);
|
|
108
|
+
warn(message, meta) {
|
|
109
|
+
this._log("WARN", message, { ...meta, console: meta?.console ?? true });
|
|
39
110
|
}
|
|
40
111
|
|
|
41
|
-
error(message, error) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
112
|
+
error(message, error, meta) {
|
|
113
|
+
this._log("ERROR", message, {
|
|
114
|
+
...meta,
|
|
115
|
+
console: meta?.console ?? true,
|
|
116
|
+
error: formatErrorDetails(error)
|
|
117
|
+
});
|
|
47
118
|
}
|
|
48
119
|
|
|
49
|
-
debug(message) {
|
|
50
|
-
|
|
51
|
-
this._log("DEBUG", message);
|
|
52
|
-
}
|
|
120
|
+
debug(message, meta) {
|
|
121
|
+
this._log("DEBUG", message, meta);
|
|
53
122
|
}
|
|
54
123
|
|
|
55
|
-
_log(level, message) {
|
|
124
|
+
_log(level, message, meta = {}) {
|
|
56
125
|
const timestamp = formatDate(new Date());
|
|
57
|
-
const
|
|
126
|
+
const { console: consoleMode, ...details } = meta;
|
|
127
|
+
const fields = {
|
|
128
|
+
session: this.sessionId || "uninitialized",
|
|
129
|
+
...details
|
|
130
|
+
};
|
|
131
|
+
const metaSuffix = formatMeta(fields);
|
|
132
|
+
const logLine = `[${timestamp}] [${level}] ${message}${metaSuffix ? ` | ${metaSuffix}` : ""}`;
|
|
58
133
|
|
|
59
|
-
// Write to file
|
|
60
134
|
if (this.logFile) {
|
|
61
135
|
try {
|
|
62
136
|
fs.appendFileSync(this.logFile, logLine + "\n");
|
|
@@ -65,21 +139,52 @@ class Logger {
|
|
|
65
139
|
}
|
|
66
140
|
}
|
|
67
141
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const isWarn = level === "WARN";
|
|
72
|
-
|
|
73
|
-
// If it's debug and debug mode is off, skip console
|
|
74
|
-
if (isDebug && !this.config?.debug) return;
|
|
142
|
+
if (!this._shouldWriteToConsole(level, consoleMode)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
75
145
|
|
|
76
|
-
if (
|
|
146
|
+
if (level === "ERROR" || level === "WARN") {
|
|
77
147
|
console.error(logLine);
|
|
78
148
|
} else {
|
|
79
149
|
console.log(logLine);
|
|
80
150
|
}
|
|
81
151
|
}
|
|
82
152
|
|
|
153
|
+
_shouldWriteToConsole(level, consoleMode) {
|
|
154
|
+
if (consoleMode === false) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (level === "ERROR" || level === "WARN") {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (level === "DEBUG") {
|
|
163
|
+
if (consoleMode === true) {
|
|
164
|
+
return Boolean(this.config?.debug);
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (consoleMode === true) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (consoleMode === "debug") {
|
|
174
|
+
return Boolean(this.config?.debug);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_createSessionId() {
|
|
181
|
+
return [
|
|
182
|
+
Date.now().toString(36),
|
|
183
|
+
process.pid.toString(36),
|
|
184
|
+
Math.random().toString(36).slice(2, 8)
|
|
185
|
+
].join("-");
|
|
186
|
+
}
|
|
187
|
+
|
|
83
188
|
_cleanupOldLogs(logsDir) {
|
|
84
189
|
try {
|
|
85
190
|
const files = fs.readdirSync(logsDir);
|
package/src/review-runner.js
CHANGED
|
@@ -18,7 +18,11 @@ import { runReviewerPrompt } from "./reviewers.js";
|
|
|
18
18
|
|
|
19
19
|
async function reviewChange(config, backend, targetInfo, changeId, progress) {
|
|
20
20
|
const displayId = backend.formatChangeId(changeId);
|
|
21
|
-
logger.info(`Starting review for ${backend.changeName} ${displayId}
|
|
21
|
+
logger.info(`Starting review for ${backend.changeName} ${displayId}`, {
|
|
22
|
+
scope: "review",
|
|
23
|
+
repository: backend.kind,
|
|
24
|
+
changeId: displayId
|
|
25
|
+
});
|
|
22
26
|
progress?.update(0.05, "loading change details");
|
|
23
27
|
const details = await backend.getChangeDetails(config, targetInfo, changeId);
|
|
24
28
|
const resolvedChangeId = details.id;
|
|
@@ -68,7 +72,13 @@ async function reviewChange(config, backend, targetInfo, changeId, progress) {
|
|
|
68
72
|
|
|
69
73
|
for (const reviewerName of reviewersToTry) {
|
|
70
74
|
currentReviewerConfig = { ...config, reviewer: reviewerName };
|
|
71
|
-
logger.debug(`Trying reviewer: ${reviewerName}
|
|
75
|
+
logger.debug(`Trying reviewer: ${reviewerName}`, {
|
|
76
|
+
scope: "review",
|
|
77
|
+
repository: backend.kind,
|
|
78
|
+
changeId: details.displayId,
|
|
79
|
+
reviewer: reviewerName,
|
|
80
|
+
console: "debug"
|
|
81
|
+
});
|
|
72
82
|
progress?.update(0.45, `running reviewer ${reviewerName}`);
|
|
73
83
|
|
|
74
84
|
try {
|
|
@@ -88,13 +98,24 @@ async function reviewChange(config, backend, targetInfo, changeId, progress) {
|
|
|
88
98
|
break;
|
|
89
99
|
}
|
|
90
100
|
} catch (err) {
|
|
91
|
-
logger.error(`Reviewer prompt failed for ${reviewerName}
|
|
101
|
+
logger.error(`Reviewer prompt failed for ${reviewerName}`, err, {
|
|
102
|
+
scope: "review",
|
|
103
|
+
repository: backend.kind,
|
|
104
|
+
changeId: details.displayId,
|
|
105
|
+
reviewer: reviewerName,
|
|
106
|
+
console: false
|
|
107
|
+
});
|
|
92
108
|
// If it's the last one, it will throw below or break loop anyway
|
|
93
109
|
}
|
|
94
110
|
|
|
95
111
|
if (reviewerName !== reviewersToTry[reviewersToTry.length - 1]) {
|
|
96
112
|
const msg = `${reviewer?.displayName || reviewerName} failed for ${details.displayId}; trying next reviewer...`;
|
|
97
|
-
logger.warn(msg
|
|
113
|
+
logger.warn(msg, {
|
|
114
|
+
scope: "review",
|
|
115
|
+
repository: backend.kind,
|
|
116
|
+
changeId: details.displayId,
|
|
117
|
+
reviewer: reviewerName
|
|
118
|
+
});
|
|
98
119
|
}
|
|
99
120
|
}
|
|
100
121
|
|
|
@@ -105,7 +126,17 @@ async function reviewChange(config, backend, targetInfo, changeId, progress) {
|
|
|
105
126
|
}
|
|
106
127
|
|
|
107
128
|
progress?.update(0.82, "writing report");
|
|
108
|
-
logger.debug(
|
|
129
|
+
logger.debug("Token usage recorded", {
|
|
130
|
+
scope: "review",
|
|
131
|
+
repository: backend.kind,
|
|
132
|
+
changeId: details.displayId,
|
|
133
|
+
reviewer: reviewer.displayName,
|
|
134
|
+
inputTokens: tokenUsage.inputTokens,
|
|
135
|
+
outputTokens: tokenUsage.outputTokens,
|
|
136
|
+
totalTokens: tokenUsage.totalTokens,
|
|
137
|
+
source: tokenUsage.source,
|
|
138
|
+
console: "debug"
|
|
139
|
+
});
|
|
109
140
|
const report = buildReport(currentReviewerConfig, backend, targetInfo, details, diffPayloads, reviewer, reviewerResult, tokenUsage);
|
|
110
141
|
const outputFile = path.join(config.outputDir, backend.getReportFileName(resolvedChangeId));
|
|
111
142
|
const jsonOutputFile = outputFile.replace(/\.md$/i, ".json");
|
|
@@ -126,7 +157,13 @@ async function reviewChange(config, backend, targetInfo, changeId, progress) {
|
|
|
126
157
|
shouldWriteFormat(config, "json") ? `json: ${jsonOutputFile}` : null
|
|
127
158
|
].filter(Boolean);
|
|
128
159
|
|
|
129
|
-
logger.info(`Completed review for ${displayId}: ${outputLabels.join(" | ") || "(no report file generated)"}
|
|
160
|
+
logger.info(`Completed review for ${displayId}: ${outputLabels.join(" | ") || "(no report file generated)"}`, {
|
|
161
|
+
scope: "review",
|
|
162
|
+
repository: backend.kind,
|
|
163
|
+
changeId: displayId,
|
|
164
|
+
reviewer: reviewer.displayName,
|
|
165
|
+
console: true
|
|
166
|
+
});
|
|
130
167
|
|
|
131
168
|
return {
|
|
132
169
|
success: true,
|
|
@@ -149,9 +186,12 @@ export async function runReviewCycle(config) {
|
|
|
149
186
|
await ensureDir(config.outputDir);
|
|
150
187
|
|
|
151
188
|
const { backend, targetInfo } = await resolveRepositoryContext(config);
|
|
152
|
-
logger.debug(
|
|
153
|
-
|
|
154
|
-
|
|
189
|
+
logger.debug("Resolved repository context", {
|
|
190
|
+
scope: "session",
|
|
191
|
+
backend: backend.kind,
|
|
192
|
+
target: targetInfo.targetDisplay || config.target,
|
|
193
|
+
console: "debug"
|
|
194
|
+
});
|
|
155
195
|
|
|
156
196
|
let changeIdsToReview = [];
|
|
157
197
|
|
|
@@ -167,7 +207,12 @@ export async function runReviewCycle(config) {
|
|
|
167
207
|
}
|
|
168
208
|
|
|
169
209
|
if (changeIdsToReview.length === 0) {
|
|
170
|
-
logger.info("No changes found to review."
|
|
210
|
+
logger.info("No changes found to review.", {
|
|
211
|
+
scope: "session",
|
|
212
|
+
backend: backend.kind,
|
|
213
|
+
target: targetInfo.targetDisplay || config.target,
|
|
214
|
+
console: true
|
|
215
|
+
});
|
|
171
216
|
return;
|
|
172
217
|
}
|
|
173
218
|
|
|
@@ -176,12 +221,22 @@ export async function runReviewCycle(config) {
|
|
|
176
221
|
const batchSummary = isUncommittedBatch
|
|
177
222
|
? `Reviewing ${backend.displayName} uncommitted changes`
|
|
178
223
|
: `Reviewing ${backend.displayName} ${backend.changeName}s ${formatChangeList(backend, changeIdsToReview)}`;
|
|
179
|
-
logger.info(batchSummary
|
|
224
|
+
logger.info(batchSummary, {
|
|
225
|
+
scope: "session",
|
|
226
|
+
backend: backend.kind,
|
|
227
|
+
count: changeIdsToReview.length,
|
|
228
|
+
console: true
|
|
229
|
+
});
|
|
180
230
|
const progress = createProgressReporter(`${backend.displayName} ${backend.changeName} batch`);
|
|
181
231
|
progress.update(0, `0/${changeIdsToReview.length} completed`);
|
|
182
232
|
|
|
183
233
|
for (const [index, changeId] of changeIdsToReview.entries()) {
|
|
184
|
-
logger.debug(`Starting review for ${backend.formatChangeId(changeId)}
|
|
234
|
+
logger.debug(`Starting review for ${backend.formatChangeId(changeId)}`, {
|
|
235
|
+
scope: "review",
|
|
236
|
+
repository: backend.kind,
|
|
237
|
+
changeId: backend.formatChangeId(changeId),
|
|
238
|
+
console: "debug"
|
|
239
|
+
});
|
|
185
240
|
const displayId = backend.formatChangeId(changeId);
|
|
186
241
|
updateOverallProgress(progress, index, changeIdsToReview.length, 0, `starting ${displayId}`);
|
|
187
242
|
|
package/src/shell.js
CHANGED
|
@@ -24,13 +24,18 @@ export async function runCommand(command, args = [], options = {}) {
|
|
|
24
24
|
debug = false
|
|
25
25
|
} = options;
|
|
26
26
|
|
|
27
|
-
logger.debug(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
logger.debug(`run: ${command}`, {
|
|
28
|
+
scope: "command",
|
|
29
|
+
command,
|
|
30
|
+
args,
|
|
31
|
+
cwd: cwd || "",
|
|
32
|
+
timeoutMs: timeoutMs || 0,
|
|
33
|
+
input: input ? summarizeOutput(input) : "",
|
|
34
|
+
console: debug ? "debug" : false
|
|
35
|
+
});
|
|
32
36
|
|
|
33
37
|
return await new Promise((resolve, reject) => {
|
|
38
|
+
const startedAt = Date.now();
|
|
34
39
|
const child = spawn(command, args, {
|
|
35
40
|
cwd,
|
|
36
41
|
env: {
|
|
@@ -54,7 +59,12 @@ export async function runCommand(command, args = [], options = {}) {
|
|
|
54
59
|
});
|
|
55
60
|
|
|
56
61
|
child.on("error", (err) => {
|
|
57
|
-
logger.error(`spawn error: ${command}`, err
|
|
62
|
+
logger.error(`spawn error: ${command}`, err, {
|
|
63
|
+
scope: "command",
|
|
64
|
+
command,
|
|
65
|
+
args,
|
|
66
|
+
cwd: cwd || ""
|
|
67
|
+
});
|
|
58
68
|
reject(err);
|
|
59
69
|
});
|
|
60
70
|
|
|
@@ -71,14 +81,26 @@ export async function runCommand(command, args = [], options = {}) {
|
|
|
71
81
|
stdout: trim ? stdout.trim() : stdout,
|
|
72
82
|
stderr: trim ? stderr.trim() : stderr
|
|
73
83
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
84
|
+
const durationMs = Date.now() - startedAt;
|
|
85
|
+
|
|
86
|
+
const exitMeta = {
|
|
87
|
+
scope: "command",
|
|
88
|
+
command,
|
|
89
|
+
args,
|
|
90
|
+
cwd: cwd || "",
|
|
91
|
+
code: result.code,
|
|
92
|
+
timedOut: result.timedOut,
|
|
93
|
+
allowFailure,
|
|
94
|
+
durationMs,
|
|
95
|
+
stdout: summarizeOutput(result.stdout),
|
|
96
|
+
stderr: summarizeOutput(result.stderr),
|
|
97
|
+
console: debug ? "debug" : false
|
|
98
|
+
};
|
|
77
99
|
|
|
78
|
-
if (
|
|
79
|
-
logger.error(
|
|
100
|
+
if ((result.code !== 0 || result.timedOut) && !allowFailure) {
|
|
101
|
+
logger.error(`exit: ${command}`, null, exitMeta);
|
|
80
102
|
} else {
|
|
81
|
-
logger.debug(
|
|
103
|
+
logger.debug(`exit: ${command}`, exitMeta);
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
if ((result.code !== 0 || result.timedOut) && !allowFailure) {
|