From 923e4c8682ca498d705cf169ad72719faef77e1a Mon Sep 17 00:00:00 2001 From: Tim Nunamaker Date: Sun, 6 Sep 2026 06:51:38 -0500 Subject: [PATCH 1/3] fix(ci): repair the macOS Intel and Windows artifact builds The Windows release job has failed on every pull request since better-sqlite3 entered the dependency tree. node-gyp cannot parse the Visual Studio install path on the current windows-latest image, which is now a bare "\18\" (VS 2026 naming), and fails with unknown version "undefined". npm ci performs native rebuilds with npm's own bundled node-gyp and ignores npm_config_node_gyp, so pointing at a newer node-gyp does not help. Pin the job to windows-2022, whose image predates the rename, with a dated TODO recording when the pin can be lifted. The macOS jobs let Tauri run bundle_dmg.sh even though the DMG it produces is deleted and rebuilt by scripts/create-macos-dmg.mjs in "Finalize platform bundles". That duplicate work is not free: on run 33818382290 it hung on hdiutil for 24 minutes and failed the macos-15-intel job, leaving an orphaned diskimages-help process. Build only the app bundle on macOS so the redundant script never runs. Both macOS legs execute it, so both are covered. Restricting the bundle means Tauri no longer creates the dmg directory, so create it before the cleanup find, which would otherwise abort under set -euo pipefail. Signed-off-by: Tim Nunamaker Assisted-by: AI --- .github/workflows/release.yml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 344c996a2..3bbdb6e12 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,15 @@ jobs: pkg_target: node22-linux-x64 binary_name: playwright-runner ps_binary_name: personal-server - - platform: windows-latest + # Pinned to windows-2022 (not windows-latest) because that runner image's Visual + # Studio install path changed to a bare "\18\" (VS 2026 naming) that node-gyp's + # bundled-with-npm version can't parse ("unknown version \"undefined\"" when + # building better-sqlite3). npm_config_node_gyp does not work around this: npm's + # run-script hardcodes its own bundled node-gyp for automatic native rebuilds + # during `npm ci`, ignoring that config (npm/cli#2839). Unpin once actions/setup-node's + # Node 22 LTS bundles npm>=11.6.3 (node-gyp>=12.1.0, which added VS2026 detection). + # TODO(2026-09-06): revisit and unpin — see PDP-Connect/data-connect#47. + - platform: windows-2022 os_family: windows target: x86_64-pc-windows-msvc artifact_key: windows-x64 @@ -165,19 +173,28 @@ jobs: codesign --force --options runtime --timestamp --entitlements personal-server/entitlements.plist --sign "$APPLE_SIGNING_IDENTITY" personal-server/dist/${{ matrix.ps_binary_name }} find personal-server/dist/node_modules -name '*.node' -type f -exec codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" {} \; + # On macOS the shipped DMG is not the one Tauri builds. "Finalize platform bundles" + # below deletes every Tauri-produced .dmg and rebuilds it with + # scripts/create-macos-dmg.mjs, which retries and detaches stale devices on hdiutil + # "resource busy". Letting Tauri run its own bundle_dmg.sh first is therefore pure + # duplicate work whose output is discarded — and it is not free: that script can hang + # on hdiutil until the job dies (macos-15-intel, run 33818382290: 24m in bundle_dmg.sh, + # then an orphaned diskimages-help process at teardown). Both macOS legs run it, so + # both carry the risk. Restricting the macOS bundle to "app" skips it entirely; "app" + # is the .app bundle the DMG step already depends on, so nothing else changes. - name: Build signed Tauri app if: matrix.os_family == 'macos' && env.APPLE_SIGNING_AVAILABLE == 'true' uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # action-v1.0.0 env: APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} with: - args: --target ${{ matrix.target }} + args: --target ${{ matrix.target }} --bundles app - name: Build unsigned Tauri app if: matrix.os_family != 'macos' || env.APPLE_SIGNING_AVAILABLE != 'true' uses: tauri-apps/tauri-action@1deb371b0cd8bd54025b384f1cd735e725c4060f # action-v1.0.0 with: - args: --target ${{ matrix.target }} + args: --target ${{ matrix.target }}${{ matrix.os_family == 'macos' && ' --bundles app' || '' }} - name: Finalize platform bundles shell: bash @@ -205,6 +222,7 @@ jobs: dmg_path="$bundle_root/dmg/DataConnect_${version}_${arch}.dmg" staging_dir="$RUNNER_TEMP/dmg-${arch}" rm -rf "$staging_dir" + mkdir -p "$bundle_root/dmg" find "$bundle_root/dmg" -maxdepth 1 -type f -name '*.dmg' -delete mkdir -p "$staging_dir" cp -R "$app" "$staging_dir/" From 1cc502fa26b773c4cead7aa55acd43089462f5ec Mon Sep 17 00:00:00 2001 From: Tim Nunamaker Date: Sun, 6 Sep 2026 08:49:03 -0500 Subject: [PATCH 2/3] fix(vendor): run npx through a shell on Windows in vendored build scripts With the runner pinned to windows-2022, npm ci gets past better-sqlite3 and now fails in the vendored read-core build script: npm error path ...\reference-implementation\vendor\read-core npm error Error: spawn npx ENOENT npm error spawnargs: [ 'tsc', '--project', 'tsconfig.build.json' ], execFile resolves a bare command name by PATH lookup alone. Without a shell there is no PATHEXT resolution, so on Windows it never finds the npx.cmd shim npm writes for bin entries. Both read-core and mcp-server run this script from a prepare hook, so npm ci executes them on every install; read-core failed first only because it is installed first. Pass shell: platform === "win32" so the lookup goes through cmd.exe there and stays a direct spawn everywhere else. This matches the existing cross-platform fix on main for these same scripts (068bc8180, which stopped them shelling out to pnpm). Signed-off-by: Tim Nunamaker Assisted-by: AI --- reference-implementation/vendor/mcp-server/scripts/build.ts | 6 ++++++ reference-implementation/vendor/read-core/scripts/build.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/reference-implementation/vendor/mcp-server/scripts/build.ts b/reference-implementation/vendor/mcp-server/scripts/build.ts index cc0668231..b0e8ce854 100644 --- a/reference-implementation/vendor/mcp-server/scripts/build.ts +++ b/reference-implementation/vendor/mcp-server/scripts/build.ts @@ -4,6 +4,7 @@ import { execFile } from "node:child_process"; import { chmod, rm } from "node:fs/promises"; import { dirname, join } from "node:path"; +import { platform } from "node:process"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -12,7 +13,12 @@ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); const distRoot = join(packageRoot, "dist"); await rm(distRoot, { force: true, recursive: true }); +// execFile resolves a bare command name via PATH lookup only; on win32 that +// misses the .cmd shim npm writes for bin entries (no shell means no PATHEXT +// resolution), so this failed with ENOENT on every Windows build of a +// consumer package that vendors this script. await execFileAsync("npx", ["tsc", "--project", "tsconfig.build.json"], { cwd: packageRoot, + shell: platform === "win32", }); await chmod(join(distRoot, "bin", "pdpp-mcp-server.js"), 0o755); diff --git a/reference-implementation/vendor/read-core/scripts/build.ts b/reference-implementation/vendor/read-core/scripts/build.ts index a400133ac..7dbe75c69 100644 --- a/reference-implementation/vendor/read-core/scripts/build.ts +++ b/reference-implementation/vendor/read-core/scripts/build.ts @@ -4,6 +4,7 @@ import { execFile } from "node:child_process"; import { rm } from "node:fs/promises"; import path from "node:path"; +import { platform } from "node:process"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; @@ -12,6 +13,11 @@ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), " const distDir = path.join(packageRoot, "dist"); await rm(distDir, { force: true, recursive: true }); +// execFile resolves a bare command name via PATH lookup only; on win32 that +// misses the .cmd shim npm writes for bin entries (no shell means no PATHEXT +// resolution), so this failed with ENOENT on every Windows build of a +// consumer package that vendors this script. await execFileAsync("npx", ["tsc", "--project", "tsconfig.build.json"], { cwd: packageRoot, + shell: platform === "win32", }); From 2ede4a9af8e0a9e4402f30017f074f85c58e66ea Mon Sep 17 00:00:00 2001 From: Tim Nunamaker Date: Sun, 6 Sep 2026 09:10:45 -0500 Subject: [PATCH 3/3] fix(tauri): gate the Unix-only kill_process_group import for Windows With npm ci fixed, the Windows Tauri build reaches rustc and fails: error[E0432]: unresolved import `super::server::kill_process_group` --> src\commands\ref_server.rs:35:5 note: found an item that was configured out --> src\commands\server.rs:13:8 kill_process_group is defined under #[cfg(unix)] in server.rs, and all four call sites in ref_server.rs are already inside #[cfg(unix)] blocks. Only the import was ungated, so the module failed to resolve on Windows and nowhere else. Verified by mutation on a reduced model of the same cfg structure: with the gate the non-unix configuration compiles; removing it reproduces E0432 exactly. Unix keeps the import in use, so no unused-import warning appears there. Signed-off-by: Tim Nunamaker Assisted-by: AI --- src-tauri/src/commands/ref_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src-tauri/src/commands/ref_server.rs b/src-tauri/src/commands/ref_server.rs index f0857e252..1e8658a7b 100644 --- a/src-tauri/src/commands/ref_server.rs +++ b/src-tauri/src/commands/ref_server.rs @@ -32,6 +32,7 @@ use std::sync::Mutex; use std::time::Duration; use tauri::{AppHandle, Emitter}; +#[cfg(unix)] use super::server::kill_process_group; static REF_SERVER_PROCESS: Mutex> = Mutex::new(None);