Let the shared output guard classify desktop products
This commit is contained in:
@@ -120,6 +120,10 @@ marker. Files, symlinks, and source directories are rejected. The exact npm
|
||||
destinations (`ui-tui/dist`, `hermes_cli/web_dist`, `apps/desktop/dist`, and
|
||||
`apps/desktop/build/native-deps`) remain rebuildable without a prior marker.
|
||||
Other in-tree products live beneath `.build/` or `apps/desktop/build/products/`.
|
||||
`frontend-common.mjs` classifies these destinations independently of which source
|
||||
children already exist, so a warm desktop rebuild uses the same rule as a fresh
|
||||
build. Explicit stamp, icon, native-tree and dependency inputs remain protected,
|
||||
even when they live beneath a generated destination.
|
||||
|
||||
### Icons and native inputs
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ 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, [
|
||||
...readdirSync(join(resolve(source), app)).filter(name => name !== 'dist').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))),
|
||||
// productOutput owns source/generated classification. Protect prepared
|
||||
// inputs explicitly, including dependency symlinks outside the checkout.
|
||||
`${app}/node_modules`, 'node_modules',
|
||||
...[join(resolve(icons), app, 'public'), stamp, nativeDeps].map(input => relative(resolve(source), resolve(input))),
|
||||
]))
|
||||
const publicIcons = join(resolve(icons), app, 'public')
|
||||
if (!existsSync(join(publicIcons, 'apple-touch-icon.png'))) throw new Error(`Missing desktop icon: ${join(publicIcons, 'apple-touch-icon.png')}`)
|
||||
|
||||
@@ -50,6 +50,9 @@ function requireOwnedOutput(out, source) {
|
||||
}
|
||||
}
|
||||
|
||||
// Classify in-tree destinations here, not by enumerating existing workspace
|
||||
// children: generated parents also exist after the first build. inputs names
|
||||
// explicit prepared trees/files; their protection wins even in generated homes.
|
||||
export function productOutput(source, out, inputs) {
|
||||
if (!source || !out) throw new Error('source and output paths are required')
|
||||
const src = realpathSync(path.resolve(source))
|
||||
|
||||
@@ -97,6 +97,31 @@ 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('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')
|
||||
const input = fixture()
|
||||
input.out = join(input.source, 'apps/desktop/build/products/desktop')
|
||||
await buildDesktop(input)
|
||||
put(join(input.source, 'apps/desktop/src/index.js'), 'document.getElementById("app").textContent = "warm rebuild"')
|
||||
await buildDesktop(input)
|
||||
expect(productCurrent({ ...input, product: 'desktop' })).toBe(true)
|
||||
const assets = files(join(input.out, 'assets')).map(([, bytes]) => Buffer.from(bytes, 'base64').toString()).join('')
|
||||
expect(assets).toContain('warm rebuild')
|
||||
|
||||
// The generated-path allowance never overrides an explicitly prepared input,
|
||||
// even when that input itself is an earlier builder-owned product.
|
||||
const built = files(input.out)
|
||||
for (const prepared of [
|
||||
{ stamp: join(input.out, 'hermes-build.json') },
|
||||
{ nativeDeps: join(input.out, 'node_modules') },
|
||||
{ icons: input.out },
|
||||
]) {
|
||||
await expect(buildDesktop({ ...input, ...prepared })).rejects.toThrow(/overlap/)
|
||||
expect(files(input.out)).toEqual(built)
|
||||
}
|
||||
}, 30000)
|
||||
|
||||
test('a prepared input changing during desktop compilation cannot publish a current receipt', async () => {
|
||||
const { buildDesktop } = await import('../scripts/build/desktop.mjs')
|
||||
const input = fixture()
|
||||
@@ -137,7 +162,7 @@ test('typecheck uses scratch state and incomplete prepared inputs fail before pu
|
||||
rmSync(join(input.icons, 'apps/desktop/public/apple-touch-icon.png'))
|
||||
await expect(buildDesktop(input)).rejects.toThrow(/icon/i)
|
||||
expect(files(input.out)).toEqual(built)
|
||||
await expect(buildDesktop({ ...input, out: join(input.source, 'apps/desktop/scripts') })).rejects.toThrow(/overlap/)
|
||||
await expect(buildDesktop({ ...input, out: join(input.source, 'apps/desktop/scripts') })).rejects.toThrow(/Output/)
|
||||
put(join(input.icons, 'apps/desktop/public/apple-touch-icon.png'), 'fresh icon')
|
||||
rmSync(join(input.nativeDeps, 'node-pty/build'), { recursive: true, force: true })
|
||||
rmSync(join(input.nativeDeps, 'node-pty/prebuilds'), { recursive: true, force: true })
|
||||
|
||||
Reference in New Issue
Block a user