@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 fs from 'fs';
2
2
  import path from 'path';
3
3
  import { createRequire } from 'module';
4
+ import chalk from 'chalk';
4
5
 
5
6
  const require = createRequire(import.meta.url), subpath = (...parts) => './' + path.posix.join(...parts),
6
7
  BUNDLED_DIR = 'bundled', NM_DIR = 'node_modules', PKG_JSON = 'package.json',
@@ -15,10 +16,6 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
15
16
  IOS_ARCHES = ['arm64', 'x64'], INDEX_FILES = ['index.js', 'index.cjs', 'index.mjs'],
16
17
  // ==================== 通用工具 ====================
17
18
  toPosix = p => p.replace(POSIX_RE, '/'),
18
- isSkippedPkg = rel => {
19
- const seg = rel.split('/'), name = seg[0].startsWith('@') ? seg[0] + '/' + (seg[1] || '') : seg[0];
20
- return SKIP_PKGS.has(name) || SKIP_PKGS.has(seg[0]);
21
- },
22
19
  pickCond = (v, prefer) => {
23
20
  if (typeof v === 'string') return v;
24
21
  if (v && typeof v === 'object') {
@@ -194,8 +191,8 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
194
191
  if (excludePkgs.has(relName)) { skipped++; continue; }
195
192
  if (SKIP_PKGS.has(relName)) { skipped++; continue; }
196
193
  if (hasNativeModule(pkgDir)) { skipped++; continue; }
197
- // 删除安装期脚本字段:运行时无安装期,且 Gradle npm rebuild 会触发脚本导致失败
198
- // 原生模块包已在上一步跳过(其 scripts 是编译必需,必须保留)
194
+ // 删除安装期脚本字段:运行时无安装期,且 Gradle npm rebuild 会触发脚本导致失败
195
+ // 原生模块包已在上一步跳过(其 scripts 是编译必需,必须保留)
199
196
  if (j.scripts) { delete j.scripts; fs.writeFileSync(pj, JSON.stringify(j, null, 2)); }
200
197
  if (countJS(pkgDir) <= 1) { skipped++; continue; }
201
198
  // 已优化过的包跳过
@@ -234,7 +231,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
234
231
  }
235
232
  if (isESM) newExports[e.key] = subpath(BUNDLED_DIR, outName);
236
233
  else {
237
- // CJS 包:若 bundle 末尾是 module.exports = require_xxx()(整体赋值),
234
+ // CJS 包:若 bundle 末尾是 module.exports = require_xxx()(整体赋值),
238
235
  // cjs-module-lexer 静态分析拿不到命名导出,此时定位主入口的 __commonJS 块,
239
236
  // 块内抓 exports.X = 形式,追加显式声明让 lexer 识别;
240
237
  try {
@@ -316,12 +313,12 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
316
313
  walkDel(pkgDir), totalDeleted += deleted, merged++;
317
314
  if (resJsHits && resJsHits.size > 0 && resLog)
318
315
  for (const [k, v] of resJsHits) resLog.push(` ${relName}: ${k} (${v} 个 JS)`);
319
- if (process.env.OPTIMIZE_DEBUG) console.log(` ✓ ${relName} (${parse.entries.length} 入口)`);
316
+ if (process.env.OPTIMIZE_DEBUG) console.log(chalk.gray(` ✓ ${relName} (${parse.entries.length} 入口)`));
320
317
  }
321
318
 
322
319
  if (resLog && resLog.length > 0) {
323
- console.log(' 处理保留的资源 JS(被引用则合并,否则删除):');
324
- for (const line of resLog) console.log(line);
320
+ console.log(chalk.gray(' 处理保留的资源 JS(被引用则合并,否则删除):'));
321
+ for (const line of resLog) console.log(chalk.gray(line));
325
322
  }
326
323
  return { merged, skipped, totalDeleted };
327
324
  },
@@ -403,12 +400,12 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
403
400
  if (cnt > 0) cleared += cnt, pkgItems.push(`${item}(${cnt})`);
404
401
  }
