@flun/nodejs-mobile-gyp 1.2.2 → 1.2.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # 变更日志
2
2
 
3
+ ## [1.2.3] - 2026-11-10 22:15
4
+ ### 优化
5
+ - 整体细节优化+依赖包更新;
6
+
3
7
  ## [1.2.2] - 2026-11-06 11:02
4
8
  ### 优化
5
9
  - 细节优化;
@@ -27,25 +31,4 @@
27
31
 
28
32
  ### 文档
29
33
  - README 的包目录结构同步磁盘实际内容:补 `gyp/pyproject.toml`、`gyp/README.md`、
30
- `gyp/test_gyp.py`,移除不存在的 `gyp/LICENSE`;顶层文件按 `files` 顺序重排;
31
-
32
- ## [1.2.0] - 2026-11-02 16:05
33
- ### 新增
34
- - 帮助文本重写为中文三层结构:`用法:` 为 0 级,`命令:`/`选项:` 缩进 2,各条目缩进 4;
35
- 命令含别名 `ls`/`rm`,选项简写在前、完整写法置于行尾注释;末尾仅输出版本号行;
36
- - 无参数、`help`、`-h`、`--help` 均打印完整帮助并以 exit 0 退出;
37
- - 帮助文本按语义着色:`用法:` 用 `chalk.blueBright`,分组名用 `chalk.cyan`,命令与选项名用 `chalk.cyanBright`,
38
- 说明文字用 `chalk.gray`,末尾版本号用 `chalk.hex('#B8860B')`;非 TTY 下自动关闭颜色,不向管道泄漏 ANSI;
39
- - 未知命令提示着色:错误标记用 `chalk.red`,命令名用 `chalk.yellow`;
40
-
41
- ### 修复
42
- - 未知命令改为输出 `✗ 未知命令: xxx` + 一个空行 + 完整帮助,并以 exit 1 退出;
43
- 此前未知参数不报错,静默继续;
44
- - `-h` 此前被忽略,现作为帮助入口;该行为变更可能影响依赖旧行为的脚本;
45
- - 帮助文本命令区与选项区说明列错位:命令区原补到显示列 17,选项区 23,现统一补到 23;
46
- 补白改为按显示宽度计算,中日韩全角字符按 2 列计,此前按码元计导致含中文的选项名整体左移;
47
- - `package.json` 的 `dependencies` 补声明 `chalk`;`lib/node-gyp.js` 已引用该模块,
48
- 此前未声明,从 npm 安装后缺少依赖会导致 `--help` 直接报错;
49
-
50
- ### 文档
51
- - `lib/` 下 12 个 js 去除 BOM;`维护.md`、`package.json`、`lib/util.js` 换行符由 CRLF 改为 LF;
34
+ `gyp/test_gyp.py`,移除不存在的 `gyp/LICENSE`;顶层文件按 `files` 顺序重排;
package/bin/node-gyp.js CHANGED
@@ -1,27 +1,23 @@
1
1
  #!/usr/bin/env node
2
-
2
+ import chalk from 'chalk';
3
3
  import envPaths from 'env-paths';
4
- import os from 'os';
5
4
  import fs from 'fs';
6
- import chalk from 'chalk';
7
- import { Gyp } from '../lib/node-gyp.js';
5
+ import os from 'os';
8
6
  import * as log from '../lib/log.js';
7
+ import { Gyp } from '../lib/node-gyp.js';
9
8
 
10
9
  process.title = 'nodejs-mobile-gyp';
11
10
  /**
12
11
  * 处理并执行选中的命令;
13
12
  */
14
-
15
13
  const prog = new Gyp(), homeDir = os.homedir();
16
14
  let completed = false;
17
15
  prog.parseArgv(process.argv), prog.devDir = prog.opts.devdir;
18
16
 
19
17
  if (prog.devDir) prog.devDir = prog.devDir.replace(/^~/, homeDir);
20
18
  else if (homeDir) prog.devDir = envPaths('node-gyp', { suffix: '' }).cache;
