@flun/node-mobile-app 4.0.6 → 4.0.8

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.
Files changed (49) hide show
  1. package/Android /346/236/204/345/273/272/346/214/207/345/215/227.md" +84 -76
  2. package/CHANGELOG.md +10 -8
  3. package/LICENSE +9 -4
  4. package/README.md +170 -77
  5. package/bin/cli.js +9 -9
  6. package/cli-assets/server.js +7 -4
  7. package/mobileAppConfig.js +32 -32
  8. package/package.json +5 -5
  9. package/scripts/copy-files.js +1 -1
  10. package/src/commands/build.js +9 -4
  11. package/src/commands/clean.js +3 -2
  12. package/src/commands/test.js +13 -9
  13. package/src/config.js +3 -3
  14. package/src/copy.js +41 -36
  15. package/src/fingerprint.js +4 -3
  16. package/src/generate.js +5 -3
  17. package/src/install.js +38 -27
  18. package/src/optimize-node-modules.js +10 -5
  19. package/src/patcher.js +37 -32
  20. package/src/platform-ios.js +6 -6
  21. package/src/utils.js +26 -22
  22. package/template/App.tsx +4 -4
  23. package/template/Gemfile +1 -1
  24. package/template/android/app/build.gradle +1 -1
  25. package/template/android/app/proguard-rules.pro +1 -1
  26. package/template/android/app/src/main/java/com/myapp/MainActivity.kt +1 -1
  27. package/template/android/app/src/main/java/com/myapp/MainApplication.kt +1 -1
  28. package/template/android/app/src/main/res/drawable/rn_edit_text_material.xml +6 -3
  29. package/template/android/app/src/main/res/raw/keep.xml +1 -1
  30. package/template/android/app/src/main/res/values/strings.xml +1 -1
  31. package/template/android/app/src/main/res/values/styles.xml +1 -1
  32. package/template/android/build.gradle +1 -1
  33. package/template/android/gradle.properties +1 -1
  34. package/template/android/gradlew +3 -2
  35. package/template/android/gradlew.bat +3 -2
  36. package/template/android/settings.gradle +1 -1
  37. package/template/app.json +1 -1
  38. package/template/index.js +2 -2
  39. package/template/ios/.xcode.env +1 -1
  40. package/template/ios/MyApp/Images.xcassets/AppIcon.appiconset/Contents.json +1 -1
  41. package/template/ios/MyApp/Images.xcassets/Contents.json +1 -1
  42. package/template/ios/MyApp/Info.plist +1 -1
  43. package/template/ios/MyApp/LaunchScreen.storyboard +30 -12
  44. package/template/ios/MyApp/PrivacyInfo.xcprivacy +1 -1
  45. package/template/ios/MyApp.xcodeproj/project.pbxproj +34 -15
  46. package/template/ios/MyApp.xcodeproj/xcshareddata/xcschemes/MyApp.xcscheme +1 -1
  47. package/template/ios/Podfile +1 -1
  48. package/template/package.json +1 -1
  49. package//350/207/252/345/256/232/344/271/211libnode/346/214/207/345/215/227.md +131 -98
