diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 4f4a69a3e5..3be5ab9acd 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -2241,7 +2241,7 @@ jobs: path: /tmp/gh-aw/aw_info.json if-no-files-found: warn - name: Run AI Inference - uses: actions/ai-inference@334892bb203895caaed82ec52d23c1ed9385151e # v1 + uses: actions/ai-inference@334892bb203895caaed82ec52d23c1ed9385151e # v2.0.4 env: GH_AW_MCP_CONFIG: /tmp/gh-aw/mcp-config/mcp-servers.json GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 8433f151f1..ce61da11ec 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -6056,13 +6056,13 @@ jobs: - name: Download Go modules run: go mod download - name: Generate SBOM (SPDX format) - uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.10 + uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.11 with: artifact-name: sbom.spdx.json format: spdx-json output-file: sbom.spdx.json - name: Generate SBOM (CycloneDX format) - uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.10 + uses: anchore/sbom-action@43a17d6e7add2b5535efe4dcae9952337c479a93 # v0.20.11 with: artifact-name: sbom.cdx.json format: cyclonedx-json @@ -6261,7 +6261,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Release with gh-extension-precompile - uses: cli/gh-extension-precompile@9e2237c30f869ad3bcaed6a4be2cd43564dd421b # v2 + uses: cli/gh-extension-precompile@9e2237c30f869ad3bcaed6a4be2cd43564dd421b # v2.1.0 with: build_script_override: scripts/build-release.sh go_version_file: go.mod diff --git a/.github/workflows/stale-repo-identifier.lock.yml b/.github/workflows/stale-repo-identifier.lock.yml index a8cd91dcd0..21caadf429 100644 --- a/.github/workflows/stale-repo-identifier.lock.yml +++ b/.github/workflows/stale-repo-identifier.lock.yml @@ -176,7 +176,7 @@ jobs: ORGANIZATION: ${{ env.ORGANIZATION }} id: stale-repos name: Run stale_repos tool - uses: github/stale-repos@a21e55567b83cf3c3f3f9085d3038dc6cee02598 # v3 + uses: github/stale-repos@a21e55567b83cf3c3f3f9085d3038dc6cee02598 # v3.0.2 - env: INACTIVE_REPOS: ${{ steps.stale-repos.outputs.inactiveRepos }} name: Save stale repos output diff --git a/.github/workflows/super-linter.lock.yml b/.github/workflows/super-linter.lock.yml index b15660af2f..4c2fde522c 100644 --- a/.github/workflows/super-linter.lock.yml +++ b/.github/workflows/super-linter.lock.yml @@ -6189,7 +6189,7 @@ jobs: persist-credentials: false - name: Super-linter id: super-linter - uses: super-linter/super-linter@47984f49b4e87383eed97890fe2dca6063bbd9c3 # v8.2.1 + uses: super-linter/super-linter@47984f49b4e87383eed97890fe2dca6063bbd9c3 # v8.3.1 env: CREATE_LOG_FILE: "true" DEFAULT_BRANCH: main diff --git a/pkg/workflow/compiler_safe_outputs_core.go b/pkg/workflow/compiler_safe_outputs_core.go index a5e8cb383c..bfb5adf9fb 100644 --- a/pkg/workflow/compiler_safe_outputs_core.go +++ b/pkg/workflow/compiler_safe_outputs_core.go @@ -387,7 +387,7 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa appTokenSteps := c.buildGitHubAppTokenMintStep(data.SafeOutputs.App, permissions) // Calculate insertion index: after setup action (if present) and artifact downloads, but before safe output steps insertIndex := 0 - + // Count setup action steps (checkout + setup if in dev mode, or just setup) setupActionRef := c.resolveActionReference("./actions/setup", data) if setupActionRef != "" { @@ -396,10 +396,10 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa } insertIndex += 4 // Setup step (4 lines: name, uses, with, destination) } - + // Add artifact download steps count insertIndex += len(buildAgentOutputDownloadSteps()) - + // Add patch download steps if present if data.SafeOutputs.CreatePullRequests != nil || data.SafeOutputs.PushToPullRequestBranch != nil { patchDownloadSteps := buildArtifactDownloadSteps(ArtifactDownloadConfig{ @@ -410,7 +410,7 @@ func (c *Compiler) buildConsolidatedSafeOutputsJob(data *WorkflowData, mainJobNa }) insertIndex += len(patchDownloadSteps) } - + // Insert app token steps newSteps := make([]string, 0) newSteps = append(newSteps, steps[:insertIndex]...) diff --git a/pkg/workflow/js/add_comment.cjs b/pkg/workflow/js/add_comment.cjs index bfb7e51177..80cc9399e6 100644 --- a/pkg/workflow/js/add_comment.cjs +++ b/pkg/workflow/js/add_comment.cjs @@ -565,4 +565,5 @@ async function main() { core.info(`Successfully created ${createdComments.length} comment(s)`); return createdComments; } -await main(); + +module.exports = { main }; diff --git a/pkg/workflow/js/add_comment.test.cjs b/pkg/workflow/js/add_comment.test.cjs index 4c350de591..da0f7934ad 100644 --- a/pkg/workflow/js/add_comment.test.cjs +++ b/pkg/workflow/js/add_comment.test.cjs @@ -50,17 +50,20 @@ const mockCore = { }), it("should skip when no agent output is provided", async () => { (delete process.env.GH_AW_AGENT_OUTPUT, - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("No GH_AW_AGENT_OUTPUT environment variable found"), expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled()); }), it("should skip when agent output is empty", async () => { - (setAgentOutput(""), await eval(`(async () => { ${createCommentScript} })()`), expect(mockCore.info).toHaveBeenCalledWith("Agent output content is empty"), expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled()); + (setAgentOutput(""), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), + expect(mockCore.info).toHaveBeenCalledWith("Agent output content is empty"), + expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled()); }), it("should skip when not in issue or PR context", async () => { (setAgentOutput({ items: [{ type: "add_comment", body: "Test comment content" }] }), (global.context.eventName = "push"), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith('Target is "triggering" but not running in issue, pull request, or discussion context, skipping comment creation'), expect(mockGithub.rest.issues.createComment).not.toHaveBeenCalled()); }), @@ -68,7 +71,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_comment", body: "Test comment content" }] }), (global.context.eventName = "issues")); const mockComment = { id: 456, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-456" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, body: expect.stringContaining("Test comment content") }), expect(mockCore.setOutput).toHaveBeenCalledWith("comment_id", 456), expect(mockCore.setOutput).toHaveBeenCalledWith("comment_url", mockComment.html_url), @@ -79,14 +82,14 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_comment", body: "Test PR comment content" }] }), (global.context.eventName = "pull_request"), (global.context.payload.pull_request = { number: 789 }), delete global.context.payload.issue); const mockComment = { id: 789, html_url: "https://github.com/testowner/testrepo/issues/789#issuecomment-789" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 789, body: expect.stringContaining("Test PR comment content") })); }), it("should include run information in comment body", async () => { (setAgentOutput({ items: [{ type: "add_comment", body: "Test content" }] }), (global.context.eventName = "issues"), (global.context.payload.issue = { number: 123 })); const mockComment = { id: 456, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-456" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled(), expect(mockGithub.rest.issues.createComment.mock.calls).toHaveLength(1)); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; @@ -100,7 +103,7 @@ const mockCore = { (global.context.eventName = "issues"), (global.context.payload.issue = { number: 123 })); const mockComment = { id: 456, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-456" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Test content with source"), expect(callArgs.body).toContain("[đŸ´â€â˜ ī¸ Test Workflow]"), @@ -114,7 +117,7 @@ const mockCore = { (global.context.eventName = "issues"), (global.context.payload.issue = { number: 123 })); const mockComment = { id: 456, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-456" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Test content without source"), expect(callArgs.body).toContain("[đŸ´â€â˜ ī¸ Test Workflow]"), expect(callArgs.body).not.toContain("gh aw add")); }), @@ -125,7 +128,7 @@ const mockCore = { (global.context.payload.issue = { number: 123 }), delete global.context.payload.repository); const mockComment = { id: 456, html_url: "https://github.enterprise.com/testowner/testrepo/issues/123#issuecomment-456" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Test content with custom server"), expect(callArgs.body).toContain("https://github.enterprise.com/testowner/testrepo/actions/runs/12345"), @@ -139,14 +142,14 @@ const mockCore = { (global.context.payload.issue = { number: 123 }), delete global.context.payload.repository); const mockComment = { id: 456, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-456" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled()); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Test content with fallback"), expect(callArgs.body).toContain("https://github.com/testowner/testrepo/actions/runs/12345")); }), it("should include triggering issue number in footer when in issue context", async () => { (setAgentOutput({ items: [{ type: "add_comment", body: "Comment from issue context" }] }), (process.env.GH_AW_WORKFLOW_NAME = "Test Workflow"), (global.context.eventName = "issues"), (global.context.payload.issue = { number: 42 })); const mockComment = { id: 789, html_url: "https://github.com/testowner/testrepo/issues/42#issuecomment-789" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`)); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`)); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Comment from issue context"), expect(callArgs.body).toContain("[đŸ´â€â˜ ī¸ Test Workflow]"), expect(callArgs.body).toContain("#42")); }), @@ -157,7 +160,7 @@ const mockCore = { delete global.context.payload.issue, (global.context.payload.pull_request = { number: 123 })); const mockComment = { id: 890, html_url: "https://github.com/testowner/testrepo/pull/123#issuecomment-890" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`)); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`)); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Comment from PR context"), expect(callArgs.body).toContain("[đŸ´â€â˜ ī¸ Test Workflow]"), expect(callArgs.body).toContain("#123"), delete global.context.payload.pull_request); }), @@ -172,7 +175,7 @@ const mockCore = { (process.env.GH_AW_CREATED_PULL_REQUEST_URL = "https://github.com/testowner/testrepo/pull/101"), (process.env.GH_AW_CREATED_PULL_REQUEST_NUMBER = "101")); const mockComment = { id: 890, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-890" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`)); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`)); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("#### Related Items"), expect(callArgs.body).toMatch(/####\s+Related Items/), @@ -199,7 +202,7 @@ const mockCore = { (process.env.GH_AW_CREATED_DISCUSSION_NUMBER = "789"), (process.env.GH_AW_CREATED_PULL_REQUEST_URL = "https://github.com/testowner/testrepo/pull/101"), (process.env.GH_AW_CREATED_PULL_REQUEST_NUMBER = "101"), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockCore.summary.addRaw).toHaveBeenCalled()); const summaryContent = mockCore.summary.addRaw.mock.calls[0][0]; (expect(summaryContent).toContain("#### Related Items"), @@ -229,7 +232,7 @@ const mockCore = { addDiscussionComment: { comment: { id: "DC_kwDOPc1QR84BpqRt", body: "Test discussion comment", createdAt: "2025-10-19T22:00:00Z", url: "https://github.com/testowner/testrepo/discussions/1993#discussioncomment-123" } }, }), (global.github.graphql = mockGraphqlResponse), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGraphqlResponse).toHaveBeenCalledTimes(2), expect(mockGraphqlResponse.mock.calls[0][0]).toContain("query"), expect(mockGraphqlResponse.mock.calls[0][0]).toContain("discussion(number: $num)"), @@ -259,7 +262,7 @@ const mockCore = { addDiscussionComment: { comment: { id: "DC_kwDOPc1QR84BpqRv", body: "Test explicit discussion comment", createdAt: "2025-10-22T12:00:00Z", url: "https://github.com/testowner/testrepo/discussions/2001#discussioncomment-456" } }, }), (global.github.graphql = mockGraphqlResponse), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGraphqlResponse).toHaveBeenCalledTimes(2), expect(mockGraphqlResponse.mock.calls[0][0]).toContain("query"), expect(mockGraphqlResponse.mock.calls[0][0]).toContain("discussion(number: $num)"), @@ -280,7 +283,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_comment", body: "This comment references issue #aw_aabbccdd1122 which was created earlier." }] }), (process.env.GH_AW_TEMPORARY_ID_MAP = JSON.stringify({ aw_aabbccdd1122: 456 })), mockGithub.rest.issues.createComment.mockResolvedValue({ data: { id: 99999, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-99999" } }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith(expect.objectContaining({ body: expect.stringContaining("#456") })), expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith(expect.objectContaining({ body: expect.not.stringContaining("#aw_aabbccdd1122") })), delete process.env.GH_AW_TEMPORARY_ID_MAP); @@ -289,7 +292,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_comment", body: "Test comment" }] }), (process.env.GH_AW_TEMPORARY_ID_MAP = JSON.stringify({ aw_abc123: 100, aw_def456: 200 })), mockGithub.rest.issues.createComment.mockResolvedValue({ data: { id: 99999, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-99999" } }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("Loaded temporary ID map with 2 entries"), delete process.env.GH_AW_TEMPORARY_ID_MAP); }), @@ -297,7 +300,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_comment", body: "Comment with #aw_000000000000 that won't be resolved" }] }), (process.env.GH_AW_TEMPORARY_ID_MAP = "{}"), mockGithub.rest.issues.createComment.mockResolvedValue({ data: { id: 99999, html_url: "https://github.com/testowner/testrepo/issues/123#issuecomment-99999" } }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.createComment).toHaveBeenCalledWith(expect.objectContaining({ body: expect.stringContaining("#aw_000000000000") })), delete process.env.GH_AW_TEMPORARY_ID_MAP); }), @@ -308,7 +311,7 @@ const mockCore = { (global.context.eventName = "issues"), (global.context.payload.issue = { number: 456 })); const mockComment = { id: 999, html_url: "https://github.com/testowner/testrepo/issues/456#issuecomment-999" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`)); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`)); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Test comment with custom footer"), expect(callArgs.body).toContain("Custom AI footer by [Custom Workflow]"), @@ -326,7 +329,7 @@ const mockCore = { (global.context.eventName = "issues"), (global.context.payload.issue = { number: 789 })); const mockComment = { id: 1001, html_url: "https://github.com/testowner/testrepo/issues/789#issuecomment-1001" }; - (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript} })()`)); + (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockComment }), await eval(`(async () => { ${createCommentScript}; await main(); })()`)); const callArgs = mockGithub.rest.issues.createComment.mock.calls[0][0]; (expect(callArgs.body).toContain("Test comment with custom footer and install"), expect(callArgs.body).toContain("Generated by [Custom Workflow]"), @@ -353,7 +356,7 @@ const mockCore = { (mockGithub.graphql = vi.fn().mockResolvedValue({ minimizeComment: { minimizedComment: { isMinimized: !0 } } }))); const mockNewComment = { id: 4, html_url: "https://github.com/testowner/testrepo/issues/100#issuecomment-4" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockNewComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.listComments).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 100, per_page: 100, page: 1 }), expect(mockGithub.graphql).toHaveBeenCalledTimes(2), expect(mockGithub.graphql).toHaveBeenCalledWith(expect.stringContaining("minimizeComment"), expect.objectContaining({ nodeId: "IC_oldcomment1", classifier: "OUTDATED" })), @@ -371,7 +374,7 @@ const mockCore = { (mockGithub.graphql = vi.fn())); const mockNewComment = { id: 5, html_url: "https://github.com/testowner/testrepo/issues/200#issuecomment-5" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockNewComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.listComments).not.toHaveBeenCalled(), expect(mockGithub.graphql).not.toHaveBeenCalled(), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled(), @@ -386,7 +389,7 @@ const mockCore = { (mockGithub.graphql = vi.fn())); const mockNewComment = { id: 6, html_url: "https://github.com/testowner/testrepo/issues/300#issuecomment-6" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockNewComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.listComments).not.toHaveBeenCalled(), expect(mockGithub.graphql).not.toHaveBeenCalled(), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled(), @@ -403,7 +406,7 @@ const mockCore = { (mockGithub.graphql = vi.fn().mockResolvedValue({ minimizeComment: { minimizedComment: { isMinimized: !0 } } }))); const mockNewComment = { id: 2, html_url: "https://github.com/testowner/testrepo/issues/400#issuecomment-2" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockNewComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.graphql).toHaveBeenCalledWith(expect.stringContaining("minimizeComment"), expect.objectContaining({ nodeId: "IC_oldcomment1", classifier: "OUTDATED" })), delete process.env.GITHUB_WORKFLOW, delete process.env.GH_AW_HIDE_OLDER_COMMENTS, @@ -420,7 +423,7 @@ const mockCore = { (mockGithub.graphql = vi.fn())); const mockNewComment = { id: 3, html_url: "https://github.com/testowner/testrepo/issues/500#issuecomment-3" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockNewComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.rest.issues.listComments).not.toHaveBeenCalled(), expect(mockGithub.graphql).not.toHaveBeenCalled(), expect(mockGithub.rest.issues.createComment).toHaveBeenCalled(), @@ -439,7 +442,7 @@ const mockCore = { (mockGithub.graphql = vi.fn().mockResolvedValue({ minimizeComment: { minimizedComment: { isMinimized: !0 } } }))); const mockNewComment = { id: 4, html_url: "https://github.com/testowner/testrepo/issues/600#issuecomment-4" }; (mockGithub.rest.issues.createComment.mockResolvedValue({ data: mockNewComment }), - await eval(`(async () => { ${createCommentScript} })()`), + await eval(`(async () => { ${createCommentScript}; await main(); })()`), expect(mockGithub.graphql).toHaveBeenCalledWith(expect.stringContaining("minimizeComment"), expect.objectContaining({ nodeId: "IC_oldcomment1", classifier: "OUTDATED" })), delete process.env.GITHUB_WORKFLOW, delete process.env.GH_AW_HIDE_OLDER_COMMENTS, diff --git a/pkg/workflow/js/add_labels.cjs b/pkg/workflow/js/add_labels.cjs index 48f20ec479..cd76c83a5a 100644 --- a/pkg/workflow/js/add_labels.cjs +++ b/pkg/workflow/js/add_labels.cjs @@ -122,4 +122,5 @@ ${labelsListMarkdown} core.setFailed(`Failed to add labels: ${errorMessage}`); } } -await main(); + +module.exports = { main }; diff --git a/pkg/workflow/js/add_labels.test.cjs b/pkg/workflow/js/add_labels.test.cjs index f750206e14..60a649629f 100644 --- a/pkg/workflow/js/add_labels.test.cjs +++ b/pkg/workflow/js/add_labels.test.cjs @@ -58,14 +58,14 @@ const mockCore = { (it("should skip when no agent output is provided", async () => { ((process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), delete process.env.GH_AW_AGENT_OUTPUT, - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("No GH_AW_AGENT_OUTPUT environment variable found"), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); }), it("should skip when agent output is empty", async () => { (setAgentOutput(""), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("Agent output content is empty"), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); }), @@ -74,7 +74,7 @@ const mockCore = { delete process.env.GH_AW_LABELS_ALLOWED, (process.env.GH_AW_LABELS_MAX_COUNT = "10"), mockGithub.rest.issues.addLabels.mockResolvedValue({}), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("No label addition restrictions - any label additions are allowed"), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement", "custom-label"] })); }), @@ -83,7 +83,7 @@ const mockCore = { (process.env.GH_AW_LABELS_ALLOWED = " "), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), mockGithub.rest.issues.addLabels.mockResolvedValue({}), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("No label addition restrictions - any label additions are allowed"), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement", "custom-label"] })); }), @@ -92,7 +92,7 @@ const mockCore = { (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), mockGithub.rest.issues.addLabels.mockResolvedValue({}), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith(`Allowed label additions: ${JSON.stringify(["bug", "enhancement"])}`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement"] })); }), @@ -100,7 +100,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (process.env.GH_AW_LABELS_MAX_COUNT = "invalid"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.setFailed).toHaveBeenCalledWith("Invalid max value: invalid. Must be a positive integer"), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); }), @@ -108,7 +108,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (process.env.GH_AW_LABELS_MAX_COUNT = "0"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.setFailed).toHaveBeenCalledWith("Invalid max value: 0. Must be a positive integer"), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); }), @@ -116,7 +116,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement", "feature", "documentation"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement,feature,documentation"), delete process.env.GH_AW_LABELS_MAX_COUNT, - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("Max count: 1"), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug"] })); })); @@ -126,7 +126,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (global.context.eventName = "push"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith('Target is "triggering" but not running in issue or pull request context, skipping label addition'), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); }), @@ -134,7 +134,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (global.context.eventName = "issue_comment"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalled()); }), it("should work with pull_request event", async () => { @@ -143,7 +143,7 @@ const mockCore = { (global.context.eventName = "pull_request"), (global.context.payload.pull_request = { number: 456 }), delete global.context.payload.issue, - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 456, labels: ["bug"] })); }), it("should work with pull_request_review event", async () => { @@ -152,7 +152,7 @@ const mockCore = { (global.context.eventName = "pull_request_review"), (global.context.payload.pull_request = { number: 789 }), delete global.context.payload.issue, - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 789, labels: ["bug"] })); }), it("should fail when issue context detected but no issue in payload", async () => { @@ -160,7 +160,7 @@ const mockCore = { (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (global.context.eventName = "issues"), delete global.context.payload.issue, - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.setFailed).toHaveBeenCalledWith("Issue context detected but no issue found in payload"), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); }), @@ -170,7 +170,7 @@ const mockCore = { (global.context.eventName = "pull_request"), delete global.context.payload.issue, delete global.context.payload.pull_request, - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.setFailed).toHaveBeenCalledWith("Pull request context detected but no pull request found in payload"), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); })); @@ -180,7 +180,7 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement", "documentation"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement,feature"), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement"] }), expect(mockCore.setOutput).toHaveBeenCalledWith("labels_added", "bug\nenhancement"), expect(mockCore.summary.addRaw).toHaveBeenCalled(), @@ -190,13 +190,13 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement"] })); }), it("should fail when line starts with dash (removal indication)", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "-enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.setFailed).toHaveBeenCalledWith("Label removal is not permitted. Found line starting with '-': -enhancement"), expect(mockGithub.rest.issues.addLabels).not.toHaveBeenCalled()); }), @@ -204,21 +204,21 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement", "bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement"] })); }), it("should enforce max count limit", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement", "feature", "documentation", "question"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement,feature,documentation,question"), (process.env.GH_AW_LABELS_MAX_COUNT = "2"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("Too many labels (5), limiting to 2"), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement"] })); }), it("should skip when no valid labels found", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["invalid", "another-invalid"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("No labels to add"), expect(mockCore.setOutput).toHaveBeenCalledWith("labels_added", ""), expect(mockCore.summary.addRaw).toHaveBeenCalledWith(expect.stringContaining("No labels were added")), @@ -231,7 +231,7 @@ const mockCore = { (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement,feature"), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), mockGithub.rest.issues.addLabels.mockResolvedValue({}), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement"] }), expect(mockCore.info).toHaveBeenCalledWith("Successfully added 2 labels to issue #123"), expect(mockCore.setOutput).toHaveBeenCalledWith("labels_added", "bug\nenhancement")); @@ -245,7 +245,7 @@ const mockCore = { (global.context.payload.pull_request = { number: 456 }), delete global.context.payload.issue, mockGithub.rest.issues.addLabels.mockResolvedValue({}), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("Successfully added 1 labels to pull request #456")); const summaryCall = mockCore.summary.addRaw.mock.calls.find(call => call[0].includes("Successfully added 1 label(s) to pull request #456")); expect(summaryCall).toBeDefined(); @@ -255,7 +255,7 @@ const mockCore = { const apiError = new Error("Label does not exist"); mockGithub.rest.issues.addLabels.mockRejectedValue(apiError); const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - (await eval(`(async () => { ${addLabelsScript} })()`), + (await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.error).toHaveBeenCalledWith("Failed to add labels: Label does not exist"), expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to add labels: Label does not exist")); }), @@ -264,7 +264,7 @@ const mockCore = { const stringError = "Something went wrong"; mockGithub.rest.issues.addLabels.mockRejectedValue(stringError); const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - (await eval(`(async () => { ${addLabelsScript} })()`), + (await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.error).toHaveBeenCalledWith("Failed to add labels: Something went wrong"), expect(mockCore.setFailed).toHaveBeenCalledWith("Failed to add labels: Something went wrong")); })); @@ -273,28 +273,28 @@ const mockCore = { (it("should log agent output content length", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith("Agent output content length: 64")); }), it("should log allowed labels and max count", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement,feature"), (process.env.GH_AW_LABELS_MAX_COUNT = "5"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith(`Allowed label additions: ${JSON.stringify(["bug", "enhancement", "feature"])}`), expect(mockCore.info).toHaveBeenCalledWith("Max count: 5")); }), it("should log requested labels", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement", "invalid"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith(`Requested labels: ${JSON.stringify(["bug", "enhancement", "invalid"])}`)); }), it("should log final labels being added", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith(`Adding 2 labels to issue #123: ${JSON.stringify(["bug", "enhancement"])}`)); })); }), @@ -303,21 +303,21 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement"] }] }), (process.env.GH_AW_LABELS_ALLOWED = " bug , enhancement , feature "), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith(`Allowed label additions: ${JSON.stringify(["bug", "enhancement", "feature"])}`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement"] })); }), it("should handle empty entries in allowed labels", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,,enhancement,"), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockCore.info).toHaveBeenCalledWith(`Allowed label additions: ${JSON.stringify(["bug", "enhancement"])}`)); }), it("should handle single label output", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug"] }] }), (process.env.GH_AW_LABELS_ALLOWED = "bug,enhancement"), mockGithub.rest.issues.addLabels.mockResolvedValue({}), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug"] }), expect(mockCore.setOutput).toHaveBeenCalledWith("labels_added", "bug")); }), @@ -325,14 +325,14 @@ const mockCore = { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug", "enhancement", "bug", "automation", "enhancement"] }] }), (process.env.GH_AW_LABELS_MAX_COUNT = "10"), mockGithub.rest.issues.addLabels.mockResolvedValue({}), - await eval(`(async () => { ${addLabelsScript} })()`), + await eval(`(async () => { ${addLabelsScript}; await main(); })()`), expect(mockGithub.rest.issues.addLabels).toHaveBeenCalledWith({ owner: "testowner", repo: "testrepo", issue_number: 123, labels: ["bug", "enhancement", "automation"] })); }), it("should sanitize labels by removing problematic characters", async () => { (setAgentOutput({ items: [{ type: "add_labels", labels: ["bug