Compare commits

..
Author SHA1 Message Date
Michael B. Gale 7a6e2f798f Merge pull request #4240 from github/mergeback/v4.39.0-to-main-6f2ac81f
Mergeback v4.39.0 refs/heads/releases/v4 into main
2026-10-15 11:25:10 +00:00
github-actions[bot] 46d3daa879 Rebuild 2026-10-15 10:57:15 +00:00
github-actions[bot] 2edace40f1 Update changelog and version after v4.39.0 2026-10-15 10:57:06 +00:00
Michael B. Gale 6f2ac81f5c Merge pull request #4239 from github/update-v4.39.0-d63383a73
Merge main into releases/v4
2026-10-15 11:55:44 +01:00
Michael B. Gale eb1c428e6e Trigger workflows 2026-10-15 11:39:58 +01:00
github-actions[bot] 444333376e Update changelog for v4.39.0 2026-10-15 10:34:02 +00:00
Michael B. Gale e5b217aeb5 Merge pull request #4238 from github/dependabot/github_actions/dot-github/workflows/actions-minor-c673fd7efc
Bump the actions-minor group across 1 directory with 3 updates
2026-10-15 10:26:50 +00:00
Michael B. Gale d63383a731 Merge pull request #4233 from github/mbg/start-proxy/report-version
Report release version the proxy is taken from in `start-proxy` telemetry
2026-10-15 10:15:24 +00:00
Michael B. Gale 8dec0b9f88 Rename path to result in tests 2026-10-15 11:03:10 +01:00
Michael B. Gale 3ec43abae3 Merge branch 'main' into dependabot/github_actions/dot-github/workflows/actions-minor-c673fd7efc 2026-10-15 11:01:21 +01:00
Michael B. Gale 4eabea6b5e Merge remote-tracking branch 'origin/main' into mbg/start-proxy/report-version 2026-10-15 10:31:39 +01:00
Michael B. Gale 57938b8568 Merge pull request #4201 from github/mario-campos/automate-changenotes-assemble
Incorporate change-notes into CHANGELOG.md at release
2026-10-15 09:20:41 +00:00
github-actions[bot] 3afe9c01db Rebuild 2026-10-14 21:26:52 +00:00
dependabot[bot] 8844fb33e0 Bump the actions-minor group across 1 directory with 3 updates
Bumps the actions-minor group with 3 updates in the /.github/workflows directory: [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/setup-node](https://github.com/actions/setup-node) and [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-14 21:24:28 +00:00
Michael B. Gale ee07b503f4 Merge pull request #4236 from github/mbg/add-missing-changenotes
Add missing change notes for recent PRs
2026-10-14 15:25:08 +00:00
Michael B. Gale a897ed5c2e Add change note for #4206 2026-10-14 16:05:40 +01:00
Michael B. Gale c8bd0195fb Add change note for #4198 2026-10-14 16:03:37 +01:00
Michael B. Gale c98271eca2 Add change note for #4148 2026-10-14 16:01:09 +01:00
Michael B. Gale 2a3e7563aa Merge pull request #4148 from github/mbg/improve-checkout-path
Validate `checkout_path` input in `analyze` action
2026-10-14 14:52:50 +00:00
Michael B. Gale f460dc7f72 Merge pull request #4206 from github/mbg/set-restore-keys
Include primary cache keys as restore key
2026-10-14 14:45:03 +00:00
Michael B. Gale f5b8e09db2 Merge pull request #4198 from github/mbg/start-proxy/deprecate-fallback
Use `setup-codeql` logic for finding release assets in `start-proxy`
2026-10-14 14:39:32 +00:00
Michael B. Gale 28d5dd9818 Revert "Allow logging functions to accept string[] directly"
This reverts commit 909fda1468.
2026-10-14 15:07:40 +01:00
Michael B. Gale 962aaad938 Rename proxyBin to proxyDir 2026-10-14 13:58:37 +01:00
Michael B. Gale 71ad3a794a Fix typo in log message 2026-10-14 13:57:43 +01:00
Michael B. Gale 9da96258e6 Merge remote-tracking branch 'origin/main' into mbg/start-proxy/deprecate-fallback 2026-10-14 13:56:57 +01:00
Henry Mercer 2b07ff4ed2 Merge pull request #4208 from github/dependabot/npm_and_yarn/octokit/plugin-paginate-rest-16.0.0
Bump @octokit/plugin-paginate-rest from 15.0.0 to 16.0.0
2026-10-14 10:22:18 +00:00
Henry Mercer 85944e0b34 Merge pull request #4234 from github/dependabot/npm_and_yarn/npm-minor-2d4d2c9463
Bump the npm-minor group across 1 directory with 7 updates
2026-10-14 10:21:43 +00:00
Michael B. Gale 4f747696ca Merge branch 'main' into mbg/improve-checkout-path 2026-10-14 10:57:57 +01:00
github-actions[bot] 25fe42ad78 Rebuild 2026-10-14 09:51:41 +00:00
dependabot[bot] efdb69c1d7 Bump the npm-minor group across 1 directory with 7 updates
Bumps the npm-minor group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@actions/cache](https://github.com/actions/toolkit/tree/HEAD/packages/cache) | `5.2.0` | `5.3.0` |
| [follow-redirects](https://github.com/follow-redirects/follow-redirects) | `1.16.0` | `1.16.1` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `5.4.2` | `5.4.3` |
| [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) | `65.0.1` | `65.2.0` |
| [globals](https://github.com/sindresorhus/globals) | `17.12.0` | `17.13.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.70.1` | `8.71.1` |
| [mdast-util-from-markdown](https://github.com/syntax-tree/mdast-util-from-markdown) | `2.0.3` | `2.1.0` |



Updates `@actions/cache` from 5.2.0 to 5.3.0
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/cache/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/cache)

Updates `follow-redirects` from 1.16.0 to 1.16.1
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.16.0...v1.16.1)

Updates `js-yaml` from 5.4.2 to 5.4.3
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.4.2...5.4.3)

Updates `eslint-plugin-jsdoc` from 65.0.1 to 65.2.0
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v65.0.1...v65.2.0)

Updates `globals` from 17.12.0 to 17.13.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.12.0...v17.13.0)

Updates `typescript-eslint` from 8.70.1 to 8.71.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.1/packages/typescript-eslint)

