@flun/node-mobile-app 3.1.5 → 4.0.1

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,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs';
3
3
  import { run, ensureDir } from './utils.js';
4
+ import chalk from 'chalk';
4
5
 
5
6
  const SCHEME = 'MyApp',
6
7
  /** iOS 相关命令只能在 macOS 上跑 */
@@ -16,7 +17,7 @@ const SCHEME = 'MyApp',
16
17
  * 执行 pod install(若 Pods/ 已存在且 Podfile.lock 未变,cocoapods 会自动跳过)
17
18
  */
18
19
  podInstall = iosDir => {
19
- console.log(' pod install ...'), run('pod', ['install'], { cwd: iosDir, inherit: true });
20
+ console.log(chalk.cyan(' pod install ...')), run('pod', ['install'], { cwd: iosDir, inherit: true });
20
21
  },
21
22
  /**
22
23
  * 把 config.ios.signing.certificate 导入临时 keychain,供 xcodebuild 签名用
@@ -28,7 +29,7 @@ const SCHEME = 'MyApp',
28
29
 
29
30
  const certPath = path.resolve(userProjectDir, signing.certificate);
30
31
  if (!fs.existsSync(certPath)) {
31
- console.warn(' ⚠️ iOS 签名证书不存在,跳过:', certPath);
32
+ console.warn(chalk.yellow(' ⚠️ iOS 签名证书不存在,跳过: ' + certPath));
32
33
  return null;
33
34
  }
34
35
 
@@ -38,7 +39,7 @@ const SCHEME = 'MyApp',
38
39
 
39
40
  // 清掉同名的旧 keychain(重复构建时)
40
41
  if (fs.existsSync(keychainPath)) fs.rmSync(keychainPath, { force: true });
41
- console.log(' 创建临时 keychain 并导入证书 ...');
42
+ console.log(chalk.cyan(' 创建临时 keychain 并导入证书 ...'));
42
43
  run('security', ['create-keychain', '-p', keychainPassword, keychainPath], { quiet: true });
43
44
  run('security', ['set-keychain-settings', '-lut', '3600', keychainPath], { quiet: true });
44
45
  run('security', ['unlock-keychain', '-p', keychainPassword, keychainPath], { quiet: true });
@@ -84,7 +85,7 @@ const SCHEME = 'MyApp',
84
85
  const keychain = setupSigningKeychain(buildDir, userProjectDir, config);
85
86
  if (keychain) args.push('OTHER_CODE_SIGN_FLAGS=--keychain ' + keychain.keychainPath);
86
87
 
87
- console.log(' xcodebuild archive ...'), run('xcodebuild', args, { cwd: iosDir, quiet: true });
88
+ console.log(chalk.cyan(' xcodebuild archive ...')), run('xcodebuild', args, { cwd: iosDir, quiet: true });
88
89
  return archivePath;
89
90
  },
90
91
  /**
@@ -123,7 +124,7 @@ const SCHEME = 'MyApp',
123
124
  ].filter(Boolean).join('\n');
124
125
  fs.writeFileSync(plistPath, plist, 'utf-8'); // 生成 exportOptions.plist
125
126
 
126
- console.log(' xcodebuild -exportArchive ...');
127
+ console.log(chalk.cyan(' xcodebuild -exportArchive ...'));
127
128
  run('xcodebuild', [
128
129
  '-exportArchive',
129
130
  '-archivePath', archivePath,
package/src/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { spawnSync } from 'child_process';
3
+ import { spawnSync, spawn } from 'child_process';
4
+ import chalk from 'chalk';
4
5
 
5
6
  const ensureDir = dir => {
6
7
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
@@ -80,6 +81,20 @@ const ensureDir = dir => {
80
81
  }
81
82
  return spawnSync(bin, args, { ...baseOpts, shell: false });
82
83
  },
84
+ /**
85
+ * 异步 spawn:参数同 spawnCmd,返回 ChildProcess
86
+ * @param {string} bin 命令
87
+ * @param {string[]} args 参数
88
+ * @param {object} [baseOpts] spawn 选项
89
+ * @returns {import('child_process').ChildProcess}
90
+ */
91
+ spawnCmdAsync = (bin, args, baseOpts = {}) => {
92
+ if (process.platform === 'win32') {
93
+ const cmd = [bin, ...args].map(quoteShellArg).join(' ');
94
+ return spawn(cmd, { ...baseOpts, shell: true });
95
+ }
96
+ return spawn(bin, args, { ...baseOpts, shell: false });
97
+ },
83
98
  /**
84
99
  * 执行命令:默认实时输出(inherit);quiet=true 时捕获输出,成功静默、失败才打印
85
100
  */
@@ -108,6 +123,30 @@ const ensureDir = dir => {
108
123
  }
109
124
  return r;
110
125
  },
126
+ /**
127
+ * 执行命令:实时输出,仅首行加缩进前缀,其余行原样
128
+ * @param {string} bin 命令
129
+ * @param {string[]} args 参数
130
+ * @param {object} [opts] spawn 选项 + prefix
131
+ * @returns {Promise<void>}
132
+ */
133
+ runStream = (bin, args, opts = {}) => new Promise((resolve, reject) => {
134
+ const { prefix = ' ', quiet = false, ...restOpts } = opts, child = spawnCmdAsync(bin, args, restOpts),
135
+ verbose = !quiet || process.env.OPTIMIZE_DEBUG, KEY_RE = /Installing|Installed|FAILED|error|Error|失败|退出码/;
136
+ let first = true;
137
+ const write = chunk => {
138
+ for (const line of chunk.toString().split(/\r?\n/)) {
139
+ if (!line.trim()) continue;
140
+ if (!verbose && !KEY_RE.test(line)) continue;
141
+ process.stdout.write((first ? prefix : '') + line + '\n');
142
+ first = false;
143
+ }
144
+ };
145
+ if (child.stdout) child.stdout.on('data', write);
146
+ if (child.stderr) child.stderr.on('data', write);
147
+ child.on('error', reject);
148
+ child.on('close', code => code === 0 ? resolve() : reject(new Error(`${bin} ${args.join(' ')} 退出码 ${code}`)));
149
+ }),
111
150
  /**
112
151
  * 定位 adb(配置 > 环境变量 > 默认路径 > PATH)
113
152
  */
