nvda-addon-testkit 0.1.0__tar.gz → 0.1.3__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.
Files changed (132) hide show
  1. nvda_addon_testkit-0.1.3/.github/CODEOWNERS +1 -0
  2. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/.github}/CONTRIBUTING.md +5 -5
  3. nvda_addon_testkit-0.1.3/.github/workflows/CI.yml +98 -0
  4. nvda_addon_testkit-0.1.3/.github/workflows/notify-docs.yml +52 -0
  5. nvda_addon_testkit-0.1.3/.github/workflows/prepare-release.yml +154 -0
  6. nvda_addon_testkit-0.1.3/.github/workflows/project-sync.yml +15 -0
  7. nvda_addon_testkit-0.1.3/.github/workflows/publish-python.yml +85 -0
  8. nvda_addon_testkit-0.1.3/.github/workflows/sonar-fork-coverage.yml +120 -0
  9. nvda_addon_testkit-0.1.3/.github/workflows/sonar-fork-scan.yml +205 -0
  10. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/.github/workflows/sonar.yml +23 -6
  11. nvda_addon_testkit-0.1.3/.github/workflows/stale.yml +43 -0
  12. nvda_addon_testkit-0.1.3/.github/workflows/zizmor.yml +32 -0
  13. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/.gitignore +1 -0
  14. nvda_addon_testkit-0.1.3/.sonarlint/connectedMode.json +5 -0
  15. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/PKG-INFO +22 -2
  16. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/README.md +17 -0
  17. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/action.yml +11 -4
  18. nvda_addon_testkit-0.1.3/docs/antora.yml +5 -0
  19. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/docs/modules/ROOT}/examples/demo-addon/globalPlugins/testkit_demo.py +2 -6
  20. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/docs/modules/ROOT}/examples/demo-addon/manifest.ini +4 -1
  21. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/docs/modules/ROOT/examples}/tests_e2e/conftest.py +0 -6
  22. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/docs/modules/ROOT/examples}/tests_e2e/test_demo_addon.py +6 -7
  23. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/docs/modules/ROOT/examples}/tests_e2e/test_smoke.py +6 -7
  24. nvda_addon_testkit-0.1.3/docs/modules/ROOT/nav.adoc +15 -0
  25. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/ci-guide.adoc +143 -0
  26. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/contributing.adoc +3 -0
  27. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/example-project.adoc +71 -0
  28. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/guide/addons.adoc +40 -0
  29. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/guide/braille.adoc +18 -0
  30. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/guide/config.adoc +24 -0
  31. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/guide/fixtures.adoc +42 -0
  32. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/guide/keys.adoc +15 -0
  33. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/guide/log.adoc +17 -0
  34. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/guide/speech.adoc +16 -0
  35. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/index.adoc +72 -0
  36. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/installation.adoc +35 -0
  37. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/troubleshooting.adoc +112 -0
  38. nvda_addon_testkit-0.1.3/docs/modules/ROOT/pages/tutorial.adoc +110 -0
  39. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/pyproject.toml +22 -7
  40. nvda_addon_testkit-0.1.3/scripts/next-version.sh +55 -0
  41. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/__init__.py +4 -13
  42. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/addons_api.py +0 -7
  43. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/braille_tap.py +0 -5
  44. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/config_api.py +0 -10
  45. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/input_api.py +0 -3
  46. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/log_tap.py +1 -1
  47. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/mainthread.py +0 -3
  48. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/registry.py +0 -2
  49. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/serialise.py +0 -3
  50. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/server.py +0 -1
  51. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/speech_tap.py +0 -1
  52. nvda_addon_testkit-0.1.3/spy/manifest.ini +17 -0
  53. nvda_addon_testkit-0.1.3/src/nvda_testkit/_spy/nvda-testkit-spy.nvda-addon +0 -0
  54. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/_version.py +2 -2
  55. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/client.py +0 -4
  56. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/download.py +0 -3
  57. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/plugin.py +0 -5
  58. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/portable.py +0 -4
  59. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/process.py +1 -15
  60. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/provisioning.py +0 -6
  61. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/rpcclient.py +0 -6
  62. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/settings.py +0 -1
  63. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/speechtypes.py +0 -3
  64. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/fake_nvda.py +0 -17
  65. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/fixtures/snapshot_index_alpha.html +1 -1
  66. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_client.py +0 -6
  67. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_download.py +0 -1
  68. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_fake_nvda.py +0 -5
  69. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_plugin.py +0 -8
  70. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_portable.py +0 -2
  71. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_process.py +1 -3
  72. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_resolve.py +0 -1
  73. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/nvda_stubs.py +0 -9
  74. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_braille_tap.py +0 -2
  75. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_config_api.py +0 -3
  76. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_mainthread.py +0 -1
  77. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_plugin.py +0 -5
  78. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_server.py +0 -1
  79. nvda_addon_testkit-0.1.3/uv.lock +1188 -0
  80. nvda_addon_testkit-0.1.0/.github/release-drafter.yml +0 -36
  81. nvda_addon_testkit-0.1.0/.github/workflows/ci.yml +0 -74
  82. nvda_addon_testkit-0.1.0/.github/workflows/publish.yml +0 -40
  83. nvda_addon_testkit-0.1.0/.github/workflows/release-drafter.yml +0 -23
  84. nvda_addon_testkit-0.1.0/spy/manifest.ini +0 -8
  85. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  86. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  87. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  88. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/.github/pull_request_template.md +0 -0
  89. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/.github}/renovate.json +0 -0
  90. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/LICENSE +0 -0
  91. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3/docs/modules/ROOT}/examples/demo-addon/build.py +0 -0
  92. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/ruff.toml +0 -0
  93. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/sonar-project.properties +0 -0
  94. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/spy/globalPlugins/nvda_testkit_spy/eval_api.py +0 -0
  95. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/__init__.py +0 -0
  96. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/cli.py +0 -0
  97. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/errors.py +0 -0
  98. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/namespaces/__init__.py +0 -0
  99. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/namespaces/addons.py +0 -0
  100. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/namespaces/braille.py +0 -0
  101. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/namespaces/config.py +0 -0
  102. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/namespaces/keys.py +0 -0
  103. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/namespaces/log.py +0 -0
  104. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/namespaces/speech.py +0 -0
  105. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/resolve.py +0 -0
  106. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/src/nvda_testkit/spybundle.py +0 -0
  107. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/conftest.py +0 -0
  108. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/fixtures/update_check_stable.txt +0 -0
  109. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_addons_namespace.py +0 -0
  110. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_braille_namespace.py +0 -0
  111. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_build_spy.py +0 -0
  112. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_cli.py +0 -0
  113. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_config_namespace.py +0 -0
  114. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_e2e_conftest.py +0 -0
  115. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_errors.py +0 -0
  116. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_keys_namespace.py +0 -0
  117. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_log_namespace.py +0 -0
  118. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_provisioning.py +0 -0
  119. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_rpcclient.py +0 -0
  120. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_settings.py +0 -0
  121. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_speech_namespace.py +0 -0
  122. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests/test_speechtypes.py +0 -0
  123. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/__init__.py +0 -0
  124. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/conftest.py +0 -0
  125. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_addons_api.py +0 -0
  126. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_eval_api.py +0 -0
  127. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_input_api.py +0 -0
  128. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_log_tap.py +0 -0
  129. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_registry.py +0 -0
  130. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_serialise.py +0 -0
  131. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tests_spy/test_speech_tap.py +0 -0
  132. {nvda_addon_testkit-0.1.0 → nvda_addon_testkit-0.1.3}/tools/build_spy.py +0 -0
