Skip to content

chore(release): add the Windows x64 release path - #2182

Merged
Astro-Han merged 15 commits into
mainfrom
chore/release-windows-workflow
Aug 4, 2026
Merged

chore(release): add the Windows x64 release path#2182
Astro-Han merged 15 commits into
mainfrom
chore/release-windows-workflow

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Maka has only ever shipped an Apple Silicon macOS build. 0.1.5 is meant to ship Windows x64 as well, so this adds the missing half of the release path and turns the single-platform workflow into a matrix.

  • apps/desktop/electron-builder.config.mjs gains a win section: NSIS installer + ZIP for x64, Maka-${version}-win-${arch}.${ext}, and assets/icon.png (electron-builder converts it to .ico through the same path macOS already uses).
  • There is no Authenticode certificate yet, so the Windows build is unsigned. Being unsigned is the absence of a certificate — electron-builder skips signing when none is configured — so the only thing that has to be stated is verifyUpdateCodeSignature: false, which electron-updater requires before it will install an unsigned update.
  • scripts/package-windows-x64.mjs mirrors the macOS packaging script: fail-closed on anything but a Windows x64 host, assert the .exe, .zip, and latest.yml, then drop win-unpacked.
  • scripts/verify-packaged-app.mjs is new and holds what the two verifiers share (command running, resource require/forbid lists, the node-pty probe, the filesystem-worker and renderer smokes, sha256). scripts/verify-macos-arm64-dmg.mjs keeps only its macOS-specific logic and imports the rest; its exported signatures are unchanged.
  • scripts/verify-windows-x64.mjs verifies the win-unpacked directory electron-builder just produced — the NSIS installer has no inspectable app structure, and the ZIP is an archive of exactly that directory — checking the x64 PE machine word, the product version, the resource require/forbid lists, and the packaged node-pty and renderer smokes, then writing .sha256 for the installer and the ZIP. (macOS mounts its DMG instead because notarizing and stapling rewrite the DMG after packaging.)
  • The workflow is now Release desktop (release-desktop.yml): a build matrix over macos-15 and windows-2025 with the Apple-only steps behind if: matrix.platform == 'macos', and a separate publish job that creates the one draft release from both platforms' artifacts. Splitting publish out is what keeps two parallel jobs from racing to create the same draft.
  • .github/RELEASE_CHECKLIST.md gains a Windows acceptance section, including the expected SmartScreen warning and winget for ripgrep.

Review rounds

An external review plus local verification turned up two defects in the first commit, both fixed in 20a2357:

  • win.sign: false is not a valid electron-builder option. The 26.8.1 schema sets additionalProperties: false, so validateConfiguration rejected the whole config object — and since macOS and Windows share one config, this broke the existing macOS release too. Removed; unsigned is now expressed by configuring no certificate.
  • The Windows version check could never pass. rcedit writes the product version resource in Windows' four-part form (appInfo.getVersionInWeirdWindowsForm), so 0.1.5 ships as 0.1.5.0, and the verifier compared it strictly against the manifest's 0.1.5. The unit test hid this by mocking the three-part string — a mock-shaped oracle. The verifier now compares the first three parts and requires a numeric build part, and the test pins the real four-part form.

A second, fresh-context review of the combined branch then found a third blocker, fixed in ab58cd9:

  • spawn('npm') cannot resolve on Windows. libuv's process launcher tries only .com and .exe and ignores PATHEXT, and npm on Windows is npm.cmd — so package-windows-x64.mjs would have died with ENOENT on its first command, before any packaging happened. The macOS twin is unaffected because POSIX execvp resolves the extensionless shim, which is exactly why the copy read as correct. runCommand now goes through the shell on win32 only, and a test pins the spawn strategy on both platforms since the suite cannot run on Windows.

All three were invisible to CI because nothing outside the release workflow ever loads that config or runs those scripts, and no CI job runs on Windows. Two seams closed: scripts/electron-builder-config.test.mjs validates the config against electron-builder's own validator (with a negative case that reproduces the sign bug), and scripts/ci-test-plan.mjs now routes the new release scripts, the shared verify-packaged-app.mjs, and the release config to the lanes that cover them.

