import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { assertCloudWebDistFresh, buildCloudWebDist, cloudWebDistAliasFiles, inspectCloudWebDistFreshness } from "./dist-contract.ts"; test("Cloud Web dist build emits Vue assets and route aliases", async () => { const rootDir = makeCloudWebFixture(); try { const distDir = await buildCloudWebDist(rootDir); assert.equal(distDir, path.join(rootDir, "dist")); const html = read(path.join(distDir, "index.html")); assert.match(html, /
<\/div>/u); assert.match(html, /src="\/app\.js"/u); assert.match(html, /href="\/assets\/index\.css"/u); assert.match(html, /href="\/assets\/favicon\.svg"/u); assert.match(read(path.join(distDir, "app.js")), /fixture-app/u); assert.match(read(path.join(distDir, "assets/index.css")), /fixture-shell/u); assert.equal(read(path.join(distDir, "help.md")), read(path.join(rootDir, "help.md"))); for (const relativePath of cloudWebDistAliasFiles) { assert.equal(read(path.join(distDir, relativePath)), html, `alias asset ${relativePath}`); } const freshness = await assertCloudWebDistFresh(rootDir); assert.equal(freshness.status, "pass"); assert.deepEqual(freshness.mismatches, []); } finally { fs.rmSync(rootDir, { recursive: true, force: true }); } }); test("Cloud Web dist freshness reports stale generated app asset", async () => { const rootDir = makeCloudWebFixture(); try { await buildCloudWebDist(rootDir); fs.writeFileSync(path.join(rootDir, "src/App.vue"), "\n"); const freshness = await inspectCloudWebDistFreshness(rootDir); assert.equal(freshness.status, "blocked"); assert.ok(freshness.mismatches.includes("app.js")); assert.equal(freshness.files.find((file) => file.path === "app.js")?.status, "mismatch"); } finally { fs.rmSync(rootDir, { recursive: true, force: true }); } }); function makeCloudWebFixture(): string { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "hwlab-cloud-web-dist-")); linkPackageNodeModules(rootDir); write(rootDir, "package.json", JSON.stringify({ type: "module", dependencies: { "@vitejs/plugin-vue": "^5.2.3", vite: "5.4.10", vue: "^3.4.0" } }, null, 2)); write(rootDir, "index.html", [ "", "", " ", " ", " ", " fixture", " ", "
", "", "" ].join("\n")); write(rootDir, "vite.config.ts", [ "import vue from '@vitejs/plugin-vue';", "export default {", " plugins: [vue()],", " build: {", " outDir: 'dist',", " emptyOutDir: true,", " rollupOptions: { output: { entryFileNames: 'app.js', chunkFileNames: 'assets/[name].js', assetFileNames: 'assets/[name][extname]' } }", " }", "};", "" ].join("\n")); write(rootDir, "src/main.ts", "import { createApp } from 'vue';\nimport App from './App.vue';\nimport './style.css';\ncreateApp(App).mount('#app');\n"); write(rootDir, "src/App.vue", "\n"); write(rootDir, "src/style.css", ".fixture-shell { color: #0f766e; }\n"); write(rootDir, "favicon.svg", "\n"); write(rootDir, "help.md", "# fixture help\n"); return rootDir; } function linkPackageNodeModules(rootDir: string): void { const packageNodeModules = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "node_modules"); const fixtureNodeModules = path.join(rootDir, "node_modules"); if (!fs.existsSync(packageNodeModules) || fs.existsSync(fixtureNodeModules)) return; fs.symlinkSync(packageNodeModules, fixtureNodeModules, "dir"); } function write(rootDir: string, relativePath: string, text: string): void { const filePath = path.join(rootDir, relativePath); fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, text); } function read(filePath: string): string { return fs.readFileSync(filePath, "utf8"); }