From 76d272b378aa837093185a0439d325ab7b913d3a Mon Sep 17 00:00:00 2001 From: tianmo Date: Sat, 5 Sep 2026 22:21:52 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=99=A8=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 1 + connector/spine-cli.mjs | 12 ++++- 停止Spine连接器-Windows.bat | 27 +++++++---- 启动Spine连接器-Windows.bat | 90 ++++++++++++++++++++++++++++++++----- 4 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..69b47b5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.bat text eol=crlf diff --git a/connector/spine-cli.mjs b/connector/spine-cli.mjs index 6625a39..5796563 100644 --- a/connector/spine-cli.mjs +++ b/connector/spine-cli.mjs @@ -1,7 +1,7 @@ import { access, stat } from 'node:fs/promises' import { constants } from 'node:fs' import { homedir, platform } from 'node:os' -import { join } from 'node:path' +import { delimiter, join } from 'node:path' import { execFile } from 'node:child_process' import { createSpine38ImportOptions } from './spine38-project.mjs' @@ -28,7 +28,15 @@ export async function findSpineExecutable() { } else if (platform() === 'win32') { const programFiles = [process.env.ProgramFiles, process.env['ProgramFiles(x86)'], process.env.LOCALAPPDATA].filter(Boolean) for (const root of programFiles) { - candidates.push(join(root, 'Spine', 'Spine.com'), join(root, 'Esoteric Software', 'Spine', 'Spine.com')) + candidates.push( + join(root, 'Spine', 'Spine.com'), + join(root, 'Esoteric Software', 'Spine', 'Spine.com'), + join(root, 'Programs', 'Spine', 'Spine.com'), + ) + } + // 支持用户将 Spine 安装在自定义目录并把该目录加入 Windows PATH。 + for (const root of String(process.env.Path || process.env.PATH || '').split(delimiter).filter(Boolean)) { + candidates.push(join(root.replace(/^"|"$/g, ''), 'Spine.com')) } } else { candidates.push('/opt/Spine/Spine.sh', join(homedir(), 'Spine/Spine.sh'), '/usr/local/bin/Spine', '/usr/bin/Spine') diff --git a/停止Spine连接器-Windows.bat b/停止Spine连接器-Windows.bat index 982aafc..f6cd0ec 100644 --- a/停止Spine连接器-Windows.bat +++ b/停止Spine连接器-Windows.bat @@ -4,6 +4,7 @@ setlocal set "PROJECT_DIR=%~dp0" set "PID_FILE=%PROJECT_DIR%.run\spine-connector-windows.pid" +set "CONNECTOR_ENTRY=%PROJECT_DIR%connector\spine-connector.mjs" set "CONNECTOR_PID=" if exist "%PID_FILE%" set /p CONNECTOR_PID=<"%PID_FILE%" @@ -11,17 +12,27 @@ powershell -NoProfile -Command ^ "$pidValue=0; [void][int]::TryParse($env:CONNECTOR_PID,[ref]$pidValue);" ^ "$process=if($pidValue -gt 0){Get-CimInstance Win32_Process -Filter ('ProcessId = ' + $pidValue) -ErrorAction SilentlyContinue};" ^ "if($null -eq $process){$listener=Get-NetTCPConnection -LocalPort 27843 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1; if($listener){$process=Get-CimInstance Win32_Process -Filter ('ProcessId = ' + $listener.OwningProcess) -ErrorAction SilentlyContinue}};" ^ - "if ($null -eq $process) { exit 2 };" ^ - "$expected=(Join-Path $env:PROJECT_DIR 'connector\spine-connector.mjs');" ^ - "if (($process.CommandLine -notlike ('*' + $expected + '*')) -and ($process.CommandLine -notlike '*connector/spine-connector.mjs*') -and ($process.CommandLine -notlike '*connector\spine-connector.mjs*')) { exit 3 };" ^ - "Stop-Process -Id $process.ProcessId -Force" + "if($null -eq $process){exit 2};" ^ + "$expected=$env:CONNECTOR_ENTRY;" ^ + "if((-not $process.CommandLine) -or $process.CommandLine.IndexOf($expected,[StringComparison]::OrdinalIgnoreCase) -lt 0){exit 3};" ^ + "Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop" set "STOP_RESULT=%ERRORLEVEL%" -del /q "%PID_FILE%" >nul 2>&1 +if "%STOP_RESULT%"=="0" ( + del /q "%PID_FILE%" >nul 2>&1 + echo Spine粒子连接器已停止。 + exit /b 0 +) +if "%STOP_RESULT%"=="2" ( + del /q "%PID_FILE%" >nul 2>&1 + echo Spine粒子连接器已经停止。 + exit /b 0 +) if "%STOP_RESULT%"=="3" ( - echo 27843 端口对应的进程不是 Spine粒子连接器,为避免误关,本次未执行停止。 + echo 27843 端口对应的进程不属于当前项目,为避免误关,本次未执行停止。 pause exit /b 1 ) -echo Spine粒子连接器已停止。 -exit /b 0 +echo 停止 Spine粒子连接器失败,请检查 PowerShell 权限或手动结束对应进程。 +pause +exit /b 1 diff --git a/启动Spine连接器-Windows.bat b/启动Spine连接器-Windows.bat index d3e2da8..67de532 100644 --- a/启动Spine连接器-Windows.bat +++ b/启动Spine连接器-Windows.bat @@ -7,18 +7,59 @@ set "RUN_DIR=%PROJECT_DIR%.run" set "PID_FILE=%RUN_DIR%\spine-connector-windows.pid" set "OUT_LOG=%RUN_DIR%\spine-connector-windows.log" set "ERROR_LOG=%RUN_DIR%\spine-connector-windows-error.log" +set "HEALTH_URL=http://127.0.0.1:27843/v1/health" +set "CONNECTOR_ENTRY=%PROJECT_DIR%connector\spine-connector.mjs" if not exist "%RUN_DIR%" mkdir "%RUN_DIR%" +if not exist "%CONNECTOR_ENTRY%" ( + echo 未找到连接器入口:%CONNECTOR_ENTRY% + pause + exit /b 1 +) cd /d "%PROJECT_DIR%" -if exist "%PID_FILE%" ( - set /p CONNECTOR_PID=<"%PID_FILE%" - powershell -NoProfile -Command "if (Get-Process -Id $env:CONNECTOR_PID -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }" +set "CONNECTOR_PID=" +if exist "%PID_FILE%" set /p CONNECTOR_PID=<"%PID_FILE%" +if defined CONNECTOR_PID ( + powershell -NoProfile -Command ^ + "$pidValue=0; [void][int]::TryParse($env:CONNECTOR_PID,[ref]$pidValue);" ^ + "$process=if($pidValue -gt 0){Get-CimInstance Win32_Process -Filter ('ProcessId = ' + $pidValue) -ErrorAction SilentlyContinue};" ^ + "$expected=$env:CONNECTOR_ENTRY;" ^ + "if($process -and $process.CommandLine -and $process.CommandLine.IndexOf($expected,[StringComparison]::OrdinalIgnoreCase) -ge 0){exit 0}else{exit 1}" + if not errorlevel 1 ( + powershell -NoProfile -Command "try{$response=Invoke-WebRequest -UseBasicParsing -Uri $env:HEALTH_URL -TimeoutSec 2; if($response.StatusCode -eq 200){exit 0}}catch{}; exit 1" + if not errorlevel 1 ( + echo Spine粒子连接器已经在运行。 + exit /b 0 + ) + powershell -NoProfile -Command "Stop-Process -Id $env:CONNECTOR_PID -Force -ErrorAction SilentlyContinue" + ) + del /q "%PID_FILE%" >nul 2>&1 +) + +rem PID 文件可能因异常退出或脚本升级而缺失,启动前检查固定端口的实际占用者。 +powershell -NoProfile -Command ^ + "$listener=Get-NetTCPConnection -LocalPort 27843 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1;" ^ + "if(-not $listener){exit 2};" ^ + "$process=Get-CimInstance Win32_Process -Filter ('ProcessId = ' + $listener.OwningProcess) -ErrorAction SilentlyContinue;" ^ + "$expected=$env:CONNECTOR_ENTRY;" ^ + "if($process -and $process.CommandLine -and $process.CommandLine.IndexOf($expected,[StringComparison]::OrdinalIgnoreCase) -ge 0){Set-Content -Path $env:PID_FILE -Value $process.ProcessId -Encoding ascii; exit 0};" ^ + "exit 3" +set "PORT_RESULT=%ERRORLEVEL%" +if "%PORT_RESULT%"=="0" ( + powershell -NoProfile -Command "try{$response=Invoke-WebRequest -UseBasicParsing -Uri $env:HEALTH_URL -TimeoutSec 2; if($response.StatusCode -eq 200){exit 0}}catch{}; exit 1" if not errorlevel 1 ( echo Spine粒子连接器已经在运行。 exit /b 0 ) - del /q "%PID_FILE%" >nul 2>&1 + echo 已发现本项目的连接器进程,但健康检查失败。请先运行停止脚本后再试。 + pause + exit /b 1 +) +if "%PORT_RESULT%"=="3" ( + echo 27843 端口已被其他程序占用,无法启动 Spine粒子连接器。 + pause + exit /b 1 ) where node >nul 2>&1 @@ -30,16 +71,45 @@ if errorlevel 1 ( echo 正在启动 Spine粒子连接器... powershell -NoProfile -Command ^ - "$node=(Get-Command node).Source; $entry=Join-Path $env:PROJECT_DIR 'connector\spine-connector.mjs';" ^ - "$process=Start-Process -FilePath $node -ArgumentList @($entry) -WorkingDirectory $env:PROJECT_DIR -RedirectStandardOutput $env:OUT_LOG -RedirectStandardError $env:ERROR_LOG -WindowStyle Hidden -PassThru;" ^ + "$node=(Get-Command node -ErrorAction Stop).Source;" ^ + "$quotedEntry=[char]34 + $env:CONNECTOR_ENTRY + [char]34;" ^ + "$process=Start-Process -FilePath $node -ArgumentList $quotedEntry -WorkingDirectory $env:PROJECT_DIR -RedirectStandardOutput $env:OUT_LOG -RedirectStandardError $env:ERROR_LOG -WindowStyle Hidden -PassThru;" ^ "Set-Content -Path $env:PID_FILE -Value $process.Id -Encoding ascii" if errorlevel 1 ( - echo 连接器启动失败。 + echo 连接器进程启动失败。 pause exit /b 1 ) -timeout /t 2 /nobreak >nul -echo Spine粒子连接器已启动,可以回到网页直接导出 .spine。 -exit /b 0 +set "CONNECTOR_PID=" +if exist "%PID_FILE%" set /p CONNECTOR_PID=<"%PID_FILE%" +powershell -NoProfile -Command ^ + "$deadline=(Get-Date).AddSeconds(10);" ^ + "do{" ^ + "$process=Get-Process -Id $env:CONNECTOR_PID -ErrorAction SilentlyContinue; if(-not $process){exit 2};" ^ + "try{$response=Invoke-WebRequest -UseBasicParsing -Uri $env:HEALTH_URL -TimeoutSec 1; if($response.StatusCode -eq 200){exit 0}}catch{};" ^ + "Start-Sleep -Milliseconds 250" ^ + "}while((Get-Date) -lt $deadline); exit 1" + +set "START_RESULT=%ERRORLEVEL%" +if "%START_RESULT%"=="0" ( + echo Spine粒子连接器启动成功,可以回到网页直接导出 .spine。 + exit /b 0 +) + +echo 连接器未能通过健康检查。 +if exist "%ERROR_LOG%" ( + echo. + echo 错误日志: + powershell -NoProfile -Command "Get-Content -Path $env:ERROR_LOG -Tail 30 -ErrorAction SilentlyContinue" +) +if exist "%OUT_LOG%" ( + echo. + echo 运行日志: + powershell -NoProfile -Command "Get-Content -Path $env:OUT_LOG -Tail 30 -ErrorAction SilentlyContinue" +) +powershell -NoProfile -Command "Stop-Process -Id $env:CONNECTOR_PID -Force -ErrorAction SilentlyContinue" +del /q "%PID_FILE%" >nul 2>&1 +pause +exit /b 1