lib-cli-exit-tools 2.2.3__tar.gz → 2.2.4__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 (77) hide show
  1. lib_cli_exit_tools-2.2.4/.github/workflows/default_cicd_public.yml +303 -0
  2. lib_cli_exit_tools-2.2.4/.github/workflows/default_release_public.yml +69 -0
  3. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/CHANGELOG.md +6 -0
  4. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/PKG-INFO +10 -10
  5. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/README.md +1 -1
  6. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/pyproject.toml +21 -10
  7. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/__init__conf__.py +1 -1
  8. lib_cli_exit_tools-2.2.3/tests/test_scripts.py +0 -233
  9. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.codacy/cli-config.yaml +0 -0
  10. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.codacy/cli.sh +0 -0
  11. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.codacy/codacy.yaml +0 -0
  12. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.codacy/tools-configs/lizard.yaml +0 -0
  13. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.codacy/tools-configs/trivy.yaml +0 -0
  14. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.devcontainer/devcontainer.json +0 -0
  15. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.devcontainer/settings.json +0 -0
  16. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.env.example +0 -0
  17. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.github/dependabot.yml +0 -0
  18. /lib_cli_exit_tools-2.2.3/.github/workflows/ci.yml → /lib_cli_exit_tools-2.2.4/.github/workflows/ci.yml_backup +0 -0
  19. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.github/workflows/codeql.yml +0 -0
  20. /lib_cli_exit_tools-2.2.3/.github/workflows/release.yml → /lib_cli_exit_tools-2.2.4/.github/workflows/release.yml_backup +0 -0
  21. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.gitignore +0 -0
  22. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.qlty/qlty.toml +0 -0
  23. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/.snyk +0 -0
  24. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/CLAUDE.md +0 -0
  25. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/CONTRIBUTING.md +0 -0
  26. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/DEVELOPMENT.md +0 -0
  27. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/INSTALL.md +0 -0
  28. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/LICENSE +0 -0
  29. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/Makefile +0 -0
  30. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/codecov.yml +0 -0
  31. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/docs/system-design/module_reference.md +0 -0
  32. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/notebooks/Quickstart.ipynb +0 -0
  33. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/__init__.py +0 -0
  34. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/__main__.py +0 -0
  35. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/_utils.py +0 -0
  36. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/build.py +0 -0
  37. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/bump.py +0 -0
  38. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/bump_major.py +0 -0
  39. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/bump_minor.py +0 -0
  40. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/bump_patch.py +0 -0
  41. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/bump_version.py +0 -0
  42. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/clean.py +0 -0
  43. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/cli.py +0 -0
  44. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/dependencies.py +0 -0
  45. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/dev.py +0 -0
  46. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/help.py +0 -0
  47. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/install.py +0 -0
  48. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/menu.py +0 -0
  49. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/push.py +0 -0
  50. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/release.py +0 -0
  51. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/run_cli.py +0 -0
  52. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/target_metadata.py +0 -0
  53. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/test.py +0 -0
  54. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/toml_config.py +0 -0
  55. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/scripts/version_current.py +0 -0
  56. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/__init__.py +0 -0
  57. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/__main__.py +0 -0
  58. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/adapters/__init__.py +0 -0
  59. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/adapters/signals.py +0 -0
  60. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/application/__init__.py +0 -0
  61. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/application/runner.py +0 -0
  62. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/cli.py +0 -0
  63. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/core/__init__.py +0 -0
  64. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/core/configuration.py +0 -0
  65. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/core/exit_codes.py +0 -0
  66. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +0 -0
  67. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/src/lib_cli_exit_tools/py.typed +0 -0
  68. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/conftest.py +0 -0
  69. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_adapters_signals.py +0 -0
  70. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_application_runner.py +0 -0
  71. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_behaviors.py +0 -0
  72. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_cli.py +0 -0
  73. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_core_configuration.py +0 -0
  74. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_core_exit_codes.py +0 -0
  75. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_metadata.py +0 -0
  76. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_module_entry.py +0 -0
  77. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.2.4}/tests/test_signals_integration.py +0 -0
