From 9109b8cfe2f46bc6f4f80cdeec8d1621d06e186d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Tue, 6 Oct 2026 17:51:21 +0800 Subject: [PATCH] fix: repair Windows release dependencies and loader paths Bundle required crypto and SSL DLLs and gate releases on a Windows native-load smoke test. Resolve loader paths independently of the working directory and encode file URLs correctly. Include the quick-login launcher built from Windows-Boot 0ff70ce10e34ac26411f9e6bcedaedfacc1e74f0. Validated builds, five path regressions, and live Shell and Windows Node startup to QR login. --- .github/workflows/release-publish.yml | 29 +++++- packages/napcat-develop/napcat.bat | 6 +- packages/napcat-framework/napcat.cjs | 3 +- packages/napcat-framework/nativeLoader.cjs | 3 +- .../napcat-napi-loader/napiLoader-debug.bat | 10 +- packages/napcat-napi-loader/napiLoader.bat | 10 +- .../napcat-shell-loader/NapCatWinBootMain.exe | Bin 35328 -> 35328 bytes .../napcat-shell-loader/launcher-user.bat | 15 ++- .../launcher-win10-user.bat | 15 ++- .../napcat-shell-loader/launcher-win10.bat | 15 ++- packages/napcat-shell-loader/launcher.bat | 15 ++- packages/napcat-shell-loader/loadNapCat.js | 5 +- packages/napcat-test/loaderPaths.test.ts | 96 ++++++++++++++++++ 13 files changed, 174 insertions(+), 48 deletions(-) create mode 100644 packages/napcat-test/loaderPaths.test.ts diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 42b2ae65..cbb72dd7 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -153,9 +153,14 @@ jobs: # ----------------------------- # 5) 拷贝 QQ 文件到 NapCat.Shell.Windows.Node # ----------------------------- - QQ_TARGETS=("avif_convert.dll" "broadcast_ipc.dll" "config.json" "libglib-2.0-0.dll" "libgobject-2.0-0.dll" "libvips-42.dll" "ncnn.dll" "opencv.dll" "package.json" "QBar.dll" "wrapper.node" "LightQuic.dll") + QQ_TARGETS=("avif_convert.dll" "broadcast_ipc.dll" "config.json" "crypto.dll" "ssl.dll" "libglib-2.0-0.dll" "libgobject-2.0-0.dll" "libvips-42.dll" "ncnn.dll" "opencv.dll" "package.json" "QBar.dll" "wrapper.node" "LightQuic.dll") for name in "${QQ_TARGETS[@]}"; do - find "$QQ_EXTRACT" -iname "$name" -exec cp -a {} "$OUT_DIR" \; || true + source_file=$(find "$QQ_EXTRACT" -type f -iname "$name" -print -quit) + if [ -z "$source_file" ]; then + echo "Missing required QQ file: $name" >&2 + exit 1 + fi + cp -a "$source_file" "$OUT_DIR/" done # ----------------------------- @@ -183,8 +188,26 @@ jobs: name: NapCat.Shell.Windows.Node path: NapCat.Shell.Windows.Node + validate-windows-node: + needs: assemble-windows-node + runs-on: windows-latest + steps: + - uses: actions/download-artifact@v7 + with: + name: NapCat.Shell.Windows.Node + path: windows-node + + - name: Verify bundled Node and QQ native dependencies + shell: pwsh + working-directory: windows-node + run: | + & .\node.exe --check .\index.js + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & .\node.exe -e "const wrapper = require('./wrapper.node'); if (!wrapper.NodeQQNTWrapperUtil) throw new Error('Missing NodeQQNTWrapperUtil'); console.log('wrapper.node native-load PASS'); process.exit(0);" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + release-napcat: - needs: [build, assemble-windows-node] + needs: [build, assemble-windows-node, validate-windows-node] runs-on: ubuntu-latest steps: - name: Checkout diff --git a/packages/napcat-develop/napcat.bat b/packages/napcat-develop/napcat.bat index 0c15cd10..8215fa44 100644 --- a/packages/napcat-develop/napcat.bat +++ b/packages/napcat-develop/napcat.bat @@ -1 +1,5 @@ -node.exe ./index.js \ No newline at end of file +@echo off +setlocal +cd /d "%~dp0" || exit /b 1 +"%~dp0node.exe" "%~dp0index.js" %* +exit /b %errorlevel% diff --git a/packages/napcat-framework/napcat.cjs b/packages/napcat-framework/napcat.cjs index 7ead94ba..dbfdaba7 100644 --- a/packages/napcat-framework/napcat.cjs +++ b/packages/napcat-framework/napcat.cjs @@ -1,6 +1,7 @@ const process = require('process'); const os = require('os'); const path = require('path'); +const { pathToFileURL } = require('url'); // 保存原始dlopen const dlopenOrig = process.dlopen; @@ -50,7 +51,7 @@ async function initializeNapCat () { console.log('[NapCat] [Info] 开始初始化NapCat'); try { const currentPath = path.dirname(__filename); - const { NCoreInitFramework, getWebUiUrl } = await import('file://' + path.join(currentPath, './napcat.mjs')); + const { NCoreInitFramework, getWebUiUrl } = await import(pathToFileURL(path.join(currentPath, './napcat.mjs')).href); getWebUiUrlFunc = getWebUiUrl; await NCoreInitFramework(wrapperSession, wrapperLoginService, (callback) => { ncCallback = callback; }); } catch (error) { diff --git a/packages/napcat-framework/nativeLoader.cjs b/packages/napcat-framework/nativeLoader.cjs index b644cd0d..56a91479 100644 --- a/packages/napcat-framework/nativeLoader.cjs +++ b/packages/napcat-framework/nativeLoader.cjs @@ -1,5 +1,6 @@ // const fs = require('fs'); const path = require('path'); +const { pathToFileURL } = require('url'); async function initializeNapCat (session, loginService, registerCallback) { // const logFile = path.join(currentPath, 'napcat.log'); @@ -12,7 +13,7 @@ async function initializeNapCat (session, loginService, registerCallback) { try { const currentPath = path.dirname(__filename); - const { NCoreInitFramework } = await import('file://' + path.join(currentPath, './napcat.mjs')); + const { NCoreInitFramework } = await import(pathToFileURL(path.join(currentPath, './napcat.mjs')).href); await NCoreInitFramework(session, loginService, (callback) => { registerCallback(callback); }); } catch (error) { console.log('[NapCat] [Error] 初始化NapCat', error); diff --git a/packages/napcat-napi-loader/napiLoader-debug.bat b/packages/napcat-napi-loader/napiLoader-debug.bat index 89f66bd3..39d5cdf8 100644 --- a/packages/napcat-napi-loader/napiLoader-debug.bat +++ b/packages/napcat-napi-loader/napiLoader-debug.bat @@ -1,8 +1,10 @@ @echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" || exit /b 1 chcp 65001 -set NAPCAT_INJECT_PATH=%cd%\napiloader.dll -set NAPCAT_LAUNCHER_PATH=%cd%\napimain.exe -set NAPCAT_MAIN_PATH=%cd%\nativeLoader.cjs +set "NAPCAT_INJECT_PATH=%cd%\napiloader.dll" +set "NAPCAT_LAUNCHER_PATH=%cd%\napimain.exe" +set "NAPCAT_MAIN_PATH=%cd%\nativeLoader.cjs" set NAPCAT_DEBUG_CONSOLE=1 :loop_read for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do ( @@ -27,4 +29,4 @@ set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/% "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%" -pause \ No newline at end of file +pause diff --git a/packages/napcat-napi-loader/napiLoader.bat b/packages/napcat-napi-loader/napiLoader.bat index 7fecb175..bcb97c80 100644 --- a/packages/napcat-napi-loader/napiLoader.bat +++ b/packages/napcat-napi-loader/napiLoader.bat @@ -1,8 +1,10 @@ @echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" || exit /b 1 chcp 65001 -set NAPCAT_INJECT_PATH=%cd%\napiloader.dll -set NAPCAT_LAUNCHER_PATH=%cd%\napimain.exe -set NAPCAT_MAIN_PATH=%cd%\nativeLoader.cjs +set "NAPCAT_INJECT_PATH=%cd%\napiloader.dll" +set "NAPCAT_LAUNCHER_PATH=%cd%\napimain.exe" +set "NAPCAT_MAIN_PATH=%cd%\nativeLoader.cjs" :loop_read for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do ( set "RetString=%%~b" @@ -24,4 +26,4 @@ if not exist "%QQpath%" ( set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/% -start "" "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%" \ No newline at end of file +start "" "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%" diff --git a/packages/napcat-shell-loader/NapCatWinBootMain.exe b/packages/napcat-shell-loader/NapCatWinBootMain.exe index 99a69b9e4663ffeff4325b9d88be4b555184b42a..4e930e16917f19ef37f9b2196011d39682a3e12d 100644 GIT binary patch literal 35328 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 diff --git a/packages/napcat-shell-loader/launcher-user.bat b/packages/napcat-shell-loader/launcher-user.bat index f6ed9d22..fbf28945 100644 --- a/packages/napcat-shell-loader/launcher-user.bat +++ b/packages/napcat-shell-loader/launcher-user.bat @@ -1,10 +1,12 @@ @echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" || exit /b 1 chcp 65001 >nul -set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json -set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js -set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll -set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe -set NAPCAT_MAIN_PATH=%cd%\napcat.mjs +set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json" +set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js" +set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll" +set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe" +set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs" :loop_read for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do ( set "RetString=%%~b" @@ -24,9 +26,6 @@ if not exist "%QQpath%" ( exit /b ) -set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/% -echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%" - "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %* pause diff --git a/packages/napcat-shell-loader/launcher-win10-user.bat b/packages/napcat-shell-loader/launcher-win10-user.bat index 6c615d12..abfad977 100644 --- a/packages/napcat-shell-loader/launcher-win10-user.bat +++ b/packages/napcat-shell-loader/launcher-win10-user.bat @@ -1,10 +1,12 @@ @echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" || exit /b 1 chcp 65001 >nul -set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json -set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js -set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll -set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe -set NAPCAT_MAIN_PATH=%cd%\napcat.mjs +set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json" +set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js" +set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll" +set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe" +set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs" :loop_read for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do ( set "RetString=%%~b" @@ -23,9 +25,6 @@ if not exist "%QQpath%" ( pause exit /b ) -set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/% -echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%" - "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %* REM Optional: -q for quick login, omit for QR code login diff --git a/packages/napcat-shell-loader/launcher-win10.bat b/packages/napcat-shell-loader/launcher-win10.bat index 271c6112..a56fcdb7 100644 --- a/packages/napcat-shell-loader/launcher-win10.bat +++ b/packages/napcat-shell-loader/launcher-win10.bat @@ -1,4 +1,6 @@ @echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" || exit /b 1 chcp 65001 >nul net session >nul 2>&1 if %ERRORLEVEL% == 0 ( @@ -9,11 +11,11 @@ if %ERRORLEVEL% == 0 ( exit ) -set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json -set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js -set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll -set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe -set NAPCAT_MAIN_PATH=%cd%\napcat.mjs +set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json" +set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js" +set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll" +set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe" +set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs" :loop_read for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do ( set "RetString=%%~b" @@ -32,9 +34,6 @@ if not exist "%QQPath%" ( pause exit /b ) -set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/% -echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%" - "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %* REM Optional: -q for quick login, omit for QR code login diff --git a/packages/napcat-shell-loader/launcher.bat b/packages/napcat-shell-loader/launcher.bat index be9dda08..17e30db8 100644 --- a/packages/napcat-shell-loader/launcher.bat +++ b/packages/napcat-shell-loader/launcher.bat @@ -1,4 +1,6 @@ @echo off +setlocal DisableDelayedExpansion +cd /d "%~dp0" || exit /b 1 chcp 65001 >nul net session >nul 2>&1 if %ERRORLEVEL% == 0 ( @@ -9,11 +11,11 @@ if %ERRORLEVEL% == 0 ( exit ) -set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json -set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js -set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll -set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe -set NAPCAT_MAIN_PATH=%cd%\napcat.mjs +set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json" +set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js" +set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll" +set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe" +set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs" :loop_read for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do ( set "RetString=%%~b" @@ -33,7 +35,4 @@ if not exist "%QQPath%" ( exit /b ) -set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/% -echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%" - "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %* diff --git a/packages/napcat-shell-loader/loadNapCat.js b/packages/napcat-shell-loader/loadNapCat.js index 25e1ed19..f3ee9d25 100644 --- a/packages/napcat-shell-loader/loadNapCat.js +++ b/packages/napcat-shell-loader/loadNapCat.js @@ -1,5 +1,6 @@ const path = require('path'); -const CurrentPath = path.dirname(__filename); +const { pathToFileURL } = require('url'); +const mainPath = process.env.NAPCAT_MAIN_PATH || path.join(__dirname, 'napcat.mjs'); (async () => { - await import('file://' + path.join(CurrentPath, './napcat/napcat.mjs')); + await import(pathToFileURL(mainPath).href); })(); diff --git a/packages/napcat-test/loaderPaths.test.ts b/packages/napcat-test/loaderPaths.test.ts new file mode 100644 index 00000000..051fdbbd --- /dev/null +++ b/packages/napcat-test/loaderPaths.test.ts @@ -0,0 +1,96 @@ +import { afterEach, describe, expect, test } from 'vitest'; +import { copyFileSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const packageRoot = fileURLToPath(new URL('../', import.meta.url)); +const fixtureRoots: string[] = []; + +function createFixture () { + const fixtureRoot = mkdtempSync(path.join(tmpdir(), 'napcat-loader-')); + fixtureRoots.push(fixtureRoot); + const installDir = path.join(fixtureRoot, '中文 space # percent%'); + mkdirSync(installDir); + return { fixtureRoot, installDir }; +} + +afterEach(() => { + for (const fixtureRoot of fixtureRoots.splice(0)) { + rmSync(fixtureRoot, { recursive: true, force: true }); + } +}); + +describe('loader paths', () => { + test('Shell loads the flat package from an unrelated working directory', () => { + const { fixtureRoot, installDir } = createFixture(); + const loaderPath = path.join(installDir, 'loadNapCat.cjs'); + copyFileSync(path.join(packageRoot, 'napcat-shell-loader/loadNapCat.js'), loaderPath); + writeFileSync(path.join(installDir, 'napcat.mjs'), 'console.log("SHELL_LOADED");'); + const environment = { ...process.env }; + delete environment['NAPCAT_MAIN_PATH']; + const output = execFileSync(process.execPath, [loaderPath], { cwd: fixtureRoot, env: environment, encoding: 'utf8' }); + expect(output).toContain('SHELL_LOADED'); + }); + + test('Shell uses the actual entry path when the loader is redirected into QQ', () => { + const { fixtureRoot, installDir } = createFixture(); + const loaderPath = path.join(fixtureRoot, 'loadNapCat.cjs'); + const entryPath = path.join(installDir, 'napcat.mjs'); + copyFileSync(path.join(packageRoot, 'napcat-shell-loader/loadNapCat.js'), loaderPath); + writeFileSync(entryPath, 'console.log("REDIRECTED_SHELL_LOADED");'); + const output = execFileSync(process.execPath, [loaderPath], { + cwd: fixtureRoot, + env: { ...process.env, NAPCAT_MAIN_PATH: entryPath }, + encoding: 'utf8', + }); + expect(output).toContain('REDIRECTED_SHELL_LOADED'); + }); + + test('Framework native loader preserves URL-sensitive path characters', () => { + const { fixtureRoot, installDir } = createFixture(); + const loaderPath = path.join(installDir, 'nativeLoader.cjs'); + copyFileSync(path.join(packageRoot, 'napcat-framework/nativeLoader.cjs'), loaderPath); + writeFileSync(path.join(installDir, 'napcat.mjs'), 'export async function NCoreInitFramework () { console.log("FRAMEWORK_LOADED"); }'); + const output = execFileSync(process.execPath, ['-e', 'require(process.argv[1]).initializeNapCat({}, {}, () => {});', loaderPath], { + cwd: fixtureRoot, + encoding: 'utf8', + }); + expect(output).toContain('FRAMEWORK_LOADED'); + expect(output).not.toContain('[Error]'); + }); + + test('Framework dlopen loader preserves URL-sensitive path characters', () => { + const { fixtureRoot, installDir } = createFixture(); + const loaderPath = path.join(installDir, 'napcat.cjs'); + copyFileSync(path.join(packageRoot, 'napcat-framework/napcat.cjs'), loaderPath); + writeFileSync(path.join(installDir, 'napcat.mjs'), 'export async function NCoreInitFramework () { console.log("DLOPEN_FRAMEWORK_LOADED"); } export function getWebUiUrl () { return ""; }'); + const script = 'process.dlopen = (module) => { module.exports = { NodeIKernelLoginService: { get: () => ({}) }, NodeIQQNTWrapperSession: { create: () => ({}) } }; }; require(process.argv[1]); process.dlopen({ exports: {} }, "wrapper.node");'; + const output = execFileSync(process.execPath, ['-e', script, loaderPath], { cwd: fixtureRoot, encoding: 'utf8' }); + expect(output).toContain('DLOPEN_FRAMEWORK_LOADED'); + expect(output).not.toContain('[Error]'); + }); + + test.skipIf(process.platform !== 'win32')('Node batch uses the bundled runtime, forwards arguments and returns the exit code', () => { + const { fixtureRoot, installDir } = createFixture(); + copyFileSync(process.execPath, path.join(installDir, 'node.exe')); + copyFileSync(path.join(packageRoot, 'napcat-develop/napcat.bat'), path.join(installDir, 'napcat.bat')); + writeFileSync(path.join(installDir, 'index.js'), 'console.log(JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2) })); process.exitCode = 7;'); + let exitStatus: number | null = null; + let output = ''; + try { + output = execFileSync('cmd.exe', ['/d', '/s', '/c', `""${path.join(installDir, 'napcat.bat')}" -q 123456"`], { + cwd: fixtureRoot, + encoding: 'utf8', + windowsVerbatimArguments: true, + }); + } catch (error) { + const result = error as { status: number; stdout: string }; + exitStatus = result.status; + output = result.stdout; + } + expect(exitStatus).toBe(7); + expect(JSON.parse(output)).toEqual({ cwd: installDir, args: ['-q', '123456'] }); + }); +});