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

Commit

Permalink
feat(unpublish): add new api with unpublish support
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Aug 30, 2018
1 parent c538f9a commit 1c9d594
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 0 deletions.
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
publish: require('./publish.js'),
unpublish: require('./unpublish.js')
}
1 change: 1 addition & 0 deletions publish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
'use strict'
249 changes: 249 additions & 0 deletions test/unpublish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
'use strict'

const figgyPudding = require('figgy-pudding')
const test = require('tap').test
const tnock = require('./util/tnock.js')

const OPTS = figgyPudding({ registry: {} })({
registry: 'https://mock.reg/'
})

const REG = OPTS.registry
const REV = '72-47f2986bfd8e8b55068b204588bbf484'
const unpub = require('../unpublish.js')

test('basic test', t => {
const doc = {
_id: 'foo',
_rev: REV,
name: 'foo',
'dist-tags': {
latest: '1.0.0'
},
versions: {
'1.0.0': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.0.tgz`
}
}
}
}
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(() => {
t.ok(true, 'foo was unpublished')
})
})

test('scoped basic test', t => {
const doc = {
_id: '@foo/bar',
_rev: REV,
name: '@foo/bar',
'dist-tags': {
latest: '1.0.0'
},
versions: {
'1.0.0': {
name: '@foo/bar',
dist: {
tarball: `${REG}/@foo/bar/-/foo-1.0.0.tgz`
}
}
}
}
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')
})
})

test('unpublish specific, last version', t => {
const doc = {
_id: 'foo',
_rev: REV,
name: 'foo',
'dist-tags': {
latest: '1.0.0'
},
versions: {
'1.0.0': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.0.tgz`
}
}
}
}
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')
})
})

test('unpublish specific version', t => {
const doc = {
_id: 'foo',
_rev: REV,
_revisions: [1, 2, 3],
_attachments: [1, 2, 3],
name: 'foo',
'dist-tags': {
latest: '1.0.1'
},
versions: {
'1.0.0': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.0.tgz`
}
},
'1.0.1': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.1.tgz`
}
}
}
}
const postEdit = {
_id: 'foo',
_rev: REV,
name: 'foo',
'dist-tags': {
latest: '1.0.0'
},
versions: {
'1.0.0': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.0.tgz`
}
}
}
}

const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(200, doc)
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')
})
})

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

test('non-404 errors', 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') }
)
})

test('packument with missing versions unpublishes whole thing', t => {
const doc = {
_id: 'foo',
_rev: REV,
name: 'foo',
'dist-tags': {
latest: '1.0.0'
}
}
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')
})
})

test('packument with missing specific version assumed unpublished', t => {
const doc = {
_id: 'foo',
_rev: REV,
name: 'foo',
'dist-tags': {
latest: '1.0.0'
},
versions: {
'1.0.0': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.0.tgz`
}
}
}
}
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')
})
})

test('unpublish specific version without dist-tag update', t => {
const doc = {
_id: 'foo',
_rev: REV,
_revisions: [1, 2, 3],
_attachments: [1, 2, 3],
name: 'foo',
'dist-tags': {
latest: '1.0.0'
},
versions: {
'1.0.0': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.0.tgz`
}
},
'1.0.1': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.1.tgz`
}
}
}
}
const postEdit = {
_id: 'foo',
_rev: REV,
name: 'foo',
'dist-tags': {
latest: '1.0.0'
},
versions: {
'1.0.0': {
name: 'foo',
dist: {
tarball: `${REG}/foo/-/foo-1.0.0.tgz`
}
}
}
}
const srv = tnock(t, REG)
srv.get('/foo?write=true').reply(200, doc)
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')
})
})
12 changes: 12 additions & 0 deletions test/util/tnock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict'

const nock = require('nock')

module.exports = tnock
function tnock (t, host) {
const server = nock(host)
t.tearDown(function () {
server.done()
})
return server
}
82 changes: 82 additions & 0 deletions unpublish.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict'

const figgyPudding = require('figgy-pudding')
const npa = require('npm-package-arg')
const npmFetch = require('npm-registry-fetch')
const semver = require('semver')
const url = require('url')

const UnpublishConfig = figgyPudding({
force: { default: false },
Promise: { default: () => Promise }
})

module.exports = unpublish
function unpublish (spec, opts) {
opts = UnpublishConfig(opts)
spec = npa(spec)
const pkgUri = spec.escapedName
return npmFetch.json(pkgUri, opts.concat({
query: { write: true }
})).then(pkg => {
if (!spec.rawSpec || spec.rawSpec === '*') {
return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, opts.concat({
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.concat({
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]
}
})

if (latestVer === version) {
pkg['dist-tags'].latest = Object.keys(
allVersions
).sort(semver.compareLoose).pop()
}

delete pkg._revisions
delete pkg._attachments
// Update packument with removed versions
return npmFetch(`${pkgUri}/-rev/${pkg._rev}`, opts.concat({
method: 'PUT',
body: pkg,
ignoreBody: true
})).then(() => {
// Remove the tarball itself
return npmFetch.json(pkgUri, opts.concat({
query: { write: true }
})).then(({ _rev, _id }) => {
const tarballUrl = url.parse(dist.tarball).pathname.substr(1)
return npmFetch(`${tarballUrl}/-rev/${_rev}`, opts.concat({
method: 'DELETE',
ignoreBody: true
}))
})
})
}
}
}, err => {
if (err.code !== 'E404') {
throw err
}
})
}

0 comments on commit 1c9d594

Please sign in to comment.