chore: bump version to 4.5.0

Bug fixes:
- Fix inverted pager detection logic (returned error instead of path)
- Fix repo.Clone ignoring destination directory parameter
- Fix sheet loading using append on pre-sized slices
- Clean up partial files on copy failure
- Trim whitespace from editor config

Security:
- Add path traversal protection for cheatsheet names

Performance:
- Move regex compilation outside search loop
- Replace string concatenation with strings.Join in search

Build:
- Remove go:generate; embed config and usage as string literals
- Parallelize release builds
- Add fuzz testing infrastructure

Testing:
- Improve test coverage from 38.9% to 50.2%
- Add fuzz tests for search, filter, tags, and validation

Documentation:
- Fix inaccurate code examples in HACKING.md
- Add missing --conf and --all options to man page
- Add ADRs for path traversal, env parsing, and search parallelization
- Update CONTRIBUTING.md to reflect project policy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Allen Lane
2026-02-14 19:56:19 -05:00
parent 7908a678df
commit cc85a4bdb1
69 changed files with 4802 additions and 577 deletions

113
Makefile
View File

@@ -3,6 +3,9 @@ makefile := $(realpath $(lastword $(MAKEFILE_LIST)))
cmd_dir := ./cmd/cheat
dist_dir := ./dist
# parallel jobs for build-release (can be overridden)
JOBS ?= 8
# executables
CAT := cat
COLUMN := column
@@ -31,6 +34,7 @@ TMPDIR := /tmp
# release binaries
releases := \
$(dist_dir)/cheat-darwin-amd64 \
$(dist_dir)/cheat-darwin-arm64 \
$(dist_dir)/cheat-linux-386 \
$(dist_dir)/cheat-linux-amd64 \
$(dist_dir)/cheat-linux-arm5 \
@@ -44,70 +48,78 @@ releases := \
## build: build an executable for your architecture
.PHONY: build
build: | clean $(dist_dir) generate fmt lint vet vendor man
build: | clean $(dist_dir) fmt lint vet vendor man
$(GO) build $(BUILD_FLAGS) -o $(dist_dir)/cheat $(cmd_dir)
## build-release: build release executables
# Runs prepare once, then builds all binaries in parallel
# Override jobs with: make build-release JOBS=16
.PHONY: build-release
build-release: $(releases)
build-release: prepare
$(MAKE) -j$(JOBS) $(releases)
# cheat-darwin-amd64
$(dist_dir)/cheat-darwin-amd64: prepare
$(dist_dir)/cheat-darwin-amd64:
GOARCH=amd64 GOOS=darwin \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-darwin-arm64
$(dist_dir)/cheat-darwin-arm64:
GOARCH=arm64 GOOS=darwin \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-386
$(dist_dir)/cheat-linux-386: prepare
$(dist_dir)/cheat-linux-386:
GOARCH=386 GOOS=linux \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-amd64
$(dist_dir)/cheat-linux-amd64: prepare
$(dist_dir)/cheat-linux-amd64:
GOARCH=amd64 GOOS=linux \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm5
$(dist_dir)/cheat-linux-arm5: prepare
$(dist_dir)/cheat-linux-arm5:
GOARCH=arm GOOS=linux GOARM=5 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm6
$(dist_dir)/cheat-linux-arm6: prepare
$(dist_dir)/cheat-linux-arm6:
GOARCH=arm GOOS=linux GOARM=6 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm7
$(dist_dir)/cheat-linux-arm7: prepare
$(dist_dir)/cheat-linux-arm7:
GOARCH=arm GOOS=linux GOARM=7 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-linux-arm64
$(dist_dir)/cheat-linux-arm64: prepare
$(dist_dir)/cheat-linux-arm64:
GOARCH=arm64 GOOS=linux \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-netbsd-amd64
$(dist_dir)/cheat-netbsd-amd64: prepare
$(dist_dir)/cheat-netbsd-amd64:
GOARCH=amd64 GOOS=netbsd \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-openbsd-amd64
$(dist_dir)/cheat-openbsd-amd64: prepare
$(dist_dir)/cheat-openbsd-amd64:
GOARCH=amd64 GOOS=openbsd \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-plan9-amd64
$(dist_dir)/cheat-plan9-amd64: prepare
$(dist_dir)/cheat-plan9-amd64:
GOARCH=amd64 GOOS=plan9 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-solaris-amd64
$(dist_dir)/cheat-solaris-amd64: prepare
$(dist_dir)/cheat-solaris-amd64:
GOARCH=amd64 GOOS=solaris \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-windows-amd64
$(dist_dir)/cheat-windows-amd64.exe: prepare
$(dist_dir)/cheat-windows-amd64.exe:
GOARCH=amd64 GOOS=windows \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(ZIP) $@.zip $@ -j
@@ -115,9 +127,9 @@ $(dist_dir)/cheat-windows-amd64.exe: prepare
$(dist_dir):
$(MKDIR) $(dist_dir)
.PHONY: generate
generate:
$(GO) generate $(cmd_dir)
# .tmp
.tmp:
$(MKDIR) .tmp
## install: build and install cheat on your PATH
.PHONY: install
@@ -127,7 +139,8 @@ install: build
## clean: remove compiled executables
.PHONY: clean
clean:
$(RM) -f $(dist_dir)/* $(cmd_dir)/str_config.go $(cmd_dir)/str_usage.go
$(RM) -f $(dist_dir)/*
$(RM) -rf .tmp
## distclean: remove the tags file
.PHONY: distclean
@@ -138,7 +151,8 @@ distclean:
## setup: install revive (linter) and scc (sloc tool)
.PHONY: setup
setup:
GO111MODULE=off $(GO) get -u github.com/boyter/scc github.com/mgechev/revive
$(GO) install github.com/boyter/scc@latest
$(GO) install github.com/mgechev/revive@latest
## sloc: count "semantic lines of code"
.PHONY: sloc
@@ -162,6 +176,7 @@ vendor:
$(GO) mod vendor && $(GO) mod tidy && $(GO) mod verify
## vendor-update: update vendored dependencies
.PHONY: vendor-update
vendor-update:
$(GO) get -t -u ./... && $(GO) mod vendor && $(GO) mod tidy && $(GO) mod verify
@@ -185,18 +200,70 @@ vet:
test:
$(GO) test ./...
## test-integration: run integration tests (requires network)
.PHONY: test-integration
test-integration:
$(GO) test -tags=integration -count=1 ./...
## test-all: run all tests (unit and integration)
.PHONY: test-all
test-all: test test-integration
## test-fuzz: run quick fuzz tests for security-critical functions
.PHONY: test-fuzz
test-fuzz:
@./build/fuzz.sh 15s
## test-fuzz-long: run extended fuzz tests (10 minutes each)
.PHONY: test-fuzz-long
test-fuzz-long:
@./build/fuzz.sh 10m
## coverage: generate a test coverage report
.PHONY: coverage
coverage:
$(GO) test ./... -coverprofile=$(TMPDIR)/cheat-coverage.out && \
$(GO) tool cover -html=$(TMPDIR)/cheat-coverage.out
coverage: .tmp
$(GO) test ./... -coverprofile=.tmp/cheat-coverage.out && \
$(GO) tool cover -html=.tmp/cheat-coverage.out -o .tmp/cheat-coverage.html && \
echo "Coverage report generated: .tmp/cheat-coverage.html" && \
(sensible-browser .tmp/cheat-coverage.html 2>/dev/null || \
xdg-open .tmp/cheat-coverage.html 2>/dev/null || \
open .tmp/cheat-coverage.html 2>/dev/null || \
echo "Please open .tmp/cheat-coverage.html in your browser")
## coverage-text: show test coverage by function in terminal
.PHONY: coverage-text
coverage-text: .tmp
$(GO) test ./... -coverprofile=.tmp/cheat-coverage.out && \
$(GO) tool cover -func=.tmp/cheat-coverage.out | $(SORT) -k3 -n
## benchmark: run performance benchmarks
.PHONY: benchmark
benchmark: .tmp
$(GO) test -tags=integration -bench=. -benchtime=10s -benchmem ./cmd/cheat | tee .tmp/benchmark-latest.txt && \
$(RM) -f cheat.test
## benchmark-cpu: run benchmarks with CPU profiling
.PHONY: benchmark-cpu
benchmark-cpu: .tmp
$(GO) test -tags=integration -bench=. -benchtime=10s -cpuprofile=.tmp/cpu.prof ./cmd/cheat && \
$(RM) -f cheat.test && \
echo "CPU profile saved to .tmp/cpu.prof" && \
echo "View with: go tool pprof -http=:8080 .tmp/cpu.prof"
## benchmark-mem: run benchmarks with memory profiling
.PHONY: benchmark-mem
benchmark-mem: .tmp
$(GO) test -tags=integration -bench=. -benchtime=10s -benchmem -memprofile=.tmp/mem.prof ./cmd/cheat && \
$(RM) -f cheat.test && \
echo "Memory profile saved to .tmp/mem.prof" && \
echo "View with: go tool pprof -http=:8080 .tmp/mem.prof"
## check: format, lint, vet, vendor, and run unit-tests
.PHONY: check
check: | vendor fmt lint vet test
.PHONY: prepare
prepare: | clean $(dist_dir) generate vendor fmt lint vet test
prepare: | clean $(dist_dir) vendor fmt lint vet test
## docker-setup: create a docker image for use during development
.PHONY: docker-setup