@@ -21,7 +21,9 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
21
21
  runBuild = async (flags) => {
22
22
  const userProjectDir = process.cwd(), release = !!flags.release, isIos = !!flags.ios, total = isIos ? 8 : 7;
23
23
 
24
- console.log(chalk.cyan(`\n=== node-mobile-app build (${isIos ? 'iOS' : 'Android'}, ${release ? 'release' : 'debug'}) ===\n`));
24
+ console.log(chalk.cyan(
25
+ `\n=== node-mobile-app build (${isIos ? 'iOS' : 'Android'}, ${release ? 'release' : 'debug'}) ===\n`
26
+ ));
25
27
  console.log(chalk.gray('用户项目: ' + userProjectDir));
26
28
  if (isIos) ensureMac(), checkIosTools();
27
29
  console.log(chalk.cyan(`\n[1/${total}] 检查用户配置与入口文件`)), ensureUserConfig(userProjectDir, PKG_ROOT);
@@ -59,10 +61,12 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
59
61
 
60
62
  if (isIos) {
61
63
  console.log(chalk.cyan(`\n[7/${total}] pod install`)), podInstall(path.join(buildDir, 'ios'));
62
- console.log(chalk.cyan(`\n[8/${total}] xcodebuild archive + export`)), xcodebuildArchive(buildDir, config, release, userProjectDir);
64
+ console.log(chalk.cyan(`\n[8/${total}] xcodebuild archive + export`)),
65
+ xcodebuildArchive(buildDir, config, release, userProjectDir);
63
66
 
64
67
  const ipaPath = xcodebuildExport(buildDir, config, release, outDir, userProjectDir),
65
- dstName = `${safeName}-${config.versionName}-${release ? 'release' : 'debug'}.ipa`, dst = path.join(outDir, dstName);
68
+ dstName = `${safeName}-${config.versionName}-${release ? 'release' : 'debug'}.ipa`,
69
+ dst = path.join(outDir, dstName);
66
70
  fs.copyFileSync(ipaPath, dst), console.log(chalk.green('\n✅ 打包完成'));
67
71
 
68
72
  const mb = (fs.statSync(dst).size / 1024 / 1024).toFixed(1);
@@ -100,7 +104,8 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
100
104
  if (apks.length === 0) console.log(chalk.yellow(' ⚠️ 未找到 APK'));
101
105
 
102
106
  for (const f of apks) {
103
- const src = path.join(apkDir, f), dstName = `${safeName}-${config.versionName}-${release ? 'release' : 'debug'}.apk`,
107
+ const src = path.join(apkDir, f),
108
+ dstName = `${safeName}-${config.versionName}-${release ? 'release' : 'debug'}.apk`,
104
109
  dst = path.join(outDir, dstName);
105
110
 
106
111
  fs.copyFileSync(src, dst);
@@ -20,8 +20,9 @@ const runClean = async () => {
20
20
 
21
21
  for (const t of targets) {
22
22
  if (fs.existsSync(t.path))
23
- removeDir(t.path), console.log(chalk.green('已清理: ' + t.label + ' -> ' + (path.relative(userProjectDir, t.path) || t.path)));
24
- else console.log(chalk.gray('跳过(不存在): ' + t.label));
23
+ removeDir(t.path),
24
+ console.log(chalk.green('已清理: ' + t.label + ' -> ' + (path.relative(userProjectDir, t.path) || t.path)));
25
+ else console.log(chalk.gray('跳过(不存在): ' + t.label));
25
26
  }
26
27
 
27
28
  console.log(chalk.green('\n✅ 清理完成'));
@@ -45,7 +45,8 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
45
45
  console.log(chalk.cyan(`\n[4/${total}] 复制用户项目`));
46
46
  const { moduleType, targetDir } = copyUserProject(userProjectDir, buildDir, config);
47
47
 
48
- console.log(chalk.cyan(`\n[5/${total}] 安装依赖与编译优化`)), await installUserDeps(targetDir, userProjectDir, config, buildDir, isIos ? 'ios' : 'android');
48
+ console.log(chalk.cyan(`\n[5/${total}] 安装依赖与编译优化`)),
49
+ await installUserDeps(targetDir, userProjectDir, config, buildDir, isIos ? 'ios' : 'android');
49
50
  patchNodejsMobilePlugin(userProjectDir, config);
50
51
  patchWebViewSsl(userProjectDir, config), writeLocalProperties(buildDir, config);
51
52
 
@@ -54,7 +55,7 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
54
55
 
55
56
  if (isIos) {
56
57
  console.log(chalk.cyan(`\n[7/${total}] pod install`)), podInstall(path.join(buildDir, 'ios'));
57
- console.log(chalk.cyan(`\n[8/${total}] 生成 JS bundle + Hermes 字节码(由 Xcode Run Script 自动)`));
58
+ console.log(chalk.cyan(`\n[8/${total}] 生成 JS bundle + Hermes 字节码(由 Xcode Run Script 自动)`));
58
59
  const bundlePath = path.join(buildDir, 'ios', 'main.jsbundle');
59
60
  runSilent('npx', [
60
61
  'react-native', 'bundle',
@@ -68,12 +69,12 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
68
69
  console.log(chalk.cyan(`\n[9/${total}] 编译并安装到模拟器/设备`));
69
70
  const args = ['react-native', 'run-ios'];
70
71
  if (flags.device) args.push('--device', flags.device);
71
- // FORCE_BUNDLING=1:即使 Debug+模拟器也把 main.jsbundle 打进 App
72
+ // FORCE_BUNDLING=1:即使 Debug+模拟器也把 main.jsbundle 打进 App
72
73
  const env = { ...process.env, FORCE_BUNDLING: '1', NODE_MOBILE_MODE: 'test' };
73
74
  run('npx', args, { cwd: buildDir, env, inherit: true });
74
75
  await markVerified(buildDir);
75
76
  } else {
76
- console.log(chalk.cyan(`\n[7/${total}] 生成 JS bundle(离线可用)`));
77
+ console.log(chalk.cyan(`\n[7/${total}] 生成 JS bundle(离线可用)`));
77
78
  const assetsDir = path.join(buildDir, 'android', 'app', 'src', 'main', 'assets'),
78
79
  resDir = path.join(buildDir, 'android', 'app', 'src', 'main', 'res');
79
80
  ensureDir(assetsDir);
@@ -89,7 +90,7 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
89
90
  ], { cwd: buildDir });
90
91
  console.log(chalk.green(' ✓ bundle 已生成'));
91
92
 
92
- console.log(chalk.cyan(`\n[8/${total}] 编译 Hermes 字节码(加速离线启动)`));
93
+ console.log(chalk.cyan(`\n[8/${total}] 编译 Hermes 字节码(加速离线启动)`));
93
94
  const hermesc = findHermesc(userProjectDir);
94
95
  if (hermesc) {
95
96
  const hbcPath = bundlePath + '.hbc';
@@ -97,7 +98,7 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
97
98
  fs.copyFileSync(hbcPath, bundlePath), fs.unlinkSync(hbcPath);
98
99
  console.log(chalk.green(' ✓ 已替换为 Hermes 字节码'));
99
100
  }
100
- else console.warn(chalk.yellow(' ⚠️ 未找到 hermesc,跳过字节码编译(离线启动会较慢)'));
101
+ else console.warn(chalk.yellow(' ⚠️ 未找到 hermesc,跳过字节码编译(离线启动会较慢)'));
101
102
 
102
103
  const mergedAsset = path.join(buildDir, 'android', 'app', 'build', 'intermediates', 'assets', 'debug',
103
104
  'mergeDebugAssets', 'index.android.bundle');
@@ -131,9 +132,11 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
131
132
  let ready = false;
132
133
  for (let i = 0; i < 60; i++) {
133
134
  await new Promise(r => setTimeout(r, 500));
134
- if (await metroReady()) { ready = true, console.log(chalk.green(' ✓ Metro 已就绪(后台常驻,支持热更新)')); break; }
135
+ if (await metroReady()) {
136
+ ready = true, console.log(chalk.green(' ✓ Metro 已就绪(后台常驻,支持热更新)')); break;
137
+ }
135
138
  }
136
- if (!ready) console.log(chalk.yellow(' ⚠️ Metro 30 秒内未就绪,详见 .metro.log;app 将回落到本地 bundle'));
139
+ if (!ready) console.log(chalk.yellow(' ⚠️ Metro 30 秒内未就绪,详见 .metro.log;app 将回落到本地 bundle'));
137
140
  }
138
141
  console.log(chalk.cyan(`\n[10/${total}] 编译 Debug 包`));
139
142
  const gradlew = process.platform === 'win32' ? 'gradlew.bat' : './gradlew';
@@ -157,7 +160,8 @@ const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
157
160
  const devPrefix = flags.device ? ['-s', flags.device] : [];
158
161
  run(adb, [...devPrefix, 'install', '-r', apkPath], { quiet: true });
159
162
  console.log(chalk.green(' ✓ 已安装到设备'));
160
- run(adb, [...devPrefix, 'shell', 'monkey', '-p', config.appId, '-c', 'android.intent.category.LAUNCHER', '1'], { quiet: true });
163
+ run(adb, [...devPrefix, 'shell', 'monkey', '-p', config.appId, '-c', 'android.intent.category.LAUNCHER', '1'],
164
+ { quiet: true });
161
165
  writeInstallState(buildDir, { appId: config.appId, variant: 'debug', installedAt: new Date().toISOString() });
162
166
  await markVerified(buildDir);
163
167
  }
package/src/config.js CHANGED
@@ -84,10 +84,10 @@ const DEFAULT_CONFIG = {
84
84
 
85
85
  const pkg = path.join(userProjectDir, 'package.json');
86
86
  if (!fs.existsSync(pkg)) errors.push('用户项目缺少 package.json');
87
- // 极简防呆:至少 2 段、每段非空、只含 [a-zA-Z0-9_-],不允许首尾点或连续点
88
- // 具体平台是否合法交给 Gradle / Xcode 报错(Android 不允许 -,iOS 允许)
87
+ // 极简防呆:至少 2 段、每段非空、只含 [a-zA-Z0-9_-],不允许首尾点或连续点
88
+ // 具体平台是否合法交给 Gradle / Xcode 报错(Android 不允许 -,iOS 允许)
89
89
  if (typeof config.appId !== 'string' || !/^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(config.appId))
90
- errors.push('appId 格式不合法(需反向域名格式,如 com.example.app): ' + config.appId);
90
+ errors.push('appId 格式不合法(需反向域名格式,如 com.example.app): ' + config.appId);
91
91
  if (!Number.isInteger(config.versionCode) || config.versionCode < 1) errors.push('versionCode 必须是正整数');
92
92
  if (!config.android.abiFilters?.length) errors.push('android.abiFilters 不能为空');
93
93
  if (errors.length) {
package/src/copy.js CHANGED
@@ -14,7 +14,7 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
14
14
  'Pods/', 'local.properties', '*.bak', 'gradle.properties.bak',
15
15
  ],
16
16
  /**
17
- * 内部:递归覆盖式复制模板,跳过 keepSet 命中的文件
17
+ * 内部:递归覆盖式复制模板,跳过 keepSet 命中的文件
18
18
  */
19
19
  copyDirOverwrite = (src, dst, exclude, buildDir, keepSet) => {
20
20
  ensureDir(dst);
@@ -30,7 +30,7 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
30
30
  }
31
31
  },
32
32
  /**
33
- * 内部:增量复制用户项目到 nodejs-project
33
+ * 内部:增量复制用户项目到 nodejs-project
34
34
  * 保留 node_modules 等由其他步骤生成的产物
35
35
  */
36
36
  copyDirIncremental = (src, dst, exclude) => {
@@ -53,15 +53,15 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
53
53
  }
54
54
  },
55
55
  /**
56
- * 获取 build 目录路径:<userProject>/node-mobile-app-build
56
+ * 获取 build 目录路径:<userProject>/node-mobile-app-build
57
57
  */
58
58
  getBuildDir = userProjectDir => path.join(userProjectDir, BUILD_DIR_NAME),
59
59
 
60
60
  /**
61
61
  * 把 buildDir/package.json 的依赖版本同步为主包 dependencies 的版本
62
- * - 依赖名单来自 buildDir/package.json(模板声明需要哪些依赖)
63
- * - 版本号来自 pkgRoot/package.json 的 dependencies(单一数据源)
64
- * - 若主包缺少某个依赖,抛错中断(避免模板占位值被真正使用)
62
+ * - 依赖名单来自 buildDir/package.json(模板声明需要哪些依赖)
63
+ * - 版本号来自 pkgRoot/package.json 的 dependencies(单一数据源)
64
+ * - 若主包缺少某个依赖,抛错中断(避免模板占位值被真正使用)
65
65
  */
66
66
  syncTemplateDependencies = (pkgRoot, buildDir) => {
67
67
  const buildPkgPath = path.join(buildDir, 'package.json');
@@ -84,25 +84,26 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
84
84
 
85
85
  if (needWrite) {
86
86
  writeIfChanged(buildPkgPath, JSON.stringify(buildPkg, null, 2) + '\n');
87
- if (depsUpdated > 0) console.log(chalk.green(' ✓ 已同步 ' + depsUpdated + ' 个依赖版本到 ' + BUILD_DIR_NAME + '/package.json'));
87
+ if (depsUpdated > 0)
88
+ console.log(chalk.green(' ✓ 已同步 ' + depsUpdated + ' 个依赖版本到 ' + BUILD_DIR_NAME + '/package.json'));
88
89
  }
89
90
 
90
91
  if (missing.length > 0) {
91
92
  throw new Error(
92
- 'template/package.json 声明的以下依赖在主包 dependencies 中不存在:\n' +
93
+ 'template/package.json 声明的以下依赖在主包 dependencies 中不存在:\n' +
93
94
  ' ' + missing.join(', ') + '\n' +
94
- ' 请在 @flun/node-mobile-app 的 package.json 中补上对应声明(版本号随意,\n' +
95
- ' 但必须存在,以便 syncTemplateDependencies 覆盖模板里的占位值)。'
95
+ ' 请在 @flun/node-mobile-app 的 package.json 中补上对应声明(版本号随意,\n' +
96
+ ' 但必须存在,以便 syncTemplateDependencies 覆盖模板里的占位值);'
96
97
  );
97
98
  }
98
99
  },
99
100
  /**
100
- * 把主包 node_modules 里被 npm 嵌套(未提升到用户根)的依赖,按需链接到用户根 node_modules 下,
101
- * 让 RN CLI / Metro 从用户根也能解析到它们。
102
- * - 判据:从用户根视角解析不到,才建链接;能解析则跳过;
103
- * - 范围:遍历主包 node_modules 顶层全部条目(含 @scope/ 下每个子项),不列举任何包名;
104
- * - 落点:只在用户根 node_modules 缺失条目时建 symlink,不覆盖已有内容、不碰 package.json / package-lock.json;
105
- * - 建链接失败:中断报错,不静默。
101
+ * 把主包 node_modules 里被 npm 嵌套(未提升到用户根)的依赖,按需链接到用户根 node_modules 下,
102
+ * 让 RN CLI / Metro 从用户根也能解析到它们;
103
+ * - 判据:从用户根视角解析不到,才建链接;能解析则跳过;
104
+ * - 范围:遍历主包 node_modules 顶层全部条目(含 @scope/ 下每个子项),不列举任何包名;
105
+ * - 落点:只在用户根 node_modules 缺失条目时建 symlink,不覆盖已有内容、不碰 package.json / package-lock.json;
106
+ * - 建链接失败:中断报错,不静默;
106
107
  */
107
108
  linkNestedDeps = (pkgRoot, buildDir) => {
108
109
  const pkgNm = path.join(pkgRoot, 'node_modules');
@@ -133,8 +134,8 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
133
134
  fs.symlinkSync(realSrc, dst, 'junction'), linked++;
134
135
  } catch (err) {
135
136
  throw new Error(
136
- '建立兜底链接失败:' + dst + ' -> ' + realSrc + '\n' +
137
- ' 原因:' + err.message + '\n' +
137
+ '建立兜底链接失败:' + dst + ' -> ' + realSrc + '\n' +
138
+ ' 原因:' + err.message + '\n' +
138
139
  ' 请检查 node_modules 是否被其它程序占用后重试'
139
140
  );
140
141
  }
@@ -143,8 +144,8 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
143
144
  },
144
145
  /**
145
146
  * 把 CLI 包里的 template/ 逐文件覆盖到 build 目录
146
- * - 只覆盖模板里存在的文件,不删除 buildDir 里已有的其他内容(保留 Gradle 增量缓存)
147
- * - 模板文件被改写时自动同步;新增文件自动添加
147
+ * - 只覆盖模板里存在的文件,不删除 buildDir 里已有的其他内容(保留 Gradle 增量缓存)
148
+ * - 模板文件被改写时自动同步;新增文件自动添加
148
149
  * - 若模板里删除了文件,旧副本残留由 CLI 版本指纹触发全量重建处理
149
150
  */
150
151
  copyTemplate = (pkgRoot, buildDir, config) => {
@@ -163,7 +164,7 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
163
164
  },
164
165
  /**
165
166
  * 复制用户 Node 项目到 <buildDir>/nodejs-assets/nodejs-project
166
- * 复制后重写 package.json:只保留 dependencies,剔除 devDependencies;用 config 的 allowScripts
167
+ * 复制后重写 package.json:只保留 dependencies,剔除 devDependencies;用 config 的 allowScripts
167
168
  */
168
169
  copyUserProject = (userProjectDir, buildDir, config) => {
169
170
  const targetDir = path.join(buildDir, 'nodejs-assets', 'nodejs-project');
@@ -174,14 +175,15 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
174
175
  'mobileAppConfig.js', 'mobileAppConfig.mjs', 'mobileAppConfig.cjs'
175
176
  ];
176
177
 
177
- // 增量复制:内容变化的文件才覆盖,保留 node_modules 和其他未变文件
178
+ // 增量复制:内容变化的文件才覆盖,保留 node_modules 和其他未变文件
178
179
  copyDirIncremental(userProjectDir, targetDir, exclude);
179
- // 黑名单式过滤:只删 devDependencies 和 allowScripts,其余字段原样保留
180
+ // 黑名单式过滤:只删 devDependencies 和 allowScripts,其余字段原样保留
180
181
  const userPkgPath = path.join(userProjectDir, 'package.json');
181
182
  if (!fs.existsSync(userPkgPath)) throw new Error('用户项目缺少 package.json');
182
183
  const pkg = readJson(userPkgPath), out = { ...pkg };
183
184
  delete out.devDependencies;
184
- out.allowScripts = (config.allowScripts && Object.keys(config.allowScripts).length) ? config.allowScripts : { node: true };
185
+ out.allowScripts =
186
+ (config.allowScripts && Object.keys(config.allowScripts).length) ? config.allowScripts : { node: true };
185
187
  writeIfChanged(path.join(targetDir, 'package.json'), JSON.stringify(out, null, 2) + '\n');
186
188
 
187
189
  console.log(chalk.green(' ✓ 已同步用户项目到 nodejs-assets/nodejs-project'));
@@ -191,11 +193,12 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
191
193
  return { pkg: out, moduleType: out.type === 'module' ? 'module' : 'commonjs', targetDir };
192
194
  },
