import fs from "node:fs"; import { readFile } from "node:fs/promises"; import path from "node:path"; export const cloudWebDistPath = "web/hwlab-cloud-web/dist"; export const cloudWebDistBuildCommand = "node web/hwlab-cloud-web/scripts/build.mjs"; export const cloudWebDistFreshnessCommand = cloudWebDistBuildCommand; export const cloudWebDistRuntimeFiles = Object.freeze([ "index.html", "styles.css", "app.mjs", "gate-summary.mjs", "runtime.mjs", "workbench-hardware-panel.mjs", "help.md", "third_party/marked/marked.esm.js", "third_party/marked/LICENSE" ]); export const cloudWebDistAliasFiles = Object.freeze(["gate/index.html", "diagnostics/gate/index.html", "help/index.html"]); function absolutePath(rootDir, relativePath) { return path.resolve(rootDir, relativePath); } function exists(filePath) { return fs.existsSync(filePath); } async function compareFile({ files, mismatches, kind, relativePath, sourcePath, distPath }) { const entry = { kind, path: relativePath, sourcePath, distPath, status: "match" }; if (!exists(sourcePath)) { entry.status = "missing_source"; entry.reason = "source file is missing"; } else if (!exists(distPath)) { entry.status = "missing_dist"; entry.reason = "dist file is missing"; } else { const [sourceText, distText] = await Promise.all([ readFile(sourcePath, "utf8"), readFile(distPath, "utf8") ]); if (sourceText !== distText) { entry.status = "mismatch"; entry.reason = "dist file differs from source"; } } if (entry.status !== "match") { mismatches.push(relativePath); } files.push(entry); } export function buildCloudWebDist(rootDir) { const distRoot = absolutePath(rootDir, "dist"); fs.rmSync(distRoot, { recursive: true, force: true }); fs.mkdirSync(distRoot, { recursive: true }); for (const relativePath of cloudWebDistRuntimeFiles) { const distPath = absolutePath(distRoot, relativePath); fs.mkdirSync(path.dirname(distPath), { recursive: true }); fs.copyFileSync(absolutePath(rootDir, relativePath), distPath); } for (const aliasPath of cloudWebDistAliasFiles) { const distPath = absolutePath(distRoot, aliasPath); fs.mkdirSync(path.dirname(distPath), { recursive: true }); fs.copyFileSync(absolutePath(rootDir, "index.html"), distPath); } return distRoot; } export async function inspectCloudWebDistFreshness(rootDir) { const distRoot = absolutePath(rootDir, "dist"); const files = []; const mismatches = []; for (const relativePath of cloudWebDistRuntimeFiles) { await compareFile({ files, mismatches, kind: "runtime", relativePath, sourcePath: absolutePath(rootDir, relativePath), distPath: absolutePath(distRoot, relativePath) }); } for (const aliasPath of cloudWebDistAliasFiles) { await compareFile({ files, mismatches, kind: "alias", relativePath: aliasPath, sourcePath: absolutePath(rootDir, "index.html"), distPath: absolutePath(distRoot, aliasPath) }); } return { status: mismatches.length === 0 ? "pass" : "blocked", distPath: cloudWebDistPath, buildCommand: cloudWebDistBuildCommand, freshnessCommand: cloudWebDistFreshnessCommand, files, mismatches }; } export function cloudWebDistFreshnessMessage(distFreshness) { const mismatches = distFreshness.mismatches.length > 0 ? distFreshness.mismatches.join(", ") : "unknown"; return [ `Cloud Web dist is stale or missing before image build: ${mismatches}.`, `Run ${cloudWebDistBuildCommand} before publishing ${cloudWebDistPath}.` ].join(" "); } export async function assertCloudWebDistFresh(rootDir) { const distFreshness = await inspectCloudWebDistFreshness(rootDir); if (distFreshness.status !== "pass") { throw new Error(cloudWebDistFreshnessMessage(distFreshness)); } return distFreshness; }