Compare commits

..

9 Commits

Author SHA1 Message Date
Michael B. Gale a6fad803a6 Rename meta.json to build-metadata.json 2026-04-30 14:30:25 +01:00
Michael B. Gale e09b8dbe8a Merge remote-tracking branch 'origin/main' into mbg/bundle-metadata-improvements 2026-04-30 14:28:42 +01:00
Michael B. Gale 6e368b7e89 Set retention and if-no-files-found 2026-04-13 13:18:56 +01:00
Michael B. Gale 2ab95cc5ab Compare baseline to current metadata 2026-04-10 18:47:56 +01:00
Michael B. Gale af6b899441 Fetch baseline bundle metadata 2026-04-10 18:47:56 +01:00
Michael B. Gale 989d6f4689 Move CODEQL_ACTION_REPO out of sync-checks.ts 2026-04-10 17:29:36 +01:00
Michael B. Gale dbfd510456 Make token parameter reusable 2026-04-10 17:09:18 +01:00
Michael B. Gale 4acf6eb187 Upload meta.json as workflow artifact 2026-04-10 12:22:50 +01:00
Michael B. Gale 8b990db7d7 Do not run bundle-metadata.ts as part of npm run build 2026-04-10 12:18:17 +01:00
39 changed files with 871 additions and 1410 deletions
+1 -3
View File
@@ -1,5 +1,5 @@
name: "CodeQL config"
queries:
queries:
- name: Run custom queries
uses: ./queries
# Run all extra query suites, both because we want to
@@ -13,5 +13,3 @@ queries:
paths-ignore:
- lib
- tests
- "**/*.test.ts"
- "**/testing-util.ts"
+8
View File
@@ -52,6 +52,14 @@ jobs:
- name: Verify compiled JS up to date
run: .github/workflows/script/check-js.sh
- name: Upload esbuild metadata
uses: actions/upload-artifact@v7
with:
name: bundle-metadata-${{ matrix.os }}-${{ matrix.node-version }}
path: build-metadata.json
retention-days: ${{ (github.ref_name == github.event.repository.default_branch && 90) || 7 }}
if-no-files-found: error
- name: Run unit tests
if: always()
run: npm test
+1 -1
View File
@@ -12,4 +12,4 @@ eslint.sarif
# for local incremental compilation
tsconfig.tsbuildinfo
# esbuild metadata file
meta.json
build-metadata.json
+1 -8
View File
@@ -4,15 +4,8 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
## [UNRELEASED]
No user facing changes.
## 4.35.3 - 01 May 2026
- _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. [#3837](https://github.com/github/codeql-action/pull/3837)
- Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. [#3850](https://github.com/github/codeql-action/pull/3850)
- Best-effort connection tests for private registries now use `GET` requests instead of `HEAD` for better compatibility with various registry implementations. For NuGet feeds, the test is now always performed against the service index. [#3853](https://github.com/github/codeql-action/pull/3853)
- Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. [#3852](https://github.com/github/codeql-action/pull/3852)
- Update default CodeQL bundle version to [2.25.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3). [#3865](https://github.com/github/codeql-action/pull/3865)
- _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. [#3837](https://github.com/github/codeql-action/pull/3837)
## 4.35.2 - 15 Apr 2026
+1 -1
View File
@@ -82,6 +82,6 @@ const context = await esbuild.context({
});
const result = await context.rebuild();
await writeFile(join(__dirname, "meta.json"), JSON.stringify(result.metafile));
await writeFile(join(__dirname, "build-metadata.json"), JSON.stringify(result.metafile));
await context.dispose();
+2 -2
View File
@@ -161813,7 +161813,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -162463,7 +162463,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+5 -5
View File
@@ -106982,7 +106982,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -107918,8 +107918,8 @@ var path6 = __toESM(require("path"));
var semver5 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.25.3";
var cliVersion = "2.25.3";
var bundleVersion = "codeql-bundle-v2.25.2";
var cliVersion = "2.25.2";
// src/overlay/index.ts
var fs4 = __toESM(require("fs"));
@@ -107998,7 +107998,7 @@ var determineBaseBranchHeadCommitOid = async function(checkoutPathOverride) {
}
}
}
if (commitOid === mergeSha && (headOid.length === 40 || headOid.length === 64) && (baseOid.length === 40 || baseOid.length === 64)) {
if (commitOid === mergeSha && headOid.length === 40 && baseOid.length === 40) {
return baseOid;
}
return void 0;
@@ -108064,7 +108064,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+4 -4
View File
@@ -103787,7 +103787,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -104405,8 +104405,8 @@ var path5 = __toESM(require("path"));
var semver5 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.25.3";
var cliVersion = "2.25.3";
var bundleVersion = "codeql-bundle-v2.25.2";
var cliVersion = "2.25.2";
// src/overlay/index.ts
var fs3 = __toESM(require("fs"));
@@ -104517,7 +104517,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+4 -4
View File
@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.25.3",
"cliVersion": "2.25.3",
"priorBundleVersion": "codeql-bundle-v2.25.2",
"priorCliVersion": "2.25.2"
"bundleVersion": "codeql-bundle-v2.25.2",
"cliVersion": "2.25.2",
"priorBundleVersion": "codeql-bundle-v2.25.1",
"priorCliVersion": "2.25.1"
}
+5 -5
View File
@@ -164923,7 +164923,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -165837,8 +165837,8 @@ var path6 = __toESM(require("path"));
var semver5 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.25.3";
var cliVersion = "2.25.3";
var bundleVersion = "codeql-bundle-v2.25.2";
var cliVersion = "2.25.2";
// src/overlay/index.ts
var fs4 = __toESM(require("fs"));
@@ -165917,7 +165917,7 @@ var determineBaseBranchHeadCommitOid = async function(checkoutPathOverride) {
}
}
}
if (commitOid === mergeSha && (headOid.length === 40 || headOid.length === 64) && (baseOid.length === 40 || baseOid.length === 64)) {
if (commitOid === mergeSha && headOid.length === 40 && baseOid.length === 40) {
return baseOid;
}
return void 0;
@@ -165983,7 +165983,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+4 -4
View File
@@ -104341,7 +104341,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -105458,8 +105458,8 @@ var path7 = __toESM(require("path"));
var semver5 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.25.3";
var cliVersion = "2.25.3";
var bundleVersion = "codeql-bundle-v2.25.2";
var cliVersion = "2.25.2";
// src/overlay/index.ts
var fs4 = __toESM(require("fs"));
@@ -105595,7 +105595,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+2 -2
View File
@@ -103795,7 +103795,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -104510,7 +104510,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+4 -4
View File
@@ -103882,7 +103882,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -104246,8 +104246,8 @@ var path5 = __toESM(require("path"));
var semver4 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.25.3";
var cliVersion = "2.25.3";
var bundleVersion = "codeql-bundle-v2.25.2";
var cliVersion = "2.25.2";
// src/overlay/index.ts
var fs4 = __toESM(require("fs"));
@@ -104358,7 +104358,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+1 -1
View File
@@ -161760,7 +161760,7 @@ function getTemporaryDirectory() {
return value !== void 0 && value !== "" ? value : getRequiredEnvParam("RUNNER_TEMP");
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
var persistedInputsKey = "persisted_inputs";
var restoreInputs = function() {
+314 -394
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -106688,7 +106688,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -107526,8 +107526,8 @@ var fs5 = __toESM(require("fs"));
var semver5 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.25.3";
var cliVersion = "2.25.3";
var bundleVersion = "codeql-bundle-v2.25.2";
var cliVersion = "2.25.2";
// src/overlay/index.ts
var fs4 = __toESM(require("fs"));
@@ -107606,7 +107606,7 @@ var determineBaseBranchHeadCommitOid = async function(checkoutPathOverride) {
}
}
}
if (commitOid === mergeSha && (headOid.length === 40 || headOid.length === 64) && (baseOid.length === 40 || baseOid.length === 64)) {
if (commitOid === mergeSha && headOid.length === 40 && baseOid.length === 40) {
return baseOid;
}
return void 0;
@@ -107672,7 +107672,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+1 -1
View File
@@ -161760,7 +161760,7 @@ function getTemporaryDirectory() {
return value !== void 0 && value !== "" ? value : getRequiredEnvParam("RUNNER_TEMP");
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
var persistedInputsKey = "persisted_inputs";
var restoreInputs = function() {
+5 -5
View File
@@ -106716,7 +106716,7 @@ function getDiffRangesJsonFilePath() {
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
}
function getActionVersion() {
return "4.35.4";
return "4.35.3";
}
function getWorkflowEventName() {
return getRequiredEnvParam("GITHUB_EVENT_NAME");
@@ -107197,8 +107197,8 @@ var path5 = __toESM(require("path"));
var semver4 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.25.3";
var cliVersion = "2.25.3";
var bundleVersion = "codeql-bundle-v2.25.2";
var cliVersion = "2.25.2";
// src/overlay/index.ts
var fs4 = __toESM(require("fs"));
@@ -107277,7 +107277,7 @@ var determineBaseBranchHeadCommitOid = async function(checkoutPathOverride) {
}
}
}
if (commitOid === mergeSha && (headOid.length === 40 || headOid.length === 64) && (baseOid.length === 40 || baseOid.length === 64)) {
if (commitOid === mergeSha && headOid.length === 40 && baseOid.length === 40) {
return baseOid;
}
return void 0;
@@ -107343,7 +107343,7 @@ var getFileOidsUnderPath = async function(basePath) {
"Cannot list Git OIDs of tracked files."
);
const fileOidMap = {};
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "codeql",
"version": "4.35.4",
"version": "4.35.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeql",
"version": "4.35.4",
"version": "4.35.3",
"license": "MIT",
"workspaces": [
"pr-checks"
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "codeql",
"version": "4.35.4",
"version": "4.35.3",
"private": true,
"description": "CodeQL action",
"scripts": {
"_build_comment": "echo 'Run the full build so we typecheck the project and can reuse the transpiled files in npm test'",
"build": "./scripts/check-node-modules.sh && npm run transpile && node build.mjs && npx tsx ./pr-checks/bundle-metadata.ts",
"build": "./scripts/check-node-modules.sh && npm run transpile && node build.mjs",
"lint": "eslint --report-unused-disable-directives --max-warnings=0 .",
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
+21
View File
@@ -1,8 +1,16 @@
import { ParseArgsConfig } from "node:util";
import * as githubUtils from "@actions/github/lib/utils";
import { type Octokit } from "@octokit/core";
import { type PaginateInterface } from "@octokit/plugin-paginate-rest";
import { type Api } from "@octokit/plugin-rest-endpoint-methods";
/** Identifies the CodeQL Action repository. */
export const CODEQL_ACTION_REPO = {
owner: "github",
repo: "codeql-action",
};
/** The type of the Octokit client. */
export type ApiClient = Octokit & Api & { paginate: PaginateInterface };
@@ -11,3 +19,16 @@ export function getApiClient(token: string): ApiClient {
const opts = githubUtils.getOctokitOptions(token);
return new githubUtils.GitHub(opts);
}
export interface TokenOption {
/** The token to use to authenticate to the GitHub API. */
token?: string;
}
/** Command-line argument parser settings for the token parameter. */
export const TOKEN_OPTION_CONFIG = {
// The token to use to authenticate to the API.
token: {
type: "string",
},
} satisfies ParseArgsConfig["options"];
+146 -7
View File
@@ -1,8 +1,43 @@
#!/usr/bin/env npx tsx
import * as fs from "node:fs/promises";
import { parseArgs, ParseArgsConfig } from "node:util";
import { BUNDLE_METADATA_FILE } from "./config";
import * as exec from "@actions/exec";
import {
ApiClient,
CODEQL_ACTION_REPO,
getApiClient,
TOKEN_OPTION_CONFIG,
} from "./api-client";
import { BASELINE_BUNDLE_METADATA_FILE, BUNDLE_METADATA_FILE } from "./config";
const optionsConfig = {
...TOKEN_OPTION_CONFIG,
branch: {
type: "string",
default: "main",
},
runner: {
type: "string",
default: "macos-latest",
},
"node-version": {
type: "string",
default: "24",
},
} satisfies ParseArgsConfig["options"];
function parseOptions() {
const { values: options } = parseArgs({
options: optionsConfig,
});
return options;
}
type Options = ReturnType<typeof parseOptions>;
interface InputInfo {
bytesInOutput: number;
@@ -23,21 +58,125 @@ function toMB(bytes: number): string {
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
}
async function getBaselineFrom(client: ApiClient, options: Options) {
const workflowRun = await client.rest.actions.listWorkflowRuns({
...CODEQL_ACTION_REPO,
branch: options.branch,
workflow_id: "pr-checks.yml",
status: "success",
per_page: 1,
event: "push",
});
if (workflowRun.data.total_count === 0) {
throw new Error(
`Expected to find a 'pr-checks.yml' run for '${options.branch}', but found none.`,
);
}
const expectedArtifactName = `bundle-metadata-${options.runner}-${options["node-version"]}`;
const artifacts = await client.rest.actions.listWorkflowRunArtifacts({
...CODEQL_ACTION_REPO,
run_id: workflowRun.data.workflow_runs[0].id,
name: expectedArtifactName,
});
if (artifacts.data.total_count === 0) {
throw new Error(
`Expected to find an artifact named '${expectedArtifactName}', but found none.`,
);
}
const downloadInfo = await client.rest.actions.downloadArtifact({
...CODEQL_ACTION_REPO,
artifact_id: artifacts.data.artifacts[0].id,
archive_format: "zip",
});
// This works fine for us with our version of Octokit, so we don't need to
// worry about over-complicating this script and handle other possibilities.
if (downloadInfo.data instanceof ArrayBuffer) {
const archivePath = `${expectedArtifactName}.zip`;
await fs.writeFile(archivePath, Buffer.from(downloadInfo.data));
console.info(`Extracting zip file: ${archivePath}`);
await exec.exec("unzip", ["-o", archivePath, "-d", "."]);
// We no longer need the archive after unzipping it.
await fs.rm(archivePath);
// Check that we have the expected file.
try {
await fs.stat(BASELINE_BUNDLE_METADATA_FILE);
} catch (err) {
throw new Error(
`Expected '${BASELINE_BUNDLE_METADATA_FILE}' to have been extracted, but it does not exist: ${err}`,
);
}
const baselineData = await fs.readFile(BASELINE_BUNDLE_METADATA_FILE);
return JSON.parse(String(baselineData)) as Metadata;
} else {
throw new Error("Expected to receive artifact data, but didn't.");
}
}
async function main() {
const options = parseOptions();
if (options.token === undefined) {
throw new Error("Missing --token");
}
// Initialise the API client.
const client = getApiClient(options.token);
const baselineMetadata = await getBaselineFrom(client, options);
const fileContents = await fs.readFile(BUNDLE_METADATA_FILE);
const metadata = JSON.parse(String(fileContents)) as Metadata;
console.info("Comparing bundle metadata to baseline...");
const filesInBaseline = new Set(Object.keys(baselineMetadata.outputs));
const filesInCurrent = new Set(Object.keys(metadata.outputs));
const filesNotPresent = filesInBaseline.difference(filesInCurrent);
if (filesNotPresent.size > 0) {
console.info(`Found ${filesNotPresent.size} file(s) which were removed:`);
for (const removedFile of filesNotPresent) {
console.info(` - ${removedFile}`);
}
}
for (const [outputFile, outputData] of Object.entries(
metadata.outputs,
).reverse()) {
console.info(`${outputFile}: ${toMB(outputData.bytes)}`);
const baselineOutputData = baselineMetadata.outputs[outputFile];
for (const [inputName, inputData] of Object.entries(outputData.inputs)) {
// Ignore any inputs that make up less than 5% of the output.
const percentage = (inputData.bytesInOutput / outputData.bytes) * 100.0;
if (percentage < 5.0) continue;
if (baselineOutputData === undefined) {
console.info(`${outputFile}: New file (${toMB(outputData.bytes)})`);
} else {
const percentageDifference =
((outputData.bytes - baselineOutputData.bytes) /
baselineOutputData.bytes) *
100.0;
console.info(` ${inputName}: ${toMB(inputData.bytesInOutput)}`);
if (Math.abs(percentageDifference) >= 5) {
console.info(
`${outputFile}: ${toMB(outputData.bytes)} (${percentageDifference.toFixed(2)}%)`,
);
for (const [inputName, inputData] of Object.entries(
outputData.inputs,
)) {
// Ignore any inputs that make up less than 5% of the output.
const percentage =
(inputData.bytesInOutput / outputData.bytes) * 100.0;
if (percentage < 5.0) continue;
console.info(` ${inputName}: ${toMB(inputData.bytesInOutput)}`);
}
}
}
}
}
+11 -1
View File
@@ -10,7 +10,17 @@ export const PR_CHECKS_DIR = __dirname;
export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml");
/** The path to the esbuild metadata file. */
export const BUNDLE_METADATA_FILE = path.join(PR_CHECKS_DIR, "..", "meta.json");
export const BUNDLE_METADATA_FILE = path.join(
PR_CHECKS_DIR,
"..",
"build-metadata.json",
);
/** The path of the baseline esbuild metadata file, once extracted from a workflow artifact. */
export const BASELINE_BUNDLE_METADATA_FILE = path.join(
PR_CHECKS_DIR,
"build-metadata.json",
);
/** The `src` directory. */
const SOURCE_ROOT = path.join(PR_CHECKS_DIR, "..", "src");
+13 -18
View File
@@ -7,16 +7,20 @@ import { parseArgs } from "node:util";
import * as yaml from "yaml";
import { type ApiClient, getApiClient } from "./api-client";
import {
type ApiClient,
CODEQL_ACTION_REPO,
getApiClient,
TOKEN_OPTION_CONFIG,
TokenOption,
} from "./api-client";
import {
OLDEST_SUPPORTED_MAJOR_VERSION,
PR_CHECK_EXCLUDED_FILE,
} from "./config";
/** Represents the command-line options. */
export interface Options {
/** The token to use to authenticate to the GitHub API. */
token?: string;
export interface Options extends TokenOption {
/** The git ref to use the checks for. */
ref?: string;
/** Whether to actually apply the changes or not. */
@@ -25,12 +29,6 @@ export interface Options {
verbose: boolean;
}
/** Identifies the CodeQL Action repository. */
const codeqlActionRepo = {
owner: "github",
repo: "codeql-action",
};
/** Represents a configuration of which checks should not be set up as required checks. */
export interface Exclusions {
/** A list of strings that, if contained in a check name, are excluded. */
@@ -100,7 +98,7 @@ async function getChecksFor(
const response = await client.paginate(
"GET /repos/{owner}/{repo}/commits/{ref}/check-runs",
{
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
ref,
},
);
@@ -133,7 +131,7 @@ async function getChecksFor(
/** Gets the current list of release branches. */
async function getReleaseBranches(client: ApiClient): Promise<string[]> {
const refs = await client.rest.git.listMatchingRefs({
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
ref: "heads/releases/v",
});
return refs.data.map((ref) => ref.ref).sort();
@@ -146,7 +144,7 @@ async function patchBranchProtectionRule(
checks: Set<string>,
) {
await client.rest.repos.setStatusCheckContexts({
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
branch,
contexts: Array.from(checks),
});
@@ -163,7 +161,7 @@ async function updateBranch(
// Query the current set of required checks for this branch.
const currentContexts = await client.rest.repos.getAllStatusCheckContexts({
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
branch,
});
@@ -205,10 +203,7 @@ async function updateBranch(
async function main(): Promise<void> {
const { values: options } = parseArgs({
options: {
// The token to use to authenticate to the API.
token: {
type: "string",
},
...TOKEN_OPTION_CONFIG,
// The git ref for which to retrieve the check runs.
ref: {
type: "string",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.25.3",
"cliVersion": "2.25.3",
"priorBundleVersion": "codeql-bundle-v2.25.2",
"priorCliVersion": "2.25.2"
"bundleVersion": "codeql-bundle-v2.25.2",
"cliVersion": "2.25.2",
"priorBundleVersion": "codeql-bundle-v2.25.1",
"priorCliVersion": "2.25.1"
}
-162
View File
@@ -193,94 +193,6 @@ test.serial(
},
);
test.serial(
"getRef() returns merge PR ref if GITHUB_SHA still checked out (SHA-256)",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const expectedRef = "refs/pull/1/merge";
const currentSha = "a".repeat(64);
process.env["GITHUB_REF"] = expectedRef;
process.env["GITHUB_SHA"] = currentSha;
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs("HEAD").resolves(currentSha);
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, expectedRef);
callback.restore();
});
},
);
test.serial(
"getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1) (SHA-256)",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const expectedRef = "refs/pull/1/merge";
process.env["GITHUB_REF"] = expectedRef;
process.env["GITHUB_SHA"] = "b".repeat(64);
const sha = "a".repeat(64);
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs("refs/remotes/pull/1/merge").resolves(sha);
callback.withArgs("HEAD").resolves(sha);
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, expectedRef);
callback.restore();
});
},
);
test.serial(
"getRef() returns head PR ref if GITHUB_REF no longer checked out (SHA-256)",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
process.env["GITHUB_REF"] = "refs/pull/1/merge";
process.env["GITHUB_SHA"] = "a".repeat(64);
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("a".repeat(64));
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(64));
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, "refs/pull/1/head");
callback.restore();
});
},
);
test.serial(
"getRef() returns ref provided as an input and ignores current HEAD (SHA-256)",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const getAdditionalInputStub = sinon.stub(
actionsUtil,
"getOptionalInput",
);
getAdditionalInputStub.withArgs("ref").resolves("refs/pull/2/merge");
getAdditionalInputStub.withArgs("sha").resolves("b".repeat(64));
// These values are be ignored
process.env["GITHUB_REF"] = "refs/pull/1/merge";
process.env["GITHUB_SHA"] = "a".repeat(64);
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs("refs/pull/1/merge").resolves("b".repeat(64));
callback.withArgs("HEAD").resolves("b".repeat(64));
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, "refs/pull/2/merge");
callback.restore();
getAdditionalInputStub.restore();
});
},
);
test.serial("isAnalyzingDefaultBranch()", async (t) => {
process.env["GITHUB_EVENT_NAME"] = "push";
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "true";
@@ -393,25 +305,6 @@ test.serial("determineBaseBranchHeadCommitOid other error", async (t) => {
infoStub.restore();
});
test.serial(
"determineBaseBranchHeadCommitOid returns baseOid for SHA-256 merge commit",
async (t) => {
const mergeSha = "a".repeat(64);
const baseOid = "b".repeat(64);
const headOid = "c".repeat(64);
process.env["GITHUB_EVENT_NAME"] = "pull_request";
process.env["GITHUB_SHA"] = mergeSha;
sinon
.stub(gitUtils as any, "runGitCommand")
.resolves(`commit ${mergeSha}\nparent ${baseOid}\nparent ${headOid}\n`);
const result = await gitUtils.determineBaseBranchHeadCommitOid(__dirname);
t.deepEqual(result, baseOid);
},
);
test.serial("decodeGitFilePath unquoted strings", async (t) => {
t.deepEqual(gitUtils.decodeGitFilePath("foo"), "foo");
t.deepEqual(gitUtils.decodeGitFilePath("foo bar"), "foo bar");
@@ -589,61 +482,6 @@ test.serial(
},
);
test.serial(
"getFileOidsUnderPath handles SHA-256 OIDs (64-char)",
async (t) => {
await withTmpDir(async (tmpDir) => {
sinon
.stub(gitUtils as any, "runGitCommand")
.callsFake(async (_cwd: any, args: any) => {
if (args[0] === "rev-parse") {
return `${tmpDir}\n`;
}
return (
"100644 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2c0d4b7e8f9a1234567890ab 0\tlib/git-utils.js\n" +
"100644 aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899 0\tsrc/git-utils.ts"
);
});
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
t.deepEqual(result, {
"lib/git-utils.js":
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2c0d4b7e8f9a1234567890ab",
"src/git-utils.ts":
"aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
});
});
},
);
test.serial(
"getFileOidsUnderPath handles mixed SHA-1 and SHA-256 OIDs",
async (t) => {
await withTmpDir(async (tmpDir) => {
sinon
.stub(gitUtils as any, "runGitCommand")
.callsFake(async (_cwd: any, args: any) => {
if (args[0] === "rev-parse") {
return `${tmpDir}\n`;
}
return (
"100644 30d998ded095371488be3a729eb61d86ed721a18 0\tlib/sha1-file.js\n" +
"100644 aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899 0\tsrc/sha256-file.ts"
);
});
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
t.deepEqual(result, {
"lib/sha1-file.js": "30d998ded095371488be3a729eb61d86ed721a18",
"src/sha256-file.ts":
"aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
});
});
},
);
test.serial(
"getGitVersionOrThrow returns version for valid git output",
async (t) => {
+3 -3
View File
@@ -166,8 +166,8 @@ export const determineBaseBranchHeadCommitOid = async function (
// Let's confirm our assumptions: We had a merge commit and the parsed parent data looks correct
if (
commitOid === mergeSha &&
(headOid.length === 40 || headOid.length === 64) &&
(baseOid.length === 40 || baseOid.length === 64)
headOid.length === 40 &&
baseOid.length === 40
) {
return baseOid;
}
@@ -296,7 +296,7 @@ export const getFileOidsUnderPath = async function (
// 100644 4c51bc1d9e86cd86e01b0f340cb8ce095c33b283 0\tsrc/git-utils.test.ts
// 100644 6b792ea543ce75d7a8a03df591e3c85311ecb64f 0\tsrc/git-utils.ts
// The fields are: <mode> <oid> <stage>\t<path>
const regex = /^[0-9]+ ([0-9a-f]{40,64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
-46
View File
@@ -1,46 +0,0 @@
import test from "ava";
import { setupTests } from "../testing-utils";
import * as json from ".";
setupTests(test);
const testSchema = {
requiredKey: json.string,
};
const optionalSchema = {
optionalKey: json.optional(json.string),
};
test("validateSchema - required properties are required", async (t) => {
t.false(json.validateSchema(testSchema, {}));
t.false(json.validateSchema(testSchema, { requiredKey: undefined }));
t.false(json.validateSchema(testSchema, { requiredKey: null }));
t.false(json.validateSchema(testSchema, { requiredKey: 0 }));
t.false(json.validateSchema(testSchema, { requiredKey: 123 }));
t.false(json.validateSchema(testSchema, { requiredKey: false }));
t.false(json.validateSchema(testSchema, { requiredKey: true }));
t.false(json.validateSchema(testSchema, { requiredKey: [] }));
t.false(json.validateSchema(testSchema, { requiredKey: {} }));
t.true(json.validateSchema(testSchema, { requiredKey: "" }));
t.true(json.validateSchema(testSchema, { requiredKey: "foo" }));
});
test("validateSchema - optional properties are optional", async (t) => {
// Optional fields may be absent
t.true(json.validateSchema(optionalSchema, {}));
t.true(json.validateSchema(optionalSchema, { optionalKey: undefined }));
t.true(json.validateSchema(optionalSchema, { optionalKey: null }));
// But, if present, should have the expected type
t.false(json.validateSchema(optionalSchema, { optionalKey: 0 }));
t.false(json.validateSchema(optionalSchema, { optionalKey: 123 }));
t.false(json.validateSchema(optionalSchema, { optionalKey: false }));
t.false(json.validateSchema(optionalSchema, { optionalKey: true }));
t.false(json.validateSchema(optionalSchema, { optionalKey: [] }));
t.false(json.validateSchema(optionalSchema, { optionalKey: {} }));
t.true(json.validateSchema(optionalSchema, { optionalKey: "" }));
t.true(json.validateSchema(optionalSchema, { optionalKey: "foo" }));
});
-79
View File
@@ -36,82 +36,3 @@ export function isStringOrUndefined(
): value is string | undefined {
return value === undefined || isString(value);
}
/**
* Represents a field of type `T` in a schema.
* Carries a validation function and flag indicating whether the field is required or not.
*/
export type Validator<T> = {
validate: (val: unknown) => val is T;
required: boolean;
};
/** Extracts `T` from `Validator<T>`. */
export type UnwrapValidator<V> = V extends Validator<infer A> ? A : never;
/** A validator for string fields in schemas. */
export const string = {
validate: isString,
required: true,
} as const satisfies Validator<string>;
/** Transforms a validator to be optional. */
export function optional<T>(validator: Validator<T>) {
return {
validate: (val: unknown) => {
return val === undefined || val === null || validator.validate(val);
},
required: false,
} as const satisfies Validator<T | undefined | null>;
}
/** Represents an arbitrary object schema. */
export type Schema = Record<string, Validator<any>>;
/** Extracts the required keys from `S`. */
export type RequiredKeys<S extends Schema> = {
[K in keyof S]: S[K]["required"] extends true ? K : never;
}[keyof S];
/** Extracts optional keys from `S`. */
export type OptionalKeys<S extends Schema> = {
[K in keyof S]: S[K]["required"] extends true ? never : K;
}[keyof S];
/** Constructs an object type corresponding to a schema. */
export type FromSchema<S extends Schema> = {
[K in RequiredKeys<S>]: UnwrapValidator<S[K]>;
} & { [K in OptionalKeys<S>]?: UnwrapValidator<S[K]> };
/**
* Validates that `obj` satisfies at least `schema`. Additional keys are accepted.
*
* @param schema The schema to validate against.
* @param obj The object to validate.
* @returns Asserts that `obj` is of the `schema`'s type if validation is successful.
*/
export function validateSchema<S extends Schema>(
schema: S,
obj: UnvalidatedObject<any>,
): obj is FromSchema<S> {
for (const [key, validator] of Object.entries(schema)) {
const hasKey = key in obj;
// If the property is required, but absent, fail.
if (validator.required && !hasKey) {
return false;
}
// If the property is required, but undefined or null, fail.
if (validator.required && (obj[key] === undefined || obj[key] === null)) {
return false;
}
// If the property is present, validate it.
if (hasKey && !validator.validate(obj[key])) {
return false;
}
}
return true;
}
-106
View File
@@ -1,106 +0,0 @@
import { ExecutionContext } from "ava";
import * as json from ".";
/**
* Constructs an object based on `schema` for unit tests.
* Assumes that all keys in `schema` have string values.
*
* @param includeOptional Whether to include optional properties.
* @param schema The schema to base the object on.
* @returns An object that satisfies `schema`.
*/
export function makeFromSchema<S extends json.Schema>(
includeOptional: boolean,
schema: S,
): json.FromSchema<S> {
const result = {};
for (const [key, validator] of Object.entries(schema)) {
if (!validator.required && !includeOptional) {
continue;
}
result[key] = `value-for-${key}`;
}
return result as json.FromSchema<S>;
}
/** Options for `withSchemaMatrix`. */
export interface SchemaMatrixOptions {
/** Whether cases where the properties are entirely absent should be excluded. */
excludeAbsent?: boolean;
}
/**
* Constructs a test matrix of possible objects for `schema`: all required properties
* plus all permutations of possible states for the optional properties.
*
* @param schema The schema to construct a test matrix for.
* @param body The test body to call with each value from the test matrix.
*/
export function withSchemaMatrix<S extends json.Schema>(
t: ExecutionContext<any>,
schema: S,
opts: SchemaMatrixOptions,
body: (value: json.FromSchema<S>) => void,
): void {
// Construct a base object that includes all required properties.
const required = makeFromSchema(false, schema);
// Identify optional properties.
const optionalKeys: Array<keyof S> = [];
for (const [key, validator] of Object.entries(schema)) {
if (!validator.required) {
optionalKeys.push(key);
}
}
const optionalValues = (key: keyof S) => [
null,
undefined,
`value-for-${String(key)}`,
];
// Constructs an array of test objects, starting with `required` and combining it with all
// possible states of each optional property. For example, with default settings:
//
// For { requiredKey: string }, we get: `[{ requiredKey: "some-string-value" }]`
//
// For { requiredKey: string, optionalKey?: string }, we get:
// [ { requiredKey: "some-string-value" },
// { requiredKey: "some-string-value", optionalKey: undefined },
// { requiredKey: "some-string-value", optionalKey: null },
// { requiredKey: "some-string-value", optionalKey: "some-value" },
// ]
const permutations = (keys: Array<keyof S>) => {
if (keys.length === 0) return [required];
const bases = permutations(keys.slice(1));
const result: Array<json.FromSchema<S>> = [];
const optionalKey = keys[0];
for (const base of bases) {
if (!opts.excludeAbsent) {
// Optional keys can be absent entirely.
result.push(base);
}
// Or be present and have one of the `optionalValues`.
for (const optionalValue of optionalValues(optionalKey)) {
result.push({ ...base, [optionalKey]: optionalValue });
}
}
return result;
};
// Call `body` for all test cases.
const testCases = permutations(optionalKeys);
for (const testCase of testCases) {
try {
body(testCase);
} catch (err) {
t.log(testCase);
throw err;
}
}
}
-1
View File
@@ -198,7 +198,6 @@ async function startProxy(
.map((credential) => ({
type: credential.type,
url: credential.url,
"replaces-base": credential["replaces-base"],
}));
core.setOutput("proxy_urls", JSON.stringify(registry_urls));
+127 -122
View File
@@ -8,8 +8,6 @@ import sinon from "sinon";
import * as apiClient from "./api-client";
import * as defaults from "./defaults.json";
import { setUpFeatureFlagTests } from "./feature-flags/testing-util";
import { UnvalidatedObject, validateSchema } from "./json";
import { makeFromSchema } from "./json/testing-util";
import { BuiltInLanguage } from "./languages";
import { getRunnerLogger, Logger } from "./logging";
import * as startProxyExports from "./start-proxy";
@@ -351,46 +349,131 @@ test("getCredentials throws an error when non-printable characters are used", as
}
});
for (const oidcSchemaInfo of startProxyExports.oidcSchemas) {
test(`getCredentials throws when non-printable characters are used (${oidcSchemaInfo.name} OIDC)`, (t) => {
const validCredential = makeFromSchema(true, oidcSchemaInfo.schema);
for (const key of Object.keys(validCredential)) {
const invalidAuthConfig = {
...validCredential,
[key]: "123\x00",
};
const invalidCredential: startProxyExports.RawCredential = {
type: "nuget_feed",
host: `${key}.nuget.pkg.github.com`,
...invalidAuthConfig,
};
const credentialsInput = toEncodedJSON([invalidCredential]);
const validAzureCredential: startProxyExports.AzureConfig = {
"tenant-id": "12345678-1234-1234-1234-123456789012",
"client-id": "abcdef01-2345-6789-abcd-ef0123456789",
};
t.throws(
() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
credentialsInput,
undefined,
),
{
message:
"Invalid credentials - fields must contain only printable characters",
},
);
}
});
}
const validAwsCredential: startProxyExports.AWSConfig = {
"aws-region": "us-east-1",
"account-id": "123456789012",
"role-name": "MY_ROLE",
domain: "MY_DOMAIN",
"domain-owner": "987654321098",
audience: "custom-audience",
};
const validJFrogCredential: startProxyExports.JFrogConfig = {
"jfrog-oidc-provider-name": "MY_PROVIDER",
audience: "jfrog-audience",
"identity-mapping-name": "my-mapping",
};
test("getCredentials throws an error when non-printable characters are used for Azure OIDC", (t) => {
for (const key of Object.keys(validAzureCredential)) {
const invalidAzureCredential = {
...validAzureCredential,
[key]: "123\x00",
};
const invalidCredential: startProxyExports.RawCredential = {
type: "nuget_feed",
host: `${key}.nuget.pkg.github.com`,
...invalidAzureCredential,
};
const credentialsInput = toEncodedJSON([invalidCredential]);
t.throws(
() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
credentialsInput,
undefined,
),
{
message:
"Invalid credentials - fields must contain only printable characters",
},
);
}
});
test("getCredentials throws an error when non-printable characters are used for AWS OIDC", (t) => {
for (const key of Object.keys(validAwsCredential)) {
const invalidAwsCredential = {
...validAwsCredential,
[key]: "123\x00",
};
const invalidCredential: startProxyExports.RawCredential = {
type: "nuget_feed",
host: `${key}.nuget.pkg.github.com`,
...invalidAwsCredential,
};
const credentialsInput = toEncodedJSON([invalidCredential]);
t.throws(
() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
credentialsInput,
undefined,
),
{
message:
"Invalid credentials - fields must contain only printable characters",
},
);
}
});
test("getCredentials throws an error when non-printable characters are used for JFrog OIDC", (t) => {
for (const key of Object.keys(validJFrogCredential)) {
const invalidJFrogCredential = {
...validJFrogCredential,
[key]: "123\x00",
};
const invalidCredential: startProxyExports.RawCredential = {
type: "nuget_feed",
host: `${key}.nuget.pkg.github.com`,
...invalidJFrogCredential,
};
const credentialsInput = toEncodedJSON([invalidCredential]);
t.throws(
() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
credentialsInput,
undefined,
),
{
message:
"Invalid credentials - fields must contain only printable characters",
},
);
}
});
test("getCredentials accepts OIDC configurations", (t) => {
const oidcConfigurations = startProxyExports.oidcSchemas.map(
(schemaInfo) => ({
const oidcConfigurations = [
{
type: "nuget_feed",
host: `${schemaInfo.name.toLowerCase()}.pkg.github.com`,
...makeFromSchema(true, schemaInfo.schema),
}),
);
host: "azure.pkg.github.com",
...validAzureCredential,
},
{
type: "nuget_feed",
host: "aws.pkg.github.com",
...validAwsCredential,
},
{
type: "nuget_feed",
host: "jfrog.pkg.github.com",
...validJFrogCredential,
},
];
const credentials = startProxyExports.getCredentials(
getRunnerLogger(true),
@@ -398,20 +481,12 @@ test("getCredentials accepts OIDC configurations", (t) => {
toEncodedJSON(oidcConfigurations),
BuiltInLanguage.csharp,
);
t.is(credentials.length, startProxyExports.oidcSchemas.length);
t.is(credentials.length, 3);
t.assert(credentials.every((c) => c.type === "nuget_feed"));
for (const oidcSchemaInfo of startProxyExports.oidcSchemas) {
t.assert(
credentials.some((c) =>
validateSchema(
oidcSchemaInfo.schema,
c as unknown as UnvalidatedObject<any>,
),
),
);
}
t.assert(credentials.some((c) => startProxyExports.isAzureConfig(c)));
t.assert(credentials.some((c) => startProxyExports.isAWSConfig(c)));
t.assert(credentials.some((c) => startProxyExports.isJFrogConfig(c)));
});
const getCredentialsMacro = test.macro({
@@ -457,7 +532,7 @@ test(
t.is(results[0].type, "git_server");
t.is(results[0].host, "https://github.com/");
if (startProxyExports.hasUsernameAndPassword(results[0])) {
if (startProxyExports.isUsernamePassword(results[0])) {
t.assert(results[0].password?.startsWith("ghp_"));
} else {
t.fail("Expected a `UsernamePassword`-based credential.");
@@ -488,7 +563,7 @@ test(
t.is(results[0].type, "git_server");
t.is(results[0].host, "https://github.com/");
if (startProxyExports.hasUsernameAndPassword(results[0])) {
if (startProxyExports.isUsernamePassword(results[0])) {
t.assert(results[0].password?.startsWith("ghp_"));
} else {
t.fail("Expected a `UsernamePassword`-based credential.");
@@ -564,76 +639,6 @@ test(
},
);
test("getCredentials validates 'replaces-base' correctly", async (t) => {
// Valid cases.
const credentialsInput = toEncodedJSON([
{
type: "maven_repository",
host: "maven1.pkg.github.com",
token: "abc",
"replaces-base": false,
},
{
type: "maven_repository",
host: "maven2.pkg.github.com",
token: "def",
"replaces-base": true,
},
{
type: "maven_repository",
host: "maven3.pkg.github.com",
token: "ghi",
},
]);
const credentials = startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
credentialsInput,
BuiltInLanguage.java,
false,
);
t.is(credentials.length, 3);
t.true(credentials.some((c) => c["replaces-base"] === true));
t.true(credentials.some((c) => c["replaces-base"] === false));
t.true(credentials.some((c) => c["replaces-base"] === undefined));
// Invalid cases.
const baseInvalid = {
type: "maven_repository",
host: "maven4.pkg.github.com",
token: "jkl",
};
t.throws(() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
toEncodedJSON([{ ...baseInvalid, "replaces-base": null }]),
BuiltInLanguage.actions,
false,
),
);
t.throws(() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
toEncodedJSON([{ ...baseInvalid, "replaces-base": 123 }]),
BuiltInLanguage.actions,
false,
),
);
t.throws(() =>
startProxyExports.getCredentials(
getRunnerLogger(true),
undefined,
toEncodedJSON([{ ...baseInvalid, "replaces-base": "true" }]),
BuiltInLanguage.actions,
false,
),
);
});
test("getCredentials returns all credentials for Actions when using LANGUAGE_TO_REGISTRY_TYPE", async (t) => {
const credentialsInput = toEncodedJSON(mixedCredentials);
+83 -23
View File
@@ -24,12 +24,20 @@ import {
Address,
Registry,
Credential,
hasToken,
hasUsernameAndPassword,
AuthConfig,
isToken,
isAzureConfig,
Token,
UsernamePassword,
AzureConfig,
isAWSConfig,
AWSConfig,
isJFrogConfig,
JFrogConfig,
isUsernamePassword,
hasUsername,
RawCredential,
} from "./start-proxy/types";
import { getAuthConfig } from "./start-proxy/validation";
import {
ActionName,
createStatusReportBase,
@@ -243,6 +251,75 @@ function getRegistryAddress(
}
}
/** Extracts an `AuthConfig` value from `config`. */
export function getAuthConfig(
config: json.UnvalidatedObject<AuthConfig>,
): AuthConfig {
// Start by checking for the OIDC configurations, since they have required properties
// which we can use to identify them.
if (isAzureConfig(config)) {
return {
"tenant-id": config["tenant-id"],
"client-id": config["client-id"],
} satisfies AzureConfig;
} else if (isAWSConfig(config)) {
return {
"aws-region": config["aws-region"],
"account-id": config["account-id"],
"role-name": config["role-name"],
domain: config.domain,
"domain-owner": config["domain-owner"],
audience: config.audience,
} satisfies AWSConfig;
} else if (isJFrogConfig(config)) {
return {
"jfrog-oidc-provider-name": config["jfrog-oidc-provider-name"],
"identity-mapping-name": config["identity-mapping-name"],
audience: config.audience,
} satisfies JFrogConfig;
} else if (isToken(config)) {
// There are three scenarios for non-OIDC authentication based on the registry type:
//
// 1. `username`+`token`
// 2. A `token` that combines the username and actual token, separated by ':'.
// 3. `username`+`password`
//
// In all three cases, all fields are optional. If the `token` field is present,
// we accept the configuration as a `Token` typed configuration, with the `token`
// value and an optional `username`. Otherwise, we accept the configuration
// typed as `UsernamePassword` (in the `else` clause below) with optional
// username and password. I.e. a private registry type that uses 1. or 2.,
// but has no `token` configured, will get accepted as `UsernamePassword` here.
if (isDefined(config.token)) {
// Mask token to reduce chance of accidental leakage in logs, if we have one.
core.setSecret(config.token);
}
return { username: config.username, token: config.token } satisfies Token;
} else {
let username: string | undefined = undefined;
let password: string | undefined = undefined;
// Both "username" and "password" are optional. If we have reached this point, we need
// to validate which of them are present and that they have the correct type if so.
if ("password" in config && json.isString(config.password)) {
// Mask password to reduce chance of accidental leakage in logs, if we have one.
core.setSecret(config.password);
password = config.password;
}
if ("username" in config && json.isString(config.username)) {
username = config.username;
}
// Return the `UsernamePassword` object. Both username and password may be undefined.
return {
username,
password,
} satisfies UsernamePassword;
}
}
// getCredentials returns registry credentials from action inputs.
// It prefers `registries_credentials` over `registry_secrets`.
// If neither is set, it returns an empty array.
@@ -331,11 +408,11 @@ export function getCredentials(
const noUsername =
!hasUsername(authConfig) || !isDefined(authConfig.username);
const passwordIsPAT =
hasUsernameAndPassword(authConfig) &&
isUsernamePassword(authConfig) &&
isDefined(authConfig.password) &&
isPAT(authConfig.password);
const tokenIsPAT =
hasToken(authConfig) &&
isToken(authConfig) &&
isDefined(authConfig.token) &&
isPAT(authConfig.token);
@@ -347,25 +424,8 @@ export function getCredentials(
);
}
// Construct the base credential object.
const baseCredential: Omit<Registry, keyof Address> = { type: e.type };
// If "replaces-base" is present, it must be a boolean.
if ("replaces-base" in e) {
if (
isDefined(e["replaces-base"]) &&
typeof e["replaces-base"] === "boolean"
) {
baseCredential["replaces-base"] = e["replaces-base"];
} else {
throw new ConfigurationError(
"Invalid credentials - 'replaces-base' must be a boolean",
);
}
}
out.push({
...baseCredential,
type: e.type,
...authConfig,
...address,
});
+2 -68
View File
@@ -1,6 +1,5 @@
import test from "ava";
import { makeFromSchema, withSchemaMatrix } from "../json/testing-util";
import { setupTests } from "../testing-utils";
import * as types from "./types";
@@ -27,38 +26,6 @@ const validJFrogCredential: types.JFrogConfig = {
"identity-mapping-name": "my-mapping",
};
test("hasUsername", (t) => {
// Reject the case where `username` is missing.
t.false(types.hasUsername({}));
// Test all cases where `username` is present.
withSchemaMatrix(
t,
types.usernameSchema,
{ excludeAbsent: true },
(value) => {
t.true(types.hasUsername(value));
},
);
});
test("hasUsernameAndPassword", (t) => {
// Reject cases where `username` or `password` are missing.
t.false(types.hasUsernameAndPassword({}));
t.false(types.hasUsernameAndPassword({ username: "foo" }));
t.false(types.hasUsernameAndPassword({ password: "foo" }));
// Test all cases where both `username` and `password` are present.
withSchemaMatrix(
t,
types.usernamePasswordSchema,
{ excludeAbsent: true },
(value) => {
t.true(types.hasUsernameAndPassword(value));
},
);
});
test("credentialToStr - pretty-prints valid username+password configurations", (t) => {
const secret = "password123";
const credential: types.Credential = {
@@ -140,46 +107,13 @@ test("credentialToStr - pretty-prints valid JFrog OIDC configurations", (t) => {
);
});
test("credentialToStr - pretty-prints valid Cloudsmith OIDC configurations", (t) => {
const credential: types.Credential = {
type: "maven_credential",
url: "https://localhost",
...(makeFromSchema(
true,
types.cloudsmithConfigSchema,
) as types.CloudsmithConfig),
};
const str = types.credentialToStr(credential);
t.is(
"Type: maven_credential; Url: https://localhost; Cloudsmith Namespace: value-for-namespace; Cloudsmith Service Slug: value-for-service-slug; Cloudsmith API Host: value-for-api-host;",
str,
);
});
test("credentialToStr - pretty-prints valid GCP OIDC configurations", (t) => {
const credential: types.Credential = {
type: "maven_credential",
url: "https://localhost",
...(makeFromSchema(true, types.gcpConfigSchema) as types.GCPConfig),
};
const str = types.credentialToStr(credential);
t.is(
"Type: maven_credential; Url: https://localhost; GCP Workload Identity Provider: value-for-workload-identity-provider; GCP Service Account: value-for-service-account; GCP Audience: value-for-audience;",
str,
);
});
test("credentialToStr - hides passwords", (t) => {
const secret = "password123";
const credential = {
type: "maven_credential",
password: secret,
url: "https://localhost",
} satisfies types.Credential;
};
const str = types.credentialToStr(credential);
@@ -193,7 +127,7 @@ test("credentialToStr - hides tokens", (t) => {
type: "maven_credential",
token: secret,
url: "https://localhost",
} satisfies types.Credential;
};
const str = types.credentialToStr(credential);
+88 -134
View File
@@ -9,177 +9,144 @@ import { isDefined } from "../util";
*/
export type RawCredential = UnvalidatedObject<Credential>;
/** A schema for credential objects with a username. */
export const usernameSchema = {
/** The username needed to authenticate to the package registry, if any. */
username: json.optional(json.string),
} as const satisfies json.Schema;
/** Usernames may be present for both authentication with tokens or passwords. */
export type Username = json.FromSchema<typeof usernameSchema>;
export type Username = {
/** The username needed to authenticate to the package registry, if any. */
username?: string;
};
/**
* Narrows `config` to `Username` if `config` has a `username` property.
* Not used for validation. Assumes that `config` is already a validated `AuthConfig`.
*/
/** Decides whether `config` has a username. */
export function hasUsername(config: AuthConfig): config is Username {
return "username" in config;
}
/** A schema for credential objects with a username and password. */
export const usernamePasswordSchema = {
/** The password needed to authenticate to the package registry, if any. */
password: json.optional(json.string),
...usernameSchema,
} as const satisfies json.Schema;
/**
* Fields expected for authentication based on a username and password.
* Both username and password are optional.
*/
export type UsernamePassword = json.FromSchema<typeof usernamePasswordSchema>;
export type UsernamePassword = {
/** The password needed to authenticate to the package registry, if any. */
password?: string;
} & Username;
/**
* Narrows `config` to `UsernamePassword` if it has a `username` and `password` property.
* Not used for validation. Assumes that `config` is already a validated `AuthConfig`.
*/
export function hasUsernameAndPassword(
/** Decides whether `config` is based on a username and password. */
export function isUsernamePassword(
config: AuthConfig,
): config is UsernamePassword {
return hasUsername(config) && "password" in config;
}
/** A schema for credential objects for token-based authentication. */
export const tokenSchema = {
/** The token needed to authenticate to the package registry, if any. */
token: json.optional(json.string),
...usernameSchema,
} as const satisfies json.Schema;
/**
* Fields expected for token-based authentication.
* Both username and token are optional.
*/
export type Token = json.FromSchema<typeof tokenSchema>;
/**
* Narrows `config` to `Token` if it has a `token` property.
* Not used for validation. Assumes that `config` is already a validated `AuthConfig`.
*/
export function hasToken(config: AuthConfig): config is Token {
return "token" in config;
}
export type Token = {
/** The token needed to authenticate to the package registry, if any. */
token?: string;
} & Username;
/** Decides whether `config` is token-based. */
export function isToken(
config: UnvalidatedObject<AuthConfig>,
): config is Token {
return "token" in config && json.validateSchema(tokenSchema, config);
// The "username" field is optional, but should be a string if present.
if ("username" in config && !json.isStringOrUndefined(config.username)) {
return false;
}
// The "token" field is required, and must be a string or undefined.
return "token" in config && json.isStringOrUndefined(config.token);
}
/** A schema for Azure OIDC configurations. */
export const azureConfigSchema = {
"tenant-id": json.string,
"client-id": json.string,
} as const satisfies json.Schema;
/** Configuration for Azure OIDC. */
export type AzureConfig = json.FromSchema<typeof azureConfigSchema>;
export type AzureConfig = { "tenant-id": string; "client-id": string };
/** Decides whether `config` is an Azure OIDC configuration. */
export function isAzureConfig(
config: UnvalidatedObject<AuthConfig>,
): config is AzureConfig {
return json.validateSchema(azureConfigSchema, config);
return (
"tenant-id" in config &&
"client-id" in config &&
isDefined(config["tenant-id"]) &&
isDefined(config["client-id"]) &&
json.isString(config["tenant-id"]) &&
json.isString(config["client-id"])
);
}
/** A schema for AWS OIDC configurations. */
export const awsConfigSchema = {
"aws-region": json.string,
"account-id": json.string,
"role-name": json.string,
domain: json.string,
"domain-owner": json.string,
audience: json.optional(json.string),
} as const satisfies json.Schema;
/** Configuration for AWS OIDC. */
export type AWSConfig = json.FromSchema<typeof awsConfigSchema>;
export type AWSConfig = {
"aws-region": string;
"account-id": string;
"role-name": string;
domain: string;
"domain-owner": string;
audience?: string;
};
/** Decides whether `config` is an AWS OIDC configuration. */
export function isAWSConfig(
config: UnvalidatedObject<AuthConfig>,
): config is AWSConfig {
return json.validateSchema(awsConfigSchema, config);
// All of these properties are required.
const requiredProperties = [
"aws-region",
"account-id",
"role-name",
"domain",
"domain-owner",
];
for (const property of requiredProperties) {
if (
!(property in config) ||
!isDefined(config[property]) ||
!json.isString(config[property])
) {
return false;
}
}
// The "audience" field is optional, but should be a string if present.
if ("audience" in config && !json.isStringOrUndefined(config.audience)) {
return false;
}
return true;
}
/** A schema for JFrog OIDC configurations. */
export const jfrogConfigSchema = {
"jfrog-oidc-provider-name": json.string,
audience: json.optional(json.string),
"identity-mapping-name": json.optional(json.string),
} as const satisfies json.Schema;
/** Configuration for JFrog OIDC. */
export type JFrogConfig = json.FromSchema<typeof jfrogConfigSchema>;
export type JFrogConfig = {
"jfrog-oidc-provider-name": string;
audience?: string;
"identity-mapping-name"?: string;
};
/** Decides whether `config` is a JFrog OIDC configuration. */
export function isJFrogConfig(
config: UnvalidatedObject<AuthConfig>,
): config is JFrogConfig {
return json.validateSchema(jfrogConfigSchema, config);
// The "audience" and "identity-mapping-name" fields are optional, but should be strings if present.
if ("audience" in config && !json.isStringOrUndefined(config.audience)) {
return false;
}
if (
"identity-mapping-name" in config &&
!json.isStringOrUndefined(config["identity-mapping-name"])
) {
return false;
}
return (
"jfrog-oidc-provider-name" in config &&
isDefined(config["jfrog-oidc-provider-name"]) &&
json.isString(config["jfrog-oidc-provider-name"])
);
}
/** A schema for Cloudsmith OIDC configurations. */
export const cloudsmithConfigSchema = {
namespace: json.string,
"service-slug": json.string,
"api-host": json.string,
} as const satisfies json.Schema;
/** Configuration for Cloudsmith OIDC. */
export type CloudsmithConfig = json.FromSchema<typeof cloudsmithConfigSchema>;
/** Decides whether `config` is a Cloudsmith OIDC configuration. */
export function isCloudsmithConfig(
config: UnvalidatedObject<AuthConfig>,
): config is CloudsmithConfig {
return json.validateSchema(cloudsmithConfigSchema, config);
}
/** A schema for GCP OIDC configurations. */
export const gcpConfigSchema = {
"workload-identity-provider": json.string,
"service-account": json.optional(json.string),
audience: json.optional(json.string),
} as const satisfies json.Schema;
/** Configuration for GCP OIDC. */
export type GCPConfig = json.FromSchema<typeof gcpConfigSchema>;
/** Decides whether `config` is a GCP OIDC configuration. */
export function isGCPConfig(
config: UnvalidatedObject<AuthConfig>,
): config is GCPConfig {
return json.validateSchema(gcpConfigSchema, config);
}
/** An array of all OIDC configuration schemas along with output-friendly names. */
export const oidcSchemas = [
{ schema: azureConfigSchema, name: "Azure" },
{ schema: awsConfigSchema, name: "AWS" },
{ schema: jfrogConfigSchema, name: "JFrog" },
{ schema: cloudsmithConfigSchema, name: "Cloudsmith" },
{ schema: gcpConfigSchema, name: "GCP" },
];
/** Represents all supported OIDC configurations. */
export type OIDC =
| AzureConfig
| AWSConfig
| JFrogConfig
| CloudsmithConfig
| GCPConfig;
export type OIDC = AzureConfig | AWSConfig | JFrogConfig;
/** All authentication-related fields. */
export type AuthConfig = UsernamePassword | Token | OIDC;
@@ -198,7 +165,7 @@ export type Credential = AuthConfig & Registry;
export function credentialToStr(credential: Credential): string {
let result: string = `Type: ${credential.type};`;
const appendIfDefined = (name: string, val: string | undefined | null) => {
const appendIfDefined = (name: string, val: string | undefined) => {
if (isDefined(val)) {
result += ` ${name}: ${val};`;
}
@@ -217,7 +184,7 @@ export function credentialToStr(credential: Credential): string {
isDefined(credential.password) ? "***" : undefined,
);
}
if (hasToken(credential)) {
if (isToken(credential)) {
appendIfDefined("Token", isDefined(credential.token) ? "***" : undefined);
}
@@ -238,17 +205,6 @@ export function credentialToStr(credential: Credential): string {
credential["identity-mapping-name"],
);
appendIfDefined("JFrog Audience", credential.audience);
} else if (isCloudsmithConfig(credential)) {
appendIfDefined("Cloudsmith Namespace", credential.namespace);
appendIfDefined("Cloudsmith Service Slug", credential["service-slug"]);
appendIfDefined("Cloudsmith API Host", credential["api-host"]);
} else if (isGCPConfig(credential)) {
appendIfDefined(
"GCP Workload Identity Provider",
credential["workload-identity-provider"],
);
appendIfDefined("GCP Service Account", credential["service-account"]);
appendIfDefined("GCP Audience", credential.audience);
}
return result;
@@ -258,8 +214,6 @@ export function credentialToStr(credential: Credential): string {
export type Registry = {
/** The type of the package registry. */
type: string;
/** Whether the registry replaces the base registry for the ecosystem. */
"replaces-base"?: boolean;
} & Address;
// If a registry has an `url`, then that takes precedence over the `host` which may or may
-69
View File
@@ -1,69 +0,0 @@
import test from "ava";
import * as json from "../json";
import { makeFromSchema } from "../json/testing-util";
import { setupTests } from "../testing-utils";
import * as types from "./types";
import { getAuthConfig } from "./validation";
setupTests(test);
for (const schemaTest of types.oidcSchemas) {
for (const includeOptional of [true, false]) {
const minimalName = includeOptional ? "full" : "minimal";
test(`getAuthConfig - ${schemaTest.name} - ${minimalName}`, async (t) => {
const config = makeFromSchema(includeOptional, schemaTest.schema);
t.deepEqual(
getAuthConfig({
...config,
unexpected: "unexpected-value",
} as unknown as json.UnvalidatedObject<types.AuthConfig>),
config,
);
});
}
}
test("getAuthConfig - token", async (t) => {
const config = makeFromSchema(true, types.tokenSchema);
t.deepEqual(
getAuthConfig({
...config,
unexpected: "unexpected-value",
} as json.UnvalidatedObject<types.AuthConfig>),
config,
);
});
test("getAuthConfig - username and password", async (t) => {
const config = makeFromSchema(true, types.usernamePasswordSchema);
t.deepEqual(
getAuthConfig({
...config,
unexpected: "unexpected-value",
} as json.UnvalidatedObject<types.AuthConfig>),
config,
);
});
test("getAuthConfig - empty", async (t) => {
const config = makeFromSchema(false, types.usernamePasswordSchema);
// Since the purpose of constructing the `AuthConfig` values is for
// serialisation to JSON so that they can be passed to the proxy as configuration,
// we only care that the stringified JSON representations are the same.
t.deepEqual(
JSON.stringify(
getAuthConfig({
...config,
unexpected: "unexpected-value",
} as json.UnvalidatedObject<types.AuthConfig>),
),
JSON.stringify({}),
);
});
-81
View File
@@ -1,81 +0,0 @@
import * as core from "@actions/core";
import * as json from "../json";
import { isDefined } from "../util";
import type { AuthConfig, UsernamePassword } from "./types";
import * as types from "./types";
/** Constructs a new object from `obj` with only keys that exist in `schema`. */
export function cloneCredential<S extends json.Schema>(
schema: S,
obj: json.FromSchema<S>,
): json.FromSchema<S> {
const result = {};
for (const key of Object.keys(schema)) {
// Skip keys that don't exist or don't have a value.
if (!isDefined(obj[key])) {
continue;
}
result[key] = obj[key];
}
return result as json.FromSchema<S>;
}
/** Extracts an `AuthConfig` value from `config`. */
export function getAuthConfig(
config: json.UnvalidatedObject<AuthConfig>,
): AuthConfig {
// Start by checking for the OIDC configurations, since they have required properties
// which we can use to identify them.
for (const oidcSchema of types.oidcSchemas) {
if (json.validateSchema(oidcSchema.schema, config)) {
return cloneCredential(oidcSchema.schema, config);
}
}
// Otherwise, try the basic configuration types.
if (types.isToken(config)) {
// There are three scenarios for non-OIDC authentication based on the registry type:
//
// 1. `username`+`token`
// 2. A `token` that combines the username and actual token, separated by ':'.
// 3. `username`+`password`
//
// In all three cases, all fields are optional. If the `token` field is present,
// we accept the configuration as a `Token` typed configuration, with the `token`
// value and an optional `username`. Otherwise, we accept the configuration
// typed as `UsernamePassword` (in the `else` clause below) with optional
// username and password. I.e. a private registry type that uses 1. or 2.,
// but has no `token` configured, will get accepted as `UsernamePassword` here.
if (isDefined(config.token)) {
// Mask token to reduce chance of accidental leakage in logs, if we have one.
core.setSecret(config.token);
}
return cloneCredential(types.tokenSchema, config);
} else {
let username: string | undefined = undefined;
let password: string | undefined = undefined;
// Both "username" and "password" are optional. If we have reached this point, we need
// to validate which of them are present and that they have the correct type if so.
if ("password" in config && json.isString(config.password)) {
// Mask password to reduce chance of accidental leakage in logs, if we have one.
core.setSecret(config.password);
password = config.password;
}
if ("username" in config && json.isString(config.username)) {
username = config.username;
}
// Return the `UsernamePassword` object. Both username and password may be undefined.
return {
username,
password,
} satisfies UsernamePassword;
}
}
-3
View File
@@ -160,9 +160,6 @@ export const DEFAULT_ACTIONS_VARS = {
RUNNER_OS: "Linux",
} as const satisfies Record<string, string>;
/** A 64-character SHA-256 Git OID for use in SHA-256 repository test scenarios. */
export const SHA256_GITHUB_SHA = "0".repeat(64);
// Sets environment variables that make using some libraries designed for
// use only on actions safe to use outside of actions.
export function setupActionsVars(
+1 -30
View File
@@ -12,7 +12,7 @@ import * as api from "./api-client";
import * as diffUtils from "./diff-informed-analysis-utils";
import { getRunnerLogger, Logger } from "./logging";
import * as sarif from "./sarif";
import { setupTests, SHA256_GITHUB_SHA } from "./testing-utils";
import { setupTests } from "./testing-utils";
import * as uploadLib from "./upload-lib";
import { UploadPayload } from "./upload-lib/types";
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
@@ -110,35 +110,6 @@ test.serial(
},
);
test.serial(
"validate correct payload used for PR merge commit with SHA-256 OIDs",
async (t) => {
process.env["GITHUB_EVENT_NAME"] = "pull_request";
process.env["GITHUB_SHA"] = SHA256_GITHUB_SHA;
process.env["GITHUB_BASE_REF"] = "master";
process.env["GITHUB_EVENT_PATH"] =
`${__dirname}/../src/testdata/pull_request.json`;
const sha256MergeBase = "b".repeat(64);
const prMergePayload: any = uploadLib.buildPayload(
SHA256_GITHUB_SHA,
"refs/pull/123/merge",
"key",
undefined,
"",
1234,
1,
"/opt/src",
undefined,
["CodeQL", "eslint"],
sha256MergeBase,
);
// Uploads for a merge commit use the merge base (SHA-256)
t.deepEqual(prMergePayload.base_ref, "refs/heads/master");
t.deepEqual(prMergePayload.base_sha, sha256MergeBase);
},
);
test.serial("finding SARIF files", async (t) => {
await withTmpDir(async (tmpDir) => {
// include a couple of sarif files