diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 0ad3aad4b41..da9c8126cfb 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -38,23 +38,26 @@ jobs: uses: actions/github-script@v7 with: script: | - const fs = require('fs'); - const crypto = require('crypto'); - // Generate a random filename for the output file - const randomId = crypto.randomBytes(8).toString('hex'); - const outputFile = `/tmp/aw_output_${randomId}.txt`; - // Ensure the /tmp directory exists and create empty output file - fs.mkdirSync('/tmp', { recursive: true }); - fs.writeFileSync(outputFile, '', { mode: 0o644 }); - // Verify the file was created and is writable - if (!fs.existsSync(outputFile)) { - throw new Error(`Failed to create output file: ${outputFile}`); + function main() { + const fs = require('fs'); + const crypto = require('crypto'); + // Generate a random filename for the output file + const randomId = crypto.randomBytes(8).toString('hex'); + const outputFile = `/tmp/aw_output_${randomId}.txt`; + // Ensure the /tmp directory exists and create empty output file + fs.mkdirSync('/tmp', { recursive: true }); + fs.writeFileSync(outputFile, '', { mode: 0o644 }); + // Verify the file was created and is writable + if (!fs.existsSync(outputFile)) { + throw new Error(`Failed to create output file: ${outputFile}`); + } + // Set the environment variable for subsequent steps + core.exportVariable('GITHUB_AW_OUTPUT', outputFile); + console.log('Created agentic output file:', outputFile); + // Also set as step output for reference + core.setOutput('output_file', outputFile); } - // Set the environment variable for subsequent steps - core.exportVariable('GITHUB_AW_OUTPUT', outputFile); - console.log('Created agentic output file:', outputFile); - // Also set as step output for reference - core.setOutput('output_file', outputFile); + main(); - name: Setup MCPs run: | mkdir -p /tmp/mcp-config diff --git a/.github/workflows/test-claude.lock.yml b/.github/workflows/test-claude.lock.yml index 22b18e7fe59..f9b967bfc3d 100644 --- a/.github/workflows/test-claude.lock.yml +++ b/.github/workflows/test-claude.lock.yml @@ -37,23 +37,26 @@ jobs: uses: actions/github-script@v7 with: script: | - const fs = require('fs'); - const crypto = require('crypto'); - // Generate a random filename for the output file - const randomId = crypto.randomBytes(8).toString('hex'); - const outputFile = `/tmp/aw_output_${randomId}.txt`; - // Ensure the /tmp directory exists and create empty output file - fs.mkdirSync('/tmp', { recursive: true }); - fs.writeFileSync(outputFile, '', { mode: 0o644 }); - // Verify the file was created and is writable - if (!fs.existsSync(outputFile)) { - throw new Error(`Failed to create output file: ${outputFile}`); + function main() { + const fs = require('fs'); + const crypto = require('crypto'); + // Generate a random filename for the output file + const randomId = crypto.randomBytes(8).toString('hex'); + const outputFile = `/tmp/aw_output_${randomId}.txt`; + // Ensure the /tmp directory exists and create empty output file + fs.mkdirSync('/tmp', { recursive: true }); + fs.writeFileSync(outputFile, '', { mode: 0o644 }); + // Verify the file was created and is writable + if (!fs.existsSync(outputFile)) { + throw new Error(`Failed to create output file: ${outputFile}`); + } + // Set the environment variable for subsequent steps + core.exportVariable('GITHUB_AW_OUTPUT', outputFile); + console.log('Created agentic output file:', outputFile); + // Also set as step output for reference + core.setOutput('output_file', outputFile); } - // Set the environment variable for subsequent steps - core.exportVariable('GITHUB_AW_OUTPUT', outputFile); - console.log('Created agentic output file:', outputFile); - // Also set as step output for reference - core.setOutput('output_file', outputFile); + main(); - name: Setup MCPs run: | mkdir -p /tmp/mcp-config diff --git a/.github/workflows/test-codex.lock.yml b/.github/workflows/test-codex.lock.yml index f6cb1c9ed42..c907fb4b768 100644 --- a/.github/workflows/test-codex.lock.yml +++ b/.github/workflows/test-codex.lock.yml @@ -40,23 +40,26 @@ jobs: uses: actions/github-script@v7 with: script: | - const fs = require('fs'); - const crypto = require('crypto'); - // Generate a random filename for the output file - const randomId = crypto.randomBytes(8).toString('hex'); - const outputFile = `/tmp/aw_output_${randomId}.txt`; - // Ensure the /tmp directory exists and create empty output file - fs.mkdirSync('/tmp', { recursive: true }); - fs.writeFileSync(outputFile, '', { mode: 0o644 }); - // Verify the file was created and is writable - if (!fs.existsSync(outputFile)) { - throw new Error(`Failed to create output file: ${outputFile}`); + function main() { + const fs = require('fs'); + const crypto = require('crypto'); + // Generate a random filename for the output file + const randomId = crypto.randomBytes(8).toString('hex'); + const outputFile = `/tmp/aw_output_${randomId}.txt`; + // Ensure the /tmp directory exists and create empty output file + fs.mkdirSync('/tmp', { recursive: true }); + fs.writeFileSync(outputFile, '', { mode: 0o644 }); + // Verify the file was created and is writable + if (!fs.existsSync(outputFile)) { + throw new Error(`Failed to create output file: ${outputFile}`); + } + // Set the environment variable for subsequent steps + core.exportVariable('GITHUB_AW_OUTPUT', outputFile); + console.log('Created agentic output file:', outputFile); + // Also set as step output for reference + core.setOutput('output_file', outputFile); } - // Set the environment variable for subsequent steps - core.exportVariable('GITHUB_AW_OUTPUT', outputFile); - console.log('Created agentic output file:', outputFile); - // Also set as step output for reference - core.setOutput('output_file', outputFile); + main(); - name: Setup MCPs run: | mkdir -p /tmp/mcp-config diff --git a/.github/workflows/test-proxy.lock.yml b/.github/workflows/test-proxy.lock.yml index 3222aa728bf..f22af3885ba 100644 --- a/.github/workflows/test-proxy.lock.yml +++ b/.github/workflows/test-proxy.lock.yml @@ -32,23 +32,26 @@ jobs: uses: actions/github-script@v7 with: script: | - const fs = require('fs'); - const crypto = require('crypto'); - // Generate a random filename for the output file - const randomId = crypto.randomBytes(8).toString('hex'); - const outputFile = `/tmp/aw_output_${randomId}.txt`; - // Ensure the /tmp directory exists and create empty output file - fs.mkdirSync('/tmp', { recursive: true }); - fs.writeFileSync(outputFile, '', { mode: 0o644 }); - // Verify the file was created and is writable - if (!fs.existsSync(outputFile)) { - throw new Error(`Failed to create output file: ${outputFile}`); + function main() { + const fs = require('fs'); + const crypto = require('crypto'); + // Generate a random filename for the output file + const randomId = crypto.randomBytes(8).toString('hex'); + const outputFile = `/tmp/aw_output_${randomId}.txt`; + // Ensure the /tmp directory exists and create empty output file + fs.mkdirSync('/tmp', { recursive: true }); + fs.writeFileSync(outputFile, '', { mode: 0o644 }); + // Verify the file was created and is writable + if (!fs.existsSync(outputFile)) { + throw new Error(`Failed to create output file: ${outputFile}`); + } + // Set the environment variable for subsequent steps + core.exportVariable('GITHUB_AW_OUTPUT', outputFile); + console.log('Created agentic output file:', outputFile); + // Also set as step output for reference + core.setOutput('output_file', outputFile); } - // Set the environment variable for subsequent steps - core.exportVariable('GITHUB_AW_OUTPUT', outputFile); - console.log('Created agentic output file:', outputFile); - // Also set as step output for reference - core.setOutput('output_file', outputFile); + main(); - name: Setup Proxy Configuration for MCP Network Restrictions run: | echo "Generating proxy configuration files for MCP tools with network restrictions..." diff --git a/.github/workflows/weekly-research.lock.yml b/.github/workflows/weekly-research.lock.yml index c4f66b47c60..04cf34ba5f3 100644 --- a/.github/workflows/weekly-research.lock.yml +++ b/.github/workflows/weekly-research.lock.yml @@ -37,23 +37,26 @@ jobs: uses: actions/github-script@v7 with: script: | - const fs = require('fs'); - const crypto = require('crypto'); - // Generate a random filename for the output file - const randomId = crypto.randomBytes(8).toString('hex'); - const outputFile = `/tmp/aw_output_${randomId}.txt`; - // Ensure the /tmp directory exists and create empty output file - fs.mkdirSync('/tmp', { recursive: true }); - fs.writeFileSync(outputFile, '', { mode: 0o644 }); - // Verify the file was created and is writable - if (!fs.existsSync(outputFile)) { - throw new Error(`Failed to create output file: ${outputFile}`); + function main() { + const fs = require('fs'); + const crypto = require('crypto'); + // Generate a random filename for the output file + const randomId = crypto.randomBytes(8).toString('hex'); + const outputFile = `/tmp/aw_output_${randomId}.txt`; + // Ensure the /tmp directory exists and create empty output file + fs.mkdirSync('/tmp', { recursive: true }); + fs.writeFileSync(outputFile, '', { mode: 0o644 }); + // Verify the file was created and is writable + if (!fs.existsSync(outputFile)) { + throw new Error(`Failed to create output file: ${outputFile}`); + } + // Set the environment variable for subsequent steps + core.exportVariable('GITHUB_AW_OUTPUT', outputFile); + console.log('Created agentic output file:', outputFile); + // Also set as step output for reference + core.setOutput('output_file', outputFile); } - // Set the environment variable for subsequent steps - core.exportVariable('GITHUB_AW_OUTPUT', outputFile); - console.log('Created agentic output file:', outputFile); - // Also set as step output for reference - core.setOutput('output_file', outputFile); + main(); - name: Setup MCPs run: | mkdir -p /tmp/mcp-config diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 9550afbd5a4..97cae893c0a 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -2683,23 +2683,9 @@ func (c *Compiler) generateOutputFileSetup(yaml *strings.Builder, data *Workflow yaml.WriteString(" uses: actions/github-script@v7\n") yaml.WriteString(" with:\n") yaml.WriteString(" script: |\n") - yaml.WriteString(" const fs = require('fs');\n") - yaml.WriteString(" const crypto = require('crypto');\n") - yaml.WriteString(" // Generate a random filename for the output file\n") - yaml.WriteString(" const randomId = crypto.randomBytes(8).toString('hex');\n") - yaml.WriteString(" const outputFile = `/tmp/aw_output_${randomId}.txt`;\n") - yaml.WriteString(" // Ensure the /tmp directory exists and create empty output file\n") - yaml.WriteString(" fs.mkdirSync('/tmp', { recursive: true });\n") - yaml.WriteString(" fs.writeFileSync(outputFile, '', { mode: 0o644 });\n") - yaml.WriteString(" // Verify the file was created and is writable\n") - yaml.WriteString(" if (!fs.existsSync(outputFile)) {\n") - yaml.WriteString(" throw new Error(`Failed to create output file: ${outputFile}`);\n") - yaml.WriteString(" }\n") - yaml.WriteString(" // Set the environment variable for subsequent steps\n") - yaml.WriteString(" core.exportVariable('GITHUB_AW_OUTPUT', outputFile);\n") - yaml.WriteString(" console.log('Created agentic output file:', outputFile);\n") - yaml.WriteString(" // Also set as step output for reference\n") - yaml.WriteString(" core.setOutput('output_file', outputFile);\n") + + // Use the embedded setup agent output script + WriteJavaScriptToYAML(yaml, setupAgentOutputScript) } // generateOutputCollectionStep generates a step that reads the output file and sets it as a GitHub Actions output diff --git a/pkg/workflow/js.go b/pkg/workflow/js.go index d5c7bad4797..1d057f92c6a 100644 --- a/pkg/workflow/js.go +++ b/pkg/workflow/js.go @@ -21,6 +21,9 @@ var sanitizeOutputScript string //go:embed js/add_labels.cjs var addLabelsScript string +//go:embed js/setup_agent_output.cjs +var setupAgentOutputScript string + // FormatJavaScriptForYAML formats a JavaScript script with proper indentation for embedding in YAML func FormatJavaScriptForYAML(script string) []string { var formattedLines []string diff --git a/pkg/workflow/js/setup_agent_output.cjs b/pkg/workflow/js/setup_agent_output.cjs new file mode 100644 index 00000000000..aeedbc1e594 --- /dev/null +++ b/pkg/workflow/js/setup_agent_output.cjs @@ -0,0 +1,26 @@ +function main() { + const fs = require('fs'); + const crypto = require('crypto'); + + // Generate a random filename for the output file + const randomId = crypto.randomBytes(8).toString('hex'); + const outputFile = `/tmp/aw_output_${randomId}.txt`; + + // Ensure the /tmp directory exists and create empty output file + fs.mkdirSync('/tmp', { recursive: true }); + fs.writeFileSync(outputFile, '', { mode: 0o644 }); + + // Verify the file was created and is writable + if (!fs.existsSync(outputFile)) { + throw new Error(`Failed to create output file: ${outputFile}`); + } + + // Set the environment variable for subsequent steps + core.exportVariable('GITHUB_AW_OUTPUT', outputFile); + console.log('Created agentic output file:', outputFile); + + // Also set as step output for reference + core.setOutput('output_file', outputFile); +} + +main(); \ No newline at end of file diff --git a/pkg/workflow/js/setup_agent_output.test.cjs b/pkg/workflow/js/setup_agent_output.test.cjs new file mode 100644 index 00000000000..9d5af69f18b --- /dev/null +++ b/pkg/workflow/js/setup_agent_output.test.cjs @@ -0,0 +1,135 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import fs from 'fs'; +import path from 'path'; + +// Mock the global objects that GitHub Actions provides +const mockCore = { + exportVariable: vi.fn(), + setOutput: vi.fn() +}; + +// Set up global variables +global.core = mockCore; + +describe('setup_agent_output.cjs', () => { + let setupScript; + + beforeEach(() => { + // Reset all mocks + vi.clearAllMocks(); + + // Read the script content + const scriptPath = path.join(process.cwd(), 'pkg/workflow/js/setup_agent_output.cjs'); + setupScript = fs.readFileSync(scriptPath, 'utf8'); + + // Make fs available globally for the evaluated script + global.fs = fs; + }); + + afterEach(() => { + // Clean up any test files + const files = fs.readdirSync('/tmp').filter(file => file.startsWith('aw_output_')); + files.forEach(file => { + try { + fs.unlinkSync(path.join('/tmp', file)); + } catch (e) { + // Ignore cleanup errors + } + }); + + // Clean up globals + delete global.fs; + }); + + describe('main function', () => { + it('should create output file and set environment variables', async () => { + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + // Execute the script + await eval(`(async () => { ${setupScript} })()`); + + // Check that exportVariable was called with the correct pattern + expect(mockCore.exportVariable).toHaveBeenCalledWith( + 'GITHUB_AW_OUTPUT', + expect.stringMatching(/^\/tmp\/aw_output_[0-9a-f]{16}\.txt$/) + ); + + // Check that setOutput was called with the same file path + const exportCall = mockCore.exportVariable.mock.calls[0]; + const outputCall = mockCore.setOutput.mock.calls[0]; + expect(outputCall[0]).toBe('output_file'); + expect(outputCall[1]).toBe(exportCall[1]); + + // Check that the file was actually created + const outputFile = exportCall[1]; + expect(fs.existsSync(outputFile)).toBe(true); + + // Check that console.log was called with the correct message + expect(consoleSpy).toHaveBeenCalledWith('Created agentic output file:', outputFile); + + // Check that the file is empty (as expected) + const content = fs.readFileSync(outputFile, 'utf8'); + expect(content).toBe(''); + + consoleSpy.mockRestore(); + }); + + it('should create unique output file names on multiple runs', async () => { + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + // Execute the script multiple times + await eval(`(async () => { ${setupScript} })()`); + const firstFile = mockCore.exportVariable.mock.calls[0][1]; + + // Reset mocks for second run + mockCore.exportVariable.mockClear(); + mockCore.setOutput.mockClear(); + + await eval(`(async () => { ${setupScript} })()`); + const secondFile = mockCore.exportVariable.mock.calls[0][1]; + + // Files should be different + expect(firstFile).not.toBe(secondFile); + + // Both files should exist + expect(fs.existsSync(firstFile)).toBe(true); + expect(fs.existsSync(secondFile)).toBe(true); + + consoleSpy.mockRestore(); + }); + + it('should handle file creation failure gracefully', async () => { + // Mock fs.writeFileSync to throw an error + const originalWriteFileSync = fs.writeFileSync; + fs.writeFileSync = vi.fn().mockImplementation(() => { + throw new Error('Permission denied'); + }); + + try { + await eval(`(async () => { ${setupScript} })()`); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error.message).toBe('Permission denied'); + } + + // Restore original function + fs.writeFileSync = originalWriteFileSync; + }); + + it('should verify file existence and throw error if file creation fails', async () => { + // Mock fs.existsSync to return false (simulating failed file creation) + const originalExistsSync = fs.existsSync; + fs.existsSync = vi.fn().mockReturnValue(false); + + try { + await eval(`(async () => { ${setupScript} })()`); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error.message).toMatch(/^Failed to create output file: \/tmp\/aw_output_[0-9a-f]{16}\.txt$/); + } + + // Restore original function + fs.existsSync = originalExistsSync; + }); + }); +}); \ No newline at end of file diff --git a/pkg/workflow/js_test.go b/pkg/workflow/js_test.go index 4151e450688..087e6d7fb19 100644 --- a/pkg/workflow/js_test.go +++ b/pkg/workflow/js_test.go @@ -171,6 +171,7 @@ func TestEmbeddedScriptsNotEmpty(t *testing.T) { {"createCommentScript", createCommentScript}, {"sanitizeOutputScript", sanitizeOutputScript}, {"addLabelsScript", addLabelsScript}, + {"setupAgentOutputScript", setupAgentOutputScript}, } for _, tt := range tests { diff --git a/tsconfig.json b/tsconfig.json index 46ac630a98e..e3fefd80697 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,6 +37,7 @@ "pkg/workflow/js/create_issue.cjs", "pkg/workflow/js/create_pull_request.cjs", "pkg/workflow/js/sanitize_output.cjs", + "pkg/workflow/js/setup_agent_output.cjs", "pkg/workflow/js/types/*.d.ts" ], "exclude": [