@flun/node-mobile-app 4.1.2 → 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.
@@ -1,8 +1,9 @@
1
+ import chalk from 'chalk';
2
+ import crypto from 'crypto';
1
3
  import fs from 'fs';
2
4
  import path from 'path';
3
- import crypto from 'crypto';
4
5
  import { ensureDir, removeDir } from './utils.js';
5
- import chalk from 'chalk';
6
+
6
7
 
7
8
  const FINGERPRINT_FILE = '.build-fingerprint.json',
8
9
  hashUserDeps = userProjectDir => {
@@ -15,8 +16,7 @@ const FINGERPRINT_FILE = '.build-fingerprint.json',
15
16
  }
16
17
  else h.update('no-package');
17
18
  return h.digest('hex');
18
- },
19
- writeFp = (file, fp) => fs.writeFileSync(file, JSON.stringify(fp, null, 2), 'utf-8'),
19
+ }, writeFp = (file, fp) => fs.writeFileSync(file, JSON.stringify(fp, null, 2), 'utf-8'),
20
20
  clearGradleCaches = (buildDir, userProjectDir) => {
21
21
  removeDir(path.join(buildDir, 'android', 'build'));
22
22
  removeDir(path.join(buildDir, 'android', 'app', 'build'));
@@ -45,86 +45,105 @@ const FINGERPRINT_FILE = '.build-fingerprint.json',
45
45
  };
46
46
  walk(templateDir);
47
47
  return h.digest('hex');
48
- },
49
- computeFingerprint = (userProjectDir, config, pkgRoot) => {
50
- const abiFilters = [...(config.android.abiFilters || [])].sort(), libnode = {},
51
- pluginDir = path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native'),
52
- libnodeBin = path.join(pluginDir, 'android', 'libnode', 'bin');
48
+ };
53
49
 
