Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
install.js173 linesDownload Raw Back to commands
1const { readdir } = require('node:fs/promises')
2const { resolve, join } = require('node:path')
3const { log } = require('proc-log')
4const runScript = require('@npmcli/run-script')
5const pacote = require('pacote')
6const checks = require('npm-install-checks')
7const reifyFinish = require('../utils/reify-finish.js')
8const ArboristWorkspaceCmd = require('../arborist-cmd.js')
9
10class Install extends ArboristWorkspaceCmd {
11  static description = 'Install a package'
12  static name = 'install'
13
14  // These are in the order they will show up in when running "-h" If adding to this list, consider adding also to ci.js
15  static params = [
16    'save',
17    'save-exact',
18    'global',
19    'install-strategy',
20    'legacy-bundling',
21    'global-style',
22    'omit',
23    'include',
24    'strict-peer-deps',
25    'prefer-dedupe',
26    'package-lock',
27    'package-lock-only',
28    'foreground-scripts',
29    'ignore-scripts',
30    'allow-git',
31    'audit',
32    'before',
33    'min-release-age',
34    'bin-links',
35    'fund',
36    'dry-run',
37    'cpu',
38    'os',
39    'libc',
40    ...super.params,
41  ]
42
43  static usage = ['[<package-spec> ...]']
44
45  static async completion (opts) {
46    const { partialWord } = opts
47    // install can complete to a folder with a package.json, or any package.
48    // if it has a slash, then it's gotta be a folder
49    // if it starts with https?://, then just give up, because it's a url
50    if (/^https?:\/\//.test(partialWord)) {
51      // do not complete to URLs
52      return []
53    }
54
55    if (/\//.test(partialWord)) {
56      // Complete fully to folder if there is exactly one match and it is a folder containing a package.json file.
57      // If that is not the case we return 0 matches, which will trigger the default bash complete.
58      const lastSlashIdx = partialWord.lastIndexOf('/')
59      const partialName = partialWord.slice(lastSlashIdx + 1)
60      const partialPath = partialWord.slice(0, lastSlashIdx) || '/'
61
62      const isDirMatch = async sibling => {
63        if (sibling.slice(0, partialName.length) !== partialName) {
64          return false
65        }
66
67        try {
68          const contents = await readdir(join(partialPath, sibling))
69          const result = (contents.indexOf('package.json') !== -1)
70          return result
71        } catch {
72          return false
73        }
74      }
75
76      try {
77        const siblings = await readdir(partialPath)
78        const matches = []
79        for (const sibling of siblings) {
80          if (await isDirMatch(sibling)) {
81            matches.push(sibling)
82          }
83        }
84        if (matches.length === 1) {
85          return [join(partialPath, matches[0])]
86        }
87        // no matches
88        return []
89      } catch {
90        return [] // invalid dir: no matching
91      }
92    }
93    // Note: there used to be registry completion here, but it stopped making sense somewhere around 50,000 packages on the registry
94  }
95
96  async exec (args) {
97    // the /path/to/node_modules/..
98    const globalTop = resolve(this.npm.globalDir, '..')
99    const ignoreScripts = this.npm.config.get('ignore-scripts')
100    const isGlobalInstall = this.npm.global
101    const where = isGlobalInstall ? globalTop : this.npm.prefix
102    const forced = this.npm.config.get('force')
103    const scriptShell = this.npm.config.get('script-shell') || undefined
104
105    // be very strict about engines when trying to update npm itself
106    const npmInstall = args.find(arg => arg.startsWith('npm@') || arg === 'npm')
107    if (isGlobalInstall && npmInstall) {
108      const npmOptions = this.npm.flatOptions
109      const npmManifest = await pacote.manifest(npmInstall, npmOptions)
110      try {
111        checks.checkEngine(npmManifest, npmManifest.version, process.version)
112      } catch (e) {
113        if (forced) {
114          log.warn(
115            'install',
116            `Forcing global npm install with incompatible version ${npmManifest.version} into node ${process.version}`
117          )
118        } else {
119          throw e
120        }
121      }
122    }
123
124    // don't try to install the prefix into itself
125    args = args.filter(a => resolve(a) !== this.npm.prefix)
126
127    // `npm i -g` => "install this package globally"
128    if (isGlobalInstall && !args.length) {
129      args = ['.']
130    }
131
132    // throw usage error if trying to install empty package name to global space, e.g: `npm i -g ""`
133    if (where === globalTop && !args.every(Boolean)) {
134      throw this.usageError()
135    }
136
137    const Arborist = require('@npmcli/arborist')
138    const opts = {
139      ...this.npm.flatOptions,
140      auditLevel: null,
141      path: where,
142      add: args,
143      workspaces: this.workspaceNames,
144    }
145    const arb = new Arborist(opts)
146    await arb.reify(opts)
147
148    if (!args.length && !isGlobalInstall && !ignoreScripts) {
149      const scripts = [
150        'preinstall',
151        'install',
152        'postinstall',
153        'prepublish', // XXX(npm9) should we remove this finally??
154        'preprepare',
155        'prepare',
156        'postprepare',
157      ]
158      for (const event of scripts) {
159        await runScript({
160          path: where,
161          args: [],
162          scriptShell,
163          stdio: 'inherit',
164          event,
165        })
166      }
167    }
168    await reifyFinish(this.npm, arb)
169  }
170}
171
172module.exports = Install
173 
codekingpro/portable-devtools · Team Ai