193
195
  /**
194
- * 生成 <buildDir>/android/local.properties(Gradle 需要 sdk.dir)
196
+ * 生成 <buildDir>/android/local.properties(Gradle 需要 sdk.dir)
195
197
  */
196
198
  writeLocalProperties = (buildDir, config) => {
197
199
  const sdkPath = resolveSdkPath(config);
198
- if (!sdkPath) return console.warn(chalk.yellow(' ⚠️ 未找到 Android SDK,请在 mobileAppConfig.js 里配置 android.sdkPath'));
200
+ if (!sdkPath)
201
+ return console.warn(chalk.yellow(' ⚠️ 未找到 Android SDK,请在 mobileAppConfig.js 里配置 android.sdkPath'));
199
202
 
200
203
  const localProps = path.join(buildDir, 'android', 'local.properties'),
201
204
  escaped = sdkPath.replace(/\\/g, '\\\\').replace(/:/g, '\\:');
@@ -211,17 +214,17 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
211
214
  if (fs.existsSync(entryPath)) return { created: false, path: entryPath };
212
215
 
213
216
  const fallback = path.join(pkgRoot, 'cli-assets', 'server.js');
214
- if (!fs.existsSync(fallback)) throw new Error('入口文件不存在: ' + config.serverPath + '(CLI 内置模板也缺失)');
217
+ if (!fs.existsSync(fallback)) throw new Error('入口文件不存在: ' + config.serverPath + '(CLI 内置模板也缺失)');
215
218
 
216
219
  ensureDir(path.dirname(entryPath)), fs.copyFileSync(fallback, entryPath);
217
220
  console.log(chalk.green(' ✓ 生成默认入口文件: ' + config.serverPath));
218
- console.log(chalk.gray(' (因为用户项目里没有这个文件,从 CLI 内置模板复制)'));
221
+ console.log(chalk.gray(' (因为用户项目里没有这个文件,从 CLI 内置模板复制)'));
219
222
 
220
223
  return { created: true, path: entryPath };
221
224
  },
