diff --git a/.github/commitlint.config.mjs b/.github/commitlint.config.mjs new file mode 100644 index 0000000..51b1c33 --- /dev/null +++ b/.github/commitlint.config.mjs @@ -0,0 +1,29 @@ +/* Taken from: https://github.com/wagoid/commitlint-github-action/blob/7f0a61df502599e1f1f50880aaa7ec1e2c0592f2/commitlint.config.mjs */ +/* eslint-disable import/no-extraneous-dependencies */ +import { maxLineLength } from '@commitlint/ensure' + +const bodyMaxLineLength = 100 + +const validateBodyMaxLengthIgnoringDeps = (parsedCommit) => { + const { type, scope, body } = parsedCommit + const isDepsCommit = + type === 'chore' && (scope === 'deps' || scope === 'deps-dev') + + return [ + isDepsCommit || !body || maxLineLength(body, bodyMaxLineLength), + `body's lines must not be longer than ${bodyMaxLineLength}`, + ] +} + +export default { + extends: ['@commitlint/config-conventional'], + plugins: ['commitlint-plugin-function-rules'], + rules: { + 'body-max-line-length': [0], + 'function-rules/body-max-line-length': [ + 2, + 'always', + validateBodyMaxLengthIgnoringDeps, + ], + }, +} diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..d65d2b2 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,30 @@ +version: 2 +updates: +- package-ecosystem: github-actions + commit-message: + prefix: chore + include: scope + directory: / + schedule: + interval: monthly + groups: + github-actions: + patterns: + - "*" + update-types: + - "minor" + - "patch" +- package-ecosystem: gomod + commit-message: + prefix: chore + include: scope + directory: / + schedule: + interval: monthly + groups: + gomod: + patterns: + - "*" + update-types: + - "minor" + - "patch" diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5d219e8..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: -- package-ecosystem: github-actions - directory: / - schedule: - interval: daily -- package-ecosystem: gomod - directory: / - schedule: - interval: daily diff --git a/.github/dependency-review-config.yaml b/.github/dependency-review-config.yaml new file mode 100644 index 0000000..08389a1 --- /dev/null +++ b/.github/dependency-review-config.yaml @@ -0,0 +1,20 @@ +# https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md +allow-licenses: +- 'Apache-2.0' +- 'BSD-2-Clause' +- 'BSD-2-Clause-FreeBSD' +- 'BSD-3-Clause' +- 'ISC' +- 'MIT' +- 'PostgreSQL' +- 'Python-2.0' +- 'X11' +- 'Zlib' + +allow-dependencies-licenses: +# this action is GPL-3 but it is only used in CI +# https://github.com/actions/dependency-review-action/issues/530#issuecomment-1638291806 +- pkg:githubactions/vladopajic/go-test-coverage@bcd064e5ceef1ccec5441519eb054263b6a44787 +# this package is MPL-2.0 and has a CNCF exception +# https://github.com/cncf/foundation/blob/9b8c9173c2101c1b4aedad3caf2c0128715133f6/license-exceptions/cncf-exceptions-2022-04-12.json#L43C17-L43C47 +- pkg:golang/github.com/go-sql-driver/mysql diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..98d8469 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,29 @@ +name: build +on: + pull_request: + branches: + - main +permissions: {} +jobs: + build-snapshot: + permissions: + contents: read + packages: write + runs-on: ubuntu-latest + strategy: + matrix: + binary: + - go-cli-github + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: stable + - run: echo "GOVERSION=$(go version)" >> "$GITHUB_ENV" + - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + id: goreleaser + with: + version: latest + args: build --clean --debug --single-target --snapshot diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml deleted file mode 100644 index 18b203b..0000000 --- a/.github/workflows/codeql-analysis.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: "CodeQL" -on: - push: - branches: - - main - pull_request: - # The branches below must be a subset of the branches above - branches: - - main - schedule: - - cron: '29 15 * * 1' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - language: - - 'go' - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/commitlint.yaml b/.github/workflows/commitlint.yaml deleted file mode 100644 index 99fc87c..0000000 --- a/.github/workflows/commitlint.yaml +++ /dev/null @@ -1,13 +0,0 @@ -name: Lint Commit Messages -on: pull_request - -jobs: - commitlint: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Lint Commits - uses: wagoid/commitlint-github-action@v5 diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index dee4195..3fc50e4 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -1,28 +1,29 @@ -name: Coverage +name: coverage on: push: branches: - main - +permissions: {} jobs: coverage: + permissions: + contents: write runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Configure git - run: | - git config --global user.name "$GITHUB_ACTOR" - git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" - - name: Set up go - uses: actions/setup-go@v5 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: stable - name: Calculate coverage - run: go test -v -covermode=count -coverprofile=coverage.out - - name: Convert coverage to lcov - uses: jandelgado/gcov2lcov-action@v1.0.9 - - name: Coveralls - uses: coverallsapp/github-action@v2 + run: | + go test -v -covermode=atomic -coverprofile=cover.out.raw -coverpkg=./... ./... + # remove generated code from coverage calculation + grep -Ev 'internal/mock|_enumer.go' cover.out.raw > cover.out + - name: Generage coverage badge + uses: vladopajic/go-test-coverage@1079cd4e58dda229c04ffdb6324fc3756b8542ff # v2.10.1 with: - github-token: ${{ secrets.github_token }} + profile: cover.out + local-prefix: github.com/${{ github.repository }} + git-token: ${{ secrets.GITHUB_TOKEN }} + # orphan branch for storing badges + git-branch: badges diff --git a/.github/workflows/dependabot-automerge.yaml b/.github/workflows/dependabot-automerge.yaml index 8f3942a..df319e8 100644 --- a/.github/workflows/dependabot-automerge.yaml +++ b/.github/workflows/dependabot-automerge.yaml @@ -1,17 +1,24 @@ # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request -name: Dependabot auto-merge -on: pull_request - -permissions: - contents: write - pull-requests: write - +name: dependabot auto-merge +on: + pull_request: + branches: + - main +permissions: {} jobs: - dependabot: + dependabot-automerge: + permissions: + contents: write + pull-requests: write runs-on: ubuntu-latest - if: ${{ github.actor == 'dependabot[bot]' }} + if: github.actor == 'dependabot[bot]' steps: - - name: Enable auto-merge for Dependabot PRs + - name: Fetch dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@5e5f99653a5b510e8555840e80cbf1514ad4af38 # v2.1.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Enable auto-merge for Dependabot PRs # these still need approval before merge run: gh pr merge --auto --merge "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} diff --git a/.github/workflows/dependency-review.yaml b/.github/workflows/dependency-review.yaml new file mode 100644 index 0000000..146894b --- /dev/null +++ b/.github/workflows/dependency-review.yaml @@ -0,0 +1,16 @@ +name: dependency review +on: + pull_request: + branches: + - main +permissions: {} +jobs: + dependency-review: + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2 + with: + config-file: .github/dependency-review-config.yaml diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml deleted file mode 100644 index 2a64ca2..0000000 --- a/.github/workflows/golangci-lint.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: golangci-lint -on: pull_request - -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 - with: - version: latest diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..4a412bf --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,40 @@ +name: lint +on: + pull_request: + branches: + - main +permissions: {} +jobs: + lint-go: + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: stable + - uses: golangci/golangci-lint-action@9d1e0624a798bb64f6c3cea93db47765312263dc # v5.1.0 + with: + args: --timeout=180s --enable gocritic + lint-commits: + permissions: + contents: read + pull-requests: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + fetch-depth: 0 + - uses: wagoid/commitlint-github-action@7f0a61df502599e1f1f50880aaa7ec1e2c0592f2 # v6.0.1 + with: + configFile: .github/commitlint.config.mjs + lint-actions: + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: docker://rhysd/actionlint:1.7.0@sha256:601d6faeefa07683a4a79f756f430a1850b34d575d734b1d1324692202bf312e # v1.7.0 + with: + args: -color diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..0a46ad0 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,100 @@ +name: release +on: + push: + branches: + - main +permissions: {} +jobs: + release-tag: + permissions: + # create tag + contents: write + runs-on: ubuntu-latest + outputs: + new-tag: ${{ steps.bump-tag.outputs.new }} + new-tag-version: ${{ steps.bump-tag.outputs.new_tag_version }} + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + fetch-depth: 0 + - name: Configure git + run: | + git config --global user.name "$GITHUB_ACTOR" + git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: stable + - name: Install ccv + run: > + curl -sSL https://github.com/smlx/ccv/releases/download/v0.3.2/ccv_0.3.2_linux_amd64.tar.gz + | sudo tar -xz -C /usr/local/bin ccv + - name: Bump tag if necessary + id: bump-tag + run: | + if [ -z "$(git tag -l "$(ccv)")" ]; then + git tag "$(ccv)" + git push --tags + echo "new=true" >> "$GITHUB_OUTPUT" + echo "new_tag_version=$(git tag --points-at HEAD)" >> "$GITHUB_OUTPUT" + fi + release-build: + permissions: + # create release + contents: write + # push docker images to regsitry + packages: write + # use OIDC token for signing + id-token: write + # required by attest-build-provenance + attestations: write + needs: release-tag + if: needs.release-tag.outputs.new-tag == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + fetch-depth: 0 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: stable + - name: Login to GHCR + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up environment + run: echo "GOVERSION=$(go version)" >> "$GITHUB_ENV" + - uses: advanced-security/sbom-generator-action@375dee8e6144d9fd0ec1f5667b4f6fb4faacefed # v0.0.1 + id: sbom + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Move sbom to avoid dirty git + run: mv "$GITHUB_SBOM_PATH" ./sbom.spdx.json + env: + GITHUB_SBOM_PATH: ${{ steps.sbom.outputs.fileName }} + - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + id: goreleaser + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_SBOM_PATH: ./sbom.spdx.json + # parse artifacts to the format required for image attestation + - run: | + echo "digest=$(echo "$ARTIFACTS" | jq -r '.[]|select(.type=="Docker Manifest")|select(.name|test(":v"))|.extra.Digest')" >> "$GITHUB_OUTPUT" + echo "name=$(echo "$ARTIFACTS" | jq -r '.[]|select(.type=="Docker Manifest")|select(.name|test(":v"))|.name|split(":")[0]')" >> "$GITHUB_OUTPUT" + id: image_metadata + env: + ARTIFACTS: ${{steps.goreleaser.outputs.artifacts}} + # attest archives + - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + with: + subject-path: "dist/*.tar.gz" + # attest images + - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + with: + subject-digest: ${{steps.image_metadata.outputs.digest}} + subject-name: ${{steps.image_metadata.outputs.name}} + push-to-registry: true diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml deleted file mode 100644 index 1e6f59b..0000000 --- a/.github/workflows/tag-release.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Tag and Release - -on: - push: - branches: - - main - -jobs: - tag: - runs-on: ubuntu-latest - steps: - - name: Setup go - uses: actions/setup-go@v5 - with: - go-version: stable - - name: Install ccv - run: go install github.com/smlx/ccv/cmd/ccv@latest - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Configure Git - run: | - git config --global user.name "$GITHUB_ACTOR" - git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" - - name: Bump tag if necessary - id: tag - run: | - if [ -z $(git tag -l $(ccv)) ]; then - git tag $(ccv) - git push --tags - echo "new=true" >> $GITHUB_OUTPUT - fi - - name: Run GoReleaser - if: steps.tag.outputs.new == 'true' - uses: goreleaser/goreleaser-action@v5.1.0 - with: - version: latest - args: release --clean - workdir: ./cmd/ccv - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e3e3107..23bcc31 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,19 +1,22 @@ -name: Test Suite -on: pull_request - +name: test +on: + pull_request: + branches: + - main +permissions: {} jobs: - go-test: + test-go: + permissions: + contents: read runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Configure git - run: | - git config --global user.name "$GITHUB_ACTOR" - git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com" - - name: Set up go - uses: actions/setup-go@v5 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: stable - - name: Run tests - run: go test -v . + - run: | + git config --global user.email "test@example.com" + git config --global user.name "Test" + - run: go test -v ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e275b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/dist +/cover.out +/cover.out.raw +/sbom.spdx.json diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..7e6897d --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,28 @@ +builds: +- id: ccv + binary: ccv + main: ./cmd/ccv + ldflags: + - > + -s -w + -X "main.commit={{.Commit}}" + -X "main.date={{.Date}}" + -X "main.goVersion={{.Env.GOVERSION}}" + -X "main.projectName={{.ProjectName}}" + -X "main.version=v{{.Version}}" + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - arm64 + +changelog: + use: github-native + +release: + extra_files: + - glob: "{{ .Env.GITHUB_SBOM_PATH }}" + name_template: "{{ .ProjectName }}.v{{ .Version }}.sbom.spdx.json" diff --git a/Makefile b/Makefile index 9d4e853..7d3dd20 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,25 @@ .PHONY: test test: mod-tidy - go test -v ./... + export GIT_CONFIG_NOSYSTEM=true GIT_CONFIG_GLOBAL=/tmp/gitconfig \ + && git config --global user.email "test@example.com" \ + && git config --global user.name "Test" \ + && go test -v ./... -count=1 .PHONY: mod-tidy mod-tidy: go mod tidy .PHONY: build -build: test - go build ./cmd/ccv +build: + GOVERSION=$$(go version) \ + goreleaser build --clean --debug --single-target --snapshot + +.PHONY: lint +lint: + golangci-lint run --enable gocritic + +.PHONY: cover +cover: mod-tidy generate + go test -v -covermode=atomic -coverprofile=cover.out.raw -coverpkg=./... ./... + grep -Ev 'internal/mock|_enumer.go' cover.out.raw > cover.out + go tool cover -html=cover.out diff --git a/README.md b/README.md index 8c6752e..9a65db9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Conventional Commits Versioner -![Tag and Release](https://github.com/smlx/ccv/workflows/Tag%20and%20Release/badge.svg) -[![Coverage Status](https://coveralls.io/repos/github/smlx/ccv/badge.svg?branch=main)](https://coveralls.io/github/smlx/ccv?branch=main) +[![Release](https://github.com/smlx/ccv/actions/workflows/release.yaml/badge.svg)](https://github.com/smlx/ccv/actions/workflows/release.yaml) +[![coverage](https://raw.githubusercontent.com/smlx/ccv/badges/.badges/main/coverage.svg)](https://github.com/smlx/ccv/actions/workflows/coverage.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/smlx/ccv)](https://goreportcard.com/report/github.com/smlx/ccv) -[![Go Reference](https://pkg.go.dev/badge/github.com/smlx/ccv.svg)](https://pkg.go.dev/github.com/smlx/ccv) `ccv` does one thing: it walks git commit history back from the current `HEAD` to find the most recent tag, taking note of commit messages along the way. When it reaches the most recent tag, it uses the commit messages it saw to figure out how the tag should be incremented, and prints the incremented tag. diff --git a/ccv.go b/ccv.go index e871685..7980f4f 100644 --- a/ccv.go +++ b/ccv.go @@ -1,3 +1,4 @@ +// Package ccv implements the conventional commits versioner logic. package ccv import ( @@ -18,7 +19,7 @@ var majorRegex = regexp.MustCompile(`^(fix|feat)(\(.+\))?!: |BREAKING CHANGE: `) // tag, analysing the commits it finds. func walkCommits(r *git.Repository, tagRefs map[string]string, order git.LogOrder) (*semver.Version, bool, bool, bool, error) { var major, minor, patch bool - var stopIter error = fmt.Errorf("stop commit iteration") + var stopIter = fmt.Errorf("stop commit iteration") var latestTag string // walk commit hashes back from HEAD via main commits, err := r.Log(&git.LogOptions{Order: order}) @@ -103,13 +104,12 @@ func NextVersion(path string) (string, error) { } // figure out the latest version in either parent var latestVersion *semver.Version - if latestMain == nil { + switch { + case latestMain == nil: latestVersion = latestBranch - } else if latestBranch == nil { - latestVersion = latestMain - } else if latestMain.GreaterThan(latestBranch) { + case latestBranch == nil || latestMain.GreaterThan(latestBranch): latestVersion = latestMain - } else { + default: latestVersion = latestBranch } // figure out the highest increment in either parent diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 252a9ad..0000000 --- a/commitlint.config.js +++ /dev/null @@ -1,15 +0,0 @@ -const Configuration = { - /* - * Resolve and load @commitlint/config-conventional from node_modules. - * Referenced packages must be installed - */ - extends: ['@commitlint/config-conventional'], - /* - * Any rules defined here will override rules from @commitlint/config-conventional - */ - rules: { - 'body-max-line-length': [1, 'always', 80], - }, -}; - -module.exports = Configuration;