start-command 0.37.7 → 0.37.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # start-command
2
2
 
3
+ ## 0.37.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 9ffcae7: A closed stdout/stderr pipe (`$ -- cmd | head -1`) no longer crashes start with an unhandled `EPIPE` and exit code 1: the command keeps running, its log and record are written, and start exits with the command's own exit code (#222).
8
+
3
9
  ## 0.37.7
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-command",
3
- "version": "0.37.7",
3
+ "version": "0.37.8",
4
4
  "description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
5
5
  "main": "src/bin/cli.js",
6
6
  "exports": {
package/src/bin/cli.js CHANGED
@@ -42,6 +42,17 @@ const { resolveChildExitCode } = require('../lib/exit-reason');
42
42
  const { printUsage } = require('../lib/usage');
43
43
  const { buildDisplayCommand, getCommandName } = require('../lib/shell-utils');
44
44
 
45
+ // A closed stdout/stderr pipe (`$ --version | head -1`) must neither crash
46
+ // start nor replace the command's exit code: drop the failed output and keep
47
+ // running, logging and recording as usual (issue #222).
48
+ for (const stream of [process.stdout, process.stderr]) {
49
+ stream.on('error', (err) => {
50
+ if (err.code !== 'EPIPE' && err.code !== 'ERR_STREAM_DESTROYED') {
51
+ throw err;
52
+ }
53
+ });
54
+ }
55
+
45
56
  // Configuration from environment variables
46
57
  const config = {
47
58
  // Disable automatic issue creation (useful for testing)
@@ -0,0 +1,61 @@
1
+ const { describe, it, expect } = require('bun:test');
2
+ const { spawn } = require('child_process');
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const CLI = path.join(__dirname, '../src/bin/cli.js');
8
+
9
+ // Run the CLI with the read ends of the given output pipes closed before it
10
+ // starts writing, like `$ --version | head -1` once `head` has exited.
11
+ function runWithClosedPipes(args, closed) {
12
+ return new Promise((resolve) => {
13
+ const child = spawn(process.execPath, [CLI, ...args], {
14
+ stdio: ['ignore', 'pipe', 'pipe'],
15
+ env: {
16
+ ...process.env,
17
+ START_APP_FOLDER: fs.mkdtempSync(path.join(os.tmpdir(), 'start-222-')),
18
+ START_DISABLE_AUTO_ISSUE: '1',
19
+ START_DISABLE_LOG_UPLOAD: '1',
20
+ },
21
+ });
22
+ let stderr = '';
23
+ for (const name of ['stdout', 'stderr']) {
24
+ if (closed.includes(name)) {
25
+ child[name].destroy();
26
+ } else {
27
+ child[name].on('data', (chunk) => (stderr += chunk));
28
+ }
29
+ }
30
+ child.on('close', (code) => resolve({ code, stderr }));
31
+ });
32
+ }
33
+
34
+ describe('closed output pipes (#222)', () => {
35
+ if (process.platform === 'win32') {
36
+ return;
37
+ }
38
+ const big = 'seq 1 200000';
39
+ for (const args of [['--version'], ['-v'], ['--help']]) {
40
+ it(`${args[0]} exits 0 when stdout is closed`, async () => {
41
+ const result = await runWithClosedPipes(args, ['stdout']);
42
+ expect(result.stderr).not.toContain('EPIPE');
43
+ expect(result.code).toBe(0);
44
+ }, 15000);
45
+ }
46
+ it('a command keeps its exit code when stdout is closed', async () => {
47
+ const result = await runWithClosedPipes(
48
+ ['--', 'sh', '-c', `${big}; exit 3`],
49
+ ['stdout']
50
+ );
51
+ expect(result.stderr).not.toContain('EPIPE');
52
+ expect(result.code).toBe(3);
53
+ }, 15000);
54
+ it('a command keeps its exit code when stdout and stderr are closed', async () => {
55
+ const result = await runWithClosedPipes(
56
+ ['--', 'sh', '-c', `${big}; ${big} >&2; exit 3`],
57
+ ['stdout', 'stderr']
58
+ );
59
+ expect(result.code).toBe(3);
60
+ }, 15000);
61
+ });