bunosh 0.3.0 → 0.3.2

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/bunosh.js CHANGED
@@ -60,6 +60,30 @@ import(tasksFile).then((tasks) => {
60
60
  handleBunoshfileError(error, tasksFile);
61
61
  });
62
62
 
63
+ // Handle unhandled promise rejections
64
+ process.on('unhandledRejection', (reason, promise) => {
65
+ if (!process.env.BUNOSH_COMMAND_STARTED) return;
66
+
67
+ console.error('\n❌ Unhandled Promise Rejection:');
68
+ console.error(reason instanceof Error ? reason.message : reason);
69
+ if (reason instanceof Error && reason.stack && process.env.BUNOSH_DEBUG) {
70
+ console.error(reason.stack);
71
+ }
72
+ process.exit(1);
73
+ });
74
+
75
+ // Handle uncaught exceptions
76
+ process.on('uncaughtException', (error) => {
77
+ if (!process.env.BUNOSH_COMMAND_STARTED) return;
78
+
79
+ console.error('\n❌ Uncaught Exception:');
80
+ console.error(error.message);
81
+ if (error.stack && process.env.BUNOSH_DEBUG) {
82
+ console.error(error.stack);
83
+ }
84
+ process.exit(1);
85
+ });
86
+
63
87
  function handleBunoshfileError(error, filePath) {
64
88
  banner();
65
89
  console.log();
package/index.js CHANGED
@@ -1,32 +1,41 @@
1
1
  import exec from "./src/tasks/exec.js";
2
+ import shell from "./src/tasks/shell.js";
2
3
  import fetch from "./src/tasks/fetch.js";
3
4
  import writeToFile from "./src/tasks/writeToFile.js";
4
5
  import copyFile from "./src/tasks/copyFile.js";
5
6
  import ai from "./src/tasks/ai.js";
6
7
  import { ask, yell, say } from "./src/io.js";
7
- import { task, stopOnFail, ignoreFail } from "./src/task.js";
8
-
9
- export { exec, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
8
+ import { task, tryTask, stopOnFail, ignoreFail, stopOnFailures, ignoreFailures, silence, prints, silent } from "./src/task.js";
10
9
 
10
+ export { exec, shell, fetch, writeToFile, copyFile, ai, ask, yell, say, task, tryTask, stopOnFail, ignoreFail, stopOnFailures, ignoreFailures, silence, prints, silent };
11
11
 
12
12
  export function buildCmd(cmd) {
13
- return function(args) {
14
- return exec`${cmd} ${args}`
15
- }
13
+ return function (args) {
14
+ return exec`${cmd} ${args}`;
15
+ };
16
16
  }
17
17
 
18
18
  global.bunosh = {
19
- ask, yell, say,
19
+ ask,
20
+ yell,
21
+ say,
20
22
  fetch,
21
23
  exec,
24
+ shell,
22
25
  writeToFile,
23
26
  copyFile,
24
27
  ai,
25
28
  stopOnFail,
26
29
  ignoreFail,
27
30
  task,
31
+ try: tryTask,
32
+ stopOnFailures,
33
+ ignoreFailures,
34
+ silence,
35
+ prints,
36
+ silent,
28
37
  buildCmd,
29
38
  $: exec,
30
- }
39
+ };
31
40
 
32
41
  export default global.bunosh;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunosh",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "module": "index.js",
6
6
  "bin": {
@@ -25,7 +25,6 @@
25
25
  "debug": "^4.4.1",
26
26
  "fs-extra": "^11.3.0",
27
27
  "inquirer": "^12.6.3",
28
- "open-editor": "^5.1.0",
29
28
  "timer-node": "^5.0.9",
30
29
  "zod": "^4.1.5"
31
30
  },
@@ -5,6 +5,7 @@ const STATUS_CONFIG = {
5
5
  start: { icon: '▶', color: 'blue' },
6
6
  finish: { icon: '✓', color: 'green' },
7
7
  error: { icon: '✗', color: 'red' },
8
+ warning: { icon: '⚠', color: 'yellow' },
8
9
  output: { icon: ' ', color: 'white' },
9
10
  info: { icon: ' ', color: 'dim' }
10
11
  };
@@ -22,6 +22,10 @@ export class GitHubActionsFormatter extends BaseFormatter {
22
22
  const errorDetails = extra.error ? ` - ${extra.error}` : '';
23
23
  return `::endgroup::\n::error::❌ ${fullTaskName}${errorDetails}`;
24
24
 
25
+ case 'warning':
26
+ const warningDetails = extra.error ? ` - ${extra.error}` : '';
27
+ return `::endgroup::\n::warning::⚠️ ${fullTaskName}${warningDetails}`;
28
+
25
29
  case 'output':
26
30
  return taskName;
27
31
 
package/src/io.js CHANGED
@@ -6,11 +6,65 @@ export function say(...args) {
6
6
  console.log('!', ...args);
7
7
  }
8
8
 
9
- export async function ask(question, opts = {}) {
9
+ export async function ask(question, defaultValueOrOptions = {}, options = {}) {
10
+ // Smart parameter detection
11
+ let opts = {};
12
+
13
+ // If second parameter is not an object, it's a default value
14
+ if (defaultValueOrOptions !== null && typeof defaultValueOrOptions !== 'object') {
15
+ opts.default = defaultValueOrOptions;
16
+ opts = { ...opts, ...options }; // Merge with third parameter options
17
+
18
+ // Auto-detect type based on default value
19
+ if (typeof defaultValueOrOptions === 'boolean') {
20
+ opts.type = 'confirm';
21
+ }
22
+ } else if (Array.isArray(defaultValueOrOptions)) {
23
+ // If it's an array, treat as choices
24
+ opts.choices = defaultValueOrOptions;
25
+ opts = { ...opts, ...options }; // Merge with third parameter options
26
+ } else {
27
+ // Traditional object parameter
28
+ opts = { ...defaultValueOrOptions, ...options };
29
+ }
30
+
31
+ // Route to appropriate handler based on options
32
+ if (opts.editor || opts.multiline) {
33
+ return await askWithEditor(question, opts);
34
+ }
35
+
36
+ if (opts.choices) {
37
+ return await askWithChoices(question, opts);
38
+ }
39
+
10
40
  const answers = await inquirer.prompt({ name: question, message: question, ...opts })
11
41
  return Object.values(answers)[0];
12
42
  }
13
43
 
44
+ async function askWithEditor(question, opts = {}) {
45
+ const answers = await inquirer.prompt({
46
+ name: question,
47
+ message: question,
48
+ type: 'editor',
49
+ ...opts
50
+ });
51
+ return Object.values(answers)[0];
52
+ }
53
+
54
+ async function askWithChoices(question, opts = {}) {
55
+ const promptType = opts.multiple ? 'checkbox' : 'list';
56
+
57
+ const answers = await inquirer.prompt({
58
+ name: question,
59
+ message: question,
60
+ type: promptType,
61
+ choices: opts.choices,
62
+ ...opts
63
+ });
64
+
65
+ return Object.values(answers)[0];
66
+ }
67
+
14
68
  export function yell(text) {
15
69
  console.log();
16
70
 
@@ -0,0 +1,95 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+
4
+ export default function openEditor(files, options = {}) {
5
+ if (!Array.isArray(files) || files.length === 0) {
6
+ throw new Error('Files array is required and cannot be empty');
7
+ }
8
+
9
+ const editor = options.editor || getDefaultEditor();
10
+ const fileArgs = buildEditorArgs(editor, files);
11
+
12
+ return new Promise((resolve, reject) => {
13
+ const child = spawn(editor, fileArgs, {
14
+ stdio: 'inherit',
15
+ detached: true
16
+ });
17
+
18
+ child.on('error', (err) => {
19
+ reject(new Error(`Failed to open editor '${editor}': ${err.message}`));
20
+ });
21
+
22
+ child.on('spawn', () => {
23
+ resolve();
24
+ });
25
+
26
+ if (child.pid) {
27
+ child.unref();
28
+ }
29
+ });
30
+ }
31
+
32
+ function getDefaultEditor() {
33
+ if (process.env.EDITOR) {
34
+ return process.env.EDITOR;
35
+ }
36
+
37
+ const editors = ['code', 'subl', 'atom', 'vim', 'nvim', 'nano', 'gedit'];
38
+
39
+ for (const editor of editors) {
40
+ if (isCommandAvailable(editor)) {
41
+ return editor;
42
+ }
43
+ }
44
+
45
+ return process.platform === 'win32' ? 'notepad' : 'vi';
46
+ }
47
+
48
+ function isCommandAvailable(command) {
49
+ try {
50
+ const { execSync } = require('child_process');
51
+ execSync(`which ${command}`, { stdio: 'ignore' });
52
+ return true;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ function buildEditorArgs(editor, files) {
59
+ const editorName = path.basename(editor);
60
+ const args = [];
61
+
62
+ for (const fileInfo of files) {
63
+ const filePath = typeof fileInfo === 'string' ? fileInfo : fileInfo.file;
64
+
65
+ if (!filePath) {
66
+ continue;
67
+ }
68
+
69
+ if (fileInfo.line && typeof fileInfo === 'object') {
70
+ switch (editorName) {
71
+ case 'code':
72
+ case 'code-insiders':
73
+ args.push('--goto', `${filePath}:${fileInfo.line}:${fileInfo.column || 1}`);
74
+ break;
75
+ case 'subl':
76
+ case 'sublime_text':
77
+ args.push(`${filePath}:${fileInfo.line}:${fileInfo.column || 1}`);
78
+ break;
79
+ case 'vim':
80
+ case 'nvim':
81
+ args.push(`+${fileInfo.line}`, filePath);
82
+ break;
83
+ case 'nano':
84
+ args.push(`+${fileInfo.line}`, filePath);
85
+ break;
86
+ default:
87
+ args.push(filePath);
88
+ }
89
+ } else {
90
+ args.push(filePath);
91
+ }
92
+ }
93
+
94
+ return args;
95
+ }
package/src/printer.js CHANGED
@@ -65,6 +65,17 @@ export class Printer {
65
65
  this.print(taskName, 'error', extra);
66
66
  }
67
67
 
68
+ warning(taskName, error = null, extra = {}) {
69
+ if (this.startTimeout) {
70
+ clearTimeout(this.startTimeout);
71
+ this.startTimeout = null;
72
+ }
73
+ if (error) {
74
+ extra.error = typeof error === 'string' ? error : error.message;
75
+ }
76
+ this.print(taskName, 'warning', extra);
77
+ }
78
+
68
79
  output(line, isError = false) {
69
80
  if (!line.trim()) return;
70
81
 
package/src/program.js CHANGED
@@ -4,7 +4,7 @@ import traverseDefault from "@babel/traverse";
4
4
  const traverse = traverseDefault.default || traverseDefault;
5
5
  import color from "chalk";
6
6
  import fs from 'fs';
7
- import openEditor from 'open-editor';
7
+ import openEditor from './open-editor.js';
8
8
  import { yell } from './io.js';
9
9
  import cprint from "./font.js";
10
10
  import { handleCompletion, detectCurrentShell, installCompletion, getCompletionPaths } from './completion.js';
@@ -251,12 +251,16 @@ export default function bunosh(commands, source) {
251
251
 
252
252
  const editCmd = program.command('edit')
253
253
  .description('Open the bunosh file in your editor. $EDITOR or \'code\' is used.')
254
- .action(() => {
255
- openEditor([{
256
- file: BUNOSHFILE,
257
- }], {
258
- editor: process.env.EDITOR ? null : 'code',
259
- });
254
+ .action(async () => {
255
+ try {
256
+ await openEditor([{
257
+ file: BUNOSHFILE,
258
+ }]);
259
+ } catch (error) {
260
+ console.error(error.message);
261
+ console.error('Set $EDITOR environment variable to use a different editor');
262
+ process.exit(1);
263
+ }
260
264
  });
261
265
 
262
266
  internalCommands.push(editCmd);
package/src/task.js CHANGED
@@ -4,7 +4,8 @@ import Printer from './printer.js';
4
4
  export const TaskStatus = {
5
5
  RUNNING: 'running',
6
6
  FAIL: 'fail',
7
- SUCCESS: 'success'
7
+ SUCCESS: 'success',
8
+ WARNING: 'warning'
8
9
  };
9
10
 
10
11
  export const tasksExecuted = [];
@@ -12,6 +13,7 @@ export const runningTasks = new Map();
12
13
 
13
14
  let taskCounter = 0;
14
15
  let stopFailToggle = true;
16
+ let globalSilenceMode = false;
15
17
  const asyncLocalStorage = new AsyncLocalStorage();
16
18
 
17
19
 
@@ -23,16 +25,50 @@ export function ignoreFail(enable = true) {
23
25
  stopFailToggle = !enable;
24
26
  }
25
27
 
28
+ // Global failure mode control
29
+ // true = stop on failures (exit with code 1), false = continue on failures
30
+ let stopOnFailuresMode = false;
31
+
32
+ export function ignoreFailures() {
33
+ stopOnFailuresMode = false;
34
+ }
35
+
36
+ export function stopOnFailures() {
37
+ stopOnFailuresMode = true;
38
+ }
39
+
40
+ export function silence() {
41
+ globalSilenceMode = true;
42
+ }
43
+
44
+ export function prints() {
45
+ globalSilenceMode = false;
46
+ }
47
+
26
48
  const startTime = Date.now();
27
49
 
28
50
  process.on('exit', (code) => {
29
51
  if (!process.env.BUNOSH_COMMAND_STARTED) return;
30
52
 
31
53
  const totalTime = Date.now() - startTime;
32
- const success = code === 0;
33
54
  const tasksFailed = tasksExecuted.filter(ti => ti.result?.status === TaskStatus.FAIL).length;
55
+ const tasksWarning = tasksExecuted.filter(ti => ti.result?.status === TaskStatus.WARNING).length;
56
+
57
+ // Check if we're in test environment
58
+ const isTestEnvironment = process.env.NODE_ENV === 'test' ||
59
+ typeof Bun?.jest !== 'undefined' ||
60
+ process.argv.some(arg => arg.includes('vitest') || arg.includes('jest') || arg.includes('--test') || arg.includes('test:'));
61
+
62
+ // Set exit code to 1 if any tasks failed AND we're not in ignoreFailures mode AND not in test environment
63
+ // Note: if stopOnFailuresMode is true, we would have already exited immediately
64
+ if (tasksFailed > 0 && !stopOnFailuresMode && !isTestEnvironment) {
65
+ process.exitCode = 1;
66
+ }
67
+
68
+ const finalExitCode = (tasksFailed > 0 && !stopOnFailuresMode && !isTestEnvironment) ? 1 : code;
69
+ const success = finalExitCode === 0;
34
70
 
35
- console.log(`\n🍲 ${success ? '' : 'FAIL '}Exit Code: ${code} | Tasks: ${tasksExecuted.length}${tasksFailed ? ` | Failed: ${tasksFailed}` : ''} | Time: ${totalTime}ms`);
71
+ console.log(`\n🍲 ${success ? '' : 'FAIL '}Exit Code: ${finalExitCode} | Tasks: ${tasksExecuted.length}${tasksFailed ? ` | Failed: ${tasksFailed}` : ''}${tasksWarning ? ` | Warnings: ${tasksWarning}` : ''} | Time: ${totalTime}ms`);
36
72
  });
37
73
 
38
74
  export function getRunningTaskCount() {
@@ -78,7 +114,53 @@ export class TaskInfo {
78
114
  }
79
115
  }
80
116
 
81
- export async function task(name, fn) {
117
+ export async function tryTask(name, fn, isSilent = false) {
118
+ if (!fn) {
119
+ fn = name;
120
+ name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
121
+ }
122
+
123
+ const taskInfo = new TaskInfo(name, Date.now(), TaskStatus.RUNNING);
124
+
125
+ tasksExecuted.push(taskInfo);
126
+ runningTasks.set(taskInfo.id, taskInfo);
127
+
128
+ const shouldPrint = !globalSilenceMode && !isSilent;
129
+ const printer = new Printer('task', taskInfo.id);
130
+ if (shouldPrint) printer.start(name);
131
+
132
+ try {
133
+ const result = await asyncLocalStorage.run(taskInfo.id, async () => {
134
+ return await Promise.resolve(fn());
135
+ });
136
+
137
+ const endTime = Date.now();
138
+ const duration = endTime - taskInfo.startTime;
139
+
140
+ taskInfo.status = TaskStatus.SUCCESS;
141
+ taskInfo.duration = duration;
142
+ taskInfo.result = { status: TaskStatus.SUCCESS, output: result };
143
+
144
+ if (shouldPrint) printer.finish(name);
145
+ runningTasks.delete(taskInfo.id);
146
+
147
+ return true;
148
+ } catch (err) {
149
+ const endTime = Date.now();
150
+ const duration = endTime - taskInfo.startTime;
151
+
152
+ taskInfo.status = TaskStatus.WARNING;
153
+ taskInfo.duration = duration;
154
+ taskInfo.result = { status: TaskStatus.WARNING, output: err.message };
155
+
156
+ if (shouldPrint) printer.warning(name, err);
157
+ runningTasks.delete(taskInfo.id);
158
+
159
+ return false;
160
+ }
161
+ }
162
+
163
+ export async function task(name, fn, isSilent = false) {
82
164
  if (!fn) {
83
165
  fn = name;
84
166
  name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
@@ -89,8 +171,9 @@ export async function task(name, fn) {
89
171
  tasksExecuted.push(taskInfo);
90
172
  runningTasks.set(taskInfo.id, taskInfo);
91
173
 
174
+ const shouldPrint = !globalSilenceMode && !isSilent;
92
175
  const printer = new Printer('task', taskInfo.id);
93
- printer.start(name);
176
+ if (shouldPrint) printer.start(name);
94
177
 
95
178
  try {
96
179
  const result = await asyncLocalStorage.run(taskInfo.id, async () => {
@@ -104,7 +187,7 @@ export async function task(name, fn) {
104
187
  taskInfo.duration = duration;
105
188
  taskInfo.result = { status: TaskStatus.SUCCESS, output: result };
106
189
 
107
- printer.finish(name);
190
+ if (shouldPrint) printer.finish(name);
108
191
  runningTasks.delete(taskInfo.id);
109
192
 
110
193
  return result;
@@ -116,14 +199,20 @@ export async function task(name, fn) {
116
199
  taskInfo.duration = duration;
117
200
  taskInfo.result = { status: TaskStatus.FAIL, output: err.message };
118
201
 
119
- printer.error(name, err);
202
+ if (shouldPrint) printer.error(name, err);
120
203
  runningTasks.delete(taskInfo.id);
121
204
 
122
205
  // Don't exit during testing
123
206
  const isTestEnvironment = process.env.NODE_ENV === 'test' ||
124
207
  typeof Bun?.jest !== 'undefined' ||
125
- process.argv.some(arg => arg.includes('test'));
208
+ process.argv.some(arg => arg.includes('vitest') || arg.includes('jest') || arg.includes('--test') || arg.includes('test:'));
126
209
 
210
+ // Exit immediately if stopOnFailures mode is enabled
211
+ if (stopOnFailuresMode && !isTestEnvironment) {
212
+ process.exit(1);
213
+ }
214
+
215
+ // Also exit if stopFailToggle is enabled (legacy behavior)
127
216
  if (stopFailToggle && !isTestEnvironment) {
128
217
  process.exit(1);
129
218
  }
@@ -132,6 +221,32 @@ export async function task(name, fn) {
132
221
  }
133
222
  }
134
223
 
224
+ export class SilentTaskWrapper {
225
+ constructor() {
226
+ this.silent = true;
227
+ }
228
+
229
+ async try(name, fn) {
230
+ if (!fn) {
231
+ fn = name;
232
+ name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
233
+ }
234
+ return await tryTask(name, fn, true);
235
+ }
236
+
237
+ async task(name, fn) {
238
+ if (!fn) {
239
+ fn = name;
240
+ name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
241
+ }
242
+ return await task(name, fn, true);
243
+ }
244
+ }
245
+
246
+ export function silent() {
247
+ return new SilentTaskWrapper();
248
+ }
249
+
135
250
  export class TaskResult {
136
251
  constructor({ status, output }) {
137
252
  this.status = status;
@@ -146,6 +261,10 @@ export class TaskResult {
146
261
  return this.status === TaskStatus.SUCCESS;
147
262
  }
148
263
 
264
+ get hasWarning() {
265
+ return this.status === TaskStatus.WARNING;
266
+ }
267
+
149
268
  static fail(output = null) {
150
269
  return new TaskResult({ status: TaskStatus.FAIL, output });
151
270
  }
@@ -153,4 +272,8 @@ export class TaskResult {
153
272
  static success(output = null) {
154
273
  return new TaskResult({ status: TaskStatus.SUCCESS, output });
155
274
  }
275
+
276
+ static warning(output = null) {
277
+ return new TaskResult({ status: TaskStatus.WARNING, output });
278
+ }
156
279
  }
@@ -0,0 +1,119 @@
1
+ import { TaskResult, createTaskInfo, finishTaskInfo } from "../task.js";
2
+ import Printer from "../printer.js";
3
+
4
+ const isBun = typeof Bun !== 'undefined' && typeof Bun.spawn === 'function';
5
+
6
+ export default function shell(strings, ...values) {
7
+ const cmd = strings.reduce((accumulator, str, i) => {
8
+ return accumulator + str + (values[i] || "");
9
+ }, "");
10
+
11
+ let envs = null;
12
+ let cwd = null;
13
+
14
+ const cmdPromise = new Promise(async (resolve, reject) => {
15
+ const extraInfo = {};
16
+ if (cwd) extraInfo.cwd = cwd;
17
+ if (envs) extraInfo.env = envs;
18
+
19
+ if (!isBun) {
20
+ const { default: exec } = await import("./exec.js");
21
+ let execPromise = exec(strings, ...values);
22
+ if (envs) execPromise = execPromise.env(envs);
23
+ if (cwd) execPromise = execPromise.cwd(cwd);
24
+ const result = await execPromise;
25
+ resolve(result);
26
+ return;
27
+ }
28
+
29
+ const taskInfo = createTaskInfo(cmd);
30
+ const printer = new Printer("shell", taskInfo.id);
31
+ printer.start(cmd, extraInfo);
32
+
33
+ try {
34
+ const { $ } = await import("bun");
35
+
36
+ let shell = $;
37
+
38
+ if (cwd) {
39
+ shell = shell.cwd(cwd);
40
+ }
41
+
42
+ if (envs) {
43
+ shell = shell.env(envs);
44
+ }
45
+
46
+ let result;
47
+ try {
48
+ result = await shell(strings, ...values);
49
+
50
+ const output = await result.text();
51
+
52
+ printer.finish(cmd);
53
+ finishTaskInfo(taskInfo, true, null, output.trim());
54
+ resolve(TaskResult.success(output.trim()));
55
+ return;
56
+
57
+ } catch (shellError) {
58
+ const isCommandNotFound = shellError.stderr &&
59
+ (shellError.stderr.includes('command not found') ||
60
+ shellError.stderr.includes('bun: command not found'));
61
+
62
+ if (isCommandNotFound) {
63
+ printer.finish(cmd);
64
+ finishTaskInfo(taskInfo, true, null, "fallback to exec");
65
+
66
+ const { default: exec } = await import("./exec.js");
67
+ let execPromise = exec`${cmd}`;
68
+ if (envs) execPromise = execPromise.env(envs);
69
+ if (cwd) execPromise = execPromise.cwd(cwd);
70
+ const result = await execPromise;
71
+ resolve(result);
72
+ return;
73
+ }
74
+
75
+ if (shellError.exitCode !== undefined) {
76
+ const stderr = shellError.stderr ? Buffer.isBuffer(shellError.stderr) ? shellError.stderr.toString() : shellError.stderr : "";
77
+ const stdout = shellError.stdout ? Buffer.isBuffer(shellError.stdout) ? shellError.stdout.toString() : shellError.stdout : "";
78
+ const errorOutput = (stderr + stdout).trim() || `Command failed with exit code ${shellError.exitCode}`;
79
+
80
+ if (errorOutput) {
81
+ const lines = errorOutput.split('\n');
82
+ for (const line of lines) {
83
+ if (line.trim()) {
84
+ printer.output(line, true);
85
+ }
86
+ }
87
+ }
88
+
89
+ const error = new Error(`Exit code: ${shellError.exitCode}`);
90
+ printer.error(cmd, null, { exitCode: shellError.exitCode });
91
+ finishTaskInfo(taskInfo, false, error, errorOutput);
92
+ resolve(TaskResult.fail(errorOutput));
93
+ return;
94
+ } else {
95
+ const errorMessage = shellError.message || shellError.toString();
96
+ printer.error(cmd, shellError);
97
+ finishTaskInfo(taskInfo, false, shellError, errorMessage);
98
+ resolve(TaskResult.fail(errorMessage));
99
+ }
100
+ }
101
+ } catch (error) {
102
+ printer.error(cmd, error);
103
+ finishTaskInfo(taskInfo, false, error, error.message);
104
+ resolve(TaskResult.fail(error.message));
105
+ }
106
+ });
107
+
108
+ cmdPromise.env = (newEnvs) => {
109
+ envs = newEnvs;
110
+ return cmdPromise;
111
+ };
112
+
113
+ cmdPromise.cwd = (newCwd) => {
114
+ cwd = newCwd;
115
+ return cmdPromise;
116
+ };
117
+
118
+ return cmdPromise;
119
+ }