feat(desktop): add Linux .deb and .rpm installer output formats#35296
Merged
crowlKats merged 1 commit intoJun 19, 2026
Conversation
Recognize .deb and .rpm via the --output extension (like .dmg/.AppImage) and wrap the staged Linux app dir in the chosen package. - .deb: hand-rolled ar + tar.gz (existing tar/flate2 deps, no new crate). - .rpm: pure-Rust rpm crate with a zstd payload (gzip would pull flate2's zlib-rs shim, clashing at link time with deno's zlib-ng). Requires use CEF sonames with the ()(64bit) ELF-class suffix. Both produce the same install tree (/usr/lib/<pkg>, /usr/bin symlink, .desktop, hicolor icon) and carry the curated CEF runtime dependency list. Metadata derives from existing config; no new config fields. Bails with a clear message when the target isn't Linux.
littledivy
approved these changes
Jun 19, 2026
littledivy
added a commit
that referenced
this pull request
Jun 21, 2026
## Summary Adds `.msi` as a `deno desktop` output format, alongside the existing `.dmg`/`.AppImage` and the `.deb`/`.rpm` formats added in #35296. As with those, the format is chosen by the `--output` extension (or the per-platform `output` key in the `desktop` block of `deno.json` — whose Windows description already documented `MyApp.msi`); the driver strips the extension, builds the intermediate Windows app dir, then wraps it in an MSI. The MSI database is authored entirely in pure Rust via the [`msi`](https://crates.io/crates/msi) crate, with the file payload stored in an embedded MSZIP cabinet ([`cab`](https://crates.io/crates/cab) crate), so it **cross-compiles from any host** — only the *target* must be Windows (gated like `.deb`/`.rpm` are gated on a Linux target). The app installs per-machine under `ProgramFiles64Folder\<AppName>\`, mirroring the staged app-dir tree exactly (nested directories like CEF's `locales/` are preserved); uninstall removes it. ``` %ProgramFiles%\<AppName>\ <AppName>.bat (launcher) laufey.exe (CEF backend) libcef.dll, ... (CEF support files) denort.dll (compiled runtime + user code) ... (nested dirs preserved) ``` ### Notable choices - **Deterministic GUIDs** — `ProductCode`, `UpgradeCode`, the package code, and per-component GUIDs are derived via UUIDv5 from the app identity (`--identifier`), so identical inputs produce an identical installer (matching the `source_date(0)` / `mtime: 0` reproducibility of the `.rpm`/`.deb`/AppImage paths) and the `UpgradeCode` is stable across versions. - **Directory/Component model** — one `Component` per install directory (a file's directory is carried by its component), `KeyPath` = the first file in that directory, components flagged 64-bit (`msidbComponentAttributes64bit`). A single `Feature` ties them together. - **Embedded cabinet** — one MSZIP folder holding every file named by its MSI `File` key, referenced by `Media.Cabinet = #appcab`. Summary stream marks the source compressed with long file names (`x64;1033` / `Arm64;1033` template). - **No new config fields** — metadata is derived from the staged app-dir name, `--identifier`, and the default `1.0.0` version that matches the macOS bundle. ### Metadata Derived from existing config (app-dir name → `ProductName`, `--identifier` → GUID seed, default `1.0.0` version). Help text for `--output` now lists `MyApp.msi`. ## Why This rounds out the desktop installer story for Windows — `.msi` was explicitly left out of the original desktop PR (#33441). `.msi` is what most Windows users/IT deployments install through. ## Tests Pure-Rust unit tests (cross-platform): triple→arch mapping, deterministic+valid GUID derivation, 8.3 short-name minting, and a full `create_windows_msi` round-trip that parses the produced MSI back and asserts the `Property`/`Directory`/`File`/`Component`/`Media` tables, the 1..N file sequence, the two-component layout for a nested dir, and that a file's bytes round-trip out of the embedded cabinet. All pass; clippy clean. As with `.deb`/`.rpm`, the matching-OS integration check (install / launch / upgrade / uninstall on a real Windows box) can't run in the cross-platform unit suite. Major-upgrade automation (an `Upgrade` table + `RemoveExistingProducts`) is intentionally left as a follow-up. ## New dependencies `msi = "0.10.0"` and `cab = "0.6.0"` (both pure Rust). Also enables the `v5` feature on the existing `uuid` dep. Closes #35377 Ref mdsteele/rust-msi#59 Closes denoland/divybot#582 --------- Co-authored-by: divybot <[email protected]> Co-authored-by: Divy Srivastava <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
.deband.rpmasdeno desktopoutput formats, alongside the existing.dmgand.AppImage. As with those, the format is chosen by the--outputextension (or the per-platformoutputkey in thedesktopblock ofdeno.json); the driver strips the extension, builds the intermediate Linux app dir, then wraps it.Both formats produce the same install tree and are assembled in pure Rust, so they cross-compile from any host (only the target must be Linux):
.debHand-rolled
ararchive (debian-binary+control.tar.gz+data.tar.gz) using the already-presenttar/flate2deps — no new crate.Dependscarries the curated CEF runtime libraries by Debian package name;Installed-Sizeis computed..rpmBuilt with the pure-Rust
rpmcrate. Two notable choices:rpm'sgzip-compressionfeature pulls flate2's zlib-rs C shim, which collides at link time with deno's vendored zlib-ng (libz-sys). deno already depends onzstd, and modern RPM tooling reads zstd payloads.Requiresuse CEF sonames with the()(64bit)ELF-class suffix (e.g.libgtk-3.so.0()(64bit)), which is what RPM auto-Provideson 64-bit distros; a bare soname wouldn't match.Metadata
Derived from existing config (app-dir name → sanitized package name,
--identifier, default1.0.0version matching the macOS bundle). No new config fields.Why
Rounds out the desktop installer story for Linux.
.AppImagealready shipped;.deb/.rpmare what most Linux users actually install through their package manager.Tests
Added pure-Rust unit tests: package-name sanitization, triple→arch mapping,
.debar-member order / control fields / install layout, and.rpmparse-back of name/arch/Requires/files. All pass; clippy clean.The matching-OS integration check (install / launch / upgrade / uninstall) can't run in the cross-platform unit suite, so the curated dependency lists are the most likely thing needing tuning once tested on a real Debian/Fedora box.
New dependency
rpm = "0.25.1"(payload+zstd-compression, default features off). Net-new transitive crates:enum-display-derive,enum-primitive-derive,num-derive,rpm-version.