mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
Merge pull request #6486 from InfinityPacer/codex/chore/floating-build-toolchain
This commit is contained in:
@@ -20,21 +20,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout MoviePilot
|
- name: Checkout MoviePilot
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
path: MoviePilot
|
path: MoviePilot
|
||||||
|
|
||||||
- name: Checkout MoviePilot-Plugins
|
- name: Checkout MoviePilot-Plugins
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: jxxghp/MoviePilot-Plugins
|
repository: jxxghp/MoviePilot-Plugins
|
||||||
ref: main
|
ref: main
|
||||||
path: MoviePilot-Plugins
|
path: MoviePilot-Plugins
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
with:
|
||||||
version: '0.12.5'
|
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: |
|
cache-dependency-glob: |
|
||||||
|
|||||||
+25
-27
@@ -15,12 +15,10 @@ jobs:
|
|||||||
TRIVY_SKIP_JAVA_DB_UPDATE: "true"
|
TRIVY_SKIP_JAVA_DB_UPDATE: "true"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
|
||||||
version: '0.12.5'
|
|
||||||
|
|
||||||
- name: Verify dependency lock
|
- name: Verify dependency lock
|
||||||
run: uv lock --check
|
run: uv lock --check
|
||||||
@@ -29,7 +27,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
uv export --quiet --locked --no-default-groups --group runtime-standard \
|
uv export --quiet --locked --no-default-groups --group runtime-standard \
|
||||||
--no-emit-project --output-file /tmp/moviepilot-audit-standard.txt
|
--no-emit-project --output-file /tmp/moviepilot-audit-standard.txt
|
||||||
uvx --from pip-audit==2.10.1 pip-audit \
|
uvx --from pip-audit pip-audit \
|
||||||
--require-hashes --disable-pip --strict --progress-spinner off \
|
--require-hashes --disable-pip --strict --progress-spinner off \
|
||||||
--requirement /tmp/moviepilot-audit-standard.txt
|
--requirement /tmp/moviepilot-audit-standard.txt
|
||||||
|
|
||||||
@@ -40,7 +38,7 @@ jobs:
|
|||||||
--lock uv.lock \
|
--lock uv.lock \
|
||||||
--input /tmp/moviepilot-audit-free-threaded.txt \
|
--input /tmp/moviepilot-audit-free-threaded.txt \
|
||||||
--output /tmp/moviepilot-audit-free-threaded-normalized.txt
|
--output /tmp/moviepilot-audit-free-threaded-normalized.txt
|
||||||
uvx --from pip-audit==2.10.1 pip-audit \
|
uvx --from pip-audit pip-audit \
|
||||||
--no-deps --disable-pip --strict --progress-spinner off \
|
--no-deps --disable-pip --strict --progress-spinner off \
|
||||||
--requirement /tmp/moviepilot-audit-free-threaded-normalized.txt
|
--requirement /tmp/moviepilot-audit-free-threaded-normalized.txt
|
||||||
|
|
||||||
@@ -78,7 +76,7 @@ jobs:
|
|||||||
echo "frontend_sha256=$frontend_sha256" >> "$GITHUB_OUTPUT"
|
echo "frontend_sha256=$frontend_sha256" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Checkout Wiki Plugin Market
|
- name: Checkout Wiki Plugin Market
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: jxxghp/MoviePilot-Wiki
|
repository: jxxghp/MoviePilot-Wiki
|
||||||
ref: main
|
ref: main
|
||||||
@@ -113,7 +111,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker Meta
|
- name: Docker Meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3
|
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3
|
||||||
@@ -123,7 +121,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker Meta free-threaded
|
- name: Docker Meta free-threaded
|
||||||
id: meta_ft
|
id: meta_ft
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3t
|
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3t
|
||||||
@@ -132,13 +130,13 @@ jobs:
|
|||||||
type=raw,value=beta
|
type=raw,value=beta
|
||||||
|
|
||||||
- name: Set Up QEMU
|
- name: Set Up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set Up Buildx
|
- name: Set Up Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Build standard amd64 candidate
|
- name: Build standard amd64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -157,10 +155,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3-standard-docker-amd64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3-standard-docker-amd64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan standard amd64 candidate vulnerabilities
|
- name: Scan standard amd64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3-candidate:linux-amd64
|
image-ref: moviepilot-v3-candidate:linux-amd64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -170,7 +168,7 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Build standard arm64 candidate
|
- name: Build standard arm64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -189,10 +187,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3-standard-docker-arm64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3-standard-docker-arm64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan standard arm64 candidate vulnerabilities
|
- name: Scan standard arm64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3-candidate:linux-arm64
|
image-ref: moviepilot-v3-candidate:linux-arm64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -202,7 +200,7 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Build free-threaded amd64 candidate
|
- name: Build free-threaded amd64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -221,10 +219,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3t-docker-amd64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3t-docker-amd64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan free-threaded amd64 candidate vulnerabilities
|
- name: Scan free-threaded amd64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3t-candidate:linux-amd64
|
image-ref: moviepilot-v3t-candidate:linux-amd64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -234,7 +232,7 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Build free-threaded arm64 candidate
|
- name: Build free-threaded arm64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -253,10 +251,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3t-docker-arm64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3t-docker-arm64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan free-threaded arm64 candidate vulnerabilities
|
- name: Scan free-threaded arm64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3t-candidate:linux-arm64
|
image-ref: moviepilot-v3t-candidate:linux-arm64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -266,20 +264,20 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Login DockerHub
|
- name: Login DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login GitHub Container Registry
|
- name: Login GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Publish standard multi-architecture image
|
- name: Publish standard multi-architecture image
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -309,7 +307,7 @@ jobs:
|
|||||||
type=gha,scope=moviepilot-v3-standard-docker-arm64,version=2
|
type=gha,scope=moviepilot-v3-standard-docker-arm64,version=2
|
||||||
|
|
||||||
- name: Publish free-threaded multi-architecture image
|
- name: Publish free-threaded multi-architecture image
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
|
|||||||
@@ -20,15 +20,13 @@ jobs:
|
|||||||
TRIVY_SKIP_JAVA_DB_UPDATE: "true"
|
TRIVY_SKIP_JAVA_DB_UPDATE: "true"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
|
||||||
version: '0.12.5'
|
|
||||||
|
|
||||||
- name: Verify dependency lock
|
- name: Verify dependency lock
|
||||||
run: uv lock --check
|
run: uv lock --check
|
||||||
@@ -37,7 +35,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
uv export --quiet --locked --no-default-groups --group runtime-standard \
|
uv export --quiet --locked --no-default-groups --group runtime-standard \
|
||||||
--no-emit-project --output-file /tmp/moviepilot-audit-standard.txt
|
--no-emit-project --output-file /tmp/moviepilot-audit-standard.txt
|
||||||
uvx --from pip-audit==2.10.1 pip-audit \
|
uvx --from pip-audit pip-audit \
|
||||||
--require-hashes --disable-pip --strict --progress-spinner off \
|
--require-hashes --disable-pip --strict --progress-spinner off \
|
||||||
--requirement /tmp/moviepilot-audit-standard.txt
|
--requirement /tmp/moviepilot-audit-standard.txt
|
||||||
|
|
||||||
@@ -48,7 +46,7 @@ jobs:
|
|||||||
--lock uv.lock \
|
--lock uv.lock \
|
||||||
--input /tmp/moviepilot-audit-free-threaded.txt \
|
--input /tmp/moviepilot-audit-free-threaded.txt \
|
||||||
--output /tmp/moviepilot-audit-free-threaded-normalized.txt
|
--output /tmp/moviepilot-audit-free-threaded-normalized.txt
|
||||||
uvx --from pip-audit==2.10.1 pip-audit \
|
uvx --from pip-audit pip-audit \
|
||||||
--no-deps --disable-pip --strict --progress-spinner off \
|
--no-deps --disable-pip --strict --progress-spinner off \
|
||||||
--requirement /tmp/moviepilot-audit-free-threaded-normalized.txt
|
--requirement /tmp/moviepilot-audit-free-threaded-normalized.txt
|
||||||
|
|
||||||
@@ -86,7 +84,7 @@ jobs:
|
|||||||
echo "frontend_sha256=$frontend_sha256" >> "$GITHUB_OUTPUT"
|
echo "frontend_sha256=$frontend_sha256" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Checkout Wiki Plugin Market
|
- name: Checkout Wiki Plugin Market
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
repository: jxxghp/MoviePilot-Wiki
|
repository: jxxghp/MoviePilot-Wiki
|
||||||
ref: main
|
ref: main
|
||||||
@@ -134,7 +132,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker Meta
|
- name: Docker Meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3
|
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3
|
||||||
@@ -144,7 +142,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker Meta free-threaded
|
- name: Docker Meta free-threaded
|
||||||
id: meta_ft
|
id: meta_ft
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3t
|
${{ secrets.DOCKER_USERNAME }}/moviepilot-v3t
|
||||||
@@ -153,13 +151,13 @@ jobs:
|
|||||||
type=raw,value=${{ env.app_version }}
|
type=raw,value=${{ env.app_version }}
|
||||||
|
|
||||||
- name: Set Up QEMU
|
- name: Set Up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set Up Buildx
|
- name: Set Up Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Build amd64 candidate
|
- name: Build amd64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -178,10 +176,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3-standard-docker-amd64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3-standard-docker-amd64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan amd64 candidate vulnerabilities
|
- name: Scan amd64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3-candidate:linux-amd64
|
image-ref: moviepilot-v3-candidate:linux-amd64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -191,7 +189,7 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Build arm64 candidate
|
- name: Build arm64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -210,10 +208,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3-standard-docker-arm64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3-standard-docker-arm64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan arm64 candidate vulnerabilities
|
- name: Scan arm64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3-candidate:linux-arm64
|
image-ref: moviepilot-v3-candidate:linux-arm64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -223,7 +221,7 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Build free-threaded amd64 candidate
|
- name: Build free-threaded amd64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -242,10 +240,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3t-docker-amd64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3t-docker-amd64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan free-threaded amd64 candidate vulnerabilities
|
- name: Scan free-threaded amd64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3t-candidate:linux-amd64
|
image-ref: moviepilot-v3t-candidate:linux-amd64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -255,7 +253,7 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Build free-threaded arm64 candidate
|
- name: Build free-threaded arm64 candidate
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -274,10 +272,10 @@ jobs:
|
|||||||
cache-to: type=gha,scope=moviepilot-v3t-docker-arm64,mode=max,version=2
|
cache-to: type=gha,scope=moviepilot-v3t-docker-arm64,mode=max,version=2
|
||||||
|
|
||||||
- name: Scan free-threaded arm64 candidate vulnerabilities
|
- name: Scan free-threaded arm64 candidate vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
|
uses: aquasecurity/trivy-action@v0.36.0
|
||||||
with:
|
with:
|
||||||
image-ref: moviepilot-v3t-candidate:linux-arm64
|
image-ref: moviepilot-v3t-candidate:linux-arm64
|
||||||
version: v0.70.0
|
version: latest
|
||||||
cache-dir: ${{ runner.temp }}/trivy
|
cache-dir: ${{ runner.temp }}/trivy
|
||||||
scanners: vuln
|
scanners: vuln
|
||||||
vuln-type: os,library
|
vuln-type: os,library
|
||||||
@@ -287,20 +285,20 @@ jobs:
|
|||||||
exit-code: 1
|
exit-code: 1
|
||||||
|
|
||||||
- name: Login DockerHub
|
- name: Login DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login GitHub Container Registry
|
- name: Login GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Publish multi-architecture image
|
- name: Publish multi-architecture image
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -332,7 +330,7 @@ jobs:
|
|||||||
type=gha,scope=moviepilot-v3-standard-docker-arm64,version=2
|
type=gha,scope=moviepilot-v3-standard-docker-arm64,version=2
|
||||||
|
|
||||||
- name: Publish free-threaded multi-architecture image
|
- name: Publish free-threaded multi-architecture image
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -481,52 +479,77 @@ jobs:
|
|||||||
|
|
||||||
- name: Get existing release body
|
- name: Get existing release body
|
||||||
id: get_release_body
|
id: get_release_body
|
||||||
continue-on-error: true
|
|
||||||
env:
|
env:
|
||||||
CHANGELOG: ${{ env.CHANGELOG }}
|
CHANGELOG: ${{ env.CHANGELOG }}
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
release_body=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
tag_name="v${{ env.app_version }}"
|
||||||
"https://api.github.com/repos/${{ github.repository }}/releases/tags/v${{ env.app_version }}" | \
|
response_file=$(mktemp)
|
||||||
jq -r '.body // ""')
|
error_file=$(mktemp)
|
||||||
|
trap 'rm -f "$response_file" "$error_file"' EXIT
|
||||||
|
|
||||||
|
if gh api --include \
|
||||||
|
"repos/${GITHUB_REPOSITORY}/releases/tags/${tag_name}" \
|
||||||
|
--jq '.body' > "$response_file" 2> "$error_file"; then
|
||||||
|
release_exists=true
|
||||||
|
release_body=$(awk 'body { sub(/\r$/, ""); print } /^[[:space:]]*$/ { body=1 }' "$response_file")
|
||||||
|
else
|
||||||
|
status_code=$(awk 'NR == 1 { print $2 }' "$response_file")
|
||||||
|
if [ "$status_code" = "404" ]; then
|
||||||
|
release_exists=false
|
||||||
|
release_body=""
|
||||||
|
else
|
||||||
|
cat "$error_file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "exists=$release_exists" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
# 如果已有手动编写的 release body,则保留;否则使用自动生成的 changelog
|
# 如果已有手动编写的 release body,则保留;否则使用自动生成的 changelog
|
||||||
if [ -n "$release_body" ] && [ "$release_body" != "null" ] && [ "$release_body" != "" ]; then
|
if [ -n "$release_body" ] && [ "$release_body" != "null" ] && [ "$release_body" != "" ]; then
|
||||||
echo "RELEASE_BODY<<EOF" >> $GITHUB_ENV
|
{
|
||||||
echo "$release_body" >> $GITHUB_ENV
|
echo "RELEASE_BODY<<EOF"
|
||||||
echo "EOF" >> $GITHUB_ENV
|
echo "$release_body"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
else
|
else
|
||||||
echo "RELEASE_BODY<<EOF" >> $GITHUB_ENV
|
{
|
||||||
echo "$CHANGELOG" >> $GITHUB_ENV
|
echo "RELEASE_BODY<<EOF"
|
||||||
echo "EOF" >> $GITHUB_ENV
|
echo "$CHANGELOG"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_ENV"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Delete Release
|
|
||||||
uses: dev-drprasad/delete-tag-and-release@v1.1
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
tag_name: v${{ env.app_version }}
|
|
||||||
delete_release: true
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Publish Release Tag
|
- name: Publish Release Tag
|
||||||
env:
|
env:
|
||||||
RELEASE_COMMIT: ${{ steps.release_snapshot.outputs.release_commit }}
|
RELEASE_COMMIT: ${{ steps.release_snapshot.outputs.release_commit }}
|
||||||
run: |
|
run: |
|
||||||
tag_name="v${{ env.app_version }}"
|
tag_name="v${{ env.app_version }}"
|
||||||
if git show-ref --verify --quiet "refs/tags/${tag_name}"; then
|
git tag -f "$tag_name" "$RELEASE_COMMIT"
|
||||||
git tag -d "$tag_name"
|
git push --force origin "refs/tags/${tag_name}"
|
||||||
fi
|
|
||||||
git tag "$tag_name" "$RELEASE_COMMIT"
|
|
||||||
git push origin "refs/tags/${tag_name}"
|
|
||||||
|
|
||||||
- name: Generate Release
|
- name: Publish Release
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
tag_name: v${{ env.app_version }}
|
|
||||||
name: v${{ env.app_version }}
|
|
||||||
body: ${{ env.RELEASE_BODY }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
make_latest: true
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
RELEASE_BODY: ${{ env.RELEASE_BODY }}
|
||||||
|
RELEASE_EXISTS: ${{ steps.get_release_body.outputs.exists }}
|
||||||
|
run: |
|
||||||
|
tag_name="v${{ env.app_version }}"
|
||||||
|
notes_file=$(mktemp)
|
||||||
|
trap 'rm -f "$notes_file"' EXIT
|
||||||
|
printf '%s\n' "$RELEASE_BODY" > "$notes_file"
|
||||||
|
if [ "$RELEASE_EXISTS" = "true" ]; then
|
||||||
|
gh release edit "$tag_name" \
|
||||||
|
--repo "$GITHUB_REPOSITORY" \
|
||||||
|
--title "$tag_name" \
|
||||||
|
--notes-file "$notes_file" \
|
||||||
|
--draft=false \
|
||||||
|
--prerelease=false \
|
||||||
|
--latest
|
||||||
|
else
|
||||||
|
gh release create "$tag_name" \
|
||||||
|
--repo "$GITHUB_REPOSITORY" \
|
||||||
|
--title "$tag_name" \
|
||||||
|
--notes-file "$notes_file" \
|
||||||
|
--latest
|
||||||
|
fi
|
||||||
|
|||||||
@@ -71,12 +71,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
with:
|
||||||
version: '0.12.5'
|
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: |
|
cache-dependency-glob: |
|
||||||
@@ -135,13 +134,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Build locked dependency stage
|
- name: Build locked dependency stage
|
||||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -177,7 +176,13 @@ jobs:
|
|||||||
assert not expected_free_threaded or callable(moviepilot_rust.zhconv_fast);
|
assert not expected_free_threaded or callable(moviepilot_rust.zhconv_fast);
|
||||||
assert sys._is_gil_enabled() == (not expected_free_threaded)"
|
assert sys._is_gil_enabled() == (not expected_free_threaded)"
|
||||||
|
|
||||||
- name: Verify pinned uv version
|
- name: Verify minimum uv version
|
||||||
env:
|
env:
|
||||||
IMAGE_TAG: ${{ matrix.image-tag }}
|
IMAGE_TAG: ${{ matrix.image-tag }}
|
||||||
run: docker run --rm "${IMAGE_TAG}" uv --version | grep -F 'uv 0.12.5'
|
run: >-
|
||||||
|
docker run --rm "${IMAGE_TAG}" /opt/venv/bin/python -c
|
||||||
|
"from packaging.version import Version;
|
||||||
|
import subprocess;
|
||||||
|
version = subprocess.check_output(['uv', '--version'], text=True).split()[1];
|
||||||
|
assert Version(version) >= Version('0.12.5'), version;
|
||||||
|
print(version)"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v7
|
- uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const issue = context.payload.issue;
|
const issue = context.payload.issue;
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/github-script@v7
|
- uses: actions/github-script@v9
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const labelRules = [
|
const labelRules = [
|
||||||
@@ -139,7 +139,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v5
|
- uses: actions/stale@v11
|
||||||
with:
|
with:
|
||||||
# 标记 stale 标签时间
|
# 标记 stale 标签时间
|
||||||
days-before-issue-stale: 30
|
days-before-issue-stale: 30
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
with:
|
||||||
version: '0.12.5'
|
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: |
|
cache-dependency-glob: |
|
||||||
|
|||||||
@@ -47,14 +47,13 @@ jobs:
|
|||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
with:
|
||||||
version: '0.12.5'
|
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: scripts/site_adapter_collector_requirements.txt
|
cache-dependency-glob: scripts/site_adapter_collector_requirements.txt
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
with:
|
||||||
version: '0.12.5'
|
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: |
|
cache-dependency-glob: |
|
||||||
@@ -101,12 +100,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
with:
|
||||||
version: '0.12.5'
|
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: |
|
cache-dependency-glob: |
|
||||||
@@ -127,12 +125,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
uses: astral-sh/setup-uv@v10.0.1
|
||||||
with:
|
with:
|
||||||
version: '0.12.5'
|
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-dependency-glob: |
|
cache-dependency-glob: |
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
ARG MOVIEPILOT_PYTHON_VARIANT="standard"
|
ARG MOVIEPILOT_PYTHON_VARIANT="standard"
|
||||||
ARG MOVIEPILOT_PYTHON_VERSION="3.14.7"
|
ARG MOVIEPILOT_PYTHON_VERSION="3.14.7"
|
||||||
|
|
||||||
FROM ghcr.io/astral-sh/uv:0.12.5@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 AS uv
|
FROM ghcr.io/astral-sh/uv:latest AS uv
|
||||||
|
|
||||||
|
|
||||||
FROM rclone/rclone:1.75.0@sha256:b06aed988cf5967de7c25be5925240983981c757f4ed1ac9d2fa659d51d60548 AS rclone
|
FROM rclone/rclone:1.75.0@sha256:b06aed988cf5967de7c25be5925240983981c757f4ed1ac9d2fa659d51d60548 AS rclone
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ MoviePilot V3 已经形成较清晰的模块化单体:`foundation`、`domain`
|
|||||||
| Python 源码量 | 约 271,400 行 | 60 个文件超过 1,000 行,14 个超过 2,000 行 |
|
| Python 源码量 | 约 271,400 行 | 60 个文件超过 1,000 行,14 个超过 2,000 行 |
|
||||||
| 长方法 | 281 个超过 80 行 | 67 个超过 150 行,23 个超过 250 行;大量是私有方法 |
|
| 长方法 | 281 个超过 80 行 | 67 个超过 150 行,23 个超过 250 行;大量是私有方法 |
|
||||||
| 全量 mypy 历史债务 | 11,808 / 596 文件 | strict frontier 当前覆盖 41 个文件,本批迁移路径的类型债务已清零 |
|
| 全量 mypy 历史债务 | 11,808 / 596 文件 | strict frontier 当前覆盖 41 个文件,本批迁移路径的类型债务已清零 |
|
||||||
| Ruff 历史诊断 | 841 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
|
| Ruff 历史诊断 | 840 | 低水位门禁通过,但规则集只覆盖 `E4/E7/E9/F/I` |
|
||||||
| 覆盖率低水位 | Application 79.39%,Domain 79.29% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
|
| 覆盖率低水位 | Application 79.39%,Domain 79.29% | Chain、Runtime、Agent、Adapter、Startup 未进入包级覆盖率门禁 |
|
||||||
|
|
||||||
### 3.3 热点文件
|
### 3.3 热点文件
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ canonical 主程序;兼容只经统一 Compat/SDK 门面提供。
|
|||||||
| S4-L2 Event strict contract | `PLANNED` | S0-L2.6,S1-L6 | 宿主事件输入/输出按风险 strict,诊断例外只属于第三方插件兼容 |
|
| S4-L2 Event strict contract | `PLANNED` | S0-L2.6,S1-L6 | 宿主事件输入/输出按风险 strict,诊断例外只属于第三方插件兼容 |
|
||||||
| S4-L3 Complexity v2 | `PLANNED` | S3 | 私有方法、class/file、圈复杂度进入门禁;所有超限通过职责拆分归零 |
|
| S4-L3 Complexity v2 | `PLANNED` | S3 | 私有方法、class/file、圈复杂度进入门禁;所有超限通过职责拆分归零 |
|
||||||
| S4-L4 全量 mypy 清零 | `PLANNED` | S3,S4-L1,S4-L2 | `mypy-baseline.json` 归零并删除债务接受路径,全宿主 strict 类型通过 |
|
| S4-L4 全量 mypy 清零 | `PLANNED` | S3,S4-L1,S4-L2 | `mypy-baseline.json` 归零并删除债务接受路径,全宿主 strict 类型通过 |
|
||||||
| S4-L5 Ruff 治理债务清零 | `PLANNED` | S3 | 当前受控 841 条诊断归零,规则集扩展经过独立审查且新增诊断为零 |
|
| S4-L5 Ruff 治理债务清零 | `PLANNED` | S3 | 当前受控 840 条诊断归零,规则集扩展经过独立审查且新增诊断为零 |
|
||||||
| S4-L6 Coverage/并发/质量证据 | `PLANNED` | S3,S4-L1,S4-L2 | 高风险包纳入 coverage;raw concurrency 分类清零;Module Quality 有真实 evidence test |
|
| S4-L6 Coverage/并发/质量证据 | `PLANNED` | S3,S4-L1,S4-L2 | 高风险包纳入 coverage;raw concurrency 分类清零;Module Quality 有真实 evidence test |
|
||||||
|
|
||||||
### S5:Plugin、Agent、Domain、Startup 与最终收口
|
### S5:Plugin、Agent、Domain、Startup 与最终收口
|
||||||
|
|||||||
+3
-3
@@ -11,7 +11,7 @@ curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v3/scripts/bootst
|
|||||||
脚本会自动:
|
脚本会自动:
|
||||||
|
|
||||||
- 检测操作系统
|
- 检测操作系统
|
||||||
- 自动检查并尽量安装 `git`、`curl`、`uv 0.12.5` 和 `Python 3.14+`
|
- 自动检查并尽量安装 `git`、`curl`、`uv 0.12.5+` 和 `Python 3.14+`
|
||||||
- 克隆 `MoviePilot`
|
- 克隆 `MoviePilot`
|
||||||
- 安装后端依赖
|
- 安装后端依赖
|
||||||
- 按当前仓库 `version.py` 中的 `FRONTEND_VERSION` 下载对应前端 release 的 `dist.zip`
|
- 按当前仓库 `version.py` 中的 `FRONTEND_VERSION` 下载对应前端 release 的 `dist.zip`
|
||||||
@@ -25,7 +25,7 @@ curl -fsSL https://raw.githubusercontent.com/jxxghp/MoviePilot/v3/scripts/bootst
|
|||||||
说明:
|
说明:
|
||||||
|
|
||||||
- 如果系统里已经有可用的 `Python 3.14+`,脚本会优先直接复用本地解释器
|
- 如果系统里已经有可用的 `Python 3.14+`,脚本会优先直接复用本地解释器
|
||||||
- 如果系统里没有可用解释器,脚本会通过固定版本的 uv 安装 Python 3.14
|
- 如果系统里没有可用解释器,脚本会通过最新稳定版 uv 安装 Python 3.14
|
||||||
- Linux 下安装系统依赖时通常需要 `sudo`
|
- Linux 下安装系统依赖时通常需要 `sudo`
|
||||||
- 复用已有仓库时,脚本现在只会因为已跟踪源码改动而阻止自动更新,不会再被 `.DS_Store` 之类未跟踪文件卡住
|
- 复用已有仓库时,脚本现在只会因为已跟踪源码改动而阻止自动更新,不会再被 `.DS_Store` 之类未跟踪文件卡住
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ moviepilot install deps --config-dir /path/to/moviepilot-config
|
|||||||
说明:
|
说明:
|
||||||
|
|
||||||
- 默认会自动选择本地已安装的 `Python 3.14+` 解释器
|
- 默认会自动选择本地已安装的 `Python 3.14+` 解释器
|
||||||
- 安装器要求 `uv 0.12.5`,并按仓库提交的 `uv.lock` 同步依赖;不会在本地重新解析一套未锁定结果
|
- 安装器要求 `uv 0.12.5+`,并按仓库提交的 `uv.lock` 同步依赖;不会在本地重新解析一套未锁定结果
|
||||||
- `moviepilot_rust` 加速扩展通过 `moviepilot-rust` PyPI 依赖安装,主项目本地安装不需要 Rust toolchain
|
- `moviepilot_rust` 加速扩展通过 `moviepilot-rust` PyPI 依赖安装,主项目本地安装不需要 Rust toolchain
|
||||||
- 安装完成后可在前端“高级设置 - 实验室”中关闭或重新开启 Rust 加速;如果后端未加载扩展,该开关会保持关闭且不可操作
|
- 安装完成后可在前端“高级设置 - 实验室”中关闭或重新开启 Rust 加速;如果后端未加载扩展,该开关会保持关闭且不可操作
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
在开始之前,请确保您的系统已安装以下软件:
|
在开始之前,请确保您的系统已安装以下软件:
|
||||||
|
|
||||||
- **Python 3.14+**
|
- **Python 3.14+**
|
||||||
- **uv 0.12.5**(Python 版本、虚拟环境和依赖锁定工具)
|
- **uv 0.12.5+**(Python 版本、虚拟环境和依赖锁定工具,推荐使用最新稳定版)
|
||||||
- **Git** (用于版本控制)
|
- **Git** (用于版本控制)
|
||||||
- **RAR 解压工具**:本地开发如需测试或使用 `.rar` 字幕包解压,请安装 `unar`、`unrar`、`7z` 或 `bsdtar` 之一;Docker 镜像会内置 `unar`。
|
- **RAR 解压工具**:本地开发如需测试或使用 `.rar` 字幕包解压,请安装 `unar`、`unrar`、`7z` 或 `bsdtar` 之一;Docker 镜像会内置 `unar`。
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ chmod +x scripts/start-local.sh
|
|||||||
1. **共享运行时依赖**:被 `app/` 生产代码直接导入,或是生产功能、后台任务、插件框架启动必需,写入 `[project].dependencies`。
|
1. **共享运行时依赖**:被 `app/` 生产代码直接导入,或是生产功能、后台任务、插件框架启动必需,写入 `[project].dependencies`。
|
||||||
2. **ABI 敏感运行依赖**:标准与 free-threaded 解释器必须选择不同制品或版本时,分别写入 `runtime-standard` 和 `runtime-free-threaded`;两组保持互斥并由运行时统一选择。
|
2. **ABI 敏感运行依赖**:标准与 free-threaded 解释器必须选择不同制品或版本时,分别写入 `runtime-standard` 和 `runtime-free-threaded`;两组保持互斥并由运行时统一选择。
|
||||||
3. **开发 / 测试 / 静态检查 / 构建依赖**:只用于单测、覆盖率、lint 辅助、源码构建等,写入 `[dependency-groups].dev`。
|
3. **开发 / 测试 / 静态检查 / 构建依赖**:只用于单测、覆盖率、lint 辅助、源码构建等,写入 `[dependency-groups].dev`。
|
||||||
4. **工具依赖**:仓库要求使用 `uv 0.12.5`;不应为了安装工具而把它加入主程序运行依赖。
|
4. **工具依赖**:仓库要求使用 `uv 0.12.5+`,推荐使用最新稳定版;不应为了安装工具而把它加入主程序运行依赖。
|
||||||
5. **插件依赖**:由插件清单声明并在插件安装阶段处理,不直接并入主程序依赖。
|
5. **插件依赖**:由插件清单声明并在插件安装阶段处理,不直接并入主程序依赖。
|
||||||
|
|
||||||
修改后更新并校验锁文件:
|
修改后更新并校验锁文件:
|
||||||
@@ -169,13 +169,13 @@ python -m scripts.generate_plugin_market_default \
|
|||||||
|
|
||||||
### 5. 运行依赖漏洞检查
|
### 5. 运行依赖漏洞检查
|
||||||
|
|
||||||
正式发布会使用固定版本的 `pip-audit` 检查 `uv.lock` 锁定的运行时依赖。依赖变更后也可以在
|
正式发布会使用最新稳定版 `pip-audit` 检查 `uv.lock` 锁定的运行时依赖。依赖变更后也可以在
|
||||||
本地执行同一检查:
|
本地执行同一检查:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv export --quiet --locked --no-dev --no-emit-project \
|
uv export --quiet --locked --no-dev --no-emit-project \
|
||||||
--output-file /tmp/moviepilot-audit-requirements.txt
|
--output-file /tmp/moviepilot-audit-requirements.txt
|
||||||
uvx --from pip-audit==2.10.1 pip-audit \
|
uvx --from pip-audit pip-audit \
|
||||||
--require-hashes --disable-pip --strict --progress-spinner off \
|
--require-hashes --disable-pip --strict --progress-spinner off \
|
||||||
--requirement /tmp/moviepilot-audit-requirements.txt
|
--requirement /tmp/moviepilot-audit-requirements.txt
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Project metadata | `pyproject.toml` — runtime dependencies in `[project].dependencies`, development tooling in `[dependency-groups].dev` |
|
| Project metadata | `pyproject.toml` — runtime dependencies in `[project].dependencies`, development tooling in `[dependency-groups].dev` |
|
||||||
| Lock | `uv.lock` — committed resolution for Python 3.14+ and supported platforms |
|
| Lock | `uv.lock` — committed resolution for Python 3.14+ and supported platforms |
|
||||||
| Package manager | uv 0.12.5 |
|
| Package manager | uv 0.12.5+(推荐最新稳定版) |
|
||||||
| Runtime install | `uv sync --locked --no-dev --no-install-project` |
|
| Runtime install | `uv sync --locked --no-dev --no-install-project` |
|
||||||
| Dev/test/lint/build install | `uv sync --locked` |
|
| Dev/test/lint/build install | `uv sync --locked` |
|
||||||
| Supported platforms | Linux x86_64/arm64, macOS x86_64/arm64, Windows x64 |
|
| Supported platforms | Linux x86_64/arm64, macOS x86_64/arm64, Windows x64 |
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
| pytest | Test runner | `uv run --locked --no-sync pytest tests/test_xxx.py` |
|
| pytest | Test runner | `uv run --locked --no-sync pytest tests/test_xxx.py` |
|
||||||
| pylint | Static analysis | `uv run --locked --no-sync pylint app/` |
|
| pylint | Static analysis | `uv run --locked --no-sync pylint app/` |
|
||||||
| uv | Lock and environment consistency | `uv lock --check && uv sync --locked --offline --inexact --no-dev --check` |
|
| uv | Lock and environment consistency | `uv lock --check && uv sync --locked --offline --inexact --no-dev --check` |
|
||||||
| pip-audit | Locked dependency vulnerability scan | `uv export --quiet --locked --no-dev --no-emit-project -o /tmp/moviepilot-audit-requirements.txt && uvx --from pip-audit==2.10.1 pip-audit --require-hashes --disable-pip --strict --progress-spinner off -r /tmp/moviepilot-audit-requirements.txt` |
|
| pip-audit | Locked dependency vulnerability scan | `uv export --quiet --locked --no-dev --no-emit-project -o /tmp/moviepilot-audit-requirements.txt && uvx --from pip-audit pip-audit --require-hashes --disable-pip --strict --progress-spinner off -r /tmp/moviepilot-audit-requirements.txt` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ uv sync --locked --offline --inexact --no-dev --check
|
|||||||
- Test, coverage, lint, and explicit build tooling belong in `[dependency-groups].dev`.
|
- Test, coverage, lint, and explicit build tooling belong in `[dependency-groups].dev`.
|
||||||
- Commit the updated `uv.lock`; do not maintain or generate main-program requirements files.
|
- Commit the updated `uv.lock`; do not maintain or generate main-program requirements files.
|
||||||
- `uv pip check` is diagnostic only because unmaintained third-party metadata may name a compatible superseded distribution.
|
- `uv pip check` is diagnostic only because unmaintained third-party metadata may name a compatible superseded distribution.
|
||||||
- Use uv 0.12.5 and Python 3.14+.
|
- Use uv 0.12.5+ and Python 3.14+; prefer the latest stable uv release.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ uv run --locked --no-sync pylint app/chain/download.py
|
|||||||
```bash
|
```bash
|
||||||
uv export --quiet --locked --no-dev --no-emit-project \
|
uv export --quiet --locked --no-dev --no-emit-project \
|
||||||
--output-file /tmp/moviepilot-audit-requirements.txt
|
--output-file /tmp/moviepilot-audit-requirements.txt
|
||||||
uvx --from pip-audit==2.10.1 pip-audit \
|
uvx --from pip-audit pip-audit \
|
||||||
--require-hashes --disable-pip --strict --progress-spinner off \
|
--require-hashes --disable-pip --strict --progress-spinner off \
|
||||||
--requirement /tmp/moviepilot-audit-requirements.txt
|
--requirement /tmp/moviepilot-audit-requirements.txt
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ uv run --locked --no-sync pylint app/
|
|||||||
```bash
|
```bash
|
||||||
uv export --quiet --locked --no-dev --no-emit-project \
|
uv export --quiet --locked --no-dev --no-emit-project \
|
||||||
--output-file /tmp/moviepilot-audit-requirements.txt
|
--output-file /tmp/moviepilot-audit-requirements.txt
|
||||||
uvx --from pip-audit==2.10.1 pip-audit \
|
uvx --from pip-audit pip-audit \
|
||||||
--require-hashes --disable-pip --strict --progress-spinner off \
|
--require-hashes --disable-pip --strict --progress-spinner off \
|
||||||
--requirement /tmp/moviepilot-audit-requirements.txt
|
--requirement /tmp/moviepilot-audit-requirements.txt
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -135,7 +135,7 @@ select = ["E4", "E7", "E9", "F", "I"]
|
|||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
package = false
|
package = false
|
||||||
required-version = "==0.12.5"
|
required-version = ">=0.12.5"
|
||||||
default-groups = ["dev", "runtime-standard"]
|
default-groups = ["dev", "runtime-standard"]
|
||||||
conflicts = [
|
conflicts = [
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ SUPERUSER=""
|
|||||||
SUPERUSER_PASSWORD=""
|
SUPERUSER_PASSWORD=""
|
||||||
OS_NAME="Unknown"
|
OS_NAME="Unknown"
|
||||||
PYTHON_BIN=""
|
PYTHON_BIN=""
|
||||||
UV_VERSION="0.12.5"
|
MIN_UV_VERSION="0.12.5"
|
||||||
BREW_BIN=""
|
BREW_BIN=""
|
||||||
PACKAGE_MANAGER=""
|
PACKAGE_MANAGER=""
|
||||||
PACKAGE_INDEX_UPDATED="false"
|
PACKAGE_INDEX_UPDATED="false"
|
||||||
@@ -384,20 +384,43 @@ ensure_base_tools() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uv_version_supported() {
|
||||||
|
local actual="$1"
|
||||||
|
local minimum="$2"
|
||||||
|
local -a actual_parts minimum_parts
|
||||||
|
local index actual_part minimum_part
|
||||||
|
|
||||||
|
IFS=. read -r -a actual_parts <<< "$actual"
|
||||||
|
IFS=. read -r -a minimum_parts <<< "$minimum"
|
||||||
|
for index in 0 1 2; do
|
||||||
|
actual_part="${actual_parts[$index]:-0}"
|
||||||
|
minimum_part="${minimum_parts[$index]:-0}"
|
||||||
|
[[ "$actual_part" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
[[ "$minimum_part" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
if (( 10#$actual_part > 10#$minimum_part )); then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if (( 10#$actual_part < 10#$minimum_part )); then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
ensure_uv() {
|
ensure_uv() {
|
||||||
if command -v uv >/dev/null 2>&1 \
|
if command -v uv >/dev/null 2>&1 \
|
||||||
&& [[ "$(uv --version 2>/dev/null | awk '{print $2}')" == "${UV_VERSION}" ]]; then
|
&& uv_version_supported "$(uv --version 2>/dev/null | awk '{print $2}')" "${MIN_UV_VERSION}"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> 自动安装 uv ${UV_VERSION}"
|
echo "==> 自动安装最新稳定版 uv"
|
||||||
env UV_INSTALL_DIR="$HOME/.local/bin" sh -c "$(curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh")"
|
env UV_INSTALL_DIR="$HOME/.local/bin" sh -c "$(curl -LsSf https://astral.sh/uv/install.sh)"
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
hash -r
|
hash -r
|
||||||
|
|
||||||
if ! command -v uv >/dev/null 2>&1 \
|
if ! command -v uv >/dev/null 2>&1 \
|
||||||
|| [[ "$(uv --version 2>/dev/null | awk '{print $2}')" != "${UV_VERSION}" ]]; then
|
|| ! uv_version_supported "$(uv --version 2>/dev/null | awk '{print $2}')" "${MIN_UV_VERSION}"; then
|
||||||
echo "uv ${UV_VERSION} 安装失败,无法继续自动安装 Python。" >&2
|
echo "uv ${MIN_UV_VERSION}+ 安装失败,无法继续自动安装 Python。" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-6
@@ -9,8 +9,8 @@ import importlib.util
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import secrets
|
|
||||||
import re
|
import re
|
||||||
|
import secrets
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -36,7 +36,8 @@ NODE_DIR = RUNTIME_DIR / "node"
|
|||||||
INSTALL_ENV_FILE = ROOT / ".moviepilot.env"
|
INSTALL_ENV_FILE = ROOT / ".moviepilot.env"
|
||||||
MIN_PYTHON_VERSION = (3, 14)
|
MIN_PYTHON_VERSION = (3, 14)
|
||||||
SUPPORTED_PYTHON_TEXT = "Python 3.14+"
|
SUPPORTED_PYTHON_TEXT = "Python 3.14+"
|
||||||
UV_VERSION = "0.12.5"
|
MIN_UV_VERSION = (0, 12, 5)
|
||||||
|
MIN_UV_VERSION_TEXT = ".".join(str(part) for part in MIN_UV_VERSION)
|
||||||
|
|
||||||
CONFIG_DIR = LEGACY_CONFIG_DIR
|
CONFIG_DIR = LEGACY_CONFIG_DIR
|
||||||
LOG_DIR = CONFIG_DIR / "logs"
|
LOG_DIR = CONFIG_DIR / "logs"
|
||||||
@@ -618,18 +619,27 @@ def get_venv_bin_dir(venv_dir: Path) -> Path:
|
|||||||
return venv_dir / "bin"
|
return venv_dir / "bin"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_uv_version(output: str) -> tuple[int, int, int] | None:
|
||||||
|
"""从 uv 版本输出中提取稳定版三段版本号。"""
|
||||||
|
match = re.match(r"^uv\s+(\d+)\.(\d+)\.(\d+)(?:\s|$)", output.strip())
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
return tuple(int(part) for part in match.groups())
|
||||||
|
|
||||||
|
|
||||||
def require_uv() -> Path:
|
def require_uv() -> Path:
|
||||||
"""返回仓库要求版本的 uv,避免不同安装入口使用不同解析器。"""
|
"""返回满足仓库最低版本要求的 uv。"""
|
||||||
uv_command = shutil.which("uv")
|
uv_command = shutil.which("uv")
|
||||||
if not uv_command:
|
if not uv_command:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"未找到 uv {UV_VERSION},请先安装后重新执行。"
|
f"未找到 uv {MIN_UV_VERSION_TEXT}+,请先安装后重新执行。"
|
||||||
)
|
)
|
||||||
uv_bin = Path(uv_command).expanduser().resolve()
|
uv_bin = Path(uv_command).expanduser().resolve()
|
||||||
version = capture([str(uv_bin), "--version"])
|
version = capture([str(uv_bin), "--version"])
|
||||||
if version.split()[:2] != ["uv", UV_VERSION]:
|
parsed_version = parse_uv_version(version)
|
||||||
|
if parsed_version is None or parsed_version < MIN_UV_VERSION:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"MoviePilot 需要 uv {UV_VERSION},当前为 {version or '未知版本'}。"
|
f"MoviePilot 需要 uv {MIN_UV_VERSION_TEXT}+,当前为 {version or '未知版本'}。"
|
||||||
)
|
)
|
||||||
return uv_bin
|
return uv_bin
|
||||||
|
|
||||||
|
|||||||
@@ -1484,9 +1484,6 @@
|
|||||||
"tests/test_release_group.py": {
|
"tests/test_release_group.py": {
|
||||||
"I001": 1
|
"I001": 1
|
||||||
},
|
},
|
||||||
"tests/test_release_supply_chain.py": {
|
|
||||||
"I001": 1
|
|
||||||
},
|
|
||||||
"tests/test_reliability_adr.py": {
|
"tests/test_reliability_adr.py": {
|
||||||
"I001": 1
|
"I001": 1
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,11 +30,7 @@ def _write_bundle(path: Path, label: str, *, extra_files: tuple[str, ...] = ())
|
|||||||
def test_dockerfile_control_bundle_build_checks_fail_closed() -> None:
|
def test_dockerfile_control_bundle_build_checks_fail_closed() -> None:
|
||||||
dockerfile = (ROOT / "docker" / "Dockerfile").read_text(encoding="utf-8")
|
dockerfile = (ROOT / "docker" / "Dockerfile").read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert (
|
assert "FROM ghcr.io/astral-sh/uv:latest AS uv" in dockerfile
|
||||||
"FROM ghcr.io/astral-sh/uv:0.12.5@sha256:"
|
|
||||||
"e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 AS uv"
|
|
||||||
in dockerfile
|
|
||||||
)
|
|
||||||
assert "COPY --from=uv /uv /usr/local/bin/uv" in dockerfile
|
assert "COPY --from=uv /uv /usr/local/bin/uv" in dockerfile
|
||||||
assert "COPY pyproject.toml uv.lock ./" in dockerfile
|
assert "COPY pyproject.toml uv.lock ./" in dockerfile
|
||||||
assert "python3 -m venv --without-pip ${VENV_PATH}" in dockerfile
|
assert "python3 -m venv --without-pip ${VENV_PATH}" in dockerfile
|
||||||
|
|||||||
@@ -217,17 +217,37 @@ class LocalSetupConfigDirTests(unittest.TestCase):
|
|||||||
self.assertIn("https://mirror.example/simple?token=abc", redacted)
|
self.assertIn("https://mirror.example/simple?token=abc", redacted)
|
||||||
self.assertNotIn("user:pass", " ".join(redacted))
|
self.assertNotIn("user:pass", " ".join(redacted))
|
||||||
|
|
||||||
def test_require_uv_accepts_repository_version(self):
|
def test_require_uv_accepts_minimum_version(self):
|
||||||
module = load_local_setup_module()
|
module = load_local_setup_module()
|
||||||
uv_bin = Path("/opt/moviepilot/bin/uv")
|
uv_bin = Path("/opt/moviepilot/bin/uv")
|
||||||
|
|
||||||
with patch.object(module.shutil, "which", return_value=str(uv_bin)), patch.object(
|
with patch.object(module.shutil, "which", return_value=str(uv_bin)), patch.object(
|
||||||
module, "capture", return_value=f"uv {module.UV_VERSION} (test-target)"
|
module, "capture", return_value=f"uv {module.MIN_UV_VERSION_TEXT} (test-target)"
|
||||||
):
|
):
|
||||||
result = module.require_uv()
|
result = module.require_uv()
|
||||||
|
|
||||||
self.assertEqual(result, uv_bin.resolve())
|
self.assertEqual(result, uv_bin.resolve())
|
||||||
|
|
||||||
|
def test_require_uv_accepts_newer_version(self):
|
||||||
|
module = load_local_setup_module()
|
||||||
|
uv_bin = Path("/opt/moviepilot/bin/uv")
|
||||||
|
|
||||||
|
with patch.object(module.shutil, "which", return_value=str(uv_bin)), patch.object(
|
||||||
|
module, "capture", return_value="uv 1.0.0 (test-target)"
|
||||||
|
):
|
||||||
|
result = module.require_uv()
|
||||||
|
|
||||||
|
self.assertEqual(result, uv_bin.resolve())
|
||||||
|
|
||||||
|
def test_require_uv_rejects_older_version(self):
|
||||||
|
module = load_local_setup_module()
|
||||||
|
uv_bin = Path("/opt/moviepilot/bin/uv")
|
||||||
|
|
||||||
|
with patch.object(module.shutil, "which", return_value=str(uv_bin)), patch.object(
|
||||||
|
module, "capture", return_value="uv 0.12.4 (test-target)"
|
||||||
|
), self.assertRaisesRegex(RuntimeError, "需要 uv 0.12.5\\+"):
|
||||||
|
module.require_uv()
|
||||||
|
|
||||||
def test_windows_expose_uv_keeps_existing_source_when_target_is_same(self):
|
def test_windows_expose_uv_keeps_existing_source_when_target_is_same(self):
|
||||||
module = load_local_setup_module()
|
module = load_local_setup_module()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
"""正式镜像发布的供应链门禁合同。"""
|
"""正式镜像发布的供应链门禁合同。"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -8,12 +11,30 @@ from ruamel.yaml import YAML
|
|||||||
|
|
||||||
from scripts.normalize_audit_requirements import normalize_requirements
|
from scripts.normalize_audit_requirements import normalize_requirements
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
DOCKERFILE = ROOT / "docker" / "Dockerfile"
|
DOCKERFILE = ROOT / "docker" / "Dockerfile"
|
||||||
RELEASE_WORKFLOW = ROOT / ".github" / "workflows" / "build-v3.yml"
|
RELEASE_WORKFLOW = ROOT / ".github" / "workflows" / "build-v3.yml"
|
||||||
BETA_WORKFLOW = ROOT / ".github" / "workflows" / "beta.yml"
|
BETA_WORKFLOW = ROOT / ".github" / "workflows" / "beta.yml"
|
||||||
|
PR_AGENT_WORKFLOW = ROOT / ".github" / "workflows" / "pr-agent.yml"
|
||||||
TRIVY_IGNORE = ROOT / ".trivyignore.yaml"
|
TRIVY_IGNORE = ROOT / ".trivyignore.yaml"
|
||||||
|
WORKFLOW_ROOT = ROOT / ".github" / "workflows"
|
||||||
|
|
||||||
|
ALLOWED_ACTION_REFS = {
|
||||||
|
"actions/checkout@v7",
|
||||||
|
"actions/setup-python@v7",
|
||||||
|
"actions/github-script@v9",
|
||||||
|
"actions/stale@v11",
|
||||||
|
"astral-sh/setup-uv@v10.0.1",
|
||||||
|
"docker/metadata-action@v6",
|
||||||
|
"docker/setup-qemu-action@v4",
|
||||||
|
"docker/setup-buildx-action@v4",
|
||||||
|
"docker/build-push-action@v7",
|
||||||
|
"docker/login-action@v4",
|
||||||
|
"aquasecurity/trivy-action@v0.36.0",
|
||||||
|
"actions/upload-artifact@v7",
|
||||||
|
"actions/download-artifact@v8",
|
||||||
|
"docker://ghcr.io/infinitypacer/pr-review-runner:latest",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _load_workflow(path: Path = RELEASE_WORKFLOW) -> dict:
|
def _load_workflow(path: Path = RELEASE_WORKFLOW) -> dict:
|
||||||
@@ -31,6 +52,66 @@ def _steps_by_name(workflow: dict) -> dict[str, dict]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _write_fake_gh(tmp_path: Path) -> Path:
|
||||||
|
"""创建可控制响应和退出状态的 gh 测试替身。"""
|
||||||
|
bin_dir = tmp_path / "bin"
|
||||||
|
bin_dir.mkdir()
|
||||||
|
gh = bin_dir / "gh"
|
||||||
|
gh.write_text(
|
||||||
|
"""#!/usr/bin/env bash
|
||||||
|
printf '%s\n' "$*" >> "$GH_LOG"
|
||||||
|
cat "$GH_RESPONSE_FILE"
|
||||||
|
cat "$GH_ERROR_FILE" >&2
|
||||||
|
exit "$GH_EXIT_CODE"
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
gh.chmod(0o755)
|
||||||
|
return bin_dir
|
||||||
|
|
||||||
|
|
||||||
|
def _run_release_script(
|
||||||
|
script: str,
|
||||||
|
tmp_path: Path,
|
||||||
|
*,
|
||||||
|
response: str = "",
|
||||||
|
error: str = "",
|
||||||
|
exit_code: int = 0,
|
||||||
|
extra_env: dict[str, str] | None = None,
|
||||||
|
) -> subprocess.CompletedProcess[str]:
|
||||||
|
"""在隔离的 gh 替身环境中执行发布 workflow 脚本。"""
|
||||||
|
bash = shutil.which("bash")
|
||||||
|
if bash is None:
|
||||||
|
pytest.skip("release workflow contract requires Bash")
|
||||||
|
response_file = tmp_path / "response.txt"
|
||||||
|
error_file = tmp_path / "error.txt"
|
||||||
|
response_file.write_text(response, encoding="utf-8")
|
||||||
|
error_file.write_text(error, encoding="utf-8")
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.update(
|
||||||
|
{
|
||||||
|
"PATH": f"{_write_fake_gh(tmp_path)}:{env['PATH']}",
|
||||||
|
"GH_RESPONSE_FILE": str(response_file),
|
||||||
|
"GH_ERROR_FILE": str(error_file),
|
||||||
|
"GH_EXIT_CODE": str(exit_code),
|
||||||
|
"GH_LOG": str(tmp_path / "gh.log"),
|
||||||
|
"GITHUB_REPOSITORY": "jxxghp/MoviePilot",
|
||||||
|
"GITHUB_ENV": str(tmp_path / "github.env"),
|
||||||
|
"GITHUB_OUTPUT": str(tmp_path / "github.output"),
|
||||||
|
"CHANGELOG": "generated changelog",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
env.update(extra_env or {})
|
||||||
|
return subprocess.run(
|
||||||
|
[bash, "-euo", "pipefail", "-c", script],
|
||||||
|
cwd=tmp_path,
|
||||||
|
env=env,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_base_image_uses_refreshable_tag_and_apt_does_not_upgrade_in_place() -> None:
|
def test_base_image_uses_refreshable_tag_and_apt_does_not_upgrade_in_place() -> None:
|
||||||
"""基础镜像允许更新,并仅显式刷新运行时安全包而非整套 Debian。"""
|
"""基础镜像允许更新,并仅显式刷新运行时安全包而非整套 Debian。"""
|
||||||
dockerfile = DOCKERFILE.read_text(encoding="utf-8")
|
dockerfile = DOCKERFILE.read_text(encoding="utf-8")
|
||||||
@@ -62,7 +143,8 @@ def test_release_audits_locked_runtime_dependencies_before_building() -> None:
|
|||||||
assert "--group runtime-standard" in audit
|
assert "--group runtime-standard" in audit
|
||||||
assert "--group runtime-free-threaded" in audit
|
assert "--group runtime-free-threaded" in audit
|
||||||
assert "scripts/normalize_audit_requirements.py" in audit
|
assert "scripts/normalize_audit_requirements.py" in audit
|
||||||
assert "pip-audit==2.10.1" in audit
|
assert "uvx --from pip-audit pip-audit" in audit
|
||||||
|
assert "pip-audit==" not in audit
|
||||||
for option in ("--require-hashes", "--no-deps", "--disable-pip", "--strict"):
|
for option in ("--require-hashes", "--no-deps", "--disable-pip", "--strict"):
|
||||||
assert option in audit
|
assert option in audit
|
||||||
|
|
||||||
@@ -142,12 +224,9 @@ def test_release_scans_both_architectures_before_registry_login_and_publish() ->
|
|||||||
):
|
):
|
||||||
scan = indexed[name]
|
scan = indexed[name]
|
||||||
assert scan["with"]["cache-dir"] == "${{ runner.temp }}/trivy"
|
assert scan["with"]["cache-dir"] == "${{ runner.temp }}/trivy"
|
||||||
assert scan["uses"] == (
|
assert scan["uses"] == "aquasecurity/trivy-action@v0.36.0"
|
||||||
"aquasecurity/trivy-action@"
|
|
||||||
"a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8"
|
|
||||||
)
|
|
||||||
assert scan["with"].items() >= {
|
assert scan["with"].items() >= {
|
||||||
"version": "v0.70.0",
|
"version": "latest",
|
||||||
"scanners": "vuln",
|
"scanners": "vuln",
|
||||||
"vuln-type": "os,library",
|
"vuln-type": "os,library",
|
||||||
"severity": "HIGH,CRITICAL",
|
"severity": "HIGH,CRITICAL",
|
||||||
@@ -170,6 +249,158 @@ def test_release_scans_both_architectures_before_registry_login_and_publish() ->
|
|||||||
assert last_scan < names.index("Publish multi-architecture image")
|
assert last_scan < names.index("Publish multi-architecture image")
|
||||||
assert last_scan < names.index("Publish free-threaded multi-architecture image")
|
assert last_scan < names.index("Publish free-threaded multi-architecture image")
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflows_follow_maintained_action_channels() -> None:
|
||||||
|
"""官方工具使用批准的稳定引用,不引入未知来源或手工 commit SHA。"""
|
||||||
|
for workflow_path in sorted(WORKFLOW_ROOT.glob("*.yml")):
|
||||||
|
workflow = _load_workflow(workflow_path)
|
||||||
|
for job_name, job in workflow.get("jobs", {}).items():
|
||||||
|
for step in job.get("steps", []):
|
||||||
|
uses = step.get("uses")
|
||||||
|
if uses:
|
||||||
|
assert uses in ALLOWED_ACTION_REFS, (
|
||||||
|
f"{workflow_path}:{job_name}:{step.get('name', '<unnamed>')}: {uses}"
|
||||||
|
)
|
||||||
|
if uses == "astral-sh/setup-uv@v10.0.1":
|
||||||
|
assert "version" not in step.get("with", {})
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_workflows_are_valid_yaml() -> None:
|
||||||
|
"""所有 GitHub Actions 工作流都必须能被 YAML 1.2 解析。"""
|
||||||
|
for workflow_path in sorted(WORKFLOW_ROOT.glob("*.yml")):
|
||||||
|
workflow = _load_workflow(workflow_path)
|
||||||
|
assert isinstance(workflow, dict), workflow_path
|
||||||
|
assert isinstance(workflow.get("jobs"), dict), workflow_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_pr_agent_keeps_pull_request_target_api_only_boundary() -> None:
|
||||||
|
"""带凭据的 PR 审查只读 GitHub API,不 checkout 或执行 PR 分支代码。"""
|
||||||
|
workflow = _load_workflow(PR_AGENT_WORKFLOW)
|
||||||
|
assert "pull_request_target" in workflow["on"]
|
||||||
|
assert workflow["permissions"] == {
|
||||||
|
"contents": "read",
|
||||||
|
"pull-requests": "write",
|
||||||
|
"issues": "write",
|
||||||
|
}
|
||||||
|
steps = workflow["jobs"]["pr-agent"]["steps"]
|
||||||
|
assert len(steps) == 1
|
||||||
|
review_step = steps[0]
|
||||||
|
assert review_step["uses"] == "docker://ghcr.io/infinitypacer/pr-review-runner:latest"
|
||||||
|
assert "run" not in review_step
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_uses_github_cli_for_tag_and_release_lifecycle() -> None:
|
||||||
|
"""正式发布复用 GitHub CLI,并只把明确不存在识别为新 Release。"""
|
||||||
|
workflow = _load_workflow()
|
||||||
|
indexed = _steps_by_name(workflow)
|
||||||
|
serialized = RELEASE_WORKFLOW.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "dev-drprasad/delete-tag-and-release" not in serialized
|
||||||
|
assert "softprops/action-gh-release" not in serialized
|
||||||
|
release_query = indexed["Get existing release body"]["run"]
|
||||||
|
assert "gh api --include" in release_query
|
||||||
|
assert 'if [ "$status_code" = "404" ]' in release_query
|
||||||
|
assert "cat \"$error_file\" >&2\n exit 1" in release_query
|
||||||
|
assert "gh release delete" not in serialized
|
||||||
|
assert 'git tag -f "$tag_name" "$RELEASE_COMMIT"' in indexed["Publish Release Tag"]["run"]
|
||||||
|
assert 'git push --force origin "refs/tags/${tag_name}"' in indexed["Publish Release Tag"]["run"]
|
||||||
|
publish_release = indexed["Publish Release"]["run"]
|
||||||
|
assert 'if [ "$RELEASE_EXISTS" = "true" ]' in publish_release
|
||||||
|
assert "gh release edit" in publish_release
|
||||||
|
assert "gh release create" in publish_release
|
||||||
|
assert '--notes-file "$notes_file"' in publish_release
|
||||||
|
assert "--draft=false" in publish_release
|
||||||
|
assert "--prerelease=false" in publish_release
|
||||||
|
assert "--latest" in publish_release
|
||||||
|
names = [step.get("name") for step in workflow["jobs"]["Docker-build"]["steps"]]
|
||||||
|
assert names.index("Get existing release body") < names.index("Publish Release Tag")
|
||||||
|
assert names.index("Publish Release Tag") < names.index("Publish Release")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("response", "exit_code", "expected_exists", "expected_body"),
|
||||||
|
[
|
||||||
|
("HTTP/2.0 200 OK\nHeader: value\n\nmanual body\n", 0, "true", "manual body"),
|
||||||
|
("HTTP/2.0 404 Not Found\n\n", 1, "false", "generated changelog"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_release_query_preserves_existing_body_or_handles_explicit_404(
|
||||||
|
tmp_path: Path,
|
||||||
|
response: str,
|
||||||
|
exit_code: int,
|
||||||
|
expected_exists: str,
|
||||||
|
expected_body: str,
|
||||||
|
) -> None:
|
||||||
|
"""已有 Release 保留正文,只有明确 404 才使用自动变更记录。"""
|
||||||
|
script = _steps_by_name(_load_workflow())["Get existing release body"]["run"]
|
||||||
|
script = script.replace("v${{ env.app_version }}", "v3.0.0")
|
||||||
|
|
||||||
|
result = _run_release_script(script, tmp_path, response=response, exit_code=exit_code)
|
||||||
|
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
output = (tmp_path / "github.output").read_text(encoding="utf-8")
|
||||||
|
environment = (tmp_path / "github.env").read_text(encoding="utf-8")
|
||||||
|
assert f"exists={expected_exists}" in output
|
||||||
|
assert expected_body in environment
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_query_fails_closed_on_non_404_error(tmp_path: Path) -> None:
|
||||||
|
"""网络或服务端错误不得伪装成 Release 不存在。"""
|
||||||
|
script = _steps_by_name(_load_workflow())["Get existing release body"]["run"]
|
||||||
|
script = script.replace("v${{ env.app_version }}", "v3.0.0")
|
||||||
|
|
||||||
|
result = _run_release_script(
|
||||||
|
script,
|
||||||
|
tmp_path,
|
||||||
|
response="HTTP/2.0 500 Internal Server Error\n\n",
|
||||||
|
error="GitHub API unavailable\n",
|
||||||
|
exit_code=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert "GitHub API unavailable" in result.stderr
|
||||||
|
assert not (tmp_path / "github.env").exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("release_exists", "expected_command"),
|
||||||
|
[("true", "release edit"), ("false", "release create")],
|
||||||
|
)
|
||||||
|
def test_release_publish_selects_edit_or_create(
|
||||||
|
tmp_path: Path,
|
||||||
|
release_exists: str,
|
||||||
|
expected_command: str,
|
||||||
|
) -> None:
|
||||||
|
"""发布阶段按查询结果原位更新或创建 Release。"""
|
||||||
|
script = _steps_by_name(_load_workflow())["Publish Release"]["run"]
|
||||||
|
script = script.replace("v${{ env.app_version }}", "v3.0.0")
|
||||||
|
|
||||||
|
result = _run_release_script(
|
||||||
|
script,
|
||||||
|
tmp_path,
|
||||||
|
extra_env={"RELEASE_EXISTS": release_exists, "RELEASE_BODY": "release notes"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
log = (tmp_path / "gh.log").read_text(encoding="utf-8")
|
||||||
|
assert expected_command in log
|
||||||
|
if release_exists == "true":
|
||||||
|
assert "--draft=false" in log
|
||||||
|
assert "--prerelease=false" in log
|
||||||
|
|
||||||
|
|
||||||
|
def test_dependency_compat_checks_minimum_uv_version() -> None:
|
||||||
|
"""依赖兼容 job 必须断言 uv 满足最低版本,而不是只打印版本。"""
|
||||||
|
workflow = _load_workflow(ROOT / ".github" / "workflows" / "dependency-compat.yml")
|
||||||
|
steps = workflow["jobs"]["docker-dependencies"]["steps"]
|
||||||
|
verify = next(step for step in steps if step.get("name") == "Verify minimum uv version")
|
||||||
|
command = verify["run"]
|
||||||
|
|
||||||
|
assert "['uv', '--version']" in command
|
||||||
|
assert "Version(version) >= Version('0.12.5')" in command
|
||||||
|
assert "assert" in command
|
||||||
|
|
||||||
|
|
||||||
def test_vulnerability_ignores_are_scoped_justified_and_time_bounded() -> None:
|
def test_vulnerability_ignores_are_scoped_justified_and_time_bounded() -> None:
|
||||||
"""漏洞豁免必须限定制品范围,并保留复查期限和接受理由。"""
|
"""漏洞豁免必须限定制品范围,并保留复查期限和接受理由。"""
|
||||||
yaml = YAML(typ="safe")
|
yaml = YAML(typ="safe")
|
||||||
|
|||||||
Reference in New Issue
Block a user