Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
feat: unpublish code refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
claudiahdz committed Mar 9, 2020
1 parent ecaeb0b commit f6bf2b8
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 111 deletions.
70 changes: 32 additions & 38 deletions test/unpublish.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const test = require('tap').test
const t = require('tap')
const tnock = require('./fixtures/tnock.js')

const OPTS = {
Expand All @@ -11,7 +11,7 @@ const REG = OPTS.registry
const REV = '72-47f2986bfd8e8b55068b204588bbf484'
const unpub = require('../unpublish.js')

test('basic test', t => {
t.test('basic test', async t => {
const doc = {
_id: 'foo',
_rev: REV,
Expand All @@ -31,12 +31,11 @@ test('basic test', t => {
const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(200, doc)
srv.delete(`/foo/-rev/${REV}`).reply(201)
return unpub('foo', OPTS).then(ret => {
t.ok(ret, 'foo was unpublished')
})
const ret = await unpub('foo', OPTS)
t.ok(ret, 'foo was unpublished')
})

test('scoped basic test', t => {
t.test('scoped basic test', async t => {
const doc = {
_id: '@foo/bar',
_rev: REV,
Expand All @@ -56,12 +55,11 @@ test('scoped basic test', t => {
const srv = tnock(t, REG)
srv.get('/@foo%2fbar?write=true').reply(200, doc)
srv.delete(`/@foo%2fbar/-rev/${REV}`).reply(201)
return unpub('@foo/bar', OPTS).then(() => {
t.ok(true, 'foo was unpublished')
})
const ret = await unpub('@foo/bar', OPTS)
t.ok(ret, 'foo was unpublished')
})

test('unpublish specific, last version', t => {
t.test('unpublish specific, last version', async t => {
const doc = {
_id: 'foo',
_rev: REV,
Expand All @@ -81,12 +79,11 @@ test('unpublish specific, last version', t => {
const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(200, doc)
srv.delete(`/foo/-rev/${REV}`).reply(201)
return unpub('foo@1.0.0', OPTS).then(() => {
t.ok(true, 'foo was unpublished')
})
const ret = await unpub('foo@1.0.0', OPTS)
t.ok(ret, 'foo was unpublished')
})

test('unpublish specific version', t => {
t.test('unpublish specific version', async t => {
const doc = {
_id: 'foo',
_rev: REV,
Expand Down Expand Up @@ -133,29 +130,29 @@ test('unpublish specific version', t => {
srv.put(`/foo/-rev/${REV}`, postEdit).reply(200)
srv.get('/foo?write=true').reply(200, postEdit)
srv.delete(`/foo/-/foo-1.0.1.tgz/-rev/${REV}`).reply(200)
return unpub('foo@1.0.1', OPTS).then(() => {
t.ok(true, 'foo was unpublished')
})
const ret = await unpub('foo@1.0.1', OPTS)
t.ok(ret, 'foo was unpublished')
})

test('404 considered a success', t => {
t.test('404 considered a success', async t => {
const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(404)
return unpub('foo', OPTS).then(() => {
t.ok(true, 'foo was unpublished')
})
const ret = await unpub('foo', OPTS)
t.ok(ret, 'foo was unpublished')
})

test('non-404 errors', t => {
t.test('non-404 errors', async t => {
const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(500)
return unpub('foo', OPTS).then(
() => { throw new Error('should not have succeeded') },
err => { t.equal(err.code, 'E500', 'got right error from server') }
)

try {
await unpub('foo', OPTS)
} catch (err) {
t.equal(err.code, 'E500', 'got right error from server')
}
})

test('packument with missing versions unpublishes whole thing', t => {
t.test('packument with missing versions unpublishes whole thing', async t => {
const doc = {
_id: 'foo',
_rev: REV,
Expand All @@ -167,12 +164,11 @@ test('packument with missing versions unpublishes whole thing', t => {
const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(200, doc)
srv.delete(`/foo/-rev/${REV}`).reply(201)
return unpub('foo@1.0.0', OPTS).then(() => {
t.ok(true, 'foo was unpublished')
})
const ret = await unpub('foo@1.0.0', OPTS)
t.ok(ret, 'foo was unpublished')
})

test('packument with missing specific version assumed unpublished', t => {
t.test('packument with missing specific version assumed unpublished', async t => {
const doc = {
_id: 'foo',
_rev: REV,
Expand All @@ -191,12 +187,11 @@ test('packument with missing specific version assumed unpublished', t => {
}
const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(200, doc)
return unpub('foo@1.0.1', OPTS).then(() => {
t.ok(true, 'foo was unpublished')
})
const ret = await unpub('foo@1.0.1', OPTS)
t.ok(ret, 'foo was unpublished')
})

test('unpublish specific version without dist-tag update', t => {
t.test('unpublish specific version without dist-tag update', async t => {
const doc = {
_id: 'foo',
_rev: REV,
Expand Down Expand Up @@ -242,7 +237,6 @@ test('unpublish specific version without dist-tag update', t => {
srv.put(`/foo/-rev/${REV}`, postEdit).reply(200)
srv.get('/foo?write=true').reply(200, postEdit)
srv.delete(`/foo/-/foo-1.0.1.tgz/-rev/${REV}`).reply(200)
return unpub('foo@1.0.1', OPTS).then(() => {
t.ok(true, 'foo was unpublished')
})
const ret = await unpub('foo@1.0.1', OPTS)
t.ok(ret, 'foo was unpublished')
})
157 changes: 84 additions & 73 deletions unpublish.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,84 +6,95 @@ const semver = require('semver')
const url = require('url')

module.exports = unpublish
function unpublish (spec, opts) {
return Promise.resolve().then(() => {
spec = npa(spec)
// NOTE: spec is used to pick the appropriate registry/auth combo.
opts = {
force: false,
...opts,
spec
}
async function unpublish (spec, opts) {
spec = npa(spec)
// spec is used to pick the appropriate registry/auth combo.
opts = {
force: false,
...opts,
spec
}

try {
const pkgUri = spec.escapedName
return npmFetch.json(pkgUri, {
const pkg = await npmFetch.json(pkgUri, {
...opts,
query: { write: true }
}).then(pkg => {
if (!spec.rawSpec || spec.rawSpec === '*') {
return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
...opts,
method: 'DELETE',
ignoreBody: true
})
} else {
const version = spec.rawSpec
const allVersions = pkg.versions || {}
const versionPublic = allVersions[version]
let dist
if (versionPublic) {
dist = allVersions[version].dist
}
delete allVersions[version]
// if it was the only version, then delete the whole package.
if (!Object.keys(allVersions).length) {
return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
...opts,
method: 'DELETE',
ignoreBody: true
})
} else if (versionPublic) {
const latestVer = pkg['dist-tags'].latest
Object.keys(pkg['dist-tags']).forEach(tag => {
if (pkg['dist-tags'][tag] === version) {
delete pkg['dist-tags'][tag]
}
})
})

const version = spec.rawSpec
const allVersions = pkg.versions || {}
const versionData = allVersions[version]

const rawSpecs = (!spec.rawSpec || spec.rawSpec === '*')
const onlyVersion = Object.keys(allVersions).length === 1
const noVersions = !Object.keys(allVersions).length

// if missing specific version,
// assumed unpublished
if (!versionData && !rawSpecs && !noVersions) {
return true
}

if (latestVer === version) {
pkg['dist-tags'].latest = Object.keys(
allVersions
).sort(semver.compareLoose).pop()
}
// unpublish all versions of a package:
// - no specs supplied "npm unpublish foo"
// - all specs ("*") "npm unpublish foo@*"
// - there was only one version
// - has no versions field on packument
if (rawSpecs || onlyVersion || noVersions) {
await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
...opts,
method: 'DELETE',
ignoreBody: true
})
return true
} else {
const dist = allVersions[version].dist
delete allVersions[version]

delete pkg._revisions
delete pkg._attachments
// Update packument with removed versions
return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
...opts,
method: 'PUT',
body: pkg,
ignoreBody: true
}).then(() => {
// Remove the tarball itself
return npmFetch.json(pkgUri, {
...opts,
query: { write: true }
}).then(({ _rev }) => {
const tarballUrl = url.parse(dist.tarball).pathname.substr(1)
return npmFetch(`${tarballUrl}/-rev/${_rev}`, {
...opts,
method: 'DELETE',
ignoreBody: true
})
})
})
const latestVer = pkg['dist-tags'].latest

// deleting dist tags associated to version
Object.keys(pkg['dist-tags']).forEach(tag => {
if (pkg['dist-tags'][tag] === version) {
delete pkg['dist-tags'][tag]
}
})

if (latestVer === version) {
pkg['dist-tags'].latest = Object.keys(
allVersions
).sort(semver.compareLoose).pop()
}
}, err => {
if (err.code !== 'E404') {
throw err
}
})
}).then(() => true)

delete pkg._revisions
delete pkg._attachments

// Update packument with removed versions
await npmFetch(`${pkgUri}/-rev/${pkg._rev}`, {
...opts,
method: 'PUT',
body: pkg,
ignoreBody: true
})

// Remove the tarball itself
const { _rev } = await npmFetch.json(pkgUri, {
...opts,
query: { write: true }
})
const tarballUrl = url.parse(dist.tarball).pathname.substr(1)
await npmFetch(`${tarballUrl}/-rev/${_rev}`, {
...opts,
method: 'DELETE',
ignoreBody: true
})
return true
}
} catch (err) {
if (err.code !== 'E404') {
throw err
}
return true
}
}

0 comments on commit f6bf2b8

Please sign in to comment.