@flun/nodejs-mobile-gyp 1.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +29 -0
  3. package/README.md +252 -0
  4. package/addon.gypi +209 -0
  5. package/bin/node-gyp.js +144 -0
  6. package/gyp/LICENSE +28 -0
  7. package/gyp/README.md +30 -0
  8. package/gyp/data/win/large-pdb-shim.cc +12 -0
  9. package/gyp/gyp +8 -0
  10. package/gyp/gyp.bat +5 -0
  11. package/gyp/gyp_main.py +45 -0
  12. package/gyp/pylib/gyp/MSVSNew.py +365 -0
  13. package/gyp/pylib/gyp/MSVSProject.py +206 -0
  14. package/gyp/pylib/gyp/MSVSSettings.py +1270 -0
  15. package/gyp/pylib/gyp/MSVSToolFile.py +59 -0
  16. package/gyp/pylib/gyp/MSVSUserFile.py +153 -0
  17. package/gyp/pylib/gyp/MSVSUtil.py +271 -0
  18. package/gyp/pylib/gyp/MSVSVersion.py +574 -0
  19. package/gyp/pylib/gyp/__init__.py +692 -0
  20. package/gyp/pylib/gyp/common.py +654 -0
  21. package/gyp/pylib/gyp/easy_xml.py +169 -0
  22. package/gyp/pylib/gyp/flock_tool.py +55 -0
  23. package/gyp/pylib/gyp/generator/__init__.py +0 -0
  24. package/gyp/pylib/gyp/generator/analyzer.py +804 -0
  25. package/gyp/pylib/gyp/generator/android.py +1173 -0
  26. package/gyp/pylib/gyp/generator/cmake.py +1318 -0
  27. package/gyp/pylib/gyp/generator/compile_commands_json.py +123 -0
  28. package/gyp/pylib/gyp/generator/dump_dependency_json.py +103 -0
  29. package/gyp/pylib/gyp/generator/eclipse.py +461 -0
  30. package/gyp/pylib/gyp/generator/gypd.py +89 -0
  31. package/gyp/pylib/gyp/generator/gypsh.py +58 -0
  32. package/gyp/pylib/gyp/generator/make.py +2710 -0
  33. package/gyp/pylib/gyp/generator/msvs.py +3981 -0
  34. package/gyp/pylib/gyp/generator/ninja.py +2933 -0
  35. package/gyp/pylib/gyp/generator/xcode.py +1397 -0
  36. package/gyp/pylib/gyp/input.py +3115 -0
  37. package/gyp/pylib/gyp/mac_tool.py +771 -0
  38. package/gyp/pylib/gyp/msvs_emulation.py +1262 -0
  39. package/gyp/pylib/gyp/ninja_syntax.py +174 -0
  40. package/gyp/pylib/gyp/simple_copy.py +61 -0
  41. package/gyp/pylib/gyp/win_tool.py +373 -0
  42. package/gyp/pylib/gyp/xcode_emulation.py +1935 -0
  43. package/gyp/pylib/gyp/xcode_ninja.py +302 -0
  44. package/gyp/pylib/gyp/xcodeproj_file.py +3198 -0
  45. package/gyp/pylib/gyp/xml_fix.py +65 -0
  46. package/gyp/pylib/packaging/LICENSE +3 -0
  47. package/gyp/pylib/packaging/LICENSE.APACHE +177 -0
  48. package/gyp/pylib/packaging/LICENSE.BSD +23 -0
  49. package/gyp/pylib/packaging/__init__.py +15 -0
  50. package/gyp/pylib/packaging/_elffile.py +108 -0
  51. package/gyp/pylib/packaging/_manylinux.py +252 -0
  52. package/gyp/pylib/packaging/_musllinux.py +83 -0
  53. package/gyp/pylib/packaging/_parser.py +359 -0
  54. package/gyp/pylib/packaging/_structures.py +61 -0
  55. package/gyp/pylib/packaging/_tokenizer.py +192 -0
  56. package/gyp/pylib/packaging/markers.py +252 -0
  57. package/gyp/pylib/packaging/metadata.py +825 -0
  58. package/gyp/pylib/packaging/py.typed +0 -0
  59. package/gyp/pylib/packaging/requirements.py +90 -0
  60. package/gyp/pylib/packaging/specifiers.py +1030 -0
  61. package/gyp/pylib/packaging/tags.py +553 -0
  62. package/gyp/pylib/packaging/utils.py +172 -0
  63. package/gyp/pylib/packaging/version.py +563 -0
  64. package/lib/Find-VisualStudio.cs +250 -0
  65. package/lib/build.js +221 -0
  66. package/lib/clean.js +19 -0
  67. package/lib/configure.js +295 -0
  68. package/lib/create-config-gypi.js +163 -0
  69. package/lib/download.js +44 -0
  70. package/lib/find-node-directory.js +62 -0
  71. package/lib/find-python.js +303 -0
  72. package/lib/find-visualstudio.js +448 -0
  73. package/lib/install.js +419 -0
  74. package/lib/list.js +30 -0
  75. package/lib/log.js +174 -0
  76. package/lib/node-gyp.js +201 -0
  77. package/lib/process-release.js +144 -0
  78. package/lib/rebuild.js +14 -0
  79. package/lib/remove.js +47 -0
  80. package/lib/util.js +83 -0
  81. package/package.json +72 -0
  82. package/src/win_delay_load_hook.cc +39 -0
