Skip to content

Commit

Permalink
Rename npm-util.js and improve method naming (#374)
Browse files Browse the repository at this point in the history
  • Loading branch information
itaisteinherz authored and sindresorhus committed Mar 23, 2019
1 parent a880f84 commit 3c436cf
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 131 deletions.
2 changes: 1 addition & 1 deletion source/cli.js
Expand Up @@ -6,7 +6,7 @@ const logSymbols = require('log-symbols');
const meow = require('meow');
const updateNotifier = require('update-notifier');
const hasYarn = require('has-yarn');
const {isPackageNameAvailable} = require('./npm-util');
const {isPackageNameAvailable} = require('./npm/util');
const version = require('./version');
const util = require('./util');
const ui = require('./ui');
Expand Down
92 changes: 0 additions & 92 deletions source/npm-util.js

This file was deleted.

2 changes: 1 addition & 1 deletion source/npm/enable-2fa.js
Expand Up @@ -2,7 +2,7 @@
const execa = require('execa');
const {from} = require('rxjs');
const {catchError} = require('rxjs/operators');
const {handleNpmError} = require('./util');
const handleNpmError = require('./handle-npm-error');

const enable2fa = (packageName, options) => {
const args = ['access', '2fa-required', packageName];
Expand Down
29 changes: 29 additions & 0 deletions source/npm/handle-npm-error.js
@@ -0,0 +1,29 @@
const listrInput = require('listr-input');
const chalk = require('chalk');
const {throwError} = require('rxjs');
const {catchError} = require('rxjs/operators');

const handleNpmError = (error, task, message, executor) => {
if (typeof message === 'function') {
executor = message;
message = undefined;
}

if (error.stderr.includes('one-time pass') || error.message.includes('user TTY')) {
const {title} = task;
task.title = `${title} ${chalk.yellow('(waiting for input…)')}`;

return listrInput('Enter OTP:', {
done: otp => {
task.title = title;
return executor(otp);
}
}).pipe(
catchError(error => handleNpmError(error, task, 'OTP was incorrect, try again:', executor))
);
}

return throwError(error);
};

module.exports = handleNpmError;
2 changes: 1 addition & 1 deletion source/npm/publish.js
Expand Up @@ -2,7 +2,7 @@
const execa = require('execa');
const {from} = require('rxjs');
const {catchError} = require('rxjs/operators');
const {handleNpmError} = require('./util');
const handleNpmError = require('./handle-npm-error');

const pkgPublish = (pkgManager, options, input) => {
const args = ['publish'];
Expand Down
109 changes: 86 additions & 23 deletions source/npm/util.js
@@ -1,29 +1,92 @@
const listrInput = require('listr-input');
const chalk = require('chalk');
const {throwError} = require('rxjs');
const {catchError} = require('rxjs/operators');

const handleNpmError = (error, task, message, executor) => {
if (typeof message === 'function') {
executor = message;
message = undefined;
'use strict';
const execa = require('execa');
const pTimeout = require('p-timeout');
const ow = require('ow');
const npmName = require('npm-name');
const version = require('../version');

exports.checkConnection = () => pTimeout(
(async () => {
try {
await execa('npm', ['ping']);
return true;
} catch (_) {
throw new Error('Connection to npm registry failed');
}
})(),
15000,
'Connection to npm registry timed out'
);

exports.username = async ({externalRegistry}) => {
const args = ['whoami'];

if (externalRegistry) {
args.push('--registry', externalRegistry);
}

if (error.stderr.includes('one-time pass') || error.message.includes('user TTY')) {
const {title} = task;
task.title = `${title} ${chalk.yellow('(waiting for input…)')}`;

return listrInput('Enter OTP:', {
done: otp => {
task.title = title;
return executor(otp);
}
}).pipe(
catchError(error => handleNpmError(error, task, 'OTP was incorrect, try again:', executor))
);
try {
return await execa.stdout('npm', args);
} catch (error) {
throw new Error(/ENEEDAUTH/.test(error.stderr) ?
'You must be logged in. Use `npm login` and try again.' :
'Authentication error. Use `npm whoami` to troubleshoot.');
}
};

exports.collaborators = async packageName => {
ow(packageName, ow.string);

try {
return await execa.stdout('npm', ['access', 'ls-collaborators', packageName]);
} catch (error) {
// Ignore non-existing package error
if (error.stderr.includes('code E404')) {
return false;
}

return throwError(error);
throw error;
}
};

exports.handleNpmError = handleNpmError;
exports.prereleaseTags = async packageName => {
ow(packageName, ow.string);

let tags = [];
try {
const {stdout} = await execa('npm', ['view', '--json', packageName, 'dist-tags']);
tags = Object.keys(JSON.parse(stdout))
.filter(tag => tag !== 'latest');
} catch (error) {
if (((JSON.parse(error.stdout) || {}).error || {}).code !== 'E404') {
throw error;
}
}

if (tags.length === 0) {
tags.push('next');
}

return tags;
};

exports.isPackageNameAvailable = async pkg => {
const isExternalRegistry = exports.isExternalRegistry(pkg);
if (isExternalRegistry) {
return true;
}

return npmName(pkg.name);
};

exports.isExternalRegistry = pkg => typeof pkg.publishConfig === 'object' && typeof pkg.publishConfig.registry === 'string';

exports.version = () => execa.stdout('npm', ['--version']);

exports.verifyRecentNpmVersion = async () => {
const npmVersion = await exports.version();

if (version(npmVersion).satisfies('<6.8.0')) {
throw new Error('Please upgrade to npm@6.8.0 or newer');
}
};
2 changes: 1 addition & 1 deletion source/prerequisite-tasks.js
Expand Up @@ -3,7 +3,7 @@ const Listr = require('listr');
const execa = require('execa');
const version = require('./version');
const git = require('./git-util');
const npm = require('./npm-util');
const npm = require('./npm/util');
const {getTagVersionPrefix} = require('./util');

module.exports = (input, pkg, options) => {
Expand Down
6 changes: 3 additions & 3 deletions source/ui.js
Expand Up @@ -5,7 +5,7 @@ const githubUrlFromGit = require('github-url-from-git');
const isScoped = require('is-scoped');
const util = require('./util');
const git = require('./git-util');
const {prereleaseTags} = require('./npm-util');
const {prereleaseTags} = require('./npm/util');
const version = require('./version');
const prettyVersionDiff = require('./pretty-version-diff');

Expand Down Expand Up @@ -98,7 +98,7 @@ module.exports = async (options, pkg) => {
type: 'list',
name: 'tag',
message: 'How should this pre-release version be tagged in npm?',
when: answers => !pkg.private && version(answers.version).isPrerelease() && !options.tag,
when: answers => !pkg.private && version.isPrereleaseOrIncrement(answers.version) && !options.tag,
choices: async () => {
const existingPrereleaseTags = await prereleaseTags(pkg.name);

Expand All @@ -116,7 +116,7 @@ module.exports = async (options, pkg) => {
type: 'input',
name: 'tag',
message: 'Tag',
when: answers => !pkg.private && version(answers.version).isPrerelease() && !options.tag && !answers.tag,
when: answers => !pkg.private && version.isPrereleaseOrIncrement(answers.version) && !options.tag && !answers.tag,
validate: input => {
if (input.length === 0) {
return 'Please specify a tag, for example, `next`.';
Expand Down
4 changes: 3 additions & 1 deletion source/version.js
Expand Up @@ -7,7 +7,7 @@ class Version {
}

isPrerelease() {
return module.exports.PRERELEASE_VERSIONS.includes(this.version) || Boolean(semver.prerelease(this.version));
return Boolean(semver.prerelease(this.version));
}

satisfies(range) {
Expand Down Expand Up @@ -46,6 +46,8 @@ module.exports = version => new Version(version);
module.exports.SEMVER_INCREMENTS = ['patch', 'minor', 'major', 'prepatch', 'preminor', 'premajor', 'prerelease'];
module.exports.PRERELEASE_VERSIONS = ['prepatch', 'preminor', 'premajor', 'prerelease'];

module.exports.isPrereleaseOrIncrement = input => module.exports(input).isPrerelease() || module.exports.PRERELEASE_VERSIONS.includes(input);

const isValidVersion = input => Boolean(semver.valid(input));

module.exports.isValidInput = input => module.exports.SEMVER_INCREMENTS.includes(input) || isValidVersion(input);
Expand Down
19 changes: 11 additions & 8 deletions test/version.js
Expand Up @@ -29,22 +29,25 @@ test('version.isValidInput', t => {
});

test('version.isPrerelease', t => {
t.false(version('patch').isPrerelease());
t.false(version('minor').isPrerelease());
t.false(version('major').isPrerelease());

t.false(version('1.0.0').isPrerelease());
t.false(version('1.1.0').isPrerelease());
t.false(version('1.0.1').isPrerelease());

t.true(version('prepatch').isPrerelease());
t.true(version('preminor').isPrerelease());
t.true(version('premajor').isPrerelease());
t.true(version('prerelease').isPrerelease());
t.true(version('1.0.0-beta').isPrerelease());
t.true(version('2.0.0-rc.2').isPrerelease());
});

test('version.isPrereleaseOrIncrement', t => {
t.false(version.isPrereleaseOrIncrement('patch'));
t.false(version.isPrereleaseOrIncrement('minor'));
t.false(version.isPrereleaseOrIncrement('major'));

t.true(version.isPrereleaseOrIncrement('prepatch'));
t.true(version.isPrereleaseOrIncrement('preminor'));
t.true(version.isPrereleaseOrIncrement('premajor'));
t.true(version.isPrereleaseOrIncrement('prerelease'));
});

test('version.getNewVersionFrom', t => {
const message = 'Version should be either patch, minor, major, prepatch, preminor, premajor, prerelease or a valid semver version.';

Expand Down

0 comments on commit 3c436cf

Please sign in to comment.