chore: sync with verdaccio master

This commit is contained in:
Juan Picado @jotadeveloper
2019-04-04 21:23:40 +02:00
parent 133a5f0171
commit 3506f32ad8
241 changed files with 57691 additions and 1932 deletions

View File

@@ -1,3 +0,0 @@
if (!__DEBUG__) {
__webpack_public_path__ = window.VERDACCIO_API_URL.replace(/\/verdaccio\/$/, '/static/') // eslint-disable-line
}

View File

@@ -1,62 +0,0 @@
import storage from './storage';
class API {
request(url, method = 'GET', options = {}) {
if (!window.VERDACCIO_API_URL) {
throw new Error('VERDACCIO_API_URL is not defined!');
}
const token = storage.getItem('token');
if (token) {
if (!options.headers) options.headers = {};
options.headers.authorization = `Bearer ${token}`;
}
if (!['http://', 'https://', '//'].some((prefix) => url.startsWith(prefix))) {
url = window.VERDACCIO_API_URL + url;
}
/**
* Handles response according to content type
* @param {object} response
* @returns {promise}
*/
function handleResponseType(response) {
if (response.headers) {
const contentType = response.headers.get('Content-Type');
if (contentType.includes('application/pdf')) {
return Promise.all([response.ok, response.blob()]);
}
if (contentType.includes('application/json')) {
return Promise.all([response.ok, response.json()]);
}
// it includes all text types
if (contentType.includes('text/')) {
return Promise.all([response.ok, response.text()]);
}
}
}
return new Promise((resolve, reject) => {
fetch(url, {
method,
credentials: 'same-origin',
...options,
})
.then(handleResponseType)
.then(([responseOk, body]) => {
if (responseOk) {
resolve(body);
} else {
reject(body);
}
})
.catch(error => {
reject(error);
});
});
}
}
export default new API();

View File

@@ -1,36 +0,0 @@
/**
* @prettier
*/
import React from 'react';
export function asyncComponent(getComponent) {
return class AsyncComponent extends React.Component {
static Component = null;
state = { Component: AsyncComponent.Component };
componentDidMount() {
const { Component } = this.state;
if (!Component) {
getComponent()
.then(({ default: Component }) => {
AsyncComponent.Component = Component;
/* eslint react/no-did-mount-set-state:0 */
this.setState({ Component });
})
.catch(err => {
console.error(err);
});
}
}
render() {
const { Component } = this.state;
if (Component) {
// eslint-disable-next-line verdaccio/jsx-spread
return <Component {...this.props} />;
}
return null;
}
};
}

View File

@@ -1,12 +0,0 @@
import API from './api';
/**
* @prettier
*/
export async function callDetailPage(packageName) {
const readMe = await API.request(`package/readme/${packageName}`, 'GET');
const packageMeta = await API.request(`sidebar/${packageName}`, 'GET');
return {readMe, packageMeta};
}

View File

@@ -1,33 +0,0 @@
/**
* @prettier
* @flow
*/
export const copyToClipBoardUtility = (str: string) => (event: SyntheticEvent<HTMLElement>) => {
event.preventDefault();
const node = document.createElement('div');
node.innerText = str;
if (document.body) {
document.body.appendChild(node);
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('copy');
// $FlowFixMe
document.body.removeChild(node);
}
};
export function getCLISetConfigRegistry(command: string, scope: string, registryUrl: string): string {
return `${command} ${scope} registry ${registryUrl}`;
}
export function getCLISetRegistry(command: string, registryUrl: string): string {
return `${command} --registry ${registryUrl}`;
}
export function getCLIChangePassword(command: string, registryUrl: string): string {
return `${command} profile set password --registry ${registryUrl}`;
}

View File

@@ -1,9 +0,0 @@
export const TEXT = {
CLIPBOARD_COPY: 'Copy to Clipboard',
};
export const NODE_MANAGER = {
npm: 'npm',
yarn: 'yarn',
pnpm: 'pnpm',
};

View File

@@ -1,5 +0,0 @@
export default function fileSizeSI(a, b, c, d, e) {
return (b = Math, c = b.log, d = 1e3, e = c(a) / c(d) | 0, a / b.pow(d, e)).toFixed(2)
+ ' ' + (e ? 'kMGTPEZY'[--e] + 'B' : 'Bytes');
};

View File