Updates `mdast-util-from-markdown` from 2.0.3 to 2.1.0
- [Release notes](https://github.com/syntax-tree/mdast-util-from-markdown/releases)
- [Commits](https://github.com/syntax-tree/mdast-util-from-markdown/compare/2.0.3...2.1.0)

---
updated-dependencies:
- dependency-name: "@actions/cache"
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor
- dependency-name: follow-redirects
  dependency-version: 1.16.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor
- dependency-name: js-yaml
  dependency-version: 5.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor
- dependency-name: eslint-plugin-jsdoc
  dependency-version: 65.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor
- dependency-name: globals
  dependency-version: 17.13.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor
- dependency-name: typescript-eslint
  dependency-version: 8.71.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor
- dependency-name: mdast-util-from-markdown
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-14 09:49:53 +00:00
Michael B. Gale a5e9aebc25 Merge branch 'main' into dependabot/npm_and_yarn/octokit/plugin-paginate-rest-16.0.0 2026-10-14 10:45:53 +01:00
Michael B. Gale 15800587e3 Add unit test for sendSuccessStatusReport 2026-10-14 10:31:29 +01:00
Michael B. Gale 6aee1f8ef9 Sort registry_types in status report 2026-10-14 10:30:14 +01:00
Michael B. Gale a7700b5a50 Merge branch 'main' into mario-campos/automate-changenotes-assemble 2026-10-14 10:07:46 +01:00
Michael B. Gale 43db2c064c Include proxy version in telemetry 2026-10-13 19:12:13 +01:00
Michael B. Gale 6308e366c9 Return release version from getProxyBinaryPath 2026-10-13 19:06:14 +01:00
Michael B. Gale aa80ca0686 Move start-proxy status report handling into its own file 2026-10-13 18:59:11 +01:00
Michael B. Gale b67b5abe29 Use parseRepositoryNwo in getCodeQLAssetDownloadURL 2026-10-13 12:13:59 +01:00
Michael B. Gale eb94de7c5c Merge pull request #4225 from github/mbg/reduce-config-requirements
Store and restore "failed" configuration if `init` step aborts
2026-10-13 09:34:24 +00:00
Michael B. Gale 9645acc2ff Revert config === undefined condition 2026-10-12 22:54:47 +01:00
Michael B. Gale 6a9f7e7fc3 Log if returning early from recordOverlayStatus due to no languages 2026-10-12 22:50:11 +01:00
Michael B. Gale 67f44bc916 Use Logger for printDebugLogs and log when returning early 2026-10-12 22:48:21 +01:00
Michael B. Gale 26c6f91e6b Throw if aborted config functions are called incorrectly 2026-10-12 22:42:38 +01:00
Michael B. Gale e3aeabd8f1 Fix example in comment 2026-10-12 22:40:25 +01:00
Michael B. Gale be78e02f2d Fix comment 2026-10-12 22:39:34 +01:00
Michael B. Gale bed4cc0475 Add log message for !config.dbLocation case 2026-10-12 22:39:08 +01:00
Michael B. Gale 5aa103d050 Add InstanceURL and use in DownloadSource 2026-10-12 15:52:22 +01:00
Michael B. Gale 8fed29eed7 Rename apiURL to instanceURL 2026-10-12 15:47:52 +01:00
Michael B. Gale a1794a03ff Fix mockGetReleaseByTag usage 2026-10-12 15:45:21 +01:00
Michael B. Gale ac44fd0901 Remove duplicated assertions 2026-10-12 15:41:25 +01:00
Michael B. Gale dd3898ae58 Await toolcache.downloadTool 2026-10-12 15:40:20 +01:00
Michael B. Gale 3d7016f514 Log more in getProxyBinaryPath 2026-10-12 15:39:38 +01:00
Michael B. Gale 06bd6e2f9d DotCom => Dotcom 2026-10-12 15:31:11 +01:00
Michael B. Gale fdc069b4ec Use DownloadSource for potentialDownloadSources 2026-10-12 15:29:50 +01:00
Michael B. Gale e93db40cce Use "asset" in comment 2026-10-12 15:28:55 +01:00
Michael B. Gale 1a92e13c5e Use GITHUB_DOTCOM_URL in getDefaultDotComDownloadURL 2026-10-12 15:28:23 +01:00
Michael B. Gale ad8ebce293 Add and use RepositoryNwoStr type 2026-10-12 15:27:49 +01:00
Michael B. Gale c1b620cc0e Allow initializeEnvironment to accept Env 2026-10-12 15:16:08 +01:00
Michael B. Gale 265d882341 Merge branch 'main' into mbg/start-proxy/deprecate-fallback 2026-10-12 14:52:24 +01:00
Michael B. Gale 099333712f Merge branch 'main' into mbg/set-restore-keys 2026-10-12 14:50:03 +01:00
Michael B. Gale ac8654a8f2 Add printDebugLogs tests 2026-10-12 13:08:08 +01:00
Michael B. Gale ecfc96e0a9 Add getDisplayActionName tests 2026-10-12 13:08:01 +01:00
Michael B. Gale 409be01136 Replace deprecated functions in init-action.post.ts 2026-10-12 13:07:55 +01:00
Michael B. Gale f4a15a13fd Try to load failed config if normal config is unavailable
Adapt relevant functions by narrowing their types or skipping steps that would otherwise have been skipped as well
2026-10-12 13:07:48 +01:00
Michael B. Gale 4fdbb71b54 Use runInActions for init-post 2026-10-12 13:07:38 +01:00
Michael B. Gale 5136039854 Store partial configuration when init is aborted 2026-10-12 13:07:36 +01:00
Michael B. Gale 19445ddfed Reduce Config requirements for tryUploadSarifIfRunFailed 2026-10-12 13:07:13 +01:00
Michael B. Gale 317f264e1f Change diagnosticsExport to not require full Config 2026-10-12 13:07:06 +01:00
Michael B. Gale 309f6af89b Change getGeneratedCodeScanningConfigPath to not require full Config 2026-10-12 13:06:59 +01:00
Michael B. Gale cf8f7b9787 Add ActionConfig type 2026-10-12 13:06:43 +01:00
Henry Mercer 0db2f2b4a5 Merge pull request #4205 from github/henrymercer/require-codeql-2-20-7
Bump minimum CodeQL CLI version to 2.20.7
2026-10-12 10:04:43 +00:00
Henry MercerandCopilot App ae96eed5bb Merge remote-tracking branch 'origin/main' into henrymercer/require-codeql-2-20-7
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-12 10:11:02 +01:00
Mario Campos e4e9b269fe Merge pull request #4211 from github/mario-campos/document-changenotes
Document change-notes process
2026-10-09 23:05:41 +00:00
Michael B. Gale 6e7fe2e990 Merge pull request #4220 from github/mbg/fix-action-state-type-inference
Fix type inference for `ActionState`
2026-10-09 22:31:18 +00:00
Mario Campos fa56eef12f Delete unnecessary filename/frontmatter validation instructions 2026-10-09 16:48:00 -05:00
Michael B. Gale 71f2680f85 Merge pull request #4209 from github/dependabot/npm_and_yarn/eslint-plugin-jsdoc-65.0.1
Bump eslint-plugin-jsdoc from 64.5.4 to 65.0.1
2026-10-09 17:59:37 +00:00
Michael B. Gale 3b399fec34 Merge branch 'main' into dependabot/npm_and_yarn/eslint-plugin-jsdoc-65.0.1 2026-10-09 18:47:49 +01:00
Mario Campos 5532d8e11e Merge pull request #4218 from github/mario-campos/loosen-changenote-validation
Do not validate change-note file names or frontmatter
2026-10-09 17:45:19 +00:00
Michael B. Gale 68f5b579fa Merge pull request #4212 from github/mbg/tests/fix-export-variable-2
Do not use `core.exportVariable` in unit tests (2nd attempt)
2026-10-09 17:38:52 +00:00
Mario Campos 45cda5b577 Delete unused path import 2026-10-09 08:57:09 -05:00
Michael B. Gale e57c0e65dd Fix type inference for ActionState 2026-10-09 14:21:47 +01:00
Mario Campos 1ced68ed2f Do not validate change-note file names or frontmatter
Presently, the strict filename or frontmatter are not being used; so we're validating them unnecessarily. So, for now, remove these checks until we decide to either do something with them, or delete the code entirely.
2026-10-08 15:11:43 -05:00
Mario CamposandMichael B. Gale 13b1321685 Refine misc. wording of unreleased-change-notes/README.md
Co-authored-by: Michael B. Gale <mbg@github.com>
2026-10-08 15:01:05 -05:00
Mario Campos cc826bb8e3 Delete unnecessary guidance for change-note body 2026-10-08 14:57:03 -05:00
Mario Campos 40d76bc727 Use actual CHANGELOG.md entries for example change-note 2026-10-08 14:55:23 -05:00
Michael B. Gale bd97dda6bf Add and use __CODEQL_ACTION_TEST_ENV__ in tests 2026-10-08 20:23:37 +01:00
Michael B. Gale d6196d8703 Rename exportVariable wrapper to exportEnvVar 2026-10-08 20:06:51 +01:00
Mario Campos 3acc10a2ce Exclude README.md from change-note validation and assembly 2026-10-08 12:53:37 -05:00
Mario Campos 2a935ca93d Move change-note documentation to unreleased-change-notes/README.md 2026-10-08 12:48:24 -05:00
Mads Navntoft 5aa5056e2d Merge pull request #4215 from github/mergeback/v4.38.3-to-main-24c54180
Mergeback v4.38.3 refs/heads/releases/v4 into main
2026-10-08 13:58:42 +00:00
Henry Mercer 768784f193 Merge branch 'main' into henrymercer/require-codeql-2-20-7 2026-10-08 14:29:21 +01:00
github-actions[bot] ea60dc6408 Rebuild 2026-10-08 13:01:10 +00:00
github-actions[bot] ce3f825b1d Update changelog and version after v4.38.3 2026-10-08 13:01:02 +00:00
Mads Navntoft 24c54180a6 Merge pull request #4214 from github/update-v4.38.3-e0b52dbdc
Merge main into releases/v4
2026-10-08 14:59:30 +02:00
Henry Mercer 69e87c56c1 Add changelog note for #4184 2026-10-08 13:42:32 +01:00
github-actions[bot] 23b682830b Update changelog for v4.38.3 2026-10-08 12:02:08 +00:00
Mario Campos 38fee79b02 Specify allowed characters in change-note filename 2026-10-07 15:21:38 -05:00
Michael B. Gale ebd711ff66 Deprecate isInTestMode in favour of a more clearly named method 2026-10-07 20:12:01 +01:00
Michael B. Gale 75ea4cb2e4 Add NODE_ENV as safe environment variable 2026-10-07 20:12:01 +01:00
Michael B. Gale 59ff83ecef Add basic eslint enforcement 2026-10-07 20:12:01 +01:00
Michael B. Gale 1728f0c676 Add wrapper around core.exportVariable 2026-10-07 20:12:00 +01:00
Michael B. Gale 63c5299790 Move isInTestMode to environment.ts 2026-10-07 20:12:00 +01:00
Henry Mercer e0b52dbdc5 Merge pull request #4210 from github/henrymercer/pin-python-for-older-clis-in-ci
Pin Python 3.13.15 for older CLI versions in the multi-language check
2026-10-07 18:12:41 +00:00
Mario Campos 39f1b3fa8d Document change-notes process 2026-10-07 13:11:34 -05:00
Henry MercerandCopilot App 6b99698c8c Pin Python 3.13.15 for older CLI versions in multi-language check
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 18:58:17 +01:00
dependabot[bot] d5b1e793ce Bump eslint-plugin-jsdoc from 64.5.4 to 65.0.1
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 64.5.4 to 65.0.1.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v64.5.4...v65.0.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-version: 65.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-07 17:55:31 +00:00
dependabot[bot] a36029cea2 Bump @octokit/plugin-paginate-rest from 15.0.0 to 16.0.0
Bumps [@octokit/plugin-paginate-rest](https://github.com/octokit/plugin-paginate-rest.js) from 15.0.0 to 16.0.0.
- [Release notes](https://github.com/octokit/plugin-paginate-rest.js/releases)
- [Commits](https://github.com/octokit/plugin-paginate-rest.js/compare/v15.0.0...v16.0.0)

---
updated-dependencies:
- dependency-name: "@octokit/plugin-paginate-rest"
  dependency-version: 16.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-07 17:55:12 +00:00
Michael B. Gale 4f73d1add8 Include primary cache keys as restore key 2026-10-07 18:15:22 +01:00
Henry MercerandCopilot App 5d90bea8a6 Add PR number to changelog entry and list it first
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:44:12 +01:00
Henry MercerandCopilot App 8e7104f197 Clarify the oldest-version comment in the Rust PR check
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:41:20 +01:00
Henry MercerandCopilot App 0b5c1fb069 Remove redundant CLI version requirement from CppDependencyInstallation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:41:15 +01:00
Henry MercerandCopilot App a8c7110b26 Remove unreachable minimum version check for Rust analysis
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:41:09 +01:00
Henry MercerandCopilot App 3c1b1b65a3 Remove CodeQL version check from debug artifact upload
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:41:03 +01:00
Henry MercerandCopilot App 10e415aae1 Remove Kotlin version limit workaround for CodeQL CLI 2.20.3
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:40:58 +01:00
Henry MercerandCopilot App b97fbb3711 Remove CODEQL_PROXY_* workaround for CLIs older than 2.20.7
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:40:52 +01:00
Henry MercerandCopilot App 17e7897d6c Add CodeQL 2.25.6 to the PR check matrix
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:40:49 +01:00
Henry MercerandCopilot App 74aeac99e7 Stop testing CodeQL versions older than 2.20.7 in PR checks
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:40:46 +01:00
Henry MercerandCopilot App 05d28f2f71 Bump version to 4.39.0
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:40:38 +01:00
Henry MercerandCopilot App 772ffcd999 Bump minimum CodeQL CLI version to 2.20.7
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:40:35 +01:00
Mads Navntoft 63d3d632fc Merge pull request #4203 from github/update-bundle/codeql-bundle-v2.27.2
Update default bundle to 2.27.2
2026-10-07 10:00:42 +00:00
github-actions[bot] 7377a10091 Add changelog note 2026-10-07 09:39:57 +00:00
github-actions[bot] c1a30cda11 Update default bundle to codeql-bundle-v2.27.2 2026-10-07 09:39:50 +00:00
Mario Campos aae22cc693 Use config vars over hardcoded literals 2026-10-06 14:50:28 -05:00
Mario Campos 0f6901c9a8 Commit change-note deletions 2026-10-06 14:43:34 -05:00
Mario Campos badda692f4 Trim whitespace of change-notes before insertion 2026-10-06 14:43:33 -05:00
Mario Campos fb4048e3e7 Transfer change-notes to CHANGELOG.md via update-release-branch.ts 2026-10-06 14:43:33 -05:00
Mario Campos 9a9f454be5 Hoist core of changenotes.mts assemble into own function 2026-10-06 14:39:02 -05:00
Michael B. Gale f9a9962aa3 Fix onceWith not asserting the once part 2026-10-06 17:26:09 +01:00
Michael B. Gale 133ffebe62 Use getCodeQLAssetDownloadURL for start-proxy 2026-10-06 17:08:44 +01:00
Michael B. Gale de99f0ee4b Add getDefaultDotComDownloadURL 2026-10-06 17:07:49 +01:00
Michael B. Gale a4f9f6fc12 Type the assetKind parameter 2026-10-06 17:07:48 +01:00
Michael B. Gale 35f12ded42 Add utility for assertions on sinon stubs with better output by default 2026-10-06 17:07:48 +01:00
Michael B. Gale da07d40a0f Refactor getCodeQLBundleDownloadURL out of setup-codeql 2026-10-06 17:07:48 +01:00
Michael B. Gale 3f57a00554 Use ActionState for getCodeQLActionRepository 2026-10-06 17:07:48 +01:00
Michael B. Gale cb7acf5396 Refactor getCodeQLActionRepository out of setup-codeql 2026-10-06 17:07:47 +01:00
Michael B. Gale 909fda1468 Allow logging functions to accept string[] directly 2026-10-06 17:07:47 +01:00
Michael B. Gale c2215db9ae Update git-utils tests 2026-09-28 10:51:26 +01:00
Michael B. Gale 5769ffc476 Merge remote-tracking branch 'origin/main' into mbg/improve-checkout-path 2026-09-28 10:49:13 +01:00
Michael B. Gale 72509a81ba Consistently use resolved path 2026-09-23 12:57:16 +01:00
Michael B. Gale a876e23075 Add tests for determineCheckoutPath 2026-09-22 16:25:47 +01:00
Michael B. Gale 6fb4a88ed6 Normalise the results of getGitRoot 2026-09-22 16:25:47 +01:00
Michael B. Gale f5716fadf4 Allow getCheckoutPathInputOrThrow to get root from config 2026-09-22 16:25:46 +01:00
Michael B. Gale 54a43494ca Propagate checkoutPath to getRef 2026-09-22 16:25:46 +01:00
Michael B. Gale ba615b5d9d Verify persisted repository root path in analyze action 2026-09-22 16:25:46 +01:00
Michael B. Gale 5dab0ccf51 Persist repository root from init action in CodeQL Action state 2026-09-22 16:25:46 +01:00
Michael B. Gale 99fe83948e Refactor: Add determineCheckoutPath function for analyze action 2026-09-22 16:25:46 +01:00
103 changed files with 3841 additions and 2660 deletions

No files matched your search

+2 -2
View File
@@ -87,7 +87,7 @@ jobs:
post-processed-sarif-path: '${{ runner.temp }}/post-processed'
- name: Upload SARIF files
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: |
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
@@ -95,7 +95,7 @@ jobs:
retention-days: 7
- name: Upload post-processed SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: |
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
output: '${{ runner.temp }}/results'
upload-database: false
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: '${{ runner.temp }}/results/javascript.sarif'
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 20.x
cache: npm
+1 -1
View File
@@ -76,7 +76,7 @@ jobs:
output: '${{ runner.temp }}/results'
upload-database: false
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: '${{ runner.temp }}/results/javascript.sarif'
+1 -1
View File
@@ -97,7 +97,7 @@ jobs:
with:
output: '${{ runner.temp }}/results'
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: '${{ runner.temp }}/results/javascript.sarif'
+2 -2
View File
@@ -44,8 +44,6 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: stable-v2.19.4
- os: ubuntu-latest
version: stable-v2.20.7
- os: ubuntu-latest
@@ -56,6 +54,8 @@ jobs:
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest
version: stable-v2.25.6
- os: ubuntu-latest
version: default
- os: ubuntu-latest
+2 -2
View File
@@ -44,8 +44,6 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: stable-v2.19.4
- os: ubuntu-latest
version: stable-v2.20.7
- os: ubuntu-latest
@@ -56,6 +54,8 @@ jobs:
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest
version: stable-v2.25.6
- os: ubuntu-latest
version: default
- os: ubuntu-latest
+2 -2
View File
@@ -44,8 +44,6 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: stable-v2.19.4
- os: ubuntu-latest
version: stable-v2.20.7
- os: ubuntu-latest
@@ -56,6 +54,8 @@ jobs:
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest
version: stable-v2.25.6
- os: ubuntu-latest
version: default
- os: ubuntu-latest
+1 -1
View File
@@ -62,7 +62,7 @@ jobs:
with:
output: '${{ runner.temp }}/results'
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: '${{ runner.temp }}/results/javascript.sarif'
+8 -7
View File
@@ -54,10 +54,6 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: stable-v2.19.4
- os: macos-15-xlarge
version: stable-v2.19.4
- os: ubuntu-latest
version: stable-v2.20.7
- os: macos-15-xlarge
@@ -78,6 +74,10 @@ jobs:
version: stable-v2.24.3
- os: macos-latest-xlarge
version: stable-v2.24.3
- os: ubuntu-latest
version: stable-v2.25.6
- os: macos-latest-xlarge
version: stable-v2.25.6
- os: ubuntu-latest
version: default
- os: macos-latest-xlarge
@@ -116,13 +116,14 @@ jobs:
version: ${{ matrix.version }}
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Install Python 3.13 for older CLI versions
# We need Python 3.13 for older CLI versions because they are not compatible with Python 3.14 or newer.
- name: Install Python 3.13.15 for older CLI versions
# Older CLI versions don't work with Python 3.13.16 or newer because their Python extractor
# imports `importlib._bootstrap._ERR_MSG`, which those Python versions no longer define.
# See https://github.com/github/codeql-action/pull/3212
if: matrix.version != 'nightly-latest' && matrix.version != 'linked'
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.13'
python-version: '3.13.15'
- name: Use Xcode 16
# Only the older CodeQL CLI versions need Xcode 16, and these run on macOS 15.
@@ -80,7 +80,7 @@ jobs:
go-version: ${{ inputs.go-version || '>=1.21.0' }}
cache: false
- name: Install Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 20.x
cache: npm
+1 -1
View File
@@ -80,7 +80,7 @@ jobs:
go-version: ${{ inputs.go-version || '>=1.21.0' }}
cache: false
- name: Install Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 20.x
cache: npm
+1 -1
View File
@@ -80,7 +80,7 @@ jobs:
go-version: ${{ inputs.go-version || '>=1.21.0' }}
cache: false
- name: Install Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 20.x
cache: npm
+1 -1
View File
@@ -80,7 +80,7 @@ jobs:
go-version: ${{ inputs.go-version || '>=1.21.0' }}
cache: false
- name: Install Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 20.x
cache: npm
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
matrix:
include:
- os: ubuntu-latest
version: stable-v2.19.4
version: stable-v2.20.7
- os: ubuntu-latest
version: stable-v2.22.1
- os: ubuntu-latest
@@ -57,7 +57,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: 'npm'
@@ -30,7 +30,7 @@ jobs:
fail-fast: false
matrix:
version:
- stable-v2.20.3
- stable-v2.20.7
- default
- linked
- nightly-latest
@@ -89,7 +89,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
- name: Check expected artifacts exist
run: |
LANGUAGES="cpp csharp go java javascript python"
+3 -3
View File
@@ -29,7 +29,7 @@ jobs:
fail-fast: false
matrix:
version:
- stable-v2.20.3
- stable-v2.20.7
- default
- linked
- nightly-latest
@@ -82,10 +82,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
- name: Check expected artifacts exist
run: |
VERSIONS="stable-v2.20.3 default linked nightly-latest"
VERSIONS="stable-v2.20.7 default linked nightly-latest"
LANGUAGES="cpp csharp go java javascript python"
for version in $VERSIONS; do
pushd "./my-debug-artifacts-${version//./}"
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # ensure we have all tags and can push commits
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: 'npm'
+4 -4
View File
@@ -42,7 +42,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
@@ -110,7 +110,7 @@ jobs:
fi
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: "npm"
@@ -175,7 +175,7 @@ jobs:
- name: Upload repo size comment
if: steps.fetch-base.outcome == 'success'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: repo-size-comment
path: ${{ runner.temp }}/repo-size/
@@ -223,7 +223,7 @@ jobs:
steps:
- name: Download repo size comment
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: repo-size-comment
path: repo-size-comment
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: npm
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
ref: ${{ env.HEAD_REF }}
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: 'npm'
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
git config --global user.name "github-actions[bot]"
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: 'npm'
@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: 'npm'
+13
View File
@@ -4,7 +4,20 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
## [UNRELEASED]
No user facing changes.
## 4.39.0 - 15 Oct 2026
- _Breaking change_: Bump the minimum required CodeQL bundle version to 2.20.7. [#4205](https://github.com/github/codeql-action/pull/4205)
- The `codeql-action/analyze` step now validates that a correct argument for the `checkout_path` input has been provided. The `checkout_path` input must be set to the path where the repository that is being analyzed is checked out at on the runner, if that path is something other than the workspace root. If the path does not point to a repository checkout or is different from the path that was used by `codeql-action/init`, then the CodeQL Action now produces a warning. [#4148](https://github.com/github/codeql-action/pull/4148)
- Resolved an issue with third-party caching implementations that prevented caches from being restored correctly. [#4206](https://github.com/github/codeql-action/pull/4206)
- The `codeql-action/start-proxy` step that is used in Code Scanning Default Setup now finds CodeQL releases using the same logic as `codeql-action/init`. This primarily benefits customers who are using GitHub instances other than `github.com` where the `codeql-action/start-proxy` step may previously have been unable to locate recent releases and ended up using outdated versions of the private registry authentication proxy as a result. [#4198](https://github.com/github/codeql-action/pull/4198)
## 4.38.3 - 08 Oct 2026
- _Upcoming breaking change_: CodeQL version 2.21.2 and earlier were discontinued on 24 September 2026 alongside GitHub Enterprise Server 3.17, and will be unsupported by the next minor release of the CodeQL Action. Added a deprecation warning for customers using these versions of CodeQL. [#4188](https://github.com/github/codeql-action/pull/4188)
- Update default CodeQL bundle version to [2.27.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.2). [#4203](https://github.com/github/codeql-action/pull/4203)
- Fixed a bug where the decision of whether to use a per-language bundle did not account for custom configurations that reference queries outside of compiled CodeQL packs. This issue was caught during internal testing and did not affect any customer repositories. We will resume the roll out of per-language bundles in the coming weeks. [#4184](https://github.com/github/codeql-action/pull/4184)
## 4.38.2 - 24 Sept 2026
+1
View File
@@ -53,6 +53,7 @@ Here are a few things you can do that will increase the likelihood of your pull
- Write tests.
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
- For user-facing changes, add a change-note file. See [unreleased-change-notes/README.md](unreleased-change-notes/README.md).
## Releasing (write access required)
+1
View File
@@ -1,3 +1,4 @@
import pkg from "./package.json" with { type: "json" };
globalThis.__CODEQL_ACTION_VERSION__ = pkg.version;
globalThis.__CODEQL_ACTION_TEST_ENV__ = "unit-test";
+2 -1
View File
@@ -78,7 +78,7 @@ const UPLOAD_LIB_SRC = "./src/upload-lib";
*
* The virtual module additionally re-exports `upload-lib` under the `uploadLib` namespace so that
* external consumers can access it via the small `lib/upload-lib.js` stub emitted below.
*
*
* A tiny stub file is emitted for each Action entrypoint, and one for `upload-lib`. Each stub
* imports the shared bundle and calls/re-exports from the respective entry point.
*
@@ -212,6 +212,7 @@ const context = await esbuild.context({
target: ["node20"],
define: {
__CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version),
__CODEQL_ACTION_TEST_ENV__: JSON.stringify(""),
},
metafile: true,
});
+20
View File
@@ -140,6 +140,17 @@ export default [
"no-async-foreach/no-async-foreach": "error",
"no-sequences": "error",
"no-shadow": "off",
// A basic check that we don't use `exportVariable` from `@actions/core`.
"no-restricted-syntax": [
"error",
{
selector: "MemberExpression[property.name='exportVariable']",
message:
"Use the `export` method of an `Env` instance or `exportEnvVar` from `environment.ts` instead.",
},
],
// This is overly restrictive with unsetting `EnvVar`s
"@typescript-eslint/no-dynamic-delete": "off",
"@typescript-eslint/no-shadow": "error",
@@ -157,6 +168,15 @@ export default [
],
},
},
{
files: ["src/environment.ts"],
// We allow `exportVariable` from `@actions/core` to be used in this file
// since it defines the wrapper around it that other modules use.
rules: {
"no-restricted-syntax": "off",
},
},
{
files: ["**/*.ts", "**/*.js"],
+4 -4
View File
@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.27.1",
"cliVersion": "2.27.1",
"priorBundleVersion": "codeql-bundle-v2.27.0",
"priorCliVersion": "2.27.0"
"bundleVersion": "codeql-bundle-v2.27.2",
"cliVersion": "2.27.2",
"priorBundleVersion": "codeql-bundle-v2.27.1",
"priorCliVersion": "2.27.1"
}
+1602 -1450
View File
File diff suppressed because it is too large. Load diff
+99 -114
View File
@@ -1,12 +1,12 @@
{
"name": "codeql",
"version": "4.38.3",
"version": "4.39.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeql",
"version": "4.38.3",
"version": "4.39.1",
"license": "MIT",
"workspaces": [
"pr-checks"
@@ -14,7 +14,7 @@
"dependencies": {
"@actions/artifact": "^5.0.3",
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
"@actions/cache": "^5.2.0",
"@actions/cache": "^5.3.0",
"@actions/core": "^2.0.3",
"@actions/exec": "^2.0.0",
"@actions/github": "^8.0.1",
@@ -23,15 +23,14 @@
"@actions/io": "^2.0.0",
"@actions/tool-cache": "^3.0.1",
"@octokit/core": "^7.0.8",
"@octokit/plugin-paginate-rest": "^15.0.0",
"@octokit/plugin-paginate-rest": "^16.0.0",
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
"@octokit/plugin-retry": "^8.1.1",
"archiver": "^8.0.0",
"fast-deep-equal": "^3.1.3",
"follow-redirects": "^1.16.0",
"follow-redirects": "^1.16.1",
"get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6",
"js-yaml": "^5.4.2",
"js-yaml": "^5.4.3",
"jsonschema": "1.5.0",
"long": "^5.3.2",
"node-forge": "^1.4.0",
@@ -58,14 +57,14 @@
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-github": "^6.1.2",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jsdoc": "^64.5.4",
"eslint-plugin-jsdoc": "^65.2.0",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^13.0.6",
"globals": "^17.12.0",
"globals": "^17.13.0",
"nock": "^14.0.17",
"sinon": "^22.1.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.70.1"
"typescript-eslint": "^8.71.1"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -459,19 +458,19 @@
}
},
"node_modules/@actions/cache": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.2.0.tgz",
"integrity": "sha512-1R1Oc8cuDNCygsIP7gLiKLGCymOw/k5FkGQkXZFcLz6/RWyMImkfP0dZX6kjA9SRAmANcKNocI2XrsIaZ1it8w==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.3.0.tgz",
"integrity": "sha512-VqJBlQFt1lpp7op7o/Z0syWywX9cN8TxjFCPjHyGHmXCiczU5NII5ZdEpGpTelUnOGFG2bO7viUx9q3q7WR/Gg==",
"license": "MIT",
"dependencies": {
"@actions/core": "^2.0.0",
"@actions/core": "^2.0.3",
"@actions/exec": "^2.0.0",
"@actions/glob": "^0.5.1",
"@actions/http-client": "^3.0.2",
"@actions/io": "^2.0.0",
"@azure/abort-controller": "^1.1.0",
"@azure/core-rest-pipeline": "^1.22.0",
"@azure/storage-blob": "^12.29.1",
"@azure/core-rest-pipeline": "^1.25.0",
"@azure/storage-blob": "^12.34.0",
"@protobuf-ts/runtime-rpc": "^2.11.1",
"semver": "^6.3.1"
}
@@ -2205,12 +2204,12 @@
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-15.0.0.tgz",
"integrity": "sha512-lw9A9YL5s4VPj+VEx8uMxaxQm2YTgrPDkrLEuZuu18R8TK3oPcXF4K6t52nx6xn1RcogZC9i188sAr/4XYJaQQ==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-16.0.0.tgz",
"integrity": "sha512-7RtUeGZypAK4EA4LrjTCnsLALNOoUcYh5vOnCNXpdIQVTQCPh4OFu7324iPqAls3KGgVnZktKUnNKZCOKyHrlA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^17.0.0"
"@octokit/types": "^18.0.0"
},
"engines": {
"node": ">= 20"
@@ -2219,21 +2218,6 @@
"@octokit/core": ">=6"
}
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
"license": "MIT"
},
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^28.0.0"
}
},
"node_modules/@octokit/plugin-request-log": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
@@ -2718,17 +2702,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz",
"integrity": "sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz",
"integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.70.1",
"@typescript-eslint/type-utils": "8.70.1",
"@typescript-eslint/utils": "8.70.1",
"@typescript-eslint/visitor-keys": "8.70.1",
"@typescript-eslint/scope-manager": "8.71.1",
"@typescript-eslint/type-utils": "8.71.1",
"@typescript-eslint/utils": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -2741,7 +2725,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.70.1",
"@typescript-eslint/parser": "^8.71.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
@@ -2757,16 +2741,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.1.tgz",
"integrity": "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz",
"integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.70.1",
"@typescript-eslint/types": "8.70.1",
"@typescript-eslint/typescript-estree": "8.70.1",
"@typescript-eslint/visitor-keys": "8.70.1",
"@typescript-eslint/scope-manager": "8.71.1",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1",
"debug": "^4.4.3"
},
"engines": {
@@ -2800,14 +2784,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz",
"integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz",
"integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.70.1",
"@typescript-eslint/types": "^8.70.1",
"@typescript-eslint/tsconfig-utils": "^8.71.1",
"@typescript-eslint/types": "^8.71.1",
"debug": "^4.4.3"
},
"engines": {
@@ -2840,14 +2824,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz",
"integrity": "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz",
"integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.70.1",
"@typescript-eslint/visitor-keys": "8.70.1"
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2858,9 +2842,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz",
"integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz",
"integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2875,15 +2859,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz",
"integrity": "sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz",
"integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.70.1",
"@typescript-eslint/typescript-estree": "8.70.1",
"@typescript-eslint/utils": "8.70.1",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1",
"@typescript-eslint/utils": "8.71.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -2918,9 +2902,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.1.tgz",
"integrity": "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz",
"integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2932,16 +2916,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz",
"integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz",
"integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.70.1",
"@typescript-eslint/tsconfig-utils": "8.70.1",
"@typescript-eslint/types": "8.70.1",
"@typescript-eslint/visitor-keys": "8.70.1",
"@typescript-eslint/project-service": "8.71.1",
"@typescript-eslint/tsconfig-utils": "8.71.1",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/visitor-keys": "8.71.1",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -3017,16 +3001,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.1.tgz",
"integrity": "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz",
"integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.70.1",
"@typescript-eslint/types": "8.70.1",
"@typescript-eslint/typescript-estree": "8.70.1"
"@typescript-eslint/scope-manager": "8.71.1",
"@typescript-eslint/types": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3041,13 +3025,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz",
"integrity": "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz",
"integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.70.1",
"@typescript-eslint/types": "8.71.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -5381,15 +5365,15 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
"version": "64.5.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.5.4.tgz",
"integrity": "sha512-xfRhXPSSWT612muJ6XTvxuVJ8zYHv99qNgBqPVqF28aSqoy4s4XbSkrWXnYA2hl5ejC02yEpk6fqkj3BmJ6Xbg==",
"version": "65.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.2.0.tgz",
"integrity": "sha512-IMZaNwdDa5otUQYGWN1pzywZLsGKswitvFFNhcO8IOsBfaWpWIDXYuZh2sYhe9MNyKN9hz0YryOFaKdpbTP5Bw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@es-joy/jsdoccomment": "~0.98.0",
"@es-joy/resolve.exports": "1.2.0",
"@typescript-eslint/utils": "^8.70.0",
"@typescript-eslint/utils": "^8.70.1",
"are-docs-informative": "^0.1.1",
"comment-parser": "1.4.9",
"debug": "^4.4.3",
@@ -5817,6 +5801,7 @@
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
@@ -6007,9 +5992,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz",
"integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==",
"funding": [
{
"type": "individual",
@@ -6293,9 +6278,9 @@
}
},
"node_modules/globals": {
"version": "17.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz",
"integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==",
"version": "17.13.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz",
"integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7136,9 +7121,9 @@
}
},
"node_modules/js-yaml": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.2.tgz",
"integrity": "sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==",
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.3.tgz",
"integrity": "sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==",
"funding": [
{
"type": "github",
@@ -7494,9 +7479,9 @@
}
},
"node_modules/mdast-util-from-markdown": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
"integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz",
"integrity": "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
@@ -9996,16 +9981,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.70.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.70.1.tgz",
"integrity": "sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==",
"version": "8.71.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.1.tgz",
"integrity": "sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.70.1",
"@typescript-eslint/parser": "8.70.1",
"@typescript-eslint/typescript-estree": "8.70.1",
"@typescript-eslint/utils": "8.70.1"
"@typescript-eslint/eslint-plugin": "8.71.1",
"@typescript-eslint/parser": "8.71.1",
"@typescript-eslint/typescript-estree": "8.71.1",
"@typescript-eslint/utils": "8.71.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10500,10 +10485,10 @@
"@actions/core": "^2.0.3",
"@actions/github": "^8.0.1",
"@octokit/core": "^7.0.8",
"@octokit/plugin-paginate-rest": ">=15.0.0",
"@octokit/plugin-paginate-rest": ">=16.0.0",
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
"lite-matter": "^0.1.2",
"mdast-util-from-markdown": "^2.0.3",
"mdast-util-from-markdown": "^2.1.0",
"semver": "^7.8.5",
"yaml": "^2.9.1"
},
+8 -9
View File
@@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "4.38.3",
"version": "4.39.1",
"private": true,
"description": "CodeQL action",
"scripts": {
@@ -22,7 +22,7 @@
"dependencies": {
"@actions/artifact": "^5.0.3",
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
"@actions/cache": "^5.2.0",
"@actions/cache": "^5.3.0",
"@actions/core": "^2.0.3",
"@actions/exec": "^2.0.0",
"@actions/github": "^8.0.1",
@@ -31,15 +31,14 @@
"@actions/io": "^2.0.0",
"@actions/tool-cache": "^3.0.1",
"@octokit/core": "^7.0.8",
"@octokit/plugin-paginate-rest": "^15.0.0",
"@octokit/plugin-paginate-rest": "^16.0.0",
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
"@octokit/plugin-retry": "^8.1.1",
"archiver": "^8.0.0",
"fast-deep-equal": "^3.1.3",
"follow-redirects": "^1.16.0",
"follow-redirects": "^1.16.1",
"get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6",
"js-yaml": "^5.4.2",
"js-yaml": "^5.4.3",
"jsonschema": "1.5.0",
"long": "^5.3.2",
"node-forge": "^1.4.0",
@@ -66,14 +65,14 @@
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-github": "^6.1.2",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jsdoc": "^64.5.4",
"eslint-plugin-jsdoc": "^65.2.0",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^13.0.6",
"globals": "^17.12.0",
"globals": "^17.13.0",
"nock": "^14.0.17",
"sinon": "^22.1.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.70.1"
"typescript-eslint": "^8.71.1"
},
"overrides": {
"@actions/tool-cache": {
-20
View File
@@ -154,26 +154,6 @@ await describe("isValidChangenoteFile", async () => {
assert.equal(isValidChangenoteFile("non-existent-file.md"), false);
});
await it("rejects invalid filename", async () => {
await withTmpFile(
"fix-bug.md",
"---\ncategory: fix\n---\n- Fixed a bug\n",
(filePath) => {
assert.equal(isValidChangenoteFile(filePath), false);
},
);
});
await it("rejects missing frontmatter", async () => {
await withTmpFile(
"2026-01-01-fix-bug.md",
"- Fixed a bug\n",
(filePath) => {
assert.equal(isValidChangenoteFile(filePath), false);
},
);
});
await it("rejects invalid Markdown", async () => {
await withTmpFile(
"2026-01-01-fix-bug.md",
+1 -16
View File
@@ -1,5 +1,4 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { matter } from "lite-matter";
import type { List, ListItem } from "mdast";
@@ -95,21 +94,7 @@ export function isValidChangenoteFile(filename: string): boolean {
return false;
}
const { data: frontmatter, content } = matter(fileData);
if (!isValidChangenoteFilename(path.basename(filename))) {
isValid = false;
console.error(
`${filename}: invalid filename; must match pattern YYYY-MM-DD-id.md`,
);
}
if (!hasValidChangenoteCategory(frontmatter)) {
isValid = false;
const categories = Object.keys(VALID_CHANGE_NOTE_CATEGORIES).join(", ");
console.error(
`${filename}: invalid category; must be one of: ${categories}`,
);
}
const { content } = matter(fileData);
if (!isValidChangenoteContent(content)) {
isValid = false;
console.error(
+25 -16
View File
@@ -28,12 +28,12 @@ interface ChangenoteFile {
/**
* Returns the absolute file paths of all files in
* {@link CHANGENOTES_DIR} (except ".gitkeep").
* {@link CHANGENOTES_DIR} (except ".gitkeep" and "README.md").
* */
function listUnreleasedChangenoteDir(): string[] {
return fs
.readdirSync(CHANGENOTES_DIR)
.filter((name) => name !== ".gitkeep")
.filter((name) => ![".gitkeep", "README.md"].includes(name))
.map((name) => path.join(CHANGENOTES_DIR, name));
}
@@ -50,6 +50,28 @@ function getChangenotes(): ChangenoteFile[] {
});
}
/**
* Populates the '[UNRELEASED]' section of CHANGELOG.md with the contents
* of the changenotes in {@link CHANGENOTES_DIR} and then deletes those changenotes.
* @param contents The contents of CHANGELOG.md
* @returns The updated contents of CHANGELOG.md
*/
export function transferChangenotesToChangelog(contents: string): string {
const changenotes = getChangenotes();
const changenoteBodies = changenotes.map((c) => c.content.trim());
const changenotePaths = changenotes.map((c) => c.absolutePath);
const changelog = parseChangelog(contents);
addBodyLinesToUnreleasedSection(changelog, changenoteBodies);
const updatedChangelog = renderChangelog(changelog);
for (const p of changenotePaths) {
fs.unlinkSync(p);
}
return updatedChangelog;
}
const entryPoint = process.argv[1];
if (entryPoint && import.meta.url === pathToFileURL(entryPoint).href) {
try {
@@ -91,26 +113,13 @@ function usage(): ExitCode {
function assemble(): ExitCode {
try {
const changenotes = getChangenotes();
const changenoteBodies = changenotes.map((c) => c.content);
const changenotePaths = changenotes.map((c) => c.absolutePath);
withChangelog((contents) => {
const changelog = parseChangelog(contents);
addBodyLinesToUnreleasedSection(changelog, changenoteBodies);
return renderChangelog(changelog);
return transferChangenotesToChangelog(contents);
}, {});
// Delete changenotes only after successful processing.
for (const p of changenotePaths) {
fs.unlinkSync(p);
}
return ExitCode.Success;
} catch (e) {
console.error("Failed to assemble changenotes to 'CHANGELOG.md'", e);
}
return ExitCode.Failure;
}
+2 -2
View File
@@ -46,7 +46,7 @@ steps:
post-processed-sarif-path: "${{ runner.temp }}/post-processed"
- name: Upload SARIF files
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: |
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
@@ -54,7 +54,7 @@ steps:
retention-days: 7
- name: Upload post-processed SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: |
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
+1 -1
View File
@@ -14,7 +14,7 @@ steps:
output: "${{ runner.temp }}/results"
upload-database: false
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: "${{ runner.temp }}/results/javascript.sarif"
+1 -1
View File
@@ -27,7 +27,7 @@ steps:
output: "${{ runner.temp }}/results"
upload-database: false
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: "${{ runner.temp }}/results/javascript.sarif"
@@ -27,7 +27,7 @@ steps:
with:
output: "${{ runner.temp }}/results"
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: "${{ runner.temp }}/results/javascript.sarif"
+1 -1
View File
@@ -12,7 +12,7 @@ steps:
with:
output: "${{ runner.temp }}/results"
- name: Upload SARIF
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
path: "${{ runner.temp }}/results/javascript.sarif"
@@ -10,7 +10,6 @@ operatingSystems:
- os: macos
runner-image: macos-15-xlarge
codeql-versions:
- stable-v2.19.4
- stable-v2.20.7
- stable-v2.21.4
- stable-v2.22.4
@@ -20,13 +19,14 @@ env:
installGo: true
installDotNet: true
steps:
- name: Install Python 3.13 for older CLI versions
# We need Python 3.13 for older CLI versions because they are not compatible with Python 3.14 or newer.
- name: Install Python 3.13.15 for older CLI versions
# Older CLI versions don't work with Python 3.13.16 or newer because their Python extractor
# imports `importlib._bootstrap._ERR_MSG`, which those Python versions no longer define.
# See https://github.com/github/codeql-action/pull/3212
if: matrix.version != 'nightly-latest' && matrix.version != 'linked'
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
python-version: "3.13.15"
- name: Use Xcode 16
# Only the older CodeQL CLI versions need Xcode 16, and these run on macOS 15.
+2 -2
View File
@@ -1,8 +1,8 @@
name: "Rust analysis"
description: "Tests creation of a Rust database"
versions:
# experimental rust support introduced, requires action to set `CODEQL_ENABLE_EXPERIMENTAL_FEATURES`
- stable-v2.19.4
# oldest CodeQL version supported by the Action, where Rust support is experimental and requires the Action to set `CODEQL_ENABLE_EXPERIMENTAL_FEATURES`
- stable-v2.20.7
# first public preview version
- stable-v2.22.1
- linked
+2 -2
View File
@@ -6,10 +6,10 @@
"@actions/core": "^2.0.3",
"@actions/github": "^8.0.1",
"@octokit/core": "^7.0.8",
"@octokit/plugin-paginate-rest": ">=15.0.0",
"@octokit/plugin-paginate-rest": ">=16.0.0",
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
"lite-matter": "^0.1.2",
"mdast-util-from-markdown": "^2.0.3",
"mdast-util-from-markdown": "^2.1.0",
"semver": "^7.8.5",
"yaml": "^2.9.1"
},
+4 -4
View File
@@ -136,8 +136,6 @@ type LanguageSetups = Partial<Record<BuiltInLanguage, LanguageSetup>>;
// The default set of CodeQL Bundle versions to use for the PR checks.
const defaultTestVersions = [
// The oldest supported CodeQL version. If bumping, update `CODEQL_MINIMUM_VERSION` in `codeql.ts`
"stable-v2.19.4",
// The last CodeQL release in the 2.20 series.
"stable-v2.20.7",
// The last CodeQL release in the 2.21 series.
"stable-v2.21.4",
@@ -147,6 +145,8 @@ const defaultTestVersions = [
"stable-v2.23.9",
// The last CodeQL release in the 2.24 series.
"stable-v2.24.3",
// The last CodeQL release in the 2.25 series.
"stable-v2.25.6",
// The default version of CodeQL for Dotcom, as determined by feature flags.
"default",
// The version of CodeQL shipped with the Action in `defaults.json`. During the release process
@@ -215,8 +215,8 @@ const languageSetups: LanguageSetups = {
name: "Install Node.js",
uses: pinnedUses(
"actions/setup-node",
"820762786026740c76f36085b0efc47a31fe5020",
"v7.0.0",
"949feb2413d6458794dcd2491c4babbbce0c15c1",
"v7.1.0",
),
with: {
"node-version": defaultLanguageVersions.javascript,
+12 -3
View File
@@ -23,7 +23,13 @@ import { parseArgs } from "node:util";
import { type ApiClient, getApiClient } from "./api-client";
import * as changelog from "./changelog";
import { DryRunOption, REPO_ROOT } from "./config";
import { transferChangenotesToChangelog } from "./changenotes";
import {
CHANGELOG_FILE,
CHANGENOTES_DIR,
DryRunOption,
REPO_ROOT,
} from "./config";
import {
getCurrentVersion,
replaceVersionInPackageJson,
@@ -693,11 +699,14 @@ export async function prepareNewBranch(
console.log("Updating changelog");
changelog.withChangelog(
(contents) => changelog.setVersionAndDate(version, contents),
(contents) => {
const populatedContents = transferChangenotesToChangelog(contents);
return changelog.setVersionAndDate(version, populatedContents);
},
{ ...options, initChangelog: true },
);
runGit(["add", "CHANGELOG.md"], {
runGit(["add", CHANGELOG_FILE, CHANGENOTES_DIR], {
dryRun: options.dryRun,
});
runGit(["commit", "-m", `Update changelog for v${version}`], {
@@ -23,7 +23,8 @@ predicate isSafeForDefaultSetup(string envVar) {
"GITHUB_BASE_REF", "GITHUB_EVENT_NAME", "GITHUB_JOB", "GITHUB_RUN_ATTEMPT", "GITHUB_RUN_ID",
"GITHUB_SHA", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKFLOW",
"GITHUB_WORKSPACE", "GOFLAGS", "ImageVersion", "JAVA_TOOL_OPTIONS", "RUNNER_ARCH",
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE"
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE",
"NODE_ENV"
]
}
+18
View File
@@ -19,6 +19,24 @@ import { getErrorMessage } from "./util";
setupTests(test);
for (const [key, displayName] of Object.entries(
statusReport.ACTION_DISPLAY_NAMES,
)) {
test(`getDisplayActionName - '${displayName}' instead of '${key}'`, (t) => {
t.is(statusReport.getDisplayActionName(key as ActionName), displayName);
});
}
test("getDisplayActionName - returns action name", (t) => {
for (const actionName of Object.values(ActionName)) {
if (statusReport.ACTION_DISPLAY_NAMES[actionName]) {
continue;
}
t.is(statusReport.getDisplayActionName(actionName), actionName);
}
});
interface RunInActionsTestOpts {
runFn?: () => Promise<any>;
expectedErrorMessage?: string;
+70 -1
View File
@@ -56,6 +56,29 @@ export interface FeatureState {
/** Identifies a type of state an Action may have. */
export type StateFeature = keyof FeatureState;
/**
* The `Env` feature implies the availability of the `ReadOnlyEnv` feature.
*
* If `T` is `Env`, this returns `Env | ReadOnlyEnv`.
* Otherwise, it is the identity and returns T.
*/
type ImpliedFeatures<T extends StateFeature> = T extends "Env"
? "Env" | "ReadOnlyEnv"
: T;
/**
* Given an object type `Obj`, this tries to lookup a corresponding `StateFeature`
* to which the object type belongs in `FeatureState`. Resolves to `never` if there
* is no match.
*/
type FeatureNameFor<Obj extends object> = {
[K in StateFeature]: [Obj] extends [FeatureState[K]]
? [FeatureState[K]] extends [Obj]
? K
: never
: never;
}[StateFeature];
/** Constructs the intersection of all state types identifies by `Fs`. */
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
? Record<never, never>
@@ -66,8 +89,54 @@ export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
? FeatureState[Head] & FieldsOf<Tail>
: never;
/**
* Symbol used for a field in `ActionState` that carries the type array of state features.
* This is a Symbol so that it doesn't clash with any property names we might want to have.
*/
const stateFeatures = Symbol();
/** Describes the state of an Action that has access to the state corresponding to `Fs`. */
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs> & {
/**
* When given a chance, TypeScript will simplify an `ActionState<Fs>` type as much as possible,
* which results in a concrete object type that doesn't mention `Fs`.
*
* That causes problems for functions which accept `ActionState<Fs>` values, but need to know the
* feature keys `Fs`. This property here explicitly captures `Fs` in the concrete object type
* that results from simplifying `ActionState<Fs>`.
*
* This is a function rather than a field, because we want to be able to provide values of type
* `ActionState<Fs>` to functions expecting `ActionState<As>` where `As` is a subset of `Fs`.
*
* Since function types are contravariant in the types of their parameters, using a function
* type here allows that to happen.
*
* Because the field is optional, we don't have to explicitly provide a value
* for it anywhere while the type is still inferred.
*
* `Fs[number]` returns the union of all features in `Fs`. We wrap it in `ImpliedFeatures`
* so that `Env` is expanded into `Env | ReadOnlyEnv`, allowing functions that expect the
* `ReadOnlyEnv` feature to be provided with an `ActionState` that has the `Env` feature
* without requiring this to be made explicit.
*/
readonly [stateFeatures]?: (ts: ImpliedFeatures<Fs[number]>) => void;
};
/** Extends `state` with an `extra` feature. */
export function extendActionState<
// In first position, so that it can be explicitly provided if `FeatureNameFor`
// should not work on `extra`.
F extends StateFeature,
Fs extends readonly StateFeature[],
E extends FeatureState[F],
>(
state: ActionState<Fs>,
extra: E,
): ActionState<[...Fs, FeatureNameFor<E> & F]> {
return { ...state, ...extra } as unknown as ActionState<
[...Fs, FeatureNameFor<E> & F]
>;
}
/** The type of an Action's main entry point. This is a function that is provided
* with a basic `ActionState` object with features that are always available.
+18 -2
View File
@@ -7,12 +7,13 @@ import {
isAnalyzingPullRequest,
isDefaultSetup,
isDynamicWorkflow,
printDebugLogs,
} from "./actions-util";
import { computeAutomationID } from "./api-client";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import { setupTests } from "./testing-utils";
import { initializeEnvironment } from "./util";
import { RecordingLogger, setupTests } from "./testing-utils";
import { initializeEnvironment, withTmpDir } from "./util";
setupTests(test);
@@ -100,6 +101,21 @@ test("computeAutomationID()", async (t) => {
);
});
test("printDebugLogs - doesn't throw when config doesn't have needed properties", async (t) => {
const logger = new RecordingLogger();
await t.notThrowsAsync(printDebugLogs(logger, {}));
await t.notThrowsAsync(printDebugLogs(logger, { languages: [] }));
await t.notThrowsAsync(printDebugLogs(logger, { dbLocation: "nowhere" }));
});
test("printDebugLogs - doesn't throw when language db doesn't exist", async (t) =>
withTmpDir(async (tmpDir) => {
const logger = new RecordingLogger();
await t.notThrowsAsync(
printDebugLogs(logger, { dbLocation: tmpDir, languages: ["javascript"] }),
);
}));
test.serial("getPullRequestBranches() with pull request context", (t) => {
withMockedContext(
{
+21 -13
View File
@@ -6,7 +6,7 @@ import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as github from "@actions/github";
import * as io from "@actions/io";
import type { Config } from "./config-utils";
import { hasConfigKey, type Config } from "./config/action-config";
import { Env, EnvVar, ActionsEnvVars, ReadOnlyEnv } from "./environment";
import { Logger } from "./logging";
import {
@@ -30,7 +30,6 @@ declare const __CODEQL_ACTION_VERSION__: string;
export interface ActionsEnv {
getRequiredInput: (name: string) => string;
getOptionalInput: (name: string) => string | undefined;
exportVariable: (name: string, value: string) => void;
}
/**
@@ -40,7 +39,6 @@ export function getActionsEnv(): ActionsEnv {
return {
getRequiredInput,
getOptionalInput,
exportVariable: core.exportVariable,
};
}
@@ -95,7 +93,7 @@ export function getActionVersion(): string {
*
* This will be "dynamic" for default setup workflow runs.
*/
export function getWorkflowEventName(env: Env = getEnv()) {
export function getWorkflowEventName(env: ReadOnlyEnv = getEnv()) {
return env.getRequired(ActionsEnvVars.GITHUB_EVENT_NAME);
}
@@ -103,7 +101,7 @@ export function getWorkflowEventName(env: Env = getEnv()) {
* Returns whether the current workflow is executing a local copy of the Action, e.g. we're running
* a workflow on the codeql-action repo itself.
*/
export function isRunningLocalAction(env: Env = getEnv()): boolean {
export function isRunningLocalAction(env: ReadOnlyEnv = getEnv()): boolean {
const relativeScriptPath = getRelativeScriptPath(env);
return (
relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath)
@@ -115,14 +113,14 @@ export function isRunningLocalAction(env: Env = getEnv()): boolean {
*
* This can be used to get the Action's name or tell if we're running a local Action.
*/
function getRelativeScriptPath(env: Env): string {
function getRelativeScriptPath(env: ReadOnlyEnv): string {
const runnerTemp = env.getRequired(ActionsEnvVars.RUNNER_TEMP);
const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions");
return path.relative(actionsDirectory, __filename);
}
/** Returns the contents of `GITHUB_EVENT_PATH` as a JSON object. */
export function getWorkflowEvent(env: Env = getEnv()): any {
export function getWorkflowEvent(env: ReadOnlyEnv = getEnv()): any {
const eventJsonFile = env.getRequired(ActionsEnvVars.GITHUB_EVENT_PATH);
try {
return JSON.parse(fs.readFileSync(eventJsonFile, "utf-8"));
@@ -133,28 +131,38 @@ export function getWorkflowEvent(env: Env = getEnv()): any {
}
}
export async function printDebugLogs(config: Config) {
export async function printDebugLogs(logger: Logger, config: Partial<Config>) {
if (
!hasConfigKey(config, "languages") ||
!hasConfigKey(config, "dbLocation")
) {
logger.debug(
`Not printing debug logs, because the configuration does not include the 'languages' or 'dbLocation' fields.`,
);
return;
}
for (const language of config.languages) {
const databaseDirectory = getCodeQLDatabasePath(config, language);
const logsDirectory = path.join(databaseDirectory, "log");
if (!doesDirectoryExist(logsDirectory)) {
core.info(`Directory ${logsDirectory} does not exist.`);
logger.info(`Directory ${logsDirectory} does not exist.`);
continue; // Skip this language database.
}
const walkLogFiles = (dir: string) => {
const entries = fs.readdirSync(dir, { withFileTypes: true });
if (entries.length === 0) {
core.info(`No debug logs found at directory ${logsDirectory}.`);
logger.info(`No debug logs found at directory ${logsDirectory}.`);
}
for (const entry of entries) {
if (entry.isFile()) {
const absolutePath = path.resolve(dir, entry.name);
core.startGroup(
logger.startGroup(
`CodeQL Debug Logs - ${language} - ${entry.name} from file at path ${absolutePath}`,
);
process.stdout.write(fs.readFileSync(absolutePath));
core.endGroup();
logger.info(fs.readFileSync(absolutePath, "utf-8"));
logger.endGroup();
} else if (entry.isDirectory()) {
walkLogFiles(path.resolve(dir, entry.name));
}
-4
View File
@@ -9,7 +9,6 @@ import * as core from "@actions/core";
import * as actionsUtil from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { getCodeQL } from "./codeql";
import { getConfig } from "./config-utils";
import * as debugArtifacts from "./debug-artifacts";
import {
@@ -38,12 +37,9 @@ export async function runWrapper() {
logger,
);
if (config !== undefined) {
const codeql = await getCodeQL(logger, config.codeQLCmd);
const version = await codeql.getVersion();
await debugArtifacts.uploadCombinedSarifArtifacts(
logger,
config.gitHubVersion.type,
version.version,
);
}
}
+2
View File
@@ -45,6 +45,7 @@ test.serial(
requiredInputStub.withArgs("token").returns("fake-token");
requiredInputStub.withArgs("upload-database").returns("false");
requiredInputStub.withArgs("output").returns("out");
requiredInputStub.withArgs("checkout_path").returns("");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("expect-error").returns("false");
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
@@ -104,6 +105,7 @@ test.serial(
requiredInputStub.withArgs("token").returns("fake-token");
requiredInputStub.withArgs("upload-database").returns("false");
requiredInputStub.withArgs("output").returns("out");
requiredInputStub.withArgs("checkout_path").returns("");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("expect-error").returns("false");
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
+16 -21
View File
@@ -4,12 +4,18 @@ import { performance } from "perf_hooks";
import * as core from "@actions/core";
import { Action, ActionState, runInActions } from "./action-common";
import {
Action,
ActionState,
extendActionState,
runInActions,
} from "./action-common";
import * as actionsUtil from "./actions-util";
import * as analyses from "./analyses";
import {
CodeQLAnalysisError,
dbIsFinalized,
determineCheckoutPath,
QueriesStatusReport,
runFinalize,
runQueries,
@@ -212,13 +218,11 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
await runAutobuild(config, BuiltInLanguage.go, logger);
}
async function run({
startedAt,
logger,
actions,
}: ActionState<["Base", "Logger", "Actions"]>) {
async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
const startedAt = action.startedAt;
const logger = action.logger;
let uploadResults:
| Partial<Record<analyses.AnalysisKind, UploadResult>>
@@ -267,17 +271,6 @@ async function run({
);
}
// Unset the CODEQL_PROXY_* environment variables when using older CodeQL
// CLIs, as they are not needed and can cause issues.
if (
process.env.CODEQL_PROXY_HOST === "" &&
!(await util.codeQlVersionAtLeast(codeql, "2.20.7"))
) {
delete process.env.CODEQL_PROXY_HOST;
delete process.env.CODEQL_PROXY_PORT;
delete process.env.CODEQL_PROXY_CA_CERTIFICATE;
}
if (actionsUtil.getOptionalInput("cleanup-level")) {
logger.info(
"The 'cleanup-level' input is ignored since the CodeQL Action now automatically " +
@@ -287,7 +280,7 @@ async function run({
const apiDetails = getApiDetails();
const outputDir = actionsUtil.getRequiredInput("output");
core.exportVariable(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
action.env.export(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
const threads = util.getThreadsFlag(
actionsUtil.getOptionalInput("threads") || process.env["CODEQL_THREADS"],
logger,
@@ -311,7 +304,7 @@ async function run({
logger,
);
const checkoutPath = actions.getRequiredInput("checkout_path");
const checkoutPath = await determineCheckoutPath(action, config);
// Setup diff informed analysis if needed (based on whether init created the file)
const diffRangePackDir = await setupDiffInformedQueryRun(
@@ -403,11 +396,13 @@ async function run({
checkoutPath,
);
const actionWithFeatures = extendActionState(action, { features });
// Possibly upload the database bundles for remote queries.
// Note: Take care with the ordering of this call since databases may be cleaned up
// at the `overlay` or `clear` level.
databaseUploadResults = await cleanupAndUploadDatabases(
{ logger, features },
actionWithFeatures,
repositoryNwo,
codeql,
config,
@@ -456,7 +451,7 @@ async function run({
`expect-error input was set to true but no error was thrown.`,
);
}
core.exportVariable(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
action.env.export(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
} catch (unwrappedError) {
const error = util.wrapError(unwrappedError);
if (
+88
View File
@@ -10,10 +10,12 @@ import {
resolveQuerySuiteAlias,
addSarifExtension,
diffRangeExtensionPackContents,
determineCheckoutPath,
} from "./analyze";
import { createStubCodeQL } from "./codeql";
import { defaultSuites } from "./config/db-config";
import { Feature } from "./feature-flags";
import * as gitUtils from "./git-utils";
import { BuiltInLanguage } from "./languages";
import { getRunnerLogger } from "./logging";
import {
@@ -21,12 +23,98 @@ import {
setupActionsVars,
createFeatures,
createTestConfig,
callee,
} from "./testing-utils";
import * as uploadLib from "./upload-lib";
import * as util from "./util";
setupTests(test);
test.serial(
"determineCheckoutPath - logs when checkout_path is not in a work tree",
async (t) => {
const expectedPath = path.resolve("/checkout/path");
const target = callee(determineCheckoutPath)
.withActions((actions) => {
sinon
.stub(actions, "getRequiredInput")
.withArgs("checkout_path")
.returns(expectedPath);
})
.withArgs(createTestConfig({}));
sinon.stub(gitUtils, "getGitRoot").resolves(undefined);
await target
.logs(t, "is not in the work tree of a git repository")
.passes(t.is, expectedPath);
},
);
test.serial(
"determineCheckoutPath - logs when checkout_path is not a repo root",
async (t) => {
const expectedPath = path.resolve("/checkout/path");
const target = callee(determineCheckoutPath)
.withActions((actions) => {
sinon
.stub(actions, "getRequiredInput")
.withArgs("checkout_path")
.returns(expectedPath);
})
.withArgs(createTestConfig({}));
sinon.stub(gitUtils, "getGitRoot").resolves("/checkout");
await target
.logs(t, "is not the root of the repository")
.passes(t.is, expectedPath);
},
);
test.serial(
"determineCheckoutPath - logs when checkout_path is not the same as repo root in config",
async (t) => {
const expectedPath = path.resolve("/checkout/path");
const target = callee(determineCheckoutPath)
.withActions((actions) => {
sinon
.stub(actions, "getRequiredInput")
.withArgs("checkout_path")
.returns(expectedPath);
})
.withArgs(createTestConfig({ repositoryRoot: "/some/other/path" }));
sinon.stub(gitUtils, "getGitRoot").resolves(expectedPath);
await target
.logs(t, "does not match that found by the 'codeql-action/init' step")
.passes(t.is, expectedPath);
},
);
test.serial(
"determineCheckoutPath - doesn't log any of the messages when all is as expected",
async (t) => {
const expectedPath = path.resolve("/checkout/path");
const target = callee(determineCheckoutPath)
.withActions((actions) => {
sinon
.stub(actions, "getRequiredInput")
.withArgs("checkout_path")
.returns(expectedPath);
})
.withArgs(createTestConfig({ repositoryRoot: expectedPath }));
sinon.stub(gitUtils, "getGitRoot").resolves(expectedPath);
await target
.notLogs(
t,
"is not in the work tree of a git repository",
"is not the root of the repository",
"does not match that found by the 'codeql-action/init' step",
)
.passes(t.is, expectedPath);
},
);
/**
* Checks the status report produced by the analyze Action.
*
+57 -2
View File
@@ -5,10 +5,12 @@ import { performance } from "perf_hooks";
import * as io from "@actions/io";
import * as yaml from "js-yaml";
import type { ActionState } from "./action-common";
import { getTemporaryDirectory } from "./actions-util";
import * as analyses from "./analyses";
import { setupCppAutobuild } from "./autobuild";
import { type CodeQL } from "./codeql";
import type { ActionConfig } from "./config/action-config";
import { defaultSuites } from "./config/db-config";
import * as configUtils from "./config-utils";
import {
@@ -22,6 +24,7 @@ import {
} from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import { FeatureEnablement, Feature } from "./feature-flags";
import { getGitRoot } from "./git-utils";
import { BuiltInLanguage, Language } from "./languages";
import { Logger, withGroupAsync } from "./logging";
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
@@ -86,6 +89,58 @@ export interface QueriesStatusReport
event_reports?: EventReport[];
}
/**
* Determines the path at which the repository being analysed is checked out at.
* Returns the value of the required `checkout_path` input and validates that it
* refers to the root of a repository.
*
* @param action The action state.
* @param config The CodeQL Action configuration state.
*/
export async function determineCheckoutPath(
action: ActionState<["Logger", "Actions"]>,
config: configUtils.Config,
) {
const checkoutPathInput = action.actions.getRequiredInput("checkout_path");
// Try to obtain the root path of the repository and validate that it matches the input.
const absCheckoutPathInput = path.resolve(checkoutPathInput);
const repositoryRoot = await getGitRoot(absCheckoutPathInput);
if (repositoryRoot === undefined) {
action.logger.warning(
[
`The directory at '${absCheckoutPathInput}' is not in the work tree of a git repository.`,
"If the repository being analyzed is checked out elsewhere,",
"you must explicitly set the 'checkout_path' input for the 'codeql-action/analyze' step to",
"the checkout path.",
].join(" "),
);
} else if (repositoryRoot !== absCheckoutPathInput) {
action.logger.warning(
[
`The directory at '${absCheckoutPathInput}' is not the root of the repository ('${repositoryRoot}').`,
"Set the 'checkout_path' input for the 'codeql-action/analyze' step to the root path of the checkout.",
].join(" "),
);
} else if (
config.repositoryRoot !== undefined &&
repositoryRoot !== config.repositoryRoot
) {
// The repository root that was persisted by the `init` step doesn't match the one we have found here.
action.logger.warning(
[
`The repository path at '${repositoryRoot}' does not match that found by the 'codeql-action/init' step: '${config.repositoryRoot}'.`,
"Ensure that the 'checkout_path' input for the 'codeql-action/analyze' step is set to the path of the same repository that",
"the 'codeql-action/init' step determined. This is either the GitHub Actions workspace or the repository root corresponding to",
"the 'source-root' input if that was provided.",
].join(" "),
);
}
return absCheckoutPathInput;
}
async function setupPythonExtractor(logger: Logger) {
const codeqlPython = process.env["CODEQL_PYTHON"];
if (codeqlPython === undefined || codeqlPython.length === 0) {
@@ -124,7 +179,7 @@ export async function runExtraction(
language === BuiltInLanguage.cpp &&
config.buildMode === BuildMode.Autobuild
) {
await setupCppAutobuild(codeql, logger);
await setupCppAutobuild(logger);
}
// The Java and C# `build-mode: none` extractors place dependencies in the
@@ -171,7 +226,7 @@ async function shouldExtractLanguage(
}
export function dbIsFinalized(
config: configUtils.Config,
config: ActionConfig<"dbLocation">,
language: Language,
logger: Logger,
) {
+2 -1
View File
@@ -17,6 +17,7 @@ import {
ReadOnlyEnv,
RegistryProxyVars,
getEnv,
exportEnvVar,
} from "./environment";
import { Logger } from "./logging";
import { getRepositoryNwo, RepositoryNwo } from "./repository";
@@ -316,7 +317,7 @@ export async function getAnalysisKey(): Promise<string> {
const jobName = getRequiredEnvParam("GITHUB_JOB");
analysisKey = `${workflowPath}:${jobName}`;
core.exportVariable(EnvVar.ANALYSIS_KEY, analysisKey);
exportEnvVar(EnvVar.ANALYSIS_KEY, analysisKey);
return analysisKey;
}
+6 -2
View File
@@ -68,7 +68,11 @@ async function sendCompletedStatusReport(
}
}
async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
async function run({
startedAt,
env,
logger,
}: ActionState<["Base", "Env", "Logger"]>) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
@@ -135,7 +139,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
return;
}
core.exportVariable(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
env.export(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
}
+8 -10
View File
@@ -1,11 +1,9 @@
import * as core from "@actions/core";
import { getTemporaryDirectory, getWorkflowEventName } from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { CodeQL, getCodeQL } from "./codeql";
import * as configUtils from "./config-utils";
import { DocUrl } from "./doc-url";
import { ActionsEnvVars, EnvVar } from "./environment";
import { ActionsEnvVars, EnvVar, exportEnvVar } from "./environment";
import { Feature, featureConfig, initFeatures } from "./feature-flags";
import { BuiltInLanguage, Language } from "./languages";
import { Logger } from "./logging";
@@ -112,7 +110,7 @@ export async function determineAutobuildLanguages(
return languages;
}
export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
export async function setupCppAutobuild(logger: Logger) {
const envVar = featureConfig[Feature.CppDependencyInstallation].envVar;
const featureName = "C++ automatic installation of dependencies";
const gitHubVersion = await getGitHubVersion();
@@ -123,7 +121,7 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
getTemporaryDirectory(),
logger,
);
if (await features.getValue(Feature.CppDependencyInstallation, codeql)) {
if (await features.getValue(Feature.CppDependencyInstallation)) {
// disable autoinstall on self-hosted runners unless explicitly requested
if (
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] === "self-hosted" &&
@@ -136,16 +134,16 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
: ""
}`,
);
core.exportVariable(envVar, "false");
exportEnvVar(envVar, "false");
} else {
logger.info(
`Enabling ${featureName}. This can be disabled by setting the ${envVar} environment variable to 'false'. See ${DocUrl.DEFINE_ENV_VARIABLES} for more information.`,
);
core.exportVariable(envVar, "true");
exportEnvVar(envVar, "true");
}
} else {
logger.info(`Disabling ${featureName}.`);
core.exportVariable(envVar, "false");
exportEnvVar(envVar, "false");
}
}
@@ -157,7 +155,7 @@ export async function runAutobuild(
logger.startGroup(`Attempting to automatically build ${language} code`);
const codeQL = await getCodeQL(logger, config.codeQLCmd);
if (language === BuiltInLanguage.cpp) {
await setupCppAutobuild(codeQL, logger);
await setupCppAutobuild(logger);
}
if (config.buildMode) {
await codeQL.extractUsingBuildMode(config, language);
@@ -165,7 +163,7 @@ export async function runAutobuild(
await codeQL.runAutobuild(config, language);
}
if (language === BuiltInLanguage.go) {
core.exportVariable(EnvVar.DID_AUTOBUILD_GOLANG, "true");
exportEnvVar(EnvVar.DID_AUTOBUILD_GOLANG, "true");
}
logger.endGroup();
}
+10 -7
View File
@@ -15,9 +15,10 @@ import * as api from "./api-client";
import * as outputCache from "./cli/output-cache";
import type { VersionInfo } from "./cli/types";
import { CliError, wrapCliConfigurationError } from "./cli-errors";
import type { ActionConfig } from "./config/action-config";
import { appendExtraQueryExclusions, type Config } from "./config-utils";
import { DocUrl } from "./doc-url";
import { EnvVar, getEnv } from "./environment";
import { EnvVar, getEnv, exportEnvVar } from "./environment";
import {
CodeQLDefaultVersionInfo,
Feature,
@@ -197,7 +198,7 @@ export interface CodeQL {
diagnosticsExport(
sarifFile: string,
automationDetailsId: string | undefined,
config: Config,
config: ActionConfig<"tempDir">,
): Promise<void>;
/** Get the location of an extractor for the specified language. */
resolveExtractor(language: Language): Promise<string>;
@@ -253,7 +254,7 @@ let cachedCodeQL: CodeQL | undefined = undefined;
* The version flags below can be used to conditionally enable certain features
* on versions newer than this.
*/
const CODEQL_MINIMUM_VERSION = "2.19.4";
const CODEQL_MINIMUM_VERSION = "2.20.7";
/**
* This version will shortly become the oldest version of CodeQL that the Action will run with.
@@ -913,7 +914,7 @@ async function getCodeQLForCmd(
async diagnosticsExport(
sarifFile: string,
automationDetailsId: string | undefined,
config: Config,
config: ActionConfig<"tempDir">,
): Promise<void> {
const args = [
"diagnostics",
@@ -1054,7 +1055,7 @@ async function getCodeQLForCmd(
}' by 'github/codeql-action/*@v${getActionVersion()}' in your code scanning workflow to ` +
"continue using this version of the CodeQL Action.",
);
core.exportVariable(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING, "true");
exportEnvVar(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING, "true");
}
return codeql;
}
@@ -1224,7 +1225,7 @@ export async function getTrapCachingExtractorConfigArgsForLang(
): Promise<string[]> {
const cacheDir = config.trapCaches[language];
if (cacheDir === undefined) return [];
const write = await isAnalyzingDefaultBranch();
const write = await isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot);
return [
`-O=${language}.trap.cache.dir=${cacheDir}`,
`-O=${language}.trap.cache.bound=${TRAP_CACHE_SIZE_MB}`,
@@ -1237,7 +1238,9 @@ export async function getTrapCachingExtractorConfigArgsForLang(
*
* This will not exist if the configuration is being parsed in the Action.
*/
function getGeneratedCodeScanningConfigPath(config: Config): string {
function getGeneratedCodeScanningConfigPath(
config: ActionConfig<"tempDir">,
): string {
return path.resolve(config.tempDir, "user-config.yaml");
}
+7 -5
View File
@@ -172,6 +172,7 @@ test.serial("load empty config", async (t) => {
createTestInitConfigInputs({
languagesInput: languages,
repository: { owner: "github", repo: "example" },
sourceRoot: tempDir,
tempDir,
codeql,
logger,
@@ -186,6 +187,7 @@ test.serial("load empty config", async (t) => {
logger,
}),
{},
undefined,
);
t.deepEqual(config, expectedConfig);
@@ -216,6 +218,7 @@ test.serial("load code quality config", async (t) => {
analysisKinds: [AnalysisKind.CodeQuality],
languagesInput: languages,
repository: { owner: "github", repo: "example" },
sourceRoot: tempDir,
tempDir,
codeql,
logger,
@@ -296,6 +299,7 @@ test.serial(
analysisKinds: [AnalysisKind.CodeQuality],
languagesInput: languages,
repository: { owner: "github", repo: "example" },
sourceRoot: tempDir,
tempDir,
codeql,
repositoryProperties,
@@ -518,6 +522,7 @@ test.serial("load non-empty input", async (t) => {
// And the config we expect it to parse to
const expectedConfig = createTestConfig({
languages: [BuiltInLanguage.javascript],
repositoryRoot: undefined,
buildMode: BuildMode.None,
originalUserInput: userConfig,
computedConfig: userConfig,
@@ -538,6 +543,7 @@ test.serial("load non-empty input", async (t) => {
state,
createTestInitConfigInputs({
languagesInput,
sourceRoot: tempDir,
buildModeInput: "none",
configFile: configFilePath,
debugArtifactName: "my-artifact",
@@ -1105,11 +1111,6 @@ const checkOverlayEnablementMacro = makeMacro({
return lang === BuiltInLanguage.java;
});
// Mock git root detection
if (setup.gitRoot !== undefined) {
sinon.stub(gitUtils, "getGitRoot").resolves(setup.gitRoot);
}
// Mock submodule detection
sinon.stub(gitUtils, "hasSubmodules").returns(setup.hasSubmodules);
@@ -1122,6 +1123,7 @@ const checkOverlayEnablementMacro = makeMacro({
codeql,
features,
setup.languages,
setup.gitRoot, // repositoryRoot
tempDir, // sourceRoot
setup.buildMode,
undefined,
+139 -27
View File
@@ -2,7 +2,6 @@ import * as fs from "fs";
import * as path from "path";
import { performance } from "perf_hooks";
import * as core from "@actions/core";
import * as yaml from "js-yaml";
import { ActionState } from "./action-common";
@@ -22,7 +21,11 @@ import {
import * as api from "./api-client";
import { getCachingKind } from "./caching-utils";
import { type CodeQL } from "./codeql";
import { type Config } from "./config/action-config";
import {
type AbortedInitConfig,
type ActionConfig,
type Config,
} from "./config/action-config";
import {
calculateAugmentation,
ExcludeQueryFilter,
@@ -46,7 +49,7 @@ import {
makeTelemetryDiagnostic,
} from "./diagnostics";
import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import { EnvVar, exportEnvVar, getEnv } from "./environment";
import * as errorMessages from "./error-messages";
import { Feature, FeatureEnablement, FeatureWithoutCLI } from "./feature-flags";
import {
@@ -72,6 +75,7 @@ import {
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
import { shouldSkipOverlayAnalysis } from "./overlay/status";
import { RepositoryNwo } from "./repository";
import { ActionName } from "./status-report";
import { ToolsFeature } from "./tools-features";
import { downloadTrapCaches } from "./trap-caching";
import {
@@ -386,6 +390,7 @@ export async function initActionState(
enableFileCoverageInformation,
}: InitConfigInputs,
userConfig: UserConfig,
repositoryRoot: string | undefined,
): Promise<Config> {
const languages = await getLanguages(
codeql,
@@ -437,6 +442,7 @@ export async function initActionState(
return {
version: getActionVersion(),
repositoryRoot,
analysisKinds,
languages,
buildMode,
@@ -466,12 +472,18 @@ async function downloadCacheWithTime(
codeQL: CodeQL,
languages: Language[],
logger: Logger,
repositoryRoot: string | undefined,
): Promise<{
trapCaches: { [language: string]: string };
trapCacheDownloadTime: number;
}> {
const start = performance.now();
const trapCaches = await downloadTrapCaches(codeQL, languages, logger);
const trapCaches = await downloadTrapCaches(
codeQL,
languages,
logger,
repositoryRoot,
);
const trapCacheDownloadTime = performance.now() - start;
return { trapCaches, trapCacheDownloadTime };
}
@@ -719,6 +731,7 @@ export async function checkOverlayEnablement(
codeql: CodeQL,
features: FeatureEnablement,
languages: Language[],
repositoryRoot: string | undefined,
sourceRoot: string,
buildMode: BuildMode | undefined,
ramInput: string | undefined,
@@ -748,6 +761,7 @@ export async function checkOverlayEnablement(
true,
codeql,
languages,
repositoryRoot,
sourceRoot,
buildMode,
gitVersion,
@@ -821,7 +835,7 @@ export async function checkOverlayEnablement(
`Setting overlay database mode to ${overlayDatabaseMode} ` +
"with caching because we are analyzing a pull request.",
);
} else if (await isAnalyzingDefaultBranch()) {
} else if (await isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) {
overlayDatabaseMode = OverlayDatabaseMode.OverlayBase;
logger.info(
`Setting overlay database mode to ${overlayDatabaseMode} ` +
@@ -837,6 +851,7 @@ export async function checkOverlayEnablement(
false,
codeql,
languages,
repositoryRoot,
sourceRoot,
buildMode,
gitVersion,
@@ -856,6 +871,7 @@ async function validateOverlayDatabaseMode(
overlayModeSetExplicitly: boolean,
codeql: CodeQL,
languages: Language[],
repositoryRoot: string | undefined,
sourceRoot: string,
buildMode: BuildMode | undefined,
gitVersion: GitVersionInfo | undefined,
@@ -891,8 +907,7 @@ async function validateOverlayDatabaseMode(
);
return new Failure(OverlayDisabledReason.IncompatibleCodeQl);
}
const gitRoot = await getGitRoot(sourceRoot);
if (gitRoot === undefined) {
if (repositoryRoot === undefined) {
logger.warning(
`Cannot build an ${overlayDatabaseMode} database because ` +
`the source root "${sourceRoot}" is not inside a git repository. ` +
@@ -900,7 +915,7 @@ async function validateOverlayDatabaseMode(
);
return new Failure(OverlayDisabledReason.NoGitRoot);
}
if (hasSubmodules(gitRoot)) {
if (hasSubmodules(repositoryRoot)) {
if (gitVersion === undefined) {
logger.warning(
`Cannot build an ${overlayDatabaseMode} database because ` +
@@ -965,10 +980,10 @@ async function setCppTrapCachingEnvironmentVariables(
);
} else if (config.trapCaches[BuiltInLanguage.cpp]) {
logger.info("Enabling TRAP caching for C/C++.");
core.exportVariable(envVar, "true");
exportEnvVar(envVar, "true");
} else {
logger.debug(`Disabling TRAP caching for C/C++.`);
core.exportVariable(envVar, "false");
exportEnvVar(envVar, "false");
}
}
}
@@ -1152,9 +1167,11 @@ export async function initConfig(
const { logger, features } = actionState;
const { tempDir } = inputs;
const repositoryRoot = await getGitRoot(inputs.sourceRoot);
const userConfig = await determineUserConfig(actionState, tempDir, inputs);
const config = await initActionState(inputs, userConfig);
const config = await initActionState(inputs, userConfig, repositoryRoot);
// If Code Quality analysis is the only enabled analysis kind, then we will initialise
// the database for Code Quality. That entails disabling the default queries and only
@@ -1236,6 +1253,7 @@ export async function initConfig(
inputs.codeql,
inputs.features,
config.languages,
repositoryRoot,
inputs.sourceRoot,
config.buildMode,
inputs.ramInput,
@@ -1289,6 +1307,7 @@ export async function initConfig(
inputs.codeql,
config.languages,
logger,
repositoryRoot,
);
config.trapCaches = trapCaches;
config.trapCacheDownloadTime = trapCacheDownloadTime;
@@ -1380,31 +1399,86 @@ export function getPathToParsedConfigFile(tempDir: string): string {
return path.join(tempDir, "config");
}
function getPathToFailedConfigFile(tempDir: string): string {
return path.join(tempDir, "failed-config");
}
/**
* Store the given config to the path returned from getPathToParsedConfigFile.
* Writes {@link config} to {@link configFilePath}.
*
* This function is not intended to be called, other than by
* {@link saveConfig} and {@link saveAbortedInitConfig} below.
*/
export async function saveConfig(config: Config, logger: Logger) {
function saveConfigFile(
logger: Logger,
configFilePath: string,
config: Partial<Config>,
) {
const configString = JSON.stringify(config);
const configFile = getPathToParsedConfigFile(config.tempDir);
fs.mkdirSync(path.dirname(configFile), { recursive: true });
fs.writeFileSync(configFile, configString, "utf8");
fs.mkdirSync(path.dirname(configFilePath), { recursive: true });
fs.writeFileSync(configFilePath, configString, "utf8");
logger.debug("Saved config:");
logger.debug(configString);
}
/**
* Get the config that has been saved to the given temp dir.
* If the config could not be found then returns undefined.
* Store the given config to the path returned from getPathToParsedConfigFile.
*/
export async function getConfig(
tempDir: string,
export async function saveConfig(config: Config, logger: Logger) {
const configFile = getPathToParsedConfigFile(config.tempDir);
saveConfigFile(logger, configFile, config);
}
/**
* Store parts of {@link config} as a failed configuration file.
*
* The file will only contain parts of {@link config} that are relevant to
* the `init-post` step in the workflow.
*/
export function saveAbortedInitConfig(
action: ActionState<["Base", "Logger"]>,
config: Config,
) {
// Should only be called by init.
if (action.name !== ActionName.Init) {
throw new Error(
`Internal error: 'saveAbortedInitConfig' should only be called by '${ActionName.Init}', but was called by '${action.name}'.`,
);
}
// Create a copy of the input `Config` that only contains the keys we want.
const failedConfig: AbortedInitConfig = {
version: config.version,
tempDir: config.tempDir,
codeQLCmd: config.codeQLCmd,
debugMode: config.debugMode,
gitHubVersion: config.gitHubVersion,
languages: config.languages,
analysisKinds: config.analysisKinds,
};
action.logger.debug(`Saving config for failed init step.`);
saveConfigFile(
action.logger,
getPathToFailedConfigFile(config.tempDir),
failedConfig,
);
}
/**
* Attempts to read a {@link Config} from {@link configFilePath}.
*
* This function is not intended to be called, other than by
* {@link getConfig} and {@link getAbortedInitConfig} below.
*/
function readConfigFile(
logger: Logger,
): Promise<Config | undefined> {
const configFile = getPathToParsedConfigFile(tempDir);
if (!fs.existsSync(configFile)) {
configFilePath: string,
): Config | undefined {
if (!fs.existsSync(configFilePath)) {
return undefined;
}
const configString = fs.readFileSync(configFile, "utf8");
const configString = fs.readFileSync(configFilePath, "utf8");
logger.debug("Loaded config:");
logger.debug(configString);
@@ -1429,6 +1503,38 @@ export async function getConfig(
return config as Config;
}
/**
* Get the config that has been saved to the given temp dir.
* If the config could not be found then returns undefined.
*/
export async function getConfig(
tempDir: string,
logger: Logger,
): Promise<Config | undefined> {
const configFile = getPathToParsedConfigFile(tempDir);
return readConfigFile(logger, configFile);
}
/**
* Loads a {@link AbortedInitConfig} from a failed `init` step if it exists.
*
* The result will only contain parts of a {@link Config} that are relevant to
* the `init-post` step in the workflow.
*/
export function getAbortedInitConfig(
action: ActionState<["Base", "Logger"]>,
tempDir: string,
): AbortedInitConfig | undefined {
// Only intended to be called by the `init-post` action.
if (action.name !== ActionName.InitPost) {
throw new Error(
`Internal error: 'getAbortedInitConfig' should only be called by '${ActionName.InitPost}', but was called by '${action.name}'.`,
);
}
return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir));
}
/**
* Generate a `qlconfig.yml` file from the `registries` input.
* This file is used by the CodeQL CLI to list the registries to use for each
@@ -1619,21 +1725,27 @@ export function appendExtraQueryExclusions(
/**
* Returns `true` if Code Scanning analysis is enabled, or `false` if not.
*/
export function isCodeScanningEnabled(config: Config): boolean {
export function isCodeScanningEnabled(
config: ActionConfig<"analysisKinds">,
): boolean {
return config.analysisKinds.includes(AnalysisKind.CodeScanning);
}
/**
* Returns `true` if Code Quality analysis is enabled, or `false` if not.
*/
export function isCodeQualityEnabled(config: Config): boolean {
export function isCodeQualityEnabled(
config: ActionConfig<"analysisKinds">,
): boolean {
return config.analysisKinds.includes(AnalysisKind.CodeQuality);
}
/**
* Returns `true` if Code Scanning Risk Assessment analysis is enabled, or `false` if not.
*/
export function isRiskAssessmentEnabled(config: Config): boolean {
export function isRiskAssessmentEnabled(
config: ActionConfig<"analysisKinds">,
): boolean {
return config.analysisKinds.includes(AnalysisKind.RiskAssessment);
}
+40
View File
@@ -16,6 +16,11 @@ export interface Config {
* The version of the CodeQL Action that the configuration is for.
*/
version: string;
/**
* The path at which the repository being analysed is checked out at, if available.
* Persisted in the CodeQL Action configuration state, so that we can consult it in later workflow steps.
*/
repositoryRoot: string | undefined;
/**
* Set of analysis kinds that are enabled.
*/
@@ -126,3 +131,38 @@ export interface Config {
*/
enableFileCoverageInformation: boolean;
}
/** Properties available in `Config`. */
export type ActionConfigProperties = keyof Config;
/**
* The CodeQL Action configuration state that is persisted
* between steps of the CodeQL Action in a CodeQL workflow.
*
* By default, this is equivalent to `Config`. By explicitly
* providing required property names for `T` this can be restricted
* to requiring only those. For example, in `ActionConfig<"codeQLCmd">`
* all properties except `codeqlCmd` are optional.
*/
export type ActionConfig<
T extends ActionConfigProperties = ActionConfigProperties,
> = Partial<Config> & Pick<Config, T>;
/** Helper function to check whether `config` has `key` to narrow the type of `config`. */
export function hasConfigKey<Key extends ActionConfigProperties>(
config: Partial<Config>,
key: Key,
): config is ActionConfig<Key> {
return config[key] !== undefined;
}
/** A minimal configuration that we can make available even when `init` aborts. */
export type AbortedInitConfig = ActionConfig<
| "version"
| "tempDir"
| "gitHubVersion"
| "debugMode"
| "languages"
| "analysisKinds"
| "codeQLCmd"
>;
+3 -3
View File
@@ -46,7 +46,7 @@ export interface DatabaseUploadResult {
}
export async function cleanupAndUploadDatabases(
action: ActionState<["Logger", "FeatureFlags"]>,
action: ActionState<["ReadOnlyEnv", "Logger", "FeatureFlags"]>,
repositoryNwo: RepositoryNwo,
codeql: CodeQL,
config: Config,
@@ -81,7 +81,7 @@ export async function cleanupAndUploadDatabases(
return [];
}
if (!(await gitUtils.isAnalyzingDefaultBranch())) {
if (!(await gitUtils.isAnalyzingDefaultBranch(action.env, checkoutPath))) {
// We only want to upload a database if we are analyzing the default branch.
logger.debug("Not analyzing default branch. Skipping upload.");
return [];
@@ -113,7 +113,7 @@ export async function cleanupAndUploadDatabases(
includeDiagnostics: false,
});
bundledDbSize = fs.statSync(bundledDb).size;
const commitOid = await gitUtils.getCommitOid(checkoutPath);
const commitOid = await gitUtils.getCommitOid(action.env, checkoutPath);
// Upload with manual retry logic. We disable Octokit's built-in retries
// because the request body is a ReadStream, which can only be consumed
// once.
+7 -55
View File
@@ -55,25 +55,20 @@ test("getArtifactSuffix", (t) => {
}
});
// These next tests check the correctness of the logic to determine whether or not
// artifacts are uploaded in debug mode. Since it's not easy to mock the actual
// call to upload an artifact, we just check that we get an "upload-failed" result,
// instead of actually uploading the artifact.
//
// For tests where we expect artifact upload to be blocked, we check for a different
// response from the function.
// These next tests check whether `uploadArtifacts` attempts to upload an artifact. Since it's not
// easy to mock the actual call to upload an artifact, we check that we get an "upload-failed"
// result, instead of actually uploading the artifact.
test("uploadDebugArtifacts when artifacts empty should emit 'no-artifacts-to-upload'", async (t) => {
test("uploadArtifacts returns 'no-artifacts-to-upload' when there are no files to upload", async (t) => {
// Test that no error is thrown if artifacts list is empty.
const logger = getActionsLogger();
await t.notThrowsAsync(async () => {
const uploaded = await debugArtifacts.uploadDebugArtifacts(
const uploaded = await debugArtifacts.uploadArtifacts(
logger,
[],
"i-dont-exist",
"artifactName",
GitHubVariant.DOTCOM,
undefined,
);
t.is(
uploaded,
@@ -83,17 +78,15 @@ test("uploadDebugArtifacts when artifacts empty should emit 'no-artifacts-to-upl
});
});
test("uploadDebugArtifacts when no codeql version is used should invoke artifact upload", async (t) => {
// Test that the artifact is uploaded.
test("uploadArtifacts attempts an upload when there are files to upload", async (t) => {
const logger = getActionsLogger();
await t.notThrowsAsync(async () => {
const uploaded = await debugArtifacts.uploadDebugArtifacts(
const uploaded = await debugArtifacts.uploadArtifacts(
logger,
["hucairz"],
"i-dont-exist",
"artifactName",
GitHubVariant.DOTCOM,
undefined,
);
t.is(
uploaded,
@@ -103,44 +96,3 @@ test("uploadDebugArtifacts when no codeql version is used should invoke artifact
);
});
});
test("uploadDebugArtifacts when new codeql version is used should invoke artifact upload", async (t) => {
// Test that the artifact is uploaded.
const logger = getActionsLogger();
await t.notThrowsAsync(async () => {
const uploaded = await debugArtifacts.uploadDebugArtifacts(
logger,
["hucairz"],
"i-dont-exist",
"artifactName",
GitHubVariant.DOTCOM,
"2.20.3",
);
t.is(
uploaded,
// The failure is expected since we don't want to actually upload any artifacts in unit tests.
"upload-failed",
"Expect failure to upload artifacts since root dir does not exist",
);
});
});
test("uploadDebugArtifacts when old codeql is used should avoid trying to upload artifacts", async (t) => {
// Test that the artifact is not uploaded.
const logger = getActionsLogger();
await t.notThrowsAsync(async () => {
const uploaded = await debugArtifacts.uploadDebugArtifacts(
logger,
["hucairz"],
"i-dont-exist",
"artifactName",
GitHubVariant.DOTCOM,
"2.20.2",
);
t.is(
uploaded,
"upload-not-supported",
"Expected artifact upload to be blocked because of old CodeQL version",
);
});
});
+15 -38
View File
@@ -10,15 +10,11 @@ import { getOptionalInput, getTemporaryDirectory } from "./actions-util";
import { dbIsFinalized } from "./analyze";
import { scanArtifactsForTokens } from "./artifact-scanner";
import { type CodeQL } from "./codeql";
import { Config } from "./config-utils";
import { EnvVar } from "./environment";
import type { ActionConfig } from "./config/action-config";
import { EnvVar, exportEnvVar } from "./environment";
import * as json from "./json";
import { Language } from "./languages";
import { Logger, withGroup } from "./logging";
import {
isSafeArtifactUpload,
SafeArtifactUploadVersion,
} from "./tools-features";
import {
bundleDb,
doesDirectoryExist,
@@ -40,7 +36,6 @@ export function sanitizeArtifactName(name: string): string {
export async function uploadCombinedSarifArtifacts(
logger: Logger,
gitHubVariant: GitHubVariant,
codeQlVersion: string | undefined,
) {
const tempDir = getTemporaryDirectory();
@@ -70,13 +65,12 @@ export async function uploadCombinedSarifArtifacts(
}
try {
await uploadDebugArtifacts(
await uploadArtifacts(
logger,
toUpload,
baseTempDir,
"combined-sarif-artifacts",
gitHubVariant,
codeQlVersion,
);
} catch (e) {
logger.warning(
@@ -95,7 +89,7 @@ export async function uploadCombinedSarifArtifacts(
* @return The path to that debug artifact, or undefined if an error occurs.
*/
function tryPrepareSarifDebugArtifact(
config: Config,
config: ActionConfig<"dbLocation">,
language: Language,
logger: Logger,
): string | undefined {
@@ -137,7 +131,7 @@ function tryPrepareSarifDebugArtifact(
*/
async function tryBundleDatabase(
codeql: CodeQL,
config: Config,
config: ActionConfig<"dbLocation">,
language: Language,
logger: Logger,
): Promise<string | undefined> {
@@ -163,6 +157,11 @@ async function tryBundleDatabase(
}
}
/** The minimal configuration needed for `tryUploadAllAvailableDebugArtifacts`. */
export type UploadDebugArtifactsConfig = ActionConfig<
"languages" | "gitHubVersion" | "dbLocation" | "debugArtifactName"
>;
/**
* Attempt to upload all available debug artifacts.
*
@@ -170,9 +169,8 @@ async function tryBundleDatabase(
*/
export async function tryUploadAllAvailableDebugArtifacts(
codeql: CodeQL,
config: Config,
config: UploadDebugArtifactsConfig,
logger: Logger,
codeQlVersion: string | undefined,
) {
const filesToUpload: string[] = [];
try {
@@ -231,13 +229,12 @@ export async function tryUploadAllAvailableDebugArtifacts(
try {
await withGroup("Uploading debug artifacts", async () =>
uploadDebugArtifacts(
uploadArtifacts(
logger,
filesToUpload,
config.dbLocation,
config.debugArtifactName,
config.gitHubVersion.type,
codeQlVersion,
),
);
} catch (e) {
@@ -283,26 +280,6 @@ export type UploadArtifactsResult =
| "upload-successful"
| "upload-failed";
export async function uploadDebugArtifacts(
logger: Logger,
toUpload: string[],
rootDir: string,
artifactName: string,
ghVariant: GitHubVariant,
codeQlVersion: string | undefined,
): Promise<UploadArtifactsResult | "upload-not-supported"> {
const uploadSupported = isSafeArtifactUpload(codeQlVersion);
if (!uploadSupported) {
core.info(
`Skipping debug artifact upload because the current CLI does not support safe upload. Please upgrade to CLI v${SafeArtifactUploadVersion} or later.`,
);
return "upload-not-supported";
}
return uploadArtifacts(logger, toUpload, rootDir, artifactName, ghVariant);
}
/**
* Uploads the specified files as a single workflow artifact.
*
@@ -330,7 +307,7 @@ export async function uploadArtifacts(
// some issues early.
if (isInTestMode()) {
await scanArtifactsForTokens(toUpload, logger);
core.exportVariable("CODEQL_ACTION_ARTIFACT_SCAN_FINISHED", "true");
exportEnvVar("CODEQL_ACTION_ARTIFACT_SCAN_FINISHED", "true");
}
const suffix = getArtifactSuffix(getOptionalInput("matrix"));
@@ -381,7 +358,7 @@ export async function getArtifactUploaderClient(
* all files in the database folder and return the path.
*/
async function createPartialDatabaseBundle(
config: Config,
config: ActionConfig<"dbLocation">,
language: Language,
): Promise<string> {
const databasePath = getCodeQLDatabasePath(config, language);
@@ -422,7 +399,7 @@ async function createPartialDatabaseBundle(
*/
async function createDatabaseBundleCli(
codeql: CodeQL,
config: Config,
config: ActionConfig<"dbLocation">,
language: Language,
): Promise<string> {
const databaseBundlePath = await bundleDb(
+4 -4
View File
@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.27.1",
"cliVersion": "2.27.1",
"priorBundleVersion": "codeql-bundle-v2.27.0",
"priorCliVersion": "2.27.0"
"bundleVersion": "codeql-bundle-v2.27.2",
"cliVersion": "2.27.2",
"priorBundleVersion": "codeql-bundle-v2.27.1",
"priorCliVersion": "2.27.1"
}
+1
View File
@@ -313,6 +313,7 @@ export async function downloadDependencyCaches(
const primaryKey = await cacheKey(codeql, features, language, patterns);
const restoreKeys: string[] = [
primaryKey,
await cachePrefix(codeql, features, language),
];
+12
View File
@@ -0,0 +1,12 @@
import test from "ava";
import { Env, ReadOnlyEnv } from "./environment";
import { getTestEnv, setupTests } from "./testing-utils";
setupTests(test);
test("isTestingEnv() is true", (t) => {
t.true(new ReadOnlyEnv(process.env).isTestingEnv());
t.true(new Env(process.env).isTestingEnv());
t.true(getTestEnv().isTestingEnv());
});
+98 -1
View File
@@ -1,3 +1,10 @@
import * as core from "@actions/core";
/**
* This constant is set in `ava.setup.mjs` for tests.
*/
declare const __CODEQL_ACTION_TEST_ENV__: string | undefined;
/**
* Environment variables used by Default Setup to communicate the private registry proxy configuration.
*/
@@ -211,8 +218,20 @@ export enum ActionsEnvVars {
RUNNER_TOOL_CACHE = "RUNNER_TOOL_CACHE",
}
/** Environment variables which are not specific to CodeQL. */
export enum SystemEnvVar {
/**
* Used by Node and related tools to indicate what kind of environment we are running in.
*/
NODE_ENV = "NODE_ENV",
}
/** A type representing all known environment variables. */
export type KnownEnvVar = EnvVar | ActionsEnvVars | RegistryProxyVars;
export type KnownEnvVar =
| EnvVar
| ActionsEnvVars
| RegistryProxyVars
| SystemEnvVar;
/**
* Gets an environment variable, but throws an error if it is not set.
@@ -289,6 +308,26 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
public entries(): Array<[string, T]> {
return Object.entries(this.vars);
}
/**
* Gets a value indicating whether we should skip uploads of
* all kinds (SARIF results, status reports, DBs, ...).
*
* This is not guaranteed to be set in all test environments.
*/
public isSkippingUploadsInTests(): boolean {
return this.getOptional(EnvVar.TEST_MODE) === "true";
}
/**
* Gets a value indicative of whether we are in a testing environment
* by testing whether the value of the `NODE_ENV` variable is "test".
* This is expected to be the case if e.g. `ava` is running the tests
* or if this instance was constructed by `getTestEnv`.
*/
public isTestingEnv(): boolean {
return __CODEQL_ACTION_TEST_ENV__ === "unit-test";
}
}
/**
@@ -307,6 +346,33 @@ export class Env<
this.changed = true;
}
/** Sets all environment variables given by `vars`. */
public setAll(vars: Record<string, T>): void {
for (const [key, val] of Object.entries(vars)) {
this.set(key, val);
}
}
/**
* Wrapper around `core.exportVariable` which does not call `core.exportVariable`
* when running unit tests. This is important, because otherwise `core.exportVariable`
* sets environment variables for other steps in a workflow when we run unit tests in CI.
*
* @param name The name of the environment variable to set and export.
* @param val The value to set and export for the environment variable.
*/
public export(name: string, val: T): void {
// Setting the environment variable for this instance is always OK, including
// in tests, since we use fresh `Env` instances whenever needed. This allows
// tests to pass that rely on that part of the `core.exportVariable` behaviour.
this.set(name, val);
// Call `core.exportVariable` whenever we are not in a test environment.
if (!this.isTestingEnv()) {
core.exportVariable(name, val);
}
}
/** Gets a value indicating whether `set` was called at least once. */
public hasChanged(): boolean {
return this.changed;
@@ -317,3 +383,34 @@ export class Env<
export function getEnv(env: NodeJS.ProcessEnv = process.env): Env {
return new Env(env);
}
/**
* Returns whether we are in test mode. This is used by CodeQL Action PR checks.
*
* In test mode, we skip several uploads (SARIF results, status reports, DBs, ...).
*
* @deprecated
* The purpose of this function is ambiguous. Use `isSkippingUploadsInTests` on
* a `ReadOnlyEnv` instance instead for equivalent behaviour. Use `isTestingEnv`
* to determine if we are running in a unit test.
*/
export function isInTestMode(): boolean {
return getEnv().isSkippingUploadsInTests();
}
/**
* Wrapper around `core.exportVariable` which does not call `core.exportVariable`
* when running unit tests. This is important, because otherwise `core.exportVariable`
* sets environment variables for other steps in a workflow when we run unit tests in CI.
*
* @deprecated Use `export` on an `Env` instance instead.
*/
export function exportEnvVar(name: string, val: any): void {
const env = getEnv();
if (typeof val === "string") {
env.export(name, val);
} else {
env.export(name, JSON.stringify(val));
}
}
+1 -1
View File
@@ -240,7 +240,7 @@ export const featureConfig = {
defaultValue: false,
envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES",
legacyApi: true,
minimumVersion: "2.15.0",
minimumVersion: undefined,
},
[Feature.CsharpCacheBuildModeNone]: {
defaultValue: false,
+109 -66
View File
@@ -7,35 +7,42 @@ import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { ActionsEnvVars, EnvVar } from "./environment";
import * as gitUtils from "./git-utils";
import { setupActionsVars, setupTests } from "./testing-utils";
import { getTestEnv, setupActionsVars, setupTests } from "./testing-utils";
import { withTmpDir } from "./util";
setupTests(test);
test.serial("getRef() throws on the empty string", async (t) => {
process.env["GITHUB_REF"] = "";
await t.throwsAsync(gitUtils.getRef);
test("getRef() throws on the empty string", async (t) => {
const env = getTestEnv({ [ActionsEnvVars.GITHUB_REF]: "" });
await t.throwsAsync(() => gitUtils.getRef(env, ""));
});
test.serial(
"getRef() returns merge PR ref if GITHUB_SHA still checked out",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const expectedRef = "refs/pull/1/merge";
const currentSha = "a".repeat(40);
process.env["GITHUB_REF"] = expectedRef;
process.env["GITHUB_SHA"] = currentSha;
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
env.setAll({
[ActionsEnvVars.GITHUB_REF]: expectedRef,
[ActionsEnvVars.GITHUB_SHA]: currentSha,
});
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs(sinon.match.string, "HEAD").resolves(currentSha);
callback
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
.resolves(currentSha);
const actualRef = await gitUtils.getRef();
const actualRef = await gitUtils.getRef(env, tmpDir);
t.deepEqual(actualRef, expectedRef);
t.is(callback.callCount, 1);
t.true(callback.calledOnceWith(tmpDir, "HEAD"));
t.true(callback.calledOnceWith(env, tmpDir, "HEAD"));
});
},
);
@@ -44,24 +51,33 @@ test.serial(
"getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)",
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(40);
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
env.setAll({
[ActionsEnvVars.GITHUB_REF]: expectedRef,
[ActionsEnvVars.GITHUB_SHA]: "b".repeat(40),
});
const sha = "a".repeat(40);
const callback = sinon.stub(gitUtils, "getCommitOid");
callback
.withArgs(sinon.match.string, "refs/remotes/pull/1/merge")
.withArgs(
sinon.match.any,
sinon.match.string,
"refs/remotes/pull/1/merge",
)
.resolves(sha);
callback
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
.resolves(sha);
callback.withArgs(sinon.match.any, "HEAD").resolves(sha);
const actualRef = await gitUtils.getRef();
const actualRef = await gitUtils.getRef(env, tmpDir);
t.deepEqual(actualRef, expectedRef);
t.is(callback.callCount, 2);
t.true(callback.calledWith(tmpDir, "HEAD"));
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
t.true(callback.calledWith(env, tmpDir, "HEAD"));
t.true(callback.calledWith(env, tmpDir, "refs/remotes/pull/1/merge"));
});
},
);
@@ -70,23 +86,28 @@ test.serial(
"getRef() returns head PR ref if GITHUB_REF no longer checked out",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
process.env["GITHUB_REF"] = "refs/pull/1/merge";
process.env["GITHUB_SHA"] = "a".repeat(40);
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
env.setAll({
[ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge",
[ActionsEnvVars.GITHUB_SHA]: "a".repeat(40),
});
const callback = sinon.stub(gitUtils, "getCommitOid");
callback
.withArgs(tmpDir, "refs/remotes/pull/1/merge")
.withArgs(sinon.match.any, tmpDir, "refs/remotes/pull/1/merge")
.resolves("a".repeat(40));
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40));
callback
.withArgs(sinon.match.any, tmpDir, "HEAD")
.resolves("b".repeat(40));
callback.throws(new Error("Unexpected getCommitOid call in test."));
const actualRef = await gitUtils.getRef();
const actualRef = await gitUtils.getRef(env, tmpDir);
t.deepEqual(actualRef, "refs/pull/1/head");
t.is(callback.callCount, 2);
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
t.true(callback.calledWith(tmpDir, "HEAD"));
t.true(callback.calledWith(env, tmpDir, "refs/remotes/pull/1/merge"));
t.true(callback.calledWith(env, tmpDir, "HEAD"));
});
},
);
@@ -95,7 +116,6 @@ test.serial(
"getRef() returns ref provided as an input and ignores current HEAD",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const getAdditionalInputStub = sinon.stub(
actionsUtil,
"getOptionalInput",
@@ -104,14 +124,22 @@ test.serial(
getAdditionalInputStub.withArgs("sha").resolves("b".repeat(40));
// These values are be ignored
process.env["GITHUB_REF"] = "refs/pull/1/merge";
process.env["GITHUB_SHA"] = "a".repeat(40);
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
env.setAll({
[ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge",
[ActionsEnvVars.GITHUB_SHA]: "a".repeat(40),
});
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("b".repeat(40));
callback.withArgs(sinon.match.any, "HEAD").resolves("b".repeat(40));
callback
.withArgs(sinon.match.any, tmpDir, "refs/pull/1/merge")
.resolves("b".repeat(40));
callback
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
.resolves("b".repeat(40));
const actualRef = await gitUtils.getRef();
const actualRef = await gitUtils.getRef(env, tmpDir);
t.deepEqual(actualRef, "refs/pull/2/merge");
// getCommitOid shouldn't be called, because the ref should be taken from the input
@@ -124,14 +152,17 @@ test.serial(
"getRef() returns CODE_SCANNING_REF as a fallback for GITHUB_REF",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const expectedRef = "refs/pull/1/HEAD";
const currentSha = "a".repeat(40);
process.env["CODE_SCANNING_REF"] = expectedRef;
process.env["GITHUB_REF"] = "";
process.env["GITHUB_SHA"] = currentSha;
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
env.setAll({
[EnvVar.CODE_SCANNING_REF]: expectedRef,
[ActionsEnvVars.GITHUB_REF]: "",
[ActionsEnvVars.GITHUB_SHA]: currentSha,
});
const actualRef = await gitUtils.getRef();
const actualRef = await gitUtils.getRef(env, tmpDir);
t.deepEqual(actualRef, expectedRef);
});
},
@@ -141,14 +172,17 @@ test.serial(
"getRef() returns GITHUB_REF over CODE_SCANNING_REF if both are provided",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const expectedRef = "refs/pull/1/merge";
const currentSha = "a".repeat(40);
process.env["CODE_SCANNING_REF"] = "refs/pull/1/HEAD";
process.env["GITHUB_REF"] = expectedRef;
process.env["GITHUB_SHA"] = currentSha;
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
env.setAll({
[EnvVar.CODE_SCANNING_REF]: "refs/pull/1/HEAD",
[ActionsEnvVars.GITHUB_REF]: expectedRef,
[ActionsEnvVars.GITHUB_SHA]: currentSha,
});
const actualRef = await gitUtils.getRef();
const actualRef = await gitUtils.getRef(env, tmpDir);
t.deepEqual(actualRef, expectedRef);
});
},
@@ -158,7 +192,9 @@ test.serial(
"getRef() throws an error if only `ref` is provided as an input",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
const getAdditionalInputStub = sinon.stub(
actionsUtil,
"getOptionalInput",
@@ -167,7 +203,7 @@ test.serial(
await t.throwsAsync(
async () => {
await gitUtils.getRef();
await gitUtils.getRef(env, tmpDir);
},
{
instanceOf: Error,
@@ -183,8 +219,12 @@ test.serial(
"getRef() throws an error if only `sha` is provided as an input",
async (t) => {
await withTmpDir(async (tmpDir: string) => {
setupActionsVars(tmpDir, tmpDir);
process.env["GITHUB_WORKSPACE"] = "/tmp";
const env = getTestEnv();
setupActionsVars(tmpDir, tmpDir, {}, env);
env.setAll({
[ActionsEnvVars.GITHUB_WORKSPACE]: "/tmp",
});
const getAdditionalInputStub = sinon.stub(
actionsUtil,
"getOptionalInput",
@@ -193,7 +233,7 @@ test.serial(
await t.throwsAsync(
async () => {
await gitUtils.getRef();
await gitUtils.getRef(env, tmpDir);
},
{
instanceOf: Error,
@@ -206,13 +246,16 @@ test.serial(
);
test.serial("isAnalyzingDefaultBranch()", async (t) => {
process.env["GITHUB_EVENT_NAME"] = "push";
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "true";
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "false";
const env = getTestEnv({
[ActionsEnvVars.GITHUB_EVENT_NAME]: "push",
CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH: "true",
});
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, ""), true);
env.set("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH", "false");
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
setupActionsVars(tmpDir, tmpDir, {}, env);
const envFile = path.join(tmpDir, "event.json");
fs.writeFileSync(
envFile,
@@ -222,17 +265,17 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
},
}),
);
process.env["GITHUB_EVENT_PATH"] = envFile;
env.set(ActionsEnvVars.GITHUB_EVENT_PATH, envFile);
process.env["GITHUB_REF"] = "main";
process.env["GITHUB_SHA"] = "1234";
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
env.set(ActionsEnvVars.GITHUB_REF, "main");
env.set(ActionsEnvVars.GITHUB_SHA, "1234");
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
process.env["GITHUB_REF"] = "refs/heads/main";
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
process.env["GITHUB_REF"] = "feature";
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
env.set(ActionsEnvVars.GITHUB_REF, "feature");
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false);
fs.writeFileSync(
envFile,
@@ -240,9 +283,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
schedule: "0 0 * * *",
}),
);
process.env["GITHUB_EVENT_NAME"] = "schedule";
process.env["GITHUB_REF"] = "refs/heads/main";
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule");
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
getAdditionalInputStub
@@ -251,9 +294,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
getAdditionalInputStub
.withArgs("sha")
.resolves("0000000000000000000000000000000000000000");
process.env["GITHUB_EVENT_NAME"] = "schedule";
process.env["GITHUB_REF"] = "refs/heads/main";
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule");
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false);
});
});
+28 -19
View File
@@ -13,6 +13,7 @@ import {
getWorkflowEvent,
getWorkflowEventName,
} from "./actions-util";
import { ActionsEnvVars, EnvVar, type ReadOnlyEnv } from "./environment";
import { ConfigurationError, getRequiredEnvParam } from "./util";
/**
@@ -101,6 +102,7 @@ export const runGitCommand = async function (
* Gets the SHA of the commit that is currently checked out.
*/
export const getCommitOid = async function (
env: ReadOnlyEnv,
checkoutPath: string,
ref = "HEAD",
): Promise<string> {
@@ -119,7 +121,9 @@ export const getCommitOid = async function (
);
return stdout.trim();
} catch {
return getOptionalInput("sha") || getRequiredEnvParam("GITHUB_SHA");
return (
getOptionalInput("sha") || env.getRequired(ActionsEnvVars.GITHUB_SHA)
);
}
};
@@ -241,7 +245,7 @@ export const getGitRoot = async function (
["rev-parse", "--show-toplevel"],
`Cannot find Git repository root from the source root ${sourceRoot}.`,
);
return stdout.trim();
return path.resolve(stdout.trim());
} catch {
// Errors are already logged by runGitCommand()
return undefined;
@@ -314,18 +318,18 @@ export const getFileOidsUnderPath = async function (
return fileOidMap;
};
function getRefFromEnv(): string {
function getRefFromEnv(env: ReadOnlyEnv): string {
// To workaround a limitation of Actions dynamic workflows not setting
// the GITHUB_REF in some cases, we accept also the ref within the
// CODE_SCANNING_REF variable. When possible, however, we prefer to use
// the GITHUB_REF as that is a protected variable and cannot be overwritten.
let refEnv: string;
try {
refEnv = getRequiredEnvParam("GITHUB_REF");
refEnv = env.getRequired(ActionsEnvVars.GITHUB_REF);
} catch (e) {
// If the GITHUB_REF is not set, we try to rescue by getting the
// CODE_SCANNING_REF.
const maybeRef = process.env["CODE_SCANNING_REF"];
const maybeRef = env.getOptional(EnvVar.CODE_SCANNING_REF);
if (maybeRef === undefined || maybeRef.length === 0) {
throw e;
}
@@ -337,15 +341,16 @@ function getRefFromEnv(): string {
/**
* Get the ref currently being analyzed.
*/
export async function getRef(): Promise<string> {
export async function getRef(
env: ReadOnlyEnv,
checkoutPath: string | undefined,
): Promise<string> {
// Will be in the form "refs/heads/master" on a push event
// or in the form "refs/pull/N/merge" on a pull_request event
const refInput = getOptionalInput("ref");
const shaInput = getOptionalInput("sha");
const checkoutPath =
getOptionalInput("checkout_path") ||
getOptionalInput("source-root") ||
getRequiredEnvParam("GITHUB_WORKSPACE");
checkoutPath =
checkoutPath ?? env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE);
const hasRefInput = !!refInput;
const hasShaInput = !!shaInput;
@@ -356,8 +361,8 @@ export async function getRef(): Promise<string> {
);
}
const ref = refInput || getRefFromEnv();
const sha = shaInput || getRequiredEnvParam("GITHUB_SHA");
const ref = refInput || getRefFromEnv(env);
const sha = shaInput || env.getRequired(ActionsEnvVars.GITHUB_SHA);
// If the ref is a user-provided input, we have to skip logic
// and assume that it is really where they want to upload the results.
@@ -374,7 +379,7 @@ export async function getRef(): Promise<string> {
return ref;
}
const head = await getCommitOid(checkoutPath, "HEAD");
const head = await getCommitOid(env, checkoutPath, "HEAD");
// in actions/checkout@v2+ we can check if git rev-parse HEAD == GITHUB_SHA
// in actions/checkout@v1 this may not be true as it checks out the repository
@@ -384,6 +389,7 @@ export async function getRef(): Promise<string> {
const hasChangedRef =
sha !== head &&
(await getCommitOid(
env,
checkoutPath,
ref.replace(/^refs\/pull\//, "refs/remotes/pull/"),
)) !== head;
@@ -410,20 +416,23 @@ function removeRefsHeadsPrefix(ref: string): string {
* environment variable can be set in cases where repository information might not be available, for
* example dynamic workflows.
*/
export async function isAnalyzingDefaultBranch(): Promise<boolean> {
if (process.env.CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH === "true") {
export async function isAnalyzingDefaultBranch(
env: ReadOnlyEnv,
checkoutPath: string | undefined,
): Promise<boolean> {
if (env.getOptional("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH") === "true") {
return true;
}
// Get the current ref and trim and refs/heads/ prefix
let currentRef = await getRef();
let currentRef = await getRef(env, checkoutPath);
currentRef = removeRefsHeadsPrefix(currentRef);
const event = getWorkflowEvent();
const event = getWorkflowEvent(env);
let defaultBranch = event?.repository?.default_branch;
if (getWorkflowEventName() === "schedule") {
defaultBranch = removeRefsHeadsPrefix(getRefFromEnv());
if (getWorkflowEventName(env) === "schedule") {
defaultBranch = removeRefsHeadsPrefix(getRefFromEnv(env));
}
return currentRef === defaultBranch;
+43 -22
View File
@@ -7,6 +7,11 @@ import * as actionsUtil from "./actions-util";
import { CodeScanning, RiskAssessment } from "./analyses";
import { getApiClient, getGitHubVersion } from "./api-client";
import { CodeQL, getCodeQL } from "./codeql";
import {
hasConfigKey,
type AbortedInitConfig,
type ActionConfig,
} from "./config/action-config";
import {
Config,
isCodeScanningEnabled,
@@ -16,6 +21,7 @@ import {
getArtifactSuffix,
getArtifactUploaderClient,
sanitizeArtifactName,
type UploadDebugArtifactsConfig,
} from "./debug-artifacts";
import * as dependencyCaching from "./dependency-caching";
import { EnvVar, ReadOnlyEnv } from "./environment";
@@ -44,7 +50,7 @@ import {
} from "./util";
import {
getCategoryInputOrThrow,
getCheckoutPathInputOrThrow,
getRepositoryRootOrThrow,
getUploadInputOrThrow,
getWorkflow,
} from "./workflow";
@@ -86,6 +92,11 @@ interface FailedSarifInfo {
checkoutPath: string;
}
/** The minimal Action configuration required to prepare and upload a failed SARIF. */
export type FailedSarifConfig = ActionConfig<
"codeQLCmd" | "tempDir" | "analysisKinds" | "languages"
>;
/**
* Tries to prepare a SARIF file that contains information about a failed analysis.
*
@@ -94,7 +105,7 @@ interface FailedSarifInfo {
async function prepareFailedSarif(
logger: Logger,
features: FeatureEnablement,
config: Config,
config: FailedSarifConfig,
): Promise<Result<FailedSarifInfo, UploadFailedSarifResult>> {
if (!config.codeQLCmd) {
return new Failure({
@@ -144,7 +155,15 @@ async function prepareFailedSarif(
});
}
const category = getCategoryInputOrThrow(workflow, jobName, matrix);
const checkoutPath = getCheckoutPathInputOrThrow(workflow, jobName, matrix);
// Try to determine the path at which the repository that we failed to analyse is checked out at.
// We need this to relativise the paths in the SARIF.
const checkoutPath = getRepositoryRootOrThrow(
workflow,
jobName,
matrix,
config,
);
const result = await generateFailedSarif(
logger,
@@ -160,7 +179,7 @@ async function prepareFailedSarif(
async function generateFailedSarif(
logger: Logger,
features: FeatureEnablement,
config: Config,
config: ActionConfig<"codeQLCmd" | "tempDir">,
category: string | undefined,
checkoutPath: string,
sarifFile?: string,
@@ -192,7 +211,7 @@ async function generateFailedSarif(
* category for the workflow.
*/
async function maybeUploadFailedSarif(
config: Config,
config: FailedSarifConfig,
repositoryNwo: RepositoryNwo,
features: FeatureEnablement,
logger: Logger,
@@ -227,7 +246,7 @@ async function maybeUploadFailedSarif(
/** Uploads a failed SARIF file as workflow artifact, if it can be generated. */
async function maybeUploadFailedSarifArtifact(
config: Config,
config: FailedSarifConfig,
features: FeatureEnablement,
logger: Logger,
): Promise<UploadFailedSarifResult> {
@@ -267,7 +286,7 @@ async function maybeUploadFailedSarifArtifact(
* @returns The results of uploading the SARIF file for the failure.
*/
export async function tryUploadSarifIfRunFailed(
config: Config,
config: FailedSarifConfig,
repositoryNwo: RepositoryNwo,
features: FeatureEnablement,
logger: Logger,
@@ -324,13 +343,12 @@ export async function tryUploadSarifIfRunFailed(
export async function uploadFailureInfo(
uploadAllAvailableDebugArtifacts: (
codeql: CodeQL,
config: Config,
config: UploadDebugArtifactsConfig,
logger: Logger,
codeQlVersion: string,
) => Promise<void>,
printDebugLogs: (config: Config) => Promise<void>,
printDebugLogs: (logger: Logger, config: Partial<Config>) => Promise<void>,
codeql: CodeQL,
config: Config,
config: Config | AbortedInitConfig,
repositoryNwo: RepositoryNwo,
features: FeatureEnablement,
jobStatus: string | undefined,
@@ -377,21 +395,15 @@ export async function uploadFailureInfo(
}
// Upload appropriate Actions artifacts for debugging
if (config.debugMode) {
if (config.debugMode && hasConfigKey(config, "dbLocation")) {
logger.info(
"Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts...",
);
const version = await codeql.getVersion();
await uploadAllAvailableDebugArtifacts(
codeql,
config,
logger,
version.version,
);
await printDebugLogs(config);
await uploadAllAvailableDebugArtifacts(codeql, config, logger);
await printDebugLogs(logger, config);
}
if (actionsUtil.isSelfHostedRunner()) {
if (actionsUtil.isSelfHostedRunner() && config.dbLocation) {
try {
fs.rmSync(config.dbLocation, {
recursive: true,
@@ -406,6 +418,10 @@ export async function uploadFailureInfo(
`Failed to clean up database cluster directory ${config.dbLocation}. Details: ${e}`,
);
}
} else if (config.dbLocation === undefined) {
logger.debug(
"Skipping cleanup of database cluster directory since the configuration does not include a database path.",
);
} else {
logger.debug(
"Skipping cleanup of database cluster directory since we are running on a GitHub-hosted " +
@@ -454,7 +470,7 @@ function isConclusiveJobStatus(jobStatus: string | undefined): boolean {
*/
async function recordOverlayStatus(
codeql: CodeQL,
config: Config,
config: Partial<Config>,
features: FeatureEnablement,
jobStatus: string | undefined,
env: ReadOnlyEnv,
@@ -466,6 +482,11 @@ async function recordOverlayStatus(
!(await features.getValue(Feature.OverlayAnalysisStatusSave))
) {
return;
} else if (config.languages === undefined) {
logger.debug(
"Not recording overlay status, because the configuration contains no 'languages'.",
);
return;
}
// Only record a failure when the job outcome tells us something about the analysis. A cancelled
+50 -35
View File
@@ -6,6 +6,7 @@
import * as core from "@actions/core";
import { runInActions, type Action, type ActionState } from "./action-common";
import {
restoreInputs,
getOptionalInput,
@@ -15,22 +16,25 @@ import {
import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
import { CachingKind } from "./caching-utils";
import { getCodeQL } from "./codeql";
import { type Config, getConfig } from "./config-utils";
import {
hasConfigKey,
type AbortedInitConfig,
type Config,
} from "./config/action-config";
import { getAbortedInitConfig, getConfig } from "./config-utils";
import * as debugArtifacts from "./debug-artifacts";
import {
DependencyCachingUsageReport,
getDependencyCacheUsage,
} from "./dependency-caching";
import { EnvVar, getEnv } from "./environment";
import { EnvVar, type ReadOnlyEnv } from "./environment";
import { initFeatures } from "./feature-flags";
import * as gitUtils from "./git-utils";
import * as initActionPostHelper from "./init-action-post-helper";
import { getActionsLogger } from "./logging";
import { getRepositoryNwo } from "./repository";
import {
StatusReportBase,
sendStatusReport,
sendUnhandledErrorStatusReport,
createStatusReportBase,
getActionsStatus,
ActionName,
@@ -45,16 +49,17 @@ interface InitPostStatusReport
initActionPostHelper.JobStatusReport,
initActionPostHelper.DependencyCachingUsageReport {}
async function run(startedAt: Date) {
async function run(action: ActionState<["Base", "Logger", "Env"]>) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
const logger = action.logger;
const logger = getActionsLogger();
let config: Config | undefined;
let config: Config | AbortedInitConfig | undefined;
let uploadFailedSarifResult:
| initActionPostHelper.UploadFailedSarifResult
| undefined;
let dependencyCachingUsage: DependencyCachingUsageReport | undefined;
try {
// Read the job status before restoring inputs, since it is provided by the Actions runtime
// environment for this step and would otherwise be overwritten by the value that the `init`
@@ -64,7 +69,15 @@ async function run(startedAt: Date) {
// Restore inputs from `init` Action.
restoreInputs(logger);
config = await getConfig(getTemporaryDirectory(), logger);
const tmpDir = getTemporaryDirectory();
config = await getConfig(tmpDir, logger);
// If we weren't able to read the normal configuration file, the `init` step failed before it could write it.
// Try to load the "failed" configuration file instead which may have been written by the `init` step
// with a minimal configuration for this post-action.
if (config === undefined) {
config = getAbortedInitConfig(action, tmpDir);
}
const gitHubVersion = config?.gitHubVersion ?? (await getGitHubVersion());
checkGitHubVersionInRange(gitHubVersion, logger);
@@ -92,7 +105,7 @@ async function run(startedAt: Date) {
repositoryNwo,
features,
jobStatus,
getEnv(),
action.env,
logger,
);
@@ -101,7 +114,11 @@ async function run(startedAt: Date) {
// do this under these circumstances to avoid slowing down analyses for PRs
// and where caching may not be enabled.
if (
(await gitUtils.isAnalyzingDefaultBranch()) &&
(await gitUtils.isAnalyzingDefaultBranch(
action.env,
config.repositoryRoot,
)) &&
hasConfigKey(config, "dependencyCachingEnabled") &&
config.dependencyCachingEnabled !== CachingKind.None
) {
dependencyCachingUsage = await getDependencyCacheUsage(logger);
@@ -112,9 +129,9 @@ async function run(startedAt: Date) {
core.setFailed(error.message);
const statusReportBase = await createStatusReportBase(
ActionName.InitPost,
action.name,
getActionsStatus(error),
startedAt,
action.startedAt,
config,
await checkDiskUsage(logger),
logger,
@@ -126,13 +143,13 @@ async function run(startedAt: Date) {
}
return;
}
const jobStatus = getFinalJobStatus(config);
const jobStatus = getFinalJobStatus(action, config);
logger.info(`CodeQL job status was ${getJobStatusDisplayName(jobStatus)}.`);
const statusReportBase = await createStatusReportBase(
ActionName.InitPost,
action.name,
"success",
startedAt,
action.startedAt,
config,
await checkDiskUsage(logger),
logger,
@@ -157,16 +174,21 @@ async function run(startedAt: Date) {
* Otherwise, we determine the job status based on whether the analyze step
* completed successfully and whether we have a valid CodeQL config.
*/
function getFinalJobStatus(config: Config | undefined): JobStatus {
const existingJobStatus = getJobStatusFromEnvironment();
function getFinalJobStatus(
action: ActionState<["Env"]>,
config: Partial<Config> | undefined,
): JobStatus {
const existingJobStatus = getJobStatusFromEnvironment(action.env);
if (existingJobStatus !== undefined) {
return existingJobStatus;
}
let jobStatus: JobStatus;
if (process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY] === "true") {
core.exportVariable(EnvVar.JOB_STATUS, JobStatus.SuccessStatus);
if (
action.env.getOptional(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY) === "true"
) {
action.env.export(EnvVar.JOB_STATUS, JobStatus.SuccessStatus);
jobStatus = JobStatus.SuccessStatus;
} else if (config !== undefined) {
// - We have computed a CodeQL config
@@ -191,7 +213,7 @@ function getFinalJobStatus(config: Config | undefined): JobStatus {
// This shouldn't be necessary, but in the odd case that we run more than one
// `init` post step, ensure the job status is consistent between them.
core.exportVariable(EnvVar.JOB_STATUS, jobStatus);
action.env.export(EnvVar.JOB_STATUS, jobStatus);
return jobStatus;
}
@@ -200,8 +222,8 @@ function getFinalJobStatus(config: Config | undefined): JobStatus {
*
* If the job status is invalid, return `UnknownStatus`.
*/
function getJobStatusFromEnvironment(): JobStatus | undefined {
const jobStatusFromEnvironment = process.env[EnvVar.JOB_STATUS];
function getJobStatusFromEnvironment(env: ReadOnlyEnv): JobStatus | undefined {
const jobStatusFromEnvironment = env.getOptional(EnvVar.JOB_STATUS);
if (jobStatusFromEnvironment !== undefined) {
// Validate the job status from the environment. If it is invalid, return unknown.
@@ -216,18 +238,11 @@ function getJobStatusFromEnvironment(): JobStatus | undefined {
return undefined;
}
const initPost: Action = {
name: ActionName.InitPost,
run,
};
export async function runWrapper() {
const startedAt = new Date();
const logger = getActionsLogger();
try {
await run(startedAt);
} catch (error) {
core.setFailed(`init post action failed: ${wrapError(error).message}`);
await sendUnhandledErrorStatusReport(
ActionName.InitPost,
startedAt,
error,
logger,
);
}
return await runInActions(initPost);
}
+39 -33
View File
@@ -5,7 +5,12 @@ import * as core from "@actions/core";
import * as io from "@actions/io";
import * as semver from "semver";
import { Action, ActionState, runInActions } from "./action-common";
import {
Action,
ActionState,
extendActionState,
runInActions,
} from "./action-common";
import {
FileCmdNotFoundError,
getActionVersion,
@@ -93,7 +98,6 @@ import {
checkActionVersion,
getErrorMessage,
BuildMode,
getOptionalEnvVar,
} from "./util";
import { checkWorkflow } from "./workflow";
@@ -253,7 +257,7 @@ async function run(
);
const repositoryProperties = repositoryPropertiesResult.orElse({});
core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true");
actionState.env.export(EnvVar.INIT_ACTION_HAS_RUN, "true");
// path.resolve() respects the intended semantics of source-root. If
// source-root is relative, it is relative to the GITHUB_WORKSPACE. If
@@ -278,7 +282,9 @@ async function run(
}
// Compute the value of the `config-file` input.
const actionStateWithFeatures = { ...actionState, features };
const actionStateWithFeatures = extendActionState(actionState, {
features,
});
configFile = await getConfigFileInput(
actionStateWithFeatures,
repositoryProperties,
@@ -349,7 +355,7 @@ async function run(
// to the workflow log. No exceptions are thrown.
await checkWorkflow(logger, codeql);
// Set CODEQL_ENABLE_EXPERIMENTAL_FEATURES for Rust if between 2.19.3 (included) and 2.22.1 (excluded)
// Set CODEQL_ENABLE_EXPERIMENTAL_FEATURES for Rust if the CLI is older than 2.22.1.
// We need to set this environment variable before initializing the config, otherwise Rust
// analysis will not be enabled (experimental language packs are only active with that environment
// variable set to `true`).
@@ -360,16 +366,10 @@ async function run(
.getRawLanguagesNoAutodetect(getOptionalInput("languages"))
.includes(BuiltInLanguage.rust)
) {
const experimental = "2.19.3";
const publicPreview = "2.22.1";
const actualVer = (await codeql.getVersion()).version;
if (semver.lt(actualVer, experimental)) {
throw new ConfigurationError(
`Rust analysis is supported by CodeQL CLI version ${experimental} or higher, but found version ${actualVer}`,
);
}
if (semver.lt(actualVer, publicPreview)) {
core.exportVariable(EnvVar.EXPERIMENTAL_FEATURES, "true");
actionState.env.export(EnvVar.EXPERIMENTAL_FEATURES, "true");
logger.info("Experimental Rust analysis enabled");
}
}
@@ -457,6 +457,18 @@ async function run(
await checkInstallPython311(config.languages, codeql);
} catch (unwrappedError) {
// If we have initialised the Action state, then try to save a subset of it for use by the `init-post` action
// so that it can try to create and upload additional diagnostic information.
if (config !== undefined) {
try {
configUtils.saveAbortedInitConfig(actionState, config);
} catch (configSaveError) {
logger.warning(
`Unable to save configuration for '${ActionName.InitPost}' step: ${getErrorMessage(configSaveError)}`,
);
}
}
const error = wrapError(unwrappedError);
core.setFailed(error.message);
const statusReportBase = await createStatusReportBase(
@@ -517,9 +529,9 @@ async function run(
}
// Forward Go flags
const goFlags = process.env["GOFLAGS"];
const goFlags = actionState.env.getOptional("GOFLAGS");
if (goFlags) {
core.exportVariable("GOFLAGS", goFlags);
actionState.env.export("GOFLAGS", goFlags);
core.warning(
"Passing the GOFLAGS env parameter to the init action is deprecated. Please move this to the analyze action.",
);
@@ -565,7 +577,7 @@ async function run(
// Store the original location of our wrapper script somewhere where we can
// later retrieve it from and cross-check that it hasn't been changed.
core.exportVariable(EnvVar.GO_BINARY_LOCATION, goWrapperPath);
actionState.env.export(EnvVar.GO_BINARY_LOCATION, goWrapperPath);
} catch (e) {
logger.warning(
`Analyzing Go on Linux, but failed to install wrapper script. Tracing custom builds may fail: ${e}`,
@@ -574,7 +586,7 @@ async function run(
} else {
// Store the location of the original Go binary, so we can check that no setup tasks were performed after the
// `init` Action ran.
core.exportVariable(EnvVar.GO_BINARY_LOCATION, goBinaryPath);
actionState.env.export(EnvVar.GO_BINARY_LOCATION, goBinaryPath);
}
} catch (e) {
logger.warning(
@@ -609,12 +621,12 @@ async function run(
// threads it would ask extractors to use. See help text for the "--ram" and "--threads"
// options at https://codeql.github.com/docs/codeql-cli/manual/database-trace-command/
// for details.
core.exportVariable(
actionState.env.export(
"CODEQL_RAM",
process.env["CODEQL_RAM"] ||
getCodeQLMemoryLimit(getOptionalInput("ram"), logger).toString(),
);
core.exportVariable(
actionState.env.export(
"CODEQL_THREADS",
process.env["CODEQL_THREADS"] ||
getThreadsFlagValue(getOptionalInput("threads"), logger).toString(),
@@ -622,21 +634,15 @@ async function run(
// Disable Kotlin extractor if feature flag set
if (await features.getValue(Feature.DisableKotlinAnalysisEnabled)) {
core.exportVariable("CODEQL_EXTRACTOR_JAVA_AGENT_DISABLE_KOTLIN", "true");
actionState.env.export(
"CODEQL_EXTRACTOR_JAVA_AGENT_DISABLE_KOTLIN",
"true",
);
}
// Emergency override to force the CodeQL CLI back to the JGit-based Git backend.
if (await features.getValue(Feature.ForceJGit)) {
core.exportVariable("CODEQL_GIT_BACKEND", "jgit");
}
const kotlinLimitVar =
"CODEQL_EXTRACTOR_KOTLIN_OVERRIDE_MAXIMUM_VERSION_LIMIT";
if (
(await codeQlVersionAtLeast(codeql, "2.20.3")) &&
!(await codeQlVersionAtLeast(codeql, "2.20.4"))
) {
core.exportVariable(kotlinLimitVar, "2.1.20");
actionState.env.export("CODEQL_GIT_BACKEND", "jgit");
}
// Restore dependency cache(s), if they exist.
@@ -685,7 +691,7 @@ async function run(
config.buildMode === BuildMode.None &&
config.languages.includes(BuiltInLanguage.java)
) {
core.exportVariable(
actionState.env.export(
EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS,
"true",
);
@@ -743,7 +749,7 @@ async function run(
const tracerConfig = await getCombinedTracerConfig(logger, codeql, config);
if (tracerConfig !== undefined) {
for (const [key, value] of Object.entries(tracerConfig.env)) {
core.exportVariable(key, value);
actionState.env.export(key, value);
}
}
@@ -751,10 +757,10 @@ async function run(
if (await features.getValue(Feature.JavaNetworkDebugging)) {
// Get the existing value of `JAVA_TOOL_OPTIONS`, if any.
const existingJavaToolOptions =
getOptionalEnvVar(JavaEnvVars.JAVA_TOOL_OPTIONS) || "";
actionState.env.getOptional(JavaEnvVars.JAVA_TOOL_OPTIONS) ?? "";
// Add the network debugging options.
core.exportVariable(
actionState.env.export(
JavaEnvVars.JAVA_TOOL_OPTIONS,
`${existingJavaToolOptions} -Djavax.net.debug=all`,
);
+16 -17
View File
@@ -1,14 +1,13 @@
import * as fs from "fs";
import path from "path";
import * as core from "@actions/core";
import * as github from "@actions/github";
import test, { ExecutionContext } from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { createStubCodeQL } from "./codeql";
import { ActionsEnvVars } from "./environment";
import * as environment from "./environment";
import { Feature } from "./feature-flags";
import {
checkPacksForOverlayCompatibility,
@@ -85,7 +84,7 @@ for (const { runnerEnv, ErrorConstructor, message } of [
`cleanupDatabaseClusterDirectory throws a ${ErrorConstructor.name} when cleanup fails on ${runnerEnv} runner`,
async (t) => {
await withTmpDir(async (tmpDir: string) => {
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnv;
process.env[environment.ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnv;
const dbLocation = path.resolve(tmpDir, "dbs");
fs.mkdirSync(dbLocation, { recursive: true });
@@ -546,7 +545,7 @@ test.serial(
test.serial(
"file coverage deprecation warning for org-owned repo with default setup recommends repo property",
(t) => {
const exportVariableStub = sinon.stub(core, "exportVariable");
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
github.context.payload = {
repository: {
@@ -566,14 +565,14 @@ test.serial(
'with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to ' +
"`true` in the repository's settings.",
);
t.true(exportVariableStub.calledOnce);
t.true(exportEnvVarStub.calledOnce);
},
);
test.serial(
"file coverage deprecation warning for org-owned repo with advanced setup recommends env var and repo property",
(t) => {
const exportVariableStub = sinon.stub(core, "exportVariable");
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
github.context.payload = {
repository: {
@@ -594,14 +593,14 @@ test.serial(
'with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to ' +
"`true` in the repository's settings.",
);
t.true(exportVariableStub.calledOnce);
t.true(exportEnvVarStub.calledOnce);
},
);
test.serial(
"file coverage deprecation warning for user-owned repo with default setup recommends advanced setup",
(t) => {
const exportVariableStub = sinon.stub(core, "exportVariable");
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
github.context.payload = {
repository: {
@@ -620,14 +619,14 @@ test.serial(
"To opt out of this change, switch to an advanced setup workflow and " +
"set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
);
t.true(exportVariableStub.calledOnce);
t.true(exportEnvVarStub.calledOnce);
},
);
test.serial(
"file coverage deprecation warning for user-owned repo with advanced setup recommends env var",
(t) => {
const exportVariableStub = sinon.stub(core, "exportVariable");
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
github.context.payload = {
repository: {
@@ -645,14 +644,14 @@ test.serial(
"to improve analysis performance. File coverage information will still be computed on non-PR analyses.\n\n" +
"To opt out of this change, set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
);
t.true(exportVariableStub.calledOnce);
t.true(exportEnvVarStub.calledOnce);
},
);
test.serial(
"file coverage deprecation warning for unknown owner type with default setup recommends advanced setup",
(t) => {
const exportVariableStub = sinon.stub(core, "exportVariable");
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
github.context.payload = { repository: undefined };
const messages: LoggedMessage[] = [];
@@ -666,14 +665,14 @@ test.serial(
"To opt out of this change, switch to an advanced setup workflow and " +
"set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
);
t.true(exportVariableStub.calledOnce);
t.true(exportEnvVarStub.calledOnce);
},
);
test.serial(
"file coverage deprecation warning for unknown owner type with advanced setup recommends env var",
(t) => {
const exportVariableStub = sinon.stub(core, "exportVariable");
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
github.context.payload = { repository: undefined };
const messages: LoggedMessage[] = [];
@@ -686,7 +685,7 @@ test.serial(
"to improve analysis performance. File coverage information will still be computed on non-PR analyses.\n\n" +
"To opt out of this change, set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
);
t.true(exportVariableStub.calledOnce);
t.true(exportEnvVarStub.calledOnce);
},
);
@@ -695,10 +694,10 @@ test.serial(
(t) => {
process.env["CODEQL_ACTION_DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION"] =
"true";
const exportVariableStub = sinon.stub(core, "exportVariable");
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
const messages: LoggedMessage[] = [];
logFileCoverageOnPrsDeprecationWarning(getRecordingLogger(messages));
t.is(messages.length, 0);
t.true(exportVariableStub.notCalled);
t.true(exportEnvVarStub.notCalled);
},
);
+2 -3
View File
@@ -1,7 +1,6 @@
import * as fs from "fs";
import * as path from "path";
import * as core from "@actions/core";
import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as github from "@actions/github";
import * as io from "@actions/io";
@@ -17,7 +16,7 @@ import {
import { GitHubApiDetails } from "./api-client";
import { CodeQL, setupCodeQL } from "./codeql";
import * as configUtils from "./config-utils";
import { EnvVar } from "./environment";
import { EnvVar, exportEnvVar } from "./environment";
import {
CodeQLDefaultVersionInfo,
Feature,
@@ -411,5 +410,5 @@ export function logFileCoverageOnPrsDeprecationWarning(logger: Logger): void {
}
logger.warning(message);
core.exportVariable(EnvVar.DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION, "true");
exportEnvVar(EnvVar.DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION, "true");
}
+2 -1
View File
@@ -8,6 +8,7 @@ import * as sinon from "sinon";
import * as actionsUtil from "../actions-util";
import * as apiClient from "../api-client";
import type { ResolveDatabaseOutput } from "../codeql";
import * as environment from "../environment";
import * as gitUtils from "../git-utils";
import { BuiltInLanguage } from "../languages";
import { getRunnerLogger } from "../logging";
@@ -82,7 +83,7 @@ const testDownloadOverlayBaseDatabaseFromCache = makeMacro({
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
sinon.stub(utils, "isInTestMode").returns(testCase.isInTestMode);
sinon.stub(environment, "isInTestMode").returns(testCase.isInTestMode);
if (testCase.restoreCacheResult instanceof Error) {
sinon
+3 -2
View File
@@ -15,6 +15,7 @@ import {
CleanupLevel,
getBaseDatabaseOidsFilePath,
getCodeQLDatabasePath,
getEnv,
getErrorMessage,
isInTestMode,
tryGetFolderBytes,
@@ -290,7 +291,7 @@ export async function downloadOverlayBaseDatabaseFromCache(
actionsCache.restoreCache(
[dbLocation],
cacheRestoreKeyPrefix,
undefined,
[cacheRestoreKeyPrefix],
{
// Azure SDK download (which is the default) uses 128MB segments; see
// https://github.com/actions/toolkit/blob/main/packages/cache/README.md.
@@ -377,7 +378,7 @@ export async function getCacheSaveKey(
`Failed to get workflow run ID or attempt ID. Reason: ${getErrorMessage(e)}`,
);
}
const sha = await getCommitOid(checkoutPath);
const sha = await getCommitOid(getEnv(), checkoutPath);
const restoreKeyPrefix = await getCacheRestoreKeyPrefix(
config,
codeQlVersion,
+1 -1
View File
@@ -130,7 +130,7 @@ export async function getOverlayStatus(
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
const foundKey = await waitForResultWithTimeLimit(
MAX_CACHE_OPERATION_MS,
actionsCache.restoreCache([statusFile], cacheKey),
actionsCache.restoreCache([statusFile], cacheKey, [cacheKey]),
() => {
logger.warning("Timed out restoring overlay status from cache.");
},
+8
View File
@@ -6,6 +6,14 @@ export interface RepositoryNwo {
repo: string;
}
/** An approximation of what a repository NWO string looks like. */
export type RepositoryNwoStr = `${string}/${string}`;
/** A basic check that `val` looks like a `RepositoryNwoStr`. */
export function isRepositoryNwoStr(val: string): val is RepositoryNwoStr {
return val.includes("/");
}
/**
* Get the repository name with owner from the environment variable
* `GITHUB_REPOSITORY`.
+10 -3
View File
@@ -1,6 +1,11 @@
import * as core from "@actions/core";
import { Action, ActionState, runInActions } from "./action-common";
import {
Action,
ActionState,
extendActionState,
runInActions,
} from "./action-common";
import {
getActionVersion,
getOptionalInput,
@@ -131,7 +136,9 @@ async function run(
);
const repositoryProperties = repositoryPropertiesResult.orElse({});
const actionStateWithFeatures = { ...actionState, features };
const actionStateWithFeatures = extendActionState(actionState, {
features,
});
const statusReportBase = await createStatusReportBase(
ActionName.SetupCodeQL,
@@ -184,7 +191,7 @@ async function run(
core.setOutput("codeql-path", codeql.getPath());
core.setOutput("codeql-version", (await codeql.getVersion()).version);
core.exportVariable(EnvVar.SETUP_CODEQL_ACTION_HAS_RUN, "true");
actionState.env.export(EnvVar.SETUP_CODEQL_ACTION_HAS_RUN, "true");
} catch (unwrappedError) {
const error = wrapError(unwrappedError);
core.setFailed(error.message);
-18
View File
@@ -126,24 +126,6 @@ test.serial("convert to semver", (t) => {
}
});
test.serial("getCodeQLActionRepository", (t) => {
const logger = getRunnerLogger(true);
initializeEnvironment("1.2.3");
// isRunningLocalAction() === true
delete process.env["GITHUB_ACTION_REPOSITORY"];
process.env["RUNNER_TEMP"] = path.dirname(__dirname);
const repoLocalRunner = setupCodeql.getCodeQLActionRepository(logger);
t.deepEqual(repoLocalRunner, "github/codeql-action");
// isRunningLocalAction() === false
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy";
const repoEnv = setupCodeql.getCodeQLActionRepository(logger);
t.deepEqual(repoEnv, "xxx/yyy");
});
test.serial(
"getCodeQLSource sets CLI version for a semver tagged bundle",
async (t) => {
+8 -79
View File
@@ -3,9 +3,7 @@ import { OutgoingHttpHeaders } from "http";
import * as path from "path";
import { performance } from "perf_hooks";
import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache";
import { default as deepEqual } from "fast-deep-equal";
import * as semver from "semver";
import { v4 as uuidV4 } from "uuid";
@@ -14,7 +12,6 @@ import {
isAnalyzingPullRequest,
isDynamicWorkflow,
isGitHubHostedRunner,
isRunningLocalAction,
} from "./actions-util";
import * as api from "./api-client";
import {
@@ -29,7 +26,7 @@ import {
makeDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
import { EnvVar, getEnv } from "./environment";
import { EnvVar, exportEnvVar, getEnv } from "./environment";
import {
CODEQL_VERSION_ZSTD_BUNDLE,
CodeQLDefaultVersionInfo,
@@ -45,6 +42,7 @@ import {
logPerLanguageBundleFallback,
} from "./per-language-bundles";
import { getBundlePlatform } from "./platform";
import { getCodeQLAssetDownloadURL } from "./setup/repository";
import * as tar from "./tar";
import {
deleteToolcacheBundles,
@@ -66,7 +64,6 @@ export enum ToolsSource {
Download = "DOWNLOAD",
}
const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
const CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing";
const CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies";
@@ -74,75 +71,6 @@ const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"];
const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"];
const CODEQL_TOOLCACHE_INPUT = "toolcache";
export function getCodeQLActionRepository(logger: Logger): string {
if (isRunningLocalAction()) {
// This handles the case where the Action does not come from an Action repository,
// e.g. our integration tests which use the Action code from the current checkout.
// In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set.
logger.info(
"The CodeQL Action is checked out locally. Using the default CodeQL Action repository.",
);
return CODEQL_DEFAULT_ACTION_REPOSITORY;
}
return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY");
}
async function getCodeQLBundleDownloadURL(
tagName: string,
apiDetails: api.GitHubApiDetails,
codeQLBundleName: string,
logger: Logger,
): Promise<string> {
const codeQLActionRepository = getCodeQLActionRepository(logger);
const potentialDownloadSources = [
// This GitHub instance, and this Action.
[apiDetails.url, codeQLActionRepository],
// This GitHub instance, and the canonical Action.
[apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY],
// GitHub.com, and the canonical Action.
[util.GITHUB_DOTCOM_URL, CODEQL_DEFAULT_ACTION_REPOSITORY],
];
// We now filter out any duplicates.
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
const uniqueDownloadSources = potentialDownloadSources.filter(
(source, index, self) => {
return !self.slice(0, index).some((other) => deepEqual(source, other));
},
);
for (const downloadSource of uniqueDownloadSources) {
const [apiURL, repository] = downloadSource;
// If we've reached the final case, short-circuit the API check since we know the bundle exists and is public.
if (
apiURL === util.GITHUB_DOTCOM_URL &&
repository === CODEQL_DEFAULT_ACTION_REPOSITORY
) {
break;
}
const [repositoryOwner, repositoryName] = repository.split("/");
try {
const release = await api.getApiClient().rest.repos.getReleaseByTag({
owner: repositoryOwner,
repo: repositoryName,
tag: tagName,
});
for (const asset of release.data.assets) {
if (asset.name === codeQLBundleName) {
logger.info(
`Found CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} with URL ${asset.url}.`,
);
return asset.url;
}
}
} catch (e) {
logger.info(
`Looked for CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} but got error ${e}.`,
);
}
}
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`;
}
function tryGetBundleVersionFromTagName(
tagName: string,
logger: Logger,
@@ -721,9 +649,10 @@ export async function getCodeQLSource(
? "zstd"
: "gzip";
const action = { env: getEnv(), logger };
const platform = getBundlePlatform();
const perLanguageBundleLanguage = await getPerLanguageBundleLanguage(
{ env: getEnv(), features, logger },
{ ...action, features },
{
rawLanguages,
otherLanguagePacksReason,
@@ -736,11 +665,11 @@ export async function getCodeQLSource(
// Resolves the combined or per-language bundle URL for the requested release.
const resolveBundleURL = (language?: BuiltInLanguage) =>
getCodeQLBundleDownloadURL(
bundleTagName,
getCodeQLAssetDownloadURL(
action,
apiDetails,
bundleTagName,
getCodeQLBundleName(compressionMethod, platform, language),
logger,
);
const combinedBundleURL = await resolveBundleURL();
@@ -1071,7 +1000,7 @@ export async function setupCodeQLBundle(
// Record that this job now has a copy of the CodeQL tools, so that a later step doesn't delete
// the toolcache out from under the path we are about to return.
core.exportVariable(EnvVar.HAS_SET_UP_CODEQL, "true");
exportEnvVar(EnvVar.HAS_SET_UP_CODEQL, "true");
return {
codeqlFolder,
+35
View File
@@ -0,0 +1,35 @@
import * as path from "path";
import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "../actions-util";
import { ActionsEnvVars } from "../environment";
import { callee, getTestEnv, setupTests } from "../testing-utils";
import { initializeEnvironment } from "../util";
import { getCodeQLActionRepository } from "./repository";
setupTests(test);
test.serial("getCodeQLActionRepository", async (t) => {
const initialEnv = getTestEnv({
[ActionsEnvVars.RUNNER_TEMP]: path.dirname(__dirname),
});
initializeEnvironment("1.2.3", initialEnv);
const target = callee(getCodeQLActionRepository)
.withArgs()
.withEnv(initialEnv);
// isRunningLocalAction() === true
await target.passes(t.deepEqual, "github/codeql-action");
// isRunningLocalAction() === false
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
await target
.withEnv((env) => {
env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "xxx/yyy");
})
.passes(t.deepEqual, "xxx/yyy");
});
+158
View File
@@ -0,0 +1,158 @@
import { ActionState } from "../action-common";
import { isRunningLocalAction } from "../actions-util";
import * as api from "../api-client";
import { ActionsEnvVars } from "../environment";
import {
isRepositoryNwoStr,
parseRepositoryNwo,
type RepositoryNwoStr,
} from "../repository";
import { GITHUB_DOTCOM_URL, type InstanceURL } from "../util";
/** The NWO of the standard CodeQL Action repository. */
export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
/**
* Retrieves the NWO of the repository that the CodeQL Action is loaded from.
* Typically, this will be `github/codeql-action`, but it may be something else for
* forks of the Action or in environments other than GitHub.com.
*
* If this function detects that we are running the Action locally (e.g. in tests),
* then we default to `CODEQL_DEFAULT_ACTION_REPOSITORY`.
*/
export function getCodeQLActionRepository(
action: ActionState<["ReadOnlyEnv", "Logger"]>,
): RepositoryNwoStr {
if (isRunningLocalAction(action.env)) {
// This handles the case where the Action does not come from an Action repository,
// e.g. our integration tests which use the Action code from the current checkout.
// In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set.
action.logger.info(
"The CodeQL Action is checked out locally. Using the default CodeQL Action repository.",
);
return CODEQL_DEFAULT_ACTION_REPOSITORY;
}
const actionRepository = action.env.getRequired(
ActionsEnvVars.GITHUB_ACTION_REPOSITORY,
);
if (isRepositoryNwoStr(actionRepository)) {
return actionRepository;
}
throw new Error(
`The value of '${ActionsEnvVars.GITHUB_ACTION_REPOSITORY}' is '${actionRepository}', which does not match the expected 'owner/repo' format.`,
);
}
/**A download source is a pair of strings. */
export type DownloadSource = [InstanceURL, RepositoryNwoStr];
/** GitHub.com, and the canonical Action. */
export const DEFAULT_DOWNLOAD_SOURCE: DownloadSource = [
GITHUB_DOTCOM_URL,
CODEQL_DEFAULT_ACTION_REPOSITORY,
];
/** Decides if the two provided download sources are the same. */
const isSameDownloadSource = (
[srcUrl, srcRepo]: DownloadSource,
[otherUrl, otherRepo]: DownloadSource,
) => {
return srcUrl === otherUrl && srcRepo === otherRepo;
};
/** A download URL is represented as a string. */
export type DownloadURL = string;
/** Gets the default GitHub.com download URL for `assetName` in a release tagged `tagName`. */
export function getDefaultDotcomDownloadURL(
tagName: string,
assetName: string,
): DownloadURL {
return `${GITHUB_DOTCOM_URL}/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`;
}
/** Enumerates log-friendly descriptions of different kinds of assets we might look for in releases. */
export type ReleaseAssetKind = "CodeQL bundle" | "private registry proxy";
/**
* Tries to find a download URL for `assetName` in a release tagged with `tagName`.
*
* Depending on where and how the CodeQL Action is running, we may be using different `apiDetails` and there may be
* different options for where to source CodeQL releases from.
*
* This function either returns the download URL for the asset for the first release we find, or
* defaults to the assumed download URL for the asset on the default CodeQL Action repository on GitHub.com.
* In the latter case, this function does not guarantee that the asset actually exists.
*
* @param action The Action state.
* @param apiDetails The details of the GitHub API in use.
* @param tagName The name of the release tag we want to obtain the asset from.
* @param assetName The name of the asset we should look for in the release.
* @param [assetKind="CodeQL bundle"] The kind of asset we are looking for to show in log messages.
* @returns A URL that we can use to download the asset.
*/
export async function getCodeQLAssetDownloadURL(
action: ActionState<["ReadOnlyEnv", "Logger"]>,
apiDetails: api.GitHubApiDetails,
tagName: string,
assetName: string,
assetKind: ReleaseAssetKind = "CodeQL bundle",
): Promise<DownloadURL> {
const codeQLActionRepository = getCodeQLActionRepository(action);
const potentialDownloadSources: DownloadSource[] = [
// This GitHub instance, and this Action.
[apiDetails.url, codeQLActionRepository],
// This GitHub instance, and the canonical Action.
[apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY],
// GitHub.com, and the canonical Action.
DEFAULT_DOWNLOAD_SOURCE,
];
// We now filter out any duplicates.
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
const uniqueDownloadSources = potentialDownloadSources.filter(
(source, index, self) => {
return !self
.slice(0, index)
.some((other) => isSameDownloadSource(source, other));
},
);
for (const [instanceURL, repository] of uniqueDownloadSources) {
// If we've reached the final case, short-circuit the API check since we know the asset exists and is public.
if (
isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [instanceURL, repository])
) {
break;
}
const { owner: repositoryOwner, repo: repositoryName } =
parseRepositoryNwo(repository);
try {
const release = await api.getApiClient().rest.repos.getReleaseByTag({
owner: repositoryOwner,
repo: repositoryName,
tag: tagName,
});
for (const asset of release.data.assets) {
if (asset.name === assetName) {
action.logger.info(
`Found ${assetKind} ${assetName} in ${repository} on ${instanceURL} with URL ${asset.url}.`,
);
return asset.url;
}
}
} catch (e) {
action.logger.info(
`Looked for ${assetKind} ${assetName} in ${repository} on ${instanceURL} but got error ${e}.`,
);
}
}
return getDefaultDotcomDownloadURL(tagName, assetName);
}
+14 -4
View File
@@ -3,9 +3,14 @@ import * as path from "path";
import * as core from "@actions/core";
import { Action, ActionState, runInActions } from "./action-common";
import {
Action,
ActionState,
extendActionState,
runInActions,
} from "./action-common";
import * as actionsUtil from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { getApiDetails, getGitHubVersion } from "./api-client";
import { FeatureEnablement, initFeatures } from "./feature-flags";
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
import { Logger } from "./logging";
@@ -98,9 +103,13 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
};
// Start the Proxy
const proxyBin = await getProxyBinaryPath({ ...action, features });
const actionWithFeatures = extendActionState(action, { features });
const proxyBin = await getProxyBinaryPath(
actionWithFeatures,
getApiDetails(action.env),
);
const proxyInfo = await startProxy(
proxyBin,
proxyBin.path,
proxyConfig,
proxyLogFilePath,
logger,
@@ -116,6 +125,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
languages: language === undefined ? undefined : [language],
},
proxyConfig.all_credentials.map((c) => c.type),
{ proxy_version: proxyBin.version },
logger,
);
} catch (unwrappedError) {
+146 -164
View File
@@ -1,22 +1,24 @@
import * as filepath from "path";
import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache";
import test, { ExecutionContext } from "ava";
import sinon from "sinon";
import { ActionState } from "./action-common";
import * as actionsUtil from "./actions-util";
import * as apiClient from "./api-client";
import * as defaults from "./defaults.json";
import { ActionsEnvVars } from "./environment";
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 { BundlePlatform, getBundlePlatform } from "./platform";
import * as repository from "./setup/repository";
import * as startProxyExports from "./start-proxy";
import * as statusReport from "./status-report";
import {
addSinonAssertions,
assertNotLogged,
checkExpectedLogMessages,
createFeatures,
@@ -24,97 +26,14 @@ import {
makeMacro,
makeTestToken,
RecordingLogger,
SAMPLE_DOTCOM_API_DETAILS,
setupTests,
withRecordingLoggerAsync,
} from "./testing-utils";
import {
ConfigurationError,
GitHubVariant,
GitHubVersion,
withTmpDir,
} from "./util";
import { GitHubVariant, GitHubVersion, withTmpDir } from "./util";
setupTests(test);
const sendFailedStatusReportTest = makeMacro({
exec: async (
t: ExecutionContext<unknown>,
err: Error,
expectedMessage: string,
expectedStatus: statusReport.ActionStatus = "failure",
) => {
const now = new Date();
// Override core.setFailed to avoid it setting the program's exit code
sinon.stub(core, "setFailed").returns();
const createStatusReportBase = sinon.stub(
statusReport,
"createStatusReportBase",
);
createStatusReportBase.resolves(undefined);
await withRecordingLoggerAsync(async (logger) => {
await startProxyExports.sendFailedStatusReport(
logger,
now,
undefined,
err,
);
// Check that the stub has been called exactly once, with the expected arguments,
// but not with the message from the error.
sinon.assert.calledOnceWithExactly(
createStatusReportBase,
statusReport.ActionName.StartProxy,
expectedStatus,
now,
sinon.match.any,
sinon.match.any,
sinon.match.any,
expectedMessage,
);
t.false(
createStatusReportBase.calledWith(
statusReport.ActionName.StartProxy,
expectedStatus,
now,
sinon.match.any,
sinon.match.any,
sinon.match.any,
sinon.match((msg: string) => msg.includes(err.message)),
),
"createStatusReportBase was called with the error message",
);
});
},
title: (providedTitle = "") => `sendFailedStatusReport - ${providedTitle}`,
});
sendFailedStatusReportTest.serial(
"reports generic error message for non-StartProxyError error",
new Error("Something went wrong today"),
"Error from start-proxy Action omitted (Error).",
);
sendFailedStatusReportTest.serial(
"reports generic error message for non-StartProxyError error with safe error message",
new Error(
startProxyExports.getStartProxyErrorMessage(
startProxyExports.StartProxyErrorType.DownloadFailed,
),
),
"Error from start-proxy Action omitted (Error).",
);
sendFailedStatusReportTest.serial(
"reports generic error message for ConfigurationError error",
new ConfigurationError("Something went wrong today"),
"Error from start-proxy Action omitted (ConfigurationError).",
"user-error",
);
const toEncodedJSON = (data: any) =>
Buffer.from(JSON.stringify(data)).toString("base64");
@@ -705,18 +624,29 @@ function mockGetApiClient(endpoints: any) {
type ReleaseAssets = Array<{ name: string; url?: string }>;
function mockGetReleaseByTag(assets?: ReleaseAssets) {
function mockGetReleaseByTag(expectedTag?: string, assets?: ReleaseAssets) {
const getReleaseByTag =
assets === undefined
? sinon.stub().rejects()
: sinon.stub().resolves({
status: 200,
data: { assets },
headers: {},
url: "GET /repos/:owner/:repo/releases/tags/:tag",
: sinon.stub().callsFake(({ tag }) => {
if (tag === expectedTag) {
return {
status: 200,
data: { assets },
headers: {},
url: "GET /repos/:owner/:repo/releases/tags/:tag",
};
} else {
return {
status: 404,
};
}
});
return mockGetApiClient({ repos: { getReleaseByTag } });
return {
getReleaseByTag,
getApiClient: mockGetApiClient({ repos: { getReleaseByTag } }),
};
}
function mockOfflineFeatures(tempDir: string, logger: Logger) {
@@ -751,25 +681,33 @@ test.serial(
logger,
features,
});
const info = await startProxyExports.getDownloadUrl(state);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
t.is(
info.url,
startProxyExports.getFallbackUrl(
startProxyExports.getProxyPackage(BundlePlatform.Linux64),
),
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
const info = await startProxyExports.getDownloadUrl(
state,
SAMPLE_DOTCOM_API_DETAILS,
);
t.true(
logger.hasMessage(`Unsupported platform android on architecture ppc`),
);
t.is(info.version, defaults.cliVersion);
t.is(
info.url,
repository.getDefaultDotcomDownloadURL(
defaults.bundleVersion,
startProxyExports.getProxyPackage(BundlePlatform.Linux64),
),
);
});
},
);
test.serial(
"getDownloadUrl returns fallback when `getReleaseByVersion` rejects",
"getDownloadUrl returns default bundle URL for linked version",
async (t) => {
const logger = new RecordingLogger();
mockGetReleaseByTag();
@@ -777,12 +715,20 @@ test.serial(
await withTmpDir(async (tempDir) => {
const features = mockOfflineFeatures(tempDir, logger);
const state = initAllState({ logger, features });
const info = await startProxyExports.getDownloadUrl(state);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
const info = await startProxyExports.getDownloadUrl(
state,
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(info.version, defaults.cliVersion);
t.is(
info.url,
startProxyExports.getFallbackUrl(
repository.getDefaultDotcomDownloadURL(
defaults.bundleVersion,
startProxyExports.getProxyPackage(getTestPlatform(state)),
),
);
@@ -791,7 +737,7 @@ test.serial(
);
test.serial(
"getDownloadUrl returns fallback when there's no matching release asset",
"getDownloadUrl doesn't check that the default asset exists",
async (t) => {
const logger = new RecordingLogger();
const testAssets = [[], [{ name: "foo" }]];
@@ -800,19 +746,28 @@ test.serial(
const features = mockOfflineFeatures(tempDir, logger);
const state = initAllState({ logger, features });
for (const assets of testAssets) {
const stub = mockGetReleaseByTag(assets);
const info = await startProxyExports.getDownloadUrl(state);
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
for (const assets of testAssets) {
const stubs = mockGetReleaseByTag(defaults.bundleVersion, assets);
const info = await startProxyExports.getDownloadUrl(
state,
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(info.version, defaults.cliVersion);
t.is(
info.url,
startProxyExports.getFallbackUrl(
repository.getDefaultDotcomDownloadURL(
defaults.bundleVersion,
startProxyExports.getProxyPackage(getTestPlatform(state)),
),
);
stub.restore();
// mockGetReleaseByTag stubs the global getApiClient; since we are calling
// that in a loop, we need to manually restore it here
stubs.getApiClient.restore();
}
});
},
@@ -828,11 +783,22 @@ test.serial("getDownloadUrl returns matching release asset", async (t) => {
url: "url-we-want",
},
];
mockGetReleaseByTag(assets);
const stubs = mockGetReleaseByTag(defaults.bundleVersion, assets);
await withTmpDir(async (tempDir) => {
state.env.set(ActionsEnvVars.RUNNER_TEMP, tempDir);
const features = mockOfflineFeatures(tempDir, logger);
const info = await startProxyExports.getDownloadUrl({ ...state, features });
// Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
const info = await startProxyExports.getDownloadUrl(
{ ...state, features },
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(stubs.getReleaseByTag.callCount, 1);
t.is(info.version, defaults.cliVersion);
t.is(info.url, "url-we-want");
@@ -965,16 +931,21 @@ test.serial(
const toolcachePath = "/path/to/proxy/dir";
sinon.stub(toolcache, "find").returns(toolcachePath);
// Stub this to always return `true` so we get the default repo for the tests.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
const features = mockOfflineFeatures(tempDir, logger);
const path = await startProxyExports.getProxyBinaryPath(
const result = await startProxyExports.getProxyBinaryPath(
initAllState({ logger, features }),
SAMPLE_DOTCOM_API_DETAILS,
);
t.assert(path);
t.assert(result);
t.is(
path,
result.path,
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
);
t.is(result.version, defaults.cliVersion);
});
},
);
@@ -985,7 +956,7 @@ test.serial(
const logger = new RecordingLogger();
const state = initAllState({ logger });
const downloadUrl = "url-we-want";
mockGetReleaseByTag([
const stubs = mockGetReleaseByTag(defaults.bundleVersion, [
{
name: startProxyExports.getProxyPackage(getTestPlatform(state)),
url: downloadUrl,
@@ -994,11 +965,6 @@ test.serial(
const toolcachePath = "/path/to/proxy/dir";
const find = sinon.stub(toolcache, "find").returns("");
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
auth: "",
url: "",
apiURL: "",
});
const getAuthorizationHeaderFor = sinon
.stub(apiClient, "getAuthorizationHeaderFor")
.returns(undefined);
@@ -1012,25 +978,36 @@ test.serial(
.resolves(extractedPath);
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
const path = await startProxyExports.getProxyBinaryPath({
...state,
features: createFeatures([]),
});
// Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
t.assert(find.calledOnce);
t.assert(getApiDetails.calledOnce);
t.assert(getAuthorizationHeaderFor.calledOnce);
t.assert(downloadTool.calledOnceWith(downloadUrl));
t.assert(extractTar.calledOnceWith(archivePath));
t.assert(cacheDir.calledOnceWith(extractedPath));
t.assert(path);
t.is(
path,
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
const result = await startProxyExports.getProxyBinaryPath(
{
...state,
features: createFeatures([]),
},
SAMPLE_DOTCOM_API_DETAILS,
);
t.is(stubs.getReleaseByTag.callCount, 1);
t.assert(find.calledOnce);
t.assert(getAuthorizationHeaderFor.calledOnce);
const st = addSinonAssertions(t);
st.onceWith(downloadTool, downloadUrl);
st.onceWith(extractTar, archivePath);
st.onceWith(cacheDir, extractedPath);
t.assert(result);
t.is(
result.path,
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
);
t.is(result.version, defaults.cliVersion);
checkExpectedLogMessages(t, logger.messages, [
`Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`,
`Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL ${downloadUrl}`,
]);
},
);
@@ -1040,10 +1017,11 @@ test.serial(
async (t) => {
const logger = new RecordingLogger();
const state = initAllState({ logger });
const expectedTag = "codeql-bundle-v2.20.1";
const expectedVersion = "2.20.1";
const expectedTag = `codeql-bundle-v${expectedVersion}`;
const expectedParams = {
owner: "github",
repo: "codeql-action",
owner: "test",
repo: "repo",
tag: expectedTag,
};
const downloadUrl = "url-we-want";
@@ -1066,11 +1044,6 @@ test.serial(
await withTmpDir(async (tempDir) => {
const toolcachePath = "/path/to/proxy/dir";
const find = sinon.stub(toolcache, "find").returns("");
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
auth: "",
url: "",
apiURL: "",
});
const getAuthorizationHeaderFor = sinon
.stub(apiClient, "getAuthorizationHeaderFor")
.returns(undefined);
@@ -1098,34 +1071,43 @@ test.serial(
const getDefaultCliVersion = sinon
.stub(features, "getEnabledDefaultCliVersions")
.resolves({
enabledVersions: [{ cliVersion: "2.20.1", tagName: expectedTag }],
enabledVersions: [
{ cliVersion: expectedVersion, tagName: expectedTag },
],
});
const path = await startProxyExports.getProxyBinaryPath({
...state,
features,
});
// Stub this to always return `false` so we force `getCodeQLAssetDownloadURL` to use the (mocked) API.
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
state.env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "test/repo");
const result = await startProxyExports.getProxyBinaryPath(
{
...state,
features,
},
SAMPLE_DOTCOM_API_DETAILS,
);
t.assert(getDefaultCliVersion.calledOnce);
sinon.assert.calledOnceWithMatch(
getReleaseByTag,
sinon.match(expectedParams),
);
t.assert(find.calledOnce);
t.assert(getApiDetails.calledOnce);
t.assert(getAuthorizationHeaderFor.calledOnce);
t.assert(downloadTool.calledOnceWith(downloadUrl));
t.assert(extractTar.calledOnceWith(archivePath));
t.assert(cacheDir.calledOnceWith(extractedPath));
t.assert(path);
const st = addSinonAssertions(t);
st.onceWith(getReleaseByTag, expectedParams);
t.assert(find.calledOnce);
t.assert(getAuthorizationHeaderFor.calledOnce);
st.onceWith(downloadTool, downloadUrl);
st.onceWith(extractTar, archivePath);
st.onceWith(cacheDir, extractedPath);
t.assert(result);
t.is(
path,
result.path,
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
);
t.is(result.version, expectedVersion);
});
checkExpectedLogMessages(t, logger.messages, [
`Found '${startProxyExports.getProxyPackage(getTestPlatform(state))}' in release '${expectedTag}' at '${downloadUrl}'`,
`Found private registry proxy ${startProxyExports.getProxyPackage(getTestPlatform(state))} in test/repo on https://github.com with URL url-we-want.`,
]);
},
);
+87 -217
View File
@@ -1,17 +1,14 @@
import * as path from "path";
import * as core from "@actions/core";
import * as toolcache from "@actions/tool-cache";
import { ActionState } from "./action-common";
import {
getApiClient,
getApiDetails,
getAuthorizationHeaderFor,
getGitHubVersion,
GitHubApiDetails,
} from "./api-client";
import * as artifactScanner from "./artifact-scanner";
import { Config } from "./config-utils";
import * as defaults from "./defaults.json";
import {
CodeQLDefaultVersionInfo,
@@ -22,6 +19,11 @@ import * as json from "./json";
import { BuiltInLanguage } from "./languages";
import { Logger } from "./logging";
import { BundlePlatform, getBundlePlatform } from "./platform";
import { getCodeQLAssetDownloadURL } from "./setup/repository";
import {
StartProxyError,
StartProxyErrorType,
} from "./start-proxy/status-report";
import {
Address,
Registry,
@@ -32,149 +34,13 @@ import {
RawCredential,
} from "./start-proxy/types";
import { getAuthConfig } from "./start-proxy/validation";
import {
ActionName,
createStatusReportBase,
getActionsStatus,
sendStatusReport,
StatusReportBase,
} from "./status-report";
import * as util from "./util";
import { ConfigurationError, getErrorMessage, isDefined } from "./util";
export * from "./start-proxy/types";
export * from "./start-proxy/status-report";
/**
* Enumerates specific error types for which we have corresponding error messages that
* are safe to include in status reports.
*/
export enum StartProxyErrorType {
DownloadFailed,
ExtractionFailed,
CacheFailed,
}
/**
* @returns The error message corresponding to the error type.
*/
export function getStartProxyErrorMessage(
errorType: StartProxyErrorType,
): string {
switch (errorType) {
case StartProxyErrorType.DownloadFailed:
return "Failed to download proxy archive.";
case StartProxyErrorType.ExtractionFailed:
return "Failed to extract proxy archive.";
case StartProxyErrorType.CacheFailed:
return "Failed to add proxy to toolcache";
}
}
/**
* We want to avoid accidentally leaking secrets that may be contained in exception
* messages in the `start-proxy` action. Consequently, we don't report the messages
* of arbitrary exceptions. This type of error ensures that the message is one from
* `StartProxyErrorType` and therefore safe to include in a status report.
*/
export class StartProxyError extends Error {
public readonly errorType: StartProxyErrorType;
constructor(errorType: StartProxyErrorType) {
super();
this.errorType = errorType;
}
}
/**
* Sends a status report for the `start-proxy` action indicating a successful outcome.
*
* @param startedAt When the action was started.
* @param config The configuration used.
* @param registry_types The types of registries that are configured.
* @param logger The logger to use.
*/
export async function sendSuccessStatusReport(
startedAt: Date,
config: Partial<Config>,
registry_types: string[],
logger: Logger,
) {
const statusReportBase = await createStatusReportBase(
ActionName.StartProxy,
"success",
startedAt,
config,
await util.checkDiskUsage(logger),
logger,
);
if (statusReportBase !== undefined) {
const statusReport: StatusReportBase = {
...statusReportBase,
registry_types: registry_types.join(","),
};
await sendStatusReport(statusReport);
}
}
/**
* Returns an error message for `error` that can safely be reported in a status report,
* i.e. that does not contain sensitive information.
*
* @param error The error for which to get an error message.
*/
export function getSafeErrorMessage(error: Error): string {
// If the error is a `StartProxyError`, resolve the error type to the corresponding
// error message.
if (error instanceof StartProxyError) {
return getStartProxyErrorMessage(error.errorType);
}
// Otherwise, omit the actual error message.
return `Error from start-proxy Action omitted (${error.constructor.name}).`;
}
/**
* Sends a status report for the `start-proxy` action indicating a failure.
*
* @param logger The logger to use.
* @param startedAt When the action was started.
* @param language The language provided as input, if any.
* @param unwrappedError The exception that was thrown.
*/
export async function sendFailedStatusReport(
logger: Logger,
startedAt: Date,
language: BuiltInLanguage | undefined,
unwrappedError: unknown,
) {
const error = util.wrapError(unwrappedError);
core.setFailed(`start-proxy action failed: ${error.message}`);
// To avoid the possibility of leaking sensitive information into the telemetry,
// we don't include arbitrary error messages. Instead, `getSafeErrorMessage` will
// return a generic message that includes the type of the error, unless it can decide
// that the message is safe to include.
const statusReportMessage = getSafeErrorMessage(error);
const errorStatusReportBase = await createStatusReportBase(
ActionName.StartProxy,
getActionsStatus(error),
startedAt,
{
languages: language === undefined ? undefined : [language],
},
await util.checkDiskUsage(logger),
logger,
statusReportMessage,
);
if (errorStatusReportBase !== undefined) {
await sendStatusReport(errorStatusReportBase);
}
}
/** The basename of the proxy artifacts. */
export const UPDATEJOB_PROXY = "update-job-proxy";
export const UPDATEJOB_PROXY_VERSION = "v2.0.20250624110901";
const UPDATEJOB_PROXY_URL_PREFIX =
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.22.0/";
function isPAT(value: string) {
return artifactScanner.isAuthToken(value, [
@@ -379,30 +245,6 @@ export function getProxyPackage(platform: BundlePlatform): string {
return `${UPDATEJOB_PROXY}-${platform}.tar.gz`;
}
/**
* Gets the fallback URL for downloading the proxy release asset.
*
* @param proxyPackage The asset name.
* @returns The full URL to download the specified asset from the fallback release.
*/
export function getFallbackUrl(proxyPackage: string): string {
return `${UPDATEJOB_PROXY_URL_PREFIX}${proxyPackage}`;
}
/**
* Uses the GitHub API to obtain information about the CodeQL CLI bundle release
* that is tagged by `version`.
*
* @returns The response from the GitHub API.
*/
async function getReleaseByVersion(version: string) {
return getApiClient().rest.repos.getReleaseByTag({
owner: "github",
repo: "codeql-action",
tag: version,
});
}
/** Uses `features` to determine the default CLI version. */
async function getCliVersionFromFeatures(
features: FeatureEnablement,
@@ -419,7 +261,8 @@ async function getCliVersionFromFeatures(
* @returns Returns the download URL and version of the proxy package we plan to use.
*/
export async function getDownloadUrl(
action: ActionState<["Base", "Logger", "FeatureFlags"]>,
action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>,
apiDetails: GitHubApiDetails,
): Promise<{ url: string; version: string }> {
// Default to linux64 if we don't recognise the platform+arch pair.
// This maintains the behaviour we had before switching to `getBundlePlatform` here.
@@ -448,37 +291,26 @@ export async function getDownloadUrl(
};
// Try to retrieve information about the CLI bundle release identified by `versionInfo`.
const cliRelease = await getReleaseByVersion(versionInfo.tagName);
const proxyAssetUrl = await getCodeQLAssetDownloadURL(
action,
apiDetails,
versionInfo.tagName,
proxyPackage,
"private registry proxy",
);
// Search the release's assets to find the one we are looking for.
for (const asset of cliRelease.data.assets) {
if (asset.name === proxyPackage) {
action.logger.info(
`Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`,
);
return {
url: asset.url,
// The `update-job-proxy` doesn't have a version as such. Since we now bundle it
// with CodeQL CLI bundle releases, we use the corresponding CLI version to
// differentiate between (potentially) different versions of `update-job-proxy`.
version: versionInfo.cliVersion,
};
}
}
return {
url: proxyAssetUrl,
// The `update-job-proxy` doesn't have a version as such. Since we now bundle it
// with CodeQL CLI bundle releases, we use the corresponding CLI version to
// differentiate between (potentially) different versions of `update-job-proxy`.
version: versionInfo.cliVersion,
};
} catch (ex) {
action.logger.warning(
throw new Error(
`Failed to retrieve information about the linked release: ${getErrorMessage(ex)}`,
);
}
// Fallback to the hard-coded URL.
action.logger.info(
`Did not find '${proxyPackage}' in the linked release, falling back to hard-coded version.`,
);
return {
url: getFallbackUrl(proxyPackage),
version: UPDATEJOB_PROXY_VERSION,
};
}
/**
@@ -497,9 +329,14 @@ export async function downloadProxy(
try {
// Download the proxy archive from `url`. We let `downloadTool` choose where
// to store it. The path to the downloaded file will be returned if successful.
return toolcache.downloadTool(url, /* dest: */ undefined, authorization, {
accept: "application/octet-stream",
});
return await toolcache.downloadTool(
url,
/* dest: */ undefined,
authorization,
{
accept: "application/octet-stream",
},
);
} catch (error) {
logger.error(
`Failed to download proxy archive from ${url}: ${getErrorMessage(error)}`,
@@ -560,37 +397,70 @@ export function getProxyFilename() {
: UPDATEJOB_PROXY;
}
export type ProxyBinary = {
/** The path to the proxy binary to run. */
path: string;
/**
* The version associated with the proxy binary.
* This usually corresponds to the CodeQL CLI release that the binary was obtained from.
*/
version: string;
};
/**
* Gets a path to the proxy binary. If possible, this function will find the proxy in the
* runner's tool cache. Otherwise, it downloads and extracts the proxy binary,
* and stores it in the tool cache.
*
* @param action The action state.
* @returns The path to the proxy binary.
* @returns The path to the proxy binary and its version.
*/
export async function getProxyBinaryPath(
action: ActionState<["Base", "Logger", "FeatureFlags"]>,
): Promise<string> {
action: ActionState<["Base", "ReadOnlyEnv", "Logger", "FeatureFlags"]>,
apiDetails: GitHubApiDetails,
): Promise<ProxyBinary> {
const logger = action.logger;
const proxyFileName = getProxyFilename();
const proxyInfo = await getDownloadUrl(action);
logger.startGroup("Setting up authentication proxy");
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
if (!proxyBin) {
const apiDetails = getApiDetails();
const authorization = getAuthorizationHeaderFor(
logger,
apiDetails,
proxyInfo.url,
);
const temp = await downloadProxy(logger, proxyInfo.url, authorization);
const extracted = await extractProxy(logger, temp);
proxyBin = await cacheProxy(
logger,
extracted,
proxyFileName,
proxyInfo.version,
try {
const proxyFileName = getProxyFilename();
const proxyInfo = await getDownloadUrl(action, apiDetails);
logger.info(
`Using authentication proxy from CodeQL release ${proxyInfo.version}.`,
);
let proxyDir = toolcache.find(proxyFileName, proxyInfo.version);
if (!proxyDir) {
logger.info(
`Authentication proxy is not in the toolcache. Will try to download it from '${proxyInfo.url}'.`,
);
const authorization = getAuthorizationHeaderFor(
logger,
apiDetails,
proxyInfo.url,
);
const temp = await downloadProxy(logger, proxyInfo.url, authorization);
logger.info(`Downloaded the authentication proxy asset to '${temp}'.`);
const extracted = await extractProxy(logger, temp);
proxyDir = await cacheProxy(
logger,
extracted,
proxyFileName,
proxyInfo.version,
);
logger.info(`Extracted the authentication proxy to '${proxyDir}'.`);
}
return {
path: path.join(proxyDir, proxyFileName),
version: proxyInfo.version,
};
} finally {
logger.endGroup();
}
return path.join(proxyBin, proxyFileName);
}
+136
View File
@@ -0,0 +1,136 @@
import * as core from "@actions/core";
import test, { ExecutionContext } from "ava";
import sinon from "sinon";
import * as statusReport from "../status-report";
import {
createTestConfig,
makeMacro,
RecordingLogger,
setupTests,
withRecordingLoggerAsync,
} from "../testing-utils";
import { ConfigurationError } from "../util";
import * as startProxyExports from "./status-report";
setupTests(test);
test.serial("sendSuccessStatusReport", async (t) => {
const logger = new RecordingLogger();
const now = new Date();
const config = createTestConfig({});
const proxy_version = "1.2.3";
const registry_types = ["git_source", "docker_registry"];
const createStatusReportBase = sinon.stub(
statusReport,
"createStatusReportBase",
);
createStatusReportBase.resolves({
action_name: statusReport.ActionName.StartProxy,
} as statusReport.StatusReportBase);
const sendStatusReport = sinon
.stub(statusReport, "sendStatusReport")
.resolves();
await startProxyExports.sendSuccessStatusReport(
now,
config,
registry_types,
{ proxy_version },
logger,
);
t.is(createStatusReportBase.callCount, 1);
t.is(sendStatusReport.callCount, 1);
t.is(sendStatusReport.args[0].length, 1);
const arg = sendStatusReport.args[0][0];
t.like(arg, {
action_name: statusReport.ActionName.StartProxy,
registry_types: "docker_registry,git_source",
start_proxy: { proxy_version },
});
});
const sendFailedStatusReportTest = makeMacro({
exec: async (
t: ExecutionContext<unknown>,
err: Error,
expectedMessage: string,
expectedStatus: statusReport.ActionStatus = "failure",
) => {
const now = new Date();
// Override core.setFailed to avoid it setting the program's exit code
sinon.stub(core, "setFailed").returns();
const createStatusReportBase = sinon.stub(
statusReport,
"createStatusReportBase",
);
createStatusReportBase.resolves(undefined);
await withRecordingLoggerAsync(async (logger) => {
await startProxyExports.sendFailedStatusReport(
logger,
now,
undefined,
err,
);
// Check that the stub has been called exactly once, with the expected arguments,
// but not with the message from the error.
sinon.assert.calledOnceWithExactly(
createStatusReportBase,
statusReport.ActionName.StartProxy,
expectedStatus,
now,
sinon.match.any,
sinon.match.any,
sinon.match.any,
expectedMessage,
);
t.false(
createStatusReportBase.calledWith(
statusReport.ActionName.StartProxy,
expectedStatus,
now,
sinon.match.any,
sinon.match.any,
sinon.match.any,
sinon.match((msg: string) => msg.includes(err.message)),
),
"createStatusReportBase was called with the error message",
);
});
},
title: (providedTitle = "") => `sendFailedStatusReport - ${providedTitle}`,
});
sendFailedStatusReportTest.serial(
"reports generic error message for non-StartProxyError error",
new Error("Something went wrong today"),
"Error from start-proxy Action omitted (Error).",
);
sendFailedStatusReportTest.serial(
"reports generic error message for non-StartProxyError error with safe error message",
new Error(
startProxyExports.getStartProxyErrorMessage(
startProxyExports.StartProxyErrorType.DownloadFailed,
),
),
"Error from start-proxy Action omitted (Error).",
);
sendFailedStatusReportTest.serial(
"reports generic error message for ConfigurationError error",
new ConfigurationError("Something went wrong today"),
"Error from start-proxy Action omitted (ConfigurationError).",
"user-error",
);
+153
View File
@@ -0,0 +1,153 @@
import * as core from "@actions/core";
import type { Config } from "../config-utils";
import type { BuiltInLanguage } from "../languages";
import type { Logger } from "../logging";
import {
ActionName,
createStatusReportBase,
getActionsStatus,
sendStatusReport,
type StatusReportBase,
} from "../status-report";
import * as util from "../util";
/** Additional status report data from `start-proxy`. */
export interface StartProxyStatus {
proxy_version: string;
}
/** The type of successful status reports from `start-proxy`. */
type StartProxySuccessReport = StatusReportBase & {
start_proxy: StartProxyStatus;
};
/**
* Sends a status report for the `start-proxy` action indicating a successful outcome.
*
* @param startedAt When the action was started.
* @param config The configuration used.
* @param registry_types The types of registries that are configured.
* @param status Status information.
* @param logger The logger to use.
*/
export async function sendSuccessStatusReport(
startedAt: Date,
config: Partial<Config>,
registry_types: string[],
status: StartProxyStatus,
logger: Logger,
) {
const statusReportBase = await createStatusReportBase(
ActionName.StartProxy,
"success",
startedAt,
config,
await util.checkDiskUsage(logger),
logger,
);
if (statusReportBase !== undefined) {
const statusReport: StartProxySuccessReport = {
...statusReportBase,
registry_types: registry_types.sort().join(","),
start_proxy: status,
};
await sendStatusReport(statusReport);
}
}
/**
* Enumerates specific error types for which we have corresponding error messages that
* are safe to include in status reports.
*/
export enum StartProxyErrorType {
DownloadFailed,
ExtractionFailed,
CacheFailed,
}
/**
* @returns The error message corresponding to the error type.
*/
export function getStartProxyErrorMessage(
errorType: StartProxyErrorType,
): string {
switch (errorType) {
case StartProxyErrorType.DownloadFailed:
return "Failed to download proxy archive.";
case StartProxyErrorType.ExtractionFailed:
return "Failed to extract proxy archive.";
case StartProxyErrorType.CacheFailed:
return "Failed to add proxy to toolcache";
}
}
/**
* We want to avoid accidentally leaking secrets that may be contained in exception
* messages in the `start-proxy` action. Consequently, we don't report the messages
* of arbitrary exceptions. This type of error ensures that the message is one from
* `StartProxyErrorType` and therefore safe to include in a status report.
*/
export class StartProxyError extends Error {
public readonly errorType: StartProxyErrorType;
constructor(errorType: StartProxyErrorType) {
super();
this.errorType = errorType;
}
}
/**
* Returns an error message for `error` that can safely be reported in a status report,
* i.e. that does not contain sensitive information.
*
* @param error The error for which to get an error message.
*/
export function getSafeErrorMessage(error: Error): string {
// If the error is a `StartProxyError`, resolve the error type to the corresponding
// error message.
if (error instanceof StartProxyError) {
return getStartProxyErrorMessage(error.errorType);
}
// Otherwise, omit the actual error message.
return `Error from start-proxy Action omitted (${error.constructor.name}).`;
}
/**
* Sends a status report for the `start-proxy` action indicating a failure.
*
* @param logger The logger to use.
* @param startedAt When the action was started.
* @param language The language provided as input, if any.
* @param unwrappedError The exception that was thrown.
*/
export async function sendFailedStatusReport(
logger: Logger,
startedAt: Date,
language: BuiltInLanguage | undefined,
unwrappedError: unknown,
) {
const error = util.wrapError(unwrappedError);
core.setFailed(`start-proxy action failed: ${error.message}`);
// To avoid the possibility of leaking sensitive information into the telemetry,
// we don't include arbitrary error messages. Instead, `getSafeErrorMessage` will
// return a generic message that includes the type of the error, unless it can decide
// that the message is safe to include.
const statusReportMessage = getSafeErrorMessage(error);
const errorStatusReportBase = await createStatusReportBase(
ActionName.StartProxy,
getActionsStatus(error),
startedAt,
{
languages: language === undefined ? undefined : [language],
},
await util.checkDiskUsage(logger),
logger,
statusReportMessage,
);
if (errorStatusReportBase !== undefined) {
await sendStatusReport(errorStatusReportBase);
}
}
+26 -14
View File
@@ -23,7 +23,13 @@ import type { ComputedInput, InputName } from "./config/inputs";
import { parseRegistriesWithoutCredentials } from "./config/pack-registries";
import type { DependencyCacheRestoreStatusReport } from "./dependency-caching";
import { DocUrl } from "./doc-url";
import { EnvVar, getEnv, ReadOnlyEnv, RegistryProxyVars } from "./environment";
import {
EnvVar,
getEnv,
ReadOnlyEnv,
RegistryProxyVars,
exportEnvVar,
} from "./environment";
import { getRef } from "./git-utils";
import * as json from "./json";
import type { Logger } from "./logging";
@@ -55,13 +61,21 @@ export enum ActionName {
UploadSarif = "upload-sarif",
}
/** A partial mapping from `ActionName` to names for actions whose display name is not the `ActionName` value. */
export const ACTION_DISPLAY_NAMES: Partial<Record<ActionName, string>> = {
[ActionName.Analyze]: "analyze",
[ActionName.InitPost]: "init post",
};
/**
* Maps an `ActionName` to its display name. Usually that is the same, except
* for `ActionName.Analyze` where it is `"analyze"` instead of `"finish"`.
* Maps an {@link ActionName} to its display name. Usually that is the same, except
* for actions which have a different display name in {@link ACTION_DISPLAY_NAMES}.
*/
export function getDisplayActionName(actionName: ActionName): string {
if (actionName === ActionName.Analyze) {
return "analyze";
const displayName = ACTION_DISPLAY_NAMES[actionName];
if (displayName !== undefined) {
return displayName;
}
return actionName;
}
@@ -71,9 +85,7 @@ export function getDisplayActionName(actionName: ActionName): string {
* environment and returns it.
* If a new UUID is generated, it is also exported as an environment variable.
*/
export function getJobUUID(
action: ActionState<["Logger", "ReadOnlyEnv", "Actions"]>,
) {
export function getJobUUID(action: ActionState<["Logger", "Env", "Actions"]>) {
// Check if we already have a UUID for the analysis and return it if so.
const existingJobRunUuid = action.env.getOptional(EnvVar.JOB_RUN_UUID);
@@ -86,7 +98,7 @@ export function getJobUUID(
const jobRunUuid = uuid.v4();
action.logger.info(`Job run UUID is ${jobRunUuid}.`);
action.actions.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid);
action.env.export(EnvVar.JOB_RUN_UUID, jobRunUuid);
return jobRunUuid;
}
@@ -268,12 +280,12 @@ export function getJobStatusDisplayName(status: JobStatus): string {
*/
function setJobStatusIfUnsuccessful(actionStatus: ActionStatus) {
if (actionStatus === "user-error") {
core.exportVariable(
exportEnvVar(
EnvVar.JOB_STATUS,
process.env[EnvVar.JOB_STATUS] ?? JobStatus.ConfigErrorStatus,
);
} else if (actionStatus === "failure" || actionStatus === "aborted") {
core.exportVariable(
exportEnvVar(
EnvVar.JOB_STATUS,
process.env[EnvVar.JOB_STATUS] ?? JobStatus.FailureStatus,
);
@@ -366,7 +378,7 @@ export async function createStatusReportBase(
try {
const commitOid =
getOptionalInput("sha") || process.env["GITHUB_SHA"] || "";
const ref = await getRef();
const ref = await getRef(getEnv(), config?.repositoryRoot);
const jobRunUUID = process.env[EnvVar.JOB_RUN_UUID] || "";
const workflowRunID = getWorkflowRunID();
const workflowRunAttempt = getWorkflowRunAttempt();
@@ -376,7 +388,7 @@ export async function createStatusReportBase(
let workflowStartedAt = process.env[EnvVar.WORKFLOW_STARTED_AT];
if (workflowStartedAt === undefined) {
workflowStartedAt = actionStartedAt.toISOString();
core.exportVariable(EnvVar.WORKFLOW_STARTED_AT, workflowStartedAt);
exportEnvVar(EnvVar.WORKFLOW_STARTED_AT, workflowStartedAt);
}
const runnerOs = getRequiredEnvParam("RUNNER_OS");
const codeQlCliVersion = getCachedCodeQlVersion(
@@ -388,7 +400,7 @@ export async function createStatusReportBase(
// re-export the testing environment variable so that it is available to subsequent steps,
// even if it was only set for this step
if (testingEnvironment) {
core.exportVariable(EnvVar.TESTING_ENVIRONMENT, testingEnvironment);
exportEnvVar(EnvVar.TESTING_ENVIRONMENT, testingEnvironment);
}
const isSteadyStateDefaultSetupRun =
process.env["CODE_SCANNING_IS_STEADY_STATE_DEFAULT_SETUP"] === "true";
+41 -4
View File
@@ -163,6 +163,46 @@ export function setupTests(testFn: TestFn<any>) {
});
}
export interface AvaSinonAssertions {
/**
* Asserts that `stub` must have been called once with at least the `expected` arguments.
*/
onceWith: <TArgs extends readonly any[]>(
stub: sinon.SinonStub<TArgs>,
...expected: sinon.MatchPartialArguments<TArgs>
) => boolean;
}
/** Adds additional assertions for use with `sinon` to `t`. */
export function addSinonAssertions(
t: ExecutionContext<unknown>,
): ExecutionContext<unknown> & AvaSinonAssertions {
return {
...t,
onceWith: <TArgs extends readonly any[]>(
stub: sinon.SinonStub<TArgs>,
...expected: sinon.MatchPartialArguments<TArgs>
) => {
// Fail if the stub hasn't been called at all so that we can safely compare
// the arguments of the first call in the else branch.
if (stub.callCount === 0) {
t.fail("The stub wasn't called.");
} else {
// Reduce the arguments of each to the same number that was provided,
// so that extra arguments don't lead to a failure.
const actual = stub.args.map<sinon.MatchPartialArguments<TArgs>>(
(args: TArgs) =>
args.slice(
0,
expected.length,
) as sinon.MatchPartialArguments<TArgs>,
);
return t.deepEqual(actual, [expected]);
}
},
};
}
/**
* Declare a reusable test implementation, with better type safety than `test.macro`.
*/
@@ -202,10 +242,6 @@ class TestActionsEnv implements ActionsEnv {
public getOptionalInput(_name: string): string | undefined {
return undefined;
}
public exportVariable(name: string, value: string): void {
this.env.set(name, value);
}
}
/**
@@ -970,6 +1006,7 @@ export function createTestConfig(overrides: Partial<Config>): Config {
{},
{
version: getActionVersion(),
repositoryRoot: undefined,
analysisKinds: [AnalysisKind.CodeScanning],
languages: [],
buildMode: undefined,
-20
View File
@@ -1,5 +1,3 @@
import * as semver from "semver";
import type { VersionInfo } from "./cli/types";
export enum ToolsFeature {
@@ -23,21 +21,3 @@ export function isSupportedToolsFeature(
): boolean {
return !!versionInfo.features && versionInfo.features[feature];
}
export const SafeArtifactUploadVersion = "2.20.3";
/**
* The first version of the CodeQL CLI where artifact upload is safe to use
* for failed runs. This is not really a feature flag, but it is easiest to
* model the behavior as a feature flag.
*
* This was not captured in a tools feature, so we need to use semver.
*
* @param codeQlVersion The version of the CodeQL CLI to check. If not provided, it is assumed to be safe.
* @returns True if artifact upload is safe to use for failed runs or false otherwise.
*/
export function isSafeArtifactUpload(codeQlVersion?: string): boolean {
return !codeQlVersion
? true
: semver.gte(codeQlVersion, SafeArtifactUploadVersion);
}
+1
View File
@@ -182,6 +182,7 @@ test.serial(
stubCodeql,
[BuiltInLanguage.javascript, BuiltInLanguage.cpp],
logger,
undefined,
);
t.assert(
stubRestore.calledOnceWith(
+15 -4
View File
@@ -14,6 +14,7 @@ import { Language } from "./languages";
import { Logger } from "./logging";
import {
asHTTPError,
getEnv,
getErrorMessage,
tryGetFolderBytes,
waitForResultWithTimeLimit,
@@ -43,6 +44,7 @@ const MAX_CACHE_OPERATION_MS = 120_000; // Two minutes
* @param codeql The CodeQL instance to use.
* @param languages The languages being analyzed.
* @param logger A logger to record some informational messages to.
* @param repositoryRoot The path at which the repository is checked out at.
* @returns A partial map from languages to TRAP cache paths on disk, with
* languages for which we shouldn't use TRAP caching omitted.
*/
@@ -50,6 +52,7 @@ export async function downloadTrapCaches(
codeql: CodeQL,
languages: Language[],
logger: Logger,
repositoryRoot: string | undefined,
): Promise<{ [language: string]: string }> {
const result: { [language: string]: string } = {};
const languagesSupportingCaching = await getLanguagesSupportingCaching(
@@ -72,7 +75,7 @@ export async function downloadTrapCaches(
result[language] = cacheDir;
}
if (await gitUtils.isAnalyzingDefaultBranch()) {
if (await gitUtils.isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) {
logger.info(
"Analyzing default branch. Skipping downloading of TRAP caches.",
);
@@ -99,6 +102,7 @@ export async function downloadTrapCaches(
const found = await waitForResultWithTimeLimit(
MAX_CACHE_OPERATION_MS,
actionsCache.restoreCache([cacheDir], preferredKey, [
preferredKey,
// Fall back to any cache with the right key prefix
await cachePrefix(codeql, language),
]),
@@ -132,7 +136,12 @@ export async function uploadTrapCaches(
config: Config,
logger: Logger,
): Promise<boolean> {
if (!(await gitUtils.isAnalyzingDefaultBranch())) return false; // Only upload caches from the default branch
// Only upload caches from the default branch
if (
!(await gitUtils.isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot))
) {
return false;
}
for (const language of config.languages) {
const cacheDir = config.trapCaches[language];
@@ -180,6 +189,8 @@ export async function cleanupTrapCaches(
features: FeatureEnablement,
logger: Logger,
): Promise<TrapCacheCleanupStatusReport> {
const env = getEnv();
if (!(await features.getValue(Feature.CleanupTrapCaches))) {
return {
trap_cache_cleanup_skipped_because: "feature disabled",
@@ -189,7 +200,7 @@ export async function cleanupTrapCaches(
"TRAP cache cleanup is deprecated and will be removed in May 2026. " +
"We recommend instead disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action.",
);
if (!(await gitUtils.isAnalyzingDefaultBranch())) {
if (!(await gitUtils.isAnalyzingDefaultBranch(env, config.repositoryRoot))) {
return {
trap_cache_cleanup_skipped_because: "not analyzing default branch",
};
@@ -200,7 +211,7 @@ export async function cleanupTrapCaches(
const allCaches = await apiClient.listActionsCaches(
CODEQL_TRAP_CACHE_PREFIX,
await gitUtils.getRef(),
await gitUtils.getRef(env, config.repositoryRoot),
);
for (const language of config.languages) {
+7 -6
View File
@@ -14,7 +14,7 @@ import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
import { CodeQL, getCodeQL } from "./codeql";
import { getConfig } from "./config-utils";
import { readDiffRangesJsonFile } from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import { ActionsEnvVars, EnvVar, exportEnvVar } from "./environment";
import { FeatureEnablement } from "./feature-flags";
import * as fingerprints from "./fingerprints";
import * as gitUtils from "./git-utils";
@@ -126,7 +126,7 @@ async function combineSarifFilesUsingCLI(
logger.warning(
`Uploading multiple SARIF runs with the same category is deprecated ${deprecationWarningMessage}. Please update your workflow to upload a single run per category. ${deprecationMoreInformationMessage}`,
);
core.exportVariable("CODEQL_MERGE_SARIF_DEPRECATION_WARNING", "true");
exportEnvVar("CODEQL_MERGE_SARIF_DEPRECATION_WARNING", "true");
}
// If not, use the naive method of combining the files.
@@ -762,12 +762,13 @@ export async function uploadPostProcessedFiles(
const zippedSarif = zlib.gzipSync(sarifPayload).toString("base64");
const checkoutURI = url.pathToFileURL(checkoutPath).href;
const env = util.getEnv();
const payload = uploadTarget.transformPayload(
buildPayload(
await gitUtils.getCommitOid(checkoutPath),
await gitUtils.getRef(),
await gitUtils.getCommitOid(env, checkoutPath),
await gitUtils.getRef(env, checkoutPath),
postProcessingResults.analysisKey,
util.getRequiredEnvParam("GITHUB_WORKFLOW"),
env.getRequired(ActionsEnvVars.GITHUB_WORKFLOW),
zippedSarif,
actionsUtil.getWorkflowRunID(),
actionsUtil.getWorkflowRunAttempt(),
@@ -1032,7 +1033,7 @@ export function validateUniqueCategory(
`Category: (${id ? id : "none"}) Tool: (${tool ? tool : "none"})`,
);
}
core.exportVariable(sentinelEnvVar, sentinelEnvVar);
exportEnvVar(sentinelEnvVar, sentinelEnvVar);
}
}
+1 -7
View File
@@ -34,13 +34,7 @@ export async function runWrapper() {
return;
}
await withGroup("Uploading combined SARIF debug artifact", () =>
debugArtifacts.uploadCombinedSarifArtifacts(
logger,
gitHubVersion.type,
// The codeqlVersion is not applicable for uploading non-codeql sarif.
// We can assume all versions are safe to upload.
undefined,
),
debugArtifacts.uploadCombinedSarifArtifacts(logger, gitHubVersion.type),
);
}
} catch (error) {
+36 -24
View File
@@ -12,15 +12,28 @@ import * as semver from "semver";
import * as apiCompatibility from "./api-compatibility.json";
import type { CodeQL } from "./codeql";
import type { ActionConfig } from "./config/action-config";
import type { Pack } from "./config/db-config";
import type { Config } from "./config-utils";
import { EnvVar, getRequiredEnvParam } from "./environment";
import {
EnvVar,
getRequiredEnvParam,
isInTestMode,
exportEnvVar,
getEnv,
type Env,
} from "./environment";
import * as json from "./json";
import { Language } from "./languages";
import { Logger } from "./logging";
// Re-export for backwards compatibility to avoid updating a lot of imports elsewhere.
export { getRequiredEnvParam, getOptionalEnvVar, getEnv } from "./environment";
export {
getRequiredEnvParam,
getOptionalEnvVar,
getEnv,
isInTestMode,
} from "./environment";
/**
* The name of the file containing the base database OIDs, as stored in the
@@ -34,10 +47,13 @@ const BASE_DATABASE_OIDS_FILE_NAME = "base-database-oids.json";
*/
const BROKEN_VERSIONS = ["0.0.0-20211207"];
/** A string representing a GitHub instance. */
export type InstanceURL = string;
/**
* The URL for github.com.
*/
export const GITHUB_DOTCOM_URL = "https://github.com";
export const GITHUB_DOTCOM_URL: InstanceURL = "https://github.com";
/**
* Default name of the debugging artifact.
@@ -435,7 +451,10 @@ export function getThreadsFlag(
/**
* Get the path where the CodeQL database for the given language lives.
*/
export function getCodeQLDatabasePath(config: Config, language: Language) {
export function getCodeQLDatabasePath(
config: ActionConfig<"dbLocation">,
language: Language,
) {
return path.resolve(config.dbLocation, language);
}
@@ -542,7 +561,7 @@ export function checkGitHubVersionInRange(
);
}
hasBeenWarnedAboutVersion = true;
core.exportVariable(CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR, true);
exportEnvVar(CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR, true);
}
export enum DisallowedAPIVersionReason {
@@ -585,12 +604,12 @@ export function assertNever(value: never): never {
* Set some initial environment variables that we can set even without
* knowing what version of CodeQL we're running.
*/
export function initializeEnvironment(version: string) {
core.exportVariable(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
core.exportVariable(EnvVar.FEATURE_SANDWICH, "false");
core.exportVariable(EnvVar.FEATURE_SARIF_COMBINE, "true");
core.exportVariable(EnvVar.FEATURE_WILL_UPLOAD, "true");
core.exportVariable(EnvVar.VERSION, version);
export function initializeEnvironment(version: string, env: Env = getEnv()) {
env.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
env.export(EnvVar.FEATURE_SANDWICH, "false");
env.export(EnvVar.FEATURE_SARIF_COMBINE, "true");
env.export(EnvVar.FEATURE_WILL_UPLOAD, "true");
env.export(EnvVar.VERSION, version);
}
export class HTTPError extends Error {
@@ -629,7 +648,9 @@ export async function codeQlVersionAtLeast(
return semver.gte((await codeql.getVersion()).version, requiredVersion);
}
export function getBaseDatabaseOidsFilePath(config: Config): string {
export function getBaseDatabaseOidsFilePath(
config: ActionConfig<"dbLocation">,
): string {
return path.join(config.dbLocation, BASE_DATABASE_OIDS_FILE_NAME);
}
@@ -641,7 +662,7 @@ export function getBaseDatabaseOidsFilePath(config: Config): string {
* given `includeDiagnostics` value.
*/
export async function bundleDb(
config: Config,
config: ActionConfig<"dbLocation">,
language: Language,
codeql: CodeQL,
dbName: string,
@@ -711,15 +732,6 @@ export function isGoodVersion(versionSpec: string) {
return !BROKEN_VERSIONS.includes(versionSpec);
}
/**
* Returns whether we are in test mode. This is used by CodeQL Action PR checks.
*
* In test mode, we skip several uploads (SARIF results, status reports, DBs, ...).
*/
export function isInTestMode(): boolean {
return process.env[EnvVar.TEST_MODE] === "true";
}
/**
* Returns whether we specifically want to skip uploading SARIF files.
*/
@@ -948,7 +960,7 @@ export async function checkDiskUsage(
} else {
logger.debug(message);
}
core.exportVariable(EnvVar.HAS_WARNED_ABOUT_DISK_SPACE, "true");
exportEnvVar(EnvVar.HAS_WARNED_ABOUT_DISK_SPACE, "true");
}
return {
numAvailableBytes: diskUsage.bavail * blockSizeInBytes,
@@ -997,7 +1009,7 @@ export function checkActionVersion(
"https://github.blog/changelog/2025-10-28-upcoming-deprecation-of-codeql-action-v3/",
);
// set LOG_VERSION_DEPRECATION env var to prevent the warning from being logged multiple times
core.exportVariable(EnvVar.LOG_VERSION_DEPRECATION, "true");
exportEnvVar(EnvVar.LOG_VERSION_DEPRECATION, "true");
}
}
}
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more