405
402
  }
406
- if (process.env.OPTIMIZE_DEBUG && pkgItems.length > 0) console.log(` ✓ ${pkgRel}: ${pkgItems.join(', ')}`);
403
+ if (process.env.OPTIMIZE_DEBUG && pkgItems.length > 0) console.log(chalk.gray(` ✓ ${pkgRel}: ${pkgItems.join(', ')}`));
407
404
  }
408
405
  return cleared;
409
406
  },
410
407
  // ==================== 清平台 ====================
411
- prunePlatform = async (nmDir, myPlat, myArch) => {
408
+ prunePlatform = async (nmDir, myPlat, myArch, targetPlatform) => {
412
409
  let removed = 0;
413
410
  const readHead = file => {
414
411
  let fd;
@@ -467,13 +464,16 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
467
464
  return null;
468
465
  },
469
466
  delFile = p => {
470
- try { fs.unlinkSync(p), removed++; } catch { }
467
+ try { fs.statSync(p).isDirectory() ? fs.rmSync(p, { recursive: true, force: true }) : fs.unlinkSync(p), removed++; } catch { }
471
468
  },
472
469
  processDir = d => {
473
470
  let entries;
474
471
  try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
475
472
  for (const e of entries) {
476
473
  const full = path.join(d, e.name);
474
+ const expectedPrebuilds = targetPlatform === 'ios' ? ['darwin'] : ['android'],
475
+ prebuildMatch = full.match(/[\\/]prebuilds[\\/](linux|darwin|win32|android)-/i);
476
+ if (prebuildMatch && !expectedPrebuilds.includes(prebuildMatch[1].toLowerCase())) { delFile(full); continue; }
477
477
  if (e.isDirectory()) { processDir(full); continue; }
478
478
  if (JS_RE.test(e.name)) continue;
479
479
  let plat = suffixPlat(e.name), arch = null;
@@ -486,7 +486,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
486
486
  if (platBad || archBad) {
487
487
  if (process.env.OPTIMIZE_DEBUG) {
488
488
  const rel = toPosix(path.relative(nmDir, full));
489
- console.log(` ✓ ${rel} (${platBad ? '平台不符: ' + plat : '架构不符: ' + arch})`);
489
+ console.log(chalk.gray(` ✓ ${rel} (${platBad ? '平台不符: ' + plat : '架构不符: ' + arch})`));
490
490
  }
491
491
  delFile(full);
492
492
  }
@@ -496,21 +496,19 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
496
496
  return removed;
497
497
  },
498
498
  // ==================== 清开发文件 ====================
499
- pruneDevFiles = async (nmDir, nativePkgs) => {
499
+ pruneDevFiles = async (nmDir) => {
500
500
  let deleted = 0;
501
501
  const PATTERNS = [
502
502
  /(^|\/)tsconfig\.json$/,
503
503
  /(^|\/)\.nycrc(\.(json|yml))?$/,
504
504
  /(^|\/)\.editorconfig$/, /(^|\/)\.prettierignore$/, /(^|\/)\.eslintignore$/,
505
505
  /(^|\/)\.dockerignore$/, /(^|\/)\.gitignore$/, /(^|\/)\.gitattributes$/, /(^|\/)\.npmignore$/,
506
- /\.(md|markdown)$/i, /\.(gyp|gypi)$/i, /\.(yml|yaml)$/i, /\.(h|c)$/,
506
+ /\.(md|markdown)$/i, /\.(yml|yaml)$/i, /\.(gyp|gypi|h|hh|hpp|c|cc|cpp|cxx|m|mm)$/i,
507
507
  /\.map$/, /metafile-.*\.json$/,
508
508
  /(^|\/)\.eslintrc(\.(json|yml|js|cjs))?$/, /(^|\/)\.babelrc(\.(json|js))?$/,
509
509
  /(^|\/)package-lock\.json$/, /(^|\/)\.package-lock\.json$/, /(^|\/)yarn\.lock$/,
510
510
  /(^|\/)package-support\.json$/,
511
511
  ], DIR_SKIP = new Set([BUNDLED_DIR]),
512
- COMPILE_EXTS = /\.(gyp|gypi|h|hh|hpp|c|cc|cpp|cxx|m|mm)$/i,
513
- inNativePkg = p => nativePkgs.some(nd => p.startsWith(nd + path.sep)),
514
512
  walk = d => {
515
513
  for (const f of fs.readdirSync(d, { withFileTypes: true })) {
516
514
  const full = path.join(d, f.name);
@@ -523,7 +521,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
523
521
  walk(full);
524
522
  } else {
525
523
  const rel = toPosix(path.relative(nmDir, full));
526
- if (PATTERNS.some(re => re.test(rel)) && !(COMPILE_EXTS.test(f.name) && (inNativePkg(full) || isSkippedPkg(rel))))
524
+ if (PATTERNS.some(re => re.test(rel)))
527
525
  try { fs.unlinkSync(full), deleted++; } catch { }
528
526
  }
529
527
  }
@@ -533,19 +531,20 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
533
531
  },
534
532
  pruneIntermediates = async nmDir => {
535
533
  let deleted = 0;
536
- const walk = d => {
537
- for (const f of fs.readdirSync(d, { withFileTypes: true })) {
538
- const full = path.join(d, f.name);
539
- if (f.isDirectory()) walk(full);
540
- else if (f.name.endsWith('.orig')) try { fs.unlinkSync(full), deleted++; } catch { }
541
- }
542
- };
534
+ const MID_RE = /\.(o|d|mk|stamp|Makefile|iobj|ipdb|pdb|exp|lib|obj|vcxproj|tlog|recipe|lastbuildstate|nativecodeanalysis\.xml)$/i,
535
+ walk = d => {
536
+ for (const f of fs.readdirSync(d, { withFileTypes: true })) {
537
+ const full = path.join(d, f.name);
538
+ if (f.isDirectory()) walk(full);
539
+ else if (full.includes(path.sep + 'obj.target' + path.sep) || f.name === 'Makefile' || f.name.endsWith('.orig') || MID_RE.test(f.name)) try { fs.unlinkSync(full), deleted++; } catch { }
540
+ }
541
+ };
543
542
  walk(nmDir);
544
543
  return deleted;
545
544
  },
546
545
  applyFilesRules = async nmDir => {
547
546
  let deleted = 0;
548
- // 1) 删顶层 node 假包(npm 上名为 node 的包,仅提供 node.exe,运行时不需要)
547
+ // 1) 删顶层 node 假包(npm 上名为 node 的包,仅提供 node.exe,运行时不需要)
549
548
  const LICENSE_RE = /^licen[sc]e/i, TS_RE = /\.(ts|cts|mts)$/, nodePkg = path.join(nmDir, 'node');
550
549
  if (fs.existsSync(nodePkg)) try { fs.rmSync(nodePkg, { recursive: true, force: true }), deleted++; } catch { }
551
550
 
@@ -595,7 +594,7 @@ const require = createRequire(import.meta.url), subpath = (...parts) => './' + p
595
594
 
596
595
  // ==================== 导出函数 ====================
597
596
  /**
598
- * 清除快照中的 verified 标记(优化开始或构建失败时调用)
597
+ * 清除快照中的 verified 标记(优化开始或构建失败时调用)
599
598
  * >查看定义:@see {@link clearVerified}
600
599
  * @param {string} snapshotDir 快照目录
601
600
  * @returns {Promise<void>}
@@ -609,7 +608,7 @@ const clearVerified = async snapshotDir => {
609
608
  } catch { }
610
609
  };
611
610
  /**
612
- * 标记快照 verified=true(构建全流程成功后由 build.js / test.js 调用)
611
+ * 标记快照 verified=true(构建全流程成功后由 build.js / test.js 调用)
613
612
  * >查看定义:@see {@link markVerified}
614
613
  * @param {string} snapshotDir 快照目录
615
614
  * @returns {Promise<void>}
@@ -625,14 +624,14 @@ const markVerified = async snapshotDir => {
625
624
  /**
626
625
  * 执行 node_modules 六阶段优化
627
626
  * >查看定义:@see {@link optimizeNodeModules}
628
- * @param {string} tempDir node_modules 所在目录(nodejs-assets/nodejs-project)
627
+ * @param {string} tempDir node_modules 所在目录(nodejs-assets/nodejs-project)
629
628
  * @param {object} [config] 优化配置
630
- * @param {string} [config.snapshotDir] 快照目录(默认同 tempDir)
631
- * @param {string} [config.targetPlatform] 目标平台:'android'(默认)或 'ios'
632
- * @param {string[]} [config.abiFilters] Android 目标架构(默认 ['arm64-v8a']);iOS 固定保留 arm64 + x64
633
- * @param {string} config.runtimeMajor 运行时 Node 主版本号(必填,从 libnode/node_version.h 读取)
629
+ * @param {string} [config.snapshotDir] 快照目录(默认同 tempDir)
630
+ * @param {string} [config.targetPlatform] 目标平台:'android'(默认)或 'ios'
631
+ * @param {string[]} [config.abiFilters] Android 目标架构(默认 ['arm64-v8a']);iOS 固定保留 arm64 + x64
632
+ * @param {string} config.runtimeMajor 运行时 Node 主版本号(必填,从 libnode/node_version.h 读取)
634
633
  * @param {string[]} [config.exclude] 排除的包名列表
635
- * @returns {Promise<object>} 各阶段统计(merged / cleared / platform / devFiles / intermediates / filesRules)
634
+ * @returns {Promise<object>} 各阶段统计(merged / cleared / platform / devFiles / intermediates / filesRules)
636
635
  */
637
636
  const optimizeNodeModules = async (tempDir, config = {}) => {
638
637
  const targetPlatform = config.targetPlatform || 'android', abiFilters = config.abiFilters || ['arm64-v8a'],
@@ -641,49 +640,49 @@ const optimizeNodeModules = async (tempDir, config = {}) => {
641
640
  snapPath = path.join(snapshotDir, SNAPSHOT_FILE), myPlat = TARGET_PLAT_MAP[targetPlatform] || [],
642
641
  myArch = targetPlatform === 'ios' ? IOS_ARCHES : (abiFilters || []).map(a => ABI_ARCH_MAP[a]).filter(Boolean);
643
642
 
644
- if (!runtimeMajor) throw new Error('optimizeNodeModules 缺少 config.runtimeMajor(应从 libnode/node_version.h 读取)');
643
+ if (!runtimeMajor) throw new Error('optimizeNodeModules 缺少 config.runtimeMajor(应从 libnode/node_version.h 读取)');
645
644
  if (!fs.existsSync(nmDir)) {
646
- console.log('[优化] node_modules 不存在,跳过');
645
+ console.log(chalk.gray('[优化] node_modules 不存在,跳过'));
647
646
  return { skipped: true };
648
647
  }
649
648
 
650
649
  await clearVerified(snapshotDir); // 执行优化前清 verified,构建成功后再由 build.js / test.js 写回 true
651
- const t0 = Date.now(), nativePkgs = findPackages(nmDir).filter(p => fs.existsSync(path.join(p, 'binding.gyp')));
650
+ const t0 = Date.now();
652
651
  try {
653
- // 阶段1: 清空资源(跳过 js,留待合并阶段处理)
654
- console.log(' [1/6] 清空复制到项目根的资源...');
652
+ // 阶段1: 清空资源(跳过 js,留待合并阶段处理)
653
+ console.log(chalk.cyan(' [1/6] 清空复制到项目根的资源...'));
655
654
  const c = await clearResources(nmDir, excludePkgs);
656
- console.log(` 清空 ${c} 个文件(js 文件保留,留待合并阶段处理)`);
655
+ console.log(chalk.green(` 清空 ${c} 个文件(js 文件保留,留待合并阶段处理)`));
657
656
 
658
657
  // 阶段2: 合并
659
- console.log(' [2/6] 合并 JS...');
658
+ console.log(chalk.cyan(' [2/6] 合并 JS...'));
660
659
  const m = await mergeAll(nmDir, excludePkgs, runtimeMajor);
661
- console.log(` 合并 ${m.merged} 包,跳过 ${m.skipped},删 ${m.totalDeleted} 个 JS`);
660
+ console.log(chalk.green(` 合并 ${m.merged} 包,跳过 ${m.skipped},删 ${m.totalDeleted} 个 JS`));
662
661
 
663
662
  // 阶段3: 清平台二进制
664
- console.log(' [3/6] 清理非当前平台二进制...');
665
- const p = await prunePlatform(nmDir, myPlat, myArch);
666
- console.log(` 清理 ${p} 项`);
663
+ console.log(chalk.cyan(' [3/6] 清理非当前平台二进制...'));
664
+ const p = await prunePlatform(nmDir, myPlat, myArch, targetPlatform);
665
+ console.log(chalk.green(` 清理 ${p} 项`));
667
666
 
668
667
  // 阶段4: 清开发文件
669
- console.log(' [4/6] 清理开发文件...');
670
- const d = await pruneDevFiles(nmDir, nativePkgs);
671
- console.log(` 清理 ${d} 个文件`);
668
+ console.log(chalk.cyan(' [4/6] 清理开发文件...'));
669
+ const d = await pruneDevFiles(nmDir);
670
+ console.log(chalk.green(` 清理 ${d} 个文件`));
672
671
 
673
672
  // 阶段5: 清中间产物
674
- console.log(' [5/6] 清理中间产物...');
673
+ console.log(chalk.cyan(' [5/6] 清理中间产物...'));
675
674
  const i = await pruneIntermediates(nmDir);
676
- console.log(` 清理 ${i} 个文件`);
675
+ console.log(chalk.green(` 清理 ${i} 个文件`));
677
676
 
678
- // 阶段6: 清除不参与运行的文件(与 build.js 的 files 数组对齐)
679
- console.log(' [6/6] 清除不参与运行的文件和空目录...');
677
+ // 阶段6: 清除不参与运行的文件(与 build.js 的 files 数组对齐)
678
+ console.log(chalk.cyan(' [6/6] 清除不参与运行的文件和空目录...'));
680
679
  const fr = await applyFilesRules(nmDir);
681
- console.log(` 清理 ${fr} 项`);
680
+ console.log(chalk.green(` 清理 ${fr} 项`));
682
681
 
683
682
  // 写 platform/abiFilters/runtimeMajor,同时 verified=false
684
683
  await writeSnapshotFlags(snapshotDir, targetPlatform, abiFilters, runtimeMajor);
685
684
  const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
686
- console.log(`[信息] 优化完成,耗时 ${elapsed}s`);
685
+ console.log(chalk.green(` [信息] 优化完成,耗时 ${elapsed}s`));
687
686
 
688
687
  return { merged: m.merged, cleared: c, platform: p, devFiles: d, intermediates: i, filesRules: fr };
689
688
  } catch (err) {
@@ -693,7 +692,7 @@ const optimizeNodeModules = async (tempDir, config = {}) => {
693
692
  }
694
693
  };
695
694
  /**
696
- * 当前优化文件的 SHA256 前 16 位(缓存指纹:优化代码一改,缓存自动失效)
695
+ * 当前优化文件的 SHA256 前 16 位(缓存指纹:优化代码一改,缓存自动失效)
697
696
  * >查看定义:@see {@link SELF_HASH}
698
697
  */
699
698
  const SELF_HASH = (() => {