@@ -1,70 +0,0 @@
import isString from 'lodash/isString';
import isNumber from 'lodash/isNumber';
import isEmpty from 'lodash/isEmpty';
import {Base64} from 'js-base64';
import API from './api';
import {HEADERS} from '../../lib/constants';
export function isTokenExpire(token) {
if (!isString(token)) {
return true;
}
let [,payload] = token.split('.');
if (!payload) {
return true;
}
try {
payload = JSON.parse(Base64.decode(payload));
} catch (error) {
// eslint-disable-next-line
console.error('Invalid token:', error, token);
return true;
}
if (!payload.exp || !isNumber(payload.exp)) {
return true;
}
// Report as expire before (real expire time - 30s)
const jsTimestamp = (payload.exp * 1000) - 30000;
const expired = Date.now() >= jsTimestamp;
return expired;
}
export async function makeLogin(username, password) {
// checks isEmpty
if (isEmpty(username) || isEmpty(password)) {
const error = {
title: 'Unable to login',
type: 'error',
description: 'Username or password can\'t be empty!',
};
return {error};
}
try {
const response = await API.request('login', 'POST', {
body: JSON.stringify({username, password}),
headers: {
Accept: HEADERS.JSON,
'Content-Type': HEADERS.JSON,
},
});
const result = {
username: response.username,
token: response.token,
};
return result;
} catch (e) {
const error = {
title: 'Unable to login',
type: 'error',
description: e.error,
};
return {error};
}
}

View File

@@ -1,87 +0,0 @@
import isString from 'lodash/isString';
import isObject from 'lodash/isObject';
import format from 'date-fns/format';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
export const TIMEFORMAT = 'DD.MM.YYYY, HH:mm:ss';
/**
* Formats license field for webui.
* @see https://docs.npmjs.com/files/package.json#license
*/
export function formatLicense(license) {
if (isString(license)) {
return license;
}
if (isObject(license) && license.type) {
return license.type;
}
return null;
}
/**
* Formats repository field for webui.
* @see https://docs.npmjs.com/files/package.json#repository
*/
export function formatRepository(repository) {
if (isString(repository)) {
return repository;
}
if (isObject(repository) && repository.url) {
return repository.url;
}
return null;
}
/**
* For <LastSync /> component
* @param {array} uplinks
*/
export function getLastUpdatedPackageTime(uplinks = {}) {
let lastUpdate = 0;
Object.keys(uplinks).forEach((upLinkName) => {
const status = uplinks[upLinkName];
if (status.fetched > lastUpdate) {
lastUpdate = status.fetched;
}
});
return lastUpdate ? formatDate(lastUpdate) : '';
}
/**
* For <LastSync /> component
* @param {Object} time
* @returns {Array} last 3 releases
*/
export function getRecentReleases(time = {}) {
const recent = Object.keys(time).map((version) => ({
version,
time: formatDate(time[version]),
}));
return recent.slice(recent.length - 3, recent.length).reverse();
}
export function formatDate(lastUpdate) {
return format(new Date(lastUpdate), TIMEFORMAT);
}
export function formatDateDistance(lastUpdate) {
return distanceInWordsToNow(new Date(lastUpdate));
}
export function getRouterPackageName(match) {
const packageName = match.params.package;
const scope = match.params.scope;
if (scope) {
return `@${scope}/${packageName}`;
}
return packageName;
}

View File

@@ -1,12 +0,0 @@
/**
* @prettier
* @flow
*/
// $FlowFixMe
import parseXSS from 'xss';
export function preventXSS(text: string) {
const encodedText = parseXSS(text);
return encodedText;
}

View File

@@ -1,12 +0,0 @@
import memoryStorage from 'localstorage-memory';
let storage;
try {
localStorage.setItem('__TEST__', '');
localStorage.removeItem('__TEST__');
storage = localStorage;
} catch (err) {
storage = memoryStorage;
}
export default storage;

5
src/utils/string.js Normal file
View File

@@ -0,0 +1,5 @@
// @flow
export function spliceURL(...args: Array<string>): string {
return Array.from(args).reduce((lastResult, current) => lastResult + current).replace(/([^:])(\/)+(.)/g, `$1/$3`);
}

View File