222
225
  /**
223
226
  * 确保用户项目根存在 mobileAppConfig.js
224
- * 不存在时从 CLI 包根复制一份(带注释的模板)
227
+ * 不存在时从 CLI 包根复制一份(带注释的模板)
225
228
  */
226
229
  ensureUserConfig = (userProjectDir, pkgRoot) => {
227
230
  const configPath = path.join(userProjectDir, 'mobileAppConfig.js');
@@ -232,16 +235,16 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
232
235
 
233
236
  fs.copyFileSync(fallback, configPath);
234
237
  console.log(chalk.green(' ✓ 生成默认配置文件: mobileAppConfig.js'));
235
- console.log(chalk.gray(' (因为用户项目里没有这个文件,从 CLI 内置模板复制)'));
238
+ console.log(chalk.gray(' (因为用户项目里没有这个文件,从 CLI 内置模板复制)'));
236
239
 
237
240
  return { created: true, path: configPath };
238
241
  },
239
242
  /**
240
- * 按 config.android.abiFilters 修改 buildDir 里的 app/build.gradle:
243
+ * 按 config.android.abiFilters 修改 buildDir 里的 app/build.gradle:
241
244
  * - defaultConfig.ndk.abiFilters
242
245
  * - gradle.properties 的 reactNativeArchitectures
243
246
  * 插件 build.gradle 读的是 project(":app").android.defaultConfig.ndk.abiFilters,
244
- * 所以必须改这里,改插件自身的 abiFilters 无效。
247
+ * 所以必须改这里,改插件自身的 abiFilters 无效;
245
248
  */