54
- if (fs.existsSync(libnodeBin)) {
55
- for (const arch of fs.readdirSync(libnodeBin)) {
56
- const so = path.join(libnodeBin, arch, 'libnode.so');
57
- if (fs.existsSync(so)) {
58
- const stat = fs.statSync(so);
59
- libnode[arch] = { size: stat.size, mtime: Math.floor(stat.mtimeMs) };
60
- }
61
- }
62
- }
50
+ /**
51
+ * 对比构建指纹,变化时清理对应缓存;指纹写入 buildDir
52
+ * >查看定义:@see {@link checkAndResetCache}
53
+ * @param {string} buildDir 构建目录
54
+ * @param {string} userProjectDir 用户项目目录
55
+ * @param {object} config 用户配置
56
+ * @param {string} pkgRoot CLI 包根目录
57
+ * @returns {void}
58
+ */
59
+ const checkAndResetCache = (buildDir, userProjectDir, config, pkgRoot) => {
60
+ ensureDir(buildDir);
63
61
 
64
- let pluginVersion = null, cliVersion = null;
65
- const pluginPkg = path.join(pluginDir, 'package.json'), cliPkg = path.join(pkgRoot, 'package.json');
66
- if (fs.existsSync(pluginPkg))
67
- try { pluginVersion = JSON.parse(fs.readFileSync(pluginPkg, 'utf-8')).version; } catch { };
68
- if (fs.existsSync(cliPkg)) try { cliVersion = JSON.parse(fs.readFileSync(cliPkg, 'utf-8')).version; } catch { };
62
+ const fpFile = path.join(buildDir, FINGERPRINT_FILE), newFp = computeFingerprint(userProjectDir, config, pkgRoot);
63
+ let oldFp = null;
64
+ if (fs.existsSync(fpFile)) try { oldFp = JSON.parse(fs.readFileSync(fpFile, 'utf-8')); } catch { };
65
+ if (!oldFp) return console.log(chalk.green(' ✓ 全新构建,无缓存可清')), writeFp(fpFile, newFp);
69
66
 
70
- const depsHash = hashUserDeps(userProjectDir), a = config.android || {},
71
- appInfo = {
72
- appName: config.appName || null,
73
- appId: config.appId || null,
74
- versionCode: config.versionCode || null,
75
- versionName: config.versionName || null,
76
- minSdkVersion: a.minSdkVersion || null,
77
- targetSdkVersion: a.targetSdkVersion || null,
78
- compileSdkVersion: a.compileSdkVersion || null,
79
- buildToolsVersion: a.buildToolsVersion || null,
80
- usesCleartextTraffic: a.usesCleartextTraffic !== false,
81
- permissions: [...(a.permissions || [])].sort(),
82
- };
67
+ const abiChanged = JSON.stringify(oldFp.abiFilters) !== JSON.stringify(newFp.abiFilters),
68
+ libnodeChanged = JSON.stringify(oldFp.libnode) !== JSON.stringify(newFp.libnode),
69
+ pluginChanged = oldFp.pluginVersion !== newFp.pluginVersion,
70
+ cliChanged = oldFp.cliVersion !== newFp.cliVersion, depsChanged = oldFp.depsHash !== newFp.depsHash,
71
+ appInfoChanged = JSON.stringify(oldFp.appInfo) !== JSON.stringify(newFp.appInfo),
72
+ templateChanged = oldFp.templateHash !== newFp.templateHash,
73
+ structural = abiChanged || libnodeChanged || pluginChanged || cliChanged || templateChanged;
83
74
 
84
- const templateHash = hashTemplate(path.join(pkgRoot, 'template'));
85
- return { abiFilters, libnode, pluginVersion, cliVersion, depsHash, appInfo, templateHash };
86
- },
87
- checkAndResetCache = (buildDir, userProjectDir, config, pkgRoot) => {
88
- ensureDir(buildDir);
75
+ if (structural) {
76
+ const reasons = [];
77
+ if (abiChanged) reasons.push('ABI 配置');
78
+ if (libnodeChanged) reasons.push('libnode.so');
79
+ if (pluginChanged) reasons.push('插件版本');
80
+ if (cliChanged) reasons.push('CLI 版本');
81
+ if (templateChanged) reasons.push('模板文件');
82
+ if (depsChanged) reasons.push('用户依赖');
83
+ if (appInfoChanged) reasons.push('应用信息');
84
+ console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 Gradle 缓存、CMake 缓存与依赖缓存'));
85
+ clearGradleCaches(buildDir, userProjectDir);
86
+ removeDir(path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native', 'android', '.cxx'));
87
+ clearDepsCaches(buildDir);
88
+ }
89
+ else if (depsChanged || appInfoChanged) {
90
+ const reasons = [], targets = [];
91
+ if (depsChanged) reasons.push('用户依赖'), targets.push('依赖缓存', 'Gradle 缓存');
92
+ if (appInfoChanged) reasons.push('应用信息'), targets.push('Gradle 缓存');
93
+ console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 ' + [...new Set(targets)].join('、')));
94
+ clearGradleCaches(buildDir, userProjectDir);
95
+ if (depsChanged) clearDepsCaches(buildDir);
96
+ }
97
+ else console.log(chalk.gray(' ✓ 缓存有效,跳过清理'));
89
98
 
90
- const fpFile = path.join(buildDir, FINGERPRINT_FILE), newFp = computeFingerprint(userProjectDir, config, pkgRoot);
91
- let oldFp = null;
92
- if (fs.existsSync(fpFile)) try { oldFp = JSON.parse(fs.readFileSync(fpFile, 'utf-8')); } catch { };
93
- if (!oldFp) return console.log(chalk.green(' ✓ 全新构建,无缓存可清')), writeFp(fpFile, newFp);
99
+ writeFp(fpFile, newFp);
100
+ };
94
101
 
95
- const abiChanged = JSON.stringify(oldFp.abiFilters) !== JSON.stringify(newFp.abiFilters),
96
- libnodeChanged = JSON.stringify(oldFp.libnode) !== JSON.stringify(newFp.libnode),
97
- pluginChanged = oldFp.pluginVersion !== newFp.pluginVersion,
98
- cliChanged = oldFp.cliVersion !== newFp.cliVersion, depsChanged = oldFp.depsHash !== newFp.depsHash,
99
- appInfoChanged = JSON.stringify(oldFp.appInfo) !== JSON.stringify(newFp.appInfo),
100
- templateChanged = oldFp.templateHash !== newFp.templateHash,
101
- structural = abiChanged || libnodeChanged || pluginChanged || cliChanged || templateChanged;
102
+ /**
103
+ * 计算当前构建指纹,用于判断是否需要清理缓存
104
+ * >查看定义:@see {@link computeFingerprint}
105
+ * @param {string} userProjectDir 用户项目目录
106
+ * @param {object} config 用户配置
107
+ * @param {string} pkgRoot CLI 包根目录
108
+ * @returns {object} 含 abiFilters、libnode、pluginVersion、cliVersion、depsHash、appInfo、templateHash
109
+ */
110
+ const computeFingerprint = (userProjectDir, config, pkgRoot) => {
111
+ const abiFilters = [...(config.android.abiFilters || [])].sort(), libnode = {},
112
+ pluginDir = path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native'),
113
+ libnodeBin = path.join(pluginDir, 'android', 'libnode', 'bin');
102
114
 
103
- if (structural) {
104
- const reasons = [];
105
- if (abiChanged) reasons.push('ABI 配置');
106
- if (libnodeChanged) reasons.push('libnode.so');
107
- if (pluginChanged) reasons.push('插件版本');
108
- if (cliChanged) reasons.push('CLI 版本');
109
- if (templateChanged) reasons.push('模板文件');
110
- if (depsChanged) reasons.push('用户依赖');
111
- if (appInfoChanged) reasons.push('应用信息');
112
- console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 Gradle 缓存、CMake 缓存与依赖缓存'));
113
- clearGradleCaches(buildDir, userProjectDir);
114
- removeDir(path.join(userProjectDir, 'node_modules', '@flun', 'nodejs-mobile-react-native', 'android', '.cxx'));
115
- clearDepsCaches(buildDir);
116
- }
117
- else if (depsChanged || appInfoChanged) {
118
- const reasons = [], targets = [];
119
- if (depsChanged) reasons.push('用户依赖'), targets.push('依赖缓存', 'Gradle 缓存');
120
- if (appInfoChanged) reasons.push('应用信息'), targets.push('Gradle 缓存');
121
- console.log(chalk.yellow(' ⚠️ 检测到变化(' + reasons.join('、') + '),清理 ' + [...new Set(targets)].join('、')));
122
- clearGradleCaches(buildDir, userProjectDir);
123
- if (depsChanged) clearDepsCaches(buildDir);
115
+ if (fs.existsSync(libnodeBin)) {
116
+ for (const arch of fs.readdirSync(libnodeBin)) {
117
+ const so = path.join(libnodeBin, arch, 'libnode.so');
118
+ if (fs.existsSync(so)) {
119
+ const stat = fs.statSync(so);
120
+ libnode[arch] = { size: stat.size, mtime: Math.floor(stat.mtimeMs) };
121
+ }
124
122
  }
125
- else console.log(chalk.gray(' ✓ 缓存有效,跳过清理'));
123
+ }
126
124
 
127
- writeFp(fpFile, newFp);
128
- };
125
+ let pluginVersion = null, cliVersion = null;
126
+ const pluginPkg = path.join(pluginDir, 'package.json'), cliPkg = path.join(pkgRoot, 'package.json');
127
+ if (fs.existsSync(pluginPkg))
128
+ try { pluginVersion = JSON.parse(fs.readFileSync(pluginPkg, 'utf-8')).version; } catch { };
129
+ if (fs.existsSync(cliPkg)) try { cliVersion = JSON.parse(fs.readFileSync(cliPkg, 'utf-8')).version; } catch { };
130
+
131
+ const depsHash = hashUserDeps(userProjectDir), a = config.android || {},
132
+ appInfo = {
133
+ appName: config.appName || null,
134
+ appId: config.appId || null,
135
+ versionCode: config.versionCode || null,
136
+ versionName: config.versionName || null,
137
+ minSdkVersion: a.minSdkVersion || null,
138
+ targetSdkVersion: a.targetSdkVersion || null,
139
+ compileSdkVersion: a.compileSdkVersion || null,
140
+ buildToolsVersion: a.buildToolsVersion || null,
141
+ usesCleartextTraffic: a.usesCleartextTraffic !== false,
142
+ permissions: [...(a.permissions || [])].sort(),
143
+ };
144
+
145
+ const templateHash = hashTemplate(path.join(pkgRoot, 'template'));
146
+ return { abiFilters, libnode, pluginVersion, cliVersion, depsHash, appInfo, templateHash };
147
+ };
129
148
 
130
- export { computeFingerprint, checkAndResetCache };
149
+ export { checkAndResetCache, computeFingerprint };
package/src/generate.js CHANGED
@@ -1,7 +1,6 @@
1
- import fs from 'fs';
1
+ import chalk from 'chalk';
2
2
  import path from 'path';
3
3
  import { writeIfChanged } from './utils.js';
4
- import chalk from 'chalk';
5
4
 
6
5
  /**
7
6
  * 生成 main.js(桥接代码,用户不需要手写)
@@ -9,6 +8,11 @@ import chalk from 'chalk';
9
8
  * 上报机制(通过 rn-bridge channel 发消息给 App.tsx):
10
9
  * - 'Node started on <URL>' server.js 成功调用 listen
11
10
  * - 'Node failed: <原因>' 加载异常或 5 秒内未捕获 listen
11
+ * >查看定义:@see {@link generateMainJs}
12
+ * @param {string} targetDir 生成目录
13
+ * @param {object} config 用户配置
14
+ * @param {string} moduleType 'module' 时注入 createRequire 头
15
+ * @returns {void}
12
16
  */
13
17
  const generateMainJs = (targetDir, config, moduleType) => {
14
18
  const entry = config.serverPath.startsWith('./') ? config.serverPath : './' + config.serverPath,
@@ -75,8 +79,8 @@ const generateMainJs = (targetDir, config, moduleType) => {
75
79
  })();
76
80
 
77
81
  ${config.advanced.keepAlive === false
78
- ? '// keepAlive: false - 不发送心跳'
79
- : "setInterval(() => rn_bridge.channel.send('heartbeat'), " + hb + ");"}
82
+ ? '// keepAlive: false - 不发送心跳'
83
+ : "setInterval(() => rn_bridge.channel.send('heartbeat'), " + hb + ");"}
80
84
 