@@ -1,37 +0,0 @@
// Verdaccio
// -------------------------
// Main colors
// -------------------------
const colors = {
black: '#000',
white: '#fff',
red: '#d32f2f',
grey: '#808080',
greySuperLight: '#f5f5f5',
greyLight: '#d3d3d3',
greyLight2: '#908ba1',
greyLight3: '#f3f4f240',
greyDark: '#a9a9a9',
greyDark2: '#586069',
greyChateau: '#95989a',
greyGainsboro: '#e3e3e3',
greyAthens: '#d3dddd',
eclipse: '#3c3c3c',
paleNavy: '#e4e8f1',
saltpan: '#f7f8f6',
snow: '#f9f9f9',
nobel01: '#999999',
nobel02: '#9f9f9f',
// Main colors
// -------------------------
primary: '#4b5e40',
secondary: '#20232a',
};
export default colors;

View File

@@ -1,27 +0,0 @@
import { injectGlobal } from 'emotion';
import { fontFamilyBase } from './fonts';
import { fontSize, fontWeight } from './sizes';
import { textColor } from './colors';
export default injectGlobal`
html,
body {
height: 100%;
}
body {
font-family: ${fontFamilyBase};
font-size: ${fontSize.base};
color: ${textColor};
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
strong {
font-weight: ${fontWeight.semiBold};
}
`;

View File

@@ -1,29 +0,0 @@
import { css } from 'emotion';
export const breakpoints = {
small: 576,
medium: 768,
large: 1024,
xlarge: 1275,
};
const mq = Object.keys(breakpoints).reduce(
(accumulator, label) => {
const prefix =
typeof breakpoints[label] === 'string'
? ''
: 'min-width:';
const suffix =
typeof breakpoints[label] === 'string' ? '' : 'px';
accumulator[label] = cls =>
css`
@media (${prefix + breakpoints[label] + suffix}) {
${cls};
}
`;
return accumulator;
},
{}
);
export default mq;

View File

@@ -1,45 +0,0 @@
/**
* @prettier
* @flow
*/
/**
* CSS to represent truncated text with an ellipsis.
*/
export function ellipsis(width: string | number) {
return {
display: 'inline-block',
maxWidth: width,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
wordWrap: 'normal',
};
}
/**
* Shorthand that accepts up to four values, including null to skip a value, and maps them to their respective directions.
*/
interface SpacingShortHand<type> {
top?: type;
right?: type;
bottom?: type;
left?: type;
}
const positionMap = ['Top', 'Right', 'Bottom', 'Left'];
export function spacing(property: 'padding' | 'margin', ...values: Array<SpacingShortHand<number | string>>) {
const [firstValue = 0, secondValue = 0, thirdValue = 0, fourthValue = 0] = values;
const valuesWithDefaults = [firstValue, secondValue, thirdValue, fourthValue];
let styles = {};
for (let i = 0; i < valuesWithDefaults.length; i += 1) {
if (valuesWithDefaults[i] || valuesWithDefaults[i] === 0) {
styles = {
...styles,
[`${property}${positionMap[i]}`]: valuesWithDefaults[i],
};
}
}
return styles;
}

View File

@@ -1,23 +0,0 @@
export const fontSize = {
xxl: '26px',
xl: '24px',
lg: '21px',
md: '18px',
base: '16px',
sm: '14px',
};
export const lineHeight = {
xl: '30px',
sm: '18px',
xs: '2',
xxs: '1.5',
};
export const fontWeight = {
light: 300,
regular: 400,
semiBold: 500,
bold: 700,
};

View File

@@ -1,6 +0,0 @@
// Spacings
// -------------------------
export const spacings = {
lg: '30px',
};

View File

@@ -1,4 +0,0 @@
export function getRegistryURL() {
// Don't add slash if it's not a sub directory
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
}

47
src/utils/user.js Normal file
View File

