Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
unpublish.js164 linesDownload Raw Back to commands
1const libaccess = require('libnpmaccess')
2const libunpub = require('libnpmpublish').unpublish
3const npa = require('npm-package-arg')
4const pacote = require('pacote')
5const { output, log } = require('proc-log')
6const pkgJson = require('@npmcli/package-json')
7const { flatten } = require('@npmcli/config/lib/definitions')
8const getIdentity = require('../utils/get-identity.js')
9const { otplease } = require('../utils/auth.js')
10const BaseCommand = require('../base-cmd.js')
11
12const LAST_REMAINING_VERSION_ERROR = 'Refusing to delete the last version of the package. It will block from republishing a new version for 24 hours.\nRun with --force to do this.'
13
14class Unpublish extends BaseCommand {
15  static description = 'Remove a package from the registry'
16  static name = 'unpublish'
17  static params = ['dry-run', 'force', 'workspace', 'workspaces']
18  static usage = ['[<package-spec>]']
19  static workspaces = true
20  static ignoreImplicitWorkspace = false
21
22  static async getKeysOfVersions (name, opts) {
23    const packument = await pacote.packument(name, {
24      ...opts,
25      spec: name,
26      query: { write: true },
27      _isRoot: true,
28    })
29    return Object.keys(packument.versions)
30  }
31
32  static async completion (args, npm) {
33    const { partialWord, conf } = args
34
35    if (conf.argv.remain.length >= 3) {
36      return []
37    }
38
39    const opts = { ...npm.flatOptions }
40    const username = await getIdentity(npm, { ...opts }).catch(() => null)
41    if (!username) {
42      return []
43    }
44
45    const access = await libaccess.getPackages(username, opts)
46    // do a bit of filtering at this point, so that we don't need to fetch versions for more than one thing, but also don't accidentally unpublish a whole project
47    let pkgs = Object.keys(access)
48    if (!partialWord || !pkgs.length) {
49      return pkgs
50    }
51
52    const pp = npa(partialWord).name
53    pkgs = pkgs.filter(p => !p.indexOf(pp))
54    if (pkgs.length > 1) {
55      return pkgs
56    }
57
58    const versions = await Unpublish.getKeysOfVersions(pkgs[0], opts)
59    if (!versions.length) {
60      return pkgs
61    } else {
62      return versions.map(v => `${pkgs[0]}@${v}`)
63    }
64  }
65
66  async exec (args, { localPrefix } = {}) {
67    if (args.length > 1) {
68      throw this.usageError()
69    }
70
71    // workspace mode
72    if (!localPrefix) {
73      localPrefix = this.npm.localPrefix
74    }
75
76    const force = this.npm.config.get('force')
77    const { silent } = this.npm
78    const dryRun = this.npm.config.get('dry-run')
79
80    let spec
81    if (args.length) {
82      spec = npa(args[0])
83      if (spec.type !== 'version' && spec.rawSpec !== '*') {
84        throw this.usageError('Can only unpublish a single version, or the entire project.\nTags and ranges are not supported.')
85      }
86    }
87
88    log.silly('unpublish', 'args[0]', args[0])
89    log.silly('unpublish', 'spec', spec)
90
91    if (spec?.rawSpec === '*' && !force) {
92      throw this.usageError('Refusing to delete entire project.\nRun with --force to do this.')
93    }
94
95    const opts = { ...this.npm.flatOptions }
96
97    let manifest
98    try {
99      const { content } = await pkgJson.prepare(localPrefix)
100      manifest = content
101    } catch (err) {
102      if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
103        if (!spec) {
104          // We needed a local package.json to figure out what package to unpublish
105          throw this.usageError()
106        }
107      } else {
108        // folks should know if ANY local package.json had a parsing error.
109        // They may be relying on `publishConfig` to be loading and we don't want to ignore errors in that case.
110        throw err
111      }
112    }
113
114    let pkgVersion // for cli output
115    if (spec) {
116      pkgVersion = spec.type === 'version' ? `@${spec.rawSpec}` : ''
117    } else {
118      spec = npa.resolve(manifest.name, manifest.version)
119      log.verbose('unpublish', manifest)
120      pkgVersion = manifest.version ? `@${manifest.version}` : ''
121      if (!manifest.version && !force) {
122        throw this.usageError('Refusing to delete entire project.\nRun with --force to do this.')
123      }
124    }
125
126    // If localPrefix has a package.json with a name that matches the package being unpublished, load up the publishConfig
127    if (manifest?.name === spec.name && manifest.publishConfig) {
128      const cliFlags = this.npm.config.data.get('cli').raw
129      // Filter out properties set in CLI flags to prioritize them over corresponding `publishConfig` settings
130      const filteredPublishConfig = Object.fromEntries(
131        Object.entries(manifest.publishConfig).filter(([key]) => !(key in cliFlags)))
132      for (const key in filteredPublishConfig) {
133        this.npm.config.checkUnknown('publishConfig', key)
134      }
135      flatten(filteredPublishConfig, opts)
136    }
137
138    const versions = await Unpublish.getKeysOfVersions(spec.name, opts)
139    if (versions.length === 1 && spec.rawSpec === versions[0] && !force) {
140      throw this.usageError(LAST_REMAINING_VERSION_ERROR)
141    }
142    if (versions.length === 1) {
143      pkgVersion = ''
144    }
145
146    if (!dryRun) {
147      await otplease(this.npm, opts, o => libunpub(spec, o))
148    }
149    if (!silent) {
150      output.standard(`- ${spec.name}${pkgVersion}`)
151    }
152  }
153
154  async execWorkspaces (args) {
155    await this.setWorkspaces()
156
157    for (const path of this.workspacePaths) {
158      await this.exec(args, { localPrefix: path })
159    }
160  }
161}
162
163module.exports = Unpublish
164 
codekingpro/portable-devtools · Team Ai