UNPKG

verdaccio

Version:
538 lines (459 loc) 15.6 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseReadme = exports.parseConfigFile = exports.ErrorCode = exports.getLatestVersion = exports.getWebProtocol = exports.validate_package = exports.validateName = exports.isObject = exports.validate_metadata = exports.combineBaseUrl = exports.tagVersion = exports.getVersion = exports.parse_address = exports.semverSort = exports.parseInterval = exports.fileExists = exports.folder_exists = exports.sortByName = exports.addScope = exports.deleteProperties = exports.addGravatarSupport = exports.DIST_TAGS = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; exports.getUserAgent = getUserAgent; exports.buildBase64Buffer = buildBase64Buffer; exports.extractTarballFromUrl = extractTarballFromUrl; exports.convertDistRemoteToLocalTarballUrls = convertDistRemoteToLocalTarballUrls; exports.getLocalRegistryTarballUri = getLocalRegistryTarballUri; exports.normalizeDistTags = normalizeDistTags; exports.buildToken = buildToken; var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); var _assert = require('assert'); var _assert2 = _interopRequireDefault(_assert); var _semver = require('semver'); var _semver2 = _interopRequireDefault(_semver); var _jsYaml = require('js-yaml'); var _jsYaml2 = _interopRequireDefault(_jsYaml); var _url = require('url'); var _url2 = _interopRequireDefault(_url); var _httpErrors = require('http-errors'); var _httpErrors2 = _interopRequireDefault(_httpErrors); var _marked = require('marked'); var _marked2 = _interopRequireDefault(_marked); var _constants = require('./constants'); var _user = require('../utils/user'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const Logger = require('./logger'); const pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars const pkgVersion = module.exports.version; const pkgName = module.exports.name; const DIST_TAGS = exports.DIST_TAGS = 'dist-tags'; function getUserAgent() { (0, _assert2.default)(_lodash2.default.isString(pkgName)); (0, _assert2.default)(_lodash2.default.isString(pkgVersion)); return `${pkgName}/${pkgVersion}`; } function buildBase64Buffer(payload) { return new Buffer(payload, 'base64'); } /** * Validate a package. * @return {Boolean} whether the package is valid or not */ function validate_package(name) { name = name.split('/', 2); if (name.length === 1) { // normal package return validateName(name[0]); } else { // scoped package return name[0][0] === '@' && validateName(name[0].slice(1)) && validateName(name[1]); } } /** * From normalize-package-data/lib/fixer.js * @param {*} name the package name * @return {Boolean} whether is valid or not */ function validateName(name) { if (_lodash2.default.isString(name) === false) { return false; } name = name.toLowerCase(); // all URL-safe characters and "@" for issue #75 return !(!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/) || name.charAt(0) === '.' || // ".bin", etc. name.charAt(0) === '-' || // "-" is reserved by couchdb name === 'node_modules' || name === '__proto__' || name === 'package.json' || name === 'favicon.ico'); } /** * Check whether an element is an Object * @param {*} obj the element * @return {Boolean} */ function isObject(obj) { return _lodash2.default.isObject(obj) && _lodash2.default.isNull(obj) === false && _lodash2.default.isArray(obj) === false; } /** * Validate the package metadata, add additional properties whether are missing within * the metadata properties. * @param {*} object * @param {*} name * @return {Object} the object with additional properties as dist-tags ad versions */ function validate_metadata(object, name) { (0, _assert2.default)(isObject(object), 'not a json object'); _assert2.default.equal(object.name, name); if (!isObject(object[DIST_TAGS])) { object[DIST_TAGS] = {}; } if (!isObject(object['versions'])) { object['versions'] = {}; } if (!isObject(object['time'])) { object['time'] = {}; } return object; } /** * Create base url for registry. * @return {String} base registry url */ function combineBaseUrl(protocol, host, prefix) { let result = `${protocol}://${host}`; if (prefix) { prefix = prefix.replace(/\/$/, ''); result = prefix.indexOf('/') === 0 ? `${result}${prefix}` : prefix; } return result; } function extractTarballFromUrl(url) { // $FlowFixMe return _url2.default.parse(url).pathname.replace(/^.*\//, ''); } /** * Iterate a packages's versions and filter each original tarball url. * @param {*} pkg * @param {*} req * @param {*} config * @return {String} a filtered package */ function convertDistRemoteToLocalTarballUrls(pkg, req, urlPrefix) { for (let ver in pkg.versions) { if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) { const distName = pkg.versions[ver].dist; if (_lodash2.default.isNull(distName) === false && _lodash2.default.isNull(distName.tarball) === false) { distName.tarball = getLocalRegistryTarballUri(distName.tarball, pkg.name, req, urlPrefix); } } } return pkg; } /** * Filter a tarball url. * @param {*} uri * @return {String} a parsed url */ function getLocalRegistryTarballUri(uri, pkgName, req, urlPrefix) { const currentHost = req.headers.host; if (!currentHost) { return uri; } const tarballName = extractTarballFromUrl(uri); const domainRegistry = combineBaseUrl(getWebProtocol(req), req.headers.host, urlPrefix); return `${domainRegistry}/${pkgName.replace(/\//g, '%2f')}/-/${tarballName}`; } /** * Create a tag for a package * @param {*} data * @param {*} version * @param {*} tag * @return {Boolean} whether a package has been tagged */ function tagVersion(data, version, tag) { if (tag) { if (data[DIST_TAGS][tag] !== version) { if (_semver2.default.parse(version, true)) { // valid version - store data[DIST_TAGS][tag] = version; return true; } } } return false; } /** * Gets version from a package object taking into account semver weirdness. * @return {String} return the semantic version of a package */ function getVersion(pkg, version) { // this condition must allow cast if (pkg.versions[version] != null) { return pkg.versions[version]; } try { version = _semver2.default.parse(version, true); for (let versionItem in pkg.versions) { // $FlowFixMe if (version.compare(_semver2.default.parse(versionItem, true)) === 0) { return pkg.versions[versionItem]; } } } catch (err) { return undefined; } } /** * Parse an internet address * Allow: - https:localhost:1234 - protocol + host + port - localhost:1234 - host + port - 1234 - port - http::1234 - protocol + port - https://localhost:443/ - full url + https - http://[::1]:443/ - ipv6 - unix:/tmp/http.sock - unix sockets - https://unix:/tmp/http.sock - unix sockets (https) * @param {*} urlAddress the internet address definition * @return {Object|Null} literal object that represent the address parsed */ function parse_address(urlAddress) { // // TODO: refactor it to something more reasonable? // // protocol : // ( host )|( ipv6 ): port / let urlPattern = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(urlAddress); if (urlPattern) { return { proto: urlPattern[2] || 'http', host: urlPattern[6] || urlPattern[7] || _constants.DEFAULT_DOMAIN, port: urlPattern[8] || _constants.DEFAULT_PORT }; } urlPattern = /^((https?):(\/\/)?)?unix:(.*)$/.exec(urlAddress); if (urlPattern) { return { proto: urlPattern[2] || 'http', path: urlPattern[4] }; } return null; } /** * Function filters out bad semver versions and sorts the array. * @return {Array} sorted Array */ function semverSort(listVersions) { return listVersions.filter(function (x) { if (!_semver2.default.parse(x, true)) { Logger.logger.warn({ ver: x }, 'ignoring bad version @{ver}'); return false; } return true; }).sort(_semver2.default.compareLoose).map(String); } /** * Flatten arrays of tags. * @param {*} data */ function normalizeDistTags(pkg) { let sorted; if (!pkg[DIST_TAGS].latest) { // overwrite latest with highest known version based on semver sort sorted = semverSort(Object.keys(pkg.versions)); if (sorted && sorted.length) { pkg[DIST_TAGS].latest = sorted.pop(); } } for (let tag in pkg[DIST_TAGS]) { if (_lodash2.default.isArray(pkg[DIST_TAGS][tag])) { if (pkg[DIST_TAGS][tag].length) { // sort array // $FlowFixMe sorted = semverSort(pkg[DIST_TAGS][tag]); if (sorted.length) { // use highest version based on semver sort pkg[DIST_TAGS][tag] = sorted.pop(); } } else { delete pkg[DIST_TAGS][tag]; } } else if (_lodash2.default.isString(pkg[DIST_TAGS][tag])) { if (!_semver2.default.parse(pkg[DIST_TAGS][tag], true)) { // if the version is invalid, delete the dist-tag entry delete pkg[DIST_TAGS][tag]; } } } } const parseIntervalTable = { '': 1000, ms: 1, s: 1000, m: 60 * 1000, h: 60 * 60 * 1000, d: 86400000, w: 7 * 86400000, M: 30 * 86400000, y: 365 * 86400000 }; /** * Parse an internal string to number * @param {*} interval * @return {Number} */ function parseInterval(interval) { if (typeof interval === 'number') { return interval * 1000; } let result = 0; let last_suffix = Infinity; interval.split(/\s+/).forEach(function (x) { if (!x) return; let m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/); if (!m || parseIntervalTable[m[4]] >= last_suffix || m[4] === '' && last_suffix !== Infinity) { throw Error('invalid interval: ' + interval); } last_suffix = parseIntervalTable[m[4]]; result += Number(m[1]) * parseIntervalTable[m[4]]; }); return result; } /** * Detect running protocol (http or https) * @param {*} req * @return {String} */ function getWebProtocol(req) { return req.get('X-Forwarded-Proto') || req.protocol; } const getLatestVersion = function (pkgInfo) { return pkgInfo[DIST_TAGS].latest; }; const ErrorCode = { getConflict: (message = 'this package is already present') => { return (0, _httpErrors2.default)(_constants.HTTP_STATUS.CONFLICT, message); }, getBadData: customMessage => { return (0, _httpErrors2.default)(_constants.HTTP_STATUS.BAD_DATA, customMessage || 'bad data'); }, getBadRequest: customMessage => { return (0, _httpErrors2.default)(_constants.HTTP_STATUS.BAD_REQUEST, customMessage); }, getInternalError: customMessage => { return customMessage ? (0, _httpErrors2.default)(_constants.HTTP_STATUS.INTERNAL_ERROR, customMessage) : (0, _httpErrors2.default)(_constants.HTTP_STATUS.INTERNAL_ERROR); }, getForbidden: (message = 'can\'t use this filename') => { return (0, _httpErrors2.default)(_constants.HTTP_STATUS.FORBIDDEN, message); }, getServiceUnavailable: (message = _constants.API_ERROR.RESOURCE_UNAVAILABLE) => { return (0, _httpErrors2.default)(_constants.HTTP_STATUS.SERVICE_UNAVAILABLE, message); }, getNotFound: customMessage => { return (0, _httpErrors2.default)(_constants.HTTP_STATUS.NOT_FOUND, customMessage || _constants.API_ERROR.NO_PACKAGE); }, getCode: (statusCode, customMessage) => { return (0, _httpErrors2.default)(statusCode, customMessage); } }; const parseConfigFile = configPath => _jsYaml2.default.safeLoad(_fs2.default.readFileSync(configPath, 'utf8')); /** * Check whether the path already exist. * @param {String} path * @return {Boolean} */ function folder_exists(path) { try { const stat = _fs2.default.statSync(path); return stat.isDirectory(); } catch (_) { return false; } } /** * Check whether the file already exist. * @param {String} path * @return {Boolean} */ function fileExists(path) { try { const stat = _fs2.default.statSync(path); return stat.isFile(); } catch (_) { return false; } } function sortByName(packages) { return packages.sort(function (a, b) { if (a.name < b.name) { return -1; } else { return 1; } }); } function addScope(scope, packageName) { return `@${scope}/${packageName}`; } function deleteProperties(propertiesToDelete, objectItem) { _lodash2.default.forEach(propertiesToDelete, property => { delete objectItem[property]; }); return objectItem; } function addGravatarSupport(pkgInfo) { const pkgInfoCopy = _extends({}, pkgInfo); const author = _lodash2.default.get(pkgInfo, 'latest.author', null); const contributors = _lodash2.default.get(pkgInfo, 'latest.contributors', []); const maintainers = _lodash2.default.get(pkgInfo, 'latest.maintainers', []); // for author. if (author && _lodash2.default.isObject(author)) { pkgInfoCopy.latest.author.avatar = (0, _user.generateGravatarUrl)(author.email); } if (author && _lodash2.default.isString(author)) { pkgInfoCopy.latest.author = { avatar: (0, _user.generateGravatarUrl)(), email: '', author }; } // for contributors if (_lodash2.default.isEmpty(contributors) === false) { pkgInfoCopy.latest.contributors = contributors.map(contributor => { contributor.avatar = (0, _user.generateGravatarUrl)(contributor.email); return contributor; }); } // for maintainers if (_lodash2.default.isEmpty(maintainers) === false) { pkgInfoCopy.latest.maintainers = maintainers.map(maintainer => { maintainer.avatar = (0, _user.generateGravatarUrl)(maintainer.email); return maintainer; }); } return pkgInfoCopy; } /** * parse package readme - markdown/ascii * @param {String} packageName name of package * @param {String} readme package readme * @return {String} converted html template */ function parseReadme(packageName, readme) { if (readme) { return (0, _marked2.default)(readme); } // logs readme not found error Logger.logger.error({ packageName }, '@{packageName}: No readme found'); return (0, _marked2.default)('ERROR: No README data found!'); } function buildToken(type, token) { return `${_lodash2.default.capitalize(type)} ${token}`; } exports.addGravatarSupport = addGravatarSupport; exports.deleteProperties = deleteProperties; exports.addScope = addScope; exports.sortByName = sortByName; exports.folder_exists = folder_exists; exports.fileExists = fileExists; exports.parseInterval = parseInterval; exports.semverSort = semverSort; exports.parse_address = parse_address; exports.getVersion = getVersion; exports.tagVersion = tagVersion; exports.combineBaseUrl = combineBaseUrl; exports.validate_metadata = validate_metadata; exports.isObject = isObject; exports.validateName = validateName; exports.validate_package = validate_package; exports.getWebProtocol = getWebProtocol; exports.getLatestVersion = getLatestVersion; exports.ErrorCode = ErrorCode; exports.parseConfigFile = parseConfigFile; exports.parseReadme = parseReadme;