Skip to content

Commit

Permalink
Add registryUrl option (#33)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
itaisteinherz and sindresorhus committed Jun 10, 2019
1 parent 519102a commit 7172737
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 12 deletions.
16 changes: 14 additions & 2 deletions index.d.ts
@@ -1,5 +1,16 @@
declare class InvalidNameErrorClass extends Error {}

declare namespace npmName {
interface Options {
/**
Registry URL to check name availability against.
Default: User's configured npm registry URL.
*/
readonly registryUrl: string;
}
}

declare const npmName: {
/**
Check whether a package name is available (not registered) on npm.
Expand All @@ -26,7 +37,7 @@ declare const npmName: {
})();
```
*/
(name: string): Promise<boolean>;
(name: string, options?: npmName.Options): Promise<boolean>;

/**
Check whether multiple package names are available (not registered) on npm.
Expand All @@ -50,7 +61,8 @@ declare const npmName: {
```
*/
many<NameType extends string>(
names: NameType[]
names: NameType[],
options?: npmName.Options
): Promise<Map<NameType, boolean>>;

InvalidNameError: typeof InvalidNameErrorClass;
Expand Down
28 changes: 21 additions & 7 deletions index.js
@@ -1,14 +1,17 @@
'use strict';
const isUrl = require('is-url-superb');
const got = require('got');
const isScoped = require('is-scoped');
const registryUrl = require('registry-url')();
const configuredRegistryUrl = require('registry-url')();
const registryAuthToken = require('registry-auth-token');
const zip = require('lodash.zip');
const validate = require('validate-npm-package-name');

class InvalidNameError extends Error {}

const request = async name => {
const request = async (name, options) => {
const registryUrl = normalizeUrl(options.registryUrl || configuredRegistryUrl);

const isValid = validate(name);
if (!isValid.validForNewPackages) {
const notices = [...isValid.warnings || [], ...isValid.errors || []].map(v => `- ${v}`);
Expand Down Expand Up @@ -46,24 +49,35 @@ const request = async name => {
}
};

const npmName = name => {
// Ensure the URL always ends in a `/`
const normalizeUrl = url => url.replace(/\/$/, '') + '/';

const npmName = async (name, options = {}) => {
if (!(typeof name === 'string' && name.length > 0)) {
throw new Error('Package name required');
}

return request(name);
if (typeof options.registryUrl !== 'undefined' && !(typeof options.registryUrl === 'string' && isUrl(options.registryUrl))) {
throw new Error('The `registryUrl` option must be a valid string URL');
}

return request(name, options);
};

module.exports = npmName;
// TODO: remove this in the next major version
module.exports.default = npmName;

module.exports.many = async names => {
module.exports.many = async (names, options = {}) => {
if (!Array.isArray(names)) {
throw new TypeError(`Expected an array, got ${typeof names}`);
throw new TypeError(`Expected an array of names, got ${typeof names}`);
}

if (typeof options.registryUrl !== 'undefined' && !(typeof options.registryUrl === 'string' && isUrl(options.registryUrl))) {
throw new Error('The `registryUrl` option must be a valid string URL');
}

const result = await Promise.all(names.map(request));
const result = await Promise.all(names.map(name => request(name, options)));
return new Map(zip(names, result));
};

Expand Down
3 changes: 3 additions & 0 deletions index.test-d.ts
Expand Up @@ -3,6 +3,9 @@ import npmName = require('.');
import {InvalidNameError} from '.';

expectType<Promise<boolean>>(npmName('chalk'));
expectType<Promise<boolean>>(npmName('got', {
registryUrl: 'https://registry.yarnpkg.com/'
}));

const manyResult = npmName.many(['chalk', '@sindresorhus/is', 'abc123']);
expectType<Promise<Map<'chalk' | '@sindresorhus/is' | 'abc123', boolean>>>(
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -32,6 +32,7 @@
"dependencies": {
"got": "^9.6.0",
"is-scoped": "^1.0.0",
"is-url-superb": "^3.0.0",
"lodash.zip": "^4.2.0",
"registry-auth-token": "^3.4.0",
"registry-url": "^4.0.0",
Expand Down
22 changes: 20 additions & 2 deletions readme.md
Expand Up @@ -41,7 +41,7 @@ const npmName = require('npm-name');

## API

### npmName(name)
### npmName(name, options?)

Returns a `Promise<boolean>` of whether the given name is available.

Expand All @@ -51,7 +51,19 @@ Type: `string`

Name to check.

### npmName.many(names)
#### options

Type: `object`

##### registryUrl

Default: User's configured npm registry URL.

Registry URL to check name availability against.

**Note:** You're unlikely to need this option. Most use-cases are best solved by using the default. You should only use this option if you need to check a package name against a specific registry.

### npmName.many(names, options?)

Returns a `Promise<Map>` of name and status.

Expand All @@ -61,6 +73,12 @@ Type: `string[]`

Multiple names to check.

#### options

Type: `object`

Same as `npmName()`.


## Related

Expand Down
21 changes: 20 additions & 1 deletion test.js
Expand Up @@ -2,13 +2,30 @@ import test from 'ava';
import uniqueString from 'unique-string';
import npmName from '.';

const registryUrl = 'https://registry.yarnpkg.com/';
const options = {registryUrl};

test('returns true when package name is available', async t => {
t.true(await npmName(uniqueString()));
const moduleName = uniqueString();

t.true(await npmName(moduleName));
t.true(await npmName(moduleName, options));
await t.throwsAsync(npmName(moduleName, {registryUrl: null}));
});

test('returns false when package name is taken', async t => {
t.false(await npmName('chalk'));
t.false(await npmName('recursive-readdir'));
t.false(await npmName('np', options));
});

test('registry url is normalized', async t => {
const moduleName = uniqueString();

t.true(await npmName(moduleName, options));
t.true(await npmName(moduleName, {
registryUrl: registryUrl.slice(0, -1) // The `.slice()` removes the trailing `/` from the URL
}));
});

test('returns a map of multiple package names', async t => {
Expand All @@ -17,6 +34,8 @@ test('returns a map of multiple package names', async t => {
const res = await npmName.many([name1, name2]);
t.false(res.get(name1));
t.true(res.get(name2));

await t.throwsAsync(npmName.many([name1, name2], {registryUrl: null}));
});

test('returns true when scoped package name is not taken', async t => {
Expand Down

0 comments on commit 7172737

Please sign in to comment.