chore(release): prepare v3.4.1 release

This commit is contained in:
gauthier-th
2026-07-30 11:21:10 +02:00
30 changed files with 999 additions and 613 deletions

View File

@@ -39,13 +39,14 @@ jobs:
with:
persist-credentials: false
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- parallel:
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
- name: Get pnpm store directory
shell: bash
@@ -118,14 +119,15 @@ jobs:
CI: true
run: pnpm install
- name: Lint
run: pnpm lint
- parallel:
- name: Lint
run: pnpm lint
- name: Formatting
run: pnpm format:check
- name: Formatting
run: pnpm format:check
- name: Build
run: pnpm build
- name: Build
run: pnpm build
unit-test:
name: Unit Tests
@@ -186,18 +188,19 @@ jobs:
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Warm cache (no push) — ${{ matrix.platform }}
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
@@ -222,17 +225,14 @@ jobs:
packages: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Log in to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
@@ -247,6 +247,10 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Extract metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6

View File

@@ -36,16 +36,17 @@ jobs:
matrix:
language: [actions, javascript]
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Initialize CodeQL
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1

View File

@@ -22,16 +22,17 @@ jobs:
outputs:
tag_version: ${{ steps.git-cliff.outputs.tag_version }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- name: Install git-cliff
uses: taiki-e/install-action@25435dc8dd3baed7417e0c96d3fe89013a5b2e09 # v2.81.3
with:
tool: git-cliff
- name: Install git-cliff
uses: taiki-e/install-action@25435dc8dd3baed7417e0c96d3fe89013a5b2e09 # v2.81.3
with:
tool: git-cliff
- name: Get tag version
id: git-cliff
@@ -59,21 +60,22 @@ jobs:
with:
ssh-key: '${{ secrets.COMMIT_KEY }}'
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- parallel:
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
# For workflows with elevated privileges we recommend disabling automatic caching.
# https://github.com/actions/setup-node
package-manager-cache: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
# For workflows with elevated privileges we recommend disabling automatic caching.
# https://github.com/actions/setup-node
package-manager-cache: false
- name: Configure git
run: |
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
- name: Configure git
run: |
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
- name: Bump package.json
run: npm version ${TAG_VERSION} --no-commit-hooks --no-git-tag-version

View File

@@ -40,15 +40,15 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: package.json
package-manager-cache: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: package.json
package-manager-cache: false
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Install dependencies
run: pnpm install --frozen-lockfile

View File

@@ -25,13 +25,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- parallel:
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
- name: Get pnpm store directory
shell: bash

View File

@@ -28,15 +28,15 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- parallel:
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: package.json
package-manager-cache: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: package.json
package-manager-cache: false
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Get pnpm store directory
shell: sh

View File

@@ -27,24 +27,25 @@ jobs:
outputs:
has_artifacts: ${{ steps.check-artifacts.outputs.has_artifacts }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- name: Install helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
- name: Install helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
- name: Install Oras
uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2
- name: Install Oras
uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Package helm charts
run: |
@@ -92,33 +93,34 @@ jobs:
needs: [package-helm-chart]
if: needs.package-helm-chart.outputs.has_artifacts == 'true'
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- name: Install helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
- name: Install helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
- name: Install Oras
uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2
- name: Install Oras
uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Downloads artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: artifacts
path: .cr-release-packages/
- name: Downloads artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: artifacts
path: .cr-release-packages/
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push charts to GHCR
env:
@@ -150,27 +152,28 @@ jobs:
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Downloads artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: artifacts
path: .cr-release-packages/
- name: Downloads artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: artifacts
path: .cr-release-packages/
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Verify signatures for each chart tag
run: |

View File

@@ -27,29 +27,31 @@ jobs:
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
- name: Set up Helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5
- name: Set up chart-testing
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
- name: Set up chart-testing
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
- name: Ensure documentation is updated
uses: docker://jnorwood/helm-docs:v1.14.2@sha256:7e562b49ab6b1dbc50c3da8f2dd6ffa8a5c6bba327b1c6335cc15ce29267979c
- parallel:
- name: Ensure documentation is updated
uses: docker://jnorwood/helm-docs:v1.14.2@sha256:7e562b49ab6b1dbc50c3da8f2dd6ffa8a5c6bba327b1c6335cc15ce29267979c
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }})
if [[ -n "$changed" ]]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "$changed"
fi
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }})
if [[ -n "$changed" ]]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "$changed"
fi
- name: Run chart-testing
if: steps.list-changed.outputs.changed == 'true'

View File

