repomatic 7.0.0__tar.gz → 7.2.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.0.0 → repomatic-7.2.0}/PKG-INFO +3 -3
- {repomatic-7.0.0 → repomatic-7.2.0}/pyproject.toml +35 -17
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/__init__.py +2 -2
- repomatic-7.2.0/repomatic/binaries_page.py +448 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/binary.py +7 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/broken_links.py +1 -1
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/cache.py +103 -91
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/changelog.py +47 -17
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/checksums.py +17 -12
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/cli.py +584 -650
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/config.py +83 -172
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/_release-engine.yaml +60 -31
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/action-publish-pypi.yaml +5 -1
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/agent-sphinx-docs.md +25 -28
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/autofix.yaml +107 -61
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/bumpversion.toml +6 -2
- repomatic-7.2.0/repomatic/data/cancel-runs.yaml +50 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/changelog.yaml +24 -18
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/debug.yaml +4 -4
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/docs.yaml +7 -7
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/labels.yaml +12 -12
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/lint.yaml +22 -19
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/lychee.toml +3 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-av-false-positive.md +7 -4
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-babysit-ci.md +20 -4
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-ship.md +13 -10
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/tests.yaml +5 -5
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/unsubscribe.yaml +24 -2
- repomatic-7.2.0/repomatic/dep_sources.py +377 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/deps_graph.py +338 -349
- repomatic-7.2.0/repomatic/docs.py +172 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/git_ops.py +107 -36
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/__init__.py +3 -6
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/actions.py +47 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/dev_release.py +1 -2
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/gh.py +100 -12
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/issue.py +19 -18
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/matrix.py +3 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/pr_body.py +226 -48
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/release_sync.py +5 -50
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/releases.py +188 -94
- {repomatic-7.0.0/repomatic → repomatic-7.2.0/repomatic/github}/sponsor.py +11 -49
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/token.py +85 -174
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/unsubscribe.py +25 -36
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/workflow_sync.py +156 -125
- repomatic-7.2.0/repomatic/gitignore.py +88 -0
- repomatic-7.2.0/repomatic/http.py +80 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/images.py +2 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/init_project.py +10 -11
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/lint_repo.py +31 -17
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/metadata.py +59 -50
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/npm.py +4 -7
- repomatic-7.0.0/repomatic/release_prep.py → repomatic-7.2.0/repomatic/prepare_release.py +18 -5
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/pypi.py +22 -16
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/pyproject.py +22 -28
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/registry.py +46 -20
- repomatic-7.2.0/repomatic/setup_guide.py +383 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/sync_ops.py +481 -184
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/bump-version.md +2 -3
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/detect-squash-merge.md +4 -3
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/fix-changelog.md +2 -5
- repomatic-7.2.0/repomatic/templates/fix-typos.md +8 -0
- repomatic-7.2.0/repomatic/templates/fix-vulnerable-deps.md +15 -0
- repomatic-7.2.0/repomatic/templates/format-images.md +5 -0
- repomatic-7.2.0/repomatic/templates/format-json.md +8 -0
- repomatic-7.2.0/repomatic/templates/format-markdown.md +8 -0
- repomatic-7.2.0/repomatic/templates/format-pyproject.md +8 -0
- repomatic-7.2.0/repomatic/templates/format-python.md +8 -0
- repomatic-7.2.0/repomatic/templates/format-shell.md +8 -0
- repomatic-7.2.0/repomatic/templates/pr-metadata.md.noformat +15 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/prepare-release.md +2 -3
- repomatic-7.2.0/repomatic/templates/setup-guide-notifications-pat.md +21 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-virustotal.md +3 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide.md +3 -1
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/sync-action-pins.md +2 -5
- repomatic-7.2.0/repomatic/templates/sync-bumpversion.md +11 -0
- repomatic-7.2.0/repomatic/templates/sync-dep-sources.md +17 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/sync-gitignore.md +2 -5
- repomatic-7.2.0/repomatic/templates/sync-mailmap.md +11 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/sync-repomatic.md +2 -5
- repomatic-7.2.0/repomatic/templates/sync-tool-versions.md +15 -0
- repomatic-7.2.0/repomatic/templates/sync-uv-lock.md +14 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/sync-workflow-pins.md +2 -5
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/update-deps-graph.md +2 -5
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/update-docs.md +2 -5
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/test_matrix.py +3 -1
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/tool_runner.py +305 -36
- repomatic-7.2.0/repomatic/tool_runner_page.py +217 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/uv.py +380 -556
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/version_sync.py +69 -39
- repomatic-7.2.0/repomatic/virustotal.py +467 -0
- repomatic-7.2.0/repomatic/vulnerable_deps.py +592 -0
- repomatic-7.0.0/repomatic/data/cancel-runs.yaml +0 -55
- repomatic-7.0.0/repomatic/github/advisories.py +0 -131
- repomatic-7.0.0/repomatic/templates/fix-typos.md +0 -11
- repomatic-7.0.0/repomatic/templates/fix-vulnerable-deps.md +0 -18
- repomatic-7.0.0/repomatic/templates/format-images.md +0 -8
- repomatic-7.0.0/repomatic/templates/format-json.md +0 -11
- repomatic-7.0.0/repomatic/templates/format-markdown.md +0 -11
- repomatic-7.0.0/repomatic/templates/format-pyproject.md +0 -11
- repomatic-7.0.0/repomatic/templates/format-python.md +0 -11
- repomatic-7.0.0/repomatic/templates/format-shell.md +0 -11
- repomatic-7.0.0/repomatic/templates/pr-metadata.md.noformat +0 -17
- repomatic-7.0.0/repomatic/templates/sync-bumpversion.md +0 -14
- repomatic-7.0.0/repomatic/templates/sync-mailmap.md +0 -14
- repomatic-7.0.0/repomatic/templates/sync-tool-versions.md +0 -18
- repomatic-7.0.0/repomatic/templates/sync-uv-lock.md +0 -17
- repomatic-7.0.0/repomatic/virustotal.py +0 -417
- {repomatic-7.0.0 → repomatic-7.2.0}/license +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/readme.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/__main__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/__init__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/agent-grunt-qa.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/agent-qa-engineer.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/autolock.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/new-link.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/code-of-conduct.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/contributing.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/contributing.zh.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/funding.yml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/.github/pull_request_template.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/__init__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/awesome_template/license +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/codecov.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/labeller-content-based.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/labeller-file-based.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/labels.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/mdformat.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/mypy.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/pytest.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/release.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/ruff.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-awesome-triage.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-benchmark-update.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-brand-assets.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-file-bug-report.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-audit.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-changelog.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-deps.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-init.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-repomatic-topics.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-sphinx-docs-sync.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-translation-sync.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/skill-upstream-audit.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/typos.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/uv.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/yamllint.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/data/zizmor.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/pr.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/github/status.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/mailmap.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/myst_converter.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/myst_docstrings.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/py.typed +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/rst_to_myst.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/__init__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/available-admonition.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/broken-links-issue.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/development-warning.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/generated-footer.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/github-releases.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/immutable-releases.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/refresh-tip.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/release-notes.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/release-sync-report.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-branch-ruleset.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-dependabot.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-fork-pr-approval.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-pages-source.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-pypi-trusted-publisher.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-token.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/setup-guide-verify.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/unavailable-admonition.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/unsubscribe-phase1.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/unsubscribe-phase2.md +0 -0
- {repomatic-7.0.0 → repomatic-7.2.0}/repomatic/templates/yanked-admonition.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repomatic
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.2.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
|
|
@@ -46,7 +46,7 @@ Classifier: Typing :: Typed
|
|
|
46
46
|
Requires-Dist: arrow>=1.3
|
|
47
47
|
Requires-Dist: backports-strenum>=1 ; python_full_version < '3.11'
|
|
48
48
|
Requires-Dist: boltons>=25
|
|
49
|
-
Requires-Dist: click-extra>=8.
|
|
49
|
+
Requires-Dist: click-extra>=8.3
|
|
50
50
|
Requires-Dist: extra-platforms>=12.0.2
|
|
51
51
|
Requires-Dist: packaging>=17
|
|
52
52
|
Requires-Dist: py-walk>=0.3.1
|
|
@@ -58,7 +58,7 @@ Requires-Dist: wcmatch>=10
|
|
|
58
58
|
Requires-Python: >=3.10
|
|
59
59
|
Project-URL: Changelog, https://github.com/kdeldycke/repomatic/blob/main/changelog.md
|
|
60
60
|
Project-URL: Documentation, https://kdeldycke.github.io/repomatic
|
|
61
|
-
Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.
|
|
61
|
+
Project-URL: Download, https://github.com/kdeldycke/repomatic/releases/tag/v7.2.0
|
|
62
62
|
Project-URL: Funding, https://github.com/sponsors/kdeldycke
|
|
63
63
|
Project-URL: Homepage, https://github.com/kdeldycke/repomatic
|
|
64
64
|
Project-URL: Issues, https://github.com/kdeldycke/repomatic/issues
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[build-system]
|
|
2
2
|
build-backend = "uv_build"
|
|
3
|
-
requires = [ "uv-build>=0.9" ]
|
|
3
|
+
requires = [ "uv-build>=0.9,<0.12" ]
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
# Docs: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
|
|
7
7
|
name = "repomatic"
|
|
8
|
-
version = "7.
|
|
8
|
+
version = "7.2.0"
|
|
9
9
|
description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
10
10
|
readme = "readme.md"
|
|
11
11
|
keywords = [
|
|
@@ -84,10 +84,10 @@ dependencies = [
|
|
|
84
84
|
"backports-strenum>=1; python_version<'3.11'",
|
|
85
85
|
# boltons 25.0.0 dropped Python 3.9, matching our requires-python >= 3.10.
|
|
86
86
|
"boltons>=25",
|
|
87
|
-
# 8.
|
|
88
|
-
#
|
|
89
|
-
# `
|
|
90
|
-
"click-extra>=8.
|
|
87
|
+
# click-extra 8.3 adds the `click:config` Sphinx directive and the
|
|
88
|
+
# `schema_field_infos()` introspection API that `configuration.md` and
|
|
89
|
+
# `show-config` are built on.
|
|
90
|
+
"click-extra>=8.3",
|
|
91
91
|
# extra-platforms 12.0.2 reimplemented is_macos() with sys.platform, avoiding
|
|
92
92
|
# the platform.platform() shell-out to ``cmd /c ver`` on Windows that broke
|
|
93
93
|
# under patched subprocess in tests and triggered a CPython 3.10.x bug.
|
|
@@ -122,7 +122,7 @@ dependencies = [
|
|
|
122
122
|
]
|
|
123
123
|
urls.Changelog = "https://github.com/kdeldycke/repomatic/blob/main/changelog.md"
|
|
124
124
|
urls.Documentation = "https://kdeldycke.github.io/repomatic"
|
|
125
|
-
urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.
|
|
125
|
+
urls.Download = "https://github.com/kdeldycke/repomatic/releases/tag/v7.2.0"
|
|
126
126
|
urls.Funding = "https://github.com/sponsors/kdeldycke"
|
|
127
127
|
urls.Homepage = "https://github.com/kdeldycke/repomatic"
|
|
128
128
|
urls.Issues = "https://github.com/kdeldycke/repomatic/issues"
|
|
@@ -162,6 +162,9 @@ docs = [
|
|
|
162
162
|
"sphinx-autodoc-typehints>=2.4",
|
|
163
163
|
# sphinx-copybutton 0.5.2 stabilized the copy button behavior for code blocks.
|
|
164
164
|
"sphinx-copybutton>=0.5.2",
|
|
165
|
+
# sphinx-datatables 1.0.0 is the first stable release; makes the binaries
|
|
166
|
+
# catalog table searchable and sortable client-side (pulls sphinxcontrib-jquery).
|
|
167
|
+
"sphinx-datatables>=1",
|
|
165
168
|
# sphinx-design 0.6 introduced the current directive syntax for cards, grids, and tabs.
|
|
166
169
|
"sphinx-design>=0.6",
|
|
167
170
|
# sphinxcontrib-mermaid 1.2.3 fixed mermaid_d3_zoom configuration support.
|
|
@@ -170,12 +173,14 @@ docs = [
|
|
|
170
173
|
"sphinxext-opengraph>=0.13",
|
|
171
174
|
]
|
|
172
175
|
# types-boltons: first stub release covering boltons 25.0 API (FrozenDict, unique, flatten,
|
|
173
|
-
# strip_ansi). types-
|
|
176
|
+
# strip_ansi). types-docutils: covers the docutils.nodes imports in docs/conf.py.
|
|
177
|
+
# types-pyyaml: stub release covering PyYAML 6.0 (safe_load, safe_dump, full_load).
|
|
174
178
|
typing = [
|
|
175
179
|
# backports-strenum: provides StrEnum stubs for mypy --python-version 3.10 analysis;
|
|
176
180
|
# not in main deps (version-gated to Python<3.11 there), so added here unconditionally.
|
|
177
181
|
"backports-strenum>=1",
|
|
178
182
|
"types-boltons>=25.0.0.20250822",
|
|
183
|
+
"types-docutils>=0.22.3.20251115",
|
|
179
184
|
"types-pyyaml>=6.0.12.9",
|
|
180
185
|
]
|
|
181
186
|
|
|
@@ -200,9 +205,7 @@ exclude-newer = "1 week"
|
|
|
200
205
|
# bypasses at that date and prunes each entry once its held version ages past
|
|
201
206
|
# `exclude-newer`, returning the package to the normal cooldown. repomatic is
|
|
202
207
|
# pinned to git main (no PyPI release to freeze), so it keeps a permanent
|
|
203
|
-
# "0 day" span.
|
|
204
|
-
# it before the 1-week cooldown elapses; sync-uv-lock prunes this entry once
|
|
205
|
-
# 8.1.0 ages past `exclude-newer`.
|
|
208
|
+
# "0 day" span.
|
|
206
209
|
exclude-newer-package = { repomatic = "0 day" }
|
|
207
210
|
# Package is at root level, not in "./src/".
|
|
208
211
|
build-backend.module-root = ""
|
|
@@ -216,7 +219,7 @@ copyright = "Kevin Deldycke <kevin@deldycke.com> and contributors. Distributed u
|
|
|
216
219
|
file-description = "🏭 Automate repository maintenance, releases, and CI/CD workflows"
|
|
217
220
|
# Numeric only: Nuitka rejects PEP 440 .devN suffixes. Kept in sync with the
|
|
218
221
|
# package version by the dedicated [tool.bumpversion] rule that strips the suffix.
|
|
219
|
-
file-version = "7.
|
|
222
|
+
file-version = "7.2.0"
|
|
220
223
|
include-data-dir = [
|
|
221
224
|
"repomatic/data/awesome_template=repomatic/data/awesome_template",
|
|
222
225
|
]
|
|
@@ -234,7 +237,7 @@ include-package-data = [ "click_extra" ]
|
|
|
234
237
|
linux-icon = "docs/assets/icon.png"
|
|
235
238
|
macos-app-icon = "docs/assets/icon.icns"
|
|
236
239
|
product-name = "Repomatic"
|
|
237
|
-
product-version = "7.
|
|
240
|
+
product-version = "7.2.0"
|
|
238
241
|
windows-icon-from-ico = "docs/assets/icon.ico"
|
|
239
242
|
|
|
240
243
|
[tool.ruff]
|
|
@@ -273,7 +276,14 @@ default.extend-identifiers = { Github = "GitHub", IOS = "iOS", Javascript = "Jav
|
|
|
273
276
|
# False positives.
|
|
274
277
|
default.extend-words = { astroid = "astroid", oject = "oject", PNGs = "PNGs" }
|
|
275
278
|
# Skip content between <!-- typos:off --> and <!-- typos:on --> markers.
|
|
276
|
-
default.extend-ignore-re = [
|
|
279
|
+
default.extend-ignore-re = [
|
|
280
|
+
"(?s)<!-- typos:off -->.*?<!-- typos:on -->",
|
|
281
|
+
# `{qeury}` is the intentional-typo example in the typos tool's own docs_notes
|
|
282
|
+
# guidance (tool_runner.py), which recommends downstream projects guard such
|
|
283
|
+
# examples and encoded hashes from the unattended fix-typos pull request.
|
|
284
|
+
"\\{qeury\\}",
|
|
285
|
+
'base32 "[0-9a-z]{52}"',
|
|
286
|
+
]
|
|
277
287
|
default.extend-ignore-identifiers-re = [
|
|
278
288
|
# error: `certifi` should be `certify`, `certifies`, `certified`
|
|
279
289
|
# ╭▸ ./docs/assets/dependencies.mmd:184:11
|
|
@@ -315,6 +325,7 @@ warn_unused_configs = true
|
|
|
315
325
|
overrides = [
|
|
316
326
|
{ module = [
|
|
317
327
|
"py_walk.*",
|
|
328
|
+
"sphinxcontrib.mermaid",
|
|
318
329
|
"vt",
|
|
319
330
|
], ignore_missing_imports = true },
|
|
320
331
|
]
|
|
@@ -340,7 +351,7 @@ run.source = [ "repomatic" ]
|
|
|
340
351
|
report.precision = 2
|
|
341
352
|
|
|
342
353
|
[tool.bumpversion]
|
|
343
|
-
current_version = "7.
|
|
354
|
+
current_version = "7.2.0"
|
|
344
355
|
# Parse versions with an optional .devN suffix (PEP 440).
|
|
345
356
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?"
|
|
346
357
|
serialize = [
|
|
@@ -395,9 +406,13 @@ search = "## [`{current_version}` (unreleased)]("
|
|
|
395
406
|
replace = "## [`{new_version}` (unreleased)]("
|
|
396
407
|
|
|
397
408
|
[[tool.bumpversion.files]]
|
|
398
|
-
# Update the version in the citation file.
|
|
409
|
+
# Update the version in the citation file. Anchored to a line start (regex) so
|
|
410
|
+
# it does not also match the `cff-version:` schema field, which would otherwise
|
|
411
|
+
# be overwritten with the package version on every bump as soon as the two
|
|
412
|
+
# values coincide once.
|
|
399
413
|
filename = "./citation.cff"
|
|
400
|
-
|
|
414
|
+
regex = true
|
|
415
|
+
search = "(?m)^version: {current_version}"
|
|
401
416
|
replace = "version: {new_version}"
|
|
402
417
|
|
|
403
418
|
[[tool.bumpversion.files]]
|
|
@@ -422,6 +437,9 @@ exclude = [
|
|
|
422
437
|
"archive\\.ph",
|
|
423
438
|
# npmjs.com returns 403 to bots.
|
|
424
439
|
"^https://www\\.npmjs\\.com/package/.*$",
|
|
440
|
+
# VirusTotal analysis pages are a JS app that rate-limits bots, and the
|
|
441
|
+
# binaries page links one per released binary.
|
|
442
|
+
"^https://www\\.virustotal\\.com/gui/.*$",
|
|
425
443
|
# star-history.com fragments are JS-rendered and invisible to lychee.
|
|
426
444
|
"^https://star-history\\.com/.*$",
|
|
427
445
|
# githubstatus.com returns 405 to HEAD requests from bots.
|
|
@@ -0,0 +1,448 @@
|
|
|
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
|
+
"""Generate the binaries catalog: a CSV data file and its `docs/binaries.md` page.
|
|
18
|
+
|
|
19
|
+
The catalog inventories every compiled binary the repository ever released,
|
|
20
|
+
one CSV row per binary: version (linking to the GitHub release), platform
|
|
21
|
+
target (linking to the direct download), release date, and the VirusTotal
|
|
22
|
+
detection snapshot (linking to the live analysis). It gives alpha and beta
|
|
23
|
+
testers a single place to grab binaries from, and the maintainer an overview
|
|
24
|
+
of how antivirus engines treat each release.
|
|
25
|
+
|
|
26
|
+
The data lives in `docs/assets/binaries.csv`, regenerated wholesale on every
|
|
27
|
+
release from the GitHub Releases API (the single source of truth for
|
|
28
|
+
published assets) and the JSON scan history maintained by `scan-virustotal`.
|
|
29
|
+
The Markdown page renders it through a single `csv-table` directive and is
|
|
30
|
+
otherwise static: it is created once from {data}`PAGE_TEMPLATE` and only its
|
|
31
|
+
marker-delimited region (the detection trend chart) is rewritten afterwards,
|
|
32
|
+
so the intro and section prose stay hand-editable per repository.
|
|
33
|
+
|
|
34
|
+
```{note}
|
|
35
|
+
On the documentation site, the table is searchable and sortable client-side
|
|
36
|
+
via the `sphinx-datatables` extension, which activates on the
|
|
37
|
+
`sphinx-datatable` CSS class. The extension is optional: without it the
|
|
38
|
+
`csv-table` directive still renders a plain table, and on GitHub the CSV
|
|
39
|
+
file itself gets the built-in searchable grid viewer.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```{note}
|
|
43
|
+
Development builds are only linked, not cataloged: the rolling dev
|
|
44
|
+
pre-release is refreshed on every push to the default branch, so any row
|
|
45
|
+
frozen into the CSV would be stale within hours, while the workflow run
|
|
46
|
+
artifacts behind the link always are the current builds.
|
|
47
|
+
```
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
from __future__ import annotations
|
|
51
|
+
|
|
52
|
+
import csv
|
|
53
|
+
import io
|
|
54
|
+
import json
|
|
55
|
+
import logging
|
|
56
|
+
from pathlib import Path
|
|
57
|
+
|
|
58
|
+
from packaging.version import InvalidVersion, Version
|
|
59
|
+
|
|
60
|
+
from .binary import NUITKA_BUILD_TARGETS
|
|
61
|
+
from .virustotal import VIRUSTOTAL_GUI_URL
|
|
62
|
+
|
|
63
|
+
TYPE_CHECKING = False
|
|
64
|
+
if TYPE_CHECKING:
|
|
65
|
+
from collections.abc import Sequence
|
|
66
|
+
|
|
67
|
+
from .github.releases import ReleaseAsset, ReleaseWithAssets
|
|
68
|
+
from .virustotal import ScanRecord
|
|
69
|
+
|
|
70
|
+
BINARY_ASSET_SUFFIXES = (".bin", ".exe")
|
|
71
|
+
"""File extensions identifying compiled binaries among release assets.
|
|
72
|
+
|
|
73
|
+
Same set the `scan-virustotal` command uploads and the release workflow
|
|
74
|
+
downloads (`--pattern` flags in `_release-engine.yaml`).
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
CSV_HEADERS = (
|
|
78
|
+
"Version",
|
|
79
|
+
"Platform",
|
|
80
|
+
"Released",
|
|
81
|
+
"VirusTotal",
|
|
82
|
+
)
|
|
83
|
+
"""Column headers of the binaries CSV.
|
|
84
|
+
|
|
85
|
+
Deliberately compact: the version cell carries the link to the GitHub
|
|
86
|
+
release, the platform cell the direct binary download, and the VirusTotal
|
|
87
|
+
cell the analysis link, so no column holds a bare URL, filename, or
|
|
88
|
+
64-character checksum.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
FLAGGED_DANGER_PCT = 10
|
|
92
|
+
"""Flagged-verdict share (percent) at which the catalog shield turns red.
|
|
93
|
+
|
|
94
|
+
Below it, a flagged binary is the routine Nuitka false-positive tail worth a
|
|
95
|
+
warning tint; from one engine in ten upward, the release deserves a
|
|
96
|
+
false-positive submission round (see the `/av-false-positive` skill).
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
PAGE_END_MARKER = "<!-- binaries-end -->"
|
|
100
|
+
"""Closing marker of the generated chart region in the binaries page."""
|
|
101
|
+
|
|
102
|
+
PAGE_START_MARKER = "<!-- binaries-start -->"
|
|
103
|
+
"""Opening marker of the generated chart region in the binaries page."""
|
|
104
|
+
|
|
105
|
+
PAGE_TEMPLATE = """\
|
|
106
|
+
---
|
|
107
|
+
orphan: true
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
# Binaries
|
|
111
|
+
|
|
112
|
+
All standalone executables published by this repository, one row per binary, newest release first. The version links to its GitHub release, the platform to the direct binary download, and the VirusTotal cell to the file's public analysis.
|
|
113
|
+
|
|
114
|
+
Compiled Python binaries are regularly flagged by heuristic antivirus engines, so every release is submitted to [VirusTotal](https://www.virustotal.com/): this seeds vendor databases with the new signatures and keeps false positives in check. The VirusTotal cell tracks those false positives: a green check marks binaries no engine flags, and flagged binaries show the share of engine verdicts flagging them, snapshotted minutes after publication and before false-positive reports get processed. The live analysis behind the link supersedes it.
|
|
115
|
+
|
|
116
|
+
## Development builds
|
|
117
|
+
|
|
118
|
+
Fresh binaries are compiled from every push to the default branch by the [release workflow]({repo_url}/actions/workflows/release.yaml). To try the latest development build: open the most recent successful run and download the artifact matching your platform (a GitHub account is required, and the binary comes wrapped in a zip). The same builds are also attached to a rolling dev pre-release, a draft only visible to repository maintainers.
|
|
119
|
+
|
|
120
|
+
<!-- binaries-start -->
|
|
121
|
+
|
|
122
|
+
<!-- binaries-end -->
|
|
123
|
+
|
|
124
|
+
## Catalog
|
|
125
|
+
|
|
126
|
+
The table is searchable and sortable on the documentation site; the raw data lives in [`binaries.csv`](assets/binaries.csv).
|
|
127
|
+
|
|
128
|
+
```{csv-table}
|
|
129
|
+
:file: assets/binaries.csv
|
|
130
|
+
:header-rows: 1
|
|
131
|
+
:class: sphinx-datatable
|
|
132
|
+
```
|
|
133
|
+
"""
|
|
134
|
+
"""Initial page content, used when the page does not exist yet.
|
|
135
|
+
|
|
136
|
+
The `{repo_url}` placeholder is substituted with `str.replace` (not
|
|
137
|
+
`str.format`, which would choke on the csv-table directive's braces).
|
|
138
|
+
Everything outside the marker pair is written once and never touched again:
|
|
139
|
+
repositories can reword the prose without fighting the generator.
|
|
140
|
+
|
|
141
|
+
:meta hide-value:
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _platform_target(name: str) -> str:
|
|
146
|
+
"""Extract the platform-arch target from a binary filename.
|
|
147
|
+
|
|
148
|
+
Matches against the known Nuitka build targets (`linux-arm64`, …), the
|
|
149
|
+
same vocabulary the compile matrix uses. Falls back to the full filename
|
|
150
|
+
for foreign naming schemes, so the cell always identifies the file.
|
|
151
|
+
"""
|
|
152
|
+
for target in NUITKA_BUILD_TARGETS:
|
|
153
|
+
if target in name:
|
|
154
|
+
return target
|
|
155
|
+
return name
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _release_version(tag: str) -> Version | None:
|
|
159
|
+
"""Parse a release tag as a version, or `None` for foreign tag schemes."""
|
|
160
|
+
try:
|
|
161
|
+
return Version(tag.removeprefix("v"))
|
|
162
|
+
except InvalidVersion:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _binary_assets(release: ReleaseWithAssets) -> list[ReleaseAsset]:
|
|
167
|
+
"""Return the release's compiled binaries, sorted by filename."""
|
|
168
|
+
return sorted(
|
|
169
|
+
(a for a in release.assets if a.name.endswith(BINARY_ASSET_SUFFIXES)),
|
|
170
|
+
key=lambda asset: asset.name,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _at_scan_records(records: Sequence[ScanRecord]) -> dict[str, ScanRecord]:
|
|
175
|
+
"""Index records by SHA-256, keeping the earliest snapshot per file.
|
|
176
|
+
|
|
177
|
+
The earliest record is the at-release snapshot; later re-scans of the
|
|
178
|
+
same file reflect vendor whitelisting, not the state the binary shipped
|
|
179
|
+
in, so they are excluded from the catalog and chart.
|
|
180
|
+
"""
|
|
181
|
+
earliest: dict[str, ScanRecord] = {}
|
|
182
|
+
for record in records:
|
|
183
|
+
current = earliest.get(record.sha256)
|
|
184
|
+
if current is None or record.scanned < current.scanned:
|
|
185
|
+
earliest[record.sha256] = record
|
|
186
|
+
return earliest
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def render_chart_section(records: Sequence[ScanRecord]) -> str:
|
|
190
|
+
"""Render the detection trend across releases as a Chart.js timeline.
|
|
191
|
+
|
|
192
|
+
Plots the share of antivirus engine verdicts flagging each release's
|
|
193
|
+
binaries (all platforms aggregated), using the at-release snapshot of
|
|
194
|
+
every file, on a true time axis: spacing reflects the actual gaps
|
|
195
|
+
between releases. Points reuse the catalog shields' color language,
|
|
196
|
+
read at view time from sphinx-design's CSS variables so they match the
|
|
197
|
+
theme exactly (with hardcoded fallbacks). The data is embedded in the
|
|
198
|
+
page rather than fetched, so the chart also works on `file://`
|
|
199
|
+
previews; only the Chart.js bundle comes from its CDN, mirroring how
|
|
200
|
+
the table's DataTables assets load.
|
|
201
|
+
|
|
202
|
+
:return: A `## VirusTotal detections` section with a `raw` HTML fence,
|
|
203
|
+
or an empty string when fewer than two releases have records (a
|
|
204
|
+
one-point trend is not a trend).
|
|
205
|
+
"""
|
|
206
|
+
at_scan = _at_scan_records(records).values()
|
|
207
|
+
per_tag: dict[str, tuple[str, int, int]] = {}
|
|
208
|
+
for record in at_scan:
|
|
209
|
+
date, flagged, total = per_tag.get(record.tag, (record.scanned, 0, 0))
|
|
210
|
+
per_tag[record.tag] = (
|
|
211
|
+
min(date, record.scanned),
|
|
212
|
+
flagged + record.stats.flagged,
|
|
213
|
+
total + record.stats.total,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
points = []
|
|
217
|
+
for tag, (date, flagged, total) in per_tag.items():
|
|
218
|
+
version = _release_version(tag)
|
|
219
|
+
if version is None:
|
|
220
|
+
continue
|
|
221
|
+
if total:
|
|
222
|
+
points.append((version, tag, date, flagged, total))
|
|
223
|
+
points.sort()
|
|
224
|
+
if len(points) < 2:
|
|
225
|
+
return ""
|
|
226
|
+
|
|
227
|
+
payload = json.dumps(
|
|
228
|
+
[
|
|
229
|
+
{
|
|
230
|
+
"date": date,
|
|
231
|
+
"flagged": flagged,
|
|
232
|
+
"pct": round(100 * flagged / total, 1),
|
|
233
|
+
"tag": tag,
|
|
234
|
+
"total": total,
|
|
235
|
+
}
|
|
236
|
+
for _version, tag, date, flagged, total in points
|
|
237
|
+
],
|
|
238
|
+
sort_keys=True,
|
|
239
|
+
)
|
|
240
|
+
return (
|
|
241
|
+
"## VirusTotal detections\n\n"
|
|
242
|
+
"Share of antivirus engine verdicts flagging the binaries of each "
|
|
243
|
+
"release, at scan time. Colors follow the catalog shields: green "
|
|
244
|
+
f"for zero detections, amber below {FLAGGED_DANGER_PCT}%, red from "
|
|
245
|
+
"there up.\n\n"
|
|
246
|
+
"```{raw} html\n"
|
|
247
|
+
'<div style="height: 320px;"><canvas id="vt-trend"></canvas></div>\n'
|
|
248
|
+
'<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/'
|
|
249
|
+
'chart.umd.min.js"></script>\n'
|
|
250
|
+
"<script>\n"
|
|
251
|
+
f"const VT_TREND = {payload};\n"
|
|
252
|
+
f"const VT_DANGER_PCT = {FLAGGED_DANGER_PCT};\n"
|
|
253
|
+
"const vtCss = getComputedStyle(document.documentElement);\n"
|
|
254
|
+
"const vtColor = (name, fallback) =>\n"
|
|
255
|
+
" vtCss.getPropertyValue(name).trim() || fallback;\n"
|
|
256
|
+
"const vtTint = (p) => {\n"
|
|
257
|
+
' if (p.pct === 0) { return vtColor("--sd-color-success", "#28a745"); }\n'
|
|
258
|
+
" return p.pct >= VT_DANGER_PCT\n"
|
|
259
|
+
' ? vtColor("--sd-color-danger", "#dc3545")\n'
|
|
260
|
+
' : vtColor("--sd-color-warning", "#f0b37e");\n'
|
|
261
|
+
"};\n"
|
|
262
|
+
'new Chart(document.getElementById("vt-trend"), {\n'
|
|
263
|
+
' type: "line",\n'
|
|
264
|
+
" data: {\n"
|
|
265
|
+
" datasets: [{\n"
|
|
266
|
+
" data: VT_TREND.map((p) => ({x: Date.parse(p.date), y: p.pct})),\n"
|
|
267
|
+
' borderColor: "#88888866",\n'
|
|
268
|
+
" pointBackgroundColor: VT_TREND.map(vtTint),\n"
|
|
269
|
+
" pointBorderColor: VT_TREND.map(vtTint),\n"
|
|
270
|
+
" pointRadius: 4,\n"
|
|
271
|
+
" tension: 0.2,\n"
|
|
272
|
+
" }],\n"
|
|
273
|
+
" },\n"
|
|
274
|
+
" options: {\n"
|
|
275
|
+
" maintainAspectRatio: false,\n"
|
|
276
|
+
" plugins: {\n"
|
|
277
|
+
" legend: {display: false},\n"
|
|
278
|
+
" tooltip: {callbacks: {\n"
|
|
279
|
+
" title: (items) => VT_TREND[items[0].dataIndex].tag,\n"
|
|
280
|
+
" label: (item) => {\n"
|
|
281
|
+
" const p = VT_TREND[item.dataIndex];\n"
|
|
282
|
+
' return p.flagged + " / " + p.total\n'
|
|
283
|
+
' + " verdicts flagged (" + p.pct + "%)";\n'
|
|
284
|
+
" },\n"
|
|
285
|
+
" }},\n"
|
|
286
|
+
" },\n"
|
|
287
|
+
" scales: {\n"
|
|
288
|
+
" x: {\n"
|
|
289
|
+
' type: "linear",\n'
|
|
290
|
+
" ticks: {\n"
|
|
291
|
+
" maxTicksLimit: 8,\n"
|
|
292
|
+
" callback: (value) =>\n"
|
|
293
|
+
" new Date(value).toISOString().slice(0, 10),\n"
|
|
294
|
+
" },\n"
|
|
295
|
+
" },\n"
|
|
296
|
+
" y: {\n"
|
|
297
|
+
" beginAtZero: true,\n"
|
|
298
|
+
' title: {display: true, text: "Flagged verdicts (%)"},\n'
|
|
299
|
+
" },\n"
|
|
300
|
+
" },\n"
|
|
301
|
+
" },\n"
|
|
302
|
+
"});\n"
|
|
303
|
+
"</script>\n"
|
|
304
|
+
"```"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def render_binaries_csv(
|
|
309
|
+
repo_slug: str,
|
|
310
|
+
releases: Sequence[ReleaseWithAssets],
|
|
311
|
+
records: Sequence[ScanRecord],
|
|
312
|
+
) -> str:
|
|
313
|
+
"""Render the catalog data as CSV, one row per released binary.
|
|
314
|
+
|
|
315
|
+
Rows cover every published release carrying compiled binaries, ordered
|
|
316
|
+
by descending version then filename. Cells hold Markdown links (parsed
|
|
317
|
+
by MyST inside the `csv-table` directive): the version to the GitHub
|
|
318
|
+
release, the platform to the binary download, and the VirusTotal cell to
|
|
319
|
+
the file's analysis. The VirusTotal cell renders the at-release snapshot
|
|
320
|
+
as a green check when no engine flags the binary, as the flagged-verdict
|
|
321
|
+
share (tinted by {data}`FLAGGED_DANGER_PCT`) otherwise, and as a bare
|
|
322
|
+
`analysis` link when no snapshot exists. Assets without a digest get an
|
|
323
|
+
empty VirusTotal cell.
|
|
324
|
+
|
|
325
|
+
```{caution}
|
|
326
|
+
The version and platform cells decorate their links with sphinx-design's
|
|
327
|
+
`octicon` role, so the rendering repository needs `sphinx-design` in its
|
|
328
|
+
documentation build (already true across this ecosystem's docs stacks).
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
:param repo_slug: Repository in `owner/repo` form.
|
|
332
|
+
:param releases: Releases from
|
|
333
|
+
{func}`repomatic.github.releases.get_releases_with_assets`.
|
|
334
|
+
:param records: Detection snapshots from the scan history file.
|
|
335
|
+
:return: The full CSV content, header row included.
|
|
336
|
+
"""
|
|
337
|
+
versioned = [
|
|
338
|
+
(version, release)
|
|
339
|
+
for release in releases
|
|
340
|
+
if not release.draft and (version := _release_version(release.tag)) is not None
|
|
341
|
+
]
|
|
342
|
+
versioned.sort(key=lambda pair: pair[0], reverse=True)
|
|
343
|
+
|
|
344
|
+
at_scan = _at_scan_records(records)
|
|
345
|
+
buffer = io.StringIO()
|
|
346
|
+
writer = csv.writer(buffer, lineterminator="\n")
|
|
347
|
+
writer.writerow(CSV_HEADERS)
|
|
348
|
+
for version, release in versioned:
|
|
349
|
+
release_url = f"https://github.com/{repo_slug}/releases/tag/{release.tag}"
|
|
350
|
+
for asset in _binary_assets(release):
|
|
351
|
+
if asset.sha256:
|
|
352
|
+
vt_url = VIRUSTOTAL_GUI_URL.format(sha256=asset.sha256)
|
|
353
|
+
record = at_scan.get(asset.sha256)
|
|
354
|
+
if record and record.stats.total:
|
|
355
|
+
# The KPI is the distance to the goal (zero false
|
|
356
|
+
# positives), so flagged rows show the failure share and
|
|
357
|
+
# clean rows reduce to a green check: exceptions stand
|
|
358
|
+
# out, the goal state stays calm.
|
|
359
|
+
if record.stats.flagged == 0:
|
|
360
|
+
vt_cell = (
|
|
361
|
+
f"[{{octicon}}`shield-check;1em;sd-text-success`]({vt_url})"
|
|
362
|
+
)
|
|
363
|
+
else:
|
|
364
|
+
pct = 100 * record.stats.flagged / record.stats.total
|
|
365
|
+
tint = (
|
|
366
|
+
"sd-text-danger"
|
|
367
|
+
if pct >= FLAGGED_DANGER_PCT
|
|
368
|
+
else "sd-text-warning"
|
|
369
|
+
)
|
|
370
|
+
vt_cell = (
|
|
371
|
+
f"[{{octicon}}`shield;1em;{tint}` {pct:.1f}%]({vt_url})"
|
|
372
|
+
)
|
|
373
|
+
else:
|
|
374
|
+
vt_cell = f"[{{octicon}}`shield` analysis]({vt_url})"
|
|
375
|
+
else:
|
|
376
|
+
vt_cell = ""
|
|
377
|
+
# The octicon role comes from sphinx-design, same as the icons in
|
|
378
|
+
# the docs page titles; the icons are part of the link text so
|
|
379
|
+
# they stay clickable with their label.
|
|
380
|
+
writer.writerow((
|
|
381
|
+
f"[`{version}` {{octicon}}`link-external`]({release_url})",
|
|
382
|
+
f"[{{octicon}}`download` `{_platform_target(asset.name)}`]"
|
|
383
|
+
f"({asset.download_url})",
|
|
384
|
+
release.date,
|
|
385
|
+
vt_cell,
|
|
386
|
+
))
|
|
387
|
+
return buffer.getvalue()
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def update_binaries_csv(csv_path: Path, content: str) -> bool:
|
|
391
|
+
"""Write the catalog CSV, creating parent directories as needed.
|
|
392
|
+
|
|
393
|
+
:param csv_path: Path to the CSV file.
|
|
394
|
+
:param content: Rendered CSV from {func}`render_binaries_csv`.
|
|
395
|
+
:return: `True` when the file was created or its content changed.
|
|
396
|
+
"""
|
|
397
|
+
if csv_path.exists() and csv_path.read_text(encoding="UTF-8") == content:
|
|
398
|
+
logging.info(f"Binaries CSV {csv_path} already up to date.")
|
|
399
|
+
return False
|
|
400
|
+
csv_path.parent.mkdir(parents=True, exist_ok=True)
|
|
401
|
+
csv_path.write_text(content, encoding="UTF-8")
|
|
402
|
+
logging.info(f"Wrote binaries CSV {csv_path}.")
|
|
403
|
+
return True
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def update_binaries_page(page_path: Path, chart_section: str, repo_slug: str) -> bool:
|
|
407
|
+
"""Create the binaries page if missing and refresh its chart region.
|
|
408
|
+
|
|
409
|
+
A missing page is created (with parent directories) from
|
|
410
|
+
{data}`PAGE_TEMPLATE`. On an existing page only the region between
|
|
411
|
+
{data}`PAGE_START_MARKER` and {data}`PAGE_END_MARKER` is replaced,
|
|
412
|
+
leaving all surrounding prose untouched.
|
|
413
|
+
|
|
414
|
+
:param page_path: Path to the Markdown page.
|
|
415
|
+
:param chart_section: Rendered chart from {func}`render_chart_section`,
|
|
416
|
+
or an empty string to leave the region empty.
|
|
417
|
+
:param repo_slug: Repository in `owner/repo` form, interpolated into the
|
|
418
|
+
template on first creation.
|
|
419
|
+
:return: `True` when the file was created or its content changed.
|
|
420
|
+
:raises ValueError: When the page exists but lacks the markers. Loud on
|
|
421
|
+
purpose: a page not written by this generator must never be
|
|
422
|
+
overwritten.
|
|
423
|
+
"""
|
|
424
|
+
original = None
|
|
425
|
+
if page_path.exists():
|
|
426
|
+
original = page_path.read_text(encoding="UTF-8")
|
|
427
|
+
if PAGE_START_MARKER not in original or PAGE_END_MARKER not in original:
|
|
428
|
+
raise ValueError(
|
|
429
|
+
f"{page_path} lacks the {PAGE_START_MARKER} / {PAGE_END_MARKER} "
|
|
430
|
+
"markers, refusing to overwrite it."
|
|
431
|
+
)
|
|
432
|
+
text = original
|
|
433
|
+
else:
|
|
434
|
+
text = PAGE_TEMPLATE.replace("{repo_url}", f"https://github.com/{repo_slug}")
|
|
435
|
+
|
|
436
|
+
before, rest = text.split(PAGE_START_MARKER, 1)
|
|
437
|
+
_, after = rest.split(PAGE_END_MARKER, 1)
|
|
438
|
+
section = chart_section.strip()
|
|
439
|
+
between = f"\n\n{section}\n\n" if section else "\n\n"
|
|
440
|
+
new_text = before + PAGE_START_MARKER + between + PAGE_END_MARKER + after
|
|
441
|
+
|
|
442
|
+
if new_text == original:
|
|
443
|
+
logging.info(f"Binaries page {page_path} already up to date.")
|
|
444
|
+
return False
|
|
445
|
+
page_path.parent.mkdir(parents=True, exist_ok=True)
|
|
446
|
+
page_path.write_text(new_text, encoding="UTF-8")
|
|
447
|
+
logging.info(f"Wrote binaries page {page_path}.")
|
|
448
|
+
return True
|
|
@@ -112,6 +112,7 @@ FLAT_BUILD_TARGETS = [
|
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
BINARY_AFFECTING_PATHS: Final[tuple[str, ...]] = (
|
|
115
|
+
".github/workflows/_release-engine.yaml",
|
|
115
116
|
".github/workflows/release.yaml",
|
|
116
117
|
"pyproject.toml",
|
|
117
118
|
"tests/",
|
|
@@ -122,6 +123,12 @@ BINARY_AFFECTING_PATHS: Final[tuple[str, ...]] = (
|
|
|
122
123
|
Project-specific source directories (derived from `[project.scripts]` in
|
|
123
124
|
`pyproject.toml`) are added dynamically by
|
|
124
125
|
{attr}`~repomatic.metadata.Metadata.binary_affecting_paths`.
|
|
126
|
+
|
|
127
|
+
The release workflow entries cover both layouts: upstream keeps the
|
|
128
|
+
`_release-engine.yaml` lane (which defines the Nuitka compile and binary
|
|
129
|
+
self-test jobs) in-repo, while downstream repos call the engine cross-repo
|
|
130
|
+
from their generated `release.yaml`, so a pin bump there rightly triggers a
|
|
131
|
+
rebuild.
|
|
125
132
|
"""
|
|
126
133
|
|
|
127
134
|
SKIP_BINARY_BUILD_BRANCHES: Final[frozenset[str]] = frozenset((
|
|
@@ -21,7 +21,7 @@ GitHub issue. Sphinx linkcheck parsing detects broken auto-generated links
|
|
|
21
21
|
(intersphinx, autodoc, type annotations) that Lychee cannot see because they
|
|
22
22
|
only exist in the rendered HTML output.
|
|
23
23
|
|
|
24
|
-
Issue lifecycle management is delegated to {mod}`~repomatic.issue`.
|
|
24
|
+
Issue lifecycle management is delegated to {mod}`~repomatic.github.issue`.
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
from __future__ import annotations
|