import { readdir, readFile } from "node:fs/promises"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; const pluginRoot = "pi-web-plugins"; const forbiddenPatterns = [ { pattern: /\bfetch\s*\(/u, message: "direct browser fetch" }, { pattern: /["'`][^"'`]*\/api\//u, message: "direct PI WEB /api URL" }, { pattern: /piWebInternal/u, message: "legacy internal plugin context" }, { pattern: /(?:\.\.\/)+src\//u, message: "imports from PI WEB source internals" }, ]; describe("bundled PI WEB plugins", () => { it("use public plugin APIs instead of direct PI WEB internals", async () => { const violations: string[] = []; for (const file of await pluginSourceFiles(pluginRoot)) { const content = await readFile(file, "utf8"); for (const { pattern, message } of forbiddenPatterns) { if (pattern.test(content)) violations.push(`${file}: ${message}`); } if (content.includes("piWebUnstable") && !content.includes("@jmfederico/pi-web/plugin-api/unstable")) { violations.push(`${file}: piWebUnstable use without explicit unstable type import`); } } expect(violations).toEqual([]); }); }); async function pluginSourceFiles(root: string): Promise { const files: string[] = []; for (const entry of await readdir(root, { withFileTypes: true })) { const path = join(root, entry.name); if (entry.isDirectory()) files.push(...await pluginSourceFiles(path)); else if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts")) files.push(path); } return files; }