import { readdir, readFile } from "node:fs/promises"; import { join } from "node:path"; import { describe, expect, it } from "@suaveplan/testing/runner"; const PKG_ROOT = join(__dirname, ".."); const SRC = join(PKG_ROOT, "src"); // Match a *use* of the global as a free identifier (assignment, property access, // or function call), not occurrences inside compound identifiers like `AudioDocument`. const BROWSER_GLOBALS = [ "window", "document", "navigator", "AudioContext", "AudioBuffer", ]; function stripCommentsAndStrings(src: string): string { // Drop /* ... */, // ..., and "..." / '...' content so we only see code. return src .replace(/\/\*[\s\S]*?\*\//g, " ") .replace(/\/\/[^\n]*/g, " ") .replace(/"(?:[^"\\]|\\.)*"/g, '""') .replace(/'(?:[^'\\]|\\.)*'/g, "''"); } function refersToGlobal(src: string, name: string): boolean { const code = stripCommentsAndStrings(src); // Token must start after a non-identifier char and be followed by a // property access (.), call site (() or [), assignment (= but not ==), // semicolon, comma, or close paren. const re = new RegExp( `(?:^|[^A-Za-z0-9_.])${name}(?=[.(\\[]|\\s*=[^=])`, "m" ); return re.test(code); } async function* walk(dir: string): AsyncGenerator { const entries = await readdir(dir, { withFileTypes: true }); for (const e of entries) { const full = join(dir, e.name); if (e.isDirectory()) yield* walk(full); else if ( e.isFile() && e.name.endsWith(".ts") && !e.name.endsWith(".test.ts") ) yield full; } } describe("audio-cli purity", () => { it("source references no browser-only globals", { timeout: 5000, }, async () => { for await (const path of walk(SRC)) { const src = await readFile(path, "utf8"); for (const global of BROWSER_GLOBALS) { expect( refersToGlobal(src, global), `${path} references ${global}` ).toBe(false); } } }); it("declared dependency set is limited to the spec list", { timeout: 5000, }, async () => { const raw = await readFile(join(PKG_ROOT, "package.json"), "utf8"); const pkg = JSON.parse(raw) as { dependencies?: Record; }; const allowed = new Set([ "@suaveplan/audio-codecs", "@suaveplan/audio-core", "@suaveplan/audio-engine-server", "@suaveplan/error", "@suaveplan/types", "@opentelemetry/api", "tslib", "zod", ]); for (const dep of Object.keys(pkg.dependencies ?? {})) { expect(allowed.has(dep), `unexpected runtime dep ${dep}`).toBe( true ); } }); });