Compare commits

...
15 Commits
Author SHA1 Message Date
e24998b8b6 ci: drop pre-cosign-v3 goreleaser versions from tests (#554)
GoReleaser v2.13.0 was the first release to ship the cosign v3
sigstore-bundle 'checksums.txt.sigstore.json' alongside the archive.
Earlier releases only publish a cosign v2 detached '.sig', which the
action's verifier does not understand and silently skips.

Drop '~> 1.26' / '~> 2.6' / 'v0.182.0' / '~> v1' from the matrix and
the install tests; pin '~> 2.13' as the minimum-supported version we
actively exercise in CI. Document v2.13.0 as the minimum cosign-
verifiable version in the README.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 15:39:15 -03:00
be2e8a39ba docs: document cosign verification in README (#553)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 15:24:42 -03:00
5e53f8eea2 ci: add release-major-tag workflow (#552)
* build: drop docker-bake in favor of plain npm

Every TypeScript action maintained by actions/* (checkout, setup-node,
setup-go, cache, upload-artifact) uses plain npm scripts. The bake
setup is a docker/* org convention and adds friction for TS work:
contributors need Docker, the dev loop is ~10x slower than npm, and
Alpine-vs-host byte drift in dist/index.js makes PRs bounce.

Replace with the standard pattern:
- .node-version pins Node 24 so contributors and CI agree
- npm scripts (build, lint, format, test, pre-checkin) replace bake
  targets one-for-one
- validate.yml runs lint + a check-dist diff (mirrors actions/setup-node)
  and a vendor check that npm install --package-lock-only is a no-op
- test.yml uses setup-node + sigstore/cosign-installer, drops bake-action
- dependabot-build.yml regenerates dist via npm instead of bake

CONTRIBUTING.md and README development section updated to match.

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

* build: align scripts and workflows with actions/* convention

Match the standard layout used by actions/checkout, actions/setup-node,
etc.:

- package.json scripts: split format/format-check (Prettier) from
  lint/lint:fix (ESLint), and have pre-checkin run all four (format,
  lint:fix, build, test) in that order.
- validate.yml lint job runs format-check + lint as separate steps.
- test.yml drops the redundant --coverage flag (now in the test script).
- Drop dependabot-build.yml: actions/* don't auto-rebuild dist on
  dependabot PRs; the check-dist style validate / build job catches
  drift and a maintainer rebuilds locally if needed.

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

* ci: add release-major-tag workflow

Adopts the actions/checkout pattern (workflow_dispatch with target +
major_version inputs that force-pushes the major tag). Doubles as a
rollback tool. Documented in CONTRIBUTING under a 'Releasing' section.

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

* ci: drop irrelevant pin comment from release-major-tag

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 15:23:21 -03:00
4068afa2f0 build: drop docker-bake in favor of plain npm (#551)
* build: drop docker-bake in favor of plain npm

Every TypeScript action maintained by actions/* (checkout, setup-node,
setup-go, cache, upload-artifact) uses plain npm scripts. The bake
setup is a docker/* org convention and adds friction for TS work:
contributors need Docker, the dev loop is ~10x slower than npm, and
Alpine-vs-host byte drift in dist/index.js makes PRs bounce.

Replace with the standard pattern:
- .node-version pins Node 24 so contributors and CI agree
- npm scripts (build, lint, format, test, pre-checkin) replace bake
  targets one-for-one
- validate.yml runs lint + a check-dist diff (mirrors actions/setup-node)
  and a vendor check that npm install --package-lock-only is a no-op
- test.yml uses setup-node + sigstore/cosign-installer, drops bake-action
- dependabot-build.yml regenerates dist via npm instead of bake

CONTRIBUTING.md and README development section updated to match.

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

* build: align scripts and workflows with actions/* convention

Match the standard layout used by actions/checkout, actions/setup-node,
etc.:

- package.json scripts: split format/format-check (Prettier) from
  lint/lint:fix (ESLint), and have pre-checkin run all four (format,
  lint:fix, build, test) in that order.
- validate.yml lint job runs format-check + lint as separate steps.
- test.yml drops the redundant --coverage flag (now in the test script).
- Drop dependabot-build.yml: actions/* don't auto-rebuild dist on
  dependabot PRs; the check-dist style validate / build job catches
  drift and a maintainer rebuilds locally if needed.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 15:22:23 -03:00
Carlos Alexandro BeckerandCopilot 213ec80f56 docs: add CONTRIBUTING with pre-commit workflow
Document the docker buildx bake pre-checkin / test / validate sequence
contributors need before pushing, and call out the Alpine-built dist/
gotcha so PRs don't bounce on build-validate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 14:36:59 -03:00
4b462d3d1d feat: verify release checksum and cosign signature (#550)
* feat: verify release checksum and cosign signature

Download checksums.txt for the release and verify the SHA-256 of the
downloaded archive against it. When cosign is available in PATH, also
download checksums.txt.sigstore.json and verify the signature against
the goreleaser/goreleaser-pro release workflow identity. Both steps
degrade gracefully (with a warning) when the corresponding artifacts
or tooling are missing.

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

* test: use install() for checksum e2e tests

Drop the http-client download helper from verifyChecksum integration
tests; call goreleaser.install() instead so the test exercises the
public API path and avoids duplicating download logic.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 14:34:46 -03:00
TimoandGitHub 01cbe076be docs: Upgrade import GPG action version (#547) 2026-04-05 12:19:35 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>CrazyMax
2a473d70e3 ci(deps): bump the actions group with 5 updates (#546)
* ci(deps): bump the actions group with 5 updates

Bumps the actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.3.0` | `6.4.0` |
| [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) | `6.3.0` | `7.0.0` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `3.12.0` | `4.0.0` |
| [docker/bake-action](https://github.com/docker/bake-action) | `6.10.0` | `7.0.0` |
| [codecov/codecov-action](https://github.com/codecov/codecov-action) | `5.5.2` | `6.0.0` |


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

Updates `crazy-max/ghaction-import-gpg` from 6.3.0 to 7.0.0
- [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases)
- [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/e89d40939c28e39f97cf32126055eeae86ba74ec...2dc316deee8e90f13e1a351ab510b4d5bc0c82cd)

Updates `docker/setup-buildx-action` from 3.12.0 to 4.0.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/8d2750c68a42422c14e847fe6c8ac0403b4cbd6f...4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd)

Updates `docker/bake-action` from 6.10.0 to 7.0.0
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/5be5f02ff8819ecd3092ea6b2e6261c31774f2b4...82490499d2e5613fcead7e128237ef0b0ea210f7)

Updates `codecov/codecov-action` from 5.5.2 to 6.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/671740ac38dd9b0130fbe1cec585b89eea48d3de...57e3a136b779b570ffcdbf80b3bdc90e7fab3de2)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: crazy-max/ghaction-import-gpg
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: docker/bake-action
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: codecov/codecov-action
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

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

* ci: switch to matrix subaction for bake

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-04-03 10:21:16 -03:00
Carlos Alexandro Becker fdcf0b9df9 clean: leftover files from node 22(?)
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-03-22 23:46:39 -03:00
Carlos Alexandro Becker 9881cc5376 fix: use new static URL
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-03-22 23:43:18 -03:00
Carlos Alexandro Becker 07f3f34e99 chore: update
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-03-22 23:20:07 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
47f0a77cfc chore(deps): bump undici from 6.23.0 to 6.24.1 (#545)
Bumps [undici](https://github.com/nodejs/undici) from 6.23.0 to 6.24.1.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.23.0...v6.24.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-22 22:55:17 -03:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
4be059cded ci(deps): bump the actions group with 2 updates (#543)
Bumps the actions group with 2 updates: [actions/setup-go](https://github.com/actions/setup-go) and [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `actions/setup-go` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4b73464bb391d4059bd26b0524d20df3927bd417)

Updates `actions/upload-artifact` from 6.0.0 to 7.0.0
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/b7c566a772e6b6bfb58ed0dc250532a479d7789f...bbbca2ddaa5d8feaa63e36b76fdaad77386f024f)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: actions/upload-artifact
  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-03-01 13:54:09 -03:00
Carlos Alexandro Becker 6c92f1d350 fix: bake vendor
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2026-02-22 20:54:03 -03:00
Carlos Alexandro BeckerandGitHub ff4cb9c029 docs: update 2026-02-22 00:08:30 -03:00
19 changed files with 501 additions and 291 deletions
+17 -12
View File
@@ -32,8 +32,7 @@ jobs:
- windows-latest
version:
- latest
- '~> 2.6'
- '~> 1.26'
- '~> 2.13'
distribution:
- goreleaser
- goreleaser-pro
@@ -45,7 +44,7 @@ jobs:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: stable
-
@@ -74,11 +73,13 @@ jobs:
matrix:
version:
- latest
- '~> 2.6'
- '~> 1.26'
- '~> 2.13'
distribution:
- goreleaser
- goreleaser-pro
cosign:
- true
- false
steps:
-
name: Checkout
@@ -87,9 +88,13 @@ jobs:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
name: Install cosign
if: matrix.cosign
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
-
name: GoReleaser
if: ${{ !(github.event_name == 'pull_request' && matrix.distribution == 'goreleaser-pro') }}
@@ -122,13 +127,13 @@ jobs:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY_TEST }}
passphrase: ${{ secrets.PASSPHRASE_TEST }}
@@ -161,7 +166,7 @@ jobs:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
@@ -178,7 +183,7 @@ jobs:
workdir: ./test
-
name: Upload assets
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: myapp
path: ./test/dist/*
@@ -193,7 +198,7 @@ jobs:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
@@ -227,7 +232,7 @@ jobs:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: 1.18
-
-46
View File
@@ -1,46 +0,0 @@
name: dependabot-build
on:
pull_request:
paths:
- 'package.json'
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.head_ref }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
-
name: Vendor
uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0
with:
targets: vendor
-
name: Pre-checkin
uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0
with:
targets: pre-checkin
-
name: Commit and push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "chore: update dist and vendor"
git push
fi
+46
View File
@@ -0,0 +1,46 @@
name: release major tag
run-name: Move ${{ github.event.inputs.major_version }} to ${{ github.event.inputs.target }}
on:
workflow_dispatch:
inputs:
target:
description: The tag, branch, or SHA the major version should point to (e.g. v7.1.0)
required: true
major_version:
type: choice
description: The major version tag to move
options:
- v7
- v6
- v5
- v4
- v3
- v2
- v1
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
contents: write
jobs:
tag:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Git config
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-
name: Move ${{ github.event.inputs.major_version }} to ${{ github.event.inputs.target }}
run: git tag -f ${{ github.event.inputs.major_version }} ${{ github.event.inputs.target }}
-
name: Push
run: git push origin ${{ github.event.inputs.major_version }} --force
+14 -5
View File
@@ -25,13 +25,22 @@ jobs:
with:
fetch-depth: 0
-
name: Test
uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
with:
source: .
targets: test
node-version-file: '.node-version'
cache: npm
-
name: Install cosign
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
-
name: Install dependencies
run: npm ci
-
name: Test
run: npm test
-
name: Upload coverage
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: ./coverage/clover.xml
+68 -17
View File
@@ -16,32 +16,83 @@ on:
pull_request:
jobs:
prepare:
lint:
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.generate.outputs.targets }}
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: List targets
id: generate
uses: docker/bake-action/subaction/list-targets@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
with:
target: validate
node-version-file: '.node-version'
cache: npm
-
name: Install dependencies
run: npm ci
-
name: Format check
run: npm run format-check
-
name: Lint
run: npm run lint
validate:
build:
runs-on: ubuntu-latest
needs:
- prepare
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.prepare.outputs.targets) }}
steps:
-
name: Validate
uses: docker/bake-action@5be5f02ff8819ecd3092ea6b2e6261c31774f2b4 # v6.10.0
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
with:
targets: ${{ matrix.target }}
node-version-file: '.node-version'
cache: npm
-
name: Install dependencies
run: npm ci --ignore-scripts
-
name: Rebuild dist
run: npm run build
-
name: Compare dist
id: diff
run: |
if [ "$(git diff --ignore-space-at-eol dist | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. Run 'npm run build' and commit dist/." >&2
git diff dist
exit 1
fi
-
name: Upload built dist on failure
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: dist
path: dist
vendor:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v6.0.0
with:
node-version-file: '.node-version'
cache: npm
-
name: Refresh package-lock.json
run: npm install --package-lock-only
-
name: Compare package-lock.json
run: |
if [ -n "$(git status --porcelain -- package-lock.json)" ]; then
echo "package-lock.json is out of sync with package.json. Run 'npm install' and commit." >&2
git diff package-lock.json
exit 1
fi
+1
View File
@@ -0,0 +1 @@
24
+89
View File
@@ -0,0 +1,89 @@
# Contributing
Thanks for your interest in contributing!
## Prerequisites
- [Node.js](https://nodejs.org/) — version pinned in [`.node-version`](./.node-version).
Tools like [`nvm`](https://github.com/nvm-sh/nvm), [`fnm`](https://github.com/Schniz/fnm),
[`asdf`](https://asdf-vm.com/), or [`mise`](https://mise.jdx.dev/) read this file
automatically.
- [`cosign`](https://docs.sigstore.dev/cosign/installation/) — only required if you
want to run the signature-verification integration tests locally.
## Setup
```sh
npm ci
```
## Pre-commit checklist
Before committing changes to `src/`, `__tests__/`, `package.json`,
`package-lock.json`, or `action.yml`:
```sh
npm run pre-checkin
```
That runs `format` + `build` + `test` — the same checks CI runs.
Then commit `dist/` along with your source changes; the action runtime loads
`dist/index.js` directly, so it must stay in sync.
If CI's `validate / build` job fails because `dist/` differs from a fresh
build, just download the `dist` artifact from the failed run and commit it —
or rerun `npm run build` locally with the Node version in `.node-version`.
## npm scripts
| Script | Purpose |
| ------------------- | ------------------------------------------------ |
| `npm run build` | Bundle `src/` to `dist/index.js` via `ncc` |
| `npm run format` | Run Prettier (write) |
| `npm run format-check` | Run Prettier (check only, used in CI) |
| `npm run lint` | Run ESLint (check only, used in CI) |
| `npm run lint:fix` | Run ESLint with `--fix` |
| `npm test` | Run Jest with coverage |
| `npm run pre-checkin` | `format` + `lint:fix` + `build` + `test` |
## Tests
`npm test` runs the full Jest suite, including integration tests that:
- Download real GoReleaser releases from GitHub
- Verify `checksums.txt` against the downloaded archive
- Verify the cosign sigstore bundle (skipped if `cosign` isn't on `PATH`,
but the CI image always has it installed)
These need outbound network access. Offline / restrictive-proxy runs will
have those tests fail — that's expected.
## Commit messages
Use [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`,
`fix:`, `test:`, `docs:`, `chore:`, `ci:`, …). Keep the subject ≤72 chars.
## Pull requests
- Target `master`.
- Make sure `npm run pre-checkin` passes.
- One logical change per PR is easier to review.
- The `signing` CI job and `goreleaser-pro` matrix entries are skipped on PRs
from forks because they need repository secrets — that's expected and not
something you need to fix.
## Releasing (maintainers)
1. Create a new GitHub Release with a semver tag (e.g. `v7.1.0`) — either
through the UI or `gh release create v7.1.0 --generate-notes`.
2. Once the release exists, run the [**release major tag**](./.github/workflows/release-major-tag.yml)
workflow from the Actions tab:
- `target`: the new tag (e.g. `v7.1.0`)
- `major_version`: the major version to repoint (e.g. `v7`)
This force-pushes the major tag to the new release so consumers using
`goreleaser/goreleaser-action@v7` pick up the change.
The same workflow doubles as a rollback tool — pass an older tag as
`target` to revert the major.
+57 -17
View File
@@ -16,6 +16,7 @@ ___
* [Usage](#usage)
* [Workflow](#workflow)
* [Verification](#verification)
* [Run on new tag](#run-on-new-tag)
* [Signing](#signing)
* [Upload artifacts](#upload-artifacts)
@@ -54,15 +55,15 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
@@ -77,6 +78,44 @@ jobs:
> **IMPORTANT**: note the `fetch-depth: 0` input in `Checkout` step. It is required for the changelog to work correctly.
### Verification
The action verifies the integrity of the downloaded GoReleaser archive
against the published `checksums.txt` automatically — no configuration
required.
If [`cosign`](https://docs.sigstore.dev/cosign/) is available on `PATH`, the
action will additionally verify the cosign sigstore signature of the
checksums file against the GoReleaser release workflow's OIDC identity. If
`cosign` isn't installed, this step is silently skipped.
> **Note**: cosign signature verification requires GoReleaser **v2.13.0 or
> newer** (and the matching `nightly`). Earlier releases ship a `.sig`
> detached signature signed with cosign v2, which is not compatible with
> the cosign v3 sigstore-bundle format the action verifies. For older
> versions the cosign step is silently skipped — only the `checksums.txt`
> SHA-256 verification runs.
To enable signature verification, install cosign before running the action:
```yaml
-
name: Install cosign
uses: sigstore/cosign-installer@v3
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
Both checksum and signature verification work for tagged releases (≥ v2.13.0)
and the `nightly` channel.
### Run on new tag
If you want to run GoReleaser only on new tag, you can use this event:
@@ -93,7 +132,7 @@ Or with a condition on GoReleaser step:
```yaml
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
if: startsWith(github.ref, 'refs/tags/')
with:
version: '~> v2'
@@ -113,13 +152,13 @@ the [Import GPG](https://github.com/crazy-max/ghaction-import-gpg) GitHub Action
-
name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v6
uses: crazy-max/ghaction-import-gpg@v7
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: release --clean
@@ -144,7 +183,7 @@ purposes. You can do that with the [actions/upload-artifact](https://github.com/
```yaml
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: release --clean
@@ -153,7 +192,7 @@ purposes. You can do that with the [actions/upload-artifact](https://github.com/
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
name: Upload assets
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: myapp
path: myfolder/dist/*
@@ -165,7 +204,7 @@ purposes. You can do that with the [actions/upload-artifact](https://github.com/
steps:
-
name: Install GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
install-only: true
-
@@ -219,7 +258,7 @@ secret named `GH_PAT`, the step will look like this:
```yaml
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
uses: goreleaser/goreleaser-action@v7
with:
version: '~> v2'
args: release --clean
@@ -233,15 +272,16 @@ If you need the auto-snapshot feature, take a look at [this example repository](
## Development
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full development workflow.
Quick reference:
```
# format code and build javascript artifacts
docker buildx bake pre-checkin
# install dependencies
npm ci
# validate all code has correctly formatted and built
docker buildx bake validate
# run tests
docker buildx bake test
# format, build dist/, and run tests
npm run pre-checkin
```
## License
+2 -2
View File
@@ -22,7 +22,7 @@ describe('getRelease', () => {
it('unknown GoReleaser release', async () => {
await expect(github.getRelease('goreleaser', 'foo')).rejects.toThrow(
new Error('Cannot find GoReleaser release foo in https://goreleaser.com/static/releases.json')
new Error('Cannot find GoReleaser release foo in https://goreleaser.com/releases.json')
);
});
@@ -100,7 +100,7 @@ describe('getRelease', () => {
it('unknown GoReleaser Pro release', async () => {
await expect(github.getRelease('goreleaser-pro', 'foo')).rejects.toThrow(
new Error('Cannot find GoReleaser release foo in https://goreleaser.com/static/releases-pro.json')
new Error('Cannot find GoReleaser release foo in https://goreleaser.com/releases-pro.json')
);
});
});
+97 -20
View File
@@ -1,38 +1,26 @@
import {describe, expect, it} from '@jest/globals';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as io from '@actions/io';
import * as goreleaser from '../src/goreleaser';
describe('install', () => {
it('acquires v0.182.0 version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'v0.182.0');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires latest version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'latest');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires v0.182.0-pro version of GoReleaser Pro', async () => {
const bin = await goreleaser.install('goreleaser-pro', 'v0.182.0-pro');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires latest v1 version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', '~> v1');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires latest v1 version of GoReleaser Pro', async () => {
const bin = await goreleaser.install('goreleaser-pro', '~> v1');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires latest v2 version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', '~> v2');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires v2.13.0 (minimum cosign-verifiable) version of GoReleaser', async () => {
const bin = await goreleaser.install('goreleaser', 'v2.13.0');
expect(fs.existsSync(bin)).toBe(true);
}, 100000);
it('acquires latest v2 version of GoReleaser Pro', async () => {
const bin = await goreleaser.install('goreleaser-pro', '~> v2');
expect(fs.existsSync(bin)).toBe(true);
@@ -53,3 +41,92 @@ describe('distribSuffix', () => {
expect(goreleaser.distribSuffix('goreleaser')).toEqual('');
});
});
describe('findChecksum', () => {
const sample = [
'*malformed-line',
'',
'abc123 goreleaser_Linux_x86_64.tar.gz',
'def456 *goreleaser_Darwin_all.tar.gz',
'789xyz checksums.txt'
].join('\n');
it('finds a checksum by filename', () => {
expect(goreleaser.findChecksum(sample, 'goreleaser_Linux_x86_64.tar.gz')).toEqual('abc123');
});
it('strips a leading asterisk on the filename (binary mode)', () => {
expect(goreleaser.findChecksum(sample, 'goreleaser_Darwin_all.tar.gz')).toEqual('def456');
});
it('returns undefined when not present', () => {
expect(goreleaser.findChecksum(sample, 'missing.tar.gz')).toBeUndefined();
});
});
describe('getCertificateIdentity', () => {
it('returns the OSS workflow identity for tagged releases', () => {
expect(goreleaser.getCertificateIdentity('goreleaser', 'v2.15.3')).toEqual(
'https://github.com/goreleaser/goreleaser/.github/workflows/release.yml@refs/tags/v2.15.3'
);
});
it('returns the Pro internal workflow identity for tagged releases', () => {
expect(goreleaser.getCertificateIdentity('goreleaser-pro', 'v2.15.3')).toEqual(
'https://github.com/goreleaser/goreleaser-pro-internal/.github/workflows/release-pro.yml@refs/tags/v2.15.3'
);
});
it('uses nightly-oss.yml@refs/heads/main for OSS nightly', () => {
expect(goreleaser.getCertificateIdentity('goreleaser', 'nightly')).toEqual(
'https://github.com/goreleaser/goreleaser/.github/workflows/nightly-oss.yml@refs/heads/main'
);
});
it('uses nightly-pro.yml@refs/heads/main for Pro nightly', () => {
expect(goreleaser.getCertificateIdentity('goreleaser-pro', 'nightly')).toEqual(
'https://github.com/goreleaser/goreleaser-pro-internal/.github/workflows/nightly-pro.yml@refs/heads/main'
);
});
});
describe('verifyChecksum', () => {
const requireCosign = async (): Promise<void> => {
const cosign = await io.which('cosign', false);
if (!cosign) {
throw new Error(
'cosign must be installed in PATH to run this integration test (apk add cosign / sigstore/cosign-installer)'
);
}
};
it('verifies a tagged OSS release end-to-end with cosign', async () => {
await requireCosign();
const bin = await goreleaser.install('goreleaser', 'v2.15.3');
expect(fs.existsSync(bin)).toBe(true);
}, 120000);
it('verifies the OSS nightly release end-to-end with cosign', async () => {
await requireCosign();
const bin = await goreleaser.install('goreleaser', 'nightly');
expect(fs.existsSync(bin)).toBe(true);
}, 120000);
it('throws on checksum mismatch', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gha-'));
const archive = path.join(dir, 'fake.tar.gz');
fs.writeFileSync(archive, 'tampered content');
await expect(
goreleaser.verifyChecksum('goreleaser', 'v2.15.3', archive, 'goreleaser_Linux_x86_64.tar.gz')
).rejects.toThrow(/Checksum mismatch/);
}, 60000);
it('throws when the filename is not in checksums.txt', async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gha-'));
const archive = path.join(dir, 'whatever.tar.gz');
fs.writeFileSync(archive, '');
await expect(
goreleaser.verifyChecksum('goreleaser', 'v2.15.3', archive, 'not-a-real-asset.tar.gz')
).rejects.toThrow(/Could not find not-a-real-asset.tar.gz in checksums.txt/);
}, 60000);
});
-71
View File
@@ -1,71 +0,0 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=24
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git
WORKDIR /src
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
npm install && mkdir /vendor && cp package-lock.json /vendor
FROM scratch AS vendor-update
COPY --from=deps /vendor /
FROM deps AS vendor-validate
RUN --mount=type=bind,target=.,rw <<EOT
set -e
git add -A
cp -rf /vendor/* .
if [ -n "$(git status --porcelain -- package-lock.json)" ]; then
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor"'
git status --porcelain -- package-lock.json
exit 1
fi
EOT
FROM deps AS build
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
npm run build && mkdir /out && cp -Rf dist /out/
FROM scratch AS build-update
COPY --from=build /out /
FROM build AS build-validate
RUN --mount=type=bind,target=.,rw <<EOT
set -e
git add -A
cp -rf /out/* .
if [ -n "$(git status --porcelain -- dist)" ]; then
echo >&2 'ERROR: Build result differs. Please build first with "docker buildx bake build"'
git status --porcelain -- dist
exit 1
fi
EOT
FROM deps AS format
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
npm run format \
&& mkdir /out && find . -name '*.ts' -not -path './node_modules/*' | cpio -pdm /out
FROM scratch AS format-update
COPY --from=format /out /
FROM deps AS lint
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
npm run lint
FROM deps AS test
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
npm run test -- --coverage --coverageDirectory=/tmp/coverage
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage /
Generated Vendored
+5 -5
View File
File diff suppressed because one or more lines are too long
Generated Vendored
-1
View File
File diff suppressed because one or more lines are too long
Generated Vendored
-1
View File
File diff suppressed because one or more lines are too long
-61
View File
@@ -1,61 +0,0 @@
target "_common" {
args = {
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1
}
}
group "default" {
targets = ["build"]
}
group "pre-checkin" {
targets = ["vendor", "format", "build"]
}
group "validate" {
targets = ["lint", "build-validate", "vendor-validate"]
}
target "build" {
dockerfile = "dev.Dockerfile"
target = "build-update"
output = ["."]
}
target "build-validate" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "build-validate"
output = ["type=cacheonly"]
}
target "format" {
dockerfile = "dev.Dockerfile"
target = "format-update"
output = ["."]
}
target "lint" {
dockerfile = "dev.Dockerfile"
target = "lint"
output = ["type=cacheonly"]
}
target "vendor" {
dockerfile = "dev.Dockerfile"
target = "vendor-update"
output = ["."]
}
target "vendor-validate" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "vendor-validate"
output = ["type=cacheonly"]
}
target "test" {
dockerfile = "dev.Dockerfile"
target = "test-coverage"
output = ["./coverage"]
}
+3 -15
View File
@@ -112,7 +112,6 @@
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/generator": "^7.28.6",
@@ -1431,7 +1430,6 @@
"integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -1473,7 +1471,6 @@
"integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.21.0",
@@ -1510,7 +1507,6 @@
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
@@ -1690,7 +1686,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2016,7 +2011,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -2473,7 +2467,6 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -2530,7 +2523,6 @@
"integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -3566,7 +3558,6 @@
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -4820,7 +4811,6 @@
"integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -5484,7 +5474,6 @@
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -5597,7 +5586,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5621,9 +5609,9 @@
}
},
"node_modules/undici": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+6 -8
View File
@@ -5,14 +5,12 @@
"type": "module",
"scripts": {
"build": "ncc build src/main.ts --minify --license licenses.txt",
"lint": "npm run prettier && npm run eslint",
"format": "npm run prettier:fix && npm run eslint:fix",
"eslint": "eslint --max-warnings=0 .",
"eslint:fix": "eslint --fix .",
"prettier": "prettier --check \"./**/*.ts\"",
"prettier:fix": "prettier --write \"./**/*.ts\"",
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
"all": "npm run build && npm run format && npm test"
"format": "prettier --write \"**/*.ts\"",
"format-check": "prettier --check \"**/*.ts\"",
"lint": "eslint --max-warnings=0 \"**/*.ts\"",
"lint:fix": "eslint --fix \"**/*.ts\"",
"test": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
"pre-checkin": "npm run format && npm run lint:fix && npm run build && npm test"
},
"repository": {
"type": "git",
+2 -2
View File
@@ -59,7 +59,7 @@ export const getReleaseTag = async (distribution: string, version: string): Prom
const tag: string = (await resolveVersion(distribution, version)) || version;
const suffix: string = goreleaser.distribSuffix(distribution);
const url = `https://goreleaser.com/static/releases${suffix}.json`;
const url = `https://goreleaser.com/releases${suffix}.json`;
const releases = await withRetry(async () => {
const http: httpm.HttpClient = new httpm.HttpClient('goreleaser-action');
@@ -108,7 +108,7 @@ interface GitHubTag {
const getAllTags = async (distribution: string): Promise<Array<string>> => {
const suffix: string = goreleaser.distribSuffix(distribution);
const url = `https://goreleaser.com/static/releases${suffix}.json`;
const url = `https://goreleaser.com/releases${suffix}.json`;
core.debug(`Downloading ${url}`);
return withRetry(async () => {
+94 -8
View File
@@ -1,26 +1,26 @@
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import * as util from 'util';
import 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';
export async function install(distribution: string, version: string): Promise<string> {
const release: github.GitHubRelease = await github.getRelease(distribution, version);
const filename = getFilename(distribution);
const downloadUrl = util.format(
'https://github.com/goreleaser/%s/releases/download/%s/%s',
distribution,
release.tag_name,
filename
);
const baseUrl = `https://github.com/goreleaser/${distribution}/releases/download/${release.tag_name}`;
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);
core.info('Extracting GoReleaser');
let extPath: string;
if (context.osPlat == 'win32') {
@@ -45,6 +45,92 @@ export async function install(distribution: string, version: string): Promise<st
return exePath;
}
export async function verifyChecksum(
distribution: string,
tag: string,
archivePath: string,
filename: string
): Promise<void> {
const baseUrl = `https://github.com/goreleaser/${distribution}/releases/download/${tag}`;
let checksumsPath: string;
try {
core.info(`Downloading ${baseUrl}/checksums.txt`);
checksumsPath = await tc.downloadTool(`${baseUrl}/checksums.txt`);
} catch (e) {
core.warning(`Skipping checksum verification: unable to download checksums.txt: ${e.message}`);
return;
}
const sha256 = crypto.createHash('sha256').update(fs.readFileSync(archivePath)).digest('hex');
const expected = findChecksum(fs.readFileSync(checksumsPath, 'utf8'), filename);
if (!expected) {
throw new Error(`Could not find ${filename} in checksums.txt`);
}
if (expected.toLowerCase() !== sha256.toLowerCase()) {
throw new Error(`Checksum mismatch for ${filename}: expected ${expected}, got ${sha256}`);
}
core.info(`Checksum verified for ${filename}`);
await verifyCosignSignature(distribution, tag, baseUrl, checksumsPath);
}
export const findChecksum = (checksumsContent: string, filename: string): string | undefined => {
const match = checksumsContent
.split('\n')
.map(line => line.trim().split(/\s+/))
.find(parts => parts.length >= 2 && parts[1].replace(/^[*]/, '') === filename);
return match ? match[0] : undefined;
};
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;
}
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;
}
const certificateIdentity = getCertificateIdentity(distribution, tag);
core.info(`Verifying checksums.txt signature with cosign (identity: ${certificateIdentity})`);
await exec.exec(cosign, [
'verify-blob',
'--certificate-identity',
certificateIdentity,
'--certificate-oidc-issuer',
'https://token.actions.githubusercontent.com',
'--bundle',
bundlePath,
checksumsPath
]);
core.info('cosign signature verified');
}
export const getCertificateIdentity = (distribution: string, tag: string): string => {
const pro = isPro(distribution);
if (tag === 'nightly') {
const workflow = pro ? 'nightly-pro.yml' : 'nightly-oss.yml';
const repo = pro ? 'goreleaser-pro-internal' : 'goreleaser';
return `https://github.com/goreleaser/${repo}/.github/workflows/${workflow}@refs/heads/main`;
}
if (pro) {
return `https://github.com/goreleaser/goreleaser-pro-internal/.github/workflows/release-pro.yml@refs/tags/${tag}`;
}
return `https://github.com/goreleaser/goreleaser/.github/workflows/release.yml@refs/tags/${tag}`;
};
export const distribSuffix = (distribution: string): string => {
return isPro(distribution) ? '-pro' : '';
};
@@ -81,7 +167,7 @@ const getFilename = (distribution: string): string => {
const platform: string = context.osPlat == 'win32' ? 'Windows' : context.osPlat == 'darwin' ? 'Darwin' : 'Linux';
const ext: string = context.osPlat == 'win32' ? 'zip' : 'tar.gz';
const suffix: string = distribSuffix(distribution);
return util.format('goreleaser%s_%s_%s.%s', suffix, platform, arch, ext);
return `goreleaser${suffix}_${platform}_${arch}.${ext}`;
};
export async function getDistPath(yamlfile: string): Promise<string> {