Skip to content

Commit 26d0926

Browse files
committed
build(desktop): stop shipping the renderer's dependency tree twice
`app.asar` carried a second copy of the renderer's dependency sources. Vite emits everything the renderer loads into `dist-renderer`; electron-builder then walked the production dependency closure of `apps/desktop/package.json` and packaged those same packages again, as sources nothing ever loads. A `files` denylist was the first attempt and could not hold. It has to name every transitive package too, so excluding `mermaid` did not stop electron-builder from independently collecting what `mermaid` hoists, and `d3-*` never matched the bare `d3` meta-package. Move the nine renderer-only direct dependencies to `devDependencies` instead. That removes them and everything only reachable through them from the closure, so the denylist is deleted rather than extended. Notices follow what ships, not what npm places in node_modules. The generator now unions the Node production closure with the closure of the renderer roots, declared once in `maka.rendererBundledDependencies` and read by both the generator and the packaged-artifact check so the two cannot drift. Without that, moving these packages out of the production closure would have dropped the notices for code that still ships inside `dist-renderer`. Verification moved from the manifest to the artifact. `assertPackagedDependencyClosure` reads `app.asar` directly: the nine must be absent, `@xterm/headless` and `@xterm/addon-unicode11` must be present because the PTY stack loads them, and every shipped renderer package must have a notice. A manifest assertion would have stayed green through a change in how electron-builder walks the closure, a transitive package leaking back in, or the notices regressing. Measured on the archive: 307 packages / 131.39 MiB of `node_modules` before, 240 / 122.98 MiB after — 67 packages and 8.41 MiB out, none added. Generated-by: Claude Code
1 parent b9a514f commit 26d0926

8 files changed

Lines changed: 255 additions & 35 deletions

