Skip to content

Commit 3ceb47a

Browse files
authored
Merge pull request #33 from retailnext/copilot/change-stdout-to-file-logging
2 parents 6711cdf + 0c6295d commit 3ceb47a

9 files changed

Lines changed: 383 additions & 137 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ jobs:
6363
id: output
6464
run: |
6565
echo "Exit Code: ${{ steps.test-action.outputs.exit_code }}"
66-
echo "Stdout: ${{ steps.test-action.outputs.stdout }}"
67-
echo "Stderr: ${{ steps.test-action.outputs.stderr }}"
66+
echo "Stdout File: ${{ steps.test-action.outputs.stdout_file }}"
67+
echo "Stderr File: ${{ steps.test-action.outputs.stderr_file }}"
68+
echo "Stdout Content:"
69+
cat "${{ steps.test-action.outputs.stdout_file }}"
6870
6971
- name: Test Custom Success Exit Codes
7072
id: test-custom-exit
@@ -77,5 +79,5 @@ jobs:
7779
run: |
7880
echo "Custom exit code test passed!"
7981
echo "Exit Code: ${{ steps.test-custom-exit.outputs.exit_code }}"
80-
echo "Stdout: ${{ steps.test-custom-exit.outputs.stdout }}"
81-
echo "Stderr: ${{ steps.test-custom-exit.outputs.stderr }}"
82+
echo "Stdout File: ${{ steps.test-custom-exit.outputs.stdout_file }}"
83+
echo "Stderr File: ${{ steps.test-custom-exit.outputs.stderr_file }}"

.github/workflows/signal-test.yml

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ jobs:
3737
if: always()
3838
run: |
3939
echo "Exit Code: ${{ steps.signal-test.outputs.exit_code }}"
40+
echo "Stdout File: ${{ steps.signal-test.outputs.stdout_file }}"
41+
echo "Stderr File: ${{ steps.signal-test.outputs.stderr_file }}"
4042
echo "=== Standard Output ==="
41-
echo "${{ steps.signal-test.outputs.stdout }}"
43+
cat "${{ steps.signal-test.outputs.stdout_file }}"
4244
echo "=== Standard Error ==="
43-
echo "${{ steps.signal-test.outputs.stderr }}"
45+
cat "${{ steps.signal-test.outputs.stderr_file }}"
4446
echo ""
4547
echo "Checking if outputs were populated..."
4648
if [ -n "${{ steps.signal-test.outputs.exit_code }}" ]; then
@@ -51,16 +53,16 @@ jobs:
5153
exit 1
5254
fi
5355
54-
if [ -n "${{ steps.signal-test.outputs.stdout }}" ]; then
55-
echo "✓ Stdout output is populated"
56+
if [ -s "${{ steps.signal-test.outputs.stdout_file }}" ]; then
57+
echo "✓ Stdout file has content"
5658
else
57-
echo "✗ Stdout output is empty"
59+
echo "✗ Stdout file is empty"
5860
exit 1
5961
fi
6062
6163
echo ""
6264
echo "Checking if signal was received..."
63-
if echo "${{ steps.signal-test.outputs.stdout }}" \
65+
if cat "${{ steps.signal-test.outputs.stdout_file }}" \
6466
| grep -q "Received"; then
6567
echo "✓ Signal was properly forwarded to child process"
6668
echo "✓ Test PASSED: Timeout triggered signal forwarding"
@@ -94,8 +96,8 @@ jobs:
9496
run: |
9597
echo "Exit Code: ${{ steps.timeout-test.outputs.exit_code }}"
9698
echo "=== Output ==="
97-
echo "${{ steps.timeout-test.outputs.stdout }}"
98-
if echo "${{ steps.timeout-test.outputs.stdout }}" \
99+
cat "${{ steps.timeout-test.outputs.stdout_file }}"
100+
if cat "${{ steps.timeout-test.outputs.stdout_file }}" \
99101
| grep -q "shutting down gracefully"; then
100102
echo "✓ Graceful shutdown worked correctly"
101103
else
@@ -124,8 +126,8 @@ jobs:
124126
- name: Verify SIGHUP Handling
125127
run: |
126128
echo "SIGHUP Test Output:"
127-
echo "${{ steps.test-sighup.outputs.stdout }}"
128-
if echo "${{ steps.test-sighup.outputs.stdout }}" \
129+
cat "${{ steps.test-sighup.outputs.stdout_file }}"
130+
if cat "${{ steps.test-sighup.outputs.stdout_file }}" \
129131
| grep -q "Received SIGHUP"; then
130132
echo "✓ SIGHUP was handled correctly"
131133
else
@@ -144,8 +146,8 @@ jobs:
144146
- name: Verify SIGINT Handling
145147
run: |
146148
echo "SIGINT Test Output:"
147-
echo "${{ steps.test-sigint.outputs.stdout }}"
148-
if echo "${{ steps.test-sigint.outputs.stdout }}" \
149+
cat "${{ steps.test-sigint.outputs.stdout_file }}"
150+
if cat "${{ steps.test-sigint.outputs.stdout_file }}" \
149151
| grep -q "Received SIGINT"; then
150152
echo "✓ SIGINT was handled correctly"
151153
else