@@ -31,82 +31,83 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
if: always() && steps.lint_pr_title.outputs.error_message != null
env:
ERROR_MESSAGE: ${{ steps.lint_pr_title.outputs.error_message }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const message = process.env.ERROR_MESSAGE;
const prNumber = context.payload.pull_request.number;
- parallel:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
if: always() && steps.lint_pr_title.outputs.error_message != null
env:
ERROR_MESSAGE: ${{ steps.lint_pr_title.outputs.error_message }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const message = process.env.ERROR_MESSAGE;
const prNumber = context.payload.pull_request.number;
const body = [
`### PR Title Validation Failed\n`,
message,
`\n---\n`,
`PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).`,
`*This check will re-run when you update your PR title.*`,
].join('\n');
const body = [
`### PR Title Validation Failed\n`,
message,
`\n---\n`,
`PR titles must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).`,
`*This check will re-run when you update your PR title.*`,
].join('\n');
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### PR Title Validation Failed')
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### PR Title Validation Failed')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
if: always() && steps.lint_pr_title.outputs.error_message == null
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
if: always() && steps.lint_pr_title.outputs.error_message == null
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### PR Title Validation Failed')
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### PR Title Validation Failed')
);
if (botComment) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
});
}
if (botComment) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
});
}
template-check:
name: Validate PR Template
@@ -117,10 +118,16 @@ jobs:
issues: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Skip bot PRs
id: bot-check
if: github.event.pull_request.user.type == 'Bot'
run: echo "skip=true" >> "$GITHUB_OUTPUT"
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -128,11 +135,6 @@ jobs:
node-version-file: 'package.json'
package-manager-cache: false
- name: Skip bot PRs
id: bot-check
if: github.event.pull_request.user.type == 'Bot'
run: echo "skip=true" >> "$GITHUB_OUTPUT"
- name: Write PR body to file
if: steps.bot-check.outputs.skip != 'true'
env:
@@ -156,130 +158,131 @@ jobs:
} >> "$GITHUB_OUTPUT"
exit 0
- name: Label and comment on failure
if: steps.bot-check.outputs.skip != 'true' && steps.check.outputs.exit_code != '0'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
ISSUES_JSON: ${{ steps.check.outputs.issues }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issues = JSON.parse(process.env.ISSUES_JSON);
const author = process.env.PR_AUTHOR;
const prNumber = context.payload.pull_request.number;
const LABEL = 'blocked:template';
- parallel:
- name: Label and comment on failure
if: steps.bot-check.outputs.skip != 'true' && steps.check.outputs.exit_code != '0'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
ISSUES_JSON: ${{ steps.check.outputs.issues }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issues = JSON.parse(process.env.ISSUES_JSON);
const author = process.env.PR_AUTHOR;
const prNumber = context.payload.pull_request.number;
const LABEL = 'blocked:template';
const issueList = issues.map(i => `- ${i}`).join('\n');
const issueList = issues.map(i => `- ${i}`).join('\n');
const commentBody = [
`Hey @${author}, thanks for submitting this PR! However, it looks like the PR template hasn't been fully filled out.\n`,
`### Issues found:\n`,
issueList,
`\n---\n`,
`**Please update your PR description to follow the [PR template](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/develop/.github/PULL_REQUEST_TEMPLATE.md).**`,
`Incomplete or missing PR descriptions may indicate insufficient review of the changes, and PRs that do not follow the template **may be closed without review**.`,
`See our [Contributing Guide](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/develop/CONTRIBUTING.md) for more details.\n`,
`*This check will automatically re-run when you edit your PR description.*`,
].join('\n');
const commentBody = [
`Hey @${author}, thanks for submitting this PR! However, it looks like the PR template hasn't been fully filled out.\n`,
`### Issues found:\n`,
issueList,
`\n---\n`,
`**Please update your PR description to follow the [PR template](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/develop/.github/PULL_REQUEST_TEMPLATE.md).**`,
`Incomplete or missing PR descriptions may indicate insufficient review of the changes, and PRs that do not follow the template **may be closed without review**.`,
`See our [Contributing Guide](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/develop/CONTRIBUTING.md) for more details.\n`,
`*This check will automatically re-run when you edit your PR description.*`,
].join('\n');
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### Issues found:')
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### Issues found:')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: commentBody,
});
}
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: commentBody,
});
}
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [LABEL],
});
} catch (e) {
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: LABEL,
color: 'B60205',
description: 'PR template not properly filled out',
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [LABEL],
});
} catch (e2) {
console.log('Could not create/add label:', e2.message);
}
}
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [LABEL],
});
} catch (e) {
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: LABEL,
color: 'B60205',
description: 'PR template not properly filled out',
});
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [LABEL],
});
} catch (e2) {
console.log('Could not create/add label:', e2.message);
}
}
core.setFailed('PR template is not properly filled out.');
core.setFailed('PR template is not properly filled out.');
- name: Remove label on success
if: steps.bot-check.outputs.skip != 'true' && steps.check.outputs.exit_code == '0'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const LABEL = 'blocked:template';
- name: Remove label on success
if: steps.bot-check.outputs.skip != 'true' && steps.check.outputs.exit_code == '0'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const LABEL = 'blocked:template';
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: LABEL,
});
} catch (e) {
console.log('Could not remove label', e.message);
}
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: LABEL,
});
} catch (e) {
console.log('Could not remove label', e.message);
}
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const allComments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### Issues found:')
);
const botComment = allComments.find(
c => c.user.type === 'Bot' && c.body && c.body.includes('### Issues found:')
);
if (botComment) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
});
}
if (botComment) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
});
}

View File

@@ -32,28 +32,29 @@ jobs:
arch: arm64
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Derive preview version from tag
id: ver
shell: bash
run: |
TAG="${GITHUB_REF_NAME}"
VER="${TAG#preview-}"
VER="${VER#v}"
echo "version=${VER}" >> "$GITHUB_OUTPUT"
echo "Building preview version: ${VER}"
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Derive preview version from tag
id: ver
shell: bash
run: |
TAG="${GITHUB_REF_NAME}"
VER="${TAG#preview-}"
VER="${VER#v}"
echo "version=${VER}" >> "$GITHUB_OUTPUT"
echo "Building preview version: ${VER}"
- name: Warm cache (no push) — ${{ matrix.platform }}
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
@@ -78,17 +79,24 @@ jobs:
packages: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Derive preview version from tag
id: ver
shell: bash
run: |
TAG="${GITHUB_REF_NAME}"
VER="${TAG#preview-}"
VER="${VER#v}"
echo "version=${VER}" >> "$GITHUB_OUTPUT"
echo "Publishing preview version: ${VER}"
- name: Log in to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
@@ -102,16 +110,9 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Derive preview version from tag
id: ver
shell: bash
run: |
TAG="${GITHUB_REF_NAME}"
VER="${TAG#preview-}"
VER="${VER#v}"
echo "version=${VER}" >> "$GITHUB_OUTPUT"
echo "Publishing preview version: ${VER}"
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Extract metadata
id: meta

View File

@@ -3,7 +3,7 @@ name: Rebuild Issue Index
on:
schedule:
- cron: "0 3 * * *"
- cron: '0 3 * * *'
workflow_dispatch:
permissions: {}
@@ -23,13 +23,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- parallel:
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: 'package.json'
- name: Get pnpm store directory
shell: bash

View File

