import { spawnSync } from "node:child_process"; import { readFile, readdir } from "node:fs/promises"; import path from "node:path"; function requiredPnpmCli(): string { const executable = process.env.npm_execpath; if (!executable) { throw new Error( "check:browser-file-storage-boundaries must run through pnpm", ); } return executable; } function runEslint(path: string) { return spawnSync( process.execPath, [ requiredPnpmCli(), "exec", "eslint", path, "--no-ignore", "--max-warnings=0", ], { encoding: "utf8" }, ); } function runEslintSource(source: string, virtualPath: string) { return spawnSync( process.execPath, [ requiredPnpmCli(), "exec", "eslint", "--stdin", "--stdin-filename", virtualPath, "--no-ignore", "--max-warnings=0", ], { encoding: "utf8", input: source }, ); } const allowed = runEslint( "tests/fixtures/browser-file-storage-boundaries/allowed", ); const forbiddenRoot = "tests/fixtures/browser-file-storage-boundaries/forbidden"; const forbiddenFiles = ( await readdir(forbiddenRoot, { withFileTypes: true }) ) .filter((entry) => entry.isFile() && /\.tsx?$/u.test(entry.name)) .map((entry) => path.join(forbiddenRoot, entry.name)); const forbiddenResults = forbiddenFiles.map((file) => ({ file, result: runEslint(file), })); const acceptedForbidden = forbiddenResults.filter( ({ result }) => result.status === 0, ); const indexedDbBypassSource = await readFile( path.join(forbiddenRoot, "direct-indexeddb.ts"), "utf8", ); const objectUrlBypassSource = await readFile( path.join(forbiddenRoot, "object-url.ts"), "utf8", ); const blobBypassSource = await readFile( path.join(forbiddenRoot, "direct-blob.ts"), "utf8", ); const crossContextAdapterSource = await readFile( "tests/fixtures/browser-file-storage-boundaries/allowed/cross-context-host.ts", "utf8", ); const browserStorageAdapterSource = await readFile( "tests/fixtures/browser-file-storage-boundaries/allowed/browser-storage-host.ts", "utf8", ); const protectedSourceFixtureNames = [ "aliased-globalthis.ts", "class-field-alias.ts", "constructor-root-escape.ts", "default-parameter-alias.ts", "direct-blob.ts", "dynamic-capability-key.ts", "global-object-container.ts", "identity-wrapped-global.ts", "instance-property-alias.ts", "property-descriptor-access.ts", ] as const; const protectedSourceFixtures = await Promise.all( protectedSourceFixtureNames.map(async (file) => ({ file, source: await readFile(path.join(forbiddenRoot, file), "utf8"), })), ); const approvedAdapterResults = [ { file: "src/adapters/storage/indexeddb/boundary-fixture.ts", result: runEslintSource( indexedDbBypassSource, "src/adapters/storage/indexeddb/boundary-fixture.ts", ), }, { file: "src/adapters/cross-context-invalidation/boundary-fixture.ts", result: runEslintSource( crossContextAdapterSource, "src/adapters/cross-context-invalidation/boundary-fixture.ts", ), }, { file: "src/adapters/storage/boundary-fixture.ts", result: runEslintSource( browserStorageAdapterSource, "src/adapters/storage/boundary-fixture.ts", ), }, { file: "src/adapters/browser-transfer/boundary-fixture.ts", result: runEslintSource( blobBypassSource, "src/adapters/browser-transfer/boundary-fixture.ts", ), }, ]; const rejectedApprovedAdapters = approvedAdapterResults.filter( ({ result }) => result.status !== 0, ); const misplacedAdapterResults = [ { file: "src/adapters/http/boundary-fixture.ts", result: runEslintSource( indexedDbBypassSource, "src/adapters/http/boundary-fixture.ts", ), }, { file: "src/features/reference-feature/adapters/boundary-fixture.ts", result: runEslintSource( objectUrlBypassSource, "src/features/reference-feature/adapters/boundary-fixture.ts", ), }, { file: "src/adapters/http/cross-context-boundary-fixture.ts", result: runEslintSource( crossContextAdapterSource, "src/adapters/http/cross-context-boundary-fixture.ts", ), }, { file: "src/adapters/http/browser-storage-boundary-fixture.ts", result: runEslintSource( browserStorageAdapterSource, "src/adapters/http/browser-storage-boundary-fixture.ts", ), }, ...protectedSourceFixtures.map(({ file, source }) => { const virtualPath = `src/presentation/${file}`; return { file: virtualPath, result: runEslintSource(source, virtualPath), }; }), ]; const acceptedMisplacedAdapters = misplacedAdapterResults.filter( ({ result }) => result.status === 0, ); if ( allowed.status !== 0 || rejectedApprovedAdapters.length > 0 || forbiddenFiles.length === 0 || acceptedForbidden.length > 0 || acceptedMisplacedAdapters.length > 0 ) { process.stderr.write(allowed.stderr || allowed.stdout); for (const { file, result } of rejectedApprovedAdapters) { process.stderr.write( `${file}: owned browser capability adapter was rejected\n`, ); process.stderr.write(result.stderr || result.stdout); } for (const { file, result } of acceptedForbidden) { process.stderr.write( `${file}: forbidden browser API fixture was accepted\n`, ); process.stderr.write(result.stderr || result.stdout); } for (const { file, result } of acceptedMisplacedAdapters) { process.stderr.write( `${file}: native browser storage access outside its owned adapter was accepted\n`, ); process.stderr.write(result.stderr || result.stdout); } process.exit(1); } process.stdout.write( `Browser file/storage boundaries: PASS (owned adapter allowed, ${forbiddenFiles.length + misplacedAdapterResults.length} direct or misplaced native access cases rejected)\n`, );