@@ -0,0 +1,303 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master ]
6
+ pull_request:
7
+ branches: [ main, master ]
8
+ schedule:
9
+ # Run daily at 3:17 AM UTC (off-peak hours, odd minute to avoid congestion)
10
+ - cron: '17 3 * * *'
11
+
12
+ jobs:
13
+ test:
14
+ name: Tests (Python ${{ matrix.python }}, ${{ matrix.os }})
15
+ runs-on: ${{ matrix.os }}
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ os: [ubuntu-latest, macos-latest, windows-latest]
20
+ # Test against all supported Python versions (3.10+)
21
+ python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
22
+ env:
23
+ PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1"
24
+ PYTHONIOENCODING: "utf-8"
25
+ UV_LINK_MODE: "copy"
26
+ steps:
27
+ - uses: actions/checkout@v6
28
+ - uses: actions/setup-python@v6
29
+ with:
30
+ python-version: ${{ matrix.python }}
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v7
33
+ with:
34
+ enable-cache: true
35
+ prune-cache: false
36
+
37
+ - name: Install tomli (Python < 3.11)
38
+ shell: bash
39
+ run: python -c "import tomllib" 2>/dev/null || uv pip install tomli --system
40
+
41
+ - name: Extract project metadata
42
+ shell: python
43
+ run: |
44
+ import os
45
+ try:
46
+ import tomllib
47
+ except ModuleNotFoundError:
48
+ import tomli as tomllib
49
+
50
+ with open('pyproject.toml', 'rb') as f:
51
+ data = tomllib.load(f)
52
+ project = data['project']['name']
53
+ module = project.replace('-', '_')
54
+ dash = project.replace('_', '-')
55
+ scripts = list(data['project'].get('scripts', {}).keys())
56
+ cli_bin = scripts[0] if scripts else dash
57
+
58
+ with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as env:
59
+ env.write(f"PROJECT_NAME={project}\n")
60
+ env.write(f"PACKAGE_MODULE={module}\n")
61
+ env.write(f"CLI_BIN={cli_bin}\n")
62
+
63
+ - name: Install journald prerequisites
64
+ if: runner.os == 'Linux'
65
+ shell: bash
66
+ run: |
67
+ sudo apt-get update
68
+ sudo apt-get install -y python3-systemd
69
+
70
+ - name: Install dev deps
71
+ shell: bash
72
+ run: |
73
+ uv pip install -e .[dev] --system
74
+
75
+ - name: Install Windows Event Log prerequisites
76
+ if: runner.os == 'Windows'
77
+ shell: bash
78
+ run: |
79
+ uv pip install pywin32 --system
80
+
81
+ - name: Restore ruff cache
82
+ id: ruff-cache
83
+ uses: actions/cache/restore@v5
84
+ with:
85
+ path: .ruff_cache
86
+ key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
87
+ restore-keys: |
88
+ ruff-${{ runner.os }}-py${{ matrix.python }}-
89
+ ruff-${{ runner.os }}-
90
+
91
+ - name: Run ruff (lint + format)
92
+ id: ruff
93
+ continue-on-error: true
94
+ shell: bash
95
+ run: |
96
+ ruff check src tests notebooks
97
+ ruff format --check src tests notebooks
98
+
99
+ - name: Run pyright (type check)
100
+ id: pyright
101
+ continue-on-error: true
102
+ shell: bash
103
+ run: pyright
104
+
105
+ - name: Run pytest (tests + coverage)
106
+ id: pytest
107
+ continue-on-error: true
108
+ shell: bash
109
+ env:
110
+ TEST_VERBOSE: "1"
111
+ run: pytest --cov=src --cov-report=term-missing --cov-report=xml
112
+
113
+ - name: Run import-linter (architecture)
114
+ id: importlinter
115
+ continue-on-error: true
116
+ shell: bash
117
+ run: lint-imports
118
+
119
+ - name: Run bandit (security scan)
120
+ id: bandit
121
+ continue-on-error: true
122
+ shell: bash
123
+ run: bandit -q -r src/${{ env.PACKAGE_MODULE }}
124
+
125
+ - name: Run pip-audit (vulnerability scan)
126
+ id: pipaudit
127
+ continue-on-error: true
128
+ shell: python
129
+ run: |
130
+ import subprocess, sys
131
+ try:
132
+ import tomllib
133
+ except ModuleNotFoundError:
134
+ import tomli as tomllib
135
+ with open("pyproject.toml", "rb") as f:
136
+ data = tomllib.load(f)
137
+ vulns = data.get("tool", {}).get("pip-audit", {}).get("ignore-vulns", [])
138
+ cmd = ["pip-audit", "--skip-editable"]
139
+ for v in vulns:
140
+ cmd.extend(["--ignore-vuln", v])
141
+ sys.exit(subprocess.call(cmd))
142
+
143
+ - name: Check all steps passed
144
+ shell: bash
145
+ run: |
146
+ if [[ "${{ steps.ruff.outcome }}" != "success" ]]; then
147
+ echo "::error::Ruff check failed"
148
+ exit 1
149
+ fi
150
+ if [[ "${{ steps.pyright.outcome }}" != "success" ]]; then
151
+ echo "::error::Pyright check failed"
152
+ exit 1
153
+ fi
154
+ if [[ "${{ steps.pytest.outcome }}" != "success" ]]; then
155
+ echo "::error::Pytest failed"
156
+ exit 1
157
+ fi
158
+ if [[ "${{ steps.importlinter.outcome }}" != "success" ]]; then
159
+ echo "::error::Import-linter check failed"
160
+ exit 1
161
+ fi
162
+ if [[ "${{ steps.bandit.outcome }}" != "success" ]]; then
163
+ echo "::error::Bandit security scan failed"
164
+ exit 1
165
+ fi
166
+ if [[ "${{ steps.pipaudit.outcome }}" != "success" ]]; then
167
+ echo "::error::pip-audit vulnerability scan failed"
168
+ exit 1
169
+ fi
170
+ echo "All checks passed!"
171
+
172
+ - name: Build wheel/sdist
173
+ shell: bash
174
+ run: uv build
175
+
176
+ - name: Verify wheel install in clean env
177
+ shell: bash
178
+ run: |
179
+ uv venv .venv_wheel
180
+ . .venv_wheel/bin/activate 2>/dev/null || . .venv_wheel/Scripts/activate 2>/dev/null
181
+ uv pip install dist/*.whl
182
+ "$CLI_BIN" --version 2>/dev/null || python -m "$PACKAGE_MODULE" --version
183
+
184
+ - name: Prune uv cache
185
+ continue-on-error: true
186
+ shell: bash
187
+ run: uv cache prune --ci
188
+
189
+ - name: Save ruff cache
190
+ if: always() && steps.ruff-cache.outputs.cache-hit != 'true'
191
+ uses: actions/cache/save@v5
192
+ with:
193
+ path: .ruff_cache
194
+ key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
195
+
196
+ pipx-uv:
197
+ name: pipx/uv verification (ubuntu)
198
+ runs-on: ubuntu-latest
199
+ steps:
200
+ - uses: actions/checkout@v6
201
+ - uses: actions/setup-python@v6
202
+ with:
203
+ python-version: "3.14"
204
+
205
+ - name: Install uv
206
+ uses: astral-sh/setup-uv@v7
207
+ with:
208
+ enable-cache: true
209
+ prune-cache: false
210
+
211
+ - name: Install tomli (Python < 3.11)
212
+ shell: bash
213
+ run: python -c "import tomllib" 2>/dev/null || uv pip install tomli --system
214
+
215
+ - name: Extract project metadata
216
+ shell: python
217
+ run: |
218
+ import os
219
+ try:
220
+ import tomllib
221
+ except ModuleNotFoundError:
222
+ import tomli as tomllib
223
+
224
+ with open('pyproject.toml', 'rb') as f:
225
+ data = tomllib.load(f)
226
+ project = data['project']['name']
227
+ module = project.replace('-', '_')
228
+ dash = project.replace('_', '-')
229
+ scripts = list(data['project'].get('scripts', {}).keys())
230
+ cli_bin = scripts[0] if scripts else dash
231
+
232
+ with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as env:
233
+ env.write(f"PROJECT_NAME={project}\n")
234
+ env.write(f"PACKAGE_MODULE={module}\n")
235
+ env.write(f"CLI_BIN={cli_bin}\n")
236
+
237
+ - name: Build wheel
238
+ run: uv build
239
+ - name: pipx install from wheel
240
+ run: |
241
+ uv pip install --system pipx
242
+ pipx ensurepath
243
+ export PATH="$HOME/.local/bin:$PATH"
244
+ pipx install dist/*.whl
245
+ "$CLI_BIN" --version
246
+
247
+ - name: uv tool install
248
+ run: |
249
+ export PATH="$HOME/.local/bin:$PATH"
250
+ uv tool install --force .
251
+ "$CLI_BIN" --version
252
+
253
+ - name: Prune uv cache
254
+ continue-on-error: true
255
+ run: uv cache prune --ci
256
+
257
+ notebooks:
258
+ name: Execute notebooks (ubuntu, Python 3.14)
259
+ runs-on: ubuntu-latest
260
+ steps:
261
+ - uses: actions/checkout@v6
262
+ - uses: actions/setup-python@v6
263
+ with:
264
+ python-version: "3.14"
265
+
266
+ - name: Install uv
267
+ uses: astral-sh/setup-uv@v7
268
+ with:
269
+ enable-cache: true
270
+ prune-cache: false
271
+
272
+ - name: Install notebook runner deps
273
+ run: |
274
+ uv pip install --system nbclient nbformat ipykernel jupyter_client
275
+ python -m ipykernel install --user --name python3 --display-name "Python 3"
276
+ - name: Execute Quickstart notebook
277
+ env:
278
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
279
+ PIP_NO_INPUT: "1"
280
+ run: |
281
+ python - << 'PY'
282
+ from pathlib import Path
283
+
284
+ import nbformat
285
+ from nbclient import NotebookClient
286
+
287
+ nb_path = Path('notebooks/Quickstart.ipynb')
288
+ if not nb_path.exists():
289
+ raise SystemExit(f"Notebook not found: {nb_path}")
290
+
291
+ notebook = nbformat.read(nb_path, as_version=4)
292
+ client = NotebookClient(
293
+ notebook,
294
+ timeout=900,
295
+ kernel_name='python3',
296
+ allow_errors=False,
297
+ )
298
+ client.execute()
299
+
300
+ out_path = Path('notebooks/Quickstart-executed.ipynb')
301
+ nbformat.write(notebook, out_path)
302
+ print(f"Executed notebook written to: {out_path}")
303
+ PY
@@ -0,0 +1,69 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ push:
7
+ tags:
8
+ - 'v*'
9
+ workflow_dispatch: {}
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ env:
17
+ UV_LINK_MODE: "copy"
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+ - uses: actions/setup-python@v6
21
+ with:
22
+ python-version: '3.14'
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v7
26
+ with:
27
+ enable-cache: true
28
+ prune-cache: false
29
+
30
+ - name: Build artifacts
31
+ run: uv build
32
+
33
+ - name: Upload artifacts
34
+ uses: actions/upload-artifact@v6
35
+ with:
36
+ name: dist
37
+ path: dist/*
38
+
39
+ - name: Prune uv cache
40
+ continue-on-error: true
41
+ run: uv cache prune --ci
42
+
43
+ publish:
44
+ needs: build
45
+ runs-on: ubuntu-latest
46
+ environment: pypi
47
+ permissions:
48
+ contents: read
49
+ steps:
50
+ - name: Verify PyPI token is set
51
+ run: |
52
+ if [ -z "${{ secrets.PYPI_API_TOKEN }}" ]; then
53
+ echo "::error title=Missing PyPI token::Set the PYPI_API_TOKEN secret in GitHub repository settings to enable PyPI publishing on releases."
54
+ echo "See: https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions"
55
+ exit 1
56
+ fi
57
+
58
+ - name: Download artifacts
59
+ uses: actions/download-artifact@v6
60
+ with:
61
+ name: dist
62
+ path: dist/
63
+
64
+ - name: Publish to PyPI
65
+ uses: pypa/gh-action-pypi-publish@release/v1
66
+ with:
67
+ password: ${{ secrets.PYPI_API_TOKEN }}
68
+ attestations: false
69
+ skip-existing: true
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
5
 
6
+ ## [2.2.4] - 2026-01-26
7
+
8
+ ### Changed
9
+ - Updated dependencies.
10
+ - Excluded `scripts/` from pyright, pytest, and CI/CD test collection.
11
+
6
12
  ## [2.2.3] - 2025-12-17
7
13
 
8
14
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lib_cli_exit_tools
3
- Version: 2.2.3
3
+ Version: 2.2.4
4
4
  Summary: CLI exit handling helpers: clean signals, exit codes, and error printing
5
5
  Project-URL: Homepage, https://github.com/bitranox/lib_cli_exit_tools
6
6
  Project-URL: Repository, https://github.com/bitranox/lib_cli_exit_tools.git
@@ -16,29 +16,29 @@ Classifier: Programming Language :: Python :: 3
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
17
  Classifier: Typing :: Typed
18
18
  Requires-Python: >=3.10
19
- Requires-Dist: rich-click>=1.9.4
19
+ Requires-Dist: rich-click>=1.9.6
20
20
  Provides-Extra: dev
21
- Requires-Dist: bandit>=1.9.2; extra == 'dev'
22
- Requires-Dist: build>=1.3.0; extra == 'dev'
21
+ Requires-Dist: bandit>=1.9.3; extra == 'dev'
22
+ Requires-Dist: build>=1.4.0; extra == 'dev'
23
23
  Requires-Dist: codecov-cli>=11.2.6; extra == 'dev'
24
- Requires-Dist: coverage[toml]>=7.13.0; extra == 'dev'
25
- Requires-Dist: hypothesis>=6.148.7; extra == 'dev'
24
+ Requires-Dist: coverage[toml]>=7.13.2; extra == 'dev'
25
+ Requires-Dist: hypothesis>=6.151.2; extra == 'dev'
26
26
  Requires-Dist: import-linter>=2.9; extra == 'dev'
27
27
  Requires-Dist: pip-audit>=2.10.0; extra == 'dev'
28
- Requires-Dist: pyright>=1.1.407; extra == 'dev'
28
+ Requires-Dist: pyright[nodejs]>=1.1.408; extra == 'dev'
29
29
  Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
30
30
  Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
31
31
  Requires-Dist: pytest>=9.0.2; extra == 'dev'
32
32
  Requires-Dist: rtoml>=0.13.0; extra == 'dev'
33
- Requires-Dist: ruff>=0.14.9; extra == 'dev'
34
- Requires-Dist: textual>=6.10.0; extra == 'dev'
33
+ Requires-Dist: ruff>=0.14.14; extra == 'dev'
34
+ Requires-Dist: textual>=7.4.0; extra == 'dev'
35
35
  Requires-Dist: twine>=6.2.0; extra == 'dev'
36
36
  Description-Content-Type: text/markdown
37
37
 
38
38
  # lib_cli_exit_tools
39
39
 
40
40
  <!-- Badges -->
41
- [![CI](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/ci.yml/badge.svg)](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/ci.yml)
41
+ [![CI](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/default_cicd_public.yml/badge.svg)](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/default_cicd_public.yml)
42
42
  [![CodeQL](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/codeql.yml/badge.svg)](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/codeql.yml)
43
43
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
44
44
  [![Open in Codespaces](https://img.shields.io/badge/Codespaces-Open-blue?logo=github&logoColor=white&style=flat-square)](https://codespaces.new/bitranox/lib_cli_exit_tools?quickstart=1)
@@ -1,7 +1,7 @@
1
1
  # lib_cli_exit_tools
2
2
 
3
3
  <!-- Badges -->
4
- [![CI](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/ci.yml/badge.svg)](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/ci.yml)
4
+ [![CI](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/default_cicd_public.yml/badge.svg)](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/default_cicd_public.yml)
5
5
  [![CodeQL](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/codeql.yml/badge.svg)](https://github.com/bitranox/lib_cli_exit_tools/actions/workflows/codeql.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![Open in Codespaces](https://img.shields.io/badge/Codespaces-Open-blue?logo=github&logoColor=white&style=flat-square)](https://codespaces.new/bitranox/lib_cli_exit_tools?quickstart=1)
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "lib_cli_exit_tools"
3
- version = "2.2.3"
3
+ version = "2.2.4"
4
4
  description = "CLI exit handling helpers: clean signals, exit codes, and error printing"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
- dependencies = ["rich-click>=1.9.4"]
7
+ dependencies = ["rich-click>=1.9.6"]
8
8
  license = { text = "MIT" }
9
9
  authors = [{ name = "bitranox", email = "bitranox@gmail.com" }]
10
10
  keywords = ["cli", "exit", "signals", "errors", "click"]
@@ -24,19 +24,19 @@ Issues = "https://github.com/bitranox/lib_cli_exit_tools/issues"
24
24
 
25
25
  [project.optional-dependencies]
26
26
  dev = [
27
- "build>=1.3.0",
27
+ "build>=1.4.0",
28
28
  "codecov-cli>=11.2.6",
29
- "coverage[toml]>=7.13.0",
30
- "hypothesis>=6.148.7",
29
+ "coverage[toml]>=7.13.2",
30
+ "hypothesis>=6.151.2",
31
31
  "import-linter>=2.9",
32
- "bandit>=1.9.2",
32
+ "bandit>=1.9.3",
33
33
  "pip-audit>=2.10.0",
34
- "pyright>=1.1.407",
34
+ "pyright[nodejs]>=1.1.408",
35
35
  "pytest>=9.0.2",
36
36
  "pytest-asyncio>=1.3.0",
37
37
  "pytest-cov>=7.0.0",
38
- "ruff>=0.14.9",
39
- "textual>=6.10.0",
38
+ "ruff>=0.14.14",
39
+ "textual>=7.4.0",
40
40
  "rtoml>=0.13.0",
41
41
  "twine>=6.2.0"
42
42
  ]
@@ -66,12 +66,13 @@ target-version = "py310"
66
66
  [tool.pyright]
67
67
  typeCheckingMode = "strict"
68
68
  exclude = [
69
- "scripts/menu.py",
69
+ "scripts/",
70
70
  ]
71
71
 
72
72
  [tool.pytest.ini_options]
73
73
  addopts = ["--doctest-modules"]
74
74
  pythonpath = ["src"]
75
+ testpaths = ["src", "tests"]
75
76
  doctest_optionflags = ["ELLIPSIS", "NORMALIZE_WHITESPACE"]
76
77
  markers = [
77
78
  "os_agnostic: test exercises behavior that is the same on every operating system",
@@ -161,6 +162,11 @@ forbidden_modules = [
161
162
  # setuptools version is managed by the CI environment/pip itself.
162
163
  # CVE-2025-68146: filelock <3.20.1 vulnerability, transitive dev dependency
163
164
  # Will be resolved when upstream packages update their dependencies.
165
+ # CVE-2026-23949: jaraco-context <6.1.0, transitive dev dependency
166
+ # CVE-2025-69277: pynacl <1.6.2, transitive dev dependency
167
+ # CVE-2026-21441: urllib3 <2.6.3, transitive dev dependency
168
+ # CVE-2026-22702: virtualenv <20.36.1, transitive dev dependency
169
+ # CVE-2026-24049: wheel <0.46.2, build-time dependency
164
170
  ignore-vulns = [
165
171
  "GHSA-4xh5-x5gv-qwph",
166
172
  "PYSEC-2022-42969",
@@ -168,4 +174,9 @@ ignore-vulns = [
168
174
  "CVE-2024-6345",
169
175
  "PYSEC-2025-49",
170
176
  "CVE-2025-68146",
177
+ "CVE-2026-23949",
178
+ "CVE-2025-69277",
179
+ "CVE-2026-21441",
180
+ "CVE-2026-22702",
181
+ "CVE-2026-24049",
171
182
  ]
@@ -24,7 +24,7 @@ name = "lib_cli_exit_tools"
24
24
  #: Human-readable summary shown in CLI help output.
25
25
  title = "CLI exit handling helpers: clean signals, exit codes, and error printing"
26
26
  #: Current release version pulled from ``pyproject.toml`` by automation.
27
- version = "2.2.3"
27
+ version = "2.2.4"
28
28
  #: Repository homepage presented to users.
29
29
  homepage = "https://github.com/bitranox/lib_cli_exit_tools"
30
30
  #: Author attribution surfaced in CLI output.
@@ -1,233 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from click.testing import CliRunner
4
- from collections.abc import Mapping, Sequence
5
- from pathlib import Path
6
- import sys
7
- from types import ModuleType, SimpleNamespace
8
- from typing import Protocol, TypedDict
9
-
10
- from pytest import MonkeyPatch
11
-
12
- import scripts.build as build
13
- import scripts.cli as cli
14
- import scripts.dev as dev
15
- import scripts.install as install
16
- import scripts.run_cli as run_cli
17
- import scripts.test as test_script
18
- from scripts._utils import RunResult
19
- from scripts import _utils
20
- from scripts import target_metadata
21
-
22
-
23
- RunCommand = Sequence[str] | str
24
- ModuleLike = ModuleType | SimpleNamespace
25
-
26
-
27
- class RecordedOptions(TypedDict):
28
- check: bool
29
- capture: bool
30
- cwd: str | None
31
- env: Mapping[str, str] | None
32
- dry_run: bool
33
-
34
-
35
- RunRecord = tuple[RunCommand, RecordedOptions]
36
-
37
-
38
- class RunStub(Protocol):
39
- def __call__(
40
- self,
41
- cmd: RunCommand,
42
- *,
43
- check: bool = True,
44
- capture: bool = True,
45
- cwd: str | None = None,
46
- env: Mapping[str, str] | None = None,
47
- dry_run: bool = False,
48
- ) -> RunResult: ...
49
-
50
-
51
- def _make_run_recorder(record: list[RunRecord]) -> RunStub:
52
- def _run(
53
- cmd: RunCommand,
54
- *,
55
- check: bool = True,
56
- capture: bool = True,
57
- cwd: str | None = None,
58
- env: Mapping[str, str] | None = None,
59
- dry_run: bool = False,
60
- ) -> RunResult:
61
- record.append(
62
- (
63
- cmd,
64
- {
65
- "check": check,
66
- "capture": capture,
67
- "cwd": cwd,
68
- "env": env,
69
- "dry_run": dry_run,
70
- },
71
- ),
72
- )
73
- return RunResult(0, "", "")
74
-
75
- return _run
76
-
77
-
78
- def test_get_project_metadata_fields():
79
- meta = _utils.get_project_metadata()
80
- assert meta.name == "lib_cli_exit_tools"
81
- assert meta.slug == "lib-cli-exit-tools"
82
- assert meta.import_package == "lib_cli_exit_tools"
83
- assert meta.coverage_source == "src/lib_cli_exit_tools"
84
- assert meta.github_tarball_url("1.2.3").endswith("/bitranox/lib_cli_exit_tools/archive/refs/tags/v1.2.3.tar.gz")
85
-
86
-
87
- def test_build_script_uses_metadata(monkeypatch: MonkeyPatch) -> None:
88
- recorded: list[RunRecord] = []
89
- monkeypatch.setattr(build, "run", _make_run_recorder(recorded))
90
- runner = CliRunner()
91
- result = runner.invoke(cli.main, ["build"])
92
- assert result.exit_code == 0
93
- commands = [" ".join(cmd) if not isinstance(cmd, str) else cmd for cmd, _ in recorded]
94
- assert any("python -m build" in cmd for cmd in commands)
95
-
96
-
97
- def test_dev_script_installs_dev_extras(monkeypatch: MonkeyPatch) -> None:
98
- recorded: list[RunRecord] = []
99
- monkeypatch.setattr(dev, "run", _make_run_recorder(recorded))
100
- runner = CliRunner()
101
- result = runner.invoke(cli.main, ["dev"])
102
- assert result.exit_code == 0
103
- first_command, _options = recorded[0]
104
- assert isinstance(first_command, list)
105
- assert first_command == [sys.executable, "-m", "pip", "install", "-e", ".[dev]"]
106
-
107
-
108
- def test_install_script_installs_package(monkeypatch: MonkeyPatch) -> None:
109
- recorded: list[RunRecord] = []
110
- monkeypatch.setattr(install, "run", _make_run_recorder(recorded))
111
- runner = CliRunner()
112
- result = runner.invoke(cli.main, ["install"])
113
- assert result.exit_code == 0
114
- first_command, _options = recorded[0]
115
- assert isinstance(first_command, list)
116
- assert first_command == [sys.executable, "-m", "pip", "install", "-e", "."]
117
-
118
-
119
- def test_run_cli_imports_dynamic_package(monkeypatch: MonkeyPatch) -> None:
120
- seen: list[str] = []
121
-
122
- def _run_cli_main(_args: Sequence[str] | None = None) -> int:
123
- return 0
124
-
125
- def fake_import(name: str) -> ModuleLike:
126
- seen.append(name)
127
- if name.endswith(".__main__"):
128
- return SimpleNamespace()
129
- if name.endswith(".cli"):
130
- return SimpleNamespace(main=_run_cli_main)
131
- raise AssertionError(f"unexpected import {name}")
132
-
133
- monkeypatch.setattr(run_cli, "import_module", fake_import)
134
- runner = CliRunner()
135
- result = runner.invoke(cli.main, ["run"])
136
- assert result.exit_code == 0
137
- package = run_cli.PROJECT.import_package
138
- assert f"{package}.cli" in seen
139
- if len(seen) == 2:
140
- assert seen == [f"{package}.__main__", f"{package}.cli"]
141
-
142
-
143
- def test_test_script_uses_pyproject_configuration(monkeypatch: MonkeyPatch) -> None:
144
- recorded: list[RunRecord] = []
145
-
146
- def _noop() -> None:
147
- return None
148
-
149
- def _always_false(_name: str) -> bool:
150
- return False
151
-
152
- monkeypatch.setattr(test_script, "bootstrap_dev", _noop)
153
- monkeypatch.setattr(_utils, "cmd_exists", _always_false)
154
- monkeypatch.setattr(test_script, "run", _make_run_recorder(recorded))
155
- runner = CliRunner()
156
- result = runner.invoke(cli.main, ["test"])
157
- assert result.exit_code == 0
158
- pytest_commands: list[list[str]] = []
159
- for cmd, _ in recorded:
160
- if isinstance(cmd, str):
161
- continue
162
- command_list = list(cmd)
163
- if command_list[:3] == ["python", "-m", "pytest"]:
164
- pytest_commands.append(command_list)
165
- assert pytest_commands, "pytest not invoked"
166
- assert any(f"--cov={test_script.COVERAGE_TARGET}" in " ".join(sequence) for sequence in pytest_commands)
167
-
168
-
169
- def test_run_coverage_invokes_python_module(monkeypatch: MonkeyPatch) -> None:
170
- recorded: list[RunRecord] = []
171
- bootstrap_calls: list[str] = []
172
-
173
- def _bootstrap() -> None:
174
- bootstrap_calls.append("bootstrap")
175
-
176
- monkeypatch.setattr(test_script, "bootstrap_dev", _bootstrap)
177
- monkeypatch.setattr(test_script, "run", _make_run_recorder(recorded))
178
-
179
- # create stale coverage artefacts to ensure cleanup happens first
180
- Path(".coverage.stale").write_text("stale", encoding="utf-8")
181
- Path("coverage.xml").write_text("old", encoding="utf-8")
182
- Path("codecov.xml").write_text("old", encoding="utf-8")
183
-
184
- test_script.run_coverage()
185
-
186
- assert bootstrap_calls == ["bootstrap"]
187
- assert len(recorded) == 2
188
-
189
- first_cmd, first_opts = recorded[0]
190
- assert isinstance(first_cmd, list)
191
- assert first_cmd[:4] == [sys.executable, "-m", "coverage", "run"]
192
- assert first_cmd[4:7] == ["-m", "pytest", "-vv"]
193
- assert first_opts["capture"] is True
194
- assert first_opts["env"] is not None
195
- assert first_opts["env"].get("COVERAGE_NO_SQL") == "1"
196
- coverage_file = first_opts["env"].get("COVERAGE_FILE", "")
197
- assert coverage_file.endswith(".coverage")
198
- assert coverage_file != ".coverage"
199
- assert Path(coverage_file).is_absolute()
200
-
201
- second_cmd, second_opts = recorded[1]
202
- assert isinstance(second_cmd, list)
203
- assert second_cmd[:5] == [sys.executable, "-m", "coverage", "report", "-m"]
204
- assert second_opts["capture"] is True
205
- assert second_opts["env"] is not None
206
- assert second_opts["env"].get("COVERAGE_NO_SQL") == "1"
207
- assert second_opts["env"].get("COVERAGE_FILE") == coverage_file
208
-
209
- assert not Path(".coverage.stale").exists()
210
- assert not Path("coverage.xml").exists()
211
- assert not Path("codecov.xml").exists()
212
-
213
-
214
- def test_cli_coverage_command_toggles_verbose(monkeypatch: MonkeyPatch) -> None:
215
- invoked: dict[str, bool] = {}
216
-
217
- def _cover(*, verbose: bool) -> None:
218
- invoked["verbose"] = verbose
219
-
220
- monkeypatch.setattr(test_script, "run_coverage", _cover)
221
- runner = CliRunner()
222
- result = runner.invoke(cli.main, ["coverage", "--verbose"])
223
-
224
- assert result.exit_code == 0
225
- assert invoked == {"verbose": True}
226
-
227
-
228
- def test_target_metadata_includes_coverage_entry() -> None:
229
- targets = target_metadata.get_targets()
230
- coverage_entries = [t for t in targets if t.name == "coverage"]
231
- assert coverage_entries
232
- (entry,) = coverage_entries
233
- assert entry.description.startswith("Run python -m coverage")