repomatic 7.4.1__tar.gz → 7.6.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.4.1 → repomatic-7.6.0}/PKG-INFO +18 -7
- {repomatic-7.4.1 → repomatic-7.6.0}/pyproject.toml +12 -8
- {repomatic-7.4.1 → repomatic-7.6.0}/pyproject.toml.orig +26 -13
- {repomatic-7.4.1 → repomatic-7.6.0}/readme.md +16 -5
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/__init__.py +2 -2
- repomatic-7.6.0/repomatic/awesome_toc.py +277 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/binaries_page.py +5 -3
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/broken_links.py +6 -20
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/changelog.py +0 -3
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/cli.py +539 -432
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/config.py +211 -15
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/_release-engine.yaml +158 -28
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/action-publish-pypi.yaml +34 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/autofix.yaml +150 -115
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/autolock.yaml +11 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/cancel-runs.yaml +13 -1
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/changelog.yaml +71 -21
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/debug.yaml +17 -2
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/docs.yaml +30 -7
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/labels.yaml +26 -5
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/lint.yaml +45 -12
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/release.yaml +48 -1
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/ruff.toml +6 -3
- repomatic-7.4.1/repomatic/data/skill-av-false-positive.md → repomatic-7.6.0/repomatic/data/skills/av-false-positive/SKILL.md +2 -3
- repomatic-7.4.1/repomatic/data/skill-awesome-triage.md → repomatic-7.6.0/repomatic/data/skills/awesome-triage/SKILL.md +2 -3
- repomatic-7.4.1/repomatic/data/skill-babysit-ci.md → repomatic-7.6.0/repomatic/data/skills/babysit-ci/SKILL.md +21 -10
- repomatic-7.4.1/repomatic/data/skill-benchmark-update.md → repomatic-7.6.0/repomatic/data/skills/benchmark-update/SKILL.md +2 -3
- repomatic-7.4.1/repomatic/data/skill-brand-assets.md → repomatic-7.6.0/repomatic/data/skills/brand-assets/SKILL.md +1 -1
- repomatic-7.4.1/repomatic/data/skill-file-bug-report.md → repomatic-7.6.0/repomatic/data/skills/file-bug-report/SKILL.md +2 -2
- repomatic-7.4.1/repomatic/data/skill-repomatic-audit.md → repomatic-7.6.0/repomatic/data/skills/repomatic-audit/SKILL.md +2 -2
- repomatic-7.4.1/repomatic/data/skill-repomatic-changelog.md → repomatic-7.6.0/repomatic/data/skills/repomatic-changelog/SKILL.md +7 -5
- repomatic-7.4.1/repomatic/data/skill-repomatic-deps.md → repomatic-7.6.0/repomatic/data/skills/repomatic-deps/SKILL.md +3 -3
- repomatic-7.4.1/repomatic/data/skill-repomatic-init.md → repomatic-7.6.0/repomatic/data/skills/repomatic-init/SKILL.md +3 -3
- repomatic-7.4.1/repomatic/data/skill-repomatic-ship.md → repomatic-7.6.0/repomatic/data/skills/repomatic-ship/SKILL.md +33 -19
- repomatic-7.4.1/repomatic/data/skill-repomatic-topics.md → repomatic-7.6.0/repomatic/data/skills/repomatic-topics/SKILL.md +2 -3
- repomatic-7.4.1/repomatic/data/skill-sphinx-docs-sync.md → repomatic-7.6.0/repomatic/data/skills/sphinx-docs-sync/SKILL.md +2 -3
- repomatic-7.4.1/repomatic/data/skill-translation-sync.md → repomatic-7.6.0/repomatic/data/skills/translation-sync/SKILL.md +2 -3
- repomatic-7.4.1/repomatic/data/skill-upstream-audit.md → repomatic-7.6.0/repomatic/data/skills/upstream-audit/SKILL.md +2 -3
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/tests.yaml +37 -3
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/unsubscribe.yaml +13 -1
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/uv.toml +4 -1
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/dep_sources.py +92 -2
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/docs.py +1 -1
- repomatic-7.6.0/repomatic/frontmatter.py +70 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/gh.py +31 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/issue.py +118 -15
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/pr_body.py +19 -33
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/release_sync.py +16 -16
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/unsubscribe.py +40 -79
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/workflow_sync.py +370 -83
- repomatic-7.6.0/repomatic/humanize.py +63 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/images.py +39 -16
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/init_project.py +371 -95
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/labels.py +82 -21
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/lint_repo.py +478 -128
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/metadata.py +38 -434
- repomatic-7.6.0/repomatic/plugin.py +339 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/prepare_release.py +125 -12
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/pypi.py +41 -14
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/pyproject.py +6 -1
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/registry.py +401 -283
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/setup_guide.py +9 -23
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/sync_ops.py +211 -75
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-tool-versions.md +1 -1
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/tool_registry.py +302 -42
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/tool_runner.py +186 -8
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/uv.py +38 -9
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/version_sync.py +171 -11
- {repomatic-7.4.1 → repomatic-7.6.0}/license +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/__main__.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/binary.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/bundle.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/cache.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/checksums.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/compat.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/__init__.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/agent-grunt-qa.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/agent-qa-engineer.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/agent-sphinx-docs.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/new-link.yaml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/.github/code-of-conduct.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/.github/contributing.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/.github/contributing.zh.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/.github/funding.yml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/.github/pull_request_template.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/__init__.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/awesome_template/license +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/bumpversion.toml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/codecov.yaml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/labeller-content-based.yaml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/labeller-file-based.yaml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/labels.toml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/lychee.toml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/mdformat.toml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/mypy.toml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/pytest.toml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/typos.toml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/yamllint.yaml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/data/zizmor.yaml +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/dep_graph.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/dep_report.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/git_ops.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/__init__.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/actions.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/dev_release.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/matrix.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/releases.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/sponsor.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/status.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/github/token.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/gitignore.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/http.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/mailmap.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/matrix_axes.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/npm.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/py.typed +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/__init__.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/available-admonition.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/broken-links-issue.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/bump-version.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/detect-squash-merge.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/development-warning.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/fix-changelog.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/fix-typos.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/fix-vulnerable-deps.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/format-images.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/format-json.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/format-markdown.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/format-pyproject.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/format-python.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/format-shell.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/generated-footer.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/github-releases.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/immutable-releases.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/pr-metadata.md.noformat +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/prepare-release.md.noformat +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/refresh-tip.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/release-notes.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/release-sync-report.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-branch-ruleset.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-dependabot.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-fork-pr-approval.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-notifications-pat.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-pages-source.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-pypi-trusted-publisher.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-sha-pinning-required.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-token.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-verify.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide-virustotal.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/setup-guide.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-action-pins.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-bumpversion.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-dep-sources.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-gitignore.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-mailmap.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-repomatic.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-uv-lock.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/sync-workflow-pins.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/unavailable-admonition.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/unsubscribe-phase1.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/unsubscribe-phase2.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/update-dep-graph.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/update-docs.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/templates/yanked-admonition.md +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/tool_runner_page.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/virustotal.py +0 -0
- {repomatic-7.4.1 → repomatic-7.6.0}/repomatic/vulnerable_deps.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repomatic
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.6.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.6.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
|
|
@@ -77,7 +77,7 @@ Description-Content-Type: text/markdown
|
|
|
77
77
|
[](https://app.codecov.io/gh/kdeldycke/repomatic)
|
|
78
78
|
[](https://github.com/kdeldycke/repomatic/actions/workflows/docs.yaml?query=branch%3Amain)
|
|
79
79
|
|
|
80
|
-
A Python CLI and `pyproject.toml` configuration that let you **release Python packages multiple times a day with only 2-clicks**. Designed for `uv`-based Python projects, but usable for other projects too.
|
|
80
|
+
A Python CLI and `pyproject.toml` configuration that let you **release Python packages multiple times a day with only 2-clicks**. Designed for `uv`-based Python projects, but usable for other projects too. Every step is a CLI command: reusable GitHub Actions workflows are only the trigger, so the same automation runs on your machine.
|
|
81
81
|
|
|
82
82
|
**Maintainer-in-the-loop**: nothing is done behind your back. A PR or issue is created every time a change is proposed or action is needed.
|
|
83
83
|
|
|
@@ -99,8 +99,10 @@ A Python CLI and `pyproject.toml` configuration that let you **release Python pa
|
|
|
99
99
|
|
|
100
100
|
## Why repomatic
|
|
101
101
|
|
|
102
|
+
repomatic does for the release process what ruff did for linting and uv did for packaging: replace a stack of single-purpose tools with one.
|
|
103
|
+
|
|
102
104
|
- [18 third-party GitHub Actions replaced](https://kdeldycke.github.io/repomatic/security.html#third-party-action-minimization) by internal CLI commands and SHA-256-verified binary downloads, keeping the supply chain attack surface minimal
|
|
103
|
-
- [
|
|
105
|
+
- [10 Python linters and formatters](https://kdeldycke.github.io/repomatic/security.html#ruff-consolidation) (pylint, pydocstyle, pycln, pyupgrade, isort, black, docformatter, mdformat-black, blacken-docs, mdformat-ruff) consolidated into ruff
|
|
104
106
|
- [5 packaging and install tools](https://kdeldycke.github.io/repomatic/security.html#uv-consolidation) (poetry, build, twine, check-wheel-contents, pip-audit) consolidated into uv
|
|
105
107
|
- All `uses:` references [pinned to full commit SHAs](https://kdeldycke.github.io/repomatic/security.html#supply-chain-security) with stabilization windows before adopting new versions, managed entirely by self-hosted sync jobs
|
|
106
108
|
- [SLSA provenance attestations](https://kdeldycke.github.io/repomatic/security.html#supply-chain-security) on every release artifact (wheels and compiled binaries)
|
|
@@ -110,7 +112,8 @@ A Python CLI and `pyproject.toml` configuration that let you **release Python pa
|
|
|
110
112
|
- Workflow security linting with [`zizmor`](https://kdeldycke.github.io/repomatic/workflows.html#github-workflows-lint-yaml-jobs) on every push to catch dangerous triggers and excessive permissions
|
|
111
113
|
- Credential scanning with [`gitleaks`](https://kdeldycke.github.io/repomatic/workflows.html#github-workflows-lint-yaml-jobs) to prevent secret leakage
|
|
112
114
|
- Single [`pyproject.toml` configuration](https://kdeldycke.github.io/repomatic/configuration.html): no extra dotfiles, no JSON configs, no YAML presets to maintain
|
|
113
|
-
- [
|
|
115
|
+
- The CLI itself [ships as standalone binaries](https://kdeldycke.github.io/repomatic/install.html#executables) (Linux / macOS / Windows, x86_64 / arm64): no Python environment needed to run it
|
|
116
|
+
- [15+ code quality tools](https://kdeldycke.github.io/repomatic/tool-runner.html) (ruff, mypy, biome, typos, mdformat, shfmt, yamllint, actionlint, lychee, oxipng, pyproject-fmt, labelmaker, gitleaks, zizmor) managed through one `repomatic run <tool>` interface with automatic installation and platform-specific binary caching
|
|
114
117
|
|
|
115
118
|
## Quick start
|
|
116
119
|
|
|
@@ -124,6 +127,13 @@ $ git push
|
|
|
124
127
|
|
|
125
128
|
Works for new and existing repositories. Managed files are always regenerated to the latest version; `changelog.md` is never overwritten. Push, and the workflows guide you through remaining setup via issues and PRs.
|
|
126
129
|
|
|
130
|
+
The workflows only call CLI commands, so every automated step can also be run and previewed locally:
|
|
131
|
+
|
|
132
|
+
```shell-session
|
|
133
|
+
$ uvx -- repomatic sync-deps --dry-run
|
|
134
|
+
$ uvx -- repomatic run ruff -- check
|
|
135
|
+
```
|
|
136
|
+
|
|
127
137
|
See `repomatic init --help` for available components and options.
|
|
128
138
|
|
|
129
139
|
## Documentation
|
|
@@ -133,10 +143,11 @@ See the **[full documentation](https://kdeldycke.github.io/repomatic/)** for:
|
|
|
133
143
|
- [Installation methods and executables](https://kdeldycke.github.io/repomatic/install.html)
|
|
134
144
|
- [`[tool.repomatic]` configuration reference](https://kdeldycke.github.io/repomatic/configuration.html)
|
|
135
145
|
- [CLI parameters](https://kdeldycke.github.io/repomatic/cli.html)
|
|
136
|
-
- [Reusable workflow reference](https://kdeldycke.github.io/repomatic/workflows.html) (all
|
|
146
|
+
- [Reusable workflow reference](https://kdeldycke.github.io/repomatic/workflows.html) (all 14 workflows with job descriptions)
|
|
137
147
|
- [Security practices and token setup](https://kdeldycke.github.io/repomatic/security.html)
|
|
138
|
-
- [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)
|
|
139
149
|
- [API reference](https://kdeldycke.github.io/repomatic/repomatic.html)
|
|
150
|
+
- [Project history](https://kdeldycke.github.io/repomatic/history.html)
|
|
140
151
|
|
|
141
152
|
## Used in
|
|
142
153
|
|
|
@@ -4,7 +4,7 @@ requires = ["uv-build>=0.8"]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "repomatic"
|
|
7
|
-
version = "7.
|
|
7
|
+
version = "7.6.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.6.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"
|
|
@@ -138,8 +138,6 @@ exclude-newer = "1 week"
|
|
|
138
138
|
requires-python = ">= 3.14"
|
|
139
139
|
|
|
140
140
|
[tool.uv.exclude-newer-package]
|
|
141
|
-
click-extra = "2026-08-02T00:00:00Z"
|
|
142
|
-
extra-platforms = "2026-07-28T00:00:00Z"
|
|
143
141
|
repomatic = "0 day"
|
|
144
142
|
|
|
145
143
|
[tool.uv.build-backend]
|
|
@@ -148,14 +146,17 @@ module-root = ""
|
|
|
148
146
|
[tool.nuitka]
|
|
149
147
|
copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed under GPL-2.0-or-later license."
|
|
150
148
|
file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
151
|
-
file-version = "7.
|
|
152
|
-
include-data-dir = [
|
|
149
|
+
file-version = "7.6.0"
|
|
150
|
+
include-data-dir = [
|
|
151
|
+
"repomatic/data/awesome_template=repomatic/data/awesome_template",
|
|
152
|
+
"repomatic/data/skills=repomatic/data/skills",
|
|
153
|
+
]
|
|
153
154
|
include-data-files = ["repomatic/templates/*.md=repomatic/templates/"]
|
|
154
155
|
include-package-data = ["click_extra"]
|
|
155
156
|
linux-icon = "docs/assets/icon.png"
|
|
156
157
|
macos-app-icon = "docs/assets/icon.icns"
|
|
157
158
|
product-name = "Repomatic"
|
|
158
|
-
product-version = "7.
|
|
159
|
+
product-version = "7.6.0"
|
|
159
160
|
windows-icon-from-ico = "docs/assets/icon.ico"
|
|
160
161
|
|
|
161
162
|
[tool.ruff]
|
|
@@ -163,6 +164,7 @@ preview = true
|
|
|
163
164
|
fix = true
|
|
164
165
|
unsafe-fixes = false
|
|
165
166
|
show-fixes = true
|
|
167
|
+
output-prefer-rule-codes = true
|
|
166
168
|
|
|
167
169
|
[tool.ruff.format]
|
|
168
170
|
docstring-code-format = true
|
|
@@ -203,6 +205,7 @@ Typescript = "TypeScript"
|
|
|
203
205
|
|
|
204
206
|
[tool.typos.default.extend-words]
|
|
205
207
|
astroid = "astroid"
|
|
208
|
+
Contribuer = "Contribuer"
|
|
206
209
|
oject = "oject"
|
|
207
210
|
PNGs = "PNGs"
|
|
208
211
|
sur = "sur"
|
|
@@ -245,7 +248,7 @@ source = ["repomatic"]
|
|
|
245
248
|
precision = 2
|
|
246
249
|
|
|
247
250
|
[tool.bumpversion]
|
|
248
|
-
current_version = "7.
|
|
251
|
+
current_version = "7.6.0"
|
|
249
252
|
parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.dev(?P<dev>\d+))?'
|
|
250
253
|
serialize = [
|
|
251
254
|
"{major}.{minor}.{patch}.dev{dev}",
|
|
@@ -330,6 +333,7 @@ pypi-package-history = [
|
|
|
330
333
|
"gha-utils",
|
|
331
334
|
"repokit",
|
|
332
335
|
]
|
|
336
|
+
release-assets = ["repomatic-plugin.zip"]
|
|
333
337
|
|
|
334
338
|
[tool.repomatic.changelog]
|
|
335
339
|
archive-location = "./docs/changelog-archive.md"
|
|
@@ -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.6.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.6.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"
|
|
@@ -223,9 +223,12 @@ typing = [
|
|
|
223
223
|
required-version = ">=0.11.15"
|
|
224
224
|
# Docs group requires Python >= 3.14 for recent MyST-Parser and click-extra features.
|
|
225
225
|
dependency-groups.docs = { requires-python = ">= 3.14" }
|
|
226
|
-
# Cooldown period
|
|
227
|
-
#
|
|
228
|
-
#
|
|
226
|
+
# Cooldown period before using newly released packages. Kept equal to
|
|
227
|
+
# `[tool.repomatic] minimum-release-age`: that value gates the `uvx` installs
|
|
228
|
+
# every workflow runs, and a wider window here would lock a version those
|
|
229
|
+
# installs refuse to resolve. Relative dates are fine: uv 0.11.8+ writes a
|
|
230
|
+
# sentinel timestamp to uv.lock for relative spans, so re-resolving never
|
|
231
|
+
# produces churn.
|
|
229
232
|
exclude-newer = "1 week"
|
|
230
233
|
# Per-package cooldown control. A fixed date holds a package at the version
|
|
231
234
|
# available then (the day after it shipped), so `uv lock --upgrade` keeps that
|
|
@@ -234,7 +237,7 @@ exclude-newer = "1 week"
|
|
|
234
237
|
# `exclude-newer`, returning the package to the normal cooldown. repomatic is
|
|
235
238
|
# pinned to git main (no PyPI release to freeze), so it keeps a permanent
|
|
236
239
|
# "0 day" span.
|
|
237
|
-
exclude-newer-package = {
|
|
240
|
+
exclude-newer-package = { repomatic = "0 day" }
|
|
238
241
|
# Package is at root level, not in "./src/".
|
|
239
242
|
build-backend.module-root = ""
|
|
240
243
|
|
|
@@ -247,9 +250,10 @@ copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed u
|
|
|
247
250
|
file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
248
251
|
# Numeric only: Nuitka rejects PEP 440 .devN suffixes. Kept in sync with the
|
|
249
252
|
# package version by the dedicated [tool.bumpversion] rule that strips the suffix.
|
|
250
|
-
file-version = "7.
|
|
253
|
+
file-version = "7.6.0"
|
|
251
254
|
include-data-dir = [
|
|
252
255
|
"repomatic/data/awesome_template=repomatic/data/awesome_template",
|
|
256
|
+
"repomatic/data/skills=repomatic/data/skills",
|
|
253
257
|
]
|
|
254
258
|
include-data-files = [
|
|
255
259
|
"repomatic/templates/*.md=repomatic/templates/",
|
|
@@ -265,7 +269,7 @@ include-package-data = [ "click_extra" ]
|
|
|
265
269
|
linux-icon = "docs/assets/icon.png"
|
|
266
270
|
macos-app-icon = "docs/assets/icon.icns"
|
|
267
271
|
product-name = "Repomatic"
|
|
268
|
-
product-version = "7.
|
|
272
|
+
product-version = "7.6.0"
|
|
269
273
|
windows-icon-from-ico = "docs/assets/icon.ico"
|
|
270
274
|
|
|
271
275
|
[tool.ruff]
|
|
@@ -299,11 +303,15 @@ lint.isort.combine-as-imports = true
|
|
|
299
303
|
# this would simplify a fix or enable a new diagnostic.
|
|
300
304
|
# https://docs.astral.sh/ruff/settings/#lint_future-annotations
|
|
301
305
|
lint.future-annotations = true
|
|
306
|
+
# Report rule codes, not the human-readable names preview mode defaults to.
|
|
307
|
+
# See the bundled repomatic/data/ruff.toml for the rationale.
|
|
308
|
+
output-prefer-rule-codes = true
|
|
302
309
|
|
|
303
310
|
[tool.typos]
|
|
304
311
|
default.extend-identifiers = { Github = "GitHub", IOS = "iOS", Javascript = "JavaScript", MacOS = "macOS", PNGs = "PNGs", PyPi = "PyPI", Typescript = "TypeScript" }
|
|
305
|
-
# False positives.
|
|
306
|
-
|
|
312
|
+
# False positives. "Contribuer" is the French for "Contributing", part of the
|
|
313
|
+
# translated-readme fixture in tests/test_awesome_toc.py.
|
|
314
|
+
default.extend-words = { astroid = "astroid", Contribuer = "Contribuer", oject = "oject", PNGs = "PNGs", sur = "sur" }
|
|
307
315
|
# Skip content between <!-- typos:off --> and <!-- typos:on --> markers.
|
|
308
316
|
default.extend-ignore-re = [
|
|
309
317
|
"(?s)<!-- typos:off -->.*?<!-- typos:on -->",
|
|
@@ -387,7 +395,7 @@ run.source = [ "repomatic" ]
|
|
|
387
395
|
report.precision = 2
|
|
388
396
|
|
|
389
397
|
[tool.bumpversion]
|
|
390
|
-
current_version = "7.
|
|
398
|
+
current_version = "7.6.0"
|
|
391
399
|
# Parse versions with an optional .devN suffix (PEP 440).
|
|
392
400
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?"
|
|
393
401
|
serialize = [
|
|
@@ -510,3 +518,8 @@ exclude = [ "workflows" ]
|
|
|
510
518
|
# the HTML docs site stay in lockstep.
|
|
511
519
|
manpages.script = "repomatic.cli:repomatic"
|
|
512
520
|
pypi-package-history = [ "gha-utils", "repokit" ]
|
|
521
|
+
# Claude Code plugin archive packed by release.yaml's pack-plugin job and
|
|
522
|
+
# attached to every GitHub release by the engine's extra-assets job. The
|
|
523
|
+
# marketplace at .claude-plugin/marketplace.json installs the plugin straight
|
|
524
|
+
# from this asset, so a release missing it breaks `/plugin install`.
|
|
525
|
+
release-assets = [ "repomatic-plugin.zip" ]
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://app.codecov.io/gh/kdeldycke/repomatic)
|
|
10
10
|
[](https://github.com/kdeldycke/repomatic/actions/workflows/docs.yaml?query=branch%3Amain)
|
|
11
11
|
|
|
12
|
-
A Python CLI and `pyproject.toml` configuration that let you **release Python packages multiple times a day with only 2-clicks**. Designed for `uv`-based Python projects, but usable for other projects too.
|
|
12
|
+
A Python CLI and `pyproject.toml` configuration that let you **release Python packages multiple times a day with only 2-clicks**. Designed for `uv`-based Python projects, but usable for other projects too. Every step is a CLI command: reusable GitHub Actions workflows are only the trigger, so the same automation runs on your machine.
|
|
13
13
|
|
|
14
14
|
**Maintainer-in-the-loop**: nothing is done behind your back. A PR or issue is created every time a change is proposed or action is needed.
|
|
15
15
|
|
|
@@ -31,8 +31,10 @@ A Python CLI and `pyproject.toml` configuration that let you **release Python pa
|
|
|
31
31
|
|
|
32
32
|
## Why repomatic
|
|
33
33
|
|
|
34
|
+
repomatic does for the release process what ruff did for linting and uv did for packaging: replace a stack of single-purpose tools with one.
|
|
35
|
+
|
|
34
36
|
- [18 third-party GitHub Actions replaced](https://kdeldycke.github.io/repomatic/security.html#third-party-action-minimization) by internal CLI commands and SHA-256-verified binary downloads, keeping the supply chain attack surface minimal
|
|
35
|
-
- [
|
|
37
|
+
- [10 Python linters and formatters](https://kdeldycke.github.io/repomatic/security.html#ruff-consolidation) (pylint, pydocstyle, pycln, pyupgrade, isort, black, docformatter, mdformat-black, blacken-docs, mdformat-ruff) consolidated into ruff
|
|
36
38
|
- [5 packaging and install tools](https://kdeldycke.github.io/repomatic/security.html#uv-consolidation) (poetry, build, twine, check-wheel-contents, pip-audit) consolidated into uv
|
|
37
39
|
- All `uses:` references [pinned to full commit SHAs](https://kdeldycke.github.io/repomatic/security.html#supply-chain-security) with stabilization windows before adopting new versions, managed entirely by self-hosted sync jobs
|
|
38
40
|
- [SLSA provenance attestations](https://kdeldycke.github.io/repomatic/security.html#supply-chain-security) on every release artifact (wheels and compiled binaries)
|
|
@@ -42,7 +44,8 @@ A Python CLI and `pyproject.toml` configuration that let you **release Python pa
|
|
|
42
44
|
- Workflow security linting with [`zizmor`](https://kdeldycke.github.io/repomatic/workflows.html#github-workflows-lint-yaml-jobs) on every push to catch dangerous triggers and excessive permissions
|
|
43
45
|
- Credential scanning with [`gitleaks`](https://kdeldycke.github.io/repomatic/workflows.html#github-workflows-lint-yaml-jobs) to prevent secret leakage
|
|
44
46
|
- Single [`pyproject.toml` configuration](https://kdeldycke.github.io/repomatic/configuration.html): no extra dotfiles, no JSON configs, no YAML presets to maintain
|
|
45
|
-
- [
|
|
47
|
+
- The CLI itself [ships as standalone binaries](https://kdeldycke.github.io/repomatic/install.html#executables) (Linux / macOS / Windows, x86_64 / arm64): no Python environment needed to run it
|
|
48
|
+
- [15+ code quality tools](https://kdeldycke.github.io/repomatic/tool-runner.html) (ruff, mypy, biome, typos, mdformat, shfmt, yamllint, actionlint, lychee, oxipng, pyproject-fmt, labelmaker, gitleaks, zizmor) managed through one `repomatic run <tool>` interface with automatic installation and platform-specific binary caching
|
|
46
49
|
|
|
47
50
|
## Quick start
|
|
48
51
|
|
|
@@ -56,6 +59,13 @@ $ git push
|
|
|
56
59
|
|
|
57
60
|
Works for new and existing repositories. Managed files are always regenerated to the latest version; `changelog.md` is never overwritten. Push, and the workflows guide you through remaining setup via issues and PRs.
|
|
58
61
|
|
|
62
|
+
The workflows only call CLI commands, so every automated step can also be run and previewed locally:
|
|
63
|
+
|
|
64
|
+
```shell-session
|
|
65
|
+
$ uvx -- repomatic sync-deps --dry-run
|
|
66
|
+
$ uvx -- repomatic run ruff -- check
|
|
67
|
+
```
|
|
68
|
+
|
|
59
69
|
See `repomatic init --help` for available components and options.
|
|
60
70
|
|
|
61
71
|
## Documentation
|
|
@@ -65,10 +75,11 @@ See the **[full documentation](https://kdeldycke.github.io/repomatic/)** for:
|
|
|
65
75
|
- [Installation methods and executables](https://kdeldycke.github.io/repomatic/install.html)
|
|
66
76
|
- [`[tool.repomatic]` configuration reference](https://kdeldycke.github.io/repomatic/configuration.html)
|
|
67
77
|
- [CLI parameters](https://kdeldycke.github.io/repomatic/cli.html)
|
|
68
|
-
- [Reusable workflow reference](https://kdeldycke.github.io/repomatic/workflows.html) (all
|
|
78
|
+
- [Reusable workflow reference](https://kdeldycke.github.io/repomatic/workflows.html) (all 14 workflows with job descriptions)
|
|
69
79
|
- [Security practices and token setup](https://kdeldycke.github.io/repomatic/security.html)
|
|
70
|
-
- [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)
|
|
71
81
|
- [API reference](https://kdeldycke.github.io/repomatic/repomatic.html)
|
|
82
|
+
- [Project history](https://kdeldycke.github.io/repomatic/history.html)
|
|
72
83
|
|
|
73
84
|
## Used in
|
|
74
85
|
|
|
@@ -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
|
|
@@ -160,7 +160,7 @@ The table is searchable and sortable on the documentation site; the raw data liv
|
|
|
160
160
|
"""
|
|
161
161
|
"""Initial page content, used when the page does not exist yet.
|
|
162
162
|
|
|
163
|
-
The
|
|
163
|
+
The ``{repo_url}`` placeholder is substituted with `str.replace` (not
|
|
164
164
|
`str.format`, which would choke on the csv-table directive's braces).
|
|
165
165
|
Everything outside the marker pair is written once and never touched again:
|
|
166
166
|
repositories can reword the prose without fighting the generator.
|
|
@@ -418,8 +418,10 @@ def render_binaries_csv(
|
|
|
418
418
|
# they stay clickable with their label.
|
|
419
419
|
writer.writerow((
|
|
420
420
|
f"[`{version}` {{octicon}}`link-external`]({release_url})",
|
|
421
|
-
|
|
422
|
-
|
|
421
|
+
(
|
|
422
|
+
f"[{{octicon}}`download` `{_platform_target(asset.name)}`]"
|
|
423
|
+
f"({asset.download_url})"
|
|
424
|
+
),
|
|
423
425
|
release.date,
|
|
424
426
|
vt_cell,
|
|
425
427
|
))
|