diff --git a/lib/commands/publish.js b/lib/commands/publish.js index c59588fefb241..f6006f24aed55 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -157,12 +157,17 @@ class Publish extends BaseCommand { } } - const latestVersion = await this.#latestPublishedVersion(resolved, registry) + const newVersion = manifest.version + const { latest: latestVersion, versions } = await this.#registryVersions(resolved, registry) const latestSemverIsGreater = !!latestVersion && semver.gte(latestVersion, manifest.version) + if (versions.includes(newVersion)) { + throw new Error(`You cannot publish over the previously published versions: ${newVersion}.`) + } + if (latestSemverIsGreater && isDefaultTag) { /* eslint-disable-next-line max-len */ - 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.`) + throw new Error(`Cannot implicitly apply the "latest" tag because published version ${latestVersion} is higher than the new version ${newVersion}. You must specify a tag using --tag.`) } const access = opts.access === null ? 'default' : opts.access @@ -204,7 +209,7 @@ class Publish extends BaseCommand { } } - async #latestPublishedVersion (spec, registry) { + async #registryVersions (spec, registry) { try { const packument = await pacote.packument(spec, { ...this.npm.flatOptions, @@ -212,7 +217,7 @@ class Publish extends BaseCommand { registry, }) if (typeof packument?.versions === 'undefined') { - return null + return { versions: [], latest: null } } const ordered = Object.keys(packument?.versions) .flatMap(v => { @@ -220,9 +225,11 @@ class Publish extends BaseCommand { return s.prerelease.length > 0 ? [] : s }) .sort((a, b) => b.compare(a)) - return ordered.length >= 1 ? ordered[0].version : null + const latest = ordered.length >= 1 ? ordered[0].version : null + const versions = ordered.map(v => v.version) + return { versions, latest } } catch (e) { - return null + return { versions: [], latest: null } } } diff --git a/test/lib/commands/publish.js b/test/lib/commands/publish.js index 10dc9b33deda4..03588c47c73b1 100644 --- a/test/lib/commands/publish.js +++ b/test/lib/commands/publish.js @@ -889,6 +889,24 @@ t.test('latest dist tag', (t) => { }, 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.')) }) + t.test('PREVENTS publish when latest version is SAME AS publishing version', async t => { + const version = '100.0.0' + const { npm, registry } = await loadNpmWithRegistry(t, init(version)) + registry.publish(pkg, { noPut: true, packuments }) + await t.rejects(async () => { + await npm.exec('publish', []) + }, new Error('You cannot publish over the previously published versions: 100.0.0.')) + }) + + t.test('PREVENTS publish when publishing version EXISTS ALREADY in the registry', async t => { + const version = '50.0.0' + const { npm, registry } = await loadNpmWithRegistry(t, init(version)) + registry.publish(pkg, { noPut: true, packuments }) + await t.rejects(async () => { + await npm.exec('publish', []) + }, new Error('You cannot publish over the previously published versions: 50.0.0.')) + }) + t.test('ALLOWS publish when latest is HIGHER than publishing version and flag', async t => { const version = '99.0.0' const { npm, registry } = await loadNpmWithRegistry(t, {