1'use strict' 2 3const figgyPudding = require('figgy-pudding') 4const npa = require('npm-package-arg') 5const npmFetch = require('npm-registry-fetch') 6const semver = require('semver') 7const url = require('url') 8 9const UnpublishConfig = figgyPudding({ 10 force: { default: false }, 11 Promise: { default: () => Promise } 12}) 13 14module.exports = unpublish 15function unpublish (spec, opts) { 16 opts = UnpublishConfig(opts) 17 return new opts.Promise(resolve => resolve()).then(() => { 18 spec = npa(spec) 19 // NOTE: spec is used to pick the appropriate registry/auth combo. 20 opts = opts.concat({ spec }) 21 const pkgUri = spec.escapedName 22 return npmFetch.json(pkgUri, opts.concat({ 23 query: { write: true } 24 })).then(pkg => { 25 if (!spec.rawSpec || spec.rawSpec === '*') { 26 return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, opts.concat({ 27 method: 'DELETE', 28 ignoreBody: true 29 })) 30 } else { 31 const version = spec.rawSpec 32 const allVersions = pkg.versions || {} 33 const versionPublic = allVersions[version] 34 let dist 35 if (versionPublic) { 36 dist = allVersions[version].dist 37 } 38 delete allVersions[version] 39 // if it was the only version, then delete the whole package. 40 if (!Object.keys(allVersions).length) { 41 return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, opts.concat({ 42 method: 'DELETE', 43 ignoreBody: true 44 })) 45 } else if (versionPublic) { 46 const latestVer = pkg['dist-tags'].latest 47 Object.keys(pkg['dist-tags']).forEach(tag => { 48 if (pkg['dist-tags'][tag] === version) { 49 delete pkg['dist-tags'][tag] 50 } 51 }) 52 53 if (latestVer === version) { 54 pkg['dist-tags'].latest = Object.keys( 55 allVersions 56 ).sort(semver.compareLoose).pop() 57 } 58 59 delete pkg._revisions 60 delete pkg._attachments 61 // Update packument with removed versions 62 return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, opts.concat({ 63 method: 'PUT', 64 body: pkg, 65 ignoreBody: true 66 })).then(() => { 67 // Remove the tarball itself 68 return npmFetch.json(pkgUri, opts.concat({ 69 query: { write: true } 70 })).then(({ _rev, _id }) => { 71 const tarballUrl = url.parse(dist.tarball).pathname.substr(1) 72 return npmFetch(`${tarballUrl}/-rev/${_rev}`, opts.concat({ 73 method: 'DELETE', 74 ignoreBody: true 75 })) 76 }) 77 }) 78 } 79 } 80 }, err => { 81 if (err.code !== 'E404') { 82 throw err 83 } 84 }) 85 }).then(() => true) 86} 87