forked from sombochea/verdaccio-ui
Merge branch '4.x-master' into 4.x-adds-unit-tests-for-repository-component
This commit is contained in:
commit
8774fd51c7
13
CHANGELOG.md
13
CHANGELOG.md
@ -2,6 +2,19 @@
|
||||
|
||||
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="0.2.2"></a>
|
||||
## [0.2.2](https://github.com/verdaccio/ui/compare/v0.2.1...v0.2.2) (2019-07-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* css repetition is not closed in Logo component ([ec243b1](https://github.com/verdaccio/ui/commit/ec243b1))
|
||||
* localhost domain download tarball button ([cca2c3c](https://github.com/verdaccio/ui/commit/cca2c3c))
|
||||
* proxy webpack setting ([5c484bb](https://github.com/verdaccio/ui/commit/5c484bb))
|
||||
* token were not being send it ([fd74c52](https://github.com/verdaccio/ui/commit/fd74c52))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.1"></a>
|
||||
## [0.2.1](https://github.com/verdaccio/ui/compare/v0.2.0...v0.2.1) (2019-07-10)
|
||||
|
||||
|
28
package.json
28
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@verdaccio/ui-theme",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"description": "Verdaccio User Interface",
|
||||
"author": {
|
||||
"name": "Verdaccio Core Team"
|
||||
@ -15,21 +15,21 @@
|
||||
"@commitlint/config-conventional": "8.0.0",
|
||||
"@material-ui/core": "3.9.3",
|
||||
"@material-ui/icons": "3.0.2",
|
||||
"@octokit/rest": "16.23.2",
|
||||
"@types/enzyme": "3.9.3",
|
||||
"@octokit/rest": "16.28.7",
|
||||
"@types/enzyme": "3.10.3",
|
||||
"@types/lodash": "4.14.134",
|
||||
"@types/material-ui": "0.21.6",
|
||||
"@types/node": "12.0.8",
|
||||
"@types/node": "12.6.8",
|
||||
"@types/react": "16.8.16",
|
||||
"@types/react-dom": "16.8.4",
|
||||
"@types/react-router-dom": "4.3.2",
|
||||
"@types/validator": "10.11.1",
|
||||
"@verdaccio/babel-preset": "0.2.1",
|
||||
"@verdaccio/eslint-config": "0.0.1",
|
||||
"@verdaccio/types": "6.1.0",
|
||||
"@verdaccio/babel-preset": "2.0.0",
|
||||
"@verdaccio/eslint-config": "2.0.0",
|
||||
"@verdaccio/types": "7.0.0",
|
||||
"autosuggest-highlight": "3.1.1",
|
||||
"babel-loader": "8.0.6",
|
||||
"bundlesize": "0.17.2",
|
||||
"bundlesize": "0.18.0",
|
||||
"codecov": "3.5.0",
|
||||
"concurrently": "4.1.0",
|
||||
"cross-env": "5.2.0",
|
||||
@ -43,7 +43,7 @@
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-prettier": "3.1.0",
|
||||
"eslint-plugin-react": "7.13.0",
|
||||
"eslint-plugin-verdaccio": "0.0.5",
|
||||
"eslint-plugin-verdaccio": "2.0.0",
|
||||
"file-loader": "2.0.0",
|
||||
"friendly-errors-webpack-plugin": "1.7.0",
|
||||
"get-stdin": "6.0.0",
|
||||
@ -86,17 +86,17 @@
|
||||
"stylelint-webpack-plugin": "0.10.5",
|
||||
"supertest": "4.0.2",
|
||||
"typeface-roboto": "0.0.54",
|
||||
"typescript": "3.5.2",
|
||||
"typescript": "3.5.3",
|
||||
"url-loader": "1.1.2",
|
||||
"validator": "10.11.0",
|
||||
"verdaccio": "4.0.3",
|
||||
"verdaccio-auth-memory": "0.0.4",
|
||||
"verdaccio": "4.1.0",
|
||||
"verdaccio-auth-memory": "1.1.5",
|
||||
"verdaccio-memory": "2.0.0",
|
||||
"webpack": "4.20.2",
|
||||
"webpack-bundle-analyzer": "3.3.2",
|
||||
"webpack-bundle-size-analyzer": "3.0.0",
|
||||
"webpack-cli": "3.2.3",
|
||||
"webpack-dev-server": "3.2.1",
|
||||
"webpack-cli": "3.3.6",
|
||||
"webpack-dev-server": "3.7.2",
|
||||
"webpack-merge": "4.2.1",
|
||||
"whatwg-fetch": "3.0.0",
|
||||
"xss": "1.0.6"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
|
||||
describe('<ActionBar /> component', () => {
|
||||
beforeEach(() => {
|
||||
@ -43,6 +43,31 @@ describe('<ActionBar /> component', () => {
|
||||
|
||||
const ActionBar = require('./ActionBar').default;
|
||||
const wrapper = shallow(<ActionBar />);
|
||||
// FIXME: this only renders the DetailContextConsumer, thus
|
||||
// the wrapper will be always empty
|
||||
expect(wrapper.html()).toEqual('');
|
||||
});
|
||||
|
||||
test('when there is a button to download a tarball', () => {
|
||||
const packageMeta = {
|
||||
latest: {
|
||||
dist: {
|
||||
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jest.doMock('../../pages/version/Version', () => ({
|
||||
DetailContextConsumer: component => {
|
||||
return component.children({ packageMeta });
|
||||
},
|
||||
}));
|
||||
|
||||
const ActionBar = require('./ActionBar').default;
|
||||
const wrapper = mount(<ActionBar />);
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
||||
const button = wrapper.find('button');
|
||||
expect(button).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
@ -8,7 +8,25 @@ import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
import { DetailContextConsumer, VersionPageConsumerProps } from '../../pages/version/Version';
|
||||
import { Fab, ActionListItem } from './styles';
|
||||
import { isURL } from '../../utils/url';
|
||||
import { isURL, extractFileName, downloadFile } from '../../utils/url';
|
||||
import api from '../../utils/api';
|
||||
|
||||
export interface Action {
|
||||
icon: string;
|
||||
title: string;
|
||||
handler?: Function;
|
||||
}
|
||||
|
||||
export async function downloadHandler(link: string): Promise<void> {
|
||||
const fileStream: Blob = await api.request(link, 'GET', {
|
||||
headers: {
|
||||
['accept']: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
const fileName = extractFileName(link);
|
||||
downloadFile(fileStream, fileName);
|
||||
}
|
||||
|
||||
const ACTIONS = {
|
||||
homepage: {
|
||||
@ -22,6 +40,7 @@ const ACTIONS = {
|
||||
tarball: {
|
||||
icon: <DownloadIcon />,
|
||||
title: 'Download tarball',
|
||||
handler: downloadHandler,
|
||||
},
|
||||
};
|
||||
|
||||
@ -54,16 +73,34 @@ class ActionBar extends Component {
|
||||
tarball,
|
||||
};
|
||||
|
||||
const renderList = Object.keys(actionsMap).reduce((component, value, key) => {
|
||||
const renderList = Object.keys(actionsMap).reduce((component: React.ReactElement[], value, key) => {
|
||||
const link = actionsMap[value];
|
||||
if (link && isURL(link)) {
|
||||
const fab = <Fab size={'small'}>{ACTIONS[value]['icon']}</Fab>;
|
||||
component.push(
|
||||
// @ts-ignore
|
||||
<Tooltip key={key} title={ACTIONS[value]['title']}>
|
||||
<>{this.renderIconsWithLink(link, fab)}</>
|
||||
</Tooltip>
|
||||
);
|
||||
const actionItem: Action = ACTIONS[value];
|
||||
if (actionItem.handler) {
|
||||
const fab = (
|
||||
<Tooltip key={key} title={actionItem['title']}>
|
||||
<Fab
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onClick={() => {
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
actionItem.handler!(link);
|
||||
}}
|
||||
size={'small'}>
|
||||
{actionItem['icon']}
|
||||
</Fab>
|
||||
</Tooltip>
|
||||
);
|
||||
component.push(fab);
|
||||
} else {
|
||||
const fab = <Fab size={'small'}>{actionItem['icon']}</Fab>;
|
||||
component.push(
|
||||
// @ts-ignore
|
||||
<Tooltip key={key} title={actionItem['title']}>
|
||||
<>{this.renderIconsWithLink(link, fab)}</>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
return component;
|
||||
}, []);
|
||||
|
@ -1,3 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ActionBar /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root-1 MuiList-padding-2\\"><li class=\\"MuiListItem-root-5 MuiListItem-default-8 MuiListItem-gutters-13 MuiListItem-alignItemsFlexStart-10 css-9q3x3c eux6shq0\\"><a href=\\"https://verdaccio.tld\\" target=\\"_blank\\"><button class=\\"MuiButtonBase-root-35 MuiFab-root-25 MuiFab-sizeSmall-33 css-96oxa0 eux6shq1\\" tabindex=\\"0\\" type=\\"button\\"><span class=\\"MuiFab-label-26\\"><svg class=\\"MuiSvgIcon-root-38\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z\\"></path><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path></svg></span></button></a><a href=\\"https://verdaccio.tld/bugs\\" target=\\"_blank\\"><button class=\\"MuiButtonBase-root-35 MuiFab-root-25 MuiFab-sizeSmall-33 css-96oxa0 eux6shq1\\" tabindex=\\"0\\" type=\\"button\\"><span class=\\"MuiFab-label-26\\"><svg class=\\"MuiSvgIcon-root-38\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z\\"></path></svg></span></button></a><a href=\\"https://verdaccio.tld/download\\" target=\\"_blank\\"><button class=\\"MuiButtonBase-root-35 MuiFab-root-25 MuiFab-sizeSmall-33 css-96oxa0 eux6shq1\\" tabindex=\\"0\\" type=\\"button\\"><span class=\\"MuiFab-label-26\\"><svg class=\\"MuiSvgIcon-root-38\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z\\"></path></svg></span></button></a></li></ul>"`;
|
||||
exports[`<ActionBar /> component should render the component in default state 1`] = `"<ul class=\\"MuiList-root-1 MuiList-padding-2\\"><li class=\\"MuiListItem-root-5 MuiListItem-default-8 MuiListItem-gutters-13 MuiListItem-alignItemsFlexStart-10 css-9q3x3c eux6shq0\\"><a href=\\"https://verdaccio.tld\\" target=\\"_blank\\"><button class=\\"MuiButtonBase-root-35 MuiFab-root-25 MuiFab-sizeSmall-33 css-96oxa0 eux6shq1\\" tabindex=\\"0\\" type=\\"button\\"><span class=\\"MuiFab-label-26\\"><svg class=\\"MuiSvgIcon-root-38\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path d=\\"M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z\\"></path><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path></svg></span></button></a><a href=\\"https://verdaccio.tld/bugs\\" target=\\"_blank\\"><button class=\\"MuiButtonBase-root-35 MuiFab-root-25 MuiFab-sizeSmall-33 css-96oxa0 eux6shq1\\" tabindex=\\"0\\" type=\\"button\\"><span class=\\"MuiFab-label-26\\"><svg class=\\"MuiSvgIcon-root-38\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z\\"></path></svg></span></button></a><button class=\\"MuiButtonBase-root-35 MuiFab-root-25 MuiFab-sizeSmall-33 css-96oxa0 eux6shq1\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Download tarball\\"><span class=\\"MuiFab-label-26\\"><svg class=\\"MuiSvgIcon-root-38\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z\\"></path></svg></span></button></li></ul>"`;
|
||||
|
||||
exports[`<ActionBar /> component when there is a button to download a tarball 1`] = `"<ul class=\\"MuiList-root-47 MuiList-padding-48\\"><li class=\\"MuiListItem-root-51 MuiListItem-default-54 MuiListItem-gutters-59 MuiListItem-alignItemsFlexStart-56 css-9q3x3c eux6shq0\\"><button class=\\"MuiButtonBase-root-81 MuiFab-root-71 MuiFab-sizeSmall-79 css-96oxa0 eux6shq1\\" tabindex=\\"0\\" type=\\"button\\" title=\\"Download tarball\\"><span class=\\"MuiFab-label-72\\"><svg class=\\"MuiSvgIcon-root-84\\" focusable=\\"false\\" viewBox=\\"0 0 24 24\\" aria-hidden=\\"true\\" role=\\"presentation\\"><path fill=\\"none\\" d=\\"M0 0h24v24H0z\\"></path><path d=\\"M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z\\"></path></svg></span><span class=\\"MuiTouchRipple-root-93\\"></span></button></li></ul>"`;
|
||||
|
28
src/utils/api.test.ts
Normal file
28
src/utils/api.test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/* eslint-disable @typescript-eslint/no-object-literal-type-assertion */
|
||||
|
||||
import { handleResponseType } from '../../src/utils/api';
|
||||
|
||||
describe('api', () => {
|
||||
// no the best mock, but I'd look for a mock library to work with fetch in the future
|
||||
// @ts-ignore
|
||||
const headers: Headers = {
|
||||
// @ts-ignore
|
||||
get: () => [],
|
||||
};
|
||||
|
||||
describe('handleResponseType', () => {
|
||||
test('should test tgz scenario', async () => {
|
||||
const blob = new Blob(['foo']);
|
||||
const blobPromise = Promise.resolve<Blob>(blob);
|
||||
const response: Response = {
|
||||
url: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
|
||||
blob: () => blobPromise,
|
||||
ok: true,
|
||||
headers,
|
||||
} as Response;
|
||||
const handled = await handleResponseType(response);
|
||||
|
||||
expect(handled).toEqual([true, blob]);
|
||||
});
|
||||
});
|
||||
});
|
@ -6,7 +6,7 @@ import '../../types';
|
||||
* @param {object} response
|
||||
* @returns {promise}
|
||||
*/
|
||||
function handleResponseType(response: Response): Promise<[boolean, Blob | string]> | Promise<void> {
|
||||
export function handleResponseType(response: Response): Promise<[boolean, Blob | string]> | Promise<void> {
|
||||
if (response.headers) {
|
||||
const contentType = response.headers.get('Content-Type') as string;
|
||||
if (contentType.includes('application/pdf')) {
|
||||
@ -19,22 +19,27 @@ function handleResponseType(response: Response): Promise<[boolean, Blob | string
|
||||
if (contentType.includes('text/')) {
|
||||
return Promise.all([response.ok, response.text()]);
|
||||
}
|
||||
|
||||
// unfortunatelly on download files there is no header available
|
||||
if (response.url && response.url.endsWith('.tgz') !== null) {
|
||||
return Promise.all([response.ok, response.blob()]);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
class API {
|
||||
public request<T>(url: string, method = 'GET', options?: RequestInit): Promise<T> {
|
||||
public request<T>(url: string, method = 'GET', options: RequestInit = { headers: {} }): Promise<T> {
|
||||
if (!window.VERDACCIO_API_URL) {
|
||||
throw new Error('VERDACCIO_API_URL is not defined!');
|
||||
}
|
||||
|
||||
const token = storage.getItem('token');
|
||||
const headers = new Headers(options && options.headers);
|
||||
if (token && options && options.headers) {
|
||||
headers.set('Authorization', `Bearer ${token}`);
|
||||
options.headers = Object.assign(options.headers, headers);
|
||||
if (token && options.headers && typeof options.headers['Authorization'] === 'undefined') {
|
||||
options.headers = Object.assign({}, options.headers, {
|
||||
['Authorization']: `Bearer ${token}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!['http://', 'https://', '//'].some(prefix => url.startsWith(prefix))) {
|
||||
@ -50,11 +55,11 @@ class API {
|
||||
})
|
||||
// @ts-ignore
|
||||
.then(handleResponseType)
|
||||
.then(([responseOk, body]) => {
|
||||
if (responseOk) {
|
||||
resolve(body);
|
||||
.then(response => {
|
||||
if (response[0]) {
|
||||
resolve(response[1]);
|
||||
} else {
|
||||
reject(body);
|
||||
reject(new Error('something went wrong'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
@ -1,27 +1,36 @@
|
||||
import { isURL, isEmail, getRegistryURL } from './url';
|
||||
import { isURL, isEmail, getRegistryURL, extractFileName } from './url';
|
||||
|
||||
describe('url', () => {
|
||||
test('isURL() - should return true for localhost', () => {
|
||||
expect(isURL('http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz')).toBeTruthy();
|
||||
describe('utils', () => {
|
||||
describe('url', () => {
|
||||
test('isURL() - should return true for localhost', () => {
|
||||
expect(isURL('http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('isURL() - should return false when protocol is missing', () => {
|
||||
expect(isURL('localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('isEmail() - should return true if valid', () => {
|
||||
expect(isEmail('email@domain.com')).toBeTruthy();
|
||||
});
|
||||
test('isEmail() - should return false if invalid', () => {
|
||||
expect(isEmail('')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('getRegistryURL() - should keep slash if location is a sub directory', () => {
|
||||
history.pushState({}, 'page title', '/-/web/detail');
|
||||
expect(getRegistryURL()).toBe('http://localhost/-/web/detail');
|
||||
history.pushState({}, 'page title', '/');
|
||||
});
|
||||
|
||||
test('getRegistryURL() - should not add slash if location is not a sub directory', () => {
|
||||
expect(getRegistryURL()).toBe('http://localhost');
|
||||
});
|
||||
});
|
||||
|
||||
test('isURL() - should return false when protocol is missing', () => {
|
||||
expect(isURL('localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('isEmail() - should return true if valid', () => {
|
||||
expect(isEmail('email@domain.com')).toBeTruthy();
|
||||
});
|
||||
test('isEmail() - should return false if invalid', () => {
|
||||
expect(isEmail('')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('getRegistryURL() - should keep slash if location is a sub directory', () => {
|
||||
history.pushState({}, 'page title', '/-/web/detail');
|
||||
expect(getRegistryURL()).toBe('http://localhost/-/web/detail');
|
||||
history.pushState({}, 'page title', '/');
|
||||
});
|
||||
test('getRegistryURL() - should not add slash if location is not a sub directory', () => {
|
||||
expect(getRegistryURL()).toBe('http://localhost');
|
||||
describe('extractFileName', () => {
|
||||
test('should return the file name', () => {
|
||||
expect(extractFileName('http://localhost:4872/juan_test_webpack1/-/test-10.0.0.tgz')).toBe('test-10.0.0.tgz');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -18,3 +18,20 @@ export function getRegistryURL(): string {
|
||||
// Don't add slash if it's not a sub directory
|
||||
return `${location.origin}${location.pathname === '/' ? '' : location.pathname}`;
|
||||
}
|
||||
|
||||
export function extractFileName(url: string): string {
|
||||
return url.substring(url.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
export function downloadFile(fileStream: Blob, fileName: string): void {
|
||||
const file = new File([fileStream], fileName, { type: 'application/octet-stream', lastModified: Date.now() });
|
||||
const objectURL = URL.createObjectURL(file);
|
||||
const fileLink = document.createElement('a');
|
||||
fileLink.href = objectURL;
|
||||
fileLink.download = fileName;
|
||||
fileLink.click();
|
||||
// firefox requires remove the object url
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(objectURL);
|
||||
}, 150);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ new WebpackDevServer(compiler, {
|
||||
},
|
||||
proxy: [
|
||||
{
|
||||
context: ['/-/verdaccio/logo', '/-/verdaccio/packages', '/-/static/logo.png'],
|
||||
context: ['/-/verdaccio/**', '**/*.tgz'],
|
||||
target: 'http://localhost:8080',
|
||||
},
|
||||
],
|
||||
|
@ -41,7 +41,7 @@ export default {
|
||||
scope: '',
|
||||
logo: 'https://verdaccio.org/img/logo/symbol/svg/verdaccio-tiny.svg',
|
||||
filename: 'index.html',
|
||||
verdaccioURL: '//localhost:8080',
|
||||
verdaccioURL: '//localhost:4872',
|
||||
template: `${env.SRC_ROOT}/template/index.html`,
|
||||
debug: true,
|
||||
inject: true,
|
||||
|
Loading…
Reference in New Issue
Block a user