package/lib/install.js ADDED
@@ -0,0 +1,419 @@
1
+ import gracefulFs from 'graceful-fs'
2
+ import os from 'os'
3
+ import { backOff } from 'exponential-backoff'
4
+ import * as tar from 'tar'
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
+ import semver from 'semver'
10
+ import { download } from './download.js'
11
+ import { processRelease } from './process-release.js'
12
+
13
+ const { createWriteStream, promises: fs } = gracefulFs
14
+ const { pipeline } = streamPromises
15
+ const win = process.platform === 'win32'
16
+
17
+ /**
18
+ * 为指定 Node 版本安装开发文件
19
+ */
20
+ const install = async (gyp, argv) => {
21
+ log.stdout()
22
+ const release = processRelease(argv, gyp, process.version, process.release)
23
+ // 按 create-config-gypi.js 的逻辑推断 target_arch,仅 Windows 用
24
+ const arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : ''
25
+ // 仅 Windows 下只需新 node.lib 时,避免下载 tarball
26
+ let shouldDownloadTarball = true
27
+
28
+ // 确定要安装哪个版本的 Node 开发文件
29
+ log.verbose('install', 'input version string %j', release.version)
30
+
31
+ if (!release.semver) {
32
+ // 无法用 semver 解析版本字符串
33
+ throw new Error('Invalid version number: ' + release.version)
34
+ }
35
+
36
+ if (semver.lt(release.version, '0.8.0')) {
37
+ throw new Error('Minimum target version is `0.8.0` or greater. Got: ' + release.version)
38
+ }
39
+
40
+ // 0.x.y-pre 版本尚未发布,无法安装,直接退出
41
+ if (release.semver.prerelease[0] === 'pre') {
42
+ log.verbose('detected "pre" node version', release.version)
43
+ if (!gyp.opts.nodedir) {
44
+ throw new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead')
45
+ }
46
+ log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir)
47
+ return
48
+ }
49
+
50
+ // 版本拍平为字符串
51
+ log.verbose('install', 'installing version: %s', release.versionDir)
52
+
53
+ // 开发文件安装目录
54
+ const devDir = path.resolve(gyp.devDir, release.versionDir)
55
+
56
+ // 如果传了 --ensure,则不总是安装,只在需要时安装
57
+ if (gyp.opts.ensure) {
58
+ log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed')
59
+ try {
60
+ await fs.stat(devDir)
61
+ } catch (err) {
62
+ if (err.code === 'ENOENT') {
63
+ log.verbose('install', 'version not already installed, continuing with install', release.version)
64
+ try {
65
+ return await go()
66
+ } catch (err) {
67
+ return rollback(err)
68
+ }
69
+ } else if (err.code === 'EACCES') {
70
+ return eaccesFallback(err)
71
+ }
72
+ throw err
73
+ }
74
+ log.verbose('install', 'version is already installed, need to check "installVersion"')
75
+ const installVersionFile = path.resolve(devDir, 'installVersion')
76
+ let installVersion = 0
77
+ try {
78
+ const ver = await fs.readFile(installVersionFile, 'ascii')
79
+ installVersion = parseInt(ver, 10) || 0
80
+ } catch (err) {
81
+ if (err.code !== 'ENOENT') {
82
+ throw err
83
+ }
84
+ }
85
+ log.verbose('got "installVersion"', installVersion)
86
+ log.verbose('needs "installVersion"', gyp.package.installVersion)
87
+ if (installVersion < gyp.package.installVersion) {
88
+ log.verbose('install', 'version is no good; reinstalling')
89
+ try {
90
+ return await go()
91
+ } catch (err) {
92
+ return rollback(err)
93
+ }
94
+ }
95
+ log.verbose('install', 'version is good')
96
+ if (win) {
97
+ log.verbose('on Windows; need to check node.lib')
98
+ const nodeLibPath = path.resolve(devDir, arch, 'node.lib')
99
+ try {
100
+ await fs.stat(nodeLibPath)
101
+ } catch (err) {
102
+ if (err.code === 'ENOENT') {
103
+ log.verbose('install', `version not already installed for ${arch}, continuing with install`, release.version)
104
+ try {
105
+ shouldDownloadTarball = false
106
+ return await go()
107
+ } catch (err) {
108
+ return rollback(err)
109
+ }
110
+ } else if (err.code === 'EACCES') {
111
+ return eaccesFallback(err)
112
+ }
113
+ throw err
114
+ }
115
+ }
116
+ } else {
117
+ try {
118
+ return await go()
119
+ } catch (err) {
120
+ return rollback(err)
121
+ }
122
+ }
123
+
124
+ async function copyDirectory (src, dest) {
125
+ try {
126
+ await fs.stat(src)
127
+ } catch {
128
+ throw new Error(`Missing source directory for copy: ${src}`)
129
+ }
130
+ await fs.mkdir(dest, { recursive: true })
131
+ const entries = await fs.readdir(src, { withFileTypes: true })
132
+ for (const entry of entries) {
133
+ if (entry.isDirectory()) {
134
+ await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name))
135
+ } else if (entry.isFile()) {
136
+ // 并行安装时,Windows 上复制文件可能报文件错误,
137
+ // 因此用指数退避解决冲突
138
+ await backOff(async () => {
139
+ try {
140
+ await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name))
141
+ } catch (err) {
142
+ // 若设置了 ensure,检查文件是否已存在,存在就足够了
143
+ if (gyp.opts.ensure && err.code === 'EBUSY') {
144
+ try {
145
+ await fs.stat(path.join(dest, entry.name))
146
+ return
147
+ } catch {}
148
+ }
149
+ throw err
150
+ }
151
+ })
152
+ } else {
153
+ throw new Error('Unexpected file directory entry type')
154
+ }
155
+ }
156
+ }
157
+
158
+ async function go () {
159
+ log.verbose('ensuring devDir is created', devDir)
160
+
161
+ // 先创建 Node 开发文件所在目录
162
+ try {
163
+ const created = await fs.mkdir(devDir, { recursive: true })
164
+
165
+ if (created) {
166
+ log.verbose('created devDir', created)
167
+ }
168
+ } catch (err) {
169
+ if (err.code === 'EACCES') {
170
+ return eaccesFallback(err)
171
+ }
172
+
173
+ throw err
174
+ }
175
+
176
+ // 下载 Node tarball
177
+ const tarPath = gyp.opts.tarball
178
+ let extractErrors = false
179
+ let extractCount = 0
180
+ const contentShasums = {}
181
+ const expectShasums = {}
182
+
183
+ // 检查从 tarball 解出的文件是否有效。
184
+ // 只有 .h 头文件和 gyp 文件会被解出
185
+ function isValid (path) {
186
+ const isValid = valid(path)
187
+ if (isValid) {
188
+ log.verbose('extracted file from tarball', path)
189
+ extractCount++
190
+ } else {
191
+ // 无效
192
+ log.silly('ignoring from tarball', path)
193
+ }
194
+ return isValid
195
+ }
196
+
197
+ function onwarn (code, message) {
198
+ extractErrors = true
199
+ log.error('error while extracting tarball', code, message)
200
+ }
201
+
202
+ // 下载并解压 tarball!
203
+ // 仅 Windows 上只需新 node.lib 时跳过
204
+
205
+ // Windows 上并行安装时(多个原生模块,很常见)tar 会报文件错误,
206
+ // 所以先把 tarball 解到临时目录,再复制过去
207
+ const tarExtractDir = win ? await fs.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-')) : devDir
208
+
209
+ try {
210
+ if (shouldDownloadTarball) {
211
+ if (tarPath) {
212
+ await tar.extract({
213
+ file: tarPath,
214
+ strip: 1,
215
+ filter: isValid,
216
+ onwarn,
217
+ cwd: tarExtractDir
218
+ })
219
+ } else {
220
+ try {
221
+ const res = await download(gyp, release.tarballUrl)
222
+
223
+ if (res.status !== 200) {
224
+ throw new Error(`${res.status} response downloading ${release.tarballUrl}`)
225
+ }
226
+
227
+ await pipeline(
228
+ res.body,
229
+ // 内容校验和
230
+ new ShaSum((_, checksum) => {
231
+ const filename = path.basename(release.tarballUrl).trim()
232
+ contentShasums[filename] = checksum
233
+ log.verbose('content checksum', filename, checksum)
234
+ }),
235
+ tar.extract({
236
+ strip: 1,
237
+ cwd: tarExtractDir,
238
+ filter: isValid,
239
+ onwarn
240
+ })
241
+ )
242
+ } catch (err) {
243
+ // tarball 下载出问题?
244
+ if (err.code === 'ENOTFOUND') {
245
+ throw new Error('This is most likely not a problem with node-gyp or the package itself and\n' +
246
+ 'is related to network connectivity. In most cases you are behind a proxy or have bad \n' +
247
+ 'network settings.')
248
+ }
249
+ throw err
250
+ }
251
+ }
252
+
253
+ // tarball 解压完成后调用
254
+ if (extractErrors || extractCount === 0) {
255
+ throw new Error('There was a fatal problem while downloading/extracting the tarball')
256
+ }
257
+
258
+ log.verbose('tarball', 'done parsing tarball')
259
+ }
260
+
261
+ const installVersionPath = path.resolve(tarExtractDir, 'installVersion')
262
+ await Promise.all([
263
+ // 需要下载 node.lib
264
+ ...(win ? [downloadNodeLib()] : []),
265
+ // 写入 "installVersion" 文件
266
+ fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
267
+ // 只有在下载了需要 SHA 校验的东西时才下载 SHASUMS.txt
268
+ ...(!tarPath || win ? [downloadShasums()] : [])
269
+ ])
270
+
271
+ log.verbose('download contents checksum', JSON.stringify(contentShasums))
272
+ // 校验内容 shasum
273
+ for (const k in contentShasums) {
274
+ log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k])
275
+ if (contentShasums[k] !== expectShasums[k]) {
276
+ throw new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k])
277
+ }
278
+ }
279
+
280
+ // 从临时解压目录复制文件到 devDir
281
+ if (tarExtractDir !== devDir) {
282
+ await copyDirectory(tarExtractDir, devDir)
283
+ }
284
+ } finally {
285
+ if (tarExtractDir !== devDir) {
286
+ try {
287
+ // 尝试清理临时目录
288
+ await fs.rm(tarExtractDir, { recursive: true })
289
+ } catch {
290
+ log.warn('failed to clean up temp tarball extract directory')
291
+ }
292
+ }
293
+ }
294
+
295
+ async function downloadShasums () {
296
+ log.verbose('check download content checksum, need to download `SHASUMS256.txt`...')
297
+ log.verbose('checksum url', release.shasumsUrl)
298
+
299
+ const res = await download(gyp, release.shasumsUrl)
300
+
301
+ if (res.status !== 200) {
302
+ throw new Error(`${res.status} status code downloading checksum`)
303
+ }
304
+
305
+ for (const line of (await res.text()).trim().split('\n')) {
306
+ const items = line.trim().split(/\s+/)
307
+ if (items.length !== 2) {
308
+ return
309
+ }
310
+
311
+ // 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
312
+ const name = items[1].replace(/^\.\//, '')
313
+ expectShasums[name] = items[0]
314
+ }
315
+
316
+ log.verbose('checksum data', JSON.stringify(expectShasums))
317
+ }
318
+
319
+ async function downloadNodeLib () {
320
+ log.verbose('on Windows; need to download `' + release.name + '.lib`...')
321
+ const dir = path.resolve(tarExtractDir, arch)
322
+ const targetLibPath = path.resolve(dir, release.name + '.lib')
323
+ const { libUrl, libPath } = release[arch]
324
+ const name = `${arch} ${release.name}.lib`
325
+ log.verbose(name, 'dir', dir)
326
+ log.verbose(name, 'url', libUrl)
327
+
328
+ await fs.mkdir(dir, { recursive: true })
329
+ log.verbose('streaming', name, 'to:', targetLibPath)
330
+
331
+ const res = await download(gyp, libUrl)
332
+
333
+ // 只下载所需的 node.lib,若未拿到则抛错
334
+ if (res.status !== 200) {
335
+ throw new Error(`${res.status} status code downloading ${name}`)
336
+ }
337
+
338
+ return pipeline(
339
+ res.body,
340
+ new ShaSum((_, checksum) => {
341
+ contentShasums[libPath] = checksum
342
+ log.verbose('content checksum', libPath, checksum)
343
+ }),
344
+ createWriteStream(targetLibPath)
345
+ )
346
+ } // downloadNodeLib()
347
+ } // go()
348
+
349
+ /**
350
+ * 检查给定文件名是否对本次安装"有效"
351
+ */
352
+
353
+ function valid (file) {
354
+ // 头文件
355
+ const extname = path.extname(file)
356
+ return extname === '.h' || extname === '.gypi'
357
+ }
358
+
359
+ async function rollback (err) {
360
+ log.warn('install', 'got an error, rolling back install')
361
+ // 出错则回滚安装
362
+ await gyp.commands.remove([release.versionDir])
363
+ throw err
364
+ }
365
+
366
+ /**
367
+ * EACCES 回退方案,用于处理 npm 的 `sudo` 行为:
368
+ * npm 在调用任何子进程(如 node-gyp)前会先降权,
369
+ * 于是 "nobody" 用户没有权限创建 dev dir。
370
+ * 作为回退,把 tmpdir() 作为本次安装的 dev dir。
371
+ * 这不理想,但至少编译能成功……
372
+ */
373
+
374
+ async function eaccesFallback (err) {
375
+ const noretry = '--node_gyp_internal_noretry'
376
+ if (argv.indexOf(noretry) !== -1) {
377
+ throw err
378
+ }
379
+ const tmpdir = os.tmpdir()
380
+ gyp.devDir = path.resolve(tmpdir, '.node-gyp')
381
+ let userString = ''
382
+ try {
383
+ // os.userInfo 在某些系统上会失败,这里不关键
384
+ userString = ` ("${os.userInfo().username}")`
385
+ } catch (e) {}
386
+ log.warn('EACCES', 'current user%s does not have permission to access the dev dir "%s"', userString, devDir)
387
+ log.warn('EACCES', 'attempting to reinstall using temporary dev dir "%s"', gyp.devDir)
388
+ if (process.cwd() === tmpdir) {
389
+ log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space')
390
+ gyp.todo.push({ name: 'remove', args: argv })
391
+ }
392
+ return gyp.commands.install([noretry].concat(argv))
393
+ }
394
+ }
395
+
396
+ /**
397
+ * SHA256 校验流
398
+ */
399
+ class ShaSum extends Transform {
400
+ constructor (callback) {
401
+ super()
402
+ this._callback = callback
403
+ this._digester = crypto.createHash('sha256')
404
+ }
405
+
406
+ _transform (chunk, _, callback) {
407
+ this._digester.update(chunk)
408
+ callback(null, chunk)
409
+ }
410
+
411
+ _flush (callback) {
412
+ this._callback(null, this._digester.digest('hex'))
413
+ callback()
414
+ }
415
+ }
416
+
417
+ install.usage = 'Install node development files for the specified node version.'
418
+
419
+ export { install }
package/lib/list.js ADDED
@@ -0,0 +1,30 @@
1
+ import gracefulFs from 'graceful-fs'
2
+ import * as log from './log.js'
3
+
4
+ const fs = gracefulFs.promises
5
+
6
+ /**
7
+ * 列出已安装的 Node 开发文件版本
8
+ */
9
+ const list = async (gyp, args) => {
10
+ const devDir = gyp.devDir
11
+ log.verbose('list', 'using node-gyp dir:', devDir)
12
+
13
+ let versions = []
14
+ try {
15
+ const dir = await fs.readdir(devDir)
16
+ if (Array.isArray(dir)) {
17
+ versions = dir.filter((v) => v !== 'current')
18
+ }
19
+ } catch (err) {
20
+ if (err && err.code !== 'ENOENT') {
21
+ throw err
22
+ }
23
+ }
24
+
25
+ return versions
26
+ }
27
+
28
+ list.usage = 'Prints a listing of the currently installed node development files'
29
+
30
+ export { list }
package/lib/log.js ADDED
@@ -0,0 +1,174 @@
1
+ import procLog from 'proc-log'
2
+ import { format } from 'util'
3
+
4
+ // 按级别生成带前缀的日志函数
5
+ const logLevels = Object.keys(procLog).filter((k) => typeof procLog[k] === 'function')
6
+ const withPrefix = (prefix) => logLevels.reduce((acc, level) => {
7
+ acc[level] = (...args) => procLog[level](prefix, ...args)
8
+ return acc
9
+ }, {})
10
+
11
+ // 极简 ANSI 颜色生成器
12
+ const COLORS = {
13
+ wrap: (str, colors) => {
14
+ const codes = colors.filter(c => typeof c === 'number')
15
+ return `\x1b[${codes.join(';')}m${str}\x1b[0m`
16
+ },
17
+ inverse: 7,
18
+ fg: {
19
+ black: 30,
20
+ red: 31,
21
+ green: 32,
22
+ yellow: 33,
23
+ blue: 34,
24
+ magenta: 35,
25
+ cyan: 36,
26
+ white: 37
27
+ },
28
+ bg: {
29
+ black: 40,
30
+ red: 41,
31
+ green: 42,
32
+ yellow: 43,
33
+ blue: 44,
34
+ magenta: 45,
35
+ cyan: 46,
36
+ white: 47
37
+ }
38
+ }
39
+
40
+ // 日志器:负责缓冲、暂停/恢复、着色输出
41
+ class Logger {
42
+ #buffer = []
43
+ #paused = null
44
+ #level = null
45
+ #stream = null
46
+
47
+ // 从吵闹到安静排序
48
+ #levels = [{
49
+ id: 'silly',
50
+ display: 'sill',
51
+ style: { inverse: true }
52
+ }, {
53
+ id: 'verbose',
54
+ display: 'verb',
55
+ style: { fg: 'cyan', bg: 'black' }
56
+ }, {
57
+ id: 'info',
58
+ style: { fg: 'green' }
59
+ }, {
60
+ id: 'http',
61
+ style: { fg: 'green', bg: 'black' }
62
+ }, {
63
+ id: 'notice',
64
+ style: { fg: 'cyan', bg: 'black' }
65
+ }, {
66
+ id: 'warn',
67
+ display: 'WARN',
68
+ style: { fg: 'black', bg: 'yellow' }
69
+ }, {
70
+ id: 'error',
71
+ display: 'ERR!',
72
+ style: { fg: 'red', bg: 'black' }
73
+ }]
74
+
75
+ constructor (stream) {
76
+ process.on('log', (...args) => this.#onLog(...args))
77
+ this.#levels = new Map(this.#levels.map((level, index) => [level.id, { ...level, index }]))
78
+ this.level = 'info'
79
+ this.stream = stream
80
+ procLog.pause()
81
+ }
82
+
83
+ get stream () {
84
+ return this.#stream
85
+ }
86
+
87
+ set stream (stream) {
88
+ this.#stream = stream
89
+ }
90
+
91
+ get level () {
92
+ return this.#levels.get(this.#level) ?? null
93
+ }
94
+
95
+ set level (level) {
96
+ this.#level = this.#levels.get(level)?.id ?? null
97
+ }
98
+
99
+ isVisible (level) {
100
+ return this.level?.index <= this.#levels.get(level)?.index ?? -1
101
+ }
102
+
103
+ #onLog (...args) {
104
+ const [level] = args
105
+
106
+ if (level === 'pause') {
107
+ this.#paused = true
108
+ return
109
+ }
110
+
111
+ if (level === 'resume') {
112
+ this.#paused = false
113
+ this.#buffer.forEach((b) => this.#log(...b))
114
+ this.#buffer.length = 0
115
+ return
116
+ }
117
+
118
+ if (this.#paused) {
119
+ this.#buffer.push(args)
120
+ return
121
+ }
122
+
123
+ this.#log(...args)
124
+ }
125
+
126
+ #color (str, { fg, bg, inverse }) {
127
+ if (!this.#stream?.isTTY) {
128
+ return str
129
+ }
130
+
131
+ return COLORS.wrap(str, [
132
+ COLORS.fg[fg],
133
+ COLORS.bg[bg],
134
+ inverse && COLORS.inverse
135
+ ])
136
+ }
137
+
138
+ #log (levelId, msgPrefix, ...args) {
139
+ if (!this.isVisible(levelId) || typeof this.#stream?.write !== 'function') {
140
+ return
141
+ }
142
+
143
+ const level = this.#levels.get(levelId)
144
+
145
+ const prefixParts = [
146
+ this.#color('gyp', { fg: 'white', bg: 'black' }),
147
+ this.#color(level.display ?? level.id, level.style)
148
+ ]
149
+ if (msgPrefix) {
150
+ prefixParts.push(this.#color(msgPrefix, { fg: 'magenta' }))
151
+ }
152
+
153
+ const prefix = prefixParts.join(' ').trim() + ' '
154
+ const lines = format(...args).split(/\r?\n/).map(l => prefix + l.trim())
155
+
156
+ this.#stream.write(lines.join('\n') + '\n')
157
+ }
158
+ }
159
+
160
+ // 用于测试时抑制日志
161
+ const NULL_LOGGER = !!process.env.NODE_GYP_NULL_LOGGER
162
+
163
+ const logger = new Logger(NULL_LOGGER ? null : process.stderr)
164
+ const stdout = NULL_LOGGER ? () => {} : (...args) => console.log(...args)
165
+
166
+ // 解构 proc-log 的函数,供末尾统一导出
167
+ const { notice, error, warn, info, verbose, http, silly, pause, resume } = procLog
168
+
169
+ export {
170
+ logger,
171
+ stdout,
172
+ withPrefix,
173
+ notice, error, warn, info, verbose, http, silly, pause, resume
174
+ }