Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
128 changes: 128 additions & 0 deletions .github/scripts/update-homebrew-formula.sh
Original file line number Diff line number Diff line change
@@ -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}" <<EOF
class ${FORMULA_CLASS} < Formula
desc "Redmine CLI generated from the Redmine OpenAPI specification"
homepage "https://github.com/${REPOSITORY}"
version "${version_number}"
license "MIT"

on_macos do
on_arm do
url "$(download_url "${darwin_arm64_archive}")"
sha256 "${darwin_arm64_sha}"
end

on_intel do
url "$(download_url "${darwin_amd64_archive}")"
sha256 "${darwin_amd64_sha}"
end
end

on_linux do
on_arm do
url "$(download_url "${linux_arm64_archive}")"
sha256 "${linux_arm64_sha}"
end

on_intel do
url "$(download_url "${linux_amd64_archive}")"
sha256 "${linux_amd64_sha}"
end
end

def install
bin.install "redmine"
end

test do
assert_match version.to_s, shell_output("#{bin}/redmine --version")
end
end
EOF

if [[ -n "${HOMEBREW_TAP_DIR:-}" ]]; then
echo "Formula written to ${tap_dir}/${FORMULA_PATH}"
exit 0
fi

cd "${tap_dir}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "${FORMULA_PATH}"
if git diff --cached --quiet; then
echo "Homebrew formula is already current"
exit 0
fi

git commit -m "Update ${FORMULA_NAME} to ${VERSION}"
git push
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,10 @@ jobs:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ github.ref_name }}
run: gh release create "$VERSION" dist/* --title "$VERSION" --generate-notes

- name: Publish Homebrew formula
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
VERSION: ${{ github.ref_name }}
HOMEBREW_TAP_REPOSITORY: muxx/homebrew-tap
run: .github/scripts/update-homebrew-formula.sh
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@

* Cover implementation changes with tests
* Add and generate documentation when adding or changing functionality
* At the end of implementation, run `make fix test gen-docs`
* At the end of implementation, run `make fix gen-docs check`

## Running Individual Checks

```bash
make build # compile
make lint # golangci-lint
make fix # auto-fix lint issues (gofmt + goimports)
make lint # verify all Go files with gofmt/goimports and run go vet
make fix # auto-fix all Go files formatting and imports
make test # all unit tests
make gen # regenerate OpenAPI command metadata and docs
make gen-docs # regenerate docs
make check # lint, test, and generated-file checks
make check-docs # verify generated docs are current
make check-gen # verify generated files are current
```

## Documentation conventions
Expand Down
45 changes: 25 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ BINARY ?= redmine
BIN_DIR ?= bin
VERSION ?= $(shell git describe --tags --always --dirty 2>/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"

Expand All @@ -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:
Expand All @@ -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; \
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_<version>_<os>_<arch>.tar.gz
redmine_<version>_windows_<arch>.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
Expand Down
3 changes: 2 additions & 1 deletion internal/redmine/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
Expand Down