Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
access.js227 linesDownload Raw Back to commands
1const libnpmaccess = require('libnpmaccess')
2const npa = require('npm-package-arg')
3const { output } = require('proc-log')
4const pkgJson = require('@npmcli/package-json')
5const localeCompare = require('@isaacs/string-locale-compare')('en')
6const { otplease } = require('../utils/auth.js')
7const getIdentity = require('../utils/get-identity.js')
8const BaseCommand = require('../base-cmd.js')
9
10const commands = [
11  'get',
12  'grant',
13  'list',
14  'revoke',
15  'set',
16]
17
18const setCommands = [
19  'status=public',
20  'status=private',
21  'mfa=none',
22  'mfa=publish',
23  'mfa=automation',
24  '2fa=none',
25  '2fa=publish',
26  '2fa=automation',
27]
28
29class Access extends BaseCommand {
30  static description = 'Set access level on published packages'
31  static name = 'access'
32  static params = [
33    'json',
34    'otp',
35    'registry',
36  ]
37
38  static usage = [
39    'list packages [<user>|<scope>|<scope:team>] [<package>]',
40    'list collaborators [<package> [<user>]]',
41    'get status [<package>]',
42    'set status=public|private [<package>]',
43    'set mfa=none|publish|automation [<package>]',
44    'grant <read-only|read-write> <scope:team> [<package>]',
45    'revoke <scope:team> [<package>]',
46  ]
47
48  static async completion (opts) {
49    const argv = opts.conf.argv.remain
50    if (argv.length === 2) {
51      return commands
52    }
53
54    if (argv.length === 3) {
55      switch (argv[2]) {
56        case 'grant':
57          return ['read-only', 'read-write']
58        case 'revoke':
59          return []
60        case 'list':
61        case 'ls':
62          return ['packages', 'collaborators']
63        case 'get':
64          return ['status']
65        case 'set':
66          return setCommands
67        default:
68          throw new Error(argv[2] + ' not recognized')
69      }
70    }
71  }
72
73  async exec ([cmd, subcmd, ...args]) {
74    if (!cmd) {
75      throw this.usageError()
76    }
77    if (!commands.includes(cmd)) {
78      throw this.usageError(`${cmd} is not a valid access command`)
79    }
80    // All commands take at least one more parameter so we can do this check up front
81    if (!subcmd) {
82      throw this.usageError()
83    }
84
85    switch (cmd) {
86      case 'grant':
87        if (!['read-only', 'read-write'].includes(subcmd)) {
88          throw this.usageError('grant must be either `read-only` or `read-write`')
89        }
90        if (!args[0]) {
91          throw this.usageError('`<scope:team>` argument is required')
92        }
93        return this.#grant(subcmd, args[0], args[1])
94      case 'revoke':
95        return this.#revoke(subcmd, args[0])
96      case 'list':
97      case 'ls':
98        if (subcmd === 'packages') {
99          return this.#listPackages(args[0], args[1])
100        }
101        if (subcmd === 'collaborators') {
102          return this.#listCollaborators(args[0], args[1])
103        }
104        throw this.usageError(`list ${subcmd} is not a valid access command`)
105      case 'get':
106        if (subcmd !== 'status') {
107          throw this.usageError(`get ${subcmd} is not a valid access command`)
108        }
109        return this.#getStatus(args[0])
110      case 'set':
111        if (!setCommands.includes(subcmd)) {
112          throw this.usageError(`set ${subcmd} is not a valid access command`)
113        }
114        return this.#set(subcmd, args[0])
115    }
116  }
117
118  async #grant (permissions, scope, pkg) {
119    await otplease(this.npm, this.npm.flatOptions, async (opts) => {
120      await libnpmaccess.setPermissions(scope, pkg, permissions, opts)
121    })
122  }
123
124  async #revoke (scope, pkg) {
125    await otplease(this.npm, this.npm.flatOptions, async (opts) => {
126      await libnpmaccess.removePermissions(scope, pkg, opts)
127    })
128  }
129
130  async #listPackages (owner, pkg) {
131    if (!owner) {
132      owner = await getIdentity(this.npm, this.npm.flatOptions)
133    }
134    const pkgs = await libnpmaccess.getPackages(owner, this.npm.flatOptions)
135    this.#output(pkgs, pkg)
136  }
137
138  async #listCollaborators (pkg, user) {
139    const pkgName = await this.#getPackage(pkg, false)
140    const collabs = await libnpmaccess.getCollaborators(pkgName, this.npm.flatOptions)
141    this.#output(collabs, user)
142  }
143
144  async #getStatus (pkg) {
145    const pkgName = await this.#getPackage(pkg, false)
146    const visibility = await libnpmaccess.getVisibility(pkgName, this.npm.flatOptions)
147    this.#output({ [pkgName]: visibility.public ? 'public' : 'private' })
148  }
149
150  async #set (subcmd, pkg) {
151    const [subkey, subval] = subcmd.split('=')
152    switch (subkey) {
153      case 'mfa':
154      case '2fa':
155        return this.#setMfa(pkg, subval)
156      case 'status':
157        return this.#setStatus(pkg, subval)
158    }
159  }
160
161  async #setMfa (pkg, level) {
162    const pkgName = await this.#getPackage(pkg, false)
163    await otplease(this.npm, this.npm.flatOptions, (opts) => {
164      return libnpmaccess.setMfa(pkgName, level, opts)
165    })
166  }
167
168  async #setStatus (pkg, status) {
169    // only scoped packages can have their access changed
170    const pkgName = await this.#getPackage(pkg, true)
171    if (status === 'private') {
172      status = 'restricted'
173    }
174    await otplease(this.npm, this.npm.flatOptions, (opts) => {
175      return libnpmaccess.setAccess(pkgName, status, opts)
176    })
177    return this.#getStatus(pkgName)
178  }
179
180  async #getPackage (name, requireScope) {
181    if (!name) {
182      try {
183        const { content } = await pkgJson.normalize(this.npm.prefix)
184        name = content.name
185      } catch (err) {
186        if (err.code === 'ENOENT') {
187          throw Object.assign(new Error('no package name given and no package.json found'), {
188            code: 'ENOENT',
189          })
190        } else {
191          throw err
192        }
193      }
194    }
195
196    const spec = npa(name)
197    if (requireScope && !spec.scope) {
198      throw this.usageError('This command is only available for scoped packages.')
199    }
200    return name
201  }
202
203  #output (items, limiter) {
204    const outputs = {}
205    const lookup = {
206      __proto__: null,
207      read: 'read-only',
208      write: 'read-write',
209    }
210    for (const item in items) {
211      const val = items[item]
212      outputs[item] = lookup[val] || val
213    }
214    if (this.npm.config.get('json')) {
215      output.buffer(outputs)
216    } else {
217      for (const item of Object.keys(outputs).sort(localeCompare)) {
218        if (!limiter || limiter === item) {
219          output.standard(`${item}: ${outputs[item]}`)
220        }
221      }
222    }
223  }
224}
225
226module.exports = Access
227 
codekingpro/portable-devtools · Team Ai