README.md

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,23 @@
66
![CodeQL](https://github.com/actions/typescript-action/actions/workflows/codeql-analysis.yml/badge.svg)
77
![Coverage](./badges/coverage.svg)
88

9-
A GitHub Action that executes an arbitrary command and captures its output,
10-
including stdout, stderr, and exit code. The action streams command output in
11-
real-time and forwards signals to the command.
9+
A GitHub Action that executes an arbitrary command and captures its output to
10+
files, including stdout, stderr, and exit code. The action streams command
11+
output in real-time and forwards signals to the command.
12+
13+
## Breaking Changes
14+
15+
**BREAKING CHANGE**: This action now writes stdout and stderr to files instead
16+
of GitHub Action outputs. This change addresses issues with passing large
17+
outputs to subsequent steps (which could cause "Argument list too long" errors).
18+
The outputs `stdout` and `stderr` have been replaced with `stdout_file` and
19+
`stderr_file`, which contain paths to the files where the output is stored.
1220

1321
## Features
1422

1523
- Execute any single command
16-
- Capture standard output, standard error, and exit code as action outputs
24+
- Capture standard output and standard error to temporary files
25+
- Output file paths available as action outputs
1726
- Stream output in real-time to the workflow logs
1827
- Forward signals (SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGABRT) to the
1928
running command
@@ -40,8 +49,12 @@ steps:
4049
- name: Print Output
4150
run: |
4251
echo "Exit Code: ${{ steps.exec.outputs.exit_code }}"
43-
echo "Stdout: ${{ steps.exec.outputs.stdout }}"
44-
echo "Stderr: ${{ steps.exec.outputs.stderr }}"
52+
echo "Stdout File: ${{ steps.exec.outputs.stdout_file }}"
53+
echo "Stderr File: ${{ steps.exec.outputs.stderr_file }}"
54+
echo "Stdout Content:"
55+
cat "${{ steps.exec.outputs.stdout_file }}"
56+
echo "Stderr Content:"
57+
cat "${{ steps.exec.outputs.stderr_file }}"
4558
```
4659
4760
## Inputs
@@ -65,13 +78,16 @@ outcomes.
6578

6679
## Outputs
6780

68-
### `stdout`
81+
### `stdout_file`
6982

70-
The standard output of the executed command.
83+
Path to the file containing the standard output of the executed command. The
84+
file is located in the directory specified by the `RUNNER_TEMP` environment
85+
variable.
7186

72-
### `stderr`
87+
### `stderr_file`
7388

74-
The standard error of the executed command.
89+
Path to the file containing the standard error of the executed command. The file
90+
is located in the directory specified by the `RUNNER_TEMP` environment variable.
7591

7692
### `exit_code`
7793

@@ -107,7 +123,8 @@ The exit code of the executed command (as a string).
107123
if: steps.run.outputs.exit_code != '0'
108124
run: |
109125
echo "Command failed with exit code ${{ steps.run.outputs.exit_code }}"
110-
echo "Error output: ${{ steps.run.outputs.stderr }}"
126+
echo "Error output:"
127+
cat "${{ steps.run.outputs.stderr_file }}"
111128
```
112129

113130
### Accept multiple exit codes as success

__tests__/main.test.ts

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* this test, so that the actual module is not imported.
77
*/
88
import { jest } from '@jest/globals'
9+
import { readFile } from 'fs/promises'
910
import * as core from '../__fixtures__/core.js'
1011

1112
// Mocks should be declared before the module being tested is imported.
@@ -31,12 +32,15 @@ describe('main.ts', () => {
3132

3233
await run()
3334

34-
// Verify outputs were set
35+
// Verify outputs were set with file paths
3536
expect(core.setOutput).toHaveBeenCalledWith(
36-
'stdout',
37-
expect.stringContaining('Hello World')
37+
'stdout_file',
38+
expect.stringMatching(/exec-.*\.stdout$/)
39+
)
40+
expect(core.setOutput).toHaveBeenCalledWith(
41+
'stderr_file',
42+
expect.stringMatching(/exec-.*\.stderr$/)
3843
)
39-
expect(core.setOutput).toHaveBeenCalledWith('stderr', expect.any(String))
4044
expect(core.setOutput).toHaveBeenCalledWith('exit_code', '0')
4145

4246
// Verify the action did not fail
@@ -52,9 +56,15 @@ describe('main.ts', () => {
5256

5357
await run()
5458

55-
// Verify outputs were set
56-
expect(core.setOutput).toHaveBeenCalledWith('stdout', expect.any(String))
57-
expect(core.setOutput).toHaveBeenCalledWith('stderr', expect.any(String))
59+
// Verify outputs were set with file paths
60+
expect(core.setOutput).toHaveBeenCalledWith(
61+
'stdout_file',
62+
expect.stringMatching(/exec-.*\.stdout$/)
63+
)
64+
expect(core.setOutput).toHaveBeenCalledWith(
65+
'stderr_file',
66+
expect.stringMatching(/exec-.*\.stderr$/)
67+
)
5868
expect(core.setOutput).toHaveBeenCalledWith('exit_code', '1')
5969

6070
// Verify that the action was marked as failed
@@ -72,9 +82,15 @@ describe('main.ts', () => {
7282

7383
await run()
7484

75-
// Verify outputs were set
76-
expect(core.setOutput).toHaveBeenCalledWith('stdout', expect.any(String))
77-
expect(core.setOutput).toHaveBeenCalledWith('stderr', expect.any(String))
85+
// Verify outputs were set with file paths
86+
expect(core.setOutput).toHaveBeenCalledWith(
87+
'stdout_file',
88+
expect.stringMatching(/exec-.*\.stdout$/)
89+
)
90+
expect(core.setOutput).toHaveBeenCalledWith(
91+
'stderr_file',
92+
expect.stringMatching(/exec-.*\.stderr$/)
93+
)
7894
expect(core.setOutput).toHaveBeenCalledWith('exit_code', '1')
7995

8096
// Verify the action did not fail
@@ -223,19 +239,28 @@ describe('main.ts', () => {
223239
})
224240

225241
describe('executeCommand', () => {
226-
it('Captures stdout from a command', async () => {
242+
it('Captures stdout from a command to file', async () => {
227243
const result = await executeCommand('echo "test output"')
228244

229-
expect(result.stdout).toContain('test output')
245+
expect(result.stdoutFile).toMatch(/exec-.*\.stdout$/)
246+
expect(result.stderrFile).toMatch(/exec-.*\.stderr$/)
230247
expect(result.exitCode).toBe(0)
248+
249+
// Verify file contents
250+
const stdoutContent = await readFile(result.stdoutFile, 'utf-8')
251+
expect(stdoutContent).toContain('test output')
231252
})
232253

233-
it('Captures stderr from a command', async () => {
254+
it('Captures stderr from a command to file', async () => {
234255
// Use sh to redirect to stderr since we can't use shell operators directly
235256
const result = await executeCommand('sh -c "echo error output >&2"')
236257

237-
expect(result.stderr).toContain('error output')
258+
expect(result.stderrFile).toMatch(/exec-.*\.stderr$/)
238259
expect(result.exitCode).toBe(0)
260+
261+
// Verify file contents
262+
const stderrContent = await readFile(result.stderrFile, 'utf-8')
263+
expect(stderrContent).toContain('error output')
239264
})
240265

241266
it('Captures exit code from a failed command', async () => {
@@ -249,25 +274,56 @@ describe('main.ts', () => {
249274
// Use sh to run multiple echo commands
250275
const result = await executeCommand('sh -c "echo line1 && echo line2"')
251276

252-
expect(result.stdout).toContain('line1')
253-
expect(result.stdout).toContain('line2')
277+
expect(result.stdoutFile).toMatch(/exec-.*\.stdout$/)
278+
expect(result.stderrFile).toMatch(/exec-.*\.stderr$/)
254279
expect(result.exitCode).toBe(0)
280+
281+
// Verify file contents
282+
const stdoutContent = await readFile(result.stdoutFile, 'utf-8')
283+
expect(stdoutContent).toContain('line1')
284+
expect(stdoutContent).toContain('line2')
255285
})
256286

257287
it('Works with commands in PATH', async () => {
258288
// Test that we can find executables in PATH without full path
259-
const result = await executeCommand('ls -la')
289+
const result = await executeCommand('echo testing')
260290

261291
expect(result.exitCode).toBe(0)
262-
expect(result.stdout.length).toBeGreaterThan(0)
292+
expect(result.stdoutFile).toMatch(/exec-.*\.stdout$/)
293+
294+
// Verify file has content
295+
const stdoutContent = await readFile(result.stdoutFile, 'utf-8')
296+
expect(stdoutContent).toContain('testing')
263297
})
264298

265299
it('Works with npm commands', async () => {
266300
// Test that npm in PATH works
267301
const result = await executeCommand('npm --version')
268302

269303
expect(result.exitCode).toBe(0)
270-
expect(result.stdout.length).toBeGreaterThan(0)
304+
expect(result.stdoutFile).toMatch(/exec-.*\.stdout$/)
305+
306+
// Verify file has content
307+
const stdoutContent = await readFile(result.stdoutFile, 'utf-8')
308+
expect(stdoutContent.length).toBeGreaterThan(0)
309+
})
310+
311+
it('Captures both stdout and stderr to separate files', async () => {
312+
const result = await executeCommand(
313+
'sh -c "echo stdout message && echo stderr message >&2"'
314+
)
315+
316+
expect(result.exitCode).toBe(0)
317+
318+
// Verify stdout file contents
319+
const stdoutContent = await readFile(result.stdoutFile, 'utf-8')
320+
expect(stdoutContent).toContain('stdout message')
321+
expect(stdoutContent).not.toContain('stderr message')
322+
323+
// Verify stderr file contents
324+
const stderrContent = await readFile(result.stderrFile, 'utf-8')
325+
expect(stderrContent).toContain('stderr message')
326+
expect(stderrContent).not.toContain('stdout message')
271327
})
272328
})
273329
})

action.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ inputs:
2121

2222
# Define your outputs here.
2323
outputs:
24-
stdout:
25-
description: The standard output of the command
26-
stderr:
27-
description: The standard error of the command
24+
stdout_file:
25+
description: Path to the file containing the standard output of the command
26+
stderr_file:
27+
description: Path to the file containing the standard error of the command
2828
exit_code:
2929
description: The exit code of the command
3030

badges/coverage.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)