mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-02 04:42:09 +08:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
067a9f7cad | ||
|
|
07ee0a602a | ||
|
|
693f2f7ac7 | ||
|
|
633f166e46 | ||
|
|
261178b6d8 | ||
|
|
0cae907de6 | ||
|
|
94942b9105 | ||
|
|
12f842d038 | ||
|
|
8e9832712a | ||
|
|
719f63b57f | ||
|
|
ef784e459d | ||
|
|
c236f5094e | ||
|
|
bdea31a7ca | ||
|
|
44d50eb67d | ||
|
|
afd9d4b5a0 | ||
|
|
8bf20d199c | ||
|
|
e156ff09ae | ||
|
|
df70146b02 | ||
|
|
de22706f3b | ||
|
|
4b87634da9 | ||
|
|
c9a6cf240d | ||
|
|
8128227302 | ||
|
|
3953e125b2 | ||
|
|
cbd5ed73a5 | ||
|
|
02e5e0e4a6 | ||
|
|
0498ba3e4f | ||
|
|
f39644ee6b | ||
|
|
488f022c05 | ||
|
|
eefd025806 |
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/node:2": {
|
"ghcr.io/devcontainers/features/node:2": {
|
||||||
"version": "24.22.1",
|
"version": "26.13.0",
|
||||||
"pnpmVersion": "12.15.0"
|
"pnpmVersion": "12.16.1"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/rust:1": {
|
"ghcr.io/devcontainers/features/rust:1": {
|
||||||
"version": "1.99.1",
|
"version": "1.99.1",
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
@@ -185,7 +185,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
@@ -282,7 +282,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
@@ -426,7 +426,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
@@ -524,7 +524,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6.1.0
|
- uses: pnpm/action-setup@v6.1.0
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
name: Contract Parity
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "src-tauri/**/*.rs"
|
||||||
|
- "crates/**/*.rs"
|
||||||
|
- "Cargo.toml"
|
||||||
|
- "Cargo.lock"
|
||||||
|
- "src-tauri/Cargo.toml"
|
||||||
|
- "crates/**/Cargo.toml"
|
||||||
|
- "rust-toolchain.toml"
|
||||||
|
- "src-tauri/tauri.conf*.json"
|
||||||
|
- "src/services/bindings.ts"
|
||||||
|
- "scripts/gen-contract.mjs"
|
||||||
|
- "scripts/check-contract-changes.mjs"
|
||||||
|
- "biome.json"
|
||||||
|
- "biome.jsonc"
|
||||||
|
- "package.json"
|
||||||
|
- "pnpm-lock.yaml"
|
||||||
|
- "pnpm-workspace.yaml"
|
||||||
|
- "scripts/contract-inputs.json"
|
||||||
|
- ".github/workflows/contract-parity.yml"
|
||||||
|
- ".husky/pre-push"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
HUSKY: 0
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
contract-parity:
|
||||||
|
runs-on: ubuntu-26.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Install Rust Stable
|
||||||
|
uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
|
||||||
|
- name: Rust Cache
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: 'v1-rust'
|
||||||
|
key: 'rust-shared-stable-ubuntu-22.04-x86_64-unknown-linux-gnu-contract'
|
||||||
|
workspaces: |
|
||||||
|
. -> target
|
||||||
|
cache-all-crates: true
|
||||||
|
cache-workspace-crates: true
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v6
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: '26.13.0'
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Provision sidecar placeholders for tauri-build
|
||||||
|
run: |
|
||||||
|
mkdir -p src-tauri/sidecar src-tauri/resources dist
|
||||||
|
touch dist/index.html
|
||||||
|
for bin in clash-verge-service clash-verge-service-install clash-verge-service-uninstall verge-mihomo verge-mihomo-alpha; do
|
||||||
|
touch "src-tauri/sidecar/${bin}-x86_64-unknown-linux-gnu"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Check generated contract without changing the checkout
|
||||||
|
run: pnpm gen:contract --check
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
if: github.event.inputs[matrix.input] == 'true'
|
if: github.event.inputs[matrix.input] == 'true'
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Pnpm Cache
|
- name: Pnpm Cache
|
||||||
|
|||||||
@@ -2,11 +2,23 @@ name: Frontend Check
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "src/**"
|
||||||
|
- "**/*.js"
|
||||||
|
- "**/*.ts"
|
||||||
|
- "**/*.tsx"
|
||||||
|
- "**/*.css"
|
||||||
|
- "**/*.scss"
|
||||||
|
- "**/*.json"
|
||||||
|
- "**/*.md"
|
||||||
|
- "pnpm-lock.yaml"
|
||||||
|
- "pnpm-workspace.yaml"
|
||||||
|
- "vite.config.*"
|
||||||
|
- ".github/workflows/frontend-check.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
HUSKY: 0
|
HUSKY: 0
|
||||||
@@ -17,46 +29,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Check frontend changes
|
|
||||||
id: check_frontend
|
|
||||||
uses: dorny/paths-filter@v4
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
frontend:
|
|
||||||
- 'src/**'
|
|
||||||
- '**/*.js'
|
|
||||||
- '**/*.ts'
|
|
||||||
- '**/*.tsx'
|
|
||||||
- '**/*.css'
|
|
||||||
- '**/*.scss'
|
|
||||||
- '**/*.json'
|
|
||||||
- '**/*.md'
|
|
||||||
- 'package.json'
|
|
||||||
- 'pnpm-lock.yaml'
|
|
||||||
- 'pnpm-workspace.yaml'
|
|
||||||
- 'knip.json'
|
|
||||||
- 'eslint.config.ts'
|
|
||||||
- 'tsconfig.json'
|
|
||||||
- 'vite.config.*'
|
|
||||||
|
|
||||||
- name: Skip if no frontend changes
|
|
||||||
if: steps.check_frontend.outputs.frontend != 'true'
|
|
||||||
run: echo "No frontend changes, skipping frontend checks."
|
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
uses: pnpm/action-setup@v6
|
uses: pnpm/action-setup@v6
|
||||||
with:
|
with:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- uses: actions/setup-node@v7
|
- uses: actions/setup-node@v7
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Restore pnpm cache
|
- name: Restore pnpm cache
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
uses: actions/cache@v6
|
uses: actions/cache@v6
|
||||||
with:
|
with:
|
||||||
path: ~/.pnpm-store
|
path: ~/.pnpm-store
|
||||||
@@ -65,94 +48,21 @@ jobs:
|
|||||||
pnpm-shared-stable-${{ runner.os }}-
|
pnpm-shared-stable-${{ runner.os }}-
|
||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
|
|
||||||
- name: Run Prettier check
|
- name: Run Prettier check
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
run: pnpm format:check
|
run: pnpm format:check
|
||||||
|
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
run: pnpm lint
|
run: pnpm lint
|
||||||
|
|
||||||
- name: Run TypeScript typecheck
|
- name: Run TypeScript typecheck
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
run: pnpm typecheck
|
run: pnpm typecheck
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
|
|
||||||
- name: Run Knip
|
- name: Run Knip
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
run: pnpm knip:check
|
run: pnpm knip:check
|
||||||
|
|
||||||
- name: Architecture gates (listen/emit only in the event bus, invoke only in services)
|
- name: Architecture gates (listen/emit only in the event bus, invoke only in services)
|
||||||
if: steps.check_frontend.outputs.frontend == 'true'
|
|
||||||
run: node scripts/check-architecture.mjs listen emit invoke
|
run: node scripts/check-architecture.mjs listen emit invoke
|
||||||
|
|
||||||
contract-parity:
|
|
||||||
runs-on: ubuntu-26.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- name: Check contract inputs
|
|
||||||
id: check_inputs
|
|
||||||
uses: dorny/paths-filter@v4
|
|
||||||
with:
|
|
||||||
filters: scripts/contract-inputs.json
|
|
||||||
|
|
||||||
- name: Skip if contract inputs unchanged
|
|
||||||
if: steps.check_inputs.outputs.contract != 'true'
|
|
||||||
run: echo "No contract inputs changed, skipping parity check."
|
|
||||||
|
|
||||||
- name: Install Rust Stable
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
uses: dtolnay/rust-toolchain@master
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
|
|
||||||
- name: Rust Cache
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: 'v1-rust'
|
|
||||||
key: 'rust-shared-stable-ubuntu-22.04-x86_64-unknown-linux-gnu-contract'
|
|
||||||
workspaces: |
|
|
||||||
. -> target
|
|
||||||
cache-all-crates: true
|
|
||||||
cache-workspace-crates: true
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
|
|
||||||
|
|
||||||
- name: Install pnpm
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
uses: pnpm/action-setup@v6
|
|
||||||
with:
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v7
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
with:
|
|
||||||
node-version: '24.22.1'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
|
|
||||||
- name: Provision sidecar placeholders for tauri-build
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
run: |
|
|
||||||
mkdir -p src-tauri/sidecar src-tauri/resources dist
|
|
||||||
touch dist/index.html
|
|
||||||
for bin in clash-verge-service clash-verge-service-install clash-verge-service-uninstall verge-mihomo verge-mihomo-alpha; do
|
|
||||||
touch "src-tauri/sidecar/${bin}-x86_64-unknown-linux-gnu"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Check generated contract without changing the checkout
|
|
||||||
if: steps.check_inputs.outputs.contract == 'true'
|
|
||||||
run: pnpm gen:contract --check
|
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ name: Clippy Lint
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "src-tauri/**"
|
||||||
|
- "crates/**"
|
||||||
|
- "Cargo.toml"
|
||||||
|
- "Cargo.lock"
|
||||||
|
- "rust-toolchain.toml"
|
||||||
|
- ".cargo/config.toml"
|
||||||
|
- ".github/workflows/lint-clippy.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
env:
|
env:
|
||||||
HUSKY: 0
|
HUSKY: 0
|
||||||
@@ -23,28 +31,6 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check src-tauri changes
|
|
||||||
if: github.event_name != 'workflow_dispatch'
|
|
||||||
id: check_changes
|
|
||||||
uses: dorny/paths-filter@v4
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
rust:
|
|
||||||
- 'src-tauri/**'
|
|
||||||
|
|
||||||
- name: Skip if src-tauri not changed
|
|
||||||
if: github.event_name != 'workflow_dispatch' && steps.check_changes.outputs.rust != 'true'
|
|
||||||
run: echo "No src-tauri changes, skipping clippy lint."
|
|
||||||
|
|
||||||
- name: Continue if src-tauri changed
|
|
||||||
if: github.event_name != 'workflow_dispatch' && steps.check_changes.outputs.rust == 'true'
|
|
||||||
run: echo "src-tauri changed, running clippy lint."
|
|
||||||
|
|
||||||
- name: Manual trigger - always run
|
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |
|
|
||||||
echo "Manual trigger detected: skipping changes check and running clippy."
|
|
||||||
|
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
@@ -79,7 +65,7 @@ jobs:
|
|||||||
run: cargo clippy-all
|
run: cargo clippy-all
|
||||||
|
|
||||||
- name: Run Windows security tests
|
- name: Run Windows security tests
|
||||||
if: matrix.os == 'windows-latest' && (github.event_name == 'workflow_dispatch' || steps.check_changes.outputs.rust == 'true')
|
if: matrix.os == 'windows-latest'
|
||||||
working-directory: ./src-tauri
|
working-directory: ./src-tauri
|
||||||
run: cargo test --all-features --target ${{ matrix.target }}
|
run: cargo test --all-features --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
name: Prepare Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [edited, closed]
|
||||||
|
branches: [dev]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
channel:
|
||||||
|
description: Release channel
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options: [rc, stable]
|
||||||
|
default: rc
|
||||||
|
release_tag:
|
||||||
|
description: 'Optional tag: vX.Y.Z-rc[.N] for RC, vX.Y.Z for stable'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: prepare-release
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
if: >-
|
||||||
|
${{ github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.pull_request.head.repo.full_name == github.repository &&
|
||||||
|
github.event.pull_request.user.login == 'github-actions[bot]' &&
|
||||||
|
((startsWith(github.event.pull_request.head.ref, 'release/v') &&
|
||||||
|
((github.event.action == 'closed' && github.event.pull_request.merged == true) ||
|
||||||
|
(github.event.changes.body && github.event.pull_request.state == 'open' &&
|
||||||
|
contains(github.event.pull_request.body, '[x] Refresh this PR from latest dev')))) ||
|
||||||
|
(startsWith(github.event.pull_request.head.ref, 'next-dev/v') &&
|
||||||
|
github.event.changes.body && github.event.pull_request.state == 'open' &&
|
||||||
|
(contains(github.event.pull_request.body, '[x] Refresh this PR from latest dev') ||
|
||||||
|
contains(github.event.pull_request.body, '[x] Use the next minor version') !=
|
||||||
|
contains(github.event.changes.body.from, '[x] Use the next minor version'))))) }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Require the dev branch
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
env:
|
||||||
|
SOURCE_REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
if [[ "$SOURCE_REF" != 'refs/heads/dev' ]]; then
|
||||||
|
echo '::error::Run Prepare Release from the dev branch.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: refs/heads/dev
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: '26.13.0'
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v6
|
||||||
|
if: github.event.action != 'closed'
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile --ignore-scripts
|
||||||
|
if: github.event.action != 'closed'
|
||||||
|
|
||||||
|
- name: Create or refresh version or publication PR
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ inputs.release_tag }}
|
||||||
|
RELEASE_CHANNEL: ${{ inputs.channel }}
|
||||||
|
RELEASE_COMMAND: ${{ github.event.action == 'closed' && 'publish-pr' || startsWith(github.event.pull_request.head.ref, 'next-dev/') && 'next-dev' || 'prepare' }}
|
||||||
|
run: node scripts-workflow/release.mjs "$RELEASE_COMMAND"
|
||||||
+123
-76
@@ -1,6 +1,9 @@
|
|||||||
name: Release Build
|
name: Release Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [edited]
|
||||||
|
branches: [main]
|
||||||
push:
|
push:
|
||||||
# -rc tag 时预览发布, 跳过 telegram 通知、跳过 winget 提交、跳过 latest.json 文件更新
|
# -rc tag 时预览发布, 跳过 telegram 通知、跳过 winget 提交、跳过 latest.json 文件更新
|
||||||
tags:
|
tags:
|
||||||
@@ -11,75 +14,59 @@ env:
|
|||||||
RUST_BACKTRACE: short
|
RUST_BACKTRACE: short
|
||||||
HUSKY: 0
|
HUSKY: 0
|
||||||
concurrency:
|
concurrency:
|
||||||
group: '${{ github.workflow }} - ${{ github.head_ref || github.ref }}'
|
group: release-publication
|
||||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_tag_version:
|
prepare_release:
|
||||||
name: Check Release Tag and package.json Version Consistency
|
name: Approve Snapshot and Prepare Draft Release
|
||||||
|
if: >-
|
||||||
|
${{ github.event_name == 'push' ||
|
||||||
|
(github.event.changes.body &&
|
||||||
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||||
|
github.event.pull_request.user.login == 'github-actions[bot]' &&
|
||||||
|
startsWith(github.event.pull_request.head.ref, 'publish/v') &&
|
||||||
|
contains(github.event.pull_request.body, '[x] Publish this approved release')) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: read
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
outputs:
|
||||||
|
release_tag: ${{ steps.source.outputs.release_tag }}
|
||||||
|
source_sha: ${{ steps.source.outputs.source_sha }}
|
||||||
|
channel: ${{ steps.source.outputs.channel }}
|
||||||
|
release_id: ${{ steps.draft.outputs.id }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout trusted release tooling
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
|
ref: refs/heads/dev
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-node@v7
|
||||||
- name: Check if tag is from main branch
|
|
||||||
run: |
|
|
||||||
TAG_REF="${GITHUB_REF##*/}"
|
|
||||||
echo "Checking if tag $TAG_REF is from main branch..."
|
|
||||||
|
|
||||||
TAG_COMMIT=$(git rev-list -n 1 $TAG_REF)
|
|
||||||
MAIN_COMMITS=$(git rev-list origin/main)
|
|
||||||
|
|
||||||
if echo "$MAIN_COMMITS" | grep -q "$TAG_COMMIT"; then
|
|
||||||
echo "✅ Tag $TAG_REF is from main branch"
|
|
||||||
else
|
|
||||||
echo "❌ Tag $TAG_REF is not from main branch"
|
|
||||||
echo "This release workflow only accepts tags from main branch."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check tag and package.json version
|
|
||||||
run: |
|
|
||||||
TAG_REF="${GITHUB_REF_NAME:-${GITHUB_REF##*/}}"
|
|
||||||
echo "Current tag: $TAG_REF"
|
|
||||||
|
|
||||||
PKG_VERSION=$(jq -r .version package.json)
|
|
||||||
echo "package.json version: $PKG_VERSION"
|
|
||||||
|
|
||||||
EXPECTED_TAG="v$PKG_VERSION"
|
|
||||||
|
|
||||||
if [[ "$TAG_REF" != "$EXPECTED_TAG" ]]; then
|
|
||||||
echo "❌ Version mismatch:"
|
|
||||||
echo " Git tag : $TAG_REF"
|
|
||||||
echo " package.json : $EXPECTED_TAG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Tag and package.json version are consistent."
|
|
||||||
|
|
||||||
prepare_release:
|
|
||||||
name: Prepare Draft Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [check_tag_version]
|
|
||||||
outputs:
|
|
||||||
release_id: ${{ steps.prepare_release.outputs.id }}
|
|
||||||
steps:
|
|
||||||
- name: Create or update draft release
|
|
||||||
id: prepare_release
|
|
||||||
uses: softprops/action-gh-release@v3
|
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref_name }}
|
node-version: '26.13.0'
|
||||||
target_commitish: ${{ github.sha }}
|
- name: Validate source and publish the approved snapshot
|
||||||
name: 'Clash Verge Rev ${{ github.ref_name }}'
|
id: source
|
||||||
body: 'Draft release, will be updated later.'
|
env:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
RELEASE_COMMAND: ${{ github.event_name == 'push' && 'validate-tag' || 'publish' }}
|
||||||
draft: true
|
run: node scripts-workflow/release.mjs "$RELEASE_COMMAND"
|
||||||
prerelease: ${{ contains(github.ref_name, '-rc') }}
|
|
||||||
|
- name: Create or reuse draft release
|
||||||
|
id: draft
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ steps.source.outputs.release_tag }}
|
||||||
|
SOURCE_SHA: ${{ steps.source.outputs.source_sha }}
|
||||||
|
run: node scripts-workflow/release.mjs prepare-draft
|
||||||
|
|
||||||
update_tag:
|
update_tag:
|
||||||
name: Update tag
|
name: Publish Completed Release
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
|
||||||
|
outputs:
|
||||||
|
source_sha: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
|
channel: ${{ needs.prepare_release.outputs.channel }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
[
|
[
|
||||||
@@ -91,6 +78,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
|
|
||||||
- name: Group changelog platforms
|
- name: Group changelog platforms
|
||||||
run: bash ./scripts-workflow/group_platforms.sh
|
run: bash ./scripts-workflow/group_platforms.sh
|
||||||
@@ -103,7 +92,7 @@ jobs:
|
|||||||
- name: Set Env
|
- name: Set Env
|
||||||
run: |
|
run: |
|
||||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||||
TAG_REF="${GITHUB_REF##*/}"
|
TAG_REF="$RELEASE_TAG"
|
||||||
echo "TAG_NAME=$TAG_REF" >> $GITHUB_ENV
|
echo "TAG_NAME=$TAG_REF" >> $GITHUB_ENV
|
||||||
VERSION=$(echo "$TAG_REF" | sed 's/^v//')
|
VERSION=$(echo "$TAG_REF" | sed 's/^v//')
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
@@ -149,10 +138,16 @@ jobs:
|
|||||||
Created at ${{ env.BUILDTIME }}.
|
Created at ${{ env.BUILDTIME }}.
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
- name: Verify release tag still points to the approved source
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SOURCE_SHA: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
|
run: node scripts-workflow/release.mjs verify-target
|
||||||
|
|
||||||
- name: Publish Release
|
- name: Publish Release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
PRERELEASE: ${{ contains(github.ref_name, '-rc') }}
|
PRERELEASE: ${{ contains(env.RELEASE_TAG, '-rc') }}
|
||||||
run: |
|
run: |
|
||||||
release_id="${{ needs.prepare_release.outputs.release_id }}"
|
release_id="${{ needs.prepare_release.outputs.release_id }}"
|
||||||
|
|
||||||
@@ -181,6 +176,8 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
name: Release Build
|
name: Release Build
|
||||||
needs: [prepare_release]
|
needs: [prepare_release]
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -200,6 +197,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
|
|
||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
@@ -230,7 +229,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -249,7 +248,6 @@ jobs:
|
|||||||
echo "Rust target ${{ matrix.target }} installed."
|
echo "Rust target ${{ matrix.target }} installed."
|
||||||
|
|
||||||
- name: Tauri build
|
- name: Tauri build
|
||||||
# 上游 5.24 修改了 latest.json 的生成逻辑,且依赖 tauri-plugin-update 2.10.0 暂未发布,故锁定在 0.5.23 版本
|
|
||||||
uses: tauri-apps/tauri-action@v1.0.0
|
uses: tauri-apps/tauri-action@v1.0.0
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||||
@@ -264,11 +262,14 @@ jobs:
|
|||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
with:
|
with:
|
||||||
releaseId: ${{ needs.prepare_release.outputs.release_id }}
|
releaseId: ${{ needs.prepare_release.outputs.release_id }}
|
||||||
tagName: ${{ github.ref_name }}
|
tagName: ${{ env.RELEASE_TAG }}
|
||||||
releaseBody: 'Draft release, will be updated later.'
|
releaseBody: 'Draft release, will be updated later.'
|
||||||
|
releaseDraft: true
|
||||||
|
prerelease: ${{ needs.prepare_release.outputs.channel == 'rc' }}
|
||||||
|
releaseCommitish: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
tauriScript: pnpm
|
tauriScript: pnpm
|
||||||
args: --target ${{ matrix.target }}
|
args: --target ${{ matrix.target }}
|
||||||
includeUpdaterJson: true
|
uploadUpdaterJson: ${{ needs.prepare_release.outputs.channel == 'stable' }}
|
||||||
|
|
||||||
- name: Attest Windows bundles
|
- name: Attest Windows bundles
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@@ -293,6 +294,8 @@ jobs:
|
|||||||
release-for-linux-arm:
|
release-for-linux-arm:
|
||||||
name: Release Build for Linux ARM
|
name: Release Build for Linux ARM
|
||||||
needs: [prepare_release]
|
needs: [prepare_release]
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -307,6 +310,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
|
|
||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
@@ -331,7 +336,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v6
|
uses: pnpm/action-setup@v6
|
||||||
@@ -420,7 +425,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
gh release upload "${{ github.ref_name }}" \
|
gh release upload "$RELEASE_TAG" \
|
||||||
target/${{ matrix.target }}/release/bundle/deb/*.deb \
|
target/${{ matrix.target }}/release/bundle/deb/*.deb \
|
||||||
target/${{ matrix.target }}/release/bundle/rpm/*.rpm \
|
target/${{ matrix.target }}/release/bundle/rpm/*.rpm \
|
||||||
--clobber
|
--clobber
|
||||||
@@ -428,6 +433,8 @@ jobs:
|
|||||||
release-for-fixed-webview2:
|
release-for-fixed-webview2:
|
||||||
name: Release Build for Fixed WebView2
|
name: Release Build for Fixed WebView2
|
||||||
needs: [prepare_release]
|
needs: [prepare_release]
|
||||||
|
env:
|
||||||
|
RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -442,6 +449,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
|
|
||||||
- name: Install Rust Stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
@@ -466,7 +475,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -532,22 +541,55 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
$files = Get-ChildItem "target\${{ matrix.target }}\release\bundle\nsis\*setup*" | Select-Object -ExpandProperty FullName
|
$files = Get-ChildItem "target\${{ matrix.target }}\release\bundle\nsis\*setup*" | Select-Object -ExpandProperty FullName
|
||||||
gh release upload "${{ github.ref_name }}" $files --clobber
|
gh release upload "$env:RELEASE_TAG" $files --clobber
|
||||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||||
|
|
||||||
|
prepare-next-dev:
|
||||||
|
name: Prepare Next Dev Version
|
||||||
|
if: ${{ needs.update_tag.outputs.channel == 'stable' }}
|
||||||
|
needs: [prepare_release, update_tag]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
concurrency:
|
||||||
|
group: prepare-release
|
||||||
|
cancel-in-progress: false
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
RELEASE_TAG: ${{ needs.prepare_release.outputs.release_tag }}
|
||||||
|
SOURCE_SHA: ${{ needs.prepare_release.outputs.source_sha }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: refs/heads/dev
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-node@v7
|
||||||
|
with:
|
||||||
|
node-version: '26.13.0'
|
||||||
|
- uses: pnpm/action-setup@v6
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
- name: Prepare version and changelog PR
|
||||||
|
run: |
|
||||||
|
pnpm install --frozen-lockfile --ignore-scripts
|
||||||
|
node scripts-workflow/release.mjs next-dev
|
||||||
|
|
||||||
release-update:
|
release-update:
|
||||||
if: ${{ !contains(github.ref_name, '-rc') }}
|
if: ${{ needs.update_tag.outputs.channel == 'stable' }}
|
||||||
name: Release Update
|
name: Release Update
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [update_tag]
|
needs: [update_tag]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.update_tag.outputs.source_sha }}
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -563,17 +605,19 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
release-update-for-fixed-webview2:
|
release-update-for-fixed-webview2:
|
||||||
if: ${{ !contains(github.ref_name, '-rc') }}
|
if: ${{ needs.update_tag.outputs.channel == 'stable' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [update_tag]
|
needs: [update_tag]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.update_tag.outputs.source_sha }}
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -589,7 +633,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
submit-to-winget:
|
submit-to-winget:
|
||||||
if: ${{ !contains(github.ref_name, '-rc') }}
|
if: ${{ needs.update_tag.outputs.channel == 'stable' }}
|
||||||
name: Submit to Winget
|
name: Submit to Winget
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [update_tag, release-update]
|
needs: [update_tag, release-update]
|
||||||
@@ -597,6 +641,7 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
with:
|
with:
|
||||||
|
ref: ${{ needs.update_tag.outputs.source_sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
run: |
|
run: |
|
||||||
@@ -613,7 +658,7 @@ jobs:
|
|||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
|
||||||
notify-telegram:
|
notify-telegram:
|
||||||
if: ${{ !contains(github.ref_name, '-rc') }}
|
if: ${{ needs.update_tag.outputs.channel == 'stable' }}
|
||||||
name: Notify Telegram
|
name: Notify Telegram
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
@@ -626,6 +671,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.update_tag.outputs.source_sha }}
|
||||||
|
|
||||||
- name: Group changelog platforms
|
- name: Group changelog platforms
|
||||||
run: bash ./scripts-workflow/group_platforms.sh
|
run: bash ./scripts-workflow/group_platforms.sh
|
||||||
@@ -638,7 +685,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ name: Check Formatting
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "src-tauri/**"
|
||||||
|
- "**/*.rs"
|
||||||
|
- "rustfmt.toml"
|
||||||
|
- "rust-toolchain.toml"
|
||||||
|
- ".github/workflows/rustfmt.yml"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -19,25 +25,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Check Rust changes
|
|
||||||
id: check_rust
|
|
||||||
uses: dorny/paths-filter@v4
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
rust:
|
|
||||||
- 'src-tauri/**'
|
|
||||||
- '**/*.rs'
|
|
||||||
|
|
||||||
- name: Skip if no Rust changes
|
|
||||||
if: steps.check_rust.outputs.rust != 'true'
|
|
||||||
run: echo "No Rust changes, skipping rustfmt."
|
|
||||||
|
|
||||||
- name: install Rust stable and rustfmt
|
- name: install Rust stable and rustfmt
|
||||||
if: steps.check_rust.outputs.rust == 'true'
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
|
|
||||||
- name: run cargo fmt
|
- name: run cargo fmt
|
||||||
if: steps.check_rust.outputs.rust == 'true'
|
|
||||||
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
|
run: cargo fmt --manifest-path ./src-tauri/Cargo.toml --all -- --check
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v6
|
uses: pnpm/action-setup@v6
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: '24.22.1'
|
node-version: '26.13.0'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v6
|
- uses: pnpm/action-setup@v6
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
nodejs 24.22.1
|
nodejs 26.13.0
|
||||||
pnpm 12.15.0
|
pnpm 12.16.1
|
||||||
rust 1.99.1
|
rust 1.99.1
|
||||||
|
|||||||
+19
-6
@@ -28,7 +28,7 @@ Before contributing, you need to set up your development environment. Follow the
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
1. **Install Rust and Node.js**
|
1. **Install Rust and Node.js**
|
||||||
Our project requires both Rust and Node.js. Follow the official installation instructions [here](https://tauri.app/start/prerequisites/).
|
Follow the [Tauri prerequisites](https://tauri.app/start/prerequisites/) for your operating system. Use the Node.js version in [`.tool-versions`](.tool-versions); [`rust-toolchain.toml`](rust-toolchain.toml) selects the Rust version and required components when you run Cargo from this repository.
|
||||||
|
|
||||||
### Windows Users
|
### Windows Users
|
||||||
|
|
||||||
@@ -51,18 +51,21 @@ rustup set default-host x86_64-pc-windows-msvc
|
|||||||
|
|
||||||
### Install Node.js Package Manager
|
### Install Node.js Package Manager
|
||||||
|
|
||||||
Enable `corepack`:
|
[Corepack is no longer bundled with Node.js starting with Node.js 25](https://nodejs.org/api/corepack.html). Install it globally if it is not already available, then enable it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
npm install --global corepack
|
||||||
corepack enable
|
corepack enable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run pnpm from this repository so Corepack selects the version pinned by `packageManager` in [`package.json`](package.json).
|
||||||
|
|
||||||
### Install Project Dependencies
|
### Install Project Dependencies
|
||||||
|
|
||||||
Node.js dependencies:
|
Node.js dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install
|
pnpm install --frozen-lockfile
|
||||||
```
|
```
|
||||||
|
|
||||||
Ubuntu-only system packages:
|
Ubuntu-only system packages:
|
||||||
@@ -78,14 +81,24 @@ pnpm run prebuild
|
|||||||
pnpm run prebuild --force # Re-download and overwrite Mihomo core and service binaries
|
pnpm run prebuild --force # Re-download and overwrite Mihomo core and service binaries
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Prepare the Development Service Source
|
||||||
|
|
||||||
|
`pnpm dev` builds the development service from a sibling checkout of [`clash-verge-service-ipc`](https://github.com/clash-verge-rev/clash-verge-service-ipc). The binaries downloaded by `pnpm prebuild` do not replace this source checkout.
|
||||||
|
|
||||||
|
Check the `clash_verge_service_ipc` dependency in [`src-tauri/Cargo.toml`](src-tauri/Cargo.toml), then clone the service repository alongside `clash-verge-rev` and check out the matching tag. If the sibling directory already exists, reuse it and check its version rather than overwriting it. Without this checkout, `pnpm dev` fails when it tries to access `../clash-verge-service-ipc/Cargo.toml`.
|
||||||
|
|
||||||
### Run the Development Server
|
### Run the Development Server
|
||||||
|
|
||||||
|
Run these commands from the root of `clash-verge-rev`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm dev # Standard
|
pnpm dev # Build the development service and start the desktop app
|
||||||
pnpm dev:diff # If an app instance already exists
|
pnpm dev:diff # Alias of pnpm dev
|
||||||
pnpm dev:tauri # Run Tauri development mode
|
pnpm dev:tauri # Run Tauri directly with the tauri-dev feature
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For frontend-only development, use `pnpm web:dev`. It does not start the Rust backend or build the development service.
|
||||||
|
|
||||||
### Build the Project
|
### Build the Project
|
||||||
|
|
||||||
Standard build:
|
Standard build:
|
||||||
|
|||||||
Generated
+38
-69
@@ -33,6 +33,7 @@ dependencies = [
|
|||||||
"cipher",
|
"cipher",
|
||||||
"cpubits",
|
"cpubits",
|
||||||
"cpufeatures 0.3.0",
|
"cpufeatures 0.3.0",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -135,15 +136,6 @@ version = "1.0.104"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arbitrary"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
|
||||||
dependencies = [
|
|
||||||
"derive_arbitrary",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arboard"
|
name = "arboard"
|
||||||
version = "3.6.1"
|
version = "3.6.1"
|
||||||
@@ -1175,7 +1167,7 @@ dependencies = [
|
|||||||
"windows-service",
|
"windows-service",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
"winreg 0.56.0",
|
"winreg 0.56.0",
|
||||||
"zip 9.0.4",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1342,7 +1334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1870,17 +1862,6 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive_arbitrary"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.119",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -2034,7 +2015,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users 0.5.2",
|
"redox_users 0.5.2",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2293,7 +2274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2809,7 +2790,7 @@ dependencies = [
|
|||||||
"gobject-sys 0.21.5",
|
"gobject-sys 0.21.5",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps 7.0.8",
|
"system-deps 7.0.8",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3363,7 +3344,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core 0.62.2",
|
"windows-core 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4051,9 +4032,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lzma-rust2"
|
name = "lzma-rust2"
|
||||||
version = "0.22.0"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7277c9742229dc25243236d578b6471a798c554512c6f6a04365ec2e87c2a3bb"
|
checksum = "f1c46f3fcd2e888afdb01a07efc071cb21300e07c68d32c3e8b01b5c8cb1fcaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sha2 0.11.1",
|
"sha2 0.11.1",
|
||||||
]
|
]
|
||||||
@@ -4234,7 +4215,7 @@ dependencies = [
|
|||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4401,7 +4382,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4878,7 +4859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5543,7 +5524,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6047,7 +6028,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.4.15",
|
"linux-raw-sys 0.4.15",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6060,7 +6041,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.12.1",
|
"linux-raw-sys 0.12.1",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6119,7 +6100,7 @@ dependencies = [
|
|||||||
"security-framework",
|
"security-framework",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs",
|
"webpki-root-certs",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6739,7 +6720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7088,9 +7069,9 @@ checksum = "c0e973b34477b7823833469eb0f5a3a60370fef7a453e02d751b59180d0a5a05"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tao"
|
name = "tao"
|
||||||
version = "0.37.1"
|
version = "0.37.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f37f381f4e048e6cdf038b5705f8cf14ad108279d46eb968140a7b291aba9400"
|
checksum = "d5777b06fe6435126ae8d816eba269e050e1e10d302cf5461e9c49eccabc3548"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.2",
|
"bitflags 2.13.2",
|
||||||
"block2",
|
"block2",
|
||||||
@@ -7575,11 +7556,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-updater"
|
name = "tauri-plugin-updater"
|
||||||
version = "2.14.0"
|
version = "2.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7259989d4fe11b2bf51f7d671642249b1837c4ae2da1430dc713ae38f98ceeec"
|
checksum = "d957abbcb3f9f6c76d22a3a0f012f30880640d0bccb5634d2b12a9937e4c872b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.23.1",
|
||||||
"dirs 7.0.0",
|
"dirs 7.0.0",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -7604,7 +7585,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
"zip 4.6.1",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -7805,10 +7786,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8474,7 +8455,7 @@ dependencies = [
|
|||||||
"png 0.18.1",
|
"png 0.18.1",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8553,9 +8534,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-path"
|
name = "typed-path"
|
||||||
version = "0.12.3"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
|
checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typeid"
|
name = "typeid"
|
||||||
@@ -8583,7 +8564,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"memoffset",
|
"memoffset",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9125,7 +9106,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9146,7 +9127,7 @@ dependencies = [
|
|||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-quartz-core",
|
"objc2-quartz-core",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
"windows-version",
|
"windows-version",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -9721,7 +9702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -9762,11 +9743,11 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wry"
|
name = "wry"
|
||||||
version = "0.57.0"
|
version = "0.57.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0"
|
checksum = "759c50ddad0ef9913bb5496cb9d509a50433a3b78cb0238d61105526589cf0e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.23.1",
|
||||||
"block2",
|
"block2",
|
||||||
"cookie",
|
"cookie",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
@@ -9787,7 +9768,6 @@ dependencies = [
|
|||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"objc2-web-kit",
|
"objc2-web-kit",
|
||||||
"once_cell",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
@@ -10054,21 +10034,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "4.6.1"
|
version = "9.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb"
|
||||||
dependencies = [
|
|
||||||
"arbitrary",
|
|
||||||
"crc32fast",
|
|
||||||
"indexmap 2.14.0",
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zip"
|
|
||||||
version = "9.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a954114b47a54df49f83d2722814e86b23d4bb1b54a1b90fb7d92b41619ddc22"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
@@ -10087,6 +10055,7 @@ dependencies = [
|
|||||||
"sha1 0.11.0",
|
"sha1 0.11.0",
|
||||||
"time",
|
"time",
|
||||||
"typed-path",
|
"typed-path",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
"zopfli",
|
"zopfli",
|
||||||
"zstd",
|
"zstd",
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
## v2.5.9
|
## v2.5.9
|
||||||
|
|
||||||
|
- **Mihomo(Meta) 内核升级至 v1.19.33**
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><strong> 🐞 修复问题 </strong></summary>
|
<summary><strong> 🐞 修复问题 </strong></summary>
|
||||||
|
|
||||||
|
- 修复规则扩展包含空列表项时,「编辑规则」界面报错的问题
|
||||||
|
- 修复 重启应用后定时自动备份不执行的问题
|
||||||
- 修复托盘测速与代理页行为不一致的问题,移除托盘「测试延迟」入口
|
- 修复托盘测速与代理页行为不一致的问题,移除托盘「测试延迟」入口
|
||||||
- 修复通过 URL 导入订阅时,即使导入的订阅并非当前使用的订阅,也会重新加载当前配置
|
- 修复通过 URL 导入订阅时,即使导入的订阅并非当前使用的订阅,也会重新加载当前配置
|
||||||
- 修复 首页 网站测速失败后卡片一直显示测试中
|
- 修复 首页 网站测速失败后卡片一直显示测试中
|
||||||
- 修复 代理 页展开分组后不显示最新测速结果,需切换节点才刷新
|
- 修复 代理 页展开分组后不显示最新测速结果,需切换节点才刷新
|
||||||
|
- 修复 代理 在全局模式下无法展开节点组
|
||||||
|
- 修复 自定义 CSS 无法修改编辑器字体和字号的问题
|
||||||
|
- 修复 WebDAV 设置清空后无法保存
|
||||||
|
|
||||||
|
**🍎 macOS**
|
||||||
|
|
||||||
|
- macOS:修复 Bigsur 上首页测速和流量图无法显示的问题
|
||||||
|
|
||||||
**🐧 Linux**
|
**🐧 Linux**
|
||||||
|
|
||||||
@@ -18,6 +29,8 @@
|
|||||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||||
|
|
||||||
- 新增虚拟网卡设置的「DNS 模式」选项,支持配置 mihomo 系统 DNS 行为
|
- 新增虚拟网卡设置的「DNS 模式」选项,支持配置 mihomo 系统 DNS 行为
|
||||||
|
- 新增「IP 信息」自动刷新开关,关闭后仍可手动刷新
|
||||||
|
- 新增 Muse AI 解锁检测,针对 Muse AI 特有的 Cloudflare 人机验证增加「Risky」状态标记
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ mod chatgpt;
|
|||||||
mod claude;
|
mod claude;
|
||||||
mod disney_plus;
|
mod disney_plus;
|
||||||
mod gemini;
|
mod gemini;
|
||||||
|
mod muse;
|
||||||
mod netflix;
|
mod netflix;
|
||||||
mod prime_video;
|
mod prime_video;
|
||||||
mod spotify;
|
mod spotify;
|
||||||
@@ -30,6 +31,7 @@ enum UnlockCheck {
|
|||||||
ChatGptWeb,
|
ChatGptWeb,
|
||||||
Claude,
|
Claude,
|
||||||
Gemini,
|
Gemini,
|
||||||
|
MuseAi,
|
||||||
YouTubePremium,
|
YouTubePremium,
|
||||||
BahamutAnime,
|
BahamutAnime,
|
||||||
Netflix,
|
Netflix,
|
||||||
@@ -46,6 +48,7 @@ impl UnlockCheck {
|
|||||||
Self::ChatGptWeb,
|
Self::ChatGptWeb,
|
||||||
Self::Claude,
|
Self::Claude,
|
||||||
Self::Gemini,
|
Self::Gemini,
|
||||||
|
Self::MuseAi,
|
||||||
Self::YouTubePremium,
|
Self::YouTubePremium,
|
||||||
Self::BahamutAnime,
|
Self::BahamutAnime,
|
||||||
Self::Netflix,
|
Self::Netflix,
|
||||||
@@ -66,6 +69,7 @@ impl UnlockCheck {
|
|||||||
Self::ChatGptWeb => chatgpt::CHATGPT_WEB_NAME,
|
Self::ChatGptWeb => chatgpt::CHATGPT_WEB_NAME,
|
||||||
Self::Claude => claude::CLAUDE_NAME,
|
Self::Claude => claude::CLAUDE_NAME,
|
||||||
Self::Gemini => gemini::GEMINI_NAME,
|
Self::Gemini => gemini::GEMINI_NAME,
|
||||||
|
Self::MuseAi => muse::MUSE_AI_NAME,
|
||||||
Self::YouTubePremium => youtube::YOUTUBE_PREMIUM_NAME,
|
Self::YouTubePremium => youtube::YOUTUBE_PREMIUM_NAME,
|
||||||
Self::BahamutAnime => bahamut::BAHAMUT_ANIME_NAME,
|
Self::BahamutAnime => bahamut::BAHAMUT_ANIME_NAME,
|
||||||
Self::Netflix => netflix::NETFLIX_NAME,
|
Self::Netflix => netflix::NETFLIX_NAME,
|
||||||
@@ -83,6 +87,7 @@ impl UnlockCheck {
|
|||||||
Self::ChatGptWeb => chatgpt::check_chatgpt(client).await,
|
Self::ChatGptWeb => chatgpt::check_chatgpt(client).await,
|
||||||
Self::Claude => claude::check_claude(client).await,
|
Self::Claude => claude::check_claude(client).await,
|
||||||
Self::Gemini => gemini::check_gemini(client).await,
|
Self::Gemini => gemini::check_gemini(client).await,
|
||||||
|
Self::MuseAi => muse::check_muse_ai(client).await,
|
||||||
Self::YouTubePremium => youtube::check_youtube_premium(client).await,
|
Self::YouTubePremium => youtube::check_youtube_premium(client).await,
|
||||||
Self::BahamutAnime => bahamut::check_bahamut_anime(client).await,
|
Self::BahamutAnime => bahamut::check_bahamut_anime(client).await,
|
||||||
Self::Netflix => netflix::check_netflix(client).await,
|
Self::Netflix => netflix::check_netflix(client).await,
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
use crate::utils::{classify_restricted_status, get_trace_location};
|
||||||
|
|
||||||
|
use super::UnlockItem;
|
||||||
|
|
||||||
|
pub(crate) const MUSE_AI_NAME: &str = "Muse AI";
|
||||||
|
|
||||||
|
const MUSE_URL: &str = "https://muse.ai";
|
||||||
|
const CLOUDFLARE_TRACE_URL: &str = "https://www.cloudflare.com/cdn-cgi/trace";
|
||||||
|
|
||||||
|
// The aymh ("Are You a Human") gateway answers plain HTTP requests with a
|
||||||
|
// generic Meta error page that always contains the Facebook logo asset.
|
||||||
|
const AYMH_ERROR_MARKERS: &[&str] = &["Sorry, something went wrong", "static.facebook.com/images/logos"];
|
||||||
|
|
||||||
|
pub(super) async fn check_muse_ai(client: &Client) -> UnlockItem {
|
||||||
|
// muse.ai itself is served by Vercel, so resolve the exit region through
|
||||||
|
// the generic Cloudflare trace endpoint instead of a service-specific one.
|
||||||
|
let region = get_trace_location(client, CLOUDFLARE_TRACE_URL)
|
||||||
|
.await
|
||||||
|
.map(|code| code.trim().to_ascii_uppercase())
|
||||||
|
.map(|code| UnlockItem::region_label(&code));
|
||||||
|
|
||||||
|
let Ok(response) = client.get(MUSE_URL).send().await else {
|
||||||
|
return UnlockItem::checked(MUSE_AI_NAME, "Failed (Network Connection)", region);
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
let landed_on_aymh = response.url().host_str() == Some("auth.muse.ai");
|
||||||
|
let body = response.text().await.unwrap_or_default();
|
||||||
|
|
||||||
|
// Every region is redirected to the aymh anti-bot gateway before the app
|
||||||
|
// loads, and a non-browser HTTP client cannot finish the JavaScript
|
||||||
|
// challenge. It returns the Meta error page, so reaching this point only
|
||||||
|
// proves the exit can connect to Muse's auth infrastructure; availability
|
||||||
|
// cannot be verified over plain HTTP and is reported as `Risky`.
|
||||||
|
let behind_anti_bot = landed_on_aymh || AYMH_ERROR_MARKERS.iter().any(|marker| body.contains(marker));
|
||||||
|
|
||||||
|
let result = if behind_anti_bot {
|
||||||
|
"Risky"
|
||||||
|
} else if status.is_success() {
|
||||||
|
"Yes"
|
||||||
|
} else {
|
||||||
|
classify_restricted_status(status).unwrap_or("Failed")
|
||||||
|
};
|
||||||
|
|
||||||
|
UnlockItem::checked(MUSE_AI_NAME, result, region)
|
||||||
|
}
|
||||||
@@ -129,6 +129,7 @@ export default defineConfig([
|
|||||||
'eslint.config.ts',
|
'eslint.config.ts',
|
||||||
`vite.config.mts`,
|
`vite.config.mts`,
|
||||||
'scripts/*.mjs',
|
'scripts/*.mjs',
|
||||||
|
'scripts-workflow/*.mjs',
|
||||||
'tests/*.ts',
|
'tests/*.ts',
|
||||||
'src/polyfills/*.js',
|
'src/polyfills/*.js',
|
||||||
'tests/*.mjs',
|
'tests/*.mjs',
|
||||||
@@ -238,6 +239,13 @@ export default defineConfig([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['scripts-workflow/*.mjs'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: globals.node,
|
||||||
|
},
|
||||||
|
rules: { 'es-x/no-object-groupby': 'off' },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
files: ['scripts/perf/*.{mjs,mts}'],
|
files: ['scripts/perf/*.{mjs,mts}'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
|
|||||||
+10
-10
@@ -14,7 +14,7 @@
|
|||||||
"build:fast": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build -- --profile fast-release",
|
"build:fast": "cross-env NODE_OPTIONS='--max-old-space-size=4096' tauri build -- --profile fast-release",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"web:dev": "vite",
|
"web:dev": "vite",
|
||||||
"web:build": "tsc --noEmit && vite build",
|
"web:build": "tsrs -p . --noEmit && vite build",
|
||||||
"web:serve": "vite preview",
|
"web:serve": "vite preview",
|
||||||
"prebuild": "node scripts/prebuild.mjs",
|
"prebuild": "node scripts/prebuild.mjs",
|
||||||
"updater": "node scripts/updater.mjs",
|
"updater": "node scripts/updater.mjs",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"i18n:types": "node scripts/generate-i18n-keys.mjs",
|
"i18n:types": "node scripts/generate-i18n-keys.mjs",
|
||||||
"gen:contract": "node scripts/gen-contract.mjs",
|
"gen:contract": "node scripts/gen-contract.mjs",
|
||||||
"arch:check": "node scripts/check-architecture.mjs listen emit invoke",
|
"arch:check": "node scripts/check-architecture.mjs listen emit invoke",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsrs -p . --noEmit",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"knip:check": "knip",
|
"knip:check": "knip",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"@tauri-apps/plugin-fs": "2.7.1",
|
"@tauri-apps/plugin-fs": "2.7.1",
|
||||||
"@tauri-apps/plugin-http": "2.8.3",
|
"@tauri-apps/plugin-http": "2.8.3",
|
||||||
"@tauri-apps/plugin-opener": "2.7.2",
|
"@tauri-apps/plugin-opener": "2.7.2",
|
||||||
"@tauri-apps/plugin-updater": "2.14.0",
|
"@tauri-apps/plugin-updater": "2.14.1",
|
||||||
"ahooks": "^3.10.0",
|
"ahooks": "^3.10.0",
|
||||||
"cidr-block": "^2.3.0",
|
"cidr-block": "^2.3.0",
|
||||||
"compare-versions": "^6.1.1",
|
"compare-versions": "^6.1.1",
|
||||||
@@ -79,12 +79,11 @@
|
|||||||
"react-dom": "19.3.1",
|
"react-dom": "19.3.1",
|
||||||
"react-error-boundary": "6.1.6",
|
"react-error-boundary": "6.1.6",
|
||||||
"react-hook-form": "^7.88.0",
|
"react-hook-form": "^7.88.0",
|
||||||
"react-i18next": "17.1.8",
|
"react-i18next": "17.1.9",
|
||||||
"react-markdown": "10.1.0",
|
"react-markdown": "10.1.0",
|
||||||
"react-router": "^8.4.0",
|
"react-router": "^8.4.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-sanitize": "^6.0.0",
|
"rehype-sanitize": "^6.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
|
||||||
"swr": "^2.5.1",
|
"swr": "^2.5.1",
|
||||||
"tauri-plugin-mihomo-api": "github:clash-verge-rev/tauri-plugin-mihomo#main",
|
"tauri-plugin-mihomo-api": "github:clash-verge-rev/tauri-plugin-mihomo#main",
|
||||||
"types-pac": "^1.0.3",
|
"types-pac": "^1.0.3",
|
||||||
@@ -97,7 +96,7 @@
|
|||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@tauri-apps/cli": "2.12.3",
|
"@tauri-apps/cli": "2.12.3",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^24.13.6",
|
"@types/node": "^26.0.0",
|
||||||
"@types/react": "19.3.0",
|
"@types/react": "19.3.0",
|
||||||
"@types/react-dom": "19.3.0",
|
"@types/react-dom": "19.3.0",
|
||||||
"@types/validator": "^13.15.10",
|
"@types/validator": "^13.15.10",
|
||||||
@@ -118,23 +117,24 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.5.7",
|
"eslint-plugin-react-refresh": "^0.5.7",
|
||||||
"eslint-plugin-sukka": "^10.2.0",
|
"eslint-plugin-sukka": "^10.2.0",
|
||||||
"eslint-plugin-unused-imports": "^4.4.1",
|
"eslint-plugin-unused-imports": "^4.4.1",
|
||||||
"glob": "^13.0.6",
|
"glob": "^14.0.0",
|
||||||
"globals": "^17.12.0",
|
"globals": "^17.12.0",
|
||||||
"https-proxy-agent": "^9.1.0",
|
"https-proxy-agent": "^9.1.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jiti": "^2.7.0",
|
"jiti": "^2.7.0",
|
||||||
"knip": "6.43.0",
|
"knip": "6.44.1",
|
||||||
"lint-staged": "^17.5.1",
|
"lint-staged": "^17.5.1",
|
||||||
"sass": "^1.104.1",
|
"sass": "^1.104.1",
|
||||||
"tar": "^7.5.22",
|
"tar": "^7.5.22",
|
||||||
"terser": "^5.51.2",
|
"terser": "^5.51.2",
|
||||||
|
"tsrs": "0.20.0-ts7.1.0-beta",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"typescript-eslint": "^8.70.0",
|
"typescript-eslint": "^8.70.0",
|
||||||
"vite": "^8.3.0",
|
"vite": "^8.3.0",
|
||||||
"vite-plugin-svgr": "^5.2.0",
|
"vite-plugin-svgr": "^5.2.0",
|
||||||
"vitest": "^5.0.1",
|
"vitest": "^5.0.1",
|
||||||
"webdriverio": "10.1.0",
|
"webdriverio": "10.1.0",
|
||||||
"ws": "8.22.0"
|
"ws": "8.22.1"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx,js,mjs}": [
|
"*.{ts,tsx,js,mjs}": [
|
||||||
@@ -146,5 +146,5 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@12.15.0"
|
"packageManager": "pnpm@12.16.1"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+239
-322
File diff suppressed because it is too large
Load Diff
@@ -10,3 +10,11 @@ allowBuilds:
|
|||||||
unrs-resolver: true
|
unrs-resolver: true
|
||||||
minimumReleaseAgeExclude:
|
minimumReleaseAgeExclude:
|
||||||
- dayjs@1.11.21
|
- dayjs@1.11.21
|
||||||
|
- "@tsrs/darwin-arm64@0.20.0-ts7.1.0-beta"
|
||||||
|
- "@tsrs/darwin-x64@0.20.0-ts7.1.0-beta"
|
||||||
|
- "@tsrs/linux-arm64-gnu@0.20.0-ts7.1.0-beta"
|
||||||
|
- "@tsrs/linux-arm64-musl@0.20.0-ts7.1.0-beta"
|
||||||
|
- "@tsrs/linux-x64-gnu@0.20.0-ts7.1.0-beta"
|
||||||
|
- "@tsrs/linux-x64-musl@0.20.0-ts7.1.0-beta"
|
||||||
|
- "@tsrs/win32-x64@0.20.0-ts7.1.0-beta"
|
||||||
|
- tsrs@0.20.0-ts7.1.0-beta
|
||||||
|
|||||||
@@ -79,6 +79,13 @@
|
|||||||
"matchPackageNames": ["typescript"],
|
"matchPackageNames": ["typescript"],
|
||||||
"allowedVersions": "<7.0.0 || >=7.1.0"
|
"allowedVersions": "<7.0.0 || >=7.1.0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Review tsrs bumps alone; the version encodes the pinned TypeScript release",
|
||||||
|
"matchPackageNames": ["tsrs", "/^@tsrs//"],
|
||||||
|
"groupName": "tsrs",
|
||||||
|
"rangeStrategy": "pin",
|
||||||
|
"automerge": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Prevent project from supply chain attack",
|
"description": "Prevent project from supply chain attack",
|
||||||
"matchDatasources": ["npm"],
|
"matchDatasources": ["npm"],
|
||||||
|
|||||||
@@ -0,0 +1,761 @@
|
|||||||
|
import { execFileSync } from 'node:child_process'
|
||||||
|
import { appendFileSync, readFileSync, writeFileSync } from 'node:fs'
|
||||||
|
|
||||||
|
const env = process.env
|
||||||
|
const repository = env.GITHUB_REPOSITORY
|
||||||
|
const refreshLabel = 'Refresh this PR from latest dev'
|
||||||
|
const publishLabel = 'Publish this approved release'
|
||||||
|
const minorLabel = 'Use the next minor version'
|
||||||
|
const checked = (body, label = refreshLabel) =>
|
||||||
|
(body || '')
|
||||||
|
.split('\n')
|
||||||
|
.some((line) => line.toLowerCase() === `- [x] ${label}`.toLowerCase())
|
||||||
|
const versionPattern = '(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)'
|
||||||
|
const rcPattern = new RegExp(`^v${versionPattern}-rc(?:\\.(0|[1-9][0-9]*))?$`)
|
||||||
|
const stablePattern = new RegExp(`^v${versionPattern}$`)
|
||||||
|
const versionFiles = [
|
||||||
|
'package.json',
|
||||||
|
'src-tauri/tauri.conf.json',
|
||||||
|
'src-tauri/Cargo.toml',
|
||||||
|
'Cargo.lock',
|
||||||
|
]
|
||||||
|
const read = (path) => readFileSync(path, 'utf8')
|
||||||
|
const json = (path) => JSON.parse(read(path))
|
||||||
|
const run = (command, args, options = {}) =>
|
||||||
|
execFileSync(command, args, { encoding: 'utf8', ...options }).trim()
|
||||||
|
const git = (...args) => run('git', args)
|
||||||
|
const gitFile = (sha, path) =>
|
||||||
|
execFileSync('git', ['show', `${sha}:${path}`], { encoding: 'utf8' })
|
||||||
|
const isRc = (tag) => tag.match(rcPattern)?.[0] === tag
|
||||||
|
const isRelease = (tag) => isRc(tag) || stablePattern.test(tag)
|
||||||
|
const titleFor = (tag) => `${isRc(tag) ? 'Pre-Release' : 'Release'}: ${tag}`
|
||||||
|
const branchTag = (branch, prefix = 'release/') =>
|
||||||
|
branch.startsWith(prefix) && isRelease(branch.slice(prefix.length))
|
||||||
|
? branch.slice(prefix.length)
|
||||||
|
: undefined
|
||||||
|
const latest = (tags) =>
|
||||||
|
tags.sort((a, b) => a.localeCompare(b, 'en', { numeric: true })).at(-1)
|
||||||
|
const summary = (text) => appendFileSync(env.GITHUB_STEP_SUMMARY, `${text}\n`)
|
||||||
|
const endpoint = (path) =>
|
||||||
|
path === 'graphql' ? path : `repos/${repository}/${path}`
|
||||||
|
const list = (path) =>
|
||||||
|
JSON.parse(run('gh', ['api', '--paginate', '--slurp', endpoint(path)])).flat()
|
||||||
|
|
||||||
|
function api(path, body, method = 'POST') {
|
||||||
|
const response = run(
|
||||||
|
'gh',
|
||||||
|
['api', '--method', method, endpoint(path), '--input', '-'],
|
||||||
|
{
|
||||||
|
input: JSON.stringify(body),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const result = response ? JSON.parse(response) : null
|
||||||
|
if (result?.errors) throw new Error(JSON.stringify(result.errors))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const get = (path) => JSON.parse(run('gh', ['api', endpoint(path)]))
|
||||||
|
const canWrite = (login) =>
|
||||||
|
['write', 'maintain', 'admin'].includes(
|
||||||
|
get(`collaborators/${login}/permission`).permission,
|
||||||
|
)
|
||||||
|
|
||||||
|
function output(values) {
|
||||||
|
appendFileSync(
|
||||||
|
env.GITHUB_OUTPUT,
|
||||||
|
Object.entries(values)
|
||||||
|
.map(([key, value]) => `${key}=${value}\n`)
|
||||||
|
.join(''),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function versionPatternFor(path) {
|
||||||
|
if (path.endsWith('.json')) return /("version"\s*:\s*")[^"]+/
|
||||||
|
return path === 'Cargo.lock'
|
||||||
|
? /(\[\[package\]\]\r?\nname = "clash-verge"\r?\nversion = ")[^"]+/
|
||||||
|
: /(^version\s*=\s*")[^"]+/m
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateVersions(tag, sha) {
|
||||||
|
for (const path of versionFiles) {
|
||||||
|
const content = sha ? gitFile(sha, path) : read(path)
|
||||||
|
const version = path.endsWith('.json')
|
||||||
|
? JSON.parse(content).version
|
||||||
|
: content.match(versionPatternFor(path))?.[0].split('"').at(-1)
|
||||||
|
if (`v${version}` !== tag)
|
||||||
|
throw new Error(`${path} version does not match ${tag}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateVersionCommit(tag, sha) {
|
||||||
|
validateVersions(tag, sha)
|
||||||
|
const commit = get(`commits/${sha}`)
|
||||||
|
if (!commit.commit.verification.verified || commit.parents.length !== 1)
|
||||||
|
throw new Error('The version commit must be signed and have one parent.')
|
||||||
|
const title = commit.commit.message.split('\n')[0]
|
||||||
|
if (title.replace(/ \(#\d+\)$/, '') !== titleFor(tag))
|
||||||
|
throw new Error('The final version commit has an unexpected title.')
|
||||||
|
const parent = commit.parents[0].sha
|
||||||
|
const changed = git('diff', '--name-only', parent, sha)
|
||||||
|
.split('\n')
|
||||||
|
.filter(Boolean)
|
||||||
|
if (
|
||||||
|
changed.some((path) => !versionFiles.includes(path)) ||
|
||||||
|
git('diff', '--summary', parent, sha)
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
'The version commit changes files outside the four version files.',
|
||||||
|
)
|
||||||
|
for (const path of versionFiles) {
|
||||||
|
const before = gitFile(parent, path)
|
||||||
|
const after = gitFile(sha, path)
|
||||||
|
if (
|
||||||
|
before.replace(versionPatternFor(path), '$1VERSION') !==
|
||||||
|
after.replace(versionPatternFor(path), '$1VERSION')
|
||||||
|
)
|
||||||
|
throw new Error(`${path} contains changes other than its version.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remoteRefs() {
|
||||||
|
return git('ls-remote', '--heads', '--tags', '--refs', 'origin')
|
||||||
|
.split('\n')
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line) => line.split('\t'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchBranches() {
|
||||||
|
git(
|
||||||
|
'fetch',
|
||||||
|
'--no-tags',
|
||||||
|
'origin',
|
||||||
|
'+refs/heads/dev:refs/remotes/origin/dev',
|
||||||
|
'+refs/heads/main:refs/remotes/origin/main',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
let refresh
|
||||||
|
if (env.GITHUB_EVENT_NAME === 'pull_request_target') {
|
||||||
|
const event = json(env.GITHUB_EVENT_PATH)
|
||||||
|
if (checked(event.changes?.body?.from) || !checked(event.pull_request.body))
|
||||||
|
return
|
||||||
|
if (!canWrite(event.sender.login))
|
||||||
|
throw new Error('Refreshing a version PR requires write access.')
|
||||||
|
refresh = get(`pulls/${event.pull_request.number}`)
|
||||||
|
if (refresh.state !== 'open' || !checked(refresh.body)) return
|
||||||
|
requireBotPr(refresh, 'dev', 'release/')
|
||||||
|
}
|
||||||
|
fetchBranches()
|
||||||
|
git('checkout', '--detach', 'origin/dev')
|
||||||
|
const channel = refresh
|
||||||
|
? isRc(branchTag(refresh.head.ref))
|
||||||
|
? 'rc'
|
||||||
|
: 'stable'
|
||||||
|
: env.RELEASE_CHANNEL || 'rc'
|
||||||
|
if (!['rc', 'stable'].includes(channel))
|
||||||
|
throw new Error('Unknown release channel.')
|
||||||
|
const requested = refresh
|
||||||
|
? branchTag(refresh.head.ref)
|
||||||
|
: env.RELEASE_TAG || ''
|
||||||
|
if (
|
||||||
|
requested &&
|
||||||
|
!(channel === 'rc' ? isRc(requested) : stablePattern.test(requested))
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
channel === 'rc'
|
||||||
|
? 'Expected vX.Y.Z-rc or vX.Y.Z-rc.N.'
|
||||||
|
: 'Expected vX.Y.Z.',
|
||||||
|
)
|
||||||
|
const existing =
|
||||||
|
!refresh &&
|
||||||
|
list('pulls?state=open&base=dev&per_page=100').find(
|
||||||
|
(pr) =>
|
||||||
|
pr.head.repo?.full_name === repository &&
|
||||||
|
pr.user.login === 'github-actions[bot]' &&
|
||||||
|
branchTag(pr.head.ref) &&
|
||||||
|
isRc(branchTag(pr.head.ref)) === (channel === 'rc') &&
|
||||||
|
(!requested || branchTag(pr.head.ref) === requested),
|
||||||
|
)
|
||||||
|
if (existing) {
|
||||||
|
api(`issues/${existing.number}/labels`, { labels: ['release'] })
|
||||||
|
output({ existing_pr: existing.html_url })
|
||||||
|
summary(
|
||||||
|
`Existing version PR: ${existing.html_url}\nClose it before preparing a replacement; its candidate is unchanged.`,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const base = (requested || `v${json('package.json').version}`).split(
|
||||||
|
/[-+]/,
|
||||||
|
)[0]
|
||||||
|
if (!stablePattern.test(base))
|
||||||
|
throw new Error(`Invalid base version: ${base}`)
|
||||||
|
const refs = remoteRefs().map(([, ref]) => ref)
|
||||||
|
const tags = refs
|
||||||
|
.filter((ref) => ref.startsWith('refs/tags/'))
|
||||||
|
.map((ref) => ref.slice(10))
|
||||||
|
const reserved = refs
|
||||||
|
.filter((ref) => ref.startsWith('refs/heads/'))
|
||||||
|
.filter((ref) => !refresh || ref !== `refs/heads/${refresh.head.ref}`)
|
||||||
|
.map(
|
||||||
|
(ref) => branchTag(ref.slice(11)) || branchTag(ref.slice(11), 'publish/'),
|
||||||
|
)
|
||||||
|
.filter(Boolean)
|
||||||
|
const sameBase = (tag) => isRc(tag) && tag.startsWith(`${base}-rc`)
|
||||||
|
const occupied = [...tags, ...reserved]
|
||||||
|
const last = latest(occupied.filter(sameBase))
|
||||||
|
const tag =
|
||||||
|
requested ||
|
||||||
|
(channel === 'stable'
|
||||||
|
? base
|
||||||
|
: last
|
||||||
|
? `${base}-rc.${BigInt(last.match(rcPattern)[4] ?? 0) + 1n}`
|
||||||
|
: `${base}-rc`)
|
||||||
|
if (occupied.includes(tag))
|
||||||
|
throw new Error(`Tag ${tag} already exists or is reserved.`)
|
||||||
|
const sha = git('rev-parse', 'HEAD')
|
||||||
|
const body = releaseBody(tag, sha, tags, false)
|
||||||
|
summary(body)
|
||||||
|
if (
|
||||||
|
remoteRefs().some(
|
||||||
|
([, ref]) =>
|
||||||
|
ref === `refs/tags/${tag}` || ref === `refs/heads/publish/${tag}`,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new Error(`Tag ${tag} already exists or is reserved for publication.`)
|
||||||
|
if (json('package.json').version !== tag.slice(1))
|
||||||
|
run('node', ['scripts/release-version.mjs', tag])
|
||||||
|
validateVersions(tag)
|
||||||
|
const title = titleFor(tag)
|
||||||
|
const branch = refresh
|
||||||
|
? `release-refresh/${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}`
|
||||||
|
: `release/${tag}`
|
||||||
|
writeVersionPr({ sha, body, refresh, title, branch, files: versionFiles })
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseBody(tag, sha, tags, publishing) {
|
||||||
|
const previous =
|
||||||
|
(isRc(tag) &&
|
||||||
|
latest(
|
||||||
|
tags.filter(
|
||||||
|
(value) =>
|
||||||
|
isRc(value) &&
|
||||||
|
value.startsWith(`${tag.split('-')[0]}-rc`) &&
|
||||||
|
value !== tag,
|
||||||
|
),
|
||||||
|
)) ||
|
||||||
|
latest(tags.filter((value) => stablePattern.test(value) && value !== tag))
|
||||||
|
const url = `${env.GITHUB_SERVER_URL}/${repository}`
|
||||||
|
let range = sha
|
||||||
|
let comparison = 'No previous release tag'
|
||||||
|
if (previous) {
|
||||||
|
git('fetch', '--no-tags', 'origin', `refs/tags/${previous}`)
|
||||||
|
range = `${git('rev-parse', 'FETCH_HEAD^{commit}')}..${sha}`
|
||||||
|
comparison = `Changes since **${previous}** · [Compare changes](${url}/compare/${previous}...${sha})`
|
||||||
|
}
|
||||||
|
comparison += ` · **${git('rev-list', '--count', range)} commits**`
|
||||||
|
const commits = git('log', '--format=%h %s', range)
|
||||||
|
const groups = Object.groupBy(commits ? commits.split('\n') : [], (line) => {
|
||||||
|
if (/^\S+ chore\(deps\)!?: /i.test(line)) return 'deps'
|
||||||
|
const type = line.match(/^\S+ (\w+)(?:\([^)]*\))?!?: /)?.[1]?.toLowerCase()
|
||||||
|
return type === 'doc' ? 'docs' : type || 'other'
|
||||||
|
})
|
||||||
|
const labels = {
|
||||||
|
fix: 'Fixes',
|
||||||
|
feat: 'Features',
|
||||||
|
perf: 'Performance',
|
||||||
|
revert: 'Reverts',
|
||||||
|
refactor: 'Refactoring',
|
||||||
|
build: 'Build',
|
||||||
|
deps: 'Dependencies',
|
||||||
|
ci: 'CI',
|
||||||
|
docs: 'Documentation',
|
||||||
|
test: 'Tests',
|
||||||
|
chore: 'Maintenance',
|
||||||
|
style: 'Style',
|
||||||
|
other: 'Other',
|
||||||
|
}
|
||||||
|
const names = {
|
||||||
|
macos: 'macOS',
|
||||||
|
windows: 'Windows',
|
||||||
|
ui: 'UI',
|
||||||
|
ci: 'CI',
|
||||||
|
tun: 'TUN',
|
||||||
|
}
|
||||||
|
const order = Object.keys(labels)
|
||||||
|
const rank = (type) =>
|
||||||
|
order.includes(type) ? order.indexOf(type) : order.length
|
||||||
|
const scope =
|
||||||
|
Object.entries(groups)
|
||||||
|
.sort(([a], [b]) => rank(a) - rank(b) || a.localeCompare(b))
|
||||||
|
.map(([type, lines]) => {
|
||||||
|
const entries = lines.map((line) => {
|
||||||
|
const hash = line.slice(0, line.indexOf(' '))
|
||||||
|
const title = line
|
||||||
|
.slice(hash.length + 1)
|
||||||
|
.replace(/\s*\((?:[\w.-]+\/[\w.-]+)?#\d+\)/g, '')
|
||||||
|
.replace(/(?:[\w.-]+\/[\w.-]+)?#\d+\b/g, '')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim()
|
||||||
|
.replace(
|
||||||
|
/^\w+(?:\(([^)]*)\))?(!)?:\s*/,
|
||||||
|
(_, scope, breaking) =>
|
||||||
|
`${breaking ? 'Breaking: ' : ''}${scope ? `${Object.hasOwn(names, scope) ? names[scope] : scope}: ` : ''}`,
|
||||||
|
)
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replace(/[\\`*_[\]]/g, '\\$&')
|
||||||
|
return `- ${title} [#${hash}](${url}/commit/${hash})`
|
||||||
|
})
|
||||||
|
return `### ${Object.hasOwn(labels, type) ? labels[type] : type} · ${lines.length}\n\n${entries.join('\n')}`
|
||||||
|
})
|
||||||
|
.join('\n\n') || 'No new commits.'
|
||||||
|
return `## ${titleFor(tag)}
|
||||||
|
|
||||||
|
Source: \`dev\` · [${sha}](${url}/commit/${sha}) \n${comparison}
|
||||||
|
${
|
||||||
|
publishing
|
||||||
|
? `<!-- release-source:${sha} -->
|
||||||
|
|
||||||
|
After approval, select the checkbox below to publish.
|
||||||
|
Do not use the normal Squash or Rebase merge buttons.
|
||||||
|
|
||||||
|
- [ ] ${publishLabel}`
|
||||||
|
: `
|
||||||
|
- [ ] ${refreshLabel}
|
||||||
|
|
||||||
|
Refreshing requires a new approval.`
|
||||||
|
}
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
${scope}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVersionPr({ sha, body, refresh, title, branch, files }) {
|
||||||
|
// Create-only ref and expectedHeadOid prevent replacing another candidate.
|
||||||
|
api('git/refs', { ref: `refs/heads/${branch}`, sha })
|
||||||
|
let pr
|
||||||
|
try {
|
||||||
|
const commit = api('graphql', {
|
||||||
|
query: `mutation($input: CreateCommitOnBranchInput!) {
|
||||||
|
createCommitOnBranch(input: $input) { commit { oid } }
|
||||||
|
}`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
branch: { repositoryNameWithOwner: repository, branchName: branch },
|
||||||
|
expectedHeadOid: sha,
|
||||||
|
message: { headline: title },
|
||||||
|
fileChanges: {
|
||||||
|
additions: files.map((path) => ({
|
||||||
|
path,
|
||||||
|
contents: readFileSync(path).toString('base64'),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (!commit.data?.createCommitOnBranch?.commit?.oid)
|
||||||
|
throw new Error('Signed version commit failed.')
|
||||||
|
if (refresh) {
|
||||||
|
const current = get(`pulls/${refresh.number}`)
|
||||||
|
if (
|
||||||
|
current.state !== 'open' ||
|
||||||
|
current.head.sha !== refresh.head.sha ||
|
||||||
|
current.base.ref !== refresh.base.ref ||
|
||||||
|
current.body !== refresh.body
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
'The version pull request changed while preparing the update.',
|
||||||
|
)
|
||||||
|
const oid = commit.data.createCommitOnBranch.commit.oid
|
||||||
|
updateRefs(refresh.base.repo.node_id, [
|
||||||
|
{
|
||||||
|
name: `refs/heads/${refresh.head.ref}`,
|
||||||
|
beforeOid: refresh.head.sha,
|
||||||
|
afterOid: oid,
|
||||||
|
force: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `refs/heads/${branch}`,
|
||||||
|
beforeOid: oid,
|
||||||
|
afterOid: '0'.repeat(40),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
for (const review of list(
|
||||||
|
`pulls/${refresh.number}/reviews?per_page=100`,
|
||||||
|
)) {
|
||||||
|
if (review.state === 'APPROVED')
|
||||||
|
api(
|
||||||
|
`pulls/${refresh.number}/reviews/${review.id}/dismissals`,
|
||||||
|
{
|
||||||
|
message:
|
||||||
|
'The version PR was refreshed from dev; please review again.',
|
||||||
|
},
|
||||||
|
'PUT',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pr = api(`pulls/${refresh.number}`, { title, body }, 'PATCH')
|
||||||
|
} else {
|
||||||
|
pr = api('pulls', {
|
||||||
|
head: branch,
|
||||||
|
base: 'dev',
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (refresh) {
|
||||||
|
try {
|
||||||
|
api(`git/refs/heads/${branch}`, {}, 'DELETE')
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
api(`issues/${pr.number}/labels`, { labels: ['release'] })
|
||||||
|
summary(`Version pull request: ${pr.html_url}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRefs(repositoryId, refUpdates) {
|
||||||
|
api('graphql', {
|
||||||
|
query: `mutation($input: UpdateRefsInput!) { updateRefs(input: $input) { clientMutationId } }`,
|
||||||
|
variables: { input: { repositoryId, refUpdates } },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireBotPr(pr, base, prefix) {
|
||||||
|
const tag = branchTag(pr.head.ref, prefix)
|
||||||
|
if (
|
||||||
|
!tag ||
|
||||||
|
pr.base.ref !== base ||
|
||||||
|
pr.base.repo.full_name !== repository ||
|
||||||
|
pr.head.repo?.full_name !== repository ||
|
||||||
|
pr.user.login !== 'github-actions[bot]'
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
'Expected a release PR created by this repository workflow.',
|
||||||
|
)
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
function publishPr() {
|
||||||
|
const event = json(env.GITHUB_EVENT_PATH)
|
||||||
|
const pr = get(`pulls/${event.pull_request.number}`)
|
||||||
|
const tag = requireBotPr(pr, 'dev', 'release/')
|
||||||
|
if (!pr.merged || pr.merge_commit_sha !== event.pull_request.merge_commit_sha)
|
||||||
|
throw new Error('Expected the final merged version commit on dev.')
|
||||||
|
const sha = pr.merge_commit_sha
|
||||||
|
fetchBranches()
|
||||||
|
git('fetch', '--no-tags', 'origin', sha)
|
||||||
|
git('merge-base', '--is-ancestor', sha, 'origin/dev')
|
||||||
|
validateVersionCommit(tag, sha)
|
||||||
|
const branch = `publish/${tag}`
|
||||||
|
const existing = list(
|
||||||
|
`pulls?state=all&base=main&head=${repository.split('/')[0]}:${branch}&per_page=100`,
|
||||||
|
)
|
||||||
|
if (existing.length) {
|
||||||
|
const publication = existing[0]
|
||||||
|
requireBotPr(publication, 'main', 'publish/')
|
||||||
|
if (publication.head.sha !== sha)
|
||||||
|
throw new Error(
|
||||||
|
'This tag is already reserved by a different publication snapshot.',
|
||||||
|
)
|
||||||
|
summary(`Existing publication PR: ${publication.html_url}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const refs = remoteRefs()
|
||||||
|
if (refs.some(([, ref]) => ref === `refs/tags/${tag}`))
|
||||||
|
throw new Error(`Tag ${tag} already exists.`)
|
||||||
|
git('merge-base', '--is-ancestor', 'origin/main', sha)
|
||||||
|
const head = refs.find(([, ref]) => ref === `refs/heads/${branch}`)?.[0]
|
||||||
|
if (head && head !== sha)
|
||||||
|
throw new Error('Publication branch already points to another commit.')
|
||||||
|
if (!head) api('git/refs', { ref: `refs/heads/${branch}`, sha })
|
||||||
|
const body = releaseBody(
|
||||||
|
tag,
|
||||||
|
sha,
|
||||||
|
refs
|
||||||
|
.filter(([, ref]) => ref.startsWith('refs/tags/'))
|
||||||
|
.map(([, ref]) => ref.slice(10)),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
const publication = api('pulls', {
|
||||||
|
head: branch,
|
||||||
|
base: 'main',
|
||||||
|
title: titleFor(tag),
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
api(`issues/${publication.number}/labels`, { labels: ['release'] })
|
||||||
|
summary(`Publication PR: ${publication.html_url}\n\n${body}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireApproval(pr, sha) {
|
||||||
|
const reviews = new Map()
|
||||||
|
for (const review of list(`pulls/${pr.number}/reviews?per_page=100`)) {
|
||||||
|
if (['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review.state))
|
||||||
|
reviews.set(review.user.login, review)
|
||||||
|
}
|
||||||
|
let approved = false
|
||||||
|
for (const review of reviews.values()) {
|
||||||
|
if (!canWrite(review.user.login)) continue
|
||||||
|
if (review.state === 'CHANGES_REQUESTED')
|
||||||
|
throw new Error('A reviewer with write access has requested changes.')
|
||||||
|
if (review.state === 'APPROVED' && review.commit_id === sha) approved = true
|
||||||
|
}
|
||||||
|
if (!approved)
|
||||||
|
throw new Error(
|
||||||
|
'The current PR head needs approval from a reviewer with write access.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseOutput(tag, sha) {
|
||||||
|
output({
|
||||||
|
release_tag: tag,
|
||||||
|
source_sha: sha,
|
||||||
|
channel: isRc(tag) ? 'rc' : 'stable',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function publish() {
|
||||||
|
const event = json(env.GITHUB_EVENT_PATH)
|
||||||
|
const tag = requireBotPr(event.pull_request, 'main', 'publish/')
|
||||||
|
if (
|
||||||
|
checked(event.changes?.body?.from, publishLabel) ||
|
||||||
|
!checked(event.pull_request.body, publishLabel)
|
||||||
|
)
|
||||||
|
throw new Error('Publishing requires a new checkbox selection.')
|
||||||
|
if (!canWrite(event.sender.login))
|
||||||
|
throw new Error('Publishing requires write access.')
|
||||||
|
const sha = event.pull_request.head.sha
|
||||||
|
const pr = get(`pulls/${event.pull_request.number}`)
|
||||||
|
requireBotPr(pr, 'main', 'publish/')
|
||||||
|
if (
|
||||||
|
pr.head.sha !== sha ||
|
||||||
|
!checked(pr.body, publishLabel) ||
|
||||||
|
!pr.body.includes(`<!-- release-source:${sha} -->`)
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
'The publication snapshot changed after the checkbox selection.',
|
||||||
|
)
|
||||||
|
fetchBranches()
|
||||||
|
git('fetch', '--no-tags', 'origin', sha)
|
||||||
|
git('merge-base', '--is-ancestor', sha, 'origin/dev')
|
||||||
|
validateVersionCommit(tag, sha)
|
||||||
|
const marker = `Release workflow run: ${env.GITHUB_RUN_ID}\nSource: ${sha}\nPR: ${pr.number}`
|
||||||
|
const existing = remoteRefs().find(([, ref]) => ref === `refs/tags/${tag}`)
|
||||||
|
if (existing) {
|
||||||
|
const ref = get(`git/ref/tags/${tag}`)
|
||||||
|
const object =
|
||||||
|
ref.object.type === 'tag' && get(`git/tags/${ref.object.sha}`)
|
||||||
|
if (
|
||||||
|
!object ||
|
||||||
|
object.object.type !== 'commit' ||
|
||||||
|
object.object.sha !== sha ||
|
||||||
|
object.message.trim() !== marker ||
|
||||||
|
Number(env.GITHUB_RUN_ATTEMPT) <= 1
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
'An existing tag can only be recovered by its original release run.',
|
||||||
|
)
|
||||||
|
git('merge-base', '--is-ancestor', sha, 'origin/main')
|
||||||
|
summary(`Resuming ${tag} at ${sha} from this release run.`)
|
||||||
|
} else {
|
||||||
|
if (pr.state !== 'open' || pr.draft)
|
||||||
|
throw new Error('Expected an open, ready publication PR.')
|
||||||
|
requireApproval(pr, sha)
|
||||||
|
const main = git('rev-parse', 'origin/main')
|
||||||
|
git('merge-base', '--is-ancestor', main, sha)
|
||||||
|
const object = api('git/tags', {
|
||||||
|
tag,
|
||||||
|
message: marker,
|
||||||
|
object: sha,
|
||||||
|
type: 'commit',
|
||||||
|
})
|
||||||
|
// The tag records run ownership in the same transaction as the fast-forward.
|
||||||
|
updateRefs(pr.base.repo.node_id, [
|
||||||
|
{ name: 'refs/heads/main', beforeOid: main, afterOid: sha, force: false },
|
||||||
|
{
|
||||||
|
name: `refs/heads/${pr.head.ref}`,
|
||||||
|
beforeOid: sha,
|
||||||
|
afterOid: sha,
|
||||||
|
force: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `refs/tags/${tag}`,
|
||||||
|
beforeOid: '0'.repeat(40),
|
||||||
|
afterOid: object.sha,
|
||||||
|
force: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
summary(`Fast-forwarded main and tagged ${tag} at approved commit ${sha}.`)
|
||||||
|
}
|
||||||
|
releaseOutput(tag, sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateTag() {
|
||||||
|
const tag = env.GITHUB_REF_NAME
|
||||||
|
if (!isRelease(tag)) throw new Error('Expected a stable or RC release tag.')
|
||||||
|
fetchBranches()
|
||||||
|
const sha = git('rev-parse', `${env.GITHUB_SHA}^{commit}`)
|
||||||
|
const ref = get(`git/ref/tags/${tag}`)
|
||||||
|
const object = ref.object.type === 'tag' && get(`git/tags/${ref.object.sha}`)
|
||||||
|
if (object && object.message.includes('Release workflow run:'))
|
||||||
|
throw new Error(
|
||||||
|
'Resume the original publication run for workflow-owned tags.',
|
||||||
|
)
|
||||||
|
git('fetch', '--no-tags', 'origin', `refs/tags/${tag}`)
|
||||||
|
if (git('rev-parse', 'FETCH_HEAD^{commit}') !== sha)
|
||||||
|
throw new Error('The tag moved after this run started.')
|
||||||
|
for (const branch of ['main', 'dev'])
|
||||||
|
git('merge-base', '--is-ancestor', sha, `origin/${branch}`)
|
||||||
|
validateVersions(tag, sha)
|
||||||
|
releaseOutput(tag, sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareDraft() {
|
||||||
|
const tag = env.RELEASE_TAG
|
||||||
|
verifyTarget()
|
||||||
|
const release = list('releases?per_page=100').find(
|
||||||
|
(release) => release.tag_name === tag,
|
||||||
|
)
|
||||||
|
if (release && !release.draft)
|
||||||
|
throw new Error('This release is already public.')
|
||||||
|
const draft =
|
||||||
|
release ||
|
||||||
|
api('releases', {
|
||||||
|
tag_name: tag,
|
||||||
|
target_commitish: env.SOURCE_SHA,
|
||||||
|
name: `Clash Verge Rev ${tag}`,
|
||||||
|
body: 'Draft release, will be updated later.',
|
||||||
|
draft: true,
|
||||||
|
prerelease: isRc(tag),
|
||||||
|
})
|
||||||
|
output({ id: draft.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyTarget(tag = env.RELEASE_TAG, sha = env.SOURCE_SHA) {
|
||||||
|
const ref = get(`git/ref/tags/${tag}`)
|
||||||
|
const target =
|
||||||
|
ref.object.type === 'tag'
|
||||||
|
? get(`git/tags/${ref.object.sha}`).object
|
||||||
|
: ref.object
|
||||||
|
if (target.type !== 'commit' || target.sha !== sha)
|
||||||
|
throw new Error('The release tag no longer points to the validated source.')
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextDev() {
|
||||||
|
let refresh
|
||||||
|
let tag = env.RELEASE_TAG
|
||||||
|
let source = env.SOURCE_SHA
|
||||||
|
let minor = false
|
||||||
|
if (env.GITHUB_EVENT_NAME === 'pull_request_target') {
|
||||||
|
const event = json(env.GITHUB_EVENT_PATH)
|
||||||
|
const oldBody = event.changes?.body?.from
|
||||||
|
const body = event.pull_request.body
|
||||||
|
if (
|
||||||
|
checked(oldBody, minorLabel) === checked(body, minorLabel) &&
|
||||||
|
(checked(oldBody) || !checked(body))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if (!canWrite(event.sender.login))
|
||||||
|
throw new Error('Changing the next dev version requires write access.')
|
||||||
|
refresh = get(`pulls/${event.pull_request.number}`)
|
||||||
|
if (refresh.state !== 'open') return
|
||||||
|
tag = requireBotPr(refresh, 'dev', 'next-dev/')
|
||||||
|
if (
|
||||||
|
refresh.head.sha !== event.pull_request.head.sha ||
|
||||||
|
refresh.body !== body
|
||||||
|
)
|
||||||
|
throw new Error(
|
||||||
|
'The version update PR changed after the checkbox selection.',
|
||||||
|
)
|
||||||
|
minor = checked(body, minorLabel)
|
||||||
|
source = body.match(/<!-- next-dev-source:([a-f0-9]{40}) -->/)?.[1]
|
||||||
|
}
|
||||||
|
if (!stablePattern.test(tag) || !/^[a-f0-9]{40}$/.test(source))
|
||||||
|
throw new Error('The next dev version requires a stable release snapshot.')
|
||||||
|
verifyTarget(tag, source)
|
||||||
|
const release = get(`releases/tags/${tag}`)
|
||||||
|
if (release.draft || release.prerelease)
|
||||||
|
throw new Error('The stable release must be public before preparing dev.')
|
||||||
|
const branch = `next-dev/${tag}`
|
||||||
|
if (!refresh) {
|
||||||
|
const existing = list(
|
||||||
|
`pulls?state=all&base=dev&head=${repository.split('/')[0]}:${branch}&per_page=100`,
|
||||||
|
)[0]
|
||||||
|
if (existing) {
|
||||||
|
requireBotPr(existing, 'dev', 'next-dev/')
|
||||||
|
summary(`Existing dev version PR: ${existing.html_url}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchBranches()
|
||||||
|
git('fetch', '--no-tags', 'origin', source)
|
||||||
|
git('merge-base', '--is-ancestor', source, 'origin/main')
|
||||||
|
git('merge-base', '--is-ancestor', source, 'origin/dev')
|
||||||
|
git('checkout', '--detach', 'origin/dev')
|
||||||
|
const sha = git('rev-parse', 'HEAD')
|
||||||
|
validateVersions(tag, source)
|
||||||
|
validateVersions(tag, sha)
|
||||||
|
if (read('Changelog.md') !== gitFile(source, 'Changelog.md'))
|
||||||
|
throw new Error(
|
||||||
|
'Changelog.md changed after this release. Preserve the unreleased entries and prepare the dev update manually.',
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
!read('Changelog.md').split('\n').includes(`## ${tag}`) ||
|
||||||
|
read('docs/Changelog.history.md').split('\n').includes(`## ${tag}`)
|
||||||
|
)
|
||||||
|
throw new Error('The released changelog is missing or already archived.')
|
||||||
|
const [major, minorVersion, patch] = tag.slice(1).split('.').map(BigInt)
|
||||||
|
const patchTag = `v${major}.${minorVersion}.${patch + 1n}`
|
||||||
|
const minorTag = `v${major}.${minorVersion + 1n}.0`
|
||||||
|
const nextTag = minor ? minorTag : patchTag
|
||||||
|
run('pnpm', ['release-version', nextTag])
|
||||||
|
run('bash', ['scripts-workflow/bump_changelog.sh'])
|
||||||
|
const changelog = read('Changelog.md')
|
||||||
|
if (!changelog.includes('## v(Version Goes Here)'))
|
||||||
|
throw new Error('The changelog template has no version placeholder.')
|
||||||
|
writeFileSync(
|
||||||
|
'Changelog.md',
|
||||||
|
changelog.replace('## v(Version Goes Here)', `## ${nextTag}`),
|
||||||
|
)
|
||||||
|
validateVersions(nextTag)
|
||||||
|
const title = `Dev: ${nextTag}`
|
||||||
|
const body = `## ${title}
|
||||||
|
|
||||||
|
After [${tag}](${env.GITHUB_SERVER_URL}/${repository}/releases/tag/${tag}).
|
||||||
|
<!-- next-dev-source:${source} -->
|
||||||
|
|
||||||
|
Patch: \`${patchTag}\` · Minor: \`${minorTag}\`
|
||||||
|
|
||||||
|
- [${minor ? 'x' : ' '}] ${minorLabel}
|
||||||
|
- [ ] ${refreshLabel}
|
||||||
|
|
||||||
|
Changing the version or refreshing requires a new approval.
|
||||||
|
`
|
||||||
|
writeVersionPr({
|
||||||
|
sha,
|
||||||
|
body,
|
||||||
|
refresh,
|
||||||
|
title,
|
||||||
|
branch: refresh
|
||||||
|
? `next-dev-refresh/${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}`
|
||||||
|
: branch,
|
||||||
|
files: [...versionFiles, 'Changelog.md', 'docs/Changelog.history.md'],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
prepare,
|
||||||
|
'next-dev': nextDev,
|
||||||
|
'publish-pr': publishPr,
|
||||||
|
publish,
|
||||||
|
'validate-tag': validateTag,
|
||||||
|
'prepare-draft': prepareDraft,
|
||||||
|
'verify-target': verifyTarget,
|
||||||
|
}
|
||||||
|
const command = commands[process.argv[2]]
|
||||||
|
if (!command) throw new Error('Unknown release command.')
|
||||||
|
command()
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
"pnpm-lock.yaml",
|
"pnpm-lock.yaml",
|
||||||
"pnpm-workspace.yaml",
|
"pnpm-workspace.yaml",
|
||||||
"scripts/contract-inputs.json",
|
"scripts/contract-inputs.json",
|
||||||
".github/workflows/frontend-check.yml",
|
".github/workflows/contract-parity.yml",
|
||||||
".husky/pre-push"
|
".husky/pre-push"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ tauri-plugin-fs = "2.7.1"
|
|||||||
tauri-plugin-process = "2.4.0"
|
tauri-plugin-process = "2.4.0"
|
||||||
tauri-plugin-deep-link = "2.6.2"
|
tauri-plugin-deep-link = "2.6.2"
|
||||||
tauri-plugin-window-state = "2.5.0"
|
tauri-plugin-window-state = "2.5.0"
|
||||||
zip = "9.0.4"
|
zip = "9.1.3"
|
||||||
flate2 = "1.1.10"
|
flate2 = "1.1.10"
|
||||||
reqwest_dav = "0.3.3"
|
reqwest_dav = "0.3.3"
|
||||||
aes-gcm = { version = "0.11.1" }
|
aes-gcm = { version = "0.11.1" }
|
||||||
@@ -154,7 +154,7 @@ windows-sys.workspace = true
|
|||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-autostart = "2.7.0"
|
tauri-plugin-autostart = "2.7.0"
|
||||||
tauri-plugin-global-shortcut = "2.4.1"
|
tauri-plugin-global-shortcut = "2.4.1"
|
||||||
tauri-plugin-updater = "2.14.0"
|
tauri-plugin-updater = "2.14.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { workspace = true }
|
criterion = { workspace = true }
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ impl AutoBackupManager {
|
|||||||
{
|
{
|
||||||
*self.settings.write() = settings;
|
*self.settings.write() = settings;
|
||||||
}
|
}
|
||||||
let _ = self.settings_tx.send(settings);
|
let _ = self.settings_tx.send_replace(settings);
|
||||||
self.maybe_start_runner(settings);
|
self.maybe_start_runner(settings);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ impl AutoBackupManager {
|
|||||||
{
|
{
|
||||||
*self.settings.write() = settings;
|
*self.settings.write() = settings;
|
||||||
}
|
}
|
||||||
let _ = self.settings_tx.send(settings);
|
let _ = self.settings_tx.send_replace(settings);
|
||||||
self.maybe_start_runner(settings);
|
self.maybe_start_runner(settings);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub async fn patch_verge(patch: &IVerge, not_save_file: bool) -> Result<()> {
|
|||||||
|
|
||||||
/// Apply a patch without post-update reconciliation.
|
/// Apply a patch without post-update reconciliation.
|
||||||
pub(super) async fn apply_verge_patch(patch: &IVerge, not_save_file: bool) -> Result<()> {
|
pub(super) async fn apply_verge_patch(patch: &IVerge, not_save_file: bool) -> Result<()> {
|
||||||
let config_write = Config::try_lock_config_write()?;
|
let config_write = Config::lock_config_write().await;
|
||||||
apply_verge_patch_locked(&config_write, patch, not_save_file).await
|
apply_verge_patch_locked(&config_write, patch, not_save_file).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -316,7 +316,6 @@ pub async fn hide() {
|
|||||||
{
|
{
|
||||||
let _ = window.hide();
|
let _ = window.hide();
|
||||||
}
|
}
|
||||||
handle::Handle::global().set_activation_policy_accessory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -289,9 +289,6 @@ pub fn run() -> std::process::ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_window_close(api: &tauri::WindowEvent) {
|
pub fn handle_window_close(api: &tauri::WindowEvent) {
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
handle::Handle::global().set_activation_policy_accessory();
|
|
||||||
|
|
||||||
if core::handle::Handle::global().is_exiting() {
|
if core::handle::Handle::global().is_exiting() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,19 @@ import {
|
|||||||
VisibilityOffOutlined,
|
VisibilityOffOutlined,
|
||||||
VisibilityOutlined,
|
VisibilityOutlined,
|
||||||
} from '@mui/icons-material'
|
} from '@mui/icons-material'
|
||||||
import { Box, Button, IconButton, Skeleton, Typography } from '@mui/material'
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
FormControlLabel,
|
||||||
|
IconButton,
|
||||||
|
Skeleton,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material'
|
||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||||
import { useEffect } from 'foxact/use-abortable-effect'
|
import { useEffect } from 'foxact/use-abortable-effect'
|
||||||
import { useIntersection } from 'foxact/use-intersection'
|
import { useIntersection } from 'foxact/use-intersection'
|
||||||
|
import { useLocalStorage } from 'foxact/use-local-storage'
|
||||||
import type { XOR } from 'foxts/ts-xor'
|
import type { XOR } from 'foxts/ts-xor'
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
@@ -99,9 +108,13 @@ const IPInfoCardContainer = forwardRef<HTMLElement, React.PropsWithChildren>(
|
|||||||
export const IpInfoCard = () => {
|
export const IpInfoCard = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [showIp, setShowIp] = useState(false)
|
const [showIp, setShowIp] = useState(false)
|
||||||
|
const [autoRefresh, setAutoRefresh] = useLocalStorage(
|
||||||
|
'ip-info-auto-refresh',
|
||||||
|
true,
|
||||||
|
)
|
||||||
const appWindow = useMemo(() => getCurrentWebviewWindow(), [])
|
const appWindow = useMemo(() => getCurrentWebviewWindow(), [])
|
||||||
|
|
||||||
// Once intersected, refreshes stay enabled until unmount.
|
// Once intersected, the card stays eligible for refresh until unmount.
|
||||||
const [containerRef, hasIntersected, _resetIntersected] = useIntersection({
|
const [containerRef, hasIntersected, _resetIntersected] = useIntersection({
|
||||||
rootMargin: '0px',
|
rootMargin: '0px',
|
||||||
})
|
})
|
||||||
@@ -151,6 +164,8 @@ export const IpInfoCard = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!autoRefresh) return
|
||||||
|
|
||||||
let timer: number | null = null
|
let timer: number | null = null
|
||||||
|
|
||||||
// Prefer DOM intersection state; Tauri visibility is unreliable on some platforms.
|
// Prefer DOM intersection state; Tauri visibility is unreliable on some platforms.
|
||||||
@@ -190,7 +205,7 @@ export const IpInfoCard = () => {
|
|||||||
if (timer != null) clearInterval(timer)
|
if (timer != null) clearInterval(timer)
|
||||||
document.removeEventListener('visibilitychange', onVisibilityChange)
|
document.removeEventListener('visibilitychange', onVisibilityChange)
|
||||||
}
|
}
|
||||||
}, [hasIntersected])
|
}, [autoRefresh, hasIntersected])
|
||||||
|
|
||||||
const toggleShowIp = useCallback(() => {
|
const toggleShowIp = useCallback(() => {
|
||||||
setShowIp((prev) => !prev)
|
setShowIp((prev) => !prev)
|
||||||
@@ -364,12 +379,25 @@ export const IpInfoCard = () => {
|
|||||||
fontSize: '0.7rem',
|
fontSize: '0.7rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
sx={{ m: 0 }}
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
checked={autoRefresh}
|
||||||
|
onChange={(_, checked) => setAutoRefresh(checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
{t('home.components.ipInfo.labels.autoRefresh')}
|
{t('home.components.ipInfo.labels.autoRefresh')}
|
||||||
{countdown.type === 'countdown'
|
{autoRefresh &&
|
||||||
|
(countdown.type === 'countdown'
|
||||||
? `: ${countdown.remainingSeconds}s`
|
? `: ${countdown.remainingSeconds}s`
|
||||||
: '...'}
|
: '...')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -397,9 +397,9 @@ export const RulesEditorViewer = (props: Props) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setPrependSeq(obj?.prepend || [])
|
setPrependSeq((obj?.prepend || []).filter((rule) => rule !== null))
|
||||||
setAppendSeq(obj?.append || [])
|
setAppendSeq((obj?.append || []).filter((rule) => rule !== null))
|
||||||
setDeleteSeq(obj?.delete || [])
|
setDeleteSeq((obj?.delete || []).filter((rule) => rule !== null))
|
||||||
hasLoadedSeqConfigRef.current = true
|
hasLoadedSeqConfigRef.current = true
|
||||||
}, [property])
|
}, [property])
|
||||||
|
|
||||||
@@ -417,9 +417,9 @@ export const RulesEditorViewer = (props: Props) => {
|
|||||||
|
|
||||||
hasLoadedSeqConfigRef.current = true
|
hasLoadedSeqConfigRef.current = true
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
setPrependSeq(obj?.prepend ?? [])
|
setPrependSeq((obj?.prepend ?? []).filter((rule) => rule !== null))
|
||||||
setAppendSeq(obj?.append ?? [])
|
setAppendSeq((obj?.append ?? []).filter((rule) => rule !== null))
|
||||||
setDeleteSeq(obj?.delete ?? [])
|
setDeleteSeq((obj?.delete ?? []).filter((rule) => rule !== null))
|
||||||
})
|
})
|
||||||
setVisualization(true)
|
setVisualization(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ function NormalProxyGroups(props: { mode: string }) {
|
|||||||
/>
|
/>
|
||||||
</DragDropProvider>
|
</DragDropProvider>
|
||||||
|
|
||||||
{mode === 'rule' && (
|
{(mode === 'rule' || mode === 'global') && (
|
||||||
<ProxyGroupNavigator
|
<ProxyGroupNavigator
|
||||||
proxyGroupNames={proxyGroupNames}
|
proxyGroupNames={proxyGroupNames}
|
||||||
onGroupLocation={handleGroupLocationByName}
|
onGroupLocation={handleGroupLocationByName}
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export const useRenderList = (
|
|||||||
if (!proxyView) return []
|
if (!proxyView) return []
|
||||||
if (isChainMode)
|
if (isChainMode)
|
||||||
return selectedGroup ? [selectedGroup] : [CHAIN_DELAY_GROUP]
|
return selectedGroup ? [selectedGroup] : [CHAIN_DELAY_GROUP]
|
||||||
return mode === 'rule' || mode === 'script'
|
return mode === 'rule' || mode === 'global'
|
||||||
? proxyView.groups.map(({ name }) => name)
|
? proxyView.groups.map(({ name }) => name)
|
||||||
: proxyView.global
|
: proxyView.global
|
||||||
? [proxyView.global.name]
|
? [proxyView.global.name]
|
||||||
@@ -257,7 +257,7 @@ export const useRenderList = (
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const useRule = mode === 'rule' || mode === 'script'
|
const useRule = mode === 'rule' || mode === 'global'
|
||||||
const renderGroups = useRule
|
const renderGroups = useRule
|
||||||
? proxyView.groups
|
? proxyView.groups
|
||||||
: proxyView.global === null
|
: proxyView.global === null
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ export const BackupConfigViewer = memo(
|
|||||||
|
|
||||||
const { register, handleSubmit, watch } = useForm<IWebDavConfig>({
|
const { register, handleSubmit, watch } = useForm<IWebDavConfig>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
url: webdav_url!,
|
url: webdav_url ?? '',
|
||||||
username: webdav_username!,
|
username: webdav_username ?? '',
|
||||||
password: webdav_password!,
|
password: webdav_password ?? '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const url = watch('url')
|
const url = watch('url')
|
||||||
@@ -50,9 +50,9 @@ export const BackupConfigViewer = memo(
|
|||||||
const password = watch('password')
|
const password = watch('password')
|
||||||
|
|
||||||
const webdavChanged =
|
const webdavChanged =
|
||||||
webdav_url !== url ||
|
(webdav_url ?? '') !== url ||
|
||||||
webdav_username !== username ||
|
(webdav_username ?? '') !== username ||
|
||||||
webdav_password !== password
|
(webdav_password ?? '') !== password
|
||||||
|
|
||||||
const webdavSignature = buildWebdavSignature(verge)
|
const webdavSignature = buildWebdavSignature(verge)
|
||||||
const webdavStatus = getWebdavStatus(webdavSignature)
|
const webdavStatus = getWebdavStatus(webdavSignature)
|
||||||
@@ -69,9 +69,9 @@ export const BackupConfigViewer = memo(
|
|||||||
}, [webdav_url, webdav_username, webdav_password, onInit, shouldAutoInit])
|
}, [webdav_url, webdav_username, webdav_password, onInit, shouldAutoInit])
|
||||||
|
|
||||||
const checkForm = () => {
|
const checkForm = () => {
|
||||||
const username = usernameRef.current?.value
|
const username = usernameRef.current?.value.trim()
|
||||||
const password = passwordRef.current?.value
|
const password = passwordRef.current?.value
|
||||||
const url = urlRef.current?.value
|
const url = urlRef.current?.value.trim()
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
urlRef.current?.focus()
|
urlRef.current?.focus()
|
||||||
@@ -95,14 +95,15 @@ export const BackupConfigViewer = memo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const save = useLockFn(async (data: IWebDavConfig) => {
|
const save = useLockFn(async (data: IWebDavConfig) => {
|
||||||
checkForm()
|
const trimmedUrl = data.url.trim()
|
||||||
|
const trimmedUsername = data.username.trim()
|
||||||
|
const isClearing = !trimmedUrl && !trimmedUsername && !data.password
|
||||||
|
if (!isClearing) checkForm()
|
||||||
const signature = buildWebdavSignature({
|
const signature = buildWebdavSignature({
|
||||||
webdav_url: data.url,
|
webdav_url: data.url,
|
||||||
webdav_username: data.username,
|
webdav_username: data.username,
|
||||||
webdav_password: data.password,
|
webdav_password: data.password,
|
||||||
})
|
})
|
||||||
const trimmedUrl = data.url.trim()
|
|
||||||
const trimmedUsername = data.username.trim()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -127,7 +128,7 @@ export const BackupConfigViewer = memo(
|
|||||||
)
|
)
|
||||||
setWebdavStatus(signature, 'unknown')
|
setWebdavStatus(signature, 'unknown')
|
||||||
showNotice.success('settings.modals.backup.messages.webdavConfigSaved')
|
showNotice.success('settings.modals.backup.messages.webdavConfigSaved')
|
||||||
await onSaveSuccess(signature)
|
if (!isClearing) await onSaveSuccess(signature)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showNotice.error(
|
showNotice.error(
|
||||||
'settings.modals.backup.messages.webdavConfigSaveFailed',
|
'settings.modals.backup.messages.webdavConfigSaveFailed',
|
||||||
@@ -223,9 +224,9 @@ export const BackupConfigViewer = memo(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{webdavChanged ||
|
{webdavChanged ||
|
||||||
webdav_url === undefined ||
|
!webdav_url ||
|
||||||
webdav_username === undefined ||
|
!webdav_username ||
|
||||||
webdav_password === undefined ? (
|
!webdav_password ? (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color={'primary'}
|
color={'primary'}
|
||||||
|
|||||||
@@ -72,12 +72,10 @@ const LazyReactMarkdown = lazy(async () => {
|
|||||||
{ default: ReactMarkdown },
|
{ default: ReactMarkdown },
|
||||||
{ default: rehypeRaw },
|
{ default: rehypeRaw },
|
||||||
{ default: rehypeSanitize, defaultSchema },
|
{ default: rehypeSanitize, defaultSchema },
|
||||||
{ default: remarkGfm },
|
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
import('react-markdown'),
|
import('react-markdown'),
|
||||||
import('rehype-raw'),
|
import('rehype-raw'),
|
||||||
import('rehype-sanitize'),
|
import('rehype-sanitize'),
|
||||||
import('remark-gfm'),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const sanitizeSchema = {
|
const sanitizeSchema = {
|
||||||
@@ -104,7 +102,6 @@ const LazyReactMarkdown = lazy(async () => {
|
|||||||
default: (props: ReactMarkdownOptions) => (
|
default: (props: ReactMarkdownOptions) => (
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
{...props}
|
{...props}
|
||||||
remarkPlugins={[remarkGfm, ...(props.remarkPlugins ?? [])]}
|
|
||||||
rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}
|
rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
|
"risky": "غير مؤكد",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"disallowedIsp": "Disallowed ISP",
|
"disallowedIsp": "Disallowed ISP",
|
||||||
"originalsOnly": "Originals Only",
|
"originalsOnly": "Originals Only",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Unterstützt",
|
"yes": "Unterstützt",
|
||||||
"no": "Nicht unterstützt",
|
"no": "Nicht unterstützt",
|
||||||
"failed": "Test fehlgeschlagen",
|
"failed": "Test fehlgeschlagen",
|
||||||
|
"risky": "Ungewiss",
|
||||||
"completed": "Prüfung abgeschlossen",
|
"completed": "Prüfung abgeschlossen",
|
||||||
"disallowedIsp": "Nicht zugelassener Internetdienstanbieter",
|
"disallowedIsp": "Nicht zugelassener Internetdienstanbieter",
|
||||||
"originalsOnly": "Nur Original",
|
"originalsOnly": "Nur Original",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
|
"risky": "Risky",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"disallowedIsp": "Disallowed ISP",
|
"disallowedIsp": "Disallowed ISP",
|
||||||
"originalsOnly": "Originals Only",
|
"originalsOnly": "Originals Only",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Soportado",
|
"yes": "Soportado",
|
||||||
"no": "No soportado",
|
"no": "No soportado",
|
||||||
"failed": "Prueba fallida",
|
"failed": "Prueba fallida",
|
||||||
|
"risky": "Dudoso",
|
||||||
"completed": "Detección completada",
|
"completed": "Detección completada",
|
||||||
"disallowedIsp": "Proveedor de servicios de Internet no permitido",
|
"disallowedIsp": "Proveedor de servicios de Internet no permitido",
|
||||||
"originalsOnly": "Solo originales",
|
"originalsOnly": "Solo originales",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
|
"risky": "نامشخص",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"disallowedIsp": "Disallowed ISP",
|
"disallowedIsp": "Disallowed ISP",
|
||||||
"originalsOnly": "Originals Only",
|
"originalsOnly": "Originals Only",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
|
"risky": "Tidak pasti",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"disallowedIsp": "Disallowed ISP",
|
"disallowedIsp": "Disallowed ISP",
|
||||||
"originalsOnly": "Originals Only",
|
"originalsOnly": "Originals Only",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "サポートする",
|
"yes": "サポートする",
|
||||||
"no": "サポートしない",
|
"no": "サポートしない",
|
||||||
"failed": "テストに失敗しました。",
|
"failed": "テストに失敗しました。",
|
||||||
|
"risky": "要確認",
|
||||||
"completed": "検査完了",
|
"completed": "検査完了",
|
||||||
"disallowedIsp": "許可されていないインターネットサービスプロバイダー",
|
"disallowedIsp": "許可されていないインターネットサービスプロバイダー",
|
||||||
"originalsOnly": "オリジナルのみ",
|
"originalsOnly": "オリジナルのみ",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "예",
|
"yes": "예",
|
||||||
"no": "아니오",
|
"no": "아니오",
|
||||||
"failed": "실패",
|
"failed": "실패",
|
||||||
|
"risky": "확인 필요",
|
||||||
"completed": "완료",
|
"completed": "완료",
|
||||||
"disallowedIsp": "허용되지 않는 ISP",
|
"disallowedIsp": "허용되지 않는 ISP",
|
||||||
"originalsOnly": "오리지널만",
|
"originalsOnly": "오리지널만",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Sim",
|
"yes": "Sim",
|
||||||
"no": "Não",
|
"no": "Não",
|
||||||
"failed": "Falhou",
|
"failed": "Falhou",
|
||||||
|
"risky": "Incerto",
|
||||||
"completed": "Concluído",
|
"completed": "Concluído",
|
||||||
"disallowedIsp": "ISP não permitido",
|
"disallowedIsp": "ISP não permitido",
|
||||||
"originalsOnly": "Somente originais",
|
"originalsOnly": "Somente originais",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
"no": "Нет",
|
"no": "Нет",
|
||||||
"failed": "Ошибка",
|
"failed": "Ошибка",
|
||||||
|
"risky": "Неясно",
|
||||||
"completed": "Завершено",
|
"completed": "Завершено",
|
||||||
"disallowedIsp": "Провайдер заблокирован",
|
"disallowedIsp": "Провайдер заблокирован",
|
||||||
"originalsOnly": "Только Originals",
|
"originalsOnly": "Только Originals",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Evet",
|
"yes": "Evet",
|
||||||
"no": "Hayır",
|
"no": "Hayır",
|
||||||
"failed": "Başarısız",
|
"failed": "Başarısız",
|
||||||
|
"risky": "Belirsiz",
|
||||||
"completed": "Tamamlandı",
|
"completed": "Tamamlandı",
|
||||||
"disallowedIsp": "İzin Verilmeyen ISP",
|
"disallowedIsp": "İzin Verilmeyen ISP",
|
||||||
"originalsOnly": "Yalnızca Orijinaller",
|
"originalsOnly": "Yalnızca Orijinaller",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
|
"risky": "Билгесез",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"disallowedIsp": "Disallowed ISP",
|
"disallowedIsp": "Disallowed ISP",
|
||||||
"originalsOnly": "Originals Only",
|
"originalsOnly": "Originals Only",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "支持",
|
"yes": "支持",
|
||||||
"no": "不支持",
|
"no": "不支持",
|
||||||
"failed": "测试失败",
|
"failed": "测试失败",
|
||||||
|
"risky": "存疑",
|
||||||
"completed": "检测完成",
|
"completed": "检测完成",
|
||||||
"disallowedIsp": "不允许的 ISP",
|
"disallowedIsp": "不允许的 ISP",
|
||||||
"originalsOnly": "仅限原创",
|
"originalsOnly": "仅限原创",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"yes": "支援",
|
"yes": "支援",
|
||||||
"no": "不支援",
|
"no": "不支援",
|
||||||
"failed": "測試失敗",
|
"failed": "測試失敗",
|
||||||
|
"risky": "存疑",
|
||||||
"completed": "檢測完成",
|
"completed": "檢測完成",
|
||||||
"disallowedIsp": "不允許的網際網路服務供應商",
|
"disallowedIsp": "不允許的網際網路服務供應商",
|
||||||
"originalsOnly": "僅限原創",
|
"originalsOnly": "僅限原創",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const wrapCssInjectionWithScope = (css?: string) => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const scopeRoot = CSS_INJECTION_SCOPE_ROOT
|
const scopeRoot = CSS_INJECTION_SCOPE_ROOT
|
||||||
const scopeLimit = CSS_INJECTION_SCOPE_LIMIT
|
const scopeLimit = `${CSS_INJECTION_SCOPE_LIMIT} > *`
|
||||||
const scopedBlock = `@scope (${scopeRoot}) to (${scopeLimit}) {
|
const scopedBlock = `@scope (${scopeRoot}) to (${scopeLimit}) {
|
||||||
${css}
|
${css}
|
||||||
}`
|
}`
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const STATUS_LABEL_KEYS: Record<string, string> = {
|
|||||||
Yes: 'tests.statuses.test.yes',
|
Yes: 'tests.statuses.test.yes',
|
||||||
No: 'tests.statuses.test.no',
|
No: 'tests.statuses.test.no',
|
||||||
Failed: 'tests.statuses.test.failed',
|
Failed: 'tests.statuses.test.failed',
|
||||||
|
Risky: 'tests.statuses.test.risky',
|
||||||
Completed: 'tests.statuses.test.completed',
|
Completed: 'tests.statuses.test.completed',
|
||||||
'Disallowed ISP': 'tests.statuses.test.disallowedIsp',
|
'Disallowed ISP': 'tests.statuses.test.disallowedIsp',
|
||||||
'Originals Only': 'tests.statuses.test.originalsOnly',
|
'Originals Only': 'tests.statuses.test.originalsOnly',
|
||||||
@@ -196,7 +197,7 @@ const UnlockPage = () => {
|
|||||||
if (status === 'Pending') return 'default'
|
if (status === 'Pending') return 'default'
|
||||||
if (status === 'Yes') return 'success'
|
if (status === 'Yes') return 'success'
|
||||||
if (status === 'No') return 'error'
|
if (status === 'No') return 'error'
|
||||||
if (status === 'Soon') return 'warning'
|
if (status === 'Soon' || status === 'Risky') return 'warning'
|
||||||
if (status.includes('Failed')) return 'error'
|
if (status.includes('Failed')) return 'error'
|
||||||
if (status === 'Completed') return 'info'
|
if (status === 'Completed') return 'info'
|
||||||
if (
|
if (
|
||||||
@@ -222,7 +223,8 @@ const UnlockPage = () => {
|
|||||||
const getStatusBorderColor = (status: string) => {
|
const getStatusBorderColor = (status: string) => {
|
||||||
if (status === 'Yes') return theme.palette.success.main
|
if (status === 'Yes') return theme.palette.success.main
|
||||||
if (status === 'No') return theme.palette.error.main
|
if (status === 'No') return theme.palette.error.main
|
||||||
if (status === 'Soon') return theme.palette.warning.main
|
if (status === 'Soon' || status === 'Risky')
|
||||||
|
return theme.palette.warning.main
|
||||||
if (status.includes('Failed')) return theme.palette.error.main
|
if (status.includes('Failed')) return theme.palette.error.main
|
||||||
if (status === 'Completed') return theme.palette.info.main
|
if (status === 'Completed') return theme.palette.info.main
|
||||||
return theme.palette.divider
|
return theme.palette.divider
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ export const getWebdavStatus = (signature: string): WebdavStatus => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const setWebdavStatus = (signature: string, status: WebdavStatus) => {
|
export const setWebdavStatus = (signature: string, status: WebdavStatus) => {
|
||||||
if (!signature || !canUseStorage()) return
|
if (!canUseStorage()) return
|
||||||
|
if (!signature) {
|
||||||
|
localStorage.removeItem(WEBDAV_STATUS_KEY)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const payload: WebdavStatusCache = {
|
const payload: WebdavStatusCache = {
|
||||||
signature,
|
signature,
|
||||||
|
|||||||
+3
-1
@@ -14,7 +14,9 @@ export default defineConfig({
|
|||||||
legacy({
|
legacy({
|
||||||
modernTargets: ['edge>=109', 'safari>=14'],
|
modernTargets: ['edge>=109', 'safari>=14'],
|
||||||
renderLegacyChunks: false,
|
renderLegacyChunks: false,
|
||||||
modernPolyfills: ['es.object.has-own', 'web.structured-clone'],
|
// Auto-detected from the emitted bundle, dependencies included,
|
||||||
|
// so post-Safari-14 APIs never depend on extending a manual list.
|
||||||
|
modernPolyfills: true,
|
||||||
additionalModernPolyfills: [
|
additionalModernPolyfills: [
|
||||||
path.resolve('./src/polyfills/matchMedia.js'),
|
path.resolve('./src/polyfills/matchMedia.js'),
|
||||||
path.resolve('./src/polyfills/WeakRef.js'),
|
path.resolve('./src/polyfills/WeakRef.js'),
|
||||||
|
|||||||
Reference in New Issue
Block a user