fix(update): embed signed config in Metro debug

这个提交包含在:
XuqmGroup 2026-07-27 11:23:14 +08:00
父节点 198574546b
当前提交 78721ee2f0
共有 3 个文件被更改,包括 67 次插入15 次删除

查看文件

@ -449,3 +449,17 @@ pnpm --dir packages/update test
- App4 按 RN 0.86 官方模板切换到 Gradle 9.3.1 后,真实执行 `:xuqm_rn-update:testDebugUnitTest` 成功,并完成 451 tasks 六模块 Debug APK 构建;原生存储、暂存、SemVer 与 Bridge 拆分均进入宿主编译,不只是独立源码测试。 - App4 按 RN 0.86 官方模板切换到 Gradle 9.3.1 后,真实执行 `:xuqm_rn-update:testDebugUnitTest` 成功,并完成 451 tasks 六模块 Debug APK 构建;原生存储、暂存、SemVer 与 Bridge 拆分均进入宿主编译,不只是独立源码测试。
- 本轮 SDK 定向复核结果common 21 tests、update CLI/Metro/native-baseline 14 tests、release-set/plugin-registry 9 tests、bugcollect 5 tests、xwebview 4 tests;根工作区及各 package TypeScript、Prettier 和 update 发布包 35 文件校验均通过。App4 完整门禁同步达到 20 suites / 87 tests。 - 本轮 SDK 定向复核结果common 21 tests、update CLI/Metro/native-baseline 14 tests、release-set/plugin-registry 9 tests、bugcollect 5 tests、xwebview 4 tests;根工作区及各 package TypeScript、Prettier 和 update 发布包 35 文件校验均通过。App4 完整门禁同步达到 20 suites / 87 tests。
- Gradle 9 构建仍报告的弃用项来自 React Native/三方插件,不通过 SDK 关闭 warning。后续升级必须继续以宿主完整构建和原生单测为门禁,不能只验证 TypeScript 或 npm 打包。 - Gradle 9 构建仍报告的弃用项来自 React Native/三方插件,不通过 SDK 关闭 warning。后续升级必须继续以宿主完整构建和原生单测为门禁,不能只验证 TypeScript 或 npm 打包。
### 2026-07-27 / Metro Debug 与原生配置统一
- App4 使用 Nexus 精确版本执行 `pnpm start` + `pnpm android` 时,JS 能通过 Metro
虚拟模块解析 V2 配置,但 Android 原生 common 报告未找到配置。根因是
`USE_METRO=true` 同时排除了插件 Bundle 和签名配置 assets,破坏了“一份配置、
JS/原生一次初始化”的契约。
- `xuqm-bundles.gradle` 将签名配置复制拆成独立 `CopyXuqmConfigTask`,所有 Android
variant 都从宿主唯一 `src/assets/config/*.xuqmconfig` 生成
`assets/config/config.xuqmconfig`。Metro Debug 不生成插件 Bundle;Release 或
`USE_METRO=false` 才继续执行完整 Bundle/资源任务。
- update 定向测试 46 项通过CLI/打包 28,领域 18,Prettier 通过。尚需 Jenkins
发布新的 update alpha,并在 App4 改用该 Nexus 版本后执行 Android Debug/Release
宿主构建和模拟器日志复核。

查看文件

