From 4f60a2673a96d3b677b00ebef49eece22a5991ad Mon Sep 17 00:00:00 2001 From: Devin-AXIS Date: Fri, 14 Aug 2026 17:24:14 +0800 Subject: [PATCH] fix: bound macOS signing file scans --- .../electron/sidecar-packaging.test.mjs | 7 +- patches/@electron__osx-sign@1.3.1.patch | 92 +++++++++++++++++++ pnpm-lock.yaml | 7 +- pnpm-workspace.yaml | 6 ++ 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 patches/@electron__osx-sign@1.3.1.patch diff --git a/apps/desktop/electron/sidecar-packaging.test.mjs b/apps/desktop/electron/sidecar-packaging.test.mjs index 5902edf55..b3e0d0e92 100644 --- a/apps/desktop/electron/sidecar-packaging.test.mjs +++ b/apps/desktop/electron/sidecar-packaging.test.mjs @@ -11,11 +11,13 @@ import { stageServerConstants, stageServerRuntimeTypes } from "../scripts/server const afterPack = afterPackModule.default ?? afterPackModule; it("prepares and packages the DSH CLI outside app.asar", async () => { - const [builderConfig, mainSource, buildSource, devSource] = await Promise.all([ + const [builderConfig, mainSource, buildSource, devSource, workspaceConfig, osxSignPatch] = await Promise.all([ readFile(new URL("../electron-builder.yml", import.meta.url), "utf8"), readFile(new URL("./main.mjs", import.meta.url), "utf8"), readFile(new URL("../scripts/electron-build.mjs", import.meta.url), "utf8"), readFile(new URL("../scripts/electron-dev.mjs", import.meta.url), "utf8"), + readFile(new URL("../../../pnpm-workspace.yaml", import.meta.url), "utf8"), + readFile(new URL("../../../patches/@electron__osx-sign@1.3.1.patch", import.meta.url), "utf8"), ]); assert.match(builderConfig, /from: dsh-runtime\s+to: dsh-runtime/); assert.match(builderConfig, /from: \.\.\/\.\.\/examples\/plugin-packages\/deepseek-harness/); @@ -23,6 +25,9 @@ it("prepares and packages the DSH CLI outside app.asar", async () => { assert.match(mainSource, /IPOLLOWORK_DSH_CLI/); assert.match(buildSource, /prepare-dsh-runtime\.mjs/); assert.match(devSource, /prepare-dsh-runtime\.mjs/); + assert.match(workspaceConfig, /@electron\/osx-sign@1\.3\.1.*@electron__osx-sign@1\.3\.1\.patch/); + assert.match(osxSignPatch, /maxConcurrentFileOperations = 64/); + assert.match(osxSignPatch, /withFileOperationLimit\(\(\) => getFilePathIfBinary\(filePath\)\)/); }); it("stages constants beside every compiled server module that imports them", async () => { diff --git a/patches/@electron__osx-sign@1.3.1.patch b/patches/@electron__osx-sign@1.3.1.patch new file mode 100644 index 000000000..4598a7af5 --- /dev/null +++ b/patches/@electron__osx-sign@1.3.1.patch @@ -0,0 +1,92 @@ +diff --git a/dist/cjs/util.js b/dist/cjs/util.js +index 77e64b56872bc65bcb0f60ecfbec84eb3da02c6f..a8f8840691357600548ab80869321acad8b3b5fc 100644 +--- a/dist/cjs/util.js ++++ b/dist/cjs/util.js +@@ -149,19 +149,37 @@ exports.validateOptsPlatform = validateOptsPlatform; + */ + async function walkAsync(dirPath) { + (0, exports.debugLog)('Walking... ' + dirPath); ++ const maxConcurrentFileOperations = 64; ++ let activeFileOperations = 0; ++ const waitingFileOperations = []; ++ async function withFileOperationLimit(operation) { ++ if (activeFileOperations >= maxConcurrentFileOperations) { ++ await new Promise((resolve) => waitingFileOperations.push(resolve)); ++ } ++ activeFileOperations += 1; ++ try { ++ return await operation(); ++ } ++ finally { ++ activeFileOperations -= 1; ++ const next = waitingFileOperations.shift(); ++ if (next) ++ next(); ++ } ++ } + async function _walkAsync(dirPath) { +- const children = await fs.readdir(dirPath); ++ const children = await withFileOperationLimit(() => fs.readdir(dirPath)); + return await Promise.all(children.map(async (child) => { + const filePath = path.resolve(dirPath, child); +- const stat = await fs.stat(filePath); ++ const stat = await withFileOperationLimit(() => fs.stat(filePath)); + if (stat.isFile()) { + switch (path.extname(filePath)) { + case '.cstemp': // Temporary file generated from past codesign + (0, exports.debugLog)('Removing... ' + filePath); +- await fs.remove(filePath); ++ await withFileOperationLimit(() => fs.remove(filePath)); + return null; + default: +- return await getFilePathIfBinary(filePath); ++ return await withFileOperationLimit(() => getFilePathIfBinary(filePath)); + } + } + else if (stat.isDirectory() && !stat.isSymbolicLink()) { +diff --git a/dist/esm/util.js b/dist/esm/util.js +index d3f66d54b1a88e7410d78affe17de87302db02a3..e9a347b4b7be7b2570c0a0babb2ec50bd0ac1a5f 100644 +--- a/dist/esm/util.js ++++ b/dist/esm/util.js +@@ -113,19 +113,37 @@ export async function validateOptsPlatform(opts) { + */ + export async function walkAsync(dirPath) { + debugLog('Walking... ' + dirPath); ++ const maxConcurrentFileOperations = 64; ++ let activeFileOperations = 0; ++ const waitingFileOperations = []; ++ async function withFileOperationLimit(operation) { ++ if (activeFileOperations >= maxConcurrentFileOperations) { ++ await new Promise((resolve) => waitingFileOperations.push(resolve)); ++ } ++ activeFileOperations += 1; ++ try { ++ return await operation(); ++ } ++ finally { ++ activeFileOperations -= 1; ++ const next = waitingFileOperations.shift(); ++ if (next) ++ next(); ++ } ++ } + async function _walkAsync(dirPath) { +- const children = await fs.readdir(dirPath); ++ const children = await withFileOperationLimit(() => fs.readdir(dirPath)); + return await Promise.all(children.map(async (child) => { + const filePath = path.resolve(dirPath, child); +- const stat = await fs.stat(filePath); ++ const stat = await withFileOperationLimit(() => fs.stat(filePath)); + if (stat.isFile()) { + switch (path.extname(filePath)) { + case '.cstemp': // Temporary file generated from past codesign + debugLog('Removing... ' + filePath); +- await fs.remove(filePath); ++ await withFileOperationLimit(() => fs.remove(filePath)); + return null; + default: +- return await getFilePathIfBinary(filePath); ++ return await withFileOperationLimit(() => getFilePathIfBinary(filePath)); + } + } + else if (stat.isDirectory() && !stat.isSymbolicLink()) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db0b0d47e..636ca034a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ catalogs: specifier: 19.2.4 version: 19.2.4 +patchedDependencies: + '@electron/osx-sign@1.3.1': 08103f4e834f24e2dc827072346be658e39b88ddc12526a09c4481828e405b40 + importers: .: @@ -7266,7 +7269,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/osx-sign@1.3.1': + '@electron/osx-sign@1.3.1(patch_hash=08103f4e834f24e2dc827072346be658e39b88ddc12526a09c4481828e405b40)': dependencies: compare-version: 0.1.2 debug: 4.4.3 @@ -9245,7 +9248,7 @@ snapshots: dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.5.0 - '@electron/osx-sign': 1.3.1 + '@electron/osx-sign': 1.3.1(patch_hash=08103f4e834f24e2dc827072346be658e39b88ddc12526a09c4481828e405b40) '@electron/rebuild': 3.6.1 '@electron/universal': 2.0.1 '@malept/flatpak-bundler': 0.4.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c977c691e..36c55ff02 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -41,3 +41,9 @@ allowBuilds: node-pty: true protobufjs: true sharp: true + +# electron/osx-sign 1.3.1 walks every bundled file with unbounded parallel I/O. +# The bundled DeepSeek Harness runtime is large enough to exhaust macOS file +# descriptors during release signing, so keep filesystem reads bounded. +patchedDependencies: + "@electron/osx-sign@1.3.1": patches/@electron__osx-sign@1.3.1.patch