lib-cli-exit-tools 2.2.3__tar.gz → 2.3.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.
Files changed (84) hide show
  1. lib_cli_exit_tools-2.3.0/.claude/commands/tfbp.md +84 -0
  2. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/cli.sh +27 -25
  3. lib_cli_exit_tools-2.3.0/.data_arch_violations.json +38 -0
  4. lib_cli_exit_tools-2.3.0/.github/actions/extract-metadata/action.yml +92 -0
  5. lib_cli_exit_tools-2.3.0/.github/workflows/default_cicd_public.yml +283 -0
  6. lib_cli_exit_tools-2.2.3/.github/workflows/release.yml → lib_cli_exit_tools-2.3.0/.github/workflows/default_release_public.yml +39 -20
  7. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.gitignore +1 -0
  8. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/CHANGELOG.md +23 -0
  9. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/CLAUDE.md +3 -3
  10. lib_cli_exit_tools-2.3.0/Makefile +235 -0
  11. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/PKG-INFO +11 -11
  12. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/README.md +1 -1
  13. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/pyproject.toml +27 -11
  14. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/__init__.py +1 -0
  15. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/__init__conf__.py +1 -1
  16. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/application/runner.py +20 -28
  17. lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/__init__.py +27 -0
  18. lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/commands.py +69 -0
  19. lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/group.py +123 -0
  20. lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/styling.py +111 -0
  21. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/core/configuration.py +20 -4
  22. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/core/exit_codes.py +2 -2
  23. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +2 -1
  24. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/conftest.py +2 -1
  25. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_cli.py +27 -19
  26. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_core_configuration.py +5 -4
  27. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_core_exit_codes.py +2 -1
  28. lib_cli_exit_tools-2.2.3/.github/workflows/ci.yml +0 -208
  29. lib_cli_exit_tools-2.2.3/Makefile +0 -60
  30. lib_cli_exit_tools-2.2.3/scripts/__init__.py +0 -3
  31. lib_cli_exit_tools-2.2.3/scripts/__main__.py +0 -7
  32. lib_cli_exit_tools-2.2.3/scripts/_utils.py +0 -768
  33. lib_cli_exit_tools-2.2.3/scripts/build.py +0 -52
  34. lib_cli_exit_tools-2.2.3/scripts/bump.py +0 -32
  35. lib_cli_exit_tools-2.2.3/scripts/bump_major.py +0 -20
  36. lib_cli_exit_tools-2.2.3/scripts/bump_minor.py +0 -20
  37. lib_cli_exit_tools-2.2.3/scripts/bump_patch.py +0 -20
  38. lib_cli_exit_tools-2.2.3/scripts/bump_version.py +0 -80
  39. lib_cli_exit_tools-2.2.3/scripts/clean.py +0 -75
  40. lib_cli_exit_tools-2.2.3/scripts/cli.py +0 -232
  41. lib_cli_exit_tools-2.2.3/scripts/dependencies.py +0 -590
  42. lib_cli_exit_tools-2.2.3/scripts/dev.py +0 -19
  43. lib_cli_exit_tools-2.2.3/scripts/help.py +0 -41
  44. lib_cli_exit_tools-2.2.3/scripts/install.py +0 -19
  45. lib_cli_exit_tools-2.2.3/scripts/menu.py +0 -582
  46. lib_cli_exit_tools-2.2.3/scripts/push.py +0 -309
  47. lib_cli_exit_tools-2.2.3/scripts/release.py +0 -92
  48. lib_cli_exit_tools-2.2.3/scripts/run_cli.py +0 -154
  49. lib_cli_exit_tools-2.2.3/scripts/target_metadata.py +0 -202
  50. lib_cli_exit_tools-2.2.3/scripts/test.py +0 -1004
  51. lib_cli_exit_tools-2.2.3/scripts/toml_config.py +0 -433
  52. lib_cli_exit_tools-2.2.3/scripts/version_current.py +0 -20
  53. lib_cli_exit_tools-2.2.3/src/lib_cli_exit_tools/cli.py +0 -261
  54. lib_cli_exit_tools-2.2.3/tests/test_scripts.py +0 -233
  55. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/cli-config.yaml +0 -0
  56. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/codacy.yaml +0 -0
  57. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/tools-configs/lizard.yaml +0 -0
  58. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/tools-configs/trivy.yaml +0 -0
  59. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.devcontainer/devcontainer.json +0 -0
  60. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.devcontainer/settings.json +0 -0
  61. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.env.example +0 -0
  62. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.github/dependabot.yml +0 -0
  63. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.github/workflows/codeql.yml +0 -0
  64. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.qlty/qlty.toml +0 -0
  65. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.snyk +0 -0
  66. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/CONTRIBUTING.md +0 -0
  67. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/DEVELOPMENT.md +0 -0
  68. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/INSTALL.md +0 -0
  69. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/LICENSE +0 -0
  70. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/codecov.yml +0 -0
  71. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/docs/system-design/module_reference.md +0 -0
  72. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/notebooks/Quickstart.ipynb +0 -0
  73. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/__main__.py +0 -0
  74. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/adapters/__init__.py +0 -0
  75. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/adapters/signals.py +0 -0
  76. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/application/__init__.py +0 -0
  77. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/core/__init__.py +0 -0
  78. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/py.typed +0 -0
  79. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_adapters_signals.py +0 -0
  80. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_application_runner.py +0 -0
  81. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_behaviors.py +0 -0
  82. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_metadata.py +0 -0
  83. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_module_entry.py +0 -0
  84. {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_signals_integration.py +0 -0
@@ -0,0 +1,84 @@
1
+ Test, Fix, Bump, Push — full release pipeline.
2
+
3
+ Execute the following steps sequentially. Do NOT skip steps. Stop and ask the user if any decision requires human judgement.
4
+
5
+ ---
6
+
7
+ ## Step 1 — Run Tests
8
+
9
+ Run `make test` and capture the full output.
10
+
11
+ ## Step 2 — Fix All Errors
12
+
13
+ If `make test` failed:
14
+ - Analyze every error in the output (lint, type-check, unit tests, shellcheck, etc.).
15
+ - Fix all errors in the source code.
16
+ - Re-run `make test` to confirm all errors are resolved.
17
+ - Repeat until `make test` passes with zero failures.
18
+
19
+ If `make test` passed, proceed to the next step.
20
+
21
+ ## Step 3 — Determine Semver Bump Level
22
+
23
+ Analyze all changes since the last released version:
24
+ - Run `git log $(git describe --tags --abbrev=0)..HEAD --oneline` to list commits since the last tag.
25
+ - Run `git diff $(git describe --tags --abbrev=0)..HEAD --stat` to see which files changed.
26
+ - Apply semver rules:
27
+ - **major** — breaking API changes, removed public functions/classes, changed signatures
28
+ - **minor** — new features, new public API, new modules, backward-compatible additions
29
+ - **patch** — bug fixes, documentation, internal refactors, dependency updates, CI/CD changes
30
+ - Present your reasoning and recommended bump level to the user for confirmation before proceeding.
31
+
32
+ ## Step 4 — Bump Version
33
+
34
+ Run the appropriate command based on the confirmed semver level:
35
+ - `make bump-patch` for patch
36
+ - `make bump-minor` for minor
37
+ - `make bump-major` for major
38
+
39
+ ## Step 5 — Update Changelog
40
+
41
+ Update `CHANGELOG.md` with the important changes since the last version:
42
+ - Group changes under the new version heading with the current date.
43
+ - Categorize entries (Added, Changed, Fixed, Removed, etc.) following Keep a Changelog format.
44
+ - Be concise but cover all user-visible and developer-visible changes.
45
+
46
+ ## Step 6 — Create Commit Message
47
+
48
+ Compose a meaningful commit message that:
49
+ - Summarizes the version bump and key changes.
50
+ - References the new version number.
51
+ - Follows conventional commit style if the project uses it.
52
+
53
+ Present the commit message to the user for approval.
54
+
55
+ ## Step 7 — Push
56
+
57
+ Run `make push <approved commit message>` to run tests, commit, and push to the remote.
58
+
59
+ ## Step 8 — Monitor GitHub CI
60
+
61
+ After pushing:
62
+ - Run `gh run list --limit 1` to find the latest workflow run.
63
+ - Run `gh run watch <run-id>` to monitor it until completion.
64
+ - If the run **succeeds**, proceed to Step 9.
65
+ - If the run **fails**:
66
+ - Run `gh run view <run-id> --log-failed` to get failure details.
67
+ - Fix all errors in the source code.
68
+ - Go back to **Step 5** (update changelog if the fix is meaningful, re-commit, re-push).
69
+
70
+ ## Step 9 — Release
71
+
72
+ Once GitHub CI is green:
73
+ - Run `make release` to create the version tag and GitHub release.
74
+
75
+ ## Step 10 — Monitor Release CI
76
+
77
+ After releasing:
78
+ - Run `gh run list --limit 1` to find the release workflow run.
79
+ - Run `gh run watch <run-id>` to monitor it until completion.
80
+ - If the run **succeeds**, report the release URL and confirm completion.
81
+ - If the run **fails**:
82
+ - Run `gh run view <run-id> --log-failed` to get failure details.
83
+ - Fix all errors in the source code.
84
+ - Go back to **Step 5** (update changelog, re-commit, re-push, re-release).
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
-
4
3
  set -e +o pipefail
5
4
 
6
5
  # Set up paths first
@@ -11,15 +10,15 @@ os_name=$(uname)
11
10
  arch=$(uname -m)
12
11
 
13
12
  case "$arch" in
14
- "x86_64")
15
- arch="amd64"
16
- ;;
17
- "x86")
18
- arch="386"
19
- ;;
20
- "aarch64"|"arm64")
21
- arch="arm64"
22
- ;;
13
+ "x86_64")
14
+ arch="amd64"
15
+ ;;
16
+ "x86")
17
+ arch="386"
18
+ ;;
19
+ "aarch64" | "arm64")
20
+ arch="arm64"
21
+ ;;
23
22
  esac
