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:
- a portable ZIP containing the complete packaged Windows application tree;
- a Squirrel current-user setup executable;
- a Windows Installer (
.msi) package; - NuGet packages and the
RELEASESupdate-feed manifest.
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.