From bd788afa785bb4dcdc007f64c890073bc1c81d40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:08:44 +0000 Subject: [PATCH] chore: sync actions from gh-aw@v0.65.4 --- setup/js/add_comment.cjs | 9 ++- setup/js/check_workflow_timestamp_api.cjs | 44 +++++++++++++- setup/js/create_code_scanning_alert.cjs | 5 ++ setup/js/messages_run_status.cjs | 73 +++++++---------------- setup/js/push_repo_memory.cjs | 31 ++++++++-- 5 files changed, 102 insertions(+), 60 deletions(-) diff --git a/setup/js/add_comment.cjs b/setup/js/add_comment.cjs index 73b8f35..ef922ca 100644 --- a/setup/js/add_comment.cjs +++ b/setup/js/add_comment.cjs @@ -599,7 +599,14 @@ async function main(config = {}) { /** @type {{ id: string | number, html_url: string }} */ let comment; if (isDiscussion) { - comment = await commentOnDiscussion(githubClient, repoParts.owner, repoParts.repo, itemNumber, processedBody, null); + // When triggered by a discussion_comment event (without explicit item_number), + // reply as a threaded comment to the triggering comment instead of posting top-level. + const hasExplicitItemNumber = message.item_number !== undefined && message.item_number !== null; + const replyToId = context.eventName === "discussion_comment" && !hasExplicitItemNumber ? context.payload?.comment?.node_id : null; + if (replyToId) { + core.info(`Replying as threaded comment to discussion comment node ID: ${replyToId}`); + } + comment = await commentOnDiscussion(githubClient, repoParts.owner, repoParts.repo, itemNumber, processedBody, replyToId); } else { // Use REST API for issues/PRs const { data } = await githubClient.rest.issues.createComment({ diff --git a/setup/js/check_workflow_timestamp_api.cjs b/setup/js/check_workflow_timestamp_api.cjs index caa9ccc..825e9e4 100644 --- a/setup/js/check_workflow_timestamp_api.cjs +++ b/setup/js/check_workflow_timestamp_api.cjs @@ -30,8 +30,48 @@ async function main() { core.info(` Source: ${workflowMdPath}`); core.info(` Lock file: ${lockFilePath}`); - const { owner, repo } = context.repo; - const ref = context.sha; + // Determine workflow source repository from GITHUB_WORKFLOW_REF for cross-repo support. + // GITHUB_WORKFLOW_REF format: owner/repo/.github/workflows/file.yml@ref + // This env var always reflects the repo where the workflow file is defined, + // not the repo where the triggering event occurred (context.repo). + // When running cross-repo via org rulesets, context.repo points to the target + // repository, not the repository that defines the workflow files. + const workflowEnvRef = process.env.GITHUB_WORKFLOW_REF || ""; + const currentRepo = process.env.GITHUB_REPOSITORY || `${context.repo.owner}/${context.repo.repo}`; + + // Parse owner, repo, and optional ref from GITHUB_WORKFLOW_REF as a single unit so that + // repo and ref are always consistent with each other. The @ref segment may be absent (e.g. + // when the env var was set without a ref suffix), so treat it as optional. + const workflowRefMatch = workflowEnvRef.match(/^([^/]+)\/([^/]+)\/.+?(?:@(.+))?$/); + + // Use the workflow source repo if parseable, otherwise fall back to context.repo + const owner = workflowRefMatch ? workflowRefMatch[1] : context.repo.owner; + const repo = workflowRefMatch ? workflowRefMatch[2] : context.repo.repo; + const workflowRepo = `${owner}/${repo}`; + + // Determine ref in a way that keeps repo+ref consistent: + // - If a ref is present in GITHUB_WORKFLOW_REF, use it. + // - For same-repo runs without a parsed ref, fall back to context.sha (existing behavior). + // - For cross-repo runs without a parsed ref, omit ref so the API uses the default branch + // (avoids mixing source repo owner/name with a SHA that only exists in the triggering repo). + let ref; + if (workflowRefMatch && workflowRefMatch[3]) { + ref = workflowRefMatch[3]; + } else if (workflowRepo === currentRepo) { + ref = context.sha; + } else { + ref = undefined; + } + + core.info(`GITHUB_WORKFLOW_REF: ${workflowEnvRef || "(not set)"}`); + core.info(`GITHUB_REPOSITORY: ${currentRepo}`); + core.info(`Resolved source repo: ${owner}/${repo} @ ${ref || "(default branch)"}`); + + if (workflowRepo !== currentRepo) { + core.info(`Cross-repo invocation detected: workflow source is "${workflowRepo}", current repo is "${currentRepo}"`); + } else { + core.info(`Same-repo invocation: checking out ${workflowRepo} @ ${ref}`); + } // Helper function to compute and compare frontmatter hashes // Returns: { match: boolean, storedHash: string, recomputedHash: string } or null on error diff --git a/setup/js/create_code_scanning_alert.cjs b/setup/js/create_code_scanning_alert.cjs index 63b52ad..153c163 100644 --- a/setup/js/create_code_scanning_alert.cjs +++ b/setup/js/create_code_scanning_alert.cjs @@ -28,6 +28,10 @@ async function main(config = {}) { core.info(`Create code scanning alert configuration: max=${maxFindings === 0 ? "unlimited" : maxFindings}`); core.info(`Driver name: ${driverName}`); core.info(`Workflow filename for rule ID prefix: ${workflowFilename}`); + core.info(`Working directory: ${process.cwd()}`); + core.info(`GitHub ref: ${process.env.GITHUB_REF || "(not set)"}`); + core.info(`GitHub SHA: ${process.env.GITHUB_SHA || "(not set)"}`); + core.info(`GitHub repository: ${process.env.GITHUB_REPOSITORY || "(not set)"}`); // Track how many items we've processed for max limit let processedCount = 0; @@ -39,6 +43,7 @@ async function main(config = {}) { // SARIF file path const sarifFileName = "code-scanning-alert.sarif"; const sarifFilePath = path.join(process.cwd(), sarifFileName); + core.info(`SARIF file will be written to: ${sarifFilePath}`); /** * Generate and write SARIF file with all collected findings diff --git a/setup/js/messages_run_status.cjs b/setup/js/messages_run_status.cjs index 88c9523..e1d7e20 100644 --- a/setup/js/messages_run_status.cjs +++ b/setup/js/messages_run_status.cjs @@ -1,5 +1,4 @@ // @ts-check -/// /** * Run Status Message Module @@ -10,6 +9,19 @@ const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cjs"); +/** + * Renders a message using a custom template from config or a default template. + * @param {string} messageKey - Key in the messages config (e.g., "runStarted") + * @param {string} defaultTemplate - Default template string with {placeholder} syntax + * @param {Object} ctx - Context object for template substitution + * @returns {string} Rendered message + */ +function renderConfiguredMessage(messageKey, defaultTemplate, ctx) { + const messages = getMessages(); + const template = messages?.[messageKey] ?? defaultTemplate; + return renderTemplate(template, toSnakeCase(ctx)); +} + /** * @typedef {Object} RunStartedContext * @property {string} workflowName - Name of the workflow @@ -23,16 +35,7 @@ const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cj * @returns {string} Run-started message */ function getRunStartedMessage(ctx) { - const messages = getMessages(); - - // Create context with both camelCase and snake_case keys - const templateContext = toSnakeCase(ctx); - - // Default run-started template - const defaultMessage = "🚀 [{workflow_name}]({run_url}) has started processing this {event_type}"; - - // Use custom message if configured - return messages?.runStarted ? renderTemplate(messages.runStarted, templateContext) : renderTemplate(defaultMessage, templateContext); + return renderConfiguredMessage("runStarted", "🚀 [{workflow_name}]({run_url}) has started processing this {event_type}", ctx); } /** @@ -47,16 +50,7 @@ function getRunStartedMessage(ctx) { * @returns {string} Run-success message */ function getRunSuccessMessage(ctx) { - const messages = getMessages(); - - // Create context with both camelCase and snake_case keys - const templateContext = toSnakeCase(ctx); - - // Default run-success template - const defaultMessage = "✅ [{workflow_name}]({run_url}) completed successfully!"; - - // Use custom message if configured - return messages?.runSuccess ? renderTemplate(messages.runSuccess, templateContext) : renderTemplate(defaultMessage, templateContext); + return renderConfiguredMessage("runSuccess", "✅ [{workflow_name}]({run_url}) completed successfully!", ctx); } /** @@ -72,16 +66,7 @@ function getRunSuccessMessage(ctx) { * @returns {string} Run-failure message */ function getRunFailureMessage(ctx) { - const messages = getMessages(); - - // Create context with both camelCase and snake_case keys - const templateContext = toSnakeCase(ctx); - - // Default run-failure template - const defaultMessage = "❌ [{workflow_name}]({run_url}) {status}. Please review the logs for details."; - - // Use custom message if configured - return messages?.runFailure ? renderTemplate(messages.runFailure, templateContext) : renderTemplate(defaultMessage, templateContext); + return renderConfiguredMessage("runFailure", "❌ [{workflow_name}]({run_url}) {status}. Please review the logs for details.", ctx); } /** @@ -96,16 +81,7 @@ function getRunFailureMessage(ctx) { * @returns {string} Detection-failure message */ function getDetectionFailureMessage(ctx) { - const messages = getMessages(); - - // Create context with both camelCase and snake_case keys - const templateContext = toSnakeCase(ctx); - - // Default detection-failure template - const defaultMessage = "⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details."; - - // Use custom message if configured - return messages?.detectionFailure ? renderTemplate(messages.detectionFailure, templateContext) : renderTemplate(defaultMessage, templateContext); + return renderConfiguredMessage("detectionFailure", "⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", ctx); } /** @@ -120,10 +96,7 @@ function getDetectionFailureMessage(ctx) { * @returns {string} Pull-request-created message */ function getPullRequestCreatedMessage(ctx) { - const messages = getMessages(); - const templateContext = toSnakeCase(ctx); - const defaultMessage = "Pull request created: [#{item_number}]({item_url})"; - return messages?.pullRequestCreated ? renderTemplate(messages.pullRequestCreated, templateContext) : renderTemplate(defaultMessage, templateContext); + return renderConfiguredMessage("pullRequestCreated", "Pull request created: [#{item_number}]({item_url})", ctx); } /** @@ -138,10 +111,7 @@ function getPullRequestCreatedMessage(ctx) { * @returns {string} Issue-created message */ function getIssueCreatedMessage(ctx) { - const messages = getMessages(); - const templateContext = toSnakeCase(ctx); - const defaultMessage = "Issue created: [#{item_number}]({item_url})"; - return messages?.issueCreated ? renderTemplate(messages.issueCreated, templateContext) : renderTemplate(defaultMessage, templateContext); + return renderConfiguredMessage("issueCreated", "Issue created: [#{item_number}]({item_url})", ctx); } /** @@ -157,10 +127,7 @@ function getIssueCreatedMessage(ctx) { * @returns {string} Commit-pushed message */ function getCommitPushedMessage(ctx) { - const messages = getMessages(); - const templateContext = toSnakeCase(ctx); - const defaultMessage = "Commit pushed: [`{short_sha}`]({commit_url})"; - return messages?.commitPushed ? renderTemplate(messages.commitPushed, templateContext) : renderTemplate(defaultMessage, templateContext); + return renderConfiguredMessage("commitPushed", "Commit pushed: [`{short_sha}`]({commit_url})", ctx); } module.exports = { diff --git a/setup/js/push_repo_memory.cjs b/setup/js/push_repo_memory.cjs index 6dc404a..6c8d6d3 100644 --- a/setup/js/push_repo_memory.cjs +++ b/setup/js/push_repo_memory.cjs @@ -384,24 +384,47 @@ async function main() { } // Validate total patch size before committing + // Only additions (new content) are counted toward the patch size limit. + // Deletions are ignored since removing content is acceptable and does not + // contribute to the size of the content being pushed. try { const patchContent = execGitSync(["diff", "--cached"], { stdio: "pipe" }); - const patchSizeBytes = Buffer.byteLength(patchContent, "utf8"); + // Count only added lines (starting with '+', excluding '+++' file-header lines) + const addedSizeBytes = patchContent + .split("\n") + .filter(line => line.startsWith("+") && !line.startsWith("+++")) + .reduce((sum, line) => sum + Buffer.byteLength(line + "\n", "utf8"), 0); + const patchSizeBytes = addedSizeBytes; const patchSizeKb = Math.ceil(patchSizeBytes / 1024); const maxPatchSizeKb = Math.floor(maxPatchSize / 1024); // Allow 20% overhead to account for git diff format (headers, context lines, etc.) const effectiveMaxPatchSize = Math.floor(maxPatchSize * 1.2); const effectiveMaxPatchSizeKb = Math.floor(effectiveMaxPatchSize / 1024); - core.info(`Patch size: ${patchSizeKb} KB (${patchSizeBytes} bytes) (configured limit: ${maxPatchSizeKb} KB (${maxPatchSize} bytes), effective with 20% overhead: ${effectiveMaxPatchSizeKb} KB (${effectiveMaxPatchSize} bytes))`); + const patchSizeMessage = `Patch additions size: ${patchSizeKb} KB (${patchSizeBytes} bytes) (configured limit: ${maxPatchSizeKb} KB (${maxPatchSize} bytes), effective with 20% overhead: ${effectiveMaxPatchSizeKb} KB (${effectiveMaxPatchSize} bytes))`; if (patchSizeBytes > effectiveMaxPatchSize) { + // Warn at warning level so the size is visible even without verbose mode + core.warning(patchSizeMessage); + // Add per-file diff stats to diagnose what's causing the large patch + // (e.g. a full rewrite of an accumulated history file shows old + new content in the diff) + try { + const diffStat = execGitSync(["diff", "--cached", "--stat"], { stdio: "pipe" }); + core.warning(`Patch content breakdown (git diff --stat):\n${diffStat}`); + } catch (statError) { + core.warning(`Could not retrieve diff stat: ${getErrorMessage(statError)}`); + } core.setOutput("patch_size_exceeded", "true"); core.setFailed( - `Patch size (${patchSizeKb} KB, ${patchSizeBytes} bytes) exceeds maximum allowed size (${effectiveMaxPatchSizeKb} KB, ${effectiveMaxPatchSize} bytes, configured limit: ${maxPatchSizeKb} KB with 20% overhead allowance). Reduce the number or size of changes, or increase max-patch-size.` + `Patch additions size (${patchSizeKb} KB, ${patchSizeBytes} bytes) exceeds maximum allowed size (${effectiveMaxPatchSizeKb} KB, ${effectiveMaxPatchSize} bytes, configured limit: ${maxPatchSizeKb} KB with 20% overhead allowance). Reduce the number or size of changes, or increase max-patch-size.` ); return; + } else if (patchSizeBytes > maxPatchSize) { + // Within the 20% overhead window — still log as a warning so it's visible + core.warning(patchSizeMessage); + } else { + core.info(patchSizeMessage); } } catch (error) { - core.setFailed(`Failed to compute patch size: ${getErrorMessage(error)}`); + core.setFailed(`Failed to compute patch additions size: ${getErrorMessage(error)}`); return; }