Desktop Material

Building and Packaging Desktop Material

Desktop Material is built, packaged, tested, and released as a Windows-only application. The repository retains some inherited upstream platform adapters, but macOS and Linux packages are not supported product outputs.

Build pipeline

yarn build:prod uses the Webpack configuration under app/ and script/build.ts to create the production renderer, main process, crash view, syntax highlighter, CLI, styles, source maps, licenses, and staged Electron resources. The canonical product version is app/package.json#version.

The CI workflow builds Windows x64 and Windows arm64 on windows-2022 by default. A trusted workflow_dispatch may select the fixed desktop-material-windows-local runner for those desktop build jobs; the Windows TUI core job remains on windows-2022, and pushes, pull requests, and reusable calls cannot select the self-hosted label. Windows x64 runs the full unit suite, and both architectures run the script tests and packaging gate. The supported packaged end-to-end lane installs and exercises Windows x64.

Windows packaging

yarn package runs script/package.ts and electron-winstaller to create:

The portable archive is not CI-only: a local Windows x64 production build followed by yarn package writes dist/GitHub Desktop-x64.zip alongside the installer outputs. This provides a directly movable build even when a remote release run is unavailable. It contains the packaged application directory, so extract the ZIP before starting the executable.

script/windows-portable-zip.ts uses the native %SystemRoot%\System32\tar.exe ZIP writer so archive data is streamed instead of retained in Node memory and ZIP64-capable tooling handles large package entries. It writes to a controlled .partial.zip, lists the completed archive to reject truncation or corruption, requires a non-empty result, and atomically renames it to the final path. A stale destination or failed partial archive is removed rather than mistaken for the current package. The destination must stay outside the packaged source tree.

The automated release workflow publishes the x64 portable ZIP, setup executable, MSI, RELEASES, and both exact-name copies of the full NuGet package. It verifies that every required asset is non-empty before publication. Automatic and Super Express packages share the validated <base>-z<9-letter-base-26-GitHub-run-ID> version namespace so Squirrel can order Releases across both lanes. The leading z migrates installations from the older incompatible b… and s… namespaces; the alphabetic payload also avoids the installed legacy comparer's 32-bit overflow on modern numeric run IDs. Windows packaging is permanently unsigned. Every package lane disables certificate auto-discovery, clears Windows signing and Azure identity inputs, and verifies that both the setup executable and MSI report NotSigned. A signature or attempted signer invocation fails the build. Release notes warn that the unsigned artifacts may trigger Windows SmartScreen or an unknown-publisher prompt.

The direct .github/workflows/super-express-release-windows.yml build and publish jobs both use [self-hosted, Windows, X64, desktop-material-windows-local]. The publisher downloads and rechecks the package, stages and validates a unique draft, then publishes it as a uniquely tagged non-Latest Release. Exact workflow timing is written and verified before Latest reconciliation. A same-job failure removes the captured release and exact new tag, then restores the prior Latest. It never reuses or overwrites an earlier tag.

Publication boundary

.github/workflows/build-installers.yml runs only after the complete CI workflow succeeds for main. It checks out the exact CI SHA, proves that SHA is an eligible main push, requires a new unique release tag, builds and packages Windows x64, revalidates the tag, and publishes one immutable non-draft Release. A successful target superseded during the build remains published but non-latest. The shared promotion helper only advances the update feed for current main, reconciles the greatest valid same-SHA version, and demotes a candidate if main changes during promotion. A failed CI publishes no Release.

Linux runners used for lint, Pages, or CodeQL are infrastructure only. They do not produce Linux application packages. No macOS build, signing, packaging, or E2E lane is part of the supported pipeline.

Failure modes and verification

Build, unit, script, package, archive-create/list, installed-E2E, missing-asset, unexpected-signature, invalid-version, existing-tag, and remote-query failures stop release publication. A stale post-build head preserves its immutable Release without promoting it to the updater feed. The tracked CI safety test enforces the Windows-only matrix, requires the x64 portable ZIP as a non-empty release asset, and rejects macOS runners or Apple signing inputs in the application workflow. Portable-ZIP and CI focused checks passed 11/11 along with script TypeScript and focused lint, format, and diff checks. The combined changed-surface gate passed 165/165 across 18 suites. A complete remote package receipt now exists: exact-source CI 29977738533 and Build Installers 29978844761 succeeded for 04246fdf12c09446b88d2f40130581d603131c8e. Release v3.6.3-beta3-zadtberjmv published the portable ZIP, setup EXE, MSI, RELEASES, and both full NuGet package names as six non-empty assets. Installed Squirrel acceptance proved the legacy s000000000201 migration, and Super Express run 29980281736 published the greater same-SHA v3.6.3-beta3-zadtbhvdfc package that reached the real update-ready UI.