@ -40,10 +40,6 @@ abstract class GenerateXuqmBundlesTask extends DefaultTask {
@PathSensitive(PathSensitivity.RELATIVE) @PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getPackageFile() abstract RegularFileProperty getPackageFile()
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getSignedConfigFile()
/** /**
* RN/ bundle native baseline * RN/ bundle native baseline
* Gradle out-of-date * Gradle out-of-date
@ -92,12 +88,6 @@ abstract class GenerateXuqmBundlesTask extends DefaultTask {
appVersion.get(), appVersion.get(),
) )
}.assertNormalExitValue() }.assertNormalExitValue()
project.copy {
from(signedConfigFile.get().asFile)
into(assetsOutputDirectory.get().dir("config"))
rename { "config.xuqmconfig" }
}
// Android Bundle AssetManager require() // Android Bundle AssetManager require()
// aapt2 drawable drawable-* assets // aapt2 drawable drawable-* assets
project.delete(resources) project.delete(resources)
@ -112,6 +102,31 @@ abstract class GenerateXuqmBundlesTask extends DefaultTask {
} }
} }
/**
* Debug 使 Metro Bundle
* Android SDK assets JS
*/
@DisableCachingByDefault(because = "Signed config is a small immutable build input")
abstract class CopyXuqmConfigTask extends DefaultTask {
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
abstract RegularFileProperty getSignedConfigFile()
@OutputDirectory
abstract DirectoryProperty getAssetsOutputDirectory()
@TaskAction
void copyConfig() {
def output = assetsOutputDirectory.get().asFile
project.delete(output)
project.copy {
from(signedConfigFile.get().asFile)
into(new File(output, "config"))
rename { "config.xuqmconfig" }
}
}
}
if (!plugins.hasPlugin("com.android.application")) { if (!plugins.hasPlugin("com.android.application")) {
throw new GradleException("xuqm-bundles.gradle must be applied to an Android application module") throw new GradleException("xuqm-bundles.gradle must be applied to an Android application module")
} }
@ -165,11 +180,6 @@ def prepareXuqmBundles = tasks.register(
cliFile.set(new File(xuqmProjectRoot, "node_modules/@xuqm/rn-update/scripts/xuqm-rn.mjs")) cliFile.set(new File(xuqmProjectRoot, "node_modules/@xuqm/rn-update/scripts/xuqm-rn.mjs"))
configFile.set(new File(xuqmProjectRoot, "xuqm.modules.json")) configFile.set(new File(xuqmProjectRoot, "xuqm.modules.json"))
packageFile.set(new File(xuqmProjectRoot, "package.json")) packageFile.set(new File(xuqmProjectRoot, "package.json"))
signedConfigFile.set(
xuqmSignedConfigCandidates
? xuqmSignedConfigCandidates.first()
: new File(xuqmSignedConfigDirectory, "config.xuqmconfig")
)
sourceFiles.from(project.fileTree(xuqmProjectRoot) { sourceFiles.from(project.fileTree(xuqmProjectRoot) {
include("src/**") include("src/**")
include("assets/**") include("assets/**")
@ -223,7 +233,25 @@ def prepareXuqmBundles = tasks.register(
resourcesOutputDirectory.set(layout.buildDirectory.dir("generated/xuqm/res")) resourcesOutputDirectory.set(layout.buildDirectory.dir("generated/xuqm/res"))
} }
def prepareXuqmConfig = tasks.register(
"prepareXuqmConfigAssets",
CopyXuqmConfigTask,
) {
group = "xuqm"
description = "Copies the single signed Xuqm config into Android assets."
signedConfigFile.set(
xuqmSignedConfigCandidates
? xuqmSignedConfigCandidates.first()
: new File(xuqmSignedConfigDirectory, "config.xuqmconfig")
)
assetsOutputDirectory.set(layout.buildDirectory.dir("generated/xuqm/config-assets"))
}
androidComponents.onVariants(androidComponents.selector().all()) { variant -> androidComponents.onVariants(androidComponents.selector().all()) { variant ->
variant.sources.assets?.addGeneratedSourceDirectory(
prepareXuqmConfig,
{ task -> task.assetsOutputDirectory },
)
if (variant.buildType == "release" || !xuqmUseMetro.get()) { if (variant.buildType == "release" || !xuqmUseMetro.get()) {
variant.sources.assets?.addGeneratedSourceDirectory( variant.sources.assets?.addGeneratedSourceDirectory(
prepareXuqmBundles, prepareXuqmBundles,

查看文件

@ -31,6 +31,16 @@ test('Gradle models and forwards the exact embedded release identity', () => {
assert.match(gradle, /@Input\s+abstract Property<String> getBugCollectMode\(\)/) assert.match(gradle, /@Input\s+abstract Property<String> getBugCollectMode\(\)/)
assert.match(gradle, /environment\("XUQM_BUILD_ID", buildId\.get\(\)\)/) assert.match(gradle, /environment\("XUQM_BUILD_ID", buildId\.get\(\)\)/)
assert.match(gradle, /environment\("XUQM_BUGCOLLECT_MODE", bugCollectMode\.get\(\)\)/) assert.match(gradle, /environment\("XUQM_BUGCOLLECT_MODE", bugCollectMode\.get\(\)\)/)
assert.match(gradle, /abstract class CopyXuqmConfigTask extends DefaultTask/)
assert.match(
gradle,
/variant\.sources\.assets\?\.addGeneratedSourceDirectory\(\s*prepareXuqmConfig/,
)
const bundleTask = gradle.slice(
gradle.indexOf('abstract class GenerateXuqmBundlesTask'),
gradle.indexOf('abstract class CopyXuqmConfigTask'),
)
assert.doesNotMatch(bundleTask, /getSignedConfigFile\(\)/)
}) })
test('embed creates a complete versioned manifest from a host fixture', () => { test('embed creates a complete versioned manifest from a host fixture', () => {