@@ -75,18 +75,19 @@ jobs:
env:
VERSION: ${{ github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Warm cache [${{ matrix.platform }}]
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
@@ -114,17 +115,14 @@ jobs:
env:
VERSION: ${{ github.ref_name }}
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Log in to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
@@ -139,6 +137,10 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Commit timestamp
id: ts
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
- name: Extract metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6
@@ -172,23 +174,24 @@ jobs:
cache-to: type=gha,mode=max
provenance: false
- name: Resolve manifest digest
id: digests
run: |
DIGEST=$(docker buildx imagetools inspect "${{ env.DOCKER_HUB }}:${{ env.VERSION }}" --format '{{json .Manifest.Digest}}' | tr -d '"')
echo "IMAGE_DIGEST=$DIGEST" >> $GITHUB_OUTPUT
- parallel:
- name: Resolve manifest digest
id: digests
run: |
DIGEST=$(docker buildx imagetools inspect "${{ env.DOCKER_HUB }}:${{ env.VERSION }}" --format '{{json .Manifest.Digest}}' | tr -d '"')
echo "IMAGE_DIGEST=$DIGEST" >> $GITHUB_OUTPUT
- name: Also tag :latest (non-pre-release only)
shell: bash
if: ${{ !contains(env.VERSION, '-') }}
run: |
docker buildx imagetools create \
-t ${{ env.DOCKER_HUB }}:latest \
${{ env.DOCKER_HUB }}:${{ env.VERSION }}
- name: Also tag :latest (non-pre-release only)
shell: bash
if: ${{ !contains(env.VERSION, '-') }}
run: |
docker buildx imagetools create \
-t ${{ env.DOCKER_HUB }}:latest \
${{ env.DOCKER_HUB }}:${{ env.VERSION }}
docker buildx imagetools create \
-t ghcr.io/${{ github.repository }}:latest \
ghcr.io/${{ github.repository }}:${{ env.VERSION }}
docker buildx imagetools create \
-t ghcr.io/${{ github.repository }}:latest \
ghcr.io/${{ github.repository }}:${{ env.VERSION }}
sign:
name: Sign images and create SBOM attestations
@@ -202,16 +205,17 @@ jobs:
VERSION: ${{ github.ref_name }}
COSIGN_YES: 'true'
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- parallel:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Install Trivy
uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
- name: Install Trivy
uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
- name: Log in to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
@@ -271,27 +275,28 @@ jobs:
- name: Install Cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Verify signatures
run: |
cosign verify "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}" \
--certificate-identity "https://github.com/${{ github.workflow_ref }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
- parallel:
- name: Verify signatures
run: |
cosign verify "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}" \
--certificate-identity "https://github.com/${{ github.workflow_ref }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
cosign verify "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}" \
--certificate-identity "https://github.com/${{ github.workflow_ref }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
cosign verify "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}" \
--certificate-identity "https://github.com/${{ github.workflow_ref }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
# - name: Verify attestations
# run: |
# cosign verify-attestation "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}" \
# --type cyclonedx \
# --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
# --certificate-oidc-issuer "https://token.actions.githubusercontent.com" > /dev/null
- name: Verify attestations
run: |
cosign verify-attestation "ghcr.io/${{ github.repository }}@${{ needs.publish.outputs.image_digest }}" \
--type cyclonedx \
--certificate-identity "https://github.com/${{ github.workflow_ref }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
# cosign verify-attestation "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}" \
# --type cyclonedx \
# --certificate-identity "https://github.com/${{ github.workflow_ref }}" \
# --certificate-oidc-issuer "https://token.actions.githubusercontent.com" > /dev/null
cosign verify-attestation "${{ env.DOCKER_HUB }}@${{ needs.publish.outputs.image_digest }}" \
--type cyclonedx \
--certificate-identity "https://github.com/${{ github.workflow_ref }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
publish-release:
name: Publish release

View File

@@ -24,20 +24,21 @@ jobs:
pull-requests: write
if: github.actor == 'renovate[bot]'
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- parallel:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: 2138788
private-key: ${{ secrets.APP_SEERR_HELM_PRIVATE_KEY }}
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ secrets.APP_SEERR_HELM_CLIENT_ID }}
private-key: ${{ secrets.APP_SEERR_HELM_PRIVATE_KEY }}
- name: Set up chart-testing
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
- name: Set up chart-testing
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
- name: Run chart-testing (list-changed)
id: list-changed

View File

