diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 67d1021..ca2b95d 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: https://github.com/actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.21' cache: true - name: Install golangci-lint @@ -34,7 +34,7 @@ jobs: - name: Set up Go uses: https://github.com/actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.21' cache: true - name: Build application @@ -49,7 +49,7 @@ jobs: - name: Set up Go uses: https://github.com/actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.21' cache: true - name: Run unit tests @@ -66,13 +66,13 @@ jobs: - name: Set up Go uses: https://github.com/actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.21' cache: true - name: Build production binary run: | make build - echo "Binary built at: $(pwd)/bin/fgj" + echo "Binary built at: $(pwd)/bin/fj" - name: Run functional tests run: go test -v -race -tags=functional ./tests/functional/... diff --git a/.gitea/workflows/nightly.yml b/.gitea/workflows/nightly.yml index b1d76a4..ef53290 100644 --- a/.gitea/workflows/nightly.yml +++ b/.gitea/workflows/nightly.yml @@ -18,13 +18,13 @@ jobs: - name: Set up Go uses: https://github.com/actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.21' cache: true - name: Build production binary run: | make build - echo "Binary built at: $(pwd)/bin/fgj" + echo "Binary built at: $(pwd)/bin/fj" - name: Run functional tests run: go test -v -race -tags=functional ./tests/functional/... diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml deleted file mode 100644 index b6b24b6..0000000 --- a/.gitea/workflows/release.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*' - -jobs: - goreleaser: - runs-on: codeberg-small - steps: - - name: Checkout code - uses: https://github.com/actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Fetch tags - run: git fetch --force --tags - - - name: Set up Go - uses: https://github.com/actions/setup-go@v5 - with: - go-version: '1.24' - cache: true - - - name: Run GoReleaser - uses: https://github.com/goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser - version: "~> v2" - args: release --clean - env: - # Forgejo Actions injects GITEA_TOKEN for the workflow by default; - # override with RELEASE_TOKEN secret if a longer-lived token is needed. - GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITEA_TOKEN }} - GORELEASER_FORCE_TOKEN: gitea diff --git a/.gitignore b/.gitignore index de67f36..58e31fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,12 @@ # Binaries -fgj +fj +bin/ *.exe *.exe~ *.dll *.so *.dylib -# Goreleaser output -dist/ -bin/ - # Test binary *.test diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 367e6ab..0000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,91 +0,0 @@ -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -version: 2 - -project_name: fgj - -before: - hooks: - - go mod tidy - -builds: - - id: fgj - binary: fgj - main: . - env: - - CGO_ENABLED=0 - flags: - - -trimpath - ldflags: - - -s -w -X "forgejo.zerova.net/public/fgj-sid/cmd.version={{ .Version }}" - goos: - - linux - - darwin - - windows - - freebsd - goarch: - - amd64 - - arm64 - - arm - goarm: - - "6" - - "7" - ignore: - - goos: darwin - goarch: arm - - goos: windows - goarch: arm - - goos: freebsd - goarch: arm - -archives: - - id: default - name_template: >- - {{ .ProjectName }}- - {{- .Version }}- - {{- .Os }}- - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }} - formats: [tar.gz] - format_overrides: - - goos: windows - formats: [zip] - files: - - README.md - - LICENSE - - CHANGELOG.md - -checksum: - name_template: "checksums.txt" - algorithm: sha256 - -snapshot: - version_template: "{{ incpatch .Version }}-next" - -changelog: - sort: asc - use: git - filters: - exclude: - - "^docs:" - - "^test:" - - "^chore:" - - "^ci:" - - "Merge pull request" - - "Merge branch" - -gitea_urls: - api: https://forgejo.zerova.net/api/v1 - download: https://forgejo.zerova.net - -release: - draft: false - prerelease: auto - mode: replace - header: | - ## fgj {{ .Tag }} - - Install with `go install forgejo.zerova.net/public/fgj-sid@{{ .Tag }}` or download a prebuilt binary below. - footer: | - **Full Changelog**: https://forgejo.zerova.net/public/fgj-sid/compare/{{ .PreviousTag }}...{{ .Tag }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ef142..170c5ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,180 +5,151 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] — 0.4.0 +## [0.4.0] - 2026-05-02 -### Added — Repository Management +Audit-driven hardening pass. Three reviewers (Codex + two Claude agents +with non-overlapping focuses) found 13 issues across cmd/ and internal/; +this release ships fixes for all 13. -- `fgj branch {list,rename,delete}` — list branches with protection - status, rename branches, delete (protected branches are refused). -- `fgj branch {protect,unprotect}` — create, replace, or remove branch - protection rules with `--require-approvals`, - `--dismiss-stale-approvals`, `--require-signed-commits`, - `--block-on-rejected-reviews`, `--block-on-outdated-branch`, - `--push-whitelist`, `--merge-whitelist`, `--require-status-checks`. - `protect` is idempotent (create-or-edit). -- `fgj repo delete` — type-to-confirm deletion; `--yes` for scripts. -- `fgj repo search` — search repositories on the current host by - query, topic, or description; filter by `--type`, `--owner`, - `--private`, `--archived`. -- `fgj repo migrate` — import from GitHub, GitLab, Gitea, Gogs, or - a plain Git remote; supports mirror mode and selective import of - wiki/labels/milestones/issues/PRs/releases/LFS. -- `fgj repo create-from-template` — scaffold a repo from a template, - with fine-grained control over what to copy (content, topics, - labels, webhooks, git hooks, avatar). +### BREAKING -### Added — Pull Requests +- `--json=fields` syntax removed. The flag was a string with + `NoOptDefVal=" "` sentinel — `--json` alone meant "everything", + `--json=fields` projected. That produced `--json string[=" "]` in + `--help` and required a literal `=` because `--json fields` was parsed + as the bare flag plus a positional. **Migration**: `--json=fields` → + `--json-fields fields`. Bare `--json` still means "all fields as JSON". + `--json` and `--json-fields` are mutually exclusive; `--jq` composes + with either. -- `fgj pr clean ` — delete the local branch created by - `fgj pr checkout`. Refuses if the PR is open (use `--force`) or - if the branch is currently checked out. -- `fgj pr approve ` / `fgj pr reject ` — shortcuts over - `pr review`; `reject` requires a body. -- `fgj pr review-comments ` — list inline review comments across - every review on a PR. -- `fgj pr resolve ` / `fgj pr unresolve ` — - mark review threads (un)resolved. Requires Forgejo 8.x+ / - Gitea 1.22+ server-side. +### Added -### Added — Notifications & Organizations - -- `fgj notification list [--all]` / `fgj notification read ` — - list unread (default) or all notifications; mark individual - threads read. -- `fgj notification {unread,pin,unpin}` — flip thread state - (complements `read`). Uses the Gitea `NotifyStatus` enum. -- `fgj org {list,create,delete}` — list your orgs, create with - visibility/description, delete with confirmation. -- `fgj webhook {list,create,update,delete}` — full CRUD on repo - webhooks: gitea/slack/discord/etc. hook types, event selection, - content type, secret, branch filter, auth header. - -### Added — Releases, Actions, Milestones, Time - -- `fgj release asset {list,create,delete}` — granular release - attachment management. `delete` accepts numeric IDs or filenames. -- `fgj actions run delete` — delete a completed workflow run. Refuses - non-terminal runs unless `--force`; suggests `actions run cancel` - for those. -- `fgj milestone issues {add,remove}` — associate or disassociate - issues with a milestone. Milestone accepted as title or id. -- `fgj time {list,add,delete,reset}` — tracked-time management. Accepts - Go duration strings (`30m`, `1h30m`). `list` with no arg shows the - authenticated user's times across all repos. - -### Added — Misc - -- `fgj open [number] [--url]` — launch the repo / issue / PR page in - the default browser; auto-detects issue-vs-PR; prints URL on - non-TTY stdout or with `--url`. -- `fgj whoami` — show the authenticated user and host. -- `fgj admin user list` — admin-gated user enumeration. -- `fgj logins {list,default}` — complement to `fgj auth`. `list` shows - all configured hosts in a table, highlighting the default. `default` - gets/sets which hostname wins when no other signal is present. -- `pr list` / `issue list` gain `--since` and `--before` flags - accepting `YYYY-MM-DD`, RFC 3339, `YYYY-MM-DD HH:MM:SS`, or relative - deltas (`7d`, `24h`, `2w`, `1m`). Server-side filter for issues, - client-side for PRs (SDK lacks a PR-side filter). -- `fgj label update` added as an alias for `fgj label edit`. -- `fgj repo {archive,unarchive}` — toggle a repository's archived state - via `EditRepo`. Archiving prompts for confirmation (requires `--yes` - in non-TTY environments); unarchiving is reversible and skips the - prompt. -- `fgj completion install [shell]` — idempotently writes the - completion script to the shell-standard location (XDG for bash, - `~/.zsh/completions/_fgj` for zsh, `~/.config/fish/completions/fgj.fish` - for fish; brew prefix on macOS when present). Supports `--dry-run` - and `--system` (bash only, prints the required sudo command). +- `fj api --json` / `--json-fields` / `--jq` — projection and jq filtering + for raw API responses. Routes through the same `addJSONFlags` helpers + as the other list commands. Closes the inconsistency where `fj api` + was the only command returning raw JSON without these knobs. +- `fj api --paginate` — follows RFC 5988 `Link: rel="next"` headers and + concatenates JSON array pages, gh-compatible. Validates same-origin + before forwarding the bearer token to the next URL. +- `cmd/paginate.go` — generic `paginateGitea[T any]` helper. Applied to + `repo list`, `pr list`, `issue list`. Previously only `release list` + walked pages; the others passed `PageSize: limit` directly to the + gitea SDK, which silently caps PageSize at 50, so `--limit > 50` was + truncated without warning. +- `CLAUDE.md` — guide for Claude Code sessions: layout, codex review + pattern, release process, homebrew tap update steps. ### Changed -- `fgj actions secret create` stdin handling reworked. Adds `--body` - (inline) and `--body-file` (path or `-` for stdin) flags; interactive - prompts now use hidden input via `term.ReadPassword`; piped stdin is - read whole (prior `fmt.Scanln` broke on spaces/newlines and echoed - the typed value). Empty values are rejected. -- `HostConfig` gains an optional `default: true` field. When no other - signal selects a host (flag, `FGJ_HOST`, git remote, `match_dirs`), - the host marked default wins before the `codeberg.org` fallback. - Multiple `default: true` entries are tolerated with a stderr - warning; alphabetical-first wins. -- Gitea SDK bumped `v0.22.1` → `v0.23.2` (last release compatible with - Go 1.24; `v0.24+` requires Go 1.26). - -### Development - -- Switched to standard semver tags (`v0.3.1`, `v0.4.0`, …); retired - letter-suffix scheme (`v0.3.0a`…`v0.3.0f`) which Go's module resolver - ignored, leaving `go install @latest` pointing at the pre-migration - `v0.3.0` tag. -- Version string is now injected at build time via `-ldflags`; the - hardcoded constant in `cmd/root.go` has been replaced with a - `var version = "dev"` fallback. `make build` derives the version from - `git describe --tags --always --dirty`. -- Added `.goreleaser.yaml` for multi-platform release builds - (linux/darwin/windows/freebsd × amd64/arm64/arm) with SHA256 - checksums and auto-generated release notes. -- Added `.gitea/workflows/release.yml` that publishes release artifacts - to the Forgejo release page on tag push. -- Aligned CI Go version (`1.24`) with `go.mod`; previously CI ran on - `1.21` while `go.mod` required `1.24`. - -## [0.3.1] - 2026-04-19 +- `--json` flag rebuilt as a plain `Bool`. `--json-fields` keeps + comma-separated projection. Both registered via `addJSONFlags` and + marked `MutuallyExclusive`. +- `cmd/actions.go` — `run` and `workflow` subtrees converted from + package-level `var`s to factory functions (`newRunCmd`, + `newWorkflowCmd`, ...). `cmd/aliases.go` shrank from 142 → 17 lines + and now calls those same factories with a `parentLabel` parameter that + disambiguates the alias variant. Result: `diff` of `fj run list + --help` flags vs `fj actions run list --help` flags is now empty. + Drift between the two paths is structurally impossible. +- `fj api` now uses `internal/api.SharedHTTPClient` (30s timeout, pooled + connections) instead of a zero-value `&http.Client{}` with no timeout. + A hung Forgejo no longer pins the CLI indefinitely. +- `fj api` response body bounded by `io.LimitReader` at 64 MB to prevent + OOM-on-self. +- `cmd/auth.go` removed redundant local `--hostname` declarations on + three subcommands. The persistent flag on rootCmd is now the only + declaration; previously local declarations shadowed it, so + `fj --hostname=X auth login` and `fj auth login --hostname=X` went + through different code paths. +- `--token` on `auth login` emits a stderr warning when used (visible + in `ps auxe` and shell history). Flag not removed; just discoverable. +- Error handling: `Hint` is now a structured field on `CLIError`. + JSON-error consumers get clean structure; the human renderer still + appends `\nHint: ...`. Dropped substring matching of `"401"`/`"403"` + against rendered error strings (would match issue #403); now relies + exclusively on typed `*api.APIError`. +- Network errors (`no such host`, `connection refused`, `i/o timeout`) + return a structured `CLIError` with code `ErrNetworkError` and a hint. +- Config dir created with mode 0700 instead of 0755. ### Fixed -- `go install forgejo.zerova.net/public/fgj-sid@latest` now resolves - correctly. Previous releases used letter-suffix tags (`v0.3.0a`–`f`) - which are not valid Go module versions and were ignored by the - module resolver, leaving `@latest` pinned to `v0.3.0` — a commit - that predates the module-path migration from `codeberg.org/romaintb/fgj`. +- `--config ` now actually honored. Previously fed only into + Viper; every command that touched config went through + `internal/config.Load()` / `Save()` which always read the default + path. So `fj --config other.yaml auth login` writes to other.yaml now. +- `fj run list --json`, `fj workflow list --json`, `fj wiki view --json` + now produce JSON. `cmd/aliases.go` registered `--json` as `Bool` but + handlers called `wantJSON()` which does `GetString("json")` — pflag + returned a type-error that `wantJSON` silently swallowed. + `cmd/wiki.go` had the inverse bug (`GetBool` against an + `addJSONFlags`-registered string flag). Both routed through + `addJSONFlags`/`wantJSON`/`outputJSON` consistently now. +- `migrateConfigDir` opens dst with `O_TRUNC`. Previously a partially- + pre-existing dst file would have legacy contents overwrite a prefix + and leave stale tail bytes — silent YAML/token corruption. Refactored + close handling into `copyOneConfigFile`. + +### Security + +- `fj api` endpoint path traversal closed. `fj api '/../admin/users'` + previously normalized through `http.NewRequest` to + `https://host/admin/users` — silently sending authenticated traffic + to non-API paths. Endpoint is now parsed via `url.Parse`, `..` + segments rejected, then `JoinPath` onto the `/api/v1` base. + URL-encoded `%2E%2E` is also caught because Go decodes before our + split. +- `fj api --paginate` validates same-origin before forwarding the + bearer token to a `Link: rel="next"` URL. Refuses to reattach + `Authorization` if the next URL's scheme isn't `https` or its host + doesn't match the configured one. +- `initConfig` warns on stderr if the resolved config file is world or + group readable (`mode & 0o077 != 0`). ## [0.3.0c] - 2026-03-21 ### Added #### Label Management -- `fgj label list` - List repository labels -- `fgj label create` - Create a label with color and description -- `fgj label edit` - Edit label name, color, or description -- `fgj label delete` - Delete a label +- `fj label list` - List repository labels +- `fj label create` - Create a label with color and description +- `fj label edit` - Edit label name, color, or description +- `fj label delete` - Delete a label #### Milestone Management -- `fgj milestone list` - List milestones with state filtering -- `fgj milestone view` - View milestone details -- `fgj milestone create` - Create a milestone with description and due date -- `fgj milestone edit` - Edit milestone title, description, due date, or state -- `fgj milestone delete` - Delete a milestone +- `fj milestone list` - List milestones with state filtering +- `fj milestone view` - View milestone details +- `fj milestone create` - Create a milestone with description and due date +- `fj milestone edit` - Edit milestone title, description, due date, or state +- `fj milestone delete` - Delete a milestone #### Wiki Management -- `fgj wiki list` - List wiki pages -- `fgj wiki view` - View wiki page content -- `fgj wiki create` - Create a wiki page from flag or file -- `fgj wiki edit` - Edit a wiki page -- `fgj wiki delete` - Delete a wiki page +- `fj wiki list` - List wiki pages +- `fj wiki view` - View wiki page content +- `fj wiki create` - Create a wiki page from flag or file +- `fj wiki edit` - Edit a wiki page +- `fj wiki delete` - Delete a wiki page #### Issue Dependencies -- `fgj issue edit --add-dependency ` - Add issue dependency -- `fgj issue edit --remove-dependency ` - Remove issue dependency +- `fj issue edit --add-dependency ` - Add issue dependency +- `fj issue edit --remove-dependency ` - Remove issue dependency ## [0.3.0b] - 2026-03-21 ### Added #### Repository Management -- `fgj repo edit` - Edit repository settings (visibility, description, homepage, default branch) +- `fj repo edit` - Edit repository settings (visibility, description, homepage, default branch) ### Fixed -- `fgj repo create --public` flag was defined but never read; now properly wired up +- `fj repo create --public` flag was defined but never read; now properly wired up ## [0.3.0a] - 2026-03-21 ### Added #### Raw API Access -- `fgj api ` - Make authenticated REST API requests to any Forgejo/Gitea endpoint +- `fj api ` - Make authenticated REST API requests to any Forgejo/Gitea endpoint - HTTP method selection (`--method`/`-X`), auto-switches to POST when fields are provided - JSON field assembly (`--field`/`-f`) with type inference (bool, int, float, null, string) - Raw string fields (`--raw-field`/`-F`) @@ -188,14 +159,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Response header display (`--include`/`-i`) #### Pull Request Management -- `fgj pr diff ` - View the diff for a pull request +- `fj pr diff ` - View the diff for a pull request - Colorized output (`--color auto/always/never`) - Changed file names only (`--name-only`) - Diffstat summary (`--stat`) -- `fgj pr comment ` - Add a comment to a pull request +- `fj pr comment ` - Add a comment to a pull request - Body from flag (`--body`/`-b`) or file (`--body-file`, `-` for stdin) - JSON output (`--json`) -- `fgj pr review ` - Submit a review on a pull request +- `fj pr review ` - Submit a review on a pull request - Approve (`--approve`/`-a`), request changes (`--request-changes`/`-r`), or comment (`--comment`/`-c`) - Body from flag or file - JSON output (`--json`) @@ -211,30 +182,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added #### Forgejo Actions -- `fgj actions run watch ` - Poll a run until completion -- `fgj actions run rerun ` - Trigger a rerun of a workflow run -- `fgj actions run cancel ` - Cancel an in-progress workflow run -- `fgj actions workflow enable ` - Enable a workflow -- `fgj actions workflow disable ` - Disable a workflow +- `fj actions run watch ` - Poll a run until completion +- `fj actions run rerun ` - Trigger a rerun of a workflow run +- `fj actions run cancel ` - Cancel an in-progress workflow run +- `fj actions workflow enable ` - Enable a workflow +- `fj actions workflow disable ` - Disable a workflow #### Repository Management -- `fgj repo create ` - Create a new repository with full option set: `--private`/`--public`, `--description`, `--add-readme`, `--gitignore`, `--license`, `--homepage`, `--clone`, `--team` +- `fj repo create ` - Create a new repository with full option set: `--private`/`--public`, `--description`, `--add-readme`, `--gitignore`, `--license`, `--homepage`, `--clone`, `--team` #### Issue Management -- `fgj issue create -l