Welcome to mirror list, hosted at ThFree Co, Russian Federation.

npm.js « lib - github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 4d22b531a4662cc0566dd3e19c6315ad1764069c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
const EventEmitter = require('events')
const { resolve, dirname } = require('path')
const Config = require('@npmcli/config')

// Patch the global fs module here at the app level
require('graceful-fs').gracefulify(require('fs'))

const { definitions, flatten, shorthands } = require('./utils/config/index.js')
const { shellouts } = require('./utils/cmd-list.js')
const usage = require('./utils/npm-usage.js')

const which = require('which')

const deref = require('./utils/deref-command.js')
const LogFile = require('./utils/log-file.js')
const Timers = require('./utils/timers.js')
const Display = require('./utils/display.js')
const log = require('./utils/log-shim')
const replaceInfo = require('./utils/replace-info.js')

let warnedNonDashArg = false
const _load = Symbol('_load')
const _tmpFolder = Symbol('_tmpFolder')
const _title = Symbol('_title')
const pkg = require('../package.json')

class Npm extends EventEmitter {
  static get version () {
    return pkg.version
  }

  #unloaded = false
  #timers = null
  #logFile = null
  #display = null

  constructor () {
    super()
    this.command = null
    this.#logFile = new LogFile()
    this.#display = new Display()
    this.#timers = new Timers({
      start: 'npm',
      listener: (name, ms) => {
        const args = ['timing', name, `Completed in ${ms}ms`]
        this.#logFile.log(...args)
        this.#display.log(...args)
      },
    })
    this.config = new Config({
      npmPath: dirname(__dirname),
      definitions,
      flatten,
      shorthands,
      log,
    })
    this[_title] = process.title
    this.updateNotification = null
  }

  get version () {
    return this.constructor.version
  }

  get shelloutCommands () {
    return shellouts
  }

  deref (c) {
    return deref(c)
  }

  // Get an instantiated npm command
  // npm.command is already taken as the currently running command, a refactor
  // would be needed to change this
  async cmd (cmd) {
    await this.load()
    const command = this.deref(cmd)
    if (!command) {
      throw Object.assign(new Error(`Unknown command ${cmd}`), {
        code: 'EUNKNOWNCOMMAND',
      })
    }
    const Impl = require(`./commands/${command}.js`)
    const impl = new Impl(this)
    return impl
  }

  // Call an npm command
  async exec (cmd, args) {
    const command = await this.cmd(cmd)
    process.emit('time', `command:${cmd}`)

    // since 'test', 'start', 'stop', etc. commands re-enter this function
    // to call the run-script command, we need to only set it one time.
    if (!this.command) {
      process.env.npm_command = command.name
      this.command = command.name
    }

    // Options are prefixed by a hyphen-minus (-, \u2d).
    // Other dash-type chars look similar but are invalid.
    if (!warnedNonDashArg) {
      args
        .filter(arg => /^[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]/.test(arg))
        .forEach(arg => {
          warnedNonDashArg = true
          log.error(
            'arg',
            'Argument starts with non-ascii dash, this is probably invalid:',
            arg
          )
        })
    }

    const workspacesEnabled = this.config.get('workspaces')
    const workspacesFilters = this.config.get('workspace')
    if (workspacesEnabled === false && workspacesFilters.length > 0) {
      throw new Error('Can not use --no-workspaces and --workspace at the same time')
    }

    const filterByWorkspaces = workspacesEnabled || workspacesFilters.length > 0
    // normally this would go in the constructor, but our tests don't
    // actually use a real npm object so this.npm.config isn't always
    // populated.  this is the compromise until we can make that a reality
    // and then move this into the constructor.
    command.workspaces = this.config.get('workspaces')
    command.workspacePaths = null
    // normally this would be evaluated in base-command#setWorkspaces, see
    // above for explanation
    command.includeWorkspaceRoot = this.config.get('include-workspace-root')

    if (this.config.get('usage')) {
      this.output(command.usage)
      return
    }
    if (filterByWorkspaces) {
      if (this.config.get('global')) {
        throw new Error('Workspaces not supported for global packages')
      }

      return command.execWorkspaces(args, this.config.get('workspace')).finally(() => {
        process.emit('timeEnd', `command:${cmd}`)
      })
    } else {
      return command.exec(args).finally(() => {
        process.emit('timeEnd', `command:${cmd}`)
      })
    }
  }

  async load () {
    if (!this.loadPromise) {
      process.emit('time', 'npm:load')
      this.loadPromise = new Promise((resolve, reject) => {
        this[_load]()
          .catch(er => er)
          .then(er => {
            this.loadErr = er
            if (!er && this.config.get('force')) {
              log.warn('using --force', 'Recommended protections disabled.')
            }

            process.emit('timeEnd', 'npm:load')
            if (er) {
              return reject(er)
            }
            resolve()
          })
      })
    }
    return this.loadPromise
  }

  get loaded () {
    return this.config.loaded
  }

  // This gets called at the end of the exit handler and
  // during any tests to cleanup all of our listeners
  // Everything in here should be synchronous
  unload () {
    // Track if we've already unloaded so we dont
    // write multiple timing files. This is only an
    // issue in tests right now since we unload
    // in both tap teardowns and the exit handler
    if (this.#unloaded) {
      return
    }
    this.#timers.off()
    this.#display.off()
    this.#logFile.off()
    if (this.loaded && this.config.get('timing')) {
      this.#timers.writeFile({
        command: process.argv.slice(2),
        // We used to only ever report a single log file
        // so to be backwards compatible report the last logfile
        // XXX: remove this in npm 9 or just keep it forever
        logfile: this.logFiles[this.logFiles.length - 1],
        logfiles: this.logFiles,
        version: this.version,
      })
    }
    this.#unloaded = true
  }

  get title () {
    return this[_title]
  }

  set title (t) {
    process.title = t
    this[_title] = t
  }

  async [_load] () {
    process.emit('time', 'npm:load:whichnode')
    let node
    try {
      node = which.sync(process.argv[0])
    } catch {
      // TODO should we throw here?
    }
    process.emit('timeEnd', 'npm:load:whichnode')
    if (node && node.toUpperCase() !== process.execPath.toUpperCase()) {
      log.verbose('node symlink', node)
      process.execPath = node
      this.config.execPath = node
    }

    process.emit('time', 'npm:load:configload')
    await this.config.load()
    process.emit('timeEnd', 'npm:load:configload')

    this.argv = this.config.parsedArgv.remain
    // note: this MUST be shorter than the actual argv length, because it
    // uses the same memory, so node will truncate it if it's too long.
    // if it's a token revocation, then the argv contains a secret, so
    // don't show that.  (Regrettable historical choice to put it there.)
    // Any other secrets are configs only, so showing only the positional
    // args keeps those from being leaked.
    process.emit('time', 'npm:load:setTitle')
    const tokrev = deref(this.argv[0]) === 'token' && this.argv[1] === 'revoke'
    this.title = tokrev
      ? 'npm token revoke' + (this.argv[2] ? ' ***' : '')
      : replaceInfo(['npm', ...this.argv].join(' '))
    process.emit('timeEnd', 'npm:load:setTitle')

    process.emit('time', 'npm:load:display')
    this.#display.load({
      // Use logColor since that is based on stderr
      color: this.logColor,
      progress: this.flatOptions.progress,
      timing: this.config.get('timing'),
      loglevel: this.config.get('loglevel'),
      unicode: this.config.get('unicode'),
      heading: this.config.get('heading'),
    })
    process.emit('timeEnd', 'npm:load:display')
    process.env.COLOR = this.color ? '1' : '0'

    process.emit('time', 'npm:load:logFile')
    this.#logFile.load({
      dir: resolve(this.cache, '_logs'),
      logsMax: this.config.get('logs-max'),
    })
    log.verbose('logfile', this.#logFile.files[0])
    process.emit('timeEnd', 'npm:load:logFile')

    process.emit('time', 'npm:load:timers')
    this.#timers.load({
      dir: this.cache,
    })
    process.emit('timeEnd', 'npm:load:timers')

    process.emit('time', 'npm:load:configScope')
    const configScope = this.config.get('scope')
    if (configScope && !/^@/.test(configScope)) {
      this.config.set('scope', `@${configScope}`, this.config.find('scope'))
    }
    process.emit('timeEnd', 'npm:load:configScope')
  }

  get flatOptions () {
    const { flat } = this.config
    if (this.command) {
      flat.npmCommand = this.command
    }
    return flat
  }

  // color and logColor are a special derived values that takes into
  // consideration not only the config, but whether or not we are operating
  // in a tty with the associated output (stdout/stderr)
  get color () {
    return this.flatOptions.color
  }

  get logColor () {
    return this.flatOptions.logColor
  }

  get lockfileVersion () {
    return 2
  }

  get unfinishedTimers () {
    return this.#timers.unfinished
  }

  get finishedTimers () {
    return this.#timers.finished
  }

  get started () {
    return this.#timers.started
  }

  get logFiles () {
    return this.#logFile.files
  }

  get cache () {
    return this.config.get('cache')
  }

  set cache (r) {
    this.config.set('cache', r)
  }

  get globalPrefix () {
    return this.config.globalPrefix
  }

  set globalPrefix (r) {
    this.config.globalPrefix = r
  }

  get localPrefix () {
    return this.config.localPrefix
  }

  set localPrefix (r) {
    this.config.localPrefix = r
  }

  get globalDir () {
    return process.platform !== 'win32'
      ? resolve(this.globalPrefix, 'lib', 'node_modules')
      : resolve(this.globalPrefix, 'node_modules')
  }

  get localDir () {
    return resolve(this.localPrefix, 'node_modules')
  }

  get dir () {
    return this.config.get('global') ? this.globalDir : this.localDir
  }

  get globalBin () {
    const b = this.globalPrefix
    return process.platform !== 'win32' ? resolve(b, 'bin') : b
  }

  get localBin () {
    return resolve(this.dir, '.bin')
  }

  get bin () {
    return this.config.get('global') ? this.globalBin : this.localBin
  }

  get prefix () {
    return this.config.get('global') ? this.globalPrefix : this.localPrefix
  }

  set prefix (r) {
    const k = this.config.get('global') ? 'globalPrefix' : 'localPrefix'
    this[k] = r
  }

  get usage () {
    return usage(this)
  }

  // XXX add logging to see if we actually use this
  get tmp () {
    if (!this[_tmpFolder]) {
      const rand = require('crypto').randomBytes(4).toString('hex')
      this[_tmpFolder] = `npm-${process.pid}-${rand}`
    }
    return resolve(this.config.get('tmp'), this[_tmpFolder])
  }

  // output to stdout in a progress bar compatible way
  output (...msg) {
    log.clearProgress()
    // eslint-disable-next-line no-console
    console.log(...msg)
    log.showProgress()
  }
}
module.exports = Npm