openvibers/source.openvibe.games Commits

sh files

CO Copilot committed 29 Jun 2026 · 07b1daf6ca · 3 files, +502 −0


Changes

ddiff --git a/tools/fix-windows-python-shim-for-msbuild-and-rerun.sh b/tools/fix-windows-python-shim-for-msbuild-and-rerun.sh
nnew file mode 100755
iindex 0000000..ac47e9a
---- /dev/null
++++ b/tools/fix-windows-python-shim-for-msbuild-and-rerun.sh
@@@ -0,0 +1,168 @@
++#!/usr/bin/env bash
++set -euo pipefail
++
++ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
++cd "$ROOT"
++BRANCH="$(git rev-parse --abbrev-ref HEAD)"
++WORKFLOW="windows-source-sdk-dlls.yml"
++
++if [[ -x tools/openvibe-gh-repo.sh ]]; then
++ # shellcheck disable=SC1091
++ REPO="$(tools/openvibe-gh-repo.sh)"
++else
++ REPO="$(git remote get-url origin | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
++fi
++REPO="${REPO%.git}"
++
++echo "[openvibe] fix Windows MSBuild Python shim + rerun"
++echo "[openvibe] root=$ROOT"
++echo "[openvibe] branch=$BRANCH"
++echo "[openvibe] repo=$REPO"
++
++# Keep setup-python in the workflow. This is still useful, but vcvars/msbuild can lose PATH,
++# so the PowerShell build script will also create its own python.bat shim.
++python3 - <<'PY'
++from pathlib import Path
++p = Path('.github/workflows/windows-source-sdk-dlls.yml')
++if p.exists():
++ s = p.read_text()
++ if 'actions/setup-python@v5' not in s:
++ marker = ' - name: Setup MSBuild\n'
++ block = """ - name: Setup Python\n uses: actions/setup-python@v5\n with:\n python-version: '3.x'\n\n"""
++ if marker in s:
++ s = s.replace(marker, block + marker, 1)
++ else:
++ s += "\n" + block
++ p.write_text(s)
++PY
++
++python3 - <<'PY'
++from pathlib import Path
++p = Path('tools/build-sdk-windows.ps1')
++s = p.read_text()
++marker = 'OPENVIBE_PYTHON_SHIM_FOR_MSBUILD'
++func = r'''
++# OPENVIBE_PYTHON_SHIM_FOR_MSBUILD
++function Ensure-PythonCommandForMSBuild {
++ $shimDir = Join-Path $Root "artifacts/python-shim"
++ New-Item -ItemType Directory -Force -Path $shimDir | Out-Null
++ $log = Join-Path $LogDir "python-version.txt"
++ "=== Ensure-PythonCommandForMSBuild ===" | Out-File $log
++ "initial PATH=$env:PATH" | Out-File $log -Append
++ "pythonLocation=$env:pythonLocation" | Out-File $log -Append
++ "Python_ROOT_DIR=$env:Python_ROOT_DIR" | Out-File $log -Append
++
++ $candidates = New-Object System.Collections.Generic.List[string]
++ foreach ($base in @($env:pythonLocation, $env:Python_ROOT_DIR, $env:Python3_ROOT_DIR)) {
++ if ($base) {
++ [void]$candidates.Add((Join-Path $base "python.exe"))
++ [void]$candidates.Add((Join-Path $base "python3.exe"))
++ }
++ }
++
++ foreach ($name in @("python.exe", "python3.exe", "py.exe")) {
++ $cmd = Get-Command $name -ErrorAction SilentlyContinue
++ if ($cmd -and $cmd.Source) { [void]$candidates.Add($cmd.Source) }
++ }
++
++ foreach ($rootCandidate in @("C:\hostedtoolcache\windows\Python", "C:\hostedtoolcache\windows\PyPy")) {
++ if (Test-Path $rootCandidate) {
++ Get-ChildItem -Path $rootCandidate -Recurse -File -Filter "python.exe" -ErrorAction SilentlyContinue |
++ Sort-Object LastWriteTime -Descending |
++ ForEach-Object { [void]$candidates.Add($_.FullName) }
++ }
++ }
++
++ $py = $null
++ foreach ($cand in ($candidates | Select-Object -Unique)) {
++ if ($cand -and (Test-Path $cand)) {
++ $py = (Resolve-Path $cand).Path
++ break
++ }
++ }
++
++ if (-not $py) {
++ "candidate list:" | Out-File $log -Append
++ $candidates | Out-File $log -Append
++ throw "Python executable not found after setup-python/vcvars. Check python-version.txt in the debug artifact."
++ }
++
++ $pyDir = Split-Path -Parent $py
++ $bat = Join-Path $shimDir "python.bat"
++ $cmdFile = Join-Path $shimDir "python.cmd"
++ $batContent = @"
++@echo off
++"$py" %*
++exit /b %ERRORLEVEL%
++"@
++ Set-Content -Encoding ascii -Path $bat -Value $batContent
++ Set-Content -Encoding ascii -Path $cmdFile -Value $batContent
++
++ # Put the shim first so custom build steps that literally run `python` find it.
++ $env:PATH = "$shimDir;$pyDir;$env:PATH"
++ "selected python=$py" | Out-File $log -Append
++ "shimDir=$shimDir" | Out-File $log -Append
++ "updated PATH=$env:PATH" | Out-File $log -Append
++ & $py --version 2>&1 | Tee-Object -FilePath $log -Append
++ & cmd.exe /d /s /c "where python" 2>&1 | Tee-Object -FilePath $log -Append
++}
++'''
++
++if marker not in s:
++ # Insert after Require function, before Find-VcVars64.
++ needle = 'function Find-VcVars64 {'
++ if needle not in s:
++ raise SystemExit('Could not find Find-VcVars64 insertion point in tools/build-sdk-windows.ps1')
++ s = s.replace(needle, func + '\n' + needle, 1)
++
++# Ensure it is called after tool where dumps in the x64 dev shell, before apply-openvibe-sdk/build.
++call = 'Ensure-PythonCommandForMSBuild'
++if call not in s.split('# Apply OpenVibe source files', 1)[0]:
++ needle = 'where.exe lib | Out-File (Join-Path $LogDir "where-lib.txt")\n'
++ if needle in s:
++ s = s.replace(needle, needle + call + '\n', 1)
++ else:
++ needle2 = 'Say "cl.exe already available"\n'
++ if needle2 not in s:
++ raise SystemExit('Could not find tool setup insertion point in tools/build-sdk-windows.ps1')
++ s = s.replace(needle2, needle2 + call + '\n', 1)
++
++p.write_text(s)
++PY
++
++# Commit patch.
++echo "[openvibe] git diff summary"
++git diff --stat .github/workflows/windows-source-sdk-dlls.yml tools/build-sdk-windows.ps1 || true
++
++git add .github/workflows/windows-source-sdk-dlls.yml tools/build-sdk-windows.ps1
++if git diff --cached --quiet; then
++ echo "[openvibe] no code changes to commit"
++else
++ git commit -m "Make python available to Windows MSBuild custom steps"
++fi
++
++echo "[openvibe] pushing $BRANCH"
++git push origin "$BRANCH"
++
++# Trigger and watch/download using the repo helper if available.
++if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
++ echo "[openvibe] triggering clean Windows DLL build"
++ tools/trigger-windows-dll-build-clean.sh
++else
++ echo "[openvibe] triggering workflow"
++ gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH"
++ sleep 8
++ RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
++ echo "[openvibe] watching run $RUN_ID"
++ gh run watch --repo "$REPO" "$RUN_ID" --exit-status || true
++ if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
++ tools/windows-workflow-debug-and-install.sh || true
++ fi
++fi
++
++if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
++ echo "[openvibe] verify DLL content"
++ tools/verify-openvibe-dll-content.sh || true
++fi
++
++echo "[openvibe] done"
ddiff --git a/tools/fix-windows-server-python-project-patch-and-rerun.sh b/tools/fix-windows-server-python-project-patch-and-rerun.sh
nnew file mode 100755
iindex 0000000..d1e81ef
---- /dev/null
++++ b/tools/fix-windows-server-python-project-patch-and-rerun.sh
@@@ -0,0 +1,146 @@
++#!/usr/bin/env bash
++set -euo pipefail
++
++ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
++cd "$ROOT"
++BRANCH="$(git branch --show-current)"
++REPO="$(git remote get-url origin | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
++
++echo "[openvibe] fix Windows server vcxproj python custom build commands + rerun"
++echo "[openvibe] root=$ROOT"
++echo "[openvibe] branch=$BRANCH"
++echo "[openvibe] repo=$REPO"
++
++python3 - <<'PY'
++from pathlib import Path
++p = Path('tools/build-sdk-windows.ps1')
++s = p.read_text()
++orig = s
++
++# 1) Ensure the robust MSBuild python shim is actually called. The previous patch
++# defined Ensure-PythonCommandForMSBuild but only called Ensure-PythonOnPath.
++needle = 'Ensure-PythonOnPath\n'
++if needle in s and 'Ensure-PythonCommandForMSBuild\n' not in s.split(needle,1)[1][:120]:
++ s = s.replace(needle, 'Ensure-PythonOnPath\nEnsure-PythonCommandForMSBuild\n', 1)
++
++# 2) Make Ensure-PythonCommandForMSBuild publish the selected shim path for later project-file patching.
++needle2 = '$env:PATH = "$shimDir;$pyDir;$env:PATH"\n'
++if needle2 in s and '$script:OpenVibePythonCommand' not in s[s.find(needle2):s.find(needle2)+260]:
++ s = s.replace(needle2, needle2 + ' $script:OpenVibePythonCommand = $bat\n', 1)
++
++# 3) Add a generated vcxproj patcher. The Source SDK VPC emits custom build commands
++# that literally start with `python ...`. On hosted Windows, MSBuild sometimes does
++# not resolve batch shims through PATH for these custom steps. Rewriting generated
++# project files to call the absolute shim is deterministic.
++marker = 'function Patch-GeneratedPythonCustomBuildCommands'
++func = r'''
++function Patch-GeneratedPythonCustomBuildCommands {
++ if (-not $script:OpenVibePythonCommand -or !(Test-Path $script:OpenVibePythonCommand)) {
++ Ensure-PythonCommandForMSBuild
++ }
++
++ $py = $script:OpenVibePythonCommand
++ Say "patching generated vcxproj python custom build commands to $py"
++ $patchLog = Join-Path $LogDir "python-vcxproj-patches.txt"
++ "python command=$py" | Out-File $patchLog
++
++ $projects = @(Get-ChildItem -Path $Src -Recurse -File -Filter "*.vcxproj" -ErrorAction SilentlyContinue)
++ foreach ($proj in $projects) {
++ $text = Get-Content -Raw $proj.FullName
++ $before = $text
++
++ # XML command bodies can contain python at line start, after XML tags, or after &&.
++ # Replace only executable tokens, preserving surrounding whitespace/tags.
++ $text = [regex]::Replace($text, '(?im)(^|[>`r`n])([ `t]*)python(\.exe)?([ `t]+)', {
++ param($m)
++ return $m.Groups[1].Value + $m.Groups[2].Value + '"' + $py + '"' + $m.Groups[4].Value
++ })
++ $text = [regex]::Replace($text, '(?i)(&amp;&amp;[ `t]*)python(\.exe)?([ `t]+)', {
++ param($m)
++ return $m.Groups[1].Value + '"' + $py + '"' + $m.Groups[3].Value
++ })
++ $text = [regex]::Replace($text, '(?i)(&&[ `t]*)python(\.exe)?([ `t]+)', {
++ param($m)
++ return $m.Groups[1].Value + '"' + $py + '"' + $m.Groups[3].Value
++ })
++
++ if ($text -ne $before) {
++ Set-Content -Encoding utf8 -Path $proj.FullName -Value $text
++ "patched $($proj.FullName)" | Out-File $patchLog -Append
++ }
++ }
++}
++'''
++if marker not in s:
++ insert_after = 'function Invoke-MSBuildProject([System.IO.FileInfo]$proj, [string]$label) {'
++ idx = s.find(insert_after)
++ if idx == -1:
++ raise SystemExit('Could not find Invoke-MSBuildProject insertion point')
++ s = s[:idx] + func + '\n\n' + s[idx:]
++
++# 4) Call patcher after project generation/project discovery but before building dependencies/client/server.
++call = 'Patch-GeneratedPythonCustomBuildCommands\n\nBuild-SourceSdkDependencyProjects'
++if call not in s:
++ s = s.replace('Build-SourceSdkDependencyProjects\n\n$beforeBuild = Get-Date', call + '\n\n$beforeBuild = Get-Date', 1)
++
++if s == orig:
++ print('[openvibe] build-sdk-windows.ps1 already had python project patch')
++else:
++ p.write_text(s)
++ print('[openvibe] patched tools/build-sdk-windows.ps1')
++PY
++
++cat > docs/WINDOWS_SERVER_PYTHON_CUSTOM_BUILD.md <<'EOF_DOC'
++# Windows server.vcxproj Python custom build fix
++
++Valve Source SDK's generated server project uses custom build commands that run
++`python` while converting `.nut` script resources into generated headers. On
++GitHub-hosted Windows runners, `actions/setup-python` may succeed while the VPC
++emitted MSBuild custom step still cannot resolve the literal `python` command.
++
++The Windows build script now:
++
++1. creates a deterministic `python.bat`/`python.cmd` shim;
++2. prepends it to PATH;
++3. rewrites generated `.vcxproj` custom build command bodies so `python ...`
++ uses the absolute shim path.
++
++This keeps the generated Source SDK tree disposable while making CI builds
++reliable.
++EOF_DOC
++
++echo "[openvibe] git diff summary"
++git diff --stat
++
++git add tools/build-sdk-windows.ps1 docs/WINDOWS_SERVER_PYTHON_CUSTOM_BUILD.md
++if git diff --cached --quiet; then
++ echo "[openvibe] no changes to commit"
++else
++ git commit -m "Patch Windows server Python custom build commands"
++fi
++
++echo "[openvibe] pushing $BRANCH"
++git push origin "$BRANCH"
++
++if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
++ echo "[openvibe] triggering clean Windows DLL build"
++ tools/trigger-windows-dll-build-clean.sh || true
++else
++ echo "[openvibe] triggering workflow via gh"
++ gh workflow run windows-source-sdk-dlls.yml --repo "$REPO" --ref "$BRANCH"
++ sleep 5
++ RUN_ID="$(gh run list --repo "$REPO" --workflow windows-source-sdk-dlls.yml --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
++ echo "[openvibe] watching run $RUN_ID"
++ gh run watch "$RUN_ID" --repo "$REPO" --exit-status || true
++fi
++
++if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
++ echo "[openvibe] downloading diagnostics/artifacts"
++ tools/windows-workflow-debug-and-install.sh || true
++fi
++
++if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
++ tools/verify-openvibe-dll-content.sh || true
++fi
++
++echo "[openvibe] done"
ddiff --git a/tools/fix-windows-server-quickjs-cpp-nutheaders-and-rerun.sh b/tools/fix-windows-server-quickjs-cpp-nutheaders-and-rerun.sh
nnew file mode 100755
iindex 0000000..8087eb0
---- /dev/null
++++ b/tools/fix-windows-server-quickjs-cpp-nutheaders-and-rerun.sh
@@@ -0,0 +1,188 @@
++#!/usr/bin/env bash
++set -euo pipefail
++
++log(){ printf '[openvibe] %s\n' "$*"; }
++warn(){ printf '[openvibe warn] %s\n' "$*" >&2; }
++
++ROOT="${OPENVIBE_ROOT:-$(pwd)}"
++cd "$ROOT"
++BRANCH="$(git branch --show-current)"
++REPO="$(git config --get remote.origin.url | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
++: "${REPO:=OpenVibers/OpenVibe.Games}"
++
++log "fix Windows server build: QuickJS C++ header + vscript nut headers + real msbuild failures"
++log "root=$ROOT"
++log "branch=$BRANCH"
++log "repo=$REPO"
++
++python3 - <<'PY'
++from pathlib import Path
++p = Path('tools/build-sdk-windows.ps1')
++text = p.read_text()
++
++# 1) Make Invoke-MSBuildProject stop leaking Tee-Object output into the function return value.
++old = '& msbuild $proj.FullName /m /p:Configuration="$($p.Configuration)" /p:Platform="$($p.Platform)" /t:Build /v:minimal /nologo 2>&1 | Tee-Object -FilePath $log'
++new = '& msbuild $proj.FullName /m /p:Configuration="$($p.Configuration)" /p:Platform="$($p.Platform)" /t:Build /v:minimal /nologo 2>&1 | Tee-Object -FilePath $log | Out-Host'
++if old in text:
++ text = text.replace(old, new)
++
++# 2) Ensure the stronger python shim is actually called and exposes paths for other helpers.
++if 'Ensure-PythonOnPath\nEnsure-PythonCommandForMSBuild' not in text and 'Ensure-PythonOnPath\r\nEnsure-PythonCommandForMSBuild' not in text:
++ text = text.replace('Ensure-PythonOnPath\n', 'Ensure-PythonOnPath\nEnsure-PythonCommandForMSBuild\n')
++
++# Add script-scoped python vars inside Ensure-PythonCommandForMSBuild if not present.
++if '$script:OpenVibePythonExe = $py' not in text:
++ text = text.replace(' $pyDir = Split-Path -Parent $py\n', ' $pyDir = Split-Path -Parent $py\n $script:OpenVibePythonExe = $py\n')
++if '$script:OpenVibePythonShim = $bat' not in text:
++ text = text.replace(' Set-Content -Encoding ascii -Path $cmdFile -Value $batContent\n', ' Set-Content -Encoding ascii -Path $cmdFile -Value $batContent\n $script:OpenVibePythonShim = $bat\n')
++
++helper = r'''
++
++# OPENVIBE_FIX_QJS_CPP_AND_NUT_HEADERS
++function Patch-QuickJsHeaderForMsvcCpp {
++ $header = Join-Path $Sdk "src/game/shared/openvibe/third_party/quickjs/quickjs.h"
++ if (!(Test-Path $header)) {
++ Say "QuickJS header not present yet, skipping C++ compatibility patch: $header"
++ return
++ }
++
++ $log = Join-Path $LogDir "quickjs-cpp-header-patch.txt"
++ "header=$header" | Out-File $log
++ $q = Get-Content -Raw $header
++ $orig = $q
++
++ $mkvalOld = '#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .uint64 = (uint32_t)(val) }, tag }'
++ $mkvalNew = @'
++#if defined(__cplusplus)
++static inline JSValue JS_MKVAL_CPP(int tag, uint32_t val) { JSValue v; v.u.uint64 = (uint32_t)val; v.tag = tag; return v; }
++#define JS_MKVAL(tag, val) JS_MKVAL_CPP((tag), (uint32_t)(val))
++#else
++#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .uint64 = (uint32_t)(val) }, tag }
++#endif
++'@
++ $q = $q.Replace($mkvalOld, $mkvalNew)
++
++ $mkptrOld = '#define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag }'
++ $mkptrNew = @'
++#if defined(__cplusplus)
++static inline JSValue JS_MKPTR_CPP(int tag, void *p) { JSValue v; v.u.ptr = p; v.tag = tag; return v; }
++#define JS_MKPTR(tag, p) JS_MKPTR_CPP((tag), (void *)(p))
++#else
++#define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag }
++#endif
++'@
++ $q = $q.Replace($mkptrOld, $mkptrNew)
++
++ $nanOld = '#define JS_NAN (JSValue){ .u.float64 = JS_FLOAT64_NAN, JS_TAG_FLOAT64 }'
++ $nanNew = @'
++#if defined(__cplusplus)
++static inline JSValue JS_NAN_CPP(void) { JSValue v; v.u.float64 = JS_FLOAT64_NAN; v.tag = JS_TAG_FLOAT64; return v; }
++#define JS_NAN JS_NAN_CPP()
++#else
++#define JS_NAN (JSValue){ .u.float64 = JS_FLOAT64_NAN, JS_TAG_FLOAT64 }
++#endif
++'@
++ $q = $q.Replace($nanOld, $nanNew)
++
++ $q = $q.Replace(' JSCFunctionType ft = { .generic_magic = func };', ' JSCFunctionType ft; memset(&ft, 0, sizeof(ft)); ft.generic_magic = func;')
++
++ if ($q -ne $orig) {
++ Set-Content -Encoding ascii -Path $header -Value $q
++ "patched=1" | Out-File $log -Append
++ Say "patched QuickJS header for MSVC C++ compound literal/designated initializer compatibility"
++ } else {
++ "patched=0" | Out-File $log -Append
++ Say "QuickJS header C++ patch made no changes"
++ }
++}
++
++function Ensure-ServerNutHeaders {
++ $serverDir = Join-Path $Src "game/server"
++ $textToArray = Join-Path $Src "devtools/bin/texttoarray.py"
++ $log = Join-Path $LogDir "server-nut-headers.txt"
++ "serverDir=$serverDir" | Out-File $log
++ "textToArray=$textToArray" | Out-File $log -Append
++ "pythonExe=$script:OpenVibePythonExe" | Out-File $log -Append
++ "pythonShim=$script:OpenVibePythonShim" | Out-File $log -Append
++
++ if (!(Test-Path $textToArray)) {
++ throw "Missing Source SDK texttoarray.py at $textToArray"
++ }
++ if (-not $script:OpenVibePythonExe -or !(Test-Path $script:OpenVibePythonExe)) {
++ throw "Python exe was not captured for nut header generation. Check python-version.txt."
++ }
++
++ foreach ($name in @("spawn_helper", "vscript_server")) {
++ $input = Join-Path $serverDir "$name.nut"
++ $out = Join-Path $serverDir "${name}_nut.h"
++ if (!(Test-Path $input)) {
++ "missing input $input" | Out-File $log -Append
++ continue
++ }
++ Say "generating $out from $input"
++ & $script:OpenVibePythonExe $textToArray $input "g_Script_$name" | Set-Content -Encoding ascii -Path $out
++ if ($LASTEXITCODE -ne 0) { throw "texttoarray.py failed for $input" }
++ if (!(Test-Path $out)) { throw "Expected generated nut header was not created: $out" }
++ "generated $out" | Out-File $log -Append
++ }
++}
++'''
++
++if 'function Patch-QuickJsHeaderForMsvcCpp' not in text:
++ marker = 'function Find-VcVars64 {'
++ text = text.replace(marker, helper + '\n' + marker)
++
++# Call QuickJS C++ header patch right after QuickJS lib build and before Set-Location.
++if 'Patch-QuickJsHeaderForMsvcCpp\n\nSet-Location $Src' not in text:
++ text = text.replace('\nSet-Location $Src\n', '\nPatch-QuickJsHeaderForMsvcCpp\n\nSet-Location $Src\n')
++
++# Generate vscript headers after project generation/patches and before dependency/client/server builds.
++if 'Ensure-ServerNutHeaders\nBuild-SourceSdkDependencyProjects' not in text:
++ text = text.replace('Build-SourceSdkDependencyProjects\n', 'Ensure-ServerNutHeaders\nBuild-SourceSdkDependencyProjects\n')
++
++p.write_text(text)
++PY
++
++cat > docs/WINDOWS_SERVER_QJS_CPP_NUT_HEADERS.md <<'EOF'
++# Windows server build: QuickJS C++ and vscript nut headers
++
++The Windows HL2MP server build needs two extra fixes on hosted Actions runners:
++
++- The Source SDK custom build step for `vscript_server.nut` can report success without producing `vscript_server_nut.h`, so the build script pre-generates `spawn_helper_nut.h` and `vscript_server_nut.h` with `devtools/bin/texttoarray.py`.
++- QuickJS is compiled as C with `clang-cl`, but its header is also included from C++ server files. The script patches the SDK copy of `quickjs.h` so MSVC C++ can compile QuickJS value macros without C compound literals/designated initializers.
++
++The patch is applied only to the generated SDK copy under `engine/source-sdk-2013`, not to the vendored source files.
++EOF
++
++log "git diff summary"
++git diff --stat
++
++git add tools/build-sdk-windows.ps1 docs/WINDOWS_SERVER_QJS_CPP_NUT_HEADERS.md
++if git diff --cached --quiet; then
++ log "nothing new to commit"
++else
++ git commit -m "Fix Windows server QuickJS C++ and nut headers"
++fi
++
++log "pushing $BRANCH"
++git push origin "$BRANCH"
++
++if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
++ log "triggering clean Windows DLL build"
++ tools/trigger-windows-dll-build-clean.sh || true
++elif [[ -x tools/gh-windows-build-and-install.sh ]]; then
++ log "triggering Windows DLL build/install helper"
++ tools/gh-windows-build-and-install.sh || true
++else
++ log "triggering workflow directly"
++ gh workflow run windows-source-sdk-dlls.yml --repo "$REPO" --ref "$BRANCH"
++ sleep 5
++ run_id="$(gh run list --repo "$REPO" --workflow windows-source-sdk-dlls.yml --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
++ log "watching run $run_id"
++ gh run watch "$run_id" --repo "$REPO" --exit-status || true
++ if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
++ tools/windows-workflow-debug-and-install.sh "$run_id" || true
++ fi
++fi
++
++log "done"