111 lines
2.8 KiB
TypeScript
111 lines
2.8 KiB
TypeScript
import fs from 'node:fs'
|
|
|
|
type Rule = {
|
|
file: string
|
|
patterns: string[]
|
|
}
|
|
|
|
const RULES: Rule[] = [
|
|
{
|
|
file: 'src/lib/api-errors.ts',
|
|
patterns: ['x-request-id', 'api.request.start', 'api.request.finish', 'api.request.error'],
|
|
},
|
|
{
|
|
file: 'src/lib/workers/shared.ts',
|
|
patterns: ['worker.start', 'worker.completed', 'worker.failed', 'durationMs', 'errorCode'],
|
|
},
|
|
{
|
|
file: 'src/app/api/sse/route.ts',
|
|
patterns: ['sse.connect', 'sse.replay', 'sse.disconnect'],
|
|
},
|
|
{
|
|
file: 'scripts/watchdog.ts',
|
|
patterns: ['watchdog.started', 'watchdog.tick.ok', 'watchdog.tick.failed'],
|
|
},
|
|
{
|
|
file: 'scripts/bull-board.ts',
|
|
patterns: ['bull_board.started', 'bull_board.shutdown'],
|
|
},
|
|
{
|
|
file: 'src/lib/task/submitter.ts',
|
|
patterns: ['requestId', 'task.submit.created', 'task.submit.enqueued'],
|
|
},
|
|
{
|
|
file: 'src/lib/task/types.ts',
|
|
patterns: ['trace', 'requestId'],
|
|
},
|
|
]
|
|
|
|
function read(file: string) {
|
|
return fs.readFileSync(file, 'utf8')
|
|
}
|
|
|
|
function checkRules() {
|
|
const violations: string[] = []
|
|
for (const rule of RULES) {
|
|
const content = read(rule.file)
|
|
for (const pattern of rule.patterns) {
|
|
if (!content.includes(pattern)) {
|
|
violations.push(`${rule.file} missing "${pattern}"`)
|
|
}
|
|
}
|
|
}
|
|
return violations
|
|
}
|
|
|
|
function checkSubmitTaskRoutes() {
|
|
const root = 'src/app/api'
|
|
const files = walk(root).filter((file) => file.endsWith('/route.ts'))
|
|
const submitTaskFiles = files.filter((file) => read(file).includes('submitTask('))
|
|
const violations: string[] = []
|
|
|
|
for (const file of submitTaskFiles) {
|
|
const content = read(file)
|
|
if (!content.includes('getRequestId')) {
|
|
violations.push(`${file} uses submitTask but does not import getRequestId`)
|
|
continue
|
|
}
|
|
if (!content.includes('requestId: getRequestId(request)')) {
|
|
violations.push(`${file} uses submitTask but does not pass requestId`)
|
|
}
|
|
}
|
|
|
|
return { submitTaskFiles, violations }
|
|
}
|
|
|
|
function walk(dir: string): string[] {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
const out: string[] = []
|
|
|
|
for (const entry of entries) {
|
|
const next = `${dir}/${entry.name}`
|
|
if (entry.isDirectory()) {
|
|
out.push(...walk(next))
|
|
} else {
|
|
out.push(next)
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
function main() {
|
|
const violations = checkRules()
|
|
const submitTaskResult = checkSubmitTaskRoutes()
|
|
violations.push(...submitTaskResult.violations)
|
|
|
|
if (violations.length > 0) {
|
|
process.stderr.write('[check:log-semantic] semantic violations detected:\n')
|
|
for (const violation of violations) {
|
|
process.stderr.write(`- ${violation}\n`)
|
|
}
|
|
process.exit(1)
|
|
}
|
|
|
|
process.stdout.write(
|
|
`[check:log-semantic] ok rules=${RULES.length} submitTaskRoutes=${submitTaskResult.submitTaskFiles.length}\n`,
|
|
)
|
|
}
|
|
|
|
main()
|