Files
2026-07-31 09:18:32 +03:00

149 lines
7.0 KiB
Makefile

GO ?= go
BIN_DIR ?= bin
BASH ?= bash
CMD := ./cmd/bkt
MACOS_CODESIGN_ID ?= io.github.avivsinai.bitbucket-cli
ifeq ($(OS),Windows_NT)
# The Windows recipes below use cmd.exe syntax, but GNU Make prefers sh.exe
# from PATH (e.g. Git for Windows) when present — pin the shell explicitly.
SHELL := cmd.exe
.SHELLFLAGS := /C
BIN_EXT := .exe
NULL_DEVICE := NUL
BUILD_DATE_VALUE := $(shell powershell -NoProfile -ExecutionPolicy Bypass -Command "(Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')")
MKDIR_P = if not exist "$(subst /,\,$(1))" mkdir "$(subst /,\,$(1))"
RM_RF = if exist "$(subst /,\,$(1))" rmdir /s /q "$(subst /,\,$(1))"
else
BIN_EXT :=
NULL_DEVICE := /dev/null
BUILD_DATE_VALUE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
MKDIR_P = mkdir -p "$(1)"
RM_RF = rm -rf "$(1)"
endif
BIN := $(BIN_DIR)/bkt$(BIN_EXT)
# Tracked and untracked (non-ignored) Go sources; $(wildcard) drops entries
# for files that are tracked but deleted from the working tree.
SOURCES := $(wildcard $(shell git ls-files --cached --others --exclude-standard -- "cmd/*.go" "internal/*.go" "pkg/*.go" 2>$(NULL_DEVICE)))
GIT_TAG := $(shell git describe --tags --exact-match 2>$(NULL_DEVICE))
GIT_COMMIT_SHORT := $(shell git rev-parse --short HEAD 2>$(NULL_DEVICE))
GIT_COMMIT := $(shell git rev-parse HEAD 2>$(NULL_DEVICE))
GIT_DIRTY := $(shell git diff-index --quiet HEAD -- 2>$(NULL_DEVICE) || echo dirty)
VERSION ?= $(if $(GIT_TAG),$(GIT_TAG),dev-$(if $(GIT_COMMIT_SHORT),$(GIT_COMMIT_SHORT),unknown)$(if $(GIT_DIRTY),-dirty,))
COMMIT ?= $(if $(GIT_COMMIT),$(GIT_COMMIT),unknown)
BUILD_DATE ?= $(if $(BUILD_DATE_VALUE),$(BUILD_DATE_VALUE),unknown)
BKT_OAUTH_CLIENT_ID ?=
BKT_OAUTH_CLIENT_SECRET ?=
LDFLAGS := -s -w \
-X github.com/avivsinai/bitbucket-cli/internal/build.versionFromLdflags=$(VERSION) \
-X github.com/avivsinai/bitbucket-cli/internal/build.commitFromLdflags=$(COMMIT) \
-X github.com/avivsinai/bitbucket-cli/internal/build.dateFromLdflags=$(BUILD_DATE) \
-X github.com/avivsinai/bitbucket-cli/pkg/oauth.cloudClientID=$(BKT_OAUTH_CLIENT_ID) \
-X github.com/avivsinai/bitbucket-cli/pkg/oauth.cloudClientSecret=$(BKT_OAUTH_CLIENT_SECRET)
.PHONY: build fmt lint test tidy sbom release snapshot clean check-skills sync-skills check-generated-skill release-local generate-skill nix-update-vendor-hash require-bash
build: $(BIN)
# Skill integrity: skills/ is canonical; .claude/skills/ and .agents/skills/ are mirrors.
check-skills:
ifeq ($(OS),Windows_NT)
@powershell -NoProfile -ExecutionPolicy Bypass -Command "$$ErrorActionPreference='Stop'; Write-Host 'Checking skill mirrors...'; foreach ($$mirror in @('.claude/skills/bkt','.agents/skills/bkt')) { $$item=Get-Item -LiteralPath $$mirror -Force; if (-not $$item.PSIsContainer -or (($$item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) { Write-Error ($$mirror + ' is not a regular directory'); exit 1 }; git diff --no-index --quiet -- skills/bkt $$mirror; if ($$LASTEXITCODE -ne 0) { Write-Error ($$mirror + ' content mismatch; run make sync-skills'); exit 1 } }; Write-Host 'Skill mirrors valid'"
else
@echo "Checking skill mirrors..."
@for mirror in .claude/skills/bkt .agents/skills/bkt; do \
test -d "$$mirror" && test ! -L "$$mirror" || { echo "ERROR: $$mirror is not a regular directory"; exit 1; }; \
git diff --no-index --quiet -- skills/bkt "$$mirror" || { echo "ERROR: $$mirror content mismatch; run make sync-skills"; exit 1; }; \
done
@echo "Skill mirrors valid"
endif
sync-skills:
ifeq ($(OS),Windows_NT)
@powershell -NoProfile -ExecutionPolicy Bypass -Command "$$ErrorActionPreference='Stop'; if (-not (Test-Path -LiteralPath skills/bkt -PathType Container)) { Write-Error 'skills/bkt is not a directory'; exit 1 }; foreach ($$mirror in @('.claude/skills/bkt','.agents/skills/bkt')) { if (Test-Path -LiteralPath $$mirror) { Remove-Item -LiteralPath $$mirror -Recurse -Force }; Copy-Item -LiteralPath skills/bkt -Destination $$mirror -Recurse }; Write-Host 'Skill mirrors synced'"
else
@test -d skills/bkt || { echo "ERROR: skills/bkt is not a directory"; exit 1; }
@for mirror in .claude/skills/bkt .agents/skills/bkt; do \
rm -rf "$$mirror"; \
cp -R skills/bkt "$$mirror"; \
done
@echo "Skill mirrors synced"
endif
$(BIN): $(SOURCES) go.mod go.sum
@$(call MKDIR_P,$(BIN_DIR))
$(GO) build -trimpath -ldflags "$(LDFLAGS)" -o "$(BIN)" $(CMD)
ifneq ($(OS),Windows_NT)
./scripts/codesign-macos.sh "$(BIN)" "$(MACOS_CODESIGN_ID)"
endif
fmt:
$(GO) fmt ./...
lint:
golangci-lint run
test:
$(GO) test ./...
tidy:
$(GO) mod tidy
sbom:
ifeq ($(OS),Windows_NT)
@powershell -NoProfile -ExecutionPolicy Bypass -Command "if (-not (Get-Command syft -ErrorAction SilentlyContinue)) { Write-Error 'syft not installed; install from https://github.com/anchore/syft'; exit 1 }"
else
@if ! command -v syft >/dev/null 2>&1; then \
echo "syft not installed; install from https://github.com/anchore/syft" >&2; \
exit 1; \
fi
endif
syft dir:. -o cyclonedx-json=sbom.cdx.json
release-local:
goreleaser release --clean
snapshot:
ifeq ($(OS),Windows_NT)
@powershell -NoProfile -ExecutionPolicy Bypass -Command "if (-not (Get-Command goreleaser -ErrorAction SilentlyContinue)) { Write-Error 'goreleaser not installed. Install from https://goreleaser.com/install/'; exit 1 }"
else
@command -v goreleaser >/dev/null 2>&1 || { echo "goreleaser not installed. Run: brew install goreleaser"; exit 1; }
endif
goreleaser release --snapshot --clean --skip=publish
clean:
@$(call RM_RF,$(BIN_DIR))
@$(call RM_RF,dist)
generate-skill:
$(GO) run ./cmd/docgen -o skills/bkt/rules
$(MAKE) sync-skills
check-generated-skill: require-bash
ifeq ($(OS),Windows_NT)
@set "GO=$(GO)" && "$(BASH)" ./scripts/check-generated-skill.sh
else
@GO="$(GO)" "$(BASH)" ./scripts/check-generated-skill.sh
endif
nix-update-vendor-hash: require-bash
"$(BASH)" ./scripts/update-nix-vendor-hash.sh
release: require-bash
ifeq ($(OS),Windows_NT)
@if "$(RELEASE_VERSION)"=="" (echo usage: mingw32-make release RELEASE_VERSION=X.Y.Z [RELEASE_DATE=YYYY-MM-DD] [RELEASE_SKIP_VERIFY=1] [RELEASE_ALLOW_EMPTY=1] [RELEASE_NO_AUTO_MERGE=1] & exit /b 1)
"$(BASH)" ./scripts/release.sh "$(RELEASE_VERSION)" $(if $(RELEASE_DATE),--date $(RELEASE_DATE),) $(if $(RELEASE_SKIP_VERIFY),--skip-verify,) $(if $(RELEASE_ALLOW_EMPTY),--allow-empty,) $(if $(RELEASE_NO_AUTO_MERGE),--no-auto-merge,)
else
@test -n "$(RELEASE_VERSION)" || (echo "usage: make release RELEASE_VERSION=X.Y.Z [RELEASE_DATE=YYYY-MM-DD] [RELEASE_SKIP_VERIFY=1] [RELEASE_ALLOW_EMPTY=1] [RELEASE_NO_AUTO_MERGE=1]" && exit 1)
"$(BASH)" ./scripts/release.sh "$(RELEASE_VERSION)" $(if $(RELEASE_DATE),--date $(RELEASE_DATE),) $(if $(RELEASE_SKIP_VERIFY),--skip-verify,) $(if $(RELEASE_ALLOW_EMPTY),--allow-empty,) $(if $(RELEASE_NO_AUTO_MERGE),--no-auto-merge,)
endif
require-bash:
ifeq ($(OS),Windows_NT)
@powershell -NoProfile -ExecutionPolicy Bypass -Command "if (-not (Get-Command '$(BASH)' -ErrorAction SilentlyContinue)) { Write-Error '$(BASH) not installed; required for this target'; exit 1 }"
else
@command -v "$(BASH)" >/dev/null 2>&1 || { echo "$(BASH) not installed; required for this target" >&2; exit 1; }
endif