mirror of
https://github.com/github/codeql-action.git
synced 2026-11-05 04:29:40 +00:00
Maintain action versions by workflow in sync-back.ts
This avoids failures on PRs where `sync-back.ts` is run as part of the "Rebuild" job if there are already version inconsistencies on the base branch.
This commit is contained in:
@@ -56,9 +56,18 @@ jobs:
|
||||
|
||||
const result = scanGeneratedWorkflows(workflowDir);
|
||||
|
||||
assert.equal(result["actions/checkout"], "v4");
|
||||
assert.equal(result["actions/setup-node"], "v5");
|
||||
assert.equal(result["actions/setup-go"], "v6");
|
||||
assert.deepEqual(
|
||||
result.actionVersions["actions/checkout"],
|
||||
new Set(["v4"]),
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.actionVersions["actions/setup-node"],
|
||||
new Set(["v5"]),
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.actionVersions["actions/setup-go"],
|
||||
new Set(["v6"]),
|
||||
);
|
||||
});
|
||||
|
||||
await it("scanning workflows with version comments", () => {
|
||||
@@ -78,12 +87,18 @@ jobs:
|
||||
|
||||
const result = scanGeneratedWorkflows(workflowDir);
|
||||
|
||||
assert.equal(result["actions/checkout"], "v4");
|
||||
assert.equal(
|
||||
result["ruby/setup-ruby"],
|
||||
"44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0",
|
||||
assert.deepEqual(
|
||||
result.actionVersions["actions/checkout"],
|
||||
new Set(["v4"]),
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.actionVersions["ruby/setup-ruby"],
|
||||
new Set(["44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0"]),
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.actionVersions["actions/setup-python"],
|
||||
new Set(["v6 # Latest Python"]),
|
||||
);
|
||||
assert.equal(result["actions/setup-python"], "v6 # Latest Python");
|
||||
});
|
||||
|
||||
await it("ignores local actions", () => {
|
||||
@@ -103,9 +118,15 @@ jobs:
|
||||
|
||||
const result = scanGeneratedWorkflows(workflowDir);
|
||||
|
||||
assert.equal(result["actions/checkout"], "v4");
|
||||
assert.equal("./.github/actions/local-action" in result, false);
|
||||
assert.equal("./another-local-action" in result, false);
|
||||
assert.deepEqual(
|
||||
result.actionVersions["actions/checkout"],
|
||||
new Set(["v4"]),
|
||||
);
|
||||
assert.equal(
|
||||
"./.github/actions/local-action" in result.actionVersions,
|
||||
false,
|
||||
);
|
||||
assert.equal("./another-local-action" in result.actionVersions, false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,8 +149,8 @@ const steps = [
|
||||
fs.writeFileSync(syncTsPath, syncTsContent);
|
||||
|
||||
const actionVersions = {
|
||||
"actions/setup-node": "v5",
|
||||
"actions/setup-go": "v6",
|
||||
"actions/setup-node": new Set(["v5"]),
|
||||
"actions/setup-go": new Set(["v6"]),
|
||||
};
|
||||
|
||||
const result = updateSyncTs(syncTsPath, actionVersions);
|
||||
@@ -155,7 +176,7 @@ const steps = [
|
||||
fs.writeFileSync(syncTsPath, syncTsContent);
|
||||
|
||||
const actionVersions = {
|
||||
"actions/setup-node": "v5 # Latest version",
|
||||
"actions/setup-node": new Set(["v5 # Latest version"]),
|
||||
};
|
||||
|
||||
const result = updateSyncTs(syncTsPath, actionVersions);
|
||||
@@ -182,7 +203,7 @@ const steps = [
|
||||
fs.writeFileSync(syncTsPath, syncTsContent);
|
||||
|
||||
const actionVersions = {
|
||||
"actions/setup-node": "v5",
|
||||
"actions/setup-node": new Set(["v5"]),
|
||||
};
|
||||
|
||||
const result = updateSyncTs(syncTsPath, actionVersions);
|
||||
@@ -206,7 +227,9 @@ const steps = [
|
||||
fs.writeFileSync(syncTsPath, syncTsContent);
|
||||
|
||||
const actionVersions = {
|
||||
"actions/setup-node": "48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0",
|
||||
"actions/setup-node": new Set([
|
||||
"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0",
|
||||
]),
|
||||
};
|
||||
|
||||
const result = updateSyncTs(syncTsPath, actionVersions);
|
||||
@@ -244,8 +267,9 @@ steps:
|
||||
"actions/checkout": "v4",
|
||||
"actions/setup-node": "v5 # Latest",
|
||||
};
|
||||
const byWorkflow = { ["test.yml"]: actionVersions };
|
||||
|
||||
const result = updateTemplateFiles(checksDir, actionVersions);
|
||||
const result = updateTemplateFiles(checksDir, byWorkflow);
|
||||
assert.equal(result.length, 1);
|
||||
assert.ok(result.includes(templatePath));
|
||||
|
||||
@@ -269,8 +293,9 @@ steps:
|
||||
const actionVersions = {
|
||||
"ruby/setup-ruby": "55511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0",
|
||||
};
|
||||
const byWorkflow = { ["test.yml"]: actionVersions };
|
||||
|
||||
const result = updateTemplateFiles(checksDir, actionVersions);
|
||||
const result = updateTemplateFiles(checksDir, byWorkflow);
|
||||
assert.equal(result.length, 1);
|
||||
|
||||
const updatedContent = fs.readFileSync(templatePath, "utf8");
|
||||
|
||||
@@ -27,6 +27,27 @@ const CHECKS_DIR = path.join(PR_CHECKS_DIR, "checks");
|
||||
const WORKFLOW_DIR = path.join(REPO_ROOT, ".github", "workflows");
|
||||
const SYNC_TS_PATH = path.join(PR_CHECKS_DIR, "sync.ts");
|
||||
|
||||
/** Type alias for action names. */
|
||||
type ActionName = string;
|
||||
|
||||
/** Type alias for action versions. May include a comment. */
|
||||
type ActionVersion = string;
|
||||
|
||||
/** Mappings of {@link ActionName}s to {@link ActionVersion}s. */
|
||||
type ActionVersions = Record<ActionName, ActionVersion>;
|
||||
|
||||
/** Mappings of workflow names to {@link ActionVersions}. */
|
||||
type ActionVersionsByWorkflow = Record<string, ActionVersions>;
|
||||
|
||||
/** The result of {@link scanGeneratedWorkflows}. */
|
||||
type ScanWorkflowResult = {
|
||||
/** A mapping of {@link ActionName}s to all encountered {@link ActionVersion}s. */
|
||||
actionVersions: Record<ActionName, Set<ActionVersion>>;
|
||||
|
||||
/** A mapping of workflow names to {@link ActionVersions}. */
|
||||
actionVersionsByWorkflow: ActionVersionsByWorkflow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scan generated workflow files to extract the latest action versions.
|
||||
*
|
||||
@@ -35,8 +56,9 @@ const SYNC_TS_PATH = path.join(PR_CHECKS_DIR, "sync.ts");
|
||||
*/
|
||||
export function scanGeneratedWorkflows(
|
||||
workflowDir: string,
|
||||
): Record<string, string> {
|
||||
const actionVersions: Record<string, string> = {};
|
||||
): ScanWorkflowResult {
|
||||
const actionVersionsByWorkflow: ActionVersionsByWorkflow = {};
|
||||
const actionVersions: Record<ActionName, Set<ActionVersion>> = {};
|
||||
|
||||
const generatedFiles = fs
|
||||
.readdirSync(workflowDir)
|
||||
@@ -46,6 +68,10 @@ export function scanGeneratedWorkflows(
|
||||
for (const filePath of generatedFiles) {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
// Get the basename of the workflow and drop the leading `__` from it.
|
||||
const workflowName = path.basename(filePath).slice(2);
|
||||
actionVersionsByWorkflow[workflowName] = {};
|
||||
|
||||
// Find all action uses in the file, including potential comments
|
||||
// This pattern captures: action_name@version_with_possible_comment
|
||||
const pattern = /uses:\s+([^/\s]+\/[^@\s]+)@([^@\n]+)/g;
|
||||
@@ -57,13 +83,26 @@ export function scanGeneratedWorkflows(
|
||||
|
||||
// Only track non-local actions (those with / but not starting with ./)
|
||||
if (!actionName.startsWith("./")) {
|
||||
// Assume that version numbers are consistent (this should be the case on a Dependabot update PR)
|
||||
actionVersions[actionName] = versionWithComment;
|
||||
// Record the version of the action for this workflow.
|
||||
actionVersionsByWorkflow[workflowName][actionName] = versionWithComment;
|
||||
|
||||
// Record this version for the action so that we can write it to `sync.ts` if needed.
|
||||
// Usually we'd expect there to be exactly one version per action across all workflows,
|
||||
// but sometimes these can go out-of-sync if e.g. a workflow is added in a branch and a
|
||||
// Dependabot PR is merged in the meantime that changes the version of an action that is
|
||||
// used in the new workflow. Different versions of the same action across different
|
||||
// workflows is not fundamentally a problem, but we may need to pick one for `sync.ts`
|
||||
// so we warn there if we have multiple versions of an action.
|
||||
if (actionVersions[actionName] === undefined) {
|
||||
actionVersions[actionName] = new Set([versionWithComment]);
|
||||
} else if (!actionVersions[actionName].has(versionWithComment)) {
|
||||
actionVersions[actionName].add(versionWithComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actionVersions;
|
||||
return { actionVersions, actionVersionsByWorkflow };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +118,7 @@ export function scanGeneratedWorkflows(
|
||||
*/
|
||||
export function updateSyncTs(
|
||||
syncTsPath: string,
|
||||
actionVersions: Record<string, string>,
|
||||
actionVersions: Record<ActionName, Set<ActionVersion>>,
|
||||
): boolean {
|
||||
if (!fs.existsSync(syncTsPath)) {
|
||||
throw new Error(`Could not find ${syncTsPath}`);
|
||||
@@ -89,9 +128,18 @@ export function updateSyncTs(
|
||||
const originalContent = content;
|
||||
|
||||
// Update hardcoded action versions
|
||||
for (const [actionName, versionWithComment] of Object.entries(
|
||||
for (const [actionName, versionsWithComments] of Object.entries(
|
||||
actionVersions,
|
||||
)) {
|
||||
// We usually expect `versionsWithComments` to contain one version. If there are multiple,
|
||||
// we arbitrarily pick one and warn about it.
|
||||
const versionsWithCommentsArray = Array.from(versionsWithComments);
|
||||
const versionWithComment = versionsWithCommentsArray[0];
|
||||
if (versionsWithComments.size > 1) {
|
||||
console.warn(
|
||||
`Inconsistent versions for ${actionName}. Arbitrarily picking '${versionWithComment}'. All found: ${versionsWithCommentsArray.join(", ")}`,
|
||||
);
|
||||
}
|
||||
// Split the scanned value into the ref (e.g. a commit SHA) and the optional
|
||||
// trailing version comment (e.g. `v6.0.3`).
|
||||
const ref = versionWithComment.includes("#")
|
||||
@@ -138,12 +186,12 @@ export function updateSyncTs(
|
||||
* Update action versions in template files in pr-checks/checks/
|
||||
*
|
||||
* @param checksDir - Path to pr-checks/checks directory
|
||||
* @param actionVersions - Map of action names to versions (may include comments)
|
||||
* @param actionVersionsByWorkflow - Map of workflows to maps of action names to versions (may include comments)
|
||||
* @returns List of files that were modified
|
||||
*/
|
||||
export function updateTemplateFiles(
|
||||
checksDir: string,
|
||||
actionVersions: Record<string, string>,
|
||||
actionVersionsByWorkflow: ActionVersionsByWorkflow,
|
||||
): string[] {
|
||||
const modifiedFiles: string[] = [];
|
||||
|
||||
@@ -156,6 +204,16 @@ export function updateTemplateFiles(
|
||||
let content = fs.readFileSync(filePath, "utf8");
|
||||
const originalContent = content;
|
||||
|
||||
const workflowName = path.basename(filePath);
|
||||
const actionVersions = actionVersionsByWorkflow[workflowName];
|
||||
|
||||
if (actionVersions === undefined) {
|
||||
console.info(
|
||||
`Found no action versions for workflow template '${filePath}'.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update action versions
|
||||
for (const [actionName, versionWithComment] of Object.entries(
|
||||
actionVersions,
|
||||
@@ -191,12 +249,15 @@ function main(): number {
|
||||
const verbose = values.verbose ?? false;
|
||||
|
||||
console.info("Scanning generated workflows for latest action versions...");
|
||||
const actionVersions = scanGeneratedWorkflows(WORKFLOW_DIR);
|
||||
const { actionVersions, actionVersionsByWorkflow } =
|
||||
scanGeneratedWorkflows(WORKFLOW_DIR);
|
||||
|
||||
if (verbose) {
|
||||
console.info("Found action versions:");
|
||||
for (const [action, version] of Object.entries(actionVersions)) {
|
||||
console.info(` ${action}@${version}`);
|
||||
for (const [action, versions] of Object.entries(actionVersions)) {
|
||||
for (const version of versions) {
|
||||
console.info(` ${action}@${version}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +276,10 @@ function main(): number {
|
||||
}
|
||||
|
||||
// Update template files
|
||||
const templateModified = updateTemplateFiles(CHECKS_DIR, actionVersions);
|
||||
const templateModified = updateTemplateFiles(
|
||||
CHECKS_DIR,
|
||||
actionVersionsByWorkflow,
|
||||
);
|
||||
modifiedFiles.push(...templateModified);
|
||||
|
||||
if (modifiedFiles.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user