-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for configurable registries and applicable auth opt…
- Loading branch information
Showing
4 changed files
with
149 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import {UsageError} from 'clipanion'; | ||
import {OutgoingHttpHeaders} from 'http2'; | ||
|
||
import * as httpUtils from './httpUtils'; | ||
|
||
// load abbreviated metadata as that's all we need for these calls | ||
// see: https://github.com/npm/registry/blob/cfe04736f34db9274a780184d1cdb2fb3e4ead2a/docs/responses/package-metadata.md | ||
export const DEFAULT_HEADERS: OutgoingHttpHeaders = { | ||
[`Accept`]: `application/vnd.npm.install-v1+json`, | ||
}; | ||
export const DEFAULT_NPM_REGISTRY_URL = `https://registry.npmjs.org`; | ||
|
||
export async function fetchAsJson(packageName: string) { | ||
const npmRegistryUrl = process.env.COREPACK_NPM_REGISTRY || DEFAULT_NPM_REGISTRY_URL; | ||
|
||
if (process.env.COREPACK_ENABLE_NETWORK === `0`) | ||
throw new UsageError(`Network access disabled by the environment; can't reach npm repository ${npmRegistryUrl}`); | ||
|
||
const headers = {...DEFAULT_HEADERS}; | ||
|
||
if (`COREPACK_NPM_TOKEN` in process.env) { | ||
headers.authorization = `Bearer ${process.env.COREPACK_NPM_TOKEN}`; | ||
} else if (`COREPACK_NPM_USERNAME` in process.env | ||
&& `COREPACK_NPM_PASSWORD` in process.env) { | ||
const encodedCreds = Buffer.from(`${process.env.COREPACK_NPM_USERNAME}:${process.env.COREPACK_NPM_PASSWORD}`, `utf8`).toString(`base64`); | ||
headers.authorization = `Basic ${encodedCreds}`; | ||
} | ||
|
||
return httpUtils.fetchAsJson(`${npmRegistryUrl}/${packageName}`, {headers}); | ||
} | ||
|
||
export async function fetchLatestStableVersion(packageName: string) { | ||
const metadata = await fetchAsJson(packageName); | ||
const {latest} = metadata[`dist-tags`]; | ||
if (latest === undefined) throw new Error(`${packageName} does not have a "latest" tag.`); | ||
|
||
const {shasum} = metadata.versions[latest].dist; | ||
|
||
return `${latest}+sha1.${shasum}`; | ||
} | ||
|
||
export async function fetchAvailableTags(packageName: string) { | ||
const metadata = await fetchAsJson(packageName); | ||
return metadata[`dist-tags`]; | ||
} | ||
|
||
export async function fetchAvailableVersions(packageName: string) { | ||
const metadata = await fetchAsJson(packageName); | ||
return Object.keys(metadata.versions); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import {fetchAsJson as httpFetchAsJson} from '../sources/httpUtils'; | ||
import {DEFAULT_HEADERS, DEFAULT_NPM_REGISTRY_URL, fetchAsJson} from '../sources/npmRegistryUtils'; | ||
|
||
jest.mock(`../sources/httpUtils`); | ||
|
||
describe(`npm registry utils fetchAsJson`, () => { | ||
const OLD_ENV = process.env; | ||
|
||
beforeEach(() => { | ||
process.env = {...OLD_ENV}; // Make a copy | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
afterEach(() => { | ||
process.env = OLD_ENV; // Restore old environment | ||
}); | ||
|
||
it(`throw usage error if COREPACK_ENABLE_NETWORK env is set to 0`, async () => { | ||
process.env.COREPACK_ENABLE_NETWORK = `0`; | ||
|
||
await expect(fetchAsJson(`package-name`)).rejects.toThrowError(); | ||
}); | ||
|
||
it(`loads from DEFAULT_NPM_REGISTRY_URL by default`, async () => { | ||
await fetchAsJson(`package-name`); | ||
|
||
expect(httpFetchAsJson).toBeCalled(); | ||
expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: DEFAULT_HEADERS}); | ||
}); | ||
|
||
it(`loads from custom COREPACK_NPM_REGISTRY if set`, async () => { | ||
process.env.COREPACK_NPM_REGISTRY = `https://registry.example.org`; | ||
await fetchAsJson(`package-name`); | ||
|
||
expect(httpFetchAsJson).toBeCalled(); | ||
expect(httpFetchAsJson).lastCalledWith(`${process.env.COREPACK_NPM_REGISTRY}/package-name`, {headers: DEFAULT_HEADERS}); | ||
}); | ||
|
||
it(`adds authorization header with bearer token if COREPACK_NPM_TOKEN is set`, async () => { | ||
process.env.COREPACK_NPM_TOKEN = `foo`; | ||
|
||
await fetchAsJson(`package-name`); | ||
|
||
expect(httpFetchAsJson).toBeCalled(); | ||
expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: { | ||
...DEFAULT_HEADERS, | ||
authorization: `Bearer ${process.env.COREPACK_NPM_TOKEN}`, | ||
}}); | ||
}); | ||
|
||
it(`only adds authorization header with bearer token if COREPACK_NPM_TOKEN and COREPACK_NPM_USERNAME are set`, async () => { | ||
process.env.COREPACK_NPM_TOKEN = `foo`; | ||
process.env.COREPACK_NPM_USERNAME = `bar`; | ||
process.env.COREPACK_NPM_PASSWORD = `foobar`; | ||
|
||
await fetchAsJson(`package-name`); | ||
|
||
expect(httpFetchAsJson).toBeCalled(); | ||
expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: { | ||
...DEFAULT_HEADERS, | ||
authorization: `Bearer ${process.env.COREPACK_NPM_TOKEN}`, | ||
}}); | ||
}); | ||
|
||
|
||
it(`adds authorization header with basic auth if COREPACK_NPM_USERNAME and COREPACK_NPM_PASSWORD are set`, async () => { | ||
process.env.COREPACK_NPM_USERNAME = `foo`; | ||
process.env.COREPACK_NPM_PASSWORD = `bar`; | ||
|
||
const encodedCreds = Buffer.from(`${process.env.COREPACK_NPM_USERNAME}:${process.env.COREPACK_NPM_PASSWORD}`, `utf8`).toString(`base64`); | ||
|
||
await fetchAsJson(`package-name`); | ||
|
||
expect(httpFetchAsJson).toBeCalled(); | ||
expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: { | ||
...DEFAULT_HEADERS, | ||
authorization: `Basic ${encodedCreds}`, | ||
}}); | ||
}); | ||
|
||
it(`does not add authorization header if COREPACK_NPM_USERNAME is set and COREPACK_NPM_PASSWORD is not.`, async () => { | ||
process.env.COREPACK_NPM_USERNAME = `foo`; | ||
|
||
await fetchAsJson(`package-name`); | ||
|
||
expect(httpFetchAsJson).toBeCalled(); | ||
expect(httpFetchAsJson).lastCalledWith(`${DEFAULT_NPM_REGISTRY_URL}/package-name`, {headers: DEFAULT_HEADERS}); | ||
}); | ||
}); |