21
- else {
22
- throw new Error('nodejs-mobile-gyp 需要指定用户主目录,请设置环境变量 HOME 或 USERPROFILE;'
23
- + '也可用 --devdir /path/to/.node-gyp 覆盖');
24
- }
19
+ else throw new Error('nodejs-mobile-gyp 需要指定用户主目录,请设置环境变量 HOME 或 USERPROFILE;'
20
+ + '也可用 --devdir /path/to/.node-gyp 覆盖');
25
21
 
26
22
  if (prog.todo.length === 0) {
27
23
  const helpArgs = ['help', '-h', '--help'], hasHelp = process.argv.some(a => helpArgs.includes(a)),
package/lib/build.js CHANGED
@@ -1,11 +1,11 @@
1
+ import { spawnSync } from 'child_process';
2
+ import fsSync from 'fs';
3
+ import { glob } from 'glob';
1
4
  import gracefulFs from 'graceful-fs';
5
+ import os from 'os';
2
6
  import path from 'path';
3
- import { glob } from 'glob';
4
- import * as log from './log.js';
5
7
  import which from 'which';
6
- import os from 'os';
7
- import { spawnSync } from 'child_process';
8
- import fsSync from 'fs';
8
+ import * as log from './log.js';
9
9
 
10
10
  const fs = gracefulFs.promises, _hostIsWindows = process.platform === 'win32',
11
11
  // Android 交叉编译即使宿主是 Windows,也必须走 make(make-android 生成器)
@@ -65,151 +65,155 @@ const fs = gracefulFs.promises, _hostIsWindows = process.platform === 'win32',
65
65
  }
66
66
  }
67
67
  return null;
68
- },
68
+ };
69
69
 
