diff --git a/package.json b/package.json index 2f1a3e2..c59f58b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,11 @@ { "name": "jiapuapp", "private": true, + "scripts": { + "check": "node scripts/check-project.mjs && npm --prefix design-pipeline run check", + "check:project": "node scripts/check-project.mjs", + "check:assets": "node design-pipeline/scripts/validate-runtime-asset-inventory.mjs design-pipeline/manifests/runtime-assets.json" + }, "devDependencies": { "yaml": "^2.8.1" } diff --git a/scripts/check-project.mjs b/scripts/check-project.mjs new file mode 100644 index 0000000..d051aba --- /dev/null +++ b/scripts/check-project.mjs @@ -0,0 +1,133 @@ +import fs from 'node:fs' +import path from 'node:path' + +import YAML from 'yaml' + +const workspace = process.cwd() +const failures = [] + +const fail = (message) => failures.push(message) +const readText = (filePath) => fs.readFileSync(path.join(workspace, filePath), 'utf8') +const listFiles = (entryPaths, extensions) => { + const files = [] + const visit = (entryPath) => { + const absolutePath = path.join(workspace, entryPath) + if (!fs.existsSync(absolutePath)) return + const stat = fs.statSync(absolutePath) + if (stat.isDirectory()) { + for (const name of fs.readdirSync(absolutePath)) { + visit(path.join(entryPath, name)) + } + return + } + if (extensions.has(path.extname(entryPath))) files.push(entryPath) + } + entryPaths.forEach(visit) + return files +} + +const parseJson = (filePath) => { + try { + return JSON.parse(readText(filePath)) + } catch (error) { + fail(`${filePath} 不是有效 JSON:${error.message}`) + return null + } +} + +const pagesConfig = parseJson('pages.json') +parseJson('manifest.json') +parseJson('package.json') +parseJson('package-lock.json') + +const routeSource = readText('utils/navigation/routes.js') +const routeEntries = [...routeSource.matchAll(/^\s{2}([A-Z]\d{2}): defineRoute\(\{[\s\S]*?^\s{2}\}\),/gm)] +const routeKeys = routeEntries.map((match) => match[1]) +const routePaths = routeEntries.map((match) => match[0].match(/path: "([^"]+)"/)?.[1]?.replace(/^\//, '')) +const configuredPages = pagesConfig?.pages?.map((page) => page.path) || [] + +if (new Set(routeKeys).size !== routeKeys.length) fail('路由键存在重复') +if (routePaths.some((routePath) => !routePath)) fail('存在没有静态 path 的路由') +for (const pagePath of configuredPages) { + if (!fs.existsSync(path.join(workspace, `${pagePath}.vue`))) fail(`页面文件缺失:${pagePath}.vue`) + if (!routePaths.includes(pagePath)) fail(`页面没有路由语义:${pagePath}`) +} +for (const routePath of routePaths) { + if (!configuredPages.includes(routePath)) fail(`路由没有在 pages.json 注册:${routePath}`) +} + +const sourceFiles = listFiles( + ['App.vue', 'main.js', 'pages', 'components', 'composables', 'services', 'utils'], + new Set(['.js', '.vue']), +) +const importPattern = /(?:from\s*|import\s*)["']([^"']+)["']/g +for (const filePath of sourceFiles) { + const source = readText(filePath) + if (filePath.endsWith('.vue') && !/<(?:template|script)(?:\s|>)/.test(source)) { + fail(`${filePath} 缺少