24
23
 
25
24
  if [ -z "$CODACY_CLI_V2_TMP_FOLDER" ]; then
@@ -34,10 +33,10 @@ fi
34
33
 
35
34
  version_file="$CODACY_CLI_V2_TMP_FOLDER/version.yaml"
36
35
 
37
-
38
36
  get_version_from_yaml() {
39
37
  if [ -f "$version_file" ]; then
40
- local version=$(grep -o 'version: *"[^"]*"' "$version_file" | cut -d'"' -f2)
38
+ local version
39
+ version=$(grep -o 'version: *"[^"]*"' "$version_file" | cut -d'"' -f2)
41
40
  if [ -n "$version" ]; then
42
41
  echo "$version"
43
42
  return 0
@@ -49,20 +48,24 @@ get_version_from_yaml() {
49
48
  get_latest_version() {
50
49
  local response
51
50
  if [ -n "$GH_TOKEN" ]; then
52
- response=$(curl -Lq --header "Authorization: Bearer $GH_TOKEN" "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null)
51
+ response=$(curl -Lq \
52
+ --header "Authorization: Bearer $GH_TOKEN" \
53
+ "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null)
53
54
  else
54
- response=$(curl -Lq "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null)
55
+ response=$(curl -Lq \
56
+ "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null)
55
57
  fi
56
58
 
57
59
  handle_rate_limit "$response"
58
- local version=$(echo "$response" | grep -m 1 tag_name | cut -d'"' -f4)
60
+ local version
61
+ version=$(echo "$response" | grep -m 1 tag_name | cut -d'"' -f4)
59
62
  echo "$version"
60
63
  }
61
64
 
62
65
  handle_rate_limit() {
63
66
  local response="$1"
64
67
  if echo "$response" | grep -q "API rate limit exceeded"; then
65
- fatal "Error: GitHub API rate limit exceeded. Please try again later"
68
+ fatal "Error: GitHub API rate limit exceeded. Please try again later"
66
69
  fi
67
70
  }
68
71
 
@@ -70,9 +73,9 @@ download_file() {
70
73
  local url="$1"
71
74
 
72
75
  echo "Downloading from URL: ${url}"
73
- if command -v curl > /dev/null 2>&1; then
76
+ if command -v curl >/dev/null 2>&1; then
74
77
  curl -# -LS "$url" -O
75
- elif command -v wget > /dev/null 2>&1; then
78
+ elif command -v wget >/dev/null 2>&1; then
76
79
  wget "$url"
77
80
  else
78
81
  fatal "Error: Could not find curl or wget, please install one."
@@ -83,7 +86,7 @@ download() {
83
86
  local url="$1"
84
87
  local output_folder="$2"
85
88
 
86
- ( cd "$output_folder" && download_file "$url" )
89
+ (cd "$output_folder" && download_file "$url")
87
90
  }
88
91
 
89
92
  download_cli() {
@@ -95,7 +98,7 @@ download_cli() {
95
98
  local version="$3"
96
99
 
97
100
  if [ ! -f "$bin_path" ]; then
98
- echo "📥 Downloading CLI version $version..."
101
+ echo "Downloading CLI version $version..."
99
102
 
100
103
  remote_file="codacy-cli-v2_${version}_${suffix}_${arch}.tar.gz"
101
104
  url="https://github.com/codacy/codacy-cli-v2/releases/download/${version}/${remote_file}"
@@ -107,16 +110,16 @@ download_cli() {
107
110
 
108
111
  # Warn if CODACY_CLI_V2_VERSION is set and update is requested
109
112
  if [ -n "$CODACY_CLI_V2_VERSION" ] && [ "$1" = "update" ]; then
110
- echo "⚠️ Warning: Performing update with forced version $CODACY_CLI_V2_VERSION"
113
+ echo "Warning: Performing update with forced version $CODACY_CLI_V2_VERSION"
111
114
  echo " Unset CODACY_CLI_V2_VERSION to use the latest version"
112
115
  fi
113
116
 
114
117
  # Ensure version.yaml exists and is up to date
115
118
  if [ ! -f "$version_file" ] || [ "$1" = "update" ]; then
116
- echo "ℹ️ Fetching latest version..."
119
+ echo "Fetching latest version..."
117
120
  version=$(get_latest_version)
118
121
  mkdir -p "$CODACY_CLI_V2_TMP_FOLDER"
119
- echo "version: \"$version\"" > "$version_file"
122
+ echo "version: \"$version\"" >"$version_file"
120
123
  fi
121
124
 
122
125
  # Set the version to use
@@ -126,7 +129,6 @@ else
126
129
  version=$(get_version_from_yaml)
127
130
  fi
128
131
 
129
-
130
132
  # Set up version-specific paths
131
133
  bin_folder="${CODACY_CLI_V2_TMP_FOLDER}/${version}"
132
134
 
@@ -146,4 +148,4 @@ if [ "$#" -eq 1 ] && [ "$1" = "download" ]; then
146
148
  echo "Codacy cli v2 download succeeded"
147
149
  else
148
150
  eval "$run_command $*"
149
- fi
151
+ fi
@@ -0,0 +1,38 @@
1
+ {
2
+ "pass": 1,
3
+ "total_violations": 0,
4
+ "files": {
5
+ "src/lib_cli_exit_tools/core/configuration.py": {
6
+ "violations": [],
7
+ "status": "clean"
8
+ },
9
+ "src/lib_cli_exit_tools/core/exit_codes.py": {
10
+ "violations": [],
11
+ "status": "clean"
12
+ },
13
+ "src/lib_cli_exit_tools/application/runner.py": {
14
+ "violations": [],
15
+ "status": "clean"
16
+ },
17
+ "src/lib_cli_exit_tools/__init__.py": {
18
+ "violations": [],
19
+ "status": "clean"
20
+ },
21
+ "src/lib_cli_exit_tools/__init__conf__.py": {
22
+ "violations": [],
23
+ "status": "clean"
24
+ },
25
+ "src/lib_cli_exit_tools/cli.py": {
26
+ "violations": [],
27
+ "status": "clean"
28
+ },
29
+ "src/lib_cli_exit_tools/lib_cli_exit_tools.py": {
30
+ "violations": [],
31
+ "status": "clean"
32
+ },
33
+ "src/lib_cli_exit_tools/adapters/signals.py": {
34
+ "violations": [],
35
+ "status": "clean"
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,92 @@
1
+ name: Extract project metadata
2
+ description: >-
3
+ Parse pyproject.toml and expose project metadata as step outputs.
4
+ Requires Python to be available in the runner environment.
5
+ Auto-installs rtoml if not already present.
6
+
7
+ outputs:
8
+ PACKAGE_MODULE:
9
+ description: Python-importable module name (underscored)
10
+ value: ${{ steps.meta.outputs.PACKAGE_MODULE }}
11
+ PROJECT_NAME:
12
+ description: Project name from pyproject.toml
13
+ value: ${{ steps.meta.outputs.PROJECT_NAME }}
14
+ CLI_BIN:
15
+ description: CLI entry-point binary name
16
+ value: ${{ steps.meta.outputs.CLI_BIN }}
17
+ SRC_PATH:
18
+ description: Source directory path (e.g. "src")
19
+ value: ${{ steps.meta.outputs.SRC_PATH }}
20
+ COV_FAIL_UNDER:
21
+ description: Minimum coverage percentage from tool.coverage.report.fail_under
22
+ value: ${{ steps.meta.outputs.COV_FAIL_UNDER }}
23
+ COV_REPORT_FILE:
24
+ description: Coverage report filename (e.g. "coverage.xml")
25
+ value: ${{ steps.meta.outputs.COV_REPORT_FILE }}
26
+ PYTEST_VERBOSITY:
27
+ description: Pytest verbosity flag (e.g. "-vv")
28
+ value: ${{ steps.meta.outputs.PYTEST_VERBOSITY }}
29
+ BANDIT_SKIP_FLAG:
30
+ description: Bandit skip flag string (e.g. "-s B101,B601") or empty
31
+ value: ${{ steps.meta.outputs.BANDIT_SKIP_FLAG }}
32
+
33
+ runs:
34
+ using: composite
35
+ steps:
36
+ - name: Extract project metadata from pyproject.toml
37
+ id: meta
38
+ shell: python
39
+ run: |
40
+ import os, subprocess, sys
41
+ try:
42
+ import rtoml
43
+ except ModuleNotFoundError:
44
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "rtoml", "-q"])
45
+ import rtoml
46
+
47
+ from pathlib import Path
48
+ data = rtoml.load(Path('pyproject.toml'))
49
+
50
+ # --- [project] section ---
51
+ project = data['project']['name']
52
+ module = project.replace('-', '_')
53
+ dash = project.replace('_', '-')
54
+ scripts = list(data['project'].get('scripts', {}).keys())
55
+ cli_bin = scripts[0] if scripts else dash
56
+
57
+ # --- [tool.scripts.test] section ---
58
+ scripts_test = data.get('tool', {}).get('scripts', {}).get('test', {})
59
+ src_path = scripts_test.get('src-path', 'src')
60
+ pytest_verbosity = scripts_test.get('pytest-verbosity', '-vv')
61
+ cov_report_file = scripts_test.get('coverage-report-file', 'coverage.xml')
62
+
63
+ # --- [tool.coverage.report] section ---
64
+ cov_fail_under = (
65
+ data.get('tool', {}).get('coverage', {}).get('report', {}).get('fail_under', 90)
66
+ )
67
+
68
+ # --- [tool.bandit] section ---
69
+ bandit_skips = data.get('tool', {}).get('bandit', {}).get('skips', [])
70
+ bandit_skip_flag = f"-s {','.join(bandit_skips)}" if bandit_skips else ""
71
+
72
+ # Write to GITHUB_OUTPUT for composite action outputs
73
+ with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as out:
74
+ out.write(f"PROJECT_NAME={project}\n")
75
+ out.write(f"PACKAGE_MODULE={module}\n")
76
+ out.write(f"CLI_BIN={cli_bin}\n")
77
+ out.write(f"SRC_PATH={src_path}\n")
78
+ out.write(f"PYTEST_VERBOSITY={pytest_verbosity}\n")
79
+ out.write(f"COV_REPORT_FILE={cov_report_file}\n")
80
+ out.write(f"COV_FAIL_UNDER={cov_fail_under}\n")
81
+ out.write(f"BANDIT_SKIP_FLAG={bandit_skip_flag}\n")
82
+
83
+ # Also write to GITHUB_ENV so subsequent steps can use ${{ env.VAR }}
84
+ with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as env:
85
+ env.write(f"PROJECT_NAME={project}\n")
86
+ env.write(f"PACKAGE_MODULE={module}\n")
87
+ env.write(f"CLI_BIN={cli_bin}\n")
88
+ env.write(f"SRC_PATH={src_path}\n")
89
+ env.write(f"PYTEST_VERBOSITY={pytest_verbosity}\n")
90
+ env.write(f"COV_REPORT_FILE={cov_report_file}\n")
91
+ env.write(f"COV_FAIL_UNDER={cov_fail_under}\n")
92
+ env.write(f"BANDIT_SKIP_FLAG={bandit_skip_flag}\n")
@@ -0,0 +1,283 @@
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
+ UV_BREAK_SYSTEM_PACKAGES: "1"
27
+ steps:
28
+ - uses: actions/checkout@v6
29
+ - uses: actions/setup-python@v6
30
+ with:
31
+ python-version: ${{ matrix.python }}
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v7
34
+ with:
35
+ enable-cache: true
36
+ prune-cache: false
37
+
38
+ - name: Install bash 4+ on macOS
39
+ if: runner.os == 'macOS'
40
+ run: |
41
+ brew install bash
42
+ echo "$(brew --prefix)/bin" >> "$GITHUB_PATH"
43
+
44
+ - name: Extract project metadata
45
+ id: metadata
46
+ uses: ./.github/actions/extract-metadata
47
+
48
+ - name: Install journald prerequisites
49
+ if: runner.os == 'Linux'
50
+ shell: bash
51
+ run: |
52
+ sudo apt-get update
53
+ sudo apt-get install -y python3-systemd
54
+
55
+ - name: Install dev deps
56
+ shell: bash
57
+ run: |
58
+ uv pip install -e .[dev] --system
59
+
60
+ - name: Install Windows Event Log prerequisites
61
+ if: runner.os == 'Windows'
62
+ shell: bash
63
+ run: |
64
+ uv pip install pywin32 --system
65
+
66
+ - name: Restore ruff cache
67
+ id: ruff-cache
68
+ uses: actions/cache/restore@v5
69
+ with:
70
+ path: .ruff_cache
71
+ key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
72
+ restore-keys: |
73
+ ruff-${{ runner.os }}-py${{ matrix.python }}-
74
+ ruff-${{ runner.os }}-
75
+
76
+ - name: Run ruff (lint + format)
77
+ id: ruff
78
+ continue-on-error: true
79
+ shell: bash
80
+ run: |
81
+ ruff check .
82
+ ruff format --check .
83
+
84
+ - name: Run pyright (type check)
85
+ id: pyright
86
+ continue-on-error: true
87
+ shell: bash
88
+ run: pyright
89
+
90
+ - name: Run pytest (tests + coverage)
91
+ id: pytest
92
+ continue-on-error: true
93
+ shell: bash
94
+ env:
95
+ TEST_VERBOSE: "1"
96
+ # Write coverage SQLite database to local disk — network mounts
97
+ # and stale journal files cause "database is locked" errors.
98
+ COVERAGE_FILE: "${{ runner.temp }}/.coverage"
99
+ run: >-
100
+ pytest -m "not local_only"
101
+ --cov=${{ env.SRC_PATH }}/${{ env.PACKAGE_MODULE }}
102
+ --cov-report=term-missing
103
+ --cov-report=xml:${{ env.COV_REPORT_FILE }}
104
+ --cov-fail-under=${{ env.COV_FAIL_UNDER }}
105
+ ${{ env.PYTEST_VERBOSITY }}
106
+
107
+ - name: Upload coverage to Codecov
108
+ if: >-
109
+ always()
110
+ && steps.pytest.outcome == 'success'
111
+ && matrix.os == 'ubuntu-latest'
112
+ && matrix.python == '3.13'
113
+ uses: codecov/codecov-action@v5
114
+ with:
115
+ files: ${{ env.COV_REPORT_FILE }}
116
+ fail_ci_if_error: false
117
+
118
+ - name: Run import-linter (architecture)
119
+ id: importlinter
120
+ continue-on-error: true
121
+ shell: bash
122
+ run: lint-imports
123
+
124
+ - name: Run bandit (security scan)
125
+ id: bandit
126
+ continue-on-error: true
127
+ shell: bash
128
+ run: bandit -q -r ${{ env.SRC_PATH }}/${{ env.PACKAGE_MODULE }} ${{ env.BANDIT_SKIP_FLAG }}
129
+
130
+ - name: Run pip-audit (vulnerability scan)
131
+ id: pipaudit
132
+ continue-on-error: true
133
+ shell: python
134
+ run: |
135
+ import subprocess, sys
136
+ import rtoml
137
+ from pathlib import Path
138
+ data = rtoml.load(Path("pyproject.toml"))
139
+ vulns = data.get("tool", {}).get("pip-audit", {}).get("ignore-vulns", [])
140
+ cmd = ["pip-audit", "--skip-editable"]
141
+ for v in vulns:
142
+ cmd.extend(["--ignore-vuln", v])
143
+ sys.exit(subprocess.call(cmd))
144
+
145
+ - name: Check all steps passed
146
+ shell: bash
147
+ run: |
148
+ if [[ "${{ steps.ruff.outcome }}" != "success" ]]; then
149
+ echo "::error::Ruff check failed"
150
+ exit 1
151
+ fi
152
+ if [[ "${{ steps.pyright.outcome }}" != "success" ]]; then
153
+ echo "::error::Pyright check failed"
154
+ exit 1
155
+ fi
156
+ if [[ "${{ steps.pytest.outcome }}" != "success" ]]; then
157
+ echo "::error::Pytest failed"
158
+ exit 1
159
+ fi
160
+ if [[ "${{ steps.importlinter.outcome }}" != "success" ]]; then
161
+ echo "::error::Import-linter check failed"
162
+ exit 1
163
+ fi
164
+ if [[ "${{ steps.bandit.outcome }}" != "success" ]]; then
165
+ echo "::error::Bandit security scan failed"
166
+ exit 1
167
+ fi
168
+ if [[ "${{ steps.pipaudit.outcome }}" != "success" ]]; then
169
+ echo "::error::pip-audit vulnerability scan failed"
170
+ exit 1
171
+ fi
172
+ echo "All checks passed!"
173
+
174
+ - name: Build wheel/sdist
175
+ shell: bash
176
+ run: uv build
177
+
178
+ - name: Verify wheel install in clean env
179
+ shell: bash
180
+ run: |
181
+ uv venv .venv_wheel
182
+ . .venv_wheel/bin/activate 2>/dev/null || . .venv_wheel/Scripts/activate 2>/dev/null
183
+ uv pip install dist/*.whl
184
+ "$CLI_BIN" --version 2>/dev/null || python -m "$PACKAGE_MODULE" --version
185
+
186
+ - name: Prune uv cache
187
+ continue-on-error: true
188
+ shell: bash
189
+ run: uv cache prune --ci
190
+
191
+ - name: Save ruff cache
192
+ if: always() && steps.ruff-cache.outputs.cache-hit != 'true'
193
+ uses: actions/cache/save@v5
194
+ with:
195
+ path: .ruff_cache
196
+ key: ruff-${{ runner.os }}-py${{ matrix.python }}-${{ github.sha }}
197
+
198
+ pipx-uv:
199
+ name: pipx/uv verification (ubuntu)
200
+ runs-on: ubuntu-latest
201
+ steps:
202
+ - uses: actions/checkout@v6
203
+ - uses: actions/setup-python@v6
204
+ with:
205
+ python-version: "3.14"
206
+
207
+ - name: Install uv
208
+ uses: astral-sh/setup-uv@v7
209
+ with:
210
+ enable-cache: true
211
+ prune-cache: false
212
+
213
+ - name: Extract project metadata
214
+ id: metadata
215
+ uses: ./.github/actions/extract-metadata
216
+
217
+ - name: Build wheel
218
+ run: uv build
219
+ - name: pipx install from wheel
220
+ run: |
221
+ uv pip install --system pipx
222
+ pipx ensurepath
223
+ export PATH="$HOME/.local/bin:$PATH"
224
+ pipx install dist/*.whl
225
+ "$CLI_BIN" --version
226
+
227
+ - name: uv tool install
228
+ run: |
229
+ export PATH="$HOME/.local/bin:$PATH"
230
+ uv tool install --force .
231
+ "$CLI_BIN" --version
232
+
233
+ - name: Prune uv cache
234
+ continue-on-error: true
235
+ run: uv cache prune --ci
236
+
237
+ notebooks:
238
+ name: Execute notebooks (ubuntu, Python 3.14)
239
+ runs-on: ubuntu-latest
240
+ steps:
241
+ - uses: actions/checkout@v6
242
+ - uses: actions/setup-python@v6
243
+ with:
244
+ python-version: "3.14"
245
+
246
+ - name: Install uv
247
+ uses: astral-sh/setup-uv@v7
248
+ with:
249
+ enable-cache: true
250
+ prune-cache: false
251
+
252
+ - name: Install notebook runner deps
253
+ run: |
254
+ uv pip install --system nbclient nbformat ipykernel jupyter_client
255
+ python -m ipykernel install --user --name python3 --display-name "Python 3"
256
+ - name: Execute Quickstart notebook
257
+ env:
258
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
259
+ PIP_NO_INPUT: "1"
260
+ run: |
261
+ python - << 'PY'
262
+ from pathlib import Path
263
+
264
+ import nbformat
265
+ from nbclient import NotebookClient
266
+
267
+ nb_path = Path('notebooks/Quickstart.ipynb')
268
+ if not nb_path.exists():
269
+ raise SystemExit(f"Notebook not found: {nb_path}")
270
+
271
+ notebook = nbformat.read(nb_path, as_version=4)
272
+ client = NotebookClient(
273
+ notebook,
274
+ timeout=900,
275
+ kernel_name='python3',
276
+ allow_errors=False,
277
+ )
278
+ client.execute()
279
+
280
+ out_path = Path('notebooks/Quickstart-executed.ipynb')
281
+ nbformat.write(notebook, out_path)
282
+ print(f"Executed notebook written to: {out_path}")
283
+ PY