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

3 files changed

Lines changed: 85 additions & 22 deletions

File tree

‎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)