@flun/node-mobile-app 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Android/346/236/204/345/273/272/346/214/207/345/215/227.md +1 -1
- package/CHANGELOG.md +17 -27
- package/README.md +67 -13
- package/bin/cli.js +3 -3
- package/mobileAppConfig.js +17 -4
- package/package.json +1 -1
- package/scripts/copy-files.js +3 -2
- package/src/commands/build.js +105 -99
- package/src/commands/clean.js +12 -6
- package/src/commands/test.js +141 -132
- package/src/config.js +88 -52
- package/src/copy.js +302 -215
- package/src/fingerprint.js +96 -77
- package/src/generate.js +33 -21
- package/src/install.js +176 -162
- package/src/optimize-node-modules.js +5 -4
- package/src/patcher.js +438 -358
- package/src/platform-ios.js +122 -106
- package/src/utils.js +357 -254
- package/template/.prettierrc.js +8 -2
- package/template/App.tsx +98 -87
- package/template/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/template/babel.config.js +8 -2
- package/template/eslint.config.js +8 -2
- package/template/metro.config.js +9 -3
- package//350/207/252/345/256/232/344/271/211libnode/346/214/207/345/215/227.md +2 -2
package/src/install.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
2
3
|
import fs from 'fs';
|
|
3
|
-
import { run } from './utils.js';
|
|
4
|
-
import { clearVerified, optimizeNodeModules, SELF_HASH } from './optimize-node-modules.js';
|
|
5
4
|
import { createRequire } from 'module';
|
|
6
|
-
import
|
|
5
|
+
import path from 'path';
|
|
7
6
|
import { resolveSdkPath } from './config.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import { clearVerified, optimizeNodeModules, SELF_HASH } from './optimize-node-modules.js';
|
|
8
|
+
import { run } from './utils.js';
|
|
9
|
+
|
|
10
|
+
const ABI_MAP = {
|
|
11
|
+
'armeabi-v7a': { arch: 'arm', prefix: 'armv7a-linux-androideabi' },
|
|
12
|
+
'arm64-v8a': { arch: 'arm64', prefix: 'aarch64-linux-android' },
|
|
13
|
+
'x86_64': { arch: 'x64', prefix: 'x86_64-linux-android' }
|
|
14
|
+
},
|
|
10
15
|
/**
|
|
11
16
|
* 内部:从 libnode 的 node_version.h 读取运行时 Node 主版本号
|
|
12
17
|
* 读不到直接报错退出(说明 libnode 目录不完整,继续执行必失败)
|
|
13
18
|
*/
|
|
14
19
|
resolveRuntimeMajor = (userProjectDir, targetPlatform) => {
|
|
15
20
|
const sub = targetPlatform === 'ios' ? 'ios' : 'android',
|
|
16
|
-
header = path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native',
|
|
17
|
-
|
|
21
|
+
header = path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native', sub,
|
|
22
|
+
'libnode', 'include', 'node', 'node_version.h');
|
|
18
23
|
if (!fs.existsSync(header)) {
|
|
19
24
|
console.error(chalk.red(' [错误] 未找到 node_version.h: ' + header));
|
|
20
25
|
console.error(chalk.red(' 说明 libnode 目录不完整,请重新安装依赖;')), process.exit(1);
|
|
@@ -37,8 +42,7 @@ const
|
|
|
37
42
|
} catch { return false; }
|
|
38
43
|
|
|
39
44
|
const nodeModulesDir = path.join(userProjectDir, 'node_modules');
|
|
40
|
-
for (const name of Object.keys(deps))
|
|
41
|
-
if (fs.existsSync(path.join(nodeModulesDir, name, 'binding.gyp'))) return true;
|
|
45
|
+
for (const name of Object.keys(deps)) if (fs.existsSync(path.join(nodeModulesDir, name, 'binding.gyp'))) return true;
|
|
42
46
|
return false;
|
|
43
47
|
},
|
|
44
48
|
/**
|
|
@@ -49,11 +53,7 @@ const
|
|
|
49
53
|
for (const c of candidates) if (fs.existsSync(path.join(c, 'android', 'build.gradle'))) return c;
|
|
50
54
|
return null;
|
|
51
55
|
},
|
|
52
|
-
|
|
53
|
-
'armeabi-v7a': { arch: 'arm', prefix: 'armv7a-linux-androideabi' },
|
|
54
|
-
'arm64-v8a': { arch: 'arm64', prefix: 'aarch64-linux-android' },
|
|
55
|
-
'x86_64': { arch: 'x64', prefix: 'x86_64-linux-android' }
|
|
56
|
-
},
|
|
56
|
+
|
|
57
57
|
resolveNdkVersion = buildDir => {
|
|
58
58
|
const gradle = path.join(buildDir, 'android', 'build.gradle');
|
|
59
59
|
if (!fs.existsSync(gradle))
|
|
@@ -77,10 +77,11 @@ const
|
|
|
77
77
|
if (entry.startsWith('@')) {
|
|
78
78
|
for (const sub of fs.readdirSync(full)) {
|
|
79
79
|
const pkgDir = path.join(full, sub);
|
|
80
|
-
if (fs.statSync(pkgDir).isDirectory()
|
|
81
|
-
|
|
80
|
+
if (fs.statSync(pkgDir).isDirectory() && fs.existsSync(path.join(pkgDir, 'binding.gyp')))
|
|
81
|
+
result.push(entry + '/' + sub);
|
|
82
82
|
}
|
|
83
|
-
}
|
|
83
|
+
}
|
|
84
|
+
else if (fs.existsSync(path.join(full, 'binding.gyp'))) result.push(entry);
|
|
84
85
|
}
|
|
85
86
|
return result;
|
|
86
87
|
},
|
|
@@ -93,16 +94,14 @@ const
|
|
|
93
94
|
console.error(chalk.red(' [错误] 未找到 NDK ' + ndkVersion + ': ' + ndkPath)), process.exit(1);
|
|
94
95
|
const pluginDir = findPluginDir(userProjectDir);
|
|
95
96
|
if (!pluginDir) console.error(chalk.red(' [错误] 未找到 @flun/nodejs-mobile-react-native 插件')), process.exit(1);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
toolSuffix = process.platform === 'win32' ? '.cmd' : '',
|
|
101
|
-
binSuffix = process.platform === 'win32' ? '.exe' : '';
|
|
97
|
+
const gypPath = createRequire(path.join(pluginDir, 'package.json')).resolve('@flun/nodejs-mobile-gyp/bin/node-gyp.js'),
|
|
98
|
+
libnodeDir = path.join(pluginDir, 'android', 'libnode'), hostOs = process.platform === 'darwin' ? 'mac' : 'linux',
|
|
99
|
+
toolSuffix = process.platform === 'win32' ? '.cmd' : '', binSuffix = process.platform === 'win32' ? '.exe' : '',
|
|
100
|
+
hostTag = { win32: 'windows-x86_64', darwin: 'darwin-x86_64', linux: 'linux-x86_64' }[process.platform];
|
|
102
101
|
if (!hostTag) console.error(chalk.red(' [错误] 不支持的操作系统: ' + process.platform)), process.exit(1);
|
|
103
102
|
const toolchain = path.join(ndkPath, 'toolchains', 'llvm', 'prebuilt', hostTag),
|
|
104
|
-
assetsDir = path.dirname(targetDir), gradleProjectDir = path.join(buildDir, 'android')
|
|
105
|
-
|
|
103
|
+
assetsDir = path.dirname(targetDir), gradleProjectDir = path.join(buildDir, 'android'),
|
|
104
|
+
patchScript = path.join(pluginDir, 'scripts', 'patch-package.js');
|
|
106
105
|
if (fs.existsSync(patchScript)) run(process.execPath, [patchScript, path.join(targetDir, 'node_modules')],
|
|
107
106
|
{ cwd: targetDir, inherit: true });
|
|
108
107
|
for (const abi of abiFilters) {
|
|
@@ -132,11 +131,10 @@ const
|
|
|
132
131
|
}
|
|
133
132
|
catch (e) { console.error(chalk.red(' [错误] 原生模块编译失败 (' + abi + '): ' + e.message)), process.exit(1); }
|
|
134
133
|
const missing = findNativePackages(targetDir).filter(name => {
|
|
135
|
-
const pkgDir = path.join(targetDir, 'node_modules', name),
|
|
134
|
+
const pkgDir = path.join(targetDir, 'node_modules', name), releaseDir = path.join(pkgDir, 'build', 'Release'),
|
|
135
|
+
hasBuild = fs.existsSync(releaseDir) && fs.readdirSync(releaseDir).some(f => f.endsWith('.node')),
|
|
136
136
|
prebuildDir = path.join(pkgDir, 'prebuilds', 'android-' + map.arch),
|
|
137
|
-
hasPrebuild = fs.existsSync(prebuildDir) && fs.readdirSync(prebuildDir).some(f => f.endsWith('.node'))
|
|
138
|
-
releaseDir = path.join(pkgDir, 'build', 'Release'),
|
|
139
|
-
hasBuild = fs.existsSync(releaseDir) && fs.readdirSync(releaseDir).some(f => f.endsWith('.node'));
|
|
137
|
+
hasPrebuild = fs.existsSync(prebuildDir) && fs.readdirSync(prebuildDir).some(f => f.endsWith('.node'));
|
|
140
138
|
return !hasPrebuild && !hasBuild;
|
|
141
139
|
});
|
|
142
140
|
if (missing.length > 0) {
|
|
@@ -177,152 +175,168 @@ const
|
|
|
177
175
|
c = c.substring(0, idx) + injected + c.substring(idx);
|
|
178
176
|
fs.writeFileSync(file, c, 'utf-8');
|
|
179
177
|
console.log(chalk.green(' ✓ 已应用 iOS WebView SSL 补丁(允许 localhost 自签证书)'));
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* 装用户项目的生产依赖到 <buildDir>/nodejs-assets/nodejs-project/
|
|
183
|
-
* 只装 dependencies,不装 devDependencies
|
|
184
|
-
* 依赖 + 环境未变化且上次构建完成时跳过,否则按需重装 / 仅优化
|
|
185
|
-
* 快照记录在 buildDir/.deps-snapshot.json(与桌面版一致)
|
|
186
|
-
*/
|
|
187
|
-
installUserDeps = async (targetDir, userProjectDir, config, buildDir, targetPlatform) => {
|
|
188
|
-
const assetsDir = path.dirname(targetDir), bnmFile = path.join(assetsDir, 'BUILD_NATIVE_MODULES.txt'),
|
|
189
|
-
nodeModulesDir = path.join(targetDir, 'node_modules'), snapshotPath = path.join(buildDir, '.deps-snapshot.json'),
|
|
190
|
-
forceRebuild = process.env.NODE_MOBILE_FORCE_REBUILD === '1', hasNative = hasNativeModules(userProjectDir),
|
|
191
|
-
optimizeCfg = config.optimize || {}, thisOptimize = optimizeCfg.enabled !== false,
|
|
192
|
-
abiFilters = [...(config.android.abiFilters || [])].sort(),
|
|
193
|
-
runtimeMajor = resolveRuntimeMajor(userProjectDir, targetPlatform);
|
|
194
|
-
|
|
195
|
-
fs.writeFileSync(bnmFile, hasNative ? '1' : '0');
|
|
196
|
-
if (hasNative) console.log(chalk.gray(' ℹ️ 检测到原生模块(binding.gyp)'));
|
|
178
|
+
};
|
|
197
179
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
180
|
+
/**
|
|
181
|
+
* 装用户项目的生产依赖到 <buildDir>/nodejs-assets/nodejs-project/
|
|
182
|
+
* 只装 dependencies,不装 devDependencies
|
|
183
|
+
* 依赖 + 环境未变化且上次构建完成时跳过,否则按需重装 / 仅优化
|
|
184
|
+
* 快照记录在 buildDir/.deps-snapshot.json(与桌面版一致)
|
|
185
|
+
* >查看定义:@see {@link installUserDeps}
|
|
186
|
+
* @param {string} targetDir nodejs-project 目录
|
|
187
|
+
* @param {string} userProjectDir 用户项目目录
|
|
188
|
+
* @param {object} config 用户配置
|
|
189
|
+
* @param {string} buildDir 构建目录
|
|
190
|
+
* @param {string} targetPlatform 目标平台('android' 或 'ios')
|
|
191
|
+
* @returns {Promise<void>}
|
|
192
|
+
*/
|
|
193
|
+
const installUserDeps = async (targetDir, userProjectDir, config, buildDir, targetPlatform) => {
|
|
194
|
+
const assetsDir = path.dirname(targetDir), bnmFile = path.join(assetsDir, 'BUILD_NATIVE_MODULES.txt'),
|
|
195
|
+
nodeModulesDir = path.join(targetDir, 'node_modules'), snapshotPath = path.join(buildDir, '.deps-snapshot.json'),
|
|
196
|
+
forceRebuild = process.env.NODE_MOBILE_FORCE_REBUILD === '1', hasNative = hasNativeModules(userProjectDir),
|
|
197
|
+
abiFilters = [...(config.android.abiFilters || [])].sort(), optimizeCfg = config.optimize || {},
|
|
198
|
+
thisOptimize = optimizeCfg.enabled !== false, runtimeMajor = resolveRuntimeMajor(userProjectDir, targetPlatform);
|
|
203
199
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const old = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8')), verifiedOk = old.verified === true,
|
|
207
|
-
depsSame = JSON.stringify({
|
|
208
|
-
deps: old.deps, type: old.type, hasNative: old.hasNative, forceRebuild: old.forceRebuild
|
|
209
|
-
}) === JSON.stringify(snapshotData),
|
|
210
|
-
envSame = old.platform === targetPlatform && JSON.stringify(old.abiFilters) === JSON.stringify(abiFilters) &&
|
|
211
|
-
old.runtimeMajor === runtimeMajor,
|
|
212
|
-
lastOptimized = old.optimized !== false, selfHashSame = old.optimizeHash === SELF_HASH,
|
|
213
|
-
installerSame = old.installerHash === installerHash, nmExists = fs.existsSync(nodeModulesDir);
|
|
214
|
-
if (!depsSame) reasons.push('依赖清单已变化');
|
|
215
|
-
if (!verifiedOk) reasons.push('上次构建未完成');
|
|
216
|
-
if (!envSame) reasons.push('运行环境已变化');
|
|
217
|
-
if (!nmExists) reasons.push('缓存目录缺失');
|
|
218
|
-
if (lastOptimized && !thisOptimize) reasons.push('优化已由开启转为关闭');
|
|
219
|
-
if (thisOptimize && !selfHashSame && lastOptimized) reasons.push('优化程序已更新');
|
|
220
|
-
if (!installerSame) reasons.push('安装器已更新');
|
|
221
|
-
if (reasons.length > 0) {
|
|
222
|
-
shouldInstall = true, shouldOptimize = thisOptimize;
|
|
223
|
-
console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖')
|
|
224
|
-
+ '(原因: ' + reasons.join(', ') + ')'));
|
|
225
|
-
}
|
|
226
|
-
else if (thisOptimize && !lastOptimized)
|
|
227
|
-
shouldInstall = false, shouldOptimize = true, console.log(chalk.gray(' ℹ️ 依赖与环境未变化,仅需优化'));
|
|
228
|
-
else {
|
|
229
|
-
shouldInstall = false, shouldOptimize = false, console.log(chalk.gray(' ✓ 依赖与环境未变化,跳过安装与优化'));
|
|
230
|
-
await clearVerified(buildDir);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
} else
|
|
234
|
-
console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖') + '(原因: 缓存快照缺失)'));
|
|
200
|
+
fs.writeFileSync(bnmFile, hasNative ? '1' : '0');
|
|
201
|
+
if (hasNative) console.log(chalk.gray(' ℹ️ 检测到原生模块(binding.gyp)'));
|
|
235
202
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
203
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(targetDir, 'package.json'), 'utf-8')), deps = pkg.dependencies || {},
|
|
204
|
+
sortedDeps = Object.keys(deps).sort().reduce((acc, k) => { acc[k] = deps[k]; return acc; }, {}),
|
|
205
|
+
installerHash = createHash('sha256').update(fs.readFileSync(new URL(import.meta.url), 'utf-8'))
|
|
206
|
+
.digest('hex').slice(0, 16), snapshotData = { deps: sortedDeps, type: pkg.type || '', hasNative, forceRebuild };
|
|
239
207
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
208
|
+
let shouldInstall = true, shouldOptimize = thisOptimize, reasons = [];
|
|
209
|
+
if (fs.existsSync(snapshotPath)) {
|
|
210
|
+
const old = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8')), verifiedOk = old.verified === true,
|
|
211
|
+
depsSame = JSON.stringify({
|
|
212
|
+
deps: old.deps, type: old.type, hasNative: old.hasNative, forceRebuild: old.forceRebuild
|
|
213
|
+
}) === JSON.stringify(snapshotData),
|
|
214
|
+
envSame = old.platform === targetPlatform && JSON.stringify(old.abiFilters) === JSON.stringify(abiFilters) &&
|
|
215
|
+
old.runtimeMajor === runtimeMajor,
|
|
216
|
+
lastOptimized = old.optimized !== false, selfHashSame = old.optimizeHash === SELF_HASH,
|
|
217
|
+
installerSame = old.installerHash === installerHash, nmExists = fs.existsSync(nodeModulesDir);
|
|
218
|
+
if (!depsSame) reasons.push('依赖清单已变化');
|
|
219
|
+
if (!verifiedOk) reasons.push('上次构建未完成');
|
|
220
|
+
if (!envSame) reasons.push('运行环境已变化');
|
|
221
|
+
if (!nmExists) reasons.push('缓存目录缺失');
|
|
222
|
+
if (lastOptimized && !thisOptimize) reasons.push('优化已由开启转为关闭');
|
|
223
|
+
if (thisOptimize && !selfHashSame && lastOptimized) reasons.push('优化程序已更新');
|
|
224
|
+
if (!installerSame) reasons.push('安装器已更新');
|
|
225
|
+
if (reasons.length > 0) {
|
|
226
|
+
shouldInstall = true, shouldOptimize = thisOptimize;
|
|
227
|
+
console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖')
|
|
228
|
+
+ '(原因: ' + reasons.join(', ') + ')'));
|
|
229
|
+
}
|
|
230
|
+
else if (thisOptimize && !lastOptimized)
|
|
231
|
+
shouldInstall = false, shouldOptimize = true, console.log(chalk.gray(' ℹ️ 依赖与环境未变化,仅需优化'));
|
|
232
|
+
else {
|
|
233
|
+
shouldInstall = false, shouldOptimize = false, console.log(chalk.gray(' ✓ 依赖与环境未变化,跳过安装与优化'));
|
|
234
|
+
await clearVerified(buildDir);
|
|
235
|
+
return;
|
|
251
236
|
}
|
|
237
|
+
}
|
|
238
|
+
else console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖') + '(原因: 缓存快照缺失)'));
|
|
252
239
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
240
|
+
if (shouldInstall) {
|
|
241
|
+
if (fs.existsSync(nodeModulesDir))
|
|
242
|
+
console.log(chalk.cyan(' 清理旧 node_modules...')), fs.rmSync(nodeModulesDir, { recursive: true, force: true });
|
|
243
|
+
|
|
244
|
+
try { fs.rmSync(snapshotPath, { force: true }); } catch { }
|
|
245
|
+
const env = { ...process.env };
|
|
246
|
+
if (hasNative) env.NODEJS_MOBILE_BUILD_NATIVE_MODULES = '1';
|
|
247
|
+
console.log(chalk.cyan(' 安装用户生产依赖(nodejs-assets/nodejs-project)...'));
|
|
248
|
+
run('npm', ['install', '--omit=dev', '--no-audit', '--no-fund'], { cwd: targetDir, env, quiet: true });
|
|
249
|
+
const snap = {
|
|
250
|
+
...snapshotData, platform: targetPlatform, abiFilters, runtimeMajor,
|
|
251
|
+
optimizeHash: SELF_HASH, installerHash, optimized: false, verified: false
|
|
252
|
+
};
|
|
253
|
+
fs.writeFileSync(snapshotPath, JSON.stringify(snap, null, 2));
|
|
254
|
+
console.log(chalk.green(' ✓ 用户依赖安装完成'));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (hasNative && targetPlatform === 'android' && shouldInstall)
|
|
258
|
+
compileNativeModules(targetDir, userProjectDir, config, buildDir, abiFilters);
|
|
259
|
+
if (shouldOptimize) {
|
|
260
|
+
console.log(chalk.cyan(' [信息] 正在执行优化...'));
|
|
261
|
+
try {
|
|
262
|
+
await optimizeNodeModules(targetDir, {
|
|
263
|
+
targetPlatform, abiFilters, runtimeMajor, exclude: optimizeCfg.exclude || [], snapshotDir: buildDir
|
|
264
|
+
});
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.error(chalk.red(' [错误] node_modules 优化失败:' + err.message));
|
|
267
|
+
console.error(chalk.red(' [提示] 可在 mobileAppConfig.js 中设置 optimize: { enabled: false } 关闭优化,'));
|
|
268
|
+
console.error(chalk.red(' 或用 optimize: { exclude: ["包名"] } 排除问题包;')), process.exit(1);
|
|
266
269
|
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
* 修改 @flun/nodejs-mobile-react-native 插件 build.gradle 的 abiFilters
|
|
270
|
-
*/
|
|
271
|
-
patchNodejsMobilePlugin = (userProjectDir, config) => {
|
|
272
|
-
const pluginDir = findPluginDir(userProjectDir);
|
|
273
|
-
if (!pluginDir) return console.warn(chalk.yellow(' ⚠️ 未找到 @flun/nodejs-mobile-react-native 插件,跳过 patch'));
|
|
270
|
+
}
|
|
271
|
+
};
|
|
274
272
|
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
/**
|
|
274
|
+
* 修改 @flun/nodejs-mobile-react-native 插件 build.gradle 的 abiFilters
|
|
275
|
+
* >查看定义:@see {@link patchNodejsMobilePlugin}
|
|
276
|
+
* @param {string} userProjectDir 用户项目目录
|
|
277
|
+
* @param {object} config 用户配置
|
|
278
|
+
* @returns {void}
|
|
279
|
+
*/
|
|
280
|
+
const patchNodejsMobilePlugin = (userProjectDir, config) => {
|
|
281
|
+
const pluginDir = findPluginDir(userProjectDir);
|
|
282
|
+
if (!pluginDir) return console.warn(chalk.yellow(' ⚠️ 未找到 @flun/nodejs-mobile-react-native 插件,跳过 patch'));
|
|
277
283
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
c = c.replace(/abiFilters\s*=\s*\[[^\]]*\]/, `abiFilters = [${abiList}]`);
|
|
284
|
+
const file = path.join(pluginDir, 'android', 'build.gradle'),
|
|
285
|
+
abiList = config.android.abiFilters.map((x) => `"${x}"`).join(', ');
|
|
281
286
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
let c = fs.readFileSync(file, 'utf-8');
|
|
288
|
+
const before = c;
|
|
289
|
+
c = c.replace(/abiFilters\s*=\s*\[[^\]]*\]/, `abiFilters = [${abiList}]`);
|
|
290
|
+
|
|
291
|
+
if (c !== before)
|
|
292
|
+
fs.writeFileSync(file, c, 'utf-8'), console.log(chalk.green(` ✓ 已设置插件 abiFilters = [${abiList}]`));
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 让 react-native-webview 对 localhost 自签名证书放行
|
|
297
|
+
* Android: patch RNCWebViewClient.java
|
|
298
|
+
* iOS: patch RNCWebViewImpl.m
|
|
299
|
+
* 仅在 config.webview.allowSelfSignedCert === true 时应用
|
|
300
|
+
* 每次 test/build 都检查(npm 重装 webview 后会覆盖补丁)
|
|
301
|
+
* >查看定义:@see {@link patchWebViewSsl}
|
|
302
|
+
* @param {string} userProjectDir 用户项目目录
|
|
303
|
+
* @param {object} config 用户配置
|
|
304
|
+
* @returns {void}
|
|
305
|
+
*/
|
|
306
|
+
const patchWebViewSsl = (userProjectDir, config) => {
|
|
307
|
+
if (!config.webview || config.webview.allowSelfSignedCert !== true) return;
|
|
294
308
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
309
|
+
const file = path.join(
|
|
310
|
+
userProjectDir, 'node_modules', 'react-native-webview', 'android', 'src', 'main', 'java', 'com',
|
|
311
|
+
'reactnativecommunity', 'webview', 'RNCWebViewClient.java'
|
|
312
|
+
);
|
|
299
313
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
314
|
+
if (fs.existsSync(file)) {
|
|
315
|
+
let c = fs.readFileSync(file, 'utf-8');
|
|
316
|
+
if (c.includes('LOCALHOST_SSL_BYPASS')) console.log(chalk.green(' ✓ Android WebView SSL 补丁已应用'));
|
|
317
|
+
else {
|
|
318
|
+
const old = 'handler.cancel();', methodIndex = c.indexOf('onReceivedSslError');
|
|
319
|
+
if (methodIndex !== -1) {
|
|
320
|
+
const cancelIndex = c.indexOf(old, methodIndex);
|
|
321
|
+
if (cancelIndex !== -1) {
|
|
322
|
+
const newCode = [
|
|
323
|
+
'// LOCALHOST_SSL_BYPASS: 对 localhost/127.0.0.1 放行自签名证书(仅本地测试用)',
|
|
324
|
+
' if (failingUrl != null',
|
|
325
|
+
' && (failingUrl.startsWith("https://127.0.0.1") || ' +
|
|
326
|
+
' failingUrl.startsWith("https://localhost"))) {',
|
|
327
|
+
' handler.proceed();',
|
|
328
|
+
' return;',
|
|
329
|
+
' }',
|
|
330
|
+
' handler.cancel();',
|
|
331
|
+
].join('\n');
|
|
332
|
+
c = c.substring(0, cancelIndex) + newCode + c.substring(cancelIndex + old.length);
|
|
333
|
+
fs.writeFileSync(file, c, 'utf-8');
|
|
334
|
+
console.log(chalk.green(' ✓ 已应用 Android WebView SSL 补丁(允许 localhost 自签证书)'));
|
|
322
335
|
}
|
|
323
336
|
}
|
|
324
337
|
}
|
|
325
|
-
|
|
326
|
-
|
|
338
|
+
}
|
|
339
|
+
patchWebViewSslIos(userProjectDir); // iOS 分支
|
|
340
|
+
};
|
|
327
341
|
|
|
328
342
|
export { installUserDeps, patchNodejsMobilePlugin, patchWebViewSsl };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
|
-
import
|
|
4
|
+
import path from 'path';
|
|
5
5
|
|
|
6
6
|
const require = createRequire(import.meta.url), subpath = (...parts) => './' + path.posix.join(...parts),
|
|
7
7
|
BUNDLED_DIR = 'bundled', NM_DIR = 'node_modules', PKG_JSON = 'package.json',
|
|
@@ -491,8 +491,9 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
|
|
|
491
491
|
const platBad = plat && !myPlat.includes(plat), archBad = plat && arch && !myArch.includes(arch);
|
|
492
492
|
if (platBad || archBad) {
|
|
493
493
|
if (process.env.OPTIMIZE_DEBUG) {
|
|
494
|
-
const rel = toPosix(path.relative(nmDir, full))
|
|
495
|
-
|
|
494
|
+
const rel = toPosix(path.relative(nmDir, full)),
|
|
495
|
+
why = platBad ? '平台不符: ' + plat : '架构不符: ' + arch;
|
|
496
|
+
console.log(chalk.gray(` ✓ ${rel} (${why})`));
|
|
496
497
|
}
|
|
497
498
|
delFile(full);
|
|
498
499
|
}
|