-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy path.pre-commit-config.yaml
More file actions
450 lines (417 loc) · 20.7 KB
/
Copy path.pre-commit-config.yaml
File metadata and controls
450 lines (417 loc) · 20.7 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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# Pre-commit hooks for djust
# Install: pip install pre-commit && pre-commit install
# Run manually: pre-commit run --all-files
repos:
# Python - Ruff for linting and formatting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.11
hooks:
# Linter
- id: ruff
args: [--fix]
files: ^(python/|tests/)
# Formatter
- id: ruff-format
files: ^(python/|tests/)
# Rust - Formatting and linting
- repo: local
hooks:
# Rust formatting
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt
language: system
types: [rust]
pass_filenames: false
args: [--all, --check]
# Compile-heavy lint runs once before push; commits keep cargo fmt.
- id: cargo-clippy
name: cargo clippy
entry: cargo clippy
language: system
types: [rust]
pass_filenames: false
args: [--all-targets, --all-features, --, -D, warnings]
stages: [pre-push]
# General file checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
# Vendored outputs (make vendor) are byte-exact: their manifests pin each
# file's SRI hash, so a whitespace "fix" would break the integrity check.
- id: trailing-whitespace
exclude: '\.min\.js(\.map)?$|\.pxd$|^python/djust/components/static/djust_components/(vendor/|markdown-visual\.)|^python/djust/admin_ext/static/djust_admin/admin\.css$'
- id: end-of-file-fixer
exclude: '\.min\.js(\.map)?$|\.pxd$|^python/djust/components/static/djust_components/(vendor/|markdown-visual\.)|^python/djust/admin_ext/static/djust_admin/admin\.css$'
- id: check-yaml
- id: check-added-large-files
args: [--maxkb=1000]
# .test_durations is the pytest-split balance file for the CI shards
# (~3.3 MB: one {nodeid: seconds} line per test). It must be complete:
# pytest-split fills a MISSING entry with the average of the entries
# present, so a pruned "slow tests only" file would be worse than
# none. Regenerated wholesale by `make test-durations`.
exclude: '^\.test_durations$'
- id: check-merge-conflict
- id: check-toml
# `.pxd` excluded: Pixelmator logo files in python/djust/ are
# actually binary ZIP archives that `identify` (pre-commit's
# text-detection lib) misclassifies as text on the `.pxd`
# extension. Without this exclude, the hook auto-"fixes" CR
# bytes inside the binary payload and corrupts the asset (#1215).
- id: mixed-line-ending
exclude: '\.pxd$'
# Security - Python security linter
- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
# Skip expected patterns in this codebase:
# B703/B308: mark_safe - expected in templates
# B324: MD5 - used for non-security cache keys
# B301: pickle - used for JIT function caching
# B102: exec - used for JIT code generation
args: ["-ll", "-ii", "-x", "tests/,python/tests/,python/djust/tests/", "-s", "B703,B308"]
files: ^python/
# JavaScript - Build, test, and lint
- repo: local
hooks:
- id: check-bundle-init-order
name: bundle init-order static lint (#1372)
entry: node scripts/check-bundle-init-order.mjs
language: system
files: ^python/djust/static/djust/src/
types: [javascript]
pass_filenames: false
- id: check-cross-iife-refs
name: bundle cross-IIFE bare-reference static guard (#1706)
entry: node scripts/check-cross-iife-refs.mjs
language: system
files: ^python/djust/static/djust/src/
types: [javascript]
pass_filenames: false
- id: build-js
name: build client.js from src/ modules
entry: bash -c 'bash scripts/build-client.sh && git add python/djust/static/djust/client.js python/djust/static/djust/debug-panel.js python/djust/static/djust/client-sizes.json'
language: system
files: ^python/djust/static/djust/src/
types: [javascript]
pass_filenames: false
# Full JS suite runs before push and in CI, not on every local commit.
- id: npm-test
name: npm test
entry: npm test
language: system
types: [javascript]
pass_filenames: false
stages: [pre-push]
- id: eslint
name: eslint
# Gate on errors (severity 2) AND warnings (severity 1). After #1719
# drove the project-wide warning count to 0 (each remaining
# security/detect-object-injection site carries a justified
# eslint-disable comment), --max-warnings 0 re-introduces a ceiling
# so the count can only go down — any new warning blocks the commit,
# alongside the real XSS/no-script-url error gates.
entry: npx eslint --no-warn-ignored --max-warnings 0
language: system
files: ^python/djust/static/djust/[^/]*\.js$
pass_filenames: true
# Python - mypy type-checking (lenient global + strict islands; ADR-023)
# Scoped to python/djust source changes. The config (pyproject [tool.mypy])
# makes the gate GREEN against the legacy baseline while enforcing strict
# rules on the strict-island modules (security/*, rate_limit, validation,
# permissions, the _rust.pyi boundary, etc.). Runs the full package because
# mypy needs cross-module context; warm runs are ~0.3s thanks to the cache.
- repo: local
hooks:
- id: mypy
name: mypy type-check (ADR-023 strict islands)
entry: bash -c 'bash scripts/run-with-venv-python.sh -m mypy python/djust'
language: system
files: ^python/djust/.*\.pyi?$
pass_filenames: false
# Security - Secret detection
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
exclude: package-lock\.json
# CHANGELOG validation — catches drift between claimed test counts and
# actual tests in referenced files. See scripts/check-changelog-test-counts.py
# and tests/test_changelog_test_counts.py. Closes #908.
- repo: local
hooks:
- id: check-retro-coverage
name: check completed drain buckets have a RETRO.md entry (#2848)
entry: python scripts/check-retro-coverage.py
language: system
files: ^(ROADMAP|RETRO)\.md$
pass_filenames: false
- id: check-changelog-test-counts
name: check CHANGELOG test counts
entry: python scripts/check-changelog-test-counts.py
language: python
files: ^(CHANGELOG\.md|changelog\.d/.*\.md)$
pass_filenames: false
# changelog.d/ fragments (one file per PR, folded into [Unreleased] at the
# release cut by `make changelog-compile`). Validates every fragment's
# section suffix + bullet body + test-count claims, and refuses a commit
# that edits CHANGELOG.md's [Unreleased] body directly — that section is
# written only by the compile now, so parallel PRs stop conflicting on it.
# See scripts/changelog-fragments.py and tests/test_changelog_fragments.py.
- id: check-changelog-fragments
name: check changelog.d/ fragments (+ no direct [Unreleased] edits)
entry: python scripts/changelog-fragments.py check --cached
language: python
files: ^(changelog\.d/|CHANGELOG\.md$)
pass_filenames: false
# Pin already-shipped CHANGELOG sections against the newest release tag —
# a stray branch merge must never silently rewrite shipped history (the
# v1.1.0rc5 consolidation incident). See
# scripts/check-changelog-tagged-sections.py and
# tests/test_changelog_tagged_sections.py. Closes #2028.
- id: check-changelog-tagged-sections
name: check CHANGELOG shipped-section pins (#2028)
entry: python scripts/check-changelog-tagged-sections.py
language: python
files: ^CHANGELOG\.md$
pass_filenames: false
# Fragments citing a file path or test class that does not exist are the
# same defect class as #2652's phantom doc methods, and five shipped PRs
# carried such claims (#2849). Resolves backtick-quoted paths and
# Test-class names in fragments against the tree; count claims stay with
# check-changelog-test-counts above (shared parser, no third one). See
# scripts/check-changelog-fragment-references.py and
# tests/test_changelog_fragment_references.py. Closes #2849.
- id: check-changelog-fragment-references
name: check changelog.d fragment path/class references (#2849)
entry: python scripts/check-changelog-fragment-references.py
language: python
files: ^changelog\.d/.*\.md$
pass_filenames: false
# ADR status/version-line consistency — catches the drift class #1492
# reconciled by hand (Accepted ADR still has a `Target version:` line).
# See scripts/check-adr-status.py and tests/test_check_adr_status.py.
# Closes #1501.
- repo: local
hooks:
- id: check-adr-status
name: check ADR status/version-line consistency (#1501)
entry: python scripts/check-adr-status.py
language: python
files: ^docs/adr/.*\.md$
pass_filenames: false
# Doc-snippet smoke test + claim assertions — catches doc-rot in fenced
# Python snippets (syntax/phantom-import) and the Django-floor / JS
# bundle-size claims in README/QUICKSTART, plus symbol/import resolvability
# in docs/website/guides/*.md (#1707 — the guard for #1559/#1699's
# hallucinated djust.tenants symbols). See scripts/check-doc-snippets.py
# and tests/test_check_doc_snippets.py. Closes #1500 (parts a+b), #1707.
- repo: local
hooks:
- id: check-doc-snippets
name: check doc snippets + version/size claims (#1500, #1707)
entry: bash -c 'PYTHONPATH=. bash scripts/run-with-venv-python.sh scripts/check-doc-snippets.py'
language: system
files: ^(README|QUICKSTART|CLAUDE)\.md$|^pyproject\.toml$|^docs/website/guides/.*\.md$|^python/djust/static/djust/client-sizes\.json$
pass_filenames: false
# Methods named in the mixin guides must exist on the mixins.
# docs/website/guides/pwa.md carried EIGHT phantom PWA methods — in an
# API table and in two runnable examples — until #2655 removed them;
# two hand-sweeps during that PR each fixed the sites they were pointed
# at and missed a second example lower in the file. See
# scripts/check-doc-api-references.py: it reads table rows and bullet
# rows, FAILS when a registered doc matches neither (a checker that
# cannot see a doc reads as coverage while providing none), and FAILS
# when this hook's `files:` pattern covers a doc DOC_CLASSES does not.
- id: check-doc-api-references
name: check documented methods exist on their classes (#2652)
entry: bash -c 'PYTHONPATH=. bash scripts/run-with-venv-python.sh scripts/check-doc-api-references.py'
language: system
files: ^docs/(website/)?guides/(pwa|multi-tenant)\.md$|^python/djust/(pwa/mixins|tenants/mixin)\.py$|^scripts/check-doc-api-references\.py$
pass_filenames: false
# docs/TEMPLATE_BACKEND.md's supported/unsupported tag and filter lists are
# generated from the engine's own registries (the ARITY filter table, the
# parser's tag match arms, python/djust/template_tags/ handlers) against the
# installed Django. This fails when the committed block is stale; run
# `make template-backend-lists` to regenerate. Runs through the venv
# resolver because it imports the compiled `djust._rust` for the handler
# set. See scripts/generate-template-backend-lists.py and
# tests/test_generate_template_backend_lists.py. Closes #2533.
# ADR-034 C4-Q2: the interactive-components reference tables in
# docs/website/api-reference/components.md are generated from the component
# contracts. Run `make interactive-reference` to regenerate.
- repo: local
hooks:
- id: check-interactive-reference
name: check generated interactive-components reference (ADR-034)
entry: bash -c 'PYTHONPATH=python:. bash scripts/run-with-venv-python.sh scripts/generate-interactive-reference.py'
language: system
files: ^(docs/website/api-reference/components\.md|python/djust/components/(_interactive|interactive)\.py|scripts/generate-interactive-reference\.py)$
pass_filenames: false
- repo: local
hooks:
- id: check-template-backend-lists
name: check generated template-backend support lists (#2533)
entry: bash -c 'PYTHONPATH=. bash scripts/run-with-venv-python.sh scripts/generate-template-backend-lists.py'
language: system
files: ^(docs/TEMPLATE_BACKEND\.md|crates/djust_templates/src/(parser|filter_arity)\.rs|python/djust/template_tags/.*\.py|python/djust/template_filters\.py|python/djust/template_libraries\.py|scripts/generate-template-backend-lists\.py|uv\.lock)$
pass_filenames: false
# Lockfile self-entry version sync — catches the drift class #1487 cited
# (`make version` bumps the manifests but a lockfile self-entry stays
# pinned at the old version), plus the SBOM's djust version (#3184).
# Scoped to manifests/lockfiles/SBOM so a routine
# third-party `uv add` does not trigger it. See
# scripts/check-lockfile-versions.py and
# tests/test_check_lockfile_versions.py. Closes #1498.
- repo: local
hooks:
- id: check-lockfile-versions
name: check lockfile self-entry version sync (#1498)
entry: python scripts/check-lockfile-versions.py
language: python
files: ^(pyproject\.toml|Cargo\.toml|uv\.lock|Cargo\.lock|python/djust/djust\.cdx\.json)$
pass_filenames: false
# Refuse a uv.lock that records a developer-local package index. `uv lock`
# writes whichever index it resolved against, so a global ~/.config/uv/uv.toml
# mirror leaks machine-local URLs into a shared file. It fails silently — uv
# installs from the per-wheel `url` fields, so CI stays green — which is why
# it has now leaked twice (e39a9242, then 5fe74931).
- repo: local
hooks:
- id: check-lockfile-registry
name: check uv.lock records a public index
entry: python scripts/check-lockfile-registry.py
language: python
files: ^uv\.lock$
pass_filenames: false
# Pin documented flag defaults to the real default in config.py. The
# virtual_keyed_ops flip (#2017) needed FIVE manual doc sweeps, each of which
# under-counted — and the misses were sentences a few lines from ones just
# edited. Structural check, not a grep: it reads the actual default and
# requires each registered doc site to state the same value.
- repo: local
hooks:
- id: check-flag-default-consistency
name: check documented flag defaults match the code
entry: python scripts/check-flag-default-consistency.py
language: python
files: ^(python/djust/config\.py|crates/djust_vdom/src/diff\.rs|docs/adr/.*\.md|scripts/check-flag-default-consistency\.py)$
pass_filenames: false
# Pre-push hooks (slower checks that run before git push)
- repo: local
hooks:
# Python tests
# PREPEND the current worktree's python/ to PYTHONPATH so a worktree
# push tests the worktree's source, not the MAIN checkout's (the editable
# `djust.pth` points at the main tree). `--worktree-pythonpath` emits the
# path to prepend (empty in the main checkout) and symlinks the matching
# compiled `.so` so `import djust._rust` keeps working. See #1810.
- id: pytest
name: pytest
# #2034 — include python/djust/tests/ so the pre-push suite matches the
# (now-blocking) CI Python job; the explicit paths override pyproject's
# testpaths, which is how this dir was historically absent from both.
# Wrapped (#2139): on failure the wrapper re-runs ONLY the failing
# tests against the merge-base and says which are pre-existing. When
# main is red, every branch's push bounces for a reason that is not
# the branch's, and that distinction is the only thing the pusher
# needs. It cost three failed pushes to derive by hand last time.
entry: bash scripts/pre-push-pytest.sh
language: system
types: [python]
pass_filenames: false
stages: [pre-push]
# Rust tests
- id: cargo-test
name: cargo test
# PYO3_PYTHON must resolve to an EMBEDDABLE interpreter, not just any
# project-venv python: when the venv's base interpreter is uv's
# python-build-standalone, embedded-PyO3 test binaries deterministically
# fail bootstrap (`init_fs_encoding`, baked `sys.prefix='/install'`).
# scripts/embeddable-python.sh resolves a safe interpreter (closes #2072).
# Scoped (#2526) to the crates the pushed range touches (+ their
# dependents); the whole workspace when Cargo.toml/Cargo.lock/djust_core
# changed. See scripts/pre-push-cargo-test.sh and scripts/select-tests.py.
entry: bash scripts/pre-push-cargo-test.sh
language: system
types: [rust]
pass_filenames: false
stages: [pre-push]
# Rust dependency audit
- id: cargo-audit
name: cargo audit
entry: cargo audit
language: system
types: [rust]
pass_filenames: false
stages: [pre-push]
# Flag new `# noqa: F822` annotations in __all__ (Action #146)
# Ruff silences py/undefined-export but CodeQL flags it; prefer
# TYPE_CHECKING-conditional imports (PR #924 pattern). See
# scripts/check-noqa-f822.sh for rationale + override path.
- id: check-noqa-f822
name: check noqa F822
entry: bash scripts/check-noqa-f822.sh
language: system
types: [python]
pass_filenames: false
stages: [pre-push]
# Sweep docs/**/*.md for stale cross-references (#1075)
# Pairs with `make docs-lint` for manual / CI invocation.
- id: docs-lint
name: docs stale-MD-ref check
entry: bash -c 'PYTHONPATH=. bash scripts/run-with-venv-python.sh scripts/docs-lint.py'
language: system
files: ^docs/.*\.md$
pass_filenames: false
stages: [pre-push]
# Flag newly-added private methods with zero callers anywhere (#1209).
# Prevents the dead-code-misleads-investigator failure mode that
# produced `_lazy_serialize_context` (PR #1206 / #1205). Pre-existing
# dead methods are intentionally NOT flagged — only methods added
# in the current branch (vs origin/main). Escape hatch: annotate
# the def line with `# noqa: dead-method-allowed`.
- id: check-no-dead-private-methods
name: check no dead private methods
entry: bash -c 'bash scripts/run-with-venv-python.sh scripts/check-no-dead-private-methods.py'
language: system
types: [python]
pass_filenames: false
stages: [pre-push]
# Flag bare comma-list ``Closes #X, #Y`` in branch commit messages (#1227).
# GitHub's auto-close parser only matches a closing keyword when it
# precedes EACH ref — comma-list shape closes only the FIRST issue.
# PR #1225 + PR #1226 both bit this in 24 hours; the fix is structural.
- id: check-no-comma-list-closes
name: check no comma-list Closes
entry: bash -c 'bash scripts/run-with-venv-python.sh scripts/check-no-comma-list-closes.py'
language: system
always_run: true
pass_filenames: false
stages: [pre-push]
# Django's `{# #}` comment is SINGLE-LINE only. A multi-line one is not a
# comment: the lexer emits it as TEXT, so it renders on the page and any
# tag written inside it *executes*. Three bites in one session — a printed
# comment above the masthead, a doubled `{% block %}` that 500'd the page,
# and an autoescaped selector rendered as body text — none of which raise
# or fail a test. Mechanical, so it gets a mechanical check.
- id: check-template-comments
name: check no multi-line {# #} template comments
entry: bash -c 'bash scripts/run-with-venv-python.sh scripts/check-template-comments.py'
language: system
always_run: true
pass_filenames: false
stages: [pre-commit]
# Cross-reference tag-emit _event defaults against component/mixin
# handler methods. Catches #1275-class (stale/typo'd emit default)
# bugs before they reach CI (#1290).
- id: check-handler-contracts
name: check handler contracts
entry: bash -c 'bash scripts/run-with-venv-python.sh scripts/check-handler-contracts.py'
language: system
always_run: true
pass_filenames: false
stages: [pre-push]