Skip to content

Commit 96e7361

Browse files
chore: improve documentation, CI pipelines, and release automation (#81)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1 parent 4887437 commit 96e7361

21 files changed

Lines changed: 429 additions & 56 deletions

.github/workflows/ci.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: CI
2+
3+
# Run on every push and on pull requests targeting main.
4+
on:
5+
push:
6+
branches: ['**']
7+
pull_request:
8+
branches: [main]
9+
10+
# Read-only access is sufficient for running tests and linting.
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
# Run the full test suite with race detection and generate a coverage report.
16+
test:
17+
name: Test
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v6
22+
23+
# Go version is read from go.mod so it stays in sync with the project.
24+
- name: Set up Go
25+
uses: actions/setup-go@v6
26+
with:
27+
go-version-file: go.mod
28+
29+
# Verify dependency checksums match go.sum (detects corrupted or tampered modules).
30+
- name: Verify dependencies
31+
run: go mod verify
32+
33+
- name: Run tests
34+
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
35+
36+
# Print coverage summary to the CI log.
37+
- name: Coverage summary
38+
run: go tool cover -func=coverage.out | tail -1
39+
40+
# Static analysis: go vet for built-in checks, golangci-lint for extended linting.
41+
lint:
42+
name: Lint
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: Checkout
46+
uses: actions/checkout@v6
47+
48+
- name: Set up Go
49+
uses: actions/setup-go@v6
50+
with:
51+
go-version-file: go.mod
52+
53+
- name: Run go vet
54+
run: go vet ./...
55+
56+
# golangci-lint v2 runs 50+ linters in a single pass.
57+
# Without a .golangci.yml config file it uses sensible defaults.
58+
- name: Run golangci-lint
59+
uses: golangci/golangci-lint-action@v7
60+
with:
61+
version: v2.10.1
62+
63+
# Verify the project compiles. Cross-platform builds are handled by
64+
# GoReleaser at release time, so a single-platform check suffices here.
65+
build:
66+
name: Build
67+
runs-on: ubuntu-latest
68+
steps:
69+
- name: Checkout
70+
uses: actions/checkout@v6
71+
72+
- name: Set up Go
73+
uses: actions/setup-go@v6
74+
with:
75+
go-version-file: go.mod
76+
77+
- name: Build
78+
run: go build -o /dev/null .
79+
80+
# Validate .goreleaser.yml so config errors are caught before tagging a release.
81+
- name: Verify GoReleaser config
82+
uses: goreleaser/goreleaser-action@v6
83+
with:
84+
args: check

.github/workflows/govulncheck.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Runs Go's official vulnerability scanner against project dependencies.
2+
# Fails the job if any known vulnerabilities affect the code.
3+
# See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
4+
5+
name: govulncheck
6+
7+
on:
8+
push:
9+
branches: [main]
10+
pull_request:
11+
# Weekly scan catches new vulnerabilities even when the code hasn't changed.
12+
schedule:
13+
- cron: '0 9 * * 1' # Every Monday at 9:00 UTC
14+
15+
permissions:
16+
contents: read
17+
issues: write
18+
19+
jobs:
20+
govulncheck:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v6
24+
with:
25+
persist-credentials: false
26+
27+
- uses: actions/setup-go@v6
28+
with:
29+
go-version-file: go.mod
30+
31+
# golang/govulncheck-action runs govulncheck against all packages (./...).
32+
# With the default text output format, the job fails if vulnerabilities are found.
33+
- uses: golang/govulncheck-action@v1
34+
with:
35+
go-version-file: go.mod
36+
37+
# Open a GitHub issue when the scheduled scan finds vulnerabilities.
38+
# Only runs on cron failures — PR and push failures are visible in the checks UI.
39+
notify:
40+
needs: govulncheck
41+
runs-on: ubuntu-latest
42+
if: failure() && github.event_name == 'schedule'
43+
steps:
44+
- uses: actions/github-script@v7
45+
with:
46+
script: |
47+
await github.rest.issues.create({
48+
owner: context.repo.owner,
49+
repo: context.repo.repo,
50+
title: 'govulncheck: vulnerability detected',
51+
body: [
52+
'The weekly [govulncheck scan](' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + ') found vulnerabilities.',
53+
'',
54+
'Review the workflow run and update affected dependencies.'
55+
].join('\n'),
56+
labels: ['security']
57+
});

.github/workflows/main.yml

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,38 @@
1-
# This is a basic workflow to help you get started with Actions
2-
31
name: Release with GoReleaser
42

5-
# Controls when the action will run.
3+
# Triggered by pushing a version tag (e.g. v1.2.3) or manually via the Actions tab.
64
on:
7-
# Triggers the release workflow only for new tags
85
push:
96
tags:
107
- '*'
11-
12-
# Allows you to run this workflow manually from the Actions tab
138
workflow_dispatch:
149

15-
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
10+
# GoReleaser needs write access to create the GitHub release and upload artifacts.
11+
permissions:
12+
contents: write
13+
1614
jobs:
17-
# This workflow contains a single job called "build"
1815
release:
19-
# The type of runner that the job will run on
2016
runs-on: ubuntu-latest
21-
22-
# Steps represent a sequence of tasks that will be executed as part of the job
2317
steps:
24-
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
18+
# Full history is required for GoReleaser to generate the changelog.
2519
- name: Checkout
2620
uses: actions/checkout@v6
2721
with:
2822
fetch-depth: 0
2923

30-
# Runs a single command using the runners shell
24+
# Go version is read from go.mod so it stays in sync with the project.
3125
- name: Set up Go
3226
uses: actions/setup-go@v6
3327
with:
34-
go-version: 1.26
28+
go-version-file: go.mod
3529

36-
# Runs a set of commands using the runners shell
30+
# GoReleaser builds, packages and publishes the release.
31+
# See .goreleaser.yml for build targets and packaging configuration.
3732
- name: Run GoReleaser
3833
uses: goreleaser/goreleaser-action@v7
3934
with:
4035
version: latest
4136
args: release --clean
4237
env:
4338
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44-

.github/workflows/pr-title.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Ensure PR titles follow the Conventional Commits format so that
2+
# squash-merged commits produce a clean changelog via GoReleaser.
3+
# See: https://www.conventionalcommits.org
4+
name: PR Title
5+
6+
on:
7+
pull_request_target:
8+
types: [opened, edited, synchronize, reopened]
9+
10+
permissions:
11+
pull-requests: read
12+
13+
jobs:
14+
lint:
15+
name: Validate PR title
16+
runs-on: ubuntu-latest
17+
steps:
18+
# Validates that the PR title matches the conventional commit format.
19+
# Examples of valid titles:
20+
# feat: add token refresh command
21+
# fix(auth): handle expired tokens
22+
# docs: update CONTRIBUTING.md
23+
- uses: amannn/action-semantic-pull-request@v5
24+
env:
25+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26+
with:
27+
# Types allowed in PR titles. These map to GoReleaser changelog groups.
28+
types: |
29+
feat
30+
fix
31+
docs
32+
chore
33+
ci
34+
test
35+
refactor
36+
perf
37+
style
38+
build
39+
revert
40+
# Require a scope in parentheses (optional — set to true to enforce).
41+
requireScope: false

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ Temporary Items
6161
*.yaml
6262
*.yml
6363

64+
# Allow test golden files and GitHub config
65+
!**/testdata/**
66+
!.github/**
67+
6468
dist/
6569
/imscli
6670
go.mod.local

.goreleaser.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,24 @@ checksum:
2222
name_template: 'checksums.txt'
2323
snapshot:
2424
version_template: "{{ .Version }}-next"
25+
# Group commits by conventional-commit prefix for cleaner release notes.
2526
changelog:
2627
sort: asc
28+
groups:
29+
- title: Features
30+
regexp: '^.*?feat(\([^)]+\))?!?:.+$'
31+
order: 0
32+
- title: Bug Fixes
33+
regexp: '^.*?fix(\([^)]+\))?!?:.+$'
34+
order: 1
35+
- title: Other
36+
order: 999
2737
filters:
2838
exclude:
2939
- '^docs:'
3040
- '^test:'
41+
- '^ci:'
42+
- '^chore:'
3143
nfpms:
3244
- file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
3345
homepage: "https://github.com/adobe/imscli"

CONTRIBUTING.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,91 @@
11
# Contributing
2-
## Release process
2+
3+
This project wraps [adobe/ims-go](https://github.com/adobe/ims-go).
4+
5+
## Development
6+
7+
```bash
8+
# Build
9+
go build -o imscli .
10+
11+
# Run all tests
12+
go test ./...
13+
14+
# Run a single test by name
15+
go test ./ims/ -run TestValidateURL
16+
17+
# Vet (static analysis)
18+
go vet ./...
19+
```
20+
21+
## CI Pipelines
22+
23+
All pipelines are defined in `.github/workflows/`.
24+
25+
### CI (`ci.yml`)
26+
27+
**Triggers:** Every push to any branch and pull requests targeting `main`.
28+
29+
Runs three parallel jobs:
30+
31+
- **Test** — Runs `go test -race` with coverage and prints a coverage summary to the log. Verifies dependency checksums with `go mod verify`.
32+
- **Lint** — Runs `go vet` and [golangci-lint](https://golangci-lint.run/) for extended static analysis.
33+
- **Build** — Verifies the project compiles and validates `.goreleaser.yml` with `goreleaser check`. Cross-platform builds are handled by GoReleaser at release time, so a single-platform check suffices here.
34+
35+
### PR Title (`pr-title.yml`)
36+
37+
**Triggers:** When a pull request is opened, edited, synchronized or reopened.
38+
39+
Validates that the PR title follows the [Conventional Commits](https://www.conventionalcommits.org) format (e.g. `feat: add token refresh`, `fix(auth): handle expired tokens`). This is enforced because the repository is configured for **squash merging only** — the PR title becomes the commit message on `main`, and GoReleaser uses these prefixes to group the release changelog into Features, Bug Fixes, etc.
40+
41+
### Release (`main.yml`)
42+
43+
**Triggers:** Pushing a version tag (e.g. `v1.2.3`) or manual dispatch from the Actions tab.
44+
45+
Runs [GoReleaser](https://goreleaser.com) to build cross-platform binaries (linux/darwin/windows × amd64/arm64), package them as archives and system packages (deb, rpm, apk), generate a changelog grouped by commit type, and publish a GitHub Release with all artifacts.
46+
47+
### CodeQL (`codeql-analysis.yml`)
48+
49+
**Triggers:** Every push, pull requests targeting `main`, and weekly on a cron schedule.
50+
51+
Runs GitHub's CodeQL security analysis to detect vulnerabilities in the Go source code.
52+
53+
### govulncheck (`govulncheck.yml`)
54+
55+
**Triggers:** Every push to `main`, pull requests, and weekly on Monday at 9:00 UTC.
56+
57+
Runs Go's official vulnerability scanner ([govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck)) against all packages. The job fails if any known vulnerabilities in the Go vulnerability database affect the code. Unlike general-purpose scanners, govulncheck traces call graphs — it only reports vulnerabilities in functions your code actually calls. The weekly schedule catches new vulnerabilities even when the code hasn't changed. If the scheduled scan fails, a GitHub issue labeled `security` is created automatically.
58+
59+
## Repository Settings
60+
61+
- **Squash merge only** — Merge commits and rebase merging are disabled. The PR title is used as the squash commit message, ensuring conventional commit messages land on `main`.
62+
- **Auto-delete branches** — Head branches are automatically deleted after a PR is merged.
63+
- **Renovate auto-merge**[Renovate](https://docs.renovatebot.com/) monitors `go.mod` for dependency updates and opens PRs automatically. Patch updates (e.g., `v1.8.0``v1.8.1`) are auto-merged after CI passes. Minor and major updates require manual review. Configuration lives in `renovate.json`.
64+
- **Go version pinning** — All CI workflows use `go-version-file: go.mod` so the Go compiler version is controlled by the `toolchain` directive in `go.mod`. To upgrade Go, update `go.mod` (Renovate opens PRs for this automatically). To downgrade after a bad release, revert the `toolchain` line in `go.mod` — all workflows pick up the change immediately.
65+
66+
## Release Process
367

468
In order to standardize the release process, [goreleaser](https://goreleaser.com) has been adopted.
569

670
To build and release a new version:
771
```
872
git tag vX.X.X && git push --tags
9-
gorelease --rm-dist
73+
goreleaser release --clean
1074
```
1175

1276
The binary version is set automatically to the git tag.
1377

1478
Please tag new versions using [semantic versioning](https://semver.org/spec/v2.0.0.html).
79+
80+
## Development Notes
81+
82+
### PersistentPreRunE and subcommands
83+
84+
The root command defines a `PersistentPreRunE` that loads configuration from flags, environment variables, and config files (see `cmd/root.go`). In cobra, if a subcommand defines its own `PersistentPreRunE`, it **overrides** the parent's — the root's `PersistentPreRunE` will not run for that subcommand or its children. If you need to add a `PersistentPreRunE` to a subcommand, you must explicitly call the parent's first.
85+
86+
## Additional Reading
87+
88+
The `docs/` directory contains write-ups on non-obvious problems encountered during development:
89+
90+
- [OAuth Local Server: Unhandled Serve() Error](docs/oauth-serve-error.md) — Why the `Serve` goroutine error is captured via a buffered channel, and why the channel must be buffered.
91+
- [OAuth Local Server Shutdown Deadlock](docs/oauth-shutdown-deadlock.md) — How an unbuffered channel creates a deadlock between the HTTP handler and `Shutdown()`, and the two-part fix.

0 commit comments

Comments
 (0)