diff --git a/.github/workflows/windows-package.yml b/.github/workflows/windows-package.yml index 36dcc0a85d..1a75e1d91a 100644 --- a/.github/workflows/windows-package.yml +++ b/.github/workflows/windows-package.yml @@ -52,6 +52,11 @@ on: type: string default: '' required: false + compression_level: + description: '本轮安装器压缩等级:0..9;留空保留机器配置' + type: string + default: '' + required: false permissions: contents: read @@ -77,6 +82,7 @@ jobs: PACKAGE_SIGNING: ${{ inputs.signing }} PACKAGE_CACHE_NAMESPACE: ${{ inputs.cache_namespace }} PACKAGE_BUILD_ROOT: ${{ inputs.build_root }} + PACKAGE_WINDOWS_COMPRESSION_LEVEL: ${{ inputs.compression_level }} steps: - name: Reset packaging checkout working-directory: ${{ runner.temp }} diff --git a/apps/desktop/packaging/README.i18n.yaml b/apps/desktop/packaging/README.i18n.yaml index 3aea527fec..7592f49a87 100644 --- a/apps/desktop/packaging/README.i18n.yaml +++ b/apps/desktop/packaging/README.i18n.yaml @@ -45,8 +45,8 @@ en: b42f103358d0dfd7 zh: a534164ad9eb00da /desktop-packaging/runtime-file-selection-and-installation-checks: - en: 34273efbc5084038 - zh: f255e29623b6af76 + en: d87a53ab0f8b0db9 + zh: c3b2b96085f29d3b /desktop-packaging/desktop-runtime-preparation: en: bce2ee195a876985 zh: ae45df21c23e86c2 diff --git a/apps/desktop/packaging/README.md b/apps/desktop/packaging/README.md index 211dac96e5..034cc8efd6 100644 --- a/apps/desktop/packaging/README.md +++ b/apps/desktop/packaging/README.md @@ -258,7 +258,7 @@ When extraction fails, the installer writes a report with the 7-Zip result and i Windows packaging fixes the 7-Zip filter to `BCJ` for compatibility with the bundled NSIS decoder. This preserves ARM64 binaries carried by dependencies in x64 installers; automatic ARM64 filtering produces entries that this decoder cannot extract. -`DSH_DESKTOP_WINDOWS_COMPRESSION_LEVEL` in the Windows dotenv or machine preset selects installer archive compression from `0` to `9`. If omitted, the existing `ELECTRON_BUILDER_COMPRESSION_LEVEL` environment override remains effective, otherwise the default is `9`. Lower levels trade installer size for packaging time; qualify the complete signed workflow and artifact size before changing a release preset. +`DSH_DESKTOP_WINDOWS_COMPRESSION_LEVEL` in the Windows dotenv or machine preset selects installer archive compression from `0` to `9`. If omitted, the existing `ELECTRON_BUILDER_COMPRESSION_LEVEL` environment override remains effective, otherwise the default is `9`. The manual Windows workflow accepts an optional `compression_level` from `0` to `9` that overrides the file setting for that run; leave it empty to retain the machine setting. Lower levels trade installer size for packaging time; qualify the complete signed workflow and artifact size before changing a release preset. NSIS removes its temporary extraction tree during installation, before the completion page or an automatic launch. The installed production packages remain ordinary files; startup does not extract them again. Installation still writes the complete application tree. diff --git a/apps/desktop/packaging/README.zh.md b/apps/desktop/packaging/README.zh.md index 154aac9ca4..d73b6b4725 100644 --- a/apps/desktop/packaging/README.zh.md +++ b/apps/desktop/packaging/README.zh.md @@ -258,7 +258,7 @@ Windows 安装器在启动时和选定目标目录后检查应用是否正在运 Windows 打包将 7-Zip 过滤器固定为 `BCJ`,以兼容内置的 NSIS 解码器。这样可以保留 x64 安装包中由依赖携带的 ARM64 二进制文件;自动 ARM64 过滤会生成该解码器无法解压的条目。 -Windows dotenv 或机器 preset 中的 `DSH_DESKTOP_WINDOWS_COMPRESSION_LEVEL` 选择 `0` 至 `9` 的安装包归档压缩等级。省略时,现有 `ELECTRON_BUILDER_COMPRESSION_LEVEL` 环境覆盖仍然生效,否则默认为 `9`。较低等级以安装包体积换取打包时间;修改发布 preset 前应验证完整签名 workflow 和产物体积。 +Windows dotenv 或机器 preset 中的 `DSH_DESKTOP_WINDOWS_COMPRESSION_LEVEL` 选择 `0` 至 `9` 的安装包归档压缩等级。省略时,现有 `ELECTRON_BUILDER_COMPRESSION_LEVEL` 环境覆盖仍然生效,否则默认为 `9`。手动 Windows workflow 接受可选的 `compression_level`,取值为 `0` 至 `9`,仅对本轮覆盖文件中的设置;留空则保留机器设置。较低等级以安装包体积换取打包时间;修改发布 preset 前应验证完整签名 workflow 和产物体积。 NSIS 在安装阶段清理临时解压目录,完成后才显示完成页或自动启动应用。已安装的生产依赖保持为普通文件;启动时不会再次解压。安装仍会写入完整的应用目录树。 diff --git a/apps/desktop/scripts/desktop-package-environment.d.mts b/apps/desktop/scripts/desktop-package-environment.d.mts index 995c117d07..4b9faf2481 100644 --- a/apps/desktop/scripts/desktop-package-environment.d.mts +++ b/apps/desktop/scripts/desktop-package-environment.d.mts @@ -10,6 +10,7 @@ export function localMacOSPackageEnvironment(environment?: NodeJS.ProcessEnv): N /** * Read a machine preset when selected, otherwise the target's required dotenv; no ambient release fallback. * PACKAGE_TEST_RELEASE_ID overrides the test preset only; blank retains its configured ID. + * PACKAGE_WINDOWS_COMPRESSION_LEVEL explicitly overrides Windows compression; blank retains the file setting. * @param platform Target platform. * @param environment Parent environment, retained only for unrelated build tools. * @param appRoot Desktop application directory; relative credential paths resolve here. diff --git a/apps/desktop/scripts/desktop-package-environment.mjs b/apps/desktop/scripts/desktop-package-environment.mjs index ff9ba89bde..22c65d1aa1 100644 --- a/apps/desktop/scripts/desktop-package-environment.mjs +++ b/apps/desktop/scripts/desktop-package-environment.mjs @@ -37,6 +37,7 @@ export function localMacOSPackageEnvironment(environment = process.env) { /** * Read a machine preset when selected, otherwise the target's required dotenv; no ambient release fallback. * PACKAGE_TEST_RELEASE_ID overrides the test preset only; blank retains its configured ID. + * PACKAGE_WINDOWS_COMPRESSION_LEVEL explicitly overrides Windows compression; blank retains the file setting. * @param {'win32' | 'darwin'} platform Target platform. * @param {NodeJS.ProcessEnv} environment Parent environment, retained only for unrelated build tools. * @param {string} appRoot Desktop application directory; relative credential paths resolve here. @@ -44,6 +45,12 @@ export function localMacOSPackageEnvironment(environment = process.env) { * @returns {NodeJS.ProcessEnv} Isolated environment with file-owned release settings. */ export function loadDesktopPackageEnvironment(platform, environment = process.env, appRoot = APP_ROOT, options = {}) { + const compression = environment.PACKAGE_WINDOWS_COMPRESSION_LEVEL + if (compression !== undefined && compression !== '') { + if (platform !== 'win32' || !/^[0-9]$/u.test(compression)) { + throw new Error('desktop package: PACKAGE_WINDOWS_COMPRESSION_LEVEL requires Windows and an integer from 0 to 9') + } + } const preset = environment.DSH_PACKAGING_PRESET let path = join(appRoot, platform === 'win32' ? '.env.windows' : '.env.macos') if (preset !== undefined) { @@ -69,6 +76,7 @@ export function loadDesktopPackageEnvironment(platform, environment = process.en : MACOS_SETTING.test(name) && !publicPlatformSetting.test(name))) } } + if (compression !== undefined && compression !== '') settings.DSH_DESKTOP_WINDOWS_COMPRESSION_LEVEL = compression const releaseId = environment.PACKAGE_TEST_RELEASE_ID?.trim() if (releaseId) { if (preset !== 'test') throw new Error('desktop package: test release ID override requires the test preset') diff --git a/apps/desktop/scripts/prepare-windows-ci.mjs b/apps/desktop/scripts/prepare-windows-ci.mjs index ac571095f4..b89227885a 100644 --- a/apps/desktop/scripts/prepare-windows-ci.mjs +++ b/apps/desktop/scripts/prepare-windows-ci.mjs @@ -5,6 +5,7 @@ import { execFileSync } from 'node:child_process' import { desktopCiVersion as windowsCiVersion } from './desktop-ci-version.mjs' export { desktopCiVersion as windowsCiVersion } from './desktop-ci-version.mjs' import { loadDesktopPackageEnvironment, validateDesktopPackageEnvironment } from './desktop-package-environment.mjs' +import { resolveWindowsPackageSettings } from './windows-package-settings.mjs' if (process.argv[1] !== undefined && resolve(process.argv[1]) === import.meta.filename) { const appRoot = resolve(import.meta.dirname, '..') @@ -22,6 +23,7 @@ if (process.argv[1] !== undefined && resolve(process.argv[1]) === import.meta.fi } } validateDesktopPackageEnvironment(environment, { platform: 'win32', arch: 'x64' }, { unsigned: mode === 'unsigned' }) + console.log(`Windows installer compression level: ${resolveWindowsPackageSettings(environment).compressionLevel}`) if (!process.env.GITHUB_OUTPUT || !process.env.GITHUB_STEP_SUMMARY) throw new Error('Run this adapter from GitHub Actions') appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`) appendFileSync(process.env.GITHUB_STEP_SUMMARY, `Windows x64 ${mode} installer\n\nVersion: ${version}\n\nCommit: ${execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()}\n\nChannel: ${process.env.DSH_DESKTOP_UPDATE_CHANNEL}\n\nSource: ${process.env.PACKAGE_SOURCE_REF}\n\nPreset: ${process.env.DSH_PACKAGING_PRESET}\n`) diff --git a/apps/desktop/tests/desktop-package-environment.spec.ts b/apps/desktop/tests/desktop-package-environment.spec.ts index 9a359d3529..9390af557d 100644 --- a/apps/desktop/tests/desktop-package-environment.spec.ts +++ b/apps/desktop/tests/desktop-package-environment.spec.ts @@ -32,6 +32,25 @@ describe('Desktop local packaging configuration', () => { }) }) + it.each(['0', '3', '9'])('applies an explicit Windows workflow compression override %s', async (value) => { + await withDirectory(async (directory) => { + await writeFile(join(directory, '.env.windows'), 'DSH_DESKTOP_WINDOWS_COMPRESSION_LEVEL=5\n') + const selected = loadDesktopPackageEnvironment('win32', { PACKAGE_WINDOWS_COMPRESSION_LEVEL: value }, directory) + expect(resolveWindowsPackageSettings(selected).compressionLevel).toBe(Number(value)) + const unchanged = loadDesktopPackageEnvironment('win32', { PACKAGE_WINDOWS_COMPRESSION_LEVEL: '' }, directory) + expect(resolveWindowsPackageSettings(unchanged).compressionLevel).toBe(5) + }) + }) + + it.each([' ', '-1', '10', '3.5', '03', 'fast'])('rejects invalid workflow compression %j before reading settings', (value) => { + expect(() => loadDesktopPackageEnvironment('win32', { PACKAGE_WINDOWS_COMPRESSION_LEVEL: value }, 'missing-settings')) + .toThrow('PACKAGE_WINDOWS_COMPRESSION_LEVEL') + }) + + it('rejects a Windows compression override for macOS before reading settings', () => { + expect(() => loadDesktopPackageEnvironment('darwin', { PACKAGE_WINDOWS_COMPRESSION_LEVEL: '3' }, 'missing-settings')) + .toThrow('PACKAGE_WINDOWS_COMPRESSION_LEVEL') + }) it.each(['', '-1', '10', '3.5', '03', 'fast'])('rejects invalid compression %j before preparation', (value) => { const selected = { ...RELEASE, DSH_DESKTOP_WINDOWS_COMPRESSION_LEVEL: value } expect(() => { validateDesktopPackageEnvironment(selected, WINDOWS, { unsigned: true }) }) diff --git a/scripts/windows-package-workflow.spec.ts b/scripts/windows-package-workflow.spec.ts index 59d01fb894..48661ac935 100644 --- a/scripts/windows-package-workflow.spec.ts +++ b/scripts/windows-package-workflow.spec.ts @@ -68,9 +68,11 @@ it('routes manual packaging to the dedicated runner with explicit preset and sig expect(workflow.concurrency).toEqual({ group: 'windows-desktop-package', 'cancel-in-progress': false }) expect(workflow.permissions).toEqual({ contents: 'read' }) expect(Object.keys(workflow.on.workflow_dispatch!.inputs).sort()).toEqual([ - 'build_root', 'build_version', 'cache_namespace', 'preset', 'signing', 'source_ref', 'test_release_id', 'update_channel', 'version_mode', + 'build_root', 'build_version', 'cache_namespace', 'compression_level', 'preset', 'signing', 'source_ref', 'test_release_id', 'update_channel', 'version_mode', ]) expect(workflow.on.workflow_dispatch!.inputs.signing!.default).toBe('signed') + expect(workflow.on.workflow_dispatch!.inputs.compression_level).toMatchObject({ type: 'string', default: '', required: false }) + expect(job.env.PACKAGE_WINDOWS_COMPRESSION_LEVEL).toBe('${{ inputs.compression_level }}') expect(workflow.on.workflow_dispatch!.inputs.build_version!.required).toBe(false) expect(workflow.on.workflow_dispatch!.inputs.version_mode!.default).toBe('default') expect(workflow.on.workflow_dispatch!.inputs.source_ref!.default).toBe('master')