Compare commits

...
19 Commits
Author SHA1 Message Date
3f5f9adaf7 Document the new Gradle signing key for dependency verification (#1071)
Follow-up to #1069, which bumped the
`github-dependency-graph-gradle-plugin` default to `1.5.0`.

`1.5.0` is signed with a **new** Gradle signing subkey, and the key
currently documented in `docs/setup-gradle.md` has been revoked
upstream. Without this docs change, the snippet we publish will fail
dependency verification for Dependency Graph generation.

I verified the published signatures rather than relying on the
changelog:

| Artifact | Issuer key | |
|---|---|---|
| `github-dependency-graph-gradle-plugin:1.5.0` | `D9B2DFBD9F3298BA` |
new |
| `github-dependency-graph-gradle-plugin:1.4.2` | `893A028475557671` |
old |
| `develocity-gradle-plugin:4.5.0` | `893A028475557671` | old |

So the docs now list **both** keys instead of swapping one for the
other. The Develocity Gradle plugin (`com.gradle`) is still signed with
the old key, so replacing it outright would have broken Develocity
injection for builds with dependency verification enabled.

Docs-only change; no source or `dist` impact.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-07 18:57:35 -06:00
bot-githubaction b031f6d1a9 [bot] Update dist directory 2026-09-08 00:39:22 +00:00
5bc4175609 Bump dependency-graph-gradle-plugin to 1.5.0 (#1069)
Updates the default version of the [GitHub Dependency Graph Gradle
Plugin](https://plugins.gradle.org/plugin/org.gradle.github-dependency-graph-gradle-plugin)
applied by the `dependency-submission` init-script from `1.4.2` to
`1.5.0`.

`1.5.0` is the latest version published to the Gradle Plugin Portal.

Users can still override this via the `dependency-graph-plugin.version`
input parameter (`DEPENDENCY_GRAPH_PLUGIN_VERSION`), so this only
changes the default.

## Notes

The init-script is read at runtime from `sources/src/resources` rather
than being inlined into the bundle, so this single source change is the
whole fix — no `dist` update is needed for the new version to take
effect (CI refreshes `dist` as usual).

## Verification

- `npm run check` (prettier + eslint) passes
- `npm run test`: 489 passed. The 2 failing wrapper-validation suites
are pre-existing network-dependent failures on this machine — confirmed
identical on a clean tree with this change stashed
- Confirmed `1.5.0` is published to the plugin portal (and that a bogus
version 404s, so the check is meaningful)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-07 18:38:40 -06:00
Daz DeBoerGitHubbot-githubactiondependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f3ff59b4e4 Combined automated updates: wrapper checksums, npm dependencies, setup-java (#1070)
Combines three pending automated update PRs into a single pull request
so they can be reviewed and merged together.

Supersedes:

- #1045 — Update known wrapper checksums
- #1066 — Bump the npm-dependencies group in /sources (eslint 10.9.1 →
10.10.0, globals 17.11.0 → 17.12.0)
- #1067 — Bump actions/setup-java from 5.7.0 to 6.0.0 in the
github-actions group

Each original commit is cherry-picked unchanged; there were no
overlapping files or conflicts.

## Verification

- `./build install` (clean install + format + lint + compile) passes.
- `./build test`: 490/491 pass. The single failure is
`wrapper-validation/checksums.test.ts › fetches wrapper jar checksums
for snapshots`, a pre-existing network-dependent failure on this machine
— it fails identically with `main`'s checksums file, so it is unrelated
to these changes.
- `dist/` is intentionally untouched; CI regenerates it.

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: bot-githubaction <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-07 18:38:22 -06:00
7927085475 Update .tool-versions: node 24.18.0, gradle 9.7.1, java 17 (#1068)
The pins in `.tool-versions` had drifted well behind what CI and the
Gradle wrappers use. One of
them was not just stale but broken.

## node 24.3.0 → 24.18.0

CI runs `node-version: 24`, which resolves to a much newer 24.x. That
gap is not harmless: it
recently masked a real CI failure outright, because jest 30.5.0's
breakage of nock interception
reproduces on 24.18.0 but not on 24.3.0. A local run that passes is
worth something again.

Verified with a clean `./build install` and repeated `./build test` runs
on both versions. Both show
the same stable core of two failures — the network-dependent
wrapper-validation checksum tests —
plus an occasional third flake in the same area that varies between runs
and between node versions.
Nothing attributable to the bump.

## java liberica-11 → liberica-17

This pin was broken. The init-scripts wrapper is on Gradle 9.7.1, which
refuses to run a build on
JVM 11:

```
Gradle requires JVM 17 or later to run.
Your build is currently configured to use JVM 11.
```

So `./build init-scripts` could not have worked with the pinned
toolchain. `./gradlew --version`
*does* start under JVM 11 and reports successfully, which is likely how
the pin survived unnoticed —
only an actual build fails.

17 matches what `ci-init-script-check.yml` provisions, so local runs
mirror CI. Verified with a
clean `./gradlew check` in `sources/test/init-scripts`: BUILD SUCCESSFUL
in 3m57s.

Java 21 was also tried and passes (3m53s), so moving further later is
unblocked. 17 is chosen only
to track CI.

## gradle 8.14.2 → 9.7.1

Follows the wrappers, which the workflow samples and init-scripts tests
are now on. This matches how
the file has been maintained before (e.g. "Use Gradle 8.14 everywhere").
Nothing in `./build`
invokes the asdf gradle — every path goes through `./gradlew` — so this
only keeps ad-hoc CLI use
consistent.

## Notes

`ci-check-and-unit-test.yml` still provisions `gradle-version: '8.14.2'`
for its unit-test job.
That may be equally stale, but it is a CI behaviour change rather than
local tooling, so it is left
alone here.

One flaky run was seen while verifying: two `TestDevelocityInjection`
cases against Gradle 6.0.1
failed once, then passed on re-run and on repeat clean builds. Unrelated
to these version changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-07 17:59:30 -06:00
bot-githubaction c3897a4e73 [bot] Update dist directory 2026-09-07 22:59:44 +00:00
Daz DeBoerGitHubbot-githubactiondependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Claude Opus 5
6b92be1905 Update dependencies (#1065)
Rolls the outstanding dependency updates into a single change, along
with fixes for two breaking
changes they brought with them.

## Gradle Wrapper 9.6.1 → 9.7.1

Updated in all five wrapped projects: `sources/test/init-scripts` and
the `gradle-plugin`,
`groovy-dsl`, `java-toolchain` and `kotlin-dsl` workflow samples.

## GitHub Actions

- `actions/setup-java` v5.7.0 → v6.0.0
- `github/codeql-action/init` and `github/codeql-action/analyze`
re-pinned to a newer v3.29.5 commit

## npm dependencies

- `@typescript-eslint/eslint-plugin` 8.66.0 → 8.69.0
- `esbuild` 0.28.1 → 0.28.2
- `eslint` 10.8.0 → 10.9.1
- `globals` 17.9.0 → 17.11.0

`jest` stays at 30.4.2 and `@jest/globals` at 30.4.1 — see below.

## setup-java v6 needs signature verification disabled for EOL JDKs

v6 enables `verify-signature` by default. Temurin 16 and 20 are EOL and
publish no signatures, so
the toolchain-detection job could no longer install them:

```
Java setup process failed due to: Input 'verify-signature' is enabled,
but no signature URL was found for Temurin version 16.0.2+7.
```

Verification is disabled for those two steps only. The Java 17 and
matrix steps still verify.

## jest is held at 30.4.x because 30.5.0 breaks nock

jest 30.5.0 reworked the ESM module registry (ES modules keyed by full
URL, modules shared across
overlapping CommonJS/ESM graphs). nock ends up patching a different
module instance than the one
`@actions/http-client` reaches undici through, so it no longer
intercepts anything: all five mocked
tests in `short-lived-token.test.ts` silently hit the real network and
resolve `null`. Two further
tests in that file keep passing only vacuously — they assert `null`,
which is also what an
unintercepted request returns.

Bisected on node 24.18.0, changing only the jest version:

| jest | result |
| --- | --- |
| 30.5.0 | 5 failed, 38 passed |
| 30.4.2 | 43 passed |

nock 15.0.0 does not fix it, so this cannot be resolved by moving nock
forward. The node version
matters too: 24.3.0 masks the failure completely, which is why it
reproduced only in CI.

Because `jest` is an exactly-pinned direct devDependency, the pin by
itself holds the entire test
runtime at 30.4.x — `jest-cli`, `jest-runtime`, `jest-environment-node`,
`@jest/core`,
`@jest/transform`, `@jest/types`, `expect` and `jest-util` all resolve
to 30.4.x with no `overrides`
block. Nothing in the tree forces jest forward; only dependabot proposes
it. So the constraint is
expressed in `.github/dependabot.yml`, alongside the existing
`typescript` and `@types/node`
entries, ignoring `jest` and `@jest/globals` `>=30.5.0` with a comment
recording when it can be
lifted.

`pretty-format` is left to float to 30.5.1 via the types-only
`@types/jest`; it renders test output
and has no bearing on nock or module resolution.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: bot-githubaction <[email protected]>
Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: bot-githubaction <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-07 16:58:55 -06:00
bot-githubaction 0d208da058 [bot] Update dist directory 2026-09-07 22:35:18 +00:00
e49d0a36a8 Report EOL and maintenance status for Gradle versions (#1057)
This PR reports the support status of every Gradle version used in a
workflow:

- **End-of-life** — two or more major versions behind the latest
release.
- **Out of date** — one major version behind the latest release, or more
than two minor versions behind on the current major.

The information is surfaced as job annotations and in the Job Summary:
an icon beside the Gradle version in the build results table, and a
message below it.

| version kind | job annotation | version table | below the table |
| ------ | ----- | ---- | --------- |
| EOL | warning | ⚠️ | expandable section, pointing to the Gradle
Security Subscription |
| Out of date | notice | ℹ️ | one-line legend, pointing to the Gradle
release lifecycle docs |
| Current | none | — | — |

A single expandable section covers every end-of-life version, naming
them in its summary line and listing the affected release lines in its
body. The upgrade legend likewise appears once per job, however many
versions carry the info icon.

Notes on what is deliberately *not* reported:

- **Patch releases.** Only the major and minor version are considered,
so being on `9.7.0` when `9.7.1` exists is not flagged.
- **Pre-releases.** Release candidates, milestones and snapshots are
never reported, so testing against a nightly or an RC produces no
annotations.

The latest Gradle release is determined from the wrapper checksum data
already bundled with the action, so no network access is required. That
data is refreshed weekly, and the two-minor grace band absorbs the lag.

Note that the annotations are emitted independently of
`add-job-summary`: setting it to `never` suppresses the Job Summary
itself, but the warning and notice annotations remain.

### Examples

The `demo-job-summary` workflow has a `support-status-eol-and-outdated`
job that builds with three end-of-life versions (`6.9.4`, `7.4`,
`7.6.6`), two out-of-date versions (`8.0.2`, `8.14.5`) and the current
release, so all three statuses appear in one summary:

* **Job Summary**:
https://github.com/gradle/actions/actions/runs/34166688755#summary-101879071306
* **Annotations**:
https://github.com/gradle/actions/actions/runs/34166688755/job/101879071306
(expand annotations)

That workflow is never triggered automatically; run it manually against
a branch to review the rendering.

### Implementation

* Adds `GradleVersion`, parsing and ordering versions the same way
Gradle's own `org.gradle.util.GradleVersion` does. This replaces the
previous `versionIsAtLeast` helper and removes the `semver` dependency.
* Adds `gradle-support-status.ts`, which owns the classification policy
and both of its presentations (annotations and Job Summary section)
behind a three-function API, so `job-summary.ts` only asks for the icon
and the rendered block.

### Testing

* Unit tests for version ordering, classification, the annotations and
the rendered summary.
* `integ-test-gradle-support-status.yml`, which derives its expected
versions from the bundled release data so the assertions cannot drift
from the action's own view, then asserts against the real job log.
* The demo workflow job linked above, for reviewing the rendering by
eye.

Documented under [Build
reporting](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#gradle-version-support-status)
in `docs/setup-gradle.md`.

---------

Co-authored-by: Louis Jacomet <[email protected]>
Co-authored-by: Daz DeBoer <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-07 16:34:26 -06:00
Christoph ObexerandGitHub 575435b1ea Use the root-qualified :wrapper task (#1064)
Replaces the unqualified `wrapper` task name with the
root-project-qualified
`:wrapper` in the two places this repo names it:

- `.github/workflows/demo-job-summary.yml` — the kotlin-dsl sample
invocation
- `docs/wrapper-validation.md` — the guidance for regenerating an
unverifiable
  `gradle-wrapper.jar`

An unqualified `wrapper` matches the task in every project of a
multi-project
build. The root-qualified form is unambiguous, matches the gradle/gradle
convention, and is required under Configure on Demand.

Part of the org-wide rollout tracked in gradle/gradle-private#5228;
original
issue gradle/gradle#37761.

Signed-off-by: Christoph Obexer <[email protected]>
2026-09-07 14:42:32 -06:00
bot-githubaction a27deee331 [bot] Update dist directory 2026-09-01 21:00:35 +00:00
02d4784b6e Relax Develocity access key format validation (#1061)
Develocity access keys have the format `server=key[;server=key]*`. The
short-lived-token code validated that format with:

```
/^([^;=\s]+=\w+)(;[^;=\s]+=\w+)*$/
```

This is too strict about `key`, and an OIDC token value fails it. There
were two independent problems:

1. `\w+` rejects the `.`, `-`, `_` and `=` padding a JWT contains.
2. Even had the regex passed, `parse` split each entry with
`hostKey.split('=')` and took `pair[1]`, so a key containing `=` would
have been **silently truncated** — a worse failure than rejection, since
the mangled key would then be sent to the server.

## Change

Drop `accessKeyRegexp` and the separate `isValid` gate; validate
structurally in `parse` instead, asserting only what is needed to split
the value:

- split on `;`, then split each entry on its **first** `=` only
- hostname: non-empty and free of whitespace (it cannot contain `=` or
`;` by construction)
- key: non-empty and free of whitespace — nothing else is assumed about
its shape

Two small intentional behaviour deltas beyond the fix:

- The whole value is now trimmed, so a trailing newline on a secret no
longer rejects the key. Internal whitespace still rejects.
- Empty entries (`host=key;`, `;host=key`) still reject, as before.

## Testing

Added cases to `short-lived-token.test.ts`: a JWT-shaped key value,
first-separator-only splitting (`host1=a=b==` → key `a=b==`),
surrounding-whitespace tolerance, a `raw()` round-trip preserving `==`
padding, and a table of nine rejection cases. 32 tests pass; prettier,
eslint and `./build` are clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
2026-09-01 20:59:47 +00:00
bot-githubaction 346a44564f [bot] Update dist directory 2026-08-25 15:45:34 +00:00
910b061d4d Add cache-provider: external to skip Gradle User Home caching (#1059)
Users relying on an external mechanism to save/restore Gradle User Home
(e.g. Develocity Artifact Cache) previously had to set cache-disabled:
true, which is confusing since caching isn't actually disabled — it's
just not managed by this action — and the Job Summary misleadingly
reported caching as "Disabled".

cache-provider: external skips Gradle User Home restore/save (same as
cache-disabled) but reports a distinct "External" status in the Job
Summary, explaining that caching is handled by another provider.

---------

Co-authored-by: Claude Sonnet 5 <[email protected]>
2026-08-25 09:44:41 -06:00
bot-githubaction 4ca152ae04 [bot] Update dist directory 2026-08-25 14:40:34 +00:00
eed01f09d3 Add action heading to Gradle job summary (#1058)
Job summaries from setup-gradle/dependency-submission had no top-level
heading, so they read poorly when another action's summary content lands
in the same job. Name the action explicitly so the block is attributable
when summaries are stacked.

---------

Co-authored-by: Claude Sonnet 5 <[email protected]>
2026-08-25 14:39:48 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
889dc537cf Bump the npm-dependencies group in /sources with 3 updates (#1038)
Bumps the npm-dependencies group in /sources with 3 updates:
[@types/semver](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/semver),
[@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin)
and [globals](https://github.com/sindresorhus/globals).

Updates `@types/semver` from 7.7.1 to 7.8.0
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/semver">compare
view</a></li>
</ul>
</details>
<br />

Updates `@typescript-eslint/eslint-plugin` from 8.65.0 to 8.66.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">@​typescript-eslint/eslint-plugin's
releases</a>.</em></p>
<blockquote>
<h2>v8.66.0</h2>
<h2>8.66.0 (2026-08-03)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>typescript-estree:</strong> handle import.defer() as
ImportExpression (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12609">#12609</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-conversion]
ignore shadowed built-ins (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12590">#12590</a>)</li>
<li><strong>eslint-plugin:</strong> [prefer-nullish-coalescing] handle
shadowed Boolean calls (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12591">#12591</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
don't report defaults used by other overloads (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12607">#12607</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-parameters]
check MappedType key remapping (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12588">#12588</a>)</li>
<li><strong>eslint-plugin:</strong> [class-literal-property-style]
preserve type annotations and don't drop decorators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12617">#12617</a>)</li>
<li><strong>website:</strong> list
<code>onUnsupportedTypeScriptVersion</code> in parser options (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12585">#12585</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>Steffen Schroeder</li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.66.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md">@​typescript-eslint/eslint-plugin's
changelog</a>.</em></p>
<blockquote>
<h2>8.66.0 (2026-08-03)</h2>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [class-literal-property-style]
preserve type annotations and don't drop decorators (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12617">#12617</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-parameters]
check MappedType key remapping (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12588">#12588</a>)</li>
<li><strong>eslint-plugin:</strong> [no-useless-default-assignment]
don't report defaults used by other overloads (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12607">#12607</a>)</li>
<li><strong>eslint-plugin:</strong> [prefer-nullish-coalescing] handle
shadowed Boolean calls (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12591">#12591</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-conversion]
ignore shadowed built-ins (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12590">#12590</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.66.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/e51b11ba3ab31837762c675f62f0d4dcb1abc4fb"><code>e51b11b</code></a>
chore(release): publish 8.66.0</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/20cb15e6dbed8f42b7857a75e34f96d231b95449"><code>20cb15e</code></a>
chore(eslint-plugin): split prefer-optional-chain.test.ts in multiple
files (...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/780a8c52d54fcb6420232b2f53974959a9b26dff"><code>780a8c5</code></a>
docs(eslint-plugin): [member-ordering] add lacking commas in default
member t...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/08dd427202e08de304cf172472e3a97de3f382cf"><code>08dd427</code></a>
fix(eslint-plugin): [class-literal-property-style] preserve type
annotations ...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/6be25d3b45f189d0c71018fb4ba7a2c9be4e14de"><code>6be25d3</code></a>
fix(eslint-plugin): [no-unnecessary-type-parameters] check MappedType
key rem...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/8826d0ebf7ff178afb6b0e72bf22d9132e4c83a2"><code>8826d0e</code></a>
fix(eslint-plugin): [no-useless-default-assignment] don't report
defaults use...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/7aecc1c44e60bc4b47402ce63a79587746766d9f"><code>7aecc1c</code></a>
fix(eslint-plugin): [prefer-nullish-coalescing] handle shadowed Boolean
calls...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/5ffa76049a0dd6c73ba9c0a7e8388ab1324f740f"><code>5ffa760</code></a>
fix(eslint-plugin): [no-unnecessary-type-conversion] ignore shadowed
built-in...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/f282f84df6418d405c5fbaa4312bf6c951b34957"><code>f282f84</code></a>
chore(eslint-plugin): fix all
<code>eslint-plugin/require-test-error-positions</code> (#...</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/c8cafa4ae9a0b57a64ba23659eba25a01301684a"><code>c8cafa4</code></a>
docs: [unified-signatures] caveats regarding signatures merging (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin/issues/12581">#12581</a>)</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.66.0/packages/eslint-plugin">compare
view</a></li>
</ul>
</details>
<br />

Updates `globals` from 17.8.0 to 17.9.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sindresorhus/globals/releases">globals's
releases</a>.</em></p>
<blockquote>
<h2>v17.9.0</h2>
<ul>
<li>Update globals (2026-08-01) (<a
href="https://redirect.github.com/sindresorhus/globals/issues/348">#348</a>)
5a958ed</li>
</ul>
<hr />
<p><a
href="https://github.com/sindresorhus/globals/compare/v17.8.0...v17.9.0">https://github.com/sindresorhus/globals/compare/v17.8.0...v17.9.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sindresorhus/globals/commit/8e7b9358b190d26acca88cc2804707fbcd90a229"><code>8e7b935</code></a>
17.9.0</li>
<li><a
href="https://github.com/sindresorhus/globals/commit/5a958ed06064fb879b64416655109d8eaffd2611"><code>5a958ed</code></a>
Update globals (2026-08-01) (<a
href="https://redirect.github.com/sindresorhus/globals/issues/348">#348</a>)</li>
<li>See full diff in <a
href="https://github.com/sindresorhus/globals/compare/v17.8.0...v17.9.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 19:21:58 -06:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
968aeb62d7 Bump the github-actions group across 3 directories with 6 updates (#1039)
Bumps the github-actions group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [gradle/actions/setup-gradle](https://github.com/gradle/actions) |
`6.2.0` | `6.3.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) |
`4.37.4` | `4.37.5` |
|
[github/codeql-action/analyze](https://github.com/github/codeql-action)
| `4.37.4` | `4.37.5` |
| [actions/setup-java](https://github.com/actions/setup-java) | `5.6.0`
| `5.7.0` |
| [gradle/actions/wrapper-validation](https://github.com/gradle/actions)
| `6.2.0` | `6.3.0` |

Bumps the github-actions group with 1 update in the
/.github/actions/build-dist directory:
[actions/setup-node](https://github.com/actions/setup-node).
Bumps the github-actions group with 1 update in the
/.github/actions/init-integ-test directory:
[actions/setup-java](https://github.com/actions/setup-java).

Updates `gradle/actions/setup-gradle` from 6.2.0 to 6.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/gradle/actions/releases">gradle/actions/setup-gradle's
releases</a>.</em></p>
<blockquote>
<h2>v6.3.0</h2>
<h2>Highlights</h2>
<h3>Enhanced Caching: Windows fixes and a cache-protocol bump</h3>
<p>This release updates <code>gradle-actions-caching</code> to
<strong>v1.0.0</strong> (up from v0.7.0), which fixes two significant
caching defects, both most visible on Windows:</p>
<ul>
<li>
<p><strong>Cache entries failed to store at all on Windows.</strong>.
Every entry failed
with <code>Path Validation Error: Path(s) specified in the action for
caching do(es) not exist</code>, even though the Gradle User Home was
fully intact. Nothing was stored, so
every downstream job ran against an empty Gradle User Home. The cause
was a nested,
unpatched copy of <code>@actions/glob</code> combined with a silently
swallowed <code>require()</code> in
the bundle, which left Windows path separators unnormalized.</p>
</li>
<li>
<p><strong>Cache cleanup deleted instrumented jars that were in
use.</strong> A bug in key
hashing for paths shorter than 64 characters made cleanup judge freshly
created
<code>caches/jars-9</code> entries as unused and remove them, so the
<code>instrumented-jars</code> entry
was never saved and every job re-instrumented its classpaths.</p>
<p>Also included: cache entry names are now consistent between the save
and restore
reports — restore previously fell back to showing the raw glob pattern
(e.g.
<code>/home/runner/.gradle/caches/modules-*/files-*/*/*/*/*/</code>)
instead of <code>dependencies</code>.</p>
</li>
</ul>
<blockquote>
<p>[!IMPORTANT]
<strong>Existing cache entries are invalidated by this release.</strong>
The cache protocol
version was bumped to <code>v2</code>, so the first run after upgrading
will be a cache miss
and will repopulate the cache. No configuration changes are
required.</p>
</blockquote>
<h3>Basic caching warns instead of failing silently</h3>
<p>The basic (open-source) caching provider now emits a warning and
reports
<code>(Entry not saved: save failed)</code> in the Job Summary when a
cache save fails, rather
than reporting success (<a
href="https://redirect.github.com/gradle/actions/issues/1028">#1028</a>).</p>
<h3>Dependency submission works with Isolated Projects</h3>
<p><code>dependency-submission</code> now disables Isolated Projects via
a promoted property, so
dependency graph generation works on builds that enable it (<a
href="https://redirect.github.com/gradle/actions/issues/1025">#1025</a>).
Thanks to <a
href="https://github.com/reinsch82"><code>@​reinsch82</code></a> for the
contribution.</p>
<h3>Updated defaults</h3>
<ul>
<li>Injected Develocity Gradle plugin: <strong>4.4.2 →
4.5.0</strong></li>
<li>36 new known-good wrapper checksums added for
<code>wrapper-validation</code></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Render configuration-cache status in the caching Job Summary by <a
href="https://github.com/bigdaz"><code>@​bigdaz</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/989">gradle/actions#989</a></li>
<li>Update gradle-actions-caching library to v0.8.0 by <a
href="https://github.com/bot-githubaction"><code>@​bot-githubaction</code></a>
in <a
href="https://redirect.github.com/gradle/actions/pull/993">gradle/actions#993</a></li>
<li>Support experimental project-entry caching (configuration-cache +
build-logic) by <a
href="https://github.com/bigdaz"><code>@​bigdaz</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/994">gradle/actions#994</a></li>
<li>Update gradle-actions-caching library to v0.9.0 by <a
href="https://github.com/bot-githubaction"><code>@​bot-githubaction</code></a>
in <a
href="https://redirect.github.com/gradle/actions/pull/996">gradle/actions#996</a></li>
<li>Disable Isolated Projects via promoted property in
dependency-submission by <a
href="https://github.com/reinsch82"><code>@​reinsch82</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/1025">gradle/actions#1025</a></li>
<li>Add Windows coverage for caching via a new smoke-test suite by <a
href="https://github.com/bigdaz"><code>@​bigdaz</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/1027">gradle/actions#1027</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/gradle/actions/commit/9c971963bec38e04b3d30dcc455b5382be2fdbfb"><code>9c97196</code></a>
Bump the github-actions group across 2 directories with 9 updates (<a
href="https://redirect.github.com/gradle/actions/issues/1024">#1024</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/760e4a476b57fc6216ff816bbd682e14f43f66ee"><code>760e4a4</code></a>
Bump the npm-dependencies group across 1 directory with 2 updates (<a
href="https://redirect.github.com/gradle/actions/issues/1037">#1037</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/73e4c4222be7ce7f3685c9277a9e698ea3a28534"><code>73e4c42</code></a>
Update gradle-actions-caching library to v1.0.0 (<a
href="https://redirect.github.com/gradle/actions/issues/1029">#1029</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/a9d14388b4a819d9b607d85c115000e421e46eec"><code>a9d1438</code></a>
Add dependabot ignore rules for TypeScript 7.x and
<code>@​types/node</code> 25.x/26.x</li>
<li><a
href="https://github.com/gradle/actions/commit/68f37005d75fc0fdb3e66cc267d1f007b3de27d1"><code>68f3700</code></a>
[bot] Update dist directory</li>
<li><a
href="https://github.com/gradle/actions/commit/5971332a8f9f37459fbe710cc8b2ec8f125cdf54"><code>5971332</code></a>
Bump Gradle Wrapper to 9.6.1, wrapper checksums, and Develocity plugin
to 4.5...</li>
<li><a
href="https://github.com/gradle/actions/commit/b5bc804016a397046a67d7f83089c374ff4af6a7"><code>b5bc804</code></a>
[bot] Update dist directory</li>
<li><a
href="https://github.com/gradle/actions/commit/dcbab4e9198b2315584afbb3d582bdaa2a7aee42"><code>dcbab4e</code></a>
Bump npm-dependencies group with TypeScript 6.0.3,
<code>@​types/node</code> 24.x, and secu...</li>
<li><a
href="https://github.com/gradle/actions/commit/ca8d95717f747a46ed048a911f0e8a348829aef1"><code>ca8d957</code></a>
Move non-smoke restore-gradle-home tests back to the integ-test suite
(<a
href="https://redirect.github.com/gradle/actions/issues/1032">#1032</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/4318659b28d9d152a98ad848b621397f3e386420"><code>4318659</code></a>
[bot] Update dist directory</li>
<li>Additional commits viewable in <a
href="https://github.com/gradle/actions/compare/3f131e8634966bd73d06cc69884922b02e6faf92...9c971963bec38e04b3d30dcc455b5382be2fdbfb">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/init` from 4.37.4 to 4.37.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/d1ba80a13dd99fba24a470575428917156a28b43"><code>d1ba80a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4067">#4067</a>
from github/update-v4.37.5-1cd4d01d5</li>
<li><a
href="https://github.com/github/codeql-action/commit/e74600b0d945db9734eb044f95cd43f34b773451"><code>e74600b</code></a>
Update changelog for v4.37.5</li>
<li><a
href="https://github.com/github/codeql-action/commit/1cd4d01d58f636bbdbffbfd3c121b3446d07e9f3"><code>1cd4d01</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4061">#4061</a>
from github/henrymercer/turbo-system</li>
<li><a
href="https://github.com/github/codeql-action/commit/d2bfc30bc3373a3ec945dc3f93bc8b8f07fa16b9"><code>d2bfc30</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4050">#4050</a>
from github/mbg/status/registries</li>
<li><a
href="https://github.com/github/codeql-action/commit/68028fcb1605f3cdd37e4e1845c3f78af017a3ea"><code>68028fc</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4062">#4062</a>
from github/sam-robson/migrate-enterprise-release-pat</li>
<li><a
href="https://github.com/github/codeql-action/commit/c29563eeaafbc75499c7bb0d74bf77b3506c1cbd"><code>c29563e</code></a>
ci: use federated enterprise release PAT</li>
<li><a
href="https://github.com/github/codeql-action/commit/155e5229973b426bd1ae2f83bb1bf42417fa2a8f"><code>155e522</code></a>
Link the PR from the changelog entry</li>
<li><a
href="https://github.com/github/codeql-action/commit/2d3b351ea6452a9b21346f8d64567e5b833924de"><code>2d3b351</code></a>
Handle network errors when streaming the CodeQL bundle download</li>
<li><a
href="https://github.com/github/codeql-action/commit/5d3eb98e4a780bfe1a53f57fbdc278ea5da1274b"><code>5d3eb98</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4055">#4055</a>
from github/dependabot/npm_and_yarn/npm-minor-203262...</li>
<li><a
href="https://github.com/github/codeql-action/commit/c5f739bd6460e096aaf40962517e46a846b8b6b1"><code>c5f739b</code></a>
Merge branch 'main' into
dependabot/npm_and_yarn/npm-minor-2032624187</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...d1ba80a13dd99fba24a470575428917156a28b43">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.4 to 4.37.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/d1ba80a13dd99fba24a470575428917156a28b43"><code>d1ba80a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4067">#4067</a>
from github/update-v4.37.5-1cd4d01d5</li>
<li><a
href="https://github.com/github/codeql-action/commit/e74600b0d945db9734eb044f95cd43f34b773451"><code>e74600b</code></a>
Update changelog for v4.37.5</li>
<li><a
href="https://github.com/github/codeql-action/commit/1cd4d01d58f636bbdbffbfd3c121b3446d07e9f3"><code>1cd4d01</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4061">#4061</a>
from github/henrymercer/turbo-system</li>
<li><a
href="https://github.com/github/codeql-action/commit/d2bfc30bc3373a3ec945dc3f93bc8b8f07fa16b9"><code>d2bfc30</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4050">#4050</a>
from github/mbg/status/registries</li>
<li><a
href="https://github.com/github/codeql-action/commit/68028fcb1605f3cdd37e4e1845c3f78af017a3ea"><code>68028fc</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4062">#4062</a>
from github/sam-robson/migrate-enterprise-release-pat</li>
<li><a
href="https://github.com/github/codeql-action/commit/c29563eeaafbc75499c7bb0d74bf77b3506c1cbd"><code>c29563e</code></a>
ci: use federated enterprise release PAT</li>
<li><a
href="https://github.com/github/codeql-action/commit/155e5229973b426bd1ae2f83bb1bf42417fa2a8f"><code>155e522</code></a>
Link the PR from the changelog entry</li>
<li><a
href="https://github.com/github/codeql-action/commit/2d3b351ea6452a9b21346f8d64567e5b833924de"><code>2d3b351</code></a>
Handle network errors when streaming the CodeQL bundle download</li>
<li><a
href="https://github.com/github/codeql-action/commit/5d3eb98e4a780bfe1a53f57fbdc278ea5da1274b"><code>5d3eb98</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4055">#4055</a>
from github/dependabot/npm_and_yarn/npm-minor-203262...</li>
<li><a
href="https://github.com/github/codeql-action/commit/c5f739bd6460e096aaf40962517e46a846b8b6b1"><code>c5f739b</code></a>
Merge branch 'main' into
dependabot/npm_and_yarn/npm-minor-2032624187</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...d1ba80a13dd99fba24a470575428917156a28b43">compare
view</a></li>
</ul>
</details>
<br />

Updates `actions/setup-java` from 5.6.0 to 5.7.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-java/releases">actions/setup-java's
releases</a>.</em></p>
<blockquote>
<h2>v5.7.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix npm audit failures on releases/v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1154">actions/setup-java#1154</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1153">actions/setup-java#1153</a></li>
<li>Deprecate legacy Adopt distributions in v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1186">actions/setup-java#1186</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0">https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-java/commit/b6effb05e454b25005698d916606bdc6ffcbf961"><code>b6effb0</code></a>
Deprecate legacy Adopt distributions in v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1186">#1186</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/e498d2a66a953492f322542257b22125c989b422"><code>e498d2a</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions (<a
href="https://redirect.github.com/actions/setup-java/issues/1153">#1153</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/6a3384db745932178632d0e22b2bd28cad1678e6"><code>6a3384d</code></a>
Fix npm audit failures on releases/v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1154">#1154</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/setup-java/compare/03ad4de0992f5dab5e18fcb136590ce7c4a0ac95...b6effb05e454b25005698d916606bdc6ffcbf961">compare
view</a></li>
</ul>
</details>
<br />

Updates `gradle/actions/wrapper-validation` from 6.2.0 to 6.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/gradle/actions/releases">gradle/actions/wrapper-validation's
releases</a>.</em></p>
<blockquote>
<h2>v6.3.0</h2>
<h2>Highlights</h2>
<h3>Enhanced Caching: Windows fixes and a cache-protocol bump</h3>
<p>This release updates <code>gradle-actions-caching</code> to
<strong>v1.0.0</strong> (up from v0.7.0), which fixes two significant
caching defects, both most visible on Windows:</p>
<ul>
<li>
<p><strong>Cache entries failed to store at all on Windows.</strong>.
Every entry failed
with <code>Path Validation Error: Path(s) specified in the action for
caching do(es) not exist</code>, even though the Gradle User Home was
fully intact. Nothing was stored, so
every downstream job ran against an empty Gradle User Home. The cause
was a nested,
unpatched copy of <code>@actions/glob</code> combined with a silently
swallowed <code>require()</code> in
the bundle, which left Windows path separators unnormalized.</p>
</li>
<li>
<p><strong>Cache cleanup deleted instrumented jars that were in
use.</strong> A bug in key
hashing for paths shorter than 64 characters made cleanup judge freshly
created
<code>caches/jars-9</code> entries as unused and remove them, so the
<code>instrumented-jars</code> entry
was never saved and every job re-instrumented its classpaths.</p>
<p>Also included: cache entry names are now consistent between the save
and restore
reports — restore previously fell back to showing the raw glob pattern
(e.g.
<code>/home/runner/.gradle/caches/modules-*/files-*/*/*/*/*/</code>)
instead of <code>dependencies</code>.</p>
</li>
</ul>
<blockquote>
<p>[!IMPORTANT]
<strong>Existing cache entries are invalidated by this release.</strong>
The cache protocol
version was bumped to <code>v2</code>, so the first run after upgrading
will be a cache miss
and will repopulate the cache. No configuration changes are
required.</p>
</blockquote>
<h3>Basic caching warns instead of failing silently</h3>
<p>The basic (open-source) caching provider now emits a warning and
reports
<code>(Entry not saved: save failed)</code> in the Job Summary when a
cache save fails, rather
than reporting success (<a
href="https://redirect.github.com/gradle/actions/issues/1028">#1028</a>).</p>
<h3>Dependency submission works with Isolated Projects</h3>
<p><code>dependency-submission</code> now disables Isolated Projects via
a promoted property, so
dependency graph generation works on builds that enable it (<a
href="https://redirect.github.com/gradle/actions/issues/1025">#1025</a>).
Thanks to <a
href="https://github.com/reinsch82"><code>@​reinsch82</code></a> for the
contribution.</p>
<h3>Updated defaults</h3>
<ul>
<li>Injected Develocity Gradle plugin: <strong>4.4.2 →
4.5.0</strong></li>
<li>36 new known-good wrapper checksums added for
<code>wrapper-validation</code></li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Render configuration-cache status in the caching Job Summary by <a
href="https://github.com/bigdaz"><code>@​bigdaz</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/989">gradle/actions#989</a></li>
<li>Update gradle-actions-caching library to v0.8.0 by <a
href="https://github.com/bot-githubaction"><code>@​bot-githubaction</code></a>
in <a
href="https://redirect.github.com/gradle/actions/pull/993">gradle/actions#993</a></li>
<li>Support experimental project-entry caching (configuration-cache +
build-logic) by <a
href="https://github.com/bigdaz"><code>@​bigdaz</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/994">gradle/actions#994</a></li>
<li>Update gradle-actions-caching library to v0.9.0 by <a
href="https://github.com/bot-githubaction"><code>@​bot-githubaction</code></a>
in <a
href="https://redirect.github.com/gradle/actions/pull/996">gradle/actions#996</a></li>
<li>Disable Isolated Projects via promoted property in
dependency-submission by <a
href="https://github.com/reinsch82"><code>@​reinsch82</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/1025">gradle/actions#1025</a></li>
<li>Add Windows coverage for caching via a new smoke-test suite by <a
href="https://github.com/bigdaz"><code>@​bigdaz</code></a> in <a
href="https://redirect.github.com/gradle/actions/pull/1027">gradle/actions#1027</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/gradle/actions/commit/9c971963bec38e04b3d30dcc455b5382be2fdbfb"><code>9c97196</code></a>
Bump the github-actions group across 2 directories with 9 updates (<a
href="https://redirect.github.com/gradle/actions/issues/1024">#1024</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/760e4a476b57fc6216ff816bbd682e14f43f66ee"><code>760e4a4</code></a>
Bump the npm-dependencies group across 1 directory with 2 updates (<a
href="https://redirect.github.com/gradle/actions/issues/1037">#1037</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/73e4c4222be7ce7f3685c9277a9e698ea3a28534"><code>73e4c42</code></a>
Update gradle-actions-caching library to v1.0.0 (<a
href="https://redirect.github.com/gradle/actions/issues/1029">#1029</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/a9d14388b4a819d9b607d85c115000e421e46eec"><code>a9d1438</code></a>
Add dependabot ignore rules for TypeScript 7.x and
<code>@​types/node</code> 25.x/26.x</li>
<li><a
href="https://github.com/gradle/actions/commit/68f37005d75fc0fdb3e66cc267d1f007b3de27d1"><code>68f3700</code></a>
[bot] Update dist directory</li>
<li><a
href="https://github.com/gradle/actions/commit/5971332a8f9f37459fbe710cc8b2ec8f125cdf54"><code>5971332</code></a>
Bump Gradle Wrapper to 9.6.1, wrapper checksums, and Develocity plugin
to 4.5...</li>
<li><a
href="https://github.com/gradle/actions/commit/b5bc804016a397046a67d7f83089c374ff4af6a7"><code>b5bc804</code></a>
[bot] Update dist directory</li>
<li><a
href="https://github.com/gradle/actions/commit/dcbab4e9198b2315584afbb3d582bdaa2a7aee42"><code>dcbab4e</code></a>
Bump npm-dependencies group with TypeScript 6.0.3,
<code>@​types/node</code> 24.x, and secu...</li>
<li><a
href="https://github.com/gradle/actions/commit/ca8d95717f747a46ed048a911f0e8a348829aef1"><code>ca8d957</code></a>
Move non-smoke restore-gradle-home tests back to the integ-test suite
(<a
href="https://redirect.github.com/gradle/actions/issues/1032">#1032</a>)</li>
<li><a
href="https://github.com/gradle/actions/commit/4318659b28d9d152a98ad848b621397f3e386420"><code>4318659</code></a>
[bot] Update dist directory</li>
<li>Additional commits viewable in <a
href="https://github.com/gradle/actions/compare/3f131e8634966bd73d06cc69884922b02e6faf92...9c971963bec38e04b3d30dcc455b5382be2fdbfb">compare
view</a></li>
</ul>
</details>
<br />

Updates `actions/setup-node` from 6.4.0 to 7.0.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-node/releases">actions/setup-node's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements:</h3>
<ul>
<li>Add cache-primary-key and cache-matched-key as outputs by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1577">actions/setup-node#1577</a></li>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1574">actions/setup-node#1574</a></li>
</ul>
<h3>Bug fixes:</h3>
<ul>
<li>Remove dummy NODE_AUTH_TOKEN export by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1558">actions/setup-node#1558</a></li>
<li>Only use <code>mirrorToken</code> in <code>getManifest</code> if
it's provided by <a
href="https://github.com/deiga"><code>@​deiga</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
</ul>
<h3>Documentation updates:</h3>
<ul>
<li>Add documentation for publishing to npm with Trusted Publisher
(OIDC) by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li>docs: Update restore-only cache documentation by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1550">actions/setup-node#1550</a></li>
<li>docs: Update caching recommendations to mitigate cache poisoning
risks by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1567">actions/setup-node#1567</a></li>
</ul>
<h3>Dependency update:</h3>
<ul>
<li>Upgrade <code>@​actions/cache</code> to 5.1.0, log cache write
denied by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li><a href="https://github.com/deiga"><code>@​deiga</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
<li><a href="https://github.com/jasongin"><code>@​jasongin</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6...v7.0.0">https://github.com/actions/setup-node/compare/v6...v7.0.0</a></p>
<h2>v6.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update <code>@​actions/cache</code> to 5.1.0 and add security
overrides for undici and fast-xml-parser by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1579">actions/setup-node#1579</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0">https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-node/commit/820762786026740c76f36085b0efc47a31fe5020"><code>8207627</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-node/issues/1574">#1574</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/04be95cf3511ea51ebf9f224ddfb99cc7ab87cd4"><code>04be95c</code></a>
Add cache-primary-key and cache-matched-key as outputs (<a
href="https://redirect.github.com/actions/setup-node/issues/1577">#1577</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/7c2c68d20d402ed6a201ada70a81341941093140"><code>7c2c68d</code></a>
docs: Update caching recommendations to mitigate cache poisoning risks
(<a
href="https://redirect.github.com/actions/setup-node/issues/1567">#1567</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/6a61c0375d66246de94630495909f12cf8dac84d"><code>6a61c03</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/setup-node/issues/1569">#1569</a>
from jasongin/update-actions-cache-5.1.0</li>
<li><a
href="https://github.com/actions/setup-node/commit/30eb73b41ded577900c1ebf968ef95cdf8f7434f"><code>30eb73b</code></a>
Resolve high-severity audit issues</li>
<li><a
href="https://github.com/actions/setup-node/commit/4e1a87a501d0302f99e30e2748568adcb388d09f"><code>4e1a87a</code></a>
Update dist</li>
<li><a
href="https://github.com/actions/setup-node/commit/360237f0c01778d0c17291f75c56d6feae4f7574"><code>360237f</code></a>
Strict equality</li>
<li><a
href="https://github.com/actions/setup-node/commit/4f8aac5beb2f0854bc79651567a18c67eb0b9de3"><code>4f8aac5</code></a>
Bump <code>@​actions/cache</code> to 5.1.0, log cache write denied</li>
<li><a
href="https://github.com/actions/setup-node/commit/f4a67bbeca970f103397d3d2b9462cf787cd2980"><code>f4a67bb</code></a>
Only use <code>mirrorToken</code> in <code>getManifest</code> if it's
provided (<a
href="https://redirect.github.com/actions/setup-node/issues/1548">#1548</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/0355742c943ddb13ca8a6b700f824231caa91e75"><code>0355742</code></a>
Remove dummy NODE_AUTH_TOKEN export (<a
href="https://redirect.github.com/actions/setup-node/issues/1558">#1558</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-node/compare/48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e...820762786026740c76f36085b0efc47a31fe5020">compare
view</a></li>
</ul>
</details>
<br />

Updates `actions/setup-java` from 5.6.0 to 5.7.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-java/releases">actions/setup-java's
releases</a>.</em></p>
<blockquote>
<h2>v5.7.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix npm audit failures on releases/v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1154">actions/setup-java#1154</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1153">actions/setup-java#1153</a></li>
<li>Deprecate legacy Adopt distributions in v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1186">actions/setup-java#1186</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0">https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-java/commit/b6effb05e454b25005698d916606bdc6ffcbf961"><code>b6effb0</code></a>
Deprecate legacy Adopt distributions in v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1186">#1186</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/e498d2a66a953492f322542257b22125c989b422"><code>e498d2a</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions (<a
href="https://redirect.github.com/actions/setup-java/issues/1153">#1153</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/6a3384db745932178632d0e22b2bd28cad1678e6"><code>6a3384d</code></a>
Fix npm audit failures on releases/v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1154">#1154</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/setup-java/compare/03ad4de0992f5dab5e18fcb136590ce7c4a0ac95...b6effb05e454b25005698d916606bdc6ffcbf961">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-06 19:21:45 -06:00
Daz DeBoerandClaude Opus 5 b7d46eb472 Document immutable releases in the release process
Release immutability is now enabled for this repository, so a published
release permanently locks its Git tag to a commit, and deleting the
release does not free the tag name for reuse.

Reorder the steps so the signed tag is created and pushed before the
release is drafted, and have the draft select that existing tag. Letting
GitHub create the tag at publish time produces an unsigned, lightweight
tag pointing at whatever main resolves to at that moment - both of which
immutability then makes permanent.

Also record that the v6 major tag must never have a release attached:
immutability locks only tags that have a release of their own, which is
what keeps the conventional major-tag force-push legal.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-02 13:54:30 -06:00
62 changed files with 4773 additions and 2192 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ name: 'Build and upload distribution'
runs:
using: "composite"
steps:
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: npm
+1 -1
View File
@@ -10,7 +10,7 @@ runs:
using: "composite"
steps:
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: 'temurin'
java-version: ${{ inputs.java-version }}
+7
View File
@@ -20,6 +20,13 @@ updates:
versions: ["7.x"]
- dependency-name: "@types/node"
versions: ["25.x", "26.x"]
# jest 30.5.0 reworked the ESM module registry, and nock no longer intercepts requests
# made through @actions/http-client (undici): every mocked test silently hits the real
# network instead. nock 15 does not fix it. Drop these two once they interoperate again.
- dependency-name: "jest"
versions: [">=30.5.0"]
- dependency-name: "@jest/globals"
versions: [">=30.5.0"]
- package-ecosystem: "github-actions"
# github-actions with directory: "/" only monitors .github/workflows
# https://github.com/dependabot/dependabot-core/issues/6345
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
cache-dependency-path: sources/package-lock.json
- name: Setup Gradle
# Use a released version to avoid breakages
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
env:
ALLOWED_GRADLE_WRAPPER_CHECKSUMS: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 # Invalid wrapper jar used for testing
with:
+2 -2
View File
@@ -35,7 +35,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v3.29.5
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3.29.5
with:
languages: ${{ matrix.language }}
config: |
@@ -43,4 +43,4 @@ jobs:
- sources/src
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v3.29.5
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v3.29.5
+2 -2
View File
@@ -36,14 +36,14 @@ jobs:
sources/test/init-scripts/**
- name: Setup Java
if: steps.changes.outputs.any_changed == 'true' || github.event_name != 'pull_request'
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: 17
- name: Setup Gradle
if: steps.changes.outputs.any_changed == 'true' || github.event_name != 'pull_request'
# Use a released version to avoid breakages
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
env:
ALLOWED_GRADLE_WRAPPER_CHECKSUMS: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 # Invalid wrapper jar used for testing
- name: Run integration tests
+1
View File
@@ -36,6 +36,7 @@ jobs:
other-integ-tests:
permissions:
contents: write
actions: read
needs: caching-integ-tests
uses: ./.github/workflows/suite-integ-test-other.yml
concurrency:
+1
View File
@@ -47,6 +47,7 @@ jobs:
other-integ-tests:
permissions:
contents: write
actions: read
needs: caching-integ-tests
uses: ./.github/workflows/suite-integ-test-other.yml
concurrency:
+1 -1
View File
@@ -12,6 +12,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
with:
allow-checksums: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+91 -1
View File
@@ -46,7 +46,7 @@ jobs:
run: |
./gradlew tasks --no-daemon
./gradlew help check
./gradlew wrapper --gradle-version 8.7 --gradle-distribution-sha256-sum 544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
./gradlew :wrapper --gradle-version 8.7 --gradle-distribution-sha256-sum 544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
- name: Fail groovy-dsl project
working-directory: .github/workflow-samples/groovy-dsl
continue-on-error: true
@@ -57,6 +57,79 @@ jobs:
build-root-directory: .github/workflow-samples/groovy-dsl
dependency-graph: generate-and-upload
# Exercises every Gradle version support status in a single Job Summary: end-of-life versions all
# share one expandable section and are each marked with a warning sign, out-of-date versions share
# a single info sign and legend, and the current version is left unmarked.
# Three end-of-life versions and two out-of-date ones exercise the plural wording throughout, and
# the two 7.x versions alongside a 6.x one check that each affected release line is named once,
# as "Gradle 6.x and 7.x releases".
# Versions are picked relative to Gradle 9.x being current; revisit once Gradle 10 is released.
support-status-eol-and-outdated:
needs: build-distribution
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Java 11, since Gradle 6.x cannot run on the Java 17 used elsewhere in this workflow.
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
with:
java-version: '11'
- name: Setup Gradle 6.9.4 (end-of-life)
uses: ./setup-gradle
with:
gradle-version: '6.9.4'
- name: Build with Gradle 6.9.4
working-directory: .github/workflow-samples/no-wrapper
run: gradle help
- name: Setup Gradle 7.4 (end-of-life)
uses: ./setup-gradle
with:
gradle-version: '7.4'
- name: Build with Gradle 7.4
working-directory: .github/workflow-samples/no-wrapper
run: gradle help
- name: Setup Gradle 7.6.6 (end-of-life)
uses: ./setup-gradle
with:
gradle-version: '7.6.6'
- name: Build with Gradle 7.6.6
working-directory: .github/workflow-samples/no-wrapper
run: gradle help
- name: Setup Gradle 8.0.2 (out of date)
uses: ./setup-gradle
with:
gradle-version: '8.0.2'
- name: Build with Gradle 8.0.2
working-directory: .github/workflow-samples/no-wrapper
run: gradle help
- name: Setup Gradle 8.14.5 (out of date)
uses: ./setup-gradle
with:
gradle-version: '8.14.5'
- name: Build with Gradle 8.14.5
working-directory: .github/workflow-samples/no-wrapper
run: gradle help
# Gradle 9 and later require Java 17
- name: Setup Java 17
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: 17
- name: Setup Gradle current (no status reported)
uses: ./setup-gradle
with:
gradle-version: current
- name: Build with the current Gradle version
working-directory: .github/workflow-samples/no-wrapper
run: gradle help
successful-builds-with-no-summary:
needs: build-distribution
runs-on: ubuntu-latest
@@ -132,6 +205,23 @@ jobs:
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew assemble
cache-provider-external:
needs: build-distribution
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Gradle
uses: ./setup-gradle
with:
cache-provider: external
- name: Build kotlin-dsl project
working-directory: .github/workflow-samples/kotlin-dsl
run: ./gradlew assemble
basic-caching:
needs: build-distribution
runs-on: ubuntu-latest
@@ -134,6 +134,30 @@ jobs:
script: |
core.setFailed('No Build Scan detected')
# Test that build scans are captured when caching is external
caching-config-cache-provider-external:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
- name: Setup Gradle
uses: ./setup-gradle
with:
cache-provider: external
- name: Build using Gradle wrapper
id: gradle
working-directory: .github/workflow-samples/groovy-dsl
run: ./gradlew help
- name: Check Build Scan url is captured
if: ${{ !steps.gradle.outputs.build-scan-url }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
core.setFailed('No Build Scan detected')
# Test seed the cache with cache-write-only and verify with cache-read-only
caching-config-seed-write-only:
env:
@@ -65,15 +65,19 @@ jobs:
uses: ./.github/actions/init-integ-test
- name: Setup Java 16
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: 'temurin'
java-version: 16
# Temurin 16 is EOL and publishes no signatures: setup-java v6 enables verify-signature by default
verify-signature: false
- name: Setup Java 20
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: 'temurin'
java-version: 20
# Temurin 20 is EOL and publishes no signatures: setup-java v6 enables verify-signature by default
verify-signature: false
- name: Setup Gradle
uses: ./setup-gradle
- name: List detected toolchains
@@ -0,0 +1,148 @@
name: Test Gradle support status reporting
on:
workflow_call:
inputs:
cache-key-prefix:
type: string
default: '0'
skip-dist:
type: boolean
default: false
env:
SKIP_DIST: ${{ inputs.skip-dist }}
GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX: gradle-support-status-${{ inputs.cache-key-prefix }}
permissions:
contents: read
jobs:
report-outdated-versions:
runs-on: ubuntu-latest
outputs:
prev-oldest: ${{ steps.versions.outputs.prev-oldest }}
prev-newest: ${{ steps.versions.outputs.prev-newest }}
steps:
- name: Checkout sources
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Initialize integ-test
uses: ./.github/actions/init-integ-test
# Derived from the bundled release data so the expectations cannot drift from the action's own view.
- name: Determine versions for each support status
id: versions
run: |
set -euo pipefail
eval "$(jq -r '
[ .[].version
| select(test("^[0-9]+\\.[0-9]+(\\.[0-9]+)?$"))
| {v: ., k: (((split(".") | map(tonumber)) + [0, 0, 0]) | .[0:3])}
]
| sort_by(.k) as $finals
| ($finals | last) as $latest
| [$finals[] | select(.k[0] == $latest.k[0] - 1)] as $prev
| "KNOWN_LATEST=\($latest.v)",
"PREV_OLDEST=\($prev | first | .v)",
"PREV_NEWEST=\($prev | last | .v)"
' sources/src/wrapper-validation/wrapper-checksums.json)"
echo "Known latest : ${KNOWN_LATEST}"
echo "Previous major, oldest : ${PREV_OLDEST}"
echo "Previous major, newest : ${PREV_NEWEST}"
{
echo "prev-oldest=${PREV_OLDEST}"
echo "prev-newest=${PREV_NEWEST}"
} >> "$GITHUB_OUTPUT"
- name: Setup Gradle with an end-of-life version
uses: ./setup-gradle
with:
cache-read-only: false
gradle-version: '7.6.4'
- name: Build with the end-of-life version
working-directory: .github/workflow-samples/no-wrapper
run: gradle help "-DgradleVersionCheck=7.6.4"
- name: Setup Gradle with the oldest release of the previous major
uses: ./setup-gradle
with:
gradle-version: ${{ steps.versions.outputs.prev-oldest }}
- name: Build with the oldest release of the previous major
working-directory: .github/workflow-samples/no-wrapper
run: gradle help "-DgradleVersionCheck=${{ steps.versions.outputs.prev-oldest }}"
- name: Setup Gradle with the newest release of the previous major
uses: ./setup-gradle
with:
gradle-version: ${{ steps.versions.outputs.prev-newest }}
- name: Build with the newest release of the previous major
working-directory: .github/workflow-samples/no-wrapper
run: gradle help "-DgradleVersionCheck=${{ steps.versions.outputs.prev-newest }}"
verify-report:
needs: report-outdated-versions
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- name: Check the support-status report was emitted
env:
GH_TOKEN: ${{ github.token }}
PREV_OLDEST: ${{ needs.report-outdated-versions.outputs.prev-oldest }}
PREV_NEWEST: ${{ needs.report-outdated-versions.outputs.prev-newest }}
run: |
set -euo pipefail
job_id=$(gh api \
"/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${GITHUB_RUN_ATTEMPT}/jobs" \
--paginate --jq '.jobs[] | select(.name | endswith("report-outdated-versions")) | .id')
if [ -z "$job_id" ]; then
echo "Could not find the 'report-outdated-versions' Job in this workflow run"
exit 1
fi
gh api --allow-escape-sequences "/repos/${GITHUB_REPOSITORY}/actions/jobs/${job_id}/logs" > job.log
assert_logged() {
if ! grep -qF -- "$1" job.log; then
echo "Expected the Job log to contain: $1"
echo "--- support-status output found instead ---"
grep -E '##\[(warning|notice)\]|<summary>:warning:|consider upgrading' job.log || echo "(none)"
exit 1
fi
}
refute_logged() {
if grep -qF -- "$1" job.log; then
echo "Did not expect the Job log to contain: $1"
exit 1
fi
}
# The runner renders '::warning::'/'::notice::' into the log as '##[warning]'/'##[notice]'.
assert_logged "##[warning]Gradle 7.6.4 is end-of-life"
assert_logged "##[notice]Gradle ${PREV_OLDEST} is out of date: consider updating to the latest Gradle version."
assert_logged "##[notice]Gradle ${PREV_NEWEST} is out of date: consider updating to the latest Gradle version."
# Out-of-date versions get a notice, never a warning.
refute_logged "##[warning]Gradle ${PREV_OLDEST}"
refute_logged "##[warning]Gradle ${PREV_NEWEST}"
# The job summary is echoed to the log, so the rendered report can be checked directly.
assert_logged "<summary>:warning: Gradle 7.6.4 is end-of-life</summary>"
assert_logged "Update to the latest Gradle version."
assert_logged "<a href=\"https://gradle.org/security-subscription/?utm_source=github-action\">Gradle Security Subscription</a>"
# Table signs: a warning for end-of-life, the info sign for everything still supported.
assert_logged "<td align='center'>7.6.4 :warning:</td>"
assert_logged "<td align='center'>${PREV_OLDEST} :information_source:</td>"
assert_logged "<td align='center'>${PREV_NEWEST} :information_source:</td>"
# One legend for the whole job, no matter how many versions carry the info sign.
legends=$(grep -cF -- "consider upgrading" job.log || true)
if [ "$legends" != "1" ]; then
echo "Expected exactly one upgrade legend, found ${legends}"
exit 1
fi
assert_logged "<p>:information_source: Gradle version is out of date — consider upgrading. See <a href=\"https://docs.gradle.org/current/userguide/feature_lifecycle.html#eol_support\">Gradle release lifecycle</a></p>"
@@ -56,7 +56,7 @@ jobs:
run: gradle help "-DgradleVersionCheck=7.1.1"
# Configure JDK 17 for Gradle 9 and later
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: 17
@@ -110,7 +110,7 @@ jobs:
uses: ./.github/actions/init-integ-test
- name: Setup Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: ${{ matrix.java-version }}
@@ -28,6 +28,14 @@ jobs:
secrets:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DV_SOLUTIONS_ACCESS_KEY }}
gradle-support-status:
permissions:
contents: read
actions: read
uses: ./.github/workflows/integ-test-gradle-support-status.yml
with:
skip-dist: ${{ inputs.skip-dist }}
provision-gradle-versions:
uses: ./.github/workflows/integ-test-provision-gradle-versions.yml
with:
+1
View File
@@ -1,3 +1,4 @@
.git
.vscode
.claude
.idea
+3 -3
View File
@@ -1,4 +1,4 @@
# Configuration file for asdf version manager
nodejs 24.3.0
gradle 8.14.2
java liberica-11.0.26+9
nodejs 24.18.0
gradle 9.7.1
java liberica-17.0.14+10
+21 -2
View File
@@ -9,20 +9,39 @@
- If new features have been added, bump the minor version (eg `4.1.1` → `4.2.0`)
- If a new major release is required, bump the major version (eg `4.1.1` → `5.0.0`)
- Note: The gradle actions follow the GitHub Actions convention of including a .0 patch number for the first release of a minor version, unlike the Gradle convention which omits the trailing .0.
- Release immutability is a one-time repository setting (Settings → General → Releases → "Enable release immutability"), and applies to every release published thereafter.
## Release gradle/actions
- Create a tag for the release. The tag should have the format `v6.2.0`
> [!IMPORTANT]
> Releases in this repository are [immutable](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases).
> Once a release is published, its Git tag is permanently locked to a commit and its assets can no
> longer be changed. Deleting the release does not release the tag name for reuse — a mistake in
> `v6.3.0` means the next release must be `v6.3.1`. Everything below is ordered so that the tag is
> correct *before* anything is published.
>
> Drafts are exempt: a draft release, and its tag, can still be edited or deleted freely.
- Create and push the tag for the release **before** drafting it. The tag should have the format `v6.2.0`
- Your HEAD must point at the commit to be released.
- From CLI: `git tag -s -m "v6.2.0" v6.2.0 && git push --tags`
- Note that we sign the tag and set the commit message for the tag to the newly released version.
- Do **not** let GitHub create the tag when the release is published. It creates an unsigned,
lightweight tag pointing at whatever `main` resolves to at that moment — so anything merged
while the draft was open is silently included. Immutability makes both permanent.
- Go to https://github.com/gradle/actions/releases and "Draft new release"
- Use the newly created tag and copy the tag name exactly as the release title.
- Select the existing tag — do not use "Create new tag on publish" — and copy the tag name exactly as the release title.
- Craft release notes content based on issues closed, PRs merged and commits
- Include a Full changelog link in the format https://github.com/gradle/actions/compare/v2.12.0...v3.0.0
- Finalize the notes now, while the release is still a draft.
- Publish the release.
- Confirm immutability took effect: `gh api repos/gradle/actions/releases/tags/v6.2.0 --jq .immutable` should print `true`.
- Force push the `v6` tag (or current major version) to point to the new release. It is conventional for users to bind to a major release version using this tag.
- From CLI: `git tag -f -s -a -m "v6.2.0" v6 && git push -f --tags`
- Note that we sign the tag and set the commit message for the tag to the newly released version.
- Your HEAD must point at the commit to be tagged.
- Never attach a GitHub release to the `v6` tag. Immutability locks only those tags that have a
release of their own, so the major tag remains movable precisely as long as it has none.
## Post release steps
+1
View File
@@ -31,6 +31,7 @@ inputs:
Specifies the caching implementation to use.
'enhanced' (default) uses the full-featured commercial caching service (gradle-actions-caching).
'basic' uses a simple open-source caching implementation based on GitHub Actions cache.
'external' disables Gradle User Home caching by this action, for use when Gradle User Home is already saved and restored by another mechanism (e.g. Develocity Artifact Cache).
required: false
default: 'enhanced'
+153 -144
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+162 -144
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+152 -143
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+198 -181
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+90 -84
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+50 -1
View File
@@ -133,6 +133,7 @@ You choose which provider to use via the `cache-provider` input:
- **`enhanced`** (default): Uses the full-featured commercial `gradle-actions-caching` library. Provides advanced features like fine-grained cache entries, intelligent cache cleanup, and deduplication. See [Enhanced Caching](#enhanced-caching) for details.
- **`basic`**: A fully open-source (MIT) caching implementation built on the standard GitHub Actions cache (`@actions/cache`). Uses the same caching strategy as `actions/setup-java` with `cache: gradle`. See [Basic Caching](#basic-caching) for details.
- **`external`**: Disables Gradle User Home caching by this action, for use when another mechanism already saves and restores Gradle User Home. See [Using an external cache provider](#using-an-external-cache-provider) for details.
```yaml
# Use the open-source basic cache provider
@@ -185,7 +186,17 @@ Specifically:
- Avoid using `actions/cache` configured to cache the Gradle User Home, [as described in this example](https://github.com/actions/cache/blob/main/examples.md#java---gradle).
- Avoid using `actions/setup-java` with the `cache: gradle` option, [as described here](https://github.com/actions/setup-java#caching-gradle-dependencies).
Using either of these mechanisms may interfere with the caching provided by this action. If you choose to use a different mechanism to save and restore the Gradle User Home, you should disable the caching provided by this action, as described above.
Using either of these mechanisms may interfere with the caching provided by this action. If you choose to use a different mechanism to save and restore the Gradle User Home, you should disable the caching provided by this action, as described above, or [use `cache-provider: external`](#using-an-external-cache-provider) if that mechanism is a dedicated Gradle User Home cache.
#### Using an external cache provider
If Gradle User Home is already saved and restored by another mechanism — for example the [Develocity Artifact Cache](https://github.com/gradle/actions-caching) — set `cache-provider: external` rather than `cache-disabled: true`.
Both settings stop `setup-gradle` from restoring or saving Gradle User Home, but `cache-provider: external` reflects the actual reason: caching is still happening, just not through this action. It also updates the Job Summary to say that caching was handled externally, rather than implying that caching was turned off.
```yaml
cache-provider: external
```
## Enhanced Caching
@@ -507,6 +518,30 @@ so that a Job Summary is never generated, or so that a Job Summary is only gener
add-job-summary: 'on-failure' # Valid values are 'always' (default), 'never', and 'on-failure'
```
### Gradle version support status
The Job Summary reports the support status of each Gradle version used in the workflow, and the action adds a
Job annotation for any version that is no longer current. The latest Gradle release is determined from release data
bundled with the action, so no network access is required.
A version is reported when it is:
- **End-of-life** — two or more major versions behind the latest release. Each such version is marked with :warning:
in the build results table, a single expandable section below the table explains that the affected release lines
receive no further fixes (security fixes included), and a warning annotation is added to the Job for each version.
If you cannot upgrade, the [Gradle Security Subscription](https://gradle.org/security-subscription/) offers
continued support for older versions.
- **Out of date** — one major version behind the latest release, or more than two minor versions behind on the current
major. The version is marked with :information_source: in the build results table and a notice annotation is added to
the Job. Note that a version one major behind is still in "maintenance only" support and receives critical bug fixes
and security fixes; an older minor of the current major has simply been superseded. See
[Gradle release lifecycle](https://docs.gradle.org/current/userguide/feature_lifecycle.html#eol_support) for details.
Patch releases are not reported: only the major and minor version are considered. Release candidates, milestones and
snapshots are never reported, so testing against a pre-release build will not produce annotations.
Note that these annotations are always emitted, independent of the `add-job-summary` setting. Setting
`add-job-summary: 'never'` suppresses the Job Summary itself, but the warning and notice annotations remain.
### Excluding specific Gradle builds from Job Summary
The Job Summary works by installing an init-script in Gradle User Home which will record details of any Gradle execution during the workflow.
@@ -1005,5 +1040,19 @@ Each of the plugins is signed by Gradle, and you can simply add the following sn
<trusting group="com.gradle"/>
<trusting group="org.gradle"/>
</trusted-key>
<trusted-key id="E2879931BCA1A42E55F2D64DD9B2DFBD9F3298BA">
<trusting group="org.gradle"/>
</trusted-key>
</trusted-keys>
```
Two keys are listed because Gradle has rotated its signing key, and the plugins applied by these actions
are not all signed with the same one:
- `E2879931BCA1A42E55F2D64DD9B2DFBD9F3298BA` is the current signing subkey. The
`github-dependency-graph-gradle-plugin` used for Dependency Graph generation is signed with it from
version `1.5.0` onward.
- `7B79ADD11F8A779FE90FD3D0893A028475557671` is the older key, which has since been revoked. It is still
required: the Develocity Gradle plugin (`com.gradle`) is signed with it, as are `org.gradle` plugin
versions released before the rotation.
Keep both entries unless you pin plugin versions and have confirmed which key each one uses.
+1 -1
View File
@@ -111,7 +111,7 @@ we highly recommend that you reach out to us at [[email protected]](mailto:sec
#### Unverifiable Wrapper Jars
Wrapper Jars generated by Gradle versions `3.3` to `4.0` are not verifiable because those files were dynamically generated by Gradle in a non-reproducible way. It's not possible to verify the `gradle-wrapper.jar` for those versions are legitimate using a hash comparison. If you have a validation failure, you should try to determine if the `gradle-wrapper.jar` was generated by one of these versions before running the build.
- If the Gradle version in `gradle-wrapper.properties` is outside of this range, you can regenerate the `gradle-wrapper.jar` by running `./gradlew wrapper`. This will generate a new, verifiable wrapper jar.
- If the Gradle version in `gradle-wrapper.properties` is outside of this range, you can regenerate the `gradle-wrapper.jar` by running `./gradlew :wrapper`. This will generate a new, verifiable wrapper jar.
- If you need to run your build with a version of Gradle between 3.3 and 4.0, you can use a newer version of Gradle to generate the `gradle-wrapper.jar`.
#### Wrapper Jar stored with Git LFS
+1
View File
@@ -14,6 +14,7 @@ inputs:
Specifies the caching implementation to use.
'enhanced' (default) uses the full-featured commercial caching service (gradle-actions-caching).
'basic' uses a simple open-source caching implementation based on GitHub Actions cache.
'external' disables Gradle User Home caching by this action, for use when Gradle User Home is already saved and restored by another mechanism (e.g. Develocity Artifact Cache).
required: false
default: 'enhanced'
+2540 -1215
View File
File diff suppressed because it is too large Load Diff
+4 -6
View File
@@ -45,7 +45,6 @@
"@actions/tool-cache": "4.0.0",
"@octokit/webhooks-types": "7.6.1",
"cheerio": "1.2.0",
"semver": "7.8.5",
"string-argv": "0.3.2",
"unhomoglyph": "1.0.6",
"which": "7.0.0"
@@ -54,14 +53,13 @@
"@jest/globals": "30.4.1",
"@types/jest": "30.0.0",
"@types/node": "24.13.3",
"@types/semver": "7.7.1",
"@types/unzipper": "0.10.11",
"@types/which": "3.0.4",
"@typescript-eslint/eslint-plugin": "8.65.0",
"@typescript-eslint/eslint-plugin": "8.69.0",
"dedent": "1.7.2",
"esbuild": "0.28.1",
"eslint": "10.8.0",
"globals": "17.8.0",
"esbuild": "0.28.2",
"eslint": "10.10.0",
"globals": "17.12.0",
"jest": "30.4.2",
"nock": "14.0.17",
"npm-run-all": "4.1.5",
+14 -5
View File
@@ -5,14 +5,18 @@ import {pathToFileURL} from 'url'
import {CacheConfig, CacheProvider} from './configuration'
import {BasicCacheService} from './cache-service-basic'
import {BuildResult} from './build-results'
import {CacheOptions, CacheReport, CacheService} from './cache-service'
import {CacheOptions, CacheReport, CacheService, CacheStatus} from './cache-service'
import {ProviderNote} from './caching-report'
const ENHANCED_CACHE_MESSAGE = `Enhanced Caching: This build is using the proprietary 'gradle-actions-caching' provider for optimized caching support. See https://github.com/gradle/actions/blob/main/DISTRIBUTION.md for terms of use and opt-out instructions.`
const BASIC_CACHE_MESSAGE = `Basic Caching: This build uses the basic open-source caching provider. For faster builds and advanced features, consider switching to the Enhanced Caching provider. See https://github.com/gradle/actions/blob/main/DISTRIBUTION.md for details.`
const EXTERNAL_CACHE_MESSAGE = `External Caching: Gradle User Home is managed by an external caching provider. This action will not restore or save Gradle User Home.`
class NoOpCacheService implements CacheService {
constructor(private readonly status: CacheStatus) {}
async restore(_gradleUserHome: string, _cacheOptions: CacheOptions): Promise<void> {
return
}
@@ -22,14 +26,19 @@ class NoOpCacheService implements CacheService {
_buildResults: BuildResult[],
_cacheOptions: CacheOptions
): Promise<CacheReport> {
return {status: 'disabled', entries: []}
return {status: this.status, entries: []}
}
}
export async function getCacheService(cacheConfig: CacheConfig): Promise<CacheService> {
if (cacheConfig.getCacheProvider() === CacheProvider.External) {
logCacheMessage(EXTERNAL_CACHE_MESSAGE)
return new NoOpCacheService('external')
}
if (cacheConfig.isCacheDisabled()) {
logCacheMessage('Cache is disabled: will not restore state from previous builds.')
return new NoOpCacheService()
return new NoOpCacheService('disabled')
}
if (cacheConfig.getCacheProvider() === CacheProvider.Basic) {
@@ -43,10 +52,10 @@ export async function getCacheService(cacheConfig: CacheConfig): Promise<CacheSe
/**
* Identifies the caching provider for the Job Summary. Returns `undefined` when
* caching is disabled, since no provider is engaged in that case.
* caching is disabled or managed externally, since no provider is engaged in that case.
*/
export function getProviderNote(cacheConfig: CacheConfig): ProviderNote | undefined {
if (cacheConfig.isCacheDisabled()) {
if (cacheConfig.isCacheDisabled() || cacheConfig.getCacheProvider() === CacheProvider.External) {
return undefined
}
return cacheConfig.getCacheProvider() === CacheProvider.Basic ? {kind: 'basic'} : {kind: 'enhanced'}
+12 -2
View File
@@ -15,10 +15,20 @@ export interface CacheOptions {
}
export type CacheStatus =
'enabled' | 'read-only' | 'write-only' | 'disabled' | 'disabled-existing-home' | 'not-available'
| 'enabled' // Gradle User Home was restored from and saved to the cache
| 'read-only' // restored from the cache, but not saved
| 'write-only' // saved to the cache, but not restored
| 'disabled' // caching was turned off via the cache-disabled parameter
| 'disabled-existing-home' // a pre-existing Gradle User Home was found, so caching was skipped
| 'not-available' // the GitHub Actions cache service could not be reached
| 'external' // Gradle User Home is cached by an external provider, not by this action
export type CacheCleanupStatus =
'enabled' | 'disabled-param' | 'disabled-failure' | 'disabled-config-cache-hit' | 'disabled-readonly'
| 'enabled' // stale files were purged from Gradle User Home before saving
| 'disabled-param' // disabled via action parameter
| 'disabled-failure' // skipped due to a build failure
| 'disabled-config-cache-hit' // skipped due to configuration-cache reuse
| 'disabled-readonly' // always disabled when the cache is read-only
export type ProjectCacheStatus =
| 'not-enabled' // the hidden opt-in env var was not set (rendered as nothing)
+9 -2
View File
@@ -17,7 +17,8 @@ const STATUS_COPY: Record<CacheStatus, string> = {
'write-only': `[Cache was write-only](${DOCS}#using-the-cache-write-only) — Gradle User Home was not restored from the cache.`,
disabled: `[Caching was disabled](${DOCS}#disabling-caching) — Gradle User Home was not restored from or saved to the cache.`,
'disabled-existing-home': `⚠️ [Caching was skipped](${DOCS}#overwriting-an-existing-gradle-user-home) — a pre-existing Gradle User Home was found, so the cache was not restored or saved.`,
'not-available': `Caching is not available — the GitHub Actions cache service could not be reached, so Gradle User Home was not restored or saved.`
'not-available': `Caching is not available — the GitHub Actions cache service could not be reached, so Gradle User Home was not restored or saved.`,
external: `[Gradle User Home is cached externally](${DOCS}#using-an-external-cache-provider) — this action did not restore or save the Gradle User Home.`
}
const CLEANUP_COPY: Record<CacheCleanupStatus, string> = {
@@ -63,7 +64,13 @@ function isActive(status: CacheStatus): boolean {
function renderHeading(status: CacheStatus, providerNote?: ProviderNote): string {
if (!isActive(status)) {
const label =
status === 'disabled-existing-home' ? 'Skipped' : status === 'not-available' ? 'Unavailable' : 'Disabled'
status === 'disabled-existing-home'
? 'Skipped'
: status === 'not-available'
? 'Unavailable'
: status === 'external'
? 'External'
: 'Disabled'
return `<h4>Gradle State Caching - ${label}</h4>`
}
+7 -2
View File
@@ -175,14 +175,19 @@ export class CacheConfig {
case 'enhanced':
case '':
return CacheProvider.Enhanced
case 'external':
return CacheProvider.External
}
throw TypeError(`The value '${val}' is not valid for 'cache-provider'. Valid values are: [basic, enhanced].`)
throw TypeError(
`The value '${val}' is not valid for 'cache-provider'. Valid values are: [basic, enhanced, external].`
)
}
}
export enum CacheProvider {
Basic = 'basic',
Enhanced = 'enhanced'
Enhanced = 'enhanced',
External = 'external'
}
export enum CacheCleanupOption {
+52 -11
View File
@@ -147,7 +147,6 @@ type HostnameAccessKey = {
}
export class DevelocityAccessCredentials {
static readonly accessKeyRegexp = /^([^;=\s]+=\w+)(;[^;=\s]+=\w+)*$/
readonly keys: HostnameAccessKey[]
private constructor(allKeys: HostnameAccessKey[]) {
@@ -160,17 +159,63 @@ export class DevelocityAccessCredentials {
private static readonly keyDelimiter = ';'
private static readonly hostDelimiter = '='
private static readonly whitespace = /\s/
/**
* Parse a `host=key[;host=key]*` access key value.
*
* Only the structure needed to split the value is validated: entries are separated by `;`, and
* each entry is a hostname followed by `=` and a key, where the hostname contains no `=`, `;` or
* whitespace, and the key is non-empty and contains no `;` or whitespace. Nothing else is
* assumed about the key: it may be an OIDC token containing `.`, `-`, `_` and `=` padding, so
* each entry is split on its _first_ `=` only.
*
* Returns `null` if the value doesn't match, emitting a warning that describes what is wrong.
*/
static parse(rawKey: string): DevelocityAccessCredentials | null {
if (!this.isValid(rawKey)) {
const trimmedKey = rawKey.trim()
if (!trimmedKey) {
return null
}
return new DevelocityAccessCredentials(
rawKey.split(this.keyDelimiter).map(hostKey => {
const pair = hostKey.split(this.hostDelimiter)
return {hostname: pair[0], key: pair[1]}
})
const keys = new Array<HostnameAccessKey>()
const entries = trimmedKey.split(this.keyDelimiter)
for (const [index, entry] of entries.entries()) {
const separatorIndex = entry.indexOf(this.hostDelimiter)
if (separatorIndex === -1) {
return this.warnBadlyFormed(index, entries.length, `no '${this.hostDelimiter}' separator`)
}
const hostname = entry.substring(0, separatorIndex)
const key = entry.substring(separatorIndex + 1)
if (!hostname) {
return this.warnBadlyFormed(index, entries.length, 'empty server name')
}
if (!key) {
return this.warnBadlyFormed(index, entries.length, 'empty key')
}
if (this.whitespace.test(hostname)) {
return this.warnBadlyFormed(index, entries.length, 'whitespace in the server name')
}
if (this.whitespace.test(key)) {
return this.warnBadlyFormed(index, entries.length, 'whitespace in the key')
}
keys.push({hostname, key})
}
return new DevelocityAccessCredentials(keys)
}
/**
* Warn that an access key value is badly formed and cannot be parsed. Reports only the position
* of the offending entry and the reason: the value is a secret, and is not yet registered for
* masking at this point, so no part of it is ever included in the message.
*/
private static warnBadlyFormed(index: number, entryCount: number, reason: string): null {
const location = entryCount > 1 ? `entry ${index + 1} of ${entryCount}` : 'the value'
core.warning(
`Ignoring badly formed Develocity access key: ${reason} in ${location}. ` +
`The expected format is 'server${this.hostDelimiter}key` +
`[${this.keyDelimiter}server${this.hostDelimiter}key]*'.`
)
return null
}
isEmpty(): boolean {
@@ -182,10 +227,6 @@ export class DevelocityAccessCredentials {
.map(k => `${k.hostname}${DevelocityAccessCredentials.hostDelimiter}${k.key}`)
.join(DevelocityAccessCredentials.keyDelimiter)
}
private static isValid(allKeys: string): boolean {
return this.accessKeyRegexp.test(allKeys)
}
}
/**
-62
View File
@@ -2,7 +2,6 @@ import * as core from '@actions/core'
import * as exec from '@actions/exec'
import which from 'which'
import * as semver from 'semver'
import * as provisioner from './provision'
import * as gradlew from './gradlew'
@@ -34,48 +33,6 @@ async function executeGradleBuild(executable: string | undefined, root: string,
}
}
export function versionIsAtLeast(actualVersion: string, requiredVersion: string): boolean {
if (actualVersion === requiredVersion) {
return true
}
const actual = new GradleVersion(actualVersion)
const required = new GradleVersion(requiredVersion)
const actualSemver = semver.coerce(actual.versionPart)!
const comparisonSemver = semver.coerce(required.versionPart)!
if (semver.gt(actualSemver, comparisonSemver)) {
return true // Actual version is greater than comparison. So it's at least as new.
}
if (semver.lt(actualSemver, comparisonSemver)) {
return false // Actual version is less than comparison. So it's not as new.
}
// Actual and required version numbers are equal, so compare the other parts
if (actual.snapshotPart || required.snapshotPart) {
if (actual.snapshotPart && !required.snapshotPart && !required.stagePart) {
return false // Actual has a snapshot, but required is a plain version. Required is newer.
}
if (required.snapshotPart && !actual.snapshotPart && !actual.stagePart) {
return true // Required has a snapshot, but actual is a plain version. Actual is newer.
}
return false // Cannot compare case where both versions have a snapshot or stage
}
if (actual.stagePart) {
if (required.stagePart) {
return actual.stagePart >= required.stagePart // Compare stages for newer
}
return false // Actual has a stage, but required does not. So required is always newer.
}
return true // Actual has no stage part or snapshot part, so it cannot be older than required.
}
export async function findGradleExecutableOnPath(): Promise<string | null> {
return await which('gradle', {nothrow: true})
}
@@ -90,22 +47,3 @@ export function parseGradleVersionFromOutput(output: string): string | undefined
const versionString = output.match(regex)?.[1]
return versionString
}
class GradleVersion {
static PATTERN = /((\d+)(\.\d+)+)(-([a-z]+)-(\w+))?(-(SNAPSHOT|\d{14}([-+]\d{4})?))?/
versionPart: string
stagePart: string
snapshotPart: string
constructor(readonly version: string) {
const matcher = GradleVersion.PATTERN.exec(version)
if (!matcher) {
throw new Error(`'${version}' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')`)
}
this.versionPart = matcher[1]
this.stagePart = matcher[4]
this.snapshotPart = matcher[7]
}
}
+152
View File
@@ -0,0 +1,152 @@
import * as core from '@actions/core'
import {GradleVersion} from './gradle-version'
import wrapperChecksums from './wrapper-validation/wrapper-checksums.json'
/** Minor lines behind the latest that stay unreported on the current major. */
const MINOR_GRACE = 2
const SECURITY_SUBSCRIPTION = 'https://gradle.org/security-subscription/?utm_source=github-action'
const FEATURE_LIFECYCLE_DOC = 'https://docs.gradle.org/current/userguide/feature_lifecycle.html#eol_support'
enum SupportStatus {
Current = 'current',
Behind = 'behind',
Eol = 'eol'
}
/** The sign shown beside a version in the build-results table; Current has nothing to say. */
const SIGN: Record<SupportStatus, string> = {
[SupportStatus.Current]: '',
[SupportStatus.Behind]: ':information_source:',
[SupportStatus.Eol]: ':warning:'
}
const UPGRADE_LEGEND =
`<p>${SIGN[SupportStatus.Behind]} Gradle version is out of date — consider upgrading. ` +
`See <a href="${FEATURE_LIFECYCLE_DOC}">Gradle release lifecycle</a></p>`
/**
* Wording shared by the end-of-life annotation, which names a single version, and the end-of-life
* section of the Job Summary, which covers every flagged version at once.
*/
const eolHeadline = (versions: GradleVersion[]): string =>
versions.length === 1
? `Gradle ${versions[0].version} is end-of-life`
: `Gradle ${andList(versions.map(version => version.version))} are end-of-life`
const eolDetail = (versions: GradleVersion[]): string =>
`Gradle ${andList([...new Set(versions.map(version => `${version.major}.x`))])} releases receive ` +
`no further fixes, security fixes included. Update to the latest Gradle version.`
/** Joins for prose: 'a', 'a and b', 'a, b and c'. */
function andList(items: string[]): string {
return items.length <= 1 ? (items[0] ?? '') : `${items.slice(0, -1).join(', ')} and ${items.at(-1)}`
}
class ReleaseIndex {
private readonly latest: GradleVersion
constructor(releasedVersions: string[]) {
const finalReleases = releasedVersions
.map(version => new GradleVersion(version))
.filter(version => version.isFinalRelease())
if (finalReleases.length === 0) {
throw new Error('The Gradle release data contains no final release')
}
this.latest = finalReleases.reduce((max, version) => (max.compareTo(version) >= 0 ? max : version))
}
private classifyVersion(version: GradleVersion): SupportStatus {
if (!version.isFinalRelease()) {
return SupportStatus.Current
}
const majorsBehind = this.latest.major - version.major
if (majorsBehind >= 2) {
return SupportStatus.Eol
}
if (majorsBehind === 1) {
return SupportStatus.Behind
}
if (version.major > this.latest.major) {
return SupportStatus.Current // newer than the bundled data knows about
}
// Same major as the latest release: only the minor distance matters, so a newer patch is never reported.
return this.latest.minor - version.minor > MINOR_GRACE ? SupportStatus.Behind : SupportStatus.Current
}
/** Support status of a single version; an unparseable string is treated as current. */
classify(gradleVersion: string): SupportStatus {
const version = GradleVersion.parseUntrusted(gradleVersion)
return version ? this.classifyVersion(version) : SupportStatus.Current
}
/** The flagged versions grouped by status (Current omitted). */
classified(gradleVersions: string[]): Map<SupportStatus, GradleVersion[]> {
const byStatus = new Map<SupportStatus, GradleVersion[]>()
const parsed = [...new Set(gradleVersions)]
.map(version => GradleVersion.parseUntrusted(version))
.filter((version): version is GradleVersion => version !== undefined)
.sort(GradleVersion.compare)
for (const version of parsed) {
const status = this.classifyVersion(version)
if (status !== SupportStatus.Current) {
byStatus.set(status, [...(byStatus.get(status) ?? []), version])
}
}
return byStatus
}
}
const RELEASES = new ReleaseIndex(wrapperChecksums.map(entry => entry.version))
/** The status sign shown beside a version in the build-results table, or '' when there is nothing to say. */
export function supportStatusSign(gradleVersion: string): string {
return SIGN[RELEASES.classify(gradleVersion)]
}
/** Job annotations: a warning for each end-of-life version, a notice for each version merely out of date. */
export function reportSupportStatus(gradleVersions: string[]): void {
const byStatus = RELEASES.classified(gradleVersions)
for (const version of byStatus.get(SupportStatus.Eol) ?? []) {
core.warning(
`${eolHeadline([version])}. ${eolDetail([version])} If you cannot upgrade, see ${SECURITY_SUBSCRIPTION} for options`,
{title: 'End-of-life Gradle version'}
)
}
for (const version of byStatus.get(SupportStatus.Behind) ?? []) {
core.notice(
`Gradle ${version.version} is out of date: consider updating to the latest Gradle version. See ${FEATURE_LIFECYCLE_DOC}`,
{title: 'Out-of-date Gradle version'}
)
}
}
/** The fold-and-paragraph report placed under the build-results table. */
export function renderSupportStatus(gradleVersions: string[]): string {
const byStatus = RELEASES.classified(gradleVersions)
const blocks: string[] = []
const eol = byStatus.get(SupportStatus.Eol) ?? []
if (eol.length > 0) {
// One section however many versions are end-of-life; the table already marks which they are.
blocks.push(renderEolSection(eol))
}
if (byStatus.has(SupportStatus.Behind)) {
blocks.push(UPGRADE_LEGEND)
}
// The leading blank line closes the preceding HTML block, so each rendering stands on its own.
return blocks.length > 0 ? `\n${blocks.join('\n')}\n` : ''
}
function renderEolSection(versions: GradleVersion[]): string {
return `<details>
<summary>${SIGN[SupportStatus.Eol]} ${eolHeadline(versions)}</summary>
<p>${eolDetail(versions)}</p>
<p>If you cannot upgrade, see the <a href="${SECURITY_SUBSCRIPTION}">Gradle Security Subscription</a> for options.</p>
</details>`
}
+134
View File
@@ -0,0 +1,134 @@
interface Stage {
readonly rank: number
readonly number: number
readonly patchNo: string
}
/**
* A Gradle version, parsed and ordered the same way Gradle's own `org.gradle.util.GradleVersion` does
*/
export class GradleVersion {
private static readonly PATTERN = /^((\d+)(\.\d+)+)(-([a-zA-Z]+)-(\w+))?(-(SNAPSHOT|\d{14}([-+]\d{4})?))?$/
// Ranks match Gradle's stage numbers; anything unrecognised sits between milestone and preview.
private static readonly STAGE_RANK: Record<string, number> = {milestone: 0, preview: 2, rc: 3}
private static readonly STAGE_UNKNOWN = 1
readonly major: number
readonly minor: number
readonly patch: number
private readonly versionPart: string
private readonly stage: Stage | undefined
private readonly snapshot: number | undefined
constructor(readonly version: string) {
const matcher = GradleVersion.PATTERN.exec(version)
if (!matcher) {
throw new Error(`'${version}' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')`)
}
this.versionPart = matcher[1]
this.stage = GradleVersion.parseStage(matcher[5], matcher[6])
this.snapshot = GradleVersion.parseSnapshot(matcher[8], matcher[9])
const parts = this.versionPart.split('.')
this.major = Number(parts[0])
this.minor = Number(parts[1])
this.patch = parts.length > 2 ? Number(parts[2]) : 0
}
isFinalRelease(): boolean {
return this.stage === undefined && this.snapshot === undefined
}
compareTo(other: GradleVersion): number {
const parts = this.versionPart.split('.')
const otherParts = other.versionPart.split('.')
for (let i = 0; i < parts.length && i < otherParts.length; i++) {
if (Number(parts[i]) !== Number(otherParts[i])) {
return Math.sign(Number(parts[i]) - Number(otherParts[i]))
}
}
if (parts.length !== otherParts.length) {
return Math.sign(parts.length - otherParts.length)
}
if (this.stage && other.stage) {
const stageDiff = GradleVersion.compareStages(this.stage, other.stage)
if (stageDiff !== 0) {
return stageDiff
}
} else if (this.stage) {
return -1 // a staged version precedes the final release of the same base
} else if (other.stage) {
return 1
}
// A version with no snapshot is newer than any snapshot of the same base.
const thisSnapshot = this.snapshot ?? Number.MAX_SAFE_INTEGER
const otherSnapshot = other.snapshot ?? Number.MAX_SAFE_INTEGER
if (thisSnapshot !== otherSnapshot) {
return Math.sign(thisSnapshot - otherSnapshot)
}
return this.version < other.version ? -1 : this.version > other.version ? 1 : 0
}
static parseUntrusted(version: string): GradleVersion | undefined {
try {
return new GradleVersion(version)
} catch {
return undefined
}
}
/** Comparator for sorting; `this: void` marks it safe to pass detached, as `Array.sort` does. */
static compare(this: void, a: GradleVersion, b: GradleVersion): number {
return a.compareTo(b)
}
private static parseStage(name: string | undefined, numberPart: string | undefined): Stage | undefined {
if (name === undefined) {
return undefined
}
// Stage names are matched exactly, as Gradle does: 'RC' is not 'rc', and ranks as unknown.
const rank = GradleVersion.STAGE_RANK[name] ?? GradleVersion.STAGE_UNKNOWN
// The whole stage string must be a number with an optional letter suffix; anything else ranks as 0,
// leaving versions such as branch names to be separated by the version string itself.
const match = /^(\d+)([a-z])?$/.exec(numberPart ?? '')
return {rank, number: match ? Number(match[1]) : 0, patchNo: match?.[2] ?? '_'}
}
private static parseSnapshot(snapshot: string | undefined, timezone: string | undefined): number | undefined {
if (snapshot === undefined) {
return undefined
}
if (snapshot === 'SNAPSHOT') {
return 0
}
// A 14-digit timestamp (yyyyMMddHHmmss), optionally with a timezone offset like +0000 (UTC when absent).
const date = `${snapshot.slice(0, 4)}-${snapshot.slice(4, 6)}-${snapshot.slice(6, 8)}`
const time = `${snapshot.slice(8, 10)}:${snapshot.slice(10, 12)}:${snapshot.slice(12, 14)}`
const offset = timezone ? `${timezone.slice(0, 3)}:${timezone.slice(3, 5)}` : 'Z'
const instant = Date.parse(`${date}T${time}${offset}`)
if (Number.isNaN(instant)) {
// The digits matched the pattern but name no real instant. Rejecting here keeps a NaN out of
// compareTo, which would otherwise make ordering undefined. Gradle likewise fails to parse.
throw new Error(`'${snapshot}${timezone ?? ''}' is not a valid Gradle snapshot timestamp`)
}
return instant
}
private static compareStages(a: Stage, b: Stage): number {
if (a.rank !== b.rank) {
return Math.sign(a.rank - b.rank)
}
if (a.number !== b.number) {
return Math.sign(a.number - b.number)
}
if (a.patchNo !== b.patchNo) {
return a.patchNo < b.patchNo ? -1 : 1
}
return 0
}
}
+20 -6
View File
@@ -6,6 +6,7 @@ import {CacheReport} from './cache-service'
import {ProviderNote, renderCachingReport} from './caching-report'
import {DependencyGraphConfig, getActionId, getGithubToken, getJobMatrix, SummaryConfig} from './configuration'
import {Deprecation, getDeprecations, getErrors} from './deprecation-collector'
import {renderSupportStatus, supportStatusSign} from './gradle-support-status'
export async function generateJobSummary(
buildResults: BuildResult[],
@@ -15,8 +16,11 @@ export async function generateJobSummary(
): Promise<void> {
core.startGroup('Generating Job Summary')
const heading = renderActionHeading()
const errors = renderErrors()
if (errors) {
core.summary.addRaw(heading)
core.summary.addRaw(errors)
await core.summary.write()
return
@@ -31,6 +35,7 @@ export async function generateJobSummary(
core.info(cachingReport)
if (config.shouldGenerateJobSummary(hasFailure)) {
core.summary.addRaw(heading)
core.summary.addRaw(summaryTable)
core.summary.addRaw(cachingReport)
await core.summary.write()
@@ -57,8 +62,7 @@ async function addPRComment(jobSummary: string): Promise<void> {
core.info(`Adding Job Summary as comment to PR #${pull_request_number}.`)
const prComment = `${jobMarker(context)}
<h3>Job Summary for Gradle</h3>
<a href="${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}" target="_blank">
${renderActionHeading()}<a href="${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}" target="_blank">
<h5>${context.workflow} :: <em>${context.job}</em></h5>
</a>
@@ -94,7 +98,13 @@ Note that this permission is never available for a workflow triggered from a rep
}
export function renderSummaryTable(results: BuildResult[]): string {
return `${renderDeprecations()}\n${renderBuildResults(results)}`
return `${renderDeprecations()}\n${renderBuildResults(results)}\n${renderSupportStatus(results.map(result => result.gradleVersion))}`
}
function renderActionHeading(): string {
const actionId = getActionId()
const caption = actionId ? ` <sub><em>captured by ${actionId}</em></sub>` : ''
return `<h3>Gradle Builds${caption}</h3>\n\n`
}
function renderErrors(): string | undefined {
@@ -138,8 +148,7 @@ function renderBuildResults(results: BuildResult[]): string {
<th>Build Outcome</th>
<th>Build&nbsp;Scan®</th>
</tr>${results.map(result => renderBuildResultRow(result)).join('')}
</table>
`
</table>`
}
function anyFailed(results: BuildResult[]): boolean {
@@ -151,12 +160,17 @@ function renderBuildResultRow(result: BuildResult): string {
<tr>
<td>${truncateString(result.rootProjectName, 30)}</td>
<td>${truncateString(result.requestedTasks, 60)}</td>
<td align='center'>${result.gradleVersion}</td>
<td align='center'>${renderGradleVersion(result.gradleVersion)}</td>
<td align='center'>${renderOutcome(result)}</td>
<td>${renderBuildScan(result)}</td>
</tr>`
}
function renderGradleVersion(gradleVersion: string): string {
const sign = supportStatusSign(gradleVersion)
return sign ? `${gradleVersion} ${sign}` : gradleVersion
}
function renderOutcome(result: BuildResult): string {
return result.buildFailed ? ':x:' : ':white_check_mark:'
}
@@ -6,7 +6,7 @@ buildscript {
def pluginRepositoryUrl = getInputParam('gradle.plugin-repository.url') ?: 'https://plugins.gradle.org/m2'
def pluginRepositoryUsername = getInputParam('gradle.plugin-repository.username')
def pluginRepositoryPassword = getInputParam('gradle.plugin-repository.password')
def dependencyGraphPluginVersion = getInputParam('dependency-graph-plugin.version') ?: '1.4.2'
def dependencyGraphPluginVersion = getInputParam('dependency-graph-plugin.version') ?: '1.5.0'
logger.lifecycle("Resolving dependency graph plugin ${dependencyGraphPluginVersion} from plugin repository: ${pluginRepositoryUrl}")
repositories {
+2
View File
@@ -19,6 +19,7 @@ import {
} from './configuration'
import * as wrapperValidator from './wrapper-validation/wrapper-validator'
import {initializeGradleUserHome} from './gradle-user-home'
import {reportSupportStatus} from './gradle-support-status'
const GRADLE_SETUP_VAR = 'GRADLE_BUILD_ACTION_SETUP_COMPLETED'
const GRADLE_USER_HOME = 'GRADLE_USER_HOME'
@@ -86,6 +87,7 @@ export async function complete(
cacheOptionsFrom(cacheConfig, develocityServerUrl, cacheToken)
)
await jobSummary.generateJobSummary(buildResults, cacheReport, getProviderNote(cacheConfig), summaryConfig)
reportSupportStatus(buildResults.map(result => result.gradleVersion))
markBuildResultsProcessed()
@@ -1,4 +1,24 @@
[
{
"version": "9.8.0-milestone-2",
"checksum": "2b2e2cee3d8a8e5379b4f1c5902419404e83c1dba5ff55192ad5986e3f44cd6e"
},
{
"version": "9.7.1",
"checksum": "7a9ce74cff467ca1bf60a4fcd9f05185acceda4d0f382434d393e17864262c5d"
},
{
"version": "9.8.0-milestone-1",
"checksum": "2b2e2cee3d8a8e5379b4f1c5902419404e83c1dba5ff55192ad5986e3f44cd6e"
},
{
"version": "9.7.0",
"checksum": "7a9ce74cff467ca1bf60a4fcd9f05185acceda4d0f382434d393e17864262c5d"
},
{
"version": "9.7.0-rc-3",
"checksum": "7a9ce74cff467ca1bf60a4fcd9f05185acceda4d0f382434d393e17864262c5d"
},
{
"version": "9.7.0-rc-2",
"checksum": "7a9ce74cff467ca1bf60a4fcd9f05185acceda4d0f382434d393e17864262c5d"
Binary file not shown.
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
@@ -45,6 +45,51 @@ describe('getCacheService selection logic', () => {
expect(service).toBeInstanceOf(BasicCacheService)
})
it('returns NoOpCacheService reporting "external" when cache-provider is external', async () => {
const {getCacheService} = await import('../../src/cache-service-loader')
const mockConfig = {
isCacheDisabled: () => false,
getCacheProvider: () => CacheProvider.External
} as unknown as CacheConfig
const service = await getCacheService(mockConfig)
const report = await service.save('/home/.gradle', [], {
disabled: false,
readOnly: false,
writeOnly: false,
overwriteExisting: false,
strictMatch: false,
cleanup: 'never',
includes: [],
excludes: []
})
expect(report.status).toBe('external')
expect(report.entries).toHaveLength(0)
})
it('reports "external" even when cache-disabled is also true', async () => {
const {getCacheService} = await import('../../src/cache-service-loader')
const mockConfig = {
isCacheDisabled: () => true,
getCacheProvider: () => CacheProvider.External
} as unknown as CacheConfig
const service = await getCacheService(mockConfig)
const report = await service.save('/home/.gradle', [], {
disabled: false,
readOnly: false,
writeOnly: false,
overwriteExisting: false,
strictMatch: false,
cleanup: 'never',
includes: [],
excludes: []
})
expect(report.status).toBe('external')
})
describe('getProviderNote', () => {
it('returns undefined when cache is disabled', async () => {
const {getProviderNote} = await import('../../src/cache-service-loader')
@@ -56,6 +101,16 @@ describe('getCacheService selection logic', () => {
expect(getProviderNote(mockConfig)).toBeUndefined()
})
it('returns undefined when cache-provider is external', async () => {
const {getProviderNote} = await import('../../src/cache-service-loader')
const mockConfig = {
isCacheDisabled: () => false,
getCacheProvider: () => CacheProvider.External
} as unknown as CacheConfig
expect(getProviderNote(mockConfig)).toBeUndefined()
})
it('returns basic note for the basic provider', async () => {
const {getProviderNote} = await import('../../src/cache-service-loader')
const mockConfig = {
+10
View File
@@ -142,4 +142,14 @@ describe('renderCachingReport', () => {
expect(md).toContain('<h4>Gradle State Caching - Unavailable</h4>')
expect(md).not.toContain('<details>')
})
it('renders a compact external report with no provider note', () => {
const report: CacheReport = {status: 'external', entries: []}
const md = renderCachingReport(report, undefined)
expect(md).toContain('<h4>Gradle State Caching - External</h4>')
expect(md).toContain('cached externally')
expect(md).not.toContain('<details>')
expect(md).not.toContain('DISTRIBUTION.md')
})
})
@@ -0,0 +1,253 @@
import * as fs from 'fs'
import * as path from 'path'
import {beforeEach, describe, expect, it, jest} from '@jest/globals'
const mockWarning = jest.fn<(message: string, properties?: {title?: string}) => void>()
const mockNotice = jest.fn<(message: string, properties?: {title?: string}) => void>()
jest.unstable_mockModule('@actions/core', () => ({
warning: mockWarning,
notice: mockNotice
}))
// Mirrors the real release shape: latest 9.7.1, last 8.x minor is 8.14 with patches up to 8.14.5.
const RELEASED = [
'9.7.1',
'9.7.0',
'9.6.1',
'9.6.0',
'9.5.1',
'9.5.0',
'9.4.1',
'9.4.0',
'9.2.1',
'9.2.0',
'9.0.0',
'8.14.5',
'8.14',
'8.13',
'8.3',
'8.0.2',
'8.0',
'7.6.4',
'1.0'
]
const asChecksumEntries = (versions: string[]): {version: string; checksum: string}[] =>
versions.map(version => ({version, checksum: ''}))
let releasedVersions = asChecksumEntries(RELEASED)
jest.unstable_mockModule('../../src/wrapper-validation/wrapper-checksums.json', () => ({
get default() {
return releasedVersions
}
}))
const {renderSupportStatus, reportSupportStatus, supportStatusSign} = await import('../../src/gradle-support-status')
/** Re-imports the module so that its release index is built from `released` instead of RELEASED. */
async function withReleaseData(released: string[]): Promise<typeof import('../../src/gradle-support-status')> {
releasedVersions = asChecksumEntries(released)
jest.resetModules()
try {
return await import('../../src/gradle-support-status')
} finally {
releasedVersions = asChecksumEntries(RELEASED)
}
}
const NO_SIGN = ''
const OUT_OF_DATE = ':information_source:'
const END_OF_LIFE = ':warning:'
const DOC = 'https://docs.gradle.org/current/userguide/feature_lifecycle.html#eol_support'
const SECURITY_SUBSCRIPTION = 'https://gradle.org/security-subscription/?utm_source=github-action'
describe('supportStatusSign', () => {
it.each(['7.6.4', '1.0', '4.10.3'])('marks %s as end-of-life', version => {
expect(supportStatusSign(version)).toBe(END_OF_LIFE)
})
it.each(['8.0', '8.0.2', '8.3', '8.13', '8.14', '8.14.3', '8.14.5'])(
'marks %s as out of date, being on the previous major',
version => {
expect(supportStatusSign(version)).toBe(OUT_OF_DATE)
}
)
it.each(['8.15', '8.15.1'])(
'marks %s, a previous-major minor newer than the release data, as out of date',
version => {
expect(supportStatusSign(version)).toBe(OUT_OF_DATE)
}
)
it('marks a previous major absent from the release data as out of date', async () => {
const {supportStatusSign: sign} = await withReleaseData(['9.7.1', '7.6.4'])
expect(sign('8.0')).toBe(OUT_OF_DATE)
})
it.each(['9.0.0', '9.2.1', '9.4.1'])('marks %s as out of date, being more than 2 minors back', version => {
expect(supportStatusSign(version)).toBe(OUT_OF_DATE)
})
it.each(['9.5.0', '9.5.1', '9.6.1', '9.7.1'])('leaves %s unmarked, inside the grace band', version => {
expect(supportStatusSign(version)).toBe(NO_SIGN)
})
it('does not mark a version merely because a newer patch exists', () => {
expect(supportStatusSign('9.6.0')).toBe(NO_SIGN)
expect(supportStatusSign('9.7.0')).toBe(NO_SIGN)
})
it.each(['10.0', '10.4.2'])('leaves %s unmarked, newer than the release data', version => {
expect(supportStatusSign(version)).toBe(NO_SIGN)
})
it.each(['9.8.0-rc-1', '7.0-milestone-1', '9.8-20260101120000+0000'])(
'leaves non-final version %s unmarked',
version => {
expect(supportStatusSign(version)).toBe(NO_SIGN)
}
)
it('does not throw on an unparseable version', () => {
expect(supportStatusSign('')).toBe(NO_SIGN)
expect(supportStatusSign('unknown')).toBe(NO_SIGN)
})
})
describe('bundled release data', () => {
it('contains a final release, so the release index builds instead of failing', async () => {
const bundled: {version: string}[] = JSON.parse(
fs.readFileSync(path.resolve('src/wrapper-validation/wrapper-checksums.json'), 'utf-8')
)
const {supportStatusSign: sign} = await withReleaseData(bundled.map(entry => entry.version))
expect(sign('1.0')).toBe(END_OF_LIFE)
})
})
describe('reportSupportStatus', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('warns about an end-of-life version', () => {
reportSupportStatus(['7.6.4'])
expect(mockWarning).toHaveBeenCalledTimes(1)
expect(mockNotice).not.toHaveBeenCalled()
const [message, properties] = mockWarning.mock.calls[0]
expect(message).toBe(
`Gradle 7.6.4 is end-of-life. Gradle 7.x releases receive no further fixes, security fixes included. Update to the latest Gradle version. If you cannot upgrade, see ${SECURITY_SUBSCRIPTION} for options`
)
expect(properties?.title).toBe('End-of-life Gradle version')
})
it.each(['8.0', '8.14.3', '8.14.5', '9.2.1'])('notices %s as out of date', version => {
reportSupportStatus([version])
expect(mockWarning).not.toHaveBeenCalled()
expect(mockNotice).toHaveBeenCalledTimes(1)
const [message, properties] = mockNotice.mock.calls[0]
expect(message).toBe(
`Gradle ${version} is out of date: consider updating to the latest Gradle version. See ${DOC}`
)
expect(properties?.title).toBe('Out-of-date Gradle version')
})
it.each(['9.5.1', '9.6.1', '9.7.1'])('stays quiet about %s, inside the grace band', version => {
reportSupportStatus([version])
expect(mockWarning).not.toHaveBeenCalled()
expect(mockNotice).not.toHaveBeenCalled()
})
it('annotates each distinct version once, at the level its status warrants', () => {
reportSupportStatus(['7.6.4', '4.10.3', '7.6.4', '8.0', '9.2.1', '8.0'])
expect(mockWarning).toHaveBeenCalledTimes(2)
expect(mockNotice).toHaveBeenCalledTimes(2)
})
it('says nothing when no Gradle build ran', () => {
reportSupportStatus([])
expect(mockWarning).not.toHaveBeenCalled()
expect(mockNotice).not.toHaveBeenCalled()
})
})
describe('renderSupportStatus', () => {
const LEGEND =
`<p>${OUT_OF_DATE} Gradle version is out of date — consider upgrading. ` +
`See <a href="${DOC}">Gradle release lifecycle</a></p>`
it('folds end-of-life versions under a warning sign, linking the security subscription', () => {
const rendered = renderSupportStatus(['7.6.4'])
expect(rendered).toContain(`<summary>${END_OF_LIFE} Gradle 7.6.4 is end-of-life</summary>`)
expect(rendered).toContain(
'Gradle 7.x releases receive no further fixes, security fixes included. Update to the latest Gradle version.'
)
expect(rendered).toContain(`<a href="${SECURITY_SUBSCRIPTION}">Gradle Security Subscription</a>`)
expect(rendered).not.toContain(LEGEND)
})
it('names every end-of-life version in the summary line, and reads as a plural', () => {
const rendered = renderSupportStatus(['7.6.4', '1.0', '4.10.3'])
expect(rendered).toContain(`<summary>${END_OF_LIFE} Gradle 1.0, 4.10.3 and 7.6.4 are end-of-life</summary>`)
})
it('names every affected release line when versions span more than one', () => {
const rendered = renderSupportStatus(['7.6.4', '1.0', '4.10.3'])
expect(rendered).toContain('Gradle 1.x, 4.x and 7.x releases receive no further fixes')
})
it('names each release line once when several versions share one', () => {
const rendered = renderSupportStatus(['7.6.4', '7.6.6', '1.0'])
expect(rendered).toContain('Gradle 1.x and 7.x releases receive no further fixes')
})
it('names no version outside the versions that are end-of-life', () => {
expect(renderSupportStatus(['7.6.4'])).not.toContain('9.7.1')
})
it.each(['8.0', '8.14.3', '8.14.5', '9.2.1'])('adds only the legend for %s', version => {
expect(renderSupportStatus([version]).trim()).toBe(LEGEND)
})
it('adds the legend exactly once however many versions are flagged', () => {
const rendered = renderSupportStatus(['9.2.1', '9.4.1', '8.14.5', '8.14.3', '8.3', '8.0'])
expect(rendered.trim()).toBe(LEGEND)
})
it('emits a single section however many versions are end-of-life, plus the single legend', () => {
const rendered = renderSupportStatus(['9.6.1', '9.2.1', '8.14.5', '8.0', '7.6.4', '1.0'])
expect(rendered.match(/<details>/g)).toHaveLength(1)
expect(rendered.match(/consider upgrading/g)).toHaveLength(1)
expect(rendered).toContain(`<summary>${END_OF_LIFE} Gradle 1.0 and 7.6.4 are end-of-life</summary>`)
})
it('names no version outside the fold', () => {
const rendered = renderSupportStatus(['8.0', '9.2.1'])
expect(rendered).not.toContain('8.0')
expect(rendered).not.toContain('9.2.1')
})
it('renders nothing when every version is inside the grace band', () => {
expect(renderSupportStatus(['9.5.1', '9.6.1', '9.7.1'])).toBe('')
})
it('renders nothing when no Gradle build ran', () => {
expect(renderSupportStatus([])).toBe('')
})
})
+145 -122
View File
@@ -1,154 +1,177 @@
import {describe, expect, it} from '@jest/globals'
import {versionIsAtLeast, parseGradleVersionFromOutput} from '../../src/execution/gradle'
import {GradleVersion} from '../../src/gradle-version'
import {parseGradleVersionFromOutput} from '../../src/execution/gradle'
describe('gradle', () => {
describe('can compare versions that are', () => {
function versionsAreOrdered(versions: string[]): void {
for (let i = 0; i < versions.length; i++) {
// Compare with all other versions
for (let j = 0; j < versions.length; j++) {
if (i >= j) {
it(`${versions[i]} is at least ${versions[j]}`, () => {
expect(versionIsAtLeast(versions[i], versions[j])).toBe(true)
})
} else {
it(`${versions[i]} is NOT at least ${versions[j]}`, () => {
expect(versionIsAtLeast(versions[i], versions[j])).toBe(false)
})
}
}
}
}
function order(a: string, b: string): number {
return Math.sign(GradleVersion.compare(new GradleVersion(a), new GradleVersion(b)))
}
function versionsAreNotOrdered(versions: string[]): void {
for (let i = 0; i < versions.length; i++) {
// Compare with all other versions
for (let j = 0; j < versions.length; j++) {
if (i !== j) {
it(`${versions[i]} is NOT at least ${versions[j]}`, () => {
expect(versionIsAtLeast(versions[i], versions[j])).toBe(false)
})
}
}
}
}
function versionsAreEqual(versions: string[]): void {
for (let i = 0; i < versions.length; i++) {
// Compare with all other versions
for (let j = 0; j < versions.length; j++) {
it(`${versions[i]} is at least ${versions[j]}`, () => {
expect(versionIsAtLeast(versions[i], versions[j])).toBe(true)
})
}
}
/** Asserts every pairing of `versions`, which must be listed oldest first. */
function ascending(versions: string[]): void {
for (let i = 0; i < versions.length; i++) {
for (let j = 0; j < versions.length; j++) {
const expected = Math.sign(i - j)
it(`${versions[i]} vs ${versions[j]} is ${expected}`, () => {
expect(order(versions[i], versions[j])).toBe(expected)
})
}
}
}
describe('GradleVersion', () => {
describe('orders', () => {
describe('simple versions', () => {
versionsAreOrdered(['6.0', '6.7', '6.7.1', '6.7.2', '7.0', '7.0.1', '7.1', '8.0', '8.12.1'])
ascending(['6.0', '6.7', '6.7.1', '6.7.2', '7.0', '7.0.1', '7.1', '8.0', '8.12.1'])
})
versionsAreEqual(['7.0', '7.0.0'])
versionsAreEqual(['7.1', '7.1.0'])
describe('a shorter base is older than a longer one that extends it', () => {
ascending(['7.0', '7.0.0'])
ascending(['7.1', '7.1.0'])
})
describe('rc versions', () => {
versionsAreOrdered([
'8.10', '8.11-rc-1', '8.11-rc-2', '8.11', '8.11.1-rc-1', '8.11.1'
])
ascending(['8.10', '8.11-rc-1', '8.11-rc-2', '8.11', '8.11.1-rc-1', '8.11.1'])
})
describe('milestone versions', () => {
versionsAreOrdered([
'8.12.1', '8.12.2-milestone-1', '8.12.2', '8.13-milestone-1', '8.13-milestone-2', '8.13'
])
versionsAreOrdered([
'8.12.1', '8.12.2-milestone-1', '8.12.2-milestone-2', '8.12.2-rc-1', '8.12.2'
])
ascending(['8.12.1', '8.12.2-milestone-1', '8.12.2', '8.13-milestone-1', '8.13-milestone-2', '8.13'])
})
describe('preview versions', () => {
versionsAreOrdered([
'8.12.1', '8.12.2-preview-1', '8.12.2', '8.13-preview-1', '8.13-preview-2', '8.13'
])
versionsAreOrdered([
'8.12.1', '8.12.2-milestone-1', '8.12.2-preview-1', '8.12.2-rc-1', '8.12.2'
])
ascending(['8.12.1', '8.12.2-preview-1', '8.12.2', '8.13-preview-1', '8.13-preview-2', '8.13'])
})
describe('milestone before preview before rc before final', () => {
ascending(['8.12.2-milestone-1', '8.12.2-preview-1', '8.12.2-rc-1', '8.12.2'])
})
describe('snapshot versions', () => {
versionsAreOrdered([
'8.10.1', '8.10.2-20240828012138+0000', '8.10.2', '8.11-20240829002031+0000', '8.11'
])
versionsAreOrdered([
'9.0', '9.1-branch-provider_api_migration_public_api_changes-20240826121451+0000', '9.1'
])
versionsAreNotOrdered([
'8.10.2-20240828012138+0000', '8.10.2-20240828010000+1000', '8.10.2-milestone-1'
])
ascending(['8.10.1', '8.10.2-20240828012138+0000', '8.10.2', '8.11-20240829002031+0000', '8.11'])
ascending(['9.0', '9.1-branch-provider_api_migration_public_api_changes-20240826121451+0000', '9.1'])
})
describe('snapshots order by instant, accounting for timezone', () => {
ascending(['8.10.2-milestone-1', '8.10.2-20240828010000+1000', '8.10.2-20240828012138+0000', '8.10.2'])
})
})
describe('can parse version from output', () => {
it('major version', async () => {
const output = `
------------------------------------------------------------
Gradle 8.9
------------------------------------------------------------
`
const version = await parseGradleVersionFromOutput(output)!
expect(version).toBe('8.9')
// Gradle's `Stage.from` matches the whole stage string against /(\d+)([a-z])?/, so a stage number is
// read only when the string is entirely a number with an optional letter suffix.
describe('reads a stage number only from a fully numeric stage string', () => {
it('orders plain stage numbers numerically, not as text', () => {
// Were these compared as text, '8.11-rc-10' would sort before '8.11-rc-2'.
expect(order('8.11-rc-2', '8.11-rc-10')).toBe(-1)
})
it('patch version', async () => {
const output = `
------------------------------------------------------------
Gradle 8.9.1
------------------------------------------------------------
`
const version = await parseGradleVersionFromOutput(output)!
expect(version).toBe('8.9.1')
describe('a letter suffix orders after the bare number', () => {
ascending(['8.11-rc-1', '8.11-rc-1a', '8.11-rc-1b', '8.11-rc-2'])
})
it('rc version', async () => {
const output = `
------------------------------------------------------------
Gradle 8.9-rc-1
------------------------------------------------------------
`
const version = await parseGradleVersionFromOutput(output)!
expect(version).toBe('8.9-rc-1')
it('ignores digits embedded in a longer stage string', () => {
// 'issue10' and 'issue9' both rank as stage number 0, leaving the version string to break the
// tie; reading the digits positionally would instead sort 'issue10' after 'issue9'.
expect(order('8.0-branch-issue10-20240828012138+0000', '8.0-branch-issue9-20240828012138+0000')).toBe(-1)
})
it('milestone version', async () => {
const output = `
------------------------------------------------------------
Gradle 8.0-milestone-6
------------------------------------------------------------
`
const version = await parseGradleVersionFromOutput(output)!
expect(version).toBe('8.0-milestone-6')
})
describe('matches stage names exactly, as Gradle does', () => {
// 'RC' is not 'rc', so it ranks as unknown (1) and precedes preview (2) rather than following it.
describe('an unrecognised spelling ranks between milestone and preview', () => {
ascending(['8.0-milestone-1', '8.0-RC-1', '8.0-preview-1', '8.0-rc-1', '8.0'])
})
it('snapshot version', async () => {
const output = `
------------------------------------------------------------
Gradle 8.10.2-20240828012138+0000
------------------------------------------------------------
`
const version = await parseGradleVersionFromOutput(output)!
expect(version).toBe('8.10.2-20240828012138+0000')
})
describe('rejects a timestamp that names no real instant', () => {
it.each(['8.0-99999999999999', '8.0-20241301012138', '8.0-20240828992138', '8.0-20240828012138+9999'])(
'throws for %s',
version => {
expect(() => new GradleVersion(version)).toThrow('is not a valid Gradle snapshot timestamp')
}
)
it.each(['8.0-99999999999999', '8.0-20240828012138+9999'])(
'reports %s as unparseable rather than yielding an uncomparable version',
version => {
expect(GradleVersion.parseUntrusted(version)).toBeUndefined()
}
)
})
describe('isFinalRelease', () => {
it.each(['1.0', '8.14.5', '9.7.1'])('treats %s as final', version => {
expect(new GradleVersion(version).isFinalRelease()).toBe(true)
})
it('branch version', async () => {
const output = `
------------------------------------------------------------
Gradle 9.0-branch-provider_api_migration_public_api_changes-20240830060514+0000
------------------------------------------------------------
`
const version = await parseGradleVersionFromOutput(output)!
expect(version).toBe('9.0-branch-provider_api_migration_public_api_changes-20240830060514+0000')
it.each(['8.11-rc-1', '8.0-milestone-6', '8.10.2-20240828012138+0000', '9.0-SNAPSHOT'])(
'treats %s as not final',
version => {
expect(new GradleVersion(version).isFinalRelease()).toBe(false)
}
)
})
describe('parseUntrusted', () => {
it.each(['', 'unknown', 'v1.0'])('returns undefined for the invalid version %s', version => {
expect(GradleVersion.parseUntrusted(version)).toBeUndefined()
})
})
})
describe('parseGradleVersionFromOutput', () => {
it('major version', () => {
const output = `
------------------------------------------------------------
Gradle 8.9
------------------------------------------------------------
`
expect(parseGradleVersionFromOutput(output)).toBe('8.9')
})
it('patch version', () => {
const output = `
------------------------------------------------------------
Gradle 8.9.1
------------------------------------------------------------
`
expect(parseGradleVersionFromOutput(output)).toBe('8.9.1')
})
it('rc version', () => {
const output = `
------------------------------------------------------------
Gradle 8.9-rc-1
------------------------------------------------------------
`
expect(parseGradleVersionFromOutput(output)).toBe('8.9-rc-1')
})
it('milestone version', () => {
const output = `
------------------------------------------------------------
Gradle 8.0-milestone-6
------------------------------------------------------------
`
expect(parseGradleVersionFromOutput(output)).toBe('8.0-milestone-6')
})
it('snapshot version', () => {
const output = `
------------------------------------------------------------
Gradle 8.10.2-20240828012138+0000
------------------------------------------------------------
`
expect(parseGradleVersionFromOutput(output)).toBe('8.10.2-20240828012138+0000')
})
it('branch version', () => {
const output = `
------------------------------------------------------------
Gradle 9.0-branch-provider_api_migration_public_api_changes-20240830060514+0000
------------------------------------------------------------
`
expect(parseGradleVersionFromOutput(output)).toBe(
'9.0-branch-provider_api_migration_public_api_changes-20240830060514+0000'
)
})
})
+79 -11
View File
@@ -1,9 +1,31 @@
import dedent from 'dedent'
import * as github from '@actions/github'
import {afterEach, describe, expect, it} from '@jest/globals'
import {afterEach, describe, expect, it, jest} from '@jest/globals'
import {BuildResult} from '../../src/build-results'
import {jobMarker, renderSummaryTable} from '../../src/job-summary'
// The support-status policy itself is covered by gradle-support-status.test.ts; these tests only
// check that its output reaches the table. '8.0' is the sole release so the tables below, all of
// which build with Gradle 8.0, render without a status sign.
let releasedVersions = [{version: '8.0', checksum: ''}]
jest.unstable_mockModule('../../src/wrapper-validation/wrapper-checksums.json', () => ({
get default() {
return releasedVersions
}
}))
const {jobMarker, renderSummaryTable} = await import('../../src/job-summary')
/** Renders the summary table with the release index built from `versions` rather than the bundled data. */
async function renderWith(versions: string[], results: BuildResult[]): Promise<string> {
releasedVersions = versions.map(version => ({version, checksum: ''}))
jest.resetModules()
const {renderSummaryTable: render} = await import('../../src/job-summary')
return render(results)
}
const DOC = 'https://docs.gradle.org/current/userguide/feature_lifecycle.html#eol_support'
const SECURITY_SUBSCRIPTION = 'https://gradle.org/security-subscription/?utm_source=github-action'
const MATRIX_INPUT_ENV = 'INPUT_WORKFLOW-JOB-CONTEXT'
@@ -30,19 +52,20 @@ const failedHelpBuild: BuildResult = {
const longArgsBuild: BuildResult = {
...successfulHelpBuild,
requestedTasks: 'check publishMyLongNamePluginPublicationToMavenCentral publishMyLongNamePluginPublicationToPluginPortal',
requestedTasks:
'check publishMyLongNamePluginPublicationToMavenCentral publishMyLongNamePluginPublicationToPluginPortal'
}
const scanPublishDisabledBuild: BuildResult = {
...successfulHelpBuild,
buildScanUri: '',
buildScanFailed: false,
buildScanFailed: false
}
const scanPublishFailedBuild: BuildResult = {
...successfulHelpBuild,
buildScanUri: '',
buildScanFailed: true,
buildScanFailed: true
}
describe('renderSummaryTable', () => {
@@ -66,7 +89,7 @@ describe('renderSummaryTable', () => {
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
</tr>
</table>
`);
`)
})
it('failed build', () => {
const table = renderSummaryTable([failedHelpBuild])
@@ -87,7 +110,7 @@ describe('renderSummaryTable', () => {
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
</tr>
</table>
`);
`)
})
describe('when build scan', () => {
it('publishing disabled', () => {
@@ -109,7 +132,7 @@ describe('renderSummaryTable', () => {
<td><a href="https://scans.gradle.com" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Not%20published-lightgrey" alt="Build Scan not published" /></a></td>
</tr>
</table>
`);
`)
})
it('publishing failed', () => {
const table = renderSummaryTable([scanPublishFailedBuild])
@@ -130,7 +153,7 @@ describe('renderSummaryTable', () => {
<td><a href="https://docs.gradle.com/develocity/gradle-plugin/#troubleshooting" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Publish%20failed-orange" alt="Build Scan publish failed" /></a></td>
</tr>
</table>
`);
`)
})
})
it('multiple builds', () => {
@@ -159,7 +182,7 @@ describe('renderSummaryTable', () => {
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
</tr>
</table>
`);
`)
})
it('truncating long requested tasks', () => {
const table = renderSummaryTable([longArgsBuild])
@@ -180,11 +203,56 @@ describe('renderSummaryTable', () => {
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
</tr>
</table>
`);
`)
})
})
})
describe('Gradle version support status', () => {
it('warns on an end-of-life version and folds the detail below the table', async () => {
const table = await renderWith(['10.0.0', '8.0'], [successfulHelpBuild])
expect(table.trim()).toBe(dedent`
<table>
<tr>
<th>Gradle Root Project</th>
<th>Requested Tasks</th>
<th>Gradle Version</th>
<th>Build Outcome</th>
<th>Build&nbsp;Scan®</th>
</tr>
<tr>
<td>root</td>
<td>help</td>
<td align='center'>8.0 :warning:</td>
<td align='center'>:white_check_mark:</td>
<td><a href="https://scans.gradle.com/s/abc123" rel="nofollow" target="_blank"><img src="https://img.shields.io/badge/Build%20Scan%C2%AE-06A0CE?logo=Gradle" alt="Build Scan published" /></a></td>
</tr>
</table>
<details>
<summary>:warning: Gradle 8.0 is end-of-life</summary>
<p>Gradle 8.x releases receive no further fixes, security fixes included. Update to the latest Gradle version.</p>
<p>If you cannot upgrade, see the <a href="${SECURITY_SUBSCRIPTION}">Gradle Security Subscription</a> for options.</p>
</details>
`)
})
it('signs a still-supported but outdated version with the info sign and one legend', async () => {
const table = await renderWith(['9.0.0', '8.1', '8.0'], [successfulHelpBuild])
expect(table).toContain(`<td align='center'>8.0 :information_source:</td>`)
expect(table).toContain(
`<p>:information_source: Gradle version is out of date — consider upgrading. ` +
`See <a href="${DOC}">Gradle release lifecycle</a></p>`
)
expect(table).not.toContain('<details>')
})
it('adds nothing for a version inside the grace band', async () => {
const table = await renderWith(['8.2', '8.0'], [successfulHelpBuild])
expect(table).toContain(`<td align='center'>8.0</td>`)
expect(table).not.toContain(':information_source:')
expect(table).not.toContain('consider upgrading')
})
})
describe('jobMarker', () => {
const original = process.env[MATRIX_INPUT_ENV]
+105 -1
View File
@@ -1,8 +1,65 @@
import nock from "nock";
import {describe, expect, it} from '@jest/globals'
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals'
import {DevelocityAccessCredentials, getToken, resolveTokenForServer} from "../../src/develocity/short-lived-token";
describe('access key format warnings', () => {
// `core.warning` is an ESM export and cannot be spied on, so capture the workflow command it
// writes to stdout instead.
let stdout: jest.SpiedFunction<typeof process.stdout.write>
const warnings = (): string[] =>
stdout.mock.calls
.map(call => String(call[0]))
.filter(line => line.startsWith('::warning::'))
.map(line => line.substring('::warning::'.length).trim())
beforeEach(() => {
stdout = jest.spyOn(process.stdout, 'write').mockImplementation(() => true)
})
afterEach(() => {
stdout.mockRestore()
})
it.each([
['no separator', 'host1', "no '=' separator in the value"],
['a trailing separator', 'host1=key1;', "no '=' separator in entry 2 of 2"],
['a leading separator', ';host1=key1', "no '=' separator in entry 1 of 2"],
['an empty hostname', '=key1', 'empty server name in the value'],
['an empty key', 'host1=', 'empty key in the value'],
['whitespace in the hostname', 'ho st1=key1', 'whitespace in the server name in the value'],
['whitespace in the key', 'host1=ke y1', 'whitespace in the key in the value'],
['whitespace around a separator', 'host1=key1; host2=key2', 'whitespace in the server name in entry 2 of 2'],
])('warns about %s', (_description, rawKey, expectedReason) => {
expect(DevelocityAccessCredentials.parse(rawKey)).toBeNull()
expect(warnings()).toEqual([
`Ignoring badly formed Develocity access key: ${expectedReason}. The expected format is 'server=key[;server=key]*'.`
])
})
it('never includes any part of the access key value in the warning', () => {
expect(DevelocityAccessCredentials.parse('my-host=my sec ret')).toBeNull()
const message = warnings()[0]
expect(message).not.toContain('my-host')
expect(message).not.toContain('sec')
})
it('does not warn for a valid access key', () => {
expect(DevelocityAccessCredentials.parse('host1=key1;host2=key2')).not.toBeNull()
expect(warnings()).toEqual([])
})
it('does not warn for an empty access key', () => {
expect(DevelocityAccessCredentials.parse(' ')).toBeNull()
expect(warnings()).toEqual([])
})
})
describe('short lived tokens', () => {
it('parse valid access key should return an object', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse('some-host.local=key1;host2=key2');
@@ -19,6 +76,53 @@ describe('short lived tokens', () => {
expect(develocityAccessCredentials).toBeNull()
})
it('parse access key with an OIDC token value should return an object', async () => {
const oidcToken = 'eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJyZXBvOmZvby9iYXI_-x=.c2lnbmF0dXJl=='
let develocityAccessCredentials = DevelocityAccessCredentials.parse(`some-host.local=${oidcToken}`);
expect(develocityAccessCredentials).toStrictEqual(DevelocityAccessCredentials.of([
{hostname: 'some-host.local', key: oidcToken}])
)
})
it('parse access key splits each entry on the first separator only', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse('host1=a=b==;host2=c=d');
expect(develocityAccessCredentials).toStrictEqual(DevelocityAccessCredentials.of([
{hostname: 'host1', key: 'a=b=='},
{hostname: 'host2', key: 'c=d'}])
)
})
it('parse access key tolerates surrounding whitespace', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse(' host1=key1\n');
expect(develocityAccessCredentials).toStrictEqual(DevelocityAccessCredentials.of([
{hostname: 'host1', key: 'key1'}])
)
})
it.each([
['no separator', 'host1'],
['a trailing separator', 'host1=key1;'],
['a leading separator', ';host1=key1'],
['an empty hostname', '=key1'],
['an empty key', 'host1='],
['whitespace in the hostname', 'ho st1=key1'],
['whitespace in the key', 'host1=ke y1'],
['whitespace around a separator', 'host1=key1; host2=key2'],
['one invalid entry', 'host1=key1;random'],
])('parse access key with %s should return null', async (_description, rawKey) => {
expect(DevelocityAccessCredentials.parse(rawKey)).toBeNull()
})
it('access key with an OIDC token value as raw string', async () => {
const rawKey = 'host1=eyJhbGciOiJSUzI1NiJ9.payload.signature==;host2=key2'
let develocityAccessCredentials = DevelocityAccessCredentials.parse(rawKey);
expect(develocityAccessCredentials?.raw()).toBe(rawKey)
})
it('parse empty access key should return null', async () => {
let develocityAccessCredentials = DevelocityAccessCredentials.parse('');