repomatic 7.0.0__tar.gz → 7.1.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.1.0}/PKG-INFO +3 -3
- {repomatic-7.0.0 → repomatic-7.1.0}/pyproject.toml +21 -14
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/__init__.py +2 -2
- repomatic-7.1.0/repomatic/binaries_page.py +448 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/binary.py +7 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/cache.py +6 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/checksums.py +17 -12
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/cli.py +352 -96
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/config.py +16 -131
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/_release-engine.yaml +44 -22
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/action-publish-pypi.yaml +4 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/agent-sphinx-docs.md +24 -27
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/autofix.yaml +47 -43
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/changelog.yaml +20 -14
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/debug.yaml +1 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/docs.yaml +4 -4
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/labels.yaml +6 -6
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/lint.yaml +14 -11
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/lychee.toml +3 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-av-false-positive.md +7 -4
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-babysit-ci.md +14 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-repomatic-ship.md +1 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/tests.yaml +1 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/unsubscribe.yaml +21 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/deps_graph.py +66 -20
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/git_ops.py +93 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/issue.py +17 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/pr_body.py +94 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/releases.py +94 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/workflow_sync.py +19 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/lint_repo.py +30 -11
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/metadata.py +8 -3
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/sync_ops.py +141 -43
- repomatic-7.1.0/repomatic/templates/setup-guide-notifications-pat.md +21 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide.md +3 -1
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/tool_runner.py +256 -9
- repomatic-7.1.0/repomatic/tool_runner_page.py +217 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/uv.py +212 -28
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/version_sync.py +19 -0
- repomatic-7.1.0/repomatic/virustotal.py +467 -0
- repomatic-7.0.0/repomatic/virustotal.py +0 -417
- {repomatic-7.0.0 → repomatic-7.1.0}/license +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/readme.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/__main__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/broken_links.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/changelog.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/__init__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/agent-grunt-qa.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/agent-qa-engineer.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/autolock.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/.github/ISSUE_TEMPLATE/new-link.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/.github/code-of-conduct.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/.github/contributing.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/.github/contributing.zh.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/.github/funding.yml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/.github/pull_request_template.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/__init__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/awesome_template/license +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/bumpversion.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/cancel-runs.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/codecov.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/labeller-content-based.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/labeller-file-based.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/labels.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/mdformat.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/mypy.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/pytest.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/release.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/ruff.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-awesome-triage.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-benchmark-update.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-brand-assets.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-file-bug-report.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-repomatic-audit.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-repomatic-changelog.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-repomatic-deps.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-repomatic-init.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-repomatic-topics.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-sphinx-docs-sync.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-translation-sync.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/skill-upstream-audit.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/typos.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/uv.toml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/yamllint.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/data/zizmor.yaml +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/__init__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/actions.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/advisories.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/dev_release.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/gh.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/matrix.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/pr.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/release_sync.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/status.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/token.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/github/unsubscribe.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/images.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/init_project.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/mailmap.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/myst_converter.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/myst_docstrings.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/npm.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/py.typed +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/pypi.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/pyproject.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/registry.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/release_prep.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/rst_to_myst.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/sponsor.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/__init__.py +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/available-admonition.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/broken-links-issue.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/bump-version.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/detect-squash-merge.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/development-warning.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/fix-changelog.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/fix-typos.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/fix-vulnerable-deps.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/format-images.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/format-json.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/format-markdown.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/format-pyproject.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/format-python.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/format-shell.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/generated-footer.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/github-releases.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/immutable-releases.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/pr-metadata.md.noformat +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/prepare-release.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/refresh-tip.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/release-notes.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/release-sync-report.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-branch-ruleset.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-dependabot.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-fork-pr-approval.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-pages-source.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-pypi-trusted-publisher.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-token.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-verify.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/setup-guide-virustotal.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-action-pins.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-bumpversion.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-gitignore.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-mailmap.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-repomatic.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-tool-versions.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-uv-lock.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/sync-workflow-pins.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/unavailable-admonition.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/unsubscribe-phase1.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/unsubscribe-phase2.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/update-deps-graph.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/update-docs.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/templates/yanked-admonition.md +0 -0
- {repomatic-7.0.0 → repomatic-7.1.0}/repomatic/test_matrix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repomatic
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.1.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.1.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
|
|
@@ -5,7 +5,7 @@ requires = [ "uv-build>=0.9" ]
|
|
|
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.1.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.1.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,10 +205,8 @@ 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
|
-
|
|
205
|
-
# 8.1.0 ages past `exclude-newer`.
|
|
206
|
-
exclude-newer-package = { repomatic = "0 day" }
|
|
208
|
+
# "0 day" span.
|
|
209
|
+
exclude-newer-package = { click-extra = "2026-07-10T00:00:00Z", repomatic = "0 day" }
|
|
207
210
|
# Package is at root level, not in "./src/".
|
|
208
211
|
build-backend.module-root = ""
|
|
209
212
|
|
|
@@ -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.1.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.1.0"
|
|
238
241
|
windows-icon-from-ico = "docs/assets/icon.ico"
|
|
239
242
|
|
|
240
243
|
[tool.ruff]
|
|
@@ -315,6 +318,7 @@ warn_unused_configs = true
|
|
|
315
318
|
overrides = [
|
|
316
319
|
{ module = [
|
|
317
320
|
"py_walk.*",
|
|
321
|
+
"sphinxcontrib.mermaid",
|
|
318
322
|
"vt",
|
|
319
323
|
], ignore_missing_imports = true },
|
|
320
324
|
]
|
|
@@ -340,7 +344,7 @@ run.source = [ "repomatic" ]
|
|
|
340
344
|
report.precision = 2
|
|
341
345
|
|
|
342
346
|
[tool.bumpversion]
|
|
343
|
-
current_version = "7.
|
|
347
|
+
current_version = "7.1.0"
|
|
344
348
|
# Parse versions with an optional .devN suffix (PEP 440).
|
|
345
349
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?"
|
|
346
350
|
serialize = [
|
|
@@ -422,6 +426,9 @@ exclude = [
|
|
|
422
426
|
"archive\\.ph",
|
|
423
427
|
# npmjs.com returns 403 to bots.
|
|
424
428
|
"^https://www\\.npmjs\\.com/package/.*$",
|
|
429
|
+
# VirusTotal analysis pages are a JS app that rate-limits bots, and the
|
|
430
|
+
# binaries page links one per released binary.
|
|
431
|
+
"^https://www\\.virustotal\\.com/gui/.*$",
|
|
425
432
|
# star-history.com fragments are JS-rendered and invisible to lychee.
|
|
426
433
|
"^https://star-history\\.com/.*$",
|
|
427
434
|
# 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((
|
|
@@ -248,6 +248,12 @@ def store_binary(
|
|
|
248
248
|
tmp_path = Path(tmp)
|
|
249
249
|
shutil.copy2(source, tmp_path)
|
|
250
250
|
tmp_path.chmod(0o755)
|
|
251
|
+
# copy2 preserves the source mtime, which for a binary extracted from
|
|
252
|
+
# a release archive is the upstream build date. Purging ages entries
|
|
253
|
+
# by mtime, so an old build date would get the entry deleted by the
|
|
254
|
+
# auto_purge() call below, before this store even returns. Stamp the
|
|
255
|
+
# store time instead.
|
|
256
|
+
os.utime(tmp_path)
|
|
251
257
|
tmp_path.replace(dest)
|
|
252
258
|
except BaseException:
|
|
253
259
|
# Clean up partial writes on any failure.
|