105 lines
3.2 KiB
JavaScript
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()
|