From 377a5afc78bd54fc9046a324af4b4e569a333890 Mon Sep 17 00:00:00 2001 From: reggi Date: Tue, 21 Jan 2025 17:10:32 -0500 Subject: [PATCH 1/5] fix: publish honor force for no dist tag and registry version check --- lib/commands/publish.js | 35 +++++++++++++++--------- mock-registry/lib/index.js | 20 +++++++------- test/lib/commands/publish.js | 52 +++++++++++++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 22 deletions(-) diff --git a/lib/commands/publish.js b/lib/commands/publish.js index 764a3c15c9999..6fbf2bc9f40bf 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -114,12 +114,14 @@ class Publish extends BaseCommand { // so that we send the latest and greatest thing to the registry // note that publishConfig might have changed as well! manifest = await this.#getManifest(spec, opts, true) - - const isPreRelease = Boolean(semver.parse(manifest.version).prerelease.length) + const force = this.npm.config.get('force') const isDefaultTag = this.npm.config.isDefault('tag') && !manifest.publishConfig?.tag - if (isPreRelease && isDefaultTag) { - throw new Error('You must specify a tag using --tag when publishing a prerelease version.') + if (!force) { + const isPreRelease = Boolean(semver.parse(manifest.version).prerelease.length) + if (isPreRelease && isDefaultTag) { + throw new Error('You must specify a tag using --tag when publishing a prerelease version.') + } } // 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 { } } - const latestVersion = await this.#highestPublishedVersion(resolved, registry) - const latestSemverIsGreater = !!latestVersion && semver.gte(latestVersion, manifest.version) + if (!force) { + const { latest: latestVersion, versions } = await this.#registryVersions(resolved, registry) + const latestSemverIsGreater = !!latestVersion && semver.gte(latestVersion, manifest.version) - if (latestSemverIsGreater && isDefaultTag) { - 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.`) + if (versions.includes(manifest.version)) { + throw new Error(`You cannot publish over the previously published versions: ${manifest.version}.`) + } + + if (latestSemverIsGreater && isDefaultTag) { + /* eslint-disable-next-line max-len */ + throw new Error(`Cannot implicitly apply the "latest" tag because previously published version ${latestVersion} is higher than the new version ${manifest.version}. You must specify a tag using --tag.`) + } } const access = opts.access === null ? 'default' : opts.access @@ -202,7 +211,7 @@ class Publish extends BaseCommand { } } - async #highestPublishedVersion (spec, registry) { + async #registryVersions (spec, registry) { try { const packument = await pacote.packument(spec, { ...this.npm.flatOptions, @@ -210,7 +219,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 => { @@ -221,9 +230,11 @@ class Publish extends BaseCommand { return 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/mock-registry/lib/index.js b/mock-registry/lib/index.js index 3b06681b7ed31..8248631519054 100644 --- a/mock-registry/lib/index.js +++ b/mock-registry/lib/index.js @@ -359,16 +359,18 @@ class MockRegistry { } publish (name, { - packageJson, access, noPut, putCode, manifest, packuments, + packageJson, access, noGet, noPut, putCode, manifest, packuments, } = {}) { - // this getPackage call is used to get the latest semver version before publish - if (manifest) { - this.getPackage(name, { code: 200, resp: manifest }) - } else if (packuments) { - this.getPackage(name, { code: 200, resp: this.manifest({ name, packuments }) }) - } else { - // assumes the package does not exist yet and will 404 x2 from pacote.manifest - this.getPackage(name, { times: 2, code: 404 }) + if (!noGet) { + // this getPackage call is used to get the latest semver version before publish + if (manifest) { + this.getPackage(name, { code: 200, resp: manifest }) + } else if (packuments) { + this.getPackage(name, { code: 200, resp: this.manifest({ name, packuments }) }) + } else { + // assumes the package does not exist yet and will 404 x2 from pacote.manifest + this.getPackage(name, { times: 2, code: 404 }) + } } if (!noPut) { this.putPackage(name, { code: putCode, packageJson, access }) diff --git a/test/lib/commands/publish.js b/test/lib/commands/publish.js index cd5c1bdad6aa3..f310587418b1f 100644 --- a/test/lib/commands/publish.js +++ b/test/lib/commands/publish.js @@ -853,6 +853,28 @@ t.test('prerelease dist tag', (t) => { await npm.exec('publish', []) }) + t.test('does not abort when prerelease and force', async t => { + const packageJson = { + ...pkgJson, + version: '1.0.0-0', + publishConfig: { registry: alternateRegistry }, + } + const { npm, registry } = await loadNpmWithRegistry(t, { + config: { + loglevel: 'silent', + force: true, + [`${alternateRegistry.slice(6)}/:_authToken`]: 'test-other-token', + }, + prefixDir: { + 'package.json': JSON.stringify(packageJson, null, 2), + }, + registry: alternateRegistry, + authorization: 'test-other-token', + }) + registry.publish(pkg, { noGet: true, packageJson }) + await npm.exec('publish', []) + }) + t.end() }) @@ -886,7 +908,7 @@ t.test('semver highest dist tag', async t => { registry.publish(pkg, { noPut: true, packuments }) await t.rejects(async () => { await npm.exec('publish', []) - }, 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.')) + }, 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.')) }) 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 => { registry.publish(pkg, { packuments }) await npm.exec('publish', []) }) + + await 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.')) + }) + + await 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.')) + }) + + await t.test('ALLOWS publish when latest is HIGHER than publishing version and flag --force', async t => { + const version = '99.0.0' + const { npm, registry } = await loadNpmWithRegistry(t, { + ...init({ version }), + argv: ['--force'], + }) + registry.publish(pkg, { noGet: true, packuments }) + await npm.exec('publish', []) + }) }) From f850fe29964fec506732e8930f75e0e760f3cc4d Mon Sep 17 00:00:00 2001 From: reggi Date: Wed, 22 Jan 2025 10:51:25 -0500 Subject: [PATCH 2/5] use highestVersion --- lib/commands/publish.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/commands/publish.js b/lib/commands/publish.js index 6fbf2bc9f40bf..fc8d0093745c9 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -159,8 +159,8 @@ class Publish extends BaseCommand { } if (!force) { - const { latest: latestVersion, versions } = await this.#registryVersions(resolved, registry) - const latestSemverIsGreater = !!latestVersion && semver.gte(latestVersion, manifest.version) + const { highestVersion, versions } = await this.#registryVersions(resolved, registry) + const latestSemverIsGreater = !!highestVersion && semver.gte(highestVersion, manifest.version) if (versions.includes(manifest.version)) { throw new Error(`You cannot publish over the previously published versions: ${manifest.version}.`) @@ -168,7 +168,7 @@ class Publish extends BaseCommand { if (latestSemverIsGreater && isDefaultTag) { /* eslint-disable-next-line max-len */ - throw new Error(`Cannot implicitly apply the "latest" tag because previously 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 previously published version ${highestVersion} is higher than the new version ${manifest.version}. You must specify a tag using --tag.`) } } @@ -230,9 +230,9 @@ class Publish extends BaseCommand { return s }) .sort((a, b) => b.compare(a)) - const latest = ordered.length >= 1 ? ordered[0].version : null + const highestVersion = ordered.length >= 1 ? ordered[0].version : null const versions = ordered.map(v => v.version) - return { versions, latest } + return { versions, highestVersion } } catch (e) { return { versions: [], latest: null } } From 3b19e637a4b5453f3d82ccf58ae6e50b1ad7624a Mon Sep 17 00:00:00 2001 From: reggi Date: Wed, 22 Jan 2025 10:53:16 -0500 Subject: [PATCH 3/5] fix highestVersion --- lib/commands/publish.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commands/publish.js b/lib/commands/publish.js index fc8d0093745c9..617a5503c012f 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -219,7 +219,7 @@ class Publish extends BaseCommand { registry, }) if (typeof packument?.versions === 'undefined') { - return { versions: [], latest: null } + return { versions: [], highestVersion: null } } const ordered = Object.keys(packument?.versions) .flatMap(v => { @@ -234,7 +234,7 @@ class Publish extends BaseCommand { const versions = ordered.map(v => v.version) return { versions, highestVersion } } catch (e) { - return { versions: [], latest: null } + return { versions: [], highestVersion: null } } } From 17910571ac9824e785428e07f4844585de79724b Mon Sep 17 00:00:00 2001 From: reggi Date: Wed, 22 Jan 2025 11:01:50 -0500 Subject: [PATCH 4/5] rm latest --- lib/commands/publish.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/commands/publish.js b/lib/commands/publish.js index 617a5503c012f..4e356ed58a97e 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -160,14 +160,13 @@ class Publish extends BaseCommand { if (!force) { const { highestVersion, versions } = await this.#registryVersions(resolved, registry) - const latestSemverIsGreater = !!highestVersion && semver.gte(highestVersion, manifest.version) + const highestVersionIsGreater = !!highestVersion && semver.gte(highestVersion, manifest.version) if (versions.includes(manifest.version)) { throw new Error(`You cannot publish over the previously published versions: ${manifest.version}.`) } - if (latestSemverIsGreater && isDefaultTag) { - /* eslint-disable-next-line max-len */ + if (highestVersionIsGreater && isDefaultTag) { 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.`) } } From 547e02d56b6214e17fffb5d383ad3d551dbd2bed Mon Sep 17 00:00:00 2001 From: reggi Date: Wed, 22 Jan 2025 11:09:14 -0500 Subject: [PATCH 5/5] scoffs --- lib/commands/publish.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/commands/publish.js b/lib/commands/publish.js index 4e356ed58a97e..1967e05a23534 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -160,6 +160,7 @@ class Publish extends BaseCommand { if (!force) { const { highestVersion, versions } = await this.#registryVersions(resolved, registry) + /* eslint-disable-next-line max-len */ const highestVersionIsGreater = !!highestVersion && semver.gte(highestVersion, manifest.version) if (versions.includes(manifest.version)) {