Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
link.js186 linesDownload Raw Back to commands
1const { readdir } = require('node:fs/promises')
2const { resolve } = require('node:path')
3const npa = require('npm-package-arg')
4const pkgJson = require('@npmcli/package-json')
5const semver = require('semver')
6const reifyFinish = require('../utils/reify-finish.js')
7const ArboristWorkspaceCmd = require('../arborist-cmd.js')
8
9class Link extends ArboristWorkspaceCmd {
10  static description = 'Symlink a package folder'
11  static name = 'link'
12  static usage = [
13    '[<package-spec>]',
14  ]
15
16  static params = [
17    'save',
18    'save-exact',
19    'global',
20    'install-strategy',
21    'legacy-bundling',
22    'global-style',
23    'strict-peer-deps',
24    'package-lock',
25    'omit',
26    'include',
27    'ignore-scripts',
28    'allow-git',
29    'audit',
30    'bin-links',
31    'fund',
32    'dry-run',
33    ...super.params,
34  ]
35
36  static async completion (opts, npm) {
37    const dir = npm.globalDir
38    const files = await readdir(dir)
39    return files.filter(f => !/^[._-]/.test(f))
40  }
41
42  async exec (args) {
43    if (this.npm.global) {
44      throw Object.assign(
45        new Error(
46          'link should never be --global.\n' +
47          'Please re-run this command with --local'
48        ),
49        { code: 'ELINKGLOBAL' }
50      )
51    }
52    // install-links is implicitly false when running `npm link`
53    this.npm.config.set('install-links', false)
54
55    // link with no args: symlink the folder to the global location
56    // link with package arg: symlink the global to the local
57    args = args.filter(a => resolve(a) !== this.npm.prefix)
58    return args.length
59      ? this.linkInstall(args)
60      : this.linkPkg()
61  }
62
63  async linkInstall (args) {
64    // load current packages from the global space, and then add symlinks installs locally
65    const globalTop = resolve(this.npm.globalDir, '..')
66    const Arborist = require('@npmcli/arborist')
67    const globalOpts = {
68      ...this.npm.flatOptions,
69      Arborist,
70      path: globalTop,
71      global: true,
72      prune: false,
73    }
74    const globalArb = new Arborist(globalOpts)
75
76    // get only current top-level packages from the global space
77    const globals = await globalArb.loadActual({
78      filter: (node, kid) =>
79        !node.isRoot || args.some(a => npa(a).name === kid),
80    })
81
82    // any extra arg that is missing from the current global space should be reified there first
83    const missing = this.missingArgsFromTree(globals, args)
84    if (missing.length) {
85      await globalArb.reify({
86        ...globalOpts,
87        add: missing,
88      })
89    }
90
91    // get a list of module names that should be linked in the local prefix
92    const names = []
93    for (const a of args) {
94      const arg = npa(a)
95      if (arg.type === 'directory') {
96        const { content } = await pkgJson.normalize(arg.fetchSpec)
97        names.push(content.name)
98      } else {
99        names.push(arg.name)
100      }
101    }
102
103    // npm link should not save=true by default unless you're using any of --save-dev or other types
104    const save =
105      Boolean(
106        (this.npm.config.find('save') !== 'default' &&
107        this.npm.config.get('save')) ||
108        this.npm.config.get('save-optional') ||
109        this.npm.config.get('save-peer') ||
110        this.npm.config.get('save-dev') ||
111        this.npm.config.get('save-prod')
112      )
113    // create a new arborist instance for the local prefix and
114    // reify all the pending names as symlinks there
115    const localArb = new Arborist({
116      ...this.npm.flatOptions,
117      prune: false,
118      path: this.npm.prefix,
119      save,
120    })
121    await localArb.reify({
122      ...this.npm.flatOptions,
123      prune: false,
124      path: this.npm.prefix,
125      add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`),
126      save,
127      workspaces: this.workspaceNames,
128    })
129
130    await reifyFinish(this.npm, localArb)
131  }
132
133  async linkPkg () {
134    const wsp = this.workspacePaths
135    const paths = wsp && wsp.length ? wsp : [this.npm.prefix]
136    const add = paths.map(path => `file:${path}`)
137    const globalTop = resolve(this.npm.globalDir, '..')
138    const Arborist = require('@npmcli/arborist')
139    const arb = new Arborist({
140      ...this.npm.flatOptions,
141      Arborist,
142      path: globalTop,
143      global: true,
144    })
145    await arb.reify({
146      add,
147    })
148    await reifyFinish(this.npm, arb)
149  }
150
151  // Returns a list of items that can't be fulfilled by things found in the current arborist inventory
152  missingArgsFromTree (tree, args) {
153    if (tree.isLink) {
154      return this.missingArgsFromTree(tree.target, args)
155    }
156
157    const foundNodes = []
158    const missing = args.filter(a => {
159      const arg = npa(a)
160      const nodes = tree.children.values()
161      const argFound = [...nodes].every(node => {
162        // TODO: write tests for unmatching version specs
163        // this is hard to test atm but should be simple once we have a mocked registry again
164        if (arg.name !== node.name /* istanbul ignore next */ || (
165          arg.version &&
166          /* istanbul ignore next */
167          !semver.satisfies(node.version, arg.version)
168        )) {
169          foundNodes.push(node)
170          return true
171        }
172      })
173      return argFound
174    })
175
176    // remote nodes from the loaded tree in order to avoid dropping them later when reifying
177    for (const node of foundNodes) {
178      node.parent = null
179    }
180
181    return missing
182  }
183}
184
185module.exports = Link
186 
codekingpro/portable-devtools · Team Ai