From 1370b90970c127dfc8a0a6af4a9e28d8a86106f0 Mon Sep 17 00:00:00 2001 From: NewWorldVenture Date: Wed, 9 Sep 2026 18:42:29 -0400 Subject: [PATCH] Make repository audits portable across Windows and POSIX --- scripts/audit-supabase-queries.mjs | 6 +++--- tests/household-trail.test.ts | 5 +++-- tests/i18n-server-boundary.test.ts | 6 +++--- tests/metadata-title-not-catalogue-key.test.ts | 16 ++++++++++------ tests/untrusted-fence-ratchet.test.ts | 5 +++-- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/scripts/audit-supabase-queries.mjs b/scripts/audit-supabase-queries.mjs index fae2817c4..0846df8d5 100644 --- a/scripts/audit-supabase-queries.mjs +++ b/scripts/audit-supabase-queries.mjs @@ -24,7 +24,7 @@ // Usage: node scripts/audit-supabase-queries.mjs (exit 1 on any finding) import { readFileSync, readdirSync, statSync } from 'node:fs'; -import { dirname, extname, join, relative, resolve } from 'node:path'; +import { dirname, extname, join, relative, resolve, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); @@ -160,7 +160,7 @@ function readApiRoutes() { const path = join(dir, entry); if (statSync(path).isDirectory()) walk(path); else if (/^route\.(ts|tsx|js)$/.test(entry)) { - routes.push(`/${relative(join(ROOT, 'app'), dir).split('/').join('/')}`); + routes.push(`/${relative(join(ROOT, 'app'), dir).split(sep).join('/')}`); } } })(join(ROOT, 'app', 'api')); @@ -324,7 +324,7 @@ export function auditSupabaseQueries() { for (const file of listSourceFiles()) { const src = readFileSync(file, 'utf8'); - const rel = relative(ROOT, file); + const rel = relative(ROOT, file).split(sep).join('/'); const lineAt = (index) => src.slice(0, index).split('\n').length; const fromCall = /\.from\(\s*'([a-z0-9_]+)'\s*\)/g; diff --git a/tests/household-trail.test.ts b/tests/household-trail.test.ts index 78921b868..9bf267c2a 100644 --- a/tests/household-trail.test.ts +++ b/tests/household-trail.test.ts @@ -8,7 +8,7 @@ // lands in both trails — lives alongside it once the service records them. import { readFileSync } from 'node:fs'; import { globSync } from 'node:fs'; -import { join } from 'node:path'; +import { join, sep } from 'node:path'; import { describe, expect, it } from 'vitest'; import { describeTrail, describeTrailRow, type TrailRow } from '@/lib/activity/trail'; import { trailActionFor } from '@/lib/ai/tools/execute'; @@ -134,7 +134,8 @@ describe('the trail has exactly one writer', () => { it('routes every household-trail write through lib/services/activity', () => { const writers = globSync('lib/services/**/index.ts') - .filter((f) => /from\('audit_logs'\)\s*\.insert/.test(code(repo(f)))); + .filter((f) => /from\('audit_logs'\)\s*\.insert/.test(code(repo(f)))) + .map((f) => f.split(sep).join('/')); expect(writers).toEqual(['lib/services/activity/index.ts']); }); diff --git a/tests/i18n-server-boundary.test.ts b/tests/i18n-server-boundary.test.ts index e61ec8328..58782984b 100644 --- a/tests/i18n-server-boundary.test.ts +++ b/tests/i18n-server-boundary.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { readFileSync, readdirSync, statSync } from 'node:fs'; -import { dirname, join, relative, resolve } from 'node:path'; +import { basename, dirname, join, relative, resolve } from 'node:path'; // `lib/i18n/server.ts` imports `next/headers`. A CLIENT module that reaches it // fails `next build` with "You're importing a component that needs next/headers" @@ -189,13 +189,13 @@ describe('lib/i18n/server never reaches the browser', () => { * entry and STOPS at the first `'use client'`. A module the server can still * reach when it stops may not call a hook. */ -const ROUTE_ENTRY = /(^|\/)(page|layout|template|default|not-found|loading|route)\.tsx?$/; +const ROUTE_ENTRY = /^(page|layout|template|default|not-found|loading|route)\.tsx?$/; function serverRenderedModules(): Set { const roots = files.filter( (file) => file.startsWith(join(ROOT, 'app')) - && ROUTE_ENTRY.test(file) + && ROUTE_ENTRY.test(basename(file)) && !sources.get(file)!.declaresClient, ); const reached = new Set(roots); diff --git a/tests/metadata-title-not-catalogue-key.test.ts b/tests/metadata-title-not-catalogue-key.test.ts index a29913224..d28915f4a 100644 --- a/tests/metadata-title-not-catalogue-key.test.ts +++ b/tests/metadata-title-not-catalogue-key.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { readFileSync } from 'node:fs'; -import { execSync } from 'node:child_process'; +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; // A Next.js `metadata` export is static: it cannot await `getTranslations()`, so // whatever string sits in `title` is rendered VERBATIM into . A catalogue @@ -12,15 +12,19 @@ import { execSync } from 'node:child_process'; // nor rendered through `t()`. So it needs its own guard. const CATALOGUE_KEY = /^[a-z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*$/; -function pageFiles(): string[] { - return execSync("find app -name 'page.tsx' -o -name 'layout.tsx'", { encoding: 'utf8' }) - .split('\n').filter(Boolean); +function pageFiles(directory = 'app'): string[] { + return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const path = join(directory, entry.name); + return entry.isDirectory() ? pageFiles(path) : ['page.tsx', 'layout.tsx'].includes(entry.name) ? [path] : []; + }); } describe('metadata titles are real text, never catalogue keys', () => { it('has no page shipping a catalogue key as its browser-tab title', () => { const offenders: string[] = []; - for (const file of pageFiles()) { + const files = pageFiles(); + expect(files.length).toBeGreaterThan(100); + for (const file of files) { const source = readFileSync(file, 'utf8'); for (const m of source.matchAll(/export const metadata: Metadata = \{[^}]*title: '([^']+)'/g)) { if (CATALOGUE_KEY.test(m[1])) offenders.push(`${file}: ${m[1]}`); diff --git a/tests/untrusted-fence-ratchet.test.ts b/tests/untrusted-fence-ratchet.test.ts index 9d2ed82cb..5d79cd473 100644 --- a/tests/untrusted-fence-ratchet.test.ts +++ b/tests/untrusted-fence-ratchet.test.ts @@ -1,5 +1,5 @@ import { readFileSync, readdirSync, statSync } from 'node:fs'; -import { join } from 'node:path'; +import { join, sep } from 'node:path'; import { describe, expect, it } from 'vitest'; // §44's fence is genuinely well built — nonce markers, a rule the model is told @@ -45,7 +45,8 @@ function toolCallingModules(): string[] { const src = readFileSync(path, 'utf8'); if (!/resolveProvider|provider\.(complete|runTools)/.test(src)) return false; return /tools:\s*(?!\[\s*\])[A-Za-z_[]/.test(src); - }); + }) + .map((path) => path.split(sep).join('/')); } describe('the untrusted-content fence reaches every tool-calling prompt', () => {