Files
hermes-agent/scripts/build/webapp.mjs
T
Bear Huddlestonandemozilla b5eb5ee7a8 feat(webapp): carry the browser-hosted Desktop's server pieces from #93508
Carried from BearHuddleston's #93508 onto main. The Webapp relay and the
profile-incarnation fence stay out: carried code follows main's
named-profile rule and refuses a missing or deleted profile home.

- Webapp routes: one-use launch tickets, window handoff, and gateway
  WebSocket tickets minted for the signed-in browser
- Browser surface policy and the SPA document policy
- A host terminal for the browser that survives reconnects
- Staged browser uploads and file tickets; file.attach takes a staged upload
- hermes sessions adopt
- Webapp bundle build helpers and the renderer freshness check
- Regenerated gateway contracts

Co-authored-by: emozilla <emozilla@nousresearch.com>
2026-10-25 03:22:14 -07:00

38 lines
2.0 KiB
JavaScript

#!/usr/bin/env node
// Pure browser-renderer compilation. The workspace is prepared by the caller
// (apps/desktop/scripts/build-webapp.mjs); the receipt certifies the checkout.
import { existsSync, readdirSync } from 'node:fs'
import { join, relative, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { productOutput, withProduct, workspaceTool } from './frontend-common.mjs'
import { buildInputs, recordProduct, sourceHash } from './freshness.mjs'
export async function buildWebapp({ source, workspace, out }) {
if (!workspace) throw new Error('A prepared Webapp workspace is required')
const app = 'apps/desktop'
workspace = resolve(workspace)
;({ source, out } = productOutput(source, out, [
// The output lives inside the Desktop workspace: protect its siblings, not the directory.
...readdirSync(join(resolve(source), app)).filter(name => !['dist', 'dist-webapp', 'build'].includes(name)).map(name => `${app}/${name}`),
'apps/shared', 'package.json', 'package-lock.json', 'scripts/build', 'node_modules',
relative(resolve(source), workspace),
]))
const inputs = buildInputs(source, 'webapp')
// The workspace is what compiles; a stale mirror must not publish under a current receipt.
if (sourceHash(workspace, 'webapp') !== inputs.sourceHash) {
throw new Error(`Prepared Webapp workspace ${workspace} does not match ${source}; prepare it again`)
}
const { build } = await import(pathToFileURL(workspaceTool(workspace, app, 'vite')).href)
await withProduct(out, async (product, scratch) => {
await build({
root: join(workspace, app),
configLoader: 'runner',
cacheDir: join(scratch, 'vite-cache'),
build: { outDir: product, emptyOutDir: true },
})
if (!existsSync(join(product, 'index.html'))) throw new Error('Webapp build did not produce index.html')
recordProduct({ source, product: 'webapp', out: product, inputs })
}, { source })
return { out, index: join(out, 'index.html') }
}