DEVX-1: fix: use python3 and venv python in workflows and Makefile
This commit is contained in:
@@ -27,19 +27,19 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m devx.tools.check_test_speed --max-seconds 10
|
python3 -m devx.tools.check_test_speed --max-seconds 10
|
||||||
- name: Documentation coverage check
|
- name: Documentation coverage check
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m devx.ci.doc_coverage --fail-on-missing
|
python3 -m devx.ci.doc_coverage --fail-on-missing
|
||||||
- name: Translation completeness check
|
- name: Translation completeness check
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m devx.ci.check_translations
|
python3 -m devx.ci.check_translations
|
||||||
- name: Dependency security scan
|
- name: Dependency security scan
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m devx.ci.classify_changes \
|
python3 -m devx.ci.classify_changes \
|
||||||
--base "origin/master" \
|
--base "origin/master" \
|
||||||
--head "${{ github.event.pull_request.head.sha || github.sha }}" \
|
--head "${{ github.event.pull_request.head.sha || github.sha }}" \
|
||||||
--github-output
|
--github-output
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.ci.release --dry-run || true
|
python3 -m devx.ci.release --dry-run || true
|
||||||
|
|
||||||
pr-review:
|
pr-review:
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m devx.ci.pr_review \
|
python3 -m devx.ci.pr_review \
|
||||||
"${{ github.event.number }}" \
|
"${{ github.event.number }}" \
|
||||||
"${{ github.repository }}"
|
"${{ github.repository }}"
|
||||||
|
|
||||||
@@ -139,13 +139,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||||
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
||||||
|
DEVX_VIKUNJA_PROJECT_ID: "8"
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
HEAD_REF: ${{ github.head_ref }}
|
HEAD_REF: ${{ github.head_ref }}
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.number }}
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
run: |
|
run: |
|
||||||
python -m devx.ci.auto_merge \
|
python3 -m devx.ci.auto_merge \
|
||||||
"$HEAD_REF" \
|
"$HEAD_REF" \
|
||||||
"$PR_TITLE" \
|
"$PR_TITLE" \
|
||||||
"$REPOSITORY" \
|
"$REPOSITORY" \
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
id: check
|
id: check
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: python -m devx.ci.detect_release_commit
|
run: python3 -m devx.ci.detect_release_commit
|
||||||
|
|
||||||
validate-commit-msg:
|
validate-commit-msg:
|
||||||
needs: [detect-type]
|
needs: [detect-type]
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
git log -1 --format=%B > commit-msg.txt
|
git log -1 --format=%B > commit-msg.txt
|
||||||
python -m devx.ci.validate_commit_msg commit-msg.txt --branch master
|
python3 -m devx.ci.validate_commit_msg commit-msg.txt --branch master
|
||||||
rm -f commit-msg.txt
|
rm -f commit-msg.txt
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@@ -88,7 +88,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.ci.release
|
python3 -m devx.ci.release
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.ci.notify_failure \
|
python3 -m devx.ci.notify_failure \
|
||||||
--repo "${{ github.repository }}" \
|
--repo "${{ github.repository }}" \
|
||||||
--run-id "${{ github.run_id }}" \
|
--run-id "${{ github.run_id }}" \
|
||||||
--workflow "post-merge/release" \
|
--workflow "post-merge/release" \
|
||||||
@@ -119,7 +119,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m devx.ci.sync_wiki --repo "${{ github.repository }}" --strict
|
python3 -m devx.ci.sync_wiki --repo "${{ github.repository }}" --strict
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
@@ -127,7 +127,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.ci.notify_failure \
|
python3 -m devx.ci.notify_failure \
|
||||||
--repo "${{ github.repository }}" \
|
--repo "${{ github.repository }}" \
|
||||||
--run-id "${{ github.run_id }}" \
|
--run-id "${{ github.run_id }}" \
|
||||||
--workflow "post-merge/sync-wiki" \
|
--workflow "post-merge/sync-wiki" \
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
PRE_COMMIT_ALLOW_NO_CONFIG: "1"
|
PRE_COMMIT_ALLOW_NO_CONFIG: "1"
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m devx.ci.push_badges
|
python3 -m devx.ci.push_badges
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
@@ -163,7 +163,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.ci.notify_failure \
|
python3 -m devx.ci.notify_failure \
|
||||||
--repo "${{ github.repository }}" \
|
--repo "${{ github.repository }}" \
|
||||||
--run-id "${{ github.run_id }}" \
|
--run-id "${{ github.run_id }}" \
|
||||||
--workflow "post-merge/badges" \
|
--workflow "post-merge/badges" \
|
||||||
@@ -185,8 +185,9 @@ jobs:
|
|||||||
- name: Update Vikunja task
|
- name: Update Vikunja task
|
||||||
env:
|
env:
|
||||||
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
VIKUNJA_TOKEN: ${{ secrets.VIKUNJA_TOKEN }}
|
||||||
|
DEVX_VIKUNJA_PROJECT_ID: "8"
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: python -m devx.ci.post_merge --git-sha "${{ github.sha }}"
|
run: python3 -m devx.ci.post_merge --git-sha "${{ github.sha }}"
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
@@ -194,10 +195,10 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.tools.install_tools --tool tea
|
python3 -m devx.tools.install_tools --tool tea
|
||||||
tea login add --name devx --url "${{ github.server_url }}" --token "$REPO_TOKEN" || true
|
tea login add --name devx --url "${{ github.server_url }}" --token "$REPO_TOKEN" || true
|
||||||
tea login default devx || true
|
tea login default devx || true
|
||||||
python -m devx.ci.notify_failure \
|
python3 -m devx.ci.notify_failure \
|
||||||
--repo "${{ github.repository }}" \
|
--repo "${{ github.repository }}" \
|
||||||
--run-id "${{ github.run_id }}" \
|
--run-id "${{ github.run_id }}" \
|
||||||
--workflow "post-merge/vikunja" \
|
--workflow "post-merge/vikunja" \
|
||||||
@@ -218,7 +219,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: python -m devx.tools.configure_repo
|
run: python3 -m devx.tools.configure_repo
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
@@ -226,10 +227,10 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.tools.install_tools --tool tea
|
python3 -m devx.tools.install_tools --tool tea
|
||||||
tea login add --name devx --url "${{ github.server_url }}" --token "$REPO_TOKEN" || true
|
tea login add --name devx --url "${{ github.server_url }}" --token "$REPO_TOKEN" || true
|
||||||
tea login default devx || true
|
tea login default devx || true
|
||||||
python -m devx.ci.notify_failure \
|
python3 -m devx.ci.notify_failure \
|
||||||
--repo "${{ github.repository }}" \
|
--repo "${{ github.repository }}" \
|
||||||
--run-id "${{ github.run_id }}" \
|
--run-id "${{ github.run_id }}" \
|
||||||
--workflow "post-merge/configure-repo" \
|
--workflow "post-merge/configure-repo" \
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: Install CI tools
|
- name: Install CI tools
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.tools.install_tools --tool git-cliff --tool tea
|
python3 -m devx.tools.install_tools --tool git-cliff --tool tea
|
||||||
- name: Configure tea login
|
- name: Configure tea login
|
||||||
env:
|
env:
|
||||||
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.ci.publish "${{ github.ref_name }}" "${{ github.repository }}"
|
python3 -m devx.ci.publish "${{ github.ref_name }}" "${{ github.repository }}"
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
python -m devx.ci.notify_failure \
|
python3 -m devx.ci.notify_failure \
|
||||||
--repo "${{ github.repository }}" \
|
--repo "${{ github.repository }}" \
|
||||||
--run-id "${{ github.run_id }}" \
|
--run-id "${{ github.run_id }}" \
|
||||||
--workflow "publish" \
|
--workflow "publish" \
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: validate-commit-msg
|
- id: validate-commit-msg
|
||||||
name: validate commit message
|
name: validate commit message
|
||||||
entry: env PYTHONPATH=src .venv/bin/python -m devx.ci.validate_commit_msg
|
entry: env PYTHONPATH=src .venv/bin/python3 -m devx.ci.validate_commit_msg
|
||||||
language: system
|
language: system
|
||||||
stages: [commit-msg]
|
stages: [commit-msg]
|
||||||
pass_filenames: true
|
pass_filenames: true
|
||||||
@@ -59,7 +59,7 @@ repos:
|
|||||||
|
|
||||||
- id: commit-msg
|
- id: commit-msg
|
||||||
name: validate commit message
|
name: validate commit message
|
||||||
entry: env PYTHONPATH=src .venv/bin/python -m devx.ci.validate_commit_msg
|
entry: env PYTHONPATH=src .venv/bin/python3 -m devx.ci.validate_commit_msg
|
||||||
language: system
|
language: system
|
||||||
stages: [commit-msg]
|
stages: [commit-msg]
|
||||||
pass_filenames: true
|
pass_filenames: true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: all setup setup-ci setup-quality setup-release install update lint lint-ruff lint-format typecheck lint-bandit lint-deps lint-all test test-unit pytest-cov clean workflow-lint workflow-dryrun workflow-check install-tools
|
.PHONY: all setup setup-ci setup-quality setup-release install update lint lint-ruff lint-format typecheck lint-bandit lint-deps lint-all test test-unit pytest-cov clean workflow-lint workflow-dryrun workflow-check install-tools install-hooks activate-scripts
|
||||||
|
|
||||||
PYTHON := python3
|
PYTHON := python3
|
||||||
VENV := .venv
|
VENV := .venv
|
||||||
@@ -7,28 +7,28 @@ BIN := $(VENV)/bin
|
|||||||
all: setup
|
all: setup
|
||||||
|
|
||||||
# Full setup for local development
|
# Full setup for local development
|
||||||
setup: $(VENV)/bin/activate .env install-tools
|
setup: $(VENV)/bin/activate .env activate-scripts install-tools
|
||||||
@$(BIN)/pip install -e '.[dev]' 2>/dev/null; \
|
@$(BIN)/pip install -e '.[dev]' 2>/dev/null; \
|
||||||
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
||||||
$(PYTHON) -m devx.tools.setup --bin "$(BIN)"
|
$(BIN)/python -m devx.tools.setup --bin "$(BIN)"
|
||||||
|
|
||||||
# Lean setup for CI jobs (pytest + lint + runtime deps)
|
# Lean setup for CI jobs (pytest + lint + runtime deps)
|
||||||
setup-ci: $(VENV)/bin/activate .env
|
setup-ci: $(VENV)/bin/activate .env
|
||||||
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
||||||
$(PYTHON) -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit --no-tea-login
|
$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit --no-tea-login
|
||||||
|
|
||||||
# Setup for quality job (lint + test deps, actionlint tool)
|
# Setup for quality job (lint + test deps, actionlint tool)
|
||||||
setup-quality: $(VENV)/bin/activate .env install-tools
|
setup-quality: $(VENV)/bin/activate .env install-tools
|
||||||
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
||||||
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
||||||
$(PYTHON) -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit --no-tea-login
|
$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit --no-tea-login
|
||||||
|
|
||||||
# Setup for release jobs (needs git-cliff, tea, lint tools)
|
# Setup for release jobs (needs git-cliff, tea, lint tools)
|
||||||
setup-release: $(VENV)/bin/activate .env
|
setup-release: $(VENV)/bin/activate .env
|
||||||
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
@$(BIN)/pip install -e '.[ci,lint]' 2>/dev/null; \
|
||||||
$(PYTHON) -m devx.tools.install_tools --tool git-cliff --tool tea; \
|
$(BIN)/python -m devx.tools.install_tools --tool git-cliff --tool tea; \
|
||||||
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
export PATH="$(HOME)/.local/bin:$$PATH"; \
|
||||||
$(PYTHON) -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit
|
$(BIN)/python -m devx.tools.setup --bin "$(BIN)" --extras "ci,lint" --no-pre-commit
|
||||||
|
|
||||||
.env:
|
.env:
|
||||||
@if [ ! -f .env ]; then cp .env.example .env; echo "Created .env from .env.example — please edit it."; fi
|
@if [ ! -f .env ]; then cp .env.example .env; echo "Created .env from .env.example — please edit it."; fi
|
||||||
@@ -38,9 +38,19 @@ $(VENV)/bin/activate:
|
|||||||
$(PYTHON) -m venv $(VENV)
|
$(PYTHON) -m venv $(VENV)
|
||||||
$(BIN)/pip install --upgrade pip setuptools wheel
|
$(BIN)/pip install --upgrade pip setuptools wheel
|
||||||
|
|
||||||
install-tools:
|
activate-scripts: $(VENV)/bin/activate
|
||||||
|
@test -f activate.sh || (echo '#!/usr/bin/env bash' > activate.sh && echo 'source "$$(cd "$$(dirname "$${BASH_SOURCE[0]}")" && pwd)/.venv/bin/activate"' >> activate.sh && chmod +x activate.sh)
|
||||||
|
@test -f activate.fish || (echo '#!/usr/bin/env fish' > activate.fish && echo 'set -l script_dir (dirname (status --current-filename))' >> activate.fish && echo 'source "$$script_dir/.venv/bin/activate.fish"' >> activate.fish && chmod +x activate.fish)
|
||||||
|
@test -f activate.zsh || (echo '#!/usr/bin/env zsh' > activate.zsh && echo '0="$${ZERO:-$${0:#$$ZSH_ARGZERO}}"' >> activate.zsh && echo '0="$${$${(M)0:#/*}:-$$PWD/$$0}"' >> activate.zsh && echo 'source "$${0:A:h}/.venv/bin/activate"' >> activate.zsh && chmod +x activate.zsh)
|
||||||
|
|
||||||
|
install-hooks:
|
||||||
|
@cp hooks/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit
|
||||||
|
@cp hooks/pre-push .git/hooks/pre-push && chmod +x .git/hooks/pre-push
|
||||||
|
@echo "Git hooks installed."
|
||||||
|
|
||||||
|
install-tools: $(VENV)/bin/activate
|
||||||
@$(BIN)/pip install -e '.' 2>/dev/null; \
|
@$(BIN)/pip install -e '.' 2>/dev/null; \
|
||||||
$(PYTHON) -m devx.tools.install_tools
|
$(BIN)/python -m devx.tools.install_tools
|
||||||
|
|
||||||
lint-ruff:
|
lint-ruff:
|
||||||
$(BIN)/ruff check src/ tests/
|
$(BIN)/ruff check src/ tests/
|
||||||
|
|||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env fish
|
||||||
|
set -l script_dir (dirname (status --current-filename))
|
||||||
|
source "$script_dir/.venv/bin/activate.fish"
|
||||||
Executable
+2
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.venv/bin/activate"
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
0="${ZERO:-${0:#$ZSH_ARGZERO}}"
|
||||||
|
0="${${(M)0:#/*}:-$PWD/$0}"
|
||||||
|
source "${0:A:h}/.venv/bin/activate"
|
||||||
+4
-1
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"index.md": "Home"
|
"index.md": "Home",
|
||||||
|
"user/cli-commands.md": "CLI-Commands",
|
||||||
|
"tech/architecture.md": "Architecture",
|
||||||
|
"tech/ci-cd-workflow.md": "CI-CD-Workflow"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
devx is a reusable Python package providing development and CI/CD tools for oblachno-oss projects.
|
||||||
|
|
||||||
|
## Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/devx/
|
||||||
|
├── __init__.py # Version (single source of truth)
|
||||||
|
├── cli.py # Click-based CLI entry point (devx command)
|
||||||
|
├── config.py # Configuration system (DEVX_ env vars)
|
||||||
|
├── api_clients.py # GiteaClient, VikunjaClient — HTTP API wrappers
|
||||||
|
├── gitea_cli.py # TeaCLI — wrapper around tea CLI with JSON parsing
|
||||||
|
├── i18n.py # Translation system (gettext-based, translations.json)
|
||||||
|
├── exceptions.py # Custom exception types (DevxError, APIError)
|
||||||
|
├── translations.json # Translation strings (en, bg, de, ru, zh)
|
||||||
|
├── ci/ # CI/CD automation modules
|
||||||
|
├── tools/ # Developer tooling modules
|
||||||
|
└── molecule/ # Optional molecule testing helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Modules
|
||||||
|
|
||||||
|
### cli.py
|
||||||
|
|
||||||
|
Click-based CLI entry point. Provides three command groups: `devx ci`, `devx tools`, `devx molecule`. Each subcommand delegates to the corresponding module via `_run_module()`.
|
||||||
|
|
||||||
|
### i18n.py
|
||||||
|
|
||||||
|
Simple i18n system using a JSON translations file. Supports en, bg, de, ru, zh. Projects can extend translations by setting `DEVX_TRANSLATIONS_PATH` to a custom JSON file.
|
||||||
|
|
||||||
|
### exceptions.py
|
||||||
|
|
||||||
|
Custom exception hierarchy: `DevxError` (base), `APIError` (HTTP errors with status code and message).
|
||||||
|
|
||||||
|
### api_clients.py
|
||||||
|
|
||||||
|
HTTP API clients with connection pooling and retry logic:
|
||||||
|
- `GiteaClient` — Gitea REST API (branch protection, labels, issues, PRs, releases, reviews)
|
||||||
|
- `VikunjaClient` — Vikunja REST API (tasks, projects, comments)
|
||||||
|
|
||||||
|
Both clients retry on transient errors (429, 5xx, connection errors) with exponential backoff.
|
||||||
|
|
||||||
|
### config.py
|
||||||
|
|
||||||
|
Configuration constants with env-var overrides (`DEVX_` prefix). Includes API URLs, timeouts, retry settings, task prefix regex, and conventional commit regex.
|
||||||
|
|
||||||
|
### gitea_cli.py
|
||||||
|
|
||||||
|
Python wrapper around the `tea` Gitea CLI tool. Parses JSON output for structured data. Used by CI scripts for Gitea API operations (issues, labels, PRs, releases, reviews).
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# CI/CD Workflow
|
||||||
|
|
||||||
|
devx uses Gitea Actions for CI/CD automation. The workflow replicates GRM's automated pipeline but without molecule tests.
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
### CI (`ci.yml`)
|
||||||
|
|
||||||
|
Runs on pull requests. Jobs:
|
||||||
|
|
||||||
|
1. **quality** — lint (ruff, pyright, bandit, actionlint), unit tests with 100% coverage, test speed check, doc coverage, translation check, dependency scan
|
||||||
|
2. **detect-changes** — classify changes as user-facing or workflow-only
|
||||||
|
3. **release-dry-run** — dry-run the release script (only if user-facing changes)
|
||||||
|
4. **pr-review** — automated PR review
|
||||||
|
5. **auto-merge** — squash-merge PR when all checks pass
|
||||||
|
|
||||||
|
### Post-merge (`post-merge.yml`)
|
||||||
|
|
||||||
|
Runs on every push to master. Jobs:
|
||||||
|
|
||||||
|
1. **detect-type** — check if commit is a release commit
|
||||||
|
2. **validate-commit-msg** — validate conventional commit format
|
||||||
|
3. **release** — calculate next version, update changelog, tag, push
|
||||||
|
4. **sync-wiki** — sync docs to Gitea wiki
|
||||||
|
5. **badges** — generate and push quality badges
|
||||||
|
6. **vikunja** — mark Vikunja task as done
|
||||||
|
7. **configure-repo** — ensure branch protection and labels
|
||||||
|
|
||||||
|
### Publish (`publish.yml`)
|
||||||
|
|
||||||
|
Runs on tag pushes (`v*`). Builds the package, publishes to Gitea PyPI registry, and creates a Gitea release.
|
||||||
|
|
||||||
|
## CI Scripts
|
||||||
|
|
||||||
|
### auto_merge.py
|
||||||
|
|
||||||
|
Auto-merge PR when all CI checks pass. Reads task ID from `.taskid`, validates PR title format, checks Vikunja task exists, squash-merges with `DEVX-N <conventional commit>` title.
|
||||||
|
|
||||||
|
### release.py
|
||||||
|
|
||||||
|
Automated release using git-cliff. Calculates next semver version from conventional commits, updates `__version__` in `__init__.py`, updates `CHANGELOG.md`, runs lint and tests, commits with `release: vX.Y.Z [skip ci]`, creates annotated tag, pushes.
|
||||||
|
|
||||||
|
### publish.py
|
||||||
|
|
||||||
|
Builds package with `python -m build`, publishes to Gitea PyPI registry via twine, creates Gitea release with git-cliff-generated notes.
|
||||||
|
|
||||||
|
### pr_review.py
|
||||||
|
|
||||||
|
Automated PR review. Checks architecture compliance, best practices, security, i18n, resource management, documentation, test coverage, and commit conventions. Posts inline comments and structured review.
|
||||||
|
|
||||||
|
### notify_failure.py
|
||||||
|
|
||||||
|
Creates a Gitea issue when a CI workflow fails. Uses tea CLI for issue creation with failure labels.
|
||||||
|
|
||||||
|
### post_merge.py
|
||||||
|
|
||||||
|
Updates Vikunja task after a merge to master. Extracts task ID from commit message, marks task as done, posts a comment with the merge SHA.
|
||||||
|
|
||||||
|
### classify_changes.py
|
||||||
|
|
||||||
|
Classifies git changes as user-facing or workflow-only. Used to skip releases for infrastructure-only changes. Patterns are configurable.
|
||||||
|
|
||||||
|
### discover_runners.py
|
||||||
|
|
||||||
|
Discovers available Gitea Actions runners at repo, org, and instance levels. Generates a dynamic matrix for parallel job distribution.
|
||||||
|
|
||||||
|
### detect_release_commit.py
|
||||||
|
|
||||||
|
Detects whether the latest git commit is a release commit. Writes `is-release=true` or `is-release=false` to GitHub output.
|
||||||
|
|
||||||
|
### push_badges.py
|
||||||
|
|
||||||
|
Generates SVG badge files from project metrics (tests, coverage, quality, version). Pushes to `badges` branch and updates README with cache-busting commit SHA URLs.
|
||||||
|
|
||||||
|
### distribute_molecule.py
|
||||||
|
|
||||||
|
Distributes molecule (scenario, platform) pairs across N parallel runners. Discovers scenarios under `ansible/roles/*/molecule/`.
|
||||||
|
|
||||||
|
### molecule_ci_guard.py
|
||||||
|
|
||||||
|
Runs molecule tests sequentially while polling Gitea for other runner failures. Aborts if another runner fails the same job.
|
||||||
|
|
||||||
|
### validate_commit_msg.py
|
||||||
|
|
||||||
|
Validates commit messages. On feature branches: conventional commits only (no `DEVX-N` prefix). On master: must have `DEVX-N` prefix from auto-merge.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# CLI Commands
|
||||||
|
|
||||||
|
devx provides a CLI with three command groups: `ci`, `tools`, and `molecule`.
|
||||||
|
|
||||||
|
## CI Commands
|
||||||
|
|
||||||
|
### `devx ci auto-merge`
|
||||||
|
|
||||||
|
Auto-merge a PR when all CI checks pass. Validates PR title, checks Vikunja task, squash-merges.
|
||||||
|
|
||||||
|
### `devx ci check-translations`
|
||||||
|
|
||||||
|
Check translation files for gaps, dead keys, and missing languages.
|
||||||
|
|
||||||
|
### `devx ci classify-changes`
|
||||||
|
|
||||||
|
Classify git changes as user-facing or workflow-only. Used to skip releases for infrastructure-only changes.
|
||||||
|
|
||||||
|
### `devx ci detect-release-commit`
|
||||||
|
|
||||||
|
Detect whether the latest git commit is a release commit (`release: vX.Y.Z [skip ci]`).
|
||||||
|
|
||||||
|
### `devx ci discover-runners`
|
||||||
|
|
||||||
|
Discover available Gitea Actions runners for dynamic job distribution.
|
||||||
|
|
||||||
|
### `devx ci doc-coverage`
|
||||||
|
|
||||||
|
Check documentation coverage for CLI commands and major modules.
|
||||||
|
|
||||||
|
### `devx ci notify-failure`
|
||||||
|
|
||||||
|
Create a Gitea issue when a CI workflow fails.
|
||||||
|
|
||||||
|
### `devx ci post-merge`
|
||||||
|
|
||||||
|
Update Vikunja task after a merge to master.
|
||||||
|
|
||||||
|
### `devx ci pr-review`
|
||||||
|
|
||||||
|
Run automated PR review: check architecture compliance, best practices, and quality.
|
||||||
|
|
||||||
|
### `devx ci publish`
|
||||||
|
|
||||||
|
Build package, publish to Gitea PyPI registry, and create Gitea release.
|
||||||
|
|
||||||
|
### `devx ci push-badges`
|
||||||
|
|
||||||
|
Generate badge SVG files and push them to the `badges` branch.
|
||||||
|
|
||||||
|
### `devx ci release`
|
||||||
|
|
||||||
|
Automated release: calculate next version, update files, tag, and push.
|
||||||
|
|
||||||
|
### `devx ci sync-wiki`
|
||||||
|
|
||||||
|
Sync documentation from `docs/` to the Gitea wiki.
|
||||||
|
|
||||||
|
### `devx ci validate-commit-msg`
|
||||||
|
|
||||||
|
Validate commit messages for conventional commit format.
|
||||||
|
|
||||||
|
## Tools Commands
|
||||||
|
|
||||||
|
### `devx tools check-test-speed`
|
||||||
|
|
||||||
|
Run unit tests and enforce a maximum execution-time budget.
|
||||||
|
|
||||||
|
### `devx tools configure-repo`
|
||||||
|
|
||||||
|
Configure repository: branch protection + labels via Gitea API.
|
||||||
|
|
||||||
|
### `devx tools generate-badges`
|
||||||
|
|
||||||
|
Generate self-contained SVG badge files from project metrics.
|
||||||
|
|
||||||
|
### `devx tools install-checkmake`
|
||||||
|
|
||||||
|
Install checkmake (Makefile linter) if not already present.
|
||||||
|
|
||||||
|
### `devx tools install-tools`
|
||||||
|
|
||||||
|
Install CI/CD development tools: actionlint, git-cliff, act_runner, tea.
|
||||||
|
|
||||||
|
### `devx tools setup`
|
||||||
|
|
||||||
|
Project setup: install Python deps and pre-commit hooks.
|
||||||
|
|
||||||
|
## Molecule Commands
|
||||||
|
|
||||||
|
### `devx molecule distribute`
|
||||||
|
|
||||||
|
Distribute molecule test pairs across parallel runners.
|
||||||
|
|
||||||
|
### `devx molecule discover-runners`
|
||||||
|
|
||||||
|
Discover available Gitea Actions runners for molecule tests.
|
||||||
|
|
||||||
|
### `devx molecule guard`
|
||||||
|
|
||||||
|
Run molecule tests sequentially with CI failure polling.
|
||||||
|
|
||||||
|
### `devx molecule all`
|
||||||
|
|
||||||
|
Run all molecule scenarios on all supported OS platforms.
|
||||||
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
# Aligned with CI timeout (ci.yml uses --max-seconds 10).
|
# Aligned with CI timeout (ci.yml uses --max-seconds 10).
|
||||||
set -e
|
set -e
|
||||||
export PYTHONPATH=src
|
export PYTHONPATH=src
|
||||||
python -m devx.tools.check_test_speed --max-seconds 10
|
python3 -m devx.tools.check_test_speed --max-seconds 10
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ from devx.i18n import _
|
|||||||
TASKID_FILE = ".taskid"
|
TASKID_FILE = ".taskid"
|
||||||
PR_TITLE_RE = re.compile(r"^DEVX-\d+:\s+.+")
|
PR_TITLE_RE = re.compile(r"^DEVX-\d+:\s+.+")
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(args: list[str], check: bool = True) -> subprocess.CompletedProcess[str]:
|
def run_cmd(args: list[str], check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from devx.config import GITEA_API_URL
|
|||||||
from devx.gitea_cli import TeaCLI, TeaCLIError
|
from devx.gitea_cli import TeaCLI, TeaCLIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
def _create_issue_via_tea(repo: str, title: str, body: str) -> int:
|
def _create_issue_via_tea(repo: str, title: str, body: str) -> int:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from devx.config import DEFAULT_PER_PAGE, TASK_ID_RE, VIKUNJA_API_URL, VIKUNJA_P
|
|||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
def _get_git_commit_message() -> str:
|
def _get_git_commit_message() -> str:
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from devx.config import GITEA_API_URL
|
|||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv()
|
||||||
|
|
||||||
# Files that are exempt from certain checks
|
# Files that are exempt from certain checks
|
||||||
WORKFLOW_ONLY_SUFFIXES = (".yml", ".yaml", ".md", ".json", ".toml", ".cfg", ".ini", ".txt")
|
WORKFLOW_ONLY_SUFFIXES = (".yml", ".yaml", ".md", ".json", ".toml", ".cfg", ".ini", ".txt")
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from devx.config import GITEA_API_URL
|
|||||||
from devx.gitea_cli import TeaCLI, TeaCLIError
|
from devx.gitea_cli import TeaCLI, TeaCLIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv()
|
||||||
|
|
||||||
CLIFF_CONFIG = "cliff.toml"
|
CLIFF_CONFIG = "cliff.toml"
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnk
|
|||||||
from devx.ci.classify_changes import has_user_facing_changes # cross-CI import, needs PYTHONPATH=.
|
from devx.ci.classify_changes import has_user_facing_changes # cross-CI import, needs PYTHONPATH=.
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv()
|
||||||
|
|
||||||
INIT_FILE = os.getenv("DEVX_VERSION_FILE", "src/devx/__init__.py")
|
INIT_FILE = os.getenv("DEVX_VERSION_FILE", "src/devx/__init__.py")
|
||||||
CHANGELOG_FILE = "CHANGELOG.md"
|
CHANGELOG_FILE = "CHANGELOG.md"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from devx.config import GITEA_API_URL
|
|||||||
from devx.exceptions import APIError
|
from devx.exceptions import APIError
|
||||||
from devx.i18n import _
|
from devx.i18n import _
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv()
|
||||||
|
|
||||||
DOCS_DIR = Path(__file__).resolve().parent.parent.parent.parent / "docs"
|
DOCS_DIR = Path(__file__).resolve().parent.parent.parent.parent / "docs"
|
||||||
MAPPING_FILE = DOCS_DIR / "mapping.json"
|
MAPPING_FILE = DOCS_DIR / "mapping.json"
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ def install_git_cliff() -> bool:
|
|||||||
if _is_installed("git-cliff"):
|
if _is_installed("git-cliff"):
|
||||||
click.echo("git-cliff: already installed")
|
click.echo("git-cliff: already installed")
|
||||||
return True
|
return True
|
||||||
arch = _arch()
|
# git-cliff uses x86_64/arm64 in release asset names (not amd64)
|
||||||
|
machine = platform.machine().lower()
|
||||||
|
arch = "x86_64" if machine in {"x86_64", "amd64"} else "arm64"
|
||||||
url = (
|
url = (
|
||||||
f"https://github.com/orhun/git-cliff/releases/download/"
|
f"https://github.com/orhun/git-cliff/releases/download/"
|
||||||
f"v{GIT_CLIFF_VERSION}/git-cliff-{GIT_CLIFF_VERSION}-{arch}-unknown-linux-gnu.tar.gz"
|
f"v{GIT_CLIFF_VERSION}/git-cliff-{GIT_CLIFF_VERSION}-{arch}-unknown-linux-gnu.tar.gz"
|
||||||
|
|||||||
+61
-6
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Project setup: install Python deps and pre-commit hooks.
|
"""Project setup: install Python deps, pre-commit hooks, and tea CLI login.
|
||||||
|
|
||||||
Usage::
|
Usage::
|
||||||
|
|
||||||
@@ -8,10 +8,15 @@ Usage::
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess # nosec B404
|
import subprocess # nosec B404
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from dotenv import load_dotenv # pyright: ignore[reportMissingImports,reportUnknownVariableType]
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
def _run(cmd: list[str]) -> None:
|
def _run(cmd: list[str]) -> None:
|
||||||
@@ -33,6 +38,55 @@ def _install_pre_commit_hooks(bin_dir: str) -> None:
|
|||||||
_run([pre_commit, "install", "--hook-type", hook_type])
|
_run([pre_commit, "install", "--hook-type", hook_type])
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_tea_login() -> None:
|
||||||
|
"""Configure tea CLI login from .env if REPO_TOKEN is set.
|
||||||
|
|
||||||
|
Idempotent: if a login with the same name already exists, it is not re-added.
|
||||||
|
Skips if tea is not installed or REPO_TOKEN is not set.
|
||||||
|
"""
|
||||||
|
tea_bin = shutil.which("tea")
|
||||||
|
if tea_bin is None:
|
||||||
|
click.echo("tea: not installed — run 'make install-tools' to install it.")
|
||||||
|
return
|
||||||
|
|
||||||
|
token = os.environ.get("REPO_TOKEN", "")
|
||||||
|
if not token:
|
||||||
|
click.echo("tea: REPO_TOKEN not set — skipping login configuration.")
|
||||||
|
return
|
||||||
|
|
||||||
|
api_url = os.environ.get("DEVX_GITEA_API_URL", "https://git.oblachno.oblachno.fyi/api/v1")
|
||||||
|
gitea_url = api_url.replace("/api/v1", "")
|
||||||
|
login_name = "devx"
|
||||||
|
|
||||||
|
result = subprocess.run( # nosec B603
|
||||||
|
[tea_bin, "login", "list", "--output", "simple"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if result.returncode == 0 and login_name in result.stdout:
|
||||||
|
click.echo(f"tea: login '{login_name}' already configured.")
|
||||||
|
return
|
||||||
|
|
||||||
|
click.echo(f"tea: configuring login '{login_name}' for {gitea_url}...")
|
||||||
|
add_result = subprocess.run( # nosec B603
|
||||||
|
[tea_bin, "login", "add", "--name", login_name, "--url", gitea_url, "--token", token],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if add_result.returncode != 0:
|
||||||
|
click.echo(f"tea: login configuration failed: {add_result.stderr.strip()}", err=True)
|
||||||
|
else:
|
||||||
|
subprocess.run( # nosec B603
|
||||||
|
[tea_bin, "login", "default", login_name],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
click.echo(f"tea: login '{login_name}' configured and set as default.")
|
||||||
|
|
||||||
|
|
||||||
def _verify(bin_dir: str) -> None:
|
def _verify(bin_dir: str) -> None:
|
||||||
"""Print versions of installed tools for verification."""
|
"""Print versions of installed tools for verification."""
|
||||||
devx = str(Path(bin_dir) / "devx")
|
devx = str(Path(bin_dir) / "devx")
|
||||||
@@ -51,7 +105,7 @@ def _verify(bin_dir: str) -> None:
|
|||||||
@click.option(
|
@click.option(
|
||||||
"--extras",
|
"--extras",
|
||||||
default="dev",
|
default="dev",
|
||||||
help="Dependency group to install: ci, lint, build, twine, or dev (default: dev).",
|
help="Dependency group to install: ci, lint, or dev (default: dev).",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--no-pre-commit",
|
"--no-pre-commit",
|
||||||
@@ -63,7 +117,7 @@ def _verify(bin_dir: str) -> None:
|
|||||||
"--no-tea-login",
|
"--no-tea-login",
|
||||||
is_flag=True,
|
is_flag=True,
|
||||||
default=False,
|
default=False,
|
||||||
help="Skip tea CLI login configuration (no-op, kept for backwards compatibility).",
|
help="Skip tea CLI login configuration.",
|
||||||
)
|
)
|
||||||
def main(
|
def main(
|
||||||
bin_dir: str,
|
bin_dir: str,
|
||||||
@@ -71,7 +125,7 @@ def main(
|
|||||||
no_pre_commit: bool,
|
no_pre_commit: bool,
|
||||||
no_tea_login: bool,
|
no_tea_login: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Install Python deps and pre-commit hooks."""
|
"""Install Python deps, pre-commit hooks, and configure tea CLI."""
|
||||||
if not Path(bin_dir).exists():
|
if not Path(bin_dir).exists():
|
||||||
raise click.ClickException(f"Bin directory not found: {bin_dir}. Run 'python3 -m venv .venv' first.")
|
raise click.ClickException(f"Bin directory not found: {bin_dir}. Run 'python3 -m venv .venv' first.")
|
||||||
|
|
||||||
@@ -82,8 +136,9 @@ def main(
|
|||||||
click.echo("Installing pre-commit hooks...")
|
click.echo("Installing pre-commit hooks...")
|
||||||
_install_pre_commit_hooks(bin_dir)
|
_install_pre_commit_hooks(bin_dir)
|
||||||
|
|
||||||
# --no-tea-login is a no-op (kept for backwards compatibility)
|
if not no_tea_login:
|
||||||
_ = no_tea_login
|
click.echo("Configuring tea CLI login...")
|
||||||
|
_configure_tea_login()
|
||||||
|
|
||||||
click.echo("")
|
click.echo("")
|
||||||
click.echo("Setup complete.")
|
click.echo("Setup complete.")
|
||||||
|
|||||||
@@ -110,6 +110,13 @@
|
|||||||
" FAIL: {title} — content mismatch or empty!": {
|
" FAIL: {title} — content mismatch or empty!": {
|
||||||
"en": " FAIL: {title} — content mismatch or empty!"
|
"en": " FAIL: {title} — content mismatch or empty!"
|
||||||
},
|
},
|
||||||
|
" MISSING: devx {cmd}": {
|
||||||
|
"en": " MISSING: devx {cmd}",
|
||||||
|
"bg": " ЛИПСВА: devx {cmd}",
|
||||||
|
"de": " FEHLT: devx {cmd}",
|
||||||
|
"ru": " ОТСУТСТВУЕТ: devx {cmd}",
|
||||||
|
"zh": " 缺失: devx {cmd}"
|
||||||
|
},
|
||||||
" MISSING: grm {cmd}": {
|
" MISSING: grm {cmd}": {
|
||||||
"en": " MISSING: grm {cmd}"
|
"en": " MISSING: grm {cmd}"
|
||||||
},
|
},
|
||||||
@@ -119,6 +126,13 @@
|
|||||||
" MISSING: {script}": {
|
" MISSING: {script}": {
|
||||||
"en": " MISSING: {script}"
|
"en": " MISSING: {script}"
|
||||||
},
|
},
|
||||||
|
" OK: devx {cmd}": {
|
||||||
|
"en": " OK: devx {cmd}",
|
||||||
|
"bg": " ОК: devx {cmd}",
|
||||||
|
"de": " OK: devx {cmd}",
|
||||||
|
"ru": " ОК: devx {cmd}",
|
||||||
|
"zh": " 正常: devx {cmd}"
|
||||||
|
},
|
||||||
" OK: grm {cmd}": {
|
" OK: grm {cmd}": {
|
||||||
"en": " OK: grm {cmd}"
|
"en": " OK: grm {cmd}"
|
||||||
},
|
},
|
||||||
@@ -204,6 +218,13 @@
|
|||||||
"ru": "ОШИБКА: VIKUNJA_TOKEN не задан.",
|
"ru": "ОШИБКА: VIKUNJA_TOKEN не задан.",
|
||||||
"zh": "错误:未设置 VIKUNJA_TOKEN。"
|
"zh": "错误:未设置 VIKUNJA_TOKEN。"
|
||||||
},
|
},
|
||||||
|
"ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.": {
|
||||||
|
"en": "ERROR: Repository name not specified. Use --repo or set DEVX_REPO_NAME.",
|
||||||
|
"bg": "ГРЕШКА: Името на хранилището не е указано. Използвайте --repo или задайте DEVX_REPO_NAME.",
|
||||||
|
"de": "FEHLER: Repository-Name nicht angegeben. Verwenden Sie --repo oder setzen Sie DEVX_REPO_NAME.",
|
||||||
|
"ru": "ОШИБКА: Имя репозитория не указано. Используйте --repo или задайте DEVX_REPO_NAME.",
|
||||||
|
"zh": "错误:未指定仓库名称。请使用 --repo 或设置 DEVX_REPO_NAME。"
|
||||||
|
},
|
||||||
"ERROR: mapping.json not found at {path}": {
|
"ERROR: mapping.json not found at {path}": {
|
||||||
"en": "ERROR: mapping.json not found at {path}"
|
"en": "ERROR: mapping.json not found at {path}"
|
||||||
},
|
},
|
||||||
@@ -239,6 +260,13 @@
|
|||||||
"Head branch is behind master. Pulling and rebasing...": {
|
"Head branch is behind master. Pulling and rebasing...": {
|
||||||
"en": "Head branch is behind master. Pulling and rebasing..."
|
"en": "Head branch is behind master. Pulling and rebasing..."
|
||||||
},
|
},
|
||||||
|
"Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}": {
|
||||||
|
"en": "Infrastructure commit (no DEVX-N task ID), skipping Vikunja update: {msg}",
|
||||||
|
"bg": "Инфраструктурен commit (без идентификатор на задача DEVX-N), пропускаме обновяването на Vikunja: {msg}",
|
||||||
|
"de": "Infrastruktur-Commit (keine DEVX-N Task-ID), Vikunja-Update wird übersprungen: {msg}",
|
||||||
|
"ru": "Инфраструктурный коммит (без ID задачи DEVX-N), пропуск обновления Vikunja: {msg}",
|
||||||
|
"zh": "基础设施提交(无 DEVX-N 任务 ID),跳过 Vikunja 更新: {msg}"
|
||||||
|
},
|
||||||
"Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}": {
|
"Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}": {
|
||||||
"en": "Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}"
|
"en": "Infrastructure commit (no GRM-N task ID), skipping Vikunja update: {msg}"
|
||||||
},
|
},
|
||||||
@@ -258,6 +286,13 @@
|
|||||||
"ru": "Слияние не удалось: HTTP {status}: {message}\nПроверьте, что PR готов и у вас есть права на слияние.",
|
"ru": "Слияние не удалось: HTTP {status}: {message}\nПроверьте, что PR готов и у вас есть права на слияние.",
|
||||||
"zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。"
|
"zh": "合并失败: HTTP {status}: {message}\n请检查 PR 是否准备就绪且您具有合并权限。"
|
||||||
},
|
},
|
||||||
|
"Module {mod} has no main() function": {
|
||||||
|
"en": "Module {mod} has no main() function",
|
||||||
|
"bg": "Модул {mod} няма функция main()",
|
||||||
|
"de": "Modul {mod} hat keine main()-Funktion",
|
||||||
|
"ru": "Модуль {mod} не имеет функции main()",
|
||||||
|
"zh": "模块 {mod} 没有 main() 函数"
|
||||||
|
},
|
||||||
"Molecule directory not found: {path}": {
|
"Molecule directory not found: {path}": {
|
||||||
"en": "Molecule directory not found: {path}",
|
"en": "Molecule directory not found: {path}",
|
||||||
"bg": "Директорията на molecule не е намерена: {path}",
|
"bg": "Директорията на molecule не е намерена: {path}",
|
||||||
@@ -314,6 +349,13 @@
|
|||||||
"ru": "Ой! Сообщение коммита должно соответствовать формату conventional commit.\n Ожидается: <type>: <description>\n Получено: {subject}\n Допустимые типы: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE",
|
"ru": "Ой! Сообщение коммита должно соответствовать формату conventional commit.\n Ожидается: <type>: <description>\n Получено: {subject}\n Допустимые типы: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE",
|
||||||
"zh": "哎呀!提交消息必须遵循 conventional commit 格式。\n 预期格式: <type>: <description>\n 实际: {subject}\n 允许的类型: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE"
|
"zh": "哎呀!提交消息必须遵循 conventional commit 格式。\n 预期格式: <type>: <description>\n 实际: {subject}\n 允许的类型: feat, fix, chore, docs, style, refactor,\n perf, test, ci, build, revert, BREAKING CHANGE"
|
||||||
},
|
},
|
||||||
|
"Oops! Do not include task ID (DEVX-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
|
||||||
|
"en": "Oops! Do not include task ID (DEVX-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
||||||
|
"bg": "Опа! Не включвайте идентификатор на задача (DEVX-N) в commit-и от feature клонове.\n Идентификаторът ще бъде добавен автоматично при сливане чрез CI.",
|
||||||
|
"de": "Ups! Keine Task-ID (DEVX-N) in Feature-Branch-Commits einfügen.\n Die Task-ID wird beim Merge automatisch über CI hinzugefügt.",
|
||||||
|
"ru": "Ой! Не включайте ID задачи (DEVX-N) в коммиты feature-веток.\n ID задачи будет добавлен автоматически при слиянии через CI.",
|
||||||
|
"zh": "哎呀!不要在 feature 分支的提交中包含任务 ID (DEVX-N)。\n 任务 ID 将在通过 CI 合并时自动添加。"
|
||||||
|
},
|
||||||
"Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
|
"Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.": {
|
||||||
"en": "Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
"en": "Oops! Do not include task ID (GRM-N) in feature branch commits.\n The task ID will be added automatically on merge via CI.",
|
||||||
"bg": "Опа! Не включвайте идентификатор на задача (GRM-N) в commit-и от feature клонове.\n Идентификаторът ще бъде добавен автоматично при сливане чрез CI.",
|
"bg": "Опа! Не включвайте идентификатор на задача (GRM-N) в commit-и от feature клонове.\n Идентификаторът ще бъде добавен автоматично при сливане чрез CI.",
|
||||||
@@ -321,6 +363,20 @@
|
|||||||
"ru": "Ой! Не включайте ID задачи (GRM-N) в коммиты feature-веток.\n ID задачи будет добавлен автоматически при слиянии через CI.",
|
"ru": "Ой! Не включайте ID задачи (GRM-N) в коммиты feature-веток.\n ID задачи будет добавлен автоматически при слиянии через CI.",
|
||||||
"zh": "哎呀!不要在 feature 分支的提交中包含任务 ID (GRM-N)。\n 任务 ID 将在通过 CI 合并时自动添加。"
|
"zh": "哎呀!不要在 feature 分支的提交中包含任务 ID (GRM-N)。\n 任务 ID 将在通过 CI 合并时自动添加。"
|
||||||
},
|
},
|
||||||
|
"Oops! Gitea PyPI registry publish failed:\n{stderr}": {
|
||||||
|
"en": "Oops! Gitea PyPI registry publish failed:\n{stderr}",
|
||||||
|
"bg": "Опа! Публикуването в Gitea PyPI registry неуспешно:\n{stderr}",
|
||||||
|
"de": "Ups! Veröffentlichung in der Gitea PyPI-Registry fehlgeschlagen:\n{stderr}",
|
||||||
|
"ru": "Ой! Публикация в Gitea PyPI registry не удалась:\n{stderr}",
|
||||||
|
"zh": "哎呀!Gitea PyPI registry 发布失败:\n{stderr}"
|
||||||
|
},
|
||||||
|
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: DEVX-N: <type>: <description>\n Got: {subject}": {
|
||||||
|
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: DEVX-N: <type>: <description>\n Got: {subject}",
|
||||||
|
"bg": "Опа! Commit-ът в клона master трябва да следва конвенционален формат след идентификатора.\n Очаква се: DEVX-N: <type>: <description>\n Получено: {subject}",
|
||||||
|
"de": "Ups! Master-Branch-Commit muss nach der Task-ID dem konventionellen Format folgen.\n Erwartet: DEVX-N: <type>: <description>\n Erhalten: {subject}",
|
||||||
|
"ru": "Ой! Коммит в ветку master после ID задачи должен соответствовать conventional формату.\n Ожидается: DEVX-N: <type>: <description>\n Получено: {subject}",
|
||||||
|
"zh": "哎呀!master 分支提交在任务 ID 后必须遵循 conventional commit 格式。\n 预期格式: DEVX-N: <type>: <description>\n 实际: {subject}"
|
||||||
|
},
|
||||||
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}": {
|
"Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}": {
|
||||||
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}",
|
"en": "Oops! Master branch commit must follow conventional format after task ID.\n Expected: GRM-N: <type>: <description>\n Got: {subject}",
|
||||||
"bg": "Опа! Commit-ът в клона master трябва да следва конвенционален формат след идентификатора.\n Очаква се: GRM-N: <type>: <description>\n Получено: {subject}",
|
"bg": "Опа! Commit-ът в клона master трябва да следва конвенционален формат след идентификатора.\n Очаква се: GRM-N: <type>: <description>\n Получено: {subject}",
|
||||||
@@ -328,6 +384,13 @@
|
|||||||
"ru": "Ой! Коммит в ветку master после ID задачи должен соответствовать conventional формату.\n Ожидается: GRM-N: <type>: <description>\n Получено: {subject}",
|
"ru": "Ой! Коммит в ветку master после ID задачи должен соответствовать conventional формату.\n Ожидается: GRM-N: <type>: <description>\n Получено: {subject}",
|
||||||
"zh": "哎呀!master 分支提交在任务 ID 后必须遵循 conventional commit 格式。\n 预期格式: GRM-N: <type>: <description>\n 实际: {subject}"
|
"zh": "哎呀!master 分支提交在任务 ID 后必须遵循 conventional commit 格式。\n 预期格式: GRM-N: <type>: <description>\n 实际: {subject}"
|
||||||
},
|
},
|
||||||
|
"Oops! Master branch commits must start with a task ID.\n Expected: DEVX-N: <conventional commit message>\n Got: {subject}": {
|
||||||
|
"en": "Oops! Master branch commits must start with a task ID.\n Expected: DEVX-N: <conventional commit message>\n Got: {subject}",
|
||||||
|
"bg": "Опа! Commit-ите в клона master трябва да започват с идентификатор на задача.\n Очаква се: DEVX-N: <conventional commit message>\n Получено: {subject}",
|
||||||
|
"de": "Ups! Master-Branch-Commits müssen mit einer Task-ID beginnen.\n Erwartet: DEVX-N: <conventional commit message>\n Erhalten: {subject}",
|
||||||
|
"ru": "Ой! Коммиты в ветку master должны начинаться с ID задачи.\n Ожидается: DEVX-N: <conventional commit message>\n Получено: {subject}",
|
||||||
|
"zh": "哎呀!master 分支的提交必须以任务 ID 开头。\n 预期格式: DEVX-N: <conventional commit message>\n 实际: {subject}"
|
||||||
|
},
|
||||||
"Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}": {
|
"Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}": {
|
||||||
"en": "Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}",
|
"en": "Oops! Master branch commits must start with a task ID.\n Expected: GRM-N: <conventional commit message>\n Got: {subject}",
|
||||||
"bg": "Опа! Commit-ите в клона master трябва да започват с идентификатор на задача.\n Очаква се: GRM-N: <conventional commit message>\n Получено: {subject}",
|
"bg": "Опа! Commit-ите в клона master трябва да започват с идентификатор на задача.\n Очаква се: GRM-N: <conventional commit message>\n Получено: {subject}",
|
||||||
@@ -338,6 +401,13 @@
|
|||||||
"Oops! No task ID found in .taskid file or branch name '{branch}'.": {
|
"Oops! No task ID found in .taskid file or branch name '{branch}'.": {
|
||||||
"en": "Oops! No task ID found in .taskid file or branch name '{branch}'."
|
"en": "Oops! No task ID found in .taskid file or branch name '{branch}'."
|
||||||
},
|
},
|
||||||
|
"Oops! PR title must follow format 'DEVX-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": {
|
||||||
|
"en": "Oops! PR title must follow format 'DEVX-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}",
|
||||||
|
"bg": "Опа! Заглавието на PR трябва да следва формата 'DEVX-N: <заглавие на задача>'.\n Очаква се: {task_id}: <заглавие на задача>\n Получено: {pr_title}",
|
||||||
|
"de": "Ups! PR-Titel muss dem Format 'DEVX-N: <Task-Titel>' folgen.\n Erwartet: {task_id}: <Task-Titel>\n Erhalten: {pr_title}",
|
||||||
|
"ru": "Ой! Заголовок PR должен соответствовать формату 'DEVX-N: <название задачи>'.\n Ожидается: {task_id}: <название задачи>\n Получено: {pr_title}",
|
||||||
|
"zh": "哎呀!PR 标题必须遵循格式 'DEVX-N: <任务标题>'。\n 预期格式: {task_id}: <任务标题>\n 实际: {pr_title}"
|
||||||
|
},
|
||||||
"Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": {
|
"Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}": {
|
||||||
"en": "Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}"
|
"en": "Oops! PR title must follow format 'GRM-N: <task title>'.\n Expected: {task_id}: <task title>\n Got: {pr_title}"
|
||||||
},
|
},
|
||||||
@@ -364,6 +434,13 @@
|
|||||||
"PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}": {
|
"PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}": {
|
||||||
"en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}"
|
"en": "PR title does not match Vikunja task title.\n Expected: {expected}\n Got: {pr_title}"
|
||||||
},
|
},
|
||||||
|
"PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.": {
|
||||||
|
"en": "PYPI_TOKEN not set and no registry URL configured — skipping PyPI publish. No worries, we'll just create the Gitea release.",
|
||||||
|
"bg": "PYPI_TOKEN не е зададен и няма конфигуриран URL на registry — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.",
|
||||||
|
"de": "PYPI_TOKEN nicht gesetzt und keine Registry-URL konfiguriert — PyPI-Veröffentlichung wird übersprungen. Keine Sorge, wir erstellen einfach das Gitea-Release.",
|
||||||
|
"ru": "PYPI_TOKEN не задан и URL registry не настроен — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.",
|
||||||
|
"zh": "未设置 PYPI_TOKEN 且未配置 registry URL — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。"
|
||||||
|
},
|
||||||
"PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.": {
|
"PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.": {
|
||||||
"en": "PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.",
|
"en": "PYPI_TOKEN not set — skipping PyPI publish. No worries, we'll just create the Gitea release.",
|
||||||
"bg": "PYPI_TOKEN не е зададен — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.",
|
"bg": "PYPI_TOKEN не е зададен — пропускаме публикуването в PyPI. Без притеснения, просто ще създадем Gitea release.",
|
||||||
@@ -371,6 +448,13 @@
|
|||||||
"ru": "PYPI_TOKEN не задан — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.",
|
"ru": "PYPI_TOKEN не задан — пропускаем публикацию в PyPI. Не беспокойтесь, мы просто создадим Gitea release.",
|
||||||
"zh": "未设置 PYPI_TOKEN — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。"
|
"zh": "未设置 PYPI_TOKEN — 跳过 PyPI 发布。别担心,我们直接创建 Gitea release。"
|
||||||
},
|
},
|
||||||
|
"Published to Gitea PyPI registry.": {
|
||||||
|
"en": "Published to Gitea PyPI registry.",
|
||||||
|
"bg": "Публикувано в Gitea PyPI registry.",
|
||||||
|
"de": "In der Gitea PyPI-Registry veröffentlicht.",
|
||||||
|
"ru": "Опубликовано в Gitea PyPI registry.",
|
||||||
|
"zh": "已发布到 Gitea PyPI registry。"
|
||||||
|
},
|
||||||
"Published to PyPI.": {
|
"Published to PyPI.": {
|
||||||
"en": "Published to PyPI.",
|
"en": "Published to PyPI.",
|
||||||
"bg": "Публикувано в PyPI.",
|
"bg": "Публикувано в PyPI.",
|
||||||
@@ -455,6 +539,13 @@
|
|||||||
"WARNING: File {file} not found — skipping.": {
|
"WARNING: File {file} not found — skipping.": {
|
||||||
"en": "WARNING: File {file} not found — skipping."
|
"en": "WARNING: File {file} not found — skipping."
|
||||||
},
|
},
|
||||||
|
"Warning: No task ID (DEVX-N) found in commit message: {msg}. Skipping Vikunja update.": {
|
||||||
|
"en": "Warning: No task ID (DEVX-N) found in commit message: {msg}. Skipping Vikunja update.",
|
||||||
|
"bg": "Предупреждение: Не е намерен идентификатор на задача (DEVX-N) в съобщението за commit: {msg}. Пропускаме обновяването на Vikunja.",
|
||||||
|
"de": "Warnung: Keine Task-ID (DEVX-N) in Commit-Nachricht gefunden: {msg}. Vikunja-Update wird übersprungen.",
|
||||||
|
"ru": "Предупреждение: ID задачи (DEVX-N) не найден в сообщении коммита: {msg}. Пропуск обновления Vikunja.",
|
||||||
|
"zh": "警告:提交消息中未找到任务 ID (DEVX-N): {msg}。跳过 Vikunja 更新。"
|
||||||
|
},
|
||||||
"Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update.": {
|
"Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update.": {
|
||||||
"en": "Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update."
|
"en": "Warning: No task ID (GRM-N) found in commit message: {msg}. Skipping Vikunja update."
|
||||||
},
|
},
|
||||||
@@ -494,10 +585,59 @@
|
|||||||
"[dry-run] Would update {init}": {
|
"[dry-run] Would update {init}": {
|
||||||
"en": "[dry-run] Would update {init}"
|
"en": "[dry-run] Would update {init}"
|
||||||
},
|
},
|
||||||
|
"active": {
|
||||||
|
"en": "active",
|
||||||
|
"bg": "активен",
|
||||||
|
"de": "aktiv",
|
||||||
|
"ru": "активен",
|
||||||
|
"zh": "活跃"
|
||||||
|
},
|
||||||
|
"completed": {
|
||||||
|
"en": "completed",
|
||||||
|
"bg": "завършен",
|
||||||
|
"de": "abgeschlossen",
|
||||||
|
"ru": "завершён",
|
||||||
|
"zh": "已完成"
|
||||||
|
},
|
||||||
|
"failed": {
|
||||||
|
"en": "failed",
|
||||||
|
"bg": "неуспешен",
|
||||||
|
"de": "fehlgeschlagen",
|
||||||
|
"ru": "неудачный",
|
||||||
|
"zh": "失败"
|
||||||
|
},
|
||||||
"git command failed ({cmd}): {stderr}": {
|
"git command failed ({cmd}): {stderr}": {
|
||||||
"en": "git command failed ({cmd}): {stderr}"
|
"en": "git command failed ({cmd}): {stderr}"
|
||||||
},
|
},
|
||||||
"git-cliff returned empty version.": {
|
"git-cliff returned empty version.": {
|
||||||
"en": "git-cliff returned empty version."
|
"en": "git-cliff returned empty version."
|
||||||
|
},
|
||||||
|
"inactive": {
|
||||||
|
"en": "inactive",
|
||||||
|
"bg": "неактивен",
|
||||||
|
"de": "inaktiv",
|
||||||
|
"ru": "неактивен",
|
||||||
|
"zh": "未激活"
|
||||||
|
},
|
||||||
|
"in_progress": {
|
||||||
|
"en": "in progress",
|
||||||
|
"bg": "в процес",
|
||||||
|
"de": "in Bearbeitung",
|
||||||
|
"ru": "в процессе",
|
||||||
|
"zh": "进行中"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"en": "pending",
|
||||||
|
"bg": "в очакване",
|
||||||
|
"de": "ausstehend",
|
||||||
|
"ru": "ожидает",
|
||||||
|
"zh": "待处理"
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
"en": "unknown",
|
||||||
|
"bg": "неизвестен",
|
||||||
|
"de": "unbekannt",
|
||||||
|
"ru": "неизвестно",
|
||||||
|
"zh": "未知"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import pytest
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
from devx.tools.setup import (
|
from devx.tools.setup import (
|
||||||
|
_configure_tea_login,
|
||||||
_install_pre_commit_hooks,
|
_install_pre_commit_hooks,
|
||||||
_install_python_deps,
|
_install_python_deps,
|
||||||
_run,
|
_run,
|
||||||
@@ -57,6 +58,67 @@ class TestInstallPreCommitHooks:
|
|||||||
assert "pre-push" in hook_types
|
assert "pre-push" in hook_types
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfigureTeaLogin:
|
||||||
|
@patch("devx.tools.setup.shutil.which", return_value=None)
|
||||||
|
def test_tea_not_installed(self, mock_which: MagicMock) -> None:
|
||||||
|
_configure_tea_login()
|
||||||
|
mock_which.assert_called_once_with("tea")
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
||||||
|
@patch.dict("os.environ", {}, clear=True)
|
||||||
|
def test_no_token(self, mock_which: MagicMock) -> None:
|
||||||
|
_configure_tea_login()
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
||||||
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok123"}, clear=True)
|
||||||
|
def test_login_already_exists(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
||||||
|
mock_run.return_value = MagicMock(returncode=0, stdout="devx\ngrm\n", stderr="")
|
||||||
|
_configure_tea_login()
|
||||||
|
# Should only call login list, not login add
|
||||||
|
assert mock_run.call_count == 1
|
||||||
|
assert "list" in mock_run.call_args[0][0]
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
||||||
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok123"}, clear=True)
|
||||||
|
def test_login_add_success(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
||||||
|
list_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||||
|
add_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||||
|
default_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||||
|
mock_run.side_effect = [list_result, add_result, default_result]
|
||||||
|
_configure_tea_login()
|
||||||
|
assert mock_run.call_count == 3
|
||||||
|
assert "add" in mock_run.call_args_list[1][0][0]
|
||||||
|
assert "default" in mock_run.call_args_list[2][0][0]
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
||||||
|
@patch.dict("os.environ", {"REPO_TOKEN": "tok123"}, clear=True)
|
||||||
|
def test_login_add_failure(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
||||||
|
list_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||||
|
add_result = MagicMock(returncode=1, stdout="", stderr="auth failed")
|
||||||
|
mock_run.side_effect = [list_result, add_result]
|
||||||
|
_configure_tea_login()
|
||||||
|
assert mock_run.call_count == 2
|
||||||
|
|
||||||
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
||||||
|
@patch.dict(
|
||||||
|
"os.environ",
|
||||||
|
{"REPO_TOKEN": "tok123", "DEVX_GITEA_API_URL": "https://custom.example.com/api/v1"},
|
||||||
|
clear=True,
|
||||||
|
)
|
||||||
|
def test_custom_gitea_url(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
||||||
|
list_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||||
|
add_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||||
|
default_result = MagicMock(returncode=0, stdout="", stderr="")
|
||||||
|
mock_run.side_effect = [list_result, add_result, default_result]
|
||||||
|
_configure_tea_login()
|
||||||
|
add_call = mock_run.call_args_list[1][0][0]
|
||||||
|
assert "https://custom.example.com" in add_call
|
||||||
|
|
||||||
|
|
||||||
class TestVerify:
|
class TestVerify:
|
||||||
@patch("devx.tools.setup.subprocess.run")
|
@patch("devx.tools.setup.subprocess.run")
|
||||||
def test_verify_runs_tools(self, mock_run: MagicMock) -> None:
|
def test_verify_runs_tools(self, mock_run: MagicMock) -> None:
|
||||||
@@ -77,6 +139,7 @@ class TestVerify:
|
|||||||
|
|
||||||
|
|
||||||
class TestMain:
|
class TestMain:
|
||||||
|
@patch("devx.tools.setup._configure_tea_login")
|
||||||
@patch("devx.tools.setup._verify")
|
@patch("devx.tools.setup._verify")
|
||||||
@patch("devx.tools.setup._install_pre_commit_hooks")
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
||||||
@patch("devx.tools.setup._install_python_deps")
|
@patch("devx.tools.setup._install_python_deps")
|
||||||
@@ -85,6 +148,7 @@ class TestMain:
|
|||||||
mock_install_deps: MagicMock,
|
mock_install_deps: MagicMock,
|
||||||
mock_install_hooks: MagicMock,
|
mock_install_hooks: MagicMock,
|
||||||
mock_verify: MagicMock,
|
mock_verify: MagicMock,
|
||||||
|
mock_tea: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
bin_dir = tmp_path / "bin"
|
bin_dir = tmp_path / "bin"
|
||||||
@@ -95,7 +159,9 @@ class TestMain:
|
|||||||
mock_install_deps.assert_called_once()
|
mock_install_deps.assert_called_once()
|
||||||
mock_install_hooks.assert_called_once()
|
mock_install_hooks.assert_called_once()
|
||||||
mock_verify.assert_called_once()
|
mock_verify.assert_called_once()
|
||||||
|
mock_tea.assert_called_once()
|
||||||
|
|
||||||
|
@patch("devx.tools.setup._configure_tea_login")
|
||||||
@patch("devx.tools.setup._verify")
|
@patch("devx.tools.setup._verify")
|
||||||
@patch("devx.tools.setup._install_pre_commit_hooks")
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
||||||
@patch("devx.tools.setup._install_python_deps")
|
@patch("devx.tools.setup._install_python_deps")
|
||||||
@@ -104,6 +170,7 @@ class TestMain:
|
|||||||
mock_install_deps: MagicMock,
|
mock_install_deps: MagicMock,
|
||||||
mock_install_hooks: MagicMock,
|
mock_install_hooks: MagicMock,
|
||||||
mock_verify: MagicMock,
|
mock_verify: MagicMock,
|
||||||
|
mock_tea: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
bin_dir = tmp_path / "bin"
|
bin_dir = tmp_path / "bin"
|
||||||
@@ -114,6 +181,7 @@ class TestMain:
|
|||||||
mock_install_deps.assert_called_once()
|
mock_install_deps.assert_called_once()
|
||||||
mock_install_hooks.assert_not_called()
|
mock_install_hooks.assert_not_called()
|
||||||
|
|
||||||
|
@patch("devx.tools.setup._configure_tea_login")
|
||||||
@patch("devx.tools.setup._verify")
|
@patch("devx.tools.setup._verify")
|
||||||
@patch("devx.tools.setup._install_pre_commit_hooks")
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
||||||
@patch("devx.tools.setup._install_python_deps")
|
@patch("devx.tools.setup._install_python_deps")
|
||||||
@@ -122,6 +190,7 @@ class TestMain:
|
|||||||
mock_install_deps: MagicMock,
|
mock_install_deps: MagicMock,
|
||||||
mock_install_hooks: MagicMock,
|
mock_install_hooks: MagicMock,
|
||||||
mock_verify: MagicMock,
|
mock_verify: MagicMock,
|
||||||
|
mock_tea: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
bin_dir = tmp_path / "bin"
|
bin_dir = tmp_path / "bin"
|
||||||
@@ -131,22 +200,24 @@ class TestMain:
|
|||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
mock_install_deps.assert_called_once_with(str(bin_dir), "ci")
|
mock_install_deps.assert_called_once_with(str(bin_dir), "ci")
|
||||||
|
|
||||||
|
@patch("devx.tools.setup._configure_tea_login")
|
||||||
@patch("devx.tools.setup._verify")
|
@patch("devx.tools.setup._verify")
|
||||||
@patch("devx.tools.setup._install_pre_commit_hooks")
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
||||||
@patch("devx.tools.setup._install_python_deps")
|
@patch("devx.tools.setup._install_python_deps")
|
||||||
def test_main_no_tea_login_noop(
|
def test_main_no_tea_login(
|
||||||
self,
|
self,
|
||||||
mock_install_deps: MagicMock,
|
mock_install_deps: MagicMock,
|
||||||
mock_install_hooks: MagicMock,
|
mock_install_hooks: MagicMock,
|
||||||
mock_verify: MagicMock,
|
mock_verify: MagicMock,
|
||||||
|
mock_tea: MagicMock,
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""--no-tea-login is a no-op (kept for backwards compatibility)."""
|
|
||||||
bin_dir = tmp_path / "bin"
|
bin_dir = tmp_path / "bin"
|
||||||
bin_dir.mkdir()
|
bin_dir.mkdir()
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(main, ["--bin", str(bin_dir), "--no-tea-login"])
|
result = runner.invoke(main, ["--bin", str(bin_dir), "--no-tea-login"])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
|
mock_tea.assert_not_called()
|
||||||
|
|
||||||
def test_main_missing_bin_dir(self) -> None:
|
def test_main_missing_bin_dir(self) -> None:
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
@@ -162,6 +233,7 @@ def test_main_module_block(tmp_path: Path) -> None:
|
|||||||
with patch.dict("os.environ", {}, clear=True):
|
with patch.dict("os.environ", {}, clear=True):
|
||||||
with patch("devx.tools.setup._install_python_deps") as mock_deps:
|
with patch("devx.tools.setup._install_python_deps") as mock_deps:
|
||||||
with patch("devx.tools.setup._install_pre_commit_hooks"):
|
with patch("devx.tools.setup._install_pre_commit_hooks"):
|
||||||
|
with patch("devx.tools.setup._configure_tea_login"):
|
||||||
with patch("devx.tools.setup._verify"):
|
with patch("devx.tools.setup._verify"):
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(main, ["--bin", str(bin_dir)])
|
result = runner.invoke(main, ["--bin", str(bin_dir)])
|
||||||
|
|||||||
Reference in New Issue
Block a user