diff --git a/README.md b/README.md index 8abf7a6..d805ad2 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ The outputs `stdout` and `stderr` have been replaced with `stdout_file` and - Capture standard output and standard error to temporary files - Output file paths available as action outputs - Stream output in real-time to the workflow logs +- Optionally hide outputs from the workflow log to protect sensitive data - Forward signals (SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGABRT) to the running command - Commands are executed directly without a shell (no shell operators like `|`, @@ -76,6 +77,17 @@ be considered successful. For example, some linters return specific exit codes for warnings vs errors, or you may want to accept multiple exit codes as valid outcomes. +### `hide_outputs` + +**Optional** When set to `"true"`, the stdout and stderr from the command are +only written to the respective output files and are not written to the +stdout/stderr of the action itself. Default is `"false"`. + +This is useful for commands that emit sensitive data (e.g., secrets, tokens) to +their output. The outputs are hidden from the GitHub Actions log but remain +available to subsequent workflow steps via the `stdout_file` and `stderr_file` +output paths. + ## Outputs ### `stdout_file` @@ -159,3 +171,19 @@ The exit code of the executed command (as a string). # Accept 0, any code from 10-15, and 20 as success success_exit_codes: '0,10-15,20' ``` + +### Hide sensitive outputs from the log + +```yaml +- name: Fetch Secret Data + id: fetch + uses: retailnext/exec-action@main + with: + command: 'my-tool --output-secret' + hide_outputs: 'true' + +- name: Process Secret Data + run: | + # The output is hidden from the log but available via the file path + process-data < "${{ steps.fetch.outputs.stdout_file }}" +``` diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 7c94feb..d64154f 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -110,6 +110,45 @@ describe('main.ts', () => { expect(core.setFailed).not.toHaveBeenCalled() }) + it('Hides outputs from process streams when hide_outputs is true', async () => { + core.getInput.mockImplementation((name: string) => { + if (name === 'command') return 'echo "Hello World"' + if (name === 'success_exit_codes') return '0' + if (name === 'hide_outputs') return 'true' + return '' + }) + + await run() + + // Verify outputs were still set with file paths + expect(core.setOutput).toHaveBeenCalledWith( + 'stdout_file', + expect.stringMatching(/exec-.*\.stdout$/) + ) + expect(core.setOutput).toHaveBeenCalledWith( + 'stderr_file', + expect.stringMatching(/exec-.*\.stderr$/) + ) + expect(core.setOutput).toHaveBeenCalledWith('exit_code', '0') + + // Verify the action did not fail + expect(core.setFailed).not.toHaveBeenCalled() + }) + + it('Does not hide outputs when hide_outputs is false', async () => { + core.getInput.mockImplementation((name: string) => { + if (name === 'command') return 'echo "Hello World"' + if (name === 'success_exit_codes') return '0' + if (name === 'hide_outputs') return 'false' + return '' + }) + + await run() + + // Verify the action did not fail + expect(core.setFailed).not.toHaveBeenCalled() + }) + it('Handles execution errors', async () => { // Use parseCommand with invalid input to trigger an error core.getInput.mockImplementation((name: string) => { @@ -308,6 +347,23 @@ describe('main.ts', () => { expect(stdoutContent.length).toBeGreaterThan(0) }) + it('Does not forward outputs to process streams when hideOutputs is true', async () => { + const result = await executeCommand( + 'sh -c "echo stdout message && echo stderr message >&2"', + { hideOutputs: true } + ) + + expect(result.exitCode).toBe(0) + + // Verify stdout file still has the output content + const stdoutContent = await readFile(result.stdoutFile, 'utf-8') + expect(stdoutContent).toContain('stdout message') + + // Verify stderr file still has the output content + const stderrContent = await readFile(result.stderrFile, 'utf-8') + expect(stderrContent).toContain('stderr message') + }) + it('Captures both stdout and stderr to separate files', async () => { const result = await executeCommand( 'sh -c "echo stdout message && echo stderr message >&2"' diff --git a/action.yml b/action.yml index f72c6d6..bfe3041 100644 --- a/action.yml +++ b/action.yml @@ -18,6 +18,15 @@ inputs: (e.g., "0,1,2") or ranges (e.g., "0-2,5,10-15"). Default is "0". required: false default: '0' + hide_outputs: + description: > + When set to true, stdout and stderr from the command are only written to + the respective output files and are not written to the stdout/stderr of + the action itself. This hides the outputs from the GitHub Actions log + while still making them available to subsequent workflow steps via the + output files. Default is "false". + required: false + default: 'false' # Define your outputs here. outputs: diff --git a/badges/coverage.svg b/badges/coverage.svg index c8dfa22..47ebeb7 100644 --- a/badges/coverage.svg +++ b/badges/coverage.svg @@ -1 +1 @@ -Coverage: 92.85%Coverage92.85% \ No newline at end of file +Coverage: 93.08%Coverage93.08% \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 4bf8aa0..f578c42 100644 --- a/dist/index.js +++ b/dist/index.js @@ -69,12 +69,14 @@ async function run() { try { const command = getInput('command', { required: true }); const successExitCodesInput = getInput('success_exit_codes'); + const hideOutputs = getInput('hide_outputs').toLowerCase() === 'true'; debug(`Executing command: ${command}`); debug(`Success exit codes: ${successExitCodesInput}`); + debug(`Hide outputs: ${hideOutputs}`); // Parse success exit codes const successExitCodes = parseSuccessExitCodes(successExitCodesInput); // Execute the command and capture outputs - const result = await executeCommand(command); + const result = await executeCommand(command, { hideOutputs }); // Set outputs for other workflow steps to use setOutput('stdout_file', result.stdoutFile); setOutput('stderr_file', result.stderrFile); @@ -211,9 +213,13 @@ function setupSignalHandlers(child) { * Execute a command and capture its output to files. * * @param command The command to execute. + * @param options Optional execution options. + * @param options.hideOutputs When true, stdout and stderr are only written to + * files and are not forwarded to process.stdout/process.stderr. * @returns A promise that resolves with file paths and exit code. */ -async function executeCommand(command) { +async function executeCommand(command, options = {}) { + const { hideOutputs = false } = options; // Parse command into executable and arguments // Simple parsing that splits on whitespace while respecting quoted strings const args = parseCommand(command); @@ -269,20 +275,24 @@ async function executeCommand(command) { stderrStreamFinished = true; checkIfComplete(); }); - // Pipe stdout to both file and process.stdout + // Pipe stdout to file, and optionally to process.stdout // By default, stream.end() is called on the destination when source emits 'end' if (child.stdout) { child.stdout.pipe(stdoutFileStream); - child.stdout.pipe(process.stdout); + if (!hideOutputs) { + child.stdout.pipe(process.stdout); + } } else { // No stdout, manually end the stream stdoutFileStream.end(); } - // Pipe stderr to both file and process.stderr + // Pipe stderr to file, and optionally to process.stderr if (child.stderr) { child.stderr.pipe(stderrFileStream); - child.stderr.pipe(process.stderr); + if (!hideOutputs) { + child.stderr.pipe(process.stderr); + } } else { // No stderr, manually end the stream diff --git a/dist/index.js.map b/dist/index.js.map index 9792588..76daf82 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../src/github-actions.ts","../src/main.ts","../src/index.ts"],"sourcesContent":["/**\n * Local implementations of GitHub Actions functions.\n * These replace the @actions/core dependency with zero external dependencies.\n */\n\nimport { appendFileSync } from 'fs'\n\n/**\n * Gets the value of an input. The value is retrieved from the environment\n * variable INPUT_ (converted to uppercase).\n *\n * @param name Name of the input to get\n * @param options Optional. If required is true, will throw if input is not set\n * @returns string\n */\nexport function getInput(\n name: string,\n options?: { required?: boolean }\n): string {\n const envName = `INPUT_${name.replace(/ /g, '_').toUpperCase()}`\n const val = process.env[envName] || ''\n\n if (options?.required && !val) {\n throw new Error(`Input required and not supplied: ${name}`)\n }\n\n return val.trim()\n}\n\n/**\n * Sets the value of an output by writing to the GITHUB_OUTPUT file.\n *\n * @param name Name of the output to set\n * @param value Value to set\n */\nexport function setOutput(name: string, value: string): void {\n const outputFile = process.env['GITHUB_OUTPUT']\n if (!outputFile) {\n // In local development without GitHub Actions environment\n return\n }\n\n // Format: name=value (with proper escaping for multiline values)\n // Use timestamp + random to ensure uniqueness\n const delimiter = `ghadelimiter_${Date.now()}_${Math.random().toString(36).substring(2)}`\n const output = `${name}<<${delimiter}\\n${value}\\n${delimiter}\\n`\n\n appendFileSync(outputFile, output, { encoding: 'utf8' })\n}\n\n/**\n * Writes debug message to stdout using GitHub Actions workflow command format.\n *\n * @param message Debug message\n */\nexport function debug(message: string): void {\n process.stdout.write(`::debug::${message}\\n`)\n}\n\n/**\n * Sets the action status to failed. Writes an error message and exits the process.\n *\n * @param message Error message\n */\nexport function setFailed(message: string): void {\n process.exitCode = 1\n process.stdout.write(`::error::${message}\\n`)\n}\n","import { spawn } from 'child_process'\nimport { createWriteStream } from 'fs'\nimport { openSync, constants } from 'fs'\nimport { tmpdir } from 'os'\nimport { join } from 'path'\nimport { randomBytes } from 'crypto'\nimport * as core from './github-actions.js'\n\n/**\n * The main function for the action.\n *\n * @returns Resolves when the action is complete.\n */\nexport async function run(): Promise {\n try {\n const command: string = core.getInput('command', { required: true })\n const successExitCodesInput: string = core.getInput('success_exit_codes')\n\n core.debug(`Executing command: ${command}`)\n core.debug(`Success exit codes: ${successExitCodesInput}`)\n\n // Parse success exit codes\n const successExitCodes = parseSuccessExitCodes(successExitCodesInput)\n\n // Execute the command and capture outputs\n const result = await executeCommand(command)\n\n // Set outputs for other workflow steps to use\n core.setOutput('stdout_file', result.stdoutFile)\n core.setOutput('stderr_file', result.stderrFile)\n core.setOutput('exit_code', result.exitCode.toString())\n\n // Check if the exit code should be treated as success\n if (!successExitCodes.has(result.exitCode)) {\n core.setFailed(`Command exited with code ${result.exitCode}`)\n }\n } catch (error) {\n // Fail the workflow run if an error occurs\n if (error instanceof Error) core.setFailed(error.message)\n }\n}\n\n/**\n * Create secure temporary output files for stdout and stderr.\n * Files are created atomically with exclusive access in RUNNER_TEMP.\n *\n * @returns Object containing file paths and file descriptors for stdout and stderr.\n */\nfunction createOutputFiles(): {\n stdoutPath: string\n stderrPath: string\n stdoutFd: number\n stderrFd: number\n} {\n // Get the temporary directory from RUNNER_TEMP environment variable\n const tempDir = process.env.RUNNER_TEMP || tmpdir()\n\n // Generate timestamp in seconds.nanoseconds format\n const now = process.hrtime.bigint()\n const seconds = now / BigInt(1_000_000_000)\n const nanoseconds = now % BigInt(1_000_000_000)\n const timestamp = `${seconds}.${nanoseconds.toString().padStart(9, '0')}`\n\n // Generate secure random suffix (16 bytes = 128 bits, base64url encoded)\n const randomSuffix = randomBytes(16)\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n\n // Create file base name\n const baseName = `exec-${timestamp}-${randomSuffix}`\n\n // Create file paths\n const stdoutPath = join(tempDir, `${baseName}.stdout`)\n const stderrPath = join(tempDir, `${baseName}.stderr`)\n\n // Open files with exclusive creation flags (O_CREAT | O_EXCL | O_WRONLY)\n // This ensures atomic creation and prevents race conditions\n // Using openSync here because createWriteStream will take ownership of the fd\n const stdoutFd = openSync(\n stdoutPath,\n constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,\n 0o600\n )\n const stderrFd = openSync(\n stderrPath,\n constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,\n 0o600\n )\n\n return {\n stdoutPath,\n stderrPath,\n stdoutFd,\n stderrFd\n }\n}\n\n/**\n * Parse the success exit codes input.\n * Supports individual codes (e.g., \"0,1,2\") and ranges (e.g., \"0-2,5,10-15\").\n *\n * @param input The success exit codes input string.\n * @returns A Set of exit codes that should be treated as success.\n */\nexport function parseSuccessExitCodes(input: string): Set {\n const exitCodes = new Set()\n\n if (!input || input.trim() === '') {\n exitCodes.add(0)\n return exitCodes\n }\n\n const parts = input.split(',').map((part) => part.trim())\n\n for (const part of parts) {\n if (part.includes('-')) {\n // Parse range (e.g., \"0-2\")\n const [startStr, endStr] = part.split('-').map((s) => s.trim())\n const start = parseInt(startStr, 10)\n const end = parseInt(endStr, 10)\n\n if (isNaN(start) || isNaN(end)) {\n throw new Error(\n `Invalid range format: \"${part}\". Expected format: \"start-end\" (e.g., \"0-2\")`\n )\n }\n\n if (start < 0 || end < 0) {\n throw new Error(\n `Invalid range: \"${part}\". Exit codes must be non-negative integers`\n )\n }\n\n if (start > end) {\n throw new Error(\n `Invalid range: \"${part}\". Start (${start}) must be less than or equal to end (${end})`\n )\n }\n\n for (let i = start; i <= end; i++) {\n exitCodes.add(i)\n }\n } else {\n // Parse individual code\n const code = parseInt(part, 10)\n if (isNaN(code)) {\n throw new Error(\n `Invalid exit code: \"${part}\". Expected a number or range (e.g., \"0\" or \"0-2\")`\n )\n }\n\n if (code < 0) {\n throw new Error(\n `Invalid exit code: \"${part}\". Exit codes must be non-negative integers`\n )\n }\n\n exitCodes.add(code)\n }\n }\n\n return exitCodes\n}\n\n/**\n * Set up signal handlers to forward signals to the child process.\n *\n * @param child The child process to forward signals to.\n * @returns A cleanup function to remove the signal handlers.\n */\nfunction setupSignalHandlers(child: ReturnType): () => void {\n const signals: NodeJS.Signals[] = [\n 'SIGINT',\n 'SIGTERM',\n 'SIGQUIT',\n 'SIGHUP',\n 'SIGPIPE',\n 'SIGABRT'\n ]\n\n // Create individual signal handlers for proper cleanup\n const signalHandlers = new Map void>()\n for (const signal of signals) {\n const handler = () => {\n core.debug(`Received ${signal}, forwarding to child process`)\n child.kill(signal)\n }\n signalHandlers.set(signal, handler)\n process.on(signal, handler)\n }\n\n // Return cleanup function\n return () => {\n for (const [signal, handler] of signalHandlers) {\n process.removeListener(signal, handler)\n }\n signalHandlers.clear()\n }\n}\n\n/**\n * Execute a command and capture its output to files.\n *\n * @param command The command to execute.\n * @returns A promise that resolves with file paths and exit code.\n */\nexport async function executeCommand(command: string): Promise<{\n stdoutFile: string\n stderrFile: string\n exitCode: number\n}> {\n // Parse command into executable and arguments\n // Simple parsing that splits on whitespace while respecting quoted strings\n const args = parseCommand(command)\n if (args.length === 0) {\n throw new Error('Command cannot be empty')\n }\n\n const executable = args[0]\n const commandArgs = args.slice(1)\n\n // Create output files\n const { stdoutPath, stderrPath, stdoutFd, stderrFd } = createOutputFiles()\n\n // Create write streams for the output files\n // autoClose: true ensures the fd is closed when the stream ends\n const stdoutFileStream = createWriteStream('', {\n fd: stdoutFd,\n autoClose: true\n })\n const stderrFileStream = createWriteStream('', {\n fd: stderrFd,\n autoClose: true\n })\n\n return new Promise((resolve, reject) => {\n // Execute command directly without shell\n const child = spawn(executable, commandArgs, {\n stdio: ['inherit', 'pipe', 'pipe']\n })\n\n let settled = false\n let stdoutStreamFinished = !child.stdout // If no stdout, mark as finished\n let stderrStreamFinished = !child.stderr // If no stderr, mark as finished\n let childExitCode: number | null = null\n\n // Set up signal forwarding\n const cleanupSignalHandlers = setupSignalHandlers(child)\n\n // Function to check if all streams are done and resolve\n const checkIfComplete = () => {\n if (\n !settled &&\n childExitCode !== null &&\n stdoutStreamFinished &&\n stderrStreamFinished\n ) {\n settled = true\n cleanupSignalHandlers()\n resolve({\n stdoutFile: stdoutPath,\n stderrFile: stderrPath,\n exitCode: childExitCode\n })\n }\n }\n\n // Track when streams finish\n stdoutFileStream.on('finish', () => {\n stdoutStreamFinished = true\n checkIfComplete()\n })\n\n stderrFileStream.on('finish', () => {\n stderrStreamFinished = true\n checkIfComplete()\n })\n\n // Pipe stdout to both file and process.stdout\n // By default, stream.end() is called on the destination when source emits 'end'\n if (child.stdout) {\n child.stdout.pipe(stdoutFileStream)\n child.stdout.pipe(process.stdout)\n } else {\n // No stdout, manually end the stream\n stdoutFileStream.end()\n }\n\n // Pipe stderr to both file and process.stderr\n if (child.stderr) {\n child.stderr.pipe(stderrFileStream)\n child.stderr.pipe(process.stderr)\n } else {\n // No stderr, manually end the stream\n stderrFileStream.end()\n }\n\n // Handle errors (e.g., command not found)\n child.on('error', (error: Error) => {\n if (!settled) {\n settled = true\n cleanupSignalHandlers()\n reject(error)\n }\n })\n\n // Handle process exit\n child.on('close', (code: number | null) => {\n childExitCode = code ?? 0\n checkIfComplete()\n })\n })\n}\n\n/**\n * Parse a command string into an array of arguments.\n * Handles quoted strings and escapes.\n *\n * @param command The command string to parse.\n * @returns An array of arguments.\n */\nexport function parseCommand(command: string): string[] {\n const args: string[] = []\n let current = ''\n let inQuotes: string | null = null\n let escaped = false\n\n for (let i = 0; i < command.length; i++) {\n const char = command[i]\n\n if (escaped) {\n current += char\n escaped = false\n continue\n }\n\n if (char === '\\\\') {\n escaped = true\n continue\n }\n\n if (inQuotes) {\n if (char === inQuotes) {\n inQuotes = null\n } else {\n current += char\n }\n } else if (char === '\"' || char === \"'\") {\n inQuotes = char\n } else if (char === ' ' || char === '\\t' || char === '\\n') {\n if (current.length > 0) {\n args.push(current)\n current = ''\n }\n } else {\n current += char\n }\n }\n\n // Handle edge cases\n if (escaped) {\n throw new Error('Invalid command: ends with an incomplete escape sequence')\n }\n\n if (inQuotes) {\n throw new Error(`Invalid command: unclosed quote (${inQuotes})`)\n }\n\n if (current.length > 0) {\n args.push(current)\n }\n\n return args\n}\n","/**\n * The entrypoint for the action. This file simply imports and runs the action's\n * main logic.\n */\nimport { run } from './main.js'\n\n/* istanbul ignore next */\nrun()\n"],"names":["core.getInput","core.debug","core.setOutput","core.setFailed"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sources":["../src/github-actions.ts","../src/main.ts","../src/index.ts"],"sourcesContent":["/**\n * Local implementations of GitHub Actions functions.\n * These replace the @actions/core dependency with zero external dependencies.\n */\n\nimport { appendFileSync } from 'fs'\n\n/**\n * Gets the value of an input. The value is retrieved from the environment\n * variable INPUT_ (converted to uppercase).\n *\n * @param name Name of the input to get\n * @param options Optional. If required is true, will throw if input is not set\n * @returns string\n */\nexport function getInput(\n name: string,\n options?: { required?: boolean }\n): string {\n const envName = `INPUT_${name.replace(/ /g, '_').toUpperCase()}`\n const val = process.env[envName] || ''\n\n if (options?.required && !val) {\n throw new Error(`Input required and not supplied: ${name}`)\n }\n\n return val.trim()\n}\n\n/**\n * Sets the value of an output by writing to the GITHUB_OUTPUT file.\n *\n * @param name Name of the output to set\n * @param value Value to set\n */\nexport function setOutput(name: string, value: string): void {\n const outputFile = process.env['GITHUB_OUTPUT']\n if (!outputFile) {\n // In local development without GitHub Actions environment\n return\n }\n\n // Format: name=value (with proper escaping for multiline values)\n // Use timestamp + random to ensure uniqueness\n const delimiter = `ghadelimiter_${Date.now()}_${Math.random().toString(36).substring(2)}`\n const output = `${name}<<${delimiter}\\n${value}\\n${delimiter}\\n`\n\n appendFileSync(outputFile, output, { encoding: 'utf8' })\n}\n\n/**\n * Writes debug message to stdout using GitHub Actions workflow command format.\n *\n * @param message Debug message\n */\nexport function debug(message: string): void {\n process.stdout.write(`::debug::${message}\\n`)\n}\n\n/**\n * Sets the action status to failed. Writes an error message and exits the process.\n *\n * @param message Error message\n */\nexport function setFailed(message: string): void {\n process.exitCode = 1\n process.stdout.write(`::error::${message}\\n`)\n}\n","import { spawn } from 'child_process'\nimport { createWriteStream } from 'fs'\nimport { openSync, constants } from 'fs'\nimport { tmpdir } from 'os'\nimport { join } from 'path'\nimport { randomBytes } from 'crypto'\nimport * as core from './github-actions.js'\n\n/**\n * The main function for the action.\n *\n * @returns Resolves when the action is complete.\n */\nexport async function run(): Promise {\n try {\n const command: string = core.getInput('command', { required: true })\n const successExitCodesInput: string = core.getInput('success_exit_codes')\n const hideOutputs: boolean =\n core.getInput('hide_outputs').toLowerCase() === 'true'\n\n core.debug(`Executing command: ${command}`)\n core.debug(`Success exit codes: ${successExitCodesInput}`)\n core.debug(`Hide outputs: ${hideOutputs}`)\n\n // Parse success exit codes\n const successExitCodes = parseSuccessExitCodes(successExitCodesInput)\n\n // Execute the command and capture outputs\n const result = await executeCommand(command, { hideOutputs })\n\n // Set outputs for other workflow steps to use\n core.setOutput('stdout_file', result.stdoutFile)\n core.setOutput('stderr_file', result.stderrFile)\n core.setOutput('exit_code', result.exitCode.toString())\n\n // Check if the exit code should be treated as success\n if (!successExitCodes.has(result.exitCode)) {\n core.setFailed(`Command exited with code ${result.exitCode}`)\n }\n } catch (error) {\n // Fail the workflow run if an error occurs\n if (error instanceof Error) core.setFailed(error.message)\n }\n}\n\n/**\n * Create secure temporary output files for stdout and stderr.\n * Files are created atomically with exclusive access in RUNNER_TEMP.\n *\n * @returns Object containing file paths and file descriptors for stdout and stderr.\n */\nfunction createOutputFiles(): {\n stdoutPath: string\n stderrPath: string\n stdoutFd: number\n stderrFd: number\n} {\n // Get the temporary directory from RUNNER_TEMP environment variable\n const tempDir = process.env.RUNNER_TEMP || tmpdir()\n\n // Generate timestamp in seconds.nanoseconds format\n const now = process.hrtime.bigint()\n const seconds = now / BigInt(1_000_000_000)\n const nanoseconds = now % BigInt(1_000_000_000)\n const timestamp = `${seconds}.${nanoseconds.toString().padStart(9, '0')}`\n\n // Generate secure random suffix (16 bytes = 128 bits, base64url encoded)\n const randomSuffix = randomBytes(16)\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n\n // Create file base name\n const baseName = `exec-${timestamp}-${randomSuffix}`\n\n // Create file paths\n const stdoutPath = join(tempDir, `${baseName}.stdout`)\n const stderrPath = join(tempDir, `${baseName}.stderr`)\n\n // Open files with exclusive creation flags (O_CREAT | O_EXCL | O_WRONLY)\n // This ensures atomic creation and prevents race conditions\n // Using openSync here because createWriteStream will take ownership of the fd\n const stdoutFd = openSync(\n stdoutPath,\n constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,\n 0o600\n )\n const stderrFd = openSync(\n stderrPath,\n constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,\n 0o600\n )\n\n return {\n stdoutPath,\n stderrPath,\n stdoutFd,\n stderrFd\n }\n}\n\n/**\n * Parse the success exit codes input.\n * Supports individual codes (e.g., \"0,1,2\") and ranges (e.g., \"0-2,5,10-15\").\n *\n * @param input The success exit codes input string.\n * @returns A Set of exit codes that should be treated as success.\n */\nexport function parseSuccessExitCodes(input: string): Set {\n const exitCodes = new Set()\n\n if (!input || input.trim() === '') {\n exitCodes.add(0)\n return exitCodes\n }\n\n const parts = input.split(',').map((part) => part.trim())\n\n for (const part of parts) {\n if (part.includes('-')) {\n // Parse range (e.g., \"0-2\")\n const [startStr, endStr] = part.split('-').map((s) => s.trim())\n const start = parseInt(startStr, 10)\n const end = parseInt(endStr, 10)\n\n if (isNaN(start) || isNaN(end)) {\n throw new Error(\n `Invalid range format: \"${part}\". Expected format: \"start-end\" (e.g., \"0-2\")`\n )\n }\n\n if (start < 0 || end < 0) {\n throw new Error(\n `Invalid range: \"${part}\". Exit codes must be non-negative integers`\n )\n }\n\n if (start > end) {\n throw new Error(\n `Invalid range: \"${part}\". Start (${start}) must be less than or equal to end (${end})`\n )\n }\n\n for (let i = start; i <= end; i++) {\n exitCodes.add(i)\n }\n } else {\n // Parse individual code\n const code = parseInt(part, 10)\n if (isNaN(code)) {\n throw new Error(\n `Invalid exit code: \"${part}\". Expected a number or range (e.g., \"0\" or \"0-2\")`\n )\n }\n\n if (code < 0) {\n throw new Error(\n `Invalid exit code: \"${part}\". Exit codes must be non-negative integers`\n )\n }\n\n exitCodes.add(code)\n }\n }\n\n return exitCodes\n}\n\n/**\n * Set up signal handlers to forward signals to the child process.\n *\n * @param child The child process to forward signals to.\n * @returns A cleanup function to remove the signal handlers.\n */\nfunction setupSignalHandlers(child: ReturnType): () => void {\n const signals: NodeJS.Signals[] = [\n 'SIGINT',\n 'SIGTERM',\n 'SIGQUIT',\n 'SIGHUP',\n 'SIGPIPE',\n 'SIGABRT'\n ]\n\n // Create individual signal handlers for proper cleanup\n const signalHandlers = new Map void>()\n for (const signal of signals) {\n const handler = () => {\n core.debug(`Received ${signal}, forwarding to child process`)\n child.kill(signal)\n }\n signalHandlers.set(signal, handler)\n process.on(signal, handler)\n }\n\n // Return cleanup function\n return () => {\n for (const [signal, handler] of signalHandlers) {\n process.removeListener(signal, handler)\n }\n signalHandlers.clear()\n }\n}\n\n/**\n * Execute a command and capture its output to files.\n *\n * @param command The command to execute.\n * @param options Optional execution options.\n * @param options.hideOutputs When true, stdout and stderr are only written to\n * files and are not forwarded to process.stdout/process.stderr.\n * @returns A promise that resolves with file paths and exit code.\n */\nexport async function executeCommand(\n command: string,\n options: { hideOutputs?: boolean } = {}\n): Promise<{\n stdoutFile: string\n stderrFile: string\n exitCode: number\n}> {\n const { hideOutputs = false } = options\n\n // Parse command into executable and arguments\n // Simple parsing that splits on whitespace while respecting quoted strings\n const args = parseCommand(command)\n if (args.length === 0) {\n throw new Error('Command cannot be empty')\n }\n\n const executable = args[0]\n const commandArgs = args.slice(1)\n\n // Create output files\n const { stdoutPath, stderrPath, stdoutFd, stderrFd } = createOutputFiles()\n\n // Create write streams for the output files\n // autoClose: true ensures the fd is closed when the stream ends\n const stdoutFileStream = createWriteStream('', {\n fd: stdoutFd,\n autoClose: true\n })\n const stderrFileStream = createWriteStream('', {\n fd: stderrFd,\n autoClose: true\n })\n\n return new Promise((resolve, reject) => {\n // Execute command directly without shell\n const child = spawn(executable, commandArgs, {\n stdio: ['inherit', 'pipe', 'pipe']\n })\n\n let settled = false\n let stdoutStreamFinished = !child.stdout // If no stdout, mark as finished\n let stderrStreamFinished = !child.stderr // If no stderr, mark as finished\n let childExitCode: number | null = null\n\n // Set up signal forwarding\n const cleanupSignalHandlers = setupSignalHandlers(child)\n\n // Function to check if all streams are done and resolve\n const checkIfComplete = () => {\n if (\n !settled &&\n childExitCode !== null &&\n stdoutStreamFinished &&\n stderrStreamFinished\n ) {\n settled = true\n cleanupSignalHandlers()\n resolve({\n stdoutFile: stdoutPath,\n stderrFile: stderrPath,\n exitCode: childExitCode\n })\n }\n }\n\n // Track when streams finish\n stdoutFileStream.on('finish', () => {\n stdoutStreamFinished = true\n checkIfComplete()\n })\n\n stderrFileStream.on('finish', () => {\n stderrStreamFinished = true\n checkIfComplete()\n })\n\n // Pipe stdout to file, and optionally to process.stdout\n // By default, stream.end() is called on the destination when source emits 'end'\n if (child.stdout) {\n child.stdout.pipe(stdoutFileStream)\n if (!hideOutputs) {\n child.stdout.pipe(process.stdout)\n }\n } else {\n // No stdout, manually end the stream\n stdoutFileStream.end()\n }\n\n // Pipe stderr to file, and optionally to process.stderr\n if (child.stderr) {\n child.stderr.pipe(stderrFileStream)\n if (!hideOutputs) {\n child.stderr.pipe(process.stderr)\n }\n } else {\n // No stderr, manually end the stream\n stderrFileStream.end()\n }\n\n // Handle errors (e.g., command not found)\n child.on('error', (error: Error) => {\n if (!settled) {\n settled = true\n cleanupSignalHandlers()\n reject(error)\n }\n })\n\n // Handle process exit\n child.on('close', (code: number | null) => {\n childExitCode = code ?? 0\n checkIfComplete()\n })\n })\n}\n\n/**\n * Parse a command string into an array of arguments.\n * Handles quoted strings and escapes.\n *\n * @param command The command string to parse.\n * @returns An array of arguments.\n */\nexport function parseCommand(command: string): string[] {\n const args: string[] = []\n let current = ''\n let inQuotes: string | null = null\n let escaped = false\n\n for (let i = 0; i < command.length; i++) {\n const char = command[i]\n\n if (escaped) {\n current += char\n escaped = false\n continue\n }\n\n if (char === '\\\\') {\n escaped = true\n continue\n }\n\n if (inQuotes) {\n if (char === inQuotes) {\n inQuotes = null\n } else {\n current += char\n }\n } else if (char === '\"' || char === \"'\") {\n inQuotes = char\n } else if (char === ' ' || char === '\\t' || char === '\\n') {\n if (current.length > 0) {\n args.push(current)\n current = ''\n }\n } else {\n current += char\n }\n }\n\n // Handle edge cases\n if (escaped) {\n throw new Error('Invalid command: ends with an incomplete escape sequence')\n }\n\n if (inQuotes) {\n throw new Error(`Invalid command: unclosed quote (${inQuotes})`)\n }\n\n if (current.length > 0) {\n args.push(current)\n }\n\n return args\n}\n","/**\n * The entrypoint for the action. This file simply imports and runs the action's\n * main logic.\n */\nimport { run } from './main.js'\n\n/* istanbul ignore next */\nrun()\n"],"names":["core.getInput","core.debug","core.setOutput","core.setFailed"],"mappings":"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"} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index be1652b..4a921a1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,15 +15,18 @@ export async function run(): Promise { try { const command: string = core.getInput('command', { required: true }) const successExitCodesInput: string = core.getInput('success_exit_codes') + const hideOutputs: boolean = + core.getInput('hide_outputs').toLowerCase() === 'true' core.debug(`Executing command: ${command}`) core.debug(`Success exit codes: ${successExitCodesInput}`) + core.debug(`Hide outputs: ${hideOutputs}`) // Parse success exit codes const successExitCodes = parseSuccessExitCodes(successExitCodesInput) // Execute the command and capture outputs - const result = await executeCommand(command) + const result = await executeCommand(command, { hideOutputs }) // Set outputs for other workflow steps to use core.setOutput('stdout_file', result.stdoutFile) @@ -204,13 +207,21 @@ function setupSignalHandlers(child: ReturnType): () => void { * Execute a command and capture its output to files. * * @param command The command to execute. + * @param options Optional execution options. + * @param options.hideOutputs When true, stdout and stderr are only written to + * files and are not forwarded to process.stdout/process.stderr. * @returns A promise that resolves with file paths and exit code. */ -export async function executeCommand(command: string): Promise<{ +export async function executeCommand( + command: string, + options: { hideOutputs?: boolean } = {} +): Promise<{ stdoutFile: string stderrFile: string exitCode: number }> { + const { hideOutputs = false } = options + // Parse command into executable and arguments // Simple parsing that splits on whitespace while respecting quoted strings const args = parseCommand(command) @@ -278,20 +289,24 @@ export async function executeCommand(command: string): Promise<{ checkIfComplete() }) - // Pipe stdout to both file and process.stdout + // Pipe stdout to file, and optionally to process.stdout // By default, stream.end() is called on the destination when source emits 'end' if (child.stdout) { child.stdout.pipe(stdoutFileStream) - child.stdout.pipe(process.stdout) + if (!hideOutputs) { + child.stdout.pipe(process.stdout) + } } else { // No stdout, manually end the stream stdoutFileStream.end() } - // Pipe stderr to both file and process.stderr + // Pipe stderr to file, and optionally to process.stderr if (child.stderr) { child.stderr.pipe(stderrFileStream) - child.stderr.pipe(process.stderr) + if (!hideOutputs) { + child.stderr.pipe(process.stderr) + } } else { // No stderr, manually end the stream stderrFileStream.end()