mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-27 04:31:27 +08:00
Add JavaScript MCP server with QuickJS WASM sandbox execution
- Implement JsServer MCP server with `run_javascript_code` tool for secure code execution - Add JsService worker management with timeout handling and error formatting - Include QuickJS WASM binary and integrate with WASI for sandboxed execution - Update UI with i18n support and JSON result formatting improvements
This commit is contained in:
parent
d4b3428160
commit
1db259cd3e
BIN
resources/wasm/qjs-wasi.wasm
Normal file
BIN
resources/wasm/qjs-wasi.wasm
Normal file
Binary file not shown.
@ -6,6 +6,7 @@ import BraveSearchServer from './brave-search'
|
||||
import DifyKnowledgeServer from './dify-knowledge'
|
||||
import FetchServer from './fetch'
|
||||
import FileSystemServer from './filesystem'
|
||||
import JsServer from './js'
|
||||
import MemoryServer from './memory'
|
||||
import PythonServer from './python'
|
||||
import ThinkingServer from './sequentialthinking'
|
||||
@ -42,6 +43,9 @@ export function createInMemoryMCPServer(
|
||||
case BuiltinMCPServerNames.python: {
|
||||
return new PythonServer().server
|
||||
}
|
||||
case BuiltinMCPServerNames.js: {
|
||||
return new JsServer().server
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown in-memory MCP server: ${name}`)
|
||||
}
|
||||
|
||||
139
src/main/mcpServers/js.ts
Normal file
139
src/main/mcpServers/js.ts
Normal file
@ -0,0 +1,139 @@
|
||||
// port from https://github.com/jlucaso1/mcp-javascript-sandbox
|
||||
import { loggerService } from '@logger'
|
||||
import { jsService } from '@main/services/JsService'
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types'
|
||||
import { z } from 'zod'
|
||||
|
||||
const TOOL_NAME = 'run_javascript_code'
|
||||
const DEFAULT_TIMEOUT = 60_000
|
||||
|
||||
export const RequestPayloadSchema = z.object({
|
||||
javascript_code: z.string().min(1).describe('The JavaScript code to execute in the sandbox.'),
|
||||
timeout: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.max(5 * 60_000)
|
||||
.optional()
|
||||
.describe('Execution timeout in milliseconds (default 60000, max 300000).')
|
||||
})
|
||||
|
||||
const logger = loggerService.withContext('MCPServer:JavaScript')
|
||||
|
||||
function formatExecutionResult(result: {
|
||||
stdout: string
|
||||
stderr: string
|
||||
error?: string | undefined
|
||||
exitCode: number
|
||||
}) {
|
||||
let combinedOutput = ''
|
||||
if (result.stdout) {
|
||||
combinedOutput += result.stdout
|
||||
}
|
||||
if (result.stderr) {
|
||||
combinedOutput += `--- stderr ---\n${result.stderr}\n--- stderr ---\n`
|
||||
}
|
||||
if (result.error) {
|
||||
combinedOutput += `--- Execution Error ---\n${result.error}\n--- Execution Error ---\n`
|
||||
}
|
||||
|
||||
const isError = Boolean(result.error) || Boolean(result.stderr?.trim()) || result.exitCode !== 0
|
||||
|
||||
return {
|
||||
combinedOutput: combinedOutput.trim(),
|
||||
isError
|
||||
}
|
||||
}
|
||||
|
||||
class JsServer {
|
||||
public server: Server
|
||||
|
||||
constructor() {
|
||||
this.server = new Server(
|
||||
{
|
||||
name: 'MCP QuickJS Runner',
|
||||
version: '1.0.0',
|
||||
description: 'An MCP server that provides a tool to execute JavaScript code in a QuickJS WASM sandbox.'
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
this.setupHandlers()
|
||||
}
|
||||
|
||||
private setupHandlers() {
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: TOOL_NAME,
|
||||
description:
|
||||
'Executes the provided JavaScript code in a secure WASM sandbox (QuickJS). Returns stdout and stderr. Non-zero exit code indicates an error.',
|
||||
inputSchema: z.toJSONSchema(RequestPayloadSchema)
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { name, arguments: args } = request.params
|
||||
|
||||
if (name !== TOOL_NAME) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Tool not found: ${name}` }],
|
||||
isError: true
|
||||
}
|
||||
}
|
||||
|
||||
const parseResult = RequestPayloadSchema.safeParse(args)
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `Invalid arguments: ${parseResult.error.message}` }],
|
||||
isError: true
|
||||
}
|
||||
}
|
||||
|
||||
const { javascript_code, timeout } = parseResult.data
|
||||
|
||||
try {
|
||||
logger.debug('Executing JavaScript code via JsService')
|
||||
const result = await jsService.executeScript(javascript_code, {
|
||||
timeout: timeout ?? DEFAULT_TIMEOUT
|
||||
})
|
||||
|
||||
const { combinedOutput, isError } = formatExecutionResult(result as any)
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: combinedOutput
|
||||
}
|
||||
],
|
||||
isError
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
logger.error(`JavaScript execution failed: ${message}`)
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Server error during tool execution: ${message}`
|
||||
}
|
||||
],
|
||||
isError: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default JsServer
|
||||
107
src/main/services/JsService.ts
Normal file
107
src/main/services/JsService.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { loggerService } from '@logger'
|
||||
|
||||
import type { JsExecutionResult } from './workers/JsWorker'
|
||||
// oxlint-disable-next-line default
|
||||
import createJsWorker from './workers/JsWorker?nodeWorker'
|
||||
|
||||
interface ExecuteScriptOptions {
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
type WorkerResponse =
|
||||
| {
|
||||
success: true
|
||||
result: JsExecutionResult
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
error: string
|
||||
}
|
||||
|
||||
const DEFAULT_TIMEOUT = 60_000
|
||||
|
||||
const logger = loggerService.withContext('JsService')
|
||||
|
||||
export class JsService {
|
||||
private static instance: JsService | null = null
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public static getInstance(): JsService {
|
||||
if (!JsService.instance) {
|
||||
JsService.instance = new JsService()
|
||||
}
|
||||
return JsService.instance
|
||||
}
|
||||
|
||||
public async executeScript(code: string, options: ExecuteScriptOptions = {}): Promise<JsExecutionResult> {
|
||||
const { timeout = DEFAULT_TIMEOUT } = options
|
||||
|
||||
if (!code || typeof code !== 'string') {
|
||||
throw new Error('JavaScript code must be a non-empty string')
|
||||
}
|
||||
|
||||
return new Promise<JsExecutionResult>((resolve, reject) => {
|
||||
const worker = createJsWorker({
|
||||
workerData: { code },
|
||||
argv: [],
|
||||
trackUnmanagedFds: false
|
||||
})
|
||||
|
||||
let settled = false
|
||||
let timeoutId: NodeJS.Timeout | null = null
|
||||
|
||||
const cleanup = async () => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = null
|
||||
}
|
||||
try {
|
||||
await worker.terminate()
|
||||
} catch {
|
||||
// ignore termination errors
|
||||
}
|
||||
}
|
||||
|
||||
const settleSuccess = async (result: JsExecutionResult) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
await cleanup()
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const settleError = async (error: Error) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
await cleanup()
|
||||
reject(error)
|
||||
}
|
||||
|
||||
worker.once('message', async (message: WorkerResponse) => {
|
||||
if (message.success) {
|
||||
await settleSuccess(message.result)
|
||||
} else {
|
||||
await settleError(new Error(message.error))
|
||||
}
|
||||
})
|
||||
|
||||
worker.once('error', async (error) => {
|
||||
logger.error(`JsWorker error: ${error instanceof Error ? error.message : String(error)}`)
|
||||
await settleError(error instanceof Error ? error : new Error(String(error)))
|
||||
})
|
||||
|
||||
worker.once('exit', async (exitCode) => {
|
||||
if (!settled && exitCode !== 0) {
|
||||
await settleError(new Error(`JsWorker exited with code ${exitCode}`))
|
||||
}
|
||||
})
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
logger.warn(`JavaScript execution timed out after ${timeout}ms`)
|
||||
void settleError(new Error('JavaScript execution timed out'))
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const jsService = JsService.getInstance()
|
||||
115
src/main/services/workers/JsWorker.ts
Normal file
115
src/main/services/workers/JsWorker.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { mkdtemp, open, readFile, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { env } from 'node:process'
|
||||
import { WASI } from 'node:wasi'
|
||||
import { parentPort, workerData } from 'node:worker_threads'
|
||||
|
||||
import loadWasm from '../../../../resources/wasm/qjs-wasi.wasm?loader'
|
||||
|
||||
interface WorkerPayload {
|
||||
code: string
|
||||
}
|
||||
|
||||
export interface JsExecutionResult {
|
||||
stdout: string
|
||||
stderr: string
|
||||
error?: string
|
||||
exitCode: number
|
||||
}
|
||||
|
||||
if (!parentPort) {
|
||||
throw new Error('JsWorker requires a parent port')
|
||||
}
|
||||
|
||||
async function runQuickJsInSandbox(jsCode: string): Promise<JsExecutionResult> {
|
||||
let tempDir: string | undefined
|
||||
let stdoutHandle: Awaited<ReturnType<typeof open>> | undefined
|
||||
let stderrHandle: Awaited<ReturnType<typeof open>> | undefined
|
||||
let stdoutPath: string | undefined
|
||||
let stderrPath: string | undefined
|
||||
|
||||
try {
|
||||
tempDir = await mkdtemp(join(tmpdir(), 'quickjs-wasi-'))
|
||||
stdoutPath = join(tempDir, 'stdout.log')
|
||||
stderrPath = join(tempDir, 'stderr.log')
|
||||
|
||||
stdoutHandle = await open(stdoutPath, 'w')
|
||||
stderrHandle = await open(stderrPath, 'w')
|
||||
|
||||
const wasi = new WASI({
|
||||
version: 'preview1',
|
||||
args: ['qjs', '-e', jsCode],
|
||||
env,
|
||||
stdin: 0,
|
||||
stdout: stdoutHandle.fd,
|
||||
stderr: stderrHandle.fd,
|
||||
returnOnExit: true
|
||||
})
|
||||
const instance = await loadWasm(wasi.getImportObject() as WebAssembly.Imports)
|
||||
|
||||
let exitCode = 0
|
||||
try {
|
||||
exitCode = wasi.start(instance)
|
||||
} catch (wasiError: any) {
|
||||
return {
|
||||
stdout: '',
|
||||
stderr: `WASI start error: ${wasiError?.message ?? String(wasiError)}`,
|
||||
error: `Sandbox execution failed during start: ${wasiError?.message ?? String(wasiError)}`,
|
||||
exitCode: -1
|
||||
}
|
||||
}
|
||||
|
||||
await stdoutHandle.close()
|
||||
stdoutHandle = undefined
|
||||
await stderrHandle.close()
|
||||
stderrHandle = undefined
|
||||
|
||||
const capturedStdout = await readFile(stdoutPath, 'utf8')
|
||||
const capturedStderr = await readFile(stderrPath, 'utf8')
|
||||
|
||||
let executionError: string | undefined
|
||||
if (exitCode !== 0) {
|
||||
executionError = `QuickJS process exited with code ${exitCode}. Check stderr for details.`
|
||||
}
|
||||
|
||||
return {
|
||||
stdout: capturedStdout,
|
||||
stderr: capturedStderr,
|
||||
error: executionError,
|
||||
exitCode
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
error: `Sandbox setup or execution failed: ${error?.message ?? String(error)}`,
|
||||
exitCode: -1
|
||||
}
|
||||
} finally {
|
||||
if (stdoutHandle) await stdoutHandle.close()
|
||||
if (stderrHandle) await stderrHandle.close()
|
||||
if (tempDir) {
|
||||
await rm(tempDir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function execute(code: string) {
|
||||
return runQuickJsInSandbox(code)
|
||||
}
|
||||
|
||||
const payload = workerData as WorkerPayload | undefined
|
||||
|
||||
if (!payload?.code || typeof payload.code !== 'string') {
|
||||
parentPort.postMessage({ success: false, error: 'JavaScript code must be provided to the worker' })
|
||||
} else {
|
||||
execute(payload.code)
|
||||
.then((result) => {
|
||||
parentPort?.postMessage({ success: true, result })
|
||||
})
|
||||
.catch((error: any) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
parentPort?.postMessage({ success: false, error: errorMessage })
|
||||
})
|
||||
}
|
||||
@ -322,7 +322,8 @@ const builtInMcpDescriptionKeyMap: Record<BuiltinMCPServerName, string> = {
|
||||
[BuiltinMCPServerNames.fetch]: 'settings.mcp.builtinServersDescriptions.fetch',
|
||||
[BuiltinMCPServerNames.filesystem]: 'settings.mcp.builtinServersDescriptions.filesystem',
|
||||
[BuiltinMCPServerNames.difyKnowledge]: 'settings.mcp.builtinServersDescriptions.dify_knowledge',
|
||||
[BuiltinMCPServerNames.python]: 'settings.mcp.builtinServersDescriptions.python'
|
||||
[BuiltinMCPServerNames.python]: 'settings.mcp.builtinServersDescriptions.python',
|
||||
[BuiltinMCPServerNames.js]: 'settings.mcp.builtinServersDescriptions.js'
|
||||
} as const
|
||||
|
||||
export const getBuiltInMcpServerDescriptionLabel = (key: string): string => {
|
||||
|
||||
@ -3346,6 +3346,7 @@
|
||||
"dify_knowledge": "Dify's MCP server implementation provides a simple API to interact with Dify. Requires configuring the Dify Key",
|
||||
"fetch": "MCP server for retrieving URL web content",
|
||||
"filesystem": "A Node.js server implementing the Model Context Protocol (MCP) for file system operations. Requires configuration of directories allowed for access.",
|
||||
"js": "Execute JavaScript code in a secure sandbox environment. Supports most standard libraries.",
|
||||
"mcp_auto_install": "Automatically install MCP service (beta)",
|
||||
"memory": "Persistent memory implementation based on a local knowledge graph. This enables the model to remember user-related information across different conversations. Requires configuring the MEMORY_FILE_PATH environment variable.",
|
||||
"no": "No description",
|
||||
|
||||
@ -3346,6 +3346,7 @@
|
||||
"dify_knowledge": "Dify 的 MCP 服务器实现,提供了一个简单的 API 来与 Dify 进行交互。需要配置 Dify Key",
|
||||
"fetch": "用于获取 URL 网页内容的 MCP 服务器",
|
||||
"filesystem": "实现文件系统操作的模型上下文协议(MCP)的 Node.js 服务器。需要配置允许访问的目录",
|
||||
"js": "在安全的沙盒环境中执行 JavaScript 代码。使用 quickJs 运行 JavaScript,支持大多数标准库和流行的第三方库",
|
||||
"mcp_auto_install": "自动安装 MCP 服务(测试版)",
|
||||
"memory": "基于本地知识图谱的持久性记忆基础实现。这使得模型能够在不同对话间记住用户的相关信息。需要配置 MEMORY_FILE_PATH 环境变量。",
|
||||
"no": "无描述",
|
||||
|
||||
@ -535,7 +535,30 @@ const CollapsedContent: FC<{ isExpanded: boolean; resultString: string }> = ({ i
|
||||
}
|
||||
|
||||
const highlight = async () => {
|
||||
const result = await highlightCode(resultString, 'json')
|
||||
// 处理转义字符
|
||||
let processedString = resultString
|
||||
try {
|
||||
// 尝试解析字符串以处理可能的转义
|
||||
const parsed = JSON.parse(resultString)
|
||||
if (typeof parsed === 'string') {
|
||||
// 如果解析后是字符串,再次尝试解析(处理双重转义)
|
||||
try {
|
||||
const doubleParsed = JSON.parse(parsed)
|
||||
processedString = JSON.stringify(doubleParsed, null, 2)
|
||||
} catch {
|
||||
// 不是有效的 JSON,使用解析后的字符串
|
||||
processedString = parsed
|
||||
}
|
||||
} else {
|
||||
// 重新格式化 JSON
|
||||
processedString = JSON.stringify(parsed, null, 2)
|
||||
}
|
||||
} catch {
|
||||
// 解析失败,使用原始字符串
|
||||
processedString = resultString
|
||||
}
|
||||
|
||||
const result = await highlightCode(processedString, 'json')
|
||||
setStyledResult(result)
|
||||
}
|
||||
|
||||
|
||||
@ -144,6 +144,13 @@ export const builtinMCPServers: BuiltinMCPServer[] = [
|
||||
type: 'inMemory',
|
||||
isActive: false,
|
||||
provider: 'CherryAI'
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
name: BuiltinMCPServerNames.js,
|
||||
type: 'inMemory',
|
||||
isActive: false,
|
||||
provider: 'CherryAI'
|
||||
}
|
||||
] as const
|
||||
|
||||
|
||||
@ -833,7 +833,8 @@ export const BuiltinMCPServerNames = {
|
||||
fetch: '@cherry/fetch',
|
||||
filesystem: '@cherry/filesystem',
|
||||
difyKnowledge: '@cherry/dify-knowledge',
|
||||
python: '@cherry/python'
|
||||
python: '@cherry/python',
|
||||
js: '@cherry/js'
|
||||
} as const
|
||||
|
||||
export type BuiltinMCPServerName = (typeof BuiltinMCPServerNames)[keyof typeof BuiltinMCPServerNames]
|
||||
|
||||
102
yarn.lock
102
yarn.lock
@ -13440,6 +13440,7 @@ __metadata:
|
||||
unified: "npm:^11.0.5"
|
||||
uuid: "npm:^10.0.0"
|
||||
vite: "npm:rolldown-vite@latest"
|
||||
vite-plugin-static-copy: "npm:^3.1.3"
|
||||
vitest: "npm:^3.2.4"
|
||||
webdav: "npm:^5.8.0"
|
||||
winston: "npm:^3.17.0"
|
||||
@ -13804,6 +13805,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"anymatch@npm:~3.1.2":
|
||||
version: 3.1.3
|
||||
resolution: "anymatch@npm:3.1.3"
|
||||
dependencies:
|
||||
normalize-path: "npm:^3.0.0"
|
||||
picomatch: "npm:^2.0.4"
|
||||
checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"app-builder-bin@npm:5.0.0-alpha.12":
|
||||
version: 5.0.0-alpha.12
|
||||
resolution: "app-builder-bin@npm:5.0.0-alpha.12"
|
||||
@ -14203,7 +14214,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"binary-extensions@npm:^2.2.0":
|
||||
"binary-extensions@npm:^2.0.0, binary-extensions@npm:^2.2.0":
|
||||
version: 2.3.0
|
||||
resolution: "binary-extensions@npm:2.3.0"
|
||||
checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5
|
||||
@ -14316,7 +14327,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"braces@npm:^3.0.3":
|
||||
"braces@npm:^3.0.3, braces@npm:~3.0.2":
|
||||
version: 3.0.3
|
||||
resolution: "braces@npm:3.0.3"
|
||||
dependencies:
|
||||
@ -14838,6 +14849,25 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chokidar@npm:^3.6.0":
|
||||
version: 3.6.0
|
||||
resolution: "chokidar@npm:3.6.0"
|
||||
dependencies:
|
||||
anymatch: "npm:~3.1.2"
|
||||
braces: "npm:~3.0.2"
|
||||
fsevents: "npm:~2.3.2"
|
||||
glob-parent: "npm:~5.1.2"
|
||||
is-binary-path: "npm:~2.1.0"
|
||||
is-glob: "npm:~4.0.1"
|
||||
normalize-path: "npm:~3.0.0"
|
||||
readdirp: "npm:~3.6.0"
|
||||
dependenciesMeta:
|
||||
fsevents:
|
||||
optional: true
|
||||
checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chokidar@npm:^4.0.3":
|
||||
version: 4.0.3
|
||||
resolution: "chokidar@npm:4.0.3"
|
||||
@ -18301,6 +18331,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:^11.3.2":
|
||||
version: 11.3.2
|
||||
resolution: "fs-extra@npm:11.3.2"
|
||||
dependencies:
|
||||
graceful-fs: "npm:^4.2.0"
|
||||
jsonfile: "npm:^6.0.1"
|
||||
universalify: "npm:^2.0.0"
|
||||
checksum: 10c0/f5d629e1bb646d5dedb4d8b24c5aad3deb8cc1d5438979d6f237146cd10e113b49a949ae1b54212c2fbc98e2d0995f38009a9a1d0520f0287943335e65fe919b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fs-extra@npm:^8.1.0":
|
||||
version: 8.1.0
|
||||
resolution: "fs-extra@npm:8.1.0"
|
||||
@ -18359,7 +18400,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fsevents@npm:~2.3.3":
|
||||
"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3":
|
||||
version: 2.3.3
|
||||
resolution: "fsevents@npm:2.3.3"
|
||||
dependencies:
|
||||
@ -18378,7 +18419,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>":
|
||||
"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin<compat/fsevents>, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin<compat/fsevents>":
|
||||
version: 2.3.3
|
||||
resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin<compat/fsevents>::version=2.3.3&hash=df0bf1"
|
||||
dependencies:
|
||||
@ -18543,7 +18584,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob-parent@npm:^5.1.2":
|
||||
"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2":
|
||||
version: 5.1.2
|
||||
resolution: "glob-parent@npm:5.1.2"
|
||||
dependencies:
|
||||
@ -19501,6 +19542,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-binary-path@npm:~2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "is-binary-path@npm:2.1.0"
|
||||
dependencies:
|
||||
binary-extensions: "npm:^2.0.0"
|
||||
checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-buffer@npm:^2.0.0":
|
||||
version: 2.0.5
|
||||
resolution: "is-buffer@npm:2.0.5"
|
||||
@ -19588,7 +19638,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3":
|
||||
"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1":
|
||||
version: 4.0.3
|
||||
resolution: "is-glob@npm:4.0.3"
|
||||
dependencies:
|
||||
@ -22766,7 +22816,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"normalize-path@npm:^3.0.0":
|
||||
"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "normalize-path@npm:3.0.0"
|
||||
checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046
|
||||
@ -23252,7 +23302,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-map@npm:^7.0.2":
|
||||
"p-map@npm:^7.0.2, p-map@npm:^7.0.3":
|
||||
version: 7.0.3
|
||||
resolution: "p-map@npm:7.0.3"
|
||||
checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c
|
||||
@ -23619,7 +23669,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"picomatch@npm:^2.3.1":
|
||||
"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "picomatch@npm:2.3.1"
|
||||
checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be
|
||||
@ -25219,6 +25269,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"readdirp@npm:~3.6.0":
|
||||
version: 3.6.0
|
||||
resolution: "readdirp@npm:3.6.0"
|
||||
dependencies:
|
||||
picomatch: "npm:^2.2.1"
|
||||
checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"redent@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "redent@npm:3.0.0"
|
||||
@ -27313,6 +27372,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinyglobby@npm:^0.2.15":
|
||||
version: 0.2.15
|
||||
resolution: "tinyglobby@npm:0.2.15"
|
||||
dependencies:
|
||||
fdir: "npm:^6.5.0"
|
||||
picomatch: "npm:^4.0.3"
|
||||
checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinypool@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "tinypool@npm:1.1.1"
|
||||
@ -28333,6 +28402,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-plugin-static-copy@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "vite-plugin-static-copy@npm:3.1.3"
|
||||
dependencies:
|
||||
chokidar: "npm:^3.6.0"
|
||||
fs-extra: "npm:^11.3.2"
|
||||
p-map: "npm:^7.0.3"
|
||||
picocolors: "npm:^1.1.1"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
peerDependencies:
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
checksum: 10c0/f58bf609246c440b4e3c0db10abf5965658c34ee03e72b94d4fc6ff35fa4568b5baa0fe36057234a4b1e84a9b4b3c2cdbff9f943b9e69d883d3a05353cbf9090
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:rolldown-vite@latest":
|
||||
version: 7.1.5
|
||||
resolution: "rolldown-vite@npm:7.1.5"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user