mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-14 21:20:48 +08:00
fix(service): adopt IPC 2.7.3 and correct dev fallback handling
This commit is contained in:
Generated
+2
-2
@@ -1223,8 +1223,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash_verge_service_ipc"
|
||||
version = "2.7.2"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc.git?tag=v2.7.2#cad270fe753553cfa2fd26fa5c338eabd210a2d5"
|
||||
version = "2.7.3"
|
||||
source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc.git?tag=v2.7.3#29251d968e1a38fac1de0ebe9d6d69402bea83b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"kode-bridge",
|
||||
|
||||
+58
-26
@@ -19,15 +19,6 @@ export const developmentServiceWatchPaths = [
|
||||
'Cargo.lock',
|
||||
].map((name) => join(serviceRepository, name))
|
||||
|
||||
function executable(name, platform) {
|
||||
return join(
|
||||
serviceRepository,
|
||||
'target',
|
||||
'debug',
|
||||
platform === 'win32' ? `${name}.exe` : name,
|
||||
)
|
||||
}
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
@@ -35,10 +26,15 @@ function run(command, args, options = {}) {
|
||||
stdio: options.stdio ?? 'inherit',
|
||||
windowsHide: true,
|
||||
})
|
||||
let stdout = ''
|
||||
child.stdout?.setEncoding('utf8')
|
||||
child.stdout?.on('data', (chunk) => {
|
||||
stdout += chunk
|
||||
})
|
||||
child.once('error', reject)
|
||||
child.once('exit', (code, signal) => {
|
||||
child.once('close', (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolvePromise()
|
||||
resolvePromise(stdout)
|
||||
return
|
||||
}
|
||||
const error = new Error(
|
||||
@@ -50,28 +46,64 @@ function run(command, args, options = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
export async function prepareDevelopmentService({
|
||||
platform = process.platform,
|
||||
} = {}) {
|
||||
export async function prepareDevelopmentService() {
|
||||
await access(serviceManifest)
|
||||
await run('cargo', [
|
||||
'build',
|
||||
'--manifest-path',
|
||||
serviceManifest,
|
||||
'--features',
|
||||
'standalone,client,development-channel',
|
||||
'--bins',
|
||||
])
|
||||
const output = await run(
|
||||
'cargo',
|
||||
[
|
||||
'build',
|
||||
'--manifest-path',
|
||||
serviceManifest,
|
||||
'--target-dir',
|
||||
process.env.CARGO_TARGET_DIR ||
|
||||
join(serviceRepository, 'target', 'development'),
|
||||
'--features',
|
||||
'standalone,client,development-channel',
|
||||
'--bins',
|
||||
'--message-format=json-render-diagnostics',
|
||||
],
|
||||
{ stdio: ['inherit', 'pipe', 'inherit'] },
|
||||
)
|
||||
|
||||
const service = executable('clash-verge-service', platform)
|
||||
await access(service)
|
||||
return dirname(service)
|
||||
const artifacts = new Map()
|
||||
for (const line of output.split(/\r?\n/).filter(Boolean)) {
|
||||
const artifact = JSON.parse(line)
|
||||
if (
|
||||
artifact.reason === 'compiler-artifact' &&
|
||||
resolve(artifact.manifest_path) === serviceManifest &&
|
||||
artifact.target.kind.includes('bin') &&
|
||||
artifact.executable
|
||||
) {
|
||||
artifacts.set(artifact.target.name, resolve(artifact.executable))
|
||||
}
|
||||
}
|
||||
const executables = [
|
||||
'clash-verge-service',
|
||||
'clash-verge-service-install',
|
||||
'clash-verge-service-uninstall',
|
||||
'service-integration-driver',
|
||||
].map((name) => {
|
||||
const executable = artifacts.get(name)
|
||||
if (!executable)
|
||||
throw new Error(`Cargo did not report the ${name} executable`)
|
||||
return executable
|
||||
})
|
||||
const serviceDirectory = dirname(executables[0])
|
||||
for (const executable of executables) {
|
||||
if (dirname(executable) !== serviceDirectory) {
|
||||
throw new Error(
|
||||
'Development service tools were built in different directories',
|
||||
)
|
||||
}
|
||||
await access(executable)
|
||||
}
|
||||
return serviceDirectory
|
||||
}
|
||||
|
||||
export async function ensureDevelopmentService({
|
||||
platform = process.platform,
|
||||
} = {}) {
|
||||
const serviceDirectory = await prepareDevelopmentService({ platform })
|
||||
const serviceDirectory = await prepareDevelopmentService()
|
||||
const extension = platform === 'win32' ? '.exe' : ''
|
||||
const installer = join(
|
||||
serviceDirectory,
|
||||
|
||||
@@ -99,7 +99,7 @@ tracing = { workspace = true }
|
||||
tauri-plugin-devtools = { version = "2.2.1", optional = true }
|
||||
tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "main" }
|
||||
async-trait = "0.1.92"
|
||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.2", version = "2.7.2", features = [
|
||||
clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.7.3", version = "2.7.3", features = [
|
||||
"client",
|
||||
] }
|
||||
arc-swap = "1.9.2"
|
||||
|
||||
@@ -491,7 +491,7 @@ fn escape_osascript_double_quoted_string(value: &str) -> String {
|
||||
value.replace('\\', "\\\\").replace('"', "\\\"")
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn shell_single_quote(value: &str) -> String {
|
||||
format!("'{}'", value.replace('\'', r"'\''"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useState } from 'react'
|
||||
import { beforeEach, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { RunState } from '@/services/cmds'
|
||||
import { useQuery } from '@/services/query-client'
|
||||
|
||||
import { ServiceMigrationDialog } from './service-migration-dialog'
|
||||
|
||||
vi.mock('react', () => ({ useState: vi.fn() }))
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}))
|
||||
vi.mock('@mui/material', () => ({ Alert: 'div' }))
|
||||
vi.mock('@/components/base', () => ({ BaseDialog: 'dialog' }))
|
||||
vi.mock('@/hooks/use-visibility', () => ({ useVisibility: () => true }))
|
||||
vi.mock('@/hooks/use-system-state', () => ({ runStateQueryKey: ['state'] }))
|
||||
vi.mock('@/services/cmds', () => ({ getRuntimeState: vi.fn() }))
|
||||
vi.mock('@/services/notice-service', () => ({ showNotice: {} }))
|
||||
vi.mock('@/services/query-client', () => ({
|
||||
useQuery: vi.fn(),
|
||||
setCacheData: vi.fn(),
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(useState)
|
||||
.mockReturnValueOnce([false, vi.fn()])
|
||||
.mockReturnValueOnce([false, vi.fn()])
|
||||
.mockReturnValueOnce([true, vi.fn()])
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ service: 'ready', mode: 'NotRunning' },
|
||||
{ service: 'unknown', mode: 'NotRunning' },
|
||||
{ service: 'unavailable', mode: 'Service' },
|
||||
{ service: 'unavailable', mode: 'NotRunning', opInFlight: true },
|
||||
{ service: 'unavailable', mode: 'NotRunning', pendingAction: 'reinstall' },
|
||||
] satisfies Partial<RunState>[])(
|
||||
'does not offer Sidecar continuation from %j',
|
||||
(state) => {
|
||||
vi.mocked(useQuery).mockReturnValue({ data: state } as ReturnType<
|
||||
typeof useQuery
|
||||
>)
|
||||
const dialog = ServiceMigrationDialog()
|
||||
expect(dialog.props.open).toBe(true)
|
||||
expect(dialog.props.disableCancel).toBe(true)
|
||||
},
|
||||
)
|
||||
|
||||
it.each([
|
||||
{ service: 'notInstalled', mode: 'NotRunning' },
|
||||
{ service: 'versionMismatch', mode: 'NotRunning' },
|
||||
{ service: 'unavailable', mode: 'NotRunning' },
|
||||
{ service: 'notInstalled', mode: 'Sidecar', pendingAction: 'install' },
|
||||
] satisfies Partial<RunState>[])(
|
||||
'offers Sidecar continuation from %j',
|
||||
(state) => {
|
||||
vi.mocked(useQuery).mockReturnValue({ data: state } as ReturnType<
|
||||
typeof useQuery
|
||||
>)
|
||||
expect(ServiceMigrationDialog().props.disableCancel).toBe(false)
|
||||
},
|
||||
)
|
||||
@@ -44,7 +44,24 @@ export const ServiceMigrationDialog = () => {
|
||||
? 'install'
|
||||
: 'reinstall'
|
||||
const open = loading || workflowIncomplete || needsDecision
|
||||
const showCheckingMessage = loading || !needsDecision
|
||||
const checking =
|
||||
loading ||
|
||||
!runState ||
|
||||
runState.opInFlight ||
|
||||
runState.service === 'unknown'
|
||||
const showCheckingMessage = checking || !needsDecision
|
||||
const canContinue = Boolean(
|
||||
runState &&
|
||||
!stateRefreshFailed &&
|
||||
(runState.pendingAction === 'install'
|
||||
? runState.mode === 'NotRunning' || runState.mode === 'Sidecar'
|
||||
: !runState.pendingAction &&
|
||||
!runState.sidecarAllowed &&
|
||||
runState.mode === 'NotRunning' &&
|
||||
(runState.service === 'notInstalled' ||
|
||||
runState.service === 'versionMismatch' ||
|
||||
runState.service === 'unavailable')),
|
||||
)
|
||||
|
||||
// One cache entry to refresh, so there is nothing left to keep coherent by hand.
|
||||
const refreshRunState = async () => {
|
||||
@@ -165,8 +182,8 @@ export const ServiceMigrationDialog = () => {
|
||||
: 'layout.components.serviceMigration.reinstall',
|
||||
)}
|
||||
cancelBtn={t('layout.components.serviceMigration.continueSidecar')}
|
||||
disableOk={loading}
|
||||
disableCancel={loading}
|
||||
disableOk={checking}
|
||||
disableCancel={checking || !canContinue}
|
||||
loading={loading}
|
||||
onOk={() => void handleServiceAction()}
|
||||
onCancel={() => void handleContinue()}
|
||||
|
||||
Reference in New Issue
Block a user