Skip to content

Commit 3922978

Browse files
chore: sync actions from gh-aw@v0.67.2 (#70)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent addd8a8 commit 3922978

13 files changed

Lines changed: 278 additions & 188 deletions

setup/index.js

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,17 @@
33

44
const { spawnSync } = require("child_process");
55
const path = require("path");
6+
const { getActionInput } = require("./js/action_input_utils.cjs");
67

78
// Record start time for the OTLP span before any setup work begins.
89
const setupStartMs = Date.now();
910

10-
// GitHub Actions sets INPUT_* env vars for JavaScript actions by converting
11-
// input names to uppercase and replacing hyphens with underscores. Explicitly
12-
// normalize inputs with hyphens in their names because some runner versions
13-
// preserve the original hyphen instead of converting it to an underscore.
14-
const safeOutputCustomTokens =
15-
process.env["INPUT_SAFE_OUTPUT_CUSTOM_TOKENS"] ||
16-
process.env["INPUT_SAFE-OUTPUT-CUSTOM-TOKENS"] ||
17-
"false";
18-
19-
// Normalize trace-id input: handle both INPUT_TRACE_ID (underscore, standard)
20-
// and INPUT_TRACE-ID (hyphen, used by some runner versions).
21-
const inputTraceId =
22-
process.env["INPUT_TRACE_ID"] ||
23-
process.env["INPUT_TRACE-ID"] ||
24-
"";
25-
26-
// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
27-
// and INPUT_JOB-NAME (hyphen, used by some runner versions).
28-
const inputJobName =
29-
process.env["INPUT_JOB_NAME"] ||
30-
process.env["INPUT_JOB-NAME"] ||
31-
"";
11+
// GitHub Actions converts input names to INPUT_<UPPER_UNDERSCORE>, but some
12+
// runner versions preserve the original hyphen form. getActionInput() handles
13+
// both forms automatically.
14+
const safeOutputCustomTokens = getActionInput("SAFE_OUTPUT_CUSTOM_TOKENS") || "false";
15+
const inputTraceId = getActionInput("TRACE_ID");
16+
const inputJobName = getActionInput("JOB_NAME");
3217

3318
const result = spawnSync(path.join(__dirname, "setup.sh"), [], {
3419
stdio: "inherit",

setup/js/action_conclusion_otlp.cjs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
*/
3131

3232
const sendOtlpSpan = require("./send_otlp_span.cjs");
33+
const { getActionInput } = require("./action_input_utils.cjs");
3334

3435
/**
3536
* Send the OTLP job-conclusion span. Non-fatal: all errors are silently
@@ -48,9 +49,7 @@ async function run() {
4849
const rawJobStartMs = parseInt(process.env.GITHUB_AW_OTEL_JOB_START_MS || "0", 10);
4950
const startMs = rawJobStartMs > 0 ? rawJobStartMs : undefined;
5051

51-
// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
52-
// and INPUT_JOB-NAME (hyphen, used by some runner versions).
53-
const jobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
52+
const jobName = getActionInput("JOB_NAME");
5453
const spanName = jobName ? `gh-aw.${jobName}.conclusion` : "gh-aw.job.conclusion";
5554
console.log(`[otlp] sending conclusion span "${spanName}" to ${endpoint}`);
5655

setup/js/action_input_utils.cjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @ts-check
2+
"use strict";
3+
4+
/**
5+
* Read a GitHub Actions input value, handling both the standard underscore form
6+
* (INPUT_<NAME>) and the hyphen form (INPUT_<NAM-E>) preserved by some runner versions.
7+
*
8+
* GitHub Actions converts input names to INPUT_<UPPER_UNDERSCORE> by default, but
9+
* some runner versions preserve the original hyphen from the input name. Checking
10+
* both forms ensures the value is resolved regardless of the runner version.
11+
*
12+
* @param {string} name - Input name in UPPER_UNDERSCORE form (e.g. "JOB_NAME")
13+
* @returns {string} Trimmed input value, or "" if not set.
14+
*/
15+
function getActionInput(name) {
16+
const hyphenName = name.replace(/_/g, "-");
17+
return (process.env[`INPUT_${name}`] || process.env[`INPUT_${hyphenName}`] || "").trim();
18+
}
19+
20+
module.exports = { getActionInput };

setup/js/action_setup_otlp.cjs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
const path = require("path");
2929
const { appendFileSync } = require("fs");
3030
const { nowMs } = require("./performance_now.cjs");
31+
const { getActionInput } = require("./action_input_utils.cjs");
3132

3233
/**
3334
* Send the OTLP job-setup span and propagate trace context via GITHUB_OUTPUT /
@@ -48,24 +49,17 @@ async function run() {
4849
// Explicitly read INPUT_TRACE_ID and pass it as options.traceId so the
4950
// activation job's trace ID is used even when process.env propagation
5051
// through GitHub Actions expression evaluation is unreliable.
51-
// Also handle INPUT_TRACE-ID (with hyphen) in case the runner preserves
52-
// the original input name hyphen instead of converting it to an underscore.
53-
const inputTraceId = (process.env.INPUT_TRACE_ID || process.env["INPUT_TRACE-ID"] || "").trim().toLowerCase();
52+
const inputTraceId = getActionInput("TRACE_ID").toLowerCase();
5453
if (inputTraceId) {
5554
console.log(`[otlp] INPUT_TRACE_ID=${inputTraceId} (will reuse activation trace)`);
5655
} else {
5756
console.log("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated");
5857
}
5958

60-
// Normalize job-name input: handle both INPUT_JOB_NAME (underscore, standard)
61-
// and INPUT_JOB-NAME (hyphen, used by some runner versions). Mirror the same
62-
// two-key lookup that INPUT_TRACE_ID uses above so script-mode invocations
63-
// (setup.sh → node action_setup_otlp.cjs) always resolve the job name even
64-
// when the runner preserves the original hyphen in the env var name.
65-
const inputJobName = (process.env.INPUT_JOB_NAME || process.env["INPUT_JOB-NAME"] || "").trim();
59+
// Normalize to the canonical underscore form so sendJobSetupSpan (which
60+
// reads process.env.INPUT_JOB_NAME) always finds the value.
61+
const inputJobName = getActionInput("JOB_NAME");
6662
if (inputJobName) {
67-
// Normalise to the canonical underscore form so sendJobSetupSpan (which
68-
// reads process.env.INPUT_JOB_NAME) always finds the value.
6963
process.env.INPUT_JOB_NAME = inputJobName;
7064
}
7165

@@ -104,7 +98,7 @@ async function run() {
10498
}
10599
// Propagate setup-end timestamp so the conclusion span can measure actual
106100
// job execution duration (setup-end → conclusion-start).
107-
const setupEndMs = Math.round(nowMs());
101+
const setupEndMs = Math.floor(nowMs());
108102
appendFileSync(process.env.GITHUB_ENV, `GITHUB_AW_OTEL_JOB_START_MS=${setupEndMs}\n`);
109103
console.log(`[otlp] GITHUB_AW_OTEL_JOB_START_MS written to GITHUB_ENV`);
110104
}

setup/js/add_reviewer.cjs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,24 @@ const { COPILOT_REVIEWER_BOT } = require("./constants.cjs");
1919
* @type {HandlerFactoryFunction}
2020
*/
2121
async function main(config = {}) {
22-
// Extract configuration
23-
const allowedReviewers = config.allowed || [];
24-
const maxCount = config.max || 10;
22+
const allowedReviewers = config.allowed ?? [];
23+
const maxCount = config.max ?? 10;
2524
const githubClient = await createAuthenticatedGitHubClient(config);
26-
27-
// Check if we're in staged mode
2825
const isStaged = isStagedMode(config);
2926

3027
core.info(`Add reviewer configuration: max=${maxCount}`);
3128
if (allowedReviewers.length > 0) {
3229
core.info(`Allowed reviewers: ${allowedReviewers.join(", ")}`);
3330
}
3431

35-
// Track how many items we've processed for max limit
3632
let processedCount = 0;
3733

3834
/**
39-
* Message handler function that processes a single add_reviewer message
4035
* @param {Object} message - The add_reviewer message to process
4136
* @param {Object} resolvedTemporaryIds - Map of temporary IDs to {repo, number}
4237
* @returns {Promise<Object>} Result with success/error status
4338
*/
4439
return async function handleAddReviewer(message, resolvedTemporaryIds) {
45-
// Check if we've hit the max limit
4640
if (processedCount >= maxCount) {
4741
core.warning(`Skipping add_reviewer: max count of ${maxCount} reached`);
4842
return {
@@ -53,7 +47,6 @@ async function main(config = {}) {
5347

5448
processedCount++;
5549

56-
// Determine PR number using helper
5750
const { prNumber, error } = getPullRequestNumber(message, context);
5851

5952
if (error) {
@@ -64,7 +57,7 @@ async function main(config = {}) {
6457
};
6558
}
6659

67-
const requestedReviewers = message.reviewers || [];
60+
const requestedReviewers = message.reviewers ?? [];
6861
core.info(`Requested reviewers: ${JSON.stringify(requestedReviewers)}`);
6962

7063
// Use shared helper to filter, sanitize, dedupe, and limit

setup/js/check_workflow_timestamp_api.cjs

Lines changed: 69 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,17 @@ async function main() {
4545

4646
// Determine workflow source repository from the workflow ref for cross-repo support.
4747
//
48-
// For cross-repo workflow_call invocations (reusable workflows called from another repo),
49-
// the GITHUB_WORKFLOW_REF env var always points to the TOP-LEVEL CALLER's workflow, not
50-
// the reusable workflow being executed. This causes the script to look for lock files in
51-
// the wrong repository.
48+
// For cross-repo reusable workflow invocations, both GITHUB_WORKFLOW_REF (env var) and
49+
// ${{ github.workflow_ref }} (injected as GH_AW_CONTEXT_WORKFLOW_REF) resolve to the
50+
// TOP-LEVEL CALLER's workflow, not the reusable workflow being executed. This causes the
51+
// script to look for lock files in the wrong repository when used alone.
5252
//
53-
// The GitHub Actions expression ${{ github.workflow_ref }} is injected as GH_AW_CONTEXT_WORKFLOW_REF
54-
// by the compiler and correctly identifies the CURRENT reusable workflow's ref even in
55-
// cross-repo workflow_call scenarios. We prefer it over GITHUB_WORKFLOW_REF when available.
53+
// The reliable fix is the referenced_workflows API lookup below, which identifies the
54+
// callee's repo/ref from the caller's run object. GH_AW_CONTEXT_WORKFLOW_REF is only
55+
// used as a fallback when the API lookup is unavailable or finds no matching entry.
5656
//
57-
// Ref: https://github.com/github/gh-aw/issues/23935
57+
// Refs: https://github.com/github/gh-aw/issues/23935
58+
// https://github.com/github/gh-aw/issues/24422
5859
const workflowEnvRef = process.env.GH_AW_CONTEXT_WORKFLOW_REF || process.env.GITHUB_WORKFLOW_REF || "";
5960
const currentRepo = process.env.GITHUB_REPOSITORY || `${context.repo.owner}/${context.repo.repo}`;
6061

@@ -82,14 +83,18 @@ async function main() {
8283
ref = undefined;
8384
}
8485

85-
// For workflow_call events, use referenced_workflows from the GitHub API run object to
86-
// resolve the callee (reusable workflow) repo and ref.
86+
// Attempt referenced_workflows API lookup to detect cross-repo callee repo/ref.
87+
//
88+
// IMPORTANT: GITHUB_EVENT_NAME inside a reusable workflow reflects the ORIGINAL trigger
89+
// event (e.g., "push", "issues"), NOT "workflow_call". We therefore cannot rely on event
90+
// name to detect cross-repo scenarios.
91+
//
92+
// Similarly, GH_AW_CONTEXT_WORKFLOW_REF (${{ github.workflow_ref }}) resolves to the
93+
// CALLER's workflow ref, not the callee's. It is used as a fallback only when the API
94+
// lookup is unavailable or finds no matching entry.
8795
//
8896
// Resolution priority:
8997
// 1. referenced_workflows[].sha — immutable commit SHA from the callee repo (most precise).
90-
// GH_AW_CONTEXT_WORKFLOW_REF (${{ github.workflow_ref }}) correctly identifies the callee
91-
// in most cases, but referenced_workflows carries the pinned sha which won't drift if a
92-
// branch ref moves during a long-running job.
9398
// 2. referenced_workflows[].ref — branch/tag ref from the callee (fallback when sha absent).
9499
// 3. GH_AW_CONTEXT_WORKFLOW_REF — injected by the compiler; used when the API is unavailable
95100
// or when no matching entry is found in referenced_workflows.
@@ -98,60 +103,66 @@ async function main() {
98103
// are set to the caller's run ID and repo. The caller's run object includes a
99104
// referenced_workflows array listing the callee's exact path, sha, and ref.
100105
//
101-
// GITHUB_EVENT_NAME and GITHUB_RUN_ID are always set in GitHub Actions environments.
102-
// context.eventName / context.runId are fallbacks for environments where env vars are absent.
106+
// Short-circuit: if the env workflow ref already ends with the current workflow file,
107+
// the env vars already correctly identify the source (same-repo or non-reusable run).
108+
// Skip the API call to avoid unnecessary rate-limit usage and permission noise.
109+
//
110+
// GITHUB_RUN_ID is always set in GitHub Actions environments.
111+
// context.runId is a fallback for environments where env vars are absent.
103112
//
104-
// Ref: https://github.com/github/gh-aw/issues/24422
105-
const eventName = process.env.GITHUB_EVENT_NAME || context.eventName;
106-
if (eventName === "workflow_call") {
107-
const runId = parseInt(process.env.GITHUB_RUN_ID || String(context.runId), 10);
108-
if (Number.isFinite(runId)) {
109-
const [runOwner, runRepo] = currentRepo.split("/");
110-
try {
111-
core.info(`workflow_call event detected, resolving callee repo via referenced_workflows API (run ${runId})`);
112-
const runResponse = await github.rest.actions.getWorkflowRun({
113-
owner: runOwner,
114-
repo: runRepo,
115-
run_id: runId,
116-
});
117-
118-
const referencedWorkflows = runResponse.data.referenced_workflows || [];
119-
core.info(`Found ${referencedWorkflows.length} referenced workflow(s) in caller run`);
120-
121-
// Find the entry whose path matches the current workflow file.
122-
// Path format: "org/repo/.github/workflows/file.lock.yml@ref"
123-
// Using replace to robustly strip the optional @ref suffix before matching.
124-
const matchingEntry = referencedWorkflows.find(wf => {
125-
const pathWithoutRef = wf.path.replace(/@.*$/, "");
126-
return pathWithoutRef.endsWith(`/.github/workflows/${workflowFile}`);
127-
});
128-
129-
if (matchingEntry) {
130-
const pathMatch = matchingEntry.path.match(GITHUB_REPO_PATH_RE);
131-
if (pathMatch) {
132-
owner = pathMatch[1];
133-
repo = pathMatch[2];
134-
// Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref.
135-
ref = matchingEntry.sha || matchingEntry.ref || pathMatch[3];
136-
workflowRepo = `${owner}/${repo}`;
137-
core.info(`Resolved callee repo from referenced_workflows: ${owner}/${repo} @ ${ref || "(default branch)"}`);
138-
core.info(` Referenced workflow path: ${matchingEntry.path}`);
139-
}
140-
} else {
141-
core.info(`No matching entry in referenced_workflows for "${workflowFile}", falling back to GH_AW_CONTEXT_WORKFLOW_REF`);
113+
// Refs: https://github.com/github/gh-aw/issues/24422
114+
const runId = parseInt(process.env.GITHUB_RUN_ID || String(context.runId), 10);
115+
const envRefWithoutAt = workflowEnvRef.replace(/@.*$/, "");
116+
const envRefMatchesWorkflow = envRefWithoutAt.endsWith(`/.github/workflows/${workflowFile}`);
117+
118+
if (envRefMatchesWorkflow) {
119+
core.info("Env workflow ref already identifies this workflow, skipping referenced_workflows API lookup");
120+
} else if (Number.isFinite(runId)) {
121+
const [runOwner, runRepo] = currentRepo.split("/");
122+
try {
123+
core.info(`Checking for cross-repo callee via referenced_workflows API (run ${runId})`);
124+
const runResponse = await github.rest.actions.getWorkflowRun({
125+
owner: runOwner,
126+
repo: runRepo,
127+
run_id: runId,
128+
});
129+
130+
const referencedWorkflows = runResponse.data.referenced_workflows || [];
131+
core.info(`Found ${referencedWorkflows.length} referenced workflow(s) in run`);
132+
133+
// Find the entry whose path matches the current workflow file.
134+
// Path format: "org/repo/.github/workflows/file.lock.yml@ref"
135+
// Using replace to robustly strip the optional @ref suffix before matching.
136+
const matchingEntry = referencedWorkflows.find(wf => {
137+
const pathWithoutRef = wf.path.replace(/@.*$/, "");
138+
return pathWithoutRef.endsWith(`/.github/workflows/${workflowFile}`);
139+
});
140+
141+
if (matchingEntry) {
142+
const pathMatch = matchingEntry.path.match(GITHUB_REPO_PATH_RE);
143+
if (pathMatch) {
144+
owner = pathMatch[1];
145+
repo = pathMatch[2];
146+
// Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref.
147+
ref = matchingEntry.sha || matchingEntry.ref || pathMatch[3];
148+
workflowRepo = `${owner}/${repo}`;
149+
core.info(`Resolved callee repo from referenced_workflows: ${owner}/${repo} @ ${ref || "(default branch)"}`);
150+
core.info(` Referenced workflow path: ${matchingEntry.path}`);
142151
}
143-
} catch (error) {
144-
core.info(`Could not fetch referenced_workflows from API: ${getErrorMessage(error)}, falling back to GH_AW_CONTEXT_WORKFLOW_REF`);
152+
} else {
153+
core.info(`No matching entry in referenced_workflows for "${workflowFile}", falling back to GH_AW_CONTEXT_WORKFLOW_REF`);
145154
}
146-
} else {
147-
core.info("workflow_call event detected but run ID is unavailable or invalid, falling back to GH_AW_CONTEXT_WORKFLOW_REF");
155+
} catch (error) {
156+
core.info(`Could not fetch referenced_workflows from API: ${getErrorMessage(error)}, falling back to GH_AW_CONTEXT_WORKFLOW_REF`);
148157
}
158+
} else {
159+
core.info("Run ID is unavailable or invalid, falling back to GH_AW_CONTEXT_WORKFLOW_REF");
149160
}
150161

151162
const contextWorkflowRef = process.env.GH_AW_CONTEXT_WORKFLOW_REF;
152163
core.info(`GITHUB_WORKFLOW_REF: ${process.env.GITHUB_WORKFLOW_REF || "(not set)"}`);
153164
if (contextWorkflowRef) {
154-
core.info(`GH_AW_CONTEXT_WORKFLOW_REF: ${contextWorkflowRef} (used for source repo resolution)`);
165+
core.info(`GH_AW_CONTEXT_WORKFLOW_REF: ${contextWorkflowRef} (available as env fallback)`);
155166
}
156167
core.info(`GITHUB_REPOSITORY: ${currentRepo}`);
157168
core.info(`Resolved source repo: ${owner}/${repo} @ ${ref || "(default branch)"}`);

0 commit comments

Comments
 (0)