Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
owner.js242 linesDownload Raw Back to commands
1const npa = require('npm-package-arg')
2const npmFetch = require('npm-registry-fetch')
3const pacote = require('pacote')
4const { log, output } = require('proc-log')
5const { otplease } = require('../utils/auth.js')
6const pkgJson = require('@npmcli/package-json')
7const BaseCommand = require('../base-cmd.js')
8const { redact } = require('@npmcli/redact')
9
10const readJson = async (path) => {
11  try {
12    const { content } = await pkgJson.normalize(path)
13    return content
14  } catch {
15    return {}
16  }
17}
18
19class Owner extends BaseCommand {
20  static description = 'Manage package owners'
21  static name = 'owner'
22  static params = [
23    'registry',
24    'otp',
25    'workspace',
26    'workspaces',
27  ]
28
29  static usage = [
30    'add <user> <package-spec>',
31    'rm <user> <package-spec>',
32    'ls <package-spec>',
33  ]
34
35  static workspaces = true
36  static ignoreImplicitWorkspace = false
37
38  static async completion (opts, npm) {
39    const argv = opts.conf.argv.remain
40    if (argv.length > 3) {
41      return []
42    }
43
44    if (argv[1] !== 'owner') {
45      argv.unshift('owner')
46    }
47
48    if (argv.length === 2) {
49      return ['add', 'rm', 'ls']
50    }
51
52    // reaches registry in order to autocomplete rm
53    if (argv[2] === 'rm') {
54      if (npm.global) {
55        return []
56      }
57      const { name } = await readJson(npm.prefix)
58      if (!name) {
59        return []
60      }
61
62      const spec = npa(name)
63      const data = await pacote.packument(spec, {
64        ...npm.flatOptions,
65        fullMetadata: true,
66        _isRoot: true,
67      })
68      if (data && data.maintainers && data.maintainers.length) {
69        return data.maintainers.map(m => m.name)
70      }
71    }
72    return []
73  }
74
75  async exec ([action, ...args]) {
76    if (action === 'ls' || action === 'list') {
77      await this.ls(args[0])
78    } else if (action === 'add') {
79      await this.changeOwners(args[0], args[1], 'add')
80    } else if (action === 'rm' || action === 'remove') {
81      await this.changeOwners(args[0], args[1], 'rm')
82    } else {
83      throw this.usageError()
84    }
85  }
86
87  async execWorkspaces ([action, ...args]) {
88    await this.setWorkspaces()
89    // ls pkg or owner add/rm package
90    if ((action === 'ls' && args.length > 0) || args.length > 1) {
91      const implicitWorkspaces = this.npm.config.get('workspace', 'default')
92      if (implicitWorkspaces.length === 0) {
93        log.warn(`Ignoring specified workspace(s)`)
94      }
95      return this.exec([action, ...args])
96    }
97
98    for (const [name] of this.workspaces) {
99      if (action === 'ls' || action === 'list') {
100        await this.ls(name)
101      } else if (action === 'add') {
102        await this.changeOwners(args[0], name, 'add')
103      } else if (action === 'rm' || action === 'remove') {
104        await this.changeOwners(args[0], name, 'rm')
105      } else {
106        throw this.usageError()
107      }
108    }
109  }
110
111  async ls (pkg) {
112    pkg = await this.getPkg(this.npm.prefix, pkg)
113    const spec = npa(pkg)
114
115    try {
116      const packumentOpts = {
117        ...this.npm.flatOptions,
118        fullMetadata:
119        true,
120        preferOnline: true,
121        _isRoot: true,
122      }
123      const { maintainers } = await pacote.packument(spec, packumentOpts)
124      if (!maintainers || !maintainers.length) {
125        output.standard('no admin found')
126      } else {
127        output.standard(maintainers.map(m => `${m.name} <${m.email}>`).join('\n'))
128      }
129    } catch (err) {
130      log.error('owner ls', "Couldn't get owner data", redact(pkg))
131      throw err
132    }
133  }
134
135  async getPkg (prefix, pkg) {
136    if (!pkg) {
137      if (this.npm.global) {
138        throw this.usageError()
139      }
140      const { name } = await readJson(prefix)
141      if (!name) {
142        throw this.usageError()
143      }
144
145      return name
146    }
147    return pkg
148  }
149
150  async changeOwners (user, pkg, addOrRm) {
151    if (!user) {
152      throw this.usageError()
153    }
154
155    pkg = await this.getPkg(this.npm.prefix, pkg)
156    log.verbose(`owner ${addOrRm}`, '%s to %s', user, pkg)
157
158    const spec = npa(pkg)
159    const uri = `/-/user/org.couchdb.user:${encodeURIComponent(user)}`
160    let u
161
162    try {
163      u = await npmFetch.json(uri, this.npm.flatOptions)
164    } catch (err) {
165      log.error('owner mutate', `Error getting user data for ${user}`)
166      throw err
167    }
168
169    // normalize user data
170    u = { name: u.name, email: u.email }
171
172    const data = await pacote.packument(spec, {
173      ...this.npm.flatOptions,
174      fullMetadata: true,
175      preferOnline: true,
176      _isRoot: true,
177    })
178
179    const owners = data.maintainers || []
180    let maintainers
181    if (addOrRm === 'add') {
182      const existing = owners.find(o => o.name === u.name)
183      if (existing) {
184        log.info(
185          'owner add',
186          `Already a package owner: ${existing.name} <${existing.email}>`
187        )
188        return
189      }
190      maintainers = [
191        ...owners,
192        u,
193      ]
194    } else {
195      maintainers = owners.filter(o => o.name !== u.name)
196
197      if (maintainers.length === owners.length) {
198        log.info('owner rm', 'Not a package owner: ' + u.name)
199        return false
200      }
201
202      if (!maintainers.length) {
203        throw Object.assign(
204          new Error(
205            'Cannot remove all owners of a package. Add someone else first.'
206          ),
207          { code: 'EOWNERRM' }
208        )
209      }
210    }
211
212    const dataPath = `/${spec.escapedName}/-rev/${encodeURIComponent(data._rev)}`
213    try {
214      const res = await otplease(this.npm, this.npm.flatOptions, opts => {
215        return npmFetch.json(dataPath, {
216          ...opts,
217          method: 'PUT',
218          body: {
219            _id: data._id,
220            _rev: data._rev,
221            maintainers,
222          },
223          spec,
224        })
225      })
226      if (addOrRm === 'add') {
227        output.standard(`+ ${user} (${spec.name})`)
228      } else {
229        output.standard(`- ${user} (${spec.name})`)
230      }
231      return res
232    } catch (err) {
233      throw Object.assign(
234        new Error('Failed to update package: ' + JSON.stringify(err.message)),
235        { code: 'EOWNERMUTATE' }
236      )
237    }
238  }
239}
240
241module.exports = Owner
242 
codekingpro/portable-devtools · Team Ai