chore: repository health check (tests, security, deps, CI) - #494
shenxianpeng wants to merge 9 commits into
Conversation
Cover docs/gen_io_doc.py (the generated inputs-outputs page) and docs/badge_hook.py (the md:* badges) with pytest, including the checks that every action input and output is documented. Add a test dependency group, pytest and coverage settings, and a workflow that runs the tests when the action metadata, the docs scripts or the tests change.
…page gen_io_doc.py printed `::warning file=docs/action.ymltitle=...` when an input or output had no minimum-version: without the comma between the properties GitHub does not create the intended annotation. The error for an undocumented field also named actions.yml instead of action.yml. Add regression tests for both messages.
`$'...'` strings do not process escapes in nushell, so the step wrote `key=<hash>\n` with a literal backslash-n into $GITHUB_OUTPUT and every cache key ended in `\n` (visible in the actions/cache logs). Use a double-quoted interpolation so the line ends with a real newline. Existing caches are not restored once, as their keys change.
The nu scripts in action.yml expanded `${{ inputs.* }}` (and a few
`github.*` values) directly into their source, so an input was parsed as
nushell code. A value with a quote or a backslash could break a step, e.g.
`auto-fix-commit-msg: "style: apply clang-format's fixes"` failed with
"Unbalanced delimiter" and `style: file:C:\cfg\.clang-format` failed with
"unrecognized escape", and an input could change what the script runs.
Map every input a script uses to an INPUT_* variable in the step's env:
and read it from there. Each value is still passed as one argument, as
before: `extra-args` stays a single `--extra-arg=...` that cpp-linter
splits on spaces, and an empty `version` still runs the LLVM install
script without arguments.
The repository's default GITHUB_TOKEN permissions are read-write, and cpp-linter.yml, mkdocs-deploy.yml and pre-commit.yml did not set any, so their jobs ran with a write token. Grant each job only what it uses: read access to the contents, plus pages: write and id-token: write for the docs deployment, as the other workflows already do. In release.yml, pass the workflow_dispatch `tag` input through an environment variable instead of expanding it into the script, and drop the `branches` filter that GitHub ignores for release events (actionlint error). The rolling tag's message now names the tag; it read "Retag $MAJOR_VERSION" because the variable sat in single quotes.
Both were pinned to year-old releases (nushell 0.106.1, uv 0.9.5). The uv.lock file is up to date for uv 0.12.21 (`uv lock --check`), and none of the breaking changes in uv 0.10 to 0.12 apply to the `uv sync` and `uv run` calls the action makes.
The action downloads uv's install script for the pinned UV_VERSION and runs it. Pin the SHA256 of the shell and PowerShell installers next to UV_VERSION and stop with an error annotation if the downloaded script does not match. For uv 0.12.21 the shell installer also checks the SHA256 of the uv archive it downloads (0.9.5 printed "no checksums to verify"), so on Linux and macOS the whole chain is verified.
Dependabot only bumps the direct dependencies, so the packages they pull in had not moved since the lock file was created. Upgrade every indirect dependency in uv.lock (direct ones are left to Dependabot). For the action itself this updates certifi, cffi, charset-normalizer, idna, pycparser, pygit2 (1.20.1 on Python >= 3.11), requests and urllib3. It also picks up fixes for published advisories: urllib3 2.8.0 (installed by the action), click 8.5.0 and pygments 2.21.0 (docs), and virtualenv 21.14.2 (dev). cpp-linter 1.14.1 still imports the pygit2 constants it uses with pygit2 1.20.1, and parses diffs the same way.
`uv sync` re-resolves and rewrites uv.lock when it does not match pyproject.toml, which would install whatever versions are current at run time instead of the reviewed, hash-pinned ones. Pass `--locked` so a stale lock file fails the step instead. With a lock file that matches (as the released action always ships), nothing changes.
Cpp-Linter Report
|
Summary
A repository health check: unit tests for the docs scripts, hardening of
action.ymland the workflows, toolchain and lock file updates, and three bug fixes found along the way. There is one commit per topic. The action's inputs, outputs and behavior stay the same, except where a fix below says otherwise.Coverage
docs/*.pyis the only Python code in the repository; the action's own logic is nushell insideaction.yml.docs/badge_hook.pyanddocs/gen_io_doc.pyhave 100% line and branch coverage (coverage.py withbranch = trueandsource = ["docs"]). The only exclusion is onepragma: no coveron the Windows fallback of a test fixture.Tests added
tests/test_gen_io_doc.pyruns the script the way mkdocs-gen-files does. It checks that every input and output inaction.ymlis documented with its version, default and description. It also covers how defaults, the experimental flag and the permission badges are rendered, the annotations for undocumented fields and missingminimum-versionentries, and that incomplete metadata fails the build.tests/test_badge_hook.pycovers every badge type, markers matched without case, and the error paths.testdependency grouppyproject.tomlRun testsworkflow that runs whenaction.yml,docs/*.py,docs/action.yml,tests/,pyproject.tomloruv.lockchangeuv run --group test pytest)Bug fixes
gen_io_doc.pyannotations: the script printed::warning file=docs/action.ymltitle=.... Without the comma between the properties, a missingminimum-versionnever became a proper annotation. The error for an undocumented field also saidactions.ymlinstead ofaction.yml. Regression tests are added.key=<hash>\nwith a literal backslash-n, because$'...'strings do not process escapes in nushell. Every cache key ended in\n(visible in the actions/cache logs). Existing caches will miss once.auto-fix-commit-msg: "style: apply clang-format's fixes"failed with "Unbalanced delimiter", andstyle: file:C:\cfg\.clang-formatfailed with "unrecognized escape". The environment variable change below fixes this.Security hardening
action.ymlinputs: the nushell scripts now read every${{ inputs.* }}value, and thegithub.*values they use, fromINPUT_*andPR_*environment variables instead of having them expanded into the script text.extra-argsstays one--extra-arg=...that cpp-linter splits on spaces, and an emptyversionstill runs the LLVM script without arguments.action.ymlgo from 45 to 0.uv sync --locked: the action now installs exactly what the hash-pinned lock file lists. A stale lock file fails the step instead of being re-resolved at run time.cpp-linter.yml,mkdocs-deploy.ymlandpre-commit.ymlnow declare least-privilege permissions, as the other workflows here and in cpp-linter/cpp-linter already do:contents: read, pluspages: writeandid-token: writefor the docs deployment.release.yml:workflow_dispatchtaginput now reaches the script through an environment variable.branchesfilter is removed; GitHub ignores it forreleaseevents, and actionlint reported it as an error.Retag v2instead of the literalRetag $MAJOR_VERSION.unpinned-usesfor the org's reusable workflows andcpp-linter-action@main, which is the org's conventionself-repositoryhintsdependabot-cooldown(see the decisions below)asserts in the docs generator.Dependency updates
action.yml, and Dependabot does not manage them.uv.lock: all indirect dependencies are refreshed, because Dependabot only bumps direct ones. Direct dependencies are left to Dependabot.CI warning fixes
release.ymland the malformed annotation fromgen_io_doc.py.categories[*].labels,version-resolver.*.labels,exclude-labels). They come fromcpp-linter/.github/.github/release-drafter.yml.properdocs, which mkdocs-gen-files 0.6.1 pulls in) and a Material for MkDocs notice about MkDocs 2.0.actions/deploy-pagesemits a NodepunycodeDeprecationWarning. These come from the org'smkdocs.ymlreusable workflow and upstream projects.ubuntu-latestmoves to Ubuntu 26.04 from 2026-10-19. The self-test usesubuntu-lateston purpose; its first runs on the new image are worth watching, because olderclang-format-Napt packages may be missing.Items needing a maintainer decision
https://apt.llvm.org/llvm.shis downloaded and run with sudo without a pin. Pinning it would break when upstream updates the script, and vendoring it would need an update for every new LLVM release.hustcer/setup-nudoes not check the nushell download.astral-sh/setup-uv: it would check uv on every platform, but it puts uv on the user's PATH, which the docs say the action does not do.dependabot-cooldownasks for at least 7 days. uv has 3, and github-actions uses the default.fail_under = 100in[tool.coverage.report]would keep them there.requires-python = ">=3.10": Python 3.10 reaches end of life this month. The action runs on 3.12 (.python-version), so this only affects how the lock is resolved.How it was verified
uv run --group test coverage run -m pytestgives 25 passed with 100% line and branch coverage, and every commit passes on its own.pre-commit run --all-files,actionlint(workflows and examples) andzizmorpass.mkdocs build --strictsucceeds; the pages are identical apart from the CONTRIBUTING note and Pygments no longer escaping'.action.yml: a local harness (not committed) ran every nushell step of the old and newaction.ymlin an Ubuntu 24.04 container under nushell 0.106.1 and 0.116.0, for 44 input scenarios. Stubuv,apt-get,brewandsudocommands recorded the arguments they received.$, backticks, parentheses, backslashes, quotes, newlines and Unicode.uv sync --locked, clang-tools 18 from apt, and cpp-linter ondocs/examples/demo, for a push event and apull_requestevent (REST diff plus pygit2 1.20.1).main, apart from the cache key.