repomatic 7.5.0__tar.gz → 7.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {repomatic-7.5.0 → repomatic-7.7.0}/PKG-INFO +3 -3
- {repomatic-7.5.0 → repomatic-7.7.0}/pyproject.toml +11 -5
- {repomatic-7.5.0 → repomatic-7.7.0}/pyproject.toml.orig +35 -9
- {repomatic-7.5.0 → repomatic-7.7.0}/readme.md +1 -1
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/__init__.py +2 -2
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/__main__.py +9 -8
- repomatic-7.7.0/repomatic/awesome_toc.py +277 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/binaries_page.py +36 -68
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/binary.py +119 -7
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/broken_links.py +34 -14
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/cache.py +51 -7
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/changelog.py +257 -117
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/checksums.py +11 -5
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/cli.py +231 -312
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/config.py +37 -6
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/_release-engine.yaml +66 -37
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/action-publish-pypi.yaml +1 -1
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/autofix.yaml +77 -79
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/cancel-runs.yaml +2 -2
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/changelog.yaml +27 -18
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/debug.yaml +4 -4
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/docs.yaml +9 -7
- repomatic-7.7.0/repomatic/data/labeller-content-based.yaml +31 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/labels.yaml +17 -15
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/lint.yaml +26 -22
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/release.yaml +47 -1
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/awesome-triage/SKILL.md +10 -10
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/babysit-ci/SKILL.md +10 -4
- repomatic-7.7.0/repomatic/data/skills/github-housekeeping/SKILL.md +129 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/repomatic-audit/SKILL.md +1 -1
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/repomatic-ship/SKILL.md +18 -2
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/tests.yaml +12 -9
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/unsubscribe.yaml +2 -2
- repomatic-7.7.0/repomatic/data/vt-trend-chart.js +52 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/dep_graph.py +14 -10
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/dep_report.py +139 -64
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/dep_sources.py +45 -57
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/docs.py +28 -16
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/git_ops.py +12 -5
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/actions.py +24 -1
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/dev_release.py +30 -7
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/gh.py +24 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/issue.py +96 -15
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/matrix.py +35 -6
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/release_sync.py +8 -27
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/releases.py +6 -22
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/sponsor.py +31 -24
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/token.py +26 -13
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/unsubscribe.py +9 -27
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/workflow_sync.py +292 -242
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/images.py +68 -60
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/init_project.py +357 -178
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/labels.py +69 -28
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/lint_repo.py +706 -56
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/mailmap.py +24 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/matrix_axes.py +0 -7
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/metadata.py +194 -136
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/npm.py +5 -1
- repomatic-7.7.0/repomatic/plugin.py +349 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/prepare_release.py +108 -66
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/pypi.py +94 -47
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/pyproject.py +46 -2
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/registry.py +110 -13
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/setup_guide.py +19 -8
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/sync_ops.py +223 -31
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/yanked-admonition.md +2 -2
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/tool_registry.py +119 -13
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/tool_runner.py +182 -127
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/tool_runner_page.py +7 -19
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/uv.py +199 -106
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/version_sync.py +106 -8
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/virustotal.py +9 -2
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/vulnerable_deps.py +29 -12
- repomatic-7.5.0/repomatic/data/labeller-content-based.yaml +0 -37
- {repomatic-7.5.0 → repomatic-7.7.0}/license +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/bundle.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/compat.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/__init__.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/agent-grunt-qa.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/agent-qa-engineer.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/agent-sphinx-docs.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/autolock.yaml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/new-link.yaml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/.github/code-of-conduct.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/.github/contributing.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/.github/contributing.zh.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/.github/funding.yml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/.github/pull_request_template.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/__init__.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/awesome_template/license +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/bumpversion.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/codecov.yaml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/labeller-file-based.yaml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/labels.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/lychee.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/mdformat.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/mypy.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/pytest.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/ruff.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/av-false-positive/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/benchmark-update/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/brand-assets/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/file-bug-report/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/repomatic-changelog/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/repomatic-deps/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/repomatic-init/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/repomatic-topics/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/sphinx-docs-sync/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/translation-sync/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/skills/upstream-audit/SKILL.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/typos.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/uv.toml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/yamllint.yaml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/data/zizmor.yaml +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/frontmatter.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/__init__.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/pr_body.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/github/status.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/gitignore.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/http.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/humanize.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/py.typed +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/__init__.py +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/available-admonition.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/broken-links-issue.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/bump-version.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/detect-squash-merge.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/development-warning.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/fix-changelog.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/fix-typos.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/fix-vulnerable-deps.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/format-images.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/format-json.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/format-markdown.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/format-pyproject.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/format-python.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/format-shell.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/generated-footer.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/github-releases.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/immutable-releases.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/pr-metadata.md.noformat +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/prepare-release.md.noformat +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/refresh-tip.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/release-notes.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/release-sync-report.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-branch-ruleset.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-dependabot.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-fork-pr-approval.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-notifications-pat.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-pages-source.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-pypi-trusted-publisher.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-sha-pinning-required.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-token.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-verify.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide-virustotal.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/setup-guide.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-action-pins.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-bumpversion.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-dep-sources.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-gitignore.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-mailmap.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-repomatic.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-tool-versions.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-uv-lock.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/sync-workflow-pins.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/unavailable-admonition.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/unsubscribe-phase1.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/unsubscribe-phase2.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/update-dep-graph.md +0 -0
- {repomatic-7.5.0 → repomatic-7.7.0}/repomatic/templates/update-docs.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repomatic
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.7.0
|
|
4
4
|
Summary: 🏭 Automate repository maintenance, releases, and CI/CD workflows
|
|
5
5
|
Keywords: changelog-formatter,dependabot-alternative,github-actions,github-labels,github-workflow,gitignore,mailmap,mypy,nuitka,python-automation,release-automation,repository-automation,repository-management,reusable-workflows,software-factory,sphinx-doc,typo,uv,workflow-reusable
|
|
6
6
|
Author: Kevin Deldycke
|
|
@@ -59,7 +59,7 @@ Requires-Dist: wcmatch>=10
|
|
|
59
59
|
Requires-Python: >=3.10
|
|
60
60
|
Project-URL: Changelog, https://github.com/kdeldycke/repomatic/blob/main/changelog.md
|
|
61
61
|
Project-URL: Documentation, https://kdeldycke.github.io/repomatic
|
|
62
|
-
Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.
|
|
62
|
+
Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.7.0
|
|
63
63
|
Project-URL: Funding, https://github.com/sponsors/kdeldycke
|
|
64
64
|
Project-URL: Homepage, https://github.com/kdeldycke/repomatic
|
|
65
65
|
Project-URL: Issues, https://github.com/kdeldycke/repomatic/issues
|
|
@@ -145,7 +145,7 @@ See the **[full documentation](https://kdeldycke.github.io/repomatic/)** for:
|
|
|
145
145
|
- [CLI parameters](https://kdeldycke.github.io/repomatic/cli.html)
|
|
146
146
|
- [Reusable workflow reference](https://kdeldycke.github.io/repomatic/workflows.html) (all 14 workflows with job descriptions)
|
|
147
147
|
- [Security practices and token setup](https://kdeldycke.github.io/repomatic/security.html)
|
|
148
|
-
- [Claude Code skills](https://kdeldycke.github.io/repomatic/skills.html)
|
|
148
|
+
- [Claude Code skills](https://kdeldycke.github.io/repomatic/skills.html) and [agents](https://kdeldycke.github.io/repomatic/agents.html), also installable as a [Claude Code plugin](https://kdeldycke.github.io/repomatic/plugin.html)
|
|
149
149
|
- [API reference](https://kdeldycke.github.io/repomatic/repomatic.html)
|
|
150
150
|
- [Project history](https://kdeldycke.github.io/repomatic/history.html)
|
|
151
151
|
|
|
@@ -4,7 +4,7 @@ requires = ["uv-build>=0.8"]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "repomatic"
|
|
7
|
-
version = "7.
|
|
7
|
+
version = "7.7.0"
|
|
8
8
|
description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
9
9
|
readme = "readme.md"
|
|
10
10
|
keywords = [
|
|
@@ -92,7 +92,7 @@ email = "kevin@deldycke.com"
|
|
|
92
92
|
[project.urls]
|
|
93
93
|
Changelog = "https://github.com/kdeldycke/repomatic/blob/main/changelog.md"
|
|
94
94
|
Documentation = "https://kdeldycke.github.io/repomatic"
|
|
95
|
-
Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.
|
|
95
|
+
Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.7.0"
|
|
96
96
|
Funding = "https://github.com/sponsors/kdeldycke"
|
|
97
97
|
Homepage = "https://github.com/kdeldycke/repomatic"
|
|
98
98
|
Issues = "https://github.com/kdeldycke/repomatic/issues"
|
|
@@ -146,7 +146,7 @@ module-root = ""
|
|
|
146
146
|
[tool.nuitka]
|
|
147
147
|
copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed under GPL-2.0-or-later license."
|
|
148
148
|
file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
149
|
-
file-version = "7.
|
|
149
|
+
file-version = "7.7.0"
|
|
150
150
|
include-data-dir = [
|
|
151
151
|
"repomatic/data/awesome_template=repomatic/data/awesome_template",
|
|
152
152
|
"repomatic/data/skills=repomatic/data/skills",
|
|
@@ -156,7 +156,7 @@ include-package-data = ["click_extra"]
|
|
|
156
156
|
linux-icon = "docs/assets/icon.png"
|
|
157
157
|
macos-app-icon = "docs/assets/icon.icns"
|
|
158
158
|
product-name = "Repomatic"
|
|
159
|
-
product-version = "7.
|
|
159
|
+
product-version = "7.7.0"
|
|
160
160
|
windows-icon-from-ico = "docs/assets/icon.ico"
|
|
161
161
|
|
|
162
162
|
[tool.ruff]
|
|
@@ -205,8 +205,10 @@ Typescript = "TypeScript"
|
|
|
205
205
|
|
|
206
206
|
[tool.typos.default.extend-words]
|
|
207
207
|
astroid = "astroid"
|
|
208
|
+
Contribuer = "Contribuer"
|
|
208
209
|
oject = "oject"
|
|
209
210
|
PNGs = "PNGs"
|
|
211
|
+
quater = "quater"
|
|
210
212
|
sur = "sur"
|
|
211
213
|
|
|
212
214
|
[tool.mypy]
|
|
@@ -247,7 +249,7 @@ source = ["repomatic"]
|
|
|
247
249
|
precision = 2
|
|
248
250
|
|
|
249
251
|
[tool.bumpversion]
|
|
250
|
-
current_version = "7.
|
|
252
|
+
current_version = "7.7.0"
|
|
251
253
|
parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.dev(?P<dev>\d+))?'
|
|
252
254
|
serialize = [
|
|
253
255
|
"{major}.{minor}.{patch}.dev{dev}",
|
|
@@ -332,9 +334,13 @@ pypi-package-history = [
|
|
|
332
334
|
"gha-utils",
|
|
333
335
|
"repokit",
|
|
334
336
|
]
|
|
337
|
+
release-assets = ["repomatic-claude-plugin.zip"]
|
|
335
338
|
|
|
336
339
|
[tool.repomatic.changelog]
|
|
337
340
|
archive-location = "./docs/changelog-archive.md"
|
|
338
341
|
|
|
339
342
|
[tool.repomatic.manpages]
|
|
340
343
|
script = "repomatic.cli:repomatic"
|
|
344
|
+
|
|
345
|
+
[tool.actionlint.paths."**/workflows/*"]
|
|
346
|
+
ignore = ['property "job_workflow_sha" is not defined']
|
|
@@ -12,7 +12,7 @@ requires = [ "uv-build>=0.8" ]
|
|
|
12
12
|
[project]
|
|
13
13
|
# Docs: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
|
|
14
14
|
name = "repomatic"
|
|
15
|
-
version = "7.
|
|
15
|
+
version = "7.7.0"
|
|
16
16
|
description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
17
17
|
readme = "readme.md"
|
|
18
18
|
keywords = [
|
|
@@ -93,8 +93,8 @@ dependencies = [
|
|
|
93
93
|
"boltons>=25",
|
|
94
94
|
# click-extra 8.8.0 is the floor: repomatic delegates to the utilities absorbed
|
|
95
95
|
# there — `format_size` (byte sizes in the image and cache reports), the
|
|
96
|
-
# `
|
|
97
|
-
#
|
|
96
|
+
# `parse_friendly_duration`/`parse_iso8601_duration` pair (uv cooldown spans
|
|
97
|
+
# in `uv.py`/`version_sync.py`), `prep_path`/`is_stdout`/`STDOUT_SENTINEL`
|
|
98
98
|
# (CLI `--output` handling), and `config_table_to_flags` (tool flag
|
|
99
99
|
# translation). Earlier floors remain in play: `binaries_page` imports
|
|
100
100
|
# `click_extra.blocks` (`replace_region`, 8.7.1) to splice the binaries page
|
|
@@ -145,7 +145,7 @@ dependencies = [
|
|
|
145
145
|
]
|
|
146
146
|
urls.Changelog = "https://github.com/kdeldycke/repomatic/blob/main/changelog.md"
|
|
147
147
|
urls.Documentation = "https://kdeldycke.github.io/repomatic"
|
|
148
|
-
urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.
|
|
148
|
+
urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.7.0"
|
|
149
149
|
urls.Funding = "https://github.com/sponsors/kdeldycke"
|
|
150
150
|
urls.Homepage = "https://github.com/kdeldycke/repomatic"
|
|
151
151
|
urls.Issues = "https://github.com/kdeldycke/repomatic/issues"
|
|
@@ -250,7 +250,7 @@ copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed u
|
|
|
250
250
|
file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
251
251
|
# Numeric only: Nuitka rejects PEP 440 .devN suffixes. Kept in sync with the
|
|
252
252
|
# package version by the dedicated [tool.bumpversion] rule that strips the suffix.
|
|
253
|
-
file-version = "7.
|
|
253
|
+
file-version = "7.7.0"
|
|
254
254
|
include-data-dir = [
|
|
255
255
|
"repomatic/data/awesome_template=repomatic/data/awesome_template",
|
|
256
256
|
"repomatic/data/skills=repomatic/data/skills",
|
|
@@ -269,7 +269,7 @@ include-package-data = [ "click_extra" ]
|
|
|
269
269
|
linux-icon = "docs/assets/icon.png"
|
|
270
270
|
macos-app-icon = "docs/assets/icon.icns"
|
|
271
271
|
product-name = "Repomatic"
|
|
272
|
-
product-version = "7.
|
|
272
|
+
product-version = "7.7.0"
|
|
273
273
|
windows-icon-from-ico = "docs/assets/icon.ico"
|
|
274
274
|
|
|
275
275
|
[tool.ruff]
|
|
@@ -309,8 +309,12 @@ output-prefer-rule-codes = true
|
|
|
309
309
|
|
|
310
310
|
[tool.typos]
|
|
311
311
|
default.extend-identifiers = { Github = "GitHub", IOS = "iOS", Javascript = "JavaScript", MacOS = "macOS", PNGs = "PNGs", PyPi = "PyPI", Typescript = "TypeScript" }
|
|
312
|
-
# False positives.
|
|
313
|
-
|
|
312
|
+
# False positives. "Contribuer" is the French for "Contributing", part of the
|
|
313
|
+
# translated-readme fixture in tests/test_awesome_toc.py. "quater" is the fourth
|
|
314
|
+
# term of the Latin ordinal series numbering the repo-lint checks (bis, ter,
|
|
315
|
+
# quater), not a misspelling of "quarter": the unattended fix-typos job rewrote
|
|
316
|
+
# it once already and had to be reverted by hand.
|
|
317
|
+
default.extend-words = { astroid = "astroid", Contribuer = "Contribuer", oject = "oject", PNGs = "PNGs", quater = "quater", sur = "sur" }
|
|
314
318
|
# Skip content between <!-- typos:off --> and <!-- typos:on --> markers.
|
|
315
319
|
default.extend-ignore-re = [
|
|
316
320
|
"(?s)<!-- typos:off -->.*?<!-- typos:on -->",
|
|
@@ -394,7 +398,7 @@ run.source = [ "repomatic" ]
|
|
|
394
398
|
report.precision = 2
|
|
395
399
|
|
|
396
400
|
[tool.bumpversion]
|
|
397
|
-
current_version = "7.
|
|
401
|
+
current_version = "7.7.0"
|
|
398
402
|
# Parse versions with an optional .devN suffix (PEP 440).
|
|
399
403
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?"
|
|
400
404
|
serialize = [
|
|
@@ -517,3 +521,25 @@ exclude = [ "workflows" ]
|
|
|
517
521
|
# the HTML docs site stay in lockstep.
|
|
518
522
|
manpages.script = "repomatic.cli:repomatic"
|
|
519
523
|
pypi-package-history = [ "gha-utils", "repokit" ]
|
|
524
|
+
# Claude Code plugin archive packed by release.yaml's pack-plugin job and
|
|
525
|
+
# attached to every GitHub release by the engine's extra-assets job. The
|
|
526
|
+
# marketplace at .claude-plugin/marketplace.json installs the plugin straight
|
|
527
|
+
# from this asset, so a release missing it breaks `/plugin install`.
|
|
528
|
+
release-assets = [ "repomatic-claude-plugin.zip" ]
|
|
529
|
+
|
|
530
|
+
[tool.actionlint]
|
|
531
|
+
# actionlint reads no pyproject section of its own: repomatic serializes this one
|
|
532
|
+
# to a temporary YAML config and passes it via `--config-file`. `paths` keys are
|
|
533
|
+
# globs relative to the repository root, and each `ignore` entry is a regular
|
|
534
|
+
# expression matched against the error message.
|
|
535
|
+
#
|
|
536
|
+
# `github.job_workflow_sha` is a real context property, holding the commit SHA of
|
|
537
|
+
# the reusable workflow file itself, which the tool-cache keys read so they do not
|
|
538
|
+
# rotate on every caller commit. GitHub documents it only as an OIDC token claim,
|
|
539
|
+
# never on the `github` context reference page, so actionlint's schema lacks it.
|
|
540
|
+
# Drop this entry once rhysd/actionlint#671 ships in a release.
|
|
541
|
+
#
|
|
542
|
+
# The glob carries no dot on purpose: click-extra expands every dotted key of
|
|
543
|
+
# `pyproject.toml`, and one starting with a dot makes it warn on each repomatic
|
|
544
|
+
# invocation. `**` matches dot-directories, so `.github/workflows/` is covered.
|
|
545
|
+
paths."**/workflows/*".ignore = [ 'property "job_workflow_sha" is not defined' ]
|
|
@@ -77,7 +77,7 @@ See the **[full documentation](https://kdeldycke.github.io/repomatic/)** for:
|
|
|
77
77
|
- [CLI parameters](https://kdeldycke.github.io/repomatic/cli.html)
|
|
78
78
|
- [Reusable workflow reference](https://kdeldycke.github.io/repomatic/workflows.html) (all 14 workflows with job descriptions)
|
|
79
79
|
- [Security practices and token setup](https://kdeldycke.github.io/repomatic/security.html)
|
|
80
|
-
- [Claude Code skills](https://kdeldycke.github.io/repomatic/skills.html)
|
|
80
|
+
- [Claude Code skills](https://kdeldycke.github.io/repomatic/skills.html) and [agents](https://kdeldycke.github.io/repomatic/agents.html), also installable as a [Claude Code plugin](https://kdeldycke.github.io/repomatic/plugin.html)
|
|
81
81
|
- [API reference](https://kdeldycke.github.io/repomatic/repomatic.html)
|
|
82
82
|
- [Project history](https://kdeldycke.github.io/repomatic/history.html)
|
|
83
83
|
|
|
@@ -35,21 +35,22 @@ from repomatic.cli import repomatic
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def main():
|
|
38
|
-
"""Execute the CLI
|
|
38
|
+
"""Execute the CLI under its canonical name, whatever the entry point.
|
|
39
|
+
|
|
40
|
+
Without `prog_name`, Click derives the displayed name from the invocation:
|
|
39
41
|
|
|
40
42
|
```{code-block} shell-session
|
|
41
43
|
$ python -m repomatic --version
|
|
42
44
|
python -m repomatic, version 4.0.0
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- plain inline package call: `python -m repomatic`,
|
|
48
|
-
- Poetry's script entry point: `repomatic = 'repomatic.__main__:main`,
|
|
49
|
-
- Nuitka's main module invocation requirement:
|
|
50
|
-
`python -m nuitka (...) repomatic/__main__.py`
|
|
47
|
+
The `main()` indirection lets three invocation styles share one entry
|
|
48
|
+
point rendering the same name:
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
- the plain module call: `python -m repomatic`,
|
|
51
|
+
- the `[project.scripts]` console script: `repomatic = "repomatic.__main__:main"`,
|
|
52
|
+
- Nuitka's main-module compilation requirement:
|
|
53
|
+
`python -m nuitka (...) repomatic/__main__.py`.
|
|
53
54
|
"""
|
|
54
55
|
repomatic(prog_name=repomatic.name)
|
|
55
56
|
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
|
|
2
|
+
#
|
|
3
|
+
# This program is Free Software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of the GNU General Public License
|
|
5
|
+
# as published by the Free Software Foundation; either version 2
|
|
6
|
+
# of the License, or (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program; if not, write to the Free Software
|
|
15
|
+
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
16
|
+
|
|
17
|
+
"""Remove the table-of-contents entries awesome-lint forbids.
|
|
18
|
+
|
|
19
|
+
Backs the `fix-awesome-toc` command, which runs on `awesome-*` repositories
|
|
20
|
+
right after `repomatic run mdformat` regenerates the ToC of every readme.
|
|
21
|
+
|
|
22
|
+
```{note}
|
|
23
|
+
This is the one operation that has no job, PR branch or template of its own,
|
|
24
|
+
against the rule in `claude.md` § Naming conventions for automated operations.
|
|
25
|
+
It corrects what `format-markdown` just wrote, so it has to share that job's
|
|
26
|
+
working tree: given its own job, the two would land in separate PRs and undo
|
|
27
|
+
each other on every push, `format-markdown` re-adding the entries this command
|
|
28
|
+
had removed.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`mdformat-toc` lists every heading in range and offers no exclusion mechanism
|
|
32
|
+
of its own, so the entries have to be deleted afterwards. Upstream tracks the
|
|
33
|
+
feature in [hukkin/mdformat-toc#17](https://github.com/hukkin/mdformat-toc/issues/17)
|
|
34
|
+
and [hukkin/mdformat-toc#20](https://github.com/hukkin/mdformat-toc/pull/20);
|
|
35
|
+
this module can go once either lands and grows a way to express the exclusion
|
|
36
|
+
in the ToC marker.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
import re
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
TYPE_CHECKING = False
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
48
|
+
|
|
49
|
+
FORBIDDEN_HEADINGS: tuple[str, ...] = (
|
|
50
|
+
"Contents",
|
|
51
|
+
"Contributing",
|
|
52
|
+
"Footnotes",
|
|
53
|
+
"Related Lists",
|
|
54
|
+
)
|
|
55
|
+
"""Headings awesome-lint refuses to see listed in the table of contents.
|
|
56
|
+
|
|
57
|
+
Mirrors the roster in
|
|
58
|
+
[awesome-lint's `toc.js`](https://github.com/sindresorhus/awesome-lint/blob/v2.2.2/rules/toc.js#L14-L18),
|
|
59
|
+
plus the heading owning the ToC itself (`Contents`), whose entry trips
|
|
60
|
+
`remark-lint:awesome-toc`:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
✖ 26:1 ToC item "Contents" does not match corresponding heading "Meta"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
These are the English names, the only ones awesome-lint knows. A translated
|
|
67
|
+
readme names the same sections in its own language, which is why matching on
|
|
68
|
+
this roster alone is not enough: see {func}`forbidden_headings_for`.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
README_RE = re.compile(r"^readme(\.[^.]+)?\.md$")
|
|
72
|
+
"""Match `readme.md` and every `readme.{lang}.md` translation beside it.
|
|
73
|
+
|
|
74
|
+
Only the repository root is scanned. The `find ./` this replaced walked the
|
|
75
|
+
whole tree, which on a checkout carrying a `node_modules/` directory would
|
|
76
|
+
have reached a few hundred vendored readmes.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
REFERENCE_README = "readme.md"
|
|
80
|
+
"""The English readme, whose heading positions every translation is mapped onto."""
|
|
81
|
+
|
|
82
|
+
HEADING_RE = re.compile(r"^\#{1,6}[ \t]+(?P<text>.+?)[ \t]*\#*[ \t]*$", re.MULTILINE)
|
|
83
|
+
"""Match an ATX heading and capture its text, closing sequence excluded."""
|
|
84
|
+
|
|
85
|
+
TOC_ENTRY_RE = re.compile(r"^[ \t]*- \[(?P<text>.+)\]\(#[^)]*\)$")
|
|
86
|
+
"""Match one `mdformat-toc` list entry and capture its link text."""
|
|
87
|
+
|
|
88
|
+
TOC_START_RE = re.compile(r"^<!--\s*mdformat-toc\s+start\b.*-->$", re.IGNORECASE)
|
|
89
|
+
"""Match the opening marker of an `mdformat-toc` block."""
|
|
90
|
+
|
|
91
|
+
TOC_END_RE = re.compile(r"^<!--\s*mdformat-toc\s+end\s*-->$", re.IGNORECASE)
|
|
92
|
+
"""Match the closing marker of an `mdformat-toc` block."""
|
|
93
|
+
|
|
94
|
+
FENCE_RE = re.compile(r"^[ \t]*(?P<fence>`{3,}|~{3,})")
|
|
95
|
+
"""Match the delimiter of a fenced code block."""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _strip_fenced_blocks(content: str) -> str:
|
|
99
|
+
"""Blank out fenced code blocks so their `#` lines are not read as headings.
|
|
100
|
+
|
|
101
|
+
Replaces the content of every fence with empty lines, keeping the line
|
|
102
|
+
count intact so positions elsewhere are unaffected.
|
|
103
|
+
|
|
104
|
+
:param content: The full Markdown document.
|
|
105
|
+
:return: The document with fenced blocks blanked out.
|
|
106
|
+
"""
|
|
107
|
+
lines = content.splitlines()
|
|
108
|
+
kept: list[str] = []
|
|
109
|
+
fence: str | None = None
|
|
110
|
+
for line in lines:
|
|
111
|
+
match = FENCE_RE.match(line)
|
|
112
|
+
if fence is None:
|
|
113
|
+
if match:
|
|
114
|
+
fence = match.group("fence")[0]
|
|
115
|
+
kept.append("")
|
|
116
|
+
continue
|
|
117
|
+
elif match and match.group("fence")[0] == fence:
|
|
118
|
+
fence = None
|
|
119
|
+
kept.append("")
|
|
120
|
+
continue
|
|
121
|
+
kept.append("" if fence else line)
|
|
122
|
+
return "\n".join(kept)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def headings(content: str) -> list[str]:
|
|
126
|
+
"""List the ATX heading texts of a Markdown document, in document order.
|
|
127
|
+
|
|
128
|
+
:param content: The full Markdown document.
|
|
129
|
+
:return: Every heading text, code fences excluded.
|
|
130
|
+
"""
|
|
131
|
+
return [m.group("text") for m in HEADING_RE.finditer(_strip_fenced_blocks(content))]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def forbidden_headings_for(
|
|
135
|
+
content: str,
|
|
136
|
+
reference_headings: Sequence[str] | None = None,
|
|
137
|
+
) -> set[str]:
|
|
138
|
+
"""Resolve which heading texts must not appear in *content*'s ToC.
|
|
139
|
+
|
|
140
|
+
Always includes the English {data}`FORBIDDEN_HEADINGS` that awesome-lint
|
|
141
|
+
knows, since a translation routinely leaves some of them untranslated.
|
|
142
|
+
|
|
143
|
+
On top of that, when *reference_headings* is given and the document has the
|
|
144
|
+
same number of headings, the heading occupying each forbidden position in
|
|
145
|
+
the reference is forbidden here too. That positional mapping is what carries
|
|
146
|
+
the rule across languages: repomatic cannot know that `贡献` translates
|
|
147
|
+
`Contributing`, but it can see that both sit at the same index of a readme
|
|
148
|
+
and its translation. Headings survive formatting untouched, so the mapping
|
|
149
|
+
holds even against an already-stripped reference.
|
|
150
|
+
|
|
151
|
+
:param content: The Markdown document to resolve the roster for.
|
|
152
|
+
:param reference_headings: Headings of {data}`REFERENCE_README`, if any.
|
|
153
|
+
:return: The heading texts whose ToC entry must be deleted.
|
|
154
|
+
"""
|
|
155
|
+
forbidden = set(FORBIDDEN_HEADINGS)
|
|
156
|
+
if not reference_headings:
|
|
157
|
+
return forbidden
|
|
158
|
+
|
|
159
|
+
own_headings = headings(content)
|
|
160
|
+
if len(own_headings) != len(reference_headings):
|
|
161
|
+
logging.warning(
|
|
162
|
+
f"Heading count differs from {REFERENCE_README} "
|
|
163
|
+
f"({len(own_headings)} vs {len(reference_headings)}): "
|
|
164
|
+
"falling back to English heading names only."
|
|
165
|
+
)
|
|
166
|
+
return forbidden
|
|
167
|
+
|
|
168
|
+
forbidden.update(
|
|
169
|
+
own_headings[index]
|
|
170
|
+
for index, heading in enumerate(reference_headings)
|
|
171
|
+
if heading in FORBIDDEN_HEADINGS
|
|
172
|
+
)
|
|
173
|
+
return forbidden
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _toc_bounds(lines: Sequence[str]) -> tuple[int, int] | None:
|
|
177
|
+
"""Locate the `mdformat-toc` block in *lines*.
|
|
178
|
+
|
|
179
|
+
:param lines: The document split into lines.
|
|
180
|
+
:return: The start and end marker indices, or `None` when either is absent.
|
|
181
|
+
"""
|
|
182
|
+
start = end = None
|
|
183
|
+
for index, line in enumerate(lines):
|
|
184
|
+
if start is None:
|
|
185
|
+
if TOC_START_RE.match(line):
|
|
186
|
+
start = index
|
|
187
|
+
elif TOC_END_RE.match(line):
|
|
188
|
+
end = index
|
|
189
|
+
break
|
|
190
|
+
if start is None or end is None:
|
|
191
|
+
return None
|
|
192
|
+
return start, end
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def strip_toc_entries(content: str, forbidden: set[str]) -> tuple[str, list[str]]:
|
|
196
|
+
"""Delete the ToC entries of *content* whose link text is *forbidden*.
|
|
197
|
+
|
|
198
|
+
Only the `mdformat-toc` block is touched: a list item elsewhere in the
|
|
199
|
+
document that happens to link the same heading is left alone.
|
|
200
|
+
|
|
201
|
+
:param content: The full Markdown document.
|
|
202
|
+
:param forbidden: Heading texts whose entry must go.
|
|
203
|
+
:return: The updated document and the link texts that were deleted.
|
|
204
|
+
"""
|
|
205
|
+
lines = content.splitlines(keepends=True)
|
|
206
|
+
bounds = _toc_bounds([line.rstrip("\r\n") for line in lines])
|
|
207
|
+
if bounds is None:
|
|
208
|
+
return content, []
|
|
209
|
+
start, end = bounds
|
|
210
|
+
|
|
211
|
+
kept: list[str] = []
|
|
212
|
+
removed: list[str] = []
|
|
213
|
+
for index, line in enumerate(lines):
|
|
214
|
+
if start < index < end:
|
|
215
|
+
match = TOC_ENTRY_RE.match(line.rstrip("\r\n"))
|
|
216
|
+
if match and match.group("text") in forbidden:
|
|
217
|
+
removed.append(match.group("text"))
|
|
218
|
+
continue
|
|
219
|
+
kept.append(line)
|
|
220
|
+
return "".join(kept), removed
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def fix_awesome_toc(root: Path | None = None) -> dict[Path, list[str]]:
|
|
224
|
+
"""Strip the forbidden ToC entries from every readme under *root*.
|
|
225
|
+
|
|
226
|
+
Reads {data}`REFERENCE_README` first so its heading positions can be mapped
|
|
227
|
+
onto each translation, then rewrites every readme that changed. Idempotent:
|
|
228
|
+
a second run finds nothing left to delete.
|
|
229
|
+
|
|
230
|
+
:param root: Directory holding the readmes. Defaults to the current one.
|
|
231
|
+
:return: The deleted entries, keyed by the readme they came from.
|
|
232
|
+
"""
|
|
233
|
+
root = Path.cwd() if root is None else root
|
|
234
|
+
|
|
235
|
+
reference = root / REFERENCE_README
|
|
236
|
+
reference_headings: list[str] | None = None
|
|
237
|
+
if reference.is_file():
|
|
238
|
+
reference_headings = headings(reference.read_text(encoding="UTF-8"))
|
|
239
|
+
else:
|
|
240
|
+
logging.warning(
|
|
241
|
+
f"No {REFERENCE_README} in {root}: translations can only be matched "
|
|
242
|
+
"on English heading names."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
report: dict[Path, list[str]] = {}
|
|
246
|
+
for readme in _readmes(root):
|
|
247
|
+
content = readme.read_text(encoding="UTF-8")
|
|
248
|
+
forbidden = forbidden_headings_for(content, reference_headings)
|
|
249
|
+
updated, removed = strip_toc_entries(content, forbidden)
|
|
250
|
+
if not removed:
|
|
251
|
+
logging.debug(f"No forbidden ToC entry in {readme}.")
|
|
252
|
+
continue
|
|
253
|
+
readme.write_text(updated, encoding="UTF-8")
|
|
254
|
+
logging.info(f"Removed {len(removed)} ToC entries from {readme}.")
|
|
255
|
+
report[readme] = removed
|
|
256
|
+
return report
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _readmes(root: Path) -> Iterator[Path]:
|
|
260
|
+
"""Yield the readmes of *root*, the reference first.
|
|
261
|
+
|
|
262
|
+
The reference goes first so its own entries are gone before a translation
|
|
263
|
+
is compared against it, which keeps a partial failure from leaving the
|
|
264
|
+
English readme dirtier than its translations.
|
|
265
|
+
|
|
266
|
+
:param root: Directory to scan.
|
|
267
|
+
:return: Each matching readme path, sorted after the reference.
|
|
268
|
+
"""
|
|
269
|
+
matches: Iterable[Path] = sorted(
|
|
270
|
+
path for path in root.iterdir() if path.is_file() and README_RE.match(path.name)
|
|
271
|
+
)
|
|
272
|
+
for path in matches:
|
|
273
|
+
if path.name == REFERENCE_README:
|
|
274
|
+
yield path
|
|
275
|
+
for path in matches:
|
|
276
|
+
if path.name != REFERENCE_README:
|
|
277
|
+
yield path
|
|
@@ -57,7 +57,8 @@ from pathlib import Path
|
|
|
57
57
|
|
|
58
58
|
from click_extra.blocks import replace_region
|
|
59
59
|
|
|
60
|
-
from .binary import NUITKA_BUILD_TARGETS
|
|
60
|
+
from .binary import BINARY_ASSET_SUFFIXES, NUITKA_BUILD_TARGETS
|
|
61
|
+
from .bundle import get_data_content
|
|
61
62
|
from .github.releases import parse_release_version
|
|
62
63
|
from .virustotal import VIRUSTOTAL_GUI_URL
|
|
63
64
|
|
|
@@ -68,11 +69,21 @@ if TYPE_CHECKING:
|
|
|
68
69
|
from .github.releases import ReleaseAsset, ReleaseWithAssets
|
|
69
70
|
from .virustotal import ScanRecord
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
"""
|
|
72
|
+
CHART_JS_URL = "https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js"
|
|
73
|
+
"""Pinned CDN artifact drawing the detections trend chart.
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
The one external artifact this module publishes into every downstream
|
|
76
|
+
repository's docs, so it carries a checksum beside the pin: bump the version
|
|
77
|
+
by hand together with {data}`CHART_JS_SRI`.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
CHART_JS_SRI = "sha384-XcdcwHqIPULERb2yDEM4R0XaQKU3YnDsrTmjACBZyfdVVqjh6xQ4/DCMd7XLcA6Y"
|
|
81
|
+
"""Subresource Integrity digest of {data}`CHART_JS_URL`.
|
|
82
|
+
|
|
83
|
+
The browser refuses the script if the CDN bytes stop matching. Recompute on
|
|
84
|
+
every version bump as the sha384 of the exact artifact, verified against the
|
|
85
|
+
same file inside the npm registry tarball before trusting the CDN copy:
|
|
86
|
+
`hashlib.sha384(artifact_bytes)` then base64.
|
|
76
87
|
"""
|
|
77
88
|
|
|
78
89
|
CSV_HEADERS = (
|
|
@@ -186,13 +197,14 @@ def _binary_assets(release: ReleaseWithAssets) -> list[ReleaseAsset]:
|
|
|
186
197
|
"""Return the release's compiled binaries, sorted by filename.
|
|
187
198
|
|
|
188
199
|
Releases also carry versionless alias copies of each binary, backing the
|
|
189
|
-
stable ``releases/latest/download`` URLs (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
stable ``releases/latest/download`` URLs (created by
|
|
201
|
+
{func}`~repomatic.binary.pack_binary_assets`, which
|
|
202
|
+
`release-assets` runs in the release engine). An alias shares its digest
|
|
203
|
+
with its versioned sibling: collapse each digest group onto its longest
|
|
204
|
+
name (the versioned one, since {func}`~repomatic.binary.versionless_alias`
|
|
205
|
+
only ever strips the version segment) so the catalog lists each binary
|
|
206
|
+
once. Assets without a recorded digest (uploaded before GitHub started
|
|
207
|
+
recording them, mid-2025) predate the aliases and are kept as-is.
|
|
196
208
|
"""
|
|
197
209
|
binaries = [a for a in release.assets if a.name.endswith(BINARY_ASSET_SUFFIXES)]
|
|
198
210
|
digest_groups: dict[str, list[ReleaseAsset]] = {}
|
|
@@ -275,6 +287,15 @@ def render_chart_section(records: Sequence[ScanRecord]) -> str:
|
|
|
275
287
|
],
|
|
276
288
|
sort_keys=True,
|
|
277
289
|
)
|
|
290
|
+
# The chart script ships as a bundled data file so it reads (and lints) as
|
|
291
|
+
# JavaScript instead of sixty quoted Python strings. Placeholder tokens are
|
|
292
|
+
# substituted with str.replace, never str.format: the script is full of
|
|
293
|
+
# literal braces.
|
|
294
|
+
script = (
|
|
295
|
+
get_data_content("vt-trend-chart.js")
|
|
296
|
+
.replace("__VT_TREND_PAYLOAD__", payload)
|
|
297
|
+
.replace("__VT_DANGER_PCT__", str(FLAGGED_DANGER_PCT))
|
|
298
|
+
)
|
|
278
299
|
return (
|
|
279
300
|
"## VirusTotal detections\n\n"
|
|
280
301
|
"Share of antivirus engine verdicts flagging the binaries of each "
|
|
@@ -283,62 +304,9 @@ def render_chart_section(records: Sequence[ScanRecord]) -> str:
|
|
|
283
304
|
"there up.\n\n"
|
|
284
305
|
"```{raw} html\n"
|
|
285
306
|
'<div style="height: 320px;"><canvas id="vt-trend"></canvas></div>\n'
|
|
286
|
-
'<script src="
|
|
287
|
-
'
|
|
288
|
-
"<script>\n"
|
|
289
|
-
f"const VT_TREND = {payload};\n"
|
|
290
|
-
f"const VT_DANGER_PCT = {FLAGGED_DANGER_PCT};\n"
|
|
291
|
-
"const vtCss = getComputedStyle(document.documentElement);\n"
|
|
292
|
-
"const vtColor = (name, fallback) =>\n"
|
|
293
|
-
" vtCss.getPropertyValue(name).trim() || fallback;\n"
|
|
294
|
-
"const vtTint = (p) => {\n"
|
|
295
|
-
' if (p.pct === 0) { return vtColor("--sd-color-success", "#28a745"); }\n'
|
|
296
|
-
" return p.pct >= VT_DANGER_PCT\n"
|
|
297
|
-
' ? vtColor("--sd-color-danger", "#dc3545")\n'
|
|
298
|
-
' : vtColor("--sd-color-warning", "#f0b37e");\n'
|
|
299
|
-
"};\n"
|
|
300
|
-
'new Chart(document.getElementById("vt-trend"), {\n'
|
|
301
|
-
' type: "line",\n'
|
|
302
|
-
" data: {\n"
|
|
303
|
-
" datasets: [{\n"
|
|
304
|
-
" data: VT_TREND.map((p) => ({x: Date.parse(p.date), y: p.pct})),\n"
|
|
305
|
-
' borderColor: "#88888866",\n'
|
|
306
|
-
" pointBackgroundColor: VT_TREND.map(vtTint),\n"
|
|
307
|
-
" pointBorderColor: VT_TREND.map(vtTint),\n"
|
|
308
|
-
" pointRadius: 4,\n"
|
|
309
|
-
" tension: 0.2,\n"
|
|
310
|
-
" }],\n"
|
|
311
|
-
" },\n"
|
|
312
|
-
" options: {\n"
|
|
313
|
-
" maintainAspectRatio: false,\n"
|
|
314
|
-
" plugins: {\n"
|
|
315
|
-
" legend: {display: false},\n"
|
|
316
|
-
" tooltip: {callbacks: {\n"
|
|
317
|
-
" title: (items) => VT_TREND[items[0].dataIndex].tag,\n"
|
|
318
|
-
" label: (item) => {\n"
|
|
319
|
-
" const p = VT_TREND[item.dataIndex];\n"
|
|
320
|
-
' return p.flagged + " / " + p.total\n'
|
|
321
|
-
' + " verdicts flagged (" + p.pct + "%)";\n'
|
|
322
|
-
" },\n"
|
|
323
|
-
" }},\n"
|
|
324
|
-
" },\n"
|
|
325
|
-
" scales: {\n"
|
|
326
|
-
" x: {\n"
|
|
327
|
-
' type: "linear",\n'
|
|
328
|
-
" ticks: {\n"
|
|
329
|
-
" maxTicksLimit: 8,\n"
|
|
330
|
-
" callback: (value) =>\n"
|
|
331
|
-
" new Date(value).toISOString().slice(0, 10),\n"
|
|
332
|
-
" },\n"
|
|
333
|
-
" },\n"
|
|
334
|
-
" y: {\n"
|
|
335
|
-
" beginAtZero: true,\n"
|
|
336
|
-
' title: {display: true, text: "Flagged verdicts (%)"},\n'
|
|
337
|
-
" },\n"
|
|
338
|
-
" },\n"
|
|
339
|
-
" },\n"
|
|
340
|
-
"});\n"
|
|
341
|
-
"</script>\n"
|
|
307
|
+
f'<script src="{CHART_JS_URL}" integrity="{CHART_JS_SRI}" '
|
|
308
|
+
'crossorigin="anonymous"></script>\n'
|
|
309
|
+
f"<script>\n{script}</script>\n"
|
|
342
310
|
"```"
|
|
343
311
|
)
|
|
344
312
|
|