@@ -0,0 +1 @@
1
+ * @ZirekHQ/maintainers
@@ -54,11 +54,11 @@ Use the pull request template. Link the issue with `Closes #N` in the PR body wh
54
54
 
55
55
  Releases are tag-driven, not version-bumped by hand — `pyproject.toml` has no `version` field. The `hatch-vcs` build hook derives the package version from the git tag at build time.
56
56
 
57
- 1. Publish a GitHub Release from `main` with a `vX.Y.Z` tag (semver; pre-1.0 minor bumps may break the fixture API).
58
- 2. `.github/workflows/publish.yml` picks up the `release: published` event, builds the sdist/wheel, and uploads to PyPI via Trusted Publishing — no token to rotate.
59
-
60
- No separate changelog file to update — Release Drafter maintains a draft release from merged PR titles/labels as you go; open the draft, set the tag, and publish it to trigger the step above.
57
+ 1. Run `prepare-release.yml` (Actions tab → **Prepare release** → Run workflow). Leave `new_tag` blank to let it compute the next version from Conventional Commit subjects since the last tag (`scripts/next-version.sh`); pass a `vX.Y.Z` value there instead to override it. Check `dry_run` to only see the computed version without tagging or publishing anything.
58
+ 2. The `release` environment gate requires a maintainer approval before anything happens — that's the human checkpoint, not the version computation.
59
+ 3. Once approved, `prepare-release.yml`'s `tag-and-release` job tags `main` at the resolved commit, force-moves the `v<major>` tag consumers pin (README and GitHub Marketplace listings use `@v1`), and publishes the GitHub Release with auto-generated notes.
60
+ 4. That tag feeds `publish-python.yml`, which builds the sdist/wheel and uploads to PyPI via Trusted Publishing — no token to rotate.
61
61
 
