构建: - 新增 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 构建失败
114 lines
3.8 KiB
JavaScript
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 }
|