Files
t/tools/check-android-contract.js
root 3117146281 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 构建失败
2026-09-12 01:02:45 +08:00

114 lines
3.8 KiB
JavaScript

#!/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 }