Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion setup/js/add_comment.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
44 changes: 42 additions & 2 deletions setup/js/check_workflow_timestamp_api.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions setup/js/create_code_scanning_alert.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
73 changes: 20 additions & 53 deletions setup/js/messages_run_status.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// @ts-check
/// <reference types="@actions/github-script" />

/**
* Run Status Message Module
Expand All @@ -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
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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 = {
Expand Down
31 changes: 27 additions & 4 deletions setup/js/push_repo_memory.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading