mirror of
https://github.com/github/codeql-action
synced 2026-10-15 20:05:33 +03:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a048e8cfae | ||
|
|
09dec6cc8c | ||
|
|
ba61470b11 | ||
|
|
31d88dd791 | ||
|
|
2f90a82ab7 | ||
|
|
ee70cd5210 | ||
|
|
516447a110 | ||
|
|
839edd2b07 | ||
|
|
b3b14d6671 | ||
|
|
711c19c2dc | ||
|
|
4138e5a7fe | ||
|
|
0bae987e7d | ||
|
|
3b64eeeb9f | ||
|
|
49e524587f | ||
|
|
18e8398a57 | ||
|
|
80de837984 | ||
|
|
03dfa6fb02 | ||
|
|
3187067e93 | ||
|
|
52077a03d9 | ||
|
|
0337b99474 | ||
|
|
b2cc23e25d | ||
|
|
82036913c8 | ||
|
|
ae7c3ea645 | ||
|
|
a0276b7dc4 | ||
|
|
f57d3726c3 | ||
|
|
6f850b5022 | ||
|
|
c73f0fa0f1 | ||
|
|
98dfd09d4a | ||
|
|
c8f0aa4c67 | ||
|
|
f3f1edbedf | ||
|
|
9f2d709a33 | ||
|
|
5f5ed8735c | ||
|
|
eda5730a8b | ||
|
|
e263f3a83a | ||
|
|
ddef289289 | ||
|
|
99576d59b9 | ||
|
|
72b0f9fed4 | ||
|
|
9fc351a8ac | ||
|
|
72f4abcfcc | ||
|
|
0630e39f3f | ||
|
|
2b0250fe46 | ||
|
|
446948f50b | ||
|
|
d24f3022a8 | ||
|
|
7ba697a3ec | ||
|
|
74374a3893 | ||
|
|
3e85884434 | ||
|
|
5aed9f7d64 | ||
|
|
f9feddd874 | ||
|
|
15f19e1870 | ||
|
|
51cc08af6f | ||
|
|
71b697dd8b | ||
|
|
f5808271b0 | ||
|
|
da26a016ee | ||
|
|
4536424fcf | ||
|
|
d98bedfdea | ||
|
|
952a538c24 | ||
|
|
04d4fd51e9 | ||
|
|
c05837d3e8 | ||
|
|
3dc99aad86 |
No files matched your search
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
java-version: ${{ inputs.java-version || '17' }}
|
||||
distribution: temurin
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
- name: Install Java
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
|
||||
with:
|
||||
java-version: ${{ inputs.java-version || '17' }}
|
||||
distribution: temurin
|
||||
|
||||
@@ -129,10 +129,6 @@ jobs:
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Use Xcode 16
|
||||
if: runner.os == 'macOS' && matrix.version != 'nightly-latest'
|
||||
run: sudo xcode-select -s "/Applications/Xcode_16.app"
|
||||
|
||||
- uses: ./../action/init
|
||||
id: init
|
||||
with:
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
|
||||
uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
-3
@@ -91,9 +91,6 @@ jobs:
|
||||
version: ${{ matrix.version }}
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Use Xcode 16
|
||||
if: runner.os == 'macOS' && matrix.version != 'nightly-latest'
|
||||
run: sudo xcode-select -s "/Applications/Xcode_16.app"
|
||||
- uses: ./../action/init
|
||||
id: init
|
||||
with:
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: npm run lint-ci
|
||||
|
||||
- name: Upload sarif
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
uses: ./upload-sarif
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
|
||||
with:
|
||||
sarif_file: eslint.sarif
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
- main
|
||||
paths:
|
||||
- .github/workflows/update-supported-enterprise-server-versions.yml
|
||||
- .github/workflows/update-supported-enterprise-server-versions/update.py
|
||||
- pr-checks/update-ghes-versions.ts
|
||||
|
||||
jobs:
|
||||
update-supported-enterprise-server-versions:
|
||||
@@ -26,8 +26,19 @@ jobs:
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Checkout CodeQL Action
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Checkout Enterprise Releases
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
@@ -35,18 +46,18 @@ jobs:
|
||||
token: ${{ secrets.ENTERPRISE_RELEASE_TOKEN }}
|
||||
path: ${{ github.workspace }}/enterprise-releases/
|
||||
sparse-checkout: releases.json
|
||||
|
||||
- name: Update Supported Enterprise Server Versions
|
||||
working-directory: pr-checks
|
||||
run: |
|
||||
cd ./.github/workflows/update-supported-enterprise-server-versions/
|
||||
python3 -m pip install pipenv
|
||||
pipenv install
|
||||
pipenv run ./update.py
|
||||
npx tsx update-ghes-versions.ts
|
||||
rm --recursive "$ENTERPRISE_RELEASES_PATH"
|
||||
npm ci
|
||||
npm run build
|
||||
env:
|
||||
ENTERPRISE_RELEASES_PATH: ${{ github.workspace }}/enterprise-releases/
|
||||
|
||||
- name: Rebuild
|
||||
run: npm run build
|
||||
|
||||
- name: Update git config
|
||||
run: |
|
||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
semver = "*"
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "e3ba923dcb4888e05de5448c18a732bf40197e80fabfa051a61c01b22c504879"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"semver": {
|
||||
"hashes": [
|
||||
"sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4",
|
||||
"sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.13.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
import semver
|
||||
|
||||
_API_COMPATIBILITY_PATH = pathlib.Path(__file__).absolute().parents[3] / "src" / "api-compatibility.json"
|
||||
_ENTERPRISE_RELEASES_PATH = pathlib.Path(os.environ["ENTERPRISE_RELEASES_PATH"])
|
||||
_RELEASE_FILE_PATH = _ENTERPRISE_RELEASES_PATH / "releases.json"
|
||||
_FIRST_SUPPORTED_RELEASE = semver.VersionInfo.parse("2.22.0") # Versions older than this did not include Code Scanning.
|
||||
|
||||
def main():
|
||||
api_compatibility_data = json.loads(_API_COMPATIBILITY_PATH.read_text())
|
||||
|
||||
releases = json.loads(_RELEASE_FILE_PATH.read_text())
|
||||
|
||||
# Remove GHES version using a previous version numbering scheme.
|
||||
if "11.10" in releases:
|
||||
del releases["11.10"]
|
||||
|
||||
oldest_supported_release = None
|
||||
newest_supported_release = semver.VersionInfo.parse(api_compatibility_data["maximumVersion"] + ".0")
|
||||
|
||||
for release_version_string, release_data in releases.items():
|
||||
release_version = semver.VersionInfo.parse(release_version_string + ".0")
|
||||
if release_version < _FIRST_SUPPORTED_RELEASE:
|
||||
continue
|
||||
|
||||
if release_version > newest_supported_release:
|
||||
feature_freeze_date = datetime.date.fromisoformat(release_data["feature_freeze"])
|
||||
if feature_freeze_date < datetime.date.today() + datetime.timedelta(weeks=2):
|
||||
newest_supported_release = release_version
|
||||
|
||||
if oldest_supported_release is None or release_version < oldest_supported_release:
|
||||
end_of_life_date = datetime.date.fromisoformat(release_data["end"])
|
||||
# The GHES version is not actually end of life until the end of the day specified by
|
||||
# `end_of_life_date`. Wait an extra week to be safe.
|
||||
is_end_of_life = datetime.date.today() > end_of_life_date + datetime.timedelta(weeks=1)
|
||||
if not is_end_of_life:
|
||||
oldest_supported_release = release_version
|
||||
|
||||
api_compatibility_data = {
|
||||
"minimumVersion": f"{oldest_supported_release.major}.{oldest_supported_release.minor}",
|
||||
"maximumVersion": f"{newest_supported_release.major}.{newest_supported_release.minor}",
|
||||
}
|
||||
_API_COMPATIBILITY_PATH.write_text(json.dumps(api_compatibility_data, sort_keys=True) + "\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+6
-2
@@ -2,10 +2,14 @@
|
||||
|
||||
See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs.
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.36.2 - 04 Jun 2026
|
||||
|
||||
- Cache CodeQL CLI version information across Actions steps. [#3943](https://github.com/github/codeql-action/pull/3943)
|
||||
- Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. [#3937](https://github.com/github/codeql-action/pull/3937)
|
||||
- Cache CodeQL CLI version information across Actions steps. [#3943](https://github.com/github/codeql-action/pull/3943)
|
||||
- Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. [#3937](https://github.com/github/codeql-action/pull/3937)
|
||||
- Update default CodeQL bundle version to [2.25.6](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6). [#3948](https://github.com/github/codeql-action/pull/3948)
|
||||
|
||||
## 4.36.1 - 02 Jun 2026
|
||||
|
||||
Generated
+12930
-7996
File diff suppressed because it is too large.
Load diff
Generated
+437
-256
File diff suppressed because it is too large.
Load diff
+9
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.36.2",
|
||||
"version": "4.36.3",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
@@ -31,16 +31,16 @@
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/plugin-retry": "^8.1.0",
|
||||
"archiver": "^7.0.1",
|
||||
"archiver": "^8.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"follow-redirects": "^1.16.0",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"js-yaml": "^4.1.1",
|
||||
"js-yaml": "^5.0.0",
|
||||
"jsonschema": "1.5.0",
|
||||
"long": "^5.3.2",
|
||||
"node-forge": "^1.4.0",
|
||||
"semver": "^7.8.1",
|
||||
"semver": "^7.8.4",
|
||||
"uuid": "^14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -48,18 +48,18 @@
|
||||
"@eslint/compat": "^2.1.0",
|
||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||
"@octokit/types": "^16.0.0",
|
||||
"@types/archiver": "^7.0.0",
|
||||
"@types/archiver": "^8.0.0",
|
||||
"@types/follow-redirects": "^1.14.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^20.19.41",
|
||||
"@types/node": "^20.19.42",
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@types/sarif": "^2.1.7",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/sinon": "^21.0.1",
|
||||
"ava": "^6.4.1",
|
||||
"esbuild": "^0.28.0",
|
||||
"esbuild": "^0.28.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.0.0",
|
||||
"eslint-plugin-import-x": "^4.16.2",
|
||||
"eslint-plugin-jsdoc": "^62.9.0",
|
||||
@@ -69,7 +69,7 @@
|
||||
"nock": "^14.0.15",
|
||||
"sinon": "^22.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.60.0"
|
||||
"typescript-eslint": "^8.61.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@actions/tool-cache": {
|
||||
|
||||
@@ -17,10 +17,6 @@ steps:
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Use Xcode 16
|
||||
if: runner.os == 'macOS' && matrix.version != 'nightly-latest'
|
||||
run: sudo xcode-select -s "/Applications/Xcode_16.app"
|
||||
|
||||
- uses: ./../action/init
|
||||
id: init
|
||||
with:
|
||||
|
||||
@@ -5,7 +5,7 @@ versions:
|
||||
- default
|
||||
steps:
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
|
||||
uses: ruby/setup-ruby@12fd324f1d0b43274fdc8130f6980590a667c455 # v1.312.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
@@ -11,9 +11,6 @@ installDotNet: true
|
||||
env:
|
||||
DOTNET_GENERATE_ASPNET_CERTIFICATE: "false"
|
||||
steps:
|
||||
- name: Use Xcode 16
|
||||
if: runner.os == 'macOS' && matrix.version != 'nightly-latest'
|
||||
run: sudo xcode-select -s "/Applications/Xcode_16.app"
|
||||
- uses: ./../action/init
|
||||
id: init
|
||||
with:
|
||||
|
||||
@@ -24,3 +24,9 @@ export const BUILTIN_LANGUAGES_FILE = path.join(
|
||||
"languages",
|
||||
"builtin.json",
|
||||
);
|
||||
|
||||
/** Path to the api-compatibility.json file. */
|
||||
export const API_COMPATIBILITY_FILE = path.join(
|
||||
SOURCE_ROOT,
|
||||
"api-compatibility.json",
|
||||
);
|
||||
@@ -7,10 +7,11 @@
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@octokit/plugin-paginate-rest": ">=9.2.2",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
||||
"semver": "^7.8.0",
|
||||
"yaml": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.41",
|
||||
"tsx": "^4.22.3"
|
||||
"@types/node": "^20.19.42",
|
||||
"tsx": "^4.22.4"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -246,8 +246,8 @@ const languageSetups: LanguageSetups = {
|
||||
name: "Install Java",
|
||||
uses: pinnedUses(
|
||||
"actions/setup-java",
|
||||
"be666c2fcd27ec809703dec50e508c2fdc7f6654",
|
||||
"v5.2.0",
|
||||
"ad2b38190b15e4d6bdf0c97fb4fca8412226d287",
|
||||
"v5.3.0",
|
||||
),
|
||||
with: {
|
||||
"java-version": `\${{ inputs.java-version || '${defaultLanguageVersions.java}' }}`,
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
/*
|
||||
* Tests for the update-ghes-versions.ts script
|
||||
*/
|
||||
|
||||
import * as assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
|
||||
import {
|
||||
addWeeks,
|
||||
determineSupportedRange,
|
||||
type EnterpriseReleases,
|
||||
parseEnterpriseVersion,
|
||||
printEnterpriseVersion,
|
||||
} from "./update-ghes-versions";
|
||||
|
||||
describe("parseEnterpriseVersion", async () => {
|
||||
await it("parses a two-component version string", () => {
|
||||
const ver = parseEnterpriseVersion("3.10");
|
||||
assert.notEqual(ver, null);
|
||||
assert.equal(ver!.major, 3);
|
||||
assert.equal(ver!.minor, 10);
|
||||
assert.equal(ver!.patch, 0);
|
||||
});
|
||||
|
||||
await it("parses a three-component version string", () => {
|
||||
const ver = parseEnterpriseVersion("3.10.2");
|
||||
assert.notEqual(ver, null);
|
||||
assert.equal(ver!.major, 3);
|
||||
assert.equal(ver!.minor, 10);
|
||||
assert.equal(ver!.patch, 2);
|
||||
});
|
||||
|
||||
await it("returns null for invalid input", () => {
|
||||
assert.equal(parseEnterpriseVersion("not-a-version"), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("printEnterpriseVersion", async () => {
|
||||
await it("prints only major.minor when patch is 0", () => {
|
||||
const ver = parseEnterpriseVersion("3.10")!;
|
||||
assert.equal(printEnterpriseVersion(ver), "3.10");
|
||||
});
|
||||
|
||||
await it("includes patch when non-zero", () => {
|
||||
const ver = parseEnterpriseVersion("3.10.2")!;
|
||||
assert.equal(printEnterpriseVersion(ver), "3.10.2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("addWeeks", async () => {
|
||||
await it("adds weeks to a date", () => {
|
||||
const date = new Date("2025-01-01T00:00:00Z");
|
||||
const result = addWeeks(date, 2);
|
||||
assert.equal(result.toISOString(), "2025-01-15T00:00:00.000Z");
|
||||
});
|
||||
|
||||
await it("does not mutate the original date", () => {
|
||||
const date = new Date("2025-01-01T00:00:00Z");
|
||||
addWeeks(date, 2);
|
||||
assert.equal(date.toISOString(), "2025-01-01T00:00:00.000Z");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper to build a release entry with a feature freeze and end-of-life date.
|
||||
* Dates are ISO date strings (e.g. "2025-06-01").
|
||||
*/
|
||||
function release(featureFreeze: string, end: string) {
|
||||
return { feature_freeze: featureFreeze, end };
|
||||
}
|
||||
|
||||
describe("determineSupportedRange", async () => {
|
||||
// A fixed "today" for deterministic tests.
|
||||
const today = new Date("2025-06-15");
|
||||
|
||||
const farPastEnd = "2020-01-01";
|
||||
const farFutureEnd = "2099-12-31";
|
||||
const farPastFreeze = "2020-01-01";
|
||||
const farFutureFreeze = "2099-12-31";
|
||||
|
||||
await it("returns the only supported release as both min and max", () => {
|
||||
const releases: EnterpriseReleases = {
|
||||
"3.10": release(farPastFreeze, farFutureEnd),
|
||||
};
|
||||
const result = determineSupportedRange(
|
||||
today,
|
||||
{ minimumVersion: "3.10", maximumVersion: "3.10" },
|
||||
releases,
|
||||
);
|
||||
assert.equal(result.minimumVersion, "3.10");
|
||||
assert.equal(result.maximumVersion, "3.10");
|
||||
});
|
||||
|
||||
await it("determines the range from multiple supported releases", () => {
|
||||
const releases: EnterpriseReleases = {
|
||||
"3.10": release(farPastFreeze, farFutureEnd),
|
||||
"3.11": release(farPastFreeze, farFutureEnd),
|
||||
"3.12": release(farPastFreeze, farFutureEnd),
|
||||
};
|
||||
const result = determineSupportedRange(
|
||||
today,
|
||||
{ minimumVersion: "3.10", maximumVersion: "3.12" },
|
||||
releases,
|
||||
);
|
||||
assert.equal(result.minimumVersion, "3.10");
|
||||
assert.equal(result.maximumVersion, "3.12");
|
||||
});
|
||||
|
||||
await it("drops an end-of-life release from the minimum", () => {
|
||||
const releases: EnterpriseReleases = {
|
||||
// 3.10 has been end of life for a long time.
|
||||
"3.10": release(farPastFreeze, farPastEnd),
|
||||
"3.11": release(farPastFreeze, farFutureEnd),
|
||||
"3.12": release(farPastFreeze, farFutureEnd),
|
||||
};
|
||||
const result = determineSupportedRange(
|
||||
today,
|
||||
{ minimumVersion: "3.10", maximumVersion: "3.12" },
|
||||
releases,
|
||||
);
|
||||
assert.equal(result.minimumVersion, "3.11");
|
||||
assert.equal(result.maximumVersion, "3.12");
|
||||
});
|
||||
|
||||
await it("bumps the maximum when a newer release's feature freeze has passed", () => {
|
||||
const releases: EnterpriseReleases = {
|
||||
"3.10": release(farPastFreeze, farFutureEnd),
|
||||
"3.11": release(farPastFreeze, farFutureEnd),
|
||||
// 3.12 has a feature freeze far in the past, so it should be picked up.
|
||||
"3.12": release(farPastFreeze, farFutureEnd),
|
||||
};
|
||||
const result = determineSupportedRange(
|
||||
today,
|
||||
// The stored maximum is 3.11, but 3.12 should be picked up.
|
||||
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||
releases,
|
||||
);
|
||||
assert.equal(result.minimumVersion, "3.10");
|
||||
assert.equal(result.maximumVersion, "3.12");
|
||||
});
|
||||
|
||||
await it("does not bump the maximum when feature freeze is far in the future", () => {
|
||||
const releases: EnterpriseReleases = {
|
||||
"3.10": release(farPastFreeze, farFutureEnd),
|
||||
"3.11": release(farPastFreeze, farFutureEnd),
|
||||
// 3.12 has a feature freeze far in the future, so it should NOT be picked up.
|
||||
"3.12": release(farFutureFreeze, farFutureEnd),
|
||||
};
|
||||
const result = determineSupportedRange(
|
||||
today,
|
||||
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||
releases,
|
||||
);
|
||||
assert.equal(result.minimumVersion, "3.10");
|
||||
assert.equal(result.maximumVersion, "3.11");
|
||||
});
|
||||
|
||||
await it("ignores releases older than the first supported release (2.22)", () => {
|
||||
const releases: EnterpriseReleases = {
|
||||
"2.21": release(farPastFreeze, farFutureEnd),
|
||||
"3.10": release(farPastFreeze, farFutureEnd),
|
||||
"3.11": release(farPastFreeze, farFutureEnd),
|
||||
};
|
||||
const result = determineSupportedRange(
|
||||
today,
|
||||
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||
releases,
|
||||
);
|
||||
// 2.21 is older than 2.22, so it should be ignored — 3.10 remains the minimum.
|
||||
assert.equal(result.minimumVersion, "3.10");
|
||||
assert.equal(result.maximumVersion, "3.11");
|
||||
});
|
||||
|
||||
await it("throws when no supported releases remain", () => {
|
||||
const releases: EnterpriseReleases = {
|
||||
// All releases are end of life.
|
||||
"3.10": release(farPastFreeze, farPastEnd),
|
||||
"3.11": release(farPastFreeze, farPastEnd),
|
||||
};
|
||||
assert.throws(
|
||||
() =>
|
||||
determineSupportedRange(
|
||||
today,
|
||||
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||
releases,
|
||||
),
|
||||
/Could not determine oldest supported release/,
|
||||
);
|
||||
});
|
||||
|
||||
await it("throws when maximumVersion is not a valid version", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
determineSupportedRange(
|
||||
today,
|
||||
{ minimumVersion: "3.10", maximumVersion: "invalid" },
|
||||
{},
|
||||
),
|
||||
/is not a valid semantic version/,
|
||||
);
|
||||
});
|
||||
});
|
||||
Executable
+243
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
/**
|
||||
* Updates src/api-compatibility.json with the current range of supported
|
||||
* GitHub Enterprise Server versions by reading the releases.json file from
|
||||
* an `enterprise-releases` checkout.
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { type SemVer } from "semver";
|
||||
import * as semver from "semver";
|
||||
|
||||
import * as json from "../src/json";
|
||||
|
||||
import { API_COMPATIBILITY_FILE } from "./config";
|
||||
|
||||
/** The first GHES version that included Code Scanning. */
|
||||
const FIRST_SUPPORTED_RELEASE: SemVer = new semver.SemVer("2.22.0");
|
||||
|
||||
/** Environment variables specific to this script. */
|
||||
export enum EnvVar {
|
||||
ENTERPRISE_RELEASES_PATH = "ENTERPRISE_RELEASES_PATH",
|
||||
}
|
||||
|
||||
/**
|
||||
* The semver specification requires three numeric components, but GHES release families
|
||||
* only have two. This function uses `semver.coerce` to first coerce the version string
|
||||
* into an acceptable input for `semver.parse`. E.g. `3.10` becomes `3.10.0`.
|
||||
*/
|
||||
export function parseEnterpriseVersion(val: string): SemVer | null {
|
||||
return semver.parse(semver.coerce(val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirroring `parseEnterpriseVersion`, this function returns only the major and minor
|
||||
* version components from `ver`.
|
||||
*/
|
||||
export function printEnterpriseVersion(ver: SemVer) {
|
||||
if (ver.patch === 0) {
|
||||
return `${ver.major}.${ver.minor}`;
|
||||
}
|
||||
return ver.toString();
|
||||
}
|
||||
|
||||
/** The JSON schema for `API_COMPATIBILITY_FILE`. */
|
||||
const apiCompatibilitySchema = {
|
||||
minimumVersion: json.string,
|
||||
maximumVersion: json.string,
|
||||
} as const satisfies json.Schema;
|
||||
|
||||
/** The type representing the expected contents of `API_COMPATIBILITY_FILE`. */
|
||||
type ApiCompatibility = json.FromSchema<typeof apiCompatibilitySchema>;
|
||||
|
||||
/** Reads the current contents of the `API_COMPATIBILITY_FILE` file. */
|
||||
export function readApiCompatibility(): ApiCompatibility {
|
||||
const apiCompatibilityData: unknown = JSON.parse(
|
||||
fs.readFileSync(API_COMPATIBILITY_FILE, "utf8"),
|
||||
);
|
||||
|
||||
if (!json.isObject(apiCompatibilityData)) {
|
||||
throw new Error(
|
||||
`Expected '${API_COMPATIBILITY_FILE}' to contain an object.`,
|
||||
);
|
||||
}
|
||||
if (!json.validateSchema(apiCompatibilitySchema, apiCompatibilityData)) {
|
||||
throw new Error(
|
||||
`The contents of '${API_COMPATIBILITY_FILE}' do not match the expected JSON schema.`,
|
||||
);
|
||||
}
|
||||
|
||||
return apiCompatibilityData;
|
||||
}
|
||||
|
||||
/** The JSON schema for entries in the `releases.json` file. */
|
||||
const releaseDataSchema = {
|
||||
feature_freeze: json.string,
|
||||
end: json.string,
|
||||
} as const satisfies json.Schema;
|
||||
|
||||
/** The type representing entries in the `releases.json` file. */
|
||||
export type ReleaseData = json.FromSchema<typeof releaseDataSchema>;
|
||||
|
||||
/** A mapping from GHES releases to release information. */
|
||||
export type EnterpriseReleases = Record<string, ReleaseData>;
|
||||
|
||||
/** Reads information about GHES releases. */
|
||||
export function readEnterpriseReleases(
|
||||
enterpriseReleasesPath: string,
|
||||
): EnterpriseReleases {
|
||||
const releaseFilePath = path.join(enterpriseReleasesPath, "releases.json");
|
||||
const releases: unknown = JSON.parse(
|
||||
fs.readFileSync(releaseFilePath, "utf8"),
|
||||
);
|
||||
|
||||
if (!json.isObject(releases)) {
|
||||
throw new Error(`Expected '${releaseFilePath}' to contain an object.`);
|
||||
}
|
||||
|
||||
// Remove GHES version using a previous version numbering scheme.
|
||||
delete releases["11.10"];
|
||||
|
||||
// Validate that the object satisfies the schema.
|
||||
for (const [, releaseData] of Object.entries(releases)) {
|
||||
if (!json.isObject(releaseData)) {
|
||||
throw new Error(
|
||||
`Expected release data to be an object, but it is ${typeof releaseData}.`,
|
||||
);
|
||||
}
|
||||
if (!json.validateSchema(releaseDataSchema, releaseData)) {
|
||||
throw new Error("Expected release data to satisfy schema.");
|
||||
}
|
||||
}
|
||||
|
||||
return releases;
|
||||
}
|
||||
|
||||
/** Adds `weeks`-many weeks to the UTC date of `date`. */
|
||||
export function addWeeks(date: Date, weeks: number): Date {
|
||||
const result = new Date(date);
|
||||
result.setUTCDate(date.getUTCDate() + weeks * 7);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Determines the current range of GHES versions we should support. */
|
||||
export function determineSupportedRange(
|
||||
today: Date,
|
||||
apiCompatibilityData: ApiCompatibility,
|
||||
releases: EnterpriseReleases,
|
||||
): ApiCompatibility {
|
||||
// We only care about the UTC date component.
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
// Our goal is to identify the oldest and newest GHES release we should support.
|
||||
// We begin with `oldestSupportRelease = undefined` so that we determine the
|
||||
// minimum from scratch and don't stick to `apiCompatibilityData.minimumVersion`
|
||||
// when it is no longer supported.
|
||||
// For `newestSupportedRelease`, we assume that `apiCompatibilityData.maximumVersion`
|
||||
// is guaranteed to not be outdated.
|
||||
let oldestSupportedRelease: SemVer | undefined;
|
||||
let newestSupportedRelease = parseEnterpriseVersion(
|
||||
apiCompatibilityData.maximumVersion,
|
||||
);
|
||||
|
||||
if (newestSupportedRelease === null) {
|
||||
throw new Error(
|
||||
`${apiCompatibilityData.maximumVersion} is not a valid semantic version.`,
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: We deliberately omit including any data from `releases` in the error messages below.
|
||||
|
||||
for (const [releaseVersionString, releaseData] of Object.entries(releases)) {
|
||||
const releaseVersion = parseEnterpriseVersion(releaseVersionString);
|
||||
|
||||
if (releaseVersion === null) {
|
||||
throw new Error("Invalid enterprise release version.");
|
||||
}
|
||||
|
||||
// Ignore GHES releases older than `FIRST_SUPPORTED_RELEASE`.
|
||||
if (semver.compare(releaseVersion, FIRST_SUPPORTED_RELEASE) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set `newestSupportedRelease` to a GHES release if it has a greater version
|
||||
// than the current `newestSupportedRelease` and the feature freeze has
|
||||
// already happened or will be in the next two weeks.
|
||||
if (semver.compare(releaseVersion, newestSupportedRelease) > 0) {
|
||||
const featureFreezeDate = new Date(releaseData.feature_freeze);
|
||||
if (featureFreezeDate < addWeeks(today, 2)) {
|
||||
newestSupportedRelease = releaseVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
oldestSupportedRelease === undefined ||
|
||||
semver.compare(releaseVersion, oldestSupportedRelease) < 0
|
||||
) {
|
||||
const endOfLifeDate = new Date(releaseData.end);
|
||||
// The GHES version is not actually end of life until the end of the day
|
||||
// specified by `endOfLifeDate`. Wait an extra week to be safe.
|
||||
const isEndOfLife = today > addWeeks(endOfLifeDate, 1);
|
||||
if (!isEndOfLife) {
|
||||
oldestSupportedRelease = releaseVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!oldestSupportedRelease) {
|
||||
throw new Error("Could not determine oldest supported release.");
|
||||
}
|
||||
|
||||
return {
|
||||
maximumVersion: printEnterpriseVersion(newestSupportedRelease),
|
||||
minimumVersion: printEnterpriseVersion(oldestSupportedRelease),
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const enterpriseReleasesPath = process.env[EnvVar.ENTERPRISE_RELEASES_PATH];
|
||||
if (!enterpriseReleasesPath) {
|
||||
throw new Error(
|
||||
`${EnvVar.ENTERPRISE_RELEASES_PATH} environment variable must be set`,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the version compatibility data stored in the repo.
|
||||
const apiCompatibilityData = readApiCompatibility();
|
||||
|
||||
// Get the GHES release information.
|
||||
const releases = readEnterpriseReleases(enterpriseReleasesPath);
|
||||
|
||||
// Determine the supported range.
|
||||
const newCompatibilityData: ApiCompatibility = determineSupportedRange(
|
||||
new Date(),
|
||||
apiCompatibilityData,
|
||||
releases,
|
||||
);
|
||||
|
||||
// If the version range has changed, write the updates to `API_COMPATIBILITY_FILE`.
|
||||
if (
|
||||
newCompatibilityData.minimumVersion !==
|
||||
apiCompatibilityData.minimumVersion ||
|
||||
newCompatibilityData.maximumVersion !== apiCompatibilityData.maximumVersion
|
||||
) {
|
||||
const data = JSON.stringify(newCompatibilityData);
|
||||
fs.writeFileSync(API_COMPATIBILITY_FILE, `${data}\n`);
|
||||
|
||||
console.log(
|
||||
`Updated '${path.basename(API_COMPATIBILITY_FILE)}': ${newCompatibilityData.minimumVersion} - ${newCompatibilityData.maximumVersion}`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`No changes, not writing to '${path.basename(API_COMPATIBILITY_FILE)}'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Only call `main` if this script was run directly.
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
@@ -21,6 +21,21 @@ import {
|
||||
*/
|
||||
declare const __CODEQL_ACTION_VERSION__: string;
|
||||
|
||||
/**
|
||||
* Abstracts over GitHub Actions functions so that we do not have to stub
|
||||
* global functions in tests.
|
||||
*/
|
||||
export interface ActionsEnv {
|
||||
getOptionalInput: (name: string) => string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real `ActionsEnv` used by production code.
|
||||
*/
|
||||
export function getActionsEnv(): ActionsEnv {
|
||||
return { getOptionalInput };
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around core.getInput for inputs that always have a value.
|
||||
* Also see getOptionalInput.
|
||||
|
||||
+2
-2
@@ -281,11 +281,11 @@ extensions:
|
||||
.join(checkoutPath, range.path)
|
||||
.replaceAll(path.sep, "/");
|
||||
|
||||
// Using yaml.dump() with `forceQuotes: true` ensures that all special
|
||||
// Using yaml.dump() with `quoteStyle: "double"` ensures that all special
|
||||
// characters are escaped, and that the path is always rendered as a
|
||||
// quoted string on a single line.
|
||||
return (
|
||||
` - [${yaml.dump(filename, { forceQuotes: true }).trim()}, ` +
|
||||
` - [${yaml.dump(filename, { quoteStyle: "single" }).trim()}, ` +
|
||||
`${range.startLine}, ${range.endLine}]\n`
|
||||
);
|
||||
})
|
||||
|
||||
+10
-38
@@ -117,16 +117,12 @@ export interface CodeQL {
|
||||
memoryFlag: string,
|
||||
enableDebugLogging: boolean,
|
||||
): Promise<void>;
|
||||
/**
|
||||
* Run 'codeql resolve languages'.
|
||||
*/
|
||||
resolveLanguages(): Promise<ResolveLanguagesOutput>;
|
||||
/**
|
||||
* Run 'codeql resolve languages' with '--format=betterjson'.
|
||||
*/
|
||||
betterResolveLanguages(options?: {
|
||||
resolveLanguages(options?: {
|
||||
filterToLanguagesWithQueries: boolean;
|
||||
}): Promise<BetterResolveLanguagesOutput>;
|
||||
}): Promise<ResolveLanguagesOutput>;
|
||||
/**
|
||||
* Run 'codeql resolve build-environment'
|
||||
*/
|
||||
@@ -239,20 +235,14 @@ export interface ResolveDatabaseOutput {
|
||||
}
|
||||
|
||||
export interface ResolveLanguagesOutput {
|
||||
[language: string]: [string];
|
||||
}
|
||||
|
||||
export interface BetterResolveLanguagesOutput {
|
||||
aliases?: {
|
||||
[alias: string]: string;
|
||||
};
|
||||
extractors: {
|
||||
[language: string]: [
|
||||
{
|
||||
extractor_root: string;
|
||||
extractor_options?: any;
|
||||
},
|
||||
];
|
||||
[language: string]: Array<{
|
||||
extractor_root: string;
|
||||
extractor_options?: any;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -460,10 +450,9 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
|
||||
"extractUsingBuildMode",
|
||||
),
|
||||
finalizeDatabase: resolveFunction(partialCodeql, "finalizeDatabase"),
|
||||
resolveLanguages: resolveFunction(partialCodeql, "resolveLanguages"),
|
||||
betterResolveLanguages: resolveFunction(
|
||||
resolveLanguages: resolveFunction(
|
||||
partialCodeql,
|
||||
"betterResolveLanguages",
|
||||
"resolveLanguages",
|
||||
async () => ({ aliases: {}, extractors: {} }),
|
||||
),
|
||||
resolveBuildEnvironment: resolveFunction(
|
||||
@@ -735,24 +724,7 @@ async function getCodeQLForCmd(
|
||||
];
|
||||
await runCli(cmd, args);
|
||||
},
|
||||
async resolveLanguages() {
|
||||
const codeqlArgs = [
|
||||
"resolve",
|
||||
"languages",
|
||||
"--format=json",
|
||||
...getExtraOptionsFromEnv(["resolve", "languages"]),
|
||||
];
|
||||
const output = await runCli(cmd, codeqlArgs);
|
||||
|
||||
try {
|
||||
return JSON.parse(output) as ResolveLanguagesOutput;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Unexpected output from codeql resolve languages: ${e}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
async betterResolveLanguages(
|
||||
async resolveLanguages(
|
||||
{
|
||||
filterToLanguagesWithQueries,
|
||||
}: {
|
||||
@@ -773,7 +745,7 @@ async function getCodeQLForCmd(
|
||||
const output = await runCli(cmd, codeqlArgs);
|
||||
|
||||
try {
|
||||
return JSON.parse(output) as BetterResolveLanguagesOutput;
|
||||
return JSON.parse(output) as ResolveLanguagesOutput;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Unexpected output from codeql resolve languages with --format=betterjson: ${e}`,
|
||||
|
||||
+11
-11
@@ -75,7 +75,7 @@ function createTestInitConfigInputs(
|
||||
repository: { owner: "github", repo: "example" },
|
||||
tempDir: "",
|
||||
codeql: createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
html: [{ extractor_root: "" }],
|
||||
@@ -150,7 +150,7 @@ test.serial("load empty config", async (t) => {
|
||||
setupActionsVars(tempDir, tempDir);
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
@@ -193,7 +193,7 @@ test.serial("load code quality config", async (t) => {
|
||||
setupActionsVars(tempDir, tempDir);
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
actions: [{ extractor_root: "" }],
|
||||
@@ -247,7 +247,7 @@ test.serial(
|
||||
setupActionsVars(tempDir, tempDir);
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
@@ -305,7 +305,7 @@ test.serial("loading a saved config produces the same config", async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
@@ -352,7 +352,7 @@ test.serial("loading config with version mismatch throws", async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
@@ -487,7 +487,7 @@ test.serial("load non-empty input", async (t) => {
|
||||
setupActionsVars(tempDir, tempDir);
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
@@ -582,7 +582,7 @@ test.serial(
|
||||
fs.mkdirSync(path.join(tempDir, "foo"));
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
@@ -615,7 +615,7 @@ test.serial(
|
||||
test.serial("API client used when reading remote config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
@@ -725,7 +725,7 @@ test.serial("No detected languages", async (t) => {
|
||||
mockListLanguages([]);
|
||||
const codeql = createStubCodeQL({
|
||||
async resolveLanguages() {
|
||||
return {};
|
||||
return { extractors: {} };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -891,7 +891,7 @@ const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
extractor_root: "",
|
||||
};
|
||||
const codeQL = createStubCodeQL({
|
||||
betterResolveLanguages: (options) =>
|
||||
resolveLanguages: (options) =>
|
||||
Promise.resolve({
|
||||
aliases: {
|
||||
"c#": BuiltInLanguage.csharp,
|
||||
|
||||
+1
-1
@@ -266,7 +266,7 @@ async function getSupportedLanguageMap(
|
||||
const resolveSupportedLanguagesUsingCli = await codeql.supportsFeature(
|
||||
ToolsFeature.BuiltinExtractorsSpecifyDefaultQueries,
|
||||
);
|
||||
const resolveResult = await codeql.betterResolveLanguages({
|
||||
const resolveResult = await codeql.resolveLanguages({
|
||||
filterToLanguagesWithQueries: resolveSupportedLanguagesUsingCli,
|
||||
});
|
||||
if (resolveSupportedLanguagesUsingCli) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import test from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { RepositoryPropertyName } from "../feature-flags/properties";
|
||||
import {
|
||||
getTestActionsEnv,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
|
||||
import { getConfigFileInput } from "./file";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("getConfigFileInput returns undefined by default", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const actionsEnv = getTestActionsEnv();
|
||||
const result = getConfigFileInput(logger, actionsEnv, {});
|
||||
t.is(result, undefined);
|
||||
});
|
||||
|
||||
const repositoryProperties = {
|
||||
[RepositoryPropertyName.CONFIG_FILE]: "/path/from/property",
|
||||
};
|
||||
|
||||
test("getConfigFileInput returns input value", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const actionsEnv = getTestActionsEnv();
|
||||
const testInput = "/some/path";
|
||||
sinon
|
||||
.stub(actionsEnv, "getOptionalInput")
|
||||
.withArgs("config-file")
|
||||
.returns(testInput);
|
||||
|
||||
// Even though both an input and repository property are configured,
|
||||
// we prefer the direct input to the Action.
|
||||
const result = getConfigFileInput(logger, actionsEnv, repositoryProperties);
|
||||
t.is(result, testInput);
|
||||
|
||||
// Check for the expected log message.
|
||||
t.true(logger.hasMessage("Using configuration file input from workflow"));
|
||||
});
|
||||
|
||||
test("getConfigFileInput returns repository property value", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const actionsEnv = getTestActionsEnv();
|
||||
|
||||
// Since there is no direct input, we should use the repository property.
|
||||
const result = getConfigFileInput(logger, actionsEnv, repositoryProperties);
|
||||
t.is(result, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]);
|
||||
|
||||
// Check for the expected log message.
|
||||
t.true(
|
||||
logger.hasMessage(
|
||||
"Using configuration file input from repository property",
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("getConfigFileInput ignores empty repository property value", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const actionsEnv = getTestActionsEnv();
|
||||
|
||||
// Since the repository property value is an empty/whitespace string, we should ignore it.
|
||||
const result = getConfigFileInput(logger, actionsEnv, {
|
||||
[RepositoryPropertyName.CONFIG_FILE]: " ",
|
||||
});
|
||||
t.is(result, undefined);
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ActionsEnv } from "../actions-util";
|
||||
import {
|
||||
RepositoryProperties,
|
||||
RepositoryPropertyName,
|
||||
} from "../feature-flags/properties";
|
||||
import { Logger } from "../logging";
|
||||
|
||||
/**
|
||||
* Gets the value that is configured for the configuration file, if any.
|
||||
*/
|
||||
export function getConfigFileInput(
|
||||
logger: Logger,
|
||||
actions: ActionsEnv,
|
||||
repositoryProperties: Partial<RepositoryProperties>,
|
||||
): string | undefined {
|
||||
const input = actions.getOptionalInput("config-file");
|
||||
|
||||
if (input !== undefined) {
|
||||
logger.info(`Using configuration file input from workflow: ${input}`);
|
||||
return input;
|
||||
}
|
||||
|
||||
const propertyValue =
|
||||
repositoryProperties[RepositoryPropertyName.CONFIG_FILE];
|
||||
|
||||
if (propertyValue !== undefined && propertyValue.trim().length > 0) {
|
||||
logger.info(
|
||||
`Using configuration file input from repository property: ${propertyValue}`,
|
||||
);
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import * as path from "path";
|
||||
import * as artifact from "@actions/artifact";
|
||||
import * as artifactLegacy from "@actions/artifact-legacy";
|
||||
import * as core from "@actions/core";
|
||||
import archiver from "archiver";
|
||||
import { ZipArchive } from "archiver";
|
||||
|
||||
import { getOptionalInput, getTemporaryDirectory } from "./actions-util";
|
||||
import { dbIsFinalized } from "./analyze";
|
||||
@@ -397,7 +397,7 @@ async function createPartialDatabaseBundle(
|
||||
await fs.promises.rm(databaseBundlePath, { force: true });
|
||||
}
|
||||
const output = fs.createWriteStream(databaseBundlePath);
|
||||
const zip = archiver("zip");
|
||||
const zip = new ZipArchive();
|
||||
|
||||
zip.on("error", (err) => {
|
||||
throw err;
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { PullRequestBranches } from "./actions-util";
|
||||
import { getApiClient, getGitHubVersion } from "./api-client";
|
||||
import type { CodeQL } from "./codeql";
|
||||
import { Feature, FeatureEnablement } from "./feature-flags";
|
||||
import { Logger, withGroupAsync } from "./logging";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwoFromEnv } from "./repository";
|
||||
import { getErrorMessage, GitHubVariant, satisfiesGHESVersion } from "./util";
|
||||
|
||||
@@ -83,16 +83,14 @@ export async function prepareDiffInformedAnalysis(
|
||||
return false;
|
||||
}
|
||||
|
||||
return await withGroupAsync("Computing PR diff ranges", async () => {
|
||||
try {
|
||||
return await computeAndPersistDiffRanges(branches, logger);
|
||||
} catch (e) {
|
||||
logger.warning(
|
||||
`Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
return await computeAndPersistDiffRanges(branches, logger);
|
||||
} catch (e) {
|
||||
logger.warning(
|
||||
`Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DiffThunkRange {
|
||||
@@ -192,6 +190,7 @@ export async function computeAndPersistDiffRanges(
|
||||
branches: PullRequestBranches,
|
||||
logger: Logger,
|
||||
): Promise<boolean> {
|
||||
logger.info("Computing PR diff ranges...");
|
||||
const ranges = await getPullRequestEditedDiffRanges(branches, logger);
|
||||
if (ranges === undefined) {
|
||||
return false;
|
||||
|
||||
@@ -134,7 +134,6 @@ export enum Feature {
|
||||
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||
/** Note that this currently only disables baseline file coverage information. */
|
||||
SkipFileCoverageOnPrs = "skip_file_coverage_on_prs",
|
||||
StartProxyRemoveUnusedRegistries = "start_proxy_remove_unused_registries",
|
||||
StartProxyUseFeaturesRelease = "start_proxy_use_features_release",
|
||||
UploadOverlayDbToApi = "upload_overlay_db_to_api",
|
||||
ValidateDbConfig = "validate_db_config",
|
||||
@@ -375,11 +374,6 @@ export const featureConfig = {
|
||||
minimumVersion: undefined,
|
||||
toolsFeature: ToolsFeature.SuppressesMissingFileBaselineWarning,
|
||||
},
|
||||
[Feature.StartProxyRemoveUnusedRegistries]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_START_PROXY_REMOVE_UNUSED_REGISTRIES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.StartProxyUseFeaturesRelease]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE",
|
||||
|
||||
@@ -77,6 +77,7 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
{ property_name: "github-codeql-config-file", value: "owner/repo" },
|
||||
{ property_name: "github-codeql-extra-queries", value: "+queries" },
|
||||
{ property_name: "unknown-property", value: "something" },
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
@@ -87,7 +88,10 @@ test.serial("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
);
|
||||
t.deepEqual(response, { "github-codeql-extra-queries": "+queries" });
|
||||
t.deepEqual(response, {
|
||||
"github-codeql-config-file": "owner/repo",
|
||||
"github-codeql-extra-queries": "+queries",
|
||||
});
|
||||
});
|
||||
|
||||
test.serial("loadPropertiesFromApi parses true boolean property", async (t) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ export const GITHUB_CODEQL_PROPERTY_PREFIX = "github-codeql-";
|
||||
* Enumerates repository property names that have some meaning to us.
|
||||
*/
|
||||
export enum RepositoryPropertyName {
|
||||
CONFIG_FILE = "github-codeql-config-file",
|
||||
DISABLE_OVERLAY = "github-codeql-disable-overlay",
|
||||
EXTRA_QUERIES = "github-codeql-extra-queries",
|
||||
FILE_COVERAGE_ON_PRS = "github-codeql-file-coverage-on-prs",
|
||||
@@ -17,6 +18,7 @@ export enum RepositoryPropertyName {
|
||||
|
||||
/** Parsed types of the known repository properties. */
|
||||
export type AllRepositoryProperties = {
|
||||
[RepositoryPropertyName.CONFIG_FILE]: string;
|
||||
[RepositoryPropertyName.DISABLE_OVERLAY]: boolean;
|
||||
[RepositoryPropertyName.EXTRA_QUERIES]: string;
|
||||
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: boolean;
|
||||
@@ -27,6 +29,7 @@ export type RepositoryProperties = Partial<AllRepositoryProperties>;
|
||||
|
||||
/** Maps known repository properties to the type we expect to get from the API. */
|
||||
export type RepositoryPropertyApiType = {
|
||||
[RepositoryPropertyName.CONFIG_FILE]: string;
|
||||
[RepositoryPropertyName.DISABLE_OVERLAY]: string;
|
||||
[RepositoryPropertyName.EXTRA_QUERIES]: string;
|
||||
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: string;
|
||||
@@ -74,6 +77,7 @@ const booleanProperty = {
|
||||
const repositoryPropertyParsers: {
|
||||
[K in RepositoryPropertyName]: PropertyInfo<K>;
|
||||
} = {
|
||||
[RepositoryPropertyName.CONFIG_FILE]: stringProperty,
|
||||
[RepositoryPropertyName.DISABLE_OVERLAY]: booleanProperty,
|
||||
[RepositoryPropertyName.EXTRA_QUERIES]: stringProperty,
|
||||
[RepositoryPropertyName.FILE_COVERAGE_ON_PRS]: booleanProperty,
|
||||
|
||||
+5
-2
@@ -9,6 +9,7 @@ import { v4 as uuidV4 } from "uuid";
|
||||
|
||||
import {
|
||||
FileCmdNotFoundError,
|
||||
getActionsEnv,
|
||||
getActionVersion,
|
||||
getFileType,
|
||||
getOptionalInput,
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
shouldRestoreCache,
|
||||
} from "./caching-utils";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { getConfigFileInput } from "./config/file";
|
||||
import * as configUtils from "./config-utils";
|
||||
import {
|
||||
DependencyCacheRestoreStatusReport,
|
||||
@@ -207,6 +209,7 @@ async function run(startedAt: Date) {
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
const logger = getActionsLogger();
|
||||
const actionsEnv = getActionsEnv();
|
||||
|
||||
let apiDetails: GitHubApiCombinedDetails;
|
||||
let config: configUtils.Config | undefined;
|
||||
@@ -251,6 +254,7 @@ async function run(startedAt: Date) {
|
||||
repositoryNwo,
|
||||
logger,
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
|
||||
// Create a unique identifier for this run.
|
||||
const jobRunUuid = uuidV4();
|
||||
@@ -259,7 +263,7 @@ async function run(startedAt: Date) {
|
||||
|
||||
core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
||||
|
||||
configFile = getOptionalInput("config-file");
|
||||
configFile = getConfigFileInput(logger, actionsEnv, repositoryProperties);
|
||||
|
||||
// path.resolve() respects the intended semantics of source-root. If
|
||||
// source-root is relative, it is relative to the GITHUB_WORKSPACE. If
|
||||
@@ -350,7 +354,6 @@ async function run(startedAt: Date) {
|
||||
|
||||
analysisKinds = await getAnalysisKinds(logger, features);
|
||||
const debugMode = getOptionalInput("debug") === "true" || core.isDebug();
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
const fileCoverageResult = await getFileCoverageInformationEnabled(
|
||||
debugMode,
|
||||
codeql,
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as core from "@actions/core";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -57,18 +57,12 @@ async function run(startedAt: Date) {
|
||||
const languageInput = actionsUtil.getOptionalInput("language");
|
||||
language = languageInput ? parseBuiltInLanguage(languageInput) : undefined;
|
||||
|
||||
// Query the FF for whether we should use the reduced registry mapping.
|
||||
const skipUnusedRegistries = await features.getValue(
|
||||
Feature.StartProxyRemoveUnusedRegistries,
|
||||
);
|
||||
|
||||
// Get the registry configurations from one of the inputs.
|
||||
const credentials = getCredentials(
|
||||
logger,
|
||||
actionsUtil.getOptionalInput("registry_secrets"),
|
||||
actionsUtil.getOptionalInput("registries_credentials"),
|
||||
language,
|
||||
skipUnusedRegistries,
|
||||
);
|
||||
|
||||
if (credentials.length === 0) {
|
||||
|
||||
+4
-22
@@ -585,7 +585,6 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
undefined,
|
||||
credentialsInput,
|
||||
BuiltInLanguage.java,
|
||||
false,
|
||||
);
|
||||
|
||||
t.is(credentials.length, 3);
|
||||
@@ -604,8 +603,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
toEncodedJSON([{ ...baseInvalid, "replaces-base": null }]),
|
||||
BuiltInLanguage.actions,
|
||||
false,
|
||||
BuiltInLanguage.java,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
@@ -613,8 +611,7 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
toEncodedJSON([{ ...baseInvalid, "replaces-base": 123 }]),
|
||||
BuiltInLanguage.actions,
|
||||
false,
|
||||
BuiltInLanguage.java,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
@@ -622,13 +619,12 @@ test("getCredentials validates 'replaces-base' correctly", async (t) => {
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
toEncodedJSON([{ ...baseInvalid, "replaces-base": "true" }]),
|
||||
BuiltInLanguage.actions,
|
||||
false,
|
||||
BuiltInLanguage.java,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("getCredentials returns all credentials for Actions when using LANGUAGE_TO_REGISTRY_TYPE", async (t) => {
|
||||
test("getCredentials returns no credentials for Actions", async (t) => {
|
||||
const credentialsInput = toEncodedJSON(mixedCredentials);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
@@ -636,20 +632,6 @@ test("getCredentials returns all credentials for Actions when using LANGUAGE_TO_
|
||||
undefined,
|
||||
credentialsInput,
|
||||
BuiltInLanguage.actions,
|
||||
false,
|
||||
);
|
||||
t.is(credentials.length, mixedCredentials.length);
|
||||
});
|
||||
|
||||
test("getCredentials returns no credentials for Actions when using NEW_LANGUAGE_TO_REGISTRY_TYPE", async (t) => {
|
||||
const credentialsInput = toEncodedJSON(mixedCredentials);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
BuiltInLanguage.actions,
|
||||
true,
|
||||
);
|
||||
t.deepEqual(credentials, []);
|
||||
});
|
||||
|
||||
+2
-16
@@ -189,17 +189,7 @@ function isPAT(value: string) {
|
||||
|
||||
type RegistryMapping = Partial<Record<BuiltInLanguage, string[]>>;
|
||||
|
||||
const LANGUAGE_TO_REGISTRY_TYPE: RegistryMapping = {
|
||||
java: ["maven_repository"],
|
||||
csharp: ["nuget_feed"],
|
||||
javascript: ["npm_registry"],
|
||||
python: ["python_index"],
|
||||
ruby: ["rubygems_server"],
|
||||
rust: ["cargo_registry"],
|
||||
go: ["goproxy_server", "git_source"],
|
||||
} as const;
|
||||
|
||||
const NEW_LANGUAGE_TO_REGISTRY_TYPE: Required<RegistryMapping> = {
|
||||
const LANGUAGE_TO_REGISTRY_TYPE: Required<RegistryMapping> = {
|
||||
actions: [],
|
||||
cpp: [],
|
||||
java: ["maven_repository"],
|
||||
@@ -251,13 +241,9 @@ export function getCredentials(
|
||||
registrySecrets: string | undefined,
|
||||
registriesCredentials: string | undefined,
|
||||
language: BuiltInLanguage | undefined,
|
||||
skipUnusedRegistries: boolean = false,
|
||||
): Credential[] {
|
||||
const registryMapping = skipUnusedRegistries
|
||||
? NEW_LANGUAGE_TO_REGISTRY_TYPE
|
||||
: LANGUAGE_TO_REGISTRY_TYPE;
|
||||
const registryTypeForLanguage = language
|
||||
? registryMapping[language]
|
||||
? LANGUAGE_TO_REGISTRY_TYPE[language]
|
||||
: undefined;
|
||||
|
||||
let credentialsStr: string;
|
||||
|
||||
+10
-1
@@ -10,7 +10,7 @@ import test, {
|
||||
import nock from "nock";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { getActionVersion } from "./actions-util";
|
||||
import { ActionsEnv, getActionVersion } from "./actions-util";
|
||||
import { AnalysisKind } from "./analyses";
|
||||
import * as apiClient from "./api-client";
|
||||
import { GitHubApiDetails } from "./api-client";
|
||||
@@ -172,6 +172,15 @@ export function makeMacro<Args extends unknown[]>(
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an `ActionsEnv` instance for use in tests.
|
||||
*/
|
||||
export function getTestActionsEnv(): ActionsEnv {
|
||||
return {
|
||||
getOptionalInput: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for environment variables typically set in an Actions
|
||||
* environment. Tests can override individual variables by passing them in the
|
||||
|
||||
@@ -38,7 +38,7 @@ const stubCodeql = createStubCodeQL({
|
||||
async getVersion() {
|
||||
return makeVersionInfo("2.10.3");
|
||||
},
|
||||
async betterResolveLanguages() {
|
||||
async resolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
[BuiltInLanguage.javascript]: [
|
||||
|
||||
+1
-1
@@ -280,7 +280,7 @@ export async function getLanguagesSupportingCaching(
|
||||
logger: Logger,
|
||||
): Promise<Language[]> {
|
||||
const result: Language[] = [];
|
||||
const resolveResult = await codeql.betterResolveLanguages();
|
||||
const resolveResult = await codeql.resolveLanguages();
|
||||
outer: for (const lang of languages) {
|
||||
const extractorsForLanguage = resolveResult.extractors[lang];
|
||||
if (extractorsForLanguage === undefined) {
|
||||
|
||||
@@ -383,7 +383,7 @@ async function testLanguageAliases(
|
||||
process.env.GITHUB_JOB = "test";
|
||||
|
||||
const codeql = await getCodeQLForTesting();
|
||||
sinon.stub(codeql, "betterResolveLanguages").resolves({
|
||||
sinon.stub(codeql, "resolveLanguages").resolves({
|
||||
aliases:
|
||||
aliases !== undefined
|
||||
? // Remap from languageName -> aliases to alias -> languageName
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ async function groupLanguagesByExtractor(
|
||||
languages: string[],
|
||||
codeql: CodeQL,
|
||||
): Promise<{ [extractorName: string]: string[] } | undefined> {
|
||||
const resolveResult = await codeql.betterResolveLanguages();
|
||||
const resolveResult = await codeql.resolveLanguages();
|
||||
if (!resolveResult.aliases) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user