diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7d5ad301..9c36edac 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -121,7 +121,7 @@ jobs: TAG: ${{ github.ref_name }} LATEST: ${{ needs.channel.outputs.latest }} run: | - node --disable-warning=ExperimentalWarning scripts/release.ts notes "$TAG" > "$RUNNER_TEMP/notes.md" + node scripts/release.ts notes "$TAG" > "$RUNNER_TEMP/notes.md" if [[ "$TAG" == *-* ]]; then gh release create "$TAG" --title "$TAG" --notes-file "$RUNNER_TEMP/notes.md" --prerelease else diff --git a/app/utils/log.ts b/app/utils/log.ts index 3d16d19a..47c2a6a5 100644 --- a/app/utils/log.ts +++ b/app/utils/log.ts @@ -3,7 +3,7 @@ import pino from "pino"; type Category = "server" | "config" | "agent" | "api" | "auth" | "sse"; type Level = "info" | "warn" | "error" | "debug"; -export interface Logger extends Record< +interface Logger extends Record< Level, (category: Category, message: string, ...args: unknown[]) => void > { @@ -11,41 +11,38 @@ export interface Logger extends Record< } const DEBUG_VALUES = new Set(["1", "true", "yes", "on"]); -export default createLogger(process.env, defaultDestination()); +const debugEnabled = DEBUG_VALUES.has(process.env.HEADPLANE_DEBUG_LOG?.trim().toLowerCase() ?? ""); -export function createLogger(env: NodeJS.ProcessEnv, destination: pino.DestinationStream): Logger { - const debugEnabled = DEBUG_VALUES.has(env.HEADPLANE_DEBUG_LOG?.trim().toLowerCase() ?? ""); - const root = pino( - { - level: debugEnabled ? "debug" : "info", - timestamp: () => `,"timestamp":"${new Date().toISOString()}"`, - formatters: { - level: (label: string) => ({ level: label }), - }, +const root = pino( + { + level: debugEnabled ? "debug" : "info", + timestamp: () => `,"timestamp":"${new Date().toISOString()}"`, + formatters: { + level: (label: string) => ({ level: label }), }, - destination, - ); + }, + destination(), +); - const at = - (level: Level) => - (category: Category, message: string, ...args: unknown[]) => - root[level]({ component: category }, message, ...args); +const at = + (level: Level) => + (category: Category, message: string, ...args: unknown[]) => + root[level]({ component: category }, message, ...args); - return { - debugEnabled, - info: at("info"), - warn: at("warn"), - error: at("error"), - debug: at("debug"), - }; -} +export default { + debugEnabled, + info: at("info"), + warn: at("warn"), + error: at("error"), + debug: at("debug"), +} satisfies Logger; -function defaultDestination(): pino.DestinationStream { +function destination(): pino.DestinationStream { if (process.env.NODE_ENV === "test") { return { write: (line) => console.log(line.trimEnd()) }; } - const destination = pino.destination({ dest: 1, sync: false }); - process.on("exit", () => destination.flushSync()); - return destination; + const stream = pino.destination({ dest: 1, sync: false }); + process.on("exit", () => stream.flushSync()); + return stream; } diff --git a/package.json b/package.json index 654fb9da..a19c32d4 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,13 @@ "pretypecheck": "cn build -q --content 'app/**/*.{ts,tsx}' --css app/tailwind.css -o app/utils/cn-tables.ts", "typecheck": "react-router typegen && tsc", "test:unit": "vitest run --project unit", - "test:integration": "vitest run --project integration --project integration:agent", - "harness": "node --disable-warning=ExperimentalWarning --import tsx tests/harness/cli.ts", + "test:integration": "vitest run --project integration:*", + "harness": "node tests/harness/launch.ts", "docs:dev": "cd docs && react-router dev", "docs:build": "cd docs && react-router build && node scripts/finish-build.ts", "lint": "oxlint", "format": "oxfmt", - "release": "node --disable-warning=ExperimentalWarning scripts/release.ts" + "release": "node scripts/release.ts" }, "dependencies": { "@base-ui/react": "^1.9.0", @@ -76,7 +76,6 @@ "tailwindcss": "^4.3.3", "tailwindcss-animate": "^1.0.7", "testcontainers": "^12.2.1", - "tsx": "^4.23.15", "typescript": "7.0.2", "vite": "^8.3.4", "vitest": "^5.0.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47977f66..134ffd62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -175,9 +175,6 @@ importers: testcontainers: specifier: ^12.2.1 version: 12.2.1 - tsx: - specifier: ^4.23.15 - version: 4.23.15 typescript: specifier: 7.0.2 version: 7.0.2 @@ -6349,6 +6346,7 @@ snapshots: '@esbuild/win32-arm64': 0.28.2 '@esbuild/win32-ia32': 0.28.2 '@esbuild/win32-x64': 0.28.2 + optional: true escalade@3.2.0: {} @@ -8413,6 +8411,7 @@ snapshots: esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 + optional: true tunnel-agent@0.6.0: dependencies: diff --git a/tests/harness/cli.ts b/tests/harness/cli.ts index 61088c8c..c378bd72 100644 --- a/tests/harness/cli.ts +++ b/tests/harness/cli.ts @@ -9,15 +9,10 @@ import { parseArgs } from "node:util"; import { stringify } from "yaml"; -// Headplane's modules expect the build-time globals Vite defines. -Object.assign(globalThis, { __VERSION__: "harness", __PREFIX__: "/admin" }); - -const { HARNESS_LABEL } = await import("./constants"); -const { applyScenario } = await import("./scenario"); -const { createTailnet } = await import("./tailnet"); -const { HEADSCALE_VERSIONS, LATEST_HEADSCALE } = await import("./versions"); -type Scenario = import("./scenario").Scenario; -type HeadscaleVersion = import("./versions").HeadscaleVersion; +import { HARNESS_LABEL } from "./constants"; +import { applyScenario, type Scenario } from "./scenario"; +import { createTailnet } from "./tailnet"; +import { HEADSCALE_VERSIONS, type HeadscaleVersion, LATEST_HEADSCALE } from "./versions"; const scenariosDir = join(import.meta.dirname, "scenarios"); diff --git a/tests/harness/launch.ts b/tests/harness/launch.ts new file mode 100644 index 00000000..dc90f15d --- /dev/null +++ b/tests/harness/launch.ts @@ -0,0 +1,10 @@ +import { createServer, createServerModuleRunner } from "vite"; + +const server = await createServer({ + configFile: "vitest.config.ts", + logLevel: "error", + appType: "custom", + server: { middlewareMode: true, hmr: false, watch: null }, +}); + +await createServerModuleRunner(server.environments.ssr).import("/tests/harness/cli.ts"); diff --git a/tests/unit/utils/log.test.ts b/tests/unit/utils/log.test.ts deleted file mode 100644 index 5d307ce8..00000000 --- a/tests/unit/utils/log.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, test } from "vitest"; - -import { createLogger } from "~/utils/log"; - -function capture(env: NodeJS.ProcessEnv) { - const lines: Record[] = []; - const log = createLogger(env, { write: (line) => lines.push(JSON.parse(line)) }); - return { log, lines }; -} - -describe("structured logger", () => { - test("writes JSON log entries", () => { - const { log, lines } = capture({}); - - log.info("server", "Listening on %s://%s:%s", "http", "127.0.0.1", 3000); - - expect(lines).toEqual([ - expect.objectContaining({ - level: "info", - component: "server", - msg: "Listening on http://127.0.0.1:3000", - timestamp: expect.any(String), - }), - ]); - }); - - test.for([{}, { HEADPLANE_DEBUG_LOG: "false" }, { HEADPLANE_DEBUG_LOG: "nope" }])( - "drops debug entries without HEADPLANE_DEBUG_LOG (%o)", - (env) => { - const { log, lines } = capture(env); - - log.debug("api", "GET %s", "/v1/node"); - - expect(log.debugEnabled).toBe(false); - expect(lines).toEqual([]); - }, - ); - - test.for(["1", "true", " YES ", "on"])( - "writes debug entries for HEADPLANE_DEBUG_LOG=%s", - (value) => { - const { log, lines } = capture({ HEADPLANE_DEBUG_LOG: value }); - - log.debug("api", "GET %s", "/v1/node"); - - expect(log.debugEnabled).toBe(true); - expect(lines).toEqual([ - expect.objectContaining({ level: "debug", component: "api", msg: "GET /v1/node" }), - ]); - }, - ); -}); diff --git a/vitest.config.ts b/vitest.config.ts index 87ada445..786483e9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ { extends: true, test: { - name: "integration", + name: "integration:app", include: ["tests/integration/**/*.test.ts"], exclude: ["node_modules/**", "build/**", "tests/integration/agent/**"], testTimeout: 120_000, @@ -49,7 +49,6 @@ export default defineConfig({ ], environment: "node", silent: "passed-only", - execArgv: ["--disable-warning=ExperimentalWarning"], exclude: ["node_modules/**", "build/**"], }, resolve: {