smooth-release
Version:
Smart CLI tool to safely and automatically do every step to release a new version of a library hosted on GitHub and published on npm
552 lines (427 loc) • 16.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _lodash = require('lodash');
var _utils = require('../utils');
var _config = require('../config');
var _config2 = _interopRequireDefault(_config);
var _token = require('../github/token');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var validateBranch = function () {
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
if (!(_config2.default.publish.branch !== null)) {
_context.next = 5;
break;
}
_utils.status.addSteps(['Validate branch']);
if (!((0, _utils.getCurrentBranch)() !== _config2.default.publish.branch)) {
_context.next = 4;
break;
}
throw new _utils.SmoothReleaseError('You must be on "' + _config2.default.publish.branch + '" branch to perform this task. Aborting.');
case 4:
_utils.status.doneStep(true);
case 5:
case 'end':
return _context.stop();
}
}
}, _callee, undefined);
}));
return function validateBranch() {
return _ref.apply(this, arguments);
};
}();
var validateNoUncommittedChanges = function () {
var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() {
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
if (!_config2.default.publish.noUncommittedChanges) {
_context2.next = 9;
break;
}
_utils.status.addSteps(['Validate uncommitted changes']);
_context2.t0 = /^([ADRM]| [ADRM])/m;
_context2.next = 5;
return (0, _utils.exec)('git status --porcelain');
case 5:
_context2.t1 = _context2.sent;
if (!_context2.t0.test.call(_context2.t0, _context2.t1)) {
_context2.next = 8;
break;
}
throw new _utils.SmoothReleaseError('You have uncommited changes in your working tree. Aborting.');
case 8:
_utils.status.doneStep(true);
case 9:
case 'end':
return _context2.stop();
}
}
}, _callee2, undefined);
}));
return function validateNoUncommittedChanges() {
return _ref2.apply(this, arguments);
};
}();
var validateNoUntrackedFiles = function () {
var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
return _regenerator2.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
if (!_config2.default.publish.noUntrackedFiles) {
_context3.next = 9;
break;
}
_utils.status.addSteps(['Validate untracked files']);
_context3.t0 = /^\?\?/m;
_context3.next = 5;
return (0, _utils.exec)('git status --porcelain');
case 5:
_context3.t1 = _context3.sent;
if (!_context3.t0.test.call(_context3.t0, _context3.t1)) {
_context3.next = 8;
break;
}
throw new _utils.SmoothReleaseError('You have untracked files in your working tree. Aborting.');
case 8:
_utils.status.doneStep(true);
case 9:
case 'end':
return _context3.stop();
}
}
}, _callee3, undefined);
}));
return function validateNoUntrackedFiles() {
return _ref3.apply(this, arguments);
};
}();
var validateInSyncWithRemote = function () {
var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4() {
var LOCAL, REMOTE, BASE;
return _regenerator2.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
if (!_config2.default.publish.inSyncWithRemote) {
_context4.next = 24;
break;
}
_utils.status.addSteps(['Validate sync with remote']);
_context4.next = 4;
return (0, _utils.exec)('git fetch');
case 4:
_context4.next = 6;
return (0, _utils.exec)('git rev-parse @', { encoding: 'utf8' });
case 6:
LOCAL = _context4.sent.trim();
_context4.next = 9;
return (0, _utils.exec)('git rev-parse @{u}', { encoding: 'utf8' });
case 9:
REMOTE = _context4.sent.trim();
_context4.next = 12;
return (0, _utils.exec)('git merge-base @ @{u}', { encoding: 'utf8' });
case 12:
BASE = _context4.sent.trim();
if (!(LOCAL !== REMOTE && LOCAL === BASE)) {
_context4.next = 17;
break;
}
throw new _utils.SmoothReleaseError('Your local branch is out-of-date. Please pull the latest remote changes. Aborting.');
case 17:
if (!(LOCAL !== REMOTE && REMOTE === BASE)) {
_context4.next = 21;
break;
}
throw new _utils.SmoothReleaseError('Your local branch is ahead of its remote branch. Please push your local changes. Aborting.');
case 21:
if (!(LOCAL !== REMOTE)) {
_context4.next = 23;
break;
}
throw new _utils.SmoothReleaseError('Your local and remote branches have diverged. Please put them in sync. Aborting.');
case 23:
_utils.status.doneStep(true);
case 24:
case 'end':
return _context4.stop();
}
}
}, _callee4, undefined);
}));
return function validateInSyncWithRemote() {
return _ref4.apply(this, arguments);
};
}();
var validateNpmCredentials = function () {
var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() {
var trim, user, packageJsonName, collaborators, packageAlreadyInRegistry, teamsWithWriteAccess, teamMembers;
return _regenerator2.default.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
if (!_config2.default.publish.validNpmCredentials) {
_context6.next = 25;
break;
}
_utils.status.addSteps(['Validate user\'s credentials for "npm"']);
trim = function trim(s) {
return s.trim();
};
_context6.next = 5;
return (0, _utils.exec)('npm whoami --registry https://registry.npmjs.org/', { encoding: 'utf8' }).then(trim).catch(function () {
return null;
});
case 5:
user = _context6.sent;
if (user) {
_context6.next = 8;
break;
}
throw new _utils.SmoothReleaseError('There is no logged in user for "npm"');
case 8:
packageJsonName = (0, _utils.getPackageJsonName)();
_context6.t0 = JSON;
_context6.next = 12;
return (0, _utils.exec)('npm access ls-collaborators ' + packageJsonName).then(trim).catch(function () {
return null;
});
case 12:
_context6.t1 = _context6.sent;
collaborators = _context6.t0.parse.call(_context6.t0, _context6.t1);
packageAlreadyInRegistry = !!collaborators;
if (!(packageAlreadyInRegistry && collaborators[user] !== 'read-write')) {
_context6.next = 24;
break;
}
teamsWithWriteAccess = (0, _keys2.default)(collaborators).filter(function (name) {
return (0, _lodash.includes)(name, ':') && collaborators[name] === 'read-write';
});
_context6.t2 = _lodash.flatten;
_context6.next = 20;
return _promise2.default.all(teamsWithWriteAccess.map(function () {
var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(team) {
return _regenerator2.default.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
_context5.t0 = JSON;
_context5.next = 3;
return (0, _utils.exec)('npm team ls ' + team).then(trim).catch(function () {
return null;
});
case 3:
_context5.t1 = _context5.sent;
return _context5.abrupt('return', _context5.t0.parse.call(_context5.t0, _context5.t1));
case 5:
case 'end':
return _context5.stop();
}
}
}, _callee5, undefined);
}));
return function (_x) {
return _ref6.apply(this, arguments);
};
}()));
case 20:
_context6.t3 = _context6.sent;
teamMembers = (0, _context6.t2)(_context6.t3);
if ((0, _lodash.includes)(teamMembers, user)) {
_context6.next = 24;
break;
}
throw new _utils.SmoothReleaseError('"' + user + '" does not have write permissions for "' + packageJsonName + '"');
case 24:
_utils.status.doneStep(true);
case 25:
case 'end':
return _context6.stop();
}
}
}, _callee6, undefined);
}));
return function validateNpmCredentials() {
return _ref5.apply(this, arguments);
};
}();
var validateGithubToken = function () {
var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7() {
var res;
return _regenerator2.default.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
if (!_config2.default.publish.validGithubToken) {
_context7.next = 12;
break;
}
_utils.status.addSteps(['Validate user\'s token for "GitHub"']);
_context7.next = 4;
return _utils.octokat.user.fetch().catch(function (x) {
return x;
});
case 4:
res = _context7.sent;
if (!(res.status === 401)) {
_context7.next = 11;
break;
}
_utils.status.doneStep(false);
_context7.next = 9;
return (0, _token.askForToken)('The stored GitHub token is invalid. Please write here a valid token:');
case 9:
_context7.next = 12;
break;
case 11:
_utils.status.doneStep(true);
case 12:
case 'end':
return _context7.stop();
}
}
}, _callee7, undefined);
}));
return function validateGithubToken() {
return _ref7.apply(this, arguments);
};
}();
var validatePackageFilesAreFilteredBeforePublish = function () {
var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee8() {
var hasPackageJsonFiles, hasNpmIgnore;
return _regenerator2.default.wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
if (!(_config2.default.publish.packageFilesFilter !== false)) {
_context8.next = 22;
break;
}
hasPackageJsonFiles = !!(0, _utils.getPackageJsonFiles)();
hasNpmIgnore = _fs2.default.existsSync((0, _utils.getRootFolderPath)() + '/.npmignore');
if (!(_config2.default.publish.packageFilesFilter === 'npmignore')) {
_context8.next = 9;
break;
}
_utils.status.addSteps(['Validate project has a ".npmignore" file']);
if (hasNpmIgnore) {
_context8.next = 7;
break;
}
throw new _utils.SmoothReleaseError('There must be a ".npmignore" file');
case 7:
_context8.next = 19;
break;
case 9:
if (!(_config2.default.publish.packageFilesFilter === 'files')) {
_context8.next = 15;
break;
}
_utils.status.addSteps(['Validate package.json contains the "files" whitelist']);
if (hasPackageJsonFiles) {
_context8.next = 13;
break;
}
throw new _utils.SmoothReleaseError('The package.json must contain the "files" whitelist');
case 13:
_context8.next = 19;
break;
case 15:
if (!(_config2.default.publish.packageFilesFilter === true)) {
_context8.next = 19;
break;
}
_utils.status.addSteps(['Validate one of ".npmignore" or "package.json.files" exist']);
if (!(!hasPackageJsonFiles && !hasNpmIgnore)) {
_context8.next = 19;
break;
}
throw new _utils.SmoothReleaseError('One of ".npmignore" or "package.json.files" must exist');
case 19:
if (!(hasPackageJsonFiles && hasNpmIgnore)) {
_context8.next = 21;
break;
}
throw new _utils.SmoothReleaseError('A project can\'t have both a ".npmignore" blacklist and a "package.json.files" whitelist');
case 21:
_utils.status.doneStep(true);
case 22:
case 'end':
return _context8.stop();
}
}
}, _callee8, undefined);
}));
return function validatePackageFilesAreFilteredBeforePublish() {
return _ref8.apply(this, arguments);
};
}();
exports.default = function () {
var _ref10 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee9(_ref9) {
var mayPublishOnNpm = _ref9.mayPublishOnNpm;
var shouldRunAtLeastOneValidation;
return _regenerator2.default.wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
shouldRunAtLeastOneValidation = (0, _lodash.some)(_config2.default.publish);
if (!shouldRunAtLeastOneValidation) {
_context9.next = 19;
break;
}
(0, _utils.title)('Run validations');
_context9.next = 5;
return validateBranch();
case 5:
_context9.next = 7;
return validateNoUncommittedChanges();
case 7:
_context9.next = 9;
return validateNoUntrackedFiles();
case 9:
_context9.next = 11;
return validateInSyncWithRemote();
case 11:
_context9.next = 13;
return validatePackageFilesAreFilteredBeforePublish();
case 13:
_context9.next = 15;
return validateGithubToken();
case 15:
_context9.t0 = mayPublishOnNpm;
if (!_context9.t0) {
_context9.next = 19;
break;
}
_context9.next = 19;
return validateNpmCredentials();
case 19:
case 'end':
return _context9.stop();
}
}
}, _callee9, undefined);
}));
return function (_x2) {
return _ref10.apply(this, arguments);
};
}();