Repository navigation
Expand file tree
/
Copy pathMakefile
More file actions
144 lines (127 loc) · 6.72 KB
/
Copy pathMakefile
File metadata and controls
144 lines (127 loc) · 6.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# scc — build, test, and npm release tasks.
#
# Day-to-day:
# make build # local binary -> ./scc
# make check # gofmt + vet + race tests (the CI gate)
#
# Release (manual — dispatches the release workflow; CI builds + publishes npm):
# make release VERSION=v0.1.0 # = gh workflow run release.yml --field version=v0.1.0
#
# Manual npm publish (bootstrap / fallback, when CI can't do it):
# make dist VERSION=v0.1.0 # cross-compile all 6 targets into dist/
# make npm-build VERSION=v0.1.0 # assemble npm/dist/ (8 packages) from those
# make npm-dry-run # validate every package without publishing
# make npm-publish [OTP=123456] # publish (skips already-published)
#
# Auth for a manual publish: either an Automation token
# npm config set //registry.npmjs.org/:_authToken <token>
# or pass a fresh 2FA code: make npm-publish OTP=123456
SHELL := bash
MODULE := github.com/protonspy/spec-claude-code
BIN := scc
VERSION ?= dev
LDFLAGS := -s -w -X $(MODULE)/internal/cli.version=$(VERSION)
DIST ?= dist
ARTIFACTS ?= $(DIST)
# GOOS/GOARCH targets shipped to npm — must match npm/scripts/build-packages.mjs
# and the release.yml build matrix.
PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64
.DEFAULT_GOAL := help
.PHONY: help
help: ## Show this help
@grep -hE '^[a-zA-Z0-9_-]+:.*## ' $(MAKEFILE_LIST) \
| awk 'BEGIN{FS=":.*## "}{printf " \033[36m%-14s\033[0m %s\n",$$1,$$2}'
.PHONY: build
build: ## Build the binary locally (-> ./scc); set VERSION to stamp it
go build -trimpath -ldflags '$(LDFLAGS)' -o $(BIN) ./cmd/$(BIN)
.PHONY: test
test: ## Run tests (race + coverage)
go test -race -coverprofile=coverage.out ./...
# test-report is the shape `scc check` asks a project for in its test gate: run the
# suite, then print one object saying how many tests ran and how much they covered.
#
# It is here because scc ships that contract and a contract with no worked example
# is one everybody implements differently. Nothing in scc runs this target — a
# workspace records whatever command it likes — but this is the four lines it takes
# in a Go project, and the four lines every other language needs a version of.
#
# The suite's own output goes to stderr so stdout carries the report and nothing
# else; the exit status is the suite's, because a runner that swallows a failure
# hides the thing the gate exists to catch.
.PHONY: test-report
test-report: ## Run the suite and print {"total": N, "coverage": P} for `scc check`
@set -o pipefail; \
go test -json -coverprofile=coverage.out ./... > .test.json 2>&1; status=$$?; \
total=$$(grep -c '"Action":"pass".*"Test":"' .test.json || true); \
cov=$$(go tool cover -func=coverage.out 2>/dev/null | awk '/^total:/{gsub("%","",$$3); print $$3}'); \
grep -E '"Action":"(fail|output)"' .test.json | sed -n 's/.*"Output":"\(.*\)"$$/\1/p' >&2 || true; \
rm -f .test.json; \
printf '{"total": %s, "coverage": %s}\n' "$${total:-0}" "$${cov:-0}"; \
exit $$status
.PHONY: fmt
fmt: ## Fail if any file is not gofmt-clean
@unformatted=$$(gofmt -l .); \
if [ -n "$$unformatted" ]; then echo "not gofmt-clean:"; echo "$$unformatted"; exit 1; fi; \
echo "gofmt clean"
.PHONY: vet
vet: ## go vet ./...
go vet ./...
.PHONY: check
check: fmt vet test ## Run the full CI gate (gofmt + vet + race tests)
# require-version fails fast when the release VERSION is left at the 'dev'
# default. Without it, dist/npm-build produce 'dev'-named tarballs (and an
# invalid npm semver), which only surfaces later as a cryptic `tar: Cannot open`
# when npm-build can't find a matching artifact.
.PHONY: require-version
require-version:
@case '$(VERSION)' in v*.*.*) : ;; *) echo "set a release VERSION like v0.1.1 (got '$(VERSION)'); e.g. make $(MAKECMDGOALS) VERSION=v0.1.1" >&2; exit 1 ;; esac
.PHONY: dist
dist: require-version ## Cross-compile every npm target into $(DIST)/ (set VERSION=vX.Y.Z)
@rm -rf '$(DIST)' && mkdir -p '$(DIST)'
@set -euo pipefail; for p in $(PLATFORMS); do \
goos=$${p%/*}; goarch=$${p#*/}; bin=$(BIN); [ "$$goos" = windows ] && bin=$(BIN).exe; \
echo ">> $$goos/$$goarch"; \
GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=0 go build -trimpath -ldflags '$(LDFLAGS)' -o '$(DIST)'/$$bin ./cmd/$(BIN); \
name=$(BIN)_$(VERSION)_$${goos}_$${goarch}; \
if [ "$$goos" = windows ]; then (cd '$(DIST)' && zip -q $$name.zip $$bin); \
else (cd '$(DIST)' && tar -czf $$name.tar.gz $$bin); fi; \
rm -f '$(DIST)'/$$bin; \
done; \
echo "artifacts in $(DIST)/"
.PHONY: npm-build
npm-build: require-version ## Assemble npm/dist/ from the artifacts (VERSION=vX.Y.Z; ARTIFACTS=dir; needs `make dist`)
node npm/scripts/build-packages.mjs '$(VERSION)' '$(ARTIFACTS)'
.PHONY: npm-dry-run
npm-dry-run: ## Dry-run publish every assembled package
@set -euo pipefail; \
if [ ! -f npm/dist/launchers/$(BIN)/package.json ]; then echo "npm/dist not assembled — run: make npm-build VERSION=vX.Y.Z first" >&2; exit 1; fi; \
for d in npm/dist/$(BIN)-*/ npm/dist/launchers/*/ npm/dist/launchers-optional/*/; do \
[ -f "$$d/package.json" ] || continue; \
echo "== $$d"; npm publish "$$d" --access public --dry-run; done
@echo "note: --dry-run does not exercise npm's name-similarity check; only a real publish does"
.PHONY: npm-publish
npm-publish: ## Publish the assembled packages, skips already-published; OTP=123456 if 2FA
@set -euo pipefail; \
if [ ! -f npm/dist/launchers/$(BIN)/package.json ]; then echo "npm/dist not assembled — run: make dist VERSION=vX.Y.Z && make npm-build VERSION=vX.Y.Z" >&2; exit 1; fi; \
otp=; if [ -n "$(OTP)" ]; then otp="--otp=$(OTP)"; fi; \
for d in npm/dist/$(BIN)-*/ npm/dist/launchers/*/ npm/dist/launchers-optional/*/; do \
[ -f "$$d/package.json" ] || continue; \
name=$$(cd "$$d" && node -p "require('./package.json').name"); \
ver=$$(cd "$$d" && node -p "require('./package.json').version"); \
if npm view "$$name@$$ver" version >/dev/null 2>&1; then \
echo "skip $$name@$$ver (already published)"; continue; fi; \
echo ">> publishing $$name@$$ver"; \
case "$$d" in \
npm/dist/launchers-optional/*) \
npm publish "$$d" --access public $$otp \
|| echo "!! optional launcher $$name was not published; the release stands on the required ones" ;; \
*) npm publish "$$d" --access public $$otp ;; \
esac; \
done
.PHONY: release
release: require-version ## Dispatch the manual release workflow (CI builds binaries + publishes npm) for VERSION
gh workflow run release.yml --field version='$(VERSION)'
@echo "dispatched release $(VERSION) — track it with: gh run list --workflow=release.yml"
.PHONY: clean
clean: ## Remove build artifacts (dist/, npm/dist/, ./scc, coverage)
rm -rf '$(DIST)' npm/dist $(BIN) $(BIN).exe coverage.out