feat(common): Metro 插件自动发现 .xuqmconfig 配置文件

新增 withXuqmConfig(metroConfig) Metro 插件:
- 扫描 src/assets/config/ 下的 *.xuqmconfig 文件
- 通过 resolveRequest 拦截 @xuqm/autoinit-config 模块
- 宿主只需放入下载的 .xuqmconfig 文件,无需重命名

对齐 Android SDK 的 ConfigFileReader 逻辑。

Co-Authored-By: Claude <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-06-18 15:01:39 +08:00
父节点 c7f1f7cb72
当前提交 4f5739da1f
共有 3 个文件被更改,包括 107 次插入6 次删除

查看文件

@ -0,0 +1,96 @@
'use strict';
/**
* Metro 插件自动发现 .xuqmconfig 配置文件
*
* 宿主只需将平台下载的 .xuqmconfig 文件放入 src/assets/config/
* SDK 自动读取并完成初始化无需重命名或创建 .ts 包装文件
*
* 对齐 Android SDK ConfigFileReader 逻辑
* 优先 config.xuqmconfig否则取第一个 *.xuqmconfig
*
* @param {import('@react-native/metro-config').MetroConfig} metroConfig
* @returns {import('@react-native/metro-config').MetroConfig}
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const VIRTUAL_MODULE_ID = '@xuqm/autoinit-config';
const CONFIG_DIR_CANDIDATES = [
'src/assets/config',
'src/assets/xuqm',
'assets/config',
'assets/xuqm',
];
function findConfigFile(projectRoot) {
for (const dir of CONFIG_DIR_CANDIDATES) {
const absDir = path.resolve(projectRoot, dir);
if (!fs.existsSync(absDir)) continue;
const files = fs.readdirSync(absDir);
// 优先 config.xuqmconfig 或 config.xuqm
const preferred = files.find(
(f) => f === 'config.xuqmconfig' || f === 'config.xuqm',
);
if (preferred) {
return path.join(absDir, preferred);
}
// 否则取第一个 .xuqmconfig
const fallback = files.find((f) => f.endsWith('.xuqmconfig'));
if (fallback) {
return path.join(absDir, fallback);
}
}
return null;
}
function withXuqmConfig(metroConfig) {
const projectRoot = metroConfig.projectRoot ?? process.cwd();
const configFile = findConfigFile(projectRoot);
if (!configFile) {
// 未找到配置文件,返回原配置autoInit 会静默跳过)
return metroConfig;
}
// 读取加密配置内容
const encryptedContent = fs.readFileSync(configFile, 'utf-8').trim();
// 生成临时 TS 文件作为虚拟模块
const tmpDir = path.join(os.tmpdir(), 'xuqm-sdk-config');
fs.mkdirSync(tmpDir, { recursive: true });
const virtualFile = path.join(tmpDir, 'autoinit-config.ts');
fs.writeFileSync(
virtualFile,
`// Auto-generated by @xuqm/rn-common/metro — do not edit\nexport const ENCRYPTED_CONFIG = ${JSON.stringify(encryptedContent)};\n`,
'utf-8',
);
// 拦截模块解析,将 @xuqm/autoinit-config 指向虚拟文件
const existingResolveRequest = metroConfig.resolver?.resolveRequest;
return {
...metroConfig,
resolver: {
...metroConfig.resolver,
resolveRequest: (context, moduleName, platform) => {
if (moduleName === VIRTUAL_MODULE_ID) {
return {
type: 'sourceFile',
filePath: virtualFile,
};
}
if (existingResolveRequest) {
return existingResolveRequest(context, moduleName, platform);
}
return context.resolveRequest(context, moduleName, platform);
},
},
};
}
module.exports = { withXuqmConfig };

查看文件

@ -6,8 +6,10 @@
"main": "src/index.ts",
"react-native": "src/index.ts",
"types": "src/index.ts",
"metro": "metro/index.js",
"files": [
"src"
"src",
"metro"
],
"private": false,
"publishConfig": {

查看文件

@ -3,13 +3,16 @@
*
* Android SDK ContentProvider
*
* React Native ContentProvider Metro moduleNameMapper
* 1. 宿 src/assets/xuqm/config.xuqmconfig.ts
* 2. 宿 babel.config.js alias: '@xuqm/autoinit-config'
* withXuqmConfig Metro
* 1. 宿.xuqmconfig src/assets/config/
* 2. metro.config.js 使 withXuqmConfig(metroConfig)
* 3. tryRequire('@xuqm/autoinit-config')
* 4. 宿
*
* alias require
* alias
* 1. 宿 src/assets/xuqm/config.xuqmconfig.ts
* 2. 宿 babel/metro config alias: '@xuqm/autoinit-config'
*
* require
*/
import {isInitialized} from './config'