From 3ec98fc111b3b1d7af4faae7102289902152abc3 Mon Sep 17 00:00:00 2001 From: William Easton Date: Fri, 13 Feb 2026 19:44:19 -0600 Subject: [PATCH] Migrate all generateFooter callers to generateFooterWithMessages Replace the legacy `generateFooter` (from `generate_footer.cjs`) with `generateFooterWithMessages` (from `messages_footer.cjs`) across all remaining callers. This enables `messages.footer` custom templates to apply everywhere, including PR reviews, issue closing, and PR ready notifications. - Migrate 5 callers: pr_review_buffer, close_pull_request, close_entity_helpers, create_issue, mark_pull_request_as_ready_for_review - Remove now-unused `generateFooter` function and its test suite from generate_footer.cjs (-217 lines net) - Clean up test setup: remove file-based GH_AW_PROMPTS_DIR scaffolding that was only needed by the old footer generator Co-authored-by: Cursor --- actions/setup/js/close_entity_helpers.cjs | 4 +- actions/setup/js/close_pull_request.cjs | 4 +- actions/setup/js/create_issue.cjs | 5 +- actions/setup/js/generate_footer.cjs | 57 ------- actions/setup/js/generate_footer.test.cjs | 148 +----------------- .../mark_pull_request_as_ready_for_review.cjs | 4 +- actions/setup/js/pr_review_buffer.cjs | 4 +- actions/setup/js/pr_review_buffer.test.cjs | 31 +--- 8 files changed, 20 insertions(+), 237 deletions(-) diff --git a/actions/setup/js/close_entity_helpers.cjs b/actions/setup/js/close_entity_helpers.cjs index 5d7dec6aac7..254dabed235 100644 --- a/actions/setup/js/close_entity_helpers.cjs +++ b/actions/setup/js/close_entity_helpers.cjs @@ -2,7 +2,7 @@ /// const { loadAgentOutput } = require("./load_agent_output.cjs"); -const { generateFooter } = require("./generate_footer.cjs"); +const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { getTrackerID } = require("./get_tracker_id.cjs"); const { getRepositoryUrl } = require("./get_repository_url.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -57,7 +57,7 @@ function buildCommentBody(body, triggeringIssueNumber, triggeringPRNumber) { const workflowSourceURL = process.env.GH_AW_WORKFLOW_SOURCE_URL || ""; const runUrl = buildRunUrl(); - return body.trim() + getTrackerID("markdown") + generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, undefined); + return body.trim() + getTrackerID("markdown") + generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, undefined); } /** diff --git a/actions/setup/js/close_pull_request.cjs b/actions/setup/js/close_pull_request.cjs index 79e3350ef71..4eb67f86927 100644 --- a/actions/setup/js/close_pull_request.cjs +++ b/actions/setup/js/close_pull_request.cjs @@ -3,7 +3,7 @@ const { getErrorMessage } = require("./error_helpers.cjs"); const { getTrackerID } = require("./get_tracker_id.cjs"); -const { generateFooter } = require("./generate_footer.cjs"); +const { generateFooterWithMessages } = require("./messages_footer.cjs"); /** * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction @@ -321,7 +321,7 @@ function buildCommentBody(body, triggeringIssueNumber, triggeringPRNumber) { const githubServer = process.env.GITHUB_SERVER_URL || "https://github.com"; const runUrl = context.payload.repository ? `${context.payload.repository.html_url}/actions/runs/${runId}` : `${githubServer}/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; - return body.trim() + getTrackerID("markdown") + generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, undefined); + return body.trim() + getTrackerID("markdown") + generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, undefined); } module.exports = { main }; diff --git a/actions/setup/js/create_issue.cjs b/actions/setup/js/create_issue.cjs index 9661badaf57..ffff2f65fac 100644 --- a/actions/setup/js/create_issue.cjs +++ b/actions/setup/js/create_issue.cjs @@ -27,7 +27,8 @@ function resetIssuesToAssignCopilot() { const { sanitizeLabelContent } = require("./sanitize_label_content.cjs"); const { sanitizeTitle, applyTitlePrefix } = require("./sanitize_title.cjs"); -const { generateFooter, generateWorkflowIdMarker } = require("./generate_footer.cjs"); +const { generateFooterWithMessages } = require("./messages_footer.cjs"); +const { generateWorkflowIdMarker } = require("./generate_footer.cjs"); const { getTrackerID } = require("./get_tracker_id.cjs"); const { generateTemporaryId, isTemporaryId, normalizeTemporaryId, replaceTemporaryIdReferences } = require("./temporary_id.cjs"); const { parseAllowedRepos, getDefaultTargetRepo, validateRepo, parseRepoSlug } = require("./repo_helpers.cjs"); @@ -444,7 +445,7 @@ async function main(config = {}) { // Generate footer and add expiration using helper // When footer is disabled, only add XML markers (no visible footer content) if (includeFooter) { - const footer = addExpirationToFooter(generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber).trimEnd(), expiresHours, "Issue"); + const footer = addExpirationToFooter(generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber).trimEnd(), expiresHours, "Issue"); bodyLines.push(``, ``, footer); } diff --git a/actions/setup/js/generate_footer.cjs b/actions/setup/js/generate_footer.cjs index d7b3dc15fce..febb4f4ba7e 100644 --- a/actions/setup/js/generate_footer.cjs +++ b/actions/setup/js/generate_footer.cjs @@ -1,10 +1,6 @@ // @ts-check /// -const fs = require("fs"); -const { getMissingInfoSections } = require("./missing_messages_helper.cjs"); -const { getBlockedDomains, generateBlockedDomainsSection } = require("./firewall_blocked_domains.cjs"); - /** * Generates a standalone workflow-id XML comment marker for searchability. * This marker enables finding all items (issues, discussions, PRs, comments) @@ -81,58 +77,6 @@ function generateXMLMarker(workflowName, runUrl) { return ``; } -/** - * Generate footer with AI attribution and workflow installation instructions - * @param {string} workflowName - Name of the workflow - * @param {string} runUrl - URL of the workflow run - * @param {string} workflowSource - Source of the workflow (owner/repo/path@ref) - * @param {string} workflowSourceURL - GitHub URL for the workflow source - * @param {number|undefined} triggeringIssueNumber - Issue number that triggered this workflow - * @param {number|undefined} triggeringPRNumber - Pull request number that triggered this workflow - * @param {number|undefined} triggeringDiscussionNumber - Discussion number that triggered this workflow - * @returns {string} Footer text - */ -function generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber) { - let footer = `\n\n> AI generated by [${workflowName}](${runUrl})`; - - // Add reference to triggering issue/PR/discussion if available - if (triggeringIssueNumber) { - footer += ` for #${triggeringIssueNumber}`; - } else if (triggeringPRNumber) { - footer += ` for #${triggeringPRNumber}`; - } else if (triggeringDiscussionNumber) { - footer += ` for discussion #${triggeringDiscussionNumber}`; - } - - if (workflowSource && workflowSourceURL) { - // Load workflow install note template - const promptsDir = process.env.GH_AW_PROMPTS_DIR || "/opt/gh-aw/prompts"; - const installNoteTemplatePath = `${promptsDir}/workflow_install_note.md`; - const installNoteTemplate = fs.readFileSync(installNoteTemplatePath, "utf8"); - const installNote = installNoteTemplate.replace("{workflow_source}", workflowSource); - footer += `\n>\n> ${installNote}`; - } - - // Add missing tools and data sections if available - const missingInfoSections = getMissingInfoSections(); - if (missingInfoSections) { - footer += missingInfoSections; - } - - // Add firewall blocked domains section if any domains were blocked - const blockedDomains = getBlockedDomains(); - const blockedDomainsSection = generateBlockedDomainsSection(blockedDomains); - if (blockedDomainsSection) { - footer += blockedDomainsSection; - } - - // Add XML comment marker for traceability - footer += "\n\n" + generateXMLMarker(workflowName, runUrl); - - footer += "\n"; - return footer; -} - /** * Generate footer for expired entity closing comments * @param {string} workflowName - Name of the workflow @@ -154,7 +98,6 @@ function generateExpiredEntityFooter(workflowName, runUrl, workflowId) { } module.exports = { - generateFooter, generateXMLMarker, generateWorkflowIdMarker, getWorkflowIdMarkerContent, diff --git a/actions/setup/js/generate_footer.test.cjs b/actions/setup/js/generate_footer.test.cjs index 9476703ddfe..5d4212d6a80 100644 --- a/actions/setup/js/generate_footer.test.cjs +++ b/actions/setup/js/generate_footer.test.cjs @@ -1,7 +1,4 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import fs from "fs"; -import path from "path"; -import os from "os"; +import { describe, it, expect, beforeEach, vi } from "vitest"; // Mock the global objects that GitHub Actions provides const mockCore = { @@ -35,31 +32,11 @@ global.core = mockCore; global.context = mockContext; describe("generate_footer.cjs", () => { - let generateFooter; let generateXMLMarker; let generateWorkflowIdMarker; let getWorkflowIdMarkerContent; - let testPromptsDir; - let originalEnv; beforeEach(async () => { - // Save original environment - originalEnv = process.env.GH_AW_PROMPTS_DIR; - - // Set up test prompts directory - testPromptsDir = path.join(os.tmpdir(), "gh-aw-test-footer", "prompts"); - if (!fs.existsSync(testPromptsDir)) { - fs.mkdirSync(testPromptsDir, { recursive: true }); - } - - // Create the workflow install note template - const templatePath = path.join(testPromptsDir, "workflow_install_note.md"); - const templateContent = "To add this workflow in your repository, run `gh aw add {workflow_source}`. See [usage guide](https://github.github.com/gh-aw/guides/packaging-imports/)."; - fs.writeFileSync(templatePath, templateContent, "utf8"); - - // Set environment to use test directory - process.env.GH_AW_PROMPTS_DIR = testPromptsDir; - // Reset mocks vi.clearAllMocks(); // Clear env vars @@ -71,134 +48,11 @@ describe("generate_footer.cjs", () => { // Dynamic import to get fresh module state const module = await import("./generate_footer.cjs"); - generateFooter = module.generateFooter; generateXMLMarker = module.generateXMLMarker; generateWorkflowIdMarker = module.generateWorkflowIdMarker; getWorkflowIdMarkerContent = module.getWorkflowIdMarkerContent; }); - afterEach(() => { - // Restore original environment - if (originalEnv !== undefined) { - process.env.GH_AW_PROMPTS_DIR = originalEnv; - } else { - delete process.env.GH_AW_PROMPTS_DIR; - } - - // Clean up test directory - if (testPromptsDir && fs.existsSync(testPromptsDir)) { - fs.rmSync(testPromptsDir, { recursive: true, force: true }); - } - }); - - describe("generateFooter", () => { - it("should generate basic footer with workflow name and run URL", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, undefined); - - expect(result).toContain("> AI generated by [Test Workflow](https://github.com/test/repo/actions/runs/123)"); - expect(result).not.toContain("for #"); - expect(result).not.toContain("gh aw add"); - }); - - it("should include issue number reference when provided", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", 42, undefined, undefined); - - expect(result).toContain("> AI generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) for #42"); - }); - - it("should include PR number reference when provided", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, 99, undefined); - - expect(result).toContain("> AI generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) for #99"); - }); - - it("should include discussion number reference when provided", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, 7); - - expect(result).toContain("> AI generated by [Test Workflow](https://github.com/test/repo/actions/runs/123) for discussion #7"); - }); - - it("should prioritize issue number over PR number", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", 42, 99, undefined); - - expect(result).toContain("for #42"); - expect(result).not.toContain("for #99"); - }); - - it("should prioritize PR number over discussion number", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, 99, 7); - - expect(result).toContain("for #99"); - expect(result).not.toContain("for discussion #7"); - }); - - it("should include workflow installation instructions when source is provided", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "owner/repo/workflow.md@main", "https://github.com/owner/repo/blob/main/workflow.md", undefined, undefined, undefined); - - expect(result).toContain("gh aw add owner/repo/workflow.md@main"); - expect(result).toContain("See [usage guide](https://github.github.com/gh-aw/guides/packaging-imports/)"); - }); - - it("should not include installation instructions when source is empty", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, undefined); - - expect(result).not.toContain("gh aw add"); - }); - - it("should include both issue reference and installation instructions", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "owner/repo/workflow.md@main", "https://github.com/owner/repo/blob/main/workflow.md", 42, undefined, undefined); - - expect(result).toContain("for #42"); - expect(result).toContain("gh aw add owner/repo/workflow.md@main"); - }); - - it("should start and end with newlines", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, undefined); - - expect(result).toMatch(/^\n\n/); - expect(result).toMatch(/\n$/); - }); - - it("should handle special characters in workflow name", () => { - const result = generateFooter("Test Workflow (v2) [beta]", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, undefined); - - expect(result).toContain("> AI generated by [Test Workflow (v2) [beta]](https://github.com/test/repo/actions/runs/123)"); - }); - - it("should include XML comment marker for traceability", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, undefined); - - expect(result).toContain(""); - }); - - it("should include engine metadata in XML marker when env vars are set", async () => { - // Set env vars before re-importing - process.env.GH_AW_ENGINE_ID = "copilot"; - process.env.GH_AW_ENGINE_VERSION = "1.0.0"; - process.env.GH_AW_ENGINE_MODEL = "gpt-5"; - - // Re-import to pick up new env vars - vi.resetModules(); - const freshModule = await import("./generate_footer.cjs"); - - const result = freshModule.generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, undefined); - - expect(result).toContain(""); - }); - - it("should not include blocked domains section when no firewall logs exist", () => { - const result = generateFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "", "", undefined, undefined, undefined); - - expect(result).not.toContain("⚠️ Firewall blocked"); - expect(result).not.toContain("
"); - }); - }); - describe("generateXMLMarker", () => { it("should generate basic XML marker with workflow name and run URL", () => { const result = generateXMLMarker("Test Workflow", "https://github.com/test/repo/actions/runs/123"); diff --git a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs index d19bb6fd9bb..27f28475ca9 100644 --- a/actions/setup/js/mark_pull_request_as_ready_for_review.cjs +++ b/actions/setup/js/mark_pull_request_as_ready_for_review.cjs @@ -5,7 +5,7 @@ * @typedef {import('./types/handler-factory').HandlerFactoryFunction} HandlerFactoryFunction */ -const { generateFooter } = require("./generate_footer.cjs"); +const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { sanitizeContent } = require("./sanitize_content.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); @@ -171,7 +171,7 @@ async function main(config = {}) { const triggeringDiscussionNumber = context.payload?.discussion?.number; const sanitizedReason = sanitizeContent(item.reason); - const footer = generateFooter(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber); + const footer = generateFooterWithMessages(workflowName, runUrl, workflowSource, workflowSourceURL, triggeringIssueNumber, triggeringPRNumber, triggeringDiscussionNumber); const commentBody = `${sanitizedReason}\n\n${footer}`; await addPullRequestComment(github, context.repo.owner, context.repo.repo, prNumber, commentBody); diff --git a/actions/setup/js/pr_review_buffer.cjs b/actions/setup/js/pr_review_buffer.cjs index 8469012f463..cc442e02b1e 100644 --- a/actions/setup/js/pr_review_buffer.cjs +++ b/actions/setup/js/pr_review_buffer.cjs @@ -15,7 +15,7 @@ * await buffer.submitReview(); */ -const { generateFooter } = require("./generate_footer.cjs"); +const { generateFooterWithMessages } = require("./messages_footer.cjs"); const { getErrorMessage } = require("./error_helpers.cjs"); /** @@ -185,7 +185,7 @@ function createReviewBuffer() { // Add footer to review body if enabled and we have footer context. // Footer is always added (even for body-less reviews) to track which workflow submitted the review. if (includeFooter && footerContext) { - body += generateFooter( + body += generateFooterWithMessages( footerContext.workflowName, footerContext.runUrl, footerContext.workflowSource, diff --git a/actions/setup/js/pr_review_buffer.test.cjs b/actions/setup/js/pr_review_buffer.test.cjs index cdc3f164d82..de1f5669769 100644 --- a/actions/setup/js/pr_review_buffer.test.cjs +++ b/actions/setup/js/pr_review_buffer.test.cjs @@ -1,7 +1,4 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import fs from "fs"; -import os from "os"; -import path from "path"; const mockCore = { debug: vi.fn(), @@ -27,19 +24,14 @@ const { createReviewBuffer } = require("./pr_review_buffer.cjs"); describe("pr_review_buffer (factory pattern)", () => { let buffer; - let testPromptsDir; - let originalEnv; + let originalMessages; beforeEach(() => { vi.clearAllMocks(); - // Save and set up test prompts directory for generateFooter - originalEnv = process.env.GH_AW_PROMPTS_DIR; - testPromptsDir = path.join(os.tmpdir(), "gh-aw-test-pr-review-buffer", "prompts"); - fs.mkdirSync(testPromptsDir, { recursive: true }); - const templateContent = "Install this workflow: `gh aw add {workflow_source}` ([usage guide](https://github.com/github/gh-aw))"; - fs.writeFileSync(path.join(testPromptsDir, "workflow_install_note.md"), templateContent, "utf8"); - process.env.GH_AW_PROMPTS_DIR = testPromptsDir; + // Save and clear messages env var (generateFooterWithMessages reads this) + originalMessages = process.env.GH_AW_SAFE_OUTPUT_MESSAGES; + delete process.env.GH_AW_SAFE_OUTPUT_MESSAGES; // Create a fresh buffer instance for each test (no shared global state) buffer = createReviewBuffer(); @@ -47,17 +39,10 @@ describe("pr_review_buffer (factory pattern)", () => { afterEach(() => { // Restore original environment - if (originalEnv !== undefined) { - process.env.GH_AW_PROMPTS_DIR = originalEnv; + if (originalMessages !== undefined) { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = originalMessages; } else { - delete process.env.GH_AW_PROMPTS_DIR; - } - - // Clean up test directory - try { - fs.rmSync(path.dirname(testPromptsDir), { recursive: true, force: true }); - } catch { - // Ignore cleanup errors + delete process.env.GH_AW_SAFE_OUTPUT_MESSAGES; } }); @@ -405,7 +390,7 @@ describe("pr_review_buffer (factory pattern)", () => { const callArgs = mockGithub.rest.pulls.createReview.mock.calls[0][0]; expect(callArgs.body).toContain("Review body"); - // Footer generated by generate_footer.cjs + // Footer generated by messages_footer.cjs expect(callArgs.body).toContain("test-workflow"); });