refactor(web): consume api v1 contracts
This commit is contained in:
@@ -21,6 +21,8 @@ const requiredFiles = [
|
||||
'src/api/client.ts',
|
||||
'src/api/projects.ts',
|
||||
'src/api/mappers.tsx',
|
||||
'src/api/search.ts',
|
||||
'src/api/inbox.ts',
|
||||
]
|
||||
|
||||
const failures = requiredFiles.filter((file) => !existsSync(file)).map((file) => `missing ${file}`)
|
||||
@@ -54,6 +56,47 @@ if (existsSync('src/app/App.tsx')) {
|
||||
}
|
||||
}
|
||||
|
||||
const apiFiles = existsSync('src/api')
|
||||
? ['client.ts', 'projects.ts', 'mappers.tsx', 'search.ts', 'inbox.ts']
|
||||
.map((name) => `src/api/${name}`)
|
||||
.filter((file) => existsSync(file))
|
||||
: []
|
||||
const apiSource = apiFiles.map((file) => readFileSync(file, 'utf8')).join('\n')
|
||||
for (const forbidden of [
|
||||
{ pattern: /\bID\s*\?\s*:/, label: 'optional PascalCase ID compatibility field' },
|
||||
{ pattern: /\bName\s*\?\s*:/, label: 'optional PascalCase Name compatibility field' },
|
||||
{ pattern: /['"`]\/api\/projects/, label: 'legacy /api/projects path' },
|
||||
{
|
||||
pattern: /\b(?:project|task|inbox)Id\s*\??\s*:\s*(?:string\s*\|\s*)?number\b/i,
|
||||
label: 'numeric project/task/inbox identity',
|
||||
},
|
||||
]) {
|
||||
if (forbidden.pattern.test(apiSource)) failures.push(`src/api contains ${forbidden.label}`)
|
||||
}
|
||||
|
||||
const clientSource = existsSync('src/api/client.ts') ? readFileSync('src/api/client.ts', 'utf8') : ''
|
||||
if (!clientSource.includes("'/api/v1/auth/login'")) failures.push('login must use /api/v1/auth/login')
|
||||
if (!clientSource.includes('export class ApiError extends Error')) failures.push('client must export ApiError')
|
||||
|
||||
const projectsSource = existsSync('src/api/projects.ts') ? readFileSync('src/api/projects.ts', 'utf8') : ''
|
||||
if (!projectsSource.includes("'/api/v1/projects'")) failures.push('project API must use /api/v1/projects')
|
||||
if (/['"`]\/api\/(?!v1\/)/.test(apiSource)) failures.push('src/api paths must use the /api/v1 prefix')
|
||||
|
||||
for (const match of apiSource.matchAll(/export type (\w+DTO)\s*=\s*\{([\s\S]*?)\n\}/g)) {
|
||||
const [, name, body] = match
|
||||
if (/^\s*\w+\s*\?\s*:/m.test(body)) failures.push(`${name} fields must be required`)
|
||||
if (/^\s*[A-Z]\w*\s*:/m.test(body)) failures.push(`${name} fields must use camelCase`)
|
||||
if (/^\s*(?:id|\w+Id)\s*:\s*number\b/im.test(body)) failures.push(`${name} identities must be strings`)
|
||||
}
|
||||
|
||||
const searchSource = existsSync('src/api/search.ts') ? readFileSync('src/api/search.ts', 'utf8') : ''
|
||||
if (searchSource && !searchSource.includes("'/api/v1/search'")) failures.push('search API must use /api/v1/search')
|
||||
|
||||
const inboxSource = existsSync('src/api/inbox.ts') ? readFileSync('src/api/inbox.ts', 'utf8') : ''
|
||||
for (const path of ['/api/v1/projects/', '/api/v1/inbox/']) {
|
||||
if (inboxSource && !inboxSource.includes(path)) failures.push(`inbox API must use ${path}`)
|
||||
}
|
||||
|
||||
const html = readFileSync('index.html', 'utf8')
|
||||
if (!html.includes('<html lang="zh-CN">')) failures.push('index language must be zh-CN')
|
||||
if (!html.includes('<title>森林AI</title>')) failures.push('document title must be 森林AI')
|
||||
|
||||
Reference in New Issue
Block a user