Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
71c69fb
Allow test golden files and GitHub config in gitignore
telegrapher Feb 28, 2026
82ad9ee
Add missing golden test files for pretty-print tests
telegrapher Feb 28, 2026
9150ded
Fix stale references and add missing commands in documentation
telegrapher Feb 28, 2026
d04325f
Improve README with badges, commands table, and quick start
telegrapher Feb 28, 2026
b1cec9b
Add development setup, CI pipeline docs, and repo settings to contrib…
telegrapher Feb 28, 2026
576d4cc
Add CI and PR title validation workflows
telegrapher Feb 28, 2026
827460e
Clean up release workflow and add changelog grouping
telegrapher Feb 28, 2026
3faa3b8
Fix unchecked error returns flagged by linter
telegrapher Feb 28, 2026
37d0923
Clarify PKCE usage for public and private clients
telegrapher Feb 28, 2026
785da2c
Replace Codecov with coverage summary in CI log
telegrapher Feb 28, 2026
e77f0a8
Add govulncheck workflow for dependency vulnerability scanning
telegrapher Feb 28, 2026
0f01bba
Link docs/ write-ups from CONTRIBUTING.md
telegrapher Feb 28, 2026
b75534b
Enable Renovate auto-merge for patch dependency updates
telegrapher Feb 28, 2026
ead6e1c
Add weekly govulncheck schedule with auto-issue on failure
telegrapher Feb 28, 2026
53b7e57
Add go mod verify and goreleaser check to CI
telegrapher Feb 28, 2026
916f711
Use go-version stable across all workflows
telegrapher Feb 28, 2026
2603279
Revert to go-version-file go.mod for controllable Go version pinning
telegrapher Feb 28, 2026
a9bc578
Document Go version pinning strategy in CONTRIBUTING.md
telegrapher Feb 28, 2026
1ecaa4d
Fix govulncheck duplicate auth header by disabling persist-credentials
telegrapher Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: CI

# Run on every push and on pull requests targeting main.
on:
push:
branches: ['**']
pull_request:
branches: [main]

# Read-only access is sufficient for running tests and linting.
permissions:
contents: read

jobs:
# Run the full test suite with race detection and generate a coverage report.
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

# Go version is read from go.mod so it stays in sync with the project.
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod

# Verify dependency checksums match go.sum (detects corrupted or tampered modules).
- name: Verify dependencies
run: go mod verify

- name: Run tests
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...

# Print coverage summary to the CI log.
- name: Coverage summary
run: go tool cover -func=coverage.out | tail -1

# Static analysis: go vet for built-in checks, golangci-lint for extended linting.
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod

- name: Run go vet
run: go vet ./...

# golangci-lint v2 runs 50+ linters in a single pass.
# Without a .golangci.yml config file it uses sensible defaults.
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v7
with:
version: v2.10.1

# Verify the project compiles. Cross-platform builds are handled by
# GoReleaser at release time, so a single-platform check suffices here.
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod

- name: Build
run: go build -o /dev/null .

# Validate .goreleaser.yml so config errors are caught before tagging a release.
- name: Verify GoReleaser config
uses: goreleaser/goreleaser-action@v6
with:
args: check
57 changes: 57 additions & 0 deletions .github/workflows/govulncheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Runs Go's official vulnerability scanner against project dependencies.
# Fails the job if any known vulnerabilities affect the code.
# See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck

name: govulncheck

on:
push:
branches: [main]
pull_request:
# Weekly scan catches new vulnerabilities even when the code hasn't changed.
schedule:
- cron: '0 9 * * 1' # Every Monday at 9:00 UTC

permissions:
contents: read
issues: write

jobs:
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- uses: actions/setup-go@v6
with:
go-version-file: go.mod

# golang/govulncheck-action runs govulncheck against all packages (./...).
# With the default text output format, the job fails if vulnerabilities are found.
- uses: golang/govulncheck-action@v1
with:
go-version-file: go.mod

# Open a GitHub issue when the scheduled scan finds vulnerabilities.
# Only runs on cron failures — PR and push failures are visible in the checks UI.
notify:
needs: govulncheck
runs-on: ubuntu-latest
if: failure() && github.event_name == 'schedule'
steps:
- uses: actions/github-script@v7
with:
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'govulncheck: vulnerability detected',
body: [
'The weekly [govulncheck scan](' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId + ') found vulnerabilities.',
'',
'Review the workflow run and update affected dependencies.'
].join('\n'),
labels: ['security']
});
26 changes: 10 additions & 16 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,44 +1,38 @@
# This is a basic workflow to help you get started with Actions

