1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-11-08 23:25:51 +01:00

Merge branch 'master' into task-826

This commit is contained in:
Juan Picado @jotadeveloper 2018-07-29 19:31:47 +02:00 committed by GitHub
commit e43e6155fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 566 additions and 320 deletions

@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="3.4.1"></a>
## [3.4.1](https://github.com/verdaccio/verdaccio/compare/v3.4.0...v3.4.1) (2018-07-27)
### Bug Fixes
* solves dependency bug for one dependency ([#857](https://github.com/verdaccio/verdaccio/issues/857)) ([f9c9c44](https://github.com/verdaccio/verdaccio/commit/f9c9c44))
<a name="3.4.0"></a>
# [3.4.0](https://github.com/verdaccio/verdaccio/compare/v3.3.0...v3.4.0) (2018-07-27)

@ -1,7 +1,7 @@
FROM node:10.3-alpine
LABEL maintainer="https://github.com/verdaccio/verdaccio"
RUN apk --no-cache add openssl && \
RUN apk --no-cache add wget openssl && \
wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 && \
chmod +x /usr/local/bin/dumb-init && \
apk del openssl && \

@ -1,6 +1,6 @@
{
"name": "verdaccio",
"version": "3.4.0",
"version": "3.4.1",
"description": "Private npm repository server",
"author": {
"name": "Alex Kocharin",
@ -18,30 +18,30 @@
"@verdaccio/file-locking": "0.0.7",
"@verdaccio/local-storage": "1.1.3",
"@verdaccio/streams": "1.0.0",
"JSONStream": "1.3.2",
"JSONStream": "1.3.3",
"asciidoctor.js": "1.5.6",
"async": "2.6.0",
"body-parser": "1.18.2",
"async": "2.6.1",
"body-parser": "1.18.3",
"bunyan": "1.8.12",
"chalk": "2.4.1",
"commander": "2.15.0",
"compression": "1.7.2",
"commander": "2.16.0",
"compression": "1.7.3",
"cookies": "0.7.1",
"cors": "2.8.4",
"date-fns": "1.29.0",
"express": "4.16.3",
"global": "4.3.2",
"handlebars": "4.0.11",
"http-errors": "1.6.2",
"http-errors": "1.6.3",
"js-base64": "2.4.8",
"js-string-escape": "1.0.1",
"js-yaml": "3.11.0",
"jsonwebtoken": "8.2.1",
"lockfile": "1.0.3",
"js-yaml": "3.12.0",
"jsonwebtoken": "8.3.0",
"lockfile": "1.0.4",
"lodash": "4.17.10",
"lunr": "0.7.0",
"marked": "0.3.17",
"mime": "2.2.0",
"marked": "0.4.0",
"mime": "2.3.1",
"minimatch": "3.0.4",
"mkdirp": "0.5.1",
"pkginfo": "0.4.1",
@ -51,23 +51,23 @@
"verdaccio-htpasswd": "0.2.2"
},
"devDependencies": {
"@commitlint/cli": "6.1.3",
"@commitlint/config-conventional": "6.1.3",
"@commitlint/cli": "7.0.0",
"@commitlint/config-conventional": "7.0.1",
"@verdaccio/types": "3.4.2",
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-eslint": "8.2.2",
"babel-jest": "23.2.0",
"babel-loader": "7.1.4",
"babel-core": "6.26.3",
"babel-eslint": "8.2.6",
"babel-jest": "23.4.0",
"babel-loader": "7.1.5",
"babel-plugin-flow-runtime": "0.17.0",
"babel-plugin-syntax-dynamic-import": "6.18.0",
"babel-plugin-transform-async-to-generator": "6.24.1",
"babel-plugin-transform-class-properties": "6.24.1",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-decorators-legacy": "1.3.5",
"babel-plugin-transform-es2015-classes": "6.24.1",
"babel-plugin-transform-runtime": "6.23.0",
"babel-polyfill": "6.26.0",
"babel-preset-env": "1.6.1",
"babel-preset-env": "1.7.0",
"babel-preset-es2015": "6.24.1",
"babel-preset-es2015-node4": "2.1.1",
"babel-preset-flow": "6.23.0",
@ -76,23 +76,23 @@
"babel-preset-stage-3": "6.24.1",
"babel-register": "6.26.0",
"babel-runtime": "6.26.0",
"codecov": "3.0.2",
"cross-env": "5.1.4",
"codecov": "3.0.4",
"cross-env": "5.2.0",
"css-loader": "0.28.10",
"element-react": "1.4.8",
"element-theme-default": "1.4.13",
"enzyme": "3.3.0",
"enzyme-adapter-react-16": "1.1.1",
"eslint": "5.0.1",
"eslint": "5.2.0",
"eslint-config-google": "0.9.1",
"eslint-loader": "2.0.0",
"eslint-plugin-babel": "4.1.2",
"eslint-plugin-flowtype": "2.49.3",
"eslint-loader": "2.1.0",
"eslint-plugin-babel": "5.1.0",
"eslint-plugin-flowtype": "2.50.0",
"eslint-plugin-import": "2.13.0",
"eslint-plugin-jest": "21.17.0",
"eslint-plugin-jest": "21.18.0",
"eslint-plugin-react": "7.10.0",
"file-loader": "1.1.11",
"flow-bin": "0.76.0",
"flow-bin": "0.77.0",
"flow-runtime": "0.17.0",
"friendly-errors-webpack-plugin": "1.7.0",
"github-markdown-css": "2.10.0",
@ -100,16 +100,16 @@
"husky": "0.15.0-rc.8",
"identity-obj-proxy": "3.0.0",
"in-publish": "2.0.0",
"jest": "23.2.0",
"jest-environment-jsdom": "23.2.0",
"jest": "23.4.1",
"jest-environment-jsdom": "23.4.0",
"jest-environment-jsdom-global": "1.1.0",
"jest-environment-node": "23.2.0",
"jest-environment-node": "23.4.0",
"localstorage-memory": "1.0.2",
"mini-css-extract-plugin": "0.4.0",
"node-mocks-http": "1.6.7",
"node-sass": "4.9.0",
"mini-css-extract-plugin": "0.4.1",
"node-mocks-http": "1.7.0",
"node-sass": "4.9.2",
"normalize.css": "8.0.0",
"optimize-css-assets-webpack-plugin": "4.0.1",
"optimize-css-assets-webpack-plugin": "5.0.0",
"ora": "1.4.0",
"prop-types": "15.6.1",
"puppeteer": "1.1.1",
@ -121,22 +121,22 @@
"rimraf": "2.6.2",
"sass-loader": "6.0.7",
"source-map-loader": "0.2.3",
"standard-version": "4.3.0",
"style-loader": "0.20.3",
"stylelint": "9.1.1",
"stylelint-config-recommended-scss": "3.1.0",
"standard-version": "4.4.0",
"style-loader": "0.21.0",
"stylelint": "9.4.0",
"stylelint-config-recommended-scss": "3.2.0",
"stylelint-scss": "2.5.0",
"stylelint-webpack-plugin": "0.10.4",
"supertest": "3.0.0",
"stylelint-webpack-plugin": "0.10.5",
"supertest": "3.1.0",
"url-loader": "0.6.2",
"verdaccio-auth-memory": "0.0.4",
"verdaccio-memory": "1.0.1",
"webpack": "4.10.2",
"verdaccio-memory": "1.0.3",
"webpack": "4.16.3",
"webpack-bundle-analyzer": "2.13.1",
"webpack-cli": "3.0.1",
"webpack-dev-server": "3.1.4",
"webpack-merge": "4.1.2",
"whatwg-fetch": "2.0.3"
"webpack-cli": "3.1.0",
"webpack-dev-server": "3.1.5",
"webpack-merge": "4.1.3",
"whatwg-fetch": "2.0.4"
},
"keywords": [
"private",

@ -63,7 +63,6 @@ export const API_MESSAGE = {
TAG_UPDATED: 'tags updated',
TAG_REMOVED: 'tag removed',
TAG_ADDED: 'package tagged',
};
export const API_ERROR = {
@ -86,6 +85,7 @@ export const API_ERROR = {
WEB_DISABLED: 'Web interface is disabled in the config file',
DEPRECATED_BASIC_HEADER: 'basic authentication is deprecated, please use JWT instead',
BAD_FORMAT_USER_GROUP: 'user groups is different than an array',
RESOURCE_UNAVAILABLE: 'resource unavailable',
};
export const APP_ERROR = {

@ -333,6 +333,7 @@ class LocalStorage implements IStorage {
this.logger.info( {name: name, version: ver}, 'unpublishing @{name}@@{version}');
delete jsonData.versions[ver];
delete jsonData.time[ver];
for (let file in jsonData._attachments) {
if (jsonData._attachments[file].version === ver) {

@ -9,7 +9,12 @@ import asciidoctor from 'asciidoctor.js';
import createError from 'http-errors';
import marked from 'marked';
import {HTTP_STATUS, API_ERROR, DEFAULT_PORT, DEFAULT_DOMAIN} from './constants';
import {
HTTP_STATUS,
API_ERROR,
DEFAULT_PORT,
DEFAULT_DOMAIN,
} from './constants';
import {generateGravatarUrl} from '../utils/user';
import type {Package} from '@verdaccio/types';
@ -44,7 +49,11 @@ function validate_package(name: any): boolean {
return validateName(name[0]);
} else {
// scoped package
return name[0][0] === '@' && validateName(name[0].slice(1)) && validateName(name[1]);
return (
name[0][0] === '@' &&
validateName(name[0].slice(1)) &&
validateName(name[1])
);
}
}
@ -60,13 +69,14 @@ function validateName(name: string): boolean {
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'
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'
);
}
@ -109,15 +119,17 @@ function validate_metadata(object: Package, name: string) {
* Create base url for registry.
* @return {String} base registry url
*/
function combineBaseUrl(protocol: string, host: string, prefix?: string): string {
function combineBaseUrl(
protocol: string,
host: string,
prefix?: string
): string {
let result = `${protocol}://${host}`;
if (prefix) {
prefix = prefix.replace(/\/$/, '');
result = (prefix.indexOf('/') === 0)
? `${result}${prefix}`
: prefix;
result = prefix.indexOf('/') === 0 ? `${result}${prefix}` : prefix;
}
return result;
@ -135,13 +147,25 @@ export function extractTarballFromUrl(url: string) {
* @param {*} config
* @return {String} a filtered package
*/
export function convertDistRemoteToLocalTarballUrls(pkg: Package, req: $Request, urlPrefix: string | void) {
export function convertDistRemoteToLocalTarballUrls(
pkg: Package,
req: $Request,
urlPrefix: string | void
) {
for (let ver in pkg.versions) {
if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) {
const distName = pkg.versions[ver].dist;
if (_.isNull(distName) === false && _.isNull(distName.tarball) === false) {
distName.tarball = getLocalRegistryTarballUri(distName.tarball, pkg.name, req, urlPrefix);
if (
_.isNull(distName) === false &&
_.isNull(distName.tarball) === false
) {
distName.tarball = getLocalRegistryTarballUri(
distName.tarball,
pkg.name,
req,
urlPrefix
);
}
}
}
@ -153,14 +177,23 @@ export function convertDistRemoteToLocalTarballUrls(pkg: Package, req: $Request,
* @param {*} uri
* @return {String} a parsed url
*/
export function getLocalRegistryTarballUri(uri: string, pkgName: string, req: $Request, urlPrefix: string | void) {
export function getLocalRegistryTarballUri(
uri: string,
pkgName: string,
req: $Request,
urlPrefix: string | void
) {
const currentHost = req.headers.host;
if (!currentHost) {
return uri;
}
const tarballName = extractTarballFromUrl(uri);
const domainRegistry = combineBaseUrl(getWebProtocol(req), req.headers.host, urlPrefix);
const domainRegistry = combineBaseUrl(
getWebProtocol(req),
req.headers.host,
urlPrefix
);
return `${domainRegistry}/${pkgName.replace(/\//g, '%2f')}/-/${tarballName}`;
}
@ -227,7 +260,9 @@ function parse_address(urlAddress: any) {
// TODO: refactor it to something more reasonable?
//
// protocol : // ( host )|( ipv6 ): port /
let urlPattern = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(urlAddress);
let urlPattern = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(
urlAddress
);
if (urlPattern) {
return {
@ -254,9 +289,10 @@ function parse_address(urlAddress: any) {
* @return {Array} sorted Array
*/
function semverSort(listVersions: Array<string>): string[] {
return listVersions.filter(function(x) {
return listVersions
.filter(function(x) {
if (!semver.parse(x, true)) {
Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' );
Logger.logger.warn({ver: x}, 'ignoring bad version @{ver}');
return false;
}
return true;
@ -275,7 +311,7 @@ export function normalizeDistTags(pkg: Package) {
// 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();
pkg[DIST_TAGS].latest = sorted.pop();
}
}
@ -286,13 +322,13 @@ export function normalizeDistTags(pkg: Package) {
// $FlowFixMe
sorted = semverSort(pkg[DIST_TAGS][tag]);
if (sorted.length) {
// use highest version based on semver sort
pkg[DIST_TAGS][tag] = sorted.pop();
// use highest version based on semver sort
pkg[DIST_TAGS][tag] = sorted.pop();
}
} else {
delete pkg[DIST_TAGS][tag];
}
} else if (_.isString(pkg[DIST_TAGS][tag] )) {
} else if (_.isString(pkg[DIST_TAGS][tag])) {
if (!semver.parse(pkg[DIST_TAGS][tag], true)) {
// if the version is invalid, delete the dist-tag entry
delete pkg[DIST_TAGS][tag];
@ -305,12 +341,12 @@ const parseIntervalTable = {
'': 1000,
ms: 1,
s: 1000,
m: 60*1000,
h: 60*60*1000,
m: 60 * 1000,
h: 60 * 60 * 1000,
d: 86400000,
w: 7*86400000,
M: 30*86400000,
y: 365*86400000,
w: 7 * 86400000,
M: 30 * 86400000,
y: 365 * 86400000,
};
/**
@ -319,7 +355,7 @@ const parseIntervalTable = {
* @return {Number}
*/
function parseInterval(interval: any) {
if (typeof(interval) === 'number') {
if (typeof interval === 'number') {
return interval * 1000;
}
let result = 0;
@ -327,9 +363,11 @@ function parseInterval(interval: any) {
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)) {
if (
!m ||
parseIntervalTable[m[4]] >= last_suffix ||
(m[4] === '' && last_suffix !== Infinity)
) {
throw Error('invalid interval: ' + interval);
}
last_suffix = parseIntervalTable[m[4]];
@ -362,24 +400,31 @@ const ErrorCode = {
return createError(HTTP_STATUS.BAD_REQUEST, customMessage);
},
getInternalError: (customMessage?: string) => {
return customMessage ? createError(HTTP_STATUS.INTERNAL_ERROR, customMessage)
return customMessage
? createError(HTTP_STATUS.INTERNAL_ERROR, customMessage)
: createError(HTTP_STATUS.INTERNAL_ERROR);
},
getForbidden: (message: string = 'can\'t use this filename') => {
return createError(HTTP_STATUS.FORBIDDEN, message);
},
getServiceUnavailable: (message: string = 'resource temporarily unavailable') => {
getServiceUnavailable: (
message: string = API_ERROR.RESOURCE_UNAVAILABLE
) => {
return createError(HTTP_STATUS.SERVICE_UNAVAILABLE, message);
},
getNotFound: (customMessage?: string) => {
return createError(HTTP_STATUS.NOT_FOUND, customMessage || API_ERROR.NO_PACKAGE);
return createError(
HTTP_STATUS.NOT_FOUND,
customMessage || API_ERROR.NO_PACKAGE
);
},
getCode: (statusCode: number, customMessage: string) => {
return createError(statusCode, customMessage);
},
};
const parseConfigFile = (configPath: string) => YAML.safeLoad(fs.readFileSync(configPath, 'utf8'));
const parseConfigFile = (configPath: string) =>
YAML.safeLoad(fs.readFileSync(configPath, 'utf8'));
/**
* Check whether the path already exist.
@ -431,28 +476,42 @@ function deleteProperties(propertiesToDelete: Array<string>, objectItem: any) {
return objectItem;
}
function addGravatarSupport(pkgInfo: any) {
if (_.isString(_.get(pkgInfo, 'latest.author.email'))) {
pkgInfo.latest.author.avatar = generateGravatarUrl(pkgInfo.latest.author.email);
} else {
// _.get can't guarantee author property exist
_.set(pkgInfo, 'latest.author.avatar', generateGravatarUrl());
function addGravatarSupport(pkgInfo: Object): Object {
const pkgInfoCopy = {...pkgInfo};
const author = _.get(pkgInfo, 'latest.author', null);
const contributors = _.get(pkgInfo, 'latest.contributors', []);
const maintainers = _.get(pkgInfo, 'latest.maintainers', []);
// for author.
if (author && _.isObject(author)) {
pkgInfoCopy.latest.author.avatar = generateGravatarUrl(author.email);
}
if (_.get(pkgInfo, 'latest.contributors.length', 0) > 0) {
pkgInfo.latest.contributors = _.map(pkgInfo.latest.contributors, (contributor) => {
if (_.isString(contributor.email)) {
contributor.avatar = generateGravatarUrl(contributor.email);
} else {
contributor.avatar = generateGravatarUrl();
}
return contributor;
}
);
if (author && _.isString(author)) {
pkgInfoCopy.latest.author = {
avatar: generateGravatarUrl(),
email: '',
author,
};
}
return pkgInfo;
// for contributors
if (_.isEmpty(contributors) === false) {
pkgInfoCopy.latest.contributors = contributors.map((contributor) => {
contributor.avatar = generateGravatarUrl(contributor.email);
return contributor;
});
}
// for maintainers
if (_.isEmpty(maintainers) === false) {
pkgInfoCopy.latest.maintainers = maintainers.map((maintainer) => {
maintainer.avatar = generateGravatarUrl(maintainer.email);
return maintainer;
});
}
return pkgInfoCopy;
}
/**
@ -467,7 +526,10 @@ function parseReadme(packageName: string, readme: string): string {
// asciidoc
if (docTypeIdentifier.test(readme)) {
const ascii = asciidoctor();
return ascii.convert(readme, {safe: 'safe', attributes: {showtitle: true, icons: 'font'}});
return ascii.convert(readme, {
safe: 'safe',
attributes: {showtitle: true, icons: 'font'},
});
}
if (readme) {

@ -1,18 +1,18 @@
// @flow
import {stringToMD5} from '../lib/crypto-utils';
import _ from 'lodash';
export const GRAVATAR_DEFAULT = 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm';
export const GRAVATAR_DEFAULT =
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm';
/**
* Generate gravatar url from email address
*/
export function generateGravatarUrl(email?: string): string {
if (typeof email === 'string') {
email = email.trim().toLocaleLowerCase();
const emailMD5 = stringToMD5(email);
export function generateGravatarUrl(email: string = ''): string {
let emailCopy = email;
if (_.isString(email) && _.size(email) > 0) {
emailCopy = email.trim().toLocaleLowerCase();
const emailMD5 = stringToMD5(emailCopy);
return `https://www.gravatar.com/avatar/${emailMD5}`;
} else {
return GRAVATAR_DEFAULT;
}
return GRAVATAR_DEFAULT;
}

@ -7,13 +7,16 @@ import ModuleContentPlaceholder from '../../ModuleContentPlaceholder';
import classes from './style.scss';
export const NO_DEPENDENCIES = 'Zero Dependencies!';
export const DEP_ITEM_CLASS = 'dependency-item';
const renderDependenciesList = (dependencies, dependenciesList) => {
return (
<ul>
{dependenciesList.map((dependenceName, index) => {
return (
<li
className="dependency-item"
className={DEP_ITEM_CLASS}
key={index}
title={`Depend on version: ${dependencies[dependenceName]}`}
>
@ -30,10 +33,10 @@ const Dependencies = ({dependencies = {}}) => {
const dependenciesList = Object.keys(dependencies);
return (
<Module title="Dependencies" className={classes.dependenciesModule}>
{dependenciesList.length > 1 ? (
{dependenciesList.length > 0 ? (
renderDependenciesList(dependencies, dependenciesList)
) : (
<ModuleContentPlaceholder text="Zero Dependencies!" />
<ModuleContentPlaceholder text={NO_DEPENDENCIES} />
)}
</Module>
);

@ -20,8 +20,8 @@ function getPackage(name) {
export default function(server: any, server2: any) {
describe('basic test endpoints', () => {
const PKG_NAME:string = 'testpkg';
const PKG_VERSION:string = '0.0.1';
const PKG_NAME: string = 'testpkg';
const PKG_VERSION: string = '0.0.1';
beforeAll(function() {
return server.auth(CREDENTIALS.user, CREDENTIALS.password)

@ -1,52 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Utilities parseReadme should pass for ascii/makrdown text to html template 1`] = `
"<h1 id=\\"project-title\\">Project Title</h1>
<p>One Paragraph of project description goes here</p>
<h2 id=\\"getting-started\\">Getting Started</h2>
<p>These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.</p>
<h3 id=\\"prerequisites\\">Prerequisites</h3>
<p>What things you need to install the software and how to install them</p>
<pre><code>Give examples
</code></pre><h3 id=\\"installing\\">Installing</h3>
<p>A step by step series of examples that tell you how to get a development env running</p>
<p>Say what the step will be</p>
<pre><code>Give the example
</code></pre><p>And repeat</p>
<pre><code>until finished
</code></pre><p>End with an example of getting some data out of the system or using it for a little demo</p>
<h2 id=\\"running-the-tests\\">Running the tests</h2>
<p>Explain how to run the automated tests for this system</p>
<h3 id=\\"break-down-into-end-to-end-tests\\">Break down into end to end tests</h3>
<p>Explain what these tests test and why</p>
<pre><code>Give an example
</code></pre><h3 id=\\"and-coding-style-tests\\">And coding style tests</h3>
<p>Explain what these tests test and why</p>
<pre><code>Give an example
</code></pre><h2 id=\\"deployment\\">Deployment</h2>
<p>Add additional notes about how to deploy this on a live system</p>
<h2 id=\\"built-with\\">Built With</h2>
<ul>
<li>The web framework used</li>
<li>Dependency Management</li>
<li>Used to generate RSS Feeds</li>
</ul>
<h2 id=\\"contributing\\">Contributing</h2>
<p>Please read <a href=\\"CONTRIBUTING.md\\">CONTRIBUTING.md</a> for details on our code of conduct, and the process for submitting pull requests to us.</p>
<h2 id=\\"versioning\\">Versioning</h2>
<p>We use <a href=\\"http://semver.org/\\">SemVer</a> for versioning. For the versions available, see the <a href=\\"https://github.com/your/project/tags\\">tags on this repository</a>.</p>
<h2 id=\\"license\\">License</h2>
<p>This project is licensed under the MIT License - see the <a href=\\"LICENSE.md\\">LICENSE.md</a> file for details</p>
<h2 id=\\"acknowledgments\\">Acknowledgments</h2>
<ul>
<li>Hat tip to anyone whose code was used</li>
<li>Inspiration</li>
<li>etc</li>
</ul>
"
`;
exports[`Utilities parseReadme should pass for ascii/makrdown text to html template 2`] = `
exports[`Utilities parseReadme should pass for ascii text to html template 1`] = `
"<h1>Hello, AsciiDoc!</h1>
<div id=\\"preamble\\">
<div class=\\"sectionbody\\">
@ -76,3 +30,44 @@ exports[`Utilities parseReadme should pass for ascii/makrdown text to html templ
</div>
</div>"
`;
exports[`Utilities parseReadme should pass for makrdown text to html template 1`] = `
"<h1 id=\\"project-title\\">Project Title</h1>
<p>One Paragraph of project description goes here</p>
<h2 id=\\"getting-started\\">Getting Started</h2>
<p>These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.</p>
<h3 id=\\"prerequisites\\">Prerequisites</h3>
<p>What things you need to install the software and how to install them</p>
<pre><code>Give examples</code></pre><h3 id=\\"installing\\">Installing</h3>
<p>A step by step series of examples that tell you how to get a development env running</p>
<p>Say what the step will be</p>
<pre><code>Give the example</code></pre><p>And repeat</p>
<pre><code>until finished</code></pre><p>End with an example of getting some data out of the system or using it for a little demo</p>
<h2 id=\\"running-the-tests\\">Running the tests</h2>
<p>Explain how to run the automated tests for this system</p>
<h3 id=\\"break-down-into-end-to-end-tests\\">Break down into end to end tests</h3>
<p>Explain what these tests test and why</p>
<pre><code>Give an example</code></pre><h3 id=\\"and-coding-style-tests\\">And coding style tests</h3>
<p>Explain what these tests test and why</p>
<pre><code>Give an example</code></pre><h2 id=\\"deployment\\">Deployment</h2>
<p>Add additional notes about how to deploy this on a live system</p>
<h2 id=\\"built-with\\">Built With</h2>
<ul>
<li>The web framework used</li>
<li>Dependency Management</li>
<li>Used to generate RSS Feeds</li>
</ul>
<h2 id=\\"contributing\\">Contributing</h2>
<p>Please read <a href=\\"CONTRIBUTING.md\\">CONTRIBUTING.md</a> for details on our code of conduct, and the process for submitting pull requests to us.</p>
<h2 id=\\"versioning\\">Versioning</h2>
<p>We use <a href=\\"http://semver.org/\\">SemVer</a> for versioning. For the versions available, see the <a href=\\"https://github.com/your/project/tags\\">tags on this repository</a>.</p>
<h2 id=\\"license\\">License</h2>
<p>This project is licensed under the MIT License - see the <a href=\\"LICENSE.md\\">LICENSE.md</a> file for details</p>
<h2 id=\\"acknowledgments\\">Acknowledgments</h2>
<ul>
<li>Hat tip to anyone whose code was used</li>
<li>Inspiration</li>
<li>etc</li>
</ul>
"
`;

@ -80,7 +80,7 @@ describe('UpStorge', () => {
describe('UpStorge::fetchTarball', () => {
test('should fetch a tarball from uplink', (done) => {
const proxy = generateProxy();
const tarball:string = `http://${DOMAIN_SERVERS}:${mockServerPort}/jquery/-/jquery-1.5.1.tgz`;
const tarball: string = `http://${DOMAIN_SERVERS}:${mockServerPort}/jquery/-/jquery-1.5.1.tgz`;
const stream = proxy.fetchTarball(tarball);
stream.on('error', function(err) {
@ -97,7 +97,7 @@ describe('UpStorge', () => {
test('should throw a 404 on fetch a tarball from uplink', (done) => {
const proxy = generateProxy();
const tarball:string = `http://${DOMAIN_SERVERS}:${mockServerPort}/jquery/-/no-exist-1.5.1.tgz`;
const tarball: string = `http://${DOMAIN_SERVERS}:${mockServerPort}/jquery/-/no-exist-1.5.1.tgz`;
const stream = proxy.fetchTarball(tarball);
stream.on('error', function(err) {
@ -117,7 +117,7 @@ describe('UpStorge', () => {
test('should be offline uplink', (done) => {
const proxy = generateProxy();
const tarball:string = 'http://404.verdaccioo.com';
const tarball: string = 'http://404.verdaccioo.com';
const stream = proxy.fetchTarball(tarball);
expect(proxy.failed_requests).toBe(0);

@ -1,18 +1,23 @@
// @flow
import assert from 'assert';
import {generateGravatarUrl, GRAVATAR_DEFAULT} from '../../../src/utils/user';
import {spliceURL} from '../../../src/utils/string';
import Package from "../../../src/webui/components/Package/index";
import {validateName as validate, convertDistRemoteToLocalTarballUrls, parseReadme} from '../../../src/lib/utils';
import Logger, {setup} from '../../../src/lib/logger';
import { generateGravatarUrl, GRAVATAR_DEFAULT } from '../../../src/utils/user';
import { spliceURL } from '../../../src/utils/string';
import Package from '../../../src/webui/components/Package/index';
import {
validateName as validate,
convertDistRemoteToLocalTarballUrls,
parseReadme,
addGravatarSupport
} from '../../../src/lib/utils';
import Logger, { setup } from '../../../src/lib/logger';
import { readFile } from '../../functional/lib/test.utils';
const readmeFile = (fileName: string = 'markdown.md') => readFile(`../../unit/partials/readme/${fileName}`);
const readmeFile = (fileName: string = 'markdown.md') =>
readFile(`../../unit/partials/readme/${fileName}`);
setup([]);
describe('Utilities', () => {
describe('String utilities', () => {
test('should splice two strings and generate a url', () => {
const url: string = spliceURL('http://domain.com', '/-/static/logo.png');
@ -28,104 +33,122 @@ describe('Utilities', () => {
});
describe('User utilities', () => {
test('should generate gravatar url with email', () => {
const gravatarUrl: string = generateGravatarUrl('user@verdaccio.org');
test('should generate gravatar url with email', () => {
const gravatarUrl: string = generateGravatarUrl('user@verdaccio.org');
expect(gravatarUrl).toMatch('https://www.gravatar.com/avatar/');
expect(gravatarUrl).not.toMatch('000000000');
});
expect(gravatarUrl).toMatch('https://www.gravatar.com/avatar/');
expect(gravatarUrl).not.toMatch('000000000');
});
test('should generate generic gravatar url', () => {
const gravatarUrl: string = generateGravatarUrl();
test('should generate generic gravatar url', () => {
const gravatarUrl: string = generateGravatarUrl();
expect(gravatarUrl).toMatch(GRAVATAR_DEFAULT);
});
expect(gravatarUrl).toMatch(GRAVATAR_DEFAULT);
});
});
describe('Validations', () => {
test('good ones', () => {
assert( validate('verdaccio') );
assert( validate('some.weird.package-zzz') );
assert( validate('old-package@0.1.2.tgz') );
assert(validate('verdaccio'));
assert(validate('some.weird.package-zzz'));
assert(validate('old-package@0.1.2.tgz'));
});
test('uppercase', () => {
assert( validate('EVE') );
assert( validate('JSONStream') );
assert(validate('EVE'));
assert(validate('JSONStream'));
});
test('no package.json', () => {
assert( !validate('package.json') );
assert(!validate('package.json'));
});
test('no path seps', () => {
assert( !validate('some/thing') );
assert( !validate('some\\thing') );
assert(!validate('some/thing'));
assert(!validate('some\\thing'));
});
test('no hidden', () => {
assert( !validate('.bin') );
assert(!validate('.bin'));
});
test('no reserved', () => {
assert( !validate('favicon.ico') );
assert( !validate('node_modules') );
assert( !validate('__proto__') );
assert(!validate('favicon.ico'));
assert(!validate('node_modules'));
assert(!validate('__proto__'));
});
test('other', () => {
assert( !validate('pk g') );
assert( !validate('pk\tg') );
assert( !validate('pk%20g') );
assert( !validate('pk+g') );
assert( !validate('pk:g') );
assert(!validate('pk g'));
assert(!validate('pk\tg'));
assert(!validate('pk%20g'));
assert(!validate('pk+g'));
assert(!validate('pk:g'));
});
});
describe('Packages utilities', () => {
const metadata: Package = {
"name": "npm_test",
"versions": {
"1.0.0": {
"dist": {
"tarball": "http:\/\/registry.org\/npm_test\/-\/npm_test-1.0.0.tgz"
name: 'npm_test',
versions: {
'1.0.0': {
dist: {
tarball: 'http://registry.org/npm_test/-/npm_test-1.0.0.tgz'
}
},
"1.0.1": {
"dist": {
"tarball": "http:\/\/registry.org\/npm_test\/-\/npm_test-1.0.1.tgz"
'1.0.1': {
dist: {
tarball: 'http://registry.org/npm_test/-/npm_test-1.0.1.tgz'
}
}
},
}
};
const buildURI = (host, version) => `http://${host}/npm_test/-/npm_test-${version}.tgz`;
const buildURI = (host, version) =>
`http://${host}/npm_test/-/npm_test-${version}.tgz`;
const host = 'fake.com';
test('convertDistRemoteToLocalTarballUrls', () => {
// $FlowFixMe
const convertDist = convertDistRemoteToLocalTarballUrls(Object.assign({}, metadata), {
headers: {
host,
const convertDist = convertDistRemoteToLocalTarballUrls(
Object.assign({}, metadata),
// $FlowFixMe
{
headers: {
host
},
get: () => 'http',
protocol: 'http'
},
get: ()=> 'http',
protocol: 'http'
}, '');
''
);
expect(convertDist.versions['1.0.0'].dist.tarball).toEqual(buildURI(host, '1.0.0'));
expect(convertDist.versions['1.0.1'].dist.tarball).toEqual(buildURI(host, '1.0.1'));
expect(convertDist.versions['1.0.0'].dist.tarball).toEqual(
buildURI(host, '1.0.0')
);
expect(convertDist.versions['1.0.1'].dist.tarball).toEqual(
buildURI(host, '1.0.1')
);
});
});
describe('parseReadme', () => {
test('should pass for ascii/makrdown text to html template', () => {
const markdown = '# markdown';
const ascii = "= AsciiDoc";
test('should pass for ascii text to html template', () => {
const ascii = '= AsciiDoc';
expect(parseReadme('testPackage', markdown)).toEqual('<h1 id="markdown">markdown</h1>\n');
expect(parseReadme('testPackage', ascii)).toEqual('<h1>AsciiDoc</h1>\n');
expect(parseReadme('testPackage', String(readmeFile('markdown.md')))).toMatchSnapshot();
expect(parseReadme('testPackage', String(readmeFile('ascii.adoc')))).toMatchSnapshot();
expect(
parseReadme('testPackage', String(readmeFile('ascii.adoc')))
).toMatchSnapshot();
});
test('should pass for makrdown text to html template', () => {
const markdown = '# markdown';
expect(parseReadme('testPackage', markdown)).toEqual(
'<h1 id="markdown">markdown</h1>\n'
);
expect(
parseReadme('testPackage', String(readmeFile('markdown.md')))
).toMatchSnapshot();
});
test('should pass for conversion of non-ascii to markdown text', () => {
@ -134,20 +157,162 @@ describe('Utilities', () => {
const randomTextNonAscii = 'simple text \n = ascii';
const randomTextMarkdown = 'simple text \n # markdown';
expect(parseReadme('testPackage', randomText)).toEqual('<p>%%%%%**##==</p>\n');
expect(parseReadme('testPackage', simpleText)).toEqual('<p>simple text</p>\n');
expect(parseReadme('testPackage', randomTextNonAscii))
.toEqual('<p>simple text \n = ascii</p>\n');
expect(parseReadme('testPackage', randomTextMarkdown))
.toEqual('<p>simple text </p>\n<h1 id="markdown">markdown</h1>\n');
expect(parseReadme('testPackage', randomText)).toEqual(
'<p>%%%%%**##==</p>\n'
);
expect(parseReadme('testPackage', simpleText)).toEqual(
'<p>simple text</p>\n'
);
expect(parseReadme('testPackage', randomTextNonAscii)).toEqual(
'<p>simple text \n = ascii</p>\n'
);
expect(parseReadme('testPackage', randomTextMarkdown)).toEqual(
'<p>simple text </p>\n<h1 id="markdown">markdown</h1>\n'
);
});
test('should show error for no readme data', () => {
const noData = '';
const spy = jest.spyOn(Logger.logger, 'error')
expect(parseReadme('testPackage', noData))
.toEqual('<p>ERROR: No README data found!</p>\n');
expect(spy).toHaveBeenCalledWith({'packageName': 'testPackage'}, '@{packageName}: No readme found');
const spy = jest.spyOn(Logger.logger, 'error');
expect(parseReadme('testPackage', noData)).toEqual(
'<p>ERROR: No README data found!</p>\n'
);
expect(spy).toHaveBeenCalledWith(
{ packageName: 'testPackage' },
'@{packageName}: No readme found'
);
});
});
describe('addGravatarSupport', () => {
test('check for blank object', () => {
expect(addGravatarSupport({})).toEqual({});
});
test('author, contributors and maintainers fields are not present', () => {
const packageInfo = {
latest: {}
};
expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
});
test('author field is a blank object', () => {
const packageInfo = { latest: { author: {} } };
expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
});
test('author field is a string type', () => {
const packageInfo = {
latest: { author: 'user@verdccio.org' }
};
const result = {
latest: {
author: {
author: 'user@verdccio.org',
avatar:
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm',
email: ''
}
}
};
expect(addGravatarSupport(packageInfo)).toEqual(result);
});
test('author field is an object type with author information', () => {
const packageInfo = {
latest: { author: { name: 'verdaccio', email: 'user@verdccio.org' } }
};
const result = {
latest: {
author: {
avatar:
'https://www.gravatar.com/avatar/794d7f6ef93d0689437de3c3e48fadc7',
email: 'user@verdccio.org',
name: 'verdaccio'
}
}
};
expect(addGravatarSupport(packageInfo)).toEqual(result);
});
test('contributor field is a blank array', () => {
const packageInfo = {
latest: {
contributors: []
}
};
expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
});
test('contributors field has contributors', () => {
const packageInfo = {
latest: {
contributors: [
{ name: 'user', email: 'user@verdccio.org' },
{ name: 'user1', email: 'user1@verdccio.org' }
]
}
};
const result = {
latest: {
contributors: [
{
avatar:
'https://www.gravatar.com/avatar/794d7f6ef93d0689437de3c3e48fadc7',
email: 'user@verdccio.org',
name: 'user'
},
{
avatar:
'https://www.gravatar.com/avatar/51105a49ce4a9c2bfabf0f6a2cba3762',
email: 'user1@verdccio.org',
name: 'user1'
}
]
}
};
expect(addGravatarSupport(packageInfo)).toEqual(result);
});
test('maintainers field is a blank array', () => {
const packageInfo = {
latest: {
maintainers: []
}
};
expect(addGravatarSupport(packageInfo)).toEqual(packageInfo);
});
test('maintainers field has maintainers', () => {
const packageInfo = {
latest: {
maintainers: [
{ name: 'user', email: 'user@verdccio.org' },
{ name: 'user1', email: 'user1@verdccio.org' }
]
}
};
const result = {
latest: {
maintainers: [
{
avatar:
'https://www.gravatar.com/avatar/794d7f6ef93d0689437de3c3e48fadc7',
email: 'user@verdccio.org',
name: 'user'
},
{
avatar:
'https://www.gravatar.com/avatar/51105a49ce4a9c2bfabf0f6a2cba3762',
email: 'user1@verdccio.org',
name: 'user1'
}
]
}
};
expect(addGravatarSupport(packageInfo)).toEqual(result);
});
});
});

@ -2,4 +2,4 @@
exports[`<PackageSidebar /> : <Dependencies /> should load dependencies 1`] = `"<div class=\\"module dependenciesModule\\"><h2 class=\\"moduleTitle\\">Dependencies</h2><div><ul><li class=\\"dependency-item\\" title=\\"Depend on version: 0.0.3\\"><a href=\\"http://localhost/#/detail/@verdaccio/file-locking\\">@verdaccio/file-locking</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: 0.0.2\\"><a href=\\"http://localhost/#/detail/@verdaccio/streams\\">@verdaccio/streams</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.1.1\\"><a href=\\"http://localhost/#/detail/JSONStream\\">JSONStream</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.1.2\\"><a href=\\"http://localhost/#/detail/apache-md5\\">apache-md5</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^2.0.1\\"><a href=\\"http://localhost/#/detail/async\\">async</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.15.0\\"><a href=\\"http://localhost/#/detail/body-parser\\">body-parser</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.8.0\\"><a href=\\"http://localhost/#/detail/bunyan\\">bunyan</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^2.0.1\\"><a href=\\"http://localhost/#/detail/chalk\\">chalk</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^2.11.0\\"><a href=\\"http://localhost/#/detail/commander\\">commander</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: 1.6.2\\"><a href=\\"http://localhost/#/detail/compression\\">compression</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^0.7.0\\"><a href=\\"http://localhost/#/detail/cookies\\">cookies</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^2.8.3\\"><a href=\\"http://localhost/#/detail/cors\\">cors</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: 4.15.3\\"><a href=\\"http://localhost/#/detail/express\\">express</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^4.3.2\\"><a href=\\"http://localhost/#/detail/global\\">global</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: 4.0.5\\"><a href=\\"http://localhost/#/detail/handlebars\\">handlebars</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.4.0\\"><a href=\\"http://localhost/#/detail/http-errors\\">http-errors</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: 1.0.1\\"><a href=\\"http://localhost/#/detail/js-string-escape\\">js-string-escape</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^3.6.0\\"><a href=\\"http://localhost/#/detail/js-yaml\\">js-yaml</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^7.4.1\\"><a href=\\"http://localhost/#/detail/jsonwebtoken\\">jsonwebtoken</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.0.1\\"><a href=\\"http://localhost/#/detail/lockfile\\">lockfile</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: 4.17.4\\"><a href=\\"http://localhost/#/detail/lodash\\">lodash</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^0.7.0\\"><a href=\\"http://localhost/#/detail/lunr\\">lunr</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: 0.3.6\\"><a href=\\"http://localhost/#/detail/marked\\">marked</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.3.6\\"><a href=\\"http://localhost/#/detail/mime\\">mime</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^3.0.2\\"><a href=\\"http://localhost/#/detail/minimatch\\">minimatch</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^0.5.1\\"><a href=\\"http://localhost/#/detail/mkdirp\\">mkdirp</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^0.4.0\\"><a href=\\"http://localhost/#/detail/pkginfo\\">pkginfo</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^2.72.0\\"><a href=\\"http://localhost/#/detail/request\\">request</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^5.1.0\\"><a href=\\"http://localhost/#/detail/semver\\">semver</a><span>, </span></li><li class=\\"dependency-item\\" title=\\"Depend on version: ^1.0.0\\"><a href=\\"http://localhost/#/detail/unix-crypt-td-js\\">unix-crypt-td-js</a></li></ul></div></div>"`;
exports[`<PackageSidebar /> : <Dependencies /> should load the package without dependecnies 1`] = `"<div class=\\"module dependenciesModule\\"><h2 class=\\"moduleTitle\\">Dependencies</h2><div><p class=\\"emptyPlaceholder\\">Zero Dependencies!</p></div></div>"`;
exports[`<PackageSidebar /> : <Dependencies /> should load the package without dependencies 1`] = `"<div class=\\"module dependenciesModule\\"><h2 class=\\"moduleTitle\\">Dependencies</h2><div><p class=\\"emptyPlaceholder\\">Zero Dependencies!</p></div></div>"`;

@ -4,11 +4,15 @@
import React from 'react';
import { shallow } from 'enzyme';
import Dependencies from '../../../../../src/webui/components/PackageSidebar/modules/Dependencies/index';
import Dependencies, {
NO_DEPENDENCIES,
DEP_ITEM_CLASS
} from '../../../../../src/webui/components/PackageSidebar/modules/Dependencies/index';
import ModuleContentPlaceholder from '../../../../../src/webui/components/PackageSidebar/ModuleContentPlaceholder';
describe('<PackageSidebar /> : <Dependencies />', () => {
it('should load dependencies', () => {
test('should load dependencies', () => {
const dependencies = {
'@verdaccio/file-locking': '0.0.3',
'@verdaccio/streams': '0.0.2',
@ -42,11 +46,15 @@ describe('<PackageSidebar /> : <Dependencies />', () => {
'unix-crypt-td-js': '^1.0.0'
};
const wrapper = shallow(<Dependencies dependencies={dependencies} />);
expect(wrapper.find(`.${DEP_ITEM_CLASS}`)).toHaveLength(Object.keys(dependencies).length);
expect(wrapper.html()).toMatchSnapshot();
});
it('should load the package without dependecnies', () => {
test('should load the package without dependencies', () => {
const wrapper = shallow(<Dependencies />);
expect(wrapper.find(ModuleContentPlaceholder).props().text).toBe(NO_DEPENDENCIES);
expect(wrapper.html()).toMatchSnapshot();
});
});

@ -1,31 +1,31 @@
---
id: unit-testing
title: "Unit Testing"
id: unit-testing(单元-测试)
title: "单元测试"
---
All tests are split in three folders:
所有测试都被分成3 个文件夹:
- `test/unit` - Tests that cover functions that transform data in an non-trivial way. These tests simply `require()` a few files and run code in there, so they are very fast.
- `test/functional` - Tests that launch a verdaccio instance and perform a series of requests to it over http. They are slower than unit tests.
- `test/integration` - Tests that launch a verdaccio instance and do requests to it using npm. They are really slow and can hit a real npm registry. **unmaintained test**
- `test/unit` - 涵盖非平凡方式转换数据的功能测试。这些测试只 `require()` 一些文件并在其中运行代码,因此它们是非常快的。
- `test/functional` - 启动verdaccio instance并在 http上执行一系列请求的测试。它们比单元测试慢一些。
- `test/integration` - 启动verdaccio instance并用 npm对其执行请求的测试。它们真的很慢并能打击到真的npm registry。 **unmaintained test**
Unit and functional tests are executed automatically by running `npm test` from the project's root directory. Integration tests are supposed to be executed manually from time to time.
单元和功能测试是从项目根目录里运行 `npm test` 来自动执行的。集成测试应该时常手动执行。
We use `jest` for all test.
我们所有测试都使用 `jest`
## The npm Script
## Npm 脚本
To run the test script you can use either `npm` or `yarn`.
要运行测试脚本,您可以使用 `npm``yarn`
yarn run test
That will trigger only two first groups of test, unit and functional.
这将只会触发测试,单元和功能的前两组。
### Using test/unit
### 使用测试/单元
The following is just an example how a unit test should looks like. Basically follow the `jest` standard.
以下只是单元测试的一个例子。基本上遵守`jest` 标准。
Try to describe what exactly does the unit test in a single sentence in the header of the `test` section.
请试着描述单元在 `test` 部分页眉里的单一句子里确切测试什么。
```javacript
const verdaccio = require('../../src/api/index');
@ -48,11 +48,11 @@ describe('basic system test', () => {
});
```
### Using test/functional
### 使用测试/功能
Funtional testing in verdaccio has a bit more of complextity that needs a deep explanation in order to success in your experience.
Verdaccio 中的功能测试有点复杂,需要深入解释来让您有成功的体验。
All starts in the `index.js` file. Let's dive in into it.
一切从`index.js`文件开始。让我们来深入了解它吧。
```javascript
// we create 3 server instances
@ -107,17 +107,17 @@ All starts in the `index.js` file. Let's dive in into it.
```
### Usage
### 使用
Here we are gonna describe how it looks like an usual functional test, check inline for more detail information.
这里我们将描述常规功能测试看起来是什么样的,请核对内联了解更多详细信息。
#### The lib/server.js
The server class is just a wrapper that simulates a `npm` client and provides a simple API for the funtional test.
服务器 class(类)只是模拟 `npm` client 的 wrapper类它为功能测试提供简单的API。
As we mention in the previous section, we are creating 3 process servers that are accessible in each process as `server1`, `server2` and ``server3`.
如我们在之前的章节里提到的, 我们正创建3 个流程服务器,可以在每个流程里以`server1`, `server2` 和 ``server3`进行访问。
Using such reference you will be able to send request to any of the 3 instance running.
通过这样的引用您可以给这任何3 个运行的instance 发送请求。
```javascript
<br />export default function(server) {
@ -129,6 +129,6 @@ Using such reference you will be able to send request to any of the 3 instance r
});
```
### Test/integration
### 测试/集成
These section never has been used, but we are looking for help to make it run properly. **All new ideas are very welcome.**
这些部分还没有被使用,但是我们在寻求帮助来让它正常运转。**欢迎任何新的想法。**

@ -2,11 +2,11 @@
id: uplinks
title: "Uplinks"
---
An *uplink* is a link with an external registry that provides acccess to external packages.
*上行链路* 是指可以访问到外部包的外部注册服务器地址。
![Uplinks](/img/uplinks.png)
![上行链路](/img/uplinks.png)
### Usage
### 用法
```yaml
uplinks:
@ -21,26 +21,26 @@ uplinks:
url: http://localhost:55666/
```
### Configuration
### 配置
You can define mutiple uplinks and each of them must have an unique name (key). They can have two properties:
你可以定义多个上行链路,每一个都必须有唯一的名称(键值). 它们可以有多个属性:
| Property | Type | Required | Example | Support | Description | Default |
| ------------ | ------- | -------- | --------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------- | ---------- |
| url | string | Yes | https://registry.npmjs.org/ | all | The registry url | npmjs |
| ca | string | No | ~./ssl/client.crt' | all | SSL path certificate | No default |
| timeout | string | No | 100ms | all | set new timeout for the request | 30s |
| maxage | string | No | 10m | all | limit maximun failure request | 2m |
| fail_timeout | string | No | 10m | all | defines max time when a request becomes a failure | 5m |
| max_fails | number | No | 2 | all | limit maximun failure request | 2 |
| cache | boolean | No | [true,false] | >= 2.1 | cache all remote tarballs in storage | true |
| auth | list | No | [see below](uplinks.md#auth-property) | >= 2.5 | assigns the header 'Authorization' [more info](http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules) | disabled |
| headers | list | No | authorization: "Bearer SecretJWToken==" | all | list of custom headers for the uplink | disabled |
| strict_ssl | boolean | No | [true,false] | >= 3.0 | If true, requires SSL certificates be valid. | true |
| 属性 | 类型 | 必须的 | 范例 | 支持版本 | 描述 | 默认值 |
| ------------ | ------- | --- | --------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------- | -------- |
| url | string | 是 | https://registry.npmjs.org/ | 全部 | 外部注册服务器URL | npmjs |
| ca | string | 否 | ~./ssl/client.crt' | 全部 | SSL证书文件路径 | 无默认值 |
| timeout | string | 否 | 100ms | 全部 | 为请求设置新的超时时间 | 30s |
| maxage | string | 否 | 10m | 全部 | 请求返回信息时效,在此时间内不会发起相同的请求 | 2m |
| fail_timeout | string | 否 | 10m | 全部 | 请求在连续失败超过指定次数后的最长等待重试时间 | 5m |
| max_fails | number | 否 | 2 | 全部 | 请求连续失败的最大次数限制 | 2 |
| cache | boolean | 否 | [true,false] | >= 2.1 | 缓存下载的远程tarball文件到本地 | true |
| auth | list | 否 | [见下文](uplinks.md#auth-property) | >= 2.5 | 指定“授权authorization”请求头的内容 [详情见](http://blog.npmjs.org/post/118393368555/deploying-with-npm-private-modules) | disabled |
| headers | list | 否 | authorization: "Bearer SecretJWToken==" | 全部 | 上行链路请求的请求头header列表 | disabled |
| strict_ssl | boolean | 否 | [true,false] | >= 3.0 | 为true时会检测SSL证书的有效性 | true |
#### Auth property
#### Auth属性
The `auth` property allows you to use an auth token with an uplink. Using the default environment variable:
`auth` 属性内容是向上行链路发起请求时提供的授权令牌。例如使用默认环境变量:
```yaml
uplinks:
@ -51,7 +51,7 @@ uplinks:
token_env: true # defaults to `process.env['NPM_TOKEN']`
```
or via a specified environment variable:
或者使用一个指定的环境变量
```yaml
uplinks:
@ -62,9 +62,9 @@ uplinks:
token_env: FOO_TOKEN
```
`token_env: FOO_TOKEN`internally will use `process.env['FOO_TOKEN']`
`token_env: FOO_TOKEN`内部将使用 `process.env['FOO_TOKEN']`
or by directly specifying a token:
或者直接指定令牌:
```yaml
uplinks:
@ -75,12 +75,12 @@ uplinks:
token: "token"
```
> Note: `token` has priority over `token_env`
> 注意: `token`的优先级高于`token_env`
### You Must know
### 须知
* Verdaccio does not use Basic Authentication since version `v2.3.0`. All tokens generated by verdaccio are based on JWT ([JSON Web Token](https://jwt.io/))
* Uplinks must be registries compatible with the `npm` endpoints. Eg: *verdaccio*, `sinopia@1.4.0`, *npmjs registry*, *yarn registry*, *JFrog*, *Nexus* and more.
* Setting `cache` to false will help to save space in your hard drive. This will avoid store `tarballs` but [it will keep metadata in folders](https://github.com/verdaccio/verdaccio/issues/391).
* Exceed with multiple uplinks might slow down the lookup of your packages due for each request a npm client does, verdaccio does 1 call for each uplink.
* The (timeout, maxage and fail_timeout) format follow the [NGINX measurement units](http://nginx.org/en/docs/syntax.html)
* 自版本 `v2.3.0`以来, Verdaccio 不在使用Basic Authentication。verdaccio 所生成的所有令牌都基于 JWT ([JSON Web Token](https://jwt.io/))
* 上行链路必须是兼容`npm`的注册服务器 例如: *verdaccio*, `sinopia@1.4.0`, *npmjs registry*, *yarn registry*, *JFrog*, *Nexus* and more.
* 设置`cache` 为false可以帮助节省你的硬盘空间。 This will avoid store `tarballs` but [it will keep metadata in folders](https://github.com/verdaccio/verdaccio/issues/391).
* 配置过多的上行链路会导致包查询速度变慢这是因为相比较一个npm客户端每发送一次的请求verdaccio却需要向每个上行链路都发送一次这样的请求
* 这些属性(timeout, maxage and fail_timeout) 的配置单位格式参考[NGINX measurement units](http://nginx.org/en/docs/syntax.html)

@ -1,31 +1,31 @@
---
id: use-cases
title: "Use Cases"
id: use-cases(使用-场景)
title: "使用场景"
---
## Using private packages
## 使用私有包
You can add users and manage which users can access which packages.
您可以添加用户并管理哪个用户可以访问哪个包。
It is recommended that you define a prefix for your private packages, for example "local", so all your private things will look like this: `local-foo`. This way you can clearly separate public packages from private ones.
建议您定义私有包的前缀。例如“local(当地)",这样您私人的东西将如下所示:`local-foo`。 通过这种方法您可以清楚地把公有包和私有包分开。
## Using public packages from npmjs.org
## 从npmjs.org使用公有包
If some package doesn't exist in the storage, server will try to fetch it from npmjs.org. If npmjs.org is down, it serves packages from cache pretending that no other packages exist. Verdaccio will download only what's needed (= requested by clients), and this information will be cached, so if client will ask the same thing second time, it can be served without asking npmjs.org for it.
如果一些包没有在存储里服务器将试着从npmjs.org中取它。 如果npmjs.org坏了它会假装没有其他的包存在, 并起到缓存包的作用。 Verdaccio将只下载需要的 (= 由客户要求的)信息, 而且此信息将被缓存这样如果客户再次问同样的事它可以马上作用而不需要问npmjs.org。
Example: if you successfully request express@3.0.1 from this server once, you'll able to do that again (with all it's dependencies) anytime even if npmjs.org is down. But say express@3.0.0 will not be downloaded until it's actually needed by somebody. And if npmjs.org is offline, this server would say that only express@3.0.1 (= only what's in the cache) is published, but nothing else.
例如如果您曾经成功从此服务器请求express@3.0.1哪怕npmjs.org 坏了,您也可以在任何时候再次请求(包含其相关项)。 但是除非有人真正需要express@3.0.0,否则它是不会被下载的。 而且如果npmjs.org脱线此服务器将会说除了express@3.0.1 =只有在缓存里的)外,没有其他的发布。
## Override public packages
## 覆盖公共包
If you want to use a modified version of some public package `foo`, you can just publish it to your local server, so when your type `npm install foo`, it'll consider installing your version.
如果您希望使用一些公共包`foo`的修正版本,您只要把它发布到您的当地服务器,这样当您输入`npm install foo`,它将考虑安装您的版本。
There's two options here:
这里有两个选项:
1. You want to create a separate fork and stop synchronizing with public version.
1. 您要创建单独的分叉并停止与公共版本同步。
If you want to do that, you should modify your configuration file so verdaccio won't make requests regarding this package to npmjs anymore. Add a separate entry for this package to *config.yaml* and remove `npmjs` from `proxy` list and restart the server.
如果您希望这么做您应该修改配置文件这样verdaccio将不再向npmjs提出此包的请求。 将此包单独添加到*config.yaml* 中并从`proxy`列表删除`npmjs`,然后重启服务器。
When you publish your package locally, you should probably start with version string higher than existing one, so it won't conflict with existing package in the cache.
当您在本地发布包,您可能应该从现有版本更高的 string开始这样它就不会和缓存中的现有包冲突。
2. You want to temporarily use your version, but return to public one as soon as it's updated.
2. 您希望临时使用自己的版本,但在它更新后立即切换回公共版本。
In order to avoid version conflicts, you should use a custom pre-release suffix of the next patch version. For example, if a public package has version 0.1.2, you can upload 0.1.3-my-temp-fix. This way your package will be used until its original maintainer updates his public package to 0.1.3.
为了避免版本冲突,您应该使用下一个补丁版本的自定义预发行的后缀。 例如,如果公共包有 0.1.2版本您可以上传0.1.3-my-temp-fix。 这样您的包将在原始维护人员更新其公共包到0.1.3之前使用。

@ -1,12 +1,12 @@
---
id: webui
title: "Web User Interface2"
title: "网页用户界面2"
---
<p align="center"><img src="https://github.com/verdaccio/verdaccio/blob/master/assets/gif/verdaccio_big_30.gif?raw=true"></p>
Verdaccio has a web user interface to display only the private packges and can be customisable.
Verdaccio有个网页用户界面,它只显示私有包并可以定制。
```yaml
web:
@ -15,12 +15,12 @@ web:
logo: logo.png
```
All access restrictions defined to [protect your packages](protect-your-dependencies.md) will also apply to the Web Interface.
所有访问限制定义为[保护包](protect-your-dependencies.md),它也将应用于网页界面。
### Configuration
### 配置
| Property | Type | Required | Example | Support | Description |
| -------- | ------- | -------- | ------------------------------ | ------- | ---------------------------------- |
| enable | boolean | No | true/false | all | allow to display the web interface |
| title | string | No | Verdaccio | all | HTML head title description |
| logo | string | No | http://my.logo.domain/logo.png | all | a URI where logo is located |
| 属性 | 类型 | 必填 | 范例 | 支持 | 描述 |
| ------ | ------- | -- | ------------------------------ | --- | ----------- |
| enable | boolean | No | true/false | all | 允许显示网页界面 |
| title | string | No | Verdaccio | all | HTML 页眉标题说明 |
| logo | string | No | http://my.logo.domain/logo.png | all | logo 位于的URI |

@ -1,4 +1,6 @@
[
"3.4.1",
"3.4.0",
"3.3.0",
"3.2.0"
]

BIN
yarn.lock

Binary file not shown.