-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathjustfile
More file actions
274 lines (234 loc) · 11.8 KB
/
Copy pathjustfile
File metadata and controls
274 lines (234 loc) · 11.8 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
commit := `git rev-parse --short HEAD 2>/dev/null || echo unknown`
ldflags := "-s -w -X github.com/sciminds/sci/internal/version.Commit=" + commit
set default-list := true
build:
go build -ldflags="{{ldflags}}" -o sci ./cmd/sci
# Type-check the build-tagged files the host platform never compiles
# (doctor_linux.go, tools_linux.go, …). CI cross-compiles for release; this
# is the local equivalent, so a _linux.go edit can't silently break the build.
build-linux:
GOOS=linux GOARCH=amd64 go build -o /dev/null ./cmd/sci
# A symlink, not a copy, is what makes every rebuild land instantly:
# `go build` writes a fresh inode each time, so macOS never sees a
# mutated ad-hoc signature (the exit-137 SIGKILL that forces zen's
# install-to-temp-and-rename dance).
#
# `sci update` un-installs this, by design and harmlessly: os.Executable
# returns the SYMLINK path on darwin, so a release binary replaces
# ~/.local/bin/sci itself and the repo build is left alone. Re-run
# `just install` to get back into dev mode.
#
# Dev mode: symlink the repo build onto PATH (so `just build` IS the installed sci)
install: build
mkdir -p ~/.local/bin
ln -sf "{{justfile_directory()}}/sci" ~/.local/bin/sci
@~/.local/bin/sci >/dev/null
@echo "sci -> $(readlink ~/.local/bin/sci) ({{commit}})"
tidy:
go mod tidy
# Install Go dev tools pinned in go.mod's `tool` block (goimports,
# golangci-lint, gopls, gofumpt, dlv) into $GOBIN, and the commit-msg hook
# enforcing the commit convention (CLAUDE.md). Run once after cloning.
bootstrap:
go install tool
ln -sf ../../scripts/commit-msg .git/hooks/commit-msg
fmt:
# Toolchain gofmt, not PATH gofmt: the homebrew copy can predate go.mod's
# toolchain and choke on newer syntax (same pin in check-ci below).
"$(go env GOROOT)/bin/gofmt" -w .
go tool goimports -w .
lint:
go tool golangci-lint run ./internal/... ./cmd/...
# Structural style rules enforced via ast-grep.
# No lipgloss.NewStyle() outside internal/uikit/; no hardcoded lipgloss.Color() outside palette/style files;
# no manual m.width/m.height literal arithmetic outside uikit. `ast-grep test` validates the rules' own fixtures.
lint-style:
ast-grep test
ast-grep scan
semgrep --config .semgrep/ --error --quiet ./internal/ ./cmd/
# Project-specific guards: import boundaries, flag conventions, API usage rules.
lint-guard:
./scripts/lint-guard.sh
# Check interactive ↔ non-interactive parity (alias for lint-guard).
scriptable: lint-guard
vet:
go vet ./internal/... ./cmd/...
test:
go test ./... -count=1
# Gate test pass — `-short` skips the cloud/network-gated command tests
# (`sci cloud` / `sci lab`, marked with testing.Short()) so a flaky network
# can't stall `just ok`. Run `just test` / `just test-cloud` for the full set.
test-gate:
go test ./... -short -count=1
# Race-detector pass. ~2x slower than `test` (re-instruments the whole tree),
# so it's CI-only (check-ci runs it on every push) and NOT on the local `just
# ok` gate. Run it by hand before merging concurrency-sensitive changes.
test-race:
go test ./... -race -count=1
# The cloud/network-gated command tests the gate skips via -short. Run before
# merging changes to `sci cloud` / `sci lab`. (CI runs the full suite, so these
# are covered there regardless.)
test-cloud:
go test ./cmd/sci -run 'TestCloud|TestLab' -count=1
# Run tests for a single package (fast TDD iteration). `just test-pkg ./internal/zot`
test-pkg PKG *ARGS:
go test {{PKG}} -count=1 {{ARGS}}
# Integration tests needing external tools (pixi, uv, quarto, marimo, typst, node)
test-slow *ARGS:
SLOW=1 go test ./internal/proj/new -v -timeout 10m -count=1 {{ARGS}}
# Canvas + GitHub Classroom integration tests (requires CANVAS_TOKEN in .env and gh auth)
test-canvas:
SLOW=1 CANVAS_TEST_TOKEN=$CANVAS_TOKEN CANVAS_TEST_URL="https://canvas.ucsd.edu/courses/63653" GH_CLASSROOM_TEST_URL="https://classroom.github.com/classrooms/232475786-test-classroom" go test ./internal/cass/ -run Integration -v -timeout 2m -count=1
# Real-Zotero-DB smoke (opt-in; reads ./zotero.sqlite or $ZOT_REAL_DB)
test-zot-real:
SLOW=1 go test ./internal/local/ ./internal/zot/hygiene/ -v -count=1
test-all: test test-slow
# Local pre-commit gate. No -race here (CI's check-ci covers it) so the loop
# stays fast; see test-race. Run `just ok`, not `check`, day to day.
check: tidy fmt vet lint lint-style lint-docs lint-guard test-gate build
# CI gate — verify-only (no file writes), no multi-arch build, no lint-style.
# Mirrors `check` so the local and CI gates can't drift: add a step here and
# .github/workflows/release.yml picks it up. Skips lint-style because semgrep
# is a Python toolchain we don't want to install on the runner; lint-guard
# stays, and the workflow already brews the rg/fd/jaq/ast-grep it shells out to.
# One intentional divergence: CI runs the FULL test suite (no -short) since it
# has network, so the cloud/lab command tests the local gate skips stay covered.
check-ci:
#!/usr/bin/env bash
set -euo pipefail
echo "==> tidy (verify go.mod / go.sum committed)"
go mod tidy
git diff --exit-code go.mod go.sum
echo "==> fmt (verify gofmt + goimports clean)"
out=$("$(go env GOROOT)/bin/gofmt" -l .); if [ -n "$out" ]; then echo "gofmt drift:"; echo "$out"; exit 1; fi
out=$(go tool goimports -l .); if [ -n "$out" ]; then echo "goimports drift:"; echo "$out"; exit 1; fi
echo "==> diff (verify conflict markers + whitespace clean)"
base=HEAD^
for ref in main origin/main; do
candidate=$(git merge-base "$ref" HEAD 2>/dev/null || true)
if [ -n "$candidate" ] && [ "$candidate" != "$(git rev-parse HEAD)" ]; then base=$candidate; break; fi
done
git diff --check "$base"
# Only the < and > markers, never `=======`: a 7-character setext heading
# underline in a .md file is indistinguishable from a conflict separator,
# and no real conflict lacks the angle-bracket markers anyway.
if git grep -nE '^(<{7}( |$)|>{7}( |$))'; then
echo "leftover conflict markers found in tracked files"
exit 1
fi
echo "==> vet"
go vet ./internal/... ./cmd/...
echo "==> lint"
go tool golangci-lint run ./internal/... ./cmd/...
echo "==> lint-docs"
go tool golangci-lint run --config .golangci-docs.yml ./internal/...
echo "==> lint-guard"
./scripts/lint-guard.sh
echo "==> test -race"
go test ./... -race -count=1
ok: check
@echo "All checks passed."
# Like `ok` but skips lint-style (semgrep/ast-grep). Use when semgrep
# rules are temporarily broken or being updated.
ok-legacy: tidy fmt vet lint lint-docs lint-guard test build
@echo "All checks (no semgrep) passed."
# Like `ok` but also runs proj/new integration tests (SLOW=1).
# Requires pixi, uv, quarto, marimo, typst, node on PATH.
# Does NOT run test-canvas / test-zot-real — those need
# credentials or live infra and stay opt-in.
ok-slow: tidy fmt vet lint lint-style lint-docs lint-guard test test-slow build
@echo "All checks (incl. slow) passed."
clean:
rm -f sci
# Regenerate internal/uikit/REFERENCE.md from godoc comments.
docs-uikit:
go run ./internal/uikit/cmd/gen-reference ./internal/uikit ./internal/uikit/REFERENCE.md
# Render embedded asciicasts to GIFs under docs/casts/ using `agg`.
# Searches both internal/help/casts/ (sci command demos) and
# internal/learn/casts/ (general terminal/git/python tutorials).
# Pass a filename stem (or glob) to limit the set:
# just casts-gif # all casts in both dirs
# just casts-gif zot-doctor # just one
# just casts-gif 'zot-*' # all zot casts (quote to protect from shell)
casts-gif FILTER='*':
#!/usr/bin/env bash
set -euo pipefail
command -v agg >/dev/null || { echo "agg not found on PATH — install from https://github.com/asciinema/agg"; exit 1; }
mkdir -p docs/casts
shopt -s nullglob
casts=()
for glob in internal/help/casts/{{FILTER}}.cast internal/learn/casts/{{FILTER}}.cast; do
[[ -f "$glob" ]] && casts+=("$glob")
done
if [[ ${#casts[@]} -eq 0 ]]; then
# Fall back to shell glob expansion for wildcard filters.
for c in internal/help/casts/{{FILTER}}.cast internal/learn/casts/{{FILTER}}.cast; do
casts+=("$c")
done
fi
if [[ ${#casts[@]} -eq 0 ]]; then
echo "no casts matched '{{FILTER}}'"
exit 1
fi
for cast in "${casts[@]}"; do
name=$(basename "$cast" .cast)
out="docs/casts/$name.gif"
echo " → $out"
agg --theme github-dark --font-size 14 --speed 1.2 --idle-time-limit 1 "$cast" "$out"
done
echo "rendered ${#casts[@]} gif(s) to docs/casts/"
# Check Go doc comments (package-level + exported symbols) via revive.
# Part of the `check`/`ok` gate; also runnable standalone for doc-audit sessions.
lint-docs:
go tool golangci-lint run --config .golangci-docs.yml ./internal/...
# Report gaps in user-facing CLI documentation (casts, gifs, README embeds, help descriptions).
doc-coverage:
./scripts/doc-coverage.sh
# Preview the release notes the next [release] commit will publish —
# everything since the last CalVer tag, grouped per cliff.toml. Tags live on
# the remote (CI mints them), so fetch first or the preview spans all history.
changelog *ARGS:
@command -v git-cliff >/dev/null || { echo "git-cliff not found — brew install git-cliff"; exit 1; }
@git fetch --tags --quiet 2>/dev/null || true
git-cliff --unreleased --strip all {{ARGS}}
# Validate commit subjects against the commit convention (CLAUDE.md).
# Default range = unpushed work; CI passes the pushed/PR range explicitly.
lint-commits RANGE="origin/main..HEAD":
./scripts/lint-commits.sh {{RANGE}}
# ─── Modernization via `go fix` (Go 1.26) ──────────────────────────────────
# Go 1.26 rewrote `go fix` into a suite of modernizers (the same ones gopls
# runs). We apply them in reviewable stages and commit each separately. Append
# `-diff` to any recipe to preview without writing, e.g. `just modernize-safe -diff`.
# Not part of the `ok` gate — run manually when bumping the Go toolchain.
# Excluded everywhere: omitzero (possible behavior change — handle by hand).
# Fixes touching generated files (e.g. zotero.gen.go) are skipped automatically;
# modernize those via the generator/template, not the file.
# Preview every auto-appliable safe fix (stages 1+2), writes nothing.
modernize-diff *ARGS:
go fix -diff -omitzero=false -newexpr=false {{ARGS}} ./...
# Stage 1 — replace interface{} with `any` in hand-written files. Optionally
# commit alone; here it's a single site so it folds into `modernize-safe`.
modernize-any *ARGS:
go fix -any {{ARGS}} ./...
# Stage 2 — remaining safe modernizers: minmax, rangeint, slices/strings
# helpers, forvar, waitgroup, … (excludes any, omitzero, and newexpr).
# Re-run until `just modernize-diff` is empty: synergistic fixes (e.g. an
# if-clamp left beside a fresh min() becomes a nested max(min(…))) surface
# only on a second pass. Pass a single analyzer to scope, e.g. `-minmax`.
modernize-safe *ARGS:
go fix -any=false -omitzero=false -newexpr=false {{ARGS}} ./...
# Stage 3 — newexpr: rewrite ptr(x) helpers + calls to Go 1.26's new(x).
# Leaves the now-unused helper funcs as dead code; delete them by hand, then
# `just ok`. Preview first with `just modernize-deadcode -diff`.
modernize-deadcode *ARGS:
go fix -newexpr {{ARGS}} ./...
run *ARGS:
go run ./cmd/sci {{ARGS}}
# Open package documentation in the browser
docs:
@echo "Starting pkgsite at http://localhost:6060/github.com/sciminds/sci"
open "http://localhost:6060/github.com/sciminds/sci"
pkgsite -http=localhost:6060
# Load .env into every recipe. This is what puts CANVAS_TOKEN (test-canvas)
# and ZOT_REAL_DB (test-zot-real) on the environment without committing them.
set dotenv-load