UNPKG

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
'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); }; }();