Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960import { readdirSync, readFileSync, existsSync } from "node:fs";import { join } from "node:path";import { ConfigError, loadConfig } from "../shared/config";import { validateManifest } from "../extension/manifest";import { findForbiddenCode } from "./extensionCheck";
export interface BuiltIO { /** File contents relative to the build folder, or null when absent. */ read(name: string): string | null; listJs(): string[];}
/** Checks a built extension folder: manifest against the allow list, named files present, no forbidden code in any script. */export function checkBuiltExtension(io: BuiltIO, allow: string[]): string[] { const text = io.read("manifest.json"); if (text === null) return ["manifest.json: missing"]; let manifest: unknown; try { manifest = JSON.parse(text); } catch { return ["manifest.json: not valid JSON"]; } const problems = validateManifest(manifest, allow).map((p) => `manifest.json: ${p}`);
const m = manifest as { background?: { service_worker?: string }; content_scripts?: Array<{ js?: string[] }> }; const named = [m.background?.service_worker, ...(m.content_scripts ?? []).flatMap((c) => c.js ?? [])].filter((n): n is string => typeof n === "string"); for (const name of named) { if (io.read(name) === null) problems.push(`${name}: named in the manifest but missing`); } for (const file of io.listJs()) { for (const f of findForbiddenCode(io.read(file) ?? "", file)) problems.push(`${f.file}:${f.line}: ${f.rule}`); } return problems;}
export const realBuiltIO = (dir: string): BuiltIO => ({ read: (name) => (existsSync(join(dir, name)) ? readFileSync(join(dir, name), "utf8") : null), listJs: () => (existsSync(dir) ? readdirSync(dir).filter((n) => n.endsWith(".js")) : []),});
export interface CheckDeps { loadAllow(configPath: string): string[]; io(dir: string): BuiltIO;}
export const realCheckDeps: CheckDeps = { loadAllow: (path) => loadConfig(path).allow, io: realBuiltIO };
/** 0 ok, 1 problems found, 2 the check could not run (config unreadable or invalid). */export function runCheckBuilt(dir: string, configPath: string, deps: CheckDeps = realCheckDeps): { exitCode: 0 | 1 | 2; output: string } { let allow: string[]; try { allow = deps.loadAllow(configPath); } catch (err) { return { exitCode: 2, output: err instanceof ConfigError ? err.message : String(err) }; } const problems = checkBuiltExtension(deps.io(dir), allow); if (problems.length === 0) return { exitCode: 0, output: "extension build: ok" }; return { exitCode: 1, output: `extension build: ${problems.length} problem(s)\n${problems.map((p) => ` - ${p}`).join("\n")}` };}