81
85
  rn_bridge.channel.on('message', msg => rn_bridge.channel.send('Echo: ' + msg));
82
86
  })();`,
@@ -84,21 +88,29 @@ const generateMainJs = (targetDir, config, moduleType) => {
84
88
  ? `import { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\n\n` : '',
85
89
  file = path.join(targetDir, 'main.js'), changed = writeIfChanged(file, header + body);
86
90
  console.log(chalk.green(changed ? ' ✓ 已生成 main.js' : ' ✓ main.js 无变化'));
87
- },
88
- generateRuntime = (buildDir, config) => {
89
- const data = {
90
- showLoading: config.webview.showLoading,
91
- loadingText: config.webview.loadingText,
92
- webview: {
93
- enableJavaScript: config.webview.enableJavaScript,
94
- enableDomStorage: config.webview.enableDomStorage,
95
- allowFileAccess: config.webview.allowFileAccess,
96
- mixedContentMode: config.webview.mixedContentMode,
97
- backgroundColor: config.webview.backgroundColor
98
- },
99
- }, content = `// 自动生成,请勿手改\nexport default ${JSON.stringify(data, null, 2)} as const;\n`,
100
- changed = writeIfChanged(path.join(buildDir, 'mobileApp.runtime.ts'), content);
101
- console.log(chalk.green(changed ? ' ✓ 已生成 mobileApp.runtime.ts' : ' ✓ mobileApp.runtime.ts 无变化'));
102
- };
91
+ };
92
+
93
+ /**
94
+ * 生成 mobileApp.runtime.ts,写入 WebView 运行时配置
95
+ * >查看定义:@see {@link generateRuntime}
96
+ * @param {string} buildDir 构建目录
97
+ * @param {object} config 用户配置
98
+ * @returns {void}
99
+ */
100
+ const generateRuntime = (buildDir, config) => {
101
+ const data = {
102
+ showLoading: config.webview.showLoading,
103
+ loadingText: config.webview.loadingText,
104
+ webview: {
105
+ enableJavaScript: config.webview.enableJavaScript,
106
+ enableDomStorage: config.webview.enableDomStorage,
107
+ allowFileAccess: config.webview.allowFileAccess,
108
+ mixedContentMode: config.webview.mixedContentMode,
109
+ backgroundColor: config.webview.backgroundColor
110
+ },
111
+ }, content = `// 自动生成,请勿手改\nexport default ${JSON.stringify(data, null, 2)} as const;\n`,
112
+ changed = writeIfChanged(path.join(buildDir, 'mobileApp.runtime.ts'), content);
113
+ console.log(chalk.green(changed ? ' ✓ 已生成 mobileApp.runtime.ts' : ' ✓ mobileApp.runtime.ts 无变化'));
114
+ };
103
115
 
104
- export { generateMainJs, generateRuntime }
116
+ export { generateMainJs, generateRuntime };