246
249
  patchAndroidAbi = (buildDir, config) => {
247
250
  const file = path.join(buildDir, 'android', 'app', 'build.gradle');
@@ -253,10 +256,10 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
253
256
 
254
257
  // defaultConfig 里的 ndk { abiFilters ... }
255
258
  c = c.replace(/(defaultConfig\s*\{[\s\S]*?ndk\s*\{\s*abiFilters\s+)("[^"]*"(?:\s*,\s*"[^"]*")*)/, `$1${abiList}`);
256
- // 兜底:没有 ndk {} 块时,插到 defaultConfig 里
259
+ // 兜底:没有 ndk {} 块时,插到 defaultConfig 里
257
260
  if (!/ndk\s*\{\s*abiFilters/.test(c))
258
261
  c = c.replace(/(defaultConfig\s*\{)/, `$1\n ndk {\n abiFilters ${abiList}\n }`);
259
- // 不 patch splits.abi.include:AGP 禁止它和 ndk.abiFilters 同时存在
262
+ // 不 patch splits.abi.include:AGP 禁止它和 ndk.abiFilters 同时存在
260
263
  if (c !== before) writeIfChanged(file, c), console.log(chalk.green(` ✓ 已设置 app ndk.abiFilters = [${abiList}]`));
261
264
 
262
265
  // 同步 gradle.properties 的 reactNativeArchitectures
@@ -265,7 +268,8 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
265
268
  let p = fs.readFileSync(propsFile, 'utf-8');
266
269
  const abiPlain = (config.android.abiFilters || []).join(','), before2 = p;
267
270
  p = p.replace(/^reactNativeArchitectures=.*/gm, `reactNativeArchitectures=${abiPlain}`);
268
- if (p !== before2) writeIfChanged(propsFile, p), console.log(chalk.green(` ✓ 已设置 reactNativeArchitectures = ${abiPlain}`));
271
+ if (p !== before2) writeIfChanged(propsFile, p),
272
+ console.log(chalk.green(` ✓ 已设置 reactNativeArchitectures = ${abiPlain}`));
269
273
  }
270
274
  },
271
275
  /**
@@ -278,7 +282,8 @@ const BUILD_DIR_NAME = 'node-mobile-app-build',
278
282
  if (extras.length === 0) return;
279
283
 
280
284
  const buildPkgPath = path.join(buildDir, 'package.json');
281
- if (!fs.existsSync(buildPkgPath)) return console.warn(chalk.yellow(' ⚠️ 未找到 buildDir/package.json,跳过 extraRnDependencies'));
285
+ if (!fs.existsSync(buildPkgPath))
286
+ return console.warn(chalk.yellow(' ⚠️ 未找到 buildDir/package.json,跳过 extraRnDependencies'));
282
287
 
283
288
  const buildPkg = readJson(buildPkgPath), missing = [];
284
289
  if (!buildPkg.dependencies) buildPkg.dependencies = {};
@@ -63,7 +63,8 @@ const FINGERPRINT_FILE = '.build-fingerprint.json',
63
63
 
64
64
  let pluginVersion = null, cliVersion = null;
65
65
  const pluginPkg = path.join(pluginDir, 'package.json'), cliPkg = path.join(pkgRoot, 'package.json');
66
- if (fs.existsSync(pluginPkg)) try { pluginVersion = JSON.parse(fs.readFileSync(pluginPkg, 'utf-8')).version; } catch { };
66
+ if (fs.existsSync(pluginPkg))
67
+ try { pluginVersion = JSON.parse(fs.readFileSync(pluginPkg, 'utf-8')).version; } catch { };
67
68
  if (fs.existsSync(cliPkg)) try { cliVersion = JSON.parse(fs.readFileSync(cliPkg, 'utf-8')).version; } catch { };
68
69
 
69
70
  const depsHash = hashUserDeps(userProjectDir), a = config.android || {},
@@ -108,7 +109,7 @@ const FINGERPRINT_FILE = '.build-fingerprint.json',
108
109
  if (templateChanged) reasons.push('模板文件');
109
110
  if (depsChanged) reasons.push('用户依赖');
110
111
  if (appInfoChanged) reasons.push('应用信息');
111
- console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 Gradle 缓存、CMake 缓存与依赖缓存'));
112
+ console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 Gradle 缓存、CMake 缓存与依赖缓存'));
112
113
  clearGradleCaches(buildDir, userProjectDir);
113
114
  removeDir(path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native', 'android', '.cxx'));
114
115
  clearDepsCaches(buildDir);
@@ -117,7 +118,7 @@ const FINGERPRINT_FILE = '.build-fingerprint.json',
117
118
  const reasons = [], targets = [];
118
119
  if (depsChanged) reasons.push('用户依赖'), targets.push('依赖缓存', 'Gradle 缓存');
119
120
  if (appInfoChanged) reasons.push('应用信息'), targets.push('Gradle 缓存');
120
- console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 ' + [...new Set(targets)].join('、')));
121
+ console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 ' + [...new Set(targets)].join('、')));
121
122
  clearGradleCaches(buildDir, userProjectDir);
122
123
  if (depsChanged) clearDepsCaches(buildDir);
123
124
  }
package/src/generate.js CHANGED
@@ -4,9 +4,9 @@ import { writeIfChanged } from './utils.js';
4
4
  import chalk from 'chalk';
5
5
 
6
6
  /**
7
- * 生成 main.js(桥接代码,用户不需要手写)
7
+ * 生成 main.js(桥接代码,用户不需要手写)
8
8
  *
9
- * 上报机制(通过 rn-bridge channel 发消息给 App.tsx):
9
+ * 上报机制(通过 rn-bridge channel 发消息给 App.tsx):
10
10
  * - 'Node started on <URL>' server.js 成功调用 listen
11
11
  * - 'Node failed: <原因>' 加载异常或 5 秒内未捕获 listen
12
12
  */
@@ -74,7 +74,9 @@ const generateMainJs = (targetDir, config, moduleType) => {
74
74
  }
75
75
  })();
