Skip to content

Commit 31455b2

Browse files
authored
fix(publish): honor force for no dist tag and registry version check (#8054)
Merges #7993 / #7994 / #7995 - [x] adds ability to --force publish without latest check - [x] adds ability to --force publish of prerelease without tag - [x] consider equality in publish dist tag check error message
1 parent 7ddfbad commit 31455b2

File tree

3 files changed

+85
-22
lines changed

3 files changed

+85
-22
lines changed

lib/commands/publish.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,14 @@ class Publish extends BaseCommand {
114114
// so that we send the latest and greatest thing to the registry
115115
// note that publishConfig might have changed as well!
116116
manifest = await this.#getManifest(spec, opts, true)
117-
118-
const isPreRelease = Boolean(semver.parse(manifest.version).prerelease.length)
117+
const force = this.npm.config.get('force')
119118
const isDefaultTag = this.npm.config.isDefault('tag') && !manifest.publishConfig?.tag
120119

121-
if (isPreRelease && isDefaultTag) {
122-
throw new Error('You must specify a tag using --tag when publishing a prerelease version.')
120+
if (!force) {
121+
const isPreRelease = Boolean(semver.parse(manifest.version).prerelease.length)
122+
if (isPreRelease && isDefaultTag) {
123+
throw new Error('You must specify a tag using --tag when publishing a prerelease version.')
124+
}
123125
}
124126

125127
// If we are not in JSON mode then we show the user the contents of the tarball
@@ -156,11 +158,18 @@ class Publish extends BaseCommand {
156158
}
157159
}
158160

159-
const latestVersion = await this.#highestPublishedVersion(resolved, registry)
160-
const latestSemverIsGreater = !!latestVersion && semver.gte(latestVersion, manifest.version)
161+
if (!force) {
162+
const { highestVersion, versions } = await this.#registryVersions(resolved, registry)
163+
/* eslint-disable-next-line max-len */
164+
const highestVersionIsGreater = !!highestVersion && semver.gte(highestVersion, manifest.version)
161165

162-
if (latestSemverIsGreater && isDefaultTag) {
163-
throw new Error(`Cannot implicitly apply the "latest" tag because published version ${latestVersion} is higher than the new version ${manifest.version}. You must specify a tag using --tag.`)
166+
if (versions.includes(manifest.version)) {
167+
throw new Error(`You cannot publish over the previously published versions: ${manifest.version}.`)
168+
}
169+
170+
if (highestVersionIsGreater && isDefaultTag) {
171+
throw new Error(`Cannot implicitly apply the "latest" tag because previously published version ${highestVersion} is higher than the new version ${manifest.version}. You must specify a tag using --tag.`)
172+
}
164173
}
165174

166175
const access = opts.access === null ? 'default' : opts.access
@@ -202,15 +211,15 @@ class Publish extends BaseCommand {
202211
}
203212
}
204213

205-
async #highestPublishedVersion (spec, registry) {
214+
async #registryVersions (spec, registry) {
206215
try {
207216
const packument = await pacote.packument(spec, {
208217
...this.npm.flatOptions,
209218
preferOnline: true,
210219
registry,
211220
})
212221
if (typeof packument?.versions === 'undefined') {
213-
return null
222+
return { versions: [], highestVersion: null }
214223
}
215224
const ordered = Object.keys(packument?.versions)
216225
.flatMap(v => {
@@ -221,9 +230,11 @@ class Publish extends BaseCommand {
221230
return s
222231
})
223232
.sort((a, b) => b.compare(a))
224-
return ordered.length >= 1 ? ordered[0].version : null
233+
const highestVersion = ordered.length >= 1 ? ordered[0].version : null
234+
const versions = ordered.map(v => v.version)
235+
return { versions, highestVersion }
225236
} catch (e) {
226-
return null
237+
return { versions: [], highestVersion: null }
227238
}
228239
}
229240

mock-registry/lib/index.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -359,16 +359,18 @@ class MockRegistry {
359359
}
360360

361361
publish (name, {
362-
packageJson, access, noPut, putCode, manifest, packuments,
362+
packageJson, access, noGet, noPut, putCode, manifest, packuments,
363363
} = {}) {
364-
// this getPackage call is used to get the latest semver version before publish
365-
if (manifest) {
366-
this.getPackage(name, { code: 200, resp: manifest })
367-
} else if (packuments) {
368-
this.getPackage(name, { code: 200, resp: this.manifest({ name, packuments }) })
369-
} else {
370-
// assumes the package does not exist yet and will 404 x2 from pacote.manifest
371-
this.getPackage(name, { times: 2, code: 404 })
364+
if (!noGet) {
365+
// this getPackage call is used to get the latest semver version before publish
366+
if (manifest) {
367+
this.getPackage(name, { code: 200, resp: manifest })
368+
} else if (packuments) {
369+
this.getPackage(name, { code: 200, resp: this.manifest({ name, packuments }) })
370+
} else {
371+
// assumes the package does not exist yet and will 404 x2 from pacote.manifest
372+
this.getPackage(name, { times: 2, code: 404 })
373+
}
372374
}
373375
if (!noPut) {
374376
this.putPackage(name, { code: putCode, packageJson, access })

test/lib/commands/publish.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,28 @@ t.test('prerelease dist tag', (t) => {
853853
await npm.exec('publish', [])
854854
})
855855

856+
t.test('does not abort when prerelease and force', async t => {
857+
const packageJson = {
858+
...pkgJson,
859+
version: '1.0.0-0',
860+
publishConfig: { registry: alternateRegistry },
861+
}
862+
const { npm, registry } = await loadNpmWithRegistry(t, {
863+
config: {
864+
loglevel: 'silent',
865+
force: true,
866+
[`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token',
867+
},
868+
prefixDir: {
869+
'package.json': JSON.stringify(packageJson, null, 2),
870+
},
871+
registry: alternateRegistry,
872+
authorization: 'test-other-token',
873+
})
874+
registry.publish(pkg, { noGet: true, packageJson })
875+
await npm.exec('publish', [])
876+
})
877+
856878
t.end()
857879
})
858880

@@ -886,7 +908,7 @@ t.test('semver highest dist tag', async t => {
886908
registry.publish(pkg, { noPut: true, packuments })
887909
await t.rejects(async () => {
888910
await npm.exec('publish', [])
889-
}, new Error('Cannot implicitly apply the "latest" tag because published version 100.0.0 is higher than the new version 99.0.0. You must specify a tag using --tag.'))
911+
}, new Error('Cannot implicitly apply the "latest" tag because previously published version 100.0.0 is higher than the new version 99.0.0. You must specify a tag using --tag.'))
890912
})
891913

892914
await t.test('ALLOWS publish when highest is HIGHER than publishing version and flag', async t => {
@@ -933,4 +955,32 @@ t.test('semver highest dist tag', async t => {
933955
registry.publish(pkg, { packuments })
934956
await npm.exec('publish', [])
935957
})
958+
959+
await t.test('PREVENTS publish when latest version is SAME AS publishing version', async t => {
960+
const version = '100.0.0'
961+
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
962+
registry.publish(pkg, { noPut: true, packuments })
963+
await t.rejects(async () => {
964+
await npm.exec('publish', [])
965+
}, new Error('You cannot publish over the previously published versions: 100.0.0.'))
966+
})
967+
968+
await t.test('PREVENTS publish when publishing version EXISTS ALREADY in the registry', async t => {
969+
const version = '50.0.0'
970+
const { npm, registry } = await loadNpmWithRegistry(t, init({ version }))
971+
registry.publish(pkg, { noPut: true, packuments })
972+
await t.rejects(async () => {
973+
await npm.exec('publish', [])
974+
}, new Error('You cannot publish over the previously published versions: 50.0.0.'))
975+
})
976+
977+
await t.test('ALLOWS publish when latest is HIGHER than publishing version and flag --force', async t => {
978+
const version = '99.0.0'
979+
const { npm, registry } = await loadNpmWithRegistry(t, {
980+
...init({ version }),
981+
argv: ['--force'],
982+
})
983+
registry.publish(pkg, { noGet: true, packuments })
984+
await npm.exec('publish', [])
985+
})
936986
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy