UNPKG

3.29 kBJavaScriptView Raw
1'use strict'
2
3const { URL } = require('url')
4const npa = require('npm-package-arg')
5const npmFetch = require('npm-registry-fetch')
6const semver = require('semver')
7
8// given a tarball url and a registry url, returns just the
9// relevant pathname portion of it, so that it can be handled
10// elegantly by npm-registry-fetch which only expects pathnames
11// and handles the registry hostname via opts
12const getPathname = (tarball, registry) => {
13 const registryUrl = new URL(registry).pathname.slice(1)
14 let tarballUrl = new URL(tarball).pathname.slice(1)
15
16 // test the tarball url to see if it starts with a possible
17 // pathname from the registry url, in that case strips that portion
18 // of it so that we only return the post-registry-url pathname
19 if (registryUrl) {
20 tarballUrl = tarballUrl.slice(registryUrl.length)
21 }
22 return tarballUrl
23}
24
25const unpublish = async (spec, opts) => {
26 spec = npa(spec)
27 // spec is used to pick the appropriate registry/auth combo.
28 opts = {
29 force: false,
30 ...opts,
31 spec,
32 }
33
34 try {
35 const pkgUri = spec.escapedName
36 const pkg = await npmFetch.json(pkgUri, {
37 ...opts,
38 query: { write: true },
39 })
40
41 const version = spec.rawSpec
42 const allVersions = pkg.versions || {}
43 const versionData = allVersions[version]
44
45 const rawSpecs = (!spec.rawSpec || spec.rawSpec === '*')
46 const onlyVersion = Object.keys(allVersions).length === 1
47 const noVersions = !Object.keys(allVersions).length
48
49 // if missing specific version,
50 // assumed unpublished
51 if (!versionData && !rawSpecs && !noVersions) {
52 return true
53 }
54
55 // unpublish all versions of a package:
56 // - no specs supplied "npm unpublish foo"
57 // - all specs ("*") "npm unpublish foo@*"
58 // - there was only one version
59 // - has no versions field on packument
60 if (rawSpecs || onlyVersion || noVersions) {
61 await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
62 ...opts,
63 method: 'DELETE',
64 ignoreBody: true,
65 })
66 return true
67 } else {
68 const dist = allVersions[version].dist
69 delete allVersions[version]
70
71 const latestVer = pkg['dist-tags'].latest
72
73 // deleting dist tags associated to version
74 Object.keys(pkg['dist-tags']).forEach(tag => {
75 if (pkg['dist-tags'][tag] === version) {
76 delete pkg['dist-tags'][tag]
77 }
78 })
79
80 if (latestVer === version) {
81 pkg['dist-tags'].latest = Object.keys(
82 allVersions
83 ).sort(semver.compareLoose).pop()
84 }
85
86 delete pkg._revisions
87 delete pkg._attachments
88
89 // Update packument with removed versions
90 await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
91 ...opts,
92 method: 'PUT',
93 body: pkg,
94 ignoreBody: true,
95 })
96
97 // Remove the tarball itself
98 const { _rev } = await npmFetch.json(pkgUri, {
99 ...opts,
100 query: { write: true },
101 })
102 const tarballUrl = getPathname(dist.tarball, opts.registry)
103 await npmFetch(`${tarballUrl}/-rev/${_rev}`, {
104 ...opts,
105 method: 'DELETE',
106 ignoreBody: true,
107 })
108 return true
109 }
110 } catch (err) {
111 if (err.code !== 'E404') {
112 throw err
113 }
114
115 return true
116 }
117}
118
119module.exports = unpublish