@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
|
@@ -129,7 +129,7 @@ NODE_MOBILE_PREBUILT_ANDROID_SKIP=1 # 只跳过 Android
|
|
|
129
129
|
**预编译产物仓库**:
|
|
130
130
|
|
|
131
131
|
- GitHub:https://github.com/OpenFlun/nodejs-mobile
|
|
132
|
-
- Gitee(
|
|
132
|
+
- Gitee(中国):https://gitee.com/OpenFlun/nodejs-mobile
|
|
133
133
|
|
|
134
134
|
**架构支持(按版本)**:
|
|
135
135
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,35 +1,25 @@
|
|
|
1
1
|
# 变更日志
|
|
2
2
|
|
|
3
|
-
## [4.
|
|
4
|
-
### 优化
|
|
5
|
-
- 细节优化;
|
|
6
|
-
|
|
7
|
-
## [4.1.0] - 2026-11-06 10:10
|
|
3
|
+
## [4.2.0] - 2026-11-09 15:33
|
|
8
4
|
### 新增
|
|
9
|
-
- 新增 `
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
- 新增 `android.gradleDistributionUrl` 配置项,可指定 Gradle 发行版的完整下载地址;未配置时按腾讯、阿里、
|
|
6
|
+
官方顺序探测,三者均不可达则回落官方并警告;已配置时直接使用,失败不回退,并提示可改回 null 或换可达地址;
|
|
7
|
+
- 新增 `android.mavenRepositories` 配置项,可自定义 Maven 仓库数组,同时替换 `settings.gradle` 与根
|
|
8
|
+
`build.gradle` 的仓库列表;未配置时保持模板默认(阿里云 public/google/gradle-plugin + 官方 google/mavenCentral);
|
|
12
9
|
### 优化
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
- `
|
|
17
|
-
|
|
10
|
+
- 导出函数统一改为独立 `const` 声明并按字母序集中导出,未导出内容仍用逗号链;导出项补全标准 JSDoc;
|
|
11
|
+
- 各文件 import 统一为第三方包在前、相对路径在后、各自字母序,花括号内名字按大小写不敏感字母序;
|
|
12
|
+
- 删除无调用方的 `readInstallState`,移除 `ensureInstallable` 未使用的 `buildDir` 参数;
|
|
13
|
+
- 模板 `App.tsx`、`.prettierrc.js`、`babel.config.js`、`eslint.config.js`、`metro.config.js` 改为
|
|
14
|
+
`const` 声明加末尾导出;
|
|
18
15
|
|
|
16
|
+
## [4.1.2] - 2026-11-07 22:27
|
|
19
17
|
### 修复
|
|
20
|
-
-
|
|
21
|
-
|
|
18
|
+
- 修复 Android 构建长时间卡在 Gradle 下载的问题;模板 `gradle-wrapper.properties` 的 `distributionUrl`
|
|
19
|
+
由 `services.gradle.org` 改为腾讯云镜像,中国直连官方源常超时且不报错;
|
|
20
|
+
- 修复 `--debug` 下 Gradle 进度条被拆成大量点与数字的问题;调试模式编译改为 `stdio: 'inherit'`,
|
|
21
|
+
让进度条直连终端刷新,不再经管道逐块拆分;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
- README 命令列表与 `mobileAppConfig.js` 的「常用命令」注释补 `example`;
|
|
25
|
-
- README 包目录结构按 `files` 字段顺序重排;
|
|
26
|
-
|
|
27
|
-
## [4.0.9] - 2026-11-01 22:23
|
|
23
|
+
## [4.1.1] - 2026-11-06 11:11
|
|
28
24
|
### 优化
|
|
29
|
-
-
|
|
30
|
-
- 补充 `--release` 的适用范围与 Android 签名说明,`--arch` 注明仅 `build` 可用;
|
|
31
|
-
- `test` 的 Android 说明改为 `adb` 可连的设备(真机或模拟器),并注明默认架构为 `arm64-v8a`;
|
|
32
|
-
### 修复
|
|
33
|
-
- `help`、`-h`、`--help` 改为正常退出,此前会以非零码退出;
|
|
34
|
-
- 未知命令现在先输出 `✗ 未知命令: <名称>` 再打印帮助,此前只打印帮助;
|
|
35
|
-
- 统一文本文件换行为 LF,去除 BOM 与末尾多余换行;
|
|
25
|
+
- 细节优化;
|
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
- **自动识别服务地址**:拦截 `server.js` 里的 `listen()` 调用,动态获取真实协议和端口
|
|
15
15
|
- **失败可见**:5 秒内未启动,加载异常,App 显示错误页并支持展开详情(开发者可查)
|
|
16
16
|
- **增量构建**:文件指纹判断缓存,无变化时 10 秒级重跑
|
|
17
|
+
- **内置镜像探测 + 可自定义**:Gradle 发行版默认走腾讯云,未配置时按腾讯、阿里、官方顺序探测;Maven 仓库默认
|
|
18
|
+
阿里云 public/google/gradle-plugin 加官方源,两者均可在 `mobileAppConfig.js` 里自定义;
|
|
17
19
|
- **跨平台**:Android(Windows / macOS / Linux)+ iOS(仅 macOS)
|
|
18
20
|
- **离线可用**:`build` 打出的 APK / IPA 内置 JS,无需连电脑也能跑
|
|
19
21
|
- **node_modules 优化(默认开启)**:构建时先交叉编译原生模块,再自动执行六阶段优化--先清空 postinstall 资源(保留 js 供合并),
|
|
@@ -154,7 +156,7 @@ npm i -D @flun/node-mobile-app
|
|
|
154
156
|
### 快速开始
|
|
155
157
|
|
|
156
158
|
```bash
|
|
157
|
-
# 1. 编辑配置:打开 mobileAppConfig.js
|
|
159
|
+
# 1. 编辑配置:打开 mobileAppConfig.js,改必填项 serverPath 或完整自定义;
|
|
158
160
|
|
|
159
161
|
# 2. 开发调试(需 adb 可连的设备,走 Metro 热更新)
|
|
160
162
|
npx node-mobile-app test
|
|
@@ -231,9 +233,28 @@ adb install -r "./out/myapp-1.0.0-release.apk"
|
|
|
231
233
|
|
|
232
234
|
## 参考配置
|
|
233
235
|
|
|
234
|
-
|
|
236
|
+
所有定制集中在这一个文件;`serverPath` 字段值是必填项;
|
|
235
237
|
|
|
236
|
-
###
|
|
238
|
+
### 入口与标识
|
|
239
|
+
|
|
240
|
+
- `serverPath`
|
|
241
|
+
- 类型:`string`
|
|
242
|
+
- 默认:`'./server.js'`
|
|
243
|
+
- 说明:Node.js 启动脚本路径,指向的文件必须存在
|
|
244
|
+
- `appId`
|
|
245
|
+
- 类型:`string`
|
|
246
|
+
- 默认:`'com.example.app'`
|
|
247
|
+
- 说明:应用唯一标识(反向域名格式,Android/iOS 通用)
|
|
248
|
+
- `versionCode`
|
|
249
|
+
- 类型:`int`
|
|
250
|
+
- 默认:`1`
|
|
251
|
+
- 说明:内部版本号,必须为正整数
|
|
252
|
+
|
|
253
|
+
构建前会校验以下项,不满足即报错:用户项目根必须有 `package.json`;`appId` 需为反向域名格式;
|
|
254
|
+
`versionCode` 需为正整数;`android.abiFilters` 不能为空;`android.gradleDistributionUrl` 与
|
|
255
|
+
`android.mavenRepositories` 若配置,格式不合法也会报错(见 Android 章节).
|
|
256
|
+
|
|
257
|
+
### 应用信息
|
|
237
258
|
|
|
238
259
|
- `appName`
|
|
239
260
|
- 类型:`string \| null`
|
|
@@ -243,35 +264,44 @@ adb install -r "./out/myapp-1.0.0-release.apk"
|
|
|
243
264
|
- 类型:`string \| null`
|
|
244
265
|
- 默认:`null`
|
|
245
266
|
- 说明:APK / IPA 输出目录(默认 `./out`)
|
|
246
|
-
- `
|
|
267
|
+
- `versionName`
|
|
268
|
+
- 类型:`string`
|
|
269
|
+
- 默认:`'1.0.0'`
|
|
270
|
+
- 说明:用户可见版本名(Android versionName / iOS CFBundleShortVersionString)
|
|
271
|
+
|
|
272
|
+
### WebView
|
|
273
|
+
|
|
274
|
+
以下字段均位于 `webview` 对象下:
|
|
275
|
+
|
|
276
|
+
- `webview.showLoading`
|
|
247
277
|
- 类型:`boolean`
|
|
248
278
|
- 默认:`true`
|
|
249
279
|
- 说明:是否显示加载指示器
|
|
250
|
-
- `loadingText`
|
|
280
|
+
- `webview.loadingText`
|
|
251
281
|
- 类型:`string`
|
|
252
282
|
- 默认:`'正在启动服务...'`
|
|
253
283
|
- 说明:加载提示文字
|
|
254
|
-
- `enableJavaScript`
|
|
284
|
+
- `webview.enableJavaScript`
|
|
255
285
|
- 类型:`boolean`
|
|
256
286
|
- 默认:`true`
|
|
257
287
|
- 说明:是否启用 JavaScript
|
|
258
|
-
- `enableDomStorage`
|
|
288
|
+
- `webview.enableDomStorage`
|
|
259
289
|
- 类型:`boolean`
|
|
260
290
|
- 默认:`true`
|
|
261
291
|
- 说明:是否启用 DOM Storage
|
|
262
|
-
- `allowFileAccess`
|
|
292
|
+
- `webview.allowFileAccess`
|
|
263
293
|
- 类型:`boolean`
|
|
264
294
|
- 默认:`true`
|
|
265
295
|
- 说明:是否允许访问本地文件
|
|
266
|
-
- `mixedContentMode`
|
|
296
|
+
- `webview.mixedContentMode`
|
|
267
297
|
- 类型:`string`
|
|
268
298
|
- 默认:`'always'`
|
|
269
299
|
- 说明:混合内容模式:`never` / `always` / `compatibility`
|
|
270
|
-
- `backgroundColor`
|
|
300
|
+
- `webview.backgroundColor`
|
|
271
301
|
- 类型:见下
|
|
272
302
|
- 默认:`'#ffffff'`
|
|
273
303
|
- 说明:WebView 背景色;支持:固定色 `'#ffffff'` / 跟随系统 `'system'` / 深浅两值 `{ light, dark }`
|
|
274
|
-
- `allowSelfSignedCert`
|
|
304
|
+
- `webview.allowSelfSignedCert`
|
|
275
305
|
- 类型:`boolean`
|
|
276
306
|
- 默认:`false`
|
|
277
307
|
- 说明:见下文"HTTPS 自签证书"
|
|
@@ -302,6 +332,16 @@ adb install -r "./out/myapp-1.0.0-release.apk"
|
|
|
302
332
|
- 类型:`string`
|
|
303
333
|
- 默认:`'37.0.0'`
|
|
304
334
|
- 说明:构建工具版本
|
|
335
|
+
- `gradleDistributionUrl`
|
|
336
|
+
- 类型:`string \| null`
|
|
337
|
+
- 默认:`null`
|
|
338
|
+
- 说明:Gradle 发行版的完整下载地址.未配置时按腾讯、阿里、官方顺序探测,三者均不可达则回落官方并警告;
|
|
339
|
+
已配置时直接使用,失败不回退,并提示可改回 `null` 或换可达地址
|
|
340
|
+
- `mavenRepositories`
|
|
341
|
+
- 类型:`string[] \| null`
|
|
342
|
+
- 默认:`null`
|
|
343
|
+
- 说明:自定义 Maven 仓库数组,同时替换 `settings.gradle` 与根 `build.gradle` 的仓库列表;未配置时保持模板默认
|
|
344
|
+
(阿里云 `public`/`google`/`gradle-plugin` + 官方 `google`/`mavenCentral`)
|
|
305
345
|
- `abiFilters`
|
|
306
346
|
- 类型:`string[]`
|
|
307
347
|
- 默认:`['arm64-v8a']`
|
|
@@ -591,14 +631,28 @@ adb logcat | grep -E "NODEJS-MOBILE|ReactNativeJS"
|
|
|
591
631
|
### Q:Android 构建卡在拉取依赖(`react-android` / `hermes-android` 等)?
|
|
592
632
|
|
|
593
633
|
Android 模板的 `settings.gradle` 与 `build.gradle` 已在 `google()` / `mavenCentral()` 之前插入阿里云镜像,
|
|
594
|
-
|
|
634
|
+
中国网络优先走镜像;海外网络镜像不命中会自动回退官方源,不影响功能.
|
|
595
635
|
|
|
596
636
|
若仍卡住:
|
|
597
637
|
|
|
598
638
|
- 检查镜像可达:浏览器打开 `https://maven.aliyun.com/repository/public` 是否正常;
|
|
599
639
|
- 检查本机 `~/.gradle/init.gradle` 是否有与仓库顺序冲突的配置(如强制替换仓库),有则临时移除以排除干扰;
|
|
640
|
+
- 若所在网络对默认镜像均不友好,可在 `mobileAppConfig.js` 里配置 `android.mavenRepositories` 指定自己的仓库数组,
|
|
641
|
+
配置后会完全替换模板默认仓库,未配置则保持模板默认;
|
|
600
642
|
- 停掉残留 Daemon 后重试:在 `node-mobile-app-build/android/` 下执行 `gradlew --stop`,再重跑 `test` / `build`.
|
|
601
643
|
|
|
644
|
+
### Q:Android 构建卡在下载 Gradle 发行版(`gradle-*-bin.zip`)?
|
|
645
|
+
|
|
646
|
+
Gradle Wrapper 下载发行版走单个 `distributionUrl`,没有多 URL 自动回退机制.默认模板指向腾讯云镜像,
|
|
647
|
+
中国网络可直接使用.若卡住:
|
|
648
|
+
|
|
649
|
+
- 直接配置完整地址:`android.gradleDistributionUrl` 填一个可达的 `gradle-*-bin.zip` 地址;
|
|
650
|
+
配置后不再探测,失败不回退,会提示改回 `null` 或换可达地址;
|
|
651
|
+
- 交回自动探测:把 `android.gradleDistributionUrl` 设为 `null`,工具会按腾讯、阿里、官方顺序探测,
|
|
652
|
+
三者均不可达时回落官方并警告;
|
|
653
|
+
- Wrapper 只对单个 URL 做重试,不会在多个地址间切换;若地址本身不可达,换地址或改回 `null` 走探测才是有效手段;
|
|
654
|
+
- 已下载完成的部分会留在 `~/.gradle/wrapper/dists/` 下,下次构建复用,不会重复下载.
|
|
655
|
+
|
|
602
656
|
### Q:Express 版本怎么选?
|
|
603
657
|
|
|
604
658
|
- **Android**:内置完整 ICU(full-icu)→ 可以用 Express 5
|
|
@@ -658,6 +712,6 @@ ISC
|
|
|
658
712
|
## 贡献
|
|
659
713
|
|
|
660
714
|
- 源码:[GitHub](https://github.com/OpenFlun/node-mobile-app)
|
|
661
|
-
-
|
|
715
|
+
- 中国镜像:[gitee](https://gitee.com/OpenFlun/node-mobile-app)
|
|
662
716
|
- 问题反馈:[Issues](https://github.com/OpenFlun/node-mobile-app/issues)
|
|
663
717
|
- 主页:[npm 包页面](https://www.npmjs.com/package/@flun/node-mobile-app#readme)
|
package/bin/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
3
|
+
import { copyFile } from '../scripts/copy-files.js';
|
|
4
4
|
import { runBuild } from '../src/commands/build.js';
|
|
5
5
|
import { runClean } from '../src/commands/clean.js';
|
|
6
|
-
import {
|
|
6
|
+
import { runTest } from '../src/commands/test.js';
|
|
7
7
|
|
|
8
8
|
// 输出用法;
|
|
9
9
|
const printHelp = () => {
|
|
@@ -34,7 +34,7 @@ const printHelp = () => {
|
|
|
34
34
|
/**
|
|
35
35
|
* 解析 --key value 参数,重复键覆盖为后者;
|
|
36
36
|
* @param {string[]} args 子命令后的参数
|
|
37
|
-
* @returns {
|
|
37
|
+
* @returns {object} 解析结果
|
|
38
38
|
*/
|
|
39
39
|
parseFlags = args => {
|
|
40
40
|
const f = {};
|
package/mobileAppConfig.js
CHANGED
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
* App 使用该地址加载页面,无需在此配置;
|
|
21
21
|
* 若 5 秒内未能识别,App 显示启动失败并可在页面展开详情查看原因;
|
|
22
22
|
* 动态端口(listen(0))会自动获取系统分配的真实端口;
|
|
23
|
+
*
|
|
24
|
+
* >查看定义:@see {@link mobileAppConfig}
|
|
23
25
|
*/
|
|
24
|
-
|
|
26
|
+
const mobileAppConfig = {
|
|
25
27
|
// ===== 必填 =====
|
|
26
28
|
serverPath: './server.js', // Node.js 启动脚本路径
|
|
27
29
|
|
|
@@ -63,6 +65,15 @@ export default {
|
|
|
63
65
|
targetSdkVersion: 36, // 目标 SDK
|
|
64
66
|
compileSdkVersion: 37, // 编译 SDK
|
|
65
67
|
buildToolsVersion: '37.0.0', // 构建工具版本
|
|
68
|
+
gradleDistributionUrl: null, // 下载地址,示例:'https://mirrors.cloud.tencent.com/gradle/gradle-9.5.1-bin.zip'
|
|
69
|
+
// Maven 仓库,用于 buildscript 插件与 allprojects 依赖;按顺序尝试,失败回退下一个
|
|
70
|
+
// 未配置时使用内置默认:阿里云 public/google/gradle-plugin + 官方 google/mavenCentral
|
|
71
|
+
mavenRepositories: null, // 示例见下,按需取消注释
|
|
72
|
+
// mavenRepositories: [
|
|
73
|
+
// 'https://maven.aliyun.com/repository/public', // Maven Central:通用开源库
|
|
74
|
+
// 'https://maven.aliyun.com/repository/google', // Google Maven:AGP、AndroidX
|
|
75
|
+
// 'https://maven.aliyun.com/repository/gradle-plugin', // Gradle 插件门户:插件本体
|
|
76
|
+
// ],
|
|
66
77
|
abiFilters: ['arm64-v8a'], // 打包架构(真机用 arm64-v8a, 模拟器加 x86_64)
|
|
67
78
|
icon: './build/icon.png', // 应用图标(建议 512x512 PNG)
|
|
68
79
|
usesCleartextTraffic: true, // 是否允许明文流量(http)
|
|
@@ -141,7 +152,7 @@ export default {
|
|
|
141
152
|
'.vscode/',
|
|
142
153
|
'.idea/',
|
|
143
154
|
'.vs/',
|
|
144
|
-
'node_modules/',
|
|
155
|
+
'node_modules/',
|
|
145
156
|
'sessions/',
|
|
146
157
|
'users.json',
|
|
147
158
|
'.env',
|
|
@@ -163,5 +174,7 @@ export default {
|
|
|
163
174
|
'unrs-resolver': true,
|
|
164
175
|
'@parcel/watcher': true,
|
|
165
176
|
'bcrypt': true,
|
|
166
|
-
}
|
|
167
|
-
};
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export default mobileAppConfig;
|
package/package.json
CHANGED
package/scripts/copy-files.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
|
|
6
6
|
// 要复制的文件位置、目标目录和目标文件路径
|
|
@@ -10,7 +10,8 @@ const __filename = fileURLToPath(import.meta.url), __dirname = path.dirname(__fi
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* 复制配置文件到项目根目录
|
|
13
|
-
* @
|
|
13
|
+
* >查看定义:@see {@link copyFile}
|
|
14
|
+
* @returns {boolean} 复制是否成功
|
|
14
15
|
*/
|
|
15
16
|
const copyFile = () => {
|
|
16
17
|
console.log(chalk.gray('项目根目录: ') + targetDir);
|
package/src/commands/build.js
CHANGED
|
@@ -1,120 +1,126 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from 'chalk';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
3
4
|
import { fileURLToPath } from 'url';
|
|
4
5
|
import { loadConfig, validateConfig } from '../config.js';
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
patchExtraRnDependencies
|
|
7
|
+
copyTemplate, copyUserProject, ensureUserConfig, ensureUserEntry, getBuildDir, patchAndroidAbi,
|
|
8
|
+
patchExtraRnDependencies, writeLocalProperties
|
|
8
9
|
} from '../copy.js';
|
|
10
|
+
import { checkAndResetCache } from '../fingerprint.js';
|
|
11
|
+
import { generateMainJs, generateRuntime } from '../generate.js';
|
|
9
12
|
import { installUserDeps, patchNodejsMobilePlugin, patchWebViewSsl } from '../install.js';
|
|
10
13
|
import { markVerified } from '../optimize-node-modules.js';
|
|
11
|
-
import { generateMainJs, generateRuntime } from '../generate.js';
|
|
12
|
-
import { checkAndResetCache } from '../fingerprint.js';
|
|
13
14
|
import {
|
|
14
|
-
|
|
15
|
+
patchAndroidConfig, patchAppConfig, patchIcon, patchIosConfig, patchIosIcon, patchMavenRepositories,
|
|
16
|
+
patchSigning, resolveAppName
|
|
15
17
|
} from '../patcher.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
18
|
+
import { checkIosTools, ensureMac, podInstall, xcodebuildArchive, xcodebuildExport } from '../platform-ios.js';
|
|
19
|
+
import { ensureDir, run, sanitizeFileName, warnGradleDownloadHint } from '../utils.js';
|
|
20
|
+
|
|
21
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
22
|
+
/**
|
|
23
|
+
* 构建桌面应用:读取配置、校验、生成工程、安装依赖、编译、打包
|
|
24
|
+
* >查看定义:@see {@link runBuild}
|
|
25
|
+
* @param {object} flags 命令行参数,含 ios / release
|
|
26
|
+
* @returns {Promise<void>}
|
|
27
|
+
*/
|
|
28
|
+
const runBuild = async (flags) => {
|
|
29
|
+
const userProjectDir = process.cwd(), release = !!flags.release, isIos = !!flags.ios, total = isIos ? 8 : 7;
|
|
30
|
+
|
|
31
|
+
console.log(chalk.cyan(
|
|
32
|
+
`\n=== node-mobile-app build (${isIos ? 'iOS' : 'Android'}, ${release ? 'release' : 'debug'}) ===\n`
|
|
33
|
+
));
|
|
34
|
+
console.log(chalk.gray('用户项目: ' + userProjectDir));
|
|
35
|
+
if (isIos) ensureMac(), checkIosTools();
|
|
36
|
+
console.log(chalk.cyan(`\n[1/${total}] 检查用户配置与入口文件`)), ensureUserConfig(userProjectDir, PKG_ROOT);
|
|
37
|
+
|
|
38
|
+
const config = await loadConfig(userProjectDir);
|
|
39
|
+
ensureUserEntry(userProjectDir, config, PKG_ROOT), validateConfig(config, userProjectDir);
|
|
40
|
+
|
|
41
|
+
const buildDir = getBuildDir(userProjectDir);
|
|
42
|
+
console.log(chalk.cyan(`\n[2/${total}] 重置 RN 工程模板`));
|
|
43
|
+
await copyTemplate(PKG_ROOT, buildDir, config);
|
|
44
|
+
patchAndroidAbi(buildDir, config);
|
|
45
|
+
patchExtraRnDependencies(buildDir, userProjectDir, config);
|
|
46
|
+
patchAppConfig(buildDir, userProjectDir, config);
|
|
47
|
+
patchAndroidConfig(buildDir, config), patchMavenRepositories(buildDir, config);
|
|
48
|
+
patchSigning(buildDir, userProjectDir, config);
|
|
49
|
+
await patchIcon(buildDir, userProjectDir, config);
|
|
50
|
+
patchIosConfig(buildDir, userProjectDir, config);
|
|
51
|
+
await patchIosIcon(buildDir, userProjectDir, config);
|
|
52
|
+
|
|
53
|
+
console.log(chalk.cyan(`\n[3/${total}] 检查缓存指纹`)), checkAndResetCache(buildDir, userProjectDir, config, PKG_ROOT);
|
|
54
|
+
console.log(chalk.cyan(`\n[4/${total}] 复制用户项目`));
|
|
55
|
+
const { moduleType, targetDir } = copyUserProject(userProjectDir, buildDir, config);
|
|
56
|
+
console.log(chalk.cyan(`\n[5/${total}] 安装依赖与编译优化`));
|
|
57
|
+
await installUserDeps(targetDir, userProjectDir, config, buildDir, isIos ? 'ios' : 'android');
|
|
58
|
+
patchNodejsMobilePlugin(userProjectDir, config);
|
|
59
|
+
patchWebViewSsl(userProjectDir, config);
|
|
60
|
+
writeLocalProperties(buildDir, config);
|
|
61
|
+
|
|
62
|
+
console.log(chalk.cyan(`\n[6/${total}] 生成桥接文件`));
|
|
63
|
+
generateMainJs(targetDir, config, moduleType), generateRuntime(buildDir, config);
|
|
64
|
+
|
|
65
|
+
const appName = resolveAppName(userProjectDir, config), safeName = sanitizeFileName(appName),
|
|
66
|
+
outDir = path.resolve(userProjectDir, config.outputDir || './out');
|
|
67
|
+
ensureDir(outDir);
|
|
68
|
+
|
|
69
|
+
if (isIos) {
|
|
70
|
+
console.log(chalk.cyan(`\n[7/${total}] pod install`)), podInstall(path.join(buildDir, 'ios'));
|
|
71
|
+
console.log(chalk.cyan(`\n[8/${total}] xcodebuild archive + export`));
|
|
72
|
+
xcodebuildArchive(buildDir, config, release, userProjectDir);
|
|
73
|
+
|
|
74
|
+
const ipaPath = xcodebuildExport(buildDir, config, release, outDir, userProjectDir),
|
|
75
|
+
dstName = `${safeName}-${config.versionName}-${release ? 'release' : 'debug'}.ipa`,
|
|
76
|
+
dst = path.join(outDir, dstName);
|
|
77
|
+
fs.copyFileSync(ipaPath, dst), console.log(chalk.green('\n✅ 打包完成'));
|
|
78
|
+
|
|
79
|
+
const mb = (fs.statSync(dst).size / 1024 / 1024).toFixed(1);
|
|
80
|
+
console.log(chalk.gray(` ${dst} (${mb} MB)`)), await markVerified(buildDir);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
19
83
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.log(chalk.cyan(
|
|
25
|
-
`\n=== node-mobile-app build (${isIos ? 'iOS' : 'Android'}, ${release ? 'release' : 'debug'}) ===\n`
|
|
26
|
-
));
|
|
27
|
-
console.log(chalk.gray('用户项目: ' + userProjectDir));
|
|
28
|
-
if (isIos) ensureMac(), checkIosTools();
|
|
29
|
-
console.log(chalk.cyan(`\n[1/${total}] 检查用户配置与入口文件`)), ensureUserConfig(userProjectDir, PKG_ROOT);
|
|
30
|
-
|
|
31
|
-
const config = await loadConfig(userProjectDir);
|
|
32
|
-
ensureUserEntry(userProjectDir, config, PKG_ROOT), validateConfig(config, userProjectDir);
|
|
33
|
-
|
|
34
|
-
const buildDir = getBuildDir(userProjectDir);
|
|
35
|
-
console.log(chalk.cyan(`\n[2/${total}] 重置 RN 工程模板`));
|
|
36
|
-
copyTemplate(PKG_ROOT, buildDir, config);
|
|
37
|
-
patchAndroidAbi(buildDir, config);
|
|
38
|
-
patchExtraRnDependencies(buildDir, userProjectDir, config);
|
|
39
|
-
patchAppConfig(buildDir, userProjectDir, config);
|
|
40
|
-
patchAndroidConfig(buildDir, config);
|
|
41
|
-
patchSigning(buildDir, userProjectDir, config);
|
|
42
|
-
await patchIcon(buildDir, userProjectDir, config);
|
|
43
|
-
patchIosConfig(buildDir, userProjectDir, config);
|
|
44
|
-
await patchIosIcon(buildDir, userProjectDir, config);
|
|
45
|
-
|
|
46
|
-
console.log(chalk.cyan(`\n[3/${total}] 检查缓存指纹`)), checkAndResetCache(buildDir, userProjectDir, config, PKG_ROOT);
|
|
47
|
-
console.log(chalk.cyan(`\n[4/${total}] 复制用户项目`));
|
|
48
|
-
const { moduleType, targetDir } = copyUserProject(userProjectDir, buildDir, config);
|
|
49
|
-
console.log(chalk.cyan(`\n[5/${total}] 安装依赖与编译优化`));
|
|
50
|
-
await installUserDeps(targetDir, userProjectDir, config, buildDir, isIos ? 'ios' : 'android');
|
|
51
|
-
patchNodejsMobilePlugin(userProjectDir, config);
|
|
52
|
-
patchWebViewSsl(userProjectDir, config);
|
|
53
|
-
writeLocalProperties(buildDir, config);
|
|
54
|
-
|
|
55
|
-
console.log(chalk.cyan(`\n[6/${total}] 生成桥接文件`));
|
|
56
|
-
generateMainJs(targetDir, config, moduleType), generateRuntime(buildDir, config);
|
|
57
|
-
|
|
58
|
-
const appName = resolveAppName(userProjectDir, config), safeName = sanitizeFileName(appName),
|
|
59
|
-
outDir = path.resolve(userProjectDir, config.outputDir || './out');
|
|
60
|
-
ensureDir(outDir);
|
|
61
|
-
|
|
62
|
-
if (isIos) {
|
|
63
|
-
console.log(chalk.cyan(`\n[7/${total}] pod install`)), podInstall(path.join(buildDir, 'ios'));
|
|
64
|
-
console.log(chalk.cyan(`\n[8/${total}] xcodebuild archive + export`)),
|
|
65
|
-
xcodebuildArchive(buildDir, config, release, userProjectDir);
|
|
66
|
-
|
|
67
|
-
const ipaPath = xcodebuildExport(buildDir, config, release, outDir, userProjectDir),
|
|
68
|
-
dstName = `${safeName}-${config.versionName}-${release ? 'release' : 'debug'}.ipa`,
|
|
69
|
-
dst = path.join(outDir, dstName);
|
|
70
|
-
fs.copyFileSync(ipaPath, dst), console.log(chalk.green('\n✅ 打包完成'));
|
|
84
|
+
console.log(chalk.cyan(`\n[7/${total}] 打包 APK`));
|
|
85
|
+
if (!process.env.OPTIMIZE_DEBUG) console.log(chalk.gray(' 打包进行中...'));
|
|
86
|
+
const assetsDir = path.join(buildDir, 'android', 'app', 'src', 'main', 'assets'),
|
|
87
|
+
resDir = path.join(buildDir, 'android', 'app', 'src', 'main', 'res');
|
|
71
88
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
ensureDir(assetsDir);
|
|
90
|
+
run('npx', [
|
|
91
|
+
'react-native', 'bundle', '--platform', 'android', '--dev', release ? 'false' : 'true',
|
|
92
|
+
'--entry-file', 'index.js', '--bundle-output',
|
|
93
|
+
path.join(assetsDir, 'index.android.bundle'), '--assets-dest', resDir,
|
|
94
|
+
], { cwd: buildDir, quiet: true });
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
ensureDir(assetsDir);
|
|
84
|
-
run('npx', [
|
|
85
|
-
'react-native', 'bundle',
|
|
86
|
-
'--platform', 'android',
|
|
87
|
-
'--dev', release ? 'false' : 'true',
|
|
88
|
-
'--entry-file', 'index.js',
|
|
89
|
-
'--bundle-output', path.join(assetsDir, 'index.android.bundle'),
|
|
90
|
-
'--assets-dest', resDir,
|
|
91
|
-
], { cwd: buildDir, quiet: true });
|
|
92
|
-
|
|
93
|
-
const task = release ? 'assembleRelease' : 'assembleDebug', gradleArgs = [task];
|
|
94
|
-
if (flags.arch) gradleArgs.push(`-PreactNativeArchitectures=${flags.arch}`);
|
|
95
|
-
|
|
96
|
-
const gradlew = process.platform === 'win32' ? 'gradlew.bat' : './gradlew';
|
|
96
|
+
const task = release ? 'assembleRelease' : 'assembleDebug', gradleArgs = [task];
|
|
97
|
+
if (flags.arch) gradleArgs.push(`-PreactNativeArchitectures=${flags.arch}`);
|
|
98
|
+
|
|
99
|
+
const gradlew = process.platform === 'win32' ? 'gradlew.bat' : './gradlew';
|
|
100
|
+
try {
|
|
97
101
|
run(gradlew, gradleArgs, { cwd: path.join(buildDir, 'android'), quiet: true });
|
|
102
|
+
} catch (err) {
|
|
103
|
+
warnGradleDownloadHint(config); throw err;
|
|
104
|
+
}
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
const apkDir = path.join(buildDir, 'android', 'app', 'build', 'outputs', 'apk', release ? 'release' : 'debug');
|
|
107
|
+
console.log(chalk.green('\n✅ 打包完成'));
|
|
101
108
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
if (fs.existsSync(apkDir)) {
|
|
110
|
+
const apks = fs.readdirSync(apkDir).filter((f) => f.endsWith('.apk'));
|
|
111
|
+
if (apks.length === 0) console.log(chalk.yellow(' ⚠️ 未找到 APK'));
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
dst = path.join(outDir, dstName);
|
|
113
|
+
for (const f of apks) {
|
|
114
|
+
const dstName = `${safeName}-${config.versionName}-${release ? 'release' : 'debug'}.apk`,
|
|
115
|
+
dst = path.join(outDir, dstName), src = path.join(apkDir, f);
|
|
110
116
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
117
|
+
fs.copyFileSync(src, dst);
|
|
118
|
+
const mb = (fs.statSync(dst).size / 1024 / 1024).toFixed(1);
|
|
119
|
+
console.log(chalk.gray(` ${dst} (${mb} MB)`));
|
|
115
120
|
}
|
|
116
|
-
else console.log(chalk.yellow(' ⚠️ 未找到 APK 输出目录: ' + apkDir));
|
|
117
|
-
await markVerified(buildDir);
|
|
118
121
|
}
|
|
122
|
+
else console.log(chalk.yellow(' ⚠️ 未找到 APK 输出目录: ' + apkDir));
|
|
123
|
+
await markVerified(buildDir);
|
|
124
|
+
};
|
|
119
125
|
|
|
120
126
|
export { runBuild };
|
package/src/commands/clean.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from 'chalk';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import
|
|
3
|
+
import path from 'path';
|
|
4
4
|
import { getBuildDir } from '../copy.js';
|
|
5
|
-
import
|
|
5
|
+
import { removeDir } from '../utils.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* 清理构建缓存:构建目录、插件构建缓存、插件 CMake 缓存
|
|
9
|
+
* >查看定义:@see {@link runClean}
|
|
10
|
+
* @returns {Promise<void>}
|
|
11
|
+
*/
|
|
7
12
|
const runClean = async () => {
|
|
8
13
|
const userProjectDir = process.cwd(), buildDir = getBuildDir(userProjectDir),
|
|
9
14
|
targets = [
|
|
@@ -19,9 +24,10 @@ const runClean = async () => {
|
|
|
19
24
|
];
|
|
20
25
|
|
|
21
26
|
for (const t of targets) {
|
|
22
|
-
if (fs.existsSync(t.path))
|
|
23
|
-
removeDir(t.path)
|
|
24
|
-
|
|
27
|
+
if (fs.existsSync(t.path)) {
|
|
28
|
+
removeDir(t.path);
|
|
29
|
+
console.log(chalk.green('已清理: ' + t.label + ' -> ' + (path.relative(userProjectDir, t.path) || t.path)));
|
|
30
|
+
}
|
|
25
31
|
else console.log(chalk.gray('跳过(不存在): ' + t.label));
|
|
26
32
|
}
|
|
27
33
|
|