The webserver serves the repo checkout directly as static files, so index.html at the repo root must be the production build (pointing at hashed /assets/*.js) rather than the dev template (/src/main.tsx), which was being served with an unrecognized application/octet-stream MIME type and blocked by the browser. - Move src/, public/, scripts/, server/, package.json, vite.config.ts, tsconfig.json, .env(.example), and the dev index.html into app/. - Add app/scripts/deploy.mjs: copies app/dist/* into the repo root after every `vite build`, cleaning up stale hashed filenames. - Wire it into `npm run build` (app/package.json). - Update run.sh to operate from app/, and .gitignore for the new layout. - Commit the current build output (index.html, assets/, manifest, sw.js, workbox-*.js, favicon.svg, icons/, LICENSE) to the repo root. - Update README with the new project layout and deployment model.
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { defineConfig, loadEnv, type Plugin } from "vite";
|
|
import react from "@vitejs/plugin-react";
|
|
import { VitePWA } from "vite-plugin-pwa";
|
|
|
|
/** Emits the repo's LICENSE file into the build output. */
|
|
function licensePlugin(): Plugin {
|
|
return {
|
|
name: "hermes-pwa-license",
|
|
apply: "build",
|
|
generateBundle() {
|
|
this.emitFile({
|
|
type: "asset",
|
|
fileName: "LICENSE",
|
|
source: readFileSync(fileURLToPath(new URL("../LICENSE", import.meta.url)), "utf-8"),
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
// During `npm run dev` you can point the dev server at a Hermes host to avoid
|
|
// CORS entirely: set HERMES_DEV_TARGET=https://hermes.example.com in a .env file
|
|
// (or the environment) and the dev server proxies /api/* to it.
|
|
export default defineConfig(({ mode }) => {
|
|
const env = loadEnv(mode, ".", "");
|
|
const target = env.HERMES_DEV_TARGET;
|
|
|
|
return {
|
|
server: {
|
|
port: 5173,
|
|
proxy: target
|
|
? Object.fromEntries(
|
|
["/api", "/v1", "/health"].map((prefix) => [
|
|
prefix,
|
|
{
|
|
target,
|
|
changeOrigin: true,
|
|
secure: false,
|
|
// SSE needs buffering disabled.
|
|
configure: (proxy) => {
|
|
proxy.on("proxyRes", (proxyRes) => {
|
|
proxyRes.headers["x-accel-buffering"] = "no";
|
|
});
|
|
},
|
|
},
|
|
]),
|
|
)
|
|
: undefined,
|
|
},
|
|
plugins: [
|
|
react(),
|
|
licensePlugin(),
|
|
VitePWA({
|
|
registerType: "autoUpdate",
|
|
includeAssets: ["icons/apple-touch-icon.png", "favicon.svg"],
|
|
manifest: {
|
|
name: "Hermes",
|
|
short_name: "Hermes",
|
|
description: "Chat with your Hermes Agent profiles, sessions, voice and images.",
|
|
theme_color: "#000810",
|
|
background_color: "#000810",
|
|
display: "standalone",
|
|
orientation: "portrait-primary",
|
|
start_url: "/",
|
|
scope: "/",
|
|
icons: [
|
|
{ src: "icons/icon-192.png", sizes: "192x192", type: "image/png" },
|
|
{ src: "icons/icon-512.png", sizes: "512x512", type: "image/png" },
|
|
{
|
|
src: "icons/maskable-512.png",
|
|
sizes: "512x512",
|
|
type: "image/png",
|
|
purpose: "maskable",
|
|
},
|
|
],
|
|
},
|
|
workbox: {
|
|
// Never cache API traffic — it is authenticated and dynamic.
|
|
navigateFallbackDenylist: [/^\/api\//],
|
|
runtimeCaching: [
|
|
{
|
|
urlPattern: ({ url }) => url.pathname.startsWith("/api/"),
|
|
handler: "NetworkOnly",
|
|
},
|
|
],
|
|
},
|
|
devOptions: {
|
|
enabled: false,
|
|
},
|
|
}),
|
|
],
|
|
};
|
|
});
|