mirror of
https://github.com/github/codeql-action.git
synced 2026-06-03 12:24:38 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25599b3fc8 | |||
| 639dddc728 | |||
| 0b27c71731 | |||
| 6679b049d5 | |||
| 13e47f85a5 | |||
| 5f152be4c2 | |||
| f4b6bd982f |
@@ -0,0 +1,30 @@
|
|||||||
|
export const ACTION_VERSIONS = {
|
||||||
|
"actions/checkout": {
|
||||||
|
"version": "v6"
|
||||||
|
},
|
||||||
|
"actions/setup-go": {
|
||||||
|
"version": "v6"
|
||||||
|
},
|
||||||
|
"actions/setup-dotnet": {
|
||||||
|
"version": "v5"
|
||||||
|
},
|
||||||
|
"actions/upload-artifact": {
|
||||||
|
"version": "v7"
|
||||||
|
},
|
||||||
|
"actions/github-script": {
|
||||||
|
"version": "v8"
|
||||||
|
},
|
||||||
|
"actions/setup-python": {
|
||||||
|
"version": "v6"
|
||||||
|
},
|
||||||
|
"actions/setup-java": {
|
||||||
|
"version": "v5"
|
||||||
|
},
|
||||||
|
"actions/setup-node": {
|
||||||
|
"version": "v6"
|
||||||
|
},
|
||||||
|
"ruby/setup-ruby": {
|
||||||
|
"version": "09a7688d3b55cf0e976497ff046b70949eeaccfd",
|
||||||
|
"comment": " v1.288.0"
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "Autobuild direct tracing (custom working directory)"
|
name: "Autobuild direct tracing (custom working directory)"
|
||||||
description: >
|
description: |
|
||||||
An end-to-end integration test of a Java repository built using 'build-mode: autobuild',
|
An end-to-end integration test of a Java repository built using 'build-mode: autobuild',
|
||||||
with direct tracing enabled and a custom working directory specified as the input to the
|
with direct tracing enabled and a custom working directory specified as the input to the
|
||||||
autobuild Action.
|
autobuild Action.
|
||||||
|
|||||||
@@ -4,12 +4,11 @@
|
|||||||
# basic mechanics of multi-registry auth is working.
|
# basic mechanics of multi-registry auth is working.
|
||||||
name: "Packaging: Download using registries"
|
name: "Packaging: Download using registries"
|
||||||
description: "Checks that specifying a registries block and associated auth works as expected"
|
description: "Checks that specifying a registries block and associated auth works as expected"
|
||||||
versions: [
|
versions:
|
||||||
# This feature is not compatible with older CLIs
|
# This feature is not compatible with older CLIs
|
||||||
"default",
|
- "default"
|
||||||
"linked",
|
- "linked"
|
||||||
"nightly-latest",
|
- "nightly-latest"
|
||||||
]
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -24,9 +23,9 @@ steps:
|
|||||||
config-file: ./.github/codeql/codeql-config-registries.yml
|
config-file: ./.github/codeql/codeql-config-registries.yml
|
||||||
languages: javascript
|
languages: javascript
|
||||||
registries: |
|
registries: |
|
||||||
- url: "https://ghcr.io/v2/"
|
- url: "https://ghcr.io/v2/"
|
||||||
packages: "*/*"
|
packages: "*/*"
|
||||||
token: "${{ secrets.GITHUB_TOKEN }}"
|
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|
||||||
- name: Verify packages installed
|
- name: Verify packages installed
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ versions: ["linked", "nightly-latest"]
|
|||||||
steps:
|
steps:
|
||||||
- uses: ./../action/init
|
- uses: ./../action/init
|
||||||
with:
|
with:
|
||||||
languages: actions # Any language without overlay support will do
|
languages: actions # Any language without overlay support will do
|
||||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||||
env:
|
env:
|
||||||
CODEQL_OVERLAY_DATABASE_MODE: overlay-base
|
CODEQL_OVERLAY_DATABASE_MODE: overlay-base
|
||||||
|
|||||||
+17
-6
@@ -5,6 +5,8 @@ import * as path from "path";
|
|||||||
|
|
||||||
import * as yaml from "yaml";
|
import * as yaml from "yaml";
|
||||||
|
|
||||||
|
import { ACTION_VERSIONS } from "./action-versions";
|
||||||
|
|
||||||
/** Known workflow input names. */
|
/** Known workflow input names. */
|
||||||
enum KnownInputName {
|
enum KnownInputName {
|
||||||
GoVersion = "go-version",
|
GoVersion = "go-version",
|
||||||
@@ -13,6 +15,9 @@ enum KnownInputName {
|
|||||||
DotnetVersion = "dotnet-version",
|
DotnetVersion = "dotnet-version",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Known Action names that we have version information for. */
|
||||||
|
type KnownAction = keyof typeof ACTION_VERSIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents workflow input definitions.
|
* Represents workflow input definitions.
|
||||||
*/
|
*/
|
||||||
@@ -94,6 +99,12 @@ const THIS_DIR = __dirname;
|
|||||||
const CHECKS_DIR = path.join(THIS_DIR, "checks");
|
const CHECKS_DIR = path.join(THIS_DIR, "checks");
|
||||||
const OUTPUT_DIR = path.join(THIS_DIR, "..", ".github", "workflows");
|
const OUTPUT_DIR = path.join(THIS_DIR, "..", ".github", "workflows");
|
||||||
|
|
||||||
|
/** Gets an `actionName@ref` string for `actionName`. */
|
||||||
|
function getActionRef(actionName: KnownAction): string {
|
||||||
|
const versionInfo = ACTION_VERSIONS[actionName];
|
||||||
|
return `${actionName}@${versionInfo.version}`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and parses a YAML file.
|
* Loads and parses a YAML file.
|
||||||
*/
|
*/
|
||||||
@@ -216,7 +227,7 @@ function main(): void {
|
|||||||
const steps: any[] = [
|
const steps: any[] = [
|
||||||
{
|
{
|
||||||
name: "Check out repository",
|
name: "Check out repository",
|
||||||
uses: "actions/checkout@v6",
|
uses: getActionRef("actions/checkout"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -226,7 +237,7 @@ function main(): void {
|
|||||||
steps.push(
|
steps.push(
|
||||||
{
|
{
|
||||||
name: "Install Node.js",
|
name: "Install Node.js",
|
||||||
uses: "actions/setup-node@v6",
|
uses: getActionRef("actions/setup-node"),
|
||||||
with: {
|
with: {
|
||||||
"node-version": "20.x",
|
"node-version": "20.x",
|
||||||
cache: "npm",
|
cache: "npm",
|
||||||
@@ -265,7 +276,7 @@ function main(): void {
|
|||||||
|
|
||||||
steps.push({
|
steps.push({
|
||||||
name: "Install Go",
|
name: "Install Go",
|
||||||
uses: "actions/setup-go@v6",
|
uses: getActionRef("actions/setup-go"),
|
||||||
with: {
|
with: {
|
||||||
"go-version":
|
"go-version":
|
||||||
"${{ inputs.go-version || '" + baseGoVersionExpr + "' }}",
|
"${{ inputs.go-version || '" + baseGoVersionExpr + "' }}",
|
||||||
@@ -289,7 +300,7 @@ function main(): void {
|
|||||||
|
|
||||||
steps.push({
|
steps.push({
|
||||||
name: "Install Java",
|
name: "Install Java",
|
||||||
uses: "actions/setup-java@v5",
|
uses: getActionRef("actions/setup-java"),
|
||||||
with: {
|
with: {
|
||||||
"java-version":
|
"java-version":
|
||||||
"${{ inputs.java-version || '" + baseJavaVersionExpr + "' }}",
|
"${{ inputs.java-version || '" + baseJavaVersionExpr + "' }}",
|
||||||
@@ -312,7 +323,7 @@ function main(): void {
|
|||||||
steps.push({
|
steps.push({
|
||||||
name: "Install Python",
|
name: "Install Python",
|
||||||
if: "matrix.version != 'nightly-latest'",
|
if: "matrix.version != 'nightly-latest'",
|
||||||
uses: "actions/setup-python@v6",
|
uses: getActionRef("actions/setup-python"),
|
||||||
with: {
|
with: {
|
||||||
"python-version":
|
"python-version":
|
||||||
"${{ inputs.python-version || '" + basePythonVersionExpr + "' }}",
|
"${{ inputs.python-version || '" + basePythonVersionExpr + "' }}",
|
||||||
@@ -333,7 +344,7 @@ function main(): void {
|
|||||||
|
|
||||||
steps.push({
|
steps.push({
|
||||||
name: "Install .NET",
|
name: "Install .NET",
|
||||||
uses: "actions/setup-dotnet@v5",
|
uses: getActionRef("actions/setup-dotnet"),
|
||||||
with: {
|
with: {
|
||||||
"dotnet-version":
|
"dotnet-version":
|
||||||
"${{ inputs.dotnet-version || '" + baseDotNetVersionExpr + "' }}",
|
"${{ inputs.dotnet-version || '" + baseDotNetVersionExpr + "' }}",
|
||||||
|
|||||||
+95
-80
@@ -11,15 +11,18 @@ import * as path from "node:path";
|
|||||||
import { afterEach, beforeEach, describe, it } from "node:test";
|
import { afterEach, beforeEach, describe, it } from "node:test";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Options,
|
||||||
scanGeneratedWorkflows,
|
scanGeneratedWorkflows,
|
||||||
updateSyncTs,
|
updateActionVersions,
|
||||||
updateTemplateFiles,
|
updateTemplateFiles,
|
||||||
} from "./sync_back";
|
} from "./sync_back";
|
||||||
|
|
||||||
let testDir: string;
|
let testDir: string;
|
||||||
let workflowDir: string;
|
let workflowDir: string;
|
||||||
let checksDir: string;
|
let checksDir: string;
|
||||||
let syncTsPath: string;
|
let actionVersionsTsPath: string;
|
||||||
|
|
||||||
|
const defaultOptions: Options = { verbose: false, force: false };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
/** Set up temporary directories and files for testing */
|
/** Set up temporary directories and files for testing */
|
||||||
@@ -29,8 +32,8 @@ beforeEach(() => {
|
|||||||
fs.mkdirSync(workflowDir, { recursive: true });
|
fs.mkdirSync(workflowDir, { recursive: true });
|
||||||
fs.mkdirSync(checksDir, { recursive: true });
|
fs.mkdirSync(checksDir, { recursive: true });
|
||||||
|
|
||||||
// Create sync.ts file path
|
// Create action-versions.ts file path
|
||||||
syncTsPath = path.join(testDir, "pr-checks", "sync.ts");
|
actionVersionsTsPath = path.join(testDir, "pr-checks", "action-versions.ts");
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -56,9 +59,18 @@ jobs:
|
|||||||
|
|
||||||
const result = scanGeneratedWorkflows(workflowDir);
|
const result = scanGeneratedWorkflows(workflowDir);
|
||||||
|
|
||||||
assert.equal(result["actions/checkout"], "v4");
|
assert.deepEqual(result["actions/checkout"], {
|
||||||
assert.equal(result["actions/setup-node"], "v5");
|
version: "v4",
|
||||||
assert.equal(result["actions/setup-go"], "v6");
|
comment: undefined,
|
||||||
|
});
|
||||||
|
assert.deepEqual(result["actions/setup-node"], {
|
||||||
|
version: "v5",
|
||||||
|
comment: undefined,
|
||||||
|
});
|
||||||
|
assert.deepEqual(result["actions/setup-go"], {
|
||||||
|
version: "v6",
|
||||||
|
comment: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("scanning workflows with version comments", () => {
|
it("scanning workflows with version comments", () => {
|
||||||
@@ -78,12 +90,18 @@ jobs:
|
|||||||
|
|
||||||
const result = scanGeneratedWorkflows(workflowDir);
|
const result = scanGeneratedWorkflows(workflowDir);
|
||||||
|
|
||||||
assert.equal(result["actions/checkout"], "v4");
|
assert.deepEqual(result["actions/checkout"], {
|
||||||
assert.equal(
|
version: "v4",
|
||||||
result["ruby/setup-ruby"],
|
comment: undefined,
|
||||||
"44511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0",
|
});
|
||||||
);
|
assert.deepEqual(result["ruby/setup-ruby"], {
|
||||||
assert.equal(result["actions/setup-python"], "v6 # Latest Python");
|
version: "44511735964dcb71245e7e55f72539531f7bc0eb",
|
||||||
|
comment: " v1.257.0",
|
||||||
|
});
|
||||||
|
assert.deepEqual(result["actions/setup-python"], {
|
||||||
|
version: "v6",
|
||||||
|
comment: " Latest Python",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores local actions", () => {
|
it("ignores local actions", () => {
|
||||||
@@ -103,89 +121,76 @@ jobs:
|
|||||||
|
|
||||||
const result = scanGeneratedWorkflows(workflowDir);
|
const result = scanGeneratedWorkflows(workflowDir);
|
||||||
|
|
||||||
assert.equal(result["actions/checkout"], "v4");
|
assert.deepEqual(result["actions/checkout"], {
|
||||||
|
version: "v4",
|
||||||
|
comment: undefined,
|
||||||
|
});
|
||||||
assert.equal("./.github/actions/local-action" in result, false);
|
assert.equal("./.github/actions/local-action" in result, false);
|
||||||
assert.equal("./another-local-action" in result, false);
|
assert.equal("./another-local-action" in result, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("updateSyncTs", () => {
|
describe("updateActionVersions", () => {
|
||||||
it("updates sync.ts file", () => {
|
it("updates action-versions.ts file", () => {
|
||||||
/** Test updating sync.ts file */
|
/** Test updating action-versions.ts file */
|
||||||
const syncTsContent = `
|
const actionVersionsTsContent = `
|
||||||
const steps = [
|
export const ACTION_VERSIONS = {
|
||||||
{
|
"actions/setup-node": {
|
||||||
uses: "actions/setup-node@v4",
|
"version": "v4"
|
||||||
with: { "node-version": "16" },
|
|
||||||
},
|
},
|
||||||
{
|
"actions/setup-go": {
|
||||||
uses: "actions/setup-go@v5",
|
"version": "v5"
|
||||||
with: { "go-version": "1.19" },
|
}
|
||||||
},
|
};
|
||||||
];
|
`.trim();
|
||||||
`;
|
|
||||||
|
|
||||||
fs.writeFileSync(syncTsPath, syncTsContent);
|
fs.writeFileSync(actionVersionsTsPath, actionVersionsTsContent);
|
||||||
|
|
||||||
const actionVersions = {
|
const actionVersions = {
|
||||||
"actions/setup-node": "v5",
|
"actions/setup-node": { version: "v5" },
|
||||||
"actions/setup-go": "v6",
|
"actions/setup-go": { version: "v6" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateSyncTs(syncTsPath, actionVersions);
|
const result = updateActionVersions(
|
||||||
|
defaultOptions,
|
||||||
|
actionVersionsTsPath,
|
||||||
|
actionVersions,
|
||||||
|
);
|
||||||
assert.equal(result, true);
|
assert.equal(result, true);
|
||||||
|
|
||||||
const updatedContent = fs.readFileSync(syncTsPath, "utf8");
|
const updatedContent = fs.readFileSync(actionVersionsTsPath, "utf8");
|
||||||
|
|
||||||
assert.ok(updatedContent.includes('uses: "actions/setup-node@v5"'));
|
assert.ok(
|
||||||
assert.ok(updatedContent.includes('uses: "actions/setup-go@v6"'));
|
updatedContent.includes('"actions/setup-node": {\n "version": "v5"'),
|
||||||
});
|
);
|
||||||
|
assert.ok(
|
||||||
it("strips comments from versions", () => {
|
updatedContent.includes('"actions/setup-go": {\n "version": "v6"'),
|
||||||
/** Test updating sync.ts file when versions have comments */
|
);
|
||||||
const syncTsContent = `
|
|
||||||
const steps = [
|
|
||||||
{
|
|
||||||
uses: "actions/setup-node@v4",
|
|
||||||
with: { "node-version": "16" },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
`;
|
|
||||||
|
|
||||||
fs.writeFileSync(syncTsPath, syncTsContent);
|
|
||||||
|
|
||||||
const actionVersions = {
|
|
||||||
"actions/setup-node": "v5 # Latest version",
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = updateSyncTs(syncTsPath, actionVersions);
|
|
||||||
assert.equal(result, true);
|
|
||||||
|
|
||||||
const updatedContent = fs.readFileSync(syncTsPath, "utf8");
|
|
||||||
|
|
||||||
// sync.ts should get the version without comment
|
|
||||||
assert.ok(updatedContent.includes('uses: "actions/setup-node@v5"'));
|
|
||||||
assert.ok(!updatedContent.includes("# Latest version"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when no changes are needed", () => {
|
it("returns false when no changes are needed", () => {
|
||||||
/** Test that updateSyncTs returns false when no changes are needed */
|
/** Test that updateActionVersions returns false when no changes are needed */
|
||||||
const syncTsContent = `
|
const actionVersionsTsContent = `
|
||||||
const steps = [
|
export const ACTION_VERSIONS = {
|
||||||
{
|
"actions/setup-node": {
|
||||||
uses: "actions/setup-node@v5",
|
"version": "v5"
|
||||||
with: { "node-version": "16" },
|
}
|
||||||
},
|
};
|
||||||
];
|
`.trimStart();
|
||||||
`;
|
|
||||||
|
|
||||||
fs.writeFileSync(syncTsPath, syncTsContent);
|
fs.writeFileSync(actionVersionsTsPath, actionVersionsTsContent);
|
||||||
|
|
||||||
const actionVersions = {
|
const actionVersions = {
|
||||||
"actions/setup-node": "v5",
|
"actions/setup-node": { version: "v5" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateSyncTs(syncTsPath, actionVersions);
|
const result = updateActionVersions(
|
||||||
|
defaultOptions,
|
||||||
|
actionVersionsTsPath,
|
||||||
|
actionVersions,
|
||||||
|
);
|
||||||
|
const updatedContent = fs.readFileSync(actionVersionsTsPath, "utf8");
|
||||||
|
assert.equal(updatedContent, actionVersionsTsContent);
|
||||||
assert.equal(result, false);
|
assert.equal(result, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -206,11 +211,15 @@ steps:
|
|||||||
fs.writeFileSync(templatePath, templateContent);
|
fs.writeFileSync(templatePath, templateContent);
|
||||||
|
|
||||||
const actionVersions = {
|
const actionVersions = {
|
||||||
"actions/checkout": "v4",
|
"actions/checkout": { version: "v4" },
|
||||||
"actions/setup-node": "v5 # Latest",
|
"actions/setup-node": { version: "v5", comment: " Latest" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateTemplateFiles(checksDir, actionVersions);
|
const result = updateTemplateFiles(
|
||||||
|
defaultOptions,
|
||||||
|
checksDir,
|
||||||
|
actionVersions,
|
||||||
|
);
|
||||||
assert.equal(result.length, 1);
|
assert.equal(result.length, 1);
|
||||||
assert.ok(result.includes(templatePath));
|
assert.ok(result.includes(templatePath));
|
||||||
|
|
||||||
@@ -232,11 +241,17 @@ steps:
|
|||||||
fs.writeFileSync(templatePath, templateContent);
|
fs.writeFileSync(templatePath, templateContent);
|
||||||
|
|
||||||
const actionVersions = {
|
const actionVersions = {
|
||||||
"ruby/setup-ruby":
|
"ruby/setup-ruby": {
|
||||||
"55511735964dcb71245e7e55f72539531f7bc0eb # v1.257.0",
|
version: "55511735964dcb71245e7e55f72539531f7bc0eb",
|
||||||
|
comment: " v1.257.0",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = updateTemplateFiles(checksDir, actionVersions);
|
const result = updateTemplateFiles(
|
||||||
|
defaultOptions,
|
||||||
|
checksDir,
|
||||||
|
actionVersions,
|
||||||
|
);
|
||||||
assert.equal(result.length, 1);
|
assert.equal(result.length, 1);
|
||||||
|
|
||||||
const updatedContent = fs.readFileSync(templatePath, "utf8");
|
const updatedContent = fs.readFileSync(templatePath, "utf8");
|
||||||
|
|||||||
+139
-76
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env npx tsx
|
#!/usr/bin/env npx tsx
|
||||||
|
|
||||||
|
import * as yaml from "yaml";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Sync-back script to automatically update action versions in source templates
|
Sync-back script to automatically update action versions in source templates
|
||||||
from the generated workflow files after Dependabot updates.
|
from the generated workflow files after Dependabot updates.
|
||||||
@@ -25,7 +27,71 @@ import * as path from "path";
|
|||||||
const THIS_DIR = __dirname;
|
const THIS_DIR = __dirname;
|
||||||
const CHECKS_DIR = path.join(THIS_DIR, "checks");
|
const CHECKS_DIR = path.join(THIS_DIR, "checks");
|
||||||
const WORKFLOW_DIR = path.join(THIS_DIR, "..", ".github", "workflows");
|
const WORKFLOW_DIR = path.join(THIS_DIR, "..", ".github", "workflows");
|
||||||
const SYNC_TS_PATH = path.join(THIS_DIR, "sync.ts");
|
const ACTION_VERSIONS_PATH = path.join(THIS_DIR, "action-versions.ts");
|
||||||
|
|
||||||
|
/** Command-line options for this program. */
|
||||||
|
export type Options = {
|
||||||
|
verbose: boolean;
|
||||||
|
force: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Records information about the version of an Action with an optional comment. */
|
||||||
|
type ActionVersion = { version: string; comment?: string };
|
||||||
|
|
||||||
|
/** Converts `info` to a string that includes the version and comment. */
|
||||||
|
function versionWithCommentStr(info: ActionVersion): string {
|
||||||
|
const comment = info.comment ? ` #${info.comment}` : "";
|
||||||
|
return `${info.version}${comment}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a `yaml.visitor` which calls `fn` for `yaml.Pair` nodes where the key is "uses" and
|
||||||
|
* the value is a `yaml.Scalar`.
|
||||||
|
*/
|
||||||
|
function usesVisitor(
|
||||||
|
fn: (
|
||||||
|
pair: yaml.Pair<yaml.Scalar, yaml.Scalar>,
|
||||||
|
actionName: string,
|
||||||
|
actionVersion: ActionVersion,
|
||||||
|
) => void,
|
||||||
|
): yaml.visitor {
|
||||||
|
return {
|
||||||
|
Pair(_, pair) {
|
||||||
|
if (
|
||||||
|
yaml.isScalar(pair.key) &&
|
||||||
|
yaml.isScalar(pair.value) &&
|
||||||
|
pair.key.value === "uses" &&
|
||||||
|
typeof pair.value.value === "string"
|
||||||
|
) {
|
||||||
|
const usesValue = pair.value.value;
|
||||||
|
|
||||||
|
// Only track non-local actions (those with / but not starting with ./)
|
||||||
|
if (!usesValue.startsWith("./")) {
|
||||||
|
const parts = (pair.value.value as string).split("@");
|
||||||
|
|
||||||
|
if (parts.length !== 2) {
|
||||||
|
throw new Error(`Unexpected 'uses' value: ${usesValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionName = parts[0];
|
||||||
|
const actionVersion = parts[1].trimEnd();
|
||||||
|
const comment = pair.value.comment?.trimEnd();
|
||||||
|
|
||||||
|
fn(pair as yaml.Pair<yaml.Scalar, yaml.Scalar>, actionName, {
|
||||||
|
version: actionVersion,
|
||||||
|
comment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not visit the children of this node.
|
||||||
|
return yaml.visit.SKIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing and continue.
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan generated workflow files to extract the latest action versions.
|
* Scan generated workflow files to extract the latest action versions.
|
||||||
@@ -33,8 +99,10 @@ const SYNC_TS_PATH = path.join(THIS_DIR, "sync.ts");
|
|||||||
* @param workflowDir - Path to .github/workflows directory
|
* @param workflowDir - Path to .github/workflows directory
|
||||||
* @returns Map from action names to their latest versions (including comments)
|
* @returns Map from action names to their latest versions (including comments)
|
||||||
*/
|
*/
|
||||||
export function scanGeneratedWorkflows(workflowDir: string): Record<string, string> {
|
export function scanGeneratedWorkflows(
|
||||||
const actionVersions: Record<string, string> = {};
|
workflowDir: string,
|
||||||
|
): Record<string, ActionVersion> {
|
||||||
|
const actionVersions: Record<string, ActionVersion> = {};
|
||||||
|
|
||||||
const generatedFiles = fs
|
const generatedFiles = fs
|
||||||
.readdirSync(workflowDir)
|
.readdirSync(workflowDir)
|
||||||
@@ -43,86 +111,63 @@ export function scanGeneratedWorkflows(workflowDir: string): Record<string, stri
|
|||||||
|
|
||||||
for (const filePath of generatedFiles) {
|
for (const filePath of generatedFiles) {
|
||||||
const content = fs.readFileSync(filePath, "utf8");
|
const content = fs.readFileSync(filePath, "utf8");
|
||||||
|
const doc = yaml.parseDocument(content);
|
||||||
|
|
||||||
// Find all action uses in the file, including potential comments
|
yaml.visit(
|
||||||
// This pattern captures: action_name@version_with_possible_comment
|
doc,
|
||||||
const pattern = /uses:\s+([^/\s]+\/[^@\s]+)@([^@\n]+)/g;
|
usesVisitor((_node, actionName, actionVersion) => {
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
|
|
||||||
while ((match = pattern.exec(content)) !== null) {
|
|
||||||
const actionName = match[1];
|
|
||||||
const versionWithComment = match[2].trimEnd();
|
|
||||||
|
|
||||||
// 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)
|
// Assume that version numbers are consistent (this should be the case on a Dependabot update PR)
|
||||||
actionVersions[actionName] = versionWithComment;
|
actionVersions[actionName] = actionVersion;
|
||||||
}
|
}),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actionVersions;
|
return actionVersions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update hardcoded action versions in pr-checks/sync.ts
|
* Update hardcoded action versions in pr-checks/action-versions.ts
|
||||||
*
|
*
|
||||||
* @param syncTsPath - Path to sync.ts file
|
* @param options - The command-line options.
|
||||||
|
* @param actionVersionsTsPath - Path to action-versions.ts file
|
||||||
* @param actionVersions - Map of action names to versions (may include comments)
|
* @param actionVersions - Map of action names to versions (may include comments)
|
||||||
* @returns True if the file was modified, false otherwise
|
* @returns True if the file was modified, false otherwise
|
||||||
*/
|
*/
|
||||||
export function updateSyncTs(
|
export function updateActionVersions(
|
||||||
syncTsPath: string,
|
options: Options,
|
||||||
actionVersions: Record<string, string>,
|
actionVersionsTsPath: string,
|
||||||
|
actionVersions: Record<string, ActionVersion>,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!fs.existsSync(syncTsPath)) {
|
// Build content for the file.
|
||||||
throw new Error(`Could not find ${syncTsPath}`);
|
let newContent: string = `export const ACTION_VERSIONS = ${JSON.stringify(actionVersions, null, 2)};\n`;
|
||||||
}
|
|
||||||
|
|
||||||
let content = fs.readFileSync(syncTsPath, "utf8");
|
if (fs.existsSync(actionVersionsTsPath)) {
|
||||||
const originalContent = content;
|
const content = fs.readFileSync(actionVersionsTsPath, "utf8");
|
||||||
|
|
||||||
|
if (content === newContent && !options.force) {
|
||||||
|
console.info(`No changes needed in ${actionVersionsTsPath}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update hardcoded action versions
|
// Update hardcoded action versions
|
||||||
for (const [actionName, versionWithComment] of Object.entries(
|
fs.writeFileSync(actionVersionsTsPath, newContent, "utf8");
|
||||||
actionVersions,
|
console.info(`Updated ${actionVersionsTsPath}`);
|
||||||
)) {
|
return true;
|
||||||
// Extract just the version part (before any comment) for sync.ts
|
|
||||||
const version = versionWithComment.includes("#")
|
|
||||||
? versionWithComment.split("#")[0].trim()
|
|
||||||
: versionWithComment.trim();
|
|
||||||
|
|
||||||
// Look for patterns like uses: "actions/setup-node@v4"
|
|
||||||
// Note that this will break if we store an Action uses reference in a
|
|
||||||
// variable - that's a risk we're happy to take since in that case the
|
|
||||||
// PR checks will just fail.
|
|
||||||
const escaped = actionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
const pattern = new RegExp(
|
|
||||||
`(uses:\\s*")${escaped}@(?:[^"]+)(")`,
|
|
||||||
"g",
|
|
||||||
);
|
|
||||||
content = content.replace(pattern, `$1${actionName}@${version}$2`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== originalContent) {
|
|
||||||
fs.writeFileSync(syncTsPath, content, "utf8");
|
|
||||||
console.info(`Updated ${syncTsPath}`);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
console.info(`No changes needed in ${syncTsPath}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update action versions in template files in pr-checks/checks/
|
* Update action versions in template files in pr-checks/checks/
|
||||||
*
|
*
|
||||||
|
* @param options - The command-line options.
|
||||||
* @param checksDir - Path to pr-checks/checks directory
|
* @param checksDir - Path to pr-checks/checks directory
|
||||||
* @param actionVersions - Map of action names to versions (may include comments)
|
* @param actionVersions - Map of action names to versions (may include comments)
|
||||||
* @returns List of files that were modified
|
* @returns List of files that were modified
|
||||||
*/
|
*/
|
||||||
export function updateTemplateFiles(
|
export function updateTemplateFiles(
|
||||||
|
options: Options,
|
||||||
checksDir: string,
|
checksDir: string,
|
||||||
actionVersions: Record<string, string>,
|
actionVersions: Record<string, ActionVersion>,
|
||||||
): string[] {
|
): string[] {
|
||||||
const modifiedFiles: string[] = [];
|
const modifiedFiles: string[] = [];
|
||||||
|
|
||||||
@@ -132,24 +177,33 @@ export function updateTemplateFiles(
|
|||||||
.map((f) => path.join(checksDir, f));
|
.map((f) => path.join(checksDir, f));
|
||||||
|
|
||||||
for (const filePath of templateFiles) {
|
for (const filePath of templateFiles) {
|
||||||
let content = fs.readFileSync(filePath, "utf8");
|
const content = fs.readFileSync(filePath, "utf8");
|
||||||
const originalContent = content;
|
const doc = yaml.parseDocument(content, { keepSourceTokens: true });
|
||||||
|
let modified: boolean = false;
|
||||||
|
|
||||||
// Update action versions
|
yaml.visit(
|
||||||
for (const [actionName, versionWithComment] of Object.entries(
|
doc,
|
||||||
actionVersions,
|
usesVisitor((pair, actionName, actionVersion) => {
|
||||||
)) {
|
// Try to look up version information for this action.
|
||||||
// Look for patterns like 'uses: actions/setup-node@v4' or 'uses: actions/setup-node@sha # comment'
|
const versionInfo = actionVersions[actionName];
|
||||||
const escaped = actionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
const pattern = new RegExp(
|
// If we found version information, and the version is different from that in the template,
|
||||||
`(uses:\\s+${escaped})@(?:[^@\n]+)`,
|
// then update the pair node accordingly.
|
||||||
"g",
|
if (versionInfo && versionInfo.version !== actionVersion.version) {
|
||||||
|
pair.value.value = `${actionName}@${versionInfo.version}`;
|
||||||
|
pair.value.comment = versionInfo.comment;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Write the YAML document back to the file if we made changes.
|
||||||
|
if (modified || options.force) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
filePath,
|
||||||
|
doc.toString({ lineWidth: 0, flowCollectionPadding: false }),
|
||||||
|
"utf8",
|
||||||
);
|
);
|
||||||
content = content.replace(pattern, `$1@${versionWithComment}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== originalContent) {
|
|
||||||
fs.writeFileSync(filePath, content, "utf8");
|
|
||||||
modifiedFiles.push(filePath);
|
modifiedFiles.push(filePath);
|
||||||
console.info(`Updated ${filePath}`);
|
console.info(`Updated ${filePath}`);
|
||||||
}
|
}
|
||||||
@@ -166,6 +220,11 @@ function main(): number {
|
|||||||
short: "v",
|
short: "v",
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
force: {
|
||||||
|
type: "boolean",
|
||||||
|
short: "f",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
strict: true,
|
strict: true,
|
||||||
});
|
});
|
||||||
@@ -178,7 +237,7 @@ function main(): number {
|
|||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.info("Found action versions:");
|
console.info("Found action versions:");
|
||||||
for (const [action, version] of Object.entries(actionVersions)) {
|
for (const [action, version] of Object.entries(actionVersions)) {
|
||||||
console.info(` ${action}@${version}`);
|
console.info(` ${action}@${versionWithCommentStr(version)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +251,16 @@ function main(): number {
|
|||||||
const modifiedFiles: string[] = [];
|
const modifiedFiles: string[] = [];
|
||||||
|
|
||||||
// Update sync.ts
|
// Update sync.ts
|
||||||
if (updateSyncTs(SYNC_TS_PATH, actionVersions)) {
|
if (updateActionVersions(values, ACTION_VERSIONS_PATH, actionVersions)) {
|
||||||
modifiedFiles.push(SYNC_TS_PATH);
|
modifiedFiles.push(ACTION_VERSIONS_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update template files
|
// Update template files
|
||||||
const templateModified = updateTemplateFiles(CHECKS_DIR, actionVersions);
|
const templateModified = updateTemplateFiles(
|
||||||
|
values,
|
||||||
|
CHECKS_DIR,
|
||||||
|
actionVersions,
|
||||||
|
);
|
||||||
modifiedFiles.push(...templateModified);
|
modifiedFiles.push(...templateModified);
|
||||||
|
|
||||||
if (modifiedFiles.length > 0) {
|
if (modifiedFiles.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user