Compare commits

..
11 Commits
Author SHA1 Message Date
73cd4ffe39 feat: reuse the goreleaser binary from the runner tool cache (#577)
* feat: cache the goreleaser binary

Look for GoReleaser in the runner tool cache before a download. This
makes a second use of the action in the same job, or any job on a
self-hosted runner, install immediately.

Add an opt-in `cache-binary` input that also stores the binary in the
GitHub Actions cache. When it hits, the action does not download the
release archive, the checksums and the signature bundle again. Cache
errors are not fatal and fall back to a download.

The runner tool cache entry is now keyed by distribution, so a Pro
binary is no longer returned for an OSS install of the same version.

Closes #476

Co-authored-by: timbretimber <105982513+timbretimber@users.noreply.github.com>
Co-authored-by: Akkuman <akkumans@qq.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* refactor: collapse the duplicated zip extract branches

Both branches called extractZip with the same destination after the
install rewrite, so only the source path differs now.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4eaf86fa-a85b-41f6-8763-612acc1ccc39
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: drop the GitHub Actions cache layer, keep the tool cache lookup

Review found that the GitHub Actions cache layer cost more than it
saved. Measured on ubuntu-latest, same job, same version:

  cold: download + checksum + cosign + extract   0.93 s
  cache hit: restore the 24 MB entry             1.28 s
  first run also pays a save                    +2.26 s

It is slower than a download in every configuration measured, because a
GitHub-hosted runner reaches the release CDN in about 0.4 s for a 15 MB
archive, and the cached entry is the larger extracted directory.

It also skipped the sha256 and cosign verification on a hit, which is
the control it was supposed to protect, and the only case where it wins
on time is when cosign is installed, which is exactly the case where
skipping is wrong. It cost 875 KB (+120%) of dist/index.js for every
user and 24 MB of repository cache quota per version and platform.

The restore and save wrappers were also dead code: @actions/cache
catches everything except ValidationError internally, so the try/catch
and the ReserveCacheError classification could never run.

What remains is the runner tool cache lookup, which is what #476 asked
for, and the distribution-keyed tool name that stops a Pro binary being
returned for an OSS install.

The tool cache test asserted that two installs return the same path, but
that path is a pure function of the tool, version and architecture: it
passed even with the tool cache wiped between the calls. It now asserts
that the second install reports a tool cache hit and does not download,
and it was verified to fail when the lookup is removed.

Co-authored-by: timbretimber <105982513+timbretimber@users.noreply.github.com>
Co-authored-by: Akkuman <akkumans@qq.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4eaf86fa-a85b-41f6-8763-612acc1ccc39
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* docs: drop the GitHub Actions cache rationale from the README

Review feedback: the paragraph explaining why the action does not use the
GitHub Actions cache is not needed. The measurements stay in the pull
request for anyone who asks again.

Also rebuilds dist for js-yaml 5.4.1, merged from master in #578.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4eaf86fa-a85b-41f6-8763-612acc1ccc39
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix(cache): preserve verification guarantees

Keep checksum-skipped downloads out of the persistent tool cache and separate checksum-only entries from cosign-verified entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5abc512e-338c-4459-98ea-d3606438d56a
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: timbretimber <105982513+timbretimber@users.noreply.github.com>
Co-authored-by: Akkuman <akkumans@qq.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4eaf86fa-a85b-41f6-8763-612acc1ccc39
Copilot-Session: 5abc512e-338c-4459-98ea-d3606438d56a
2026-09-03 00:31:48 -03:00
Carlos Alexandro Becker 97a9607547 chore: update generated files
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-09-03 00:09:19 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
689e48f9ac chore(deps): bump js-yaml from 5.2.3 to 5.4.1 in the npm group (#578)
Bumps the npm group with 1 update: [js-yaml](https://github.com/nodeca/js-yaml).


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

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 00:04:43 -03:00
Carlos Alexandro Becker 7e6b514c50 chore: update generated files
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-08-29 15:33:20 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4ec11830b6 chore(deps): bump js-yaml from 5.2.2 to 5.2.3 (#573)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 5.2.2 to 5.2.3.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.2.2...5.2.3)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-29 15:32:20 -03:00
Carlos Alexandro Becker 59060fdcfd chore: update generated files
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-08-29 15:05:04 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
177da47714 chore(deps): bump undici from 6.27.0 to 6.28.0 (#576)
Bumps [undici](https://github.com/nodejs/undici) from 6.27.0 to 6.28.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.27.0...v6.28.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.28.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-29 15:02:26 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a47e8c32ac chore(deps): bump the npm group with 2 updates (#574)
Bumps the npm group with 2 updates: [js-yaml](https://github.com/nodeca/js-yaml) and [yargs](https://github.com/yargs/yargs).


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

Updates `yargs` from 18.0.0 to 18.1.0
- [Release notes](https://github.com/yargs/yargs/releases)
- [Changelog](https://github.com/yargs/yargs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/yargs/yargs/compare/v18.0.0...v18.1.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: yargs
  dependency-version: 18.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-29 14:57:06 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dcd4e58dba ci(deps): bump the actions group with 3 updates (#575)
Bumps the actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-go](https://github.com/actions/setup-go) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/checkout` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1)

Updates `actions/setup-go` from 6.5.0 to 7.0.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/924ae3a1cded613372ab5595356fb5720e22ba16...b7ad1dad31e06c5925ef5d2fc7ad053ef454303e)

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

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: actions/setup-go
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-node
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-23 13:31:24 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Carlos Alexandro Becker
57e184bc5a chore(deps): bump the npm group with 4 updates (#571)
* chore(deps): bump the npm group with 4 updates

Bumps the npm group with 4 updates: [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core), [@actions/http-client](https://github.com/actions/toolkit/tree/HEAD/packages/http-client), [js-yaml](https://github.com/nodeca/js-yaml) and [semver](https://github.com/npm/node-semver).


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

Updates `@actions/http-client` from 4.0.0 to 4.0.1
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/http-client/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/http-client)

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

Updates `semver` from 7.7.4 to 7.8.5
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.7.4...v7.8.5)

---
updated-dependencies:
- dependency-name: "@actions/core"
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: "@actions/http-client"
  dependency-version: 4.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm
- dependency-name: js-yaml
  dependency-version: 5.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm
- dependency-name: semver
  dependency-version: 7.8.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: updates

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-07-01 14:35:34 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ed0d97425c ci(deps): bump the actions group with 3 updates (#570)
Bumps the actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [actions/setup-go](https://github.com/actions/setup-go) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `actions/checkout` from 6.0.3 to 7.0.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

Updates `actions/setup-go` from 6.4.0 to 6.5.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/4a3601121dd01d1626a1e23e37211e3254c1c06c...924ae3a1cded613372ab5595356fb5720e22ba16)

Updates `codecov/codecov-action` from 6.0.1 to 7.0.0
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/e79a6962e0d4c0c17b229090214935d2e33f8354...fb8b3582c8e4def4969c97caa2f19720cb33a72f)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/setup-go
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: codecov/codecov-action
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 14:16:38 -03:00
11 changed files with 400 additions and 88 deletions
+12 -12
View File
@@ -38,11 +38,11 @@ jobs:
- goreleaser-pro
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: stable
- name: Check
@@ -78,11 +78,11 @@ jobs:
- false
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.18
- name: Install cosign
@@ -112,11 +112,11 @@ jobs:
- windows-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.18
- name: Import GPG key
@@ -146,11 +146,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.18
- name: Check
@@ -173,11 +173,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.18
- name: GoReleaser
@@ -203,11 +203,11 @@ jobs:
- goreleaser
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: 1.18
- name: GoReleaser
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Git config
+3 -3
View File
@@ -20,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.node-version'
cache: npm
@@ -37,6 +37,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload coverage
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: ./coverage/clover.xml
+6 -6
View File
@@ -20,9 +20,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.node-version'
cache: npm
@@ -37,9 +37,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6.0.0
with:
node-version-file: '.node-version'
cache: npm
@@ -66,9 +66,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.0.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6.0.0
with:
node-version-file: '.node-version'
cache: npm
+18
View File
@@ -21,6 +21,7 @@ ___
* [Signing](#signing)
* [Upload artifacts](#upload-artifacts)
* [Install Only](#install-only)
* [Cache the binary](#cache-the-binary)
* [Customizing](#customizing)
* [inputs](#inputs)
* [outputs](#outputs)
@@ -217,6 +218,23 @@ steps:
run: goreleaser -v
```
### Cache the binary
The action looks for GoReleaser in the [runner tool cache][toolcache] before it
downloads. A second use of the action in the same job, or any job on a
self-hosted runner that already has the version, installs immediately.
A binary taken from the tool cache is not verified again. The cache records
whether checksum or checksum and cosign verification completed. When cosign is
available, the action only reuses a signature-verified entry. A binary whose
checksum could not be verified is used for that invocation but is not cached.
On a self-hosted runner the tool cache is kept between jobs, so it must be
trusted like the runner itself. GitHub-hosted runners start with an empty tool
cache in every job, so they always download and verify.
[toolcache]: https://github.com/actions/toolkit/tree/main/packages/tool-cache
## Customizing
### inputs
+121
View File
@@ -0,0 +1,121 @@
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
const find = jest.fn<(tool: string, version: string) => string>();
const downloadTool = jest.fn<(url: string) => Promise<string>>();
const extractTar = jest.fn<(archive: string) => Promise<string>>();
const extractZip = jest.fn<(archive: string) => Promise<string>>();
const cacheDir = jest.fn<(source: string, tool: string, version: string) => Promise<string>>();
const which = jest.fn<(tool: string, check: boolean) => Promise<string>>();
const exec = jest.fn<(command: string, args: string[]) => Promise<number>>();
jest.unstable_mockModule('@actions/tool-cache', () => ({
find,
downloadTool,
extractTar,
extractZip,
cacheDir
}));
jest.unstable_mockModule('@actions/io', () => ({which}));
jest.unstable_mockModule('@actions/exec', () => ({exec}));
const goreleaser = await import('../src/goreleaser');
describe('runner tool cache verification', () => {
let tempDir: string;
let archivePath: string;
let checksumsPath: string;
let bundlePath: string;
let extractedPath: string;
let cosignPath: string;
let cache: Map<string, string>;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'goreleaser-cache-'));
archivePath = path.join(tempDir, 'archive');
checksumsPath = path.join(tempDir, 'checksums.txt');
bundlePath = path.join(tempDir, 'checksums.txt.sigstore.json');
extractedPath = path.join(tempDir, 'extracted');
fs.writeFileSync(archivePath, 'archive');
fs.writeFileSync(bundlePath, '{}');
fs.mkdirSync(extractedPath);
const checksum = crypto.createHash('sha256').update('archive').digest('hex');
fs.writeFileSync(
checksumsPath,
[
`${checksum} goreleaser_Linux_x86_64.tar.gz`,
`${checksum} goreleaser_Darwin_all.tar.gz`,
`${checksum} goreleaser_Windows_x86_64.zip`
].join('\n')
);
cosignPath = '';
cache = new Map();
find.mockImplementation((tool, version) => cache.get(`${tool}:${version}`) || '');
downloadTool.mockImplementation(async url => {
if (url.endsWith('/checksums.txt')) {
return checksumsPath;
}
if (url.endsWith('/checksums.txt.sigstore.json')) {
return bundlePath;
}
return archivePath;
});
extractTar.mockResolvedValue(extractedPath);
extractZip.mockResolvedValue(extractedPath);
cacheDir.mockImplementation(async (_source, tool, version) => {
const destination = path.join(tempDir, tool, version);
cache.set(`${tool}:${version}`, destination);
return destination;
});
which.mockImplementation(async () => cosignPath);
exec.mockResolvedValue(0);
});
afterEach(() => {
fs.rmSync(tempDir, {recursive: true, force: true});
});
it('does not cache a binary when checksum verification is skipped', async () => {
downloadTool.mockImplementation(async url => {
if (url.endsWith('/checksums.txt')) {
throw new Error('checksums unavailable');
}
return archivePath;
});
await goreleaser.install('goreleaser', 'v2.15.3');
await goreleaser.install('goreleaser', 'v2.15.3');
expect(cacheDir).not.toHaveBeenCalled();
expect(downloadTool.mock.calls.filter(([url]) => !url.endsWith('/checksums.txt'))).toHaveLength(2);
});
it('does not reuse a checksum-only entry when cosign becomes available', async () => {
await goreleaser.install('goreleaser', 'v2.15.3');
expect(cacheDir).toHaveBeenLastCalledWith(extractedPath, 'goreleaser-checksum', '2.15.3');
cosignPath = '/usr/local/bin/cosign';
await goreleaser.install('goreleaser', 'v2.15.3');
await goreleaser.install('goreleaser', 'v2.15.3');
expect(exec).toHaveBeenCalledTimes(1);
expect(cacheDir).toHaveBeenLastCalledWith(extractedPath, 'goreleaser-cosign', '2.15.3');
expect(downloadTool.mock.calls.filter(([url]) => !url.includes('checksums.txt'))).toHaveLength(2);
});
it('reuses checksum verification for releases without sigstore bundles', async () => {
cosignPath = '/usr/local/bin/cosign';
await goreleaser.install('goreleaser', 'v2.12.4');
await goreleaser.install('goreleaser', 'v2.12.4');
expect(exec).not.toHaveBeenCalled();
expect(cacheDir).toHaveBeenCalledTimes(1);
expect(downloadTool.mock.calls.filter(([url]) => !url.includes('checksums.txt'))).toHaveLength(1);
});
});
+32
View File
@@ -57,6 +57,38 @@ describe('install', () => {
const bin = await goreleaser.install('goreleaser-pro', 'latest');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('reuses the runner tool cache instead of downloading again', async () => {
const first = await goreleaser.install('goreleaser', 'v2.15.3');
const written: string[] = [];
const stdout = process.stdout.write.bind(process.stdout);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.stdout.write = ((chunk: any, ...rest: any[]): boolean => {
written.push(chunk.toString());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (stdout as any)(chunk, ...rest);
}) as typeof process.stdout.write;
let second: string;
try {
second = await goreleaser.install('goreleaser', 'v2.15.3');
} finally {
process.stdout.write = stdout;
}
const logs = written.join('');
expect(logs).toContain('found in the runner tool cache');
expect(logs).not.toContain('Downloading https://github.com/goreleaser');
expect(second).toEqual(first);
expect(fs.existsSync(second)).toBe(true);
}, 100000);
it('does not share the tool cache between distributions', async () => {
const oss = await goreleaser.install('goreleaser', 'v2.15.3');
const pro = await goreleaser.install('goreleaser-pro', 'v2.15.3');
expect(pro).not.toEqual(oss);
expect(fs.existsSync(pro)).toBe(true);
}, 100000);
});
describe('distribSuffix', () => {
Generated Vendored
+8 -6
View File
File diff suppressed because one or more lines are too long
+117 -28
View File
@@ -7,13 +7,13 @@
"name": "goreleaser-action",
"license": "MIT",
"dependencies": {
"@actions/core": "^3.0.0",
"@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0",
"@actions/http-client": "^4.0.0",
"@actions/http-client": "^4.0.1",
"@actions/tool-cache": "^4.0.0",
"js-yaml": "^4.2.0",
"semver": "^7.7.4",
"yargs": "^18.0.0"
"js-yaml": "^5.4.1",
"semver": "^7.8.5",
"yargs": "^18.1.0"
},
"devDependencies": {
"@types/node": "^24.0.0",
@@ -34,9 +34,9 @@
}
},
"node_modules/@actions/core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz",
"integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^3.0.0",
@@ -53,9 +53,9 @@
}
},
"node_modules/@actions/http-client": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz",
"integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz",
"integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==",
"license": "MIT",
"dependencies": {
"tunnel": "^0.0.6",
@@ -685,6 +685,29 @@
"concat-map": "0.0.1"
}
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2757,6 +2780,29 @@
"concat-map": "0.0.1"
}
},
"node_modules/eslint/node_modules/js-yaml": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/eslint/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -3093,9 +3139,9 @@
}
},
"node_modules/get-east-asian-width": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -4217,9 +4263,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.1.tgz",
"integrity": "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==",
"funding": [
{
"type": "github",
@@ -4235,7 +4281,7 @@
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
"js-yaml": "bin/js-yaml.mjs"
}
},
"node_modules/jsesc": {
@@ -5068,9 +5114,9 @@
}
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -5619,9 +5665,9 @@
}
},
"node_modules/undici": {
"version": "6.27.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
"license": "MIT",
"engines": {
"node": ">=18.17"
@@ -5834,15 +5880,15 @@
"license": "ISC"
},
"node_modules/yargs": {
"version": "18.0.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
"version": "18.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
"integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
"license": "MIT",
"dependencies": {
"cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"string-width": "^7.2.0",
"string-width": "^8.2.1",
"y18n": "^5.0.5",
"yargs-parser": "^22.0.0"
},
@@ -5860,6 +5906,49 @@
"node": ">=12"
}
},
"node_modules/yargs/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/yargs/node_modules/string-width": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
"integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.5.0",
"strip-ansi": "^7.1.2"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yargs/node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/yargs/node_modules/yargs-parser": {
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+5 -5
View File
@@ -24,13 +24,13 @@
"author": "CrazyMax",
"license": "MIT",
"dependencies": {
"@actions/core": "^3.0.0",
"@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0",
"@actions/http-client": "^4.0.0",
"@actions/http-client": "^4.0.1",
"@actions/tool-cache": "^4.0.0",
"js-yaml": "^4.2.0",
"semver": "^7.7.4",
"yargs": "^18.0.0"
"js-yaml": "^5.4.1",
"semver": "^7.8.5",
"yargs": "^18.1.0"
},
"devDependencies": {
"@types/node": "^24.0.0",
+77 -27
View File
@@ -1,56 +1,90 @@
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import yaml from 'js-yaml';
import * as yaml from 'js-yaml';
import * as context from './context';
import * as github from './github';
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as tc from '@actions/tool-cache';
import * as semver from 'semver';
type VerificationLevel = 'checksum' | 'cosign';
export async function install(distribution: string, version: string): Promise<string> {
const release: github.GitHubRelease = await github.getRelease(distribution, version);
const tag = release.tag_name;
const toolVersion = tag.replace(/^v/, '');
const cosign = await io.which('cosign', false);
const verificationLevel = cosign && supportsCosignBundle(tag) ? 'cosign' : 'checksum';
const toolPath = findCachedTool(distribution, toolVersion, verificationLevel);
if (toolPath) {
core.info(`GoReleaser ${tag} found in the runner tool cache: ${toolPath}`);
return getExePath(toolPath);
}
const filename = getFilename(distribution);
const baseUrl = `https://github.com/goreleaser/${distribution}/releases/download/${release.tag_name}`;
const baseUrl = `https://github.com/goreleaser/${distribution}/releases/download/${tag}`;
const downloadUrl = `${baseUrl}/${filename}`;
core.info(`Downloading ${downloadUrl}`);
const downloadPath: string = await tc.downloadTool(downloadUrl);
core.debug(`Downloaded to ${downloadPath}`);
await verifyChecksum(distribution, release.tag_name, downloadPath, filename);
const verified = await verifyChecksum(distribution, tag, downloadPath, filename, cosign);
core.info('Extracting GoReleaser');
let extPath: string;
if (context.osPlat == 'win32') {
if (!downloadPath.endsWith('.zip')) {
const newPath = downloadPath + '.zip';
fs.renameSync(downloadPath, newPath);
extPath = await tc.extractZip(newPath);
} else {
extPath = await tc.extractZip(downloadPath);
let zipPath = downloadPath;
if (!zipPath.endsWith('.zip')) {
zipPath = `${downloadPath}.zip`;
fs.renameSync(downloadPath, zipPath);
}
extPath = await tc.extractZip(zipPath);
} else {
extPath = await tc.extractTar(downloadPath);
}
core.debug(`Extracted to ${extPath}`);
const cachePath: string = await tc.cacheDir(extPath, 'goreleaser-action', release.tag_name.replace(/^v/, ''));
if (!verified) {
return getExePath(extPath);
}
const cachePath: string = await tc.cacheDir(extPath, cacheToolName(distribution, verified), toolVersion);
core.debug(`Cached to ${cachePath}`);
const exePath: string = path.join(cachePath, context.osPlat == 'win32' ? 'goreleaser.exe' : 'goreleaser');
core.debug(`Exe path is ${exePath}`);
return exePath;
return getExePath(cachePath);
}
const findCachedTool = (distribution: string, version: string, required: VerificationLevel): string => {
const levels: VerificationLevel[] = required === 'cosign' ? ['cosign'] : ['cosign', 'checksum'];
for (const level of levels) {
const toolPath = tc.find(cacheToolName(distribution, level), version);
if (toolPath) {
return toolPath;
}
}
return '';
};
const cacheToolName = (distribution: string, verification: VerificationLevel): string => {
return `${distribution}-${verification}`;
};
const getExePath = (dir: string): string => {
return path.join(dir, context.osPlat == 'win32' ? 'goreleaser.exe' : 'goreleaser');
};
export async function verifyChecksum(
distribution: string,
tag: string,
archivePath: string,
filename: string
): Promise<void> {
filename: string,
cosign?: string
): Promise<VerificationLevel | undefined> {
const baseUrl = `https://github.com/goreleaser/${distribution}/releases/download/${tag}`;
let checksumsPath: string;
try {
@@ -71,7 +105,19 @@ export async function verifyChecksum(
}
core.info(`Checksum verified for ${filename}`);
await verifyCosignSignature(distribution, tag, baseUrl, checksumsPath);
const cosignPath = cosign === undefined ? await io.which('cosign', false) : cosign;
if (!cosignPath) {
core.info('cosign not found in PATH, skipping signature verification');
return 'checksum';
}
if (!supportsCosignBundle(tag)) {
core.info(`GoReleaser ${tag} does not have a sigstore bundle, skipping signature verification`);
return 'checksum';
}
if (!(await verifyCosignSignature(distribution, tag, baseUrl, checksumsPath, cosignPath))) {
return 'checksum';
}
return 'cosign';
}
export const findChecksum = (checksumsContent: string, filename: string): string | undefined => {
@@ -86,21 +132,16 @@ async function verifyCosignSignature(
distribution: string,
tag: string,
baseUrl: string,
checksumsPath: string
): Promise<void> {
const cosign = await io.which('cosign', false);
if (!cosign) {
core.info('cosign not found in PATH, skipping signature verification');
return;
}
checksumsPath: string,
cosign: string
): Promise<boolean> {
let bundlePath: string;
try {
core.info(`Downloading ${baseUrl}/checksums.txt.sigstore.json`);
bundlePath = await tc.downloadTool(`${baseUrl}/checksums.txt.sigstore.json`);
} catch (e) {
core.warning(`Skipping cosign signature verification: unable to download sigstore bundle: ${e.message}`);
return;
return false;
}
const certificateIdentity = getCertificateIdentity(distribution, tag);
@@ -116,8 +157,17 @@ async function verifyCosignSignature(
checksumsPath
]);
core.info('cosign signature verified');
return true;
}
const supportsCosignBundle = (tag: string): boolean => {
if (github.isNightlyTag(tag)) {
return true;
}
const version = semver.parse(tag.replace(/^v/, '').replace(/-pro$/, ''));
return version !== null && semver.gte(version, '2.13.0');
};
export const getCertificateIdentity = (distribution: string, tag: string): string => {
const pro = isPro(distribution);
if (github.isNightlyTag(tag)) {
@@ -171,7 +221,7 @@ const getFilename = (distribution: string): string => {
};
export async function getDistPath(yamlfile: string): Promise<string> {
const cfg = yaml.load(fs.readFileSync(yamlfile, 'utf8'));
const cfg = yaml.load(fs.readFileSync(yamlfile, 'utf8')) as {dist?: string};
return cfg.dist || 'dist';
}