70
- /**
71
- * 调用 make/msbuild 构建原生模块
72
- */
73
- build = async (gyp, argv) => {
74
- let platformMake = 'make';
70
+ /**
71
+ * 调用 make/msbuild 构建原生模块
72
+ * >查看定义:@see {@link build}
73
+ * @param {object} gyp gyp 实例
74
+ * @param {string[]} argv 命令行参数
75
+ * @returns {Promise<void>}
76
+ */
77
+ const build = async (gyp, argv) => {
78
+ let platformMake = 'make';
75
79
 
76
- if (['aix', 'os400'].includes(process.platform) || process.platform.includes('bsd')) platformMake = 'gmake';
77
- else if (win && argv.length > 0) argv = argv.map(target => '/t:' + target);
80
+ if (['aix', 'os400'].includes(process.platform) || process.platform.includes('bsd')) platformMake = 'gmake';
81
+ else if (win && argv.length > 0) argv = argv.map(target => '/t:' + target);
78
82
 
79
- const makeCommand = gyp.opts.make || process.env.MAKE || platformMake, jobs = gyp.opts.jobs || process.env.JOBS;
80
- let command = win ? 'msbuild' : makeCommand, buildType, config, arch, nodeDir, guessedSolution, python, buildBinsDir;
83
+ const makeCommand = gyp.opts.make || process.env.MAKE || platformMake, jobs = gyp.opts.jobs || process.env.JOBS;
84
+ let command = win ? 'msbuild' : makeCommand, buildType, config, arch, nodeDir, guessedSolution, python, buildBinsDir;
81
85
 
82
- /**
83
- * 真正启动子进程并编译模块
84
- */
85
- const doBuild = async () => {
86
- // 启用 verbose 构建
87
- const verbose = log.logger.isVisible('verbose');
88
- let j;
86
+ /**
87
+ * 真正启动子进程并编译模块
88
+ */
89
+ const doBuild = async () => {
90
+ // 启用 verbose 构建
91
+ const verbose = log.logger.isVisible('verbose');
92
+ let j;
93
+
94
+ if (!win && verbose) argv.push('V=1');
95
+ if (win && !verbose) argv.push('/clp:Verbosity=minimal');
96
+ if (win) argv.push('/nologo'); // Windows 上关闭 Microsoft logo
97
+
98
+ // 指定构建类型,默认 Release
99
+ if (win) {
100
+ // 把 .gypi 配置中的 target_arch 转换为 MSBuild 的 /Platform
101
+ // 因为表示"32 位 Intel"的方式很多,默认按此处理
102
+ // 注意:msbuild 的 Condition 字符串相等比较不区分大小写
103
+ const archLower = arch.toLowerCase(), p = archLower === 'x64' ? 'x64' : (archLower === 'arm' ? 'ARM'
104
+ : (archLower === 'arm64' ? 'ARM64' : 'Win32'));
105
+ argv.push('/p:Configuration=' + buildType + ';Platform=' + p);
106
+ if (jobs) {
107
+ j = parseInt(jobs, 10)
108
+ if (!isNaN(j) && j > 0) argv.push('/m:' + j);
109
+ else if (jobs.toUpperCase() === 'MAX') argv.push('/m:' + os.cpus().length);
110
+ }
111
+ }
112
+ // 调用 build 目录下的 Makefile
113
+ else {
114
+ argv.push('BUILDTYPE=' + buildType), argv.push('-C'), argv.push('build');
115
+ if (jobs) {
116
+ j = parseInt(jobs, 10);
117
+ if (!isNaN(j) && j > 0) argv.push('--jobs'), argv.push(j);
118
+ else if (jobs.toUpperCase() === 'MAX') argv.push('--jobs'), argv.push(os.cpus().length);
119
+ }
120
+ }
89
121
 
90
- if (!win && verbose) argv.push('V=1');
91
- if (win && !verbose) argv.push('/clp:Verbosity=minimal');
92
- if (win) argv.push('/nologo'); // Windows 上关闭 Microsoft logo
122
+ if (win) {
123
+ // 用户是否指定了自己的 .sln 文件?
124
+ const hasSln = argv.some(arg => path.extname(arg) === '.sln');
125
+ if (!hasSln) argv.unshift(gyp.opts.solution || guessedSolution);
126
+ }
93
127
 
94
- // 指定构建类型,默认 Release
95
- if (win) {
96
- // 把 .gypi 配置中的 target_arch 转换为 MSBuild 的 /Platform
97
- // 因为表示"32 位 Intel"的方式很多,默认按此处理
98
- // 注意:msbuild 的 Condition 字符串相等比较不区分大小写
99
- const archLower = arch.toLowerCase(), p = archLower === 'x64' ? 'x64' : (archLower === 'arm' ? 'ARM'
100
- : (archLower === 'arm64' ? 'ARM64' : 'Win32'));
101
- argv.push('/p:Configuration=' + buildType + ';Platform=' + p);
102
- if (jobs) {
103
- j = parseInt(jobs, 10)
104
- if (!isNaN(j) && j > 0) argv.push('/m:' + j);
105
- else if (jobs.toUpperCase() === 'MAX') argv.push('/m:' + os.cpus().length);
128
+ if (!win) {
129
+ // 把构建期依赖的符号链接(如 Python)加入 PATH
130
+ buildBinsDir = path.resolve('build', 'node_gyp_bins');
131
+ if (_hostIsWindows) {
132
+ const shellDir = findWindowsShellDir();
133
+ if (shellDir) {
134
+ process.env.PATH = `${shellDir};${process.env.PATH}`;
135
+ log.verbose('bin 环境', `已将 ${shellDir} 加入 PATH,供 make 使用`);
106
136
  }
137
+ else log.warn('shell 环境', '未找到 sh.exe;GNU make 的 Unix 命令可能失败;建议安装 Git for Windows');
107
138
  }
108
- // 调用 build 目录下的 Makefile
109
- else {
110
- argv.push('BUILDTYPE=' + buildType), argv.push('-C'), argv.push('build');
111
- if (jobs) {
112
- j = parseInt(jobs, 10);
113
- if (!isNaN(j) && j > 0) argv.push('--jobs'), argv.push(j);
114
- else if (jobs.toUpperCase() === 'MAX') argv.push('--jobs'), argv.push(os.cpus().length);
139
+ process.env.PATH = _hostIsWindows ? `${buildBinsDir};${process.env.PATH}` : `${buildBinsDir}:${process.env.PATH}`;
140
+ await fs.mkdir(buildBinsDir, { recursive: true });
141
+
142
+ if (_hostIsWindows) {
143
+ // Windows 无 python3 名称,且创建符号链接需要管理员权限;改用 .cmd 桥接
144
+ const shimPath = path.join(buildBinsDir, 'python3.cmd');
145
+ await fs.writeFile(shimPath, `@echo off\r\n"${python}" %*\r\n`);
146
+ log.verbose('bin 垫片', `已创建 python3.cmd 指向 "${python}" 并加入 PATH`);
147
+ } else {
148
+ const symlinkDestination = path.join(buildBinsDir, 'python3');
149
+ try {
150
+ await fs.unlink(symlinkDestination);
151
+ } catch (err) {
152
+ if (err.code !== 'ENOENT') throw err;
115
153
  }
154
+ await fs.symlink(python, symlinkDestination);
155
+ log.verbose('bin 符号链接', `已在 "${buildBinsDir}" 创建指向 "${python}" 的符号链接并加入 PATH`);
116
156
  }
157
+ }
117
158
 
159
+ const proc = gyp.spawn(command, argv);
160
+ await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
161
+ if (buildBinsDir) await fs.rm(buildBinsDir, { recursive: true }); // 清理构建期依赖的符号链接
162
+ if (code !== 0) return reject(new Error('`' + command + '` 执行失败,退出码: ' + code));
163
+ if (signal) return reject(new Error('`' + command + '` 收到信号: ' + signal));
164
+ resolve();
165
+ }))
166
+ },
167
+ /**
168
+ * 用 node-which 定位 msbuild / make 可执行文件
169
+ */
170
+ doWhich = async () => {
171
+ // Windows 使用 nodejs-mobile-gyp configure 提供的 msbuild
118
172
  if (win) {
119
- // 用户是否指定了自己的 .sln 文件?
120
- const hasSln = argv.some(arg => path.extname(arg) === '.sln');
121
- if (!hasSln) argv.unshift(gyp.opts.solution || guessedSolution);
122
- }
123
-
124
- if (!win) {
125
- // 把构建期依赖的符号链接(如 Python)加入 PATH
126
- buildBinsDir = path.resolve('build', 'node_gyp_bins');
127
- if (_hostIsWindows) {
128
- const shellDir = findWindowsShellDir();
129
- if (shellDir) {
130
- process.env.PATH = `${shellDir};${process.env.PATH}`;
131
- log.verbose('bin 环境', `已将 ${shellDir} 加入 PATH,供 make 使用`);
132
- }
133
- else log.warn('shell 环境', '未找到 sh.exe;GNU make 的 Unix 命令可能失败;建议安装 Git for Windows');
134
- }
135
- process.env.PATH = _hostIsWindows ? `${buildBinsDir};${process.env.PATH}` : `${buildBinsDir}:${process.env.PATH}`;
136
- await fs.mkdir(buildBinsDir, { recursive: true });
137
-
138
- if (_hostIsWindows) {
139
- // Windows 无 python3 名称,且创建符号链接需要管理员权限;改用 .cmd 桥接
140
- const shimPath = path.join(buildBinsDir, 'python3.cmd');
141
- await fs.writeFile(shimPath, `@echo off\r\n"${python}" %*\r\n`);
142
- log.verbose('bin 垫片', `已创建 python3.cmd 指向 "${python}" 并加入 PATH`);
143
- } else {
144
- const symlinkDestination = path.join(buildBinsDir, 'python3');
145
- try {
146
- await fs.unlink(symlinkDestination);
147
- } catch (err) {
148
- if (err.code !== 'ENOENT') throw err;
149
- }
150
- await fs.symlink(python, symlinkDestination);
151
- log.verbose('bin 符号链接', `已在 "${buildBinsDir}" 创建指向 "${python}" 的符号链接并加入 PATH`);
152
- }
173
+ if (!config.variables.msbuild_path) throw new Error('MSBuild 未设置,请先运行 `nodejs-mobile-gyp configure`');
174
+ command = config.variables.msbuild_path, log.verbose('使用 MSBuild:', command), await doBuild();
175
+ return;
153
176
  }
154
177
 
155
- const proc = gyp.spawn(command, argv);
156
- await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
157
- if (buildBinsDir) await fs.rm(buildBinsDir, { recursive: true }); // 清理构建期依赖的符号链接
158
- if (code !== 0) return reject(new Error('`' + command + '` 执行失败,退出码: ' + code));
159
- if (signal) return reject(new Error('`' + command + '` 收到信号: ' + signal));
160
- resolve();
161
- }))
178
+ // 先确认构建命令在 PATH 中
179
+ const execPath = await which(command);
180
+ log.verbose('`which` 成功解析 `' + command + '`', execPath), await doBuild();
162
181
  },
163
- /**
164
- * 用 node-which 定位 msbuild / make 可执行文件
165
- */
166
- doWhich = async () => {
167
- // Windows 使用 nodejs-mobile-gyp configure 提供的 msbuild
168
- if (win) {
169
- if (!config.variables.msbuild_path) throw new Error('MSBuild 未设置,请先运行 `nodejs-mobile-gyp configure`');
170
- command = config.variables.msbuild_path, log.verbose('使用 MSBuild:', command), await doBuild();
171
- return;
172
- }
173
-
174
- // 先确认构建命令在 PATH 中
175
- const execPath = await which(command);
176
- log.verbose('`which` 成功解析 `' + command + '`', execPath), await doBuild();
177
- },
178
- /**
179
- * Windows 下查找第一个 build/*.sln 文件
180
- */
181
- findSolutionFile = async () => {
182
- const files = await glob('build/*.sln')
183
- if (files.length === 0) throw new Error('未找到 *.sln 文件,是否运行过 configure?');
184
- guessedSolution = files[0], log.verbose('找到首个解决方案文件', guessedSolution), await doWhich();
185
- },
186
- /**
187
- * 加载 configure 阶段生成的 config.gypi
188
- */
189
- loadConfigGypi = async () => {
190
- let data;
191
- try {
192
- const configPath = path.resolve('build', 'config.gypi');
193
- data = await fs.readFile(configPath, 'utf8');
194
- } catch (err) {
195
- if (err.code === 'ENOENT') throw new Error('请先运行 `nodejs-mobile-gyp configure`');
196
- else throw err;
197
- }
182
+ /**
183
+ * Windows 下查找第一个 build/*.sln 文件
184
+ */
185
+ findSolutionFile = async () => {
186
+ const files = await glob('build/*.sln')
187
+ if (files.length === 0) throw new Error('未找到 *.sln 文件,是否运行过 configure?');
188
+ guessedSolution = files[0], log.verbose('找到首个解决方案文件', guessedSolution), await doWhich();
189
+ },
190
+ /**
191
+ * 加载 configure 阶段生成的 config.gypi
192
+ */
193
+ loadConfigGypi = async () => {
194
+ let data;
195
+ try {
196
+ const configPath = path.resolve('build', 'config.gypi');
197
+ data = await fs.readFile(configPath, 'utf8');
198
+ } catch (err) {
199
+ if (err.code === 'ENOENT') throw new Error('请先运行 `nodejs-mobile-gyp configure`');
200
+ else throw err;
201
+ }
198
202
 
199
- // 从 config 中取出 arch、buildType、nodeDir
200
- config = JSON.parse(data.replace(/#.+\n/, '')), buildType = config.target_defaults.default_configuration;
201
- arch = config.variables.target_arch, nodeDir = config.variables.nodedir, python = config.variables.python;
203
+ // 从 config 中取出 arch、buildType、nodeDir
204
+ config = JSON.parse(data.replace(/#.+\n/, '')), buildType = config.target_defaults.default_configuration;
205
+ arch = config.variables.target_arch, nodeDir = config.variables.nodedir, python = config.variables.python;
202
206
 
203
- if ('debug' in gyp.opts) buildType = gyp.opts.debug ? 'Debug' : 'Release';
204
- if (!buildType) buildType = 'Release';
207
+ if ('debug' in gyp.opts) buildType = gyp.opts.debug ? 'Debug' : 'Release';
208
+ if (!buildType) buildType = 'Release';
205
209
 
206
- log.verbose('构建类型', buildType), log.verbose('架构', arch), log.verbose('node 开发目录', nodeDir);
207
- log.verbose('Python', python);
210
+ log.verbose('构建类型', buildType), log.verbose('架构', arch), log.verbose('node 开发目录', nodeDir);
211
+ log.verbose('Python', python);
208
212
 
209
- win ? await findSolutionFile() : await doWhich();
210
- };
211
- await loadConfigGypi();
212
- };
213
+ win ? await findSolutionFile() : await doWhich();
214
+ };
215
+ await loadConfigGypi();
216
+ };
213
217
 
214
218
  build.usage = '调用 `' + (win ? 'msbuild' : 'make') + '` 构建模块';
215
219
  export { build };
package/lib/clean.js CHANGED
@@ -1,16 +1,19 @@
1
1
  import gracefulFs from 'graceful-fs';
2
2
  import * as log from './log.js';
3
3
 
4
- const fs = gracefulFs.promises,
5
- /**
6
- * 清理:删除 build 目录
7
- */
8
- clean = async (gyp, argv) => {
9
- const buildDir = 'build';
4
+ /**
5
+ * 清理:删除 build 目录
6
+ * >查看定义:@see {@link clean}
7
+ * @param {object} gyp gyp 实例
8
+ * @param {string[]} argv 命令行参数
9
+ * @returns {Promise<void>}
10
+ */
11
+ const clean = async (gyp, argv) => {
12
+ const buildDir = 'build';
10
13
 
11
- log.verbose('清理', '正在删除 "%s" 目录', buildDir);
12
- await fs.rm(buildDir, { recursive: true, force: true });
13
- }
14
+ log.verbose('清理', '正在删除 "%s" 目录', buildDir);
15
+ await gracefulFs.promises.rm(buildDir, { recursive: true, force: true });
16
+ };
14
17
 
15
18
  clean.usage = '删除生成的构建文件与 "out" 目录';
16
19
  export { clean };
package/lib/configure.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import gracefulFs from 'graceful-fs';
2
+ import os from 'os';
2
3
  import path from 'path';
4
+ import { format as msgFormat } from 'util';
5
+ import { fileURLToPath } from 'url';
6
+ import { createConfigGypi } from './create-config-gypi.js';
7
+ import { findNodeDirectory } from './find-node-directory.js';
8
+ import { PythonFinder } from './find-python.js';
3
9
  import * as log from './log.js';
4
- import os from 'os';
5
10
  import { processRelease } from './process-release.js';
6
- import { findNodeDirectory } from './find-node-directory.js';
7
- import { createConfigGypi } from './create-config-gypi.js';
8
- import { format as msgFormat } from 'util';
9
11
  import { findAccessibleSync } from './util.js';
10
- import { PythonFinder } from './find-python.js';
11
- import { fileURLToPath } from 'url';
12
12
 
13
13
  const fs = gracefulFs.promises, win = process.platform === 'win32',
14
14
  __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -22,6 +22,10 @@ if (win) {
22
22
 
23
23
  /**
24
24
  * 生成构建文件(Makefile / MSVC 工程)
25
+ * >查看定义:@see {@link configure}
26
+ * @param {object} gyp gyp 实例
27
+ * @param {string[]} argv 命令行参数
28
+ * @returns {Promise<void>}
25
29
  */
26
30
  const configure = async (gyp, argv) => {
27
31
  const buildDir = path.resolve('build'), configNames = ['config.gypi', 'common.gypi'], configs = [],