# Development This guide covers the day-to-day workflow for working on **goget** — building, testing, releasing, and contributing. ## Requirements - **Go 1.26+** - **Platform:** Linux (amd64, arm64, riscv64, loong64) or FreeBSD (amd64, arm64, riscv64) - **`just`** — task runner (`brew install just` / `apt install just` / `pacman -S just`) - **`groff`** *(optional)* — for `just man` manpage validation All Go source files carry the build constraint `//go:build linux || freebsd`. goget does not build or run on macOS or Windows. ## First-time Setup ```bash git clone https://codeberg.org/petrbalvin/goget.git cd goget just install # go mod download just test # verify everything works on your machine just build # produce bin/goget ``` A successful `just build` produces a static `bin/goget` binary with no runtime dependencies beyond the Go standard library and `golang.org/x/` packages. ## Task Reference Every task is defined in [`justfile`](../justfile) and is the canonical way to interact with the project. | Task | What it does | |------|--------------| | `just` | List all recipes (default) | | `just install` | `go mod download` | | `just run` | `go run ./cmd/goget` (development loop) | | `just build` | `gofmt -l` check + `go vet ./...` + build `bin/goget` | | `just test` | `go test -race -count=1 ./...` | | `just uninstall` | Remove `bin/` and `coverage.out` | | `just man` | Validate `man/goget.1` with `groff` (skips if absent) | | `just install-man` | Install manpage to `~/.local/share/man/man1/` (gzipped, XDG) | | `just uninstall-man` | Remove installed manpage | `just build` is **strict**: it runs `gofmt -l` (any output is a failure) plus `go vet ./...`. Both must report zero warnings before the binary compiles. This is exactly what CI checks; running it locally first saves a round-trip. ## Development Loop ```mermaid graph TD Edit[Edit source in cmd/, internal/, pkg/] Test[just test] Vet[just build runs gofmt + vet + build] Man[just man - groff validation] Commit[Conventional Commits message] Push[Push to development branch] CI[Forgejo CI: test.yml] Edit --> Test Test -->|fail| Edit Test --> Vet Vet -->|fail| Edit Vet --> Man Man -->|fail| Edit Man --> Commit Commit --> Push Push --> CI CI -->|fail| Edit CI -->|pass| Merge[Merge / open PR] ``` Typical iteration: ```bash # Make a focused change. $EDITOR cmd/goget/flags.go # Quick check that compile + format + vet pass. just build # Run the relevant tests with the race detector. just test # Validate any manpage edits. just man ``` ## Building from Source `just build` does three things in order: 1. Copies `man/goget.1` into `cmd/goget/man-page-data/` — the manpage is embedded into the binary via `//go:embed`, and `//go:embed` can only reference files within the embedding package's directory. 2. Runs `gofmt -l` over `cmd/`, `internal/`, and `pkg/`. Any output is a formatting violation that breaks the build. 3. Runs `go vet ./...` — must report zero warnings. 4. Compiles `bin/goget` with `-ldflags "-s -w"` (strips DWARF). The resulting binary is fully static. `ldd bin/goget` should report `not a dynamic executable` on Linux. ## Testing ```bash just test # full suite, race detector, count=1 go test ./internal/protocol/gemini/... # focused go test -run TestFoo ./internal/core/ # one test ``` Conventions: - Tests live next to the code as `*_test.go`. - Use table-driven tests for parsers, validators, and protocol handlers. - Cover both happy path and error path. - Test names follow `TestFunctionName_Scenario`. - Fuzz tests use `testing.F` and live next to the code they exercise. The race detector is **always** on in CI. New code that introduces a data race must be fixed, not silenced. ## Manpage Authoring The single source of truth is `man/goget.1` (troff). Editing this file is what most users eventually read via `man goget` and what `--generate-man-page` prints. Workflow: ```bash $EDITOR man/goget.1 just man # validates the troff just build # copies the file into cmd/goget/man-page-data/ just test # cmd/goget/man_page_test.go asserts every CLI flag # is documented and the embedded source still parses ``` `cmd/goget/man_page_test.go` enforces documentation coverage — adding a new flag to `defineFlags` without documenting it in `man/goget.1` breaks the build. ## Logging The project uses `internal/log` for structured output. Prefer `log.Infof`, `log.Errorf`, etc. over ad-hoc `fmt.Fprintf(os.Stderr, ...)` or `cli.Print*`. The backlog tracks a migration of the remaining ~140 legacy callsites to `internal/log`. ## Branching & Releases ```mermaid graph LR Feature[Feature branches] Development[development] Main[main] Tag[vX.Y.Z tag] Release[Forgejo release] Feature --> Development Development --> Main Main --> Tag Tag --> Release ``` - **`development`** — the only branch that receives day-to-day work. All pull requests target `development`. - **`main`** — only changes when `development` is merged for a release. Never commit directly to `main`. - **Tags** — `vX.Y.Z` tags trigger the release workflow. ### Cutting a Release 1. Bump `Version` in [`internal/core/version.go`](../internal/core/version.go). 2. Move the top `## [development]` section in [`CHANGELOG.md`](../CHANGELOG.md) to `## [X.Y.Z] — YYYY-MM-DD`, inserting a fresh empty `## [development]` section above it. 3. Ensure CI is green on `development`. 4. Merge `development` → `main`. 5. Tag and push: ```bash git tag -a vX.Y.Z -m "goget vX.Y.Z" git push origin main git push origin vX.Y.Z ``` 6. The release workflow builds binaries for all seven platform targets, extracts the matching `CHANGELOG.md` section, and publishes the release on Codeberg. ## CI Two Forgejo Actions workflows live under [`.forgejo/workflows/`](../.forgejo/workflows): ### `test.yml` Triggered on every push to `development` and on every pull request targeting `development`. | Job | What it does | |-----|--------------| | `vet` | `gofmt -l` check + `go vet ./...` (no output = pass) | | `test` | `go test -race -count=1 ./...` + coverage gate | Run the same locally before opening a PR: ```bash go vet ./... just test ``` ### `release.yml` Triggered by pushing a `v*` tag. Verifies that the tag matches `internal/core.Version`, runs the quality gate, builds static binaries for all seven targets, and publishes the release. Requires a `FORGEJO_TOKEN` secret with release-creation permissions on the runner. ## Code Style - Follow [Effective Go](https://go.dev/doc/effective_go). - Keep lines under 100 characters. - `camelCase` for locals, `PascalCase` for exported symbols. - Prefix every file with a one-line comment describing its purpose. - Use `//go:build linux || freebsd` on every source file. - Use long CLI flags only (`--header`, not `-H`). - Stick to the Go standard library and `golang.org/x/`. External dependencies require an explicit justification. - Error messages: English, lowercase, no trailing period, always with context (e.g. `open config: permission denied`). - Run `gofmt` before committing. ## Reporting Bugs Open an issue at with: - `goget --version` output. - Operating system and architecture. - The full command used and its output. - Expected vs actual behaviour. For **security issues**, email [opensource@petrbalvin.org](mailto:opensource@petrbalvin.org) instead of opening a public issue.