From 4d0d6a314d2c661e68377ae6a81d8af84eb98e17 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 04:46:04 +0000 Subject: [PATCH 1/2] clean(js): simplify messages_staged.cjs and add tests - Replace verbose ternary patterns with nullish coalescing (??) - Remove redundant intermediate variables and comments - Add messages_staged.test.cjs with 15 test cases covering: - Default title and description rendering - Custom template from GH_AW_SAFE_OUTPUT_MESSAGES config - Fallback behaviour when config key is absent - Fallback on invalid JSON config - Empty operation value - Unrecognised placeholder passthrough - Independent config key isolation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- actions/setup/js/messages_staged.cjs | 18 +--- actions/setup/js/messages_staged.test.cjs | 121 ++++++++++++++++++++++ 2 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 actions/setup/js/messages_staged.test.cjs diff --git a/actions/setup/js/messages_staged.cjs b/actions/setup/js/messages_staged.cjs index de684e1041f..82200cd50e5 100644 --- a/actions/setup/js/messages_staged.cjs +++ b/actions/setup/js/messages_staged.cjs @@ -22,15 +22,8 @@ const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cj */ function getStagedTitle(ctx) { const messages = getMessages(); - - // Create context with both camelCase and snake_case keys const templateContext = toSnakeCase(ctx); - - // Default staged title template - const defaultTitle = "## 🔍 Preview: {operation}"; - - // Use custom title if configured - return messages?.stagedTitle ? renderTemplate(messages.stagedTitle, templateContext) : renderTemplate(defaultTitle, templateContext); + return renderTemplate(messages?.stagedTitle ?? "## 🔍 Preview: {operation}", templateContext); } /** @@ -40,15 +33,8 @@ function getStagedTitle(ctx) { */ function getStagedDescription(ctx) { const messages = getMessages(); - - // Create context with both camelCase and snake_case keys const templateContext = toSnakeCase(ctx); - - // Default staged description template - const defaultDescription = "📋 The following operations would be performed if staged mode was disabled:"; - - // Use custom description if configured - return messages?.stagedDescription ? renderTemplate(messages.stagedDescription, templateContext) : renderTemplate(defaultDescription, templateContext); + return renderTemplate(messages?.stagedDescription ?? "📋 The following operations would be performed if staged mode was disabled:", templateContext); } module.exports = { diff --git a/actions/setup/js/messages_staged.test.cjs b/actions/setup/js/messages_staged.test.cjs new file mode 100644 index 00000000000..b0fb4e91073 --- /dev/null +++ b/actions/setup/js/messages_staged.test.cjs @@ -0,0 +1,121 @@ +// @ts-check +import { describe, it, expect, beforeEach, vi } from "vitest"; + +// messages_core.cjs calls core.warning on parse failures - provide a stub +const mockCore = { + info: vi.fn(), + warning: vi.fn(), + error: vi.fn(), + setFailed: vi.fn(), + setOutput: vi.fn(), +}; +global.core = mockCore; + +const { getStagedTitle, getStagedDescription } = require("./messages_staged.cjs"); + +const OPERATION = "Create Issues"; + +describe("messages_staged", () => { + beforeEach(() => { + vi.clearAllMocks(); + delete process.env.GH_AW_SAFE_OUTPUT_MESSAGES; + }); + + describe("getStagedTitle", () => { + it("returns the default title with operation substituted", () => { + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toBe(`## 🔍 Preview: ${OPERATION}`); + }); + + it("substitutes a different operation value", () => { + const title = getStagedTitle({ operation: "Add Comments" }); + expect(title).toBe("## 🔍 Preview: Add Comments"); + }); + + it("uses custom template from config", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: "Staging: {operation}" }); + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toBe(`Staging: ${OPERATION}`); + }); + + it("falls back to default when stagedTitle is absent from config", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ runSuccess: "other" }); + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toBe(`## 🔍 Preview: ${OPERATION}`); + }); + + it("falls back to default when config is invalid JSON", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = "not-json"; + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toBe(`## 🔍 Preview: ${OPERATION}`); + }); + + it("substitutes camelCase operation key as well as snake_case", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: "Op: {operation}" }); + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toContain(OPERATION); + }); + + it("returns an empty-operation title when operation is an empty string", () => { + const title = getStagedTitle({ operation: "" }); + expect(title).toBe("## 🔍 Preview: "); + }); + + it("leaves unrecognised placeholders unchanged in custom template", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: "{operation} | {unknown}" }); + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toBe(`${OPERATION} | {unknown}`); + }); + }); + + describe("getStagedDescription", () => { + it("returns the default description", () => { + const desc = getStagedDescription({ operation: OPERATION }); + expect(desc).toBe("📋 The following operations would be performed if staged mode was disabled:"); + }); + + it("uses custom description template from config", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedDescription: "Preview of: {operation}" }); + const desc = getStagedDescription({ operation: OPERATION }); + expect(desc).toBe(`Preview of: ${OPERATION}`); + }); + + it("falls back to default when stagedDescription is absent from config", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: "title only" }); + const desc = getStagedDescription({ operation: OPERATION }); + expect(desc).toBe("📋 The following operations would be performed if staged mode was disabled:"); + }); + + it("falls back to default when config is invalid JSON", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = "{bad json"; + const desc = getStagedDescription({ operation: OPERATION }); + expect(desc).toBe("📋 The following operations would be performed if staged mode was disabled:"); + }); + + it("supports custom description with operation placeholder", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedDescription: "Would run: {operation}" }); + const desc = getStagedDescription({ operation: "Close PRs" }); + expect(desc).toBe("Would run: Close PRs"); + }); + + it("default description does not contain unfilled placeholders", () => { + const desc = getStagedDescription({ operation: OPERATION }); + expect(desc).not.toMatch(/\{[^}]+\}/); + }); + }); + + describe("independent config keys", () => { + it("getStagedTitle and getStagedDescription use their own config keys independently", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ + stagedTitle: "Custom title: {operation}", + stagedDescription: "Custom desc: {operation}", + }); + + const title = getStagedTitle({ operation: OPERATION }); + const desc = getStagedDescription({ operation: OPERATION }); + + expect(title).toBe(`Custom title: ${OPERATION}`); + expect(desc).toBe(`Custom desc: ${OPERATION}`); + }); + }); +}); From 873a1dfd2593ec40314ac8e5499b7756ec0a4298 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 05:18:52 +0000 Subject: [PATCH 2/2] fix(js): restore staged message fallback semantics Agent-Logs-Url: https://github.com/github/gh-aw/sessions/2d47f5dd-4997-4261-8783-ba569fe355b3 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/messages_staged.cjs | 6 +++-- actions/setup/js/messages_staged.test.cjs | 32 ++++++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/actions/setup/js/messages_staged.cjs b/actions/setup/js/messages_staged.cjs index 82200cd50e5..73ef4ba9c99 100644 --- a/actions/setup/js/messages_staged.cjs +++ b/actions/setup/js/messages_staged.cjs @@ -23,7 +23,8 @@ const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cj function getStagedTitle(ctx) { const messages = getMessages(); const templateContext = toSnakeCase(ctx); - return renderTemplate(messages?.stagedTitle ?? "## 🔍 Preview: {operation}", templateContext); + const configuredTemplate = typeof messages?.stagedTitle === "string" ? messages.stagedTitle : ""; + return renderTemplate(configuredTemplate || "## 🔍 Preview: {operation}", templateContext); } /** @@ -34,7 +35,8 @@ function getStagedTitle(ctx) { function getStagedDescription(ctx) { const messages = getMessages(); const templateContext = toSnakeCase(ctx); - return renderTemplate(messages?.stagedDescription ?? "📋 The following operations would be performed if staged mode was disabled:", templateContext); + const configuredTemplate = typeof messages?.stagedDescription === "string" ? messages.stagedDescription : ""; + return renderTemplate(configuredTemplate || "📋 The following operations would be performed if staged mode was disabled:", templateContext); } module.exports = { diff --git a/actions/setup/js/messages_staged.test.cjs b/actions/setup/js/messages_staged.test.cjs index b0fb4e91073..ed0d4da1e2b 100644 --- a/actions/setup/js/messages_staged.test.cjs +++ b/actions/setup/js/messages_staged.test.cjs @@ -44,16 +44,28 @@ describe("messages_staged", () => { expect(title).toBe(`## 🔍 Preview: ${OPERATION}`); }); + it("falls back to default when stagedTitle is an empty string", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: "" }); + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toBe(`## 🔍 Preview: ${OPERATION}`); + }); + + it("falls back to default when stagedTitle is a falsy non-string value", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: 0 }); + const title = getStagedTitle({ operation: OPERATION }); + expect(title).toBe(`## 🔍 Preview: ${OPERATION}`); + }); + it("falls back to default when config is invalid JSON", () => { process.env.GH_AW_SAFE_OUTPUT_MESSAGES = "not-json"; const title = getStagedTitle({ operation: OPERATION }); expect(title).toBe(`## 🔍 Preview: ${OPERATION}`); }); - it("substitutes camelCase operation key as well as snake_case", () => { - process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: "Op: {operation}" }); - const title = getStagedTitle({ operation: OPERATION }); - expect(title).toContain(OPERATION); + it("supports snake_case placeholders from camelCase context keys", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedTitle: "{operation_name}: {operation}" }); + const title = getStagedTitle({ operation: OPERATION, operationName: "Create Comment" }); + expect(title).toBe(`Create Comment: ${OPERATION}`); }); it("returns an empty-operation title when operation is an empty string", () => { @@ -86,6 +98,18 @@ describe("messages_staged", () => { expect(desc).toBe("📋 The following operations would be performed if staged mode was disabled:"); }); + it("falls back to default when stagedDescription is an empty string", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedDescription: "" }); + const desc = getStagedDescription({ operation: OPERATION }); + expect(desc).toBe("📋 The following operations would be performed if staged mode was disabled:"); + }); + + it("falls back to default when stagedDescription is a falsy non-string value", () => { + process.env.GH_AW_SAFE_OUTPUT_MESSAGES = JSON.stringify({ stagedDescription: false }); + const desc = getStagedDescription({ operation: OPERATION }); + expect(desc).toBe("📋 The following operations would be performed if staged mode was disabled:"); + }); + it("falls back to default when config is invalid JSON", () => { process.env.GH_AW_SAFE_OUTPUT_MESSAGES = "{bad json"; const desc = getStagedDescription({ operation: OPERATION });