62
62
  ### Why not fully automate this (conventional commits + semantic-release)?
63
63
 
64
- Considered and deliberately skipped. PRs are squash-merged, so main's history is already one commit per PR — parsing commit prefixes to categorize a release would just be a stricter, easier-to-typo restatement of what PR labels already give Release Drafter for free. More importantly, auto-bumping the version and auto-publishing on merge removes the last human checkpoint before something goes to PyPI, and pre-1.0 semver bumps (does this `feat:` deserve a minor, or does it actually break the fixture API?) are judgment calls a bot applies too mechanically at this stage. Worth revisiting if release volume ever makes the manual tag-and-publish step the actual bottleneck.
64
+ The version bump itself *is* automated — computed from commit prefixes since the last tag. What's deliberately kept manual is the human approval gate before anything tags or publishes: pre-1.0 semver bumps (does this `feat:` deserve a minor, or does it actually break the fixture API?) are judgment calls worth a maintainer's eyes before they go to PyPI and the Marketplace. Worth revisiting if release volume ever makes that approval step the actual bottleneck.
@@ -0,0 +1,98 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ lint:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ steps:
21
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
22
+ with:
23
+ persist-credentials: false
24
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
25
+ with:
26
+ python-version: "3.13"
27
+ - run: 'pip install --only-binary :all: ruff'
28
+ - run: ruff check .
29
+ - run: ruff format --check .
30
+
31
+ dependency-audit:
32
+ runs-on: ubuntu-latest
33
+ timeout-minutes: 10
34
+ steps:
35
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
36
+ with:
37
+ persist-credentials: false
38
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
39
+ with:
40
+ python-version: "3.13"
41
+ - run: 'pip install --only-binary :all: -e ".[dev]"'
42
+ - run: 'pip install --only-binary :all: pip-audit'
43
+ - run: pip-audit
44
+
45
+ unit:
46
+ runs-on: ${{ matrix.os }}
47
+ timeout-minutes: 15
48
+ strategy:
49
+ fail-fast: false
50
+ matrix:
51
+ os: [ubuntu-latest, windows-2025]
52
+ steps:
53
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
54
+ with:
55
+ persist-credentials: false
56
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
57
+ with:
58
+ python-version: "3.13"
59
+ - run: 'pip install --only-binary :all: -e ".[dev]"'
60
+ - run: python tools/build_spy.py
61
+ # Bare `pytest` so testpaths picks up tests_spy/ too. An explicit
62
+ # `pytest tests/` silently skips every spy test in CI.
63
+ - run: pytest -v
64
+
65
+ e2e:
66
+ runs-on: ${{ matrix.os }}
67
+ timeout-minutes: 30
68
+ strategy:
69
+ fail-fast: false
70
+ matrix:
71
+ # windows-2022 covers Windows 10 22H2 (ESU). Drop it once ESU
72
+ # coverage is no longer needed.
73
+ os: [windows-2025, windows-2022]
74
+ channel: [stable, alpha]
75
+ continue-on-error: ${{ matrix.channel == 'alpha' }}
76
+ steps:
77
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
78
+ with:
79
+ persist-credentials: false
80
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
81
+ with:
82
+ python-version: "3.13"
83
+ - run: 'pip install --only-binary :all: -e ".[dev]"'
84
+ - run: python tools/build_spy.py
85
+ - name: Cache NVDA launchers
86
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
87
+ with:
88
+ path: ~/.cache/nvda-testkit
89
+ key: nvda-launcher-${{ matrix.os }}-${{ matrix.channel }}-${{ github.run_id }}
90
+ restore-keys: nvda-launcher-${{ matrix.os }}-${{ matrix.channel }}-
91
+ - run: nvda-testkit doctor
92
+ - run: pytest tests_e2e/ -v --nvda-channel=${{ matrix.channel }}
93
+ - if: failure()
94
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
95
+ with:
96
+ name: e2e-${{ matrix.os }}-${{ matrix.channel }}-artifacts
97
+ path: testOutput/
98
+ if-no-files-found: warn
@@ -0,0 +1,52 @@
1
+ name: Notify docs site
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ ref:
9
+ description: Ref or tag to notify about (defaults to the current ref)
10
+ required: false
11
+ type: string
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ dispatch:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ steps:
21
+ - name: Resolve ref
22
+ id: resolve
23
+ env:
24
+ EVENT_NAME: ${{ github.event_name }}
25
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
26
+ INPUT_REF: ${{ inputs.ref }}
27
+ DEFAULT_REF: ${{ github.ref_name }}
28
+ run: |
29
+ if [ "$EVENT_NAME" = "release" ]; then
30
+ ref="$RELEASE_TAG"
31
+ else
32
+ ref="${INPUT_REF:-$DEFAULT_REF}"
33
+ fi
34
+ if ! git check-ref-format --branch "$ref" >/dev/null 2>&1 \
35
+ && ! git check-ref-format "refs/tags/$ref" >/dev/null 2>&1; then
36
+ echo "Invalid ref: $ref" >&2
37
+ exit 1
38
+ fi
39
+ echo "ref=$ref" >> "$GITHUB_OUTPUT"
40
+
41
+ - name: Dispatch update to docs site
42
+ uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3
43
+ with:
44
+ token: ${{ secrets.DOCS_DISPATCH_TOKEN }}
45
+ repository: ZirekHQ/ZirekHQ.github.io
46
+ event-type: update-docs
47
+ client-payload: |-
48
+ {
49
+ "repository": ${{ toJSON(github.repository) }},
50
+ "ref": ${{ toJSON(steps.resolve.outputs.ref) }},
51
+ "actor": ${{ toJSON(github.actor) }}
52
+ }
@@ -0,0 +1,154 @@
1
+ name: Prepare release
2
+
3
+ # The single dispatch surface for cutting a release. hatch-vcs derives the version from the
4
+ # git tag (pyproject.toml), so there's no version file to bump or PR to open first -- the
5
+ # environment:release approval gate below is the human checkpoint instead.
6
+ #
7
+ # tag-and-release and pypi live here rather than in a separate workflow_call file: PyPI's
8
+ # trusted-publisher attestation check verifies the Build Config URI against the *root*
9
+ # dispatched workflow, not the immediate caller, so a second layer of workflow_call between
10
+ # this dispatch and publish-python.yml breaks it (confirmed by three failed release runs
11
+ # where the Build Config URI reported the wrong workflow entirely).
12
+ on:
13
+ workflow_dispatch:
14
+ inputs:
15
+ new_tag:
16
+ description: "Override the vX.Y.Z tag"
17
+ required: false
18
+ type: string
19
+ dry_run:
20
+ description: "Dry Run: compute the version, tag nothing, publish nothing"
21
+ required: false
22
+ type: boolean
23
+ default: false
24
+
25
+ permissions: {}
26
+
27
+ jobs:
28
+ compute-version:
29
+ runs-on: ubuntu-latest
30
+ timeout-minutes: 10
31
+ permissions:
32
+ contents: read
33
+ outputs:
34
+ version: ${{ steps.next.outputs.version }}
35
+ sha: ${{ steps.resolve-sha.outputs.sha }}
36
+ steps:
37
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
38
+ with:
39
+ ref: main
40
+ fetch-depth: 0
41
+ persist-credentials: false
42
+
43
+ # Exported so tag-and-release tags/checks-out this exact commit rather than the dispatch's
44
+ # own github.sha -- main can move during approve's wait, and this checkout is the only
45
+ # thing pinning which commit the computed version actually describes.
46
+ - name: Resolve commit SHA
47
+ id: resolve-sha
48
+ run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
49
+
50
+ - name: Compute next version
51
+ id: next
52
+ env:
53
+ NEW_TAG: ${{ inputs.new_tag }}
54
+ run: |
55
+ set -euo pipefail
56
+ if [ -n "${NEW_TAG:-}" ]; then
57
+ version="${NEW_TAG#v}"
58
+ if ! echo "$version" | grep -qE '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'; then
59
+ echo "::error::new_tag '${NEW_TAG}' doesn't look like a vX.Y.Z tag" >&2
60
+ exit 1
61
+ fi
62
+ else
63
+ version="$(bash scripts/next-version.sh)"
64
+ fi
65
+ echo "version=${version}" >> "$GITHUB_OUTPUT"
66
+ echo "Computed v${version}"
67
+
68
+ approve:
69
+ name: Approve release
70
+ needs: compute-version
71
+ if: inputs.dry_run != true && github.ref == 'refs/heads/main'
72
+ runs-on: ubuntu-latest
73
+ timeout-minutes: 5
74
+ environment: release
75
+ permissions: {}
76
+ steps:
77
+ - env:
78
+ VERSION: ${{ needs.compute-version.outputs.version }}
79
+ run: echo "Approved v${VERSION} for release"
80
+
81
+ tag-and-release:
82
+ needs: [compute-version, approve]
83
+ if: always() && needs.compute-version.result == 'success' && needs.approve.result == 'success'
84
+ runs-on: ubuntu-latest
85
+ timeout-minutes: 10
86
+ permissions:
87
+ contents: write
88
+ env:
89
+ VERSION: ${{ needs.compute-version.outputs.version }}
90
+ SHA: ${{ needs.compute-version.outputs.sha }}
91
+ GH_TOKEN: ${{ github.token }}
92
+ steps:
93
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
94
+ with:
95
+ ref: ${{ needs.compute-version.outputs.sha }}
96
+ persist-credentials: true
97
+
98
+ - name: Push release tag
99
+ # Checked against origin (shallow fetch), not the local clone. An annotated tag peels to
100
+ # the commit SHA via a separate `^{}`-suffixed query -- ls-remote only returns that line
101
+ # when asked for it by name, so one unsuffixed query can't tell the two cases apart.
102
+ run: |
103
+ set -euo pipefail
104
+ git config user.name "github-actions[bot]"
105
+ git config user.email "github-actions[bot]@users.noreply.github.com"
106
+ existing="$(git ls-remote origin "refs/tags/v${VERSION}^{}" | cut -f1)"
107
+ if [ -z "$existing" ]; then
108
+ existing="$(git ls-remote origin "refs/tags/v${VERSION}" | cut -f1)"
109
+ fi
110
+ if [ -n "$existing" ]; then
111
+ if [ "$existing" != "$SHA" ]; then
112
+ echo "::error::v${VERSION} already exists on origin and points at ${existing}, not this commit (${SHA})" >&2
113
+ exit 1
114
+ fi
115
+ echo "v${VERSION} already exists on origin and points at this commit -- reusing it"
116
+ elif [ "$GITHUB_REF" = "refs/heads/main" ]; then
117
+ git tag "v${VERSION}"
118
+ git push origin "v${VERSION}"
119
+ else
120
+ echo "::error::Refusing to create v${VERSION} from ${GITHUB_REF} -- release tags are only created from main" >&2
121
+ exit 1
122
+ fi
123
+
124
+ # README documents `uses: zirekhq/nvda-addon-testkit@v1`, and GitHub Marketplace
125
+ # consumers pin the same tag -- this step keeps it on the latest release in its major line.
126
+ - name: Move major version tag
127
+ run: |
128
+ set -euo pipefail
129
+ major="v${VERSION%%.*}"
130
+ git tag -fa "$major" -m "$major" "$SHA"
131
+ git push origin "$major" --force
132
+
133
+ - name: Publish GitHub Release
134
+ # Replaces the release-drafter draft this step used to patch to non-draft --
135
+ # --generate-notes computes the same changelog from merged PRs on demand instead.
136
+ run: |
137
+ set -euo pipefail
138
+ if ! gh release view "v${VERSION}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
139
+ gh release create "v${VERSION}" \
140
+ --repo "${GITHUB_REPOSITORY}" \
141
+ --target "${SHA}" \
142
+ --title "v${VERSION}" \
143
+ --generate-notes
144
+ fi
145
+
146
+ pypi:
147
+ name: PyPI
148
+ needs: [compute-version, tag-and-release]
149
+ permissions:
150
+ contents: read
151
+ id-token: write
152
+ uses: ./.github/workflows/publish-python.yml # zizmor: ignore[self-repository] low-severity style suggestion (use '$/...'), not a finding -- zizmor.yml doesn't pin a CLI version to confirm '$/' support
153
+ with:
154
+ ref: v${{ needs.compute-version.outputs.version }}
@@ -0,0 +1,15 @@
1
+ name: Project sync
2
+
3
+ on:
4
+ issues:
5
+ types: [opened, assigned]
6
+ pull_request:
7
+ types: [opened]
8
+
9
+ permissions: {}
10
+
11
+ jobs:
12
+ sync:
13
+ uses: ZirekHQ/.github/.github/workflows/project-sync.yml@54452127e2c87f8ecfe358fc88e0f1fe411859b3 # main
14
+ secrets:
15
+ PROJECT_SYNC_TOKEN: ${{ secrets.PROJECT_SYNC_TOKEN }}
@@ -0,0 +1,85 @@
1
+ name: Publish Python
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ # Manual retry path (e.g. PyPI publish failed after the GitHub release already went out).
7
+ workflow_dispatch: {}
8
+ workflow_call:
9
+ inputs:
10
+ ref:
11
+ description: "Exact commit or tag to build and publish"
12
+ required: true
13
+ type: string
14
+
15
+ permissions: {}
16
+
17
+ jobs:
18
+ # github.event_name mirrors the CALLING workflow's trigger through a workflow_call
19
+ # (prepare-release.yml's dispatch shows up as 'workflow_dispatch' here too) -- workflow_ref
20
+ # stays bound to the top-level caller, so this checks that, not event_name alone.
21
+ gate:
22
+ if: >-
23
+ github.event_name == 'workflow_dispatch' &&
24
+ startsWith(github.workflow_ref, format('{0}/.github/workflows/publish-python.yml@', github.repository))
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 5
27
+ environment: release
28
+ permissions: {}
29
+ steps:
30
+ - run: echo "approved"
31
+
32
+ build:
33
+ needs: gate
34
+ if: always() && (needs.gate.result == 'success' || needs.gate.result == 'skipped')
35
+ runs-on: ubuntu-latest
36
+ timeout-minutes: 10
37
+ permissions:
38
+ contents: read
39
+ steps:
40
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
41
+ with:
42
+ ref: ${{ inputs.ref || github.sha }}
43
+ persist-credentials: false
44
+ fetch-depth: 0
45
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
46
+ with:
47
+ python-version: "3.13"
48
+ - run: "pip install --only-binary :all: build twine"
49
+ # Without this, hatchling's `artifacts = ["src/nvda_testkit/_spy/*.nvda-addon"]`
50
+ # matches nothing -- the bundle is gitignored and only exists once built --
51
+ # so the published wheel silently ships without it and every real (non-fake)
52
+ # provision() fails require_spy_bundle().
53
+ - run: python tools/build_spy.py
54
+ - run: python -m build
55
+ - run: twine check dist/*
56
+ - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
57
+ with:
58
+ name: dist
59
+ path: dist/
60
+
61
+ publish:
62
+ needs: build
63
+ # Without this, publish silently no-ops on the release.yml call path: its default
64
+ # success() check walks the whole ancestor chain, and gate (an ancestor via build)
65
+ # is legitimately skipped there, not succeeded.
66
+ if: always() && needs.build.result == 'success'
67
+ runs-on: ubuntu-latest
68
+ timeout-minutes: 10
69
+ environment: pypi
70
+ permissions:
71
+ id-token: write
72
+ steps:
73
+ - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
74
+ with:
75
+ name: dist
76
+ path: dist/
77
+ # pypa publishes security fixes to this branch without re-tagging v1, so
78
+ # pinning trades that auto-patching for the blanket pinning policy --
79
+ # bump this SHA by hand if pypa/gh-action-pypi-publish ships a fix.
80
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
81
+ with:
82
+ # PyPI doesn't support reusable workflows as a Trusted Publisher (pypi/warehouse#11096):
83
+ # this job runs via workflow_call from prepare-release.yml, so the attestation's Build
84
+ # Config URI is the caller, not this file, and PyPI rejects it. Drop once that's fixed.
85
+ attestations: false
@@ -0,0 +1,120 @@
1
+ name: Sonar fork coverage
2
+
3
+ # The Sonar job in sonar.yml is deliberately skipped for fork PRs because
4
+ # forks are never given SONAR_TOKEN. This workflow builds what a real scan
5
+ # needs -- a full checkout (including git history, for Sonar's blame/new-code
6
+ # detection) plus a coverage report -- using only the read-only, secret-less
7
+ # token that `pull_request` gets for fork PRs, and packages it as a plain tar
8
+ # archive artifact.
9
+ #
10
+ # sonar-fork-scan.yml (triggered by a maintainer commenting `/sonar` on the
11
+ # PR after reviewing the diff) extracts that archive with plain `tar`, never a
12
+ # git operation, so it never runs git fetch/checkout against fork content and
13
+ # never executes anything from the fork -- the archive is only read
14
+ # statically by the Sonar scanner. This workflow must never gain
15
+ # `secrets:` access and must never do anything beyond running the PR's own
16
+ # test suite, exactly as any other fork `pull_request` job already safely
17
+ # does.
18
+
19
+ on:
20
+ pull_request:
21
+ branches: [main]
22
+
23
+ permissions: {}
24
+
25
+ concurrency:
26
+ group: sonar-fork-coverage-${{ github.event.pull_request.number }}
27
+ cancel-in-progress: true
28
+
29
+ jobs:
30
+ # Mirrors sonar.yml's windows_coverage job: windows_only-marked tests only
31
+ # run on Windows, so without this leg they'd land in the fork's report as
32
+ # permanently uncovered.
33
+ windows_coverage:
34
+ name: Windows coverage for fork scan
35
+ runs-on: windows-2025
36
+ timeout-minutes: 20
37
+ permissions:
38
+ contents: read
39
+ if: github.event.pull_request.head.repo.full_name != github.repository
40
+ steps:
41
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
42
+ with:
43
+ ref: ${{ github.event.pull_request.head.sha }}
44
+ persist-credentials: false
45
+
46
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
47
+ with:
48
+ python-version: "3.13"
49
+
50
+ - run: 'pip install --only-binary :all: -e ".[dev]"'
51
+ - run: python tools/build_spy.py
52
+
53
+ - name: Run tests with coverage
54
+ env:
55
+ COVERAGE_FILE: .coverage.windows.tests
56
+ # `coverage run -m pytest`, not `pytest --cov`: this package registers
57
+ # itself as a pytest11 entry-point plugin, so pytest's own plugin
58
+ # loader imports nvda_testkit's modules before pytest-cov's --cov
59
+ # flag would start tracing, permanently miscounting every top-level
60
+ # def/class line in them as uncovered. Mirrors sonar.yml.
61
+ run: coverage run -m pytest -v
62
+
63
+ - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
64
+ with:
65
+ name: sonar-fork-windows-coverage
66
+ path: .coverage.windows.*
67
+ include-hidden-files: true # coverage data files start with a dot
68
+ retention-days: 7
69
+
70
+ coverage:
71
+ name: Build coverage for fork scan
72
+ runs-on: ubuntu-latest
73
+ timeout-minutes: 20
74
+ needs: ["windows_coverage"]
75
+ permissions:
76
+ contents: read
77
+ # Same-repo PRs are scanned directly by sonar.yml; only forks need this.
78
+ if: github.event.pull_request.head.repo.full_name != github.repository
79
+ steps:
80
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
81
+ with:
82
+ # Pinned to the actual PR head, not actions/checkout's default
83
+ # ephemeral merge commit -- otherwise the scanner analyzes a
84
+ # commit SHA that doesn't match the PR, and SonarCloud can't
85
+ # attach its status to the commit branch protection actually
86
+ # checks.
87
+ ref: ${{ github.event.pull_request.head.sha }}
88
+ fetch-depth: 0 # Sonar needs full history for new-code detection and blame
89
+ persist-credentials: false
90
+
91
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
92
+ with:
93
+ python-version: "3.13"
94
+
95
+ - run: 'pip install --only-binary :all: -e ".[dev]"'
96
+ - run: python tools/build_spy.py
97
+
98
+ - name: Run tests with coverage
99
+ run: coverage run -m pytest -v
100
+
101
+ # After the run above, never before: `coverage run` erases any
102
+ # .coverage.* data files already sitting in the workspace.
103
+ - name: Download the Windows leg's coverage
104
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
105
+ with:
106
+ name: sonar-fork-windows-coverage
107
+
108
+ - name: Merge into one coverage report
109
+ run: |
110
+ python -m coverage combine --append
111
+ python -m coverage xml
112
+
113
+ - name: Archive workspace for the manual scan workflow
114
+ run: tar -czf "$RUNNER_TEMP/workspace.tar.gz" -C "$GITHUB_WORKSPACE" .
115
+
116
+ - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
117
+ with:
118
+ name: sonar-fork-workspace
119
+ path: ${{ runner.temp }}/workspace.tar.gz
120
+ retention-days: 7