From 01283f1fdda9b9d07a34ed6891b0c39ec1e2b63a Mon Sep 17 00:00:00 2001 From: kingwl Date: Sat, 10 Oct 2026 17:23:16 +0800 Subject: [PATCH] perf(desktop): compress macOS DMGs with LZMA --- THIRD_PARTY_NOTICES.md | 1 + apps/desktop/electron-builder.config.d.mts | 1 + apps/desktop/packaging/README.i18n.yaml | 4 +-- apps/desktop/packaging/README.md | 2 ++ apps/desktop/packaging/README.zh.md | 2 ++ .../scripts/electron-builder-config.mjs | 1 + apps/desktop/scripts/runtime-patch-policy.ts | 1 + apps/desktop/tests/macos-signature.spec.ts | 24 +++++++++++++++++ .../tests/prepare-runtime-patches.spec.ts | 2 +- patches/app-builder-lib@26.17.0.patch | 27 +++++++++++++++++++ pnpm-lock.yaml | 11 ++++---- pnpm-workspace.yaml | 1 + 12 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 patches/app-builder-lib@26.17.0.patch diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 3fd65e81a2..ede0681303 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -146,6 +146,7 @@ pnpm applies local patches to the following packages at install time, so shipped - `@fortune-sheet/core@1.0.4` — [`patches/@fortune-sheet__core@1.0.4.patch`](patches/@fortune-sheet__core@1.0.4.patch) - `@fortune-sheet/react@1.0.4` — [`patches/@fortune-sheet__react@1.0.4.patch`](patches/@fortune-sheet__react@1.0.4.patch) - `@yao-pkg/pkg@6.21.0` — [`patches/@yao-pkg__pkg@6.21.0.patch`](patches/@yao-pkg__pkg@6.21.0.patch) +- `app-builder-lib@26.17.0` — [`patches/app-builder-lib@26.17.0.patch`](patches/app-builder-lib@26.17.0.patch) - `exceljs@4.4.0` — [`patches/exceljs@4.4.0.patch`](patches/exceljs@4.4.0.patch) - `node-pty@1.2.0-beta.15` — [`patches/node-pty@1.2.0-beta.15.patch`](patches/node-pty@1.2.0-beta.15.patch) diff --git a/apps/desktop/electron-builder.config.d.mts b/apps/desktop/electron-builder.config.d.mts index a4d0739791..0682e6e9ad 100644 --- a/apps/desktop/electron-builder.config.d.mts +++ b/apps/desktop/electron-builder.config.d.mts @@ -30,6 +30,7 @@ export interface DesktopElectronBuilderConfig { readonly signIgnore: readonly string[] } readonly dmg: { + readonly format: 'ULMO' readonly sign: boolean readonly writeUpdateInfo: boolean } diff --git a/apps/desktop/packaging/README.i18n.yaml b/apps/desktop/packaging/README.i18n.yaml index 00a03f782a..2f49b09ee5 100644 --- a/apps/desktop/packaging/README.i18n.yaml +++ b/apps/desktop/packaging/README.i18n.yaml @@ -21,8 +21,8 @@ en: d80a92c02a583b80 zh: b27f75ff29fcea1a /desktop-packaging/configuration-and-local-builds: - en: e8b8bb1e287bff80 - zh: 09b38712c8e974bc + en: e4f526604231ed43 + zh: d412179ffdc402ba /desktop-packaging/windows-actions-packaging: en: e3f51e7130a0fe05 zh: 70fd6a40d5899871 diff --git a/apps/desktop/packaging/README.md b/apps/desktop/packaging/README.md index 37f3b0151d..6f5f5bf096 100644 --- a/apps/desktop/packaging/README.md +++ b/apps/desktop/packaging/README.md @@ -93,6 +93,8 @@ pnpm run package:desktop:win:x64 The macOS arm64 command requires Apple Silicon. The macOS x64 command runs on Intel macOS or Apple Silicon with Rosetta. The Windows x64 command requires Windows x64. Linux is not a supported Desktop release target. +Both release and local unsigned macOS DMGs use LZMA compression (`dmg.format: 'ULMO'`) in [the electron-builder configuration](../scripts/electron-builder-config.mjs). A [build-time patch](../../../patches/app-builder-lib@26.17.0.patch) adds this format to the pinned builder's validation and TypeScript declarations. + Each target owns its packed package inputs, prepared runtime, package set, dsh tree, pnpm preparation state, unpacked application, update metadata, and final artifacts under `apps/desktop/.desktop-build/targets//`. The Electron archive cache remains shared under `.desktop-build/downloads` because every archive name includes its version, platform, and architecture and is verified before extraction. A target build never consumes another target's mutable preparation state. diff --git a/apps/desktop/packaging/README.zh.md b/apps/desktop/packaging/README.zh.md index 8f3aafd37c..7e8a916dca 100644 --- a/apps/desktop/packaging/README.zh.md +++ b/apps/desktop/packaging/README.zh.md @@ -93,6 +93,8 @@ pnpm run package:desktop:win:x64 macOS arm64 命令要求 Apple Silicon。macOS x64 命令可以在 Intel macOS 或带 Rosetta 的 Apple Silicon 上运行。Windows x64 命令要求 Windows x64。Linux 不是受支持的 Desktop 发布目标。 +macOS 正式发布和本地未签名 DMG 都通过 [electron-builder 配置](../scripts/electron-builder-config.mjs)中的 `dmg.format: 'ULMO'` 使用 LZMA 压缩。[构建时补丁](../../../patches/app-builder-lib@26.17.0.patch)为固定版本 builder 的校验规则和 TypeScript 声明添加此格式。 + 每个目标都在 `apps/desktop/.desktop-build/targets//` 下持有自己的打包输入、已准备运行时、包集合、dsh 依赖树、pnpm 准备状态、未打包应用、更新元数据和最终产物。Electron 归档缓存继续由 `.desktop-build/downloads` 共享,因为每个归档文件名都包含版本、平台和架构,并且在解包前经过验证。目标构建绝不读取其他目标的可变准备状态。 diff --git a/apps/desktop/scripts/electron-builder-config.mjs b/apps/desktop/scripts/electron-builder-config.mjs index 747d20d841..02e906b12f 100644 --- a/apps/desktop/scripts/electron-builder-config.mjs +++ b/apps/desktop/scripts/electron-builder-config.mjs @@ -166,6 +166,7 @@ export function createElectronBuilderConfig( target: unsigned ? ['dmg'] : ['dmg', 'zip'], }, dmg: { + format: 'ULMO', sign: !unsigned, writeUpdateInfo: false, }, diff --git a/apps/desktop/scripts/runtime-patch-policy.ts b/apps/desktop/scripts/runtime-patch-policy.ts index f688db356d..5281f9fe35 100644 --- a/apps/desktop/scripts/runtime-patch-policy.ts +++ b/apps/desktop/scripts/runtime-patch-policy.ts @@ -13,6 +13,7 @@ export const DESKTOP_RUNTIME_PATCHES: Readonly Promise +} + const RELEASE_ENVIRONMENT = { DSH_DESKTOP_APP_ID: 'com.example.desktop', DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com', @@ -75,6 +81,7 @@ describe('desktop macOS release signature', () => { signIgnore: ['/Contents/Resources/app\\.asar\\.unpacked/dsh(?:/|$)', '/Contents/Resources/runtime/primary-runtime(?:/|$)', '\\.pak$'], }, dmg: { + format: 'ULMO', sign: true, writeUpdateInfo: false, }, @@ -87,6 +94,23 @@ describe('desktop macOS release signature', () => { expect(typeof config.artifactBuildCompleted).toBe('function') }) + it('accepts LZMA DMGs through builder validation while rejecting unknown formats', async () => { + const { createElectronBuilderConfig } = await import('../scripts/electron-builder-config.mjs') + for (const arch of ['arm64', 'x64']) { + for (const unsigned of ['0', '1']) { + const config = createElectronBuilderConfig({ + ...RELEASE_ENVIRONMENT, + DSH_DESKTOP_TARGET_ARCH: arch, + DSH_DESKTOP_UNSIGNED: unsigned, + }, 'darwin', arch) + expect(config.dmg.format).toBe('ULMO') + await expect(validateConfiguration(config, { isEnabled: false })).resolves.toBeUndefined() + } + } + await expect(validateConfiguration({ dmg: { format: 'invalid' } }, { isEnabled: false })) + .rejects.toThrow('configuration.dmg.format') + }) + it('seals PAK resources with their enclosing bundle while signing executable code', async () => { const { createElectronBuilderConfig } = await import('../electron-builder.config.mjs') const config = createElectronBuilderConfig(RELEASE_ENVIRONMENT, 'darwin', 'arm64') diff --git a/apps/desktop/tests/prepare-runtime-patches.spec.ts b/apps/desktop/tests/prepare-runtime-patches.spec.ts index 951724e114..ce95ca7638 100644 --- a/apps/desktop/tests/prepare-runtime-patches.spec.ts +++ b/apps/desktop/tests/prepare-runtime-patches.spec.ts @@ -43,7 +43,7 @@ it('classifies the current workspace and shares only runtime patch bytes', () => const before = readFileSync(join(repository, 'pnpm-lock.yaml')) const plan = prepareRuntimePatches(project, repository) expect(plan.patches.map(entry => entry.spec)).toEqual(['@earendil-works/pi-ai@1.0.2', 'node-pty@1.2.0-beta.15']) - expect(plan.workspaceOnly).toHaveLength(5) + expect(plan.workspaceOnly).toHaveLength(6) const settings = yaml.load(readFileSync(join(project, 'pnpm-workspace.yaml'), 'utf8')) as { overrides: Record } expect(settings.overrides['@earendil-works/pi-ai']).toBeUndefined() expect(settings.overrides['@earendil-works/pi-ai@^1.0.2']).toBe('1.0.2') diff --git a/patches/app-builder-lib@26.17.0.patch b/patches/app-builder-lib@26.17.0.patch new file mode 100644 index 0000000000..20f71cef1a --- /dev/null +++ b/patches/app-builder-lib@26.17.0.patch @@ -0,0 +1,27 @@ +diff --git a/out/options/macOptions.d.ts b/out/options/macOptions.d.ts +index e93987d973399b17f3970c858abb49f5e4b193d6..445a73c81e1d7eb61dc318373d2331487934d3f5 100644 +--- a/out/options/macOptions.d.ts ++++ b/out/options/macOptions.d.ts +@@ -258,7 +258,7 @@ export interface DmgOptions extends TargetSpecificOptions { + * The disk image format. `ULFO` (lzfse-compressed image (OS X 10.11+ only)). + * @default UDZO + */ +- format?: "UDRW" | "UDRO" | "UDCO" | "UDZO" | "UDBZ" | "ULFO"; ++ format?: "UDRW" | "UDRO" | "UDCO" | "UDZO" | "UDBZ" | "ULFO" | "ULMO"; + /** + * The filesystem for the DMG volume (e.g. `"APFS"` or `"HFS+"`) + * This will be changed to APFS in the next major release, so it is recommended to set it explicitly to HFS+ if you want to keep using HFS+ (e.g. for better compatibility with older macOS versions). +diff --git a/scheme.json b/scheme.json +index a637bab2668ef88e7382dd4aa5db62b74fdde898..ebcafac58971bdd7c69b78ceddcd401a75ffaa35 100644 +--- a/scheme.json ++++ b/scheme.json +@@ -979,7 +979,8 @@ + "UDRO", + "UDRW", + "UDZO", +- "ULFO" ++ "ULFO", ++ "ULMO" + ], + "type": "string" + }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9624ebcac4..af4a24cee8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,7 @@ patchedDependencies: '@fortune-sheet/core@1.0.4': 8791a230763dbf853fd65d627c68c31de37209992851377855e16cf08ea70532 '@fortune-sheet/react@1.0.4': 2932e52e79afcd4ced08cfa123b5eded786c85043da32f622f3e80af6fc96c37 '@yao-pkg/pkg@6.21.0': 75891fc22b277c145cafeab83e41baf4a70f5b8fe6dc0801c6a0b922d7ee88c7 + app-builder-lib@26.17.0: b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348 exceljs@4.4.0: c1bf8ac8f319dab382f08e3a669b715e691d46c703ac5a4163eb383fbedd1d48 node-pty@1.2.0-beta.15: 9d30053266d0f39d3debe894c675867f6782da6bcadbc94a693b3cfb9eff8ed0 @@ -722,7 +723,7 @@ importers: version: 8.18.1 app-builder-lib: specifier: 26.17.0 - version: 26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) + version: 26.17.0(patch_hash=b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348)(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) cos-nodejs-sdk-v5: specifier: 3.0.0 version: 3.0.0 @@ -26194,7 +26195,7 @@ snapshots: anynum@1.0.1: {} - app-builder-lib@26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0): + app-builder-lib@26.17.0(patch_hash=b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348)(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0): dependencies: '@electron/asar': 3.4.1 '@electron/fuses': 1.8.0 @@ -27057,7 +27058,7 @@ snapshots: dmg-builder@26.17.0(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0): dependencies: - app-builder-lib: 26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) + app-builder-lib: 26.17.0(patch_hash=b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348)(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) builder-util: 26.16.0(supports-color@9.4.0) fs-extra: 10.1.0 js-yaml: 4.3.2 @@ -27108,7 +27109,7 @@ snapshots: electron-builder-squirrel-windows@26.17.0(dmg-builder@26.17.0)(supports-color@9.4.0): dependencies: - app-builder-lib: 26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) + app-builder-lib: 26.17.0(patch_hash=b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348)(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) builder-util: 26.16.0(supports-color@9.4.0) electron-winstaller: 5.4.0(supports-color@9.4.0) transitivePeerDependencies: @@ -27117,7 +27118,7 @@ snapshots: electron-builder@26.17.0(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0): dependencies: - app-builder-lib: 26.17.0(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) + app-builder-lib: 26.17.0(patch_hash=b70dc2a2a78ffabb80a4f14fafc7c103237f2b0a207e1f296fa1c1387ea29348)(dmg-builder@26.17.0)(electron-builder-squirrel-windows@26.17.0)(supports-color@9.4.0) builder-util: 26.16.0(supports-color@9.4.0) builder-util-runtime: 9.7.0(supports-color@9.4.0) chalk: 4.1.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 98e9be027b..735a5e5cf5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -111,5 +111,6 @@ patchedDependencies: '@fortune-sheet/core@1.0.4': patches/@fortune-sheet__core@1.0.4.patch '@fortune-sheet/react@1.0.4': patches/@fortune-sheet__react@1.0.4.patch '@yao-pkg/pkg@6.21.0': patches/@yao-pkg__pkg@6.21.0.patch + app-builder-lib@26.17.0: patches/app-builder-lib@26.17.0.patch exceljs@4.4.0: patches/exceljs@4.4.0.patch node-pty@1.2.0-beta.15: patches/node-pty@1.2.0-beta.15.patch