Running it on Windows

Every defect below was found by .github/workflows/release-windows-check.yml, added here: it runs the real packaging and verification on windows-2025 for any PR that touches this path. Before it existed, that path's first execution would have been release day, on main, with a tag already reserved.

It found five, in order:

  1. spawn('npm') is unresolvable on Windows — libuv tries only .com/.exe and ignores PATHEXT; npm is npm.cmd. Died on the first command. The same rule then turned out to govern npm ls inside check:release, so it lives in scripts/npm-spawn.mjs now instead of being copied.
  2. check:third-party-notices failed on a checkout it had not touched — Git converts LF to CRLF in the working tree on Windows, and the check is a byte comparison. .gitattributes pins the tree to LF, which also keeps the shipped license text byte-identical between the two platforms' builds.
  3. The verifier extracted the ZIP it had just packaged. Expand-Archive needed over 26 minutes on a packaged Electron app and never finished. Replacing the API barely helped, because the cost is writing tens of thousands of small files on Windows, not the PowerShell pipeline. The real fix was to stop rebuilding the object under test: win-unpacked is the app, and the ZIP is an archive of it. Verification went from >26min to 9s.
  4. The node-pty probe hung after succeeding. conpty keeps a handle open after its child exits, so the probe printed maka-node-pty-ok and then sat there until the deadline. It now exits explicitly once its output is flushed. The verifier also reports each stage and bounds every command, because a silent hang is worse than a failure — that is how this one was located at all.
  5. The verifier drove a code path Windows never uses. The filesystem-worker smoke was copied from the macOS verifier, but isBuiltinFilesystemWorkerSandboxAvailable is false on Windows, so the app never launches that worker and file tools run through the workspace executor. Driving it by hand only proved that a POSIX-only sandbox boundary rejects Windows paths — something no Windows user can reach. Removed.

The end state passes: packaging 4m09s, verification 9s, including node-pty through conpty and a packaged renderer that mounts React and exposes the window.maka bridge.

Verification

  • npm run format:check, npm run lint — clean.
  • node --test scripts/electron-builder-config.test.mjs scripts/ci-test-plan.test.mjs scripts/macos-arm64-release.test.mjs scripts/windows-x64-release.test.mjs — 17/17 pass. The new scripts/windows-x64-release.test.mjs covers the host, input, architecture, and version fail-closed paths plus the unsigned-helper forbid list, and is wired into test:scripts:extended.
  • Workflow YAML parsed and its job/step structure inspected locally.
  • The release config was run through electron-builder's own validateConfiguration — passing now, failing before the fix.
  • The Windows path is now executed on every PR that touches it (see above), so it is no longer unverified.
  • Not run: the macOS half. The refactored macOS verifier has unit tests but has not run against a real DMG, so Release desktop's macOS leg is still a first execution. It is unchanged in behavior — the extraction was diffed function by function against main.

Rollout

Merge before dispatching the 0.1.5 release. #2180 carries the 0.1.5 version bump and CHANGELOG; the two are independent and can merge in either order.

Review focus

The unsigned Windows configuration and the publish-job split are the two decisions worth a second opinion.

- Cover every Windows-only-observable input in the check workflow's paths:
  npm-spawn.mjs, the notices generator, .gitattributes, and the manifests had
  each already broken this path once without being able to trigger it.
- Drop `verifyUpdateCodeSignature: false`. app-builder-lib only writes a
  publisher name when a certificate exists, and electron-updater skips the
  check when there is none, so the option is inert today and would silently
  suppress verification once signing is added.
- Delete the `appDirectory` option: no caller in production or tests.
- Narrow the runCommand comment to what the code does; deadlines are opt-in
  because codesign and notarization have no honest upper bound.
- Assert the exact forbidden resource set instead of accepting any one match.
@Astro-Han
Astro-Han marked this pull request as ready for review August 4, 2026 23:59
@Astro-Han
Astro-Han merged commit 7b0864c into main Aug 4, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant