yuki-cli 0.1.1__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.
- yuki_cli-0.1.1/.github/workflows/ci.yml +50 -0
- yuki_cli-0.1.1/.github/workflows/release.yml +214 -0
- yuki_cli-0.1.1/.gitignore +4 -0
- yuki_cli-0.1.1/CLAUDE.md +62 -0
- yuki_cli-0.1.1/Cargo.lock +1987 -0
- yuki_cli-0.1.1/Cargo.toml +35 -0
- yuki_cli-0.1.1/LICENSE +21 -0
- yuki_cli-0.1.1/Makefile +75 -0
- yuki_cli-0.1.1/PKG-INFO +167 -0
- yuki_cli-0.1.1/README.md +146 -0
- yuki_cli-0.1.1/pyproject.toml +35 -0
- yuki_cli-0.1.1/src/cli/accounts.rs +171 -0
- yuki_cli-0.1.1/src/cli/admin.rs +35 -0
- yuki_cli-0.1.1/src/cli/check.rs +422 -0
- yuki_cli-0.1.1/src/cli/contacts.rs +78 -0
- yuki_cli-0.1.1/src/cli/documents.rs +332 -0
- yuki_cli-0.1.1/src/cli/init.rs +157 -0
- yuki_cli-0.1.1/src/cli/invoices.rs +136 -0
- yuki_cli-0.1.1/src/cli/mod.rs +352 -0
- yuki_cli-0.1.1/src/cli/projects.rs +79 -0
- yuki_cli-0.1.1/src/cli/upload.rs +180 -0
- yuki_cli-0.1.1/src/cli/vat.rs +73 -0
- yuki_cli-0.1.1/src/client/accounting.rs +612 -0
- yuki_cli-0.1.1/src/client/accounting_info.rs +581 -0
- yuki_cli-0.1.1/src/client/archive.rs +452 -0
- yuki_cli-0.1.1/src/client/contact.rs +164 -0
- yuki_cli-0.1.1/src/client/mod.rs +15 -0
- yuki_cli-0.1.1/src/client/sales.rs +125 -0
- yuki_cli-0.1.1/src/client/soap_client.rs +260 -0
- yuki_cli-0.1.1/src/client/vat.rs +236 -0
- yuki_cli-0.1.1/src/config.rs +68 -0
- yuki_cli-0.1.1/src/error.rs +39 -0
- yuki_cli-0.1.1/src/lib.rs +6 -0
- yuki_cli-0.1.1/src/main.rs +311 -0
- yuki_cli-0.1.1/src/output.rs +56 -0
- yuki_cli-0.1.1/src/period.rs +77 -0
- yuki_cli-0.1.1/tests/config.rs +192 -0
- yuki_cli-0.1.1/tests/error.rs +38 -0
- yuki_cli-0.1.1/tests/output.rs +68 -0
- yuki_cli-0.1.1/tests/parsers.rs +377 -0
- yuki_cli-0.1.1/tests/period.rs +91 -0
- yuki_cli-0.1.1/tests/soap_client.rs +132 -0
- yuki_cli-0.1.1/yuki_cli_py/__init__.py +10 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
name: Lint
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
21
|
+
with:
|
|
22
|
+
components: rustfmt, clippy
|
|
23
|
+
|
|
24
|
+
- name: Run linting
|
|
25
|
+
run: make lint
|
|
26
|
+
|
|
27
|
+
test:
|
|
28
|
+
name: Test
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
|
|
33
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
34
|
+
|
|
35
|
+
- name: Run tests
|
|
36
|
+
run: make test
|
|
37
|
+
|
|
38
|
+
all-checks-passed:
|
|
39
|
+
name: All checks passed
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
needs: [lint, test]
|
|
42
|
+
if: always()
|
|
43
|
+
steps:
|
|
44
|
+
- name: Verify all checks passed
|
|
45
|
+
run: |
|
|
46
|
+
if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]; then
|
|
47
|
+
echo "Some checks failed or were cancelled"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
echo "All checks passed"
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v[0-9]*.[0-9]*.[0-9]*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
dry_run:
|
|
10
|
+
description: 'Dry run mode (skip actual publishing)'
|
|
11
|
+
required: false
|
|
12
|
+
default: true
|
|
13
|
+
type: boolean
|
|
14
|
+
skip_crates_io:
|
|
15
|
+
description: 'Skip publishing to crates.io (if already published)'
|
|
16
|
+
required: false
|
|
17
|
+
default: false
|
|
18
|
+
type: boolean
|
|
19
|
+
skip_pypi:
|
|
20
|
+
description: 'Skip publishing to PyPI (if already published)'
|
|
21
|
+
required: false
|
|
22
|
+
default: false
|
|
23
|
+
type: boolean
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
|
|
28
|
+
env:
|
|
29
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
test:
|
|
33
|
+
name: Run tests
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
|
|
38
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
39
|
+
|
|
40
|
+
- name: Verify Cargo.lock is up to date
|
|
41
|
+
run: cargo check --locked
|
|
42
|
+
|
|
43
|
+
- name: Run tests
|
|
44
|
+
run: make test
|
|
45
|
+
|
|
46
|
+
build:
|
|
47
|
+
name: Build ${{ matrix.target }}
|
|
48
|
+
needs: test
|
|
49
|
+
strategy:
|
|
50
|
+
matrix:
|
|
51
|
+
include:
|
|
52
|
+
- os: ubuntu-latest
|
|
53
|
+
target: x86_64-unknown-linux-gnu
|
|
54
|
+
- os: ubuntu-latest
|
|
55
|
+
target: aarch64-unknown-linux-gnu
|
|
56
|
+
- os: macos-latest
|
|
57
|
+
target: x86_64-apple-darwin
|
|
58
|
+
- os: macos-latest
|
|
59
|
+
target: aarch64-apple-darwin
|
|
60
|
+
runs-on: ${{ matrix.os }}
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
|
|
64
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
65
|
+
with:
|
|
66
|
+
targets: ${{ matrix.target }}
|
|
67
|
+
|
|
68
|
+
- name: Install cross-compilation tools
|
|
69
|
+
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
|
70
|
+
run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu
|
|
71
|
+
|
|
72
|
+
- name: Install maturin
|
|
73
|
+
run: pip install maturin
|
|
74
|
+
|
|
75
|
+
- name: Build wheel
|
|
76
|
+
shell: bash
|
|
77
|
+
run: |
|
|
78
|
+
case "${{ matrix.target }}" in
|
|
79
|
+
*-gnu)
|
|
80
|
+
pip install ziglang
|
|
81
|
+
maturin build --release --target ${{ matrix.target }} --compatibility manylinux_2_28 --zig
|
|
82
|
+
;;
|
|
83
|
+
*)
|
|
84
|
+
maturin build --release --target ${{ matrix.target }}
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
|
|
88
|
+
- name: Build binary
|
|
89
|
+
env:
|
|
90
|
+
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
|
91
|
+
run: cargo build --release --target ${{ matrix.target }}
|
|
92
|
+
|
|
93
|
+
- name: Verify binary
|
|
94
|
+
if: ${{ !contains(matrix.target, 'aarch64-unknown-linux') }}
|
|
95
|
+
run: ./target/${{ matrix.target }}/release/yuki --help
|
|
96
|
+
|
|
97
|
+
- name: Create release archive
|
|
98
|
+
shell: bash
|
|
99
|
+
run: |
|
|
100
|
+
VERSION=${GITHUB_REF#refs/tags/}
|
|
101
|
+
ARCHIVE_NAME="yuki-cli-${VERSION}-${{ matrix.target }}"
|
|
102
|
+
|
|
103
|
+
mkdir -p release-package
|
|
104
|
+
cp "target/${{ matrix.target }}/release/yuki" release-package/yuki
|
|
105
|
+
tar -czf "${ARCHIVE_NAME}.tar.gz" -C release-package yuki
|
|
106
|
+
|
|
107
|
+
if [[ "${{ runner.os }}" == "macOS" ]]; then
|
|
108
|
+
shasum -a 256 "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
|
|
109
|
+
else
|
|
110
|
+
sha256sum "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
rm -rf release-package
|
|
114
|
+
|
|
115
|
+
- name: Upload wheel
|
|
116
|
+
uses: actions/upload-artifact@v4
|
|
117
|
+
with:
|
|
118
|
+
path: target/wheels/*
|
|
119
|
+
name: wheel-${{ matrix.target }}
|
|
120
|
+
|
|
121
|
+
- name: Upload release archives
|
|
122
|
+
uses: actions/upload-artifact@v4
|
|
123
|
+
with:
|
|
124
|
+
path: |
|
|
125
|
+
yuki-cli-*-${{ matrix.target }}.tar.gz*
|
|
126
|
+
name: release-${{ matrix.target }}
|
|
127
|
+
|
|
128
|
+
sdist:
|
|
129
|
+
name: Build source distribution
|
|
130
|
+
runs-on: ubuntu-latest
|
|
131
|
+
needs: test
|
|
132
|
+
steps:
|
|
133
|
+
- uses: actions/checkout@v4
|
|
134
|
+
|
|
135
|
+
- name: Install maturin
|
|
136
|
+
run: pip install maturin
|
|
137
|
+
|
|
138
|
+
- name: Build sdist
|
|
139
|
+
run: maturin sdist
|
|
140
|
+
|
|
141
|
+
- uses: actions/upload-artifact@v4
|
|
142
|
+
with:
|
|
143
|
+
path: target/wheels/*.tar.gz
|
|
144
|
+
name: sdist
|
|
145
|
+
|
|
146
|
+
release:
|
|
147
|
+
runs-on: ubuntu-latest
|
|
148
|
+
needs: [build, sdist]
|
|
149
|
+
steps:
|
|
150
|
+
- uses: actions/checkout@v4
|
|
151
|
+
|
|
152
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
153
|
+
|
|
154
|
+
- name: Install uv
|
|
155
|
+
uses: astral-sh/setup-uv@v5
|
|
156
|
+
with:
|
|
157
|
+
enable-cache: false
|
|
158
|
+
|
|
159
|
+
- name: Publish to crates.io
|
|
160
|
+
if: ${{ inputs.dry_run != true && inputs.skip_crates_io != true }}
|
|
161
|
+
env:
|
|
162
|
+
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
163
|
+
run: cargo publish --locked
|
|
164
|
+
|
|
165
|
+
- name: Test crates.io publish (dry run)
|
|
166
|
+
if: ${{ inputs.dry_run == true && inputs.skip_crates_io != true }}
|
|
167
|
+
run: |
|
|
168
|
+
echo "DRY RUN: Would publish to crates.io"
|
|
169
|
+
cargo publish --dry-run --locked
|
|
170
|
+
|
|
171
|
+
- name: Skip crates.io publishing
|
|
172
|
+
if: ${{ inputs.skip_crates_io == true }}
|
|
173
|
+
run: echo "Skipping crates.io publishing as requested"
|
|
174
|
+
|
|
175
|
+
- name: Download all artifacts
|
|
176
|
+
uses: actions/download-artifact@v4
|
|
177
|
+
with:
|
|
178
|
+
path: artifacts
|
|
179
|
+
|
|
180
|
+
- name: Publish to PyPI
|
|
181
|
+
if: ${{ inputs.dry_run != true && inputs.skip_pypi != true }}
|
|
182
|
+
env:
|
|
183
|
+
TWINE_USERNAME: __token__
|
|
184
|
+
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
|
185
|
+
run: |
|
|
186
|
+
uv tool run twine upload artifacts/wheel-*/*.whl artifacts/sdist/*.tar.gz
|
|
187
|
+
|
|
188
|
+
- name: Test PyPI upload (dry run)
|
|
189
|
+
if: ${{ inputs.dry_run == true && inputs.skip_pypi != true }}
|
|
190
|
+
run: |
|
|
191
|
+
echo "DRY RUN: Would upload to PyPI:"
|
|
192
|
+
find artifacts/wheel-* -name "*.whl" -type f | sort
|
|
193
|
+
find artifacts/sdist -name "*.tar.gz" -type f | sort
|
|
194
|
+
uv tool run twine check artifacts/wheel-*/*.whl artifacts/sdist/*.tar.gz
|
|
195
|
+
|
|
196
|
+
- name: Skip PyPI publishing
|
|
197
|
+
if: ${{ inputs.skip_pypi == true }}
|
|
198
|
+
run: echo "Skipping PyPI publishing as requested"
|
|
199
|
+
|
|
200
|
+
- name: Create Release
|
|
201
|
+
if: ${{ inputs.dry_run != true }}
|
|
202
|
+
uses: softprops/action-gh-release@v2
|
|
203
|
+
with:
|
|
204
|
+
generate_release_notes: true
|
|
205
|
+
files: |
|
|
206
|
+
artifacts/release-*/yuki-cli-*.tar.gz
|
|
207
|
+
artifacts/release-*/yuki-cli-*.tar.gz.sha256
|
|
208
|
+
|
|
209
|
+
- name: Dry Run Summary
|
|
210
|
+
if: ${{ inputs.dry_run == true }}
|
|
211
|
+
run: |
|
|
212
|
+
echo "Dry run complete. Artifacts built but nothing published."
|
|
213
|
+
echo "Archives:"
|
|
214
|
+
find artifacts/release-* -type f | sort
|
yuki_cli-0.1.1/CLAUDE.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Build & Test Commands
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
make check # Run clippy (warnings=errors) + fmt check + tests
|
|
9
|
+
make test # cargo test
|
|
10
|
+
make lint # clippy + fmt check
|
|
11
|
+
make fmt # auto-format
|
|
12
|
+
make install # cargo install --path . (installs to ~/.cargo/bin/yuki)
|
|
13
|
+
cargo test <name> # Run a single test by name
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
After any code change that affects the binary, run `make install` to update `~/.cargo/bin/yuki`.
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
Three-layer design: **CLI** (clap) → **command handlers** → **typed SOAP clients**.
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/main.rs Entry point, clap dispatch, error formatting
|
|
24
|
+
src/cli/mod.rs Cli struct, Commands enum, setup_domain() helper
|
|
25
|
+
src/cli/*.rs Command handlers (one per subcommand group)
|
|
26
|
+
src/client/mod.rs local_name() helper, re-exports
|
|
27
|
+
src/client/soap_client.rs SoapEnvelope builder + SoapClient (HTTP transport, XML parsing)
|
|
28
|
+
src/client/*.rs Service-specific clients wrapping SoapClient
|
|
29
|
+
src/config.rs TOML config (~/.config/yuki/config.toml)
|
|
30
|
+
src/error.rs YukiError enum with exit codes (0/1/2/3/4)
|
|
31
|
+
src/output.rs TTY-aware table (comfy-table) / JSON output
|
|
32
|
+
src/period.rs Period string → (start_date, end_date) conversion
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### SOAP Client Pattern
|
|
36
|
+
|
|
37
|
+
Each service client (accounting, archive, vat, contact, sales) wraps `SoapClient` with a specific base URL (`https://api.yukiworks.nl/ws/{Service}.asmx`). The flow is always: `authenticate()` → `set_current_domain()` → operation calls. The `setup_domain()` helper in `cli/mod.rs` handles the first two steps.
|
|
38
|
+
|
|
39
|
+
`SoapEnvelope` is a builder: `.new("Op").session(sid).param("key", "val").build()` produces the XML envelope. All operations require `administrationID` as a parameter.
|
|
40
|
+
|
|
41
|
+
### Output Convention
|
|
42
|
+
|
|
43
|
+
Output is TTY-aware: tables for humans, JSON when piped. The `OutputFormat::from_flag()` method handles this. All command handlers follow the same pattern: build `headers` + `rows` vectors, then format with `format_table`/`format_json`.
|
|
44
|
+
|
|
45
|
+
## Yuki API Quirks
|
|
46
|
+
|
|
47
|
+
These are hard-won lessons from the actual API behavior:
|
|
48
|
+
|
|
49
|
+
- SOAP namespace: `http://www.theyukicompany.com/`
|
|
50
|
+
- SOAP faults return as HTTP 500 — must parse XML body, not just status code
|
|
51
|
+
- `SetCurrentDomain` needs `domainID` (not `administrationID`)
|
|
52
|
+
- Almost all operations need `administrationID` as a parameter
|
|
53
|
+
- Administration `ID` is an XML **attribute**, not a child element
|
|
54
|
+
- VAT operations use exact casing: `VATReturnList`, `ActiveVATCodesList`
|
|
55
|
+
- Real XML field names often differ from what you'd guess (lowercase, different names)
|
|
56
|
+
- `paymentMethod` must be int (`0` = unspecified), not empty string
|
|
57
|
+
- `SearchDocuments`: `folderID=-1, tabID=-1` means "all folders"
|
|
58
|
+
- Amount uses dot decimal separator (`695.74`), not comma
|
|
59
|
+
|
|
60
|
+
## Config
|
|
61
|
+
|
|
62
|
+
Stored at `~/.config/yuki/config.toml`. Each administration has both a `domain_id` (for `SetCurrentDomain`) and an `admin_id` (for operation parameters) — they are different UUIDs.
|