mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix(cli): avoid shell for file manager actions
This commit is contained in:
1 parent
19a45f9e5f
commit
8ae8ff6cda
4 files changed
+83
-18
No files matched your search
@@ -0,0 +1,29 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { openDirectoryPath, showPathInFolder } from './cache'
|
||||
|
||||
describe('CLI Web cache route helpers', () => {
|
||||
it('opens directories with execFile arguments instead of shell commands', async () => {
|
||||
const calls: Array<{ file: string; args: string[] }> = []
|
||||
const dirPath = '/tmp/chat"lab; touch /tmp/pwned'
|
||||
|
||||
await openDirectoryPath(dirPath, 'darwin', (file, args, callback) => {
|
||||
calls.push({ file, args })
|
||||
callback(null)
|
||||
})
|
||||
|
||||
assert.deepEqual(calls, [{ file: 'open', args: [dirPath] }])
|
||||
})
|
||||
|
||||
it('reveals files with execFile arguments instead of shell commands', async () => {
|
||||
const calls: Array<{ file: string; args: string[] }> = []
|
||||
const filePath = '/tmp/chat"lab; touch /tmp/pwned/export.json'
|
||||
|
||||
await showPathInFolder(filePath, 'darwin', (file, args, callback) => {
|
||||
calls.push({ file, args })
|
||||
callback(null)
|
||||
})
|
||||
|
||||
assert.deepEqual(calls, [{ file: 'open', args: ['-R', filePath] }])
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,53 @@
|
||||
import * as fs from 'fs'
|
||||
import * as fsp from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
import { execFile } from 'child_process'
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import type { PathProvider } from '@openchatlab/core'
|
||||
import { loadConfig } from '@openchatlab/config'
|
||||
|
||||
type ExecFileRunner = (file: string, args: string[], callback: (error: Error | null) => void) => unknown
|
||||
|
||||
function runExecFile(file: string, args: string[], runner: ExecFileRunner = execFile): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
runner(file, args, (error) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function openDirectoryPath(
|
||||
dirPath: string,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
runner?: ExecFileRunner
|
||||
): Promise<void> {
|
||||
if (platform === 'darwin') {
|
||||
await runExecFile('open', [dirPath], runner)
|
||||
} else if (platform === 'win32') {
|
||||
await runExecFile('explorer.exe', [dirPath], runner)
|
||||
} else {
|
||||
await runExecFile('xdg-open', [dirPath], runner)
|
||||
}
|
||||
}
|
||||
|
||||
export async function showPathInFolder(
|
||||
filePath: string,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
runner?: ExecFileRunner
|
||||
): Promise<void> {
|
||||
if (platform === 'darwin') {
|
||||
await runExecFile('open', ['-R', filePath], runner)
|
||||
} else if (platform === 'win32') {
|
||||
await runExecFile('explorer.exe', [`/select,${filePath}`], runner)
|
||||
} else {
|
||||
await runExecFile('xdg-open', [path.dirname(filePath)], runner)
|
||||
}
|
||||
}
|
||||
|
||||
async function getDirSize(dirPath: string): Promise<number> {
|
||||
let totalSize = 0
|
||||
try {
|
||||
@@ -142,14 +185,10 @@ export function registerCacheRoutes(server: FastifyInstance, pathProvider: PathP
|
||||
await fsp.mkdir(dirPath, { recursive: true })
|
||||
}
|
||||
|
||||
const { exec } = await import('child_process')
|
||||
const platform = process.platform
|
||||
if (platform === 'darwin') {
|
||||
exec(`open "${dirPath}"`)
|
||||
} else if (platform === 'win32') {
|
||||
exec(`explorer "${dirPath}"`)
|
||||
} else {
|
||||
exec(`xdg-open "${dirPath}"`)
|
||||
try {
|
||||
await openDirectoryPath(dirPath)
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : String(error) }
|
||||
}
|
||||
return { success: true }
|
||||
})
|
||||
@@ -216,16 +255,10 @@ export function registerCacheRoutes(server: FastifyInstance, pathProvider: PathP
|
||||
return { success: false, error: 'filePath is required' }
|
||||
}
|
||||
|
||||
const { exec } = await import('child_process')
|
||||
const platform = process.platform
|
||||
const dir = path.dirname(filePath)
|
||||
|
||||
if (platform === 'darwin') {
|
||||
exec(`open -R "${filePath}"`)
|
||||
} else if (platform === 'win32') {
|
||||
exec(`explorer /select,"${filePath}"`)
|
||||
} else {
|
||||
exec(`xdg-open "${dir}"`)
|
||||
try {
|
||||
await showPathInFolder(filePath)
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : String(error) }
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
|
||||
@@ -53,6 +53,7 @@ function isNewerVersion(latest: string, current: string): boolean {
|
||||
if (l.minor !== c.minor) return l.minor > c.minor
|
||||
if (l.patch !== c.patch) return l.patch > c.patch
|
||||
// Same core version: stable > pre-release
|
||||
// Design note: prerelease-to-prerelease updates are intentionally not surfaced.
|
||||
if (c.pre && !l.pre) return true
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ function isNewerVersion(latest: string, current: string): boolean {
|
||||
if (l.major !== c.major) return l.major > c.major
|
||||
if (l.minor !== c.minor) return l.minor > c.minor
|
||||
if (l.patch !== c.patch) return l.patch > c.patch
|
||||
// Design note: prerelease users are not prompted for prerelease-to-prerelease updates.
|
||||
// Only a stable release with the same core version should supersede a prerelease build.
|
||||
if (c.pre && !l.pre) return true
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user