fix(packaging): exclude root notes from app files (#23326)

Exclude root notes files from packaging while retaining nested runtime notes assets.

Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Neil
2026-09-26 21:45:35 -07:00
committed by GitHub
co-authored by lurunzi Codex
parent fa3642256f
commit 1c982ff2d9
2 changed files with 62 additions and 3 deletions
+2
View File
@@ -210,6 +210,8 @@ module.exports = {
// it is gitignored, but exclude it defensively so a stray local capture at
// package time never bloats app.asar.
'!pr-evidence{,/**/*}',
// Local build logs and rollback copies are never application resources.
'!notes{,/**/*}',
// Why: local agent/tooling directories may contain worktree symlink loops;
// they are never runtime inputs and must not be traversed by electron-builder.
'!{.claude,.grok,.agents,.codex}{,/**/*}',
@@ -1,8 +1,8 @@
import { existsSync } from 'node:fs'
import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { chmod, lstat, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { dirname, join } from 'node:path'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { writeMobileWebBundleFixtureTree } from './mobile-web-bundle-fixture-tree.mjs'
@@ -11,7 +11,7 @@ const SRC_MAIN_DIR = join(REPO_ROOT, 'src', 'main')
const require = createRequire(import.meta.url)
const electronBuilderConfig = require('../electron-builder.config.cjs')
const { FileMatcher } = require('app-builder-lib/out/fileMatcher')
const { copyFiles, FileMatcher } = require('app-builder-lib/out/fileMatcher')
const FpmTarget = require('app-builder-lib/out/targets/FpmTarget').default
const electronBuilderNativeRebuild = require('./electron-builder-native-rebuild.cjs')
@@ -37,6 +37,7 @@ describe('electron-builder config', () => {
'!tests{,/**/*}',
'!examples{,/**/*}',
'!pr-evidence{,/**/*}',
'!notes{,/**/*}',
'!{.claude,.grok,.agents,.codex}{,/**/*}',
'!Casks{,/**/*}',
'!{AGENTS.md,CLAUDE.md,DEVELOPING.md,bundle-size-progress.md,ORCHESTRATION_IMPLEMENTATION_CHECKLIST.md,ORCHESTRATION_STRUCTURED_OUTPUT_DESIGN.md}',
@@ -63,6 +64,62 @@ describe('electron-builder config', () => {
expect(packs('out/main/index.js')).toBe(true)
})
it.each(['file', 'directory'])('keeps a root notes %s out of app.asar', async (kind) => {
const root = await mkdtemp(join(tmpdir(), 'orca-packaging-notes-'))
const source = join(root, 'app')
const destination = join(root, 'selected')
const runtimePaths = [
'package.json',
'out/main/index.js',
'out/renderer/index.html',
'out/cli/index.js',
'out/shared/index.js',
'out/main/notes/index.js',
'out/renderer/assets/notes/help.md',
'resources/notes/help.md',
'notes.txt'
]
const notesPaths =
kind === 'file'
? ['notes']
: [
'notes/build.log',
'notes/installed-orca-backup/Orca.exe',
'notes/installed-orca-backup/resources/app.asar',
'notes/orca-windows-setup.exe',
'notes/.recovery/state.json'
]
try {
for (const fixturePath of [...runtimePaths, ...notesPaths]) {
const file = join(source, fixturePath)
await mkdir(dirname(file), { recursive: true })
await writeFile(file, 'synthetic fixture\n')
}
if (kind === 'directory') {
await mkdir(join(source, 'notes', 'empty'))
}
const matcher = new FileMatcher(
source,
destination,
(value) => value,
electronBuilderConfig.files
)
// copyFiles adds the default include and prunes excluded directories during traversal.
await copyFiles([matcher])
for (const runtimePath of runtimePaths) {
expect(await readFile(join(destination, runtimePath), 'utf8')).toBe('synthetic fixture\n')
}
const isPacked = matcher.createFilter()
for (const notesPath of new Set(['notes', ...notesPaths])) {
const file = join(source, notesPath)
expect(isPacked(file, await lstat(file)), notesPath).toBe(false)
}
expect(existsSync(join(destination, 'notes'))).toBe(false)
} finally {
await rm(root, { recursive: true, force: true })
}
})
// Why: `files` is an all-negation list, so electron-builder's default `**/*` packs
// anything without an explicit `!` entry — examples/ landed without one and shipped
// hostile-panel, the adversarial containment fixture, into 1.4.160-rc.3's app.asar.