@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/lib/install.js CHANGED
@@ -1,311 +1,314 @@
1
+ import crypto from 'crypto';
2
+ import { backOff } from 'exponential-backoff';
1
3
  import gracefulFs from 'graceful-fs';
2
4
  import os from 'os';
3
- import { backOff } from 'exponential-backoff';
4
- import * as tar from 'tar';
5
5
  import path from 'path';
6
- import { Transform, promises as streamPromises } from 'stream';
7
- import crypto from 'crypto';
8
- import * as log from './log.js';
9
6
  import semver from 'semver';
7
+ import { promises as streamPromises, Transform } from 'stream';
8
+ import * as tar from 'tar';
10
9
  import { download } from './download.js';
10
+ import * as log from './log.js';
11
11
  import { processRelease } from './process-release.js';
12
12
 
13
- const { createWriteStream, promises: fs } = gracefulFs, { pipeline } = streamPromises, win = process.platform === 'win32',
14
- /**
15
- * 为指定 Node 版本安装开发文件
16
- */
17
- install = async (gyp, argv) => {
18
- log.stdout();
19
- const release = processRelease(argv, gyp, process.version, process.release),
20
- // 按 create-config-gypi.js 的逻辑推断 target_arch,仅 Windows 用
21
- arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : '';
22
- let shouldDownloadTarball = true; // 仅 Windows 下只需新 node.lib 时,避免下载 tarball
13
+ /**
14
+ * SHA256 校验流
15
+ */
16
+ class ShaSum extends Transform {
17
+ constructor(callback) {
18
+ super(), this._callback = callback, this._digester = crypto.createHash('sha256');
19
+ }
23
20
 
24
- // 确定要安装哪个版本的 Node 开发文件
25
- log.verbose('安装', '输入版本字符串 %j', release.version);
26
- if (!release.semver) throw new Error('版本号无效: ' + release.version); // 无法用 semver 解析版本字符串
27
- if (semver.lt(release.version, '0.8.0')) throw new Error('目标版本最低为 `0.8.0`,当前: ' + release.version);
28
- // 0.x.y-pre 版本尚未发布,无法安装,直接退出
29
- if (release.semver.prerelease[0] === 'pre') {
30
- log.verbose('检测到 "pre" 版 node', release.version);
31
- if (!gyp.opts.nodedir) throw new Error('无法安装 "pre" 版本的 node,请改用 --nodedir 参数');
32
- log.verbose('传入了 --nodedir 参数;跳过安装', gyp.opts.nodedir);
33
- return;
34
- }
21
+ _transform(chunk, _, callback) {
22
+ this._digester.update(chunk), callback(null, chunk)
23
+ }
35
24
 
36
- log.verbose('安装', '正在安装版本: %s', release.versionDir); // 版本拍平为字符串
37
- const devDir = path.resolve(gyp.devDir, release.versionDir), // 开发文件安装目录
38
- valid = file => {
39
- // 头文件
40
- const extname = path.extname(file);
41
- return extname === '.h' || extname === '.gypi';
42
- },
43
- copyDirectory = async (src, dest) => {
44
- try {
45
- await fs.stat(src);
46
- } catch {
47
- throw new Error(`找不到要复制的源目录: ${src}`);
48
- }
49
- await fs.mkdir(dest, { recursive: true });
50
- const entries = await fs.readdir(src, { withFileTypes: true });
51
- for (const entry of entries) {
52
- if (entry.isDirectory()) await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name));
53
- else if (entry.isFile()) {
54
- // 并行安装时,Windows 上复制文件可能报文件错误,
55
- // 因此用指数退避解决冲突
56
- await backOff(async () => {
57
- try {
58
- await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name));
59
- } catch (err) {
60
- // 若设置了 ensure,检查文件是否已存在,存在就足够了
61
- if (gyp.opts.ensure && err.code === 'EBUSY') {
62
- try {
63
- await fs.stat(path.join(dest, entry.name));
64
- return;
65
- } catch { }
66
- }
67
- throw err;
25
+ _flush(callback) {
26
+ this._callback(null, this._digester.digest('hex')), callback()
27
+ }
28
+ }
29
+
30
+ const { createWriteStream, promises: fs } = gracefulFs, { pipeline } = streamPromises, win = process.platform === 'win32';
31
+ /**
32
+ * 下载并安装指定版本的 Node.js 头文件与库
33
+ * >查看定义:@see {@link install}
34
+ * @param {*} gyp
35
+ * @param {*} argv
36
+ * @returns {*}
37
+ */
38
+ const install = async (gyp, argv) => {
39
+ log.stdout();
40
+ const release = processRelease(argv, gyp, process.version, process.release),
41
+ // 按 create-config-gypi.js 的逻辑推断 target_arch,仅 Windows 用
42
+ arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : '';
43
+ let shouldDownloadTarball = true; // 仅 Windows 下只需新 node.lib 时,避免下载 tarball
44
+
45
+ // 确定要安装哪个版本的 Node 开发文件
46
+ log.verbose('安装', '输入版本字符串 %j', release.version);
47
+ if (!release.semver) throw new Error('版本号无效: ' + release.version); // 无法用 semver 解析版本字符串
48
+ if (semver.lt(release.version, '0.8.0')) throw new Error('目标版本最低为 `0.8.0`,当前: ' + release.version);
49
+ // 0.x.y-pre 版本尚未发布,无法安装,直接退出
50
+ if (release.semver.prerelease[0] === 'pre') {
51
+ log.verbose('检测到 "pre" 版 node', release.version);
52
+ if (!gyp.opts.nodedir) throw new Error('无法安装 "pre" 版本的 node,请改用 --nodedir 参数');
53
+ log.verbose('传入了 --nodedir 参数;跳过安装', gyp.opts.nodedir);
54
+ return;
55
+ }
56
+
57
+ log.verbose('安装', '正在安装版本: %s', release.versionDir); // 版本拍平为字符串
58
+ const devDir = path.resolve(gyp.devDir, release.versionDir), // 开发文件安装目录
59
+ valid = file => {
60
+ // 头文件
61
+ const extname = path.extname(file);
62
+ return extname === '.h' || extname === '.gypi';
63
+ },
64
+ copyDirectory = async (src, dest) => {
65
+ try {
66
+ await fs.stat(src);
67
+ } catch {
68
+ throw new Error(`找不到要复制的源目录: ${src}`);
69
+ }
70
+ await fs.mkdir(dest, { recursive: true });
71
+ const entries = await fs.readdir(src, { withFileTypes: true });
72
+ for (const entry of entries) {
73
+ if (entry.isDirectory()) await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name));
74
+ else if (entry.isFile()) {
75
+ // 并行安装时,Windows 上复制文件可能报文件错误,
76
+ // 因此用指数退避解决冲突
77
+ await backOff(async () => {
78
+ try {
79
+ await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name));
80
+ } catch (err) {
81
+ // 若设置了 ensure,检查文件是否已存在,存在就足够了
82
+ if (gyp.opts.ensure && err.code === 'EBUSY') {
83
+ try {
84
+ await fs.stat(path.join(dest, entry.name));
85
+ return;
86
+ } catch { }
68
87
  }
69
- })
70
- }
71
- else throw new Error('意外的文件目录条目类型');
72
- }
73
- },
74
- rollback = async err => {
75
- log.warn('安装', '发生错误,正在回滚安装')
76
- await gyp.commands.remove([release.versionDir]);
77
- throw err;
78
- },
79
- /**
80
- * EACCES 回退方案,用于处理 npm 的 `sudo` 行为:
81
- * npm 在调用任何子进程(如 node-gyp)前会先降权,
82
- * 于是 "nobody" 用户没有权限创建 dev dir;
83
- * 作为回退,把 tmpdir() 作为本次安装的 dev dir;
84
- * 这不理想,但至少编译能成功......
85
- */
86
- eaccesFallback = async err => {
87
- const noretry = '--node_gyp_internal_noretry';
88
- if (argv.indexOf(noretry) !== -1) throw err;
89
- const tmpdir = os.tmpdir();
90
- gyp.devDir = path.resolve(tmpdir, '.node-gyp');
91
- let userString = '';
92
- try {
93
- userString = ` ("${os.userInfo().username}")`; // os.userInfo 在某些系统上会失败,这里不关键
94
- } catch (e) { }
95
- log.warn('EACCES', '当前用户%s 没有权限访问开发文件目录 "%s"', userString, devDir);
96
- log.warn('EACCES', '尝试用临时开发文件目录重新安装 "%s"', gyp.devDir);
97
- if (process.cwd() === tmpdir) {
98
- log.verbose('tmpdir 等于 cwd', '将自动删除开发文件以节省磁盘空间');
99
- gyp.todo.push({ name: 'remove', args: argv });
88
+ throw err;
89
+ }
90
+ })
100
91
  }