@@ -116,7 +155,7 @@ const ensureDir = dir => {
116
155
  const configured = config?.android?.adbPath;
117
156
  if (configured) {
118
157
  if (fs.existsSync(configured)) return configured;
119
- console.warn('⚠️ config.android.adbPath 不存在,回退到自动查找: ' + configured);
158
+ console.warn(chalk.yellow('⚠️ config.android.adbPath 不存在,回退到自动查找: ' + configured));
120
159
  }
121
160
  // 2. 环境变量 ANDROID_HOME / ANDROID_SDK_ROOT
122
161
  for (const env of ['ANDROID_HOME', 'ANDROID_SDK_ROOT']) {
@@ -139,6 +178,53 @@ const ensureDir = dir => {
139
178
  /**
140
179
  * 定位 hermesc(Hermes 编译器,用于把 JS bundle 编成字节码)
141
180
  */
181
+ /**
182
+ * 读取安装状态标志(记录上次由本工具安装的包与变体)
183
+ * @param {string} buildDir 构建目录
184
+ * @returns {{appId:string, variant:'debug'|'release'}|null} 状态或 null
185
+ */
186
+ readInstallState = buildDir => {
187
+ const f = path.join(buildDir, '.install-state.json');
188
+ if (!fs.existsSync(f)) return null;
189
+ try { return JSON.parse(fs.readFileSync(f, 'utf-8')); } catch { return null; }
190
+ },
191
+ /**
192
+ * 写入安装状态标志(安装成功后调用)
193
+ * @param {string} buildDir 构建目录
194
+ * @param {object} state 状态
195
+ * @returns {void}
196
+ */
197
+ writeInstallState = (buildDir, state) => {
198
+ try { ensureDir(buildDir), fs.writeFileSync(path.join(buildDir, '.install-state.json'), JSON.stringify(state, null, 2)); } catch { }
199
+ },
200
+ /**
201
+ * 查询设备上已安装包的 DEBUGGABLE 标志
202
+ * @param {string} adb adb 路径
203
+ * @param {string} appId 包名
204
+ * @returns {'debug'|'release'|null} 变体或 null(未安装)
205
+ */
206
+ getInstalledVariant = (adb, appId, serial) => {
207
+ const pre = serial ? ['-s', serial] : [],
208
+ r = spawnCmd(adb, [...pre, 'shell', 'dumpsys', 'package', appId], { encoding: 'utf-8' });
209
+ if (r.status !== 0 || !r.stdout || !r.stdout.includes('Package [')) return null;
210
+ return /\bDEBUGGABLE\b/.test(r.stdout) ? 'debug' : 'release';
211
+ },
212
+ /**
213
+ * 确保待装包可覆盖安装:标志不一致或变体不符时卸载已装包
214
+ * @param {string} adb adb 路径
215
+ * @param {string} appId 包名
216
+ * @param {'debug'|'release'} want 待装变体
217
+ * @param {string} buildDir 构建目录
218
+ * @param {string} [serial] 设备序列号(多设备时指定)
219
+ * @returns {void}
220
+ */
221
+ ensureInstallable = (adb, appId, want, buildDir, serial) => {
222
+ const installed = getInstalledVariant(adb, appId, serial);
223
+ if (installed && installed !== want) {
224
+ console.log(chalk.yellow(` 已安装 ${installed} 包与待装 ${want} 包签名不同,先卸载`));
225
+ spawnCmd(adb, [...(serial ? ['-s', serial] : []), 'uninstall', appId], { encoding: 'utf-8' });
226
+ }
227
+ },
142
228
  findHermesc = userProjectDir => {
143
229
  const base = path.join(userProjectDir, 'node_modules', 'hermes-compiler', 'hermesc'), platform = process.platform;
144
230
  let sub;
@@ -165,6 +251,7 @@ const ensureDir = dir => {
165
251
  import reactPlugin from '@eslint-react/eslint-plugin';
166
252
  import reactHooks from 'eslint-plugin-react-hooks';
167
253
  import prettier from 'eslint-config-prettier';
254
+ import chalk from 'chalk';
168
255
 
169
256
  export default [
170
257
  js.configs.recommended,
@@ -185,6 +272,6 @@ const ensureDir = dir => {
185
272
  rules: tsPlugin.configs.recommended.rules }, prettier];`;
186
273
 
187
274
  export {
188
- ensureDir, removeDir, isExcluded, copyDir, readJson, writeJson, writeIfChanged, copyFileIfChanged, run, runQuiet,
189
- runSilent, findAdb, findHermesc, sanitizeFileName, buildEslintConfigSource
190
- };
275
+ ensureDir, removeDir, isExcluded, copyDir, readJson, writeJson, writeIfChanged, copyFileIfChanged, spawnCmdAsync, run, runQuiet, runStream,
276
+ runSilent, findAdb, readInstallState, writeInstallState, getInstalledVariant, ensureInstallable, findHermesc, sanitizeFileName, buildEslintConfigSource
277
+ };