name: Release with GoReleaser

# Controls when the action will run.
# Triggered by pushing a version tag (e.g. v1.2.3) or manually via the Actions tab.
on:
# Triggers the release workflow only for new tags
push:
tags:
- '*'

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
# GoReleaser needs write access to create the GitHub release and upload artifacts.
permissions:
contents: write

jobs:
# This workflow contains a single job called "build"
release:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
# Full history is required for GoReleaser to generate the changelog.
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

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

# Runs a set of commands using the runners shell
# GoReleaser builds, packages and publishes the release.
# See .goreleaser.yml for build targets and packaging configuration.
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

41 changes: 41 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Ensure PR titles follow the Conventional Commits format so that
# squash-merged commits produce a clean changelog via GoReleaser.
# See: https://www.conventionalcommits.org
name: PR Title

on:
pull_request_target:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
lint:
name: Validate PR title
runs-on: ubuntu-latest
steps:
# Validates that the PR title matches the conventional commit format.
# Examples of valid titles:
# feat: add token refresh command
# fix(auth): handle expired tokens
# docs: update CONTRIBUTING.md
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Types allowed in PR titles. These map to GoReleaser changelog groups.
types: |
feat
fix
docs
chore
ci
test
refactor
perf
style
build
revert
# Require a scope in parentheses (optional — set to true to enforce).
requireScope: false
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ Temporary Items
*.yaml
*.yml

# Allow test golden files and GitHub config
!**/testdata/**
!.github/**

dist/
/imscli
go.mod.local
12 changes: 12 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,24 @@ checksum:
name_template: 'checksums.txt'
snapshot:
version_template: "{{ .Version }}-next"
# Group commits by conventional-commit prefix for cleaner release notes.
changelog:
sort: asc
groups:
- title: Features
regexp: '^.*?feat(\([^)]+\))?!?:.+$'
order: 0
- title: Bug Fixes
regexp: '^.*?fix(\([^)]+\))?!?:.+$'
order: 1
- title: Other
order: 999
filters:
exclude:
- '^docs:'
- '^test:'
- '^ci:'
- '^chore:'
nfpms:
- file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
homepage: "https://github.com/adobe/imscli"
Expand Down
81 changes: 79 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,91 @@
# Contributing
## Release process

This project wraps [adobe/ims-go](https://github.com/adobe/ims-go).

## Development

```bash
# Build
go build -o imscli .

# Run all tests
go test ./...

# Run a single test by name
go test ./ims/ -run TestValidateURL

# Vet (static analysis)
go vet ./...
```

## CI Pipelines

All pipelines are defined in `.github/workflows/`.

### CI (`ci.yml`)

**Triggers:** Every push to any branch and pull requests targeting `main`.

Runs three parallel jobs:

- **Test** — Runs `go test -race` with coverage and prints a coverage summary to the log. Verifies dependency checksums with `go mod verify`.
- **Lint** — Runs `go vet` and [golangci-lint](https://golangci-lint.run/) for extended static analysis.
- **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.

### PR Title (`pr-title.yml`)

**Triggers:** When a pull request is opened, edited, synchronized or reopened.

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.

### Release (`main.yml`)

**Triggers:** Pushing a version tag (e.g. `v1.2.3`) or manual dispatch from the Actions tab.

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.

### CodeQL (`codeql-analysis.yml`)

**Triggers:** Every push, pull requests targeting `main`, and weekly on a cron schedule.

Runs GitHub's CodeQL security analysis to detect vulnerabilities in the Go source code.

### govulncheck (`govulncheck.yml`)

**Triggers:** Every push to `main`, pull requests, and weekly on Monday at 9:00 UTC.

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.

## Repository Settings

- **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`.
- **Auto-delete branches** — Head branches are automatically deleted after a PR is merged.
- **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`.
- **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.

## Release Process

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

To build and release a new version:
```
git tag vX.X.X && git push --tags
gorelease --rm-dist
goreleaser release --clean
```

The binary version is set automatically to the git tag.

Please tag new versions using [semantic versioning](https://semver.org/spec/v2.0.0.html).

## Development Notes

### PersistentPreRunE and subcommands

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.

## Additional Reading

The `docs/` directory contains write-ups on non-obvious problems encountered during development:

- [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.
- [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.
Loading
Loading