Rocky_Mountain_Vending/scripts/check-public-copy.mjs

105 lines
3.2 KiB
JavaScript

import { readFileSync, readdirSync, statSync } from "node:fs"
import path from "node:path"
import process from "node:process"
const ROOT = process.cwd()
const SEARCH_DIRS = ["app", "components"]
const INCLUDE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mdx"])
const IGNORE_PATTERNS = [
".backup",
`${path.sep}style-guide${path.sep}`,
`${path.sep}ui${path.sep}`,
`${path.sep}api${path.sep}`,
`${path.sep}manuals${path.sep}README.md`,
`${path.sep}components${path.sep}privacy-policy-page.tsx`,
`${path.sep}components${path.sep}terms-and-conditions-page.tsx`,
`${path.sep}app${path.sep}seaga-hy900-support${path.sep}`,
]
const BANNED_PATTERNS = [
{ label: "rest of the site", regex: /\brest of the site\b/i },
{ label: "design system", regex: /\bdesign system\b/i },
{ label: "presentation", regex: /\bpresentation\b/i },
{ label: "same clean", regex: /\bsame clean\b/i },
{ label: "same Rocky shell", regex: /\bsame rocky shell\b/i },
{ label: "dedicated page", regex: /\bdedicated page\b/i },
{ label: "intake", regex: /\bintake\b/i },
{ label: "handoff", regex: /\bhandoff\b/i },
{ label: "popup", regex: /\bpopup\b/i },
{ label: "embed", regex: /\bembed(?:ded)?\b/i },
{ label: "this page now", regex: /\bthis page now\b/i },
{
label: "consistent service experience",
regex: /\bconsistent service experience\b/i,
},
{ label: "best next step", regex: /\bbest next step\b/i },
{ label: "fuller request", regex: /\bfuller request\b/i },
{ label: "fuller intake", regex: /\bfuller intake\b/i },
]
function shouldIgnore(filePath) {
return IGNORE_PATTERNS.some((pattern) => filePath.includes(pattern))
}
function walk(dir) {
const entries = readdirSync(dir, { withFileTypes: true })
const results = []
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (shouldIgnore(fullPath)) continue
if (entry.isDirectory()) {
results.push(...walk(fullPath))
continue
}
if (!entry.isFile()) continue
if (INCLUDE_EXTENSIONS.has(path.extname(entry.name))) {
results.push(fullPath)
}
}
return results
}
function findLineNumber(content, index) {
return content.slice(0, index).split("\n").length
}
function main() {
const files = SEARCH_DIRS.flatMap((dir) => {
const fullDir = path.join(ROOT, dir)
if (!statSync(fullDir, { throwIfNoEntry: false })) return []
return walk(fullDir)
})
const findings = []
for (const filePath of files) {
const content = readFileSync(filePath, "utf8")
for (const rule of BANNED_PATTERNS) {
const match = content.match(rule.regex)
if (!match || match.index == null) continue
findings.push({
filePath,
line: findLineNumber(content, match.index),
label: rule.label,
preview: match[0],
})
}
}
if (findings.length === 0) {
console.log("Public copy check passed.")
process.exit(0)
}
console.error(
"Public copy guardrail failed. Rewrite customer-facing copy that talks about the site or UI mechanics:"
)
for (const finding of findings) {
console.error(
`- ${path.relative(ROOT, finding.filePath)}:${finding.line} [${finding.label}] ${finding.preview}`
)
}
process.exit(1)
}
main()