diff --git a/sources/npmRegistryUtils.ts b/sources/npmRegistryUtils.ts index a7d110e95..2666ccd96 100644 --- a/sources/npmRegistryUtils.ts +++ b/sources/npmRegistryUtils.ts @@ -14,7 +14,10 @@ export const DEFAULT_HEADERS: Record = { export const DEFAULT_NPM_REGISTRY_URL = `https://registry.npmjs.org`; export async function fetchAsJson(packageName: string, version?: string) { - const npmRegistryUrl = process.env.COREPACK_NPM_REGISTRY || DEFAULT_NPM_REGISTRY_URL; + // Strip any trailing slashes so a `COREPACK_NPM_REGISTRY` with a trailing + // slash does not produce a double slash in the request URL (some registries, + // e.g. registry.npmmirror.com, reject `//package` with a 404). + const npmRegistryUrl = (process.env.COREPACK_NPM_REGISTRY || DEFAULT_NPM_REGISTRY_URL).replace(/\/+$/, ``); if (process.env.COREPACK_ENABLE_NETWORK === `0`) throw new UsageError(`Network access disabled by the environment; can't reach npm repository ${npmRegistryUrl}`); diff --git a/tests/npmRegistryUtils.test.ts b/tests/npmRegistryUtils.test.ts index 728d6f977..0ba9e8740 100644 --- a/tests/npmRegistryUtils.test.ts +++ b/tests/npmRegistryUtils.test.ts @@ -35,6 +35,15 @@ describe(`npm registry utils fetchAsJson`, () => { expect(httpFetchAsJson).lastCalledWith(`${process.env.COREPACK_NPM_REGISTRY}/package-name`, {headers: DEFAULT_HEADERS}); }); + it(`does not produce a double slash when COREPACK_NPM_REGISTRY has a trailing slash`, async () => { + // `process.env` is reset after each tests in setupTests.js. + process.env.COREPACK_NPM_REGISTRY = `https://registry.example.org/`; + await fetchAsJson(`package-name`); + + expect(httpFetchAsJson).toBeCalled(); + expect(httpFetchAsJson).lastCalledWith(`https://registry.example.org/package-name`, {headers: DEFAULT_HEADERS}); + }); + it(`adds authorization header with bearer token if COREPACK_NPM_TOKEN is set`, async () => { // `process.env` is reset after each tests in setupTests.js. process.env.COREPACK_NPM_TOKEN = `foo`;