Files

178 lines
4.7 KiB
JavaScript

import { createHash } from "node:crypto";
import {
cpSync,
copyFileSync,
existsSync,
mkdirSync,
readFileSync,
writeFileSync,
} from "node:fs";
import { basename, dirname, extname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
const webRoot = dirname(fileURLToPath(import.meta.url));
const outputRoot = resolve(webRoot, "dist");
const backendOrigin = "http://127.0.0.1:8000";
const runtimeDirectories = ["assets", "icons", "vendor"];
const runtimeFiles = [
"manifest.webmanifest",
"service-worker.js",
"speech-audio-worklet.js",
"login.html",
"login.css",
"login.js",
"hardware-control.html",
"hardware-control.css",
"hardware-control.js",
"network.js",
"app.js",
"gesture-control.js",
"pwa.js",
"console-v2.js",
"self-extension.js",
"speech-input.js",
"assistant-chat.js",
"energy-monitor.js",
"firmware-workbench-entry.js",
"firmware-workbench-theme.js",
"firmware-workbench.js",
"firmware-workbench.html",
"styles.css",
"console-v2.css",
"self-extension.css",
"speech-input.css",
"assistant-chat.css",
"energy-monitor.css",
"firmware-workbench-entry.css",
"firmware-workbench.css",
"ui-consistency.css",
"codepen-glass-theme.css",
"vue-ui.js"
];
const fingerprintedRuntimeFiles = runtimeFiles.filter((file) => (
/\.(?:css|js)$/.test(file)
&& !["service-worker.js", "speech-audio-worklet.js"].includes(file)
));
const rewrittenHtmlFiles = [
"index.html",
"login.html",
"hardware-control.html",
"firmware-workbench.html",
];
function contentHash(value) {
return createHash("sha256").update(value).digest("hex").slice(0, 12);
}
function hashedRuntimeName(file, hash) {
const extension = extname(file);
return `${basename(file, extension)}.${hash}${extension}`;
}
function rewriteRuntimeReferences(value, manifest) {
let rewritten = value;
for (const [source, target] of Object.entries(manifest)) {
const escaped = source.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
rewritten = rewritten.replace(
new RegExp(`/${escaped}(?:\\?[^\\s\"'<>)]*)?`, "g"),
`/${target}`,
);
}
return rewritten;
}
function fingerprintRuntimeAssets() {
const manifest = {};
for (const file of fingerprintedRuntimeFiles) {
const source = resolve(outputRoot, file);
if (!existsSync(source)) continue;
const contents = readFileSync(source);
const hashedName = hashedRuntimeName(file, contentHash(contents));
writeFileSync(resolve(outputRoot, hashedName), contents);
manifest[file] = hashedName;
}
for (const file of rewrittenHtmlFiles) {
const target = resolve(outputRoot, file);
if (!existsSync(target)) continue;
const source = readFileSync(target, "utf8");
writeFileSync(target, rewriteRuntimeReferences(source, manifest), "utf8");
}
const serviceWorkerPath = resolve(outputRoot, "service-worker.js");
const buildHash = contentHash(JSON.stringify(manifest));
if (existsSync(serviceWorkerPath)) {
const serviceWorker = rewriteRuntimeReferences(
readFileSync(serviceWorkerPath, "utf8"),
manifest,
).replace(
/const CACHE_VERSION = "[^"]+";/,
`const CACHE_VERSION = "cognitive-agent-pad-build-${buildHash}";`,
);
writeFileSync(serviceWorkerPath, serviceWorker, "utf8");
}
writeFileSync(
resolve(outputRoot, "runtime-assets.json"),
`${JSON.stringify({ build_hash: buildHash, assets: manifest }, null, 2)}\n`,
"utf8",
);
}
function copyRuntimeAssets() {
return {
name: "copy-n7-runtime-assets",
apply: "build",
closeBundle() {
for (const directory of runtimeDirectories) {
const source = resolve(webRoot, directory);
if (existsSync(source)) {
cpSync(source, resolve(outputRoot, directory), { recursive: true });
}
}
for (const file of runtimeFiles) {
const source = resolve(webRoot, file);
if (!existsSync(source)) continue;
const target = resolve(outputRoot, file);
mkdirSync(dirname(target), { recursive: true });
copyFileSync(source, target);
}
fingerprintRuntimeAssets();
}
};
}
function backendProxy() {
return {
target: backendOrigin,
changeOrigin: true,
configure(proxy) {
proxy.on("proxyReq", (request) => request.setHeader("origin", backendOrigin));
}
};
}
export default defineConfig({
root: webRoot,
publicDir: false,
plugins: [vue(), copyRuntimeAssets()],
server: {
strictPort: true,
fs: { allow: [webRoot] },
proxy: {
"/api": backendProxy(),
"/artifacts": backendProxy()
}
},
build: {
outDir: outputRoot,
emptyOutDir: true,
assetsDir: "vite-assets",
target: "es2022",
sourcemap: true
}
});