trackerkeeper 0.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.
- trackerkeeper-0.1.0/.claude/settings.local.json +18 -0
- trackerkeeper-0.1.0/.gitattributes +5 -0
- trackerkeeper-0.1.0/.github/release-checklist-template.md +20 -0
- trackerkeeper-0.1.0/.github/workflows/aur.yml +48 -0
- trackerkeeper-0.1.0/.github/workflows/ci.yml +85 -0
- trackerkeeper-0.1.0/.github/workflows/macos.yml +224 -0
- trackerkeeper-0.1.0/.github/workflows/pypi-publish.yml +63 -0
- trackerkeeper-0.1.0/.github/workflows/release-checklist.yml +33 -0
- trackerkeeper-0.1.0/.github/workflows/release.yml +366 -0
- trackerkeeper-0.1.0/.github/workflows/winget.yml +45 -0
- trackerkeeper-0.1.0/.gitignore +10 -0
- trackerkeeper-0.1.0/AGENTS.md +125 -0
- trackerkeeper-0.1.0/CLAUDE.md +5 -0
- trackerkeeper-0.1.0/LICENSE +338 -0
- trackerkeeper-0.1.0/PKG-INFO +144 -0
- trackerkeeper-0.1.0/README.md +100 -0
- trackerkeeper-0.1.0/docs/ACCESSIBILITY.md +61 -0
- trackerkeeper-0.1.0/docs/BACKPORT.md +144 -0
- trackerkeeper-0.1.0/docs/BAKING.md +448 -0
- trackerkeeper-0.1.0/docs/CHANGELOG.md +55 -0
- trackerkeeper-0.1.0/docs/DELIVERY.md +110 -0
- trackerkeeper-0.1.0/docs/DESIGN.md +95 -0
- trackerkeeper-0.1.0/docs/MACOS.md +223 -0
- trackerkeeper-0.1.0/docs/PHILOSOPHY.md +96 -0
- trackerkeeper-0.1.0/docs/RELEASING.md +76 -0
- trackerkeeper-0.1.0/docs/TEST_BRIDGE.md +143 -0
- trackerkeeper-0.1.0/docs/TODO.md +102 -0
- trackerkeeper-0.1.0/docs/TRANSLATING.md +114 -0
- trackerkeeper-0.1.0/docs/WIND-DOWN.md +43 -0
- trackerkeeper-0.1.0/dough-sync.toml +25 -0
- trackerkeeper-0.1.0/packaging/appimage/build_appimage.sh +132 -0
- trackerkeeper-0.1.0/packaging/appimage/smoke_test_appimage.sh +39 -0
- trackerkeeper-0.1.0/packaging/aur/PKGBUILD +62 -0
- trackerkeeper-0.1.0/packaging/deb/build_deb.sh +151 -0
- trackerkeeper-0.1.0/packaging/deb/smoke_test_deb.sh +55 -0
- trackerkeeper-0.1.0/packaging/io.github.wolfgangwarehaus.trackerkeeper.desktop +20 -0
- trackerkeeper-0.1.0/packaging/io.github.wolfgangwarehaus.trackerkeeper.metainfo.xml +56 -0
- trackerkeeper-0.1.0/packaging/macos/entitlements.plist +21 -0
- trackerkeeper-0.1.0/packaging/macos/mas/entitlements.child.plist +13 -0
- trackerkeeper-0.1.0/packaging/macos/mas/entitlements.mas.plist +40 -0
- trackerkeeper-0.1.0/packaging/macos/mas/scan_symbols.sh +61 -0
- trackerkeeper-0.1.0/packaging/macos/mas/sign_mas.sh +65 -0
- trackerkeeper-0.1.0/packaging/macos/trackerkeeper.rb +24 -0
- trackerkeeper-0.1.0/packaging/msix/AppxManifest.xml +94 -0
- trackerkeeper-0.1.0/packaging/msix/STORE-SUBMISSION.md +72 -0
- trackerkeeper-0.1.0/packaging/msix/make-assets.sh +60 -0
- trackerkeeper-0.1.0/packaging/pyinstaller/launch.py +10 -0
- trackerkeeper-0.1.0/packaging/pyinstaller/trackerkeeper.spec +117 -0
- trackerkeeper-0.1.0/packaging/templates/appimage/build_appimage.sh.j2 +132 -0
- trackerkeeper-0.1.0/packaging/templates/appimage/smoke_test_appimage.sh.j2 +39 -0
- trackerkeeper-0.1.0/packaging/templates/aur/PKGBUILD.j2 +62 -0
- trackerkeeper-0.1.0/packaging/templates/deb/build_deb.sh.j2 +156 -0
- trackerkeeper-0.1.0/packaging/templates/deb/smoke_test_deb.sh.j2 +55 -0
- trackerkeeper-0.1.0/packaging/templates/macos/entitlements.plist.j2 +21 -0
- trackerkeeper-0.1.0/packaging/templates/macos/mas/entitlements.child.plist.j2 +13 -0
- trackerkeeper-0.1.0/packaging/templates/macos/mas/entitlements.mas.plist.j2 +40 -0
- trackerkeeper-0.1.0/packaging/templates/macos/mas/scan_symbols.sh.j2 +61 -0
- trackerkeeper-0.1.0/packaging/templates/macos/mas/sign_mas.sh.j2 +65 -0
- trackerkeeper-0.1.0/packaging/templates/macos/{{app_slug}}.rb.j2 +28 -0
- trackerkeeper-0.1.0/packaging/templates/msix/AppxManifest.xml.j2 +94 -0
- trackerkeeper-0.1.0/packaging/templates/msix/STORE-SUBMISSION.md.j2 +72 -0
- trackerkeeper-0.1.0/packaging/templates/msix/make-assets.sh.j2 +60 -0
- trackerkeeper-0.1.0/packaging/templates/pyinstaller/launch.py.j2 +11 -0
- trackerkeeper-0.1.0/packaging/templates/pyinstaller/{{app_slug}}.spec.j2 +117 -0
- trackerkeeper-0.1.0/packaging/templates/windows/version_info.txt.j2 +35 -0
- trackerkeeper-0.1.0/packaging/templates/windows/{{app_slug}}.iss.j2 +59 -0
- trackerkeeper-0.1.0/packaging/templates/{{app_id_base}}.desktop.j2 +20 -0
- trackerkeeper-0.1.0/packaging/templates/{{app_id_base}}.metainfo.xml.j2 +80 -0
- trackerkeeper-0.1.0/packaging/windows/trackerkeeper.iss +57 -0
- trackerkeeper-0.1.0/packaging/windows/version_info.txt +35 -0
- trackerkeeper-0.1.0/pyproject.toml +237 -0
- trackerkeeper-0.1.0/setup.cfg +4 -0
- trackerkeeper-0.1.0/tests/baselines/settings.png +0 -0
- trackerkeeper-0.1.0/tests/baselines/window.png +0 -0
- trackerkeeper-0.1.0/tests/conftest.py +61 -0
- trackerkeeper-0.1.0/tests/test_a11y.py +126 -0
- trackerkeeper-0.1.0/tests/test_app_wiring.py +121 -0
- trackerkeeper-0.1.0/tests/test_bake.py +303 -0
- trackerkeeper-0.1.0/tests/test_blur_kwin.py +84 -0
- trackerkeeper-0.1.0/tests/test_boot.py +35 -0
- trackerkeeper-0.1.0/tests/test_breadboard.py +367 -0
- trackerkeeper-0.1.0/tests/test_bridge.py +270 -0
- trackerkeeper-0.1.0/tests/test_bus.py +54 -0
- trackerkeeper-0.1.0/tests/test_catalog.py +76 -0
- trackerkeeper-0.1.0/tests/test_credentials.py +275 -0
- trackerkeeper-0.1.0/tests/test_dashboard.py +199 -0
- trackerkeeper-0.1.0/tests/test_deliver.py +258 -0
- trackerkeeper-0.1.0/tests/test_diagnostics.py +102 -0
- trackerkeeper-0.1.0/tests/test_drag_repaint.py +272 -0
- trackerkeeper-0.1.0/tests/test_i18n.py +60 -0
- trackerkeeper-0.1.0/tests/test_i18n_fmt.py +128 -0
- trackerkeeper-0.1.0/tests/test_i18n_plurals.py +125 -0
- trackerkeeper-0.1.0/tests/test_identity.py +89 -0
- trackerkeeper-0.1.0/tests/test_imports.py +103 -0
- trackerkeeper-0.1.0/tests/test_log.py +87 -0
- trackerkeeper-0.1.0/tests/test_metadata.py +194 -0
- trackerkeeper-0.1.0/tests/test_noborder.py +278 -0
- trackerkeeper-0.1.0/tests/test_power_runtime.py +34 -0
- trackerkeeper-0.1.0/tests/test_public_api.py +85 -0
- trackerkeeper-0.1.0/tests/test_relative_time.py +42 -0
- trackerkeeper-0.1.0/tests/test_rig.py +124 -0
- trackerkeeper-0.1.0/tests/test_rtl_smoke.py +75 -0
- trackerkeeper-0.1.0/tests/test_settings_migration.py +86 -0
- trackerkeeper-0.1.0/tests/test_single_instance_forwarding.py +192 -0
- trackerkeeper-0.1.0/tests/test_sources.py +230 -0
- trackerkeeper-0.1.0/tests/test_tray.py +90 -0
- trackerkeeper-0.1.0/tests/test_ui_helpers_widgets.py +31 -0
- trackerkeeper-0.1.0/tests/test_updates.py +226 -0
- trackerkeeper-0.1.0/tests/test_window_seams.py +133 -0
- trackerkeeper-0.1.0/trackerkeeper/__init__.py +55 -0
- trackerkeeper-0.1.0/trackerkeeper/__main__.py +3 -0
- trackerkeeper-0.1.0/trackerkeeper/_version.py +24 -0
- trackerkeeper-0.1.0/trackerkeeper/app.py +445 -0
- trackerkeeper-0.1.0/trackerkeeper/assets/__init__.py +2 -0
- trackerkeeper-0.1.0/trackerkeeper/assets/trackerkeeper.svg +12 -0
- trackerkeeper-0.1.0/trackerkeeper/async_io.py +299 -0
- trackerkeeper-0.1.0/trackerkeeper/autostart/__init__.py +52 -0
- trackerkeeper-0.1.0/trackerkeeper/autostart/_linux.py +129 -0
- trackerkeeper-0.1.0/trackerkeeper/autostart/_macos.py +163 -0
- trackerkeeper-0.1.0/trackerkeeper/autostart/_msix.py +181 -0
- trackerkeeper-0.1.0/trackerkeeper/autostart/_unsupported.py +22 -0
- trackerkeeper-0.1.0/trackerkeeper/autostart/_windows.py +96 -0
- trackerkeeper-0.1.0/trackerkeeper/bake.py +217 -0
- trackerkeeper-0.1.0/trackerkeeper/blur/__init__.py +212 -0
- trackerkeeper-0.1.0/trackerkeeper/blur/_dwm.py +348 -0
- trackerkeeper-0.1.0/trackerkeeper/blur/_faux_frost.py +143 -0
- trackerkeeper-0.1.0/trackerkeeper/blur/_kwin.py +478 -0
- trackerkeeper-0.1.0/trackerkeeper/blur/_macos.py +356 -0
- trackerkeeper-0.1.0/trackerkeeper/blur/_unsupported.py +39 -0
- trackerkeeper-0.1.0/trackerkeeper/boot_timing.py +75 -0
- trackerkeeper-0.1.0/trackerkeeper/breadboard.py +1554 -0
- trackerkeeper-0.1.0/trackerkeeper/bus.py +110 -0
- trackerkeeper-0.1.0/trackerkeeper/catalog.py +176 -0
- trackerkeeper-0.1.0/trackerkeeper/color_tokens.py +697 -0
- trackerkeeper-0.1.0/trackerkeeper/credentials.py +471 -0
- trackerkeeper-0.1.0/trackerkeeper/custom_tooltip.py +254 -0
- trackerkeeper-0.1.0/trackerkeeper/dashboard.py +704 -0
- trackerkeeper-0.1.0/trackerkeeper/deliver.py +442 -0
- trackerkeeper-0.1.0/trackerkeeper/design_tokens.py +415 -0
- trackerkeeper-0.1.0/trackerkeeper/diagnostics.py +168 -0
- trackerkeeper-0.1.0/trackerkeeper/drag_repaint/__init__.py +68 -0
- trackerkeeper-0.1.0/trackerkeeper/drag_repaint/_kwin.py +192 -0
- trackerkeeper-0.1.0/trackerkeeper/drag_repaint/_unsupported.py +41 -0
- trackerkeeper-0.1.0/trackerkeeper/drag_repaint/effect/dragrepaint/contents/code/main.js +86 -0
- trackerkeeper-0.1.0/trackerkeeper/drag_repaint/effect/dragrepaint/metadata.json +17 -0
- trackerkeeper-0.1.0/trackerkeeper/frosted_dialog.py +377 -0
- trackerkeeper-0.1.0/trackerkeeper/i18n/__init__.py +101 -0
- trackerkeeper-0.1.0/trackerkeeper/i18n/fmt.py +137 -0
- trackerkeeper-0.1.0/trackerkeeper/icon_button.py +137 -0
- trackerkeeper-0.1.0/trackerkeeper/icons.py +532 -0
- trackerkeeper-0.1.0/trackerkeeper/identity.py +135 -0
- trackerkeeper-0.1.0/trackerkeeper/item_dialog.py +185 -0
- trackerkeeper-0.1.0/trackerkeeper/kde_titlebar.py +196 -0
- trackerkeeper-0.1.0/trackerkeeper/keyboard_focus.py +353 -0
- trackerkeeper-0.1.0/trackerkeeper/log.py +112 -0
- trackerkeeper-0.1.0/trackerkeeper/macos_menubar.py +282 -0
- trackerkeeper-0.1.0/trackerkeeper/macos_window.py +172 -0
- trackerkeeper-0.1.0/trackerkeeper/metadata.py +121 -0
- trackerkeeper-0.1.0/trackerkeeper/noborder/__init__.py +61 -0
- trackerkeeper-0.1.0/trackerkeeper/noborder/_kwin.py +247 -0
- trackerkeeper-0.1.0/trackerkeeper/noborder/_unsupported.py +42 -0
- trackerkeeper-0.1.0/trackerkeeper/notifications/__init__.py +84 -0
- trackerkeeper-0.1.0/trackerkeeper/notifications/_linux.py +68 -0
- trackerkeeper-0.1.0/trackerkeeper/notifications/_macos.py +159 -0
- trackerkeeper-0.1.0/trackerkeeper/notifications/_unsupported.py +22 -0
- trackerkeeper-0.1.0/trackerkeeper/notifications/_windows.py +115 -0
- trackerkeeper-0.1.0/trackerkeeper/platform_compat.py +149 -0
- trackerkeeper-0.1.0/trackerkeeper/power/__init__.py +84 -0
- trackerkeeper-0.1.0/trackerkeeper/power/_linux.py +79 -0
- trackerkeeper-0.1.0/trackerkeeper/power/_unsupported.py +18 -0
- trackerkeeper-0.1.0/trackerkeeper/power/_windows.py +52 -0
- trackerkeeper-0.1.0/trackerkeeper/rig.py +338 -0
- trackerkeeper-0.1.0/trackerkeeper/scaffold.py +310 -0
- trackerkeeper-0.1.0/trackerkeeper/selector.py +529 -0
- trackerkeeper-0.1.0/trackerkeeper/settings.py +226 -0
- trackerkeeper-0.1.0/trackerkeeper/settings_dialog.py +307 -0
- trackerkeeper-0.1.0/trackerkeeper/settings_migration.py +101 -0
- trackerkeeper-0.1.0/trackerkeeper/single_instance.py +330 -0
- trackerkeeper-0.1.0/trackerkeeper/smooth_scroll.py +155 -0
- trackerkeeper-0.1.0/trackerkeeper/sources.py +318 -0
- trackerkeeper-0.1.0/trackerkeeper/system_accent.py +363 -0
- trackerkeeper-0.1.0/trackerkeeper/terminal.py +423 -0
- trackerkeeper-0.1.0/trackerkeeper/test_bridge.py +514 -0
- trackerkeeper-0.1.0/trackerkeeper/theme.py +725 -0
- trackerkeeper-0.1.0/trackerkeeper/top_bar.py +154 -0
- trackerkeeper-0.1.0/trackerkeeper/tray.py +184 -0
- trackerkeeper-0.1.0/trackerkeeper/ui_helpers.py +2294 -0
- trackerkeeper-0.1.0/trackerkeeper/update_chip.py +119 -0
- trackerkeeper-0.1.0/trackerkeeper/updates.py +216 -0
- trackerkeeper-0.1.0/trackerkeeper/win_frameless.py +364 -0
- trackerkeeper-0.1.0/trackerkeeper/window.py +609 -0
- trackerkeeper-0.1.0/trackerkeeper/windows_shortcut.py +355 -0
- trackerkeeper-0.1.0/trackerkeeper-breadboard.toml +255 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/PKG-INFO +144 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/SOURCES.txt +199 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/dependency_links.txt +1 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/entry_points.txt +8 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/requires.txt +33 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/scm_file_list.json +194 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/scm_version.json +8 -0
- trackerkeeper-0.1.0/trackerkeeper.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(ls -la /home/august/Projects/ && ls -la /home/august/Projects/dough)",
|
|
5
|
+
"Bash(xargs -n1 basename)",
|
|
6
|
+
"Bash(timeout 300 bash -lc 'ruff check . >/tmp/ruff.txt 2>&1; echo \"ruff exit=$?\"; python -m trackerkeeper.bake --check >/tmp/bake.txt 2>&1; echo \"bake exit=$?\"')",
|
|
7
|
+
"Bash(timeout 400 pytest -q)",
|
|
8
|
+
"Bash(timeout 120 pytest -q tests/test_imports.py)",
|
|
9
|
+
"Bash(python -c \"import sys; print\\(sys.prefix\\)\")",
|
|
10
|
+
"Read(//usr/bin/**)",
|
|
11
|
+
"Bash(.venv/bin/python -c \"import trackerkeeper; print\\('ok', trackerkeeper.__file__\\)\")",
|
|
12
|
+
"Bash(timeout 400 .venv/bin/python -m pytest -q)",
|
|
13
|
+
"Bash(timeout 500 xvfb-run -a .venv/bin/python -m pytest -q)",
|
|
14
|
+
"Bash(timeout 500 .venv/bin/python -m pytest -q -p no:cacheprovider)",
|
|
15
|
+
"Bash(QT_QPA_PLATFORM=offscreen timeout 500 .venv/bin/python -m pytest -q)"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Build anywhere: every text file checks out LF on every OS. Windows git
|
|
2
|
+
# defaults (core.autocrlf=true on the GitHub runners) would otherwise convert
|
|
3
|
+
# the working tree to CRLF — breaking the byte-exact `dough bake --check`
|
|
4
|
+
# drift gate and any rendered shell script.
|
|
5
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Release ${TAG} — propagation checklist
|
|
2
|
+
|
|
3
|
+
Auto-opened by `release-checklist.yml` on the `${TAG}` tag. Tick each channel as
|
|
4
|
+
it propagates. See `docs/RELEASING.md`.
|
|
5
|
+
|
|
6
|
+
## Automated — verify
|
|
7
|
+
- [ ] **GitHub release** drafted by `release.yml` — `.deb`, AppImage + `.zsync`, sdist/wheel, `SHA256SUMS`, Sigstore attestations
|
|
8
|
+
- [ ] Cross-distro **smoke tests** green (deb + AppImage self-containment)
|
|
9
|
+
- [ ] **Review the draft** and click **Publish** ← the one human gate
|
|
10
|
+
- [ ] **PyPI** — `pypi-publish.yml` uploaded `${V}`: https://pypi.org/project/trackerkeeper-base/${V}/
|
|
11
|
+
- [ ] Provenance verifies: `gh attestation verify <asset> --repo wolfgangwarehaus/trackerkeeper`
|
|
12
|
+
|
|
13
|
+
## Dormant / deferred — light up when configured (docs/TODO.md)
|
|
14
|
+
- [ ] **AUR** — `aur.yml` pushed `trackerkeeper ${V}` (needs `AUR_SSH_PRIVATE_KEY` + AUR registration)
|
|
15
|
+
- [ ] **winget / MSIX / Windows Inno** — deferred (no Windows channel yet)
|
|
16
|
+
- [ ] **macOS cask** — deferred (needs an Apple Developer account)
|
|
17
|
+
|
|
18
|
+
## Housekeeping
|
|
19
|
+
- [ ] `docs/CHANGELOG.md`: the `[${V}]` section is dated + accurate
|
|
20
|
+
- [ ] `[Unreleased]` reset to empty for the next cycle
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: aur
|
|
2
|
+
|
|
3
|
+
# Publish the PKGBUILD to the AUR when a release is PUBLISHED: stamp pkgver from
|
|
4
|
+
# the tag, refresh the checksum via updpkgsums, regenerate .SRCINFO, and push to
|
|
5
|
+
# aur@aur.archlinux.org.
|
|
6
|
+
#
|
|
7
|
+
# DORMANT until BOTH: (a) the AUR reopens new-package registration (frozen after
|
|
8
|
+
# the 2026 malware wave) AND (b) you add the AUR_SSH_PRIVATE_KEY secret (a
|
|
9
|
+
# dedicated AUR deploy keypair; public half on your AUR account). Until then the
|
|
10
|
+
# steps skip with a one-line pointer. See docs/RELEASING.md.
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
release:
|
|
14
|
+
types: [released]
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
aur:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
env:
|
|
23
|
+
HAVE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY != '' }}
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
if: env.HAVE_KEY == 'true'
|
|
27
|
+
- name: Stamp PKGBUILD pkgver from the tag
|
|
28
|
+
if: env.HAVE_KEY == 'true'
|
|
29
|
+
run: |
|
|
30
|
+
V="${{ github.event.release.tag_name }}"; V="${V#v}"
|
|
31
|
+
sed -i -E "s/^pkgver=.*/pkgver=${V}/; s/^pkgrel=.*/pkgrel=1/" packaging/aur/PKGBUILD
|
|
32
|
+
- name: Publish to the AUR
|
|
33
|
+
if: env.HAVE_KEY == 'true'
|
|
34
|
+
uses: KSXGitHub/github-actions-deploy-aur@084b0d9b15415bf9cdb65d44dad1efe37a354050 # v4.2.0
|
|
35
|
+
with:
|
|
36
|
+
pkgname: trackerkeeper
|
|
37
|
+
pkgbuild: packaging/aur/PKGBUILD
|
|
38
|
+
commit_username: wolfgangwarehaus
|
|
39
|
+
commit_email: augustvontrips@gmail.com
|
|
40
|
+
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
41
|
+
commit_message: trackerkeeper ${{ github.event.release.tag_name }}
|
|
42
|
+
updpkgsums: true
|
|
43
|
+
ssh_keyscan_types: rsa,ecdsa,ed25519
|
|
44
|
+
- name: Dormant — AUR_SSH_PRIVATE_KEY not set
|
|
45
|
+
if: env.HAVE_KEY != 'true'
|
|
46
|
+
run: |
|
|
47
|
+
echo "AUR_SSH_PRIVATE_KEY is not set → AUR auto-deploy is DORMANT (no-op)."
|
|
48
|
+
echo "(AUR new-package registration is also frozen.) See docs/RELEASING.md."
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint-and-smoke:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
# The editable install runs setuptools-scm, which derives the version
|
|
15
|
+
# from the tag — it needs the full history + tags, not a shallow clone.
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
# PySide6's offscreen platform plugin still needs the EGL/GL/xkb libs.
|
|
21
|
+
- name: System libs for headless Qt
|
|
22
|
+
run: sudo apt-get update && sudo apt-get install -y libegl1 libgl1 libxkbcommon0
|
|
23
|
+
# Install the project itself, not a hand-picked dep list — the boot smoke
|
|
24
|
+
# imports the real app, so it needs whatever [project].dependencies says
|
|
25
|
+
# (a fork that adds deps stays green without touching this file).
|
|
26
|
+
- name: Install
|
|
27
|
+
run: pip install ruff -e .
|
|
28
|
+
- name: Lint
|
|
29
|
+
run: ruff check trackerkeeper/
|
|
30
|
+
# The rig owns the smoke (one source — the same verb a maker or an AI
|
|
31
|
+
# runs locally): offscreen boot + settings dialog. See trackerkeeper/rig.py.
|
|
32
|
+
- name: Boot smoke (offscreen)
|
|
33
|
+
run: python -m trackerkeeper.rig boot
|
|
34
|
+
|
|
35
|
+
tests:
|
|
36
|
+
# Every platform trackerkeeper claims to deliver to gets the suite run ON it — the
|
|
37
|
+
# rigging-and-testing direction: a Windows/macOS code bump should fail HERE,
|
|
38
|
+
# not at release time. Linux stays the canonical leg (it also gates bake).
|
|
39
|
+
strategy:
|
|
40
|
+
fail-fast: false
|
|
41
|
+
matrix:
|
|
42
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
43
|
+
runs-on: ${{ matrix.os }}
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
with:
|
|
47
|
+
# The editable install runs setuptools-scm, which derives the version
|
|
48
|
+
# from the tag — it needs the full history + tags, not a shallow clone.
|
|
49
|
+
fetch-depth: 0
|
|
50
|
+
- uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: "3.12"
|
|
53
|
+
# PySide6's offscreen platform plugin still needs the EGL/GL/xkb libs.
|
|
54
|
+
- name: System libs for headless Qt
|
|
55
|
+
if: runner.os == 'Linux'
|
|
56
|
+
run: sudo apt-get update && sudo apt-get install -y libegl1 libgl1 libxkbcommon0
|
|
57
|
+
- name: Install (core + dev extras)
|
|
58
|
+
run: pip install -e .[dev]
|
|
59
|
+
- name: Packaging drift gate (trackerkeeper bake --check)
|
|
60
|
+
# Linux-only: the gate validates exec bits on rendered .sh, which have
|
|
61
|
+
# no meaning on the Windows filesystem.
|
|
62
|
+
if: runner.os == 'Linux'
|
|
63
|
+
run: python -m trackerkeeper.bake --check
|
|
64
|
+
- name: Pytest (offscreen)
|
|
65
|
+
env:
|
|
66
|
+
QT_QPA_PLATFORM: offscreen
|
|
67
|
+
run: pytest
|
|
68
|
+
|
|
69
|
+
# Gate-test the PyPI build itself (the publish workflow runs the same steps on a
|
|
70
|
+
# tag): catch a broken sdist/wheel or bad metadata BEFORE a release, not during.
|
|
71
|
+
build:
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v4
|
|
75
|
+
with:
|
|
76
|
+
# setuptools-scm derives the version from the tag — needs full history.
|
|
77
|
+
fetch-depth: 0
|
|
78
|
+
- uses: actions/setup-python@v5
|
|
79
|
+
with:
|
|
80
|
+
python-version: "3.12"
|
|
81
|
+
- name: Build + check sdist & wheel
|
|
82
|
+
run: |
|
|
83
|
+
python -m pip install --upgrade pip build twine
|
|
84
|
+
python -m build
|
|
85
|
+
python -m twine check dist/*
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
name: macos
|
|
2
|
+
|
|
3
|
+
# Build, sign, notarize, and ship the macOS .dmg + bump the Homebrew cask when a
|
|
4
|
+
# release is PUBLISHED. DORMANT until the Apple secrets exist (the whole job
|
|
5
|
+
# skips) — macOS is a present-but-dormant goal (docs/BAKING.md §5).
|
|
6
|
+
#
|
|
7
|
+
# One UNIVERSAL2 .dmg (arm64 + x86_64 in a single bundle) — trackerkeeper has no native
|
|
8
|
+
# deps, so a single fat build reaches both chips (see the freeze step for the
|
|
9
|
+
# per-arch-matrix fallback).
|
|
10
|
+
#
|
|
11
|
+
# Activate (one-time): a $99/yr Apple Developer membership, then add the secrets
|
|
12
|
+
# MACOS_CERTIFICATE (base64 of a Developer ID Application .p12), MACOS_CERTIFICATE_PWD,
|
|
13
|
+
# MACOS_KEYCHAIN_PWD, APPLE_TEAM_ID, and HOMEBREW_TAP_TOKEN (a PAT for the separate
|
|
14
|
+
# homebrew-<tap> repo). For NOTARIZATION add EITHER the App Store Connect API key
|
|
15
|
+
# (PREFERRED — scoped + revocable, no 2FA coupling): APPLE_API_KEY_ID,
|
|
16
|
+
# APPLE_API_ISSUER, APPLE_API_KEY_B64 (base64 of the AuthKey_XXXX.p8); OR the
|
|
17
|
+
# Apple-ID fallback: APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD. See docs/RELEASING.md.
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
release:
|
|
21
|
+
types: [released]
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
inputs:
|
|
24
|
+
tag:
|
|
25
|
+
description: "Release tag to build (e.g. v0.1.0)"
|
|
26
|
+
required: true
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: write # gh release upload
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
macos:
|
|
33
|
+
# macos-14 = the oldest FREE Apple-silicon image, and setup-python installs a
|
|
34
|
+
# universal2 CPython framework on it — so the freeze can emit both slices for
|
|
35
|
+
# a single universal2 .app/.dmg. The bundle's honest macOS floor is pinned to
|
|
36
|
+
# this runner's OS (see the freeze step). FALLBACK: a fork that later adds a
|
|
37
|
+
# native dependency WITHOUT universal2 wheels must switch to a per-arch
|
|
38
|
+
# (arm64 + x86_64) matrix — PyInstaller --target-arch universal2 aborts on the
|
|
39
|
+
# thin binaries (jellytoast ships exactly that matrix for its libmpv build).
|
|
40
|
+
runs-on: macos-14
|
|
41
|
+
env:
|
|
42
|
+
HAVE_APPLE: ${{ secrets.APPLE_TEAM_ID != '' }}
|
|
43
|
+
# secrets.* is NOT a valid context inside `if:` — referencing it there makes
|
|
44
|
+
# GitHub reject the whole workflow file at parse time (a 0s "workflow file
|
|
45
|
+
# issue" failure on every push). Gate via env, same as HAVE_APPLE.
|
|
46
|
+
HAVE_TAP: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }}
|
|
47
|
+
permissions:
|
|
48
|
+
contents: write # gh release upload (+ attest reads the artifact)
|
|
49
|
+
id-token: write # Sigstore OIDC for build provenance
|
|
50
|
+
attestations: write # write the provenance attestation
|
|
51
|
+
steps:
|
|
52
|
+
- name: Dormant — Apple secrets not set
|
|
53
|
+
if: env.HAVE_APPLE != 'true'
|
|
54
|
+
run: |
|
|
55
|
+
echo "APPLE_TEAM_ID is not set → the macOS channel is DORMANT (no-op)."
|
|
56
|
+
echo "Activate with an Apple Developer account + the secrets. See docs/RELEASING.md."
|
|
57
|
+
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
if: env.HAVE_APPLE == 'true'
|
|
60
|
+
with:
|
|
61
|
+
ref: ${{ github.event.release.tag_name || inputs.tag }}
|
|
62
|
+
fetch-depth: 0
|
|
63
|
+
- uses: actions/setup-python@v5
|
|
64
|
+
if: env.HAVE_APPLE == 'true'
|
|
65
|
+
with:
|
|
66
|
+
# setup-python's macOS builds are universal2 framework builds — needed
|
|
67
|
+
# so PyInstaller can freeze a universal2 (arm64 + x86_64) bundle.
|
|
68
|
+
python-version: "3.12"
|
|
69
|
+
- name: Resolve version
|
|
70
|
+
id: ver
|
|
71
|
+
if: env.HAVE_APPLE == 'true'
|
|
72
|
+
run: |
|
|
73
|
+
V="${{ github.event.release.tag_name || inputs.tag }}"
|
|
74
|
+
echo "version=${V#v}" >> "$GITHUB_OUTPUT"
|
|
75
|
+
|
|
76
|
+
- name: Build the .app (render manifests + freeze)
|
|
77
|
+
if: env.HAVE_APPLE == 'true'
|
|
78
|
+
env:
|
|
79
|
+
VERSION: ${{ steps.ver.outputs.version }}
|
|
80
|
+
run: |
|
|
81
|
+
python -m pip install --upgrade pip
|
|
82
|
+
pip install ".[bake]" pyinstaller
|
|
83
|
+
brew install librsvg create-dmg
|
|
84
|
+
python -m trackerkeeper.bake --packaging packaging --release-version "$VERSION" --release-date "$(date -u +%F)"
|
|
85
|
+
# Render the .icns from the brand SVG (an .iconset of the required sizes).
|
|
86
|
+
ICONSET=packaging/macos/trackerkeeper.iconset
|
|
87
|
+
mkdir -p "$ICONSET"
|
|
88
|
+
# Canonical iconutil sizes only (NOT 64 — iconutil rejects a non-standard
|
|
89
|
+
# icon_64x64). Each base size + its @2x.
|
|
90
|
+
for s in 16 32 128 256 512; do
|
|
91
|
+
rsvg-convert -w "$s" -h "$s" trackerkeeper/assets/trackerkeeper.svg -o "$ICONSET/icon_${s}x${s}.png"
|
|
92
|
+
rsvg-convert -w $((s*2)) -h $((s*2)) trackerkeeper/assets/trackerkeeper.svg -o "$ICONSET/icon_${s}x${s}@2x.png"
|
|
93
|
+
done
|
|
94
|
+
iconutil -c icns "$ICONSET" -o packaging/macos/trackerkeeper.icns
|
|
95
|
+
# UNIVERSAL2: the spec's EXE carries target_arch="universal2" (rendered
|
|
96
|
+
# by `trackerkeeper bake`), and the universal2 CPython above lets the freeze emit
|
|
97
|
+
# both slices; trackerkeeper has no native deps, so every wheel ships universal2
|
|
98
|
+
# and the freeze never hits a thin binary.
|
|
99
|
+
#
|
|
100
|
+
# HONEST FLOOR: the bundle's REAL macOS minimum is the runner OS — this is
|
|
101
|
+
# the only macOS we ever build + smoke-test on, so we can't truthfully
|
|
102
|
+
# claim to support anything older. Match the C-extension deployment target
|
|
103
|
+
# to that floor BEFORE the freeze (so any source-built wheel agrees), then
|
|
104
|
+
# stamp it into the frozen Info.plist AFTER the freeze. An unpinned build
|
|
105
|
+
# would inherit the spec's low default and dock-bounce-and-die (dyld/Qt
|
|
106
|
+
# bail before first paint) on a too-old macOS; the offscreen smoke-test
|
|
107
|
+
# below runs ON this floor, so a too-high floor can't ship silently either.
|
|
108
|
+
FLOOR="$(sw_vers -productVersion | cut -d. -f1).0"
|
|
109
|
+
export MACOSX_DEPLOYMENT_TARGET="$FLOOR"
|
|
110
|
+
pyinstaller packaging/pyinstaller/trackerkeeper.spec --noconfirm
|
|
111
|
+
/usr/libexec/PlistBuddy -c "Set :LSMinimumSystemVersion $FLOOR" \
|
|
112
|
+
"dist/trackerkeeper.app/Contents/Info.plist"
|
|
113
|
+
echo "LSMinimumSystemVersion now: $(/usr/libexec/PlistBuddy -c \
|
|
114
|
+
'Print :LSMinimumSystemVersion' dist/trackerkeeper.app/Contents/Info.plist)"
|
|
115
|
+
|
|
116
|
+
- name: Smoke-test the .app (boots headless, offscreen)
|
|
117
|
+
if: env.HAVE_APPLE == 'true'
|
|
118
|
+
# Boot the frozen binary with no display: an import/loader failure (a
|
|
119
|
+
# missing bundled lib, a broken universal2 slice) dies fast with a real
|
|
120
|
+
# exit code; surviving ~12s means Qt + the bundle initialized. Runs ON the
|
|
121
|
+
# pinned floor OS, so a false/too-high floor can't ship silently.
|
|
122
|
+
env:
|
|
123
|
+
QT_QPA_PLATFORM: offscreen
|
|
124
|
+
run: |
|
|
125
|
+
BIN="dist/trackerkeeper.app/Contents/MacOS/trackerkeeper"
|
|
126
|
+
test -x "$BIN"
|
|
127
|
+
"$BIN" &
|
|
128
|
+
PID=$!
|
|
129
|
+
sleep 12
|
|
130
|
+
if kill -0 "$PID" 2>/dev/null; then
|
|
131
|
+
echo "still alive after 12s — Qt + bundle initialized OK"
|
|
132
|
+
kill "$PID" 2>/dev/null || true
|
|
133
|
+
else
|
|
134
|
+
wait "$PID"; rc=$?
|
|
135
|
+
echo "app exited early (rc=$rc) — likely an import/loader failure"
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
- name: Import the signing certificate
|
|
140
|
+
if: env.HAVE_APPLE == 'true'
|
|
141
|
+
env:
|
|
142
|
+
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
|
143
|
+
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
|
144
|
+
MACOS_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PWD }}
|
|
145
|
+
run: |
|
|
146
|
+
echo "$MACOS_CERTIFICATE" | base64 --decode > cert.p12
|
|
147
|
+
security create-keychain -p "$MACOS_KEYCHAIN_PWD" build.keychain
|
|
148
|
+
security default-keychain -s build.keychain
|
|
149
|
+
security unlock-keychain -p "$MACOS_KEYCHAIN_PWD" build.keychain
|
|
150
|
+
security import cert.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
|
151
|
+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PWD" build.keychain
|
|
152
|
+
# codesign searches the keychain SEARCH LIST, not just the default — add ours.
|
|
153
|
+
security list-keychains -d user -s build.keychain $(security list-keychains -d user | tr -d '"')
|
|
154
|
+
rm -f cert.p12
|
|
155
|
+
|
|
156
|
+
- name: Sign, package, notarize, staple
|
|
157
|
+
if: env.HAVE_APPLE == 'true'
|
|
158
|
+
env:
|
|
159
|
+
# Notarization credentials — API key PREFERRED, Apple ID as fallback.
|
|
160
|
+
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
161
|
+
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
162
|
+
APPLE_API_KEY_B64: ${{ secrets.APPLE_API_KEY_B64 }}
|
|
163
|
+
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
164
|
+
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
165
|
+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
166
|
+
VERSION: ${{ steps.ver.outputs.version }}
|
|
167
|
+
run: |
|
|
168
|
+
IDENTITY="Developer ID Application"
|
|
169
|
+
APP="dist/trackerkeeper.app"
|
|
170
|
+
ENTITLEMENTS="packaging/macos/entitlements.plist"
|
|
171
|
+
# Sign EVERY nested Mach-O bottom-up — dylibs, .so, AND the Qt/Python
|
|
172
|
+
# .framework binaries (which are neither *.dylib nor *.so, so a name
|
|
173
|
+
# filter misses them and notarization rejects the unsigned code) — then
|
|
174
|
+
# the .app, all with the hardened runtime + entitlements (so PySide6's
|
|
175
|
+
# dylibs still load under disabled library-validation).
|
|
176
|
+
find "$APP" -type f -print0 | while IFS= read -r -d '' f; do
|
|
177
|
+
if file "$f" | grep -q "Mach-O"; then
|
|
178
|
+
codesign --force --timestamp --options runtime \
|
|
179
|
+
--entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$f"
|
|
180
|
+
fi
|
|
181
|
+
done
|
|
182
|
+
codesign --force --timestamp --options runtime \
|
|
183
|
+
--entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP"
|
|
184
|
+
codesign --verify --strict --verbose=2 "$APP"
|
|
185
|
+
DMG="dist/trackerkeeper-${VERSION}-macos.dmg"
|
|
186
|
+
create-dmg --volname "trackerkeeper ${VERSION}" --app-drop-link 480 120 \
|
|
187
|
+
--icon "trackerkeeper.app" 160 120 --window-size 640 280 "$DMG" "$APP"
|
|
188
|
+
codesign --force --timestamp --sign "$IDENTITY" "$DMG"
|
|
189
|
+
# Notarize the .dmg. PREFER the App Store Connect API key (scoped,
|
|
190
|
+
# individually revocable, not coupled to an Apple ID's 2FA) when its
|
|
191
|
+
# secrets exist; otherwise use the Apple ID + app-specific password.
|
|
192
|
+
if [ -n "${APPLE_API_KEY_ID:-}" ]; then
|
|
193
|
+
KEY="$(mktemp /tmp/AuthKey_XXXXXX.p8)"
|
|
194
|
+
echo "$APPLE_API_KEY_B64" | base64 --decode > "$KEY"
|
|
195
|
+
xcrun notarytool submit "$DMG" \
|
|
196
|
+
--key "$KEY" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER" --wait
|
|
197
|
+
rm -f "$KEY"
|
|
198
|
+
else
|
|
199
|
+
xcrun notarytool submit "$DMG" --apple-id "$APPLE_ID" \
|
|
200
|
+
--password "$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait
|
|
201
|
+
fi
|
|
202
|
+
xcrun stapler staple "$DMG"
|
|
203
|
+
|
|
204
|
+
- name: Upload the .dmg to the release
|
|
205
|
+
if: env.HAVE_APPLE == 'true'
|
|
206
|
+
env:
|
|
207
|
+
GH_TOKEN: ${{ github.token }}
|
|
208
|
+
TAG: ${{ github.event.release.tag_name || inputs.tag }}
|
|
209
|
+
run: gh release upload "$TAG" --repo "${{ github.repository }}" --clobber dist/*-macos.dmg
|
|
210
|
+
|
|
211
|
+
- name: Attest .dmg build provenance
|
|
212
|
+
if: env.HAVE_APPLE == 'true'
|
|
213
|
+
uses: actions/attest-build-provenance@v4
|
|
214
|
+
with:
|
|
215
|
+
subject-path: dist/*-macos.dmg
|
|
216
|
+
|
|
217
|
+
- name: Bump the Homebrew cask
|
|
218
|
+
if: ${{ env.HAVE_APPLE == 'true' && env.HAVE_TAP == 'true' }}
|
|
219
|
+
uses: macauley/action-homebrew-bump-cask@v1
|
|
220
|
+
with:
|
|
221
|
+
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
222
|
+
tap: wolfgangwarehaus/homebrew-tap
|
|
223
|
+
cask: trackerkeeper
|
|
224
|
+
tag: ${{ github.event.release.tag_name || inputs.tag }}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes the sdist + wheel to PyPI via **Trusted Publishing (OIDC)** — there is
|
|
4
|
+
# NO API token to store or rotate; PyPI verifies this workflow's identity directly.
|
|
5
|
+
#
|
|
6
|
+
# Triggers:
|
|
7
|
+
# - release: published → auto-publishes when a GitHub release goes public.
|
|
8
|
+
# - workflow_dispatch → manual, for a given tag (backfill / re-run).
|
|
9
|
+
#
|
|
10
|
+
# One-time PyPI setup — create a pending publisher (the project doesn't exist on
|
|
11
|
+
# PyPI yet, so use the "pending publisher" form):
|
|
12
|
+
# https://pypi.org/manage/account/publishing/ → Add a new pending publisher:
|
|
13
|
+
# PyPI Project Name: trackerkeeper-base
|
|
14
|
+
# Owner: wolfgangwarehaus
|
|
15
|
+
# Repository name: trackerkeeper
|
|
16
|
+
# Workflow name: pypi-publish.yml
|
|
17
|
+
# Environment name: pypi
|
|
18
|
+
# These must match the values below exactly or PyPI rejects the OIDC token.
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
release:
|
|
22
|
+
types: [published]
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
inputs:
|
|
25
|
+
tag:
|
|
26
|
+
description: "Tag to build + publish (e.g. v0.1.0)"
|
|
27
|
+
required: true
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: read
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
pypi:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment:
|
|
36
|
+
name: pypi
|
|
37
|
+
url: https://pypi.org/project/trackerkeeper-base/
|
|
38
|
+
permissions:
|
|
39
|
+
contents: read # gh release download
|
|
40
|
+
id-token: write # mint the short-lived OIDC token Trusted Publishing needs
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/setup-python@v5
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.12"
|
|
45
|
+
# Publish the SAME sdist + wheel bytes that release.yml built, attached to
|
|
46
|
+
# the release, and attested — don't rebuild (a second build wouldn't match
|
|
47
|
+
# the release page / SHA256SUMS / provenance). BAKING.md §6.1: Phase-2
|
|
48
|
+
# channels DOWNLOAD the published asset.
|
|
49
|
+
- name: Download the sdist + wheel from the GitHub release
|
|
50
|
+
env:
|
|
51
|
+
GH_TOKEN: ${{ github.token }}
|
|
52
|
+
TAG: ${{ github.event.release.tag_name || inputs.tag }}
|
|
53
|
+
run: |
|
|
54
|
+
mkdir -p dist
|
|
55
|
+
gh release download "$TAG" --repo "${{ github.repository }}" \
|
|
56
|
+
--pattern '*.whl' --pattern '*.tar.gz' --dir dist
|
|
57
|
+
python -m pip install --upgrade pip twine
|
|
58
|
+
python -m twine check dist/*
|
|
59
|
+
- name: Publish to PyPI (Trusted Publishing / OIDC)
|
|
60
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 (2026-07 tip)
|
|
61
|
+
with:
|
|
62
|
+
# Idempotent: a re-run won't fail on an already-uploaded version.
|
|
63
|
+
skip-existing: true
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: release-checklist
|
|
2
|
+
|
|
3
|
+
# On a v* tag push, open a per-release propagation checklist issue from
|
|
4
|
+
# .github/release-checklist-template.md — a durable board for tracking each
|
|
5
|
+
# channel as it propagates. See docs/RELEASING.md.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
issues: write
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
checklist:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- name: Open the propagation checklist issue
|
|
21
|
+
env:
|
|
22
|
+
GH_TOKEN: ${{ github.token }}
|
|
23
|
+
REPO: ${{ github.repository }}
|
|
24
|
+
TAG: ${{ github.ref_name }}
|
|
25
|
+
run: |
|
|
26
|
+
export V="${TAG#v}"
|
|
27
|
+
# Only substitute V + TAG — leave every other $-token in the template alone.
|
|
28
|
+
envsubst '${V} ${TAG}' < .github/release-checklist-template.md > /tmp/body.md
|
|
29
|
+
TITLE="Release ${TAG} — propagation checklist"
|
|
30
|
+
# Fall back without the 'release' label if it doesn't exist yet, so the
|
|
31
|
+
# issue is always created.
|
|
32
|
+
gh issue create --repo "$REPO" --title "$TITLE" --body-file /tmp/body.md --label release \
|
|
33
|
+
|| gh issue create --repo "$REPO" --title "$TITLE" --body-file /tmp/body.md
|