2026-06-18 15:01:39 +08:00
|
|
|
|
'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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 17:50:29 +08:00
|
|
|
|
function applyBugCollect(metroConfig) {
|
|
|
|
|
|
// 可选链式:安装了 @xuqm/rn-bugcollect 时,自动启用 Release 包 SourceMap 上传。
|
|
|
|
|
|
// 未安装则原样返回(不强制依赖)。
|
|
|
|
|
|
try {
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
|
|
|
|
const { withBugCollect } = require('@xuqm/rn-bugcollect/metro');
|
|
|
|
|
|
return typeof withBugCollect === 'function' ? withBugCollect(metroConfig) : metroConfig;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return metroConfig;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 18:45:17 +08:00
|
|
|
|
/** 写入目标文件(仅在缺失或内容不一致时),返回是否发生写入。 */
|
|
|
|
|
|
function syncFile(content, destPath, label) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (fs.existsSync(destPath) && fs.readFileSync(destPath, 'utf-8').trim() === content) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
|
|
|
|
fs.writeFileSync(destPath, content, 'utf-8');
|
|
|
|
|
|
console.log(`[XuqmConfig] 已同步 .xuqmconfig → ${label}`);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn(`[XuqmConfig] 同步 .xuqmconfig 到 ${label} 失败:${e.message}`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 单点放置:宿主只在 RN 侧放置一份 .xuqmconfig,构建/运行时自动同步到
|
|
|
|
|
|
* 原生位置(Android assets/config,供 XuqmMergedProvider 自动初始化;iOS 资源目录)。
|
|
|
|
|
|
* 缺失或内容不一致才复制,幂等。
|
|
|
|
|
|
*/
|
|
|
|
|
|
function syncNativeConfig(projectRoot, content) {
|
|
|
|
|
|
const fileName = 'config.xuqmconfig';
|
|
|
|
|
|
|
|
|
|
|
|
// Android:原生 ContentProvider 扫描 assets/config 下的 .xuqmconfig
|
|
|
|
|
|
if (fs.existsSync(path.resolve(projectRoot, 'android'))) {
|
|
|
|
|
|
syncFile(
|
|
|
|
|
|
content,
|
|
|
|
|
|
path.resolve(projectRoot, 'android/app/src/main/assets/config', fileName),
|
|
|
|
|
|
'android assets/config',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// iOS:复制到 app 资源目录(需在 Xcode「Copy Bundle Resources」中引用一次)
|
|
|
|
|
|
const iosDir = path.resolve(projectRoot, 'ios');
|
|
|
|
|
|
if (fs.existsSync(iosDir)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const appDir = fs
|
|
|
|
|
|
.readdirSync(iosDir, { withFileTypes: true })
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
(d) => d.isDirectory() && fs.existsSync(path.join(iosDir, d.name, 'Info.plist')),
|
|
|
|
|
|
)
|
|
|
|
|
|
.map((d) => d.name)[0];
|
|
|
|
|
|
if (appDir) {
|
|
|
|
|
|
syncFile(content, path.join(iosDir, appDir, 'config', fileName), `ios ${appDir}/config`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// 忽略 iOS 同步异常,不阻断打包
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 15:01:39 +08:00
|
|
|
|
function withXuqmConfig(metroConfig) {
|
|
|
|
|
|
const projectRoot = metroConfig.projectRoot ?? process.cwd();
|
|
|
|
|
|
const configFile = findConfigFile(projectRoot);
|
|
|
|
|
|
|
|
|
|
|
|
if (!configFile) {
|
|
|
|
|
|
// 未找到配置文件,返回原配置(autoInit 会静默跳过)
|
2026-06-25 17:50:29 +08:00
|
|
|
|
return applyBugCollect(metroConfig);
|
2026-06-18 15:01:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取加密配置内容
|
|
|
|
|
|
const encryptedContent = fs.readFileSync(configFile, 'utf-8').trim();
|
|
|
|
|
|
|
2026-06-25 18:45:17 +08:00
|
|
|
|
// 单点放置:自动同步到原生 Android/iOS(缺失或不一致才复制)
|
|
|
|
|
|
syncNativeConfig(projectRoot, encryptedContent);
|
|
|
|
|
|
|
2026-06-18 15:01:39 +08:00
|
|
|
|
// 生成临时 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;
|
|
|
|
|
|
|
2026-06-25 17:50:29 +08:00
|
|
|
|
return applyBugCollect({
|
2026-06-18 15:01:39 +08:00
|
|
|
|
...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);
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2026-06-25 17:50:29 +08:00
|
|
|
|
});
|
2026-06-18 15:01:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { withXuqmConfig };
|