feat: 添加 CaseRun native 热重载

This commit is contained in:
root
2026-07-17 03:11:45 +02:00
parent 1aaf2044f8
commit 73bda4bf1e
12 changed files with 474 additions and 23 deletions
+6
View File
@@ -6,6 +6,12 @@
"scripts": {
"deps": "node ../../scripts/worktree-deps.mjs --web",
"dev": "bun run deps --quiet && vite",
"dev:native-caserun": "CHOKIDAR_USEPOLLING=1 HWLAB_CASERUN_NATIVE_TEST=1 bun run dev",
"caserun:native": "bun scripts/caserun-native-supervisor.ts",
"caserun:native:start": "bun run scripts/caserun-native-service.ts start",
"caserun:native:stop": "bun run scripts/caserun-native-service.ts stop",
"caserun:native:status": "bun run scripts/caserun-native-service.ts status",
"caserun:native:logs": "bun run scripts/caserun-native-service.ts logs",
"check": "bun run deps --quiet && bun run scripts/tsc-check.ts && bun test scripts",
"check:tsc": "bun run deps --quiet && bun run scripts/tsc-check.ts",
"check:tsc-strict": "bun run deps --quiet && bun run scripts/tsc-check.ts --strict",
@@ -0,0 +1,40 @@
import assert from "node:assert/strict";
import test from "node:test";
import { listNativeCases, readNativeEvents, readNativeRun, resetNativeFixtures, startNativeRun } from "./caserun-native-fixtures";
test("native CaseRun fixtures expose six deterministic API-owned states", () => {
resetNativeFixtures();
const cases = listNativeCases();
assert.equal(cases.mode, "native-test");
assert.deepEqual(cases.cases.map((item) => item.caseId), ["native-queued", "native-running", "native-completed", "native-failed", "native-blocked", "native-canceled"]);
const started = startNativeRun("native-completed");
assert.equal(started?.status, "queued");
assert.equal(started?.terminal, false);
const running = readNativeRun(started!.runId);
assert.equal(running?.status, "running");
assert.equal(running?.terminal, false);
const completed = readNativeRun(started!.runId);
assert.equal(completed?.status, "completed");
assert.equal(completed?.terminal, true);
assert.match(completed?.references.aggregate.sha256 ?? "", /^[a-f0-9]{64}$/);
const events = readNativeEvents(started!.runId);
assert.deepEqual(events?.events.map((event) => event.status), ["queued", "running", "completed"]);
assert.equal(events?.mode, "native-test");
});
test("native blockers and cancellation come from the API fixture", () => {
resetNativeFixtures();
const blocked = startNativeRun("native-blocked")!;
const blockedTerminal = readNativeRun(blocked.runId)!;
assert.equal(blockedTerminal.terminal, true);
assert.equal(blockedTerminal.blocker?.code, "native-capability-blocked");
const canceled = startNativeRun("native-canceled")!;
readNativeRun(canceled.runId);
const canceledTerminal = readNativeRun(canceled.runId)!;
assert.equal(canceledTerminal.status, "canceled");
assert.equal(canceledTerminal.terminal, true);
});
@@ -0,0 +1,122 @@
import { createHash } from "node:crypto";
type NativeStatus = "queued" | "running" | "completed" | "failed" | "blocked" | "canceled";
interface NativeStep {
status: NativeStatus;
stage: string;
terminal: boolean;
blocker?: { code: string; summary: string; layer: string };
}
interface NativeRunState {
runId: string;
caseId: string;
cursor: number;
steps: NativeStep[];
}
const MODE = "native-test";
const CONTRACT_VERSION = "caserun.v1";
const BASE_TIME = Date.parse("2026-07-17T00:00:00.000Z");
const scenarios: Record<string, { title: string; steps: NativeStep[] }> = {
"native-queued": { title: "Native queued", steps: [{ status: "queued", stage: "queued", terminal: false }] },
"native-running": { title: "Native running", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "agent-running", terminal: false }] },
"native-completed": { title: "Native completed", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "agent-running", terminal: false }, { status: "completed", stage: "completed", terminal: true }] },
"native-failed": { title: "Native failed", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "building", terminal: false }, { status: "failed", stage: "failed", terminal: true, blocker: { code: "native-build-failed", summary: "Deterministic native build failure", layer: "build" } }] },
"native-blocked": { title: "Native blocked", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "blocked", stage: "preparing", terminal: true, blocker: { code: "native-capability-blocked", summary: "Deterministic native capability blocker", layer: "hwpod" } }] },
"native-canceled": { title: "Native canceled", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "agent-running", terminal: false }, { status: "canceled", stage: "canceled", terminal: true }] }
};
let runSequence = 0;
const runs = new Map<string, NativeRunState>();
function timestamp(index: number): string {
return new Date(BASE_TIME + index * 1000).toISOString();
}
function aggregateSha(runId: string, status: NativeStatus): string {
return createHash("sha256").update(`${MODE}:${runId}:${status}`).digest("hex");
}
function runResponse(run: NativeRunState) {
const step = run.steps[run.cursor] ?? run.steps.at(-1)!;
return {
ok: true,
mode: MODE,
contractVersion: CONTRACT_VERSION,
runId: run.runId,
caseId: run.caseId,
status: step.status,
stage: step.stage,
terminal: step.terminal,
blocker: step.blocker ?? null,
createdAt: timestamp(0),
updatedAt: timestamp(run.cursor),
facts: {
jobId: `job-${run.runId}`,
artifactCount: step.status === "completed" ? 2 : 0,
evidenceStatus: step.status
},
references: {
status: { href: `/v1/caserun/runs/${run.runId}` },
events: { href: `/v1/caserun/runs/${run.runId}/events`, count: run.cursor + 1 },
aggregate: { href: `/v1/caserun/runs/${run.runId}/aggregate`, sha256: aggregateSha(run.runId, step.status) },
manifest: step.status === "completed" ? { href: `/v1/caserun/runs/${run.runId}/manifest`, count: 2 } : null,
replay: null,
trace: { traceId: `trace-${run.runId}` },
hwpod: { hwpodId: "native-hwpod", nodeId: "native-node" }
},
sourceAuthority: { mode: MODE, sequence: "request-driven" },
error: null,
statusUrl: `/v1/caserun/runs/${run.runId}`
};
}
export function listNativeCases() {
const cases = Object.entries(scenarios).map(([caseId, scenario]) => ({
caseId,
title: scenario.title,
mode: MODE,
available: true,
hwpodSpec: "native-test",
runtime: { sequence: scenario.steps.map((step) => step.status) }
}));
return { ok: true, mode: MODE, contractVersion: CONTRACT_VERSION, count: cases.length, cases, caseAuthority: { mode: MODE } };
}
export function startNativeRun(caseId: string) {
const scenario = scenarios[caseId];
if (!scenario) return null;
runSequence += 1;
const runId = `${caseId}-${runSequence}`;
const run: NativeRunState = { runId, caseId, cursor: 0, steps: scenario.steps };
runs.set(runId, run);
return runResponse(run);
}
export function readNativeRun(runId: string, advance = true) {
const run = runs.get(runId);
if (!run) return null;
if (advance && run.cursor < run.steps.length - 1) run.cursor += 1;
return runResponse(run);
}
export function readNativeEvents(runId: string) {
const run = runs.get(runId);
if (!run) return null;
const current = runResponse(run);
const events = run.steps.slice(0, run.cursor + 1).map((step, index) => ({
at: timestamp(index),
status: step.status,
stage: step.stage,
payload: { mode: MODE, terminal: step.terminal, blocker: step.blocker ?? null }
}));
return { ok: true, mode: MODE, contractVersion: CONTRACT_VERSION, runId, caseId: run.caseId, status: current.status, stage: current.stage, terminal: current.terminal, sourceAuthority: current.sourceAuthority, events };
}
export function resetNativeFixtures(): void {
runSequence = 0;
runs.clear();
}
@@ -0,0 +1,61 @@
import { listNativeCases, readNativeEvents, readNativeRun, startNativeRun } from "./caserun-native-fixtures";
const port = Number(process.env.HWLAB_CASERUN_NATIVE_PORT || 4316);
const hostname = process.env.HWLAB_CASERUN_NATIVE_HOST || "127.0.0.1";
function json(data: unknown, status = 200): Response {
return Response.json(data, { status, headers: { "cache-control": "no-store" } });
}
function notFound(kind: string, id?: string): Response {
return json({ ok: false, mode: "native-test", error: { code: `${kind}-not-found`, message: id ? `${kind} ${id} not found` : `${kind} not found` } }, 404);
}
function authSession(): Record<string, unknown> {
return {
ok: true,
authenticated: true,
mode: "native-test",
sessionKind: "native-test",
user: { id: "native-admin", username: "native-admin", displayName: "Native Admin", role: "admin", status: "active" },
capabilities: { admin: "available" },
access: { nav: { profileId: "native-test", allowedIds: ["*"], valuesRedacted: true } },
valuesRedacted: true
};
}
const server = Bun.serve({
hostname,
port,
development: true,
async fetch(request) {
const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/health") return json({ ok: true, mode: "native-test" });
if (request.method === "POST" && url.pathname === "/auth/login") {
return Response.json(authSession(), { headers: { "cache-control": "no-store", "set-cookie": "hwlab_session=native-test; Path=/; HttpOnly; SameSite=Lax" } });
}
if (request.method === "GET" && url.pathname === "/auth/session") return json(authSession());
if (request.method === "GET" && url.pathname === "/v1/caserun/cases") return json(listNativeCases());
if (request.method === "POST" && url.pathname === "/v1/caserun/runs") {
const body = await request.json().catch(() => null) as { caseId?: string } | null;
if (!body?.caseId) return json({ ok: false, mode: "native-test", error: { code: "case-id-required", message: "caseId is required" } }, 400);
const run = startNativeRun(body.caseId);
return run ? json(run, 202) : notFound("case", body.caseId);
}
const eventsMatch = url.pathname.match(/^\/v1\/caserun\/runs\/([^/]+)\/events$/);
if (request.method === "GET" && eventsMatch) {
const runId = decodeURIComponent(eventsMatch[1]);
const events = readNativeEvents(runId);
return events ? json(events) : notFound("run", runId);
}
const runMatch = url.pathname.match(/^\/v1\/caserun\/runs\/([^/]+)$/);
if (request.method === "GET" && runMatch) {
const runId = decodeURIComponent(runMatch[1]);
const run = readNativeRun(runId);
return run ? json(run) : notFound("run", runId);
}
return notFound("route", url.pathname);
}
});
console.log(JSON.stringify({ event: "caserun-native-listening", mode: "native-test", hostname: server.hostname, port: server.port }));
@@ -0,0 +1,62 @@
import { mkdirSync, openSync, readFileSync, rmSync } from "node:fs";
import { resolve } from "node:path";
const action = process.argv[2];
const root = process.cwd();
const stateDir = resolve(root, ".state/caserun-native");
const pidPath = resolve(stateDir, "server.pid");
const logPath = resolve(stateDir, "server.log");
function readPid(): number | null {
try {
const pid = Number(readFileSync(pidPath, "utf8").trim());
return Number.isInteger(pid) && pid > 0 ? pid : null;
} catch {
return null;
}
}
function isRunning(pid: number | null): boolean {
if (!pid) return false;
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
mkdirSync(stateDir, { recursive: true });
const pid = readPid();
if (action === "start") {
if (isRunning(pid)) {
console.log(JSON.stringify({ ok: true, action, status: "already-running", pid, logPath }));
process.exit(0);
}
rmSync(pidPath, { force: true });
const logFd = openSync(logPath, "a");
const child = Bun.spawn([process.execPath, "scripts/caserun-native-supervisor.ts"], { cwd: root, stdin: "ignore", stdout: logFd, stderr: logFd });
child.unref();
await Bun.write(pidPath, `${child.pid}\n`);
console.log(JSON.stringify({ ok: true, action, status: "started", pid: child.pid, logPath }));
} else if (action === "stop") {
if (!isRunning(pid)) {
rmSync(pidPath, { force: true });
console.log(JSON.stringify({ ok: true, action, status: "stopped" }));
} else {
process.kill(pid!, "SIGTERM");
rmSync(pidPath, { force: true });
console.log(JSON.stringify({ ok: true, action, status: "stopping", pid }));
}
} else if (action === "status") {
const running = isRunning(pid);
console.log(JSON.stringify({ ok: running, action, status: running ? "running" : "stopped", pid: running ? pid : null, logPath }));
process.exit(running ? 0 : 1);
} else if (action === "logs") {
const lines = readFileSync(logPath, "utf8").trimEnd().split("\n").slice(-80);
console.log(lines.join("\n"));
} else {
console.error("usage: bun run scripts/caserun-native-service.ts <start|stop|status|logs>");
process.exit(2);
}
@@ -0,0 +1,49 @@
import { statSync } from "node:fs";
import { resolve } from "node:path";
const root = process.cwd();
const watchedFiles = [
resolve(root, "scripts/caserun-native-server.ts"),
resolve(root, "scripts/caserun-native-fixtures.ts")
];
let child: ReturnType<typeof Bun.spawn> | null = null;
let signature = sourceSignature();
let stopping = false;
function sourceSignature(): string {
return watchedFiles.map((path) => `${path}:${statSync(path).mtimeMs}:${statSync(path).size}`).join("|");
}
function startChild(reason: string): void {
child = Bun.spawn([process.execPath, "scripts/caserun-native-server.ts"], { cwd: root, stdin: "ignore", stdout: "inherit", stderr: "inherit" });
console.log(JSON.stringify({ event: "caserun-native-reload", mode: "native-test", reason, pid: child.pid }));
void child.exited.then((exitCode) => {
if (!stopping && child?.exitCode === exitCode) console.log(JSON.stringify({ event: "caserun-native-exit", exitCode }));
});
}
function stopChild(): void {
if (child && child.exitCode === null) child.kill("SIGTERM");
child = null;
}
startChild("initial");
const timer = setInterval(() => {
const nextSignature = sourceSignature();
if (nextSignature === signature) return;
signature = nextSignature;
stopChild();
startChild("source-change");
}, 500);
function shutdown(): void {
stopping = true;
clearInterval(timer);
stopChild();
process.exit(0);
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
@@ -0,0 +1,32 @@
export default async function caserunNativeSmoke({ page, goto, wait, screenshot, recordStep }) {
await goto("/workbench");
const panel = page.locator("#caserun-panel");
await panel.waitFor({ state: "visible", timeout: 20000 });
await page.locator("#caserun-case-select").selectOption("native-completed");
await page.locator("#caserun-start").click();
await page.locator("#caserun-refresh").click();
await wait(300);
const state = await page.locator("#caserun-status").evaluate((element) => element.textContent || "");
const events = await page.locator("#caserun-event-text").textContent();
const aggregate = await page.locator("#caserun-aggregate").textContent();
const conditions = {
mode: state.includes("native-test"),
terminal: state.includes("true"),
completed: aggregate?.includes("completed") === true,
queuedEvent: events?.includes("queued") === true,
runningEvent: events?.includes("running") === true,
completedEvent: events?.includes("completed") === true,
aggregate: aggregate?.includes("aggregate pending") === false
};
const image = await screenshot("caserun-native-completed.png");
recordStep("caserun-native-completed", { ok: Object.values(conditions).every(Boolean), conditions });
return {
ok: Object.values(conditions).every(Boolean),
mode: "native-test",
conditions,
screenshot: image,
finalUrl: page.url(),
valuesRedacted: true
};
}
@@ -20,12 +20,13 @@ const artifactCount = computed(() => Number(caserun.currentRun?.facts?.artifactC
const jobId = computed(() => String(caserun.currentRun?.facts?.jobId ?? ""));
const aggregateHash = computed(() => caserun.currentRun?.references?.aggregate?.sha256 ?? "");
const blockerText = computed(() => caserun.currentRun?.blocker ? `${caserun.currentRun.blocker.code}: ${caserun.currentRun.blocker.summary}` : "");
const fixtureMode = computed(() => String(caserun.currentRun?.mode ?? caserun.cases[0]?.mode ?? "live"));
onMounted(async () => {
await caserun.refreshCases();
selectedCaseId.value = caserun.selectedCaseId;
pollTimer = setInterval(() => {
if (["queued", "running"].includes(runStatus.value)) void caserun.refreshRun();
if (caserun.shouldPoll) void caserun.refreshRun();
}, 5000);
});
@@ -64,6 +65,8 @@ async function startRun(): Promise<void> {
<dl class="caserun-meta" id="caserun-status">
<div><dt>run</dt><dd>{{ caserun.currentRun?.runId || '-' }}</dd></div>
<div><dt>stage</dt><dd>{{ caserun.currentRun?.stage || '-' }}</dd></div>
<div><dt>mode</dt><dd>{{ fixtureMode }}</dd></div>
<div><dt>terminal</dt><dd>{{ caserun.currentRun?.terminal ?? '-' }}</dd></div>
<div><dt>job</dt><dd>{{ jobId || '-' }}</dd></div>
<div><dt>artifacts</dt><dd>{{ artifactCount }}</dd></div>
</dl>
@@ -0,0 +1,49 @@
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, expect, test, vi } from "vitest";
const { casesRequest, startRequest, runRequest, eventsRequest } = vi.hoisted(() => ({
casesRequest: vi.fn(),
startRequest: vi.fn(),
runRequest: vi.fn(),
eventsRequest: vi.fn()
}));
vi.mock("@/api", () => ({
caserunAPI: { cases: casesRequest, startRun: startRequest, run: runRequest, events: eventsRequest }
}));
import { useCaseRunStore } from "./caserun";
beforeEach(() => {
setActivePinia(createPinia());
casesRequest.mockReset();
startRequest.mockReset();
runRequest.mockReset();
eventsRequest.mockReset();
});
test("polling follows API terminal instead of status names", async () => {
runRequest.mockResolvedValue({ ok: true, data: { runId: "run-1", caseId: "native-completed", status: "custom-progress", stage: "agent-running", terminal: false } });
eventsRequest.mockResolvedValue({ ok: true, data: { runId: "run-1", events: [] } });
const store = useCaseRunStore();
await store.refreshRun("run-1");
expect(store.shouldPoll).toBe(true);
runRequest.mockResolvedValue({ ok: true, data: { runId: "run-1", caseId: "native-completed", status: "running", stage: "completed", terminal: true } });
await store.refreshRun("run-1");
expect(store.shouldPoll).toBe(false);
});
test("start keeps blocker and aggregate from API read model", async () => {
startRequest.mockResolvedValue({ ok: true, data: { runId: "run-2", caseId: "native-blocked", status: "queued", stage: "queued", terminal: false } });
runRequest.mockResolvedValue({ ok: true, data: { runId: "run-2", caseId: "native-blocked", status: "blocked", stage: "preparing", terminal: true, blocker: { code: "native-capability-blocked", summary: "blocked" }, references: { aggregate: { sha256: "abc123" } } } });
eventsRequest.mockResolvedValue({ ok: true, data: { runId: "run-2", events: [{ status: "blocked", stage: "preparing" }] } });
const store = useCaseRunStore();
await store.startRun("native-blocked");
expect(store.currentRun?.blocker?.code).toBe("native-capability-blocked");
expect(store.currentRun?.references?.aggregate?.sha256).toBe("abc123");
expect(store.shouldPoll).toBe(false);
});
+2 -1
View File
@@ -11,6 +11,7 @@ export const useCaseRunStore = defineStore("caserun", () => {
const starting = ref(false);
const error = ref<string | null>(null);
const selectedCaseId = computed(() => currentRun.value?.caseId ?? cases.value[0]?.caseId ?? "");
const shouldPoll = computed(() => Boolean(currentRun.value?.runId && currentRun.value.terminal === false));
async function refreshCases(): Promise<void> {
loading.value = true;
@@ -53,5 +54,5 @@ export const useCaseRunStore = defineStore("caserun", () => {
if (result.ok && result.data?.runId === runId) events.value = result.data.events ?? [];
}
return { cases, currentRun, events, loading, starting, error, selectedCaseId, refreshCases, startRun, refreshRun, refreshEvents };
return { cases, currentRun, events, loading, starting, error, selectedCaseId, shouldPoll, refreshCases, startRun, refreshRun, refreshEvents };
});
+3 -3
View File
@@ -516,12 +516,12 @@ export interface HwlabNodeUpdateMetadata {
observedAt: string;
}
export interface CaseRunCaseSummary { caseId: string; title?: string; mode?: string; available?: boolean; hwpodSpec?: string; subject?: Record<string, unknown> | null; expected?: Record<string, unknown> | null; runtime?: Record<string, unknown> | null; [key: string]: unknown }
export interface CaseRunCasesResponse { ok?: boolean; contractVersion?: string; count?: number; cases?: CaseRunCaseSummary[]; caseAuthority?: Record<string, unknown>; [key: string]: unknown }
export interface CaseRunCasesResponse { ok?: boolean; mode?: string; contractVersion?: string; count?: number; cases?: CaseRunCaseSummary[]; caseAuthority?: Record<string, unknown>; [key: string]: unknown }
export interface CaseRunEvent { at?: string; status?: string; stage?: string; payload?: Record<string, unknown>; [key: string]: unknown }
export interface CaseRunBlocker { code: string; summary: string; layer?: string | null; details?: Record<string, unknown> | null }
export interface CaseRunReference { href?: string; ref?: string | null; sha256?: string | null; count?: number; relationship?: Record<string, unknown> | null; [key: string]: unknown }
export interface CaseRunRunResponse { ok?: boolean; contractVersion?: string; runId?: string; caseId?: string; status?: string; stage?: string; terminal?: boolean; blocker?: CaseRunBlocker | null; createdAt?: string; updatedAt?: string; facts?: { jobId?: string | null; artifactCount?: number; evidenceStatus?: string | null }; references?: { status?: CaseRunReference; events?: CaseRunReference; aggregate?: CaseRunReference; manifest?: CaseRunReference | null; replay?: CaseRunReference | null; trace?: Record<string, unknown> | null; hwpod?: Record<string, unknown> | null }; sourceAuthority?: Record<string, unknown>; error?: { code?: string; message?: string } | null; statusUrl?: string; [key: string]: unknown }
export interface CaseRunEventsResponse { ok?: boolean; contractVersion?: string; runId?: string; caseId?: string; status?: string; stage?: string; terminal?: boolean; sourceAuthority?: Record<string, unknown>; events?: CaseRunEvent[]; [key: string]: unknown }
export interface CaseRunRunResponse { ok?: boolean; mode?: string; contractVersion?: string; runId?: string; caseId?: string; status?: string; stage?: string; terminal?: boolean; blocker?: CaseRunBlocker | null; createdAt?: string; updatedAt?: string; facts?: { jobId?: string | null; artifactCount?: number; evidenceStatus?: string | null }; references?: { status?: CaseRunReference; events?: CaseRunReference; aggregate?: CaseRunReference; manifest?: CaseRunReference | null; replay?: CaseRunReference | null; trace?: Record<string, unknown> | null; hwpod?: Record<string, unknown> | null }; sourceAuthority?: Record<string, unknown>; error?: { code?: string; message?: string } | null; statusUrl?: string; [key: string]: unknown }
export interface CaseRunEventsResponse { ok?: boolean; mode?: string; contractVersion?: string; runId?: string; caseId?: string; status?: string; stage?: string; terminal?: boolean; sourceAuthority?: Record<string, unknown>; events?: CaseRunEvent[]; [key: string]: unknown }
export interface ProviderProfilesResponse { ok?: boolean; status?: string; profiles?: Array<Record<string, unknown>>; [key: string]: unknown }
export interface ProviderProfileCatalogItem {
profile: string;
+44 -18
View File
@@ -1,25 +1,51 @@
import { Agent } from "node:http";
import { fileURLToPath, URL } from "node:url";
import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";
import { defineConfig, loadEnv } from "vite";
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
},
build: {
outDir: "dist",
emptyOutDir: true,
sourcemap: false,
rollupOptions: {
output: {
entryFileNames: "app.js",
chunkFileNames: "assets/[name].js",
assetFileNames: "assets/[name][extname]"
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const nativeCaseRunEnabled = (process.env.HWLAB_CASERUN_NATIVE_TEST ?? env.HWLAB_CASERUN_NATIVE_TEST) === "1";
const nativeCaseRunTarget = process.env.HWLAB_CASERUN_NATIVE_URL ?? env.HWLAB_CASERUN_NATIVE_URL ?? "http://127.0.0.1:4316";
const nativeCaseRunAgent = new Agent({ keepAlive: true });
return {
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
},
server: nativeCaseRunEnabled ? {
watch: {
usePolling: true,
interval: 300
},
proxy: {
"/auth": {
target: nativeCaseRunTarget,
changeOrigin: false,
agent: nativeCaseRunAgent
},
"/v1/caserun": {
target: nativeCaseRunTarget,
changeOrigin: false,
agent: nativeCaseRunAgent
}
}
} : undefined,
build: {
outDir: "dist",
emptyOutDir: true,
sourcemap: false,
rollupOptions: {
output: {
entryFileNames: "app.js",
chunkFileNames: "assets/[name].js",
assetFileNames: "assets/[name][extname]"
}
}
}
}
};
});