@jhorst11/wt 2.0.2 → 2.1.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/dist/bin/wt.d.ts +3 -0
- package/dist/bin/wt.d.ts.map +1 -0
- package/dist/bin/wt.js +83 -0
- package/dist/bin/wt.js.map +1 -0
- package/dist/src/commands.d.ts +9 -0
- package/dist/src/commands.d.ts.map +1 -0
- package/dist/src/commands.js +924 -0
- package/dist/src/commands.js.map +1 -0
- package/dist/src/config.d.ts +51 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +384 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/git.d.ts +55 -0
- package/dist/src/git.d.ts.map +1 -0
- package/dist/src/git.js +387 -0
- package/dist/src/git.js.map +1 -0
- package/dist/src/setup.d.ts +8 -0
- package/dist/src/setup.d.ts.map +1 -0
- package/dist/src/setup.js +245 -0
- package/dist/src/setup.js.map +1 -0
- package/dist/src/types.d.ts +64 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/ui.d.ts +93 -0
- package/dist/src/ui.d.ts.map +1 -0
- package/dist/src/ui.js +273 -0
- package/dist/src/ui.js.map +1 -0
- package/package.json +20 -6
- package/bin/wt.js +0 -88
- package/shell/wt.sh +0 -66
- package/src/commands.js +0 -1019
- package/src/config.js +0 -426
- package/src/git.js +0 -416
- package/src/setup.js +0 -267
- package/src/ui.js +0 -302
package/src/config.js
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { spawn } from 'child_process';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
|
-
import { once } from 'events';
|
|
6
|
-
|
|
7
|
-
const CONFIG_DIR = '.wt';
|
|
8
|
-
const CONFIG_FILE = 'config.json';
|
|
9
|
-
const WORKTREE_COLORS_FILE = 'worktree-colors.json';
|
|
10
|
-
|
|
11
|
-
/** Distinct hex colors (with #) for worktree tab/UI; cycle through for unique assignment. */
|
|
12
|
-
export const WORKTREE_COLORS_PALETTE = [
|
|
13
|
-
'#E53935', '#D81B60', '#8E24AA', '#5E35B1', '#3949AB', '#1E88E5', '#039BE5', '#00ACC1',
|
|
14
|
-
'#00897B', '#43A047', '#7CB342', '#C0CA33', '#FDD835', '#FFB300', '#FB8C00', '#F4511E',
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
function getWorktreeColorsPath(repoRoot) {
|
|
18
|
-
return join(repoRoot, CONFIG_DIR, WORKTREE_COLORS_FILE);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Load worktree name → hex color map from repo's .wt/worktree-colors.json.
|
|
23
|
-
* @returns {Record<string, string>}
|
|
24
|
-
*/
|
|
25
|
-
export function loadWorktreeColors(repoRoot) {
|
|
26
|
-
const path = getWorktreeColorsPath(repoRoot);
|
|
27
|
-
try {
|
|
28
|
-
const raw = readFileSync(path, 'utf8');
|
|
29
|
-
const data = JSON.parse(raw);
|
|
30
|
-
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
|
|
31
|
-
const out = {};
|
|
32
|
-
for (const [name, hex] of Object.entries(data)) {
|
|
33
|
-
if (typeof name === 'string' && typeof hex === 'string' && /^#[0-9A-Fa-f]{6}$/.test(hex)) {
|
|
34
|
-
out[name] = hex;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
}
|
|
39
|
-
} catch {
|
|
40
|
-
// file missing or invalid
|
|
41
|
-
}
|
|
42
|
-
return {};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Save worktree name → hex color map to repo's .wt/worktree-colors.json.
|
|
47
|
-
*/
|
|
48
|
-
export function saveWorktreeColors(repoRoot, mapping) {
|
|
49
|
-
const dir = join(repoRoot, CONFIG_DIR);
|
|
50
|
-
const path = getWorktreeColorsPath(repoRoot);
|
|
51
|
-
try {
|
|
52
|
-
mkdirSync(dir, { recursive: true });
|
|
53
|
-
writeFileSync(path, JSON.stringify(mapping, null, 2) + '\n', 'utf8');
|
|
54
|
-
} catch {
|
|
55
|
-
// ignore write errors (e.g. read-only repo)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Assign a unique color to a new worktree. Checks config overrides first, then
|
|
61
|
-
* uses first palette color not used by existing worktrees.
|
|
62
|
-
* Persists and returns the hex color (e.g. "#E53935").
|
|
63
|
-
*/
|
|
64
|
-
export function assignWorktreeColor(repoRoot, worktreeName) {
|
|
65
|
-
const current = loadWorktreeColors(repoRoot);
|
|
66
|
-
|
|
67
|
-
// Check if already assigned
|
|
68
|
-
let hex = current[worktreeName];
|
|
69
|
-
if (hex) return hex;
|
|
70
|
-
|
|
71
|
-
// Check config override
|
|
72
|
-
const config = resolveConfig(process.cwd(), repoRoot);
|
|
73
|
-
if (config.worktreeColors?.[worktreeName]) {
|
|
74
|
-
hex = config.worktreeColors[worktreeName];
|
|
75
|
-
current[worktreeName] = hex;
|
|
76
|
-
saveWorktreeColors(repoRoot, current);
|
|
77
|
-
return hex;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Auto-assign from palette (prefer custom palette if configured)
|
|
81
|
-
const palette = config.colorPalette || WORKTREE_COLORS_PALETTE;
|
|
82
|
-
const usedColors = new Set(Object.values(current));
|
|
83
|
-
|
|
84
|
-
for (const c of palette) {
|
|
85
|
-
if (!usedColors.has(c)) {
|
|
86
|
-
hex = c;
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
hex = hex || palette[usedColors.size % palette.length];
|
|
92
|
-
current[worktreeName] = hex;
|
|
93
|
-
saveWorktreeColors(repoRoot, current);
|
|
94
|
-
return hex;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Get the assigned hex color for a worktree, or null if none.
|
|
99
|
-
*/
|
|
100
|
-
export function getWorktreeColor(repoRoot, worktreeName) {
|
|
101
|
-
const current = loadWorktreeColors(repoRoot);
|
|
102
|
-
return current[worktreeName] ?? null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Remove a worktree's color assignment so the color can be reused.
|
|
107
|
-
*/
|
|
108
|
-
export function removeWorktreeColor(repoRoot, worktreeName) {
|
|
109
|
-
const current = loadWorktreeColors(repoRoot);
|
|
110
|
-
if (worktreeName in current) {
|
|
111
|
-
delete current[worktreeName];
|
|
112
|
-
saveWorktreeColors(repoRoot, current);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Load configuration from .wt/config.json or config.json at the given directory.
|
|
118
|
-
* Tries .wt/config.json first (for repo/directory configs), then config.json (for global configs).
|
|
119
|
-
* Returns an object with defaults for any missing fields.
|
|
120
|
-
*/
|
|
121
|
-
export function loadConfig(dirPath) {
|
|
122
|
-
const configPaths = [
|
|
123
|
-
join(dirPath, CONFIG_DIR, CONFIG_FILE), // .wt/config.json (for repo/directory)
|
|
124
|
-
join(dirPath, CONFIG_FILE), // config.json (for global config dir)
|
|
125
|
-
];
|
|
126
|
-
const defaults = {
|
|
127
|
-
projectsDir: undefined,
|
|
128
|
-
worktreesDir: undefined,
|
|
129
|
-
branchPrefix: undefined,
|
|
130
|
-
hooks: {},
|
|
131
|
-
worktreeColors: {},
|
|
132
|
-
colorPalette: undefined,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
let raw;
|
|
136
|
-
for (const configPath of configPaths) {
|
|
137
|
-
try {
|
|
138
|
-
raw = readFileSync(configPath, 'utf8');
|
|
139
|
-
break;
|
|
140
|
-
} catch {
|
|
141
|
-
// Try next path
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (!raw) {
|
|
146
|
-
return defaults;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
let parsed;
|
|
150
|
-
try {
|
|
151
|
-
parsed = JSON.parse(raw);
|
|
152
|
-
} catch {
|
|
153
|
-
return defaults;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
157
|
-
return defaults;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const result = { ...defaults };
|
|
161
|
-
|
|
162
|
-
if (typeof parsed.projectsDir === 'string') {
|
|
163
|
-
result.projectsDir = parsed.projectsDir;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (typeof parsed.worktreesDir === 'string') {
|
|
167
|
-
result.worktreesDir = parsed.worktreesDir;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (typeof parsed.branchPrefix === 'string') {
|
|
171
|
-
result.branchPrefix = parsed.branchPrefix;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (typeof parsed.hooks === 'object' && parsed.hooks !== null && !Array.isArray(parsed.hooks)) {
|
|
175
|
-
for (const [hookName, commands] of Object.entries(parsed.hooks)) {
|
|
176
|
-
if (Array.isArray(commands) && commands.every((c) => typeof c === 'string')) {
|
|
177
|
-
result.hooks[hookName] = commands;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (typeof parsed.worktreeColors === 'object' && parsed.worktreeColors !== null && !Array.isArray(parsed.worktreeColors)) {
|
|
183
|
-
for (const [name, hex] of Object.entries(parsed.worktreeColors)) {
|
|
184
|
-
if (typeof name === 'string' && typeof hex === 'string' && /^#[0-9A-Fa-f]{6}$/.test(hex)) {
|
|
185
|
-
result.worktreeColors[name] = hex;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (Array.isArray(parsed.colorPalette)) {
|
|
191
|
-
const validColors = parsed.colorPalette.filter(hex =>
|
|
192
|
-
typeof hex === 'string' && /^#[0-9A-Fa-f]{6}$/.test(hex)
|
|
193
|
-
);
|
|
194
|
-
if (validColors.length > 0) {
|
|
195
|
-
result.colorPalette = validColors;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Find all config files from global to cwd within repo boundaries.
|
|
204
|
-
* Walks up from cwd to repoRoot, collecting all .wt/config.json paths.
|
|
205
|
-
*
|
|
206
|
-
* @param {string} cwd - Current working directory
|
|
207
|
-
* @param {string} repoRoot - Git repository root
|
|
208
|
-
* @param {string} [globalConfigPath] - Path to global config (default: ~/.wt/config.json)
|
|
209
|
-
* @returns {string[]} Array of config file paths (global first)
|
|
210
|
-
*/
|
|
211
|
-
function findConfigFiles(cwd, repoRoot, globalConfigPath = join(homedir(), '.wt', CONFIG_FILE)) {
|
|
212
|
-
const paths = [];
|
|
213
|
-
|
|
214
|
-
// Add global config if it exists
|
|
215
|
-
try {
|
|
216
|
-
readFileSync(globalConfigPath, 'utf8');
|
|
217
|
-
paths.push(globalConfigPath);
|
|
218
|
-
} catch {
|
|
219
|
-
// Global config doesn't exist, that's fine
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Build list of directories from repoRoot down to cwd
|
|
223
|
-
// Normalize paths for comparison
|
|
224
|
-
const normalizedCwd = cwd.replace(/\/$/, '');
|
|
225
|
-
const normalizedRepoRoot = repoRoot.replace(/\/$/, '');
|
|
226
|
-
|
|
227
|
-
if (!normalizedCwd.startsWith(normalizedRepoRoot)) {
|
|
228
|
-
// cwd is outside repo root, skip walking
|
|
229
|
-
return paths;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Collect config paths from repoRoot up to cwd
|
|
233
|
-
const configPaths = [];
|
|
234
|
-
|
|
235
|
-
// Start at repoRoot
|
|
236
|
-
let current = normalizedRepoRoot;
|
|
237
|
-
const parts = normalizedCwd.slice(normalizedRepoRoot.length).split('/').filter(Boolean);
|
|
238
|
-
|
|
239
|
-
// Add repo root config
|
|
240
|
-
const repoConfigPath = join(normalizedRepoRoot, CONFIG_DIR, CONFIG_FILE);
|
|
241
|
-
try {
|
|
242
|
-
readFileSync(repoConfigPath, 'utf8');
|
|
243
|
-
configPaths.push(repoConfigPath);
|
|
244
|
-
} catch {
|
|
245
|
-
// No config at repo root
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Add configs for each directory down to cwd
|
|
249
|
-
for (const part of parts) {
|
|
250
|
-
current = join(current, part);
|
|
251
|
-
const configPath = join(current, CONFIG_DIR, CONFIG_FILE);
|
|
252
|
-
try {
|
|
253
|
-
readFileSync(configPath, 'utf8');
|
|
254
|
-
configPaths.push(configPath);
|
|
255
|
-
} catch {
|
|
256
|
-
// No config at this directory
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return paths.concat(configPaths);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Merge multiple config objects with last-wins strategy.
|
|
265
|
-
* For scalar fields, last defined value wins.
|
|
266
|
-
* For hooks object, merge all hook definitions.
|
|
267
|
-
*
|
|
268
|
-
* @param {Object[]} configs - Array of config objects (least specific first)
|
|
269
|
-
* @returns {Object} Merged config
|
|
270
|
-
*/
|
|
271
|
-
function mergeConfigs(configs) {
|
|
272
|
-
const result = {
|
|
273
|
-
projectsDir: undefined,
|
|
274
|
-
worktreesDir: undefined,
|
|
275
|
-
branchPrefix: undefined,
|
|
276
|
-
hooks: {},
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
for (const config of configs) {
|
|
280
|
-
if (config.projectsDir !== undefined) {
|
|
281
|
-
result.projectsDir = config.projectsDir;
|
|
282
|
-
}
|
|
283
|
-
if (config.worktreesDir !== undefined) {
|
|
284
|
-
result.worktreesDir = config.worktreesDir;
|
|
285
|
-
}
|
|
286
|
-
if (config.branchPrefix !== undefined) {
|
|
287
|
-
result.branchPrefix = config.branchPrefix;
|
|
288
|
-
}
|
|
289
|
-
if (config.hooks && typeof config.hooks === 'object') {
|
|
290
|
-
for (const [hookName, commands] of Object.entries(config.hooks)) {
|
|
291
|
-
result.hooks[hookName] = commands;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return result;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Resolve hierarchical config by walking up from cwd to repoRoot.
|
|
301
|
-
* Returns merged config with defaults for any missing fields.
|
|
302
|
-
*
|
|
303
|
-
* @param {string} [cwd] - Current working directory (default: process.cwd())
|
|
304
|
-
* @param {string} repoRoot - Git repository root
|
|
305
|
-
* @param {string} [globalConfigPath] - Override global config path (for testing)
|
|
306
|
-
* @returns {Object} Resolved config with all fields
|
|
307
|
-
*/
|
|
308
|
-
export function resolveConfig(cwd = process.cwd(), repoRoot, globalConfigPath) {
|
|
309
|
-
const defaults = {
|
|
310
|
-
projectsDir: join(homedir(), 'projects'),
|
|
311
|
-
worktreesDir: join(homedir(), 'projects', 'worktrees'),
|
|
312
|
-
branchPrefix: '',
|
|
313
|
-
hooks: {},
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// Determine the effective global config path
|
|
317
|
-
const effectiveGlobalConfigPath = globalConfigPath || join(homedir(), '.wt', CONFIG_FILE);
|
|
318
|
-
|
|
319
|
-
// Find all config files from global to cwd
|
|
320
|
-
const configPaths = findConfigFiles(cwd, repoRoot, effectiveGlobalConfigPath);
|
|
321
|
-
|
|
322
|
-
// Load each config file by extracting the directory path
|
|
323
|
-
const configs = configPaths.map((path) => {
|
|
324
|
-
// For global config, extract the directory containing it
|
|
325
|
-
if (path === effectiveGlobalConfigPath) {
|
|
326
|
-
const globalConfigDir = path.endsWith(CONFIG_FILE)
|
|
327
|
-
? path.slice(0, path.lastIndexOf('/'))
|
|
328
|
-
: path;
|
|
329
|
-
return loadConfig(globalConfigDir);
|
|
330
|
-
}
|
|
331
|
-
// For other configs at /path/.wt/config.json, directory is /path
|
|
332
|
-
const dirPath = path.slice(0, path.lastIndexOf('/.wt'));
|
|
333
|
-
return loadConfig(dirPath);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// Merge configs
|
|
337
|
-
const merged = mergeConfigs(configs);
|
|
338
|
-
|
|
339
|
-
// Apply defaults for missing fields
|
|
340
|
-
return {
|
|
341
|
-
projectsDir: merged.projectsDir ?? defaults.projectsDir,
|
|
342
|
-
worktreesDir: merged.worktreesDir ?? defaults.worktreesDir,
|
|
343
|
-
branchPrefix: merged.branchPrefix ?? defaults.branchPrefix,
|
|
344
|
-
hooks: merged.hooks,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const HOOK_TIMEOUT_MS = 300_000; // 5 minutes per command
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Run hook commands sequentially. Each command runs with cwd set to `wtPath`
|
|
352
|
-
* and receives WT_SOURCE, WT_BRANCH, and WT_PATH as environment variables.
|
|
353
|
-
*
|
|
354
|
-
* Options:
|
|
355
|
-
* - verbose: if true, stream stdout/stderr to the terminal; if false, suppress output and only report results.
|
|
356
|
-
* - onCommandStart(cmd, index, total): called before each command (e.g. to update a spinner).
|
|
357
|
-
*
|
|
358
|
-
* Returns an array of { command, success, error? } results.
|
|
359
|
-
* Hook failures are non-fatal — they produce warnings but don't throw.
|
|
360
|
-
*/
|
|
361
|
-
export async function runHooks(hookName, config, { source, path: wtPath, branch, name: wtName, color: wtColor }, options = {}) {
|
|
362
|
-
const commands = config.hooks?.[hookName];
|
|
363
|
-
if (!commands || commands.length === 0) return [];
|
|
364
|
-
|
|
365
|
-
const { verbose = false, onCommandStart } = options;
|
|
366
|
-
const total = commands.length;
|
|
367
|
-
|
|
368
|
-
const env = {
|
|
369
|
-
...process.env,
|
|
370
|
-
WT_SOURCE: source,
|
|
371
|
-
WT_BRANCH: branch,
|
|
372
|
-
WT_PATH: wtPath,
|
|
373
|
-
...(wtName !== undefined && { WT_NAME: wtName }),
|
|
374
|
-
...(wtColor !== undefined && wtColor !== null && { WT_COLOR: wtColor }),
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
const results = [];
|
|
378
|
-
|
|
379
|
-
for (let i = 0; i < commands.length; i++) {
|
|
380
|
-
const cmd = commands[i];
|
|
381
|
-
if (typeof onCommandStart === 'function') {
|
|
382
|
-
onCommandStart(cmd, i + 1, total);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const child = spawn(cmd, [], {
|
|
386
|
-
shell: true,
|
|
387
|
-
cwd: wtPath,
|
|
388
|
-
env,
|
|
389
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const stderrChunks = [];
|
|
393
|
-
if (verbose) {
|
|
394
|
-
child.stdout.pipe(process.stdout);
|
|
395
|
-
child.stderr.on('data', (chunk) => {
|
|
396
|
-
process.stderr.write(chunk);
|
|
397
|
-
stderrChunks.push(chunk);
|
|
398
|
-
});
|
|
399
|
-
} else {
|
|
400
|
-
child.stdout.on('data', () => {}); // consume to avoid blocking the child
|
|
401
|
-
child.stderr.on('data', (chunk) => stderrChunks.push(chunk));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const timeoutId = setTimeout(() => {
|
|
405
|
-
child.kill('SIGTERM');
|
|
406
|
-
}, HOOK_TIMEOUT_MS);
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
const [code, signal] = await once(child, 'exit');
|
|
410
|
-
clearTimeout(timeoutId);
|
|
411
|
-
if (code === 0 && !signal) {
|
|
412
|
-
results.push({ command: cmd, success: true });
|
|
413
|
-
} else {
|
|
414
|
-
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
415
|
-
const detail = stderr || (signal ? `Killed by ${signal}` : `Exited with code ${code}`);
|
|
416
|
-
results.push({ command: cmd, success: false, error: detail });
|
|
417
|
-
}
|
|
418
|
-
} catch (err) {
|
|
419
|
-
clearTimeout(timeoutId);
|
|
420
|
-
const stderr = Buffer.concat(stderrChunks).toString().trim();
|
|
421
|
-
results.push({ command: cmd, success: false, error: stderr || err.message });
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return results;
|
|
426
|
-
}
|