@@ -0,0 +1,47 @@
// @flow
import {stringToMD5} from '../lib/crypto-utils';
import _ from 'lodash';
// this is a generic avatar
// https://www.iconfinder.com/icons/403017/anonym_avatar_default_head_person_unknown_user_icon
// license: free commercial usage
export const GENERIC_AVATAR: string = `
data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1
ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYW
NrZ3JvdW5kPSJuZXcgLTI3IDI0IDEwMCAxMDAiIGhlaWdodD0iMTAwcHgiIGlkPSJ1bmtub3duIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3
g9Ii0yNyAyNCAxMDAgMTAwIiB3aWR0aD0iMTAwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy
8yMDAwL3N2ZyIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiIHhtbG5zOnhsaW5rPS
JodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48Zz48Zz48ZGVmcz48Y2lyY2xlIGN4PSIyMyIgY3k9Ijc0IiBpZD0iY2lyY2xlIi
ByPSI1MCIvPjwvZGVmcz48dXNlIGZpbGw9IiNGNUVFRTUiIG92ZXJmbG93PSJ2aXNpYmxlIiB4bGluazpocmVmPSIjY2lyY2xlIi8+PGN
saXBQYXRoIGlkPSJjaXJjbGVfMV8iPjx1c2Ugb3ZlcmZsb3c9InZpc2libGUiIHhsaW5rOmhyZWY9IiNjaXJjbGUiLz48L2NsaXBQYXR
oPjxnIGNsaXAtcGF0aD0idXJsKCNjaXJjbGVfMV8pIj48ZGVmcz48cGF0aCBkPSJNMzYsOTUuOWMwLDQsNC43LDUuMiw3LjEsNS44Yzc
uNiwyLDIyLjgsNS45LDIyLjgsNS45YzMuMiwxLjEsNS43LDMuNSw3LjEsNi42djkuOEgtMjd2LTkuOCAgICAgICBjMS4zLTMuMSwzLjk
tNS41LDcuMS02LjZjMCwwLDE1LjItMy45LDIyLjgtNS45YzIuNC0wLjYsNy4xLTEuOCw3LjEtNS44YzAtNCwwLTEwLjksMC0xMC45aDI
2QzM2LDg1LDM2LDkxLjksMzYsOTUuOXoiIGlkPSJzaG91bGRlcnMiLz48L2RlZnM+PHVzZSBmaWxsPSIjRTZDMTlDIiBvdmVyZmxvdz0
idmlzaWJsZSIgeGxpbms6aHJlZj0iI3Nob3VsZGVycyIvPjxjbGlwUGF0aCBpZD0ic2hvdWxkZXJzXzFfIj48dXNlIG92ZXJmbG93PSJ
2aXNpYmxlIiB4bGluazpocmVmPSIjc2hvdWxkZXJzIi8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjc2hvdWxkZXJzXzF
fKSIgZD0iTTIzLjIsMzVjMC4xLDAsMC4xLDAsMC4yLDBjMCwwLDAsMCwwLDAgICAgICBjMy4zLDAsOC4yLDAuMiwxMS40LDJjMy4zLD
EuOSw3LjMsNS42LDguNSwxMi4xYzIuNCwxMy43LTIuMSwzNS40LTYuMyw0Mi40Yy00LDYuNy05LjgsOS4yLTEzLjUsOS40YzAsMC0wL
jEsMC0wLjEsMCAgICAgIGMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYzAsMC0wLjEsMC0wLjEsMGMtMy43LTAuM
i05LjUtMi43LTEzLjUtOS40Yy00LjItNy04LjctMjguNy02LjMtNDIuNCAgICAgIGMxLjItNi41LDUuMi0xMC4yLDguNS0xMi4xYzMuM
i0xLjgsOC4xLTIsMTEuNC0yYzAsMCwwLDAsMCwwQzIzLjEsMzUsMjMuMSwzNSwyMy4yLDM1TDIzLjIsMzV6IiBmaWxsPSIjRDRCMDhDI
iBpZD0iaGVhZC1zaGFkb3ciLz48L2c+PC9nPjxwYXRoIGQ9Ik0yMi42LDQwYzE5LjEsMCwyMC43LDEzLjgsMjAuOCwxNS4xYzEuMSwxM
S45LTMsMjguMS02LjgsMzMuN2MtNCw1LjktOS44LDguMS0xMy41LDguMyAgICBjLTAuMiwwLTAuMiwwLTAuMywwYy0wLjEsMC0wLjEs
MC0wLjIsMEMxOC44LDk2LjgsMTMsOTQuNiw5LDg4LjdjLTMuOC01LjYtNy45LTIxLjgtNi44LTMzLjhDMi4zLDUzLjcsMy41LDQwLDIyL
jYsNDB6IiBmaWxsPSIjRjJDRUE1IiBpZD0iaGVhZCIvPjwvZz48L3N2Zz4=`;
/**
* Generate gravatar url from email address
*/
export function generateGravatarUrl(email: string = '', online: boolean = true): string {
if (online) {
if (_.isString(email) && _.size(email) > 0) {
email = email.trim().toLocaleLowerCase();
const emailMD5 = stringToMD5(email);
return `https://www.gravatar.com/avatar/${emailMD5}`;
}
return GENERIC_AVATAR;
} else {
return GENERIC_AVATAR;
}
}

View File

@@ -1,3 +0,0 @@
export function goToVerdaccioWebsite() {
window.open('https://www.verdaccio.org/', '_blank');
}