bunosh 0.3.0 → 0.3.1

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 CHANGED
@@ -373,34 +373,74 @@ Commands:
373
373
  All Bunosh functions are available via `global.bunosh`:
374
374
 
375
375
  ```javascript
376
- const { exec, fetch, writeToFile, copyFile, say, ask, yell, task } = global.bunosh;
376
+ const { exec, shell, fetch, writeToFile, copyFile, say, ask, yell, task } = global.bunosh;
377
377
  ```
378
378
 
379
- ### Shell Execution (`exec`)
379
+ ### Shell Execution
380
380
 
381
- The `exec` function runs shell commands and returns a `TaskResult` object with the command output and status.
381
+ Bunosh provides two ways to execute shell commands:
382
+
383
+ #### `exec` - Universal Shell Execution
384
+
385
+ Best for complex commands, cross-platform compatibility, and when you need real-time streaming output.
386
+
387
+ ```javascript
388
+ // Complex shell commands with pipes and redirections
389
+ await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
390
+ await exec`npm install --verbose`; // Shows progress in real-time
391
+ await exec`docker build . | tee build.log`;
392
+ ```
393
+
394
+ #### `shell` - Native Bun Shell (with Node.js fallback)
395
+
396
+ Best for simple commands when running under Bun for maximum performance.
382
397
 
383
398
  ```javascript
384
- // Simple commands
385
- await exec`echo "Hello World"`;
386
- await exec`npm install`;
399
+ // Simple, fast commands
400
+ await shell`pwd`;
401
+ await shell`echo "Hello World"`;
402
+ await shell`ls -la`;
403
+ await shell`cat package.json`;
404
+ ```
405
+
406
+ #### When to Use Which?
387
407
 
388
- // With environment variables
408
+ **Use `shell` when:**
409
+ - ✅ Running under Bun for optimal performance
410
+ - ✅ Executing simple commands (`pwd`, `ls`, `echo`, `cat`)
411
+ - ✅ Want fastest possible execution
412
+ - ✅ Working with basic file operations
413
+
414
+ **Use `exec` when:**
415
+ - ✅ Need cross-platform compatibility (Node.js + Bun)
416
+ - ✅ Using complex shell features (pipes, redirections, command chaining)
417
+ - ✅ Want real-time streaming output for long-running commands
418
+ - ✅ Running package managers (`npm install`, `docker build`)
419
+
420
+ Both support the same API and return the same `TaskResult` object:
421
+
422
+ ```javascript
423
+ // Both tasks support environment variables
424
+ await shell`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
389
425
  await exec`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
390
426
 
391
- // With working directory
427
+ // Both support working directory changes
428
+ await shell`pwd`.cwd('/tmp');
392
429
  await exec`ls -la`.cwd('/tmp');
393
430
 
394
- // Complex shell commands
395
- await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
431
+ // Choose based on complexity and performance needs
432
+ await shell`cat package.json`; // Simple, fast
433
+ await exec`npm install --verbose`; // Complex, streaming
396
434
  ```
397
435
 
398
436
  #### TaskResult Object
399
437
 
400
- The `exec` function returns a `TaskResult` object with the following properties and methods:
438
+ Both `exec` and `shell` return a `TaskResult` object with the following properties and methods:
401
439
 
402
440
  ```javascript
403
441
  const result = await exec`ls -la`;
442
+ // or
443
+ const result = await shell`ls -la`;
404
444
 
405
445
  // Properties
406
446
  result.status // 'success' or 'fail'
@@ -474,15 +514,203 @@ copyFile('template.js', 'output.js');
474
514
  ```
475
515
 
476
516
  ### User Interaction
