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.
- lib_cli_exit_tools-2.3.0/.claude/commands/tfbp.md +84 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/cli.sh +27 -25
- lib_cli_exit_tools-2.3.0/.data_arch_violations.json +38 -0
- lib_cli_exit_tools-2.3.0/.github/actions/extract-metadata/action.yml +92 -0
- lib_cli_exit_tools-2.3.0/.github/workflows/default_cicd_public.yml +283 -0
- 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
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.gitignore +1 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/CHANGELOG.md +23 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/CLAUDE.md +3 -3
- lib_cli_exit_tools-2.3.0/Makefile +235 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/PKG-INFO +11 -11
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/README.md +1 -1
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/pyproject.toml +27 -11
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/__init__.py +1 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/__init__conf__.py +1 -1
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/application/runner.py +20 -28
- lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/__init__.py +27 -0
- lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/commands.py +69 -0
- lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/group.py +123 -0
- lib_cli_exit_tools-2.3.0/src/lib_cli_exit_tools/cli/styling.py +111 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/core/configuration.py +20 -4
- {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
- {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
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/conftest.py +2 -1
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_cli.py +27 -19
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_core_configuration.py +5 -4
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_core_exit_codes.py +2 -1
- lib_cli_exit_tools-2.2.3/.github/workflows/ci.yml +0 -208
- lib_cli_exit_tools-2.2.3/Makefile +0 -60
- lib_cli_exit_tools-2.2.3/scripts/__init__.py +0 -3
- lib_cli_exit_tools-2.2.3/scripts/__main__.py +0 -7
- lib_cli_exit_tools-2.2.3/scripts/_utils.py +0 -768
- lib_cli_exit_tools-2.2.3/scripts/build.py +0 -52
- lib_cli_exit_tools-2.2.3/scripts/bump.py +0 -32
- lib_cli_exit_tools-2.2.3/scripts/bump_major.py +0 -20
- lib_cli_exit_tools-2.2.3/scripts/bump_minor.py +0 -20
- lib_cli_exit_tools-2.2.3/scripts/bump_patch.py +0 -20
- lib_cli_exit_tools-2.2.3/scripts/bump_version.py +0 -80
- lib_cli_exit_tools-2.2.3/scripts/clean.py +0 -75
- lib_cli_exit_tools-2.2.3/scripts/cli.py +0 -232
- lib_cli_exit_tools-2.2.3/scripts/dependencies.py +0 -590
- lib_cli_exit_tools-2.2.3/scripts/dev.py +0 -19
- lib_cli_exit_tools-2.2.3/scripts/help.py +0 -41
- lib_cli_exit_tools-2.2.3/scripts/install.py +0 -19
- lib_cli_exit_tools-2.2.3/scripts/menu.py +0 -582
- lib_cli_exit_tools-2.2.3/scripts/push.py +0 -309
- lib_cli_exit_tools-2.2.3/scripts/release.py +0 -92
- lib_cli_exit_tools-2.2.3/scripts/run_cli.py +0 -154
- lib_cli_exit_tools-2.2.3/scripts/target_metadata.py +0 -202
- lib_cli_exit_tools-2.2.3/scripts/test.py +0 -1004
- lib_cli_exit_tools-2.2.3/scripts/toml_config.py +0 -433
- lib_cli_exit_tools-2.2.3/scripts/version_current.py +0 -20
- lib_cli_exit_tools-2.2.3/src/lib_cli_exit_tools/cli.py +0 -261
- lib_cli_exit_tools-2.2.3/tests/test_scripts.py +0 -233
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/cli-config.yaml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/codacy.yaml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/tools-configs/lizard.yaml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.codacy/tools-configs/trivy.yaml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.devcontainer/devcontainer.json +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.devcontainer/settings.json +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.env.example +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.github/dependabot.yml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.github/workflows/codeql.yml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.qlty/qlty.toml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/.snyk +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/CONTRIBUTING.md +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/DEVELOPMENT.md +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/INSTALL.md +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/LICENSE +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/codecov.yml +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/docs/system-design/module_reference.md +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/notebooks/Quickstart.ipynb +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/__main__.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/adapters/__init__.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/adapters/signals.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/application/__init__.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/core/__init__.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/src/lib_cli_exit_tools/py.typed +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_adapters_signals.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_application_runner.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_behaviors.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_metadata.py +0 -0
- {lib_cli_exit_tools-2.2.3 → lib_cli_exit_tools-2.3.0}/tests/test_module_entry.py +0 -0
- {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
|
-
|
|
16
|
-
|
|
17
|
-
"x86")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"aarch64"|"arm64")
|
|
21
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
76
|
+
if command -v curl >/dev/null 2>&1; then
|
|
74
77
|
curl -# -LS "$url" -O
|
|
75
|
-
elif command -v wget
|
|
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
|
-
(
|
|
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 "
|
|
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 "
|
|
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 "
|
|
119
|
+
echo "Fetching latest version..."
|
|
117
120
|
version=$(get_latest_version)
|
|
118
121
|
mkdir -p "$CODACY_CLI_V2_TMP_FOLDER"
|
|
119
|
-
echo "version: \"$version\"" >
|
|
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
|