diff --git a/.github/scripts/update-homebrew-formula.sh b/.github/scripts/update-homebrew-formula.sh new file mode 100755 index 0000000..d5412c6 --- /dev/null +++ b/.github/scripts/update-homebrew-formula.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail + +VERSION="${VERSION:?VERSION is required}" +REPOSITORY="${GITHUB_REPOSITORY:-muxx/redmine-cli}" +DIST_DIR="${DIST_DIR:-dist}" +TAP_REPOSITORY="${HOMEBREW_TAP_REPOSITORY:-muxx/homebrew-tap}" +FORMULA_NAME="${HOMEBREW_FORMULA_NAME:-redmine-cli}" +FORMULA_CLASS="${HOMEBREW_FORMULA_CLASS:-RedmineCli}" +FORMULA_PATH="Formula/${FORMULA_NAME}.rb" + +version_number="${VERSION#v}" + +archive_name() { + local goos="$1" + local goarch="$2" + case "${goos}" in + windows) + printf 'redmine_%s_%s_%s.zip' "${VERSION}" "${goos}" "${goarch}" + ;; + *) + printf 'redmine_%s_%s_%s.tar.gz' "${VERSION}" "${goos}" "${goarch}" + ;; + esac +} + +archive_sha() { + local archive="$1" + awk -v archive="${archive}" '$2 == archive { print $1 }' "${DIST_DIR}/checksums.txt" +} + +download_url() { + local archive="$1" + printf 'https://github.com/%s/releases/download/%s/%s' "${REPOSITORY}" "${VERSION}" "${archive}" +} + +required_archive_sha() { + local archive="$1" + local sha + sha="$(archive_sha "${archive}")" + if [[ -z "${sha}" ]]; then + echo "missing checksum for ${archive}" >&2 + exit 1 + fi + printf '%s' "${sha}" +} + +darwin_amd64_archive="$(archive_name darwin amd64)" +darwin_arm64_archive="$(archive_name darwin arm64)" +linux_amd64_archive="$(archive_name linux amd64)" +linux_arm64_archive="$(archive_name linux arm64)" + +darwin_amd64_sha="$(required_archive_sha "${darwin_amd64_archive}")" +darwin_arm64_sha="$(required_archive_sha "${darwin_arm64_archive}")" +linux_amd64_sha="$(required_archive_sha "${linux_amd64_archive}")" +linux_arm64_sha="$(required_archive_sha "${linux_arm64_archive}")" + +tap_dir="${HOMEBREW_TAP_DIR:-}" +if [[ -z "${tap_dir}" ]]; then + if [[ -z "${GH_TOKEN:-}" ]]; then + echo "GH_TOKEN with write access to ${TAP_REPOSITORY} is required" >&2 + exit 1 + fi + tap_dir="$(mktemp -d)" + trap 'rm -rf "${tap_dir}"' EXIT + git clone "https://x-access-token:${GH_TOKEN}@github.com/${TAP_REPOSITORY}.git" "${tap_dir}" +else + mkdir -p "${tap_dir}" +fi + +mkdir -p "${tap_dir}/Formula" +cat >"${tap_dir}/${FORMULA_PATH}" </dev/null || echo dev) LDFLAGS ?= -s -w -X main.version=$(VERSION) -GOFILES := $(shell find . -name '*.go' -not -path './vendor/*') +ALL_GOFILES := $(shell find . -name '*.go' -not -path './vendor/*') +GOIMPORTS := $(shell if command -v goimports >/dev/null 2>&1; then command -v goimports; fi) +GOIMPORTS_PKG ?= golang.org/x/tools/cmd/goimports@v0.44.0 .DEFAULT_GOAL := help -.PHONY: help download-openapi-spec build install lint fix test gen gen-docs check-docs check-gen check clean +.PHONY: help download-openapi-spec build install lint fix test gen gen-docs check-gen check clean help: @echo "Targets:" @echo " download-openapi-spec Download latest Redmine OpenAPI spec to $(OPENAPI_SPEC)" @echo " build Compile $(BINARY)" @echo " install Install $(BINARY)" - @echo " lint Run golangci-lint when available, otherwise go vet" - @echo " fix Run gofmt and goimports" + @echo " lint Verify gofmt/goimports on all Go files and run go vet" + @echo " fix Run gofmt and goimports on all Go files" @echo " test Run unit tests" @echo " gen Regenerate OpenAPI command metadata and docs" @echo " gen-docs Regenerate docs/usage.md and generated metadata" - @echo " check-docs Verify docs/usage.md is current" @echo " check-gen Verify generated files are current" @echo " check Run lint, tests, and generated-file checks" @@ -42,19 +43,29 @@ install: $(GO) install -trimpath -ldflags "$(LDFLAGS)" ./cmd/redmine lint: - @if command -v golangci-lint >/dev/null 2>&1; then \ - golangci-lint run; \ + @tmp=$$(mktemp -d); \ + trap 'rm -rf "$$tmp"' EXIT; \ + for file in $(ALL_GOFILES); do \ + mkdir -p "$$tmp/$$(dirname "$$file")"; \ + cp "$$file" "$$tmp/$$file"; \ + done; \ + gofmt -w $$(find "$$tmp" -name '*.go'); \ + if [ -n "$(GOIMPORTS)" ]; then \ + "$(GOIMPORTS)" -w $$(find "$$tmp" -name '*.go'); \ else \ - echo "golangci-lint not found; running go vet"; \ - $(GO) vet ./...; \ - fi + $(GO) run $(GOIMPORTS_PKG) -w $$(find "$$tmp" -name '*.go'); \ + fi; \ + for file in $(ALL_GOFILES); do \ + diff -u "$$file" "$$tmp/$$file" || exit 1; \ + done + $(GO) vet ./... fix: - @gofmt -w $(GOFILES) - @if command -v goimports >/dev/null 2>&1; then \ - goimports -w $(GOFILES); \ + @gofmt -w $(ALL_GOFILES) + @if [ -n "$(GOIMPORTS)" ]; then \ + "$(GOIMPORTS)" -w $(ALL_GOFILES); \ else \ - $(GO) run golang.org/x/tools/cmd/goimports@latest -w $(GOFILES); \ + $(GO) run $(GOIMPORTS_PKG) -w $(ALL_GOFILES); \ fi test: @@ -65,12 +76,6 @@ gen: gen-docs: gen -check-docs: - @tmp=$$(mktemp -d); \ - trap 'rm -rf "$$tmp"' EXIT; \ - $(GO) run ./cmd/redmine-openapi-gen -spec "$(OPENAPI_SPEC)" -out "$$tmp/generated.go" -docs "$$tmp/usage.md"; \ - diff -u docs/usage.md "$$tmp/usage.md" - check-gen: @tmp=$$(mktemp -d); \ trap 'rm -rf "$$tmp"' EXIT; \ diff --git a/README.md b/README.md index 4896b9a..9b1e513 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,38 @@ A Redmine CLI tool bringing Redmine to your command line. Built based on https://d-yoshi.github.io/redmine-openapi/ ([repo](https://github.com/d-yoshi/redmine-openapi)). +## Install + +### Homebrew + +```bash +brew install muxx/tap/redmine-cli +redmine --version +``` + +The Homebrew formula is named `redmine-cli`; it installs the `redmine` executable. + +### GitHub release archive + +Download the archive for your OS and architecture from the [latest release](https://github.com/muxx/redmine-cli/releases/latest). + +Archive names use this format: + +```text +redmine___.tar.gz +redmine__windows_.zip +``` + +Example for macOS Apple Silicon: + +```bash +VERSION=v0.2.0 +curl -LO "https://github.com/muxx/redmine-cli/releases/download/${VERSION}/redmine_${VERSION}_darwin_arm64.tar.gz" +tar -xzf "redmine_${VERSION}_darwin_arm64.tar.gz" +sudo install -m 0755 "redmine_${VERSION}_darwin_arm64/redmine" /usr/local/bin/redmine +redmine --version +``` + ## Usage ```bash diff --git a/internal/redmine/client.go b/internal/redmine/client.go index 9bf70cf..528e587 100644 --- a/internal/redmine/client.go +++ b/internal/redmine/client.go @@ -64,7 +64,8 @@ func NewHTTPClient(timeout time.Duration, insecure bool) *http.Client { } transport := http.DefaultTransport.(*http.Transport).Clone() if insecure { - transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec + // Controlled by the explicit --insecure CLI flag. + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} } return &http.Client{Timeout: timeout, Transport: transport} }