apps/desktop/electron-builder.config.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ export default {
66
directories: {
77
output: 'release',
88
},
9+
// `files` names what to include; the production dependency closure of
10+
// `package.json` comes along automatically. Renderer-only packages are kept
11+
// out of that closure by living in `devDependencies` — vite bundles them into
12+
// `dist-renderer`, so a second copy of their sources in `app.asar` is never
13+
// loaded. A hand-written exclude list was tried first and could not hold: it
14+
// has to name every transitive package too, and it silently went stale.
15+
//
16+
// `@xterm/headless` stays a dependency on purpose — `@maka/runtime` imports
17+
// it for the PTY stack, so only the renderer-side xterm packages moved.
918
files: ['dist/**/*', 'dist-renderer/**/*', 'package.json', '!**/__tests__/**'],
1019
extraResources: [
1120
{

apps/desktop/package.json

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,44 +41,57 @@
4141
"smoke:browser": "npm run build:workspace-deps && npm run build:main && electron scripts/browser-observe-act-smoke.mjs"
4242
},
4343
"dependencies": {
44-
"@astryxdesign/core": "0.4.0",
45-
"@astryxdesign/theme-neutral": "0.4.0",
46-
"@dnd-kit/core": "^6.3.1",
47-
"@dnd-kit/sortable": "^10.0.0",
4844
"@jackwener/opencli": "1.8.6",
4945
"@maka/computer-use": "0.1.0",
5046
"@maka/core": "0.1.0",
5147
"@maka/mcp": "0.1.0",
5248
"@maka/runtime": "0.1.0",
5349
"@maka/runtime-host": "0.1.0",
5450
"@maka/storage": "0.1.0",
55-
"@maka/ui": "0.1.0",
56-
"@xterm/addon-fit": "^0.11.0",
57-
"@xterm/xterm": "^6.0.0",
5851
"electron-updater": "^6.8.9",
5952
"node-pty": "^1.2.0-beta.15",
6053
"qrcode": "^1.5.4",
61-
"react": "^19.2.1",
62-
"react-dom": "^19.2.1",
6354
"ws": "^8.21.0",
6455
"zod": "^4.4.3"
6556
},
6657
"devDependencies": {
6758
"@ant-design/icons-svg": "4.5.0",
59+
"@astryxdesign/core": "0.4.0",
60+
"@astryxdesign/theme-neutral": "0.4.0",
61+
"@dnd-kit/core": "^6.3.1",
62+
"@dnd-kit/sortable": "^10.0.0",
6863
"@fontsource-variable/geist": "^5.3.0",
6964
"@fontsource-variable/geist-mono": "^5.3.0",
65+
"@maka/ui": "0.1.0",
7066
"@playwright/test": "^1.62.1",
7167
"@storybook/react-vite": "^10.5.5",
7268
"@types/react": "^19.2.18",
7369
"@types/react-dom": "^19.2.4",
7470
"@types/ws": "^8.18.1",
7571
"@vitejs/plugin-react": "^6.0.5",
72+
"@xterm/addon-fit": "^0.11.0",
73+
"@xterm/xterm": "^6.0.0",
7674
"electron": "43.2.0",
7775
"electron-builder": "26.15.3",
7876
"esbuild": "^0.27.7",
7977
"linkedom": "^0.18.13",
78+
"react": "^19.2.1",
79+
"react-dom": "^19.2.1",
8080
"simple-icons": "16.28.0",
8181
"storybook": "^10.4.6",
8282
"vite": "^8.1.5"
83+
},
84+
"maka": {
85+
"rendererBundledDependencies": [
86+
"@astryxdesign/core",
87+
"@astryxdesign/theme-neutral",
88+
"@dnd-kit/core",
89+
"@dnd-kit/sortable",
90+
"@maka/ui",
91+
"@xterm/addon-fit",
92+
"@xterm/xterm",
93+
"react",
94+
"react-dom"
95+
]
8396
}
8497
}

apps/desktop/resources/licenses/npm/THIRD_PARTY_NOTICES.txt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3466,6 +3466,66 @@ MIT License
34663466

34673467
================================================================================
34683468

3469+
Package: @types/react@19.2.18
3470+
Declared license: MIT
3471+
Selected license: MIT
3472+
Repository: https://github.com/DefinitelyTyped/DefinitelyTyped.git#types/react
3473+
3474+
--- LICENSE ---
3475+
MIT License
3476+
3477+
Copyright (c) Microsoft Corporation.
3478+
3479+
Permission is hereby granted, free of charge, to any person obtaining a copy
3480+
of this software and associated documentation files (the "Software"), to deal
3481+
in the Software without restriction, including without limitation the rights
3482+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3483+
copies of the Software, and to permit persons to whom the Software is
3484+
furnished to do so, subject to the following conditions:
3485+
3486+
The above copyright notice and this permission notice shall be included in all
3487+
copies or substantial portions of the Software.
3488+
3489+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3490+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3491+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3492+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3493+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3494+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3495+
SOFTWARE
3496+
3497+
================================================================================
3498+
3499+
Package: @types/react-dom@19.2.4
3500+
Declared license: MIT
3501+
Selected license: MIT
3502+
Repository: https://github.com/DefinitelyTyped/DefinitelyTyped.git#types/react-dom
3503+
3504+
--- LICENSE ---
3505+
MIT License
3506+
3507+
Copyright (c) Microsoft Corporation.
3508+
3509+
Permission is hereby granted, free of charge, to any person obtaining a copy
3510+
of this software and associated documentation files (the "Software"), to deal
3511+
in the Software without restriction, including without limitation the rights
3512+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3513+
copies of the Software, and to permit persons to whom the Software is
3514+
furnished to do so, subject to the following conditions:
3515+
3516+
The above copyright notice and this permission notice shall be included in all
3517+
copies or substantial portions of the Software.
3518+
3519+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3520+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3521+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3522+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3523+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3524+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3525+
SOFTWARE
3526+
3527+
================================================================================
3528+
34693529
Package: @types/retry@0.12.0
34703530
Declared license: MIT
34713531
Selected license: MIT

package-lock.json

Lines changed: 17 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/generate-third-party-notices.mjs

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const TARGETS = {
1919
underline: '====================================================',
2020
outputPath: join(repoRoot, 'apps/desktop/resources/licenses/npm/THIRD_PARTY_NOTICES.txt'),
2121
validateAssets: true,
22+
// Only this target bundles a renderer, so only this one unions the vite
23+
// module graph into its notices. The CLI ships its production closure and
24+
// nothing else, and must not be asked for renderer roots it has none of.
25+
manifestPath: join(repoRoot, 'apps/desktop/package.json'),
2226
},
2327
cli: {
2428
workspaceName: 'maka-agent',
@@ -164,11 +168,11 @@ function normalizeText(text) {
164168
.trim();
165169
}
166170

167-
function collectWorkspaceClosure(workspaceName) {
171+
function npmWorkspaceTree(workspaceName, omitDev) {
168172
const tree = JSON.parse(
169173
execFileSync(
170174
'npm',
171-
['ls', '--workspace', workspaceName, '--omit=dev', '--all', '--json'],
175+
['ls', '--workspace', workspaceName, ...(omitDev ? ['--omit=dev'] : []), '--all', '--json'],
172176
npmSpawnOptions({
173177
cwd: repoRoot,
174178
encoding: 'utf8',
@@ -178,21 +182,74 @@ function collectWorkspaceClosure(workspaceName) {
178182
);
179183
const workspace = tree.dependencies?.[workspaceName];
180184
if (!workspace) throw new Error(`npm ls did not return the ${workspaceName} workspace`);
185+
return workspace;
186+
}
187+
188+
function collectInto(packages, dependencies) {
189+
for (const [name, dependency] of Object.entries(dependencies ?? {})) {
190+
if (!dependency || typeof dependency !== 'object') continue;
191+
if (!name.startsWith(WORKSPACE_PREFIX) && typeof dependency.version === 'string') {
192+
packages.set(`${name}@${dependency.version}`, { name, version: dependency.version });
193+
}
194+
collectInto(packages, dependency.dependencies);
195+
}
196+
}
197+
198+
/**
199+
* Packages the desktop workspace declares as bundled into the renderer.
200+
*
201+
* They live in `devDependencies` so electron-builder keeps a second, unread
202+
* copy of their sources out of `app.asar` — but vite bundles them into
203+
* `dist-renderer`, which the archive does carry. So they ship, and their
204+
* notices have to ship with them. Reading the list from the manifest keeps this
205+
* generator and `packaged-dependency-closure` from drifting apart.
206+
*/
207+
function rendererBundledRoots() {
208+
if (!target.manifestPath) return [];
209+
const declared = readJson(target.manifestPath)?.maka?.rendererBundledDependencies;
210+
if (!Array.isArray(declared) || declared.length === 0) {
211+
throw new Error(
212+
`${target.manifestPath}: maka.rendererBundledDependencies must list the renderer roots`,
213+
);
214+
}
215+
return declared;
216+
}
181217

218+
/**
219+
* What the packaged application actually carries: the Node production closure
220+
* that lands in `app.asar/node_modules`, plus everything reachable from the
221+
* renderer roots, which lands inside the vite bundle. Generating from the
222+
* production closure alone would drop notices for code that still ships.
223+
*/
224+
function collectWorkspaceClosure(workspaceName) {
182225
const packages = new Map();
183-
const visit = (dependencies) => {
184-
for (const [name, dependency] of Object.entries(dependencies ?? {})) {
185-
if (!dependency || typeof dependency !== 'object') continue;
186-
if (!name.startsWith(WORKSPACE_PREFIX) && typeof dependency.version === 'string') {
187-
packages.set(`${name}@${dependency.version}`, {
188-
name,
189-
version: dependency.version,
190-
});
191-
}
192-
visit(dependency.dependencies);
226+
collectInto(packages, npmWorkspaceTree(workspaceName, true).dependencies);
227+
228+
const roots = new Set(rendererBundledRoots());
229+
if (roots.size === 0) {
230+
return [...packages.values()].sort(
231+
(left, right) =>
232+
left.name.localeCompare(right.name) || left.version.localeCompare(right.version),
233+
);
234+
}
235+
const full = npmWorkspaceTree(workspaceName, false).dependencies ?? {};
236+
for (const [name, dependency] of Object.entries(full)) {
237+
if (!roots.has(name) || !dependency || typeof dependency !== 'object') continue;
238+
if (!name.startsWith(WORKSPACE_PREFIX) && typeof dependency.version === 'string') {
239+
packages.set(`${name}@${dependency.version}`, { name, version: dependency.version });
193240
}
194-
};
195-
visit(workspace.dependencies);
241+
collectInto(packages, dependency.dependencies);
242+
}
243+
// A workspace root (`@maka/ui`) carries no notice of its own — it is first
244+
// party — but its dependencies do, so absence from `packages` is only a
245+
// problem for a third-party root.
246+
const missing = [...roots].filter(
247+
(root) => !root.startsWith(WORKSPACE_PREFIX) && !Object.hasOwn(full, root),
248+
);
249+
if (missing.length > 0) {
250+
throw new Error(`renderer roots absent from the dependency tree: ${missing.join(', ')}`);
251+
}
252+
196253
return [...packages.values()].sort(
197254
(left, right) =>
198255
left.name.localeCompare(right.name) || left.version.localeCompare(right.version),

scripts/verify-macos-arm64-dmg.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
1414
import { FILESYSTEM_WORKER_PROTOCOL_VERSION } from '../packages/runtime/dist/filesystem-worker/protocol.js';
1515
import {
1616
assertMissing,
17+
assertPackagedDependencyClosure,
1718
assertPackagedResources,
1819
isolatedUserEnv,
1920
makePtyProbe,
@@ -125,6 +126,7 @@ export async function verifyPackagedMacApp(
125126

126127
await requirePath(executable);
127128
await assertPackagedResources(resources, { requirePath, forbidPath });
129+
await assertPackagedDependencyClosure(resources);
128130
await requirePath(join(resources, 'git', 'bin', 'git'));
129131

130132
const executableArchitectures = await run('lipo', ['-archs', executable]);

0 commit comments

Comments
 (0)