116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { metrics, trace } from "@opentelemetry/api";
|
|
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
|
|
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
// The complete set the spec's "Universal purity" requirement allows
|
|
// in package.json `dependencies`.
|
|
const ALLOWED_DEPENDENCIES = new Set([
|
|
"@opentelemetry/api",
|
|
"@suaveplan/error",
|
|
"@suaveplan/events",
|
|
"@suaveplan/serialize",
|
|
"@suaveplan/streams",
|
|
"@suaveplan/types",
|
|
"tslib",
|
|
"zod",
|
|
]);
|
|
|
|
// Bare-identifier globals with no legitimate use in a universal package.
|
|
const FORBIDDEN_GLOBALS = [
|
|
"window",
|
|
"document",
|
|
"navigator",
|
|
"AudioContext",
|
|
"Buffer",
|
|
];
|
|
|
|
function collectDistJs(dir: string, acc: string[] = []): string[] {
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
if (statSync(full).isDirectory()) {
|
|
collectDistJs(full, acc);
|
|
} else if (entry.endsWith(".js")) {
|
|
acc.push(full);
|
|
}
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
// Remove comments and string/template literals so import-path substrings
|
|
// (e.g. "./process/...") and message text cannot trigger false positives.
|
|
function stripStringsAndComments(src: string): string {
|
|
return src
|
|
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
.replace(/\/\/[^\n]*/g, " ")
|
|
.replace(/"(?:[^"\\]|\\.)*"/g, '""')
|
|
.replace(/'(?:[^'\\]|\\.)*'/g, "''")
|
|
.replace(/`(?:[^`\\]|\\.)*`/g, "``");
|
|
}
|
|
|
|
describe("universal purity", () => {
|
|
it("dist references no browser or Node globals", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const files = collectDistJs(join(PKG_ROOT, "dist"));
|
|
expect(files.length).toBeGreaterThan(0);
|
|
const offenders: string[] = [];
|
|
for (const file of files) {
|
|
const raw = readFileSync(file, "utf8");
|
|
const code = stripStringsAndComments(raw);
|
|
for (const name of FORBIDDEN_GLOBALS) {
|
|
const re = new RegExp(`(?<![.\\w])${name}(?![\\w])`);
|
|
if (re.test(code)) offenders.push(`${file}: ${name}`);
|
|
}
|
|
// `process` is also a domain identifier here (audio "process"
|
|
// ops/methods), so only the Node-global access pattern — a
|
|
// free `process` followed by member access — is forbidden.
|
|
if (/(?<![.\w])process\s*\./.test(code)) {
|
|
offenders.push(`${file}: process global`);
|
|
}
|
|
if (/node:fs|node:path/.test(raw)) {
|
|
offenders.push(`${file}: node: import`);
|
|
}
|
|
}
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
|
|
it("dependency set is limited to the declared workspace and zod", {
|
|
timeout: 5000,
|
|
}, () => {
|
|
const pkg = JSON.parse(
|
|
readFileSync(join(PKG_ROOT, "package.json"), "utf8")
|
|
) as { dependencies?: Record<string, string> };
|
|
const deps = Object.keys(pkg.dependencies ?? {});
|
|
expect(deps.length).toBeGreaterThan(0);
|
|
for (const dep of deps) {
|
|
expect({ dep, allowed: ALLOWED_DEPENDENCIES.has(dep) }).toEqual({
|
|
dep,
|
|
allowed: true,
|
|
});
|
|
}
|
|
});
|
|
|
|
it("importing the package mutates no global state", {
|
|
timeout: 5000,
|
|
}, async () => {
|
|
// Ignore tooling/dependency internals (vitest, zod's registry);
|
|
// the assertion is that *this package* registers nothing global.
|
|
const userGlobals = (): string[] =>
|
|
Object.keys(globalThis)
|
|
.filter((k) => !k.startsWith("__"))
|
|
.sort();
|
|
const globalsBefore = userGlobals();
|
|
const tracerProviderBefore = trace.getTracerProvider();
|
|
const meterProviderBefore = metrics.getMeterProvider();
|
|
await import("../src/index.js");
|
|
// No new globals, and no global OTel provider registered at import.
|
|
expect(userGlobals()).toEqual(globalsBefore);
|
|
expect(trace.getTracerProvider()).toBe(tracerProviderBefore);
|
|
expect(metrics.getMeterProvider()).toBe(meterProviderBefore);
|
|
});
|
|
});
|