import { existsSync, readFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; const projectRoot = resolve(import.meta.dirname, '..'); const miniRoot = join(projectRoot, 'miniprogram'); const app = JSON.parse(readFileSync(join(miniRoot, 'app.json'), 'utf8')); const allowedTags = new Set([ 'block', 'button', 'checkbox', 'checkbox-group', 'image', 'input', 'label', 'picker', 'rich-text', 'scroll-view', 'switch', 'text', 'textarea', 'view', ]); const failures = []; for (const page of app.pages) { const base = join(miniRoot, page); for (const extension of ['.ts', '.json', '.wxml', '.wxss']) { if (!existsSync(`${base}${extension}`)) failures.push(`${page}${extension} 缺失`); } const wxml = readFileSync(`${base}.wxml`, 'utf8'); if (!wxml.trim()) failures.push(`${page}.wxml 为空`); for (const match of wxml.matchAll(/<\/?([A-Za-z][A-Za-z0-9-]*)/g)) { if (!allowedTags.has(match[1])) failures.push(`${page}.wxml 使用不支持标签 <${match[1]}>`); } for (const match of wxml.matchAll(/<([A-Za-z][A-Za-z0-9-]*)\b([^>]*)>/g)) { const attributes = match[2]; if (/\bwx:(?:elif|else)\b/.test(attributes) && /\bwx:for\b/.test(attributes)) { failures.push( `${page}.wxml 的 <${match[1]}> 不能同时使用 wx:elif/wx:else 与 wx:for,请用条件 block 包裹循环`, ); } } const source = readFileSync(`${base}.ts`, 'utf8'); for (const match of source.matchAll( /wx\.(?:navigateTo|redirectTo)\(\{\s*url:\s*[`'"]([^`'"]+)/g, )) { const target = match[1].split('?')[0].replace(/^\//, ''); if (target.includes('${')) continue; if (!app.pages.includes(target)) failures.push(`${page}.ts 路由未注册:${target}`); } } if (failures.length) { console.error(failures.join('\n')); process.exit(1); } console.log(`小程序页面完整性检查通过:${app.pages.length} 个页面`);