From e99f7171e430950cc246b8edc9d50c893ac4b1cc Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 27 May 2026 12:30:05 +1000 Subject: [PATCH] ci: require docs lint when docs change (#25608) Move docs linting into the required CI umbrella and reuse the existing `changes` job so docs lint runs when docs or CI files change, plus on `main` as a backstop. This is motivated by the docs lint failures on #25601. That PR touched `.claude/docs/TESTING.md`; the standalone `Docs CI` workflow picked it up because `docs-ci.yaml` used broad `**.md` matching, but local `pnpm lint-docs` and `make lint` did not catch the same file because they only scanned `docs/**` plus root `*.md`. The first failed Docs CI run reported markdownlint errors in `.claude/docs/TESTING.md` (`MD040` and `MD031`), and the next run reported a markdown table formatter failure in the same file. That mismatch is why this PR exists: prevent unrelated PRs from being surprised by stale `.claude/docs/**` lint drift only after they happen to touch one of those files. The local docs scripts now include `.claude/docs/**`, and the old standalone `Docs CI` workflow is removed so we do not maintain separate path-filter logic outside the required CI workflow. > Generated by mux, but reviewed by a human --- .claude/docs/DEV_ISOLATION.md | 34 +++++----- .claude/docs/GO.md | 114 ++++++++++++++++---------------- .claude/docs/TROUBLESHOOTING.md | 24 +++---- .github/workflows/ci.yaml | 27 +++++++- .github/workflows/docs-ci.yaml | 71 -------------------- package.json | 5 +- 6 files changed, 115 insertions(+), 160 deletions(-) delete mode 100644 .github/workflows/docs-ci.yaml diff --git a/.claude/docs/DEV_ISOLATION.md b/.claude/docs/DEV_ISOLATION.md index 770f2fd527..ed4c7d739d 100644 --- a/.claude/docs/DEV_ISOLATION.md +++ b/.claude/docs/DEV_ISOLATION.md @@ -8,14 +8,14 @@ not add new readiness or debug endpoints for these workflows. `scripts/develop/main.go` defines these base defaults: -| Resource | Base default | Override | -|----------|--------------|----------| -| API server | `3000` | `--port`, `CODER_DEV_PORT` | -| Frontend dev server | `8080` | `--web-port`, `CODER_DEV_WEB_PORT` | -| Workspace proxy | `3010` | `--proxy-port`, `CODER_DEV_PROXY_PORT` | -| Coder Prometheus metrics | `2114` | `--prometheus-port`, `CODER_DEV_PROMETHEUS_PORT` | -| Embedded Prometheus UI | `9090` | Fixed in `scripts/develop/main.go` | -| Delve debugger | `12345` | Fixed when `--debug` is used | +| Resource | Base default | Override | +|--------------------------|--------------|--------------------------------------------------| +| API server | `3000` | `--port`, `CODER_DEV_PORT` | +| Frontend dev server | `8080` | `--web-port`, `CODER_DEV_WEB_PORT` | +| Workspace proxy | `3010` | `--proxy-port`, `CODER_DEV_PROXY_PORT` | +| Coder Prometheus metrics | `2114` | `--prometheus-port`, `CODER_DEV_PROMETHEUS_PORT` | +| Embedded Prometheus UI | `9090` | Fixed in `scripts/develop/main.go` | +| Delve debugger | `12345` | Fixed when `--debug` is used | By default, plain `./scripts/develop.sh` uses the base defaults exactly: `3000`, `8080`, `3010`, and `2114` for Coder Prometheus metrics. Set @@ -37,15 +37,15 @@ Linux. The Prometheus UI port `9090` and Delve port `12345` remain hardcoded. The develop script also supports these existing flags and environment variables: -| Purpose | Flag | Environment variable | -|---------|------|----------------------| -| Per-worktree port offset | `--port-offset` | `CODER_DEV_PORT_OFFSET` | -| Access URL | `--access-url` | `CODER_DEV_ACCESS_URL` | -| Admin password | `--password` | `CODER_DEV_ADMIN_PASSWORD` | -| Starter template | `--starter-template` | `CODER_DEV_STARTER_TEMPLATE` | -| Roll back missing migrations | `--db-rollback` | `CODER_DEV_DB_ROLLBACK` | -| Reset the development database | `--db-reset` | `CODER_DEV_DB_RESET` | -| Accept changed migration tracking | `--db-continue` | `CODER_DEV_DB_CONTINUE` | +| Purpose | Flag | Environment variable | +|-----------------------------------|----------------------|------------------------------| +| Per-worktree port offset | `--port-offset` | `CODER_DEV_PORT_OFFSET` | +| Access URL | `--access-url` | `CODER_DEV_ACCESS_URL` | +| Admin password | `--password` | `CODER_DEV_ADMIN_PASSWORD` | +| Starter template | `--starter-template` | `CODER_DEV_STARTER_TEMPLATE` | +| Roll back missing migrations | `--db-rollback` | `CODER_DEV_DB_ROLLBACK` | +| Reset the development database | `--db-reset` | `CODER_DEV_DB_RESET` | +| Accept changed migration tracking | `--db-continue` | `CODER_DEV_DB_CONTINUE` | Extra `coder server` flags can be passed after `--`. For example, `./scripts/develop.sh -- --trace` passes `--trace` to the API server. diff --git a/.claude/docs/GO.md b/.claude/docs/GO.md index a9e2631533..affdddcd00 100644 --- a/.claude/docs/GO.md +++ b/.claude/docs/GO.md @@ -92,69 +92,69 @@ The left column reflects common patterns from pre-1.22 Go. Write the right column instead. The "Since" column tells you the minimum `go` directive version required in `go.mod`. -| Old pattern | Modern replacement | Since | -|---|---|---| -| `interface{}` | `any` | 1.18 | -| `v := v` inside loops | remove it | 1.22 | -| `for i := 0; i < n; i++` | `for i := range n` | 1.22 | -| `for i := 0; i < b.N; i++` (benchmarks) | `for b.Loop()` (correct timing, future-proof) | 1.24 | -| `sort.Slice(s, func(i,j int) bool{…})` | `slices.SortFunc(s, cmpFn)` | 1.21 | -| `wg.Add(1); go func(){ defer wg.Done(); … }()` | `wg.Go(func(){…})` | 1.25 | -| `func ptr[T any](v T) *T { return &v }` | `new(expr)` e.g. `new(time.Now())` | 1.26 | -| `var target *E; errors.As(err, &target)` | `t, ok := errors.AsType[*E](err)` | 1.26 | -| Custom multi-error type | `errors.Join(err1, err2, …)` | 1.20 | -| Single `%w` for multiple causes | `fmt.Errorf("…: %w, %w", e1, e2)` | 1.20 | -| `rand.Seed(time.Now().UnixNano())` | delete it (auto-seeded); prefer `math/rand/v2` | 1.20/1.22 | -| `sync.Once` + captured variable | `sync.OnceValue(func() T {…})` / `OnceValues` | 1.21 | -| Custom `min`/`max` helpers | `min(a, b)` / `max(a, b)` builtins (any ordered type) | 1.21 | -| `for k := range m { delete(m, k) }` | `clear(m)` (also zeroes slices) | 1.21 | -| Index+slice or `SplitN(s, sep, 2)` | `strings.Cut(s, sep)` / `bytes.Cut` | 1.18 | -| `TrimPrefix` + check if anything was trimmed | `strings.CutPrefix` / `CutSuffix` (returns ok bool) | 1.20 | -| `strings.Split` + loop when no slice is needed | `strings.SplitSeq` / `Lines` / `FieldsSeq` (iterator, no alloc) | 1.24 | -| `"2006-01-02"` / `"2006-01-02 15:04:05"` / `"15:04:05"` | `time.DateOnly` / `time.DateTime` / `time.TimeOnly` | 1.20 | -| Manual `Before`/`After`/`Equal` chains for comparison | `time.Time.Compare` (returns -1/0/+1; works with `slices.SortFunc`) | 1.20 | -| Loop collecting map keys into slice | `slices.Sorted(maps.Keys(m))` | 1.23 | -| `fmt.Sprintf` + append to `[]byte` | `fmt.Appendf(buf, …)` (also `Append`, `Appendln`) | 1.18 | -| `reflect.TypeOf((*T)(nil)).Elem()` | `reflect.TypeFor[T]()` | 1.22 | -| `*(*[4]byte)(slice)` unsafe cast | `[4]byte(slice)` direct conversion | 1.20 | -| `atomic.LoadInt64` / `AddInt64` / `StoreInt64` etc. | `atomic.Int64` (also `Int32`, `Uint32`, `Uint64`, `Bool`, `Pointer[T]`) | 1.19 | -| `crypto/rand.Read(buf)` + hex/base64 encode | `crypto/rand.Text()` (one call) | 1.24 | -| Checking `crypto/rand.Read` error | don't: return is always nil | 1.24 | -| `time.Sleep` in tests | `testing/synctest` (deterministic fake clock) | 1.24/1.25 | -| `json:",omitempty"` on zero-value structs like `time.Time{}` | `json:",omitzero"` (uses `IsZero()` method) | 1.24 | -| `strings.Title` | `golang.org/x/text/cases` | 1.18 | -| `net.IP` in new code | `net/netip.Addr` (immutable, comparable, lighter) | 1.18 | -| `tools.go` with blank imports | `tool` directive in `go.mod` | 1.24 | -| `runtime.SetFinalizer` | `runtime.AddCleanup` (multiple per object, no pointer cycles) | 1.24 | -| `httputil.ReverseProxy.Director` | `.Rewrite` hook + `ProxyRequest` (Director deprecated in 1.26) | 1.20 | -| `sql.NullString`, `sql.NullInt64`, etc. | `sql.Null[T]` | 1.22 | -| Manual `ctx, cancel := context.WithCancel(…)` + `t.Cleanup(cancel)` | `t.Context()` (auto-canceled when test ends) | 1.24 | -| `if d < 0 { d = -d }` on durations | `d.Abs()` (handles `math.MinInt64`) | 1.19 | -| Implement only `TextMarshaler` | also implement `TextAppender` for alloc-free marshaling | 1.24 | -| Custom `Unwrap() error` on multi-cause errors | `Unwrap() []error` (slice form; required for tree traversal) | 1.20 | +| Old pattern | Modern replacement | Since | +|---------------------------------------------------------------------|-------------------------------------------------------------------------|-----------| +| `interface{}` | `any` | 1.18 | +| `v := v` inside loops | remove it | 1.22 | +| `for i := 0; i < n; i++` | `for i := range n` | 1.22 | +| `for i := 0; i < b.N; i++` (benchmarks) | `for b.Loop()` (correct timing, future-proof) | 1.24 | +| `sort.Slice(s, func(i,j int) bool{…})` | `slices.SortFunc(s, cmpFn)` | 1.21 | +| `wg.Add(1); go func(){ defer wg.Done(); … }()` | `wg.Go(func(){…})` | 1.25 | +| `func ptr[T any](v T) *T { return &v }` | `new(expr)` e.g. `new(time.Now())` | 1.26 | +| `var target *E; errors.As(err, &target)` | `t, ok := errors.AsType[*E](err)` | 1.26 | +| Custom multi-error type | `errors.Join(err1, err2, …)` | 1.20 | +| Single `%w` for multiple causes | `fmt.Errorf("…: %w, %w", e1, e2)` | 1.20 | +| `rand.Seed(time.Now().UnixNano())` | delete it (auto-seeded); prefer `math/rand/v2` | 1.20/1.22 | +| `sync.Once` + captured variable | `sync.OnceValue(func() T {…})` / `OnceValues` | 1.21 | +| Custom `min`/`max` helpers | `min(a, b)` / `max(a, b)` builtins (any ordered type) | 1.21 | +| `for k := range m { delete(m, k) }` | `clear(m)` (also zeroes slices) | 1.21 | +| Index+slice or `SplitN(s, sep, 2)` | `strings.Cut(s, sep)` / `bytes.Cut` | 1.18 | +| `TrimPrefix` + check if anything was trimmed | `strings.CutPrefix` / `CutSuffix` (returns ok bool) | 1.20 | +| `strings.Split` + loop when no slice is needed | `strings.SplitSeq` / `Lines` / `FieldsSeq` (iterator, no alloc) | 1.24 | +| `"2006-01-02"` / `"2006-01-02 15:04:05"` / `"15:04:05"` | `time.DateOnly` / `time.DateTime` / `time.TimeOnly` | 1.20 | +| Manual `Before`/`After`/`Equal` chains for comparison | `time.Time.Compare` (returns -1/0/+1; works with `slices.SortFunc`) | 1.20 | +| Loop collecting map keys into slice | `slices.Sorted(maps.Keys(m))` | 1.23 | +| `fmt.Sprintf` + append to `[]byte` | `fmt.Appendf(buf, …)` (also `Append`, `Appendln`) | 1.18 | +| `reflect.TypeOf((*T)(nil)).Elem()` | `reflect.TypeFor[T]()` | 1.22 | +| `*(*[4]byte)(slice)` unsafe cast | `[4]byte(slice)` direct conversion | 1.20 | +| `atomic.LoadInt64` / `AddInt64` / `StoreInt64` etc. | `atomic.Int64` (also `Int32`, `Uint32`, `Uint64`, `Bool`, `Pointer[T]`) | 1.19 | +| `crypto/rand.Read(buf)` + hex/base64 encode | `crypto/rand.Text()` (one call) | 1.24 | +| Checking `crypto/rand.Read` error | don't: return is always nil | 1.24 | +| `time.Sleep` in tests | `testing/synctest` (deterministic fake clock) | 1.24/1.25 | +| `json:",omitempty"` on zero-value structs like `time.Time{}` | `json:",omitzero"` (uses `IsZero()` method) | 1.24 | +| `strings.Title` | `golang.org/x/text/cases` | 1.18 | +| `net.IP` in new code | `net/netip.Addr` (immutable, comparable, lighter) | 1.18 | +| `tools.go` with blank imports | `tool` directive in `go.mod` | 1.24 | +| `runtime.SetFinalizer` | `runtime.AddCleanup` (multiple per object, no pointer cycles) | 1.24 | +| `httputil.ReverseProxy.Director` | `.Rewrite` hook + `ProxyRequest` (Director deprecated in 1.26) | 1.20 | +| `sql.NullString`, `sql.NullInt64`, etc. | `sql.Null[T]` | 1.22 | +| Manual `ctx, cancel := context.WithCancel(…)` + `t.Cleanup(cancel)` | `t.Context()` (auto-canceled when test ends) | 1.24 | +| `if d < 0 { d = -d }` on durations | `d.Abs()` (handles `math.MinInt64`) | 1.19 | +| Implement only `TextMarshaler` | also implement `TextAppender` for alloc-free marshaling | 1.24 | +| Custom `Unwrap() error` on multi-cause errors | `Unwrap() []error` (slice form; required for tree traversal) | 1.20 | ## New capabilities These enable things that weren't practical before. Reach for them in the described situations. -| What | Since | When to use it | -|---|---|---| -| `cmp.Or(a, b, c)` | 1.22 | Defaults/fallback chains: returns first non-zero value. Replaces verbose `if a != "" { return a }` cascades. | -| `context.WithoutCancel(ctx)` | 1.21 | Background work that must outlive the request (e.g. async cleanup after HTTP response). Derived context keeps parent's values but ignores cancellation. | -| `context.AfterFunc(ctx, fn)` | 1.21 | Register cleanup that fires on context cancellation without spawning a goroutine that blocks on `<-ctx.Done()`. | -| `context.WithCancelCause` / `Cause` | 1.20 | When callers need to know WHY a context was canceled, not just that it was. Retrieve cause with `context.Cause(ctx)`. | -| `context.WithDeadlineCause` / `WithTimeoutCause` | 1.21 | Attach a domain-specific error to deadline/timeout expiry (e.g. distinguish "DB query timed out" from "HTTP request timed out"). | -| `errors.ErrUnsupported` | 1.21 | Standard sentinel for "not supported." Use instead of per-package custom sentinels. Check with `errors.Is`. | -| `http.ResponseController` | 1.20 | Per-request flush, hijack, and deadline control without type-asserting `ResponseWriter` to `http.Flusher` or `http.Hijacker`. | -| Enhanced `ServeMux` routing | 1.22 | `"GET /items/{id}"` patterns in `http.ServeMux`. Access with `r.PathValue("id")`. Wildcards: `{name}`, catch-all: `{path...}`, exact: `{$}`. Eliminates many third-party router dependencies. | -| `os.Root` / `OpenRoot` | 1.24 | Confined directory access that prevents symlink escape. 1.25 adds `MkdirAll`, `ReadFile`, `WriteFile` for real use. | -| `os.CopyFS` | 1.23 | Copy an entire `fs.FS` to local filesystem in one call. | -| `os/signal.NotifyContext` with cause | 1.26 | Cancellation cause identifies which signal (SIGTERM vs SIGINT) triggered shutdown. | -| `io/fs.SkipAll` / `filepath.SkipAll` | 1.20 | Return from `WalkDir` callback to stop walking entirely. Cleaner than a sentinel error. | -| `GOMEMLIMIT` env / `debug.SetMemoryLimit` | 1.19 | Soft memory limit for GC. Use alongside or instead of `GOGC` in memory-constrained containers. | -| `net/url.JoinPath` | 1.19 | Join URL path segments correctly. Replaces error-prone string concatenation. | -| `go test -skip` | 1.20 | Skip tests matching a pattern. Useful when running a subset of a large test suite. | +| What | Since | When to use it | +|--------------------------------------------------|-------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `cmp.Or(a, b, c)` | 1.22 | Defaults/fallback chains: returns first non-zero value. Replaces verbose `if a != "" { return a }` cascades. | +| `context.WithoutCancel(ctx)` | 1.21 | Background work that must outlive the request (e.g. async cleanup after HTTP response). Derived context keeps parent's values but ignores cancellation. | +| `context.AfterFunc(ctx, fn)` | 1.21 | Register cleanup that fires on context cancellation without spawning a goroutine that blocks on `<-ctx.Done()`. | +| `context.WithCancelCause` / `Cause` | 1.20 | When callers need to know WHY a context was canceled, not just that it was. Retrieve cause with `context.Cause(ctx)`. | +| `context.WithDeadlineCause` / `WithTimeoutCause` | 1.21 | Attach a domain-specific error to deadline/timeout expiry (e.g. distinguish "DB query timed out" from "HTTP request timed out"). | +| `errors.ErrUnsupported` | 1.21 | Standard sentinel for "not supported." Use instead of per-package custom sentinels. Check with `errors.Is`. | +| `http.ResponseController` | 1.20 | Per-request flush, hijack, and deadline control without type-asserting `ResponseWriter` to `http.Flusher` or `http.Hijacker`. | +| Enhanced `ServeMux` routing | 1.22 | `"GET /items/{id}"` patterns in `http.ServeMux`. Access with `r.PathValue("id")`. Wildcards: `{name}`, catch-all: `{path...}`, exact: `{$}`. Eliminates many third-party router dependencies. | +| `os.Root` / `OpenRoot` | 1.24 | Confined directory access that prevents symlink escape. 1.25 adds `MkdirAll`, `ReadFile`, `WriteFile` for real use. | +| `os.CopyFS` | 1.23 | Copy an entire `fs.FS` to local filesystem in one call. | +| `os/signal.NotifyContext` with cause | 1.26 | Cancellation cause identifies which signal (SIGTERM vs SIGINT) triggered shutdown. | +| `io/fs.SkipAll` / `filepath.SkipAll` | 1.20 | Return from `WalkDir` callback to stop walking entirely. Cleaner than a sentinel error. | +| `GOMEMLIMIT` env / `debug.SetMemoryLimit` | 1.19 | Soft memory limit for GC. Use alongside or instead of `GOGC` in memory-constrained containers. | +| `net/url.JoinPath` | 1.19 | Join URL path segments correctly. Replaces error-prone string concatenation. | +| `go test -skip` | 1.20 | Skip tests matching a pattern. Useful when running a subset of a large test suite. | ## Key packages diff --git a/.claude/docs/TROUBLESHOOTING.md b/.claude/docs/TROUBLESHOOTING.md index 1788d5df84..1cc084ef34 100644 --- a/.claude/docs/TROUBLESHOOTING.md +++ b/.claude/docs/TROUBLESHOOTING.md @@ -23,48 +23,48 @@ ### Testing Issues -3. **"package should be X_test"** +1. **"package should be X_test"** - **Solution**: Use `package_test` naming for test files - Example: `identityprovider_test` for black-box testing -4. **Race conditions in tests** +2. **Race conditions in tests** - **Solution**: Use unique identifiers instead of hardcoded names - Example: `fmt.Sprintf("test-client-%s-%d", t.Name(), time.Now().UnixNano())` - Never use hardcoded names in concurrent tests -5. **Missing newlines** +3. **Missing newlines** - **Solution**: Ensure files end with newline character - Most editors can be configured to add this automatically ### OAuth2 Issues -6. **OAuth2 endpoints returning wrong error format** +1. **OAuth2 endpoints returning wrong error format** - **Solution**: Ensure OAuth2 endpoints return RFC 6749 compliant errors - Use standard error codes: `invalid_client`, `invalid_grant`, `invalid_request` - Format: `{"error": "code", "error_description": "details"}` -7. **Resource indicator validation failing** +2. **Resource indicator validation failing** - **Solution**: Ensure database stores and retrieves resource parameters correctly - Check both authorization code storage and token exchange handling -8. **PKCE tests failing** +3. **PKCE tests failing** - **Solution**: Verify both authorization code storage and token exchange handle PKCE fields - Check `CodeChallenge` and `CodeChallengeMethod` field handling ### RFC Compliance Issues -9. **RFC compliance failures** +1. **RFC compliance failures** - **Solution**: Verify against actual RFC specifications, not assumptions - Use WebFetch tool to get current RFC content for compliance verification - Read the actual RFC specifications before implementation -10. **Default value mismatches** +2. **Default value mismatches** - **Solution**: Ensure database migrations match application code defaults - Example: RFC 7591 specifies `client_secret_basic` as default, not `client_secret_post` ### Authorization Issues -11. **Authorization context errors in public endpoints** +1. **Authorization context errors in public endpoints** - **Solution**: Use `dbauthz.AsSystemRestricted(ctx)` pattern - Example: @@ -75,17 +75,17 @@ ### Authentication Issues -12. **Bearer token authentication issues** +1. **Bearer token authentication issues** - **Solution**: Check token extraction precedence and format validation - Ensure proper RFC 6750 Bearer Token Support implementation -13. **URI validation failures** +2. **URI validation failures** - **Solution**: Support both standard schemes and custom schemes per protocol requirements - Native OAuth2 apps may use custom schemes ### General Development Issues -14. **Log message formatting errors** +1. **Log message formatting errors** - **Solution**: Use lowercase, descriptive messages without special characters - Follow Go logging conventions diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b52d46dd10..cdea8e28db 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -53,7 +53,8 @@ jobs: - "**" docs: - "docs/**" - - "README.md" + - ".claude/docs/**" + - "*.md" - "examples/web-server/**" - "examples/monitoring/**" - "examples/lima/**" @@ -120,6 +121,28 @@ jobs: env: FILTER_JSON: ${{ toJSON(steps.filter.outputs) }} + lint-docs: + needs: changes + if: needs.changes.outputs.docs == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Setup Node + uses: ./.github/actions/setup-node + + - name: Check docs + run: pnpm check-docs + # Disabled due to instability. See: https://github.com/coder/coder/issues/14553 # Re-enable once the flake hash calculation is stable. # update-flake: @@ -1132,6 +1155,7 @@ jobs: - changes - fmt - lint + - lint-docs - lint-actions - gen - test-go-pg @@ -1157,6 +1181,7 @@ jobs: echo "- changes: ${{ needs.changes.result }}" echo "- fmt: ${{ needs.fmt.result }}" echo "- lint: ${{ needs.lint.result }}" + echo "- lint-docs: ${{ needs.lint-docs.result }}" echo "- lint-actions: ${{ needs.lint-actions.result }}" echo "- gen: ${{ needs.gen.result }}" echo "- test-go-pg: ${{ needs.test-go-pg.result }}" diff --git a/.github/workflows/docs-ci.yaml b/.github/workflows/docs-ci.yaml deleted file mode 100644 index 8df9850f08..0000000000 --- a/.github/workflows/docs-ci.yaml +++ /dev/null @@ -1,71 +0,0 @@ -name: Docs CI - -on: - push: - branches: - - main - # Self-reference removed from both push and pull_request: the `lint` - # and `fmt` steps gate on `tj-actions/changed-files` matching - # `docs/**` or `**.md`, so a workflow-only edit produced an empty - # run. `actionlint` and `make lint/actions` catch YAML problems - # before merge regardless. See DOCS-129. - paths: - - "docs/**" - - "**.md" - - pull_request: - # Self-reference removed; see comment under `push:` above. - paths: - - "docs/**" - - "**.md" - -permissions: - contents: read - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Setup Node - uses: ./.github/actions/setup-node - - # Per-tool changed-files filters. Each tool gets its own `changed-*` - # step scoped to the files it processes, keeping workflow-level `paths:` - # broad. Adding a tool (e.g. image linter) only needs a new step pair. - - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v45.0.7 - id: changed-md - with: - files: | - **.md - separator: "," - - # Both downstream tools take file paths as argv. `tj-actions/changed-files` - # joins paths with `separator: ","`, which the shell does not split on, so - # run the output through `tr ',' '\n' | xargs -d '\n'` to hand each path to - # the tool as a distinct argument. This tolerates filenames containing - # spaces and prevents silent fallbacks: `markdownlint-cli2` would treat a - # comma-joined string as a single non-matching glob, and - # `markdown-table-formatter` would fall back to scanning every `.md` in - # the working tree when invoked with no positional args. - # - # `printf '%s\n'` is used instead of `echo` so a hypothetical leading - # `-e` or `-n` in a path is treated as data, not a bash builtin flag. - - - name: lint - if: steps.changed-md.outputs.any_changed == 'true' - run: | - printf '%s\n' "$ALL_CHANGED_FILES" | tr ',' '\n' | xargs -d '\n' pnpm exec markdownlint-cli2 - env: - ALL_CHANGED_FILES: ${{ steps.changed-md.outputs.all_changed_files }} - - - name: fmt - if: steps.changed-md.outputs.any_changed == 'true' - run: | - printf '%s\n' "$ALL_CHANGED_FILES" | tr ',' '\n' | xargs -d '\n' pnpm exec markdown-table-formatter --check - env: - ALL_CHANGED_FILES: ${{ steps.changed-md.outputs.all_changed_files }} diff --git a/package.json b/package.json index 0f117f1237..1d23b3f423 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "version": "0.0.0", "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", "scripts": { - "format-docs": "markdown-table-formatter $(find docs -name '*.md') *.md", - "lint-docs": "markdownlint-cli2 --fix $(find docs -name '*.md') *.md", + "format-docs": "markdown-table-formatter $(find docs .claude/docs examples/web-server examples/monitoring examples/lima -name '*.md' 2>/dev/null) *.md", + "lint-docs": "markdownlint-cli2 --fix $(find docs .claude/docs examples/web-server examples/monitoring examples/lima -name '*.md' 2>/dev/null) *.md", + "check-docs": "markdownlint-cli2 $(find docs .claude/docs examples/web-server examples/monitoring examples/lima -name '*.md' 2>/dev/null) *.md && markdown-table-formatter --check $(find docs .claude/docs examples/web-server examples/monitoring examples/lima -name '*.md' 2>/dev/null) *.md", "storybook": "pnpm run -C site/ storybook" }, "devDependencies": {