178 lines
4.7 KiB
JavaScript
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
|
|
}
|
|
});
|