101
- return gyp.commands.install([noretry].concat(argv));
102
- },
103
- go = async () => {
104
- log.verbose('正在确保 devDir 已创建', devDir);
92
+ else throw new Error('意外的文件目录条目类型');
93
+ }
94
+ },
95
+ rollback = async err => {
96
+ log.warn('安装', '发生错误,正在回滚安装')
97
+ await gyp.commands.remove([release.versionDir]);
98
+ throw err;
99
+ },
100
+ /**
101
+ * EACCES 回退方案,用于处理 npm 的 `sudo` 行为:
102
+ * npm 在调用任何子进程(如 node-gyp)前会先降权,
103
+ * 于是 "nobody" 用户没有权限创建 dev dir;
104
+ * 作为回退,把 tmpdir() 作为本次安装的 dev dir;
105
+ * 这不理想,但至少编译能成功......
106
+ */
107
+ eaccesFallback = async err => {
108
+ const noretry = '--node_gyp_internal_noretry';
109
+ if (argv.indexOf(noretry) !== -1) throw err;
110
+ const tmpdir = os.tmpdir();
111
+ gyp.devDir = path.resolve(tmpdir, '.node-gyp');
112
+ let userString = '';
113
+ try {
114
+ userString = ` ("${os.userInfo().username}")`; // os.userInfo 在某些系统上会失败,这里不关键
115
+ } catch (e) { }
116
+ log.warn('EACCES', '当前用户%s 没有权限访问开发文件目录 "%s"', userString, devDir);
117
+ log.warn('EACCES', '尝试用临时开发文件目录重新安装 "%s"', gyp.devDir);
118
+ if (process.cwd() === tmpdir) {
119
+ log.verbose('tmpdir 等于 cwd', '将自动删除开发文件以节省磁盘空间');
120
+ gyp.todo.push({ name: 'remove', args: argv });
121
+ }
122
+ return gyp.commands.install([noretry].concat(argv));
123
+ },
124
+ go = async () => {
125
+ log.verbose('正在确保 devDir 已创建', devDir);
105
126
 
106
- // 先创建 Node 开发文件所在目录
107
- try {
108
- const created = await fs.mkdir(devDir, { recursive: true });
109
- if (created) log.verbose('已创建 devDir', created);
110
- } catch (err) {
111
- if (err.code === 'EACCES') return eaccesFallback(err);
112
- throw err;
113
- }
127
+ // 先创建 Node 开发文件所在目录
128
+ try {
129
+ const created = await fs.mkdir(devDir, { recursive: true });
130
+ if (created) log.verbose('已创建 devDir', created);
131
+ } catch (err) {
132
+ if (err.code === 'EACCES') return eaccesFallback(err);
133
+ throw err;
134
+ }
114
135
 
115
- // 下载 Node tarball
116
- let extractErrors = false, extractCount = 0;
117
- const tarPath = gyp.opts.tarball, contentShasums = {}, expectShasums = {},
118
- // 检查从 tarball 解出的文件是否有效;
119
- // 只有 .h 头文件和 gyp 文件会被解出
120
- isValid = path => {
121
- const isValid = valid(path);
122
- if (isValid) log.verbose('已从 tarball 解出文件', path), extractCount++;
123
- else log.silly('忽略 tarball 中的该项', path); // 无效
136
+ // 下载 Node tarball
137
+ let extractErrors = false, extractCount = 0;
138
+ const tarPath = gyp.opts.tarball, contentShasums = {}, expectShasums = {},
139
+ // 检查从 tarball 解出的文件是否有效;
140
+ // 只有 .h 头文件和 gyp 文件会被解出
141
+ isValid = path => {
142
+ const isValid = valid(path);
143
+ if (isValid) log.verbose('已从 tarball 解出文件', path), extractCount++;
144
+ else log.silly('忽略 tarball 中的该项', path); // 无效
124
145
 
125
- return isValid;
126
- },
127
- onwarn = (code, message) => {
128
- extractErrors = true, log.error('解压 tarball 出错', code, message);
129
- },
130
- // 下载并解压 tarball!
131
- // 仅 Windows 上只需新 node.lib 时跳过
132
- // Windows 上并行安装时(多个原生模块,很常见)tar 会报文件错误,
133
- // 所以先把 tarball 解到临时目录,再复制过去
134
- tarExtractDir = win ? await fs.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-')) : devDir,
135
- downloadShasums = async () => {
136
- log.verbose('检查下载内容校验值,需要下载 `SHASUMS256.txt`...');
137
- log.verbose('校验文件地址', release.shasumsUrl);
146
+ return isValid;
147
+ },
148
+ onwarn = (code, message) => {
149
+ extractErrors = true, log.error('解压 tarball 出错', code, message);
150
+ },
151
+ // 下载并解压 tarball!
152
+ // 仅 Windows 上只需新 node.lib 时跳过
153
+ // Windows 上并行安装时(多个原生模块,很常见)tar 会报文件错误,
154
+ // 所以先把 tarball 解到临时目录,再复制过去
155
+ tarExtractDir = win ? await fs.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-')) : devDir,
156
+ downloadShasums = async () => {
157
+ log.verbose('检查下载内容校验值,需要下载 `SHASUMS256.txt`...');
158
+ log.verbose('校验文件地址', release.shasumsUrl);
138
159
 
139
- const res = await download(gyp, release.shasumsUrl);
140
- if (res.status !== 200) throw new Error(`下载校验文件时返回状态码 ${res.status}`);
160
+ const res = await download(gyp, release.shasumsUrl);
161
+ if (res.status !== 200) throw new Error(`下载校验文件时返回状态码 ${res.status}`);
141
162
 
142
- for (const line of (await res.text()).trim().split('\n')) {
143
- const items = line.trim().split(/\s+/);
144
- if (items.length !== 2) return;
163
+ for (const line of (await res.text()).trim().split('\n')) {
164
+ const items = line.trim().split(/\s+/);
165
+ if (items.length !== 2) return;
145
166
 
146
- // 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
147
- const name = items[1].replace(/^\.\//, '');
148
- expectShasums[name] = items[0];
149
- }
167
+ // 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
168
+ const name = items[1].replace(/^\.\//, '');
169
+ expectShasums[name] = items[0];
170
+ }
150
171
 
151
- log.verbose('校验数据', JSON.stringify(expectShasums));
152
- },
153
- downloadNodeLib = async () => {
154
- log.verbose('Windows 上;需要下载 `' + release.name + '.lib`...');
155
- const dir = path.resolve(tarExtractDir, arch), targetLibPath = path.resolve(dir, release.name + '.lib'),
156
- { libUrl, libPath } = release[arch], name = `${arch} ${release.name}.lib`;
172
+ log.verbose('校验数据', JSON.stringify(expectShasums));
173
+ },
174
+ downloadNodeLib = async () => {
175
+ log.verbose('Windows 上;需要下载 `' + release.name + '.lib`...');
176
+ const dir = path.resolve(tarExtractDir, arch), targetLibPath = path.resolve(dir, release.name + '.lib'),
177
+ { libUrl, libPath } = release[arch], name = `${arch} ${release.name}.lib`;
157
178
 
158
- log.verbose(name, '目录', dir), log.verbose(name, '地址', libUrl);
159
- await fs.mkdir(dir, { recursive: true }), log.verbose('正在流式写入', name, '到:', targetLibPath);
179
+ log.verbose(name, '目录', dir), log.verbose(name, '地址', libUrl);
180
+ await fs.mkdir(dir, { recursive: true }), log.verbose('正在流式写入', name, '到:', targetLibPath);
160
181
 
161
- const res = await download(gyp, libUrl);
162
- // 只下载所需的 node.lib,若未拿到则抛错
163
- if (res.status !== 200) throw new Error(`下载 ${name} 时返回状态码 ${res.status}`);
182
+ const res = await download(gyp, libUrl);
183
+ // 只下载所需的 node.lib,若未拿到则抛错
184
+ if (res.status !== 200) throw new Error(`下载 ${name} 时返回状态码 ${res.status}`);
164
185
 
165
- return pipeline(
166
- res.body,
167
- new ShaSum((_, checksum) => {
168
- contentShasums[libPath] = checksum, log.verbose('内容校验值', libPath, checksum)
169
- }), createWriteStream(targetLibPath)
170
- );
171
- };
186
+ return pipeline(
187
+ res.body,
188
+ new ShaSum((_, checksum) => {
189
+ contentShasums[libPath] = checksum, log.verbose('内容校验值', libPath, checksum)
190
+ }), createWriteStream(targetLibPath)
191
+ );
192
+ };
172
193
 
173
- try {
174
- if (shouldDownloadTarball) {
175
- if (tarPath) await tar.extract({ file: tarPath, strip: 1, filter: isValid, onwarn, cwd: tarExtractDir });
176
- else {
177
- try {
178
- const res = await download(gyp, release.tarballUrl);
194
+ try {
195
+ if (shouldDownloadTarball) {
196
+ if (tarPath) await tar.extract({ file: tarPath, strip: 1, filter: isValid, onwarn, cwd: tarExtractDir });
197
+ else {
198
+ try {
199
+ const res = await download(gyp, release.tarballUrl);
179
200
 
180
- if (res.status !== 200) throw new Error(`下载 ${release.tarballUrl} 时返回状态码 ${res.status}`);
181
- await pipeline(
182
- res.body,
183
- // 内容校验和
184
- new ShaSum((_, checksum) => {
185
- const filename = path.basename(release.tarballUrl).trim();
186
- contentShasums[filename] = checksum, log.verbose('内容校验值', filename, checksum);
187
- }),
188
- tar.extract({ strip: 1, cwd: tarExtractDir, filter: isValid, onwarn })
189
- )
190
- } catch (err) {
191
- // tarball 下载出问题?
192
- if (err.code === 'ENOTFOUND')
193
- throw new Error('这通常不是 nodejs-mobile-gyp 或包本身的问题,而是网络问题;\n' +
194
- '通常是因为你在代理后,或网络设置异常;');
195
- throw err;
196
- }
201
+ if (res.status !== 200) throw new Error(`下载 ${release.tarballUrl} 时返回状态码 ${res.status}`);
202
+ await pipeline(
203
+ res.body,
204
+ // 内容校验和
205
+ new ShaSum((_, checksum) => {
206
+ const filename = path.basename(release.tarballUrl).trim();
207
+ contentShasums[filename] = checksum, log.verbose('内容校验值', filename, checksum);
208
+ }),
209
+ tar.extract({ strip: 1, cwd: tarExtractDir, filter: isValid, onwarn })
210
+ )
211
+ } catch (err) {
212
+ // tarball 下载出问题?
213
+ if (err.code === 'ENOTFOUND')
214
+ throw new Error('这通常不是 nodejs-mobile-gyp 或包本身的问题,而是网络问题;\n' +
215
+ '通常是因为你在代理后,或网络设置异常;');
216
+ throw err;
197
217
  }
198
-
199
- // tarball 解压完成后调用
200
- if (extractErrors || extractCount === 0) throw new Error('下载/解压 tarball 时发生致命错误');
201
- log.verbose('tarball', '解析 tarball 完成');
202
218
  }
203
219
 
204
- const installVersionPath = path.resolve(tarExtractDir, 'installVersion');
205
- await Promise.all([
206
- // 需要下载 node.lib
207
- ...(win ? [downloadNodeLib()] : []),
208
- // 写入 "installVersion" 文件
209
- fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
210
- // 只有在下载了需要 SHA 校验的东西时才下载 SHASUMS.txt
211
- ...(!tarPath || win ? [downloadShasums()] : [])
212
- ])
220
+ // tarball 解压完成后调用
221
+ if (extractErrors || extractCount === 0) throw new Error('下载/解压 tarball 时发生致命错误');
222
+ log.verbose('tarball', '解析 tarball 完成');
223
+ }
213
224
 
214
- log.verbose('下载内容校验值', JSON.stringify(contentShasums));
215
- // 校验内容 shasum
216
- for (const k in contentShasums) {
217
- log.verbose('正在校验 ' + k + ' 的下载校验值', '(%s == %s)', contentShasums[k], expectShasums[k]);
218
- if (contentShasums[k] !== expectShasums[k]) {
219
- throw new Error(k + ' 本地校验值与远程不一致: ' + contentShasums[k] + ' != ' + expectShasums[k]);
220
- }
221
- }
225
+ const installVersionPath = path.resolve(tarExtractDir, 'installVersion');
226
+ await Promise.all([
227
+ // 需要下载 node.lib
228
+ ...(win ? [downloadNodeLib()] : []),
229
+ // 写入 "installVersion" 文件
230
+ fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
231
+ // 只有在下载了需要 SHA 校验的东西时才下载 SHASUMS.txt
232
+ ...(!tarPath || win ? [downloadShasums()] : [])
233
+ ])
222
234
 
223
- // 从临时解压目录复制文件到 devDir
224
- if (tarExtractDir !== devDir) {
225
- await copyDirectory(tarExtractDir, devDir);
226
- }
227
- } finally {
228
- if (tarExtractDir !== devDir) {
229
- try {
230
- await fs.rm(tarExtractDir, { recursive: true }); // 尝试清理临时目录
231
- } catch { log.warn('清理临时 tarball 解压目录失败') }
235
+ log.verbose('下载内容校验值', JSON.stringify(contentShasums));
236
+ // 校验内容 shasum
237
+ for (const k in contentShasums) {
238
+ log.verbose('正在校验 ' + k + ' 的下载校验值', '(%s == %s)', contentShasums[k], expectShasums[k]);
239
+ if (contentShasums[k] !== expectShasums[k]) {
240
+ throw new Error(k + ' 本地校验值与远程不一致: ' + contentShasums[k] + ' != ' + expectShasums[k]);
232
241
  }
233
242
  }
234
- };
235
243
 
236
- // 如果传了 --ensure,则不总是安装,只在需要时安装
237
- if (gyp.opts.ensure) {
238
- log.verbose('安装', '传入了 --ensure,已安装时不再重装');
239
- try {
240
- await fs.stat(devDir);
241
- } catch (err) {
242
- if (err.code === 'ENOENT') {
243
- log.verbose('安装', '版本尚未安装,继续安装', release.version);
244
+ // 从临时解压目录复制文件到 devDir
245
+ if (tarExtractDir !== devDir) {
246
+ await copyDirectory(tarExtractDir, devDir);
247
+ }
248
+ } finally {
249
+ if (tarExtractDir !== devDir) {
244
250
  try {
245
- return await go();
246
- } catch (err) { return rollback(err) }
251
+ await fs.rm(tarExtractDir, { recursive: true }); // 尝试清理临时目录
252
+ } catch { log.warn('清理临时 tarball 解压目录失败') }
247
253
  }
248
- else if (err.code === 'EACCES') return eaccesFallback(err);
249
- throw err;
250
254
  }
251
- log.verbose('安装', '版本已安装,需要检查 "installVersion"');
252
- const installVersionFile = path.resolve(devDir, 'installVersion');
253
- let installVersion = 0;
254
- try {
255
- const ver = await fs.readFile(installVersionFile, 'ascii');
256
- installVersion = parseInt(ver, 10) || 0;
257
- } catch (err) {
258
- if (err.code !== 'ENOENT') throw err;
259
- }
260
- log.verbose('得到 "installVersion"', installVersion);
261
- log.verbose('需要 "installVersion"', gyp.package.installVersion);
262
- if (installVersion < gyp.package.installVersion) {
263
- log.verbose('安装', '版本不可用;重新安装');
255
+ };
256
+
257
+ // 如果传了 --ensure,则不总是安装,只在需要时安装
258
+ if (gyp.opts.ensure) {
259
+ log.verbose('安装', '传入了 --ensure,已安装时不再重装');
260
+ try {
261
+ await fs.stat(devDir);
262
+ } catch (err) {
263
+ if (err.code === 'ENOENT') {
264
+ log.verbose('安装', '版本尚未安装,继续安装', release.version);
264
265
  try {
265
266
  return await go();
266
267
  } catch (err) { return rollback(err) }
267
268
  }
268
- log.verbose('安装', '版本可用');
269
- if (win) {
270
- log.verbose('Windows 上;需要检查 node.lib');
271
- const nodeLibPath = path.resolve(devDir, arch, 'node.lib');
272
- try {
273
- await fs.stat(nodeLibPath);
274
- } catch (err) {
275
- if (err.code === 'ENOENT') {
276
- log.verbose('安装', `版本尚未为 ${arch} 安装,继续安装`, release.version);
277
- try {
278
- shouldDownloadTarball = false;
279
- return await go();
280
- } catch (err) { return rollback(err) }
281
- }
282
- else if (err.code === 'EACCES') return eaccesFallback(err);
283
- throw err;
284
- }
285
- }
286
- } else {
269
+ else if (err.code === 'EACCES') return eaccesFallback(err);
270
+ throw err;
271
+ }
272
+ log.verbose('安装', '版本已安装,需要检查 "installVersion"');
273
+ const installVersionFile = path.resolve(devDir, 'installVersion');
274
+ let installVersion = 0;
275
+ try {
276
+ const ver = await fs.readFile(installVersionFile, 'ascii');
277
+ installVersion = parseInt(ver, 10) || 0;
278
+ } catch (err) {
279
+ if (err.code !== 'ENOENT') throw err;
280
+ }
281
+ log.verbose('得到 "installVersion"', installVersion);
282
+ log.verbose('需要 "installVersion"', gyp.package.installVersion);
283
+ if (installVersion < gyp.package.installVersion) {
284
+ log.verbose('安装', '版本不可用;重新安装');
287
285
  try {
288
286
  return await go();
289
287
  } catch (err) { return rollback(err) }
290
288
  }
289
+ log.verbose('安装', '版本可用');
290
+ if (win) {
291
+ log.verbose('Windows 上;需要检查 node.lib');
292
+ const nodeLibPath = path.resolve(devDir, arch, 'node.lib');
293
+ try {
294
+ await fs.stat(nodeLibPath);
295
+ } catch (err) {
296
+ if (err.code === 'ENOENT') {
297
+ log.verbose('安装', `版本尚未为 ${arch} 安装,继续安装`, release.version);
298
+ try {
299
+ shouldDownloadTarball = false;
300
+ return await go();
301
+ } catch (err) { return rollback(err) }
302
+ }
303
+ else if (err.code === 'EACCES') return eaccesFallback(err);
304
+ throw err;
305
+ }
306
+ }
307
+ } else {
308
+ try {
309
+ return await go();
310
+ } catch (err) { return rollback(err) }
291
311
  }
312
+ };
292
313
 
293
- /**
294
- * SHA256 校验流
295
- */
296
- class ShaSum extends Transform {
297
- constructor(callback) {
298
- super(), this._callback = callback, this._digester = crypto.createHash('sha256');
299
- }
300
-
301
- _transform(chunk, _, callback) {
302
- this._digester.update(chunk), callback(null, chunk)
303
- }
304
-
305
- _flush(callback) {
306
- this._callback(null, this._digester.digest('hex')), callback()
307
- }
308
- }
309
-
310
- install.usage = '安装指定 node 版本的 node 开发文件;';
311
- export { install }
314
+ export { install };
package/lib/list.js CHANGED
@@ -1,24 +1,27 @@
1
1
  import gracefulFs from 'graceful-fs';
2
2
  import * as log from './log.js';
3
3
 
4
- const fs = gracefulFs.promises,
5
- /**
6
- * 列出已安装的 Node 开发文件版本
7
- */
8
- list = async (gyp, args) => {
9
- const devDir = gyp.devDir;
10
- let versions = [];
4
+ const fs = gracefulFs.promises;
5
+ /**
6
+ * 列出当前项目的编译配置
7
+ * >查看定义:@see {@link list}
8
+ * @param {*} gyp
9
+ * @param {*} args
10
+ * @returns {*}
11
+ */
12
+ const list = async (gyp, args) => {
13
+ const devDir = gyp.devDir;
14
+ let versions = [];
11
15
 
12
- log.verbose('列表', '使用 node-gyp 目录:', devDir);
13
- try {
14
- const dir = await fs.readdir(devDir);
15
- if (Array.isArray(dir)) versions = dir.filter((v) => v !== 'current');
16
- } catch (err) {
17
- if (err && err.code !== 'ENOENT') throw err;
18
- }
19
-
20
- return versions;
16
+ log.verbose('列表', '使用 node-gyp 目录:', devDir);
17
+ try {
18
+ const dir = await fs.readdir(devDir);
19
+ if (Array.isArray(dir)) versions = dir.filter((v) => v !== 'current');
20
+ } catch (err) {
21
+ if (err && err.code !== 'ENOENT') throw err;
21
22
  }
22
23
 
23
- list.usage = '列出当前已安装的 node 开发文件';
24
- export { list }
24
+ return versions;
25
+ };
26
+
27
+ export { list };