From cce89a480856755827eae4ed5383df3dcc3ee569 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 2 Sep 2026 23:47:19 +0800 Subject: [PATCH 1/6] fix(desktop): omit Windows PTY build intermediates Exclude node-gyp projects and link scratch files from the existing global file set while preserving the native addons and ConPTY runtime helpers. This avoids creating a platform-only catch-all matcher that would reintroduce tests and renderer side-files. Exercise electron-builder normalization and dependency collection against representative runtime and build-output fixtures. Generated-by: Codex --- apps/desktop/electron-builder.config.mjs | 5 + scripts/verify-packaged-app.test.mjs | 117 ++++++++++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/apps/desktop/electron-builder.config.mjs b/apps/desktop/electron-builder.config.mjs index 5893b1e875..2e3130b8ee 100644 --- a/apps/desktop/electron-builder.config.mjs +++ b/apps/desktop/electron-builder.config.mjs @@ -90,6 +90,11 @@ const baseDesktopBuilderConfig = { 'dist/**/*', 'dist-renderer/**/*', 'package.json', + // Keep node-gyp's checkout-specific projects and link intermediates out. + // Native addons, the Unix spawn helper and ConPTY's DLL/helper are runtime files. + '!**/node_modules/node-pty/build/!(Release){,/**}', + '!**/node_modules/node-pty/build/Release/!(*.node|spawn-helper|conpty){,/**}', + '!**/node_modules/node-pty/node-addon-api{,/**}', '!node_modules/@maka/{mcp,runtime,runtime-host}/package.json', '!**/__tests__/**', // FakeBackend and the Desktop E2E candidate bootstrap live under diff --git a/scripts/verify-packaged-app.test.mjs b/scripts/verify-packaged-app.test.mjs index e5ada284f9..268ef84cdc 100644 --- a/scripts/verify-packaged-app.test.mjs +++ b/scripts/verify-packaged-app.test.mjs @@ -20,15 +20,130 @@ import assert from 'node:assert/strict'; import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; -import { dirname, join } from 'node:path'; +import { dirname, join, relative } from 'node:path'; import { after, describe, test } from 'node:test'; import { createPackage } from '@electron/asar'; +import { + FileMatcher, + getMainFileMatchers, + getNodeModuleFileMatcher, +} from 'app-builder-lib/out/fileMatcher.js'; +import { NodeModuleCopyHelper } from 'app-builder-lib/out/util/NodeModuleCopyHelper.js'; +import { computeFileSets } from 'app-builder-lib/out/util/appFileCopier.js'; +import { doMergeConfigs } from 'app-builder-lib/out/util/config/config.js'; +import { resolveDesktopBuilderConfig } from '../apps/desktop/electron-builder.config.mjs'; import { asarLookupPath, assertPackagedDependencyClosure, assertPackagedResources, } from './verify-packaged-app.mjs'; +test('Windows file rules keep test code and renderer side-files out of the app', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'maka-app-package-')); + t.after(() => rm(root, { recursive: true, force: true })); + const runtimeFiles = [ + 'package.json', + 'dist/main/index.js', + 'dist-renderer/index.html', + 'dist/renderer/computer-use-overlay/index.js', + ]; + for (const name of [ + ...runtimeFiles, + 'dist/main/__tests__/about.test.js', + 'dist/main/test-only/bootstrap.js', + 'dist/renderer/agent-graph-panel.js', + ]) { + const path = join(root, name); + await mkdir(dirname(path), { recursive: true }); + await writeFile(path, name); + } + // Config normalization runs before file matching in electron-builder. + const base = resolveDesktopBuilderConfig({}); + const config = doMergeConfigs([{ ...base, files: [...base.files] }]); + const packager = { + config, + projectDir: root, + buildResourcesDir: 'build', + debugLogger: { isEnabled: false }, + }; + const platformPackager = { info: packager }; + const output = join(root, 'release'); + const matchers = getMainFileMatchers( + root, + output, + (s) => s, + config.win, + platformPackager, + output, + false, + ); + const sets = await computeFileSets(matchers, null, platformPackager, false); + const files = [ + ...new Set( + sets.flatMap((set) => set.files).map((file) => relative(root, file).replaceAll('\\', '/')), + ), + ]; + assert.deepEqual(files.sort(), runtimeFiles.sort()); +}); + +test('Desktop packaging keeps node-pty runtime files without its build intermediates', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'maka-pty-package-')); + t.after(() => rm(root, { recursive: true, force: true })); + const moduleRoot = join(root, 'node_modules', 'node-pty'); + const runtimeFiles = [ + 'package.json', + 'LICENSE', + 'lib/index.js', + 'lib/worker/conoutSocketWorker.js', + 'build/Release/conpty.node', + 'build/Release/conpty_console_list.node', + 'build/Release/pty.node', + 'build/Release/spawn-helper', + 'build/Release/conpty/conpty.dll', + 'build/Release/conpty/OpenConsole.exe', + 'prebuilds/win32-x64/conpty.node', + 'prebuilds/win32-x64/conpty/conpty.dll', + 'prebuilds/win32-x64/conpty/OpenConsole.exe', + ]; + const buildFiles = [ + 'build/conpty.vcxproj', + 'build/conpty.vcxproj.filters', + 'build/Release/conpty.exp', + 'build/Release/conpty.iobj', + 'build/Release/conpty.ipdb', + 'build/Release/obj/conpty/conpty.tlog/CL.command.1.tlog', + 'build/Release/obj/conpty/conpty.node.recipe', + 'node-addon-api/node_addon_api_except.vcxproj', + 'node-addon-api/Release/obj/node_addon_api_except/n.nativecodeanalysis.xml', + ]; + for (const name of [...runtimeFiles, ...buildFiles]) { + const path = join(moduleRoot, name); + await mkdir(dirname(path), { recursive: true }); + await writeFile(path, name); + } + const base = resolveDesktopBuilderConfig({}); + const config = doMergeConfigs([{ ...base, files: [...base.files] }]); + const packager = { + config, + appInfo: { type: 'module' }, + debugLogger: { isEnabled: false }, + getWorkspaceRoot: async () => root, + }; + const destination = join(root, 'output'); + const mainMatcher = getNodeModuleFileMatcher(root, destination, (s) => s, config.win, packager); + const matcher = new FileMatcher(moduleRoot, destination, (s) => s, mainMatcher.patterns); + const copier = new NodeModuleCopyHelper(matcher, packager); + const files = await copier.collectNodeModules( + { name: 'node-pty', dir: moduleRoot }, + [], + join('node_modules', 'node-pty'), + ); + assert.deepEqual( + files.map((file) => relative(moduleRoot, file).replaceAll('\\', '/')).sort(), + runtimeFiles.sort(), + ); +}); + test('packaged resources forbid the retired bundled Git distribution', async () => { const required = []; const forbidden = []; From 86c804ef72b34f5b7c5f32526d854f8584df7b79 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 2 Sep 2026 23:48:22 +0800 Subject: [PATCH 2/6] fix(desktop): remove variable Windows linker metadata Apply /Brepro and /DEBUG:NONE through the existing Windows command runner. /Brepro derives PE timestamps from content; /DEBUG:NONE removes unshipped PDB identity and paths after node-gyp adds /DEBUG. Two clean builds remained byte-identical after removing /INCREMENTAL:NO. Removing /DEBUG:NONE reintroduced four unpacked-payload differences, so the two remaining flags are the minimal set. Generated-by: Codex --- scripts/package-windows-x64.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/package-windows-x64.mjs b/scripts/package-windows-x64.mjs index c3194af0f2..ce957306e3 100644 --- a/scripts/package-windows-x64.mjs +++ b/scripts/package-windows-x64.mjs @@ -66,12 +66,19 @@ export function runCommand( { spawnProcess = spawn, platform = process.platform } = {}, ) { return new Promise((resolve, reject) => { + // The release does not ship PDBs. Remove their random IDs and paths from + // native binaries, and let the linker derive timestamps from the content. + // _LINK_ is appended after command-line flags, including node-gyp's /DEBUG. + const env = { + ...process.env, + _LINK_: [process.env._LINK_, '/Brepro /DEBUG:NONE'].filter(Boolean).join(' '), + }; // Every command here is a repository constant, so the shell that Windows // needs to reach npm.cmd introduces no quoting concern. const child = spawnProcess( command, args, - npmSpawnOptions({ cwd: repoRoot, env: process.env, stdio: 'inherit' }, platform), + npmSpawnOptions({ cwd: repoRoot, env, stdio: 'inherit' }, platform), ); child.once('error', reject); child.once('exit', (code, signal) => { From eb45073a2936e108e9ba85f7a2897892d9c8d0ad Mon Sep 17 00:00:00 2001 From: AstroHan Date: Thu, 3 Sep 2026 00:46:29 +0800 Subject: [PATCH 3/6] fix(release): omit ZIP modification timestamps The two clean Windows builds have identical compressed ZIP entry data; all remaining ZIP differences are DOS and NTFS modification times. Disable modification and access times at the existing 7-Zip invocation. This requires a one-line app-builder-lib patch because there is no archive flag passthrough. Test real ZIP bytes with different source timestamps and verify the extracted payload. Generated-by: Codex --- .github/workflows/release-windows-check.yml | 3 ++ LICENSE | 33 +++++++++++++ package.json | 1 + patches/app-builder-lib+26.15.3.patch | 11 +++++ scripts/windows-archive-repro.test.mjs | 54 +++++++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 patches/app-builder-lib+26.15.3.patch create mode 100644 scripts/windows-archive-repro.test.mjs diff --git a/.github/workflows/release-windows-check.yml b/.github/workflows/release-windows-check.yml index 32d29078a8..1ed6767d9e 100644 --- a/.github/workflows/release-windows-check.yml +++ b/.github/workflows/release-windows-check.yml @@ -166,6 +166,9 @@ jobs: - name: Package the Windows installer and ZIP run: npm run package:windows-x64 + - name: Check archive reproducibility with different input timestamps + run: npm run test:windows-archives + - name: Report Rust build cache run: kache report --format github >> "$GITHUB_STEP_SUMMARY" diff --git a/LICENSE b/LICENSE index 29435a6727..c3e2f7f43c 100644 --- a/LICENSE +++ b/LICENSE @@ -308,6 +308,39 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +electron-builder dependency patch + +Source: https://www.npmjs.com/package/app-builder-lib/v/26.15.3 +Repository: https://github.com/electron-userland/electron-builder +Version: 26.15.3 +Dependency patch: patches/app-builder-lib+26.15.3.patch +License: MIT + +Maka redistributes a source patch that omits modification and access times +from ZIP archives. The following MIT License applies to that material: + +The MIT License (MIT) + +Copyright (c) 2015 Loopline Systems + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + node-pty dependency patch Source: https://www.npmjs.com/package/node-pty/v/1.2.0-beta.15 diff --git a/package.json b/package.json index 8f26caf39a..d16d6d4b72 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "package:macos-autoupdate-next": "node scripts/package-macos-autoupdate-next.mjs", "verify:macos-autoupdate": "node scripts/verify-macos-autoupdate.mjs", "package:windows-x64": "node scripts/package-windows-x64.mjs", + "test:windows-archives": "node --test scripts/windows-archive-repro.test.mjs", "verify:windows-x64": "node scripts/verify-windows-x64.mjs", "verify:windows-installer": "node scripts/verify-windows-installer-lifecycle.mjs", "package:windows-autoupdate-next": "node scripts/package-windows-autoupdate-next.mjs", diff --git a/patches/app-builder-lib+26.15.3.patch b/patches/app-builder-lib+26.15.3.patch new file mode 100644 index 0000000000..bbd3bb6520 --- /dev/null +++ b/patches/app-builder-lib+26.15.3.patch @@ -0,0 +1,11 @@ +diff --git a/node_modules/app-builder-lib/out/targets/archive.js b/node_modules/app-builder-lib/out/targets/archive.js +--- a/node_modules/app-builder-lib/out/targets/archive.js ++++ b/node_modules/app-builder-lib/out/targets/archive.js +@@ -101,6 +101,7 @@ function compute7zCompressArgs(format, options = {}) { + // For all other formats the codec is implicit from the output file extension. + args.push(`-mm=${storeOnly ? "Copy" : "Deflate"}`); + args.push("-mcu"); ++ args.push("-mtm=off", "-mta=off"); + } + return args; + } diff --git a/scripts/windows-archive-repro.test.mjs b/scripts/windows-archive-repro.test.mjs new file mode 100644 index 0000000000..f3411aa1e4 --- /dev/null +++ b/scripts/windows-archive-repro.test.mjs @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { mkdir, mkdtemp, readFile, rm, utimes, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import test from 'node:test'; +import { promisify } from 'node:util'; +import { archive } from 'app-builder-lib/out/targets/archive.js'; +import { getPath7za } from 'app-builder-lib/out/toolsets/7zip.js'; + +test('the Windows ZIP path produces identical bytes regardless of input modification time', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'maka-archive-repro-')); + t.after(() => rm(root, { recursive: true, force: true })); + const input = join(root, 'input'); + await mkdir(join(input, 'empty'), { recursive: true }); + const file = join(input, 'payload.txt'); + await writeFile(file, 'identical payload'); + const zip = join(root, 'payload.zip'); + const results = []; + for (const date of [new Date('2024-01-01T00:00:00Z'), new Date('2024-02-02T00:00:00Z')]) { + await utimes(file, date, date); + await utimes(join(input, 'empty'), date, date); + await archive('zip', zip, input, { withoutDir: true }); + const { stdout } = await promisify(execFile)(await getPath7za(), [ + 'e', + '-so', + zip, + 'payload.txt', + ]); + assert.equal(stdout, 'identical payload'); + results.push(await readFile(zip)); + await rm(zip); + } + assert.ok(results[0].equals(results[1]), 'ZIP bytes changed with the input file timestamp'); +}); From 0f39461d6d939ee6dde6a871ca876cee4c0bd26b Mon Sep 17 00:00:00 2001 From: AstroHan Date: Thu, 3 Sep 2026 00:48:43 +0800 Subject: [PATCH 4/6] fix(release): omit NSIS input file timestamps The only differing NSIS header fields are the build-time FILETIMEs of app-64.7z and the uninstaller. SetDateSave off removes those fields at compilation without changing the embedded bytes. A real NSIS fixture verifies identical output after changing input timestamps; the full Windows release remains the acceptance test. Generated-by: Codex --- apps/desktop/build/installer.nsh | 4 +++ scripts/windows-archive-repro.test.mjs | 40 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/apps/desktop/build/installer.nsh b/apps/desktop/build/installer.nsh index 2a5d723e96..c0e2f08aef 100644 --- a/apps/desktop/build/installer.nsh +++ b/apps/desktop/build/installer.nsh @@ -76,6 +76,10 @@ # expand at their insertion points. Functions therefore read exclusively from # the $maka* variables assigned in customInit. +# The archive and uninstaller bytes are stable; their build-time mtimes are not. +# Their installation does not depend on preserving those input file dates. +SetDateSave off + !ifndef BUILD_UNINSTALLER !include LogicLib.nsh diff --git a/scripts/windows-archive-repro.test.mjs b/scripts/windows-archive-repro.test.mjs index f3411aa1e4..52e313707f 100644 --- a/scripts/windows-archive-repro.test.mjs +++ b/scripts/windows-archive-repro.test.mjs @@ -24,8 +24,10 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import test from 'node:test'; import { promisify } from 'node:util'; +import { fileURLToPath } from 'node:url'; import { archive } from 'app-builder-lib/out/targets/archive.js'; import { getPath7za } from 'app-builder-lib/out/toolsets/7zip.js'; +import { getMakeNsisPath } from 'app-builder-lib/out/toolsets/windows.js'; test('the Windows ZIP path produces identical bytes regardless of input modification time', async (t) => { const root = await mkdtemp(join(tmpdir(), 'maka-archive-repro-')); @@ -52,3 +54,41 @@ test('the Windows ZIP path produces identical bytes regardless of input modifica } assert.ok(results[0].equals(results[1]), 'ZIP bytes changed with the input file timestamp'); }); + +test('the NSIS include produces identical bytes regardless of embedded file modification time', async (t) => { + const root = await mkdtemp(join(tmpdir(), 'maka-nsis-repro-')); + t.after(() => rm(root, { recursive: true, force: true })); + const file = join(root, 'payload.txt'); + const payload = 'identical NSIS payload'; + await writeFile(file, payload); + const script = join(root, 'fixture.nsi'); + const include = fileURLToPath(new URL('../apps/desktop/build/installer.nsh', import.meta.url)); + await writeFile( + script, + ` +!define BUILD_UNINSTALLER +!include "${include}" +Name "Maka timestamp fixture" +OutFile "fixture.exe" +RequestExecutionLevel user +SetCompress off +Section + SetOutPath "$TEMP" + File "payload.txt" +SectionEnd +`, + ); + const nsis = await getMakeNsisPath(); + const results = []; + for (const date of [new Date('2024-01-01T00:00:00Z'), new Date('2024-02-02T00:00:00Z')]) { + await utimes(file, date, date); + await promisify(execFile)(nsis.path, ['-V2', script], { + cwd: root, + env: { ...process.env, ...nsis.env }, + }); + const bytes = await readFile(join(root, 'fixture.exe')); + assert.ok(bytes.includes(Buffer.from(payload)), 'NSIS fixture lost its embedded payload'); + results.push(bytes); + } + assert.ok(results[0].equals(results[1]), 'NSIS bytes changed with the embedded file timestamp'); +}); From 41ae263640af182690815c28ec6171793431a1cb Mon Sep 17 00:00:00 2001 From: AstroHan Date: Thu, 3 Sep 2026 00:49:44 +0800 Subject: [PATCH 5/6] test(release): cover archive patch changes in Windows CI Generated-by: Codex --- .github/workflows/release-windows-check.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release-windows-check.yml b/.github/workflows/release-windows-check.yml index 1ed6767d9e..05514265fd 100644 --- a/.github/workflows/release-windows-check.yml +++ b/.github/workflows/release-windows-check.yml @@ -36,6 +36,8 @@ on: - 'packages/cli/RUNTIME_HOST_PEER_DEPENDENCIES.rust.tsv' - 'packages/cli/RUNTIME_HOST_PEER_THIRD_PARTY_NOTICES.txt' - 'scripts/package-windows-x64.mjs' + - 'scripts/windows-archive-repro.test.mjs' + - 'patches/app-builder-lib+*.patch' - 'scripts/generate-runtime-host-peer-dependencies.mjs' - 'scripts/generate-runtime-host-peer-notices.mjs' - 'scripts/verify-windows-x64.mjs' From ffc1f1040afd3eab093657e25303064837e98bb9 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Fri, 4 Sep 2026 09:37:39 +0800 Subject: [PATCH 6/6] fix(release): use the canonical ASF source header Generated-by: Codex --- scripts/windows-archive-repro.test.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/windows-archive-repro.test.mjs b/scripts/windows-archive-repro.test.mjs index 52e313707f..210ad7bc21 100644 --- a/scripts/windows-archive-repro.test.mjs +++ b/scripts/windows-archive-repro.test.mjs @@ -1,18 +1,18 @@ /* * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file + * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file + * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at + * with the License. You may obtain a copy of the License at * - * https://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the + * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */