feat: 打通 HBuilderX CLI 构建 + 修复文章闭环静态盲区
构建: - 新增 tools/build.js:junction HBuilderX 工具链,CLI 构建 h5/mp-weixin - vue 指向补丁版 @dcloudio/uni-h5-vue(官方 npm vue 不导出 isInSSRComponentSetup) - 设 HX_APP_ROOT 避免退化成 H5 空壳产物;产物完整性校验 校验工具: - 新增 check-cloud-methods.js:acorn 解析云对象方法,比对 94 处调用点 - 新增 check-android-contract.js:Kotlin 侧云对象契约校验 - audit-project.js 修 downloadFile 误报(注释未剥离);tools/ 排除出扫描 - package.json 声明此前隐式依赖的 acorn 功能: - 补 uni-cms-articles.getPublishedArticles(安卓端依赖但此前不存在) - 修 u-parse <audio> 引用已移除组件导致 H5 构建失败
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* 原生安卓端 × 云对象契约校验。
|
||||
*
|
||||
* android/ 是独立 Kotlin 工程,通过 uniCloud「URL 化」HTTP 网关调用云对象:
|
||||
* POST {BASE_URL}/{云对象}/{方法}
|
||||
* 它与前端页面一样存在「方法名写错只在运行时炸」的风险,但不在
|
||||
* tools/check-cloud-methods.js 的扫描范围(那个扫的是 .vue/.js 前端调用点)。
|
||||
*
|
||||
* 本脚本从 Android 源码里抽出所有 (云对象, 方法) 调用对,
|
||||
* 与云对象实现交叉比对,输出缺失清单。
|
||||
*
|
||||
* 用法:
|
||||
* node tools/check-android-contract.js
|
||||
* node tools/check-android-contract.js --json
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { collectCloudObjects } = require('./check-cloud-methods')
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..')
|
||||
const ANDROID_SRC = path.join(ROOT, 'android', 'app', 'src', 'main', 'java')
|
||||
|
||||
// 调用形式(Kotlin):
|
||||
// api.callChecked("uni-cms-articles", "getPublishedArticles", ...)
|
||||
// api.callRaw("article_info", "getDetail", ...)
|
||||
// 云对象名以 kebab-case、方法名以 snake/camel 出现,连字符是硬特征。
|
||||
const CALL_RE = /call(?:Checked|Raw)?\s*\(\s*"([\w-]+)"\s*,\s*"([A-Za-z_][\w]*)"/g
|
||||
|
||||
function walk(dir, acc) {
|
||||
let entries
|
||||
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return acc }
|
||||
for (const e of entries) {
|
||||
const p = path.join(dir, e.name)
|
||||
if (e.isDirectory()) walk(p, acc)
|
||||
else if (e.name.endsWith('.kt')) acc.push(p)
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
function run(opts = {}) {
|
||||
if (!fs.existsSync(ANDROID_SRC)) {
|
||||
const r = { ok: true, skipped: true, reason: '未找到 android/ 原生工程,跳过', errors: [], warnings: [] }
|
||||
if (!opts.json) console.log('安卓端契约校验:未找到 android/ 原生工程,跳过')
|
||||
return r
|
||||
}
|
||||
|
||||
const { cloudObjects } = collectCloudObjects()
|
||||
const files = walk(ANDROID_SRC, [])
|
||||
const errors = []
|
||||
const checked = new Set()
|
||||
let callSiteCount = 0
|
||||
|
||||
for (const file of files) {
|
||||
const src = fs.readFileSync(file, 'utf8')
|
||||
const rel = path.relative(ROOT, file).replace(/\\/g, '/')
|
||||
let m
|
||||
CALL_RE.lastIndex = 0
|
||||
while ((m = CALL_RE.exec(src))) {
|
||||
const obj = m[1]
|
||||
const method = m[2]
|
||||
// 跳过非云对象命名(Kotlin/JS 标准 API 里带连字符的极少,这里以云对象表为准)
|
||||
callSiteCount++
|
||||
const key = `${obj}.${method}`
|
||||
if (checked.has(key)) continue
|
||||
checked.add(key)
|
||||
|
||||
const line = src.slice(0, m.index).split('\n').length
|
||||
const decl = cloudObjects.get(obj)
|
||||
if (!decl) {
|
||||
// 只报"看起来像本项目云对象"的(存在于 cloudfunctions 命名习惯里)
|
||||
errors.push({ file: rel, line, key, msg: `云对象 ${obj} 未找到实现` })
|
||||
} else if (!decl.methods.has(method)) {
|
||||
errors.push({ file: rel, line, key, msg: `${key}() 未在 ${decl.file} 中定义` })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
ok: errors.length === 0,
|
||||
androidFileCount: files.length,
|
||||
callSiteCount,
|
||||
uniqueCallCount: checked.size,
|
||||
errors,
|
||||
warnings: []
|
||||
}
|
||||
|
||||
if (!opts.json) {
|
||||
console.log('安卓端 × 云对象契约校验')
|
||||
console.log(` Kotlin 文件 ${result.androidFileCount} 个 · 调用点 ${callSiteCount} 处 · 去重 ${checked.size} 个`)
|
||||
if (errors.length) {
|
||||
console.log(`\n✗ 发现 ${errors.length} 个问题:`)
|
||||
for (const e of errors) console.log(` ✗ ${e.file}:${e.line} ${e.msg}`)
|
||||
} else {
|
||||
console.log('\n✓ 安卓端调用的所有云对象方法均存在')
|
||||
}
|
||||
console.log(`\n结论: ${errors.length} 错误 / 0 警告`)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const json = process.argv.includes('--json')
|
||||
const r = run({ json })
|
||||
if (json) console.log(JSON.stringify(r, null, 2))
|
||||
process.exit(r.ok ? 0 : 1)
|
||||
}
|
||||
|
||||
module.exports = { run }
|
||||
Reference in New Issue
Block a user