@sandro-sikic/maker 1.0.10 → 1.0.11

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/docs/API.md CHANGED
@@ -21,13 +21,17 @@ Exits with code 1 if not running in an interactive terminal (TTY).
21
21
  Execute shell commands with streaming output.
22
22
 
23
23
  ```javascript
24
- const result = await run('npm install', { maxLines: 10000 });
24
+ const result = await run('npm install', {
25
+ maxLines: 10000,
26
+ cwd: process.cwd(),
27
+ });
25
28
  ```
26
29
 
27
30
  **Parameters:**
28
31
 
29
32
  - `command` - Shell command string (required)
30
- - `opts.maxLines` - Max output lines to capture (default: 10000)
33
+ - `opts.maxLines` - Max output lines to capture (default: 10000). This option is used only for capturing output and is not forwarded to the spawned process.
34
+ - `opts` (other properties) - Any additional top-level properties are forwarded directly to `child_process.spawn` (e.g. `cwd`, `env`, `stdio`).
31
35
 
32
36
  **Returns:**
33
37
 
package/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export type RunResult = {
11
11
 
12
12
  export function run(
13
13
  command: string,
14
- opts?: { maxLines?: number },
14
+ opts?: Partial<import('child_process').SpawnOptions> & { maxLines?: number },
15
15
  ): Promise<RunResult>;
16
16
 
17
17
  export function onExit(cb: () => void | Promise<void>): () => void;
package/index.js CHANGED
@@ -18,9 +18,10 @@ function init() {
18
18
  * task; if you `await run(...)` (use inside an async function) the caller will wait for completion
19
19
  * and the call behaves like a foreground task.
20
20
  *
21
- * @param {string} command - Shell command to execute (will be run with `shell: true`).
21
+ * @param {string} command - Shell command to execute.
22
22
  * @param {Object} [opts]
23
- * @param {number} [opts.maxLines=10000] - Maximum number of lines to retain in captured output.
23
+ * @param {number} [opts.maxLines=10000] - Maximum number of lines to retain in captured output. (capture-only; not forwarded to the spawned process)
24
+ * @param {Object} [opts] - Options forwarded directly to `child_process.spawn` (e.g. `cwd`, `env`, `stdio`).
24
25
  * @returns {Promise<{output:string, stdout:string, stderr:string, code:number|null, isError:boolean, error:Error|null}>}
25
26
  */
26
27
  async function run(command, opts = {}) {
@@ -28,10 +29,19 @@ async function run(command, opts = {}) {
28
29
  throw new TypeError('run() requires a non-empty string command');
29
30
  }
30
31
 
31
- const { maxLines = 10000 } = opts;
32
+ // extract maxLines (used for capturing) and forward the rest of opts directly to spawn
33
+ const { maxLines = 10000, ...forwardedOpts } = opts;
34
+
32
35
  // import spawn at runtime so test mocks (vi.mock) take effect per-test
33
36
  const { spawn } = await import('child_process');
34
37
 
38
+ // Merge defaults with the caller-provided options. Caller options are forwarded
39
+ // directly to spawn; `maxLines` is used only for output capture (not forwarded).
40
+ const spawnOpts = Object.assign(
41
+ { shell: true, stdio: 'pipe' },
42
+ forwardedOpts,
43
+ );
44
+
35
45
  function trimToLastNLines(s, n) {
36
46
  if (!s) return s;
37
47
  // remove trailing newlines for accurate line counting
@@ -44,7 +54,7 @@ async function run(command, opts = {}) {
44
54
  }
45
55
 
46
56
  return new Promise((resolve) => {
47
- const child = spawn(command, { shell: true, stdio: 'pipe' });
57
+ const child = spawn(command, spawnOpts);
48
58
 
49
59
  let stdout = '';
50
60
  let stderr = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sandro-sikic/maker",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "type": "module",
5
5
  "description": "A lightweight library for building interactive command-line tools with prompts, command execution, spinners, and graceful shutdown handling",
6
6
  "main": "index.js",
@@ -405,6 +405,63 @@ describe('run() - additional edge cases', () => {
405
405
  vi.resetModules();
406
406
  });
407
407
 
408
+ it('forwards opts directly to spawn (no alias)', async () => {
409
+ vi.resetModules();
410
+ let captured;
411
+ vi.doMock('child_process', () => ({
412
+ spawn: (cmd, spawnOpts) => {
413
+ captured = spawnOpts;
414
+ const child = {
415
+ stdout: { on: () => {} },
416
+ stderr: { on: () => {} },
417
+ on: (ev, cb) => {
418
+ if (ev === 'close') cb(0);
419
+ },
420
+ };
421
+ return child;
422
+ },
423
+ }));
424
+ const { run: mockedRun } = await import('../index.js');
425
+ const res = await mockedRun('anything', {
426
+ cwd: '/tmp',
427
+ env: { FOO: 'bar' },
428
+ });
429
+ expect(captured.cwd).toBe('/tmp');
430
+ expect(captured.env).toEqual(expect.objectContaining({ FOO: 'bar' }));
431
+ expect(captured.shell).toBe(true);
432
+ expect(captured.stdio).toBe('pipe');
433
+ expect(res.code).toBe(0);
434
+ vi.resetModules();
435
+ });
436
+
437
+ it('forwards top-level spawn options (maxLines is capture-only)', async () => {
438
+ vi.resetModules();
439
+ let captured;
440
+ vi.doMock('child_process', () => ({
441
+ spawn: (cmd, spawnOpts) => {
442
+ captured = spawnOpts;
443
+ const child = {
444
+ stdout: { on: () => {} },
445
+ stderr: { on: () => {} },
446
+ on: (ev, cb) => {
447
+ if (ev === 'close') cb(0);
448
+ },
449
+ };
450
+ return child;
451
+ },
452
+ }));
453
+ const { run: mockedRun } = await import('../index.js');
454
+ const res = await mockedRun('anything', {
455
+ cwd: '/cwd',
456
+ maxLines: 5,
457
+ shell: false,
458
+ });
459
+ expect(captured.cwd).toBe('/cwd');
460
+ expect(captured.maxLines).toBeUndefined();
461
+ expect(captured.shell).toBe(false);
462
+ expect(res.code).toBe(0);
463
+ vi.resetModules();
464
+ });
408
465
  it('handles multiple data chunks correctly', async () => {
409
466
  vi.resetModules();
410
467
  vi.doMock('child_process', () => ({