517
+
518
+ #### `ask()` - Interactive User Input
519
+
520
+ The `ask()` function provides flexible ways to get user input with smart parameter detection and multiple modes:
521
+
477
522
  ```javascript
478
- // Get user input
523
+ // === SIMPLE SYNTAX WITH SMART DETECTION ===
524
+
525
+ // Basic text input
479
526
  const name = await ask('What is your name?');
480
527
 
528
+ // Text input with default value
529
+ const projectName = await ask('Project name:', 'my-awesome-app');
530
+
531
+ // Boolean confirmation (auto-detects confirm type)
532
+ const shouldContinue = await ask('Continue with deployment?', true);
533
+ const forceUpdate = await ask('Force update?', false);
534
+
535
+ // Number input with default
536
+ const port = await ask('Enter port number:', 3000);
537
+
538
+ // Single choice selection (auto-detects from array)
539
+ const framework = await ask('Choose your framework:', [
540
+ 'React', 'Vue', 'Angular', 'Svelte'
541
+ ]);
542
+
543
+ // Multiple choice selection (array + options)
544
+ const features = await ask('Select features to include:', [
545
+ 'TypeScript', 'ESLint', 'Prettier', 'Tests', 'CI/CD'
546
+ ], { multiple: true });
547
+
548
+ // === ADVANCED OPTIONS SYNTAX ===
549
+
550
+ // Multiline text input (opens system editor)
551
+ const description = await ask('Enter project description:', {
552
+ multiline: true // Same as editor: true
553
+ });
554
+
555
+ // Editor input with default content
556
+ const config = await ask('Edit configuration:', {
557
+ editor: true,
558
+ default: 'Initial content here...'
559
+ });
560
+
561
+ // Password input (hidden)
562
+ const password = await ask('Enter password:', {
563
+ type: 'password'
564
+ });
565
+
566
+ // Mixed: default value + additional options
567
+ const email = await ask('Email address:', 'user@example.com', {
568
+ validate: (input) => input.includes('@') || 'Please enter valid email'
569
+ });
570
+ ```
571
+
572
+ #### Ask Function Signatures
573
+
574
+ ```javascript
575
+ // Smart detection syntax
576
+ ask(question, defaultValue, options?)
577
+ ask(question, choices[], options?)
578
+ ask(question, options)
579
+
580
+ // Examples:
581
+ ask('Name?', 'John') // String default
582
+ ask('Continue?', true) // Boolean -> confirm type
583
+ ask('Port?', 3000) // Number default
584
+ ask('Color?', ['red', 'blue']) // Array -> choices
585
+ ask('Colors?', ['red', 'blue'], { multiple: true }) // Array + options
586
+ ```
587
+
588
+ #### Ask Options Reference
589
+
590
+ | Parameter/Option | Type | Description | Example |
591
+ |------------------|------|-------------|---------|
592
+ | **Smart Detection** | | |
593
+ | `defaultValue` | String/Number | Sets default value for text/number input | `'John'`, `3000` |
594
+ | `defaultValue` | Boolean | Auto-detects as confirmation prompt | `true`, `false` |
595
+ | `choices` | Array | Auto-detects as selection list | `['A', 'B', 'C']` |
596
+ | **Options Object** | | |
597
+ | `multiple` | Boolean | Enables multiple selections (requires `choices`) | `true` |
598
+ | `multiline` | Boolean | Opens system editor for multi-line input | `true` |
599
+ | `editor` | Boolean | Opens system editor for multi-line input (same as `multiline`) | `true` |
600
+ | `default` | Any | Default value or content (when using options object) | `'default value'` |
601
+ | `type` | String | Input type: `'input'`, `'confirm'`, `'password'`, `'number'` | `'password'` |
602
+ | `validate` | Function | Custom validation function | `(input) => input.length > 0` |
603
+
604
+ #### Advanced Ask Examples
605
+
606
+ ```javascript
607
+ /**
608
+ * Interactive project setup with smart syntax
609
+ */
610
+ export async function setupProject() {
611
+ // Simple syntax with smart detection
612
+ const projectName = await ask('Project name:', 'my-awesome-project');
613
+
614
+ const projectType = await ask('Project type:', [
615
+ 'Web App', 'API', 'CLI Tool', 'Library'
616
+ ]);
617
+
618
+ const dependencies = await ask('Select dependencies:', [
619
+ 'express', 'lodash', 'axios', 'moment', 'uuid'
620
+ ], { multiple: true });
621
+
622
+ const useTypescript = await ask('Use TypeScript?', false);
623
+
624
+ // Editor input for complex configuration
625
+ const packageJson = await ask('Customize package.json:', {
626
+ editor: true,
627
+ default: JSON.stringify({
628
+ name: projectName,
629
+ version: '1.0.0',
630
+ description: '',
631
+ dependencies: {}
632
+ }, null, 2)
633
+ });
634
+
635
+ say(`Creating ${projectType}: ${projectName}`);
636
+ say(`Dependencies: ${dependencies.join(', ')}`);
637
+ say(`TypeScript: ${useTypescript ? 'Yes' : 'No'}`);
638
+
639
+ writeToFile('package.json', packageJson);
640
+ }
641
+
642
+ /**
643
+ * Git commit with editor input
644
+ */
645
+ export async function interactiveCommit() {
646
+ const message = await ask('Enter commit message:', {
647
+ editor: true,
648
+ default: 'feat: \n\n# Write your commit message above\n# First line: brief summary (50 chars max)\n# Blank line, then detailed explanation'
649
+ });
650
+
651
+ await exec`git commit -m "${message}"`;
652
+ say('✅ Committed successfully!');
653
+ }
654
+
655
+ /**
656
+ * Database migration with smart syntax
657
+ */
658
+ export async function migrate() {
659
+ // Smart array detection for choices
660
+ const action = await ask('Migration action:', [
661
+ 'Run pending migrations',
662
+ 'Rollback last migration',
663
+ 'Reset database',
664
+ 'Create new migration'
665
+ ]);
666
+
667
+ if (action === 'Reset database') {
668
+ // Smart boolean detection for confirmation
669
+ const confirmed = await ask('⚠️ This will DELETE ALL DATA. Are you sure?', false);
670
+
671
+ if (!confirmed) {
672
+ say('Migration cancelled');
673
+ return;
674
+ }
675
+ }
676
+
677
+ // Execute migration based on selection...
678
+ }
679
+
680
+ /**
681
+ * Server configuration with mixed smart syntax
682
+ */
683
+ export async function configureServer() {
684
+ // Simple defaults
685
+ const serverName = await ask('Server name:', 'my-server');
686
+ const port = await ask('Port number:', 8080);
687
+ const enableHTTPS = await ask('Enable HTTPS?', true);
688
+
689
+ // Array with additional options
690
+ const databases = await ask('Select databases to connect:', [
691
+ 'PostgreSQL', 'MongoDB', 'Redis', 'MySQL'
692
+ ], { multiple: true });
693
+
694
+ // Mix of default + validation
695
+ const adminEmail = await ask('Admin email:', 'admin@example.com', {
696
+ validate: (email) => email.includes('@') || 'Please enter a valid email'
697
+ });
698
+
699
+ say(`Configuring ${serverName} on port ${port}`);
700
+ say(`HTTPS: ${enableHTTPS ? 'Enabled' : 'Disabled'}`);
701
+ say(`Databases: ${databases.join(', ')}`);
702
+ say(`Admin: ${adminEmail}`);
703
+ }
704
+ ```
705
+
706
+ #### Output Functions
707
+
708
+ ```javascript
481
709
  // Output messages
482
- say('Building project...'); // Normal output
483
- yell('BUILD COMPLETE!'); // Emphasized output
710
+ say('Building project...'); // Normal output with !
711
+ yell('BUILD COMPLETE!'); // Emphasized ASCII art output
484
712
 
485
- // Wrap long operations
713
+ // Wrap long operations with progress
486
714
  await task('Installing dependencies', async () => {
487
715
  await exec`npm install`;
488
716
  });
package/index.js CHANGED
@@ -1,4 +1,5 @@
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";
@@ -6,19 +7,21 @@ import ai from "./src/tasks/ai.js";
6
7
  import { ask, yell, say } from "./src/io.js";
7
8
  import { task, stopOnFail, ignoreFail } from "./src/task.js";
8
9
 
9
- export { exec, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
10
-
10
+ export { exec, shell, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
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,
@@ -27,6 +30,6 @@ global.bunosh = {
27
30
  task,
28
31
  buildCmd,
29
32
  $: exec,
30
- }
33
+ };
31
34
 
32
35
  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.1",
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
  },
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/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);
@@ -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
+ }