exist-shell 0.1.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.
- exist_shell-0.1.0/.gitguardian.yaml +5 -0
- exist_shell-0.1.0/.github/dependabot.yml +17 -0
- exist_shell-0.1.0/.github/workflows/docs.yml +30 -0
- exist_shell-0.1.0/.github/workflows/e2e.yml +104 -0
- exist_shell-0.1.0/.github/workflows/release.yml +34 -0
- exist_shell-0.1.0/.github/workflows/ruff.yml +47 -0
- exist_shell-0.1.0/.github/workflows/tests.yml +49 -0
- exist_shell-0.1.0/.github/workflows/ty.yml +47 -0
- exist_shell-0.1.0/.gitignore +10 -0
- exist_shell-0.1.0/CHANGELOG.md +45 -0
- exist_shell-0.1.0/CLAUDE.md +108 -0
- exist_shell-0.1.0/Makefile +15 -0
- exist_shell-0.1.0/PKG-INFO +10 -0
- exist_shell-0.1.0/README.md +227 -0
- exist_shell-0.1.0/docs/api.md +251 -0
- exist_shell-0.1.0/docs/commands.md +743 -0
- exist_shell-0.1.0/docs/completion.md +52 -0
- exist_shell-0.1.0/docs/configuration.md +118 -0
- exist_shell-0.1.0/docs/development.md +82 -0
- exist_shell-0.1.0/docs/index.md +68 -0
- exist_shell-0.1.0/docs/installation.md +55 -0
- exist_shell-0.1.0/docs/sync.md +152 -0
- exist_shell-0.1.0/mkdocs.yml +58 -0
- exist_shell-0.1.0/pyproject.toml +37 -0
- exist_shell-0.1.0/scripts/e2e/docker.sh +79 -0
- exist_shell-0.1.0/scripts/e2e/lib.sh +154 -0
- exist_shell-0.1.0/scripts/e2e/sections/T02_server.sh +168 -0
- exist_shell-0.1.0/scripts/e2e/sections/T03_collection.sh +184 -0
- exist_shell-0.1.0/scripts/e2e/sections/T04_ls.sh +32 -0
- exist_shell-0.1.0/scripts/e2e/sections/T05_put.sh +70 -0
- exist_shell-0.1.0/scripts/e2e/sections/T06_ls_after.sh +62 -0
- exist_shell-0.1.0/scripts/e2e/sections/T07_cat.sh +56 -0
- exist_shell-0.1.0/scripts/e2e/sections/T08_cp.sh +78 -0
- exist_shell-0.1.0/scripts/e2e/sections/T09_rm.sh +43 -0
- exist_shell-0.1.0/scripts/e2e/sections/T10_mkdir.sh +44 -0
- exist_shell-0.1.0/scripts/e2e/sections/T11_edit.sh +69 -0
- exist_shell-0.1.0/scripts/e2e/sections/T12_sync.sh +229 -0
- exist_shell-0.1.0/scripts/e2e/sections/T13_mv.sh +186 -0
- exist_shell-0.1.0/scripts/e2e/sections/T14_exec.sh +135 -0
- exist_shell-0.1.0/scripts/e2e/sections/T15_user.sh +125 -0
- exist_shell-0.1.0/scripts/e2e/sections/T16_group.sh +66 -0
- exist_shell-0.1.0/scripts/e2e/sections/T17_chown.sh +83 -0
- exist_shell-0.1.0/scripts/e2e/sections/T18_chmod.sh +88 -0
- exist_shell-0.1.0/scripts/e2e.sh +125 -0
- exist_shell-0.1.0/src/exist_shell/__init__.py +5 -0
- exist_shell-0.1.0/src/exist_shell/cache.py +186 -0
- exist_shell-0.1.0/src/exist_shell/client/__init__.py +19 -0
- exist_shell-0.1.0/src/exist_shell/client/_base.py +65 -0
- exist_shell-0.1.0/src/exist_shell/client/_collections.py +158 -0
- exist_shell-0.1.0/src/exist_shell/client/_documents.py +134 -0
- exist_shell-0.1.0/src/exist_shell/client/_groups.py +116 -0
- exist_shell-0.1.0/src/exist_shell/client/_permissions.py +172 -0
- exist_shell-0.1.0/src/exist_shell/client/_queries.py +37 -0
- exist_shell-0.1.0/src/exist_shell/client/_users.py +158 -0
- exist_shell-0.1.0/src/exist_shell/commands/__init__.py +1 -0
- exist_shell-0.1.0/src/exist_shell/commands/cat.py +51 -0
- exist_shell-0.1.0/src/exist_shell/commands/chmod.py +236 -0
- exist_shell-0.1.0/src/exist_shell/commands/chown.py +121 -0
- exist_shell-0.1.0/src/exist_shell/commands/collection.py +189 -0
- exist_shell-0.1.0/src/exist_shell/commands/cp.py +142 -0
- exist_shell-0.1.0/src/exist_shell/commands/edit.py +85 -0
- exist_shell-0.1.0/src/exist_shell/commands/exec.py +65 -0
- exist_shell-0.1.0/src/exist_shell/commands/group.py +183 -0
- exist_shell-0.1.0/src/exist_shell/commands/ls.py +66 -0
- exist_shell-0.1.0/src/exist_shell/commands/mkdir.py +24 -0
- exist_shell-0.1.0/src/exist_shell/commands/mv.py +124 -0
- exist_shell-0.1.0/src/exist_shell/commands/put.py +63 -0
- exist_shell-0.1.0/src/exist_shell/commands/rm.py +23 -0
- exist_shell-0.1.0/src/exist_shell/commands/server.py +114 -0
- exist_shell-0.1.0/src/exist_shell/commands/sync.py +767 -0
- exist_shell-0.1.0/src/exist_shell/commands/user.py +300 -0
- exist_shell-0.1.0/src/exist_shell/completions.py +221 -0
- exist_shell-0.1.0/src/exist_shell/config.py +233 -0
- exist_shell-0.1.0/src/exist_shell/exceptions.py +68 -0
- exist_shell-0.1.0/src/exist_shell/main.py +64 -0
- exist_shell-0.1.0/src/exist_shell/models.py +61 -0
- exist_shell-0.1.0/src/exist_shell/utils.py +179 -0
- exist_shell-0.1.0/src/exist_shell/xquery.py +267 -0
- exist_shell-0.1.0/tests/__init__.py +0 -0
- exist_shell-0.1.0/tests/conftest.py +56 -0
- exist_shell-0.1.0/tests/test_cache.py +181 -0
- exist_shell-0.1.0/tests/test_client.py +941 -0
- exist_shell-0.1.0/tests/test_commands_cat.py +81 -0
- exist_shell-0.1.0/tests/test_commands_chmod.py +321 -0
- exist_shell-0.1.0/tests/test_commands_chown.py +209 -0
- exist_shell-0.1.0/tests/test_commands_collection.py +266 -0
- exist_shell-0.1.0/tests/test_commands_cp.py +150 -0
- exist_shell-0.1.0/tests/test_commands_edit.py +252 -0
- exist_shell-0.1.0/tests/test_commands_exec.py +176 -0
- exist_shell-0.1.0/tests/test_commands_group.py +262 -0
- exist_shell-0.1.0/tests/test_commands_ls.py +146 -0
- exist_shell-0.1.0/tests/test_commands_mkdir.py +60 -0
- exist_shell-0.1.0/tests/test_commands_mv.py +241 -0
- exist_shell-0.1.0/tests/test_commands_put.py +144 -0
- exist_shell-0.1.0/tests/test_commands_rm.py +66 -0
- exist_shell-0.1.0/tests/test_commands_server.py +269 -0
- exist_shell-0.1.0/tests/test_commands_sync.py +636 -0
- exist_shell-0.1.0/tests/test_commands_user.py +512 -0
- exist_shell-0.1.0/tests/test_completions.py +462 -0
- exist_shell-0.1.0/tests/test_config.py +225 -0
- exist_shell-0.1.0/tests/test_utils.py +141 -0
- exist_shell-0.1.0/tests/test_xquery.py +249 -0
- exist_shell-0.1.0/uv.lock +638 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
open-pull-requests-limit: 5
|
|
8
|
+
cooldown:
|
|
9
|
+
default-days: 14
|
|
10
|
+
|
|
11
|
+
- package-ecosystem: "uv"
|
|
12
|
+
directory: "/"
|
|
13
|
+
schedule:
|
|
14
|
+
interval: "weekly"
|
|
15
|
+
open-pull-requests-limit: 5
|
|
16
|
+
cooldown:
|
|
17
|
+
default-days: 14
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- "mkdocs.yml"
|
|
9
|
+
- ".github/workflows/docs.yml"
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
|
|
26
|
+
- name: Install MkDocs
|
|
27
|
+
run: pip install mkdocs-material
|
|
28
|
+
|
|
29
|
+
- name: Deploy to GitHub Pages
|
|
30
|
+
run: mkdocs gh-deploy --force
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
name: e2e
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
matrix:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
outputs:
|
|
15
|
+
flags: ${{ steps.list.outputs.flags }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
18
|
+
- name: Build image matrix from --list-images
|
|
19
|
+
id: list
|
|
20
|
+
run: |
|
|
21
|
+
flags=$(bash scripts/e2e.sh --list-images \
|
|
22
|
+
| awk '/^\s+--/{print $1}' \
|
|
23
|
+
| jq -R . | jq -sc .)
|
|
24
|
+
echo "flags=${flags}" >> "$GITHUB_OUTPUT"
|
|
25
|
+
|
|
26
|
+
e2e:
|
|
27
|
+
needs: matrix
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
strategy:
|
|
30
|
+
matrix:
|
|
31
|
+
flag: ${{ fromJson(needs.matrix.outputs.flags) }}
|
|
32
|
+
fail-fast: false
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
35
|
+
- run: sudo apt-get install -y libxml2-utils
|
|
36
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
- run: uv sync
|
|
40
|
+
- name: Run e2e tests (${{ matrix.flag }})
|
|
41
|
+
id: run
|
|
42
|
+
continue-on-error: true
|
|
43
|
+
run: bash scripts/e2e.sh ${{ matrix.flag }} 2>&1 | tee e2e.log
|
|
44
|
+
- name: Save result artifact
|
|
45
|
+
if: always()
|
|
46
|
+
run: |
|
|
47
|
+
flag="${{ matrix.flag }}"
|
|
48
|
+
slug="${flag#--}"
|
|
49
|
+
image=$(grep "^Using image:" e2e.log | awk '{print $3}' || echo "$flag")
|
|
50
|
+
summary=$(grep -E "[0-9]+ passed, [0-9]+ failed" e2e.log | tail -1 | xargs || echo "no summary")
|
|
51
|
+
if grep -qF "FAILURES DETECTED" e2e.log 2>/dev/null; then
|
|
52
|
+
outcome="failure"
|
|
53
|
+
else
|
|
54
|
+
outcome="success"
|
|
55
|
+
fi
|
|
56
|
+
printf '%s\n%s\n%s\n' "${outcome}" "${image}" "${summary}" \
|
|
57
|
+
> "result-${slug}.txt"
|
|
58
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
59
|
+
if: always()
|
|
60
|
+
with:
|
|
61
|
+
name: e2e-result-${{ matrix.flag }}
|
|
62
|
+
path: result-*.txt
|
|
63
|
+
|
|
64
|
+
post-comment:
|
|
65
|
+
needs: [e2e]
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
if: always() && github.event_name == 'pull_request'
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
70
|
+
with:
|
|
71
|
+
pattern: e2e-result-*
|
|
72
|
+
merge-multiple: true
|
|
73
|
+
- name: Build PR comment
|
|
74
|
+
run: |
|
|
75
|
+
{
|
|
76
|
+
echo "## 🐋 e2e Tests"
|
|
77
|
+
echo ""
|
|
78
|
+
echo "| Image | Result | Tests |"
|
|
79
|
+
echo "|-------|--------|-------|"
|
|
80
|
+
for f in result-*.txt; do
|
|
81
|
+
[[ -f "$f" ]] || continue
|
|
82
|
+
mapfile -t lines < "$f"
|
|
83
|
+
status="${lines[0]}"
|
|
84
|
+
image="${lines[1]}"
|
|
85
|
+
summary="${lines[2]}"
|
|
86
|
+
[[ "$status" == "success" ]] && icon="✅" || icon="❌"
|
|
87
|
+
echo "| \`${image}\` | ${icon} | ${summary} |"
|
|
88
|
+
done
|
|
89
|
+
} > comment.md
|
|
90
|
+
cat comment.md
|
|
91
|
+
- uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v2
|
|
92
|
+
with:
|
|
93
|
+
header: e2e
|
|
94
|
+
path: comment.md
|
|
95
|
+
- name: Fail if any image had test failures
|
|
96
|
+
run: |
|
|
97
|
+
for f in result-*.txt; do
|
|
98
|
+
[[ -f "$f" ]] || continue
|
|
99
|
+
read -r status _ < "$f"
|
|
100
|
+
if [[ "$status" != "success" ]]; then
|
|
101
|
+
echo "::error::E2E failures detected — see the comment above for details."
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
done
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
if: github.actor == 'ambs'
|
|
16
|
+
environment: release
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
19
|
+
|
|
20
|
+
- name: Verify signed tag
|
|
21
|
+
run: |
|
|
22
|
+
gpg --import <<< "$GPG_PUBLIC_KEY"
|
|
23
|
+
git verify-tag "${{ github.ref_name }}"
|
|
24
|
+
env:
|
|
25
|
+
GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
|
|
26
|
+
|
|
27
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- run: uv build
|
|
32
|
+
|
|
33
|
+
- name: Publish to PyPI
|
|
34
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Ruff
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
ruff:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
16
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
- run: uv sync
|
|
20
|
+
- name: Run Ruff
|
|
21
|
+
id: ruff
|
|
22
|
+
continue-on-error: true
|
|
23
|
+
run: uv run ruff check src/ 2>&1 | tee ruff.log
|
|
24
|
+
- name: Build PR comment
|
|
25
|
+
if: github.event_name == 'pull_request'
|
|
26
|
+
run: |
|
|
27
|
+
status="${{ steps.ruff.outcome }}"
|
|
28
|
+
if [[ "$status" == "success" ]]; then
|
|
29
|
+
echo "## 🔍 Ruff — ✅ No issues" > comment.md
|
|
30
|
+
else
|
|
31
|
+
count=$(grep -cE "\.py:[0-9]" ruff.log 2>/dev/null || echo 0)
|
|
32
|
+
{
|
|
33
|
+
echo "## 🔍 Ruff — ❌ ${count} violation(s)"
|
|
34
|
+
echo ""
|
|
35
|
+
echo '```'
|
|
36
|
+
cat ruff.log
|
|
37
|
+
echo '```'
|
|
38
|
+
} > comment.md
|
|
39
|
+
fi
|
|
40
|
+
- uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v2
|
|
41
|
+
if: github.event_name == 'pull_request'
|
|
42
|
+
with:
|
|
43
|
+
header: ruff
|
|
44
|
+
path: comment.md
|
|
45
|
+
- name: Propagate failure
|
|
46
|
+
if: steps.ruff.outcome == 'failure'
|
|
47
|
+
run: exit 1
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
16
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
- run: uv sync
|
|
20
|
+
- name: Run tests
|
|
21
|
+
id: pytest
|
|
22
|
+
continue-on-error: true
|
|
23
|
+
run: uv run pytest --cov=exist_shell --cov-report=xml --cov-report=term 2>&1 | tee pytest.log
|
|
24
|
+
- name: Build PR comment
|
|
25
|
+
if: github.event_name == 'pull_request'
|
|
26
|
+
run: |
|
|
27
|
+
status="${{ steps.pytest.outcome }}"
|
|
28
|
+
[[ "$status" == "success" ]] && icon="✅ Passed" || icon="❌ Failed"
|
|
29
|
+
test_line=$(grep -oE "[0-9]+ (passed|failed|error(s)?)[^,=]*" pytest.log | tr '\n' ', ' | sed 's/, $//' || echo "—")
|
|
30
|
+
coverage=$(grep "^TOTAL" pytest.log | awk '{print $NF}' || echo "—")
|
|
31
|
+
{
|
|
32
|
+
echo "## 🧪 Tests — ${icon}"
|
|
33
|
+
echo ""
|
|
34
|
+
echo "**${test_line}** "
|
|
35
|
+
echo "**Coverage: ${coverage}**"
|
|
36
|
+
} > comment.md
|
|
37
|
+
- uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v2
|
|
38
|
+
if: github.event_name == 'pull_request'
|
|
39
|
+
with:
|
|
40
|
+
header: tests
|
|
41
|
+
path: comment.md
|
|
42
|
+
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
43
|
+
with:
|
|
44
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
45
|
+
files: coverage.xml
|
|
46
|
+
fail_ci_if_error: false
|
|
47
|
+
- name: Propagate failure
|
|
48
|
+
if: steps.pytest.outcome == 'failure'
|
|
49
|
+
run: exit 1
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: ty
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
typecheck:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
16
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
- run: uv sync
|
|
20
|
+
- name: Run ty
|
|
21
|
+
id: ty
|
|
22
|
+
continue-on-error: true
|
|
23
|
+
run: uv run ty check src/ 2>&1 | tee ty.log
|
|
24
|
+
- name: Build PR comment
|
|
25
|
+
if: github.event_name == 'pull_request'
|
|
26
|
+
run: |
|
|
27
|
+
status="${{ steps.ty.outcome }}"
|
|
28
|
+
if [[ "$status" == "success" ]]; then
|
|
29
|
+
echo "## 🔎 ty — ✅ No errors" > comment.md
|
|
30
|
+
else
|
|
31
|
+
count=$(grep -cE "^error\[" ty.log 2>/dev/null || echo 0)
|
|
32
|
+
{
|
|
33
|
+
echo "## 🔎 ty — ❌ ${count} error(s)"
|
|
34
|
+
echo ""
|
|
35
|
+
echo '```'
|
|
36
|
+
cat ty.log
|
|
37
|
+
echo '```'
|
|
38
|
+
} > comment.md
|
|
39
|
+
fi
|
|
40
|
+
- uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v2
|
|
41
|
+
if: github.event_name == 'pull_request'
|
|
42
|
+
with:
|
|
43
|
+
header: ty
|
|
44
|
+
path: comment.md
|
|
45
|
+
- name: Propagate failure
|
|
46
|
+
if: steps.ty.outcome == 'failure'
|
|
47
|
+
run: exit 1
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## unreleased
|
|
4
|
+
|
|
5
|
+
## 0.1.0 - 2026-06-23
|
|
6
|
+
|
|
7
|
+
### Commands
|
|
8
|
+
|
|
9
|
+
| Command | Description |
|
|
10
|
+
|---------|-------------|
|
|
11
|
+
| `server add / ls / rm` | Register, list, and remove eXist-db server configurations; `rm` cascade-removes associated collections |
|
|
12
|
+
| `server rename <old> <new>` | Rename a server nick; all registered collections are updated automatically |
|
|
13
|
+
| `collection add / new / ls / rm` | Manage named collection shortcuts; `new` creates the collection on the server and registers it in one step (idempotent: no-op if already exists) |
|
|
14
|
+
| `ls [path]` | List documents and sub-collections at a remote path; `--sort name\|time`, `--reverse`, `--names-only` |
|
|
15
|
+
| `cat <path>` | Print a remote document to stdout; `--raw` skips binary detection |
|
|
16
|
+
| `put <path>` | Upload a document from stdin or a local file; auto-detects MIME type |
|
|
17
|
+
| `cp <src> <dst>` | Copy documents local↔remote or remote↔remote |
|
|
18
|
+
| `rm <path...>` | Delete one or more remote documents |
|
|
19
|
+
| `mkdir <path>` | Create a remote collection (idempotent) |
|
|
20
|
+
| `edit <path>` | Download, open in `$VISUAL`/`$EDITOR`, re-upload if changed |
|
|
21
|
+
| `sync push/pull <dir> <path>` | Sync a local directory tree with a remote collection; supports `--delete`, `--dry-run`, `--force` |
|
|
22
|
+
| `mv <src> <dst>` | Move or rename a document or collection; trailing `/` on dest moves into that collection; cross-server moves not supported |
|
|
23
|
+
| `exec <nick>[:<path>]` | Read an XQuery script from a file or stdin, optionally preprocess it (version declaration, functx import), validate locally with BaseX or Saxon (auto-detected via PATH), then execute against eXist and print the result; supports `--no-fix`, `--no-validate`, `--validator`, `--list-validators` |
|
|
24
|
+
| `user ls` | List user accounts and their groups; accepts `@server` positional |
|
|
25
|
+
| `user add <user[@server]>` | Create a user account (prompts for password) |
|
|
26
|
+
| `user rm <user[@server]>` | Remove a user account |
|
|
27
|
+
| `user info <user[@server]>` | Show user account details |
|
|
28
|
+
| `user passwd <user[@server]>` | Change a user's password; `--stdin` for scripting |
|
|
29
|
+
| `group ls` | List groups and their members; accepts `@server` positional |
|
|
30
|
+
| `group add <group[@server]>` | Create a group |
|
|
31
|
+
| `group rm <group[@server]>` | Remove a group |
|
|
32
|
+
| `chown <spec> <path>` | Change owner and/or group of a document or collection; `--recursive / -R` for collections; spec forms: `owner`, `:group`, `owner:group` |
|
|
33
|
+
| `chmod <mode> <path>` | Change POSIX permissions of a document or collection; `--recursive / -R` for collections; accepts octal (`0755`) or symbolic (`u+x`, `go-w`, `a=rw`) modes |
|
|
34
|
+
|
|
35
|
+
### Infrastructure
|
|
36
|
+
|
|
37
|
+
- Shell completion for collection/document paths (with TTL cache)
|
|
38
|
+
- `<name>@<server>` syntax for ad-hoc server targeting in `collection add`, `collection new`, `user *`, and `group *`
|
|
39
|
+
- `--server` option on all commands completes registered server nicks
|
|
40
|
+
- Shell completion for `<name>@<server>` and `@<server>` argument forms
|
|
41
|
+
- Path traversal validation and URL encoding
|
|
42
|
+
- Google-style docstrings enforced via ruff
|
|
43
|
+
- CI: pytest + coverage (Codecov), ruff, ty, e2e test suite against live eXist-db in Docker
|
|
44
|
+
- CI: sticky PR comments for tests (counts + coverage), ruff, ty, and e2e workflows
|
|
45
|
+
- CI: e2e workflow with dynamic image matrix from `--list-images`; `fail-fast: false`
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project
|
|
6
|
+
|
|
7
|
+
`exsh` — a command-line tool to interact with an eXist-db server via its REST API. Subcommand structure (like git). Designed to work with shell pipes for scripting.
|
|
8
|
+
|
|
9
|
+
## Dev setup
|
|
10
|
+
|
|
11
|
+
Dependencies are managed with [uv](https://docs.astral.sh/uv/).
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv sync # install all deps including dev group
|
|
15
|
+
exsh --help
|
|
16
|
+
exsh --version
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
make checks # run ruff, ty and tests
|
|
23
|
+
make test # run tests
|
|
24
|
+
make ruff # lint with ruff
|
|
25
|
+
make ty # type-check with ty
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Branching and pull requests
|
|
29
|
+
|
|
30
|
+
All changes must go through a pull request — never commit directly to `main`.
|
|
31
|
+
|
|
32
|
+
1. Create a feature branch: `git checkout -b feature/<short-description>`
|
|
33
|
+
2. Make changes, commit with a signed commit (`git commit -S`)
|
|
34
|
+
3. Before opening a PR, always fetch and rebase onto the latest `main`:
|
|
35
|
+
```bash
|
|
36
|
+
git fetch origin main
|
|
37
|
+
git rebase origin/main
|
|
38
|
+
```
|
|
39
|
+
4. Push the branch and open a PR via `gh pr create`
|
|
40
|
+
5. Do not merge without the PR being reviewed and all CI checks passing
|
|
41
|
+
|
|
42
|
+
### Pull request descriptions
|
|
43
|
+
|
|
44
|
+
- Never include unticked checkboxes in a PR description.
|
|
45
|
+
- Run all relevant tests before opening the PR (or ask the user to run them if that is not possible).
|
|
46
|
+
- Only create the PR once everything passes — with all checklist items already ticked.
|
|
47
|
+
|
|
48
|
+
## File discipline
|
|
49
|
+
|
|
50
|
+
Never stage or commit files that were not explicitly created as part of the current task or explicitly requested by the user. This includes cache directories, build artifacts, lock files, and any other files that appear as a side effect of running tools.
|
|
51
|
+
|
|
52
|
+
## Linting
|
|
53
|
+
|
|
54
|
+
Never run `ruff --fix` across the whole project. Only apply it to a specific file, and only when a manual edit has already failed to resolve the issue.
|
|
55
|
+
|
|
56
|
+
## Docstrings
|
|
57
|
+
|
|
58
|
+
Every public module, class, and method must have a docstring. Use Google style:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
def my_method(self, path: str) -> list[str]:
|
|
62
|
+
"""Short one-line summary.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
path: The collection path under /db/.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List of item names found at the path.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
ExistNotFoundError: If the path does not exist.
|
|
72
|
+
ExistAuthError: If authentication fails.
|
|
73
|
+
"""
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `Args` section required whenever the method has parameters beyond `self`.
|
|
77
|
+
- `Returns` section required whenever the return type is not `None`.
|
|
78
|
+
- `Raises` section required whenever the method raises documented exceptions.
|
|
79
|
+
- Ruff enforces this via `select = ["D"]` with `convention = "google"`.
|
|
80
|
+
|
|
81
|
+
## Code conventions
|
|
82
|
+
|
|
83
|
+
- Python 3.11+. Never import from `typing` when a builtin works (`list`, `dict`, `str | None`, etc.).
|
|
84
|
+
- Every function parameter and return type must be explicitly annotated.
|
|
85
|
+
- Use direct Typer option assignment (`x: bool = typer.Option(...)`) over `Annotated` unless reuse across commands justifies it.
|
|
86
|
+
- `ExistClient` is the single HTTP facade — all REST calls go through it. Commands receive a client via `typer.Context.obj`.
|
|
87
|
+
- Shell completion for collection/document paths lives in `completions.py`.
|
|
88
|
+
|
|
89
|
+
## Testing conventions
|
|
90
|
+
|
|
91
|
+
- Always use `patch.object(module, 'attr')` instead of the string form `patch("module.path.attr")`. Only fall back to string-based `patch()` when `patch.object` is genuinely not applicable (e.g. patching a name in `builtins`).
|
|
92
|
+
|
|
93
|
+
## Architecture
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
src/exist_shell/
|
|
97
|
+
main.py # Typer app, global callback, subcommand registration
|
|
98
|
+
client/ # ExistClient — layered mixin package over the eXist REST API
|
|
99
|
+
# _base.py, _queries.py, _collections.py, _documents.py, _users.py
|
|
100
|
+
completions.py # shell completion (hits server at tab time)
|
|
101
|
+
cache.py # TTL cache for completions
|
|
102
|
+
config.py # server/collection config persistence
|
|
103
|
+
exceptions.py # custom exceptions
|
|
104
|
+
models.py # Pydantic API response models
|
|
105
|
+
utils.py # shared utilities
|
|
106
|
+
xquery.py # XQuery preprocessing and validation
|
|
107
|
+
commands/ # one module per subcommand: cat, collection, cp, edit, exec, ls, mkdir, put, rm, server, sync
|
|
108
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.PHONY: help ruff ty test checks
|
|
2
|
+
|
|
3
|
+
help: ## Show available targets
|
|
4
|
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-8s %s\n", $$1, $$2}'
|
|
5
|
+
|
|
6
|
+
checks: ruff ty test ## Run ruff, ty and tests
|
|
7
|
+
|
|
8
|
+
test: ## Run tests
|
|
9
|
+
uv run pytest
|
|
10
|
+
|
|
11
|
+
ruff: ## Lint with ruff
|
|
12
|
+
uv run ruff check src/
|
|
13
|
+
|
|
14
|
+
ty: ## Type-check with ty
|
|
15
|
+
uv run ty check src/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: exist-shell
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line tool to interact with eXist-db via REST
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: httpx>=0.27
|
|
7
|
+
Requires-Dist: platformdirs>=4.10.0; sys_platform == 'win32'
|
|
8
|
+
Requires-Dist: pydantic>=2.13.4
|
|
9
|
+
Requires-Dist: tomlkit>=0.15.0
|
|
10
|
+
Requires-Dist: typer>=0.26.7
|