diff --git a/AGENTS.md b/AGENTS.md index b09c382791..c8dccceff8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,19 +37,21 @@ Only pause to ask for confirmation when: ## Essential Commands -| Task | Command | Notes | -|-------------------|--------------------------|----------------------------------| -| **Development** | `./scripts/develop.sh` | ⚠️ Don't use manual build | -| **Build** | `make build` | Fat binaries (includes server) | -| **Build Slim** | `make build-slim` | Slim binaries | -| **Test** | `make test` | Full test suite | -| **Test Single** | `make test RUN=TestName` | Faster than full suite | -| **Test Postgres** | `make test-postgres` | Run tests with Postgres database | -| **Test Race** | `make test-race` | Run tests with Go race detector | -| **Lint** | `make lint` | Always run after changes | -| **Generate** | `make gen` | After database changes | -| **Format** | `make fmt` | Auto-format code | -| **Clean** | `make clean` | Clean build artifacts | +| Task | Command | Notes | +|-------------------|--------------------------|-------------------------------------| +| **Development** | `./scripts/develop.sh` | ⚠️ Don't use manual build | +| **Build** | `make build` | Fat binaries (includes server) | +| **Build Slim** | `make build-slim` | Slim binaries | +| **Test** | `make test` | Full test suite | +| **Test Single** | `make test RUN=TestName` | Faster than full suite | +| **Test Postgres** | `make test-postgres` | Run tests with Postgres database | +| **Test Race** | `make test-race` | Run tests with Go race detector | +| **Lint** | `make lint` | Always run after changes | +| **Generate** | `make gen` | After database changes | +| **Format** | `make fmt` | Auto-format code | +| **Clean** | `make clean` | Clean build artifacts | +| **Pre-commit** | `make pre-commit` | Fast CI checks (gen/fmt/lint/build) | +| **Pre-push** | `make pre-push` | All CI checks including tests | ### Documentation Commands @@ -103,6 +105,22 @@ app, err := api.Database.GetOAuth2ProviderAppByClientID(ctx, clientID) ### Full workflows available in imported WORKFLOWS.md +### Git Hooks (MANDATORY) + +Before your first commit, ensure the git hooks are installed. +Two hooks run automatically: + +- **pre-commit**: `make pre-commit` (gen, fmt, lint, typos, build). + Fast checks that catch most CI failures. +- **pre-push**: `make pre-push` (full CI suite including tests). + Runs before pushing to catch everything CI would. + +Wait for them to complete, do not skip or bypass them. + +```sh +git config core.hooksPath scripts/githooks +``` + ### Git Workflow When working on existing PRs, check out the branch first: diff --git a/Makefile b/Makefile index 81700e2c01..4bcf29deca 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ endif # Note, all find statements should be written with `.` or `./path` as # the search path so that these exclusions match. FIND_EXCLUSIONS= \ - -not \( \( -path '*/.git/*' -o -path './build/*' -o -path './vendor/*' -o -path './.coderv2/*' -o -path '*/node_modules/*' -o -path '*/out/*' -o -path './coderd/apidoc/*' -o -path '*/.next/*' -o -path '*/.terraform/*' \) -prune \) + -not \( \( -path '*/.git/*' -o -path './build/*' -o -path './vendor/*' -o -path './.coderv2/*' -o -path '*/node_modules/*' -o -path '*/out/*' -o -path './coderd/apidoc/*' -o -path '*/.next/*' -o -path '*/.terraform/*' -o -path './_gen/*' \) -prune \) # Source files used for make targets, evaluated on use. GO_SRC_FILES := $(shell find . $(FIND_EXCLUSIONS) -type f -name '*.go' -not -name '*_test.go') # Same as GO_SRC_FILES but excluding certain files that have problematic @@ -675,6 +675,108 @@ lint/migrations: ./scripts/check_pg_schema.sh "Fixtures" $(FIXTURE_FILES) .PHONY: lint/migrations +TYPOS_VERSION := $(shell grep -oP 'crate-ci/typos@\S+\s+\#\s+v\K[0-9.]+' .github/workflows/ci.yaml) + +# Map uname values to typos release asset names. +TYPOS_ARCH := $(shell uname -m) +ifeq ($(shell uname -s),Darwin) +TYPOS_OS := apple-darwin +else +TYPOS_OS := unknown-linux-musl +endif + +build/typos-$(TYPOS_VERSION): + mkdir -p build/ + curl -sSfL "https://github.com/crate-ci/typos/releases/download/v$(TYPOS_VERSION)/typos-v$(TYPOS_VERSION)-$(TYPOS_ARCH)-$(TYPOS_OS).tar.gz" \ + | tar -xzf - -C build/ ./typos + mv build/typos "$@" + +lint/typos: build/typos-$(TYPOS_VERSION) + build/typos-$(TYPOS_VERSION) --config .github/workflows/typos.toml +.PHONY: lint/typos + +# pre-commit and pre-push mirror CI "required" jobs locally. +# See the "required" job's needs list in .github/workflows/ci.yaml. +# +# pre-commit runs checks that don't need external services (Docker, +# Playwright). This is the git pre-commit hook default since test +# and Docker failures in the local environment would otherwise block +# all commits. +# +# pre-push runs the full CI suite including tests. This is the git +# pre-push hook default, catching everything CI would before pushing. +# +# Both use two-phase execution: gen+fmt first (writes files), then +# lint+build (reads files). This avoids races where gen's `go run` +# creates temporary .go files that lint's find-based checks pick up. +# Within each phase, targets run in parallel via -j. Both fail if +# any tracked files have unstaged changes afterward. +# +# Both pre-commit and pre-push: +# gen, fmt, lint, lint/typos, slim binary (local arch) +# +# pre-push only (need external services or are slow): +# site/out/index.html (pnpm build) +# test-postgres (needs Docker) +# test-js, test-e2e (needs Playwright) +# sqlc-vet (needs Docker) +# offlinedocs/check +# +# Omitted: +# test-go-pg-17 (same tests, different PG version) + +define check-unstaged + unstaged="$$(git diff --name-only)" + if [[ -n $$unstaged ]]; then + echo "ERROR: unstaged changes in tracked files:" + echo "$$unstaged" + echo + echo "Review each change (git diff), verify correctness, then stage:" + echo " git add -u && git commit" + exit 1 + fi + untracked=$$(git ls-files --other --exclude-standard) + if [[ -n $$untracked ]]; then + echo "WARNING: untracked files (not in this commit, won't be in CI):" + echo "$$untracked" + echo + fi +endef + +pre-commit: + $(MAKE) -j --output-sync=target gen fmt + $(check-unstaged) + $(MAKE) -j --output-sync=target \ + lint \ + lint/typos \ + build/coder-slim_$(GOOS)_$(GOARCH)$(GOOS_BIN_EXT) + $(check-unstaged) +.PHONY: pre-commit + +pre-push: + $(MAKE) -j --output-sync=target gen fmt + $(check-unstaged) + $(MAKE) -j --output-sync=target \ + lint \ + lint/typos \ + build/coder-slim_$(GOOS)_$(GOARCH)$(GOOS_BIN_EXT) \ + site/out/index.html \ + test-postgres \ + test-js \ + test-e2e \ + test-race \ + sqlc-vet \ + offlinedocs/check + $(check-unstaged) +.PHONY: pre-push + +offlinedocs/check: offlinedocs/node_modules/.installed + cd offlinedocs/ + pnpm format:check + pnpm lint + pnpm export +.PHONY: offlinedocs/check + # All files generated by the database should be added here, and this can be used # as a target for jobs that need to run after the database is generated. DB_GEN_FILES := \ @@ -1041,8 +1143,7 @@ docs/manifest.json: site/node_modules/.installed coderd/apidoc/.gen docs/referen tmpdir=$$(mktemp -d -p _gen) && tmpfile=$$(realpath "$$tmpdir")/$(notdir $@) && \ cp _gen/manifest-staging.json "$$tmpfile" && \ ./scripts/biome_format.sh "$$tmpfile" && \ - mv "$$tmpfile" "$@" && rm -rf "$$tmpdir" && \ - rm -f _gen/manifest-staging.json + mv "$$tmpfile" "$@" && rm -rf "$$tmpdir" coderd/apidoc/swagger.json: site/node_modules/.installed coderd/apidoc/.gen touch "$@" @@ -1165,6 +1266,11 @@ test-cli: $(MAKE) test TEST_PACKAGES="./cli..." .PHONY: test-cli +test-js: site/node_modules/.installed + cd site/ + pnpm test:ci +.PHONY: test-js + # sqlc-cloud-is-setup will fail if no SQLc auth token is set. Use this as a # dependency for any sqlc-cloud related targets. sqlc-cloud-is-setup: diff --git a/docs/about/contributing/CONTRIBUTING.md b/docs/about/contributing/CONTRIBUTING.md index 049e8e4051..52a8d1b98c 100644 --- a/docs/about/contributing/CONTRIBUTING.md +++ b/docs/about/contributing/CONTRIBUTING.md @@ -70,6 +70,14 @@ Use the following `make` commands and scripts in development: - `make build` compiles binaries and release packages - `make install` installs binaries to `$GOPATH/bin` - `make test` +- `make pre-commit` runs gen, fmt, lint, typos, and builds a slim binary +- `make pre-push` runs the full CI suite including tests + +Install the git hooks to run these automatically: + +```sh +git config core.hooksPath scripts/githooks +``` ### Running Coder on development mode diff --git a/scripts/githooks/pre-commit b/scripts/githooks/pre-commit new file mode 100755 index 0000000000..ff6f59418b --- /dev/null +++ b/scripts/githooks/pre-commit @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# +# Pre-commit hook that runs CI-equivalent checks locally. +# Runs `make pre-commit` (gen, fmt, lint, typos, build) which +# catches most CI failures without needing Docker or Playwright. +# The full CI suite (including tests) runs via the pre-push hook. +# +# Installation (worktree-compatible): +# +# git config core.hooksPath scripts/githooks +# +# Bypass: git commit --no-verify + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +unset GIT_DIR + +exec make pre-commit diff --git a/scripts/githooks/pre-push b/scripts/githooks/pre-push new file mode 100755 index 0000000000..115dee8c2e --- /dev/null +++ b/scripts/githooks/pre-push @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# +# Pre-push hook that runs the full CI suite locally. +# Runs `make pre-push` (gen, fmt, lint, typos, build, tests) +# to catch issues before they reach CI. +# +# The pre-commit hook already runs the lite checks on each commit. +# This hook adds the heavier checks (test-postgres, test-js, +# test-e2e, sqlc-vet, offlinedocs) before pushing. +# +# Installation (worktree-compatible): +# +# git config core.hooksPath scripts/githooks +# +# Bypass: git push --no-verify + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" +unset GIT_DIR + +exec make pre-push