mcp-synology 0.4.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.
- mcp_synology-0.4.0/.github/workflows/ci.yml +50 -0
- mcp_synology-0.4.0/.github/workflows/pr-labels-ci.yml +120 -0
- mcp_synology-0.4.0/.github/workflows/pr-labels.yml +151 -0
- mcp_synology-0.4.0/.github/workflows/publish.yml +67 -0
- mcp_synology-0.4.0/.github/workflows/qa-gate.yml +30 -0
- mcp_synology-0.4.0/.github/workflows/test-publish.yml +49 -0
- mcp_synology-0.4.0/.gitignore +213 -0
- mcp_synology-0.4.0/CHANGELOG.md +198 -0
- mcp_synology-0.4.0/CLAUDE.md +144 -0
- mcp_synology-0.4.0/DEVELOPMENT.md +131 -0
- mcp_synology-0.4.0/LICENSE +190 -0
- mcp_synology-0.4.0/PKG-INFO +314 -0
- mcp_synology-0.4.0/README.md +273 -0
- mcp_synology-0.4.0/docs/credentials.md +110 -0
- mcp_synology-0.4.0/docs/specs/architecture.md +401 -0
- mcp_synology-0.4.0/docs/specs/config-schema-spec.md +536 -0
- mcp_synology-0.4.0/docs/specs/filestation-module-spec.md +738 -0
- mcp_synology-0.4.0/docs/specs/project-scaffolding-spec.md +506 -0
- mcp_synology-0.4.0/examples/config-docker.yaml +8 -0
- mcp_synology-0.4.0/examples/config-minimal.yaml +12 -0
- mcp_synology-0.4.0/examples/config-power-user.yaml +42 -0
- mcp_synology-0.4.0/pyproject.toml +90 -0
- mcp_synology-0.4.0/scripts/migrate-from-synology-mcp.py +210 -0
- mcp_synology-0.4.0/scripts/vdsm_setup.py +152 -0
- mcp_synology-0.4.0/src/mcp_synology/__init__.py +5 -0
- mcp_synology-0.4.0/src/mcp_synology/__main__.py +5 -0
- mcp_synology-0.4.0/src/mcp_synology/cli/__init__.py +25 -0
- mcp_synology-0.4.0/src/mcp_synology/cli/check.py +65 -0
- mcp_synology-0.4.0/src/mcp_synology/cli/logging_.py +35 -0
- mcp_synology-0.4.0/src/mcp_synology/cli/main.py +128 -0
- mcp_synology-0.4.0/src/mcp_synology/cli/setup.py +416 -0
- mcp_synology-0.4.0/src/mcp_synology/cli/version.py +186 -0
- mcp_synology-0.4.0/src/mcp_synology/core/__init__.py +1 -0
- mcp_synology-0.4.0/src/mcp_synology/core/auth.py +212 -0
- mcp_synology-0.4.0/src/mcp_synology/core/client.py +589 -0
- mcp_synology-0.4.0/src/mcp_synology/core/config.py +319 -0
- mcp_synology-0.4.0/src/mcp_synology/core/errors.py +202 -0
- mcp_synology-0.4.0/src/mcp_synology/core/formatting.py +188 -0
- mcp_synology-0.4.0/src/mcp_synology/core/state.py +62 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/favicon.ico +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-dark-128.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-dark-16.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-dark-256.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-dark-32.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-dark-64.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-light-128.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-light-16.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-light-256.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-light-32.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-light-64.png +0 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-logo-dark.svg +54 -0
- mcp_synology-0.4.0/src/mcp_synology/icons/mcp-synology-logo-light.svg +54 -0
- mcp_synology-0.4.0/src/mcp_synology/instructions/server.md +73 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/__init__.py +191 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/filestation/__init__.py +588 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/filestation/helpers.py +142 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/filestation/listing.py +213 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/filestation/metadata.py +206 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/filestation/operations.py +452 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/filestation/search.py +222 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/filestation/transfer.py +197 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/system/__init__.py +86 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/system/info.py +126 -0
- mcp_synology-0.4.0/src/mcp_synology/modules/system/utilization.py +159 -0
- mcp_synology-0.4.0/src/mcp_synology/py.typed +0 -0
- mcp_synology-0.4.0/src/mcp_synology/server.py +263 -0
- mcp_synology-0.4.0/tests/__init__.py +0 -0
- mcp_synology-0.4.0/tests/conftest.py +72 -0
- mcp_synology-0.4.0/tests/core/__init__.py +0 -0
- mcp_synology-0.4.0/tests/core/test_auth.py +402 -0
- mcp_synology-0.4.0/tests/core/test_cli.py +628 -0
- mcp_synology-0.4.0/tests/core/test_client.py +200 -0
- mcp_synology-0.4.0/tests/core/test_config.py +213 -0
- mcp_synology-0.4.0/tests/core/test_errors.py +91 -0
- mcp_synology-0.4.0/tests/core/test_formatting.py +146 -0
- mcp_synology-0.4.0/tests/core/test_server.py +117 -0
- mcp_synology-0.4.0/tests/core/test_state.py +43 -0
- mcp_synology-0.4.0/tests/integration_config.yaml.example +40 -0
- mcp_synology-0.4.0/tests/modules/__init__.py +0 -0
- mcp_synology-0.4.0/tests/modules/filestation/__init__.py +0 -0
- mcp_synology-0.4.0/tests/modules/filestation/test_helpers.py +145 -0
- mcp_synology-0.4.0/tests/modules/filestation/test_listing.py +193 -0
- mcp_synology-0.4.0/tests/modules/filestation/test_metadata.py +118 -0
- mcp_synology-0.4.0/tests/modules/filestation/test_operations.py +245 -0
- mcp_synology-0.4.0/tests/modules/filestation/test_search.py +165 -0
- mcp_synology-0.4.0/tests/modules/filestation/test_transfer.py +390 -0
- mcp_synology-0.4.0/tests/modules/test_module_system.py +152 -0
- mcp_synology-0.4.0/tests/test_e2e.py +162 -0
- mcp_synology-0.4.0/tests/test_integration.py +953 -0
- mcp_synology-0.4.0/tests/vdsm/__init__.py +0 -0
- mcp_synology-0.4.0/tests/vdsm/config.py +95 -0
- mcp_synology-0.4.0/tests/vdsm/conftest.py +203 -0
- mcp_synology-0.4.0/tests/vdsm/container.py +216 -0
- mcp_synology-0.4.0/tests/vdsm/golden_image.py +119 -0
- mcp_synology-0.4.0/tests/vdsm/setup_dsm.py +578 -0
- mcp_synology-0.4.0/tests/vdsm/test_vdsm_integration.py +46 -0
- mcp_synology-0.4.0/uv.lock +1609 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- run: uv sync --extra dev
|
|
21
|
+
- run: uv run ruff check src/ tests/
|
|
22
|
+
- run: uv run ruff format --check src/ tests/
|
|
23
|
+
|
|
24
|
+
typecheck:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- run: uv sync --extra dev
|
|
32
|
+
- run: uv run mypy src/
|
|
33
|
+
|
|
34
|
+
test:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
strategy:
|
|
37
|
+
fail-fast: false
|
|
38
|
+
matrix:
|
|
39
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- uses: astral-sh/setup-uv@v5
|
|
43
|
+
with:
|
|
44
|
+
python-version: ${{ matrix.python-version }}
|
|
45
|
+
- run: uv sync --extra dev
|
|
46
|
+
- run: uv run pytest --cov=mcp_synology --cov-report=xml
|
|
47
|
+
- uses: codecov/codecov-action@v4
|
|
48
|
+
if: matrix.python-version == '3.12'
|
|
49
|
+
with:
|
|
50
|
+
files: coverage.xml
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
name: PR Label Automation (CI)
|
|
2
|
+
|
|
3
|
+
# Handles label transitions when CI completes (pass or fail).
|
|
4
|
+
# Split from pr-labels.yml because workflow_run fires on every CI
|
|
5
|
+
# completion (including merges to main), which caused noisy failures
|
|
6
|
+
# when there was no associated PR.
|
|
7
|
+
#
|
|
8
|
+
# NOTE: References the CI workflow by name ("CI").
|
|
9
|
+
# If the CI workflow in ci.yml is ever renamed, update the name here too.
|
|
10
|
+
#
|
|
11
|
+
# LIMITATION: workflow_run triggers always run from the default branch
|
|
12
|
+
# (main), not the PR branch. Changes to this file cannot be tested
|
|
13
|
+
# on a PR — they take effect only after merge.
|
|
14
|
+
on:
|
|
15
|
+
workflow_run:
|
|
16
|
+
workflows: [CI]
|
|
17
|
+
types: [completed]
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
pull-requests: write
|
|
21
|
+
checks: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
# When CI passes on a PR branch, promote from Awaiting CI to Ready for QA.
|
|
25
|
+
# Skips if Dev Active is present (dev isn't done yet).
|
|
26
|
+
on-ci-pass:
|
|
27
|
+
if: >-
|
|
28
|
+
github.event.workflow_run.conclusion == 'success'
|
|
29
|
+
&& github.event.workflow_run.event == 'pull_request'
|
|
30
|
+
&& github.event.workflow_run.head_branch != github.event.repository.default_branch
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- name: Promote to Ready for QA
|
|
34
|
+
env:
|
|
35
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
36
|
+
run: |
|
|
37
|
+
REPO=${{ github.repository }}
|
|
38
|
+
|
|
39
|
+
# Look up the associated PR.
|
|
40
|
+
# The API can 404 after a force-push (orphaned run).
|
|
41
|
+
# Capture output only on success so 404 body doesn't leak into $PR.
|
|
42
|
+
PR=""
|
|
43
|
+
API_OUT=$(gh api "repos/$REPO/actions/runs/${{ github.event.workflow_run.id }}/pull_requests" \
|
|
44
|
+
--jq '.[0].number // empty' 2>&1) && PR="$API_OUT" || true
|
|
45
|
+
|
|
46
|
+
# Fallback: pull_requests array is empty for dependabot PRs.
|
|
47
|
+
# Search by head branch instead.
|
|
48
|
+
if [ -z "$PR" ]; then
|
|
49
|
+
HEAD_BRANCH=${{ github.event.workflow_run.head_branch }}
|
|
50
|
+
PR=$(gh pr list --repo "$REPO" --head "$HEAD_BRANCH" --state open \
|
|
51
|
+
--json number --jq '.[0].number // empty' 2>/dev/null) || true
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
if [ -z "$PR" ]; then
|
|
55
|
+
echo "No PR associated with this workflow run — exiting"
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
LABELS=$(gh pr view "$PR" --repo "$REPO" --json labels --jq '.labels[].name')
|
|
60
|
+
|
|
61
|
+
# Dev Active means dev isn't done — don't promote.
|
|
62
|
+
# on-unlabel will handle promotion when Dev Active is removed.
|
|
63
|
+
if echo "$LABELS" | grep -q "^Dev Active$"; then
|
|
64
|
+
echo "Dev Active present — skipping promotion"
|
|
65
|
+
exit 0
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Promote if Awaiting CI is present
|
|
69
|
+
if echo "$LABELS" | grep -q "^Awaiting CI$"; then
|
|
70
|
+
REMOVE="Awaiting CI"
|
|
71
|
+
if echo "$LABELS" | grep -q "^CI Failed$"; then
|
|
72
|
+
REMOVE="Awaiting CI,CI Failed"
|
|
73
|
+
fi
|
|
74
|
+
gh pr edit "$PR" --repo "$REPO" --remove-label "$REMOVE"
|
|
75
|
+
gh pr edit "$PR" --repo "$REPO" --add-label "Ready for QA"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# When CI fails on a PR branch, set CI Failed.
|
|
79
|
+
# Skips if Dev Active is present (dev is already working).
|
|
80
|
+
on-ci-fail:
|
|
81
|
+
if: >-
|
|
82
|
+
github.event.workflow_run.conclusion == 'failure'
|
|
83
|
+
&& github.event.workflow_run.event == 'pull_request'
|
|
84
|
+
&& github.event.workflow_run.head_branch != github.event.repository.default_branch
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- name: Set CI Failed
|
|
88
|
+
env:
|
|
89
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
90
|
+
run: |
|
|
91
|
+
REPO=${{ github.repository }}
|
|
92
|
+
|
|
93
|
+
PR=""
|
|
94
|
+
API_OUT=$(gh api "repos/$REPO/actions/runs/${{ github.event.workflow_run.id }}/pull_requests" \
|
|
95
|
+
--jq '.[0].number // empty' 2>&1) && PR="$API_OUT" || true
|
|
96
|
+
|
|
97
|
+
if [ -z "$PR" ]; then
|
|
98
|
+
HEAD_BRANCH=${{ github.event.workflow_run.head_branch }}
|
|
99
|
+
PR=$(gh pr list --repo "$REPO" --head "$HEAD_BRANCH" --state open \
|
|
100
|
+
--json number --jq '.[0].number // empty' 2>/dev/null) || true
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if [ -z "$PR" ]; then
|
|
104
|
+
echo "No PR associated with this workflow run — exiting"
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
LABELS=$(gh pr view "$PR" --repo "$REPO" --json labels --jq '.labels[].name')
|
|
109
|
+
|
|
110
|
+
# Dev Active means dev is already working — don't add CI Failed
|
|
111
|
+
if echo "$LABELS" | grep -q "^Dev Active$"; then
|
|
112
|
+
echo "Dev Active present — skipping CI Failed"
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Set CI Failed if Awaiting CI is present (normal flow)
|
|
117
|
+
if echo "$LABELS" | grep -q "^Awaiting CI$"; then
|
|
118
|
+
gh pr edit "$PR" --repo "$REPO" --remove-label "Awaiting CI"
|
|
119
|
+
gh pr edit "$PR" --repo "$REPO" --add-label "CI Failed"
|
|
120
|
+
fi
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
name: PR Label Automation
|
|
2
|
+
|
|
3
|
+
# Handles label transitions triggered by PR events (push, label, unlabel).
|
|
4
|
+
# CI-completion promotion lives in pr-labels-ci.yml (separate workflow_run trigger).
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
types: [opened, synchronize, labeled, unlabeled]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
pull-requests: write
|
|
12
|
+
checks: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
# When a PR is opened or new commits are pushed, reset to Awaiting CI.
|
|
16
|
+
# Removes stale QA/workflow labels. Keeps Dev Active if present (coexists
|
|
17
|
+
# with Awaiting CI — dev is still working, CI runs but won't promote).
|
|
18
|
+
on-push:
|
|
19
|
+
if: github.event.action == 'opened' || github.event.action == 'synchronize'
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- name: Reset labels on new push
|
|
23
|
+
env:
|
|
24
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
25
|
+
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
|
|
26
|
+
run: |
|
|
27
|
+
PR=${{ github.event.pull_request.number }}
|
|
28
|
+
REPO=${{ github.repository }}
|
|
29
|
+
|
|
30
|
+
HAD_QA_ACTIVE=false
|
|
31
|
+
if echo "$PR_LABELS" | grep -q '"QA Active"'; then
|
|
32
|
+
HAD_QA_ACTIVE=true
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Remove stale workflow labels (coalesced into one API call)
|
|
36
|
+
REMOVE=""
|
|
37
|
+
for LABEL in "Ready for QA" "Ready for QA Signoff" "QA Approved" "QA Active" "QA Failed" "CI Failed"; do
|
|
38
|
+
if echo "$PR_LABELS" | grep -q "\"$LABEL\""; then
|
|
39
|
+
if [ -n "$REMOVE" ]; then
|
|
40
|
+
REMOVE="$REMOVE,$LABEL"
|
|
41
|
+
else
|
|
42
|
+
REMOVE="$LABEL"
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
if [ -n "$REMOVE" ]; then
|
|
47
|
+
gh pr edit "$PR" --repo "$REPO" --remove-label "$REMOVE"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Add Awaiting CI (unless already present).
|
|
51
|
+
# Dev Active is NOT removed — it coexists with Awaiting CI.
|
|
52
|
+
if ! echo "$PR_LABELS" | grep -q '"Awaiting CI"'; then
|
|
53
|
+
gh pr edit "$PR" --repo "$REPO" --add-label "Awaiting CI"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# If QA was actively reviewing, notify via comment
|
|
57
|
+
if [ "$HAD_QA_ACTIVE" = true ]; then
|
|
58
|
+
gh pr comment "$PR" --repo "$REPO" \
|
|
59
|
+
--body "New commits pushed while QA was active. QA review invalidated — resetting to Awaiting CI."
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# When Dev Active is removed manually, check if CI already passed.
|
|
63
|
+
# If yes, promote straight to Ready for QA. If no, Awaiting CI is
|
|
64
|
+
# already present and on-ci-pass will promote when CI completes.
|
|
65
|
+
on-unlabel:
|
|
66
|
+
if: >-
|
|
67
|
+
github.event.action == 'unlabeled'
|
|
68
|
+
&& github.event.label.name == 'Dev Active'
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- name: Transition after Dev Active removed
|
|
72
|
+
env:
|
|
73
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
74
|
+
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
|
|
75
|
+
run: |
|
|
76
|
+
PR=${{ github.event.pull_request.number }}
|
|
77
|
+
REPO=${{ github.repository }}
|
|
78
|
+
|
|
79
|
+
# Check if CI already passed for the head commit
|
|
80
|
+
HEAD_SHA=${{ github.event.pull_request.head.sha }}
|
|
81
|
+
CI_CONCLUSION=$(gh api "repos/$REPO/actions/workflows/ci.yml/runs?head_sha=$HEAD_SHA" \
|
|
82
|
+
--jq '.workflow_runs[0].conclusion // empty' 2>/dev/null) || true
|
|
83
|
+
|
|
84
|
+
if [ "$CI_CONCLUSION" = "success" ]; then
|
|
85
|
+
# CI passed — promote to Ready for QA
|
|
86
|
+
REMOVE=""
|
|
87
|
+
if echo "$PR_LABELS" | grep -q '"Awaiting CI"'; then
|
|
88
|
+
REMOVE="Awaiting CI"
|
|
89
|
+
fi
|
|
90
|
+
if echo "$PR_LABELS" | grep -q '"CI Failed"'; then
|
|
91
|
+
REMOVE="${REMOVE:+$REMOVE,}CI Failed"
|
|
92
|
+
fi
|
|
93
|
+
if [ -n "$REMOVE" ]; then
|
|
94
|
+
gh pr edit "$PR" --repo "$REPO" --remove-label "$REMOVE"
|
|
95
|
+
fi
|
|
96
|
+
gh pr edit "$PR" --repo "$REPO" --add-label "Ready for QA"
|
|
97
|
+
else
|
|
98
|
+
# CI hasn't passed — Awaiting CI should already be present
|
|
99
|
+
if ! echo "$PR_LABELS" | grep -q '"Awaiting CI"'; then
|
|
100
|
+
gh pr edit "$PR" --repo "$REPO" --add-label "Awaiting CI"
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# When a workflow label is added, clean up labels that no longer apply.
|
|
105
|
+
# Only runs for labels that have cleanup rules.
|
|
106
|
+
on-label:
|
|
107
|
+
if: >-
|
|
108
|
+
github.event.action == 'labeled'
|
|
109
|
+
&& contains(fromJSON('["Ready for QA","QA Active","Dev Active","Ready for QA Signoff","QA Failed","QA Approved"]'), github.event.label.name)
|
|
110
|
+
runs-on: ubuntu-latest
|
|
111
|
+
steps:
|
|
112
|
+
- name: Clean up stale labels
|
|
113
|
+
env:
|
|
114
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
115
|
+
ADDED_LABEL: ${{ github.event.label.name }}
|
|
116
|
+
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
|
|
117
|
+
run: |
|
|
118
|
+
PR=${{ github.event.pull_request.number }}
|
|
119
|
+
REPO=${{ github.repository }}
|
|
120
|
+
|
|
121
|
+
remove_if_present() {
|
|
122
|
+
if echo "$PR_LABELS" | grep -q "\"$1\""; then
|
|
123
|
+
gh pr edit "$PR" --repo "$REPO" --remove-label "$1"
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case "$ADDED_LABEL" in
|
|
128
|
+
"Ready for QA")
|
|
129
|
+
remove_if_present "Awaiting CI"
|
|
130
|
+
remove_if_present "CI Failed"
|
|
131
|
+
;;
|
|
132
|
+
"QA Active")
|
|
133
|
+
remove_if_present "Ready for QA"
|
|
134
|
+
;;
|
|
135
|
+
"Dev Active")
|
|
136
|
+
remove_if_present "QA Failed"
|
|
137
|
+
remove_if_present "CI Failed"
|
|
138
|
+
remove_if_present "Awaiting CI"
|
|
139
|
+
remove_if_present "Ready for QA"
|
|
140
|
+
;;
|
|
141
|
+
"Ready for QA Signoff")
|
|
142
|
+
remove_if_present "QA Active"
|
|
143
|
+
;;
|
|
144
|
+
"QA Failed")
|
|
145
|
+
remove_if_present "QA Active"
|
|
146
|
+
remove_if_present "Ready for QA Signoff"
|
|
147
|
+
;;
|
|
148
|
+
"QA Approved")
|
|
149
|
+
remove_if_present "Ready for QA Signoff"
|
|
150
|
+
;;
|
|
151
|
+
esac
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: |
|
|
23
|
+
uv sync --extra dev
|
|
24
|
+
uv run pytest
|
|
25
|
+
|
|
26
|
+
- name: Build distributions
|
|
27
|
+
run: uv build
|
|
28
|
+
|
|
29
|
+
- name: Upload distributions
|
|
30
|
+
uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish-pypi:
|
|
36
|
+
needs: build
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
environment: pypi
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write
|
|
41
|
+
steps:
|
|
42
|
+
- name: Download distributions
|
|
43
|
+
uses: actions/download-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
|
|
48
|
+
- name: Publish to PyPI
|
|
49
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
50
|
+
|
|
51
|
+
github-release:
|
|
52
|
+
needs: publish-pypi
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
permissions:
|
|
55
|
+
contents: write
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
with:
|
|
59
|
+
fetch-depth: 0
|
|
60
|
+
|
|
61
|
+
- name: Create GitHub Release
|
|
62
|
+
env:
|
|
63
|
+
GH_TOKEN: ${{ github.token }}
|
|
64
|
+
run: |
|
|
65
|
+
gh release create "${{ github.ref_name }}" \
|
|
66
|
+
--title "${{ github.ref_name }}" \
|
|
67
|
+
--generate-notes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: QA Gate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
statuses: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
qa-approved:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Set QA status
|
|
16
|
+
env:
|
|
17
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
18
|
+
run: |
|
|
19
|
+
if echo '${{ toJSON(github.event.pull_request.labels.*.name) }}' | grep -q '"QA Approved"'; then
|
|
20
|
+
STATE="success"
|
|
21
|
+
DESC="QA testing completed"
|
|
22
|
+
else
|
|
23
|
+
STATE="pending"
|
|
24
|
+
DESC="Waiting for QA — add 'QA Approved' label when testing is complete"
|
|
25
|
+
fi
|
|
26
|
+
gh api repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }} \
|
|
27
|
+
--method POST \
|
|
28
|
+
-f state="$STATE" \
|
|
29
|
+
-f description="$DESC" \
|
|
30
|
+
-f context="QA Gate"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Test Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
env:
|
|
7
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
|
|
19
|
+
- name: Run tests
|
|
20
|
+
run: |
|
|
21
|
+
uv sync --extra dev
|
|
22
|
+
uv run pytest
|
|
23
|
+
|
|
24
|
+
- name: Build distributions
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Upload distributions
|
|
28
|
+
uses: actions/upload-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: dist
|
|
31
|
+
path: dist/
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment: testpypi
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write
|
|
39
|
+
steps:
|
|
40
|
+
- name: Download distributions
|
|
41
|
+
uses: actions/download-artifact@v4
|
|
42
|
+
with:
|
|
43
|
+
name: dist
|
|
44
|
+
path: dist/
|
|
45
|
+
|
|
46
|
+
- name: Publish to TestPyPI
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
48
|
+
with:
|
|
49
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# mcp-synology
|
|
2
|
+
tests/integration_config.yaml
|
|
3
|
+
|
|
4
|
+
# Virtual DSM test infrastructure (golden images ~2GB each)
|
|
5
|
+
.vdsm/
|
|
6
|
+
|
|
7
|
+
# Byte-compiled / optimized / DLL files
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[codz]
|
|
10
|
+
*$py.class
|
|
11
|
+
|
|
12
|
+
# C extensions
|
|
13
|
+
*.so
|
|
14
|
+
|
|
15
|
+
# Distribution / packaging
|
|
16
|
+
.Python
|
|
17
|
+
build/
|
|
18
|
+
develop-eggs/
|
|
19
|
+
dist/
|
|
20
|
+
downloads/
|
|
21
|
+
eggs/
|
|
22
|
+
.eggs/
|
|
23
|
+
lib/
|
|
24
|
+
lib64/
|
|
25
|
+
parts/
|
|
26
|
+
sdist/
|
|
27
|
+
var/
|
|
28
|
+
wheels/
|
|
29
|
+
share/python-wheels/
|
|
30
|
+
*.egg-info/
|
|
31
|
+
.installed.cfg
|
|
32
|
+
*.egg
|
|
33
|
+
MANIFEST
|
|
34
|
+
|
|
35
|
+
# PyInstaller
|
|
36
|
+
# Usually these files are written by a python script from a template
|
|
37
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
38
|
+
*.manifest
|
|
39
|
+
*.spec
|
|
40
|
+
|
|
41
|
+
# Installer logs
|
|
42
|
+
pip-log.txt
|
|
43
|
+
pip-delete-this-directory.txt
|
|
44
|
+
|
|
45
|
+
# Unit test / coverage reports
|
|
46
|
+
htmlcov/
|
|
47
|
+
.tox/
|
|
48
|
+
.nox/
|
|
49
|
+
.coverage
|
|
50
|
+
.coverage.*
|
|
51
|
+
.cache
|
|
52
|
+
nosetests.xml
|
|
53
|
+
coverage.xml
|
|
54
|
+
*.cover
|
|
55
|
+
*.py.cover
|
|
56
|
+
.hypothesis/
|
|
57
|
+
.pytest_cache/
|
|
58
|
+
cover/
|
|
59
|
+
|
|
60
|
+
# Translations
|
|
61
|
+
*.mo
|
|
62
|
+
*.pot
|
|
63
|
+
|
|
64
|
+
# Django stuff:
|
|
65
|
+
*.log
|
|
66
|
+
local_settings.py
|
|
67
|
+
db.sqlite3
|
|
68
|
+
db.sqlite3-journal
|
|
69
|
+
|
|
70
|
+
# Flask stuff:
|
|
71
|
+
instance/
|
|
72
|
+
.webassets-cache
|
|
73
|
+
|
|
74
|
+
# Scrapy stuff:
|
|
75
|
+
.scrapy
|
|
76
|
+
|
|
77
|
+
# Sphinx documentation
|
|
78
|
+
docs/_build/
|
|
79
|
+
|
|
80
|
+
# PyBuilder
|
|
81
|
+
.pybuilder/
|
|
82
|
+
target/
|
|
83
|
+
|
|
84
|
+
# Jupyter Notebook
|
|
85
|
+
.ipynb_checkpoints
|
|
86
|
+
|
|
87
|
+
# IPython
|
|
88
|
+
profile_default/
|
|
89
|
+
ipython_config.py
|
|
90
|
+
|
|
91
|
+
# pyenv
|
|
92
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
93
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
94
|
+
# .python-version
|
|
95
|
+
|
|
96
|
+
# pipenv
|
|
97
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
98
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
99
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
100
|
+
# install all needed dependencies.
|
|
101
|
+
#Pipfile.lock
|
|
102
|
+
|
|
103
|
+
# UV
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
#uv.lock
|
|
108
|
+
|
|
109
|
+
# poetry
|
|
110
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
111
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
112
|
+
# commonly ignored for libraries.
|
|
113
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
114
|
+
#poetry.lock
|
|
115
|
+
#poetry.toml
|
|
116
|
+
|
|
117
|
+
# pdm
|
|
118
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
119
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
120
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
121
|
+
#pdm.lock
|
|
122
|
+
#pdm.toml
|
|
123
|
+
.pdm-python
|
|
124
|
+
.pdm-build/
|
|
125
|
+
|
|
126
|
+
# pixi
|
|
127
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
128
|
+
#pixi.lock
|
|
129
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
130
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
131
|
+
.pixi
|
|
132
|
+
|
|
133
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
134
|
+
__pypackages__/
|
|
135
|
+
|
|
136
|
+
# Celery stuff
|
|
137
|
+
celerybeat-schedule
|
|
138
|
+
celerybeat.pid
|
|
139
|
+
|
|
140
|
+
# SageMath parsed files
|
|
141
|
+
*.sage.py
|
|
142
|
+
|
|
143
|
+
# Environments
|
|
144
|
+
.env
|
|
145
|
+
.envrc
|
|
146
|
+
.venv
|
|
147
|
+
env/
|
|
148
|
+
venv/
|
|
149
|
+
ENV/
|
|
150
|
+
env.bak/
|
|
151
|
+
venv.bak/
|
|
152
|
+
|
|
153
|
+
# Spyder project settings
|
|
154
|
+
.spyderproject
|
|
155
|
+
.spyproject
|
|
156
|
+
|
|
157
|
+
# Rope project settings
|
|
158
|
+
.ropeproject
|
|
159
|
+
|
|
160
|
+
# mkdocs documentation
|
|
161
|
+
/site
|
|
162
|
+
|
|
163
|
+
# mypy
|
|
164
|
+
.mypy_cache/
|
|
165
|
+
.dmypy.json
|
|
166
|
+
dmypy.json
|
|
167
|
+
|
|
168
|
+
# Pyre type checker
|
|
169
|
+
.pyre/
|
|
170
|
+
|
|
171
|
+
# pytype static type analyzer
|
|
172
|
+
.pytype/
|
|
173
|
+
|
|
174
|
+
# Cython debug symbols
|
|
175
|
+
cython_debug/
|
|
176
|
+
|
|
177
|
+
# PyCharm
|
|
178
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
179
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
180
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
181
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
182
|
+
#.idea/
|
|
183
|
+
|
|
184
|
+
# Abstra
|
|
185
|
+
# Abstra is an AI-powered process automation framework.
|
|
186
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
187
|
+
# Learn more at https://abstra.io/docs
|
|
188
|
+
.abstra/
|
|
189
|
+
|
|
190
|
+
# Visual Studio Code
|
|
191
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
192
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
193
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
194
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
195
|
+
# .vscode/
|
|
196
|
+
|
|
197
|
+
# Ruff stuff:
|
|
198
|
+
.ruff_cache/
|
|
199
|
+
|
|
200
|
+
# PyPI configuration file
|
|
201
|
+
.pypirc
|
|
202
|
+
|
|
203
|
+
# Cursor
|
|
204
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
205
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
206
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
207
|
+
.cursorignore
|
|
208
|
+
.cursorindexingignore
|
|
209
|
+
|
|
210
|
+
# Marimo
|
|
211
|
+
marimo/_static/
|
|
212
|
+
marimo/_lsp/
|
|
213
|
+
__marimo__/
|