fix(build): protect symlinked desktop source inputs
A source child can resolve outside the checkout. The output guard must protect its canonical path before the builder checks prepared inputs. Exclude generated dist/build trees so in-tree products can still rebuild. Keep explicit prepared inputs protected even inside generated trees. The public buildDesktop regression test first failed with a missing-icon error instead of an overlap error. All 7 desktop-builder tests now pass, including real cold/warm builds under apps/desktop/build/products. Node syntax checks and git diff --check pass. No full suite or native packaging ran.
This commit is contained in:
@@ -29,9 +29,11 @@ export async function buildDesktop({ source, out, icons, stamp, nativeDeps, type
|
||||
if (!icons || !stamp || !nativeDeps) throw new Error('icons, stamp and nativeDeps are required prepared inputs')
|
||||
const app = 'apps/desktop'
|
||||
;({ source, out } = productOutput(source, out, [
|
||||
// productOutput owns source/generated classification. Protect prepared
|
||||
// inputs explicitly, including dependency symlinks outside the checkout.
|
||||
`${app}/node_modules`, 'node_modules',
|
||||
// Source children can be symlinks outside the checkout. Protect their
|
||||
// canonical paths, but leave generated dist/build trees to productOutput.
|
||||
...readdirSync(join(resolve(source), app)).filter(name => !['dist', 'build'].includes(name)).map(name => `${app}/${name}`),
|
||||
`${app}/scripts`, 'scripts/build', 'package.json', 'package-lock.json',
|
||||
'apps/shared', 'node_modules',
|
||||
...[join(resolve(icons), app, 'public'), stamp, nativeDeps].map(input => relative(resolve(source), resolve(input))),
|
||||
]))
|
||||
const publicIcons = join(resolve(icons), app, 'public')
|
||||
|
||||
@@ -97,6 +97,27 @@ test('desktop compiler consumes explicit immutable inputs, replaces variants, an
|
||||
expect(files(input.source).some(([name]) => name.includes('.vite') || name.endsWith('tsbuildinfo'))).toBe(false)
|
||||
}, 60000)
|
||||
|
||||
test('desktop output cannot overlap a source directory symlinked outside the checkout', async () => {
|
||||
const { buildDesktop } = await import('../scripts/build/desktop.mjs')
|
||||
const root = mkdtempSync(join(tmpdir(), 'desktop symlinked source-'))
|
||||
roots.push(root)
|
||||
const source = join(root, 'source')
|
||||
const externalSource = join(root, 'external-source')
|
||||
mkdirSync(join(source, 'apps/desktop'), { recursive: true })
|
||||
put(join(externalSource, 'index.js'), 'source must not change')
|
||||
symlinkSync(externalSource, join(source, 'apps/desktop/src'), 'junction')
|
||||
const out = join(externalSource, 'product')
|
||||
const before = files(root)
|
||||
|
||||
// Missing prepared inputs must not hide a failure to reject source overlap.
|
||||
await expect(buildDesktop({ source, out,
|
||||
icons: join(root, 'icons'), stamp: join(root, 'stamp.json'), nativeDeps: join(root, 'native'),
|
||||
})).rejects.toThrow(/Output must not overlap build inputs/)
|
||||
expect(files(root)).toEqual(before)
|
||||
expect(readdirSync(externalSource)).toEqual(['index.js'])
|
||||
expect(existsSync(out)).toBe(false)
|
||||
})
|
||||
|
||||
test('in-tree desktop products rebuild after build exists without replacing prepared inputs', async () => {
|
||||
const { buildDesktop } = await import('../scripts/build/desktop.mjs')
|
||||
const { productCurrent } = await import('../scripts/build/freshness.mjs')
|
||||
|
||||
Reference in New Issue
Block a user