Files
id/apps/web/vite.config.ts
T
zeekayandGitHub a24ed753a4 rewrite: Vite + @hanzo/gui monorepo (drops CF Worker + Next.js) (#2)
Replaces the Cloudflare Worker (hanzo.id-worker) and the Next.js portal
with a pnpm monorepo following the a-monorepo/id pattern.

Layout:
  apps/web/        Vite + React 19 SPA, embeds @hanzo/gui shell
    k8s/           Deployment(2) + Service + Ingress (4 hosts, 4 TLS)
  pkgs/shared/     @hanzo/id-shared — TenantConfig, resolveTenant,
                                       loadBrand (browser + node)
  pkgs/auth/       @hanzo/id-auth   — AuthClient (wraps @hanzo/iam REST)
                                       + LoginForm/SignupForm/ForgotForm/OTPForm
  pkgs/idv/        @hanzo/id-idv    — pluggable IDV (stub, persona,
                                       onfido, veriff) behind one
                                       IDVProvider interface
  legacy-nextjs/   Frozen — Next.js predecessor. Delete after v0.1.0 ships.
  Dockerfile       Two-stage: pnpm build → hanzoai/static:0.4.1 serves /spa
  README.md
  LLM.md           Architecture, dev, deploy, cutover plan

Tenant resolution: hostname → TenantConfig (orgId, iamUrl, clientId,
appName, publicOrigin, brandPackage). Built-in defaults for
hanzo.id/lux.id/zoo.id/pars.id; runtime override via
IAM_TENANT_CONFIG_JSON env (served as /config.json at pod startup).

Brand resolution: each per-org brand pkg (@hanzo/brand, @luxfi/brand,
@zooai/brand, @parsdao/brand) ships brand.json. The Vite plugin
brandJsonPlugin emits /brand/<pkg>/brand.json verbatim; the browser
fetches the right one based on the resolved tenant. No bundle bloat.

IDV: stub (default for dev), persona, onfido, veriff. Each adapter
implements `IDVProvider` from pkgs/idv/src/provider.ts. Swap providers
with one registration call at boot — portal code unchanged.

Cutover (separate ops PR — not in this commit):
  1. Tag + push image ghcr.io/hanzoai/id:0.1.0
  2. Apply k8s manifests, cert-manager issues TLS
  3. Remove CF Worker routes for hanzo.id/lux.id/zoo.id/pars.id
  4. CF A records → 129.212.164.5 (hanzo ingress LB)
  5. Archive hanzo.id-worker repo
2026-05-24 06:00:20 -07:00

79 lines
2.3 KiB
TypeScript

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import { readFileSync, existsSync } from 'fs'
/**
* Per-brand brand.json copy plugin.
*
* Each per-org brand package (`@hanzo/brand`, `@luxfi/brand`, `@zooai/brand`,
* `@parsdao/brand`) ships a `brand.json` at the package root. We serve them
* verbatim at `/brand/<pkg>/brand.json` so the runtime tenant resolver can
* fetch the right one based on hostname.
*
* Assets (logos, favicons) are imported by URL inside the per-brand `brand.json`
* (CDN URLs in production), so no further asset copying is needed.
*/
const BRAND_PACKAGES = ['@hanzo/brand', '@luxfi/brand', '@zooai/brand', '@parsdao/brand']
function brandJsonPlugin() {
return {
name: 'hanzo-id-brand-json',
configureServer(server: any) {
server.middlewares.use((req: any, res: any, next: any) => {
const m = /^\/brand\/(.+)\/brand\.json$/.exec(req.url ?? '')
if (!m) return next()
const pkg = decodeURIComponent(m[1]!)
if (!BRAND_PACKAGES.includes(pkg)) {
res.statusCode = 404
return res.end()
}
try {
const path = require.resolve(`${pkg}/brand.json`)
res.setHeader('Content-Type', 'application/json')
res.setHeader('Cache-Control', 'no-store')
return res.end(readFileSync(path, 'utf8'))
} catch {
res.statusCode = 404
return res.end()
}
})
},
generateBundle(this: any) {
for (const pkg of BRAND_PACKAGES) {
try {
const path = require.resolve(`${pkg}/brand.json`)
if (!existsSync(path)) continue
this.emitFile({
type: 'asset',
fileName: `brand/${pkg}/brand.json`,
source: readFileSync(path, 'utf8'),
})
} catch {
// pkg not installed — skip silently; only the brands listed in
// package.json deps actually need their JSON shipped.
}
}
},
}
}
export default defineConfig({
plugins: [react(), brandJsonPlugin()],
resolve: {
alias: { '@': resolve(__dirname, 'src') },
},
server: {
port: 5173,
host: '0.0.0.0',
},
preview: {
port: 5174,
host: '0.0.0.0',
},
build: {
target: 'es2022',
sourcemap: true,
},
})