@@ -27,39 +27,40 @@ jobs:
NUMBER: ${{ github.event.pull_request.number }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
steps:
- name: Label added, comment and close pull request
if: github.event.action == 'labeled' && github.event.label.name == 'ai-generated'
shell: bash
env:
BODY: >
:wave: @${{ env.PR_AUTHOR }}, thank you for your contribution!
- parallel:
- name: Label added, comment and close pull request
if: github.event.action == 'labeled' && github.event.label.name == 'ai-generated'
shell: bash
env:
BODY: >
:wave: @${{ env.PR_AUTHOR }}, thank you for your contribution!
However, this pull request has been closed because it appears to contain a significant amount of AI-generated code without sufficient human review or supervision.
However, this pull request has been closed because it appears to contain a significant amount of AI-generated code without sufficient human review or supervision.
AI-generated code can often introduce subtle bugs, poor design patterns, or inconsistent styles that make long-term maintenance difficult and reduce overall code quality. For the sake of the project's future stability and readability, we require that all contributions meet our established coding standards and demonstrate clear developer oversight.
AI-generated code can often introduce subtle bugs, poor design patterns, or inconsistent styles that make long-term maintenance difficult and reduce overall code quality. For the sake of the project's future stability and readability, we require that all contributions meet our established coding standards and demonstrate clear developer oversight.
This pull request is also too large for effective human review. Please discuss with us on how to break down these changes into smaller, more focused PRs to ensure a thorough and efficient review process.
If you'd like to revise and resubmit your changes with careful review and cleanup, we'd be happy to take another look.
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh pr comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
retry gh pr close "$NUMBER" -R "$GH_REPO" || true
gh pr lock "$NUMBER" -R "$GH_REPO" -r "spam" || true
This pull request is also too large for effective human review. Please discuss with us on how to break down these changes into smaller, more focused PRs to ensure a thorough and efficient review process.
If you'd like to revise and resubmit your changes with careful review and cleanup, we'd be happy to take another look.
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh pr comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
retry gh pr close "$NUMBER" -R "$GH_REPO" || true
gh pr lock "$NUMBER" -R "$GH_REPO" -r "spam" || true
- name: Label removed, reopen and unlock pull request
if: github.event.action == 'unlabeled' && github.event.label.name == 'ai-generated'
shell: bash
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh pr reopen "$NUMBER" -R "$GH_REPO" || true
gh pr unlock "$NUMBER" -R "$GH_REPO" || true
- name: Label removed, reopen and unlock pull request
if: github.event.action == 'unlabeled' && github.event.label.name == 'ai-generated'
shell: bash
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh pr reopen "$NUMBER" -R "$GH_REPO" || true
gh pr unlock "$NUMBER" -R "$GH_REPO" || true
- name: Remove AI-generated label on manual reopen
if: github.event.action == 'reopened'
shell: bash
run: |
gh pr edit "$NUMBER" -R "$GH_REPO" --remove-label "ai-generated" || true
gh pr unlock "$NUMBER" -R "$GH_REPO" || true
- name: Remove AI-generated label on manual reopen
if: github.event.action == 'reopened'
shell: bash
run: |
gh pr edit "$NUMBER" -R "$GH_REPO" --remove-label "ai-generated" || true
gh pr unlock "$NUMBER" -R "$GH_REPO" || true
support:
if: >
@@ -78,34 +79,35 @@ jobs:
NUMBER: ${{ github.event.issue.number }}
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
steps:
- name: Label added, comment and close issue
if: github.event.action == 'labeled' && github.event.label.name == 'support'
shell: bash
env:
BODY: >
:wave: @${{ env.ISSUE_AUTHOR }}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please use our support channels
to get help with Seerr.
- parallel:
- name: Label added, comment and close issue
if: github.event.action == 'labeled' && github.event.label.name == 'support'
shell: bash
env:
BODY: >
:wave: @${{ env.ISSUE_AUTHOR }}, we use the issue tracker exclusively
for bug reports and feature requests. However, this issue appears
to be a support request. Please use our support channels
to get help with Seerr.
- [Discord](https://discord.gg/seerr)
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh issue comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
retry gh issue close "$NUMBER" -R "$GH_REPO" || true
gh issue lock "$NUMBER" -R "$GH_REPO" -r "off_topic" || true
- [Discord](https://discord.gg/seerr)
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh issue comment "$NUMBER" -R "$GH_REPO" -b "$BODY" || true
retry gh issue close "$NUMBER" -R "$GH_REPO" || true
gh issue lock "$NUMBER" -R "$GH_REPO" -r "off_topic" || true
- name: Label removed, reopen and unlock issue
if: github.event.action == 'unlabeled' && github.event.label.name == 'support'
shell: bash
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh issue reopen "$NUMBER" -R "$GH_REPO" || true
gh issue unlock "$NUMBER" -R "$GH_REPO" || true
- name: Label removed, reopen and unlock issue
if: github.event.action == 'unlabeled' && github.event.label.name == 'support'
shell: bash
run: |
retry() { n=0; until "$@"; do n=$((n+1)); [ $n -ge 3 ] && break; echo "retry $n: $*" >&2; sleep 2; done; }
retry gh issue reopen "$NUMBER" -R "$GH_REPO" || true
gh issue unlock "$NUMBER" -R "$GH_REPO" || true
- name: Remove support label on manual reopen
if: github.event.action == 'reopened'
shell: bash
run: |
gh issue edit "$NUMBER" -R "$GH_REPO" --remove-label "support" || true
gh issue unlock "$NUMBER" -R "$GH_REPO" || true
- name: Remove support label on manual reopen
if: github.event.action == 'reopened'
shell: bash
run: |
gh issue edit "$NUMBER" -R "$GH_REPO" --remove-label "support" || true
gh issue unlock "$NUMBER" -R "$GH_REPO" || true

View File

@@ -29,14 +29,15 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: package.json
package-manager-cache: false
- parallel:
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: package.json
package-manager-cache: false
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Pnpm Setup
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Get pnpm store directory
shell: sh

View File

@@ -3,9 +3,9 @@ kubeVersion: '>=1.23.0-0'
name: seerr-chart
description: Seerr helm chart for Kubernetes
type: application
version: 3.7.0
version: 3.9.0
# renovate: image=ghcr.io/seerr-team/seerr
appVersion: 'v3.3.0'
appVersion: 'v3.4.0'
maintainers:
- name: Seerr Team
url: https://github.com/orgs/seerr-team/people

View File

@@ -1,6 +1,6 @@
# seerr-chart
![Version: 3.7.0](https://img.shields.io/badge/Version-3.7.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v3.3.0](https://img.shields.io/badge/AppVersion-v3.3.0-informational?style=flat-square)
![Version: 3.9.0](https://img.shields.io/badge/Version-3.9.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v3.4.0](https://img.shields.io/badge/AppVersion-v3.4.0-informational?style=flat-square)
Seerr helm chart for Kubernetes
@@ -53,9 +53,12 @@ If `replicaCount` value was used - remove it. Helm update should work fine after
| config.persistence.storageClass | string | `""` | Storage class for the PVC. Set to "-" to disable dynamic provisioning. Uses default storage class if no value is provided |
| config.persistence.subPath | string | `""` | Subpath of the pvc which should be mounted |
| config.persistence.volumeName | string | `""` | Name of the permanent volume to reference in the claim. Can be used to bind to existing volumes. |
| dnsConfig | object | `{}` | docs: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config |
| dnsPolicy | string | `""` | docs: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy |
| extraEnv | list | `[]` | Environment variables to add to the seerr pods |
| extraEnvFrom | list | `[]` | Environment variables from secrets or configmaps to add to the seerr pods |
| fullnameOverride | string | `""` | |
| hostUsers | bool | `true` | docs: https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/ |
| image.pullPolicy | string | `"IfNotPresent"` | |
| image.registry | string | `"ghcr.io"` | |
| image.repository | string | `"seerr-team/seerr"` | |
@@ -75,6 +78,7 @@ If `replicaCount` value was used - remove it. Helm update should work fine after
| podLabels | object | `{}` | |
| podSecurityContext.fsGroup | int | `1000` | |
| podSecurityContext.fsGroupChangePolicy | string | `"OnRootMismatch"` | |
| priorityClassName | string | `nil` | Specify a priorityclass, or use default if unset. |
| probes.livenessProbe | object | `{"initialDelaySeconds":20,"periodSeconds":15,"timeoutSeconds":3}` | Configure liveness probe |
| probes.readinessProbe | object | `{"initialDelaySeconds":60,"periodSeconds":15,"timeoutSeconds":3}` | Configure readiness probe |
| probes.startupProbe | string | `nil` | Configure startup probe |

View File

@@ -28,6 +28,19 @@ spec:
serviceAccountName: {{ include "seerr.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- with .Values.priorityClassName }}
priorityClassName: {{ . }}
{{- end }}
{{- if kindIs "bool" .Values.hostUsers }}
hostUsers: {{ .Values.hostUsers }}
{{- end }}
{{- with .Values.dnsPolicy }}
dnsPolicy: {{ . }}
{{- end }}
{{- with .Values.dnsConfig }}
dnsConfig:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
securityContext:

View File

@@ -67,6 +67,31 @@ securityContext:
seccompProfile:
type: RuntimeDefault
# -- Set a DNS policy, likely not needed unless seerr only interacts with external services.
# -- docs: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
dnsPolicy: ''
# -- Set DNS config options
# -- Setting ndots lower can prevent queries like `raw.githubusercontent.com.seerr.svc.cluster.local.`
# -- docs: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config
dnsConfig: {}
# nameservers:
# - 1.2.3.4
# searches:
# - ns1.svc.cluster-domain.example
# - my.dns.search.suffix
# options:
# - name: ndots
# value: "2"
# -- Set to false to run in an idmaped user namespace for additional isolation
# -- requires cluster support, and PV support of idmaped volumes.
# -- docs: https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/
hostUsers: true
# -- Specify a priorityclass, or use default if unset.
priorityClassName: null
service:
type: ClusterIP
port: 80

View File

@@ -8,6 +8,7 @@
packages = [
pkgs.prettier
pkgs.husky
pkgs.helm-docs
];
languages.javascript = {

View File

@@ -0,0 +1,119 @@
import assert from 'node:assert/strict';
import { afterEach, describe, it, mock } from 'node:test';
import type { AxiosInstance } from 'axios';
import RadarrAPI from '@server/api/servarr/radarr';
function buildRadarr(): RadarrAPI {
return new RadarrAPI({ url: 'http://localhost:7878/api/v3', apiKey: 'test' });
}
function getAxios(radarr: RadarrAPI): AxiosInstance {
return (radarr as unknown as { axios: AxiosInstance }).axios;
}
describe('RadarrAPI removeMovie', () => {
afterEach(() => mock.restoreAll());
it('removes the movie when it exists in the library', async () => {
const radarr = buildRadarr();
mock.method(RadarrAPI.prototype, 'getMovieByTmdbId', async () => ({
id: 7,
title: 'Test Movie',
}));
const del = mock.method(getAxios(radarr), 'delete', async () => ({}));
await radarr.removeMovie(550);
assert.strictEqual(del.mock.callCount(), 1);
assert.strictEqual(del.mock.calls[0].arguments[0], '/movie/7');
});
it('does nothing when the movie is not in the library', async () => {
const radarr = buildRadarr();
mock.method(getAxios(radarr), 'get', async () => ({
data: [{ id: 0, title: 'Fight Club' }],
}));
const del = mock.method(getAxios(radarr), 'delete', async () => ({}));
await assert.doesNotReject(() => radarr.removeMovie(550));
assert.strictEqual(del.mock.callCount(), 0);
});
it('rejects when the tmdbId is unknown to the lookup', async () => {
const radarr = buildRadarr();
mock.method(getAxios(radarr), 'get', async () => ({ data: [] }));
const del = mock.method(getAxios(radarr), 'delete', async () => ({}));
await assert.rejects(() => radarr.removeMovie(550), /Movie not found/);
assert.strictEqual(del.mock.callCount(), 0);
});
it('ignores a 404 when the movie was already removed in Radarr', async () => {
const radarr = buildRadarr();
mock.method(RadarrAPI.prototype, 'getMovieByTmdbId', async () => ({
id: 7,
title: 'Test Movie',
}));
mock.method(getAxios(radarr), 'delete', async () => {
throw { response: { status: 404 } };
});
await assert.doesNotReject(() => radarr.removeMovie(550));
});
it('rethrows errors other than 404', async () => {
const radarr = buildRadarr();
mock.method(RadarrAPI.prototype, 'getMovieByTmdbId', async () => ({
id: 7,
title: 'Test Movie',
}));
mock.method(getAxios(radarr), 'delete', async () => {
throw { response: { status: 500 } };
});
await assert.rejects(() => radarr.removeMovie(550));
});
it('rethrows a 404 from the lookup instead of treating it as removed', async () => {
const radarr = buildRadarr();
mock.method(getAxios(radarr), 'get', async () => {
throw { response: { status: 404 } };
});
const del = mock.method(getAxios(radarr), 'delete', async () => ({}));
await assert.rejects(
() => radarr.removeMovie(550),
(e: unknown) =>
(e as { response?: { status?: number } }).response?.status === 404
);
assert.strictEqual(del.mock.callCount(), 0);
});
});
describe('RadarrAPI getMovieByTmdbId', () => {
afterEach(() => mock.restoreAll());
it('rethrows a 401 from the lookup with the status intact', async () => {
const radarr = buildRadarr();
mock.method(getAxios(radarr), 'get', async () => {
throw { response: { status: 401 } };
});
await assert.rejects(
() => radarr.getMovieByTmdbId(550),
(e: unknown) =>
(e as { response?: { status?: number } }).response?.status === 401
);
});
it('throws "Movie not found" when the lookup returns no results', async () => {
const radarr = buildRadarr();
mock.method(getAxios(radarr), 'get', async () => ({ data: [] }));
await assert.rejects(() => radarr.getMovieByTmdbId(550), {
message: 'Movie not found',
});
});
});

View File

@@ -1,4 +1,5 @@
import logger from '@server/logger';
import type { AxiosResponse } from 'axios';
import ServarrBase from './base';
export interface RadarrMovieOptions {
@@ -93,26 +94,27 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
};
public async getMovieByTmdbId(id: number): Promise<RadarrMovie> {
let response: AxiosResponse<RadarrMovie[]>;
try {
const response = await this.axios.get<RadarrMovie[]>('/movie/lookup', {
response = await this.axios.get<RadarrMovie[]>('/movie/lookup', {
params: {
term: `tmdb:${id}`,
},
});
if (!response.data[0]) {
throw new Error('Movie not found');
}
return response.data[0];
} catch (e) {
logger.error('Error retrieving movie by TMDB ID', {
label: 'Radarr API',
errorMessage: e.message,
tmdbId: id,
});
throw new Error('Movie not found', { cause: e });
throw e;
}
if (!response.data[0]) {
throw new Error('Movie not found');
}
return response.data[0];
}
public addMovie = async (
@@ -267,9 +269,17 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
);
}
}
public removeMovie = async (movieId: number): Promise<void> => {
public removeMovie = async (tmdbId: number): Promise<void> => {
const { id, title } = await this.getMovieByTmdbId(tmdbId);
if (!id) {
logger.info(`[Radarr] Movie not in library, nothing to remove`, {
tmdbId,
});
return;
}
try {
const { id, title } = await this.getMovieByTmdbId(movieId);
await this.axios.delete(`/movie/${id}`, {
params: {
deleteFiles: true,
@@ -278,9 +288,13 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
});
logger.info(`[Radarr] Removed movie ${title}`);
} catch (e) {
throw new Error(`[Radarr] Failed to remove movie: ${e.message}`, {
cause: e,
});
if (e?.response?.status === 404) {
logger.info(`[Radarr] Movie already removed from Radarr`, {
tmdbId,
});
return;
}
throw e;
}
};

View File

@@ -0,0 +1,119 @@
import assert from 'node:assert/strict';
import { afterEach, describe, it, mock } from 'node:test';
import type { AxiosInstance } from 'axios';
import SonarrAPI from '@server/api/servarr/sonarr';
function buildSonarr(): SonarrAPI {
return new SonarrAPI({ url: 'http://localhost:8989/api/v3', apiKey: 'test' });
}
function getAxios(sonarr: SonarrAPI): AxiosInstance {
return (sonarr as unknown as { axios: AxiosInstance }).axios;
}
describe('SonarrAPI removeSeries', () => {
afterEach(() => mock.restoreAll());
it('removes the series when it exists in the library', async () => {
const sonarr = buildSonarr();
mock.method(SonarrAPI.prototype, 'getSeriesByTvdbId', async () => ({
id: 9,
title: 'Test Series',
}));
const del = mock.method(getAxios(sonarr), 'delete', async () => ({}));
await sonarr.removeSeries(1234);
assert.strictEqual(del.mock.callCount(), 1);
assert.strictEqual(del.mock.calls[0].arguments[0], '/series/9');
});
it('does nothing when the series is not in the library', async () => {
const sonarr = buildSonarr();
mock.method(getAxios(sonarr), 'get', async () => ({
data: [{ id: 0, title: 'Breaking Bad' }],
}));
const del = mock.method(getAxios(sonarr), 'delete', async () => ({}));
await assert.doesNotReject(() => sonarr.removeSeries(1234));
assert.strictEqual(del.mock.callCount(), 0);
});
it('rejects when the tvdbId is unknown to the lookup', async () => {
const sonarr = buildSonarr();
mock.method(getAxios(sonarr), 'get', async () => ({ data: [] }));
const del = mock.method(getAxios(sonarr), 'delete', async () => ({}));
await assert.rejects(() => sonarr.removeSeries(1234), /Series not found/);
assert.strictEqual(del.mock.callCount(), 0);
});
it('ignores a 404 when the series was already removed in Sonarr', async () => {
const sonarr = buildSonarr();
mock.method(SonarrAPI.prototype, 'getSeriesByTvdbId', async () => ({
id: 9,
title: 'Test Series',
}));
mock.method(getAxios(sonarr), 'delete', async () => {
throw { response: { status: 404 } };
});
await assert.doesNotReject(() => sonarr.removeSeries(1234));
});
it('rethrows errors other than 404', async () => {
const sonarr = buildSonarr();
mock.method(SonarrAPI.prototype, 'getSeriesByTvdbId', async () => ({
id: 9,
title: 'Test Series',
}));
mock.method(getAxios(sonarr), 'delete', async () => {
throw { response: { status: 500 } };
});
await assert.rejects(() => sonarr.removeSeries(1234));
});
it('rethrows a 404 from the lookup instead of treating it as removed', async () => {
const sonarr = buildSonarr();
mock.method(getAxios(sonarr), 'get', async () => {
throw { response: { status: 404 } };
});
const del = mock.method(getAxios(sonarr), 'delete', async () => ({}));
await assert.rejects(
() => sonarr.removeSeries(1234),
(e: unknown) =>
(e as { response?: { status?: number } }).response?.status === 404
);
assert.strictEqual(del.mock.callCount(), 0);
});
});
describe('SonarrAPI getSeriesByTvdbId', () => {
afterEach(() => mock.restoreAll());
it('rethrows a 401 from the lookup with the status intact', async () => {
const sonarr = buildSonarr();
mock.method(getAxios(sonarr), 'get', async () => {
throw { response: { status: 401 } };
});
await assert.rejects(
() => sonarr.getSeriesByTvdbId(1234),
(e: unknown) =>
(e as { response?: { status?: number } }).response?.status === 401
);
});
it('throws "Series not found" when the lookup returns no results', async () => {
const sonarr = buildSonarr();
mock.method(getAxios(sonarr), 'get', async () => ({ data: [] }));
await assert.rejects(() => sonarr.getSeriesByTvdbId(1234), {
message: 'Series not found',
});
});
});

View File

@@ -1,4 +1,5 @@
import logger from '@server/logger';
import type { AxiosResponse } from 'axios';
import ServarrBase from './base';
export interface SonarrSeason {
@@ -166,26 +167,27 @@ class SonarrAPI extends ServarrBase<{
}
public async getSeriesByTvdbId(id: number): Promise<SonarrSeries> {
let response: AxiosResponse<SonarrSeries[]>;
try {
const response = await this.axios.get<SonarrSeries[]>('/series/lookup', {
response = await this.axios.get<SonarrSeries[]>('/series/lookup', {
params: {
term: `tvdb:${id}`,
},
});
if (!response.data[0]) {
throw new Error('Series not found');
}
return response.data[0];
} catch (e) {
logger.error('Error retrieving series by tvdb ID', {
label: 'Sonarr API',
errorMessage: e.message,
tvdbId: id,
});
throw new Error('Series not found', { cause: e });
throw e;
}
if (!response.data[0]) {
throw new Error('Series not found');
}
return response.data[0];
}
public async addSeries(options: AddSeriesOptions): Promise<SonarrSeries> {
@@ -410,9 +412,17 @@ class SonarrAPI extends ServarrBase<{
return newSeasons;
}
public removeSeries = async (serieId: number): Promise<void> => {
public removeSeries = async (tvdbId: number): Promise<void> => {
const { id, title } = await this.getSeriesByTvdbId(tvdbId);
if (!id) {
logger.info(`[Sonarr] Series not in library, nothing to remove`, {
tvdbId,
});
return;
}
try {
const { id, title } = await this.getSeriesByTvdbId(serieId);
await this.axios.delete(`/series/${id}`, {
params: {
deleteFiles: true,
@@ -421,9 +431,13 @@ class SonarrAPI extends ServarrBase<{
});
logger.info(`[Sonarr] Removed series ${title}`);
} catch (e) {
throw new Error(`[Sonarr] Failed to remove series: ${e.message}`, {
cause: e,
});
if (e?.response?.status === 404) {
logger.info(`[Sonarr] Series already removed from Sonarr`, {
tvdbId,
});
return;
}
throw e;
}
};

View File

@@ -203,17 +203,21 @@ class Media {
Object.assign(this, init);
}
public resetServiceData(): void {
this.serviceId = null;
this.serviceId4k = null;
this.externalServiceId = null;
this.externalServiceId4k = null;
this.externalServiceSlug = null;
this.externalServiceSlug4k = null;
this.ratingKey = null;
this.ratingKey4k = null;
this.jellyfinMediaId = null;
this.jellyfinMediaId4k = null;
public resetServiceData(is4k?: boolean): void {
if (is4k === undefined || !is4k) {
this.serviceId = null;
this.externalServiceId = null;
this.externalServiceSlug = null;
this.ratingKey = null;
this.jellyfinMediaId = null;
}
if (is4k === undefined || is4k) {
this.serviceId4k = null;
this.externalServiceId4k = null;
this.externalServiceSlug4k = null;
this.ratingKey4k = null;
this.jellyfinMediaId4k = null;
}
}
@AfterLoad()

View File

@@ -243,18 +243,18 @@ mediaRoutes.delete(
}
if (!serviceSettings) {
logger.warn(
`There is no default ${
is4k ? '4K ' : '' + isMovie ? 'Radarr' : 'Sonarr'
}/ server configured. Did you set any of your ${
is4k ? '4K ' : '' + isMovie ? 'Radarr' : 'Sonarr'
} servers as default?`,
const arrName = `${is4k ? '4K ' : ''}${isMovie ? 'Radarr' : 'Sonarr'}`;
logger.info(
`There is no default ${arrName} server configured. Did you set any of your ${arrName} servers as default?`,
{
label: 'Media Request',
mediaId: media.id,
}
);
return;
return next({
status: 409,
message: `No ${arrName} server configured to delete media files`,
});
}
let service;
@@ -280,15 +280,27 @@ mediaRoutes.delete(
throw new Error('TVDB ID not found');
}
await (service as SonarrAPI).removeSeries(tvdbId);
for (const season of media.seasons) {
season[is4k ? 'status4k' : 'status'] = MediaStatus.DELETED;
}
}
media[is4k ? 'status4k' : 'status'] = MediaStatus.DELETED;
media.resetServiceData(is4k);
await mediaRepository.save(media);
return res.status(204).send();
} catch (e) {
logger.error('Something went wrong fetching media in delete request', {
if (e instanceof EntityNotFoundError) {
return next({ status: 404, message: 'Media not found' });
}
logger.error('Something went wrong deleting media file', {
label: 'Media',
mediaId: req.params.id,
message: e.message,
});
next({ status: 404, message: 'Media not found' });
next({ status: 500, message: 'Failed to delete media file' });
}
}
);

View File

@@ -8,6 +8,7 @@ import DownloadBlock from '@app/components/DownloadBlock';
import IssueBlock from '@app/components/IssueBlock';
import RequestBlock from '@app/components/RequestBlock';
import useSettings from '@app/hooks/useSettings';
import useToasts from '@app/hooks/useToasts';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
@@ -64,6 +65,8 @@ const messages = defineMessages('components.ManageSlideOver', {
removearr: 'Remove from {arr}',
openarr4k: 'Open in 4K {arr}',
removearr4k: 'Remove from 4K {arr}',
clearmediadataerror: 'Something went wrong while clearing the media data.',
removemediaerror: 'Something went wrong while removing the media.',
downloadstatus: 'Downloads',
markavailable: 'Mark as Available',
mark4kavailable: 'Mark as Available in 4K',
@@ -109,6 +112,7 @@ const ManageSlideOver = ({
}: ManageSlideOverMovieProps | ManageSlideOverTvProps) => {
const { user: currentUser, hasPermission } = useUser();
const intl = useIntl();
const { addToast } = useToasts();
const settings = useSettings();
const { data: watchData } = useSWR<MediaWatchDataResponse>(
settings.currentSettings.mediaServerType === MediaServerType.PLEX &&
@@ -126,18 +130,35 @@ const ManageSlideOver = ({
const deleteMedia = async () => {
if (data.mediaInfo) {
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
revalidate();
onClose();
try {
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
revalidate();
onClose();
} catch {
addToast(intl.formatMessage(messages.clearmediadataerror), {
appearance: 'error',
autoDismiss: true,
});
}
}
};
const deleteMediaFile = async (is4k = false) => {
if (data.mediaInfo) {
await axios.delete(
`/api/v1/media/${data.mediaInfo.id}/file?is4k=${is4k}`
);
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
try {
await axios.delete(
`/api/v1/media/${data.mediaInfo.id}/file?is4k=${is4k}`
);
} catch (e) {
if (!axios.isAxiosError(e) || e.response?.status !== 404) {
addToast(intl.formatMessage(messages.removemediaerror), {
appearance: 'error',
autoDismiss: true,
});
revalidate();
return;
}
}
revalidate();
onClose();
}

View File

@@ -47,6 +47,7 @@ const messages = defineMessages('components.RequestList.RequestItem', {
tvdbid: 'TheTVDB ID',
unknowntitle: 'Unknown Title',
removearr: 'Remove from {arr}',
removemediaerror: 'Something went wrong while removing the media.',
profileName: 'Profile',
});
@@ -354,10 +355,20 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
const deleteMediaFile = async () => {
if (request.media) {
await axios.delete(
`/api/v1/media/${request.media.id}/file?is4k=${request.is4k}`
);
await axios.delete(`/api/v1/media/${request.media.id}`);
try {
await axios.delete(
`/api/v1/media/${request.media.id}/file?is4k=${request.is4k}`
);
} catch (e) {
if (!axios.isAxiosError(e) || e.response?.status !== 404) {
addToast(intl.formatMessage(messages.removemediaerror), {
autoDismiss: true,
appearance: 'error',
});
revalidateList();
return;
}
}
revalidateList();
}
};

View File

@@ -750,12 +750,12 @@ const UserList = () => {
>
{intl.formatMessage(messages.created)}
</SortableColumnHeader>
<Table.TH className="w-1/12 whitespace-nowrap text-right">
<Table.TH className="w-1/12 min-w-[12rem] whitespace-nowrap text-right">
{(data.results ?? []).length > 1 && (
<div className="flex justify-end">
<Button
buttonType="warning"
className="w-full sm:min-w-[12rem]"
className="w-full"
onClick={() => setShowBulkEditModal(true)}
disabled={selectedUsers.length === 0}
>
@@ -874,33 +874,32 @@ const UserList = () => {
day: 'numeric',
})}
</Table.TD>
<Table.TD
alignText="right"
className="grid grid-cols-1 gap-1 sm:grid-cols-2 sm:gap-2"
>
<Button
buttonType="warning"
disabled={user.id === 1 && currentUser?.id !== 1}
onClick={() =>
router.push(
'/users/[userId]/settings',
`/users/${user.id}/settings`
)
}
>
{intl.formatMessage(globalMessages.edit)}
</Button>
<Button
buttonType="danger"
disabled={
user.id === 1 ||
(currentUser?.id !== 1 &&
hasPermission(Permission.ADMIN, user.permissions))
}
onClick={() => setDeleteModal({ isOpen: true, user })}
>
{intl.formatMessage(globalMessages.delete)}
</Button>
<Table.TD alignText="right">
<div className="grid grid-cols-1 gap-1 sm:grid-cols-2 sm:gap-2">
<Button
buttonType="warning"
disabled={user.id === 1 && currentUser?.id !== 1}
onClick={() =>
router.push(
'/users/[userId]/settings',
`/users/${user.id}/settings`
)
}
>
{intl.formatMessage(globalMessages.edit)}
</Button>
<Button
buttonType="danger"
disabled={
user.id === 1 ||
(currentUser?.id !== 1 &&
hasPermission(Permission.ADMIN, user.permissions))
}
onClick={() => setDeleteModal({ isOpen: true, user })}
>
{intl.formatMessage(globalMessages.delete)}
</Button>
</div>
</Table.TD>
</tr>
))}

View File

@@ -305,6 +305,7 @@
"components.Login.validationservertyperequired": "Please select a server type",
"components.Login.validationusernamerequired": "Username required",
"components.ManageSlideOver.alltime": "All Time",
"components.ManageSlideOver.clearmediadataerror": "Something went wrong while clearing the media data.",
"components.ManageSlideOver.downloadstatus": "Downloads",
"components.ManageSlideOver.manageModalAdvanced": "Advanced",
"components.ManageSlideOver.manageModalClearMedia": "Clear Data",
@@ -329,6 +330,7 @@
"components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {play} other {plays}}",
"components.ManageSlideOver.removearr": "Remove from {arr}",
"components.ManageSlideOver.removearr4k": "Remove from 4K {arr}",
"components.ManageSlideOver.removemediaerror": "Something went wrong while removing the media.",
"components.ManageSlideOver.tvshow": "series",
"components.MediaSlider.ShowMoreCard.seemore": "See More",
"components.MetadataSelector.selectMetdataProvider": "Select a metadata provider",
@@ -528,6 +530,7 @@
"components.RequestList.RequestItem.modifieduserdate": "{date} by {user}",
"components.RequestList.RequestItem.profileName": "Profile",
"components.RequestList.RequestItem.removearr": "Remove from {arr}",
"components.RequestList.RequestItem.removemediaerror": "Something went wrong while removing the media.",
"components.RequestList.RequestItem.requested": "Requested",
"components.RequestList.RequestItem.requesteddate": "Requested",
"components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",