76
76
 
77
- ${config.advanced.keepAlive === false ? '// keepAlive: false — 不发送心跳' : "setInterval(() => rn_bridge.channel.send('heartbeat'), " + hb + ");"}
77
+ ${config.advanced.keepAlive === false
78
+ ? '// keepAlive: false - 不发送心跳'
79
+ : "setInterval(() => rn_bridge.channel.send('heartbeat'), " + hb + ");"}
78
80
 
79
81
  rn_bridge.channel.on('message', msg => rn_bridge.channel.send('Echo: ' + msg));
80
82
  })();`,
package/src/install.js CHANGED
@@ -8,8 +8,8 @@ import { resolveSdkPath } from './config.js';
8
8
  import { createHash } from 'crypto';
9
9
  const
10
10
  /**
11
- * 内部:从 libnode 的 node_version.h 读取运行时 Node 主版本号
12
- * 读不到直接报错退出(说明 libnode 目录不完整,继续执行必失败)
11
+ * 内部:从 libnode 的 node_version.h 读取运行时 Node 主版本号
12
+ * 读不到直接报错退出(说明 libnode 目录不完整,继续执行必失败)
13
13
  */
14
14
  resolveRuntimeMajor = (userProjectDir, targetPlatform) => {
15
15
  const sub = targetPlatform === 'ios' ? 'ios' : 'android',
@@ -17,16 +17,16 @@ const
17
17
  sub, 'libnode', 'include', 'node', 'node_version.h');
18
18
  if (!fs.existsSync(header)) {
19
19
  console.error(chalk.red(' [错误] 未找到 node_version.h: ' + header));
20
- console.error(chalk.red(' 说明 libnode 目录不完整,请重新安装依赖。')), process.exit(1);
20
+ console.error(chalk.red(' 说明 libnode 目录不完整,请重新安装依赖;')), process.exit(1);
21
21
  }
22
22
  const m = fs.readFileSync(header, 'utf-8').match(/#define\s+NODE_MAJOR_VERSION\s+(\d+)/);
23
23
  if (!m) console.error(chalk.red(' [错误] 无法解析 node_version.h 中的 NODE_MAJOR_VERSION')), process.exit(1);
24
24
  return m[1];
25
25
  },
26
26
  /**
27
- * 内部:判断用户项目的生产依赖里是否含原生模块(binding.gyp)
27
+ * 内部:判断用户项目的生产依赖里是否含原生模块(binding.gyp)
28
28
  * 按用户根 package.json 的 dependencies 键名,去 userProjectDir/node_modules 下逐个查
29
- * 不用扫 nodejs-project/node_modules:全新构建时它还不存在
29
+ * 不用扫 nodejs-project/node_modules:全新构建时它还不存在
30
30
  */
31
31
  hasNativeModules = userProjectDir => {
32
32
  const pkgPath = path.join(userProjectDir, 'package.json');
@@ -42,7 +42,7 @@ const
42
42
  return false;
43
43
  },
44
44
  /**
45
- * 内部:定位 @flun/nodejs-mobile-react-native 插件目录
45
+ * 内部:定位 @flun/nodejs-mobile-react-native 插件目录
46
46
  */
47
47
  findPluginDir = userProjectDir => {
48
48
  const candidates = [path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native')];
@@ -56,7 +56,8 @@ const
56
56
  },
57
57
  resolveNdkVersion = buildDir => {
58
58
  const gradle = path.join(buildDir, 'android', 'build.gradle');
59
- if (!fs.existsSync(gradle)) console.error(chalk.red(' [错误] 未找到 android/build.gradle: ' + gradle)), process.exit(1);
59
+ if (!fs.existsSync(gradle))
60
+ console.error(chalk.red(' [错误] 未找到 android/build.gradle: ' + gradle)), process.exit(1);
60
61
  const m = fs.readFileSync(gradle, 'utf-8').match(/ndkVersion\s*=\s*"([^"]+)"/);
61
62
  if (!m) console.error(chalk.red(' [错误] 无法从 android/build.gradle 解析 ndkVersion')), process.exit(1);
62
63
  return m[1];
@@ -76,7 +77,8 @@ const
76
77
  if (entry.startsWith('@')) {
77
78
  for (const sub of fs.readdirSync(full)) {
78
79
  const pkgDir = path.join(full, sub);
79
- if (fs.statSync(pkgDir).isDirectory() && fs.existsSync(path.join(pkgDir, 'binding.gyp'))) result.push(entry + '/' + sub);
80
+ if (fs.statSync(pkgDir).isDirectory()
81
+ && fs.existsSync(path.join(pkgDir, 'binding.gyp'))) result.push(entry + '/' + sub);
80
82
  }
81
83
  } else if (fs.existsSync(path.join(full, 'binding.gyp'))) result.push(entry);
82
84
  }
@@ -87,7 +89,8 @@ const
87
89
  if (!sdkPath) console.error(chalk.red(' [错误] 未找到 Android SDK,无法编译原生模块')), process.exit(1);
88
90
  const ndkVersion = resolveNdkVersion(buildDir), compileSdk = resolveCompileSdk(buildDir),
89
91
  ndkPath = path.join(sdkPath, 'ndk', ndkVersion);
90
- if (!fs.existsSync(ndkPath)) console.error(chalk.red(' [错误] 未找到 NDK ' + ndkVersion + ': ' + ndkPath)), process.exit(1);
92
+ if (!fs.existsSync(ndkPath))
93
+ console.error(chalk.red(' [错误] 未找到 NDK ' + ndkVersion + ': ' + ndkPath)), process.exit(1);
91
94
  const pluginDir = findPluginDir(userProjectDir);
92
95
  if (!pluginDir) console.error(chalk.red(' [错误] 未找到 @flun/nodejs-mobile-react-native 插件')), process.exit(1);
93
96
  const libnodeDir = path.join(pluginDir, 'android', 'libnode'),
@@ -124,7 +127,9 @@ const
124
127
  path.join(targetDir, 'node_modules', '.bin'), process.env.PATH].filter(Boolean).join(path.delimiter)
125
128
  };
126
129
  console.log(chalk.cyan(' ℹ️ 编译原生模块 (' + abi + ')...'));
127
- try { run('npm', ['rebuild', '--build-from-source', '--no-audit', '--no-fund'], { cwd: targetDir, env, quiet: true }); }
130
+ try {
131
+ run('npm', ['rebuild', '--build-from-source', '--no-audit', '--no-fund'], { cwd: targetDir, env, quiet: true });
132
+ }
128
133
  catch (e) { console.error(chalk.red(' [错误] 原生模块编译失败 (' + abi + '): ' + e.message)), process.exit(1); }
129
134
  const missing = findNativePackages(targetDir).filter(name => {
130
135
  const pkgDir = path.join(targetDir, 'node_modules', name),
@@ -144,7 +149,7 @@ const
144
149
  }
145
150
  },
146
151
  /**
147
- * 内部:iOS 的 WebView SSL 补丁
152
+ * 内部:iOS 的 WebView SSL 补丁
148
153
  */
149
154
  patchWebViewSslIos = userProjectDir => {
150
155
  const file = path.join(userProjectDir, 'node_modules', 'react-native-webview', 'apple', 'RNCWebViewImpl.m');
@@ -154,12 +159,13 @@ const
154
159
  if (c.includes('LOCALHOST_SSL_BYPASS_IOS')) return console.log(chalk.green(' ✓ iOS WebView SSL 补丁已应用'));
155
160
 
156
161
  // 在 "NSString* host = nil;" 那一段之后插入 localhost 放行
157
- const anchor = ' if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {',
162
+ const anchor =
163
+ ' if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {',
158
164
  idx = c.indexOf(anchor);
159
165
  if (idx === -1) return console.warn(chalk.yellow(' ⚠️ RNCWebViewImpl.m 里未找到锚点,跳过 iOS SSL 补丁'));
160
166
 
161
167
  const injected = [
162
- ' // LOCALHOST_SSL_BYPASS_IOS: 对 localhost/127.0.0.1 放行自签名证书(仅本地测试用)',
168
+ ' // LOCALHOST_SSL_BYPASS_IOS: 对 localhost/127.0.0.1 放行自签名证书(仅本地测试用)',
163
169
  ' if (host != nil && ([host isEqualToString:@"127.0.0.1"] || [host isEqualToString:@"localhost"])) {',
164
170
  ' NSURLCredential *cred = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];',
165
171
  ' completionHandler(NSURLSessionAuthChallengeUseCredential, cred);',
@@ -170,13 +176,13 @@ const
170
176
 
171
177
  c = c.substring(0, idx) + injected + c.substring(idx);
172
178
  fs.writeFileSync(file, c, 'utf-8');
173
- console.log(chalk.green(' ✓ 已应用 iOS WebView SSL 补丁(允许 localhost 自签证书)'));
179
+ console.log(chalk.green(' ✓ 已应用 iOS WebView SSL 补丁(允许 localhost 自签证书)'));
174
180
  },
175
181
  /**
176
182
  * 装用户项目的生产依赖到 <buildDir>/nodejs-assets/nodejs-project/
177
183
  * 只装 dependencies,不装 devDependencies
178
184
  * 依赖 + 环境未变化且上次构建完成时跳过,否则按需重装 / 仅优化
179
- * 快照记录在 buildDir/.deps-snapshot.json(与桌面版一致)
185
+ * 快照记录在 buildDir/.deps-snapshot.json(与桌面版一致)
180
186
  */
181
187
  installUserDeps = async (targetDir, userProjectDir, config, buildDir, targetPlatform) => {
182
188
  const assetsDir = path.dirname(targetDir), bnmFile = path.join(assetsDir, 'BUILD_NATIVE_MODULES.txt'),
@@ -187,11 +193,12 @@ const
187
193
  runtimeMajor = resolveRuntimeMajor(userProjectDir, targetPlatform);
188
194
 
189
195
  fs.writeFileSync(bnmFile, hasNative ? '1' : '0');
190
- if (hasNative) console.log(chalk.gray(' ℹ️ 检测到原生模块(binding.gyp)'));
196
+ if (hasNative) console.log(chalk.gray(' ℹ️ 检测到原生模块(binding.gyp)'));
191
197
 
192
198
  const pkg = JSON.parse(fs.readFileSync(path.join(targetDir, 'package.json'), 'utf-8')), deps = pkg.dependencies || {},
193
199
  sortedDeps = Object.keys(deps).sort().reduce((acc, k) => { acc[k] = deps[k]; return acc; }, {}),
194
- installerHash = createHash('sha256').update(fs.readFileSync(new URL(import.meta.url), 'utf-8')).digest('hex').slice(0, 16),
200
+ installerHash = createHash('sha256').update(fs.readFileSync(new URL(import.meta.url), 'utf-8'))
201
+ .digest('hex').slice(0, 16),
195
202
  snapshotData = { deps: sortedDeps, type: pkg.type || '', hasNative, forceRebuild };
196
203
 
197
204
  let shouldInstall = true, shouldOptimize = thisOptimize, reasons = [];
@@ -213,7 +220,8 @@ const
213
220
  if (!installerSame) reasons.push('安装器已更新');
214
221
  if (reasons.length > 0) {
215
222
  shouldInstall = true, shouldOptimize = thisOptimize;
216
- console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖') + '(原因: ' + reasons.join(', ') + ')'));
223
+ console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖')
224
+ + '(原因: ' + reasons.join(', ') + ')'));
217
225
  }
218
226
  else if (thisOptimize && !lastOptimized)
219
227
  shouldInstall = false, shouldOptimize = true, console.log(chalk.gray(' ℹ️ 依赖与环境未变化,仅需优化'));
@@ -222,7 +230,8 @@ const
222
230
  await clearVerified(buildDir);
223
231
  return;
224
232
  }
225
- } else console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖') + '(原因: 缓存快照缺失)'));
233
+ } else
234
+ console.log(chalk.gray(' ℹ️ ' + (thisOptimize ? '重新安装依赖+优化' : '重新安装依赖') + '(原因: 缓存快照缺失)'));
226
235
 
227
236
  if (shouldInstall) {
228
237
  if (fs.existsSync(nodeModulesDir))
@@ -231,7 +240,7 @@ const
231
240
  try { fs.rmSync(snapshotPath, { force: true }); } catch { }
232
241
  const env = { ...process.env };
233
242
  if (hasNative) env.NODEJS_MOBILE_BUILD_NATIVE_MODULES = '1';
234
- console.log(chalk.cyan(' 安装用户生产依赖(nodejs-assets/nodejs-project)...'));
243
+ console.log(chalk.cyan(' 安装用户生产依赖(nodejs-assets/nodejs-project)...'));
235
244
  run('npm', ['install', '--omit=dev', '--no-audit', '--no-fund'], { cwd: targetDir, env, quiet: true });
236
245
  const snap = {
237
246
  ...snapshotData, platform: targetPlatform, abiFilters, runtimeMajor,
@@ -250,9 +259,9 @@ const
250
259
  targetPlatform, abiFilters, runtimeMajor, exclude: optimizeCfg.exclude || [], snapshotDir: buildDir
251
260
  });
252
261
  } catch (err) {
253
- console.error(chalk.red(' [错误] node_modules 优化失败:' + err.message));
262
+ console.error(chalk.red(' [错误] node_modules 优化失败:' + err.message));
254
263
  console.error(chalk.red(' [提示] 可在 mobileAppConfig.js 中设置 optimize: { enabled: false } 关闭优化,'));
255
- console.error(chalk.red(' 或用 optimize: { exclude: ["包名"] } 排除问题包。')), process.exit(1);
264
+ console.error(chalk.red(' 或用 optimize: { exclude: ["包名"] } 排除问题包;')), process.exit(1);
256
265
  }
257
266
  }
258
267
  },
@@ -270,14 +279,15 @@ const
270
279
  const before = c;
271
280
  c = c.replace(/abiFilters\s*=\s*\[[^\]]*\]/, `abiFilters = [${abiList}]`);
272
281
 
273
- if (c !== before) fs.writeFileSync(file, c, 'utf-8'), console.log(chalk.green(` ✓ 已设置插件 abiFilters = [${abiList}]`));
282
+ if (c !== before) fs.writeFileSync(file, c, 'utf-8'),
283
+ console.log(chalk.green(` ✓ 已设置插件 abiFilters = [${abiList}]`));
274
284
  },
275
285
  /**
276
286
  * 让 react-native-webview 对 localhost 自签名证书放行
277
287
  * Android: patch RNCWebViewClient.java
278
288
  * iOS: patch RNCWebViewImpl.m
279
289
  * 仅在 config.webview.allowSelfSignedCert === true 时应用
280
- * 每次 test/build 都检查(npm 重装 webview 后会覆盖补丁)
290
+ * 每次 test/build 都检查(npm 重装 webview 后会覆盖补丁)
281
291
  */
282
292
  patchWebViewSsl = (userProjectDir, config) => {
283
293
  if (!config.webview || config.webview.allowSelfSignedCert !== true) return;
@@ -296,9 +306,10 @@ const
296
306
  const cancelIndex = c.indexOf(old, methodIndex);
297
307
  if (cancelIndex !== -1) {
298
308
  const newCode = [
299
- '// LOCALHOST_SSL_BYPASS: 对 localhost/127.0.0.1 放行自签名证书(仅本地测试用)',
309
+ '// LOCALHOST_SSL_BYPASS: 对 localhost/127.0.0.1 放行自签名证书(仅本地测试用)',
300
310
  ' if (failingUrl != null',
301
- ' && (failingUrl.startsWith("https://127.0.0.1") || failingUrl.startsWith("https://localhost"))) {',
311
+ ' && (failingUrl.startsWith("https://127.0.0.1") || ' +
312
+ ' failingUrl.startsWith("https://localhost"))) {',
302
313
  ' handler.proceed();',
303
314
  ' return;',
304
315
  ' }',
@@ -306,7 +317,7 @@ const
306
317
  ].join('\n');
307
318
  c = c.substring(0, cancelIndex) + newCode + c.substring(cancelIndex + old.length);
308
319
  fs.writeFileSync(file, c, 'utf-8');
309
- console.log(chalk.green(' ✓ 已应用 Android WebView SSL 补丁(允许 localhost 自签证书)'));
320
+ console.log(chalk.green(' ✓ 已应用 Android WebView SSL 补丁(允许 localhost 自签证书)'));
310
321
  }
311
322
  }
312
323
  }