mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
feats: components UI for custom user interfaces (#3548)
This commit is contained in:
parent
86d813840d
commit
9997879743
409 changed files with 124796 additions and 9391 deletions
27
.changeset/orange-cows-pull.md
Normal file
27
.changeset/orange-cows-pull.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
'@verdaccio/types': major
|
||||||
|
'@verdaccio/ui-theme': major
|
||||||
|
'@verdaccio/ui-components': major
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(web): components for custom user interfaces
|
||||||
|
|
||||||
|
Provides a package that includes all components from the user interface, instead being embedded at the `@verdaccio/ui-theme` package.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -D @verdaccio/ui-components
|
||||||
|
```
|
||||||
|
|
||||||
|
The package contains
|
||||||
|
|
||||||
|
- Components
|
||||||
|
- Providers
|
||||||
|
- Redux Storage
|
||||||
|
- Layouts (precomposed layouts ready to use)
|
||||||
|
- Custom Material Theme
|
||||||
|
|
||||||
|
The `@verdaccio/ui-theme` will consume this package and will use only those are need it.
|
||||||
|
|
||||||
|
> Prerequisites are using Redux, Material-UI and Translations with `i18next`.
|
||||||
|
|
||||||
|
Users could have their own Material UI theme and build custom layouts, adding new features without the need to modify the default project.
|
|
@ -14,3 +14,7 @@ test/functional/store/*
|
||||||
docker-examples/**/lib/**/*.js
|
docker-examples/**/lib/**/*.js
|
||||||
test/cli/e2e-yarn4/bin/yarn-4.0.0-rc.14.cjs
|
test/cli/e2e-yarn4/bin/yarn-4.0.0-rc.14.cjs
|
||||||
yarn.js
|
yarn.js
|
||||||
|
# storybook
|
||||||
|
packages/ui-components/storybook-static
|
||||||
|
dist.js
|
||||||
|
bundle.js
|
||||||
|
|
74
.github/workflows/ui-components.yml
vendored
Normal file
74
.github/workflows/ui-components.yml
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
name: UI Components
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- .github/workflows/ui-components.yml
|
||||||
|
- 'packages/ui-components/**'
|
||||||
|
- 'package.json'
|
||||||
|
- 'pnpm-workspace.yaml'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
deployments: write
|
||||||
|
pull-requests: write # to comment on pull-requests
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max_old_space_size=4096
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # tag=v3
|
||||||
|
|
||||||
|
- name: Use Node
|
||||||
|
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # tag=v3
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
- name: Cache pnpm modules
|
||||||
|
uses: actions/cache@c1a5de879eb890d062a85ee0252d6036480b1fe2 # v3
|
||||||
|
env:
|
||||||
|
cache-name: cache-pnpm-modules
|
||||||
|
with:
|
||||||
|
path: ~/.pnpm-store
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
run: |
|
||||||
|
corepack enable
|
||||||
|
corepack prepare --activate pnpm@6.32.15
|
||||||
|
- name: Install
|
||||||
|
run: pnpm recursive install --frozen-lockfile
|
||||||
|
- name: Build storybook
|
||||||
|
run: pnpm ui:storybook:build
|
||||||
|
- name: Copy public content
|
||||||
|
# the msw.js worker is need it at the storybook-static folder in production
|
||||||
|
run: cp -R packages/ui-components/public/* packages/ui-components/storybook-static
|
||||||
|
- name: 🔥 Deploy Production UI Netlify
|
||||||
|
if: (github.event_name == 'push' && github.ref == 'refs/heads/master') || github.event_name == 'workflow_dispatch'
|
||||||
|
uses: verdaccio/action-netlify-deploy@1a53f098745bf78555d11b436f5ee3af87e6b566
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
netlify-site-id: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
||||||
|
build-dir: './packages/ui-components/storybook-static'
|
||||||
|
- name: 🤖 Deploy Preview UI Components Netlify
|
||||||
|
if: github.repository == 'verdaccio/verdaccio'
|
||||||
|
uses: verdaccio/action-netlify-deploy@1a53f098745bf78555d11b436f5ee3af87e6b566
|
||||||
|
id: netlify_preview_ui
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
comment-on-pull-request: true
|
||||||
|
github-deployment-is-production: false
|
||||||
|
github-deployment-is-transient: true
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
netlify-site-id: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
||||||
|
build-dir: './packages/ui-components/storybook-static'
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -41,13 +41,6 @@ packages/plugins/ui-theme/static
|
||||||
# CI Pnpm cache
|
# CI Pnpm cache
|
||||||
.pnpm-store/
|
.pnpm-store/
|
||||||
|
|
||||||
# benchmark
|
|
||||||
api-results.json
|
|
||||||
hyper-results.json
|
|
||||||
hyper-results*.json
|
|
||||||
api-results*.json
|
|
||||||
.clinic/
|
|
||||||
|
|
||||||
#docs
|
#docs
|
||||||
website/docs/api/**/*.md
|
website/docs/api/**/*.md
|
||||||
website/docs/api/**/*.yml
|
website/docs/api/**/*.yml
|
||||||
|
@ -57,3 +50,6 @@ packages/**/docs
|
||||||
# cypress
|
# cypress
|
||||||
e2e/ui/cypress/videos/**/*
|
e2e/ui/cypress/videos/**/*
|
||||||
e2e/ui/cypress/screenshots/**/*
|
e2e/ui/cypress/screenshots/**/*
|
||||||
|
|
||||||
|
# storybook
|
||||||
|
packages/ui-components/storybook-static
|
||||||
|
|
|
@ -36,3 +36,4 @@ packages/plugins/ui-theme/static/
|
||||||
test/cli/e2e-yarn4/bin/yarn-4.0.0-rc.14.cjs
|
test/cli/e2e-yarn4/bin/yarn-4.0.0-rc.14.cjs
|
||||||
yarn.js
|
yarn.js
|
||||||
website/docs/api/*
|
website/docs/api/*
|
||||||
|
packages/ui-components/storybook-static/*
|
||||||
|
|
|
@ -45,12 +45,11 @@
|
||||||
"@crowdin/cli": "3.9.1",
|
"@crowdin/cli": "3.9.1",
|
||||||
"@emotion/react": "11.10.5",
|
"@emotion/react": "11.10.5",
|
||||||
"@emotion/styled": "11.10.5",
|
"@emotion/styled": "11.10.5",
|
||||||
"@mui/material": "5.11.1",
|
"@testing-library/dom": "8.19.1",
|
||||||
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
|
"@testing-library/react": "12.1.4",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"@mui/icons-material": "5.11.0",
|
|
||||||
"@mui/styles": "5.11.1",
|
|
||||||
"@mui/system": "5.11.1",
|
|
||||||
"@trivago/prettier-plugin-sort-imports": "3.4.0",
|
"@trivago/prettier-plugin-sort-imports": "3.4.0",
|
||||||
"@types/async": "3.2.16",
|
"@types/async": "3.2.16",
|
||||||
"@types/express": "4.17.15",
|
"@types/express": "4.17.15",
|
||||||
|
@ -150,6 +149,8 @@
|
||||||
"ci:publish": "changeset publish",
|
"ci:publish": "changeset publish",
|
||||||
"ts:ref": "update-ts-references --discardComments",
|
"ts:ref": "update-ts-references --discardComments",
|
||||||
"website": "pnpm build --filter ...@verdaccio/website",
|
"website": "pnpm build --filter ...@verdaccio/website",
|
||||||
|
"ui:storybook:build": "pnpm build-storybook --filter ...@verdaccio/ui-components",
|
||||||
|
"ui:storybook": "pnpm storybook --filter ...@verdaccio/ui-components",
|
||||||
"translations": "local-crowdin-api translations",
|
"translations": "local-crowdin-api translations",
|
||||||
"crowdin:upload": "crowdin upload sources --auto-update --config ./crowdin.yaml",
|
"crowdin:upload": "crowdin upload sources --auto-update --config ./crowdin.yaml",
|
||||||
"crowdin:download": "crowdin download --verbose --config ./crowdin.yaml",
|
"crowdin:download": "crowdin download --verbose --config ./crowdin.yaml",
|
||||||
|
|
|
@ -126,7 +126,7 @@ export type TemplateUIOptions = {
|
||||||
showDownloadTarball?: boolean;
|
showDownloadTarball?: boolean;
|
||||||
showRaw?: boolean;
|
showRaw?: boolean;
|
||||||
base: string;
|
base: string;
|
||||||
primaryColor?: string;
|
primaryColor: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
logoURI?: string;
|
logoURI?: string;
|
||||||
flags: FlagsConfig;
|
flags: FlagsConfig;
|
||||||
|
|
|
@ -35,8 +35,6 @@ module.exports = Object.assign({}, config, {
|
||||||
// note: this section has to be on sync with webpack configuration
|
// note: this section has to be on sync with webpack configuration
|
||||||
'verdaccio-ui/components/(.*)': '<rootDir>/src/components/$1',
|
'verdaccio-ui/components/(.*)': '<rootDir>/src/components/$1',
|
||||||
'verdaccio-ui/utils/(.*)': '<rootDir>/src/utils/$1',
|
'verdaccio-ui/utils/(.*)': '<rootDir>/src/utils/$1',
|
||||||
'verdaccio-ui/providers/(.*)': '<rootDir>/src/providers/$1',
|
|
||||||
'verdaccio-ui/design-tokens/(.*)': '<rootDir>/src/design-tokens/$1',
|
|
||||||
'react-markdown': '<rootDir>/src/__mocks__/react-markdown.tsx',
|
'react-markdown': '<rootDir>/src/__mocks__/react-markdown.tsx',
|
||||||
'remark-*': '<rootDir>/src/__mocks__/remark-plugin.ts',
|
'remark-*': '<rootDir>/src/__mocks__/remark-plugin.ts',
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,16 +12,12 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://verdaccio.org",
|
"homepage": "https://verdaccio.org",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"peerDependencies": {
|
"devDependencies": {
|
||||||
"@emotion/react": "11.10.5",
|
"@emotion/react": "11.10.5",
|
||||||
"@emotion/styled": "11.10.5",
|
"@emotion/styled": "11.10.5",
|
||||||
"@mui/material": "5.11.1",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"@mui/icons-material": "5.11.0",
|
"@mui/icons-material": "5.11.0",
|
||||||
"@mui/styles": "5.11.1"
|
"@mui/styles": "5.11.1",
|
||||||
},
|
"@mui/material": "5.11.1",
|
||||||
"devDependencies": {
|
|
||||||
"@emotion/babel-plugin": "11.10.5",
|
"@emotion/babel-plugin": "11.10.5",
|
||||||
"@emotion/css": "11.10.5",
|
"@emotion/css": "11.10.5",
|
||||||
"@emotion/jest": "11.10.5",
|
"@emotion/jest": "11.10.5",
|
||||||
|
@ -34,6 +30,7 @@
|
||||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.52",
|
"@verdaccio/node-api": "workspace:6.0.0-6-next.52",
|
||||||
"@verdaccio/ui-components": "workspace:2.0.0-alpha.0",
|
"@verdaccio/ui-components": "workspace:2.0.0-alpha.0",
|
||||||
"@verdaccio/types": "workspace:*",
|
"@verdaccio/types": "workspace:*",
|
||||||
|
"normalize.css": "8.0.1",
|
||||||
"babel-loader": "8.3.0",
|
"babel-loader": "8.3.0",
|
||||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||||
"country-flag-icons": "1.5.5",
|
"country-flag-icons": "1.5.5",
|
||||||
|
@ -53,7 +50,6 @@
|
||||||
"msw": "0.49.2",
|
"msw": "0.49.2",
|
||||||
"mutationobserver-shim": "0.3.7",
|
"mutationobserver-shim": "0.3.7",
|
||||||
"node-mocks-http": "1.12.1",
|
"node-mocks-http": "1.12.1",
|
||||||
"normalize.css": "8.0.1",
|
|
||||||
"optimize-css-assets-webpack-plugin": "6.0.1",
|
"optimize-css-assets-webpack-plugin": "6.0.1",
|
||||||
"ora": "5.4.1",
|
"ora": "5.4.1",
|
||||||
"dompurify": "2.4.1",
|
"dompurify": "2.4.1",
|
||||||
|
|
|
@ -1,30 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { renderWithStore, screen } from 'verdaccio-ui/utils/test-react-testing-library';
|
import { renderWithStore, screen } from 'verdaccio-ui/utils/test-react-testing-library';
|
||||||
|
|
||||||
import { store } from '../store';
|
import { store } from '@verdaccio/ui-components';
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
jest.mock('verdaccio-ui/utils/storage', () => {
|
import App from './App';
|
||||||
class LocalStorageMock {
|
|
||||||
private store: Record<string, string>;
|
|
||||||
public constructor() {
|
|
||||||
this.store = {};
|
|
||||||
}
|
|
||||||
public clear(): void {
|
|
||||||
this.store = {};
|
|
||||||
}
|
|
||||||
public getItem(key: string): unknown {
|
|
||||||
return this.store[key] || null;
|
|
||||||
}
|
|
||||||
public setItem(key: string, value: string): void {
|
|
||||||
this.store[key] = value.toString();
|
|
||||||
}
|
|
||||||
public removeItem(key: string): void {
|
|
||||||
delete this.store[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new LocalStorageMock();
|
|
||||||
});
|
|
||||||
|
|
||||||
// force the windows to expand to display items
|
// force the windows to expand to display items
|
||||||
// https://github.com/bvaughn/react-virtualized/issues/493#issuecomment-640084107
|
// https://github.com/bvaughn/react-virtualized/issues/493#issuecomment-640084107
|
||||||
|
@ -40,6 +19,7 @@ describe('<App />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not display the Header component', () => {
|
test('should not display the Header component', () => {
|
||||||
|
// @ts-ignore
|
||||||
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||||
showFooter: false,
|
showFooter: false,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,33 @@
|
||||||
|
/* eslint-disable react/jsx-pascal-case */
|
||||||
|
|
||||||
/* eslint-disable react/jsx-max-depth */
|
/* eslint-disable react/jsx-max-depth */
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import FlagsIcon from 'country-flag-icons/react/3x2';
|
||||||
import React, { StrictMode, Suspense, useEffect } from 'react';
|
import React, { StrictMode, Suspense, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import Loading from 'verdaccio-ui/components/Loading';
|
import remarkGfm from 'remark-gfm';
|
||||||
import StyleBaseline from 'verdaccio-ui/design-tokens/StyleBaseline';
|
|
||||||
import loadDayJSLocale from 'verdaccio-ui/design-tokens/load-dayjs-locale';
|
|
||||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
|
||||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
|
||||||
|
|
||||||
import '../i18n/config';
|
import {
|
||||||
|
Footer,
|
||||||
|
Header,
|
||||||
|
HeaderInfoDialog,
|
||||||
|
Loading,
|
||||||
|
Theme,
|
||||||
|
TranslatorProvider,
|
||||||
|
useConfig,
|
||||||
|
} from '@verdaccio/ui-components';
|
||||||
|
|
||||||
|
import Contributors from '../components/Contributors';
|
||||||
|
import Support from '../components/Support';
|
||||||
|
import about from '../components/about.md';
|
||||||
|
import license from '../components/license.md';
|
||||||
|
import i18n from '../i18n/config';
|
||||||
|
import { listLanguages } from '../i18n/enabledLanguages';
|
||||||
|
import loadDayJSLocale from '../i18n/load-dayjs-locale';
|
||||||
import AppRoute, { history } from './AppRoute';
|
import AppRoute, { history } from './AppRoute';
|
||||||
import Footer from './Footer';
|
|
||||||
import Header from './Header';
|
|
||||||
|
|
||||||
const StyledBox = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
|
const StyledBox = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
|
||||||
backgroundColor: theme?.palette.background.default,
|
backgroundColor: theme?.palette.background.default,
|
||||||
|
@ -27,6 +42,45 @@ const StyledBoxContent = styled(Box)<{ theme?: Theme }>(({ theme }) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const Flags = styled('span')<{ theme?: Theme }>(() => ({
|
||||||
|
width: '25px',
|
||||||
|
}));
|
||||||
|
|
||||||
|
function CustomInfoDialog({ onCloseDialog, title, isOpen }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<HeaderInfoDialog
|
||||||
|
dialogTitle={title}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCloseDialog={onCloseDialog}
|
||||||
|
tabPanels={[
|
||||||
|
{
|
||||||
|
element: (
|
||||||
|
<>
|
||||||
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{about}</ReactMarkdown>
|
||||||
|
<Contributors />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ element: <ReactMarkdown remarkPlugins={[remarkGfm]}>{license}</ReactMarkdown> },
|
||||||
|
{ element: <Support /> },
|
||||||
|
]}
|
||||||
|
tabs={[
|
||||||
|
{ label: t('about') },
|
||||||
|
{ label: t('dialog.license') },
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
icon: (
|
||||||
|
<Flags>
|
||||||
|
<FlagsIcon.UA />
|
||||||
|
</Flags>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const { configOptions } = useConfig();
|
const { configOptions } = useConfig();
|
||||||
|
|
||||||
|
@ -35,20 +89,23 @@ const App: React.FC = () => {
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<Suspense fallback={<Loading />}>
|
<TranslatorProvider
|
||||||
<StyleBaseline />
|
i18n={i18n}
|
||||||
<StyledBox display="flex" flexDirection="column" height="100%">
|
listLanguages={listLanguages}
|
||||||
<>
|
loadDayJSLocale={loadDayJSLocale}
|
||||||
|
>
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<StyledBox display="flex" flexDirection="column" height="100%">
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Header />
|
<Header HeaderInfoDialog={CustomInfoDialog} />
|
||||||
<StyledBoxContent flexGrow={1}>
|
<StyledBoxContent flexGrow={1}>
|
||||||
<AppRoute />
|
<AppRoute />
|
||||||
</StyledBoxContent>
|
</StyledBoxContent>
|
||||||
</Router>
|
</Router>
|
||||||
{configOptions.showFooter && <Footer />}
|
{configOptions.showFooter && <Footer />}
|
||||||
</>
|
</StyledBox>
|
||||||
</StyledBox>
|
</Suspense>
|
||||||
</Suspense>
|
</TranslatorProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,26 +2,13 @@ import { createBrowserHistory } from 'history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route as ReactRouterDomRoute, Router, Switch } from 'react-router-dom';
|
import { Route as ReactRouterDomRoute, Router, Switch } from 'react-router-dom';
|
||||||
|
|
||||||
import loadable from './utils/loadable';
|
import { NotFound, Route, VersionProvider, loadable } from '@verdaccio/ui-components';
|
||||||
|
|
||||||
const NotFound = loadable(
|
|
||||||
() => import(/* webpackChunkName: "NotFound" */ 'verdaccio-ui/components/NotFound')
|
|
||||||
);
|
|
||||||
const VersionContextProvider = loadable(
|
|
||||||
() => import(/* webpackChunkName: "Provider" */ '../pages/Version/VersionContextProvider')
|
|
||||||
);
|
|
||||||
const VersionPage = loadable(() => import(/* webpackChunkName: "Version" */ '../pages/Version'));
|
const VersionPage = loadable(() => import(/* webpackChunkName: "Version" */ '../pages/Version'));
|
||||||
const HomePage = loadable(() => import(/* webpackChunkName: "Home" */ '../pages/home'));
|
const Front = loadable(() => import(/* webpackChunkName: "Home" */ '../pages/Front'));
|
||||||
|
|
||||||
enum Route {
|
|
||||||
ROOT = '/',
|
|
||||||
SCOPE_PACKAGE = '/-/web/detail/@:scope/:package',
|
|
||||||
SCOPE_PACKAGE_VERSION = '/-/web/detail/@:scope/:package/v/:version',
|
|
||||||
PACKAGE = '/-/web/detail/:package',
|
|
||||||
PACKAGE_VERSION = '/-/web/detail/:package/v/:version',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const history = createBrowserHistory({
|
export const history = createBrowserHistory({
|
||||||
|
// @ts-ignore
|
||||||
basename: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.url_prefix,
|
basename: window?.__VERDACCIO_BASENAME_UI_OPTIONS?.url_prefix,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,27 +17,27 @@ const AppRoute: React.FC = () => {
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
|
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
|
||||||
<HomePage />
|
<Front />
|
||||||
</ReactRouterDomRoute>
|
</ReactRouterDomRoute>
|
||||||
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
|
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
|
||||||
<VersionContextProvider>
|
<VersionProvider>
|
||||||
<VersionPage />
|
<VersionPage />
|
||||||
</VersionContextProvider>
|
</VersionProvider>
|
||||||
</ReactRouterDomRoute>
|
</ReactRouterDomRoute>
|
||||||
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
|
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
|
||||||
<VersionContextProvider>
|
<VersionProvider>
|
||||||
<VersionPage />
|
<VersionPage />
|
||||||
</VersionContextProvider>
|
</VersionProvider>
|
||||||
</ReactRouterDomRoute>
|
</ReactRouterDomRoute>
|
||||||
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
|
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
|
||||||
<VersionContextProvider>
|
<VersionProvider>
|
||||||
<VersionPage />
|
<VersionPage />
|
||||||
</VersionContextProvider>
|
</VersionProvider>
|
||||||
</ReactRouterDomRoute>
|
</ReactRouterDomRoute>
|
||||||
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
|
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
|
||||||
<VersionContextProvider>
|
<VersionProvider>
|
||||||
<VersionPage />
|
<VersionPage />
|
||||||
</VersionContextProvider>
|
</VersionProvider>
|
||||||
</ReactRouterDomRoute>
|
</ReactRouterDomRoute>
|
||||||
<ReactRouterDomRoute>
|
<ReactRouterDomRoute>
|
||||||
<NotFound />
|
<NotFound />
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
/* eslint-disable react/jsx-pascal-case */
|
|
||||||
|
|
||||||
/* eslint-disable verdaccio/jsx-spread */
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { Theme } from '@mui/material';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Tab from '@mui/material/Tab';
|
|
||||||
import Tabs from '@mui/material/Tabs';
|
|
||||||
import FlagsIcon from 'country-flag-icons/react/3x2';
|
|
||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import ReactMarkdown from 'react-markdown';
|
|
||||||
import remarkGfm from 'remark-gfm';
|
|
||||||
|
|
||||||
import Contributors from './Contributors';
|
|
||||||
import RegistryInfoDialog from './RegistryInfoDialog';
|
|
||||||
import { Support } from './Support';
|
|
||||||
import about from './about.md';
|
|
||||||
import license from './license.md';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
isOpen: boolean;
|
|
||||||
onCloseDialog: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function a11yProps(index) {
|
|
||||||
return {
|
|
||||||
id: `simple-tab-${index}`,
|
|
||||||
'aria-controls': `simple-tabpanel-${index}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function TabPanel(props) {
|
|
||||||
const { children, value, index, ...other } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
|
||||||
hidden={value !== index}
|
|
||||||
id={`simple-tabpanel-${index}`}
|
|
||||||
role="tabpanel"
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
{value === index && <Box sx={{ paddingTop: 3 }}>{children}</Box>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Flags = styled('span')<{ theme?: Theme }>(() => ({
|
|
||||||
width: '25px',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const HeaderInfoDialog: React.FC<Props> = ({ onCloseDialog, isOpen }) => {
|
|
||||||
const [value, setValue] = React.useState(0);
|
|
||||||
|
|
||||||
const handleChange = (_event, newValue) => {
|
|
||||||
setValue(newValue);
|
|
||||||
};
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<RegistryInfoDialog
|
|
||||||
onClose={onCloseDialog}
|
|
||||||
open={isOpen}
|
|
||||||
title={t('dialog.registry-info.title')}
|
|
||||||
>
|
|
||||||
<Box sx={{ width: '100%' }}>
|
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
|
||||||
<Tabs aria-label="basic tabs example" onChange={handleChange} value={value}>
|
|
||||||
<Tab label={t('about')} {...a11yProps(0)} />
|
|
||||||
<Tab label={t('dialog.license')} {...a11yProps(1)} />
|
|
||||||
<Tab
|
|
||||||
{...a11yProps(2)}
|
|
||||||
icon={
|
|
||||||
<Flags>
|
|
||||||
<FlagsIcon.UA />
|
|
||||||
</Flags>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
</Box>
|
|
||||||
<TabPanel index={0} value={value}>
|
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{about}</ReactMarkdown>
|
|
||||||
<Contributors />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel index={1} value={value}>
|
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{license}</ReactMarkdown>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel index={2} value={value}>
|
|
||||||
<Support />
|
|
||||||
</TabPanel>
|
|
||||||
</Box>
|
|
||||||
</RegistryInfoDialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default HeaderInfoDialog;
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { Support } from './Support';
|
|
||||||
|
|
||||||
export { Support };
|
|
|
@ -1,125 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
cleanup,
|
|
||||||
fireEvent,
|
|
||||||
renderWithStore,
|
|
||||||
screen,
|
|
||||||
} from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import { DetailContext, DetailContextProps } from '../../pages/Version';
|
|
||||||
import { store } from '../../store/store';
|
|
||||||
import ActionBar, { Props } from './ActionBar';
|
|
||||||
|
|
||||||
const detailContextValue: DetailContextProps = {
|
|
||||||
packageName: 'foo',
|
|
||||||
readMe: 'test',
|
|
||||||
enableLoading: () => {},
|
|
||||||
isLoading: false,
|
|
||||||
hasNotBeenFound: false,
|
|
||||||
packageMeta: {
|
|
||||||
_uplinks: {},
|
|
||||||
latest: {
|
|
||||||
name: 'verdaccio-ui/local-storage',
|
|
||||||
version: '8.0.1-next.1',
|
|
||||||
dist: {
|
|
||||||
fileCount: 0,
|
|
||||||
unpackedSize: 0,
|
|
||||||
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
|
|
||||||
},
|
|
||||||
homepage: 'https://verdaccio.org',
|
|
||||||
bugs: {
|
|
||||||
url: 'https://github.com/verdaccio/monorepo/issues',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps; props?: Props }> = ({
|
|
||||||
contextValue,
|
|
||||||
props,
|
|
||||||
}) => (
|
|
||||||
<DetailContext.Provider value={contextValue}>
|
|
||||||
<ActionBar {...props} />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<ActionBar /> component', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render the component in default state', () => {
|
|
||||||
const { container } = renderWithStore(
|
|
||||||
<ComponentToBeRendered contextValue={detailContextValue} />,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when there is no action bar data', () => {
|
|
||||||
const packageMeta = {
|
|
||||||
...detailContextValue.packageMeta,
|
|
||||||
latest: {
|
|
||||||
...detailContextValue.packageMeta.latest,
|
|
||||||
homepage: undefined,
|
|
||||||
bugs: undefined,
|
|
||||||
dist: {
|
|
||||||
...detailContextValue.packageMeta.latest.dist,
|
|
||||||
tarball: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const { container } = renderWithStore(
|
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue, packageMeta }} />,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when there is a button to download a tarball', () => {
|
|
||||||
renderWithStore(<ComponentToBeRendered contextValue={{ ...detailContextValue }} />, store);
|
|
||||||
expect(screen.getByLabelText('Download tarball')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when there is a button to raw manifest', () => {
|
|
||||||
renderWithStore(
|
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} props={{ showRaw: true }} />,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
expect(screen.getByLabelText('Raw Manifest')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when click button to raw manifest open a dialog with viewver', () => {
|
|
||||||
renderWithStore(
|
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} props={{ showRaw: true }} />,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
fireEvent.click(screen.getByLabelText('Raw Manifest'));
|
|
||||||
expect(screen.getByTestId('raw-viewver-dialog')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not display download tarball button', () => {
|
|
||||||
renderWithStore(
|
|
||||||
<ComponentToBeRendered
|
|
||||||
contextValue={{ ...detailContextValue }}
|
|
||||||
props={{ showDownloadTarball: false }}
|
|
||||||
/>,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
expect(screen.queryByLabelText('Download tarball')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not display show raw button', () => {
|
|
||||||
renderWithStore(
|
|
||||||
<ComponentToBeRendered contextValue={{ ...detailContextValue }} props={{ showRaw: false }} />,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
expect(screen.queryByLabelText('Raw Manifest')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when there is a button to open an issue', () => {
|
|
||||||
renderWithStore(<ComponentToBeRendered contextValue={{ ...detailContextValue }} />, store);
|
|
||||||
expect(screen.getByLabelText('Open an issue')).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { default as MaterialUIAppBar } from '@mui/material/AppBar';
|
|
||||||
|
|
||||||
export default MaterialUIAppBar;
|
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './Contributors';
|
|
@ -1,16 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { CopyClipboard as CopyClipboardOriginal } from '@verdaccio/ui-components';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
text: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CopyToClipBoard(props: Props) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return <CopyClipboardOriginal title={t('copy-to-clipboard')} dataTestId="copy-icon" {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CopyToClipBoard;
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { default as MaterialUIFormHelperText } from '@mui/material/FormHelperText';
|
|
||||||
|
|
||||||
export default MaterialUIFormHelperText;
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { default as MaterialUIInput } from '@mui/material/Input';
|
|
||||||
|
|
||||||
export default MaterialUIInput;
|
|
|
@ -1,40 +0,0 @@
|
||||||
import React, { MouseEvent } from 'react';
|
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
|
||||||
|
|
||||||
import Text, { TextProps } from './Text';
|
|
||||||
|
|
||||||
interface Props extends Pick<TextProps, 'variant'> {
|
|
||||||
external?: boolean;
|
|
||||||
className?: string;
|
|
||||||
to: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
type LinkRef = HTMLAnchorElement;
|
|
||||||
|
|
||||||
/* eslint-disable verdaccio/jsx-spread */
|
|
||||||
const Link = React.forwardRef<LinkRef, Props>(function Link(
|
|
||||||
{ external, to, children, variant, className, ...props },
|
|
||||||
ref
|
|
||||||
) {
|
|
||||||
const LinkTextContent = <Text variant={variant}>{children}</Text>;
|
|
||||||
return external ? (
|
|
||||||
<a
|
|
||||||
className={className}
|
|
||||||
href={to}
|
|
||||||
ref={ref}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{LinkTextContent}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<RouterLink className={className} innerRef={ref} to={to} {...props}>
|
|
||||||
{LinkTextContent}
|
|
||||||
</RouterLink>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Link;
|
|
|
@ -1,39 +0,0 @@
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import List from '@mui/material/List';
|
|
||||||
import { PRIMARY_COLOR } from 'verdaccio-ui/utils/colors';
|
|
||||||
|
|
||||||
import { default as MuiCard } from '../Card';
|
|
||||||
import { default as Typography } from '../Heading';
|
|
||||||
|
|
||||||
export const Wrapper = styled('div')({
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flex: 1,
|
|
||||||
padding: '16px',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Inner = styled('div')({
|
|
||||||
maxWidth: '650px',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const EmptyPackage = styled('img')({
|
|
||||||
width: '150px',
|
|
||||||
margin: '0 auto',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Heading = styled(Typography)({
|
|
||||||
color: PRIMARY_COLOR,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledList = styled(List)({
|
|
||||||
padding: 0,
|
|
||||||
color: PRIMARY_COLOR,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Card = styled(MuiCard)({
|
|
||||||
marginTop: '24px',
|
|
||||||
});
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { default as MaterialUIPaper } from '@mui/material/Paper';
|
|
||||||
|
|
||||||
export default MaterialUIPaper;
|
|
|
@ -78,4 +78,4 @@ const Support = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Support };
|
export default Support;
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './Support';
|
|
@ -1,6 +0,0 @@
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import Chip from '@mui/material/Chip';
|
|
||||||
|
|
||||||
export const Tag = styled(Chip)({
|
|
||||||
margin: '5px',
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './Text';
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { default as MaterialUIToolbar } from '@mui/material/Toolbar';
|
|
||||||
|
|
||||||
export default MaterialUIToolbar;
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { createContext } from 'react';
|
|
||||||
import { Language } from 'src/i18n/enabledLanguages';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
isDarkMode: boolean;
|
|
||||||
setIsDarkMode: (isDarkMode: boolean) => void;
|
|
||||||
language: Language;
|
|
||||||
setLanguage: (language: Language) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemeContext = createContext<undefined | Props>(undefined);
|
|
||||||
|
|
||||||
export default ThemeContext;
|
|
|
@ -1,55 +0,0 @@
|
||||||
import { StyledEngineProvider, Theme, ThemeProvider } from '@mui/material/styles';
|
|
||||||
import i18next from 'i18next';
|
|
||||||
import React, { useCallback, useEffect } from 'react';
|
|
||||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
|
||||||
|
|
||||||
import useLocalStorage from '../hooks/useLocalStorage';
|
|
||||||
import ThemeContext from './ThemeContext';
|
|
||||||
import loadDayJSLocale from './load-dayjs-locale';
|
|
||||||
import { ThemeMode, getTheme } from './theme';
|
|
||||||
|
|
||||||
declare module '@mui/styles/defaultTheme' {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
||||||
interface DefaultTheme extends Theme {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDarkModeDefault(darkModeConfig) {
|
|
||||||
const prefersDarkMode = window.matchMedia?.('(prefers-color-scheme:dark)').matches;
|
|
||||||
if (typeof darkModeConfig === 'boolean') {
|
|
||||||
return darkModeConfig;
|
|
||||||
} else {
|
|
||||||
return prefersDarkMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemeProviderWrapper: React.FC<{ children: any }> = ({ children }) => {
|
|
||||||
const currentLanguage = i18next.languages?.[0];
|
|
||||||
const { configOptions } = useConfig();
|
|
||||||
const isDarkModeDefault = getDarkModeDefault(configOptions.darkMode);
|
|
||||||
const isSwitchThemeEnabled = configOptions.showThemeSwitch;
|
|
||||||
const [isDarkModeStorage, setIsDarkMode] = useLocalStorage('darkMode', isDarkModeDefault);
|
|
||||||
const [language, setLanguage] = useLocalStorage('language', currentLanguage);
|
|
||||||
const isDarkMode = isSwitchThemeEnabled === true ? isDarkModeStorage : isDarkModeDefault;
|
|
||||||
const themeMode: ThemeMode = isDarkMode ? 'dark' : 'light';
|
|
||||||
|
|
||||||
const changeLanguage = useCallback(async () => {
|
|
||||||
await i18next.changeLanguage(language);
|
|
||||||
}, [language]);
|
|
||||||
|
|
||||||
const currentTheme = getTheme(themeMode, configOptions?.primaryColor);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
changeLanguage();
|
|
||||||
loadDayJSLocale();
|
|
||||||
}, [language, changeLanguage]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeContext.Provider value={{ isDarkMode, setIsDarkMode, language, setLanguage }}>
|
|
||||||
<StyledEngineProvider injectFirst={true}>
|
|
||||||
<ThemeProvider theme={currentTheme}>{children}</ThemeProvider>
|
|
||||||
</StyledEngineProvider>
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThemeProviderWrapper;
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { useTheme as muiUseTheme } from '@mui/styles';
|
|
||||||
|
|
||||||
import { Theme } from './theme';
|
|
||||||
|
|
||||||
const useTheme = () => muiUseTheme<Theme>();
|
|
||||||
|
|
||||||
export default useTheme;
|
|
|
@ -28,6 +28,10 @@ i18n
|
||||||
fallbackLng: DEFAULT_LANGUAGE,
|
fallbackLng: DEFAULT_LANGUAGE,
|
||||||
whitelist: [...listLanguagesAsString],
|
whitelist: [...listLanguagesAsString],
|
||||||
load: 'currentOnly',
|
load: 'currentOnly',
|
||||||
|
react: {
|
||||||
|
wait: true,
|
||||||
|
useSuspense: false,
|
||||||
|
},
|
||||||
resources: languages,
|
resources: languages,
|
||||||
debug: false,
|
debug: false,
|
||||||
interpolation: {
|
interpolation: {
|
||||||
|
|
|
@ -2,24 +2,27 @@ import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import AppConfigurationContext from 'verdaccio-ui/providers/config';
|
|
||||||
|
import {
|
||||||
|
AppConfigurationProvider,
|
||||||
|
StyleBaseline,
|
||||||
|
ThemeProvider,
|
||||||
|
store,
|
||||||
|
} from '@verdaccio/ui-components';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import StyleBaseline from './design-tokens/StyleBaseline';
|
|
||||||
import ThemeProvider from './design-tokens/ThemeProvider';
|
|
||||||
import { store } from './store';
|
|
||||||
|
|
||||||
const container = document.getElementById('root');
|
const container = document.getElementById('root');
|
||||||
const root = createRoot(container as HTMLElement);
|
const root = createRoot(container as HTMLElement);
|
||||||
|
|
||||||
const AppContainer = () => (
|
const AppContainer = () => (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<AppConfigurationContext>
|
<AppConfigurationProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<StyleBaseline />
|
<StyleBaseline />
|
||||||
<App />
|
<App />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</AppConfigurationContext>
|
</AppConfigurationProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
3
packages/plugins/ui-theme/src/pages/Front/Home.ts
Normal file
3
packages/plugins/ui-theme/src/pages/Front/Home.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { Home } from '@verdaccio/ui-components';
|
||||||
|
|
||||||
|
export default Home;
|
|
@ -1,32 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { cleanup, render, screen } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import { DetailContextProvider } from '../../context';
|
|
||||||
import Deprecated from './Deprecated';
|
|
||||||
|
|
||||||
describe('test Deprecated', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
const packageMeta = {
|
|
||||||
latest: {
|
|
||||||
packageName: 'foo',
|
|
||||||
version: '1.0.0',
|
|
||||||
deprecated: 'duuuude, this is deprecated',
|
|
||||||
maintainers: [],
|
|
||||||
contributors: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
test('should render the deprecated message', () => {
|
|
||||||
render(
|
|
||||||
// @ts-ignore
|
|
||||||
<DetailContextProvider value={{ packageMeta }}>
|
|
||||||
<Deprecated message={packageMeta.latest.deprecated} />
|
|
||||||
</DetailContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText('duuuude, this is deprecated')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,40 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import loadable from '../../../App/utils/loadable';
|
|
||||||
import DetailContainerContentReadme from './DetailContainerContentReadme';
|
|
||||||
|
|
||||||
export enum TabPosition {
|
|
||||||
README = 'readme',
|
|
||||||
DEPENDENCIES = 'dependencies',
|
|
||||||
VERSIONS = 'versions',
|
|
||||||
UPLINKS = 'uplinks',
|
|
||||||
}
|
|
||||||
|
|
||||||
const Versions = loadable(() => import(/* webpackChunkName: "Versions" */ './Versions'));
|
|
||||||
const UpLinks = loadable(() => import(/* webpackChunkName: "UpLinks" */ './UpLinks'));
|
|
||||||
|
|
||||||
const Dependencies = loadable(
|
|
||||||
() => import(/* webpackChunkName: "Dependencies" */ './Dependencies')
|
|
||||||
);
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
tabPosition: TabPosition;
|
|
||||||
readDescription?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DetailContainerContent: React.FC<Props> = ({ tabPosition, readDescription }) => {
|
|
||||||
switch (tabPosition) {
|
|
||||||
case TabPosition.README:
|
|
||||||
return <DetailContainerContentReadme description={readDescription} />;
|
|
||||||
case TabPosition.UPLINKS:
|
|
||||||
return <UpLinks />;
|
|
||||||
case TabPosition.VERSIONS:
|
|
||||||
return <Versions />;
|
|
||||||
case TabPosition.DEPENDENCIES:
|
|
||||||
return <Dependencies />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DetailContainerContent;
|
|
|
@ -1,58 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { cleanup, render } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import translationEN from '../../../../i18n/crowdin/ui.json';
|
|
||||||
import { DetailContext } from '../../context';
|
|
||||||
import { DetailContextProps } from '../../version-config';
|
|
||||||
import Versions from './Versions';
|
|
||||||
import data from './__partials__/data.json';
|
|
||||||
|
|
||||||
const detailContextValue: Partial<DetailContextProps> = {
|
|
||||||
packageName: 'foo',
|
|
||||||
packageMeta: data,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ComponentToBeRendered: React.FC<{ contextValue: Partial<DetailContextProps> }> = ({
|
|
||||||
contextValue,
|
|
||||||
}) => (
|
|
||||||
<MemoryRouter>
|
|
||||||
<DetailContext.Provider value={contextValue}>
|
|
||||||
<Versions />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<Version /> component', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
// FIXME: this test is not deterministic (writes `N days ago` in the snapshot, where N is random number)
|
|
||||||
test.skip('should render the component in default state', () => {
|
|
||||||
const wrapper = render(<ComponentToBeRendered contextValue={detailContextValue} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render versions', () => {
|
|
||||||
const { getByText } = render(<ComponentToBeRendered contextValue={detailContextValue} />);
|
|
||||||
|
|
||||||
expect(getByText(translationEN.versions['version-history'])).toBeTruthy();
|
|
||||||
expect(getByText(translationEN.versions['current-tags'])).toBeTruthy();
|
|
||||||
|
|
||||||
// pick some versions
|
|
||||||
expect(getByText('2.3.0')).toBeTruthy();
|
|
||||||
expect(getByText('canary')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not render versions', () => {
|
|
||||||
const { queryByText } = render(
|
|
||||||
<ComponentToBeRendered contextValue={{ packageName: detailContextValue.packageName }} />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText(translationEN.versions['version-history'])).toBeFalsy();
|
|
||||||
expect(queryByText(translationEN.versions['current-tags'])).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.todo('should click on version link');
|
|
||||||
});
|
|
|
@ -1,39 +0,0 @@
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { DetailContext } from '../../context';
|
|
||||||
import VersionsHistoryList from './VersionsHistoryList';
|
|
||||||
import VersionsTagList from './VersionsTagList';
|
|
||||||
import { StyledText } from './styles';
|
|
||||||
|
|
||||||
const Versions: React.FC = () => {
|
|
||||||
const detailContext = useContext(DetailContext);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const { packageMeta, packageName } = detailContext;
|
|
||||||
|
|
||||||
if (!packageMeta) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { versions = {}, time = {}, ['dist-tags']: distTags = {} } = packageMeta;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{distTags && Object.keys(distTags).length > 0 && packageName && (
|
|
||||||
<>
|
|
||||||
<StyledText variant="subtitle1">{t('versions.current-tags')}</StyledText>
|
|
||||||
<VersionsTagList packageName={packageName} tags={distTags} time={time} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{versions && Object.keys(versions).length > 0 && packageName && (
|
|
||||||
<>
|
|
||||||
<StyledText variant="subtitle1">{t('versions.version-history')}</StyledText>
|
|
||||||
<VersionsHistoryList packageName={packageName} time={time} versions={versions} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Versions;
|
|
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
export { default } from './DetailContainer';
|
|
|
@ -1,98 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import React from 'react';
|
|
||||||
import { renderWithStore, screen, waitFor } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import { store } from '../../../store';
|
|
||||||
import { DetailContext } from '../context';
|
|
||||||
import { DetailContextProps } from '../version-config';
|
|
||||||
import DetailSidebar from './DetailSidebar';
|
|
||||||
|
|
||||||
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({
|
|
||||||
contextValue,
|
|
||||||
}) => (
|
|
||||||
<DetailContext.Provider value={contextValue}>
|
|
||||||
<DetailSidebar />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/54010619/308341
|
|
||||||
jest.mock('react', () => {
|
|
||||||
const React = jest.requireActual('react');
|
|
||||||
React.Suspense = ({ children }) => children;
|
|
||||||
return React;
|
|
||||||
});
|
|
||||||
|
|
||||||
const detailContextValue: DetailContextProps = {
|
|
||||||
packageName: 'foo',
|
|
||||||
readMe: 'test',
|
|
||||||
enableLoading: () => {},
|
|
||||||
isLoading: false,
|
|
||||||
hasNotBeenFound: false,
|
|
||||||
packageMeta: {
|
|
||||||
_uplinks: {},
|
|
||||||
latest: {
|
|
||||||
name: 'verdaccio-ui/local-storage',
|
|
||||||
version: '8.0.1-next.1',
|
|
||||||
dist: {
|
|
||||||
fileCount: 0,
|
|
||||||
unpackedSize: 0,
|
|
||||||
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
|
|
||||||
},
|
|
||||||
homepage: 'https://verdaccio.org',
|
|
||||||
bugs: {
|
|
||||||
url: 'https://github.com/verdaccio/monorepo/issues',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('DetailSidebar', () => {
|
|
||||||
test('should render commonjs module icon', async () => {
|
|
||||||
const { getByAltText } = renderWithStore(
|
|
||||||
<ComponentToBeRendered
|
|
||||||
contextValue={_.merge(detailContextValue, {
|
|
||||||
packageMeta: {
|
|
||||||
latest: {
|
|
||||||
type: 'commonjs',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => expect(getByAltText('commonjs')).toBeInTheDocument());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render ts module icon', () => {
|
|
||||||
renderWithStore(
|
|
||||||
<ComponentToBeRendered
|
|
||||||
contextValue={_.merge(detailContextValue, {
|
|
||||||
packageMeta: {
|
|
||||||
latest: {
|
|
||||||
types: './src/index.d.ts',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
expect(screen.getByAltText('typescript')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render es6 module icon', () => {
|
|
||||||
renderWithStore(
|
|
||||||
<ComponentToBeRendered
|
|
||||||
contextValue={_.merge(detailContextValue, {
|
|
||||||
packageMeta: {
|
|
||||||
latest: {
|
|
||||||
type: 'module',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>,
|
|
||||||
store
|
|
||||||
);
|
|
||||||
expect(screen.getByAltText('es6 modules')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,73 +0,0 @@
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { PackageMetaInterface } from 'types/packageMeta';
|
|
||||||
import ActionBar from 'verdaccio-ui/components/ActionBar';
|
|
||||||
import Author from 'verdaccio-ui/components/Author';
|
|
||||||
import Paper from 'verdaccio-ui/components/Paper';
|
|
||||||
import { Theme } from 'verdaccio-ui/design-tokens/theme';
|
|
||||||
import { useConfig } from 'verdaccio-ui/providers/config';
|
|
||||||
|
|
||||||
import { DetailContext } from '..';
|
|
||||||
import loadable from '../../../App/utils/loadable';
|
|
||||||
import DetailSidebarFundButton from './DetailSidebarFundButton';
|
|
||||||
import DetailSidebarTitle from './DetailSidebarTitle';
|
|
||||||
import Developers from './Developers';
|
|
||||||
import { DeveloperType } from './Developers/DevelopersTitle';
|
|
||||||
|
|
||||||
const Engines = loadable(() => import(/* webpackChunkName: "Engines" */ './Engines'));
|
|
||||||
const Dist = loadable(() => import(/* webpackChunkName: "Dist" */ './Dist'));
|
|
||||||
const Install = loadable(() => import(/* webpackChunkName: "Install" */ './Install'));
|
|
||||||
const Repository = loadable(() => import(/* webpackChunkName: "Repository" */ './Repository'));
|
|
||||||
|
|
||||||
const getModuleType = (manifest: PackageMetaInterface) => {
|
|
||||||
if (manifest.latest.main) {
|
|
||||||
return 'commonjs';
|
|
||||||
} else if (manifest.latest.type) {
|
|
||||||
return manifest.latest.type;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DetailSidebar: React.FC = () => {
|
|
||||||
const detailContext = useContext(DetailContext);
|
|
||||||
const { packageMeta, packageName, packageVersion } = detailContext;
|
|
||||||
const { configOptions } = useConfig();
|
|
||||||
const version = packageVersion || packageMeta?.latest.version || '';
|
|
||||||
const time = packageMeta?.time ? packageMeta.time[version] : '';
|
|
||||||
|
|
||||||
if (!packageMeta || !packageName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledPaper className={'sidebar-info'}>
|
|
||||||
<DetailSidebarTitle
|
|
||||||
description={packageMeta.latest?.description}
|
|
||||||
hasTypes={typeof packageMeta.latest.types === 'string'}
|
|
||||||
isLatest={typeof packageVersion === 'undefined'}
|
|
||||||
moduleType={getModuleType(packageMeta)}
|
|
||||||
packageName={packageName}
|
|
||||||
time={time}
|
|
||||||
version={version}
|
|
||||||
/>
|
|
||||||
<ActionBar
|
|
||||||
showDownloadTarball={configOptions.showDownloadTarball}
|
|
||||||
showRaw={configOptions.showRaw}
|
|
||||||
/>
|
|
||||||
<Install />
|
|
||||||
<DetailSidebarFundButton />
|
|
||||||
<Repository />
|
|
||||||
<Engines />
|
|
||||||
<Dist />
|
|
||||||
<Author />
|
|
||||||
<Developers type={DeveloperType.MAINTAINERS} />
|
|
||||||
<Developers type={DeveloperType.CONTRIBUTORS} />
|
|
||||||
</StyledPaper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DetailSidebar;
|
|
||||||
|
|
||||||
const StyledPaper = styled(Paper)<{ theme?: Theme }>(({ theme }) => ({
|
|
||||||
padding: theme?.spacing(3, 2),
|
|
||||||
}));
|
|
|
@ -1,109 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import { DetailContext } from '../context';
|
|
||||||
import { DetailContextProps } from '../version-config';
|
|
||||||
import DetailSidebarFundButton from './DetailSidebarFundButton';
|
|
||||||
|
|
||||||
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({
|
|
||||||
contextValue,
|
|
||||||
}) => (
|
|
||||||
<DetailContext.Provider value={contextValue}>
|
|
||||||
<DetailSidebarFundButton />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
const detailContextValue: DetailContextProps = {
|
|
||||||
packageName: 'foo',
|
|
||||||
readMe: 'test',
|
|
||||||
enableLoading: () => {},
|
|
||||||
isLoading: false,
|
|
||||||
hasNotBeenFound: false,
|
|
||||||
packageMeta: {
|
|
||||||
_uplinks: {},
|
|
||||||
latest: {
|
|
||||||
name: 'verdaccio-ui/local-storage',
|
|
||||||
version: '8.0.1-next.1',
|
|
||||||
dist: {
|
|
||||||
fileCount: 0,
|
|
||||||
unpackedSize: 0,
|
|
||||||
tarball: 'http://localhost:8080/bootstrap/-/bootstrap-4.3.1.tgz',
|
|
||||||
},
|
|
||||||
homepage: 'https://verdaccio.org',
|
|
||||||
bugs: {
|
|
||||||
url: 'https://github.com/verdaccio/monorepo/issues',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('test DetailSidebarFundButton', () => {
|
|
||||||
test('should not display the button if fund is missing', () => {
|
|
||||||
const wrapper = render(<ComponentToBeRendered contextValue={detailContextValue} />);
|
|
||||||
|
|
||||||
expect(wrapper.queryByText('Fund')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not display the button if url is missing', () => {
|
|
||||||
const value = _.merge(detailContextValue, {
|
|
||||||
packageMeta: {
|
|
||||||
latest: {
|
|
||||||
funding: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
|
|
||||||
|
|
||||||
expect(wrapper.queryByText('Fund')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not display the button if url is not a string', () => {
|
|
||||||
const value = _.merge(detailContextValue, {
|
|
||||||
packageMeta: {
|
|
||||||
latest: {
|
|
||||||
funding: {
|
|
||||||
url: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
|
|
||||||
|
|
||||||
expect(wrapper.queryByText('Fund')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not display the button if url is not an url', () => {
|
|
||||||
const value = _.merge(detailContextValue, {
|
|
||||||
packageMeta: {
|
|
||||||
latest: {
|
|
||||||
funding: {
|
|
||||||
url: 'somethign different as url',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
|
|
||||||
|
|
||||||
expect(wrapper.queryByText('Fund')).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display the button if url is a valid url', () => {
|
|
||||||
const value = _.merge(detailContextValue, {
|
|
||||||
packageMeta: {
|
|
||||||
latest: {
|
|
||||||
funding: {
|
|
||||||
url: 'https://opencollective.com/verdaccio',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = render(<ComponentToBeRendered contextValue={value} />);
|
|
||||||
|
|
||||||
expect(wrapper.getByText('Fund')).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './Developers';
|
|
|
@ -1,58 +0,0 @@
|
||||||
import Avatar from '@mui/material/Avatar';
|
|
||||||
import Grid from '@mui/material/Grid';
|
|
||||||
import List from '@mui/material/List';
|
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import { DetailContext } from '../../context';
|
|
||||||
import npm from '../Install/img/npm.svg';
|
|
||||||
import node from './img/node.png';
|
|
||||||
import { EngineListItem, StyledText } from './styles';
|
|
||||||
|
|
||||||
const Engine: React.FC = () => {
|
|
||||||
const { packageMeta } = useContext(DetailContext);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const engines = packageMeta?.latest?.engines;
|
|
||||||
|
|
||||||
if (!engines || (!engines.node && !engines.npm)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid container={true}>
|
|
||||||
{engines.node && (
|
|
||||||
<Grid item={true} xs={6}>
|
|
||||||
<List
|
|
||||||
subheader={
|
|
||||||
<StyledText variant={'subtitle1'}>{t('sidebar.engines.node-js')}</StyledText>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<EngineListItem button={true}>
|
|
||||||
<Avatar src={node} />
|
|
||||||
<ListItemText primary={engines.node} />
|
|
||||||
</EngineListItem>
|
|
||||||
</List>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{engines.npm && (
|
|
||||||
<Grid item={true} xs={6}>
|
|
||||||
<List
|
|
||||||
subheader={
|
|
||||||
<StyledText variant={'subtitle1'}>{t('sidebar.engines.npm-version')}</StyledText>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<EngineListItem button={true}>
|
|
||||||
<Avatar src={npm} />
|
|
||||||
<ListItemText primary={engines.npm} />
|
|
||||||
</EngineListItem>
|
|
||||||
</List>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Engine;
|
|
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
|
@ -1,62 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render, screen } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import { DetailContext } from '../../context';
|
|
||||||
import { DetailContextProps } from '../../version-config';
|
|
||||||
import Install from './Install';
|
|
||||||
import data from './__partials__/data.json';
|
|
||||||
|
|
||||||
const detailContextValue: Partial<DetailContextProps> = {
|
|
||||||
packageName: 'foo',
|
|
||||||
packageMeta: data,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ComponentToBeRendered: React.FC = () => (
|
|
||||||
<DetailContext.Provider value={detailContextValue}>
|
|
||||||
<Install />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
/* eslint-disable react/jsx-no-bind*/
|
|
||||||
describe('<Install />', () => {
|
|
||||||
test('renders correctly', () => {
|
|
||||||
render(<ComponentToBeRendered />);
|
|
||||||
expect(screen.getByText('pnpm install foo')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('yarn add foo')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('npm install foo')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should have 3 children', () => {
|
|
||||||
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['yarn', 'pnpm', 'npm'];
|
|
||||||
const { getByTestId } = render(<ComponentToBeRendered />);
|
|
||||||
const installListItems = getByTestId('installList');
|
|
||||||
// installitems + subHeader = 4
|
|
||||||
expect(installListItems.children.length).toBe(4);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should have the element NPM', () => {
|
|
||||||
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['npm'];
|
|
||||||
const { getByTestId, queryByText } = render(<ComponentToBeRendered />);
|
|
||||||
expect(getByTestId('installListItem-npm')).toBeTruthy();
|
|
||||||
expect(queryByText(`npm install ${detailContextValue.packageName}`)).toBeTruthy();
|
|
||||||
expect(queryByText('Install using npm')).toBeTruthy();
|
|
||||||
expect(screen.queryByText('pnpm install foo')).not.toBeInTheDocument();
|
|
||||||
expect(screen.queryByText('yarn add foo')).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should have the element YARN', () => {
|
|
||||||
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['yarn'];
|
|
||||||
const { getByTestId, queryByText } = render(<ComponentToBeRendered />);
|
|
||||||
expect(getByTestId('installListItem-yarn')).toBeTruthy();
|
|
||||||
expect(queryByText(`yarn add ${detailContextValue.packageName}`)).toBeTruthy();
|
|
||||||
expect(queryByText('Install using yarn')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should have the element PNPM', () => {
|
|
||||||
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['pnpm'];
|
|
||||||
const { getByTestId, queryByText } = render(<ComponentToBeRendered />);
|
|
||||||
expect(getByTestId('installListItem-pnpm')).toBeTruthy();
|
|
||||||
expect(queryByText(`pnpm install ${detailContextValue.packageName}`)).toBeTruthy();
|
|
||||||
expect(queryByText('Install using pnpm')).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,76 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import { DetailContext } from '../../context';
|
|
||||||
import { DetailContextProps } from '../../version-config';
|
|
||||||
import Repository from './Repository';
|
|
||||||
import data from './__partials__/data.json';
|
|
||||||
|
|
||||||
const detailContextValue: DetailContextProps = {
|
|
||||||
packageName: 'foo',
|
|
||||||
readMe: 'readMe',
|
|
||||||
enableLoading: () => {},
|
|
||||||
isLoading: false,
|
|
||||||
hasNotBeenFound: false,
|
|
||||||
packageMeta: data,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ComponentToBeRendered: React.FC<{ contextValue: DetailContextProps }> = ({
|
|
||||||
contextValue,
|
|
||||||
}) => (
|
|
||||||
<DetailContext.Provider value={contextValue}>
|
|
||||||
<Repository />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<Repository /> component', () => {
|
|
||||||
test('should load the component in default state', () => {
|
|
||||||
const { container } = render(<ComponentToBeRendered contextValue={detailContextValue} />);
|
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render the component in with no repository data', () => {
|
|
||||||
const packageMeta = {
|
|
||||||
...detailContextValue.packageMeta,
|
|
||||||
latest: {
|
|
||||||
...detailContextValue.packageMeta?.latest,
|
|
||||||
repository: undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const { queryByText } = render(
|
|
||||||
<ComponentToBeRendered
|
|
||||||
contextValue={{
|
|
||||||
...detailContextValue,
|
|
||||||
packageMeta,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('Repository')).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render the component in with invalid url', () => {
|
|
||||||
const packageMeta = {
|
|
||||||
...detailContextValue.packageMeta,
|
|
||||||
latest: {
|
|
||||||
...detailContextValue.packageMeta?.latest,
|
|
||||||
repository: {
|
|
||||||
type: 'git',
|
|
||||||
url: 'git://github.com/verdaccio/ui.git',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const { queryByText } = render(
|
|
||||||
<ComponentToBeRendered
|
|
||||||
contextValue={{
|
|
||||||
...detailContextValue,
|
|
||||||
packageMeta,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByText('Repository')).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
export { default } from './DetailSidebar';
|
|
|
@ -1,67 +0,0 @@
|
||||||
import { waitFor } from '@testing-library/dom';
|
|
||||||
import React from 'react';
|
|
||||||
import { MemoryRouter } from 'react-router';
|
|
||||||
import { render } from 'verdaccio-ui/utils/test-react-testing-library';
|
|
||||||
|
|
||||||
import translationEN from '../../i18n/crowdin/ui.json';
|
|
||||||
import Version from './Version';
|
|
||||||
import data from './__partials__/data.json';
|
|
||||||
import { DetailContext } from './context';
|
|
||||||
|
|
||||||
// :-) we mock this otherways fails on render, some weird issue on material-ui
|
|
||||||
jest.mock('@mui/material/Avatar');
|
|
||||||
|
|
||||||
const detailContextValue = {
|
|
||||||
packageName: 'foo',
|
|
||||||
packageMeta: data,
|
|
||||||
readMe: 'Read me!',
|
|
||||||
enableLoading: jest.fn(),
|
|
||||||
isLoading: false,
|
|
||||||
hasNotBeenFound: false,
|
|
||||||
version: '1.0.0',
|
|
||||||
};
|
|
||||||
|
|
||||||
describe.skip('test Version page', () => {
|
|
||||||
test('should render the version page', async () => {
|
|
||||||
const { getByTestId, getByText } = render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<DetailContext.Provider value={detailContextValue}>
|
|
||||||
<Version />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
// we wait fetch response (mocked above)
|
|
||||||
await waitFor(() => getByTestId('version-layout'));
|
|
||||||
// check whether readme was loaded
|
|
||||||
const hasReadme = getByText(detailContextValue.readMe);
|
|
||||||
expect(hasReadme).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should render 404 page if the resources are not found', async () => {
|
|
||||||
const { getByText } = render(
|
|
||||||
<MemoryRouter>
|
|
||||||
<DetailContext.Provider
|
|
||||||
value={{
|
|
||||||
...detailContextValue,
|
|
||||||
hasNotBeenFound: true,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Version />
|
|
||||||
</DetailContext.Provider>
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
// we wait fetch response (mocked above)
|
|
||||||
const notFoundElement = await waitFor(() =>
|
|
||||||
getByText(translationEN.error['404']['sorry-we-could-not-find-it'])
|
|
||||||
);
|
|
||||||
expect(notFoundElement).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wanna contribute? Here we some scenarios we need to test
|
|
||||||
|
|
||||||
test.todo('should test click on tabs');
|
|
||||||
test.todo('should check what is rendered int he sidebar is correct');
|
|
||||||
test.todo('should test click back home on 404');
|
|
||||||
test.todo('should test click on elements in the sidebar');
|
|
||||||
test.todo('should test other not consider scenarios');
|
|
||||||
});
|
|
|
@ -1,10 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import Loading from 'verdaccio-ui/components/Loading';
|
|
||||||
import NotFound from 'verdaccio-ui/components/NotFound';
|
|
||||||
|
|
||||||
import { RootState } from '../../store/store';
|
import { Loading, NotFound, RootState, VersionLayout } from '@verdaccio/ui-components';
|
||||||
import VersionLayout from './VersionLayout';
|
|
||||||
|
|
||||||
const Version: React.FC = () => {
|
const Version: React.FC = () => {
|
||||||
const manifestStore = useSelector((state: RootState) => state.manifest);
|
const manifestStore = useSelector((state: RootState) => state.manifest);
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +0,0 @@
|
||||||
import { Consumer, Provider, createContext } from 'react';
|
|
||||||
|
|
||||||
import { DetailContextProps, VersionPageConsumerProps } from './version-config';
|
|
||||||
|
|
||||||
export const DetailContext = createContext<Partial<DetailContextProps>>({});
|
|
||||||
|
|
||||||
export const DetailContextProvider: Provider<Partial<VersionPageConsumerProps>> =
|
|
||||||
DetailContext.Provider;
|
|
||||||
export const DetailContextConsumer: Consumer<Partial<VersionPageConsumerProps>> =
|
|
||||||
DetailContext.Consumer;
|
|
|
@ -1,9 +0,0 @@
|
||||||
function getRouterPackageName(packageName: string, scope?: string): string {
|
|
||||||
if (scope) {
|
|
||||||
return `@${scope}/${packageName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return packageName;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getRouterPackageName;
|
|
|
@ -1,4 +1 @@
|
||||||
export { DetailContext, DetailContextConsumer, DetailContextProvider } from './context';
|
|
||||||
export { VersionPageConsumerProps, DetailContextProps } from './version-config';
|
|
||||||
|
|
||||||
export { default } from './Version';
|
export { default } from './Version';
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export { PackageList } from './PackageList';
|
|
|
@ -1 +0,0 @@
|
||||||
export { store } from './store';
|
|
|
@ -1,5 +0,0 @@
|
||||||
export const NODE_MANAGER = {
|
|
||||||
npm: 'npm',
|
|
||||||
yarn: 'yarn',
|
|
||||||
pnpm: 'pnpm',
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
export default function fileSizeSI(
|
|
||||||
a: number,
|
|
||||||
b?: typeof Math,
|
|
||||||
c?: (p: number) => number,
|
|
||||||
d?: number,
|
|
||||||
e?: number
|
|
||||||
): string {
|
|
||||||
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')
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import i18n from 'i18next';
|
|
||||||
import { initReactI18next } from 'react-i18next';
|
|
||||||
|
|
||||||
// NOTE: not used yet, but should replace the original implementation
|
|
||||||
|
|
||||||
// https://react.i18next.com/misc/testing#testing-without-stubbing
|
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
|
||||||
lng: 'en',
|
|
||||||
fallbackLng: 'en',
|
|
||||||
|
|
||||||
// have a common namespace used around the full app
|
|
||||||
ns: ['translations'],
|
|
||||||
defaultNS: 'translations',
|
|
||||||
|
|
||||||
debug: false,
|
|
||||||
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: false, // not needed for react!!
|
|
||||||
},
|
|
||||||
|
|
||||||
resources: { en: { translations: {} } },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
|
|
@ -1,70 +0,0 @@
|
||||||
// eslint-disable-next-line jest/no-mocks-import
|
|
||||||
import {
|
|
||||||
generateInvalidToken,
|
|
||||||
generateTokenWithExpirationAsString,
|
|
||||||
generateTokenWithOutExpiration,
|
|
||||||
generateTokenWithTimeRange,
|
|
||||||
} from '../../jest/unit/components/__mocks__/token';
|
|
||||||
import { isTokenExpire } from './login';
|
|
||||||
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
console.error = jest.fn();
|
|
||||||
|
|
||||||
jest.mock('verdaccio-ui/providers/API/api', () => ({
|
|
||||||
// eslint-disable-next-line jest/no-mocks-import
|
|
||||||
request: require('../../jest/unit/components/__mocks__/api').default.request,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('i18next', () => {
|
|
||||||
const translationEN = require('../../i18n/translations/ui.json');
|
|
||||||
return {
|
|
||||||
t: (key: string) => {
|
|
||||||
const splittedKey = key.split('.');
|
|
||||||
let result = translationEN;
|
|
||||||
|
|
||||||
for (const element of splittedKey) {
|
|
||||||
result = result[element];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('isTokenExpire', (): void => {
|
|
||||||
test('isTokenExpire - null is not a valid payload', (): void => {
|
|
||||||
expect(isTokenExpire(null)).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isTokenExpire - token is not a valid payload', (): void => {
|
|
||||||
expect(isTokenExpire('not_a_valid_token')).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isTokenExpire - token should not expire in 24 hrs range', (): void => {
|
|
||||||
const token = generateTokenWithTimeRange(24);
|
|
||||||
expect(isTokenExpire(token)).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isTokenExpire - token should expire for current time', (): void => {
|
|
||||||
const token = generateTokenWithTimeRange();
|
|
||||||
expect(isTokenExpire(token)).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isTokenExpire - token expiration is not available', (): void => {
|
|
||||||
const token = generateTokenWithOutExpiration();
|
|
||||||
expect(isTokenExpire(token)).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isTokenExpire - token is not a valid json token', (): void => {
|
|
||||||
const token = generateInvalidToken();
|
|
||||||
const errorToken = new SyntaxError('Unexpected token i in JSON at position 0');
|
|
||||||
const result = ['Invalid token:', errorToken, 'xxxxxx.aW52YWxpZHRva2Vu.xxxxxx'];
|
|
||||||
expect(isTokenExpire(token)).toBeTruthy();
|
|
||||||
expect(console.error).toHaveBeenCalledWith(...result);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('isTokenExpire - token expiration is not a number', (): void => {
|
|
||||||
const token = generateTokenWithExpirationAsString();
|
|
||||||
expect(isTokenExpire(token)).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { Base64 } from 'js-base64';
|
|
||||||
import isNumber from 'lodash/isNumber';
|
|
||||||
import isString from 'lodash/isString';
|
|
||||||
|
|
||||||
export function isTokenExpire(token: string | null): boolean {
|
|
||||||
if (!isString(token)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, payload] = token.split('.');
|
|
||||||
|
|
||||||
if (!payload) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let exp: number;
|
|
||||||
try {
|
|
||||||
exp = JSON.parse(Base64.decode(payload)).exp;
|
|
||||||
} catch (error: any) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('Invalid token:', error, token);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!exp || !isNumber(exp)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Report as expire before (real expire time - 30s)
|
|
||||||
const jsTimestamp = exp * 1000 - 30000;
|
|
||||||
const expired = Date.now() >= jsTimestamp;
|
|
||||||
|
|
||||||
return expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginBody {
|
|
||||||
username?: string;
|
|
||||||
token?: string;
|
|
||||||
error?: LoginError;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginError {
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import memoryStorage from 'localstorage-memory';
|
|
||||||
|
|
||||||
let storage: Storage;
|
|
||||||
try {
|
|
||||||
localStorage.setItem('__TEST__', '');
|
|
||||||
localStorage.removeItem('__TEST__');
|
|
||||||
storage = localStorage;
|
|
||||||
} catch (err: any) {
|
|
||||||
storage = memoryStorage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default storage;
|
|
|
@ -3,8 +3,8 @@ import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import ThemeProvider from 'verdaccio-ui/design-tokens/ThemeProvider';
|
|
||||||
import AppConfigurationProvider from 'verdaccio-ui/providers/config';
|
import { AppConfigurationProvider, ThemeProvider } from '@verdaccio/ui-components';
|
||||||
|
|
||||||
import i18nConfig from '../i18n/config';
|
import i18nConfig from '../i18n/config';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export function goToVerdaccioWebsite(): void {
|
|
||||||
window.open('https://verdaccio.org', '_blank');
|
|
||||||
}
|
|
|
@ -16,7 +16,6 @@ module.exports = {
|
||||||
modules: ['node_modules'],
|
modules: ['node_modules'],
|
||||||
alias: {
|
alias: {
|
||||||
'verdaccio-ui/components': `${env.SRC_ROOT}/components`,
|
'verdaccio-ui/components': `${env.SRC_ROOT}/components`,
|
||||||
'verdaccio-ui/design-tokens': `${env.SRC_ROOT}/design-tokens`,
|
|
||||||
'verdaccio-ui/utils': `${env.SRC_ROOT}/utils`,
|
'verdaccio-ui/utils': `${env.SRC_ROOT}/utils`,
|
||||||
'verdaccio-ui/providers': `${env.SRC_ROOT}/providers`,
|
'verdaccio-ui/providers': `${env.SRC_ROOT}/providers`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"verdaccio-ui/components/*": ["src/components/*"],
|
"verdaccio-ui/components/*": ["src/components/*"],
|
||||||
"verdaccio-ui/design-tokens/*": ["src/design-tokens/*"],
|
|
||||||
"verdaccio-ui/utils/*": ["src/utils/*"]
|
"verdaccio-ui/utils/*": ["src/utils/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"verdaccio-ui/components/*": ["src/components/*"],
|
"verdaccio-ui/components/*": ["src/components/*"],
|
||||||
"verdaccio-ui/design-tokens/*": ["src/design-tokens/*"],
|
|
||||||
"verdaccio-ui/utils/*": ["src/utils/*"]
|
"verdaccio-ui/utils/*": ["src/utils/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "../../.babelrc",
|
"extends": "../../.babelrc",
|
||||||
|
"plugins": ["@emotion"],
|
||||||
"presets": [
|
"presets": [
|
||||||
[
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
|
@ -11,5 +12,5 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@babel/preset-react"
|
"@babel/preset-react"
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
|
|
100
packages/ui-components/.eslintrc
Normal file
100
packages/ui-components/.eslintrc
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:verdaccio/recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"verdaccio"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"react/display-name": 0,
|
||||||
|
"react/no-deprecated": 1,
|
||||||
|
"react/jsx-no-target-blank": 1,
|
||||||
|
"react/destructuring-assignment": ["error", "always"],
|
||||||
|
"react/forbid-component-props": ["warn", { "forbid": ["style"] }],
|
||||||
|
"react/no-this-in-sfc": ["warn"],
|
||||||
|
"react/no-unsafe": ["warn"],
|
||||||
|
"react/sort-comp": ["warn", {
|
||||||
|
"order": [
|
||||||
|
"static-methods",
|
||||||
|
"lifecycle",
|
||||||
|
"render",
|
||||||
|
"everything-else",
|
||||||
|
"/^on.+$/",
|
||||||
|
"/^render.+$/"
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
"react/void-dom-elements-no-children": ["warn"],
|
||||||
|
"react/no-did-mount-set-state": ["error", "disallow-in-func"],
|
||||||
|
"react/jsx-wrap-multilines": ["error",{
|
||||||
|
"declaration": "parens",
|
||||||
|
"assignment": "parens",
|
||||||
|
"return": "parens",
|
||||||
|
"arrow": "parens",
|
||||||
|
"condition": "parens",
|
||||||
|
"logical": "parens",
|
||||||
|
"prop": "ignore"
|
||||||
|
}],
|
||||||
|
"react/jsx-boolean-value": ["error", "always"],
|
||||||
|
"react/jsx-closing-tag-location": ["error"],
|
||||||
|
"react/jsx-curly-spacing": ["error", "never"],
|
||||||
|
"react/jsx-equals-spacing": ["error", "never"],
|
||||||
|
"react/jsx-first-prop-new-line": ["error", "multiline-multiprop"],
|
||||||
|
"react/jsx-handler-names": ["warn"],
|
||||||
|
"react/jsx-indent": ["error", 2],
|
||||||
|
"react/jsx-indent-props": ["error", 2],
|
||||||
|
"react/jsx-key": ["error"],
|
||||||
|
"react/jsx-max-depth":["error", { "max": 5}],
|
||||||
|
"react/jsx-max-props-per-line": ["error", {"maximum": 3, "when": "multiline" }],
|
||||||
|
"react/jsx-no-bind": ["error", {"allowArrowFunctions": true}],
|
||||||
|
"react/jsx-no-comment-textnodes": ["error"],
|
||||||
|
"react/jsx-no-duplicate-props": ["error"],
|
||||||
|
"react/jsx-no-literals": ["error"],
|
||||||
|
"react/jsx-no-undef": ["error"],
|
||||||
|
"react/prop-types": 0,
|
||||||
|
"react/jsx-one-expression-per-line": ["error", {"allow": "single-child"}],
|
||||||
|
"react/jsx-curly-brace-presence": ["warn", { "props": "ignore", "children": "ignore" }],
|
||||||
|
"react/jsx-pascal-case": ["error"],
|
||||||
|
"react/jsx-props-no-multi-spaces": ["error"],
|
||||||
|
"react/jsx-sort-default-props": ["error"],
|
||||||
|
"react/jsx-sort-props": ["error"],
|
||||||
|
"react/no-string-refs": ["error"],
|
||||||
|
"react/no-danger-with-children": ["error"],
|
||||||
|
"react/jsx-tag-spacing": ["error", {
|
||||||
|
"closingSlash": "never",
|
||||||
|
"beforeSelfClosing": "always",
|
||||||
|
"afterOpening": "allow-multiline",
|
||||||
|
"beforeClosing": "allow"
|
||||||
|
}],
|
||||||
|
"react/prefer-es6-class": [
|
||||||
|
2,
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
|
"verdaccio/jsx-no-style": ["warn"],
|
||||||
|
"verdaccio/jsx-spread": ["warn"],
|
||||||
|
"jest/expect-expect": 0,
|
||||||
|
"quote-props":["error", "as-needed"],
|
||||||
|
"prefer-spread": 1,
|
||||||
|
"linebreak-style": 0,
|
||||||
|
"max-len": ["error", 160]
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"VERDACCIO_API_URL": true,
|
||||||
|
"RequestInit": true,
|
||||||
|
"TextFieldProps": true,
|
||||||
|
"HTMLElementTagNameMap": true,
|
||||||
|
"JSX": true,
|
||||||
|
"Reflect": true,
|
||||||
|
"__DEBUG__": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"jest/globals": true
|
||||||
|
}
|
||||||
|
}
|
13
packages/ui-components/.storybook/main.js
Normal file
13
packages/ui-components/.storybook/main.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module.exports = {
|
||||||
|
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||||
|
addons: [
|
||||||
|
'@storybook/addon-links',
|
||||||
|
'@storybook/addon-essentials',
|
||||||
|
'@storybook/addon-interactions',
|
||||||
|
],
|
||||||
|
framework: '@storybook/react',
|
||||||
|
layout: 'centered',
|
||||||
|
core: {
|
||||||
|
builder: 'webpack5',
|
||||||
|
},
|
||||||
|
};
|
10
packages/ui-components/.storybook/preview-head.html
Normal file
10
packages/ui-components/.storybook/preview-head.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<script>
|
||||||
|
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||||
|
"title": "Verdaccio Local Dev",
|
||||||
|
"sort_packages": "asc", "primary_color": null,
|
||||||
|
"pkgManagers": ["npm", "yarn", "pnpm"],
|
||||||
|
"version": "1.0.0", "flags": { "searchRemote": true },
|
||||||
|
"filename": "index.html",
|
||||||
|
"base": "http://localhost:9000/"
|
||||||
|
}
|
||||||
|
</script>
|
52
packages/ui-components/.storybook/preview.js
Normal file
52
packages/ui-components/.storybook/preview.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import Flags from 'country-flag-icons/react/3x2';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import config from '../../plugins/ui-theme/src/i18n/config';
|
||||||
|
import {
|
||||||
|
AppConfigurationProvider,
|
||||||
|
StyleBaseline,
|
||||||
|
ThemeProvider,
|
||||||
|
TranslatorProvider,
|
||||||
|
store,
|
||||||
|
} from '../src/';
|
||||||
|
|
||||||
|
const DEFAULT_LANGUAGE = 'en-US';
|
||||||
|
const listLanguages = [
|
||||||
|
{ lng: DEFAULT_LANGUAGE, icon: Flags.US, menuKey: 'lng.english' },
|
||||||
|
{ lng: 'cs-CZ', icon: Flags.CZ, menuKey: 'lng.czech' },
|
||||||
|
{ lng: 'pt-BR', icon: Flags.BR, menuKey: 'lng.portuguese' },
|
||||||
|
{ lng: 'es-ES', icon: Flags.ES, menuKey: 'lng.spanish' },
|
||||||
|
{ lng: 'de-DE', icon: Flags.DE, menuKey: 'lng.german' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const parameters = {
|
||||||
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// preview-head file contains the __VERDACCIO_BASENAME_UI_OPTIONS
|
||||||
|
// required by AppConfigurationProvider
|
||||||
|
export const withMuiTheme = (Story) => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<TranslatorProvider loadDayJSLocale={() => {}} i18n={config} listLanguages={listLanguages}>
|
||||||
|
<AppConfigurationProvider>
|
||||||
|
<ThemeProvider>
|
||||||
|
<StyleBaseline />
|
||||||
|
<Story />
|
||||||
|
</ThemeProvider>
|
||||||
|
</AppConfigurationProvider>
|
||||||
|
</TranslatorProvider>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (typeof global.process === 'undefined') {
|
||||||
|
const { worker } = require('../msw/browser');
|
||||||
|
worker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const decorators = [withMuiTheme];
|
40
packages/ui-components/jest/api/home-packages.json
Normal file
40
packages/ui-components/jest/api/home-packages.json
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"version": "1.0.22",
|
||||||
|
"description": "test",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "git+https://github.com/test/test.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": {
|
||||||
|
"name": "",
|
||||||
|
"email": "",
|
||||||
|
"url": "",
|
||||||
|
"avatar": "data:image/svg+xml;utf8,%3Csvg%20height%3D%22100%22%20viewBox%3D%22-27%2024%20100%20100%22%20width%3D%22100%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%3E%3Cdefs%3E%3Ccircle%20cx%3D%2223%22%20cy%3D%2274%22%20id%3D%22a%22%20r%3D%2250%22%2F%3E%3C%2Fdefs%3E%3Cuse%20fill%3D%22%23F5EEE5%22%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23a%22%2F%3E%3CclipPath%20id%3D%22b%22%3E%3Cuse%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23a%22%2F%3E%3C%2FclipPath%3E%3Cg%20clip-path%3D%22url(%23b)%22%3E%3Cdefs%3E%3Cpath%20d%3D%22M36%2095.9c0%204%204.7%205.2%207.1%205.8%207.6%202%2022.8%205.9%2022.8%205.9%203.2%201.1%205.7%203.5%207.1%206.6v9.8H-27v-9.8c1.3-3.1%203.9-5.5%207.1-6.6%200%200%2015.2-3.9%2022.8-5.9%202.4-.6%207.1-1.8%207.1-5.8V85h26v10.9z%22%20id%3D%22c%22%2F%3E%3C%2Fdefs%3E%3Cuse%20fill%3D%22%23E6C19C%22%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23c%22%2F%3E%3CclipPath%20id%3D%22d%22%3E%3Cuse%20overflow%3D%22visible%22%20xlink%3Ahref%3D%22%23c%22%2F%3E%3C%2FclipPath%3E%3Cpath%20clip-path%3D%22url(%23d)%22%20d%3D%22M23.2%2035h.2c3.3%200%208.2.2%2011.4%202%203.3%201.9%207.3%205.6%208.5%2012.1%202.4%2013.7-2.1%2035.4-6.3%2042.4-4%206.7-9.8%209.2-13.5%209.4H23h-.1c-3.7-.2-9.5-2.7-13.5-9.4-4.2-7-8.7-28.7-6.3-42.4%201.2-6.5%205.2-10.2%208.5-12.1%203.2-1.8%208.1-2%2011.4-2h.2z%22%20fill%3D%22%23D4B08C%22%2F%3E%3C%2Fg%3E%3Cpath%20d%3D%22M22.6%2040c19.1%200%2020.7%2013.8%2020.8%2015.1%201.1%2011.9-3%2028.1-6.8%2033.7-4%205.9-9.8%208.1-13.5%208.3h-.5c-3.8-.3-9.6-2.5-13.6-8.4-3.8-5.6-7.9-21.8-6.8-33.8C2.3%2053.7%203.5%2040%2022.6%2040z%22%20fill%3D%22%23F2CEA5%22%2F%3E%3C%2Fsvg%3E"
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"readmeFilename": "README.md",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/test/test/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/test/test#readme",
|
||||||
|
"_id": "test@1.0.22",
|
||||||
|
"_nodeVersion": "14.17.4",
|
||||||
|
"_npmVersion": "7.20.5",
|
||||||
|
"dist": {
|
||||||
|
"integrity": "sha512-2IDD0lLzGUL7YJ+17Oh9VtbOwdKLqBLS+ZFATDXi5R22TL2hZ9LBFE10bzsDovNc4xtgwZAk1/K+5LHTye4ztg==",
|
||||||
|
"shasum": "c9152f57636bce762ccb5a83113c42a5831579bc",
|
||||||
|
"tarball": "http://localhost:4873/test/-/test-1.0.22.tgz"
|
||||||
|
},
|
||||||
|
"contributors": [],
|
||||||
|
"time": "2021-08-14T20:15:19.336Z",
|
||||||
|
"users": {}
|
||||||
|
}
|
3
packages/ui-components/jest/api/jquery-readme.js
vendored
Normal file
3
packages/ui-components/jest/api/jquery-readme.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = () =>
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
'# jQuery\n\n> jQuery is a fast, small, and feature-rich JavaScript library.\n\nFor information on how to get started and how to use jQuery, please see [jQuery\'s documentation](https://api.jquery.com/).\nFor source files and issues, please visit the [jQuery repo](https://github.com/jquery/jquery).\n\nIf upgrading, please see the [blog post for 3.6.3](https://blog.jquery.com/2022/12/20/jquery-3-6-3-released-a-quick-selector-fix/). This includes notable differences from the previous version and a more readable changelog.\n\n## Including jQuery\n\nBelow are some of the most common ways to include jQuery.\n\n### Browser\n\n#### Script tag\n\n```html\n<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>\n```\n\n#### Webpack / Browserify / Babel\n\nThere are several ways to use [Webpack](https://webpack.js.org/), [Browserify](http://browserify.org/) or [Babel](https://babeljs.io/). For more information on using these tools, please refer to the corresponding project\'s documentation. In the script, including jQuery will usually look like this:\n\n```js\nimport $ from "jquery";\n```\n\nIf you need to use jQuery in a file that\'s not an ECMAScript module, you can use the CommonJS syntax:\n\n```js\nvar $ = require( "jquery" );\n```\n\n#### AMD (Asynchronous Module Definition)\n\nAMD is a module format built for the browser. For more information, we recommend [require.js\' documentation](https://requirejs.org/docs/whyamd.html).\n\n```js\ndefine( [ "jquery" ], function( $ ) {\n\n} );\n```\n\n### Node\n\nTo include jQuery in [Node](https://nodejs.org/), first install with npm.\n\n```sh\nnpm install jquery\n```\n\nFor jQuery to work in Node, a window with a document is required. Since no such window exists natively in Node, one can be mocked by tools such as [jsdom](https://github.com/jsdom/jsdom). This can be useful for testing purposes.\n\n```js\nconst { JSDOM } = require( "jsdom" );\nconst { window } = new JSDOM( "" );\nconst $ = require( "jquery" )( window );';
|
5908
packages/ui-components/jest/api/jquery-sidebar.json
Normal file
5908
packages/ui-components/jest/api/jquery-sidebar.json
Normal file
File diff suppressed because it is too large
Load diff
1192
packages/ui-components/jest/api/search-verdaccio.json
Normal file
1192
packages/ui-components/jest/api/search-verdaccio.json
Normal file
File diff suppressed because it is too large
Load diff
2
packages/ui-components/jest/api/storybook-readme.txt
Normal file
2
packages/ui-components/jest/api/storybook-readme.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<h1 id="storybook-cli">Storybook CLI</h1>
|
||||||
|
<p>This is a wrapper for <a href="https://www.npmjs.com/package/@storybook/cli">https://www.npmjs.com/package/@storybook/cli</a></p>
|
53782
packages/ui-components/jest/api/storybook-sidebar.json
Normal file
53782
packages/ui-components/jest/api/storybook-sidebar.json
Normal file
File diff suppressed because it is too large
Load diff
53782
packages/ui-components/jest/api/storybook-v.json
Normal file
53782
packages/ui-components/jest/api/storybook-v.json
Normal file
File diff suppressed because it is too large
Load diff
29
packages/ui-components/jest/identity.js
Normal file
29
packages/ui-components/jest/identity.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
let Reflect;
|
||||||
|
let idObj;
|
||||||
|
|
||||||
|
function checkIsNodeV6OrAbove() {
|
||||||
|
if (typeof process === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(process.versions.node.split('.')[0], 10) >= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkIsNodeV6OrAbove()) {
|
||||||
|
Reflect = require('harmony-reflect');
|
||||||
|
}
|
||||||
|
|
||||||
|
idObj = new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: function getter(target, key) {
|
||||||
|
if (key === '__esModule') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = idObj;
|
37
packages/ui-components/jest/jest.config.js
Normal file
37
packages/ui-components/jest/jest.config.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
const config = require('../../../jest/config');
|
||||||
|
|
||||||
|
module.exports = Object.assign({}, config, {
|
||||||
|
automock: false,
|
||||||
|
collectCoverage: false,
|
||||||
|
testEnvironment: 'jest-environment-jsdom-global',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(js|ts|tsx)$': 'babel-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['js', 'ts', 'tsx'],
|
||||||
|
testEnvironmentOptions: {
|
||||||
|
url: 'http://localhost:9000/',
|
||||||
|
},
|
||||||
|
rootDir: '..',
|
||||||
|
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', '<rootDir>/jest/setup-env.ts'],
|
||||||
|
setupFiles: ['<rootDir>/jest/setup.ts'],
|
||||||
|
transformIgnorePatterns: ['<rootDir>/node_modules/(?!react-syntax-highlighter)'],
|
||||||
|
modulePathIgnorePatterns: [
|
||||||
|
'<rootDir>/coverage',
|
||||||
|
'<rootDir>/scripts',
|
||||||
|
'<rootDir>/tools',
|
||||||
|
'<rootDir>/build',
|
||||||
|
'<rootDir>/.vscode/',
|
||||||
|
'<rootDir>/test/e2e/',
|
||||||
|
],
|
||||||
|
snapshotSerializers: ['@emotion/jest/serializer'],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'\\.(s?css)$': '<rootDir>/jest/identity.js',
|
||||||
|
'\\.(png)$': '<rootDir>/jest/identity.js',
|
||||||
|
'\\.(svg)$': '<rootDir>/jest/unit/empty.ts',
|
||||||
|
'\\.(jpg)$': '<rootDir>/jest/unit/empty.ts',
|
||||||
|
'\\.(md)$': '<rootDir>/jest/unit/empty-string.ts',
|
||||||
|
'github-markdown-css': '<rootDir>/jest/identity.js',
|
||||||
|
'react-markdown': '<rootDir>/src/__mocks__/react-markdown.tsx',
|
||||||
|
'remark-*': '<rootDir>/src/__mocks__/remark-plugin.ts',
|
||||||
|
},
|
||||||
|
});
|
1
packages/ui-components/jest/jestEnvironment.js
Normal file
1
packages/ui-components/jest/jestEnvironment.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
jest.requireActual('babel/polyfill');
|
57
packages/ui-components/jest/server-handlers.ts
Normal file
57
packages/ui-components/jest/server-handlers.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { rest } from 'msw';
|
||||||
|
|
||||||
|
const packagesPayload = require('./api/home-packages.json');
|
||||||
|
|
||||||
|
export const handlers = [
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/packages', (req, res, ctx) => {
|
||||||
|
return res(ctx.json(Array(400).fill(packagesPayload)));
|
||||||
|
}),
|
||||||
|
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/storybook', (req, res, ctx) => {
|
||||||
|
const { v } = req.params;
|
||||||
|
if (v) {
|
||||||
|
return res(ctx.json(require('./api/storybook-v.json')));
|
||||||
|
} else {
|
||||||
|
return res(ctx.json(require('./api/storybook-sidebar.json')));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/search/*', (req, res, ctx) => {
|
||||||
|
return res(ctx.json(require('./api/search-verdaccio.json')));
|
||||||
|
}),
|
||||||
|
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/package/readme/storybook', (req, res, ctx) => {
|
||||||
|
return res(
|
||||||
|
ctx.text(`<h1 id="storybook-cli">Storybook CLI MSW.js</h1>
|
||||||
|
<p>This is a wrapper for <a href="https://www.npmjs.com/package/@storybook/cli">https://www.npmjs.com/package/@storybook/cli</a></p>
|
||||||
|
`)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/jquery', (req, res, ctx) => {
|
||||||
|
return res(ctx.json(require('./api/jquery-sidebar.json')));
|
||||||
|
}),
|
||||||
|
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/package/readme/jquery', (req, res, ctx) => {
|
||||||
|
return res(ctx.text(require('./api/jquery-readme')()));
|
||||||
|
}),
|
||||||
|
|
||||||
|
rest.post<{ username: string; password: string }, { token: string; username: string }>(
|
||||||
|
'http://localhost:9000/-/verdaccio/sec/login',
|
||||||
|
// @ts-ignore
|
||||||
|
async (req, res, ctx) => {
|
||||||
|
// @ts-ignore
|
||||||
|
const body = await req.json();
|
||||||
|
if (body.username === 'fail') {
|
||||||
|
return ctx.status(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res(
|
||||||
|
ctx.json({
|
||||||
|
username: body.username,
|
||||||
|
token: 'valid token',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
2
packages/ui-components/jest/server.d.ts
vendored
Normal file
2
packages/ui-components/jest/server.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
declare const server: import('msw/lib/SetupServerApi-70cc71a7').S;
|
||||||
|
export { server };
|
6
packages/ui-components/jest/server.ts
Normal file
6
packages/ui-components/jest/server.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { setupServer } from 'msw/node';
|
||||||
|
|
||||||
|
import { handlers } from './server-handlers';
|
||||||
|
|
||||||
|
const server = setupServer(...handlers);
|
||||||
|
export { server };
|
18
packages/ui-components/jest/setup-env.ts
Normal file
18
packages/ui-components/jest/setup-env.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
|
import { server } from './server';
|
||||||
|
|
||||||
|
// mock load translations to avoid warnings
|
||||||
|
// jest.mock('../src/i18n/loadTranslationFile');
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
server.listen({
|
||||||
|
onUnhandledRequest: 'warn',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(() => server.resetHandlers());
|
||||||
|
afterAll(() => {
|
||||||
|
server.close();
|
||||||
|
});
|
31
packages/ui-components/jest/setup.ts
Normal file
31
packages/ui-components/jest/setup.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* Setup configuration for Jest
|
||||||
|
* This file includes global settings for the JEST environment.
|
||||||
|
*/
|
||||||
|
import 'mutationobserver-shim';
|
||||||
|
|
||||||
|
// @ts-ignore : Property '__VERDACCIO_BASENAME_UI_OPTIONS' does not exist on type 'Global'.
|
||||||
|
global.__VERDACCIO_BASENAME_UI_OPTIONS = {
|
||||||
|
base: 'http://localhost:9000/',
|
||||||
|
protocol: 'http',
|
||||||
|
host: 'localhost',
|
||||||
|
primaryColor: '#4b5e40',
|
||||||
|
url_prefix: '',
|
||||||
|
darkMode: false,
|
||||||
|
language: 'en-US',
|
||||||
|
uri: 'http://localhost:9000/',
|
||||||
|
pkgManagers: ['pnpm', 'yarn', 'npm'],
|
||||||
|
title: 'Verdaccio Dev UI',
|
||||||
|
scope: '',
|
||||||
|
version: 'v1.0.0',
|
||||||
|
};
|
||||||
|
|
||||||
|
// mocking few DOM methods
|
||||||
|
// @ts-ignore : Property 'document' does not exist on type 'Global'.
|
||||||
|
if (global.document) {
|
||||||
|
// @ts-ignore : Type 'Mock<{ selectNodeContents: () => void; }, []>' is not assignable to type '() => Range'.
|
||||||
|
document.createRange = jest.fn((): void => ({
|
||||||
|
selectNodeContents: (): void => {},
|
||||||
|
}));
|
||||||
|
document.execCommand = jest.fn();
|
||||||
|
}
|
24
packages/ui-components/jest/unit/components/store/login.ts
Normal file
24
packages/ui-components/jest/unit/components/store/login.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { API_ERROR } from '../../../../lib/constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API mock for login endpoint
|
||||||
|
* @param {object} config configuration of api call
|
||||||
|
* @returns {promise}
|
||||||
|
*/
|
||||||
|
export default function login(config): Promise<unknown> {
|
||||||
|
return new Promise(function loginCallbackPromise(resolve, reject): void {
|
||||||
|
const body = JSON.parse(config.body);
|
||||||
|
if (body.username === 'sam' && body.password === '1234') {
|
||||||
|
resolve({
|
||||||
|
username: 'sam',
|
||||||
|
token: 'TEST_TOKEN',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// perhaps we should rethink this reject regarding the eslint rule
|
||||||
|
/* eslint-disable prefer-promise-reject-errors */
|
||||||
|
reject({
|
||||||
|
error: API_ERROR.BAD_USERNAME_PASSWORD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Mock response for logo api
|
||||||
|
* @returns {promise}
|
||||||
|
*/
|
||||||
|
export default function <T>(): Promise<T> {
|
||||||
|
return Promise.resolve('http://localhost/-/static/logo.png');
|
||||||
|
}
|
174
packages/ui-components/jest/unit/components/store/package.ts
Normal file
174
packages/ui-components/jest/unit/components/store/package.ts
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
export const packageInformation = [
|
||||||
|
{
|
||||||
|
name: 'jquery',
|
||||||
|
title: 'jQuery',
|
||||||
|
description: 'JavaScript library for DOM operations',
|
||||||
|
version: '3.3.2-pre',
|
||||||
|
main: 'dist/jquery.js',
|
||||||
|
homepage: 'https://jquery.com',
|
||||||
|
author: {
|
||||||
|
name: 'JS Foundation and other contributors',
|
||||||
|
url: 'https://github.com/jquery/jquery/blob/master/AUTHORS.txt',
|
||||||
|
avatar: '',
|
||||||
|
},
|
||||||
|
repository: {
|
||||||
|
type: 'git',
|
||||||
|
url: 'https://github.com/jquery/jquery.git',
|
||||||
|
},
|
||||||
|
keywords: ['jquery', 'javascript', 'browser', 'library'],
|
||||||
|
bugs: {
|
||||||
|
url: 'https://github.com/jquery/jquery/issues',
|
||||||
|
},
|
||||||
|
license: 'MIT',
|
||||||
|
dependencies: {},
|
||||||
|
devDependencies: {
|
||||||
|
'babel-core': '7.0.0-beta.0',
|
||||||
|
'babel-plugin-transform-es2015-for-of': '7.0.0-beta.0',
|
||||||
|
commitplease: '3.2.0',
|
||||||
|
'core-js': '2.5.7',
|
||||||
|
'eslint-config-jquery': '1.0.1',
|
||||||
|
grunt: '1.0.3',
|
||||||
|
'grunt-babel': '7.0.0',
|
||||||
|
'grunt-cli': '1.2.0',
|
||||||
|
'grunt-compare-size': '0.4.2',
|
||||||
|
'grunt-contrib-uglify': '3.3.0',
|
||||||
|
'grunt-contrib-watch': '1.1.0',
|
||||||
|
'grunt-eslint': '20.2.0',
|
||||||
|
'grunt-git-authors': '3.2.0',
|
||||||
|
'grunt-jsonlint': '1.1.0',
|
||||||
|
'grunt-karma': '2.0.0',
|
||||||
|
'grunt-newer': '1.3.0',
|
||||||
|
'grunt-npmcopy': '0.1.0',
|
||||||
|
'gzip-js': '0.3.2',
|
||||||
|
husky: '0.14.3',
|
||||||
|
insight: '0.10.1',
|
||||||
|
jsdom: '5.6.1',
|
||||||
|
karma: '2.0.3',
|
||||||
|
'karma-browserstack-launcher': '1.3.0',
|
||||||
|
'karma-chrome-launcher': '2.2.0',
|
||||||
|
'karma-firefox-launcher': '1.1.0',
|
||||||
|
'karma-qunit': '1.2.1',
|
||||||
|
'load-grunt-tasks': '4.0.0',
|
||||||
|
'native-promise-only': '0.8.1',
|
||||||
|
'promises-aplus-tests': '2.1.2',
|
||||||
|
q: '1.5.1',
|
||||||
|
'qunit-assert-step': '1.1.1',
|
||||||
|
qunitjs: '1.23.1',
|
||||||
|
'raw-body': '2.3.3',
|
||||||
|
requirejs: '2.3.5',
|
||||||
|
sinon: '2.3.7',
|
||||||
|
sizzle: '2.3.3',
|
||||||
|
'strip-json-comments': '2.0.1',
|
||||||
|
testswarm: '1.1.0',
|
||||||
|
'uglify-js': '3.4.0',
|
||||||
|
},
|
||||||
|
scripts: {
|
||||||
|
build: 'npm install && grunt',
|
||||||
|
start: 'grunt watch',
|
||||||
|
'test:browserless': 'grunt && grunt test:slow',
|
||||||
|
'test:browser': 'grunt && grunt karma:main',
|
||||||
|
test: 'grunt && grunt test:slow && grunt karma:main',
|
||||||
|
jenkins: 'npm run test:browserless',
|
||||||
|
precommit: 'grunt lint:newer qunit_fixture',
|
||||||
|
commitmsg: 'node node_modules/commitplease',
|
||||||
|
},
|
||||||
|
commitplease: {
|
||||||
|
nohook: true,
|
||||||
|
components: [
|
||||||
|
'Docs',
|
||||||
|
'Tests',
|
||||||
|
'Build',
|
||||||
|
'Support',
|
||||||
|
'Release',
|
||||||
|
'Core',
|
||||||
|
'Ajax',
|
||||||
|
'Attributes',
|
||||||
|
'Callbacks',
|
||||||
|
'CSS',
|
||||||
|
'Data',
|
||||||
|
'Deferred',
|
||||||
|
'Deprecated',
|
||||||
|
'Dimensions',
|
||||||
|
'Effects',
|
||||||
|
'Event',
|
||||||
|
'Manipulation',
|
||||||
|
'Offset',
|
||||||
|
'Queue',
|
||||||
|
'Selector',
|
||||||
|
'Serialize',
|
||||||
|
'Traversing',
|
||||||
|
'Wrap',
|
||||||
|
],
|
||||||
|
markerPattern: '^((clos|fix|resolv)(e[sd]|ing))|^(refs?)',
|
||||||
|
ticketPattern: '^((Closes|Fixes) ([a-zA-Z]{2,}-)[0-9]+)|^(Refs? [^#])',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lodash',
|
||||||
|
version: '4.17.4',
|
||||||
|
license: 'MIT',
|
||||||
|
private: true,
|
||||||
|
main: 'lodash.js',
|
||||||
|
author: {
|
||||||
|
name: 'John david dalton',
|
||||||
|
url: 'test url',
|
||||||
|
avatar: 'test avatar',
|
||||||
|
},
|
||||||
|
engines: {
|
||||||
|
node: '>=4.0.0',
|
||||||
|
},
|
||||||
|
sideEffects: false,
|
||||||
|
scripts: {
|
||||||
|
build: 'npm run build:main && npm run build:fp',
|
||||||
|
'build:fp': 'node lib/fp/build-dist.js',
|
||||||
|
'build:fp-modules': 'node lib/fp/build-modules.js',
|
||||||
|
'build:main': 'node lib/main/build-dist.js',
|
||||||
|
'build:main-modules': 'node lib/main/build-modules.js',
|
||||||
|
doc: 'node lib/main/build-doc github && npm run test:doc',
|
||||||
|
'doc:fp': 'node lib/fp/build-doc',
|
||||||
|
'doc:site': 'node lib/main/build-doc site',
|
||||||
|
'doc:sitehtml':
|
||||||
|
'optional-dev-dependency marky-markdown@^9.0.1 && npm run doc:site && node lib/main/build-site',
|
||||||
|
pretest: 'npm run build',
|
||||||
|
style: 'eslint *.js .internal/**/*.js',
|
||||||
|
test: 'npm run test:main && npm run test:fp',
|
||||||
|
'test:doc': 'markdown-doctest doc/*.md',
|
||||||
|
'test:fp': 'node test/test-fp',
|
||||||
|
'test:main': 'node test/test',
|
||||||
|
validate: 'npm run style && npm run test',
|
||||||
|
},
|
||||||
|
devDependencies: {
|
||||||
|
async: '^2.1.4',
|
||||||
|
benchmark: '^2.1.3',
|
||||||
|
chalk: '^1.1.3',
|
||||||
|
cheerio: '^0.22.0',
|
||||||
|
'codecov.io': '~0.1.6',
|
||||||
|
coveralls: '^2.11.15',
|
||||||
|
'curl-amd': '~0.8.12',
|
||||||
|
docdown: '~0.7.2',
|
||||||
|
dojo: '^1.12.1',
|
||||||
|
ecstatic: '^2.1.0',
|
||||||
|
eslint: '^3.15.0',
|
||||||
|
'eslint-plugin-import': '^2.2.0',
|
||||||
|
'fs-extra': '~1.0.0',
|
||||||
|
glob: '^7.1.1',
|
||||||
|
istanbul: '0.4.5',
|
||||||
|
jquery: '^3.1.1',
|
||||||
|
lodash: '4.17.3',
|
||||||
|
'lodash-doc-globals': '^0.1.1',
|
||||||
|
'markdown-doctest': '^0.9.1',
|
||||||
|
'optional-dev-dependency': '^2.0.0',
|
||||||
|
platform: '^1.3.3',
|
||||||
|
'qunit-extras': '^3.0.0',
|
||||||
|
qunitjs: '^2.1.0',
|
||||||
|
request: '^2.79.0',
|
||||||
|
requirejs: '^2.3.2',
|
||||||
|
'sauce-tunnel': '^2.5.0',
|
||||||
|
'uglify-js': '2.7.5',
|
||||||
|
webpack: '^1.14.0',
|
||||||
|
},
|
||||||
|
greenkeeper: {
|
||||||
|
ignore: ['lodash'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
591
packages/ui-components/jest/unit/components/store/packageMeta.ts
Normal file
591
packages/ui-components/jest/unit/components/store/packageMeta.ts
Normal file
|
@ -0,0 +1,591 @@
|
||||||
|
export const packageMeta = {
|
||||||
|
name: 'verdaccio',
|
||||||
|
'dist-tags': { latest: '2.7.1', beta: '2.4.1-beta' },
|
||||||
|
time: {
|
||||||
|
modified: '2017-12-14T15:43:27.317Z',
|
||||||
|
created: '2016-07-28T12:48:43.536Z',
|
||||||
|
'1.4.0': '2016-07-28T12:48:43.536Z',
|
||||||
|
'2.0.0': '2016-08-26T22:36:41.762Z',
|
||||||
|
'2.0.1': '2016-08-29T13:26:21.754Z',
|
||||||
|
'2.1.0': '2016-10-12T00:48:03.025Z',
|
||||||
|
'2.1.1': '2017-02-07T06:43:22.801Z',
|
||||||
|
'2.2.0-v20170212': '2017-02-12T14:48:27.322Z',
|
||||||
|
'2.1.2': '2017-03-09T06:25:28.107Z',
|
||||||
|
'2.1.3': '2017-03-29T20:03:36.850Z',
|
||||||
|
'2.1.4': '2017-04-13T20:08:41.131Z',
|
||||||
|
'2.1.5': '2017-04-22T09:07:39.821Z',
|
||||||
|
'2.1.6': '2017-05-12T07:43:36.616Z',
|
||||||
|
'2.1.7': '2017-05-14T13:50:14.016Z',
|
||||||
|
'2.1.10': '2017-06-03T09:53:52.449Z',
|
||||||
|
'2.2.0': '2017-06-08T19:02:53.618Z',
|
||||||
|
'2.2.1': '2017-06-17T16:23:14.158Z',
|
||||||
|
'2.2.2': '2017-07-02T13:13:13.304Z',
|
||||||
|
'2.2.3': '2017-07-04T20:43:59.442Z',
|
||||||
|
'2.2.4': '2017-07-05T17:28:07.187Z',
|
||||||
|
'2.2.5': '2017-07-05T17:34:11.089Z',
|
||||||
|
'2.2.6': '2017-07-13T05:04:54.418Z',
|
||||||
|
'2.2.7': '2017-07-15T23:27:24.523Z',
|
||||||
|
'2.3.0-beta': '2017-07-15T23:31:31.664Z',
|
||||||
|
'2.2.7-r': '2017-07-18T19:44:48.946Z',
|
||||||
|
'2.3.0-beta-1': '2017-07-22T16:27:45.025Z',
|
||||||
|
'2.3.0-beta-2': '2017-07-22T17:12:09.905Z',
|
||||||
|
'2.3.0-beta-3': '2017-07-22T17:35:05.771Z',
|
||||||
|
'2.3.0-beta-4': '2017-07-22T18:22:42.563Z',
|
||||||
|
'2.3.0': '2017-07-22T23:08:37.513Z',
|
||||||
|
'2.3.1-pre': '2017-07-24T05:50:40.852Z',
|
||||||
|
'2.3.1': '2017-07-25T05:24:27.651Z',
|
||||||
|
'2.3.2': '2017-07-28T23:05:36.431Z',
|
||||||
|
'2.3.3': '2017-07-29T10:05:30.120Z',
|
||||||
|
'2.3.4': '2017-07-29T10:18:44.061Z',
|
||||||
|
'2.3.5': '2017-08-14T06:22:57.686Z',
|
||||||
|
'2.3.6': '2017-08-17T04:30:44.872Z',
|
||||||
|
'2.4.0': '2017-09-23T08:01:22.780Z',
|
||||||
|
'2.4.1-beta': '2017-10-01T08:57:14.509Z',
|
||||||
|
'2.5.0': '2017-10-01T12:31:06.333Z',
|
||||||
|
'2.5.1': '2017-10-01T13:32:06.584Z',
|
||||||
|
'2.6.0': '2017-10-18T20:22:32.836Z',
|
||||||
|
'2.6.1': '2017-10-19T17:26:24.083Z',
|
||||||
|
'2.6.2': '2017-10-21T08:37:16.527Z',
|
||||||
|
'2.6.3': '2017-10-21T16:04:05.556Z',
|
||||||
|
'2.6.4': '2017-10-31T17:47:03.647Z',
|
||||||
|
'2.6.5': '2017-11-05T09:09:31.332Z',
|
||||||
|
'2.6.6': '2017-11-08T22:47:16.504Z',
|
||||||
|
'2.7.0': '2017-12-05T23:25:06.372Z',
|
||||||
|
'2.7.1': '2017-12-14T15:43:27.317Z',
|
||||||
|
},
|
||||||
|
_uplinks: {
|
||||||
|
abc: { etag: 'ddfdxjn8m8n6gn70-8m', fetched: 1532297472000 },
|
||||||
|
npmjs: { etag: '"5a272ad2-4f6b1"', fetched: 1513266232741 },
|
||||||
|
xyz: { etag: '564748hydydygs-s7ehj', fetched: 1532124672000 },
|
||||||
|
},
|
||||||
|
_rev: '16-ba1b806df0298246',
|
||||||
|
_attachments: {},
|
||||||
|
latest: {
|
||||||
|
name: 'verdaccio',
|
||||||
|
version: '2.7.1',
|
||||||
|
description: 'Private npm repository server',
|
||||||
|
author: {
|
||||||
|
name: 'User NPM',
|
||||||
|
email: 'test@author.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/a5a236ba477ee98908600c40cda74f4a',
|
||||||
|
},
|
||||||
|
repository: {
|
||||||
|
type: 'git',
|
||||||
|
url: 'git://github.com/verdaccio/verdaccio.git',
|
||||||
|
},
|
||||||
|
main: 'index.js',
|
||||||
|
bin: { verdaccio: './bin/verdaccio' },
|
||||||
|
dependencies: {
|
||||||
|
'@verdaccio/file-locking': '0.0.3',
|
||||||
|
JSONStream: '^1.1.1',
|
||||||
|
'apache-md5': '^1.1.2',
|
||||||
|
async: '^2.0.1',
|
||||||
|
'body-parser': '^1.15.0',
|
||||||
|
bunyan: '^1.8.0',
|
||||||
|
chalk: '^2.0.1',
|
||||||
|
commander: '^2.11.0',
|
||||||
|
compression: '1.6.2',
|
||||||
|
cookies: '^0.7.0',
|
||||||
|
cors: '^2.8.3',
|
||||||
|
express: '4.15.3',
|
||||||
|
global: '^4.3.2',
|
||||||
|
handlebars: '4.0.5',
|
||||||
|
'http-errors': '^1.4.0',
|
||||||
|
'js-string-escape': '1.0.1',
|
||||||
|
'js-yaml': '^3.6.0',
|
||||||
|
jsonwebtoken: '^7.4.1',
|
||||||
|
lockfile: '^1.0.1',
|
||||||
|
lodash: '4.17.4',
|
||||||
|
lunr: '^0.7.0',
|
||||||
|
marked: '0.3.6',
|
||||||
|
mime: '^1.3.6',
|
||||||
|
minimatch: '^3.0.2',
|
||||||
|
mkdirp: '^0.5.1',
|
||||||
|
pkginfo: '^0.4.0',
|
||||||
|
request: '^2.72.0',
|
||||||
|
semver: '^5.1.0',
|
||||||
|
'unix-crypt-td-js': '^1.0.0',
|
||||||
|
},
|
||||||
|
devDependencies: {
|
||||||
|
axios: '0.16.2',
|
||||||
|
'babel-cli': '6.24.1',
|
||||||
|
'babel-core': '6.25.0',
|
||||||
|
'babel-eslint': '7.2.3',
|
||||||
|
'babel-loader': '7.1.1',
|
||||||
|
'babel-plugin-flow-runtime': '0.11.1',
|
||||||
|
'babel-plugin-transform-decorators-legacy': '1.3.4',
|
||||||
|
'babel-plugin-transform-runtime': '6.23.0',
|
||||||
|
'babel-polyfill': '^6.26.0',
|
||||||
|
'babel-preset-env': '1.5.2',
|
||||||
|
'babel-preset-flow': '6.23.0',
|
||||||
|
'babel-preset-react': '6.24.1',
|
||||||
|
'babel-preset-stage-2': '6.24.1',
|
||||||
|
'babel-preset-stage-3': '6.24.1',
|
||||||
|
'babel-runtime': '6.23.0',
|
||||||
|
'codacy-coverage': '2.0.2',
|
||||||
|
codecov: '2.2.0',
|
||||||
|
coveralls: '2.13.1',
|
||||||
|
'css-loader': '0.28.4',
|
||||||
|
'element-react': '1.0.16',
|
||||||
|
'element-theme-default': '1.3.7',
|
||||||
|
eslint: '4.2.0',
|
||||||
|
'eslint-config-google': '0.8.0',
|
||||||
|
'eslint-loader': '1.8.0',
|
||||||
|
'eslint-plugin-babel': '4.1.1',
|
||||||
|
'eslint-plugin-flowtype': '2.35.0',
|
||||||
|
'eslint-plugin-import': '2.6.1',
|
||||||
|
'eslint-plugin-react': '7.1.0',
|
||||||
|
'extract-text-webpack-plugin': '3.0.0',
|
||||||
|
'file-loader': '0.11.2',
|
||||||
|
'flow-runtime': '0.13.0',
|
||||||
|
'friendly-errors-webpack-plugin': '1.6.1',
|
||||||
|
'fs-extra': '4.0.1',
|
||||||
|
'github-markdown-css': '2.8.0',
|
||||||
|
'html-webpack-plugin': '2.29.0',
|
||||||
|
'in-publish': '2.0.0',
|
||||||
|
'localstorage-memory': '1.0.2',
|
||||||
|
mocha: '3.4.2',
|
||||||
|
'mocha-lcov-reporter': '1.3.0',
|
||||||
|
'node-sass': '4.5.3',
|
||||||
|
'normalize.css': '7.0.0',
|
||||||
|
nyc: '11.0.3',
|
||||||
|
ora: '1.3.0',
|
||||||
|
'prop-types': '15.5.10',
|
||||||
|
react: '15.6.1',
|
||||||
|
'react-dom': '15.6.1',
|
||||||
|
'react-hot-loader': '3.0.0-beta.7',
|
||||||
|
'react-router-dom': '4.1.1',
|
||||||
|
'react-syntax-highlighter': '5.6.2',
|
||||||
|
rimraf: '2.6.1',
|
||||||
|
'sass-loader': '6.0.6',
|
||||||
|
'source-map-loader': '0.2.1',
|
||||||
|
'standard-version': '4.2.0',
|
||||||
|
'style-loader': '0.18.2',
|
||||||
|
stylelint: '7.13.0',
|
||||||
|
'stylelint-config-standard': '16.0.0',
|
||||||
|
'stylelint-webpack-plugin': '0.8.0',
|
||||||
|
'url-loader': '0.5.8',
|
||||||
|
webpack: '3.2.0',
|
||||||
|
'webpack-dev-server': '2.5.0',
|
||||||
|
'webpack-merge': '4.1.0',
|
||||||
|
},
|
||||||
|
keywords: [
|
||||||
|
'private',
|
||||||
|
'package',
|
||||||
|
'repository',
|
||||||
|
'registry',
|
||||||
|
'enterprise',
|
||||||
|
'modules',
|
||||||
|
'proxy',
|
||||||
|
'server',
|
||||||
|
],
|
||||||
|
scripts: {
|
||||||
|
release: 'standard-version -a -s',
|
||||||
|
prepublish: 'in-publish && npm run build:webui || not-in-publish',
|
||||||
|
test: 'mocha ./test/functional ./test/unit --reporter=spec --full-trace',
|
||||||
|
'pre:ci': 'npm run build:webui',
|
||||||
|
'test:ci': 'npm run test:coverage',
|
||||||
|
'test:only': 'mocha ./test/functional ./test/unit',
|
||||||
|
'test:coverage': 'nyc npm t',
|
||||||
|
'coverage:html': 'nyc report --reporter=html',
|
||||||
|
'coverage:publish': 'nyc report --reporter=lcov | codecov',
|
||||||
|
lint: 'eslint .',
|
||||||
|
'lint:css': "stylelint 'src/**/*.scss' --syntax scss",
|
||||||
|
'pre:webpack': 'npm run lint && rimraf static/*',
|
||||||
|
'dev:webui': 'babel-node tools/dev.server.js',
|
||||||
|
'build:webui': 'npm run pre:webpack && webpack --config tools/webpack.prod.config.babel.js',
|
||||||
|
'build:docker': 'docker build -t verdaccio . --no-cache',
|
||||||
|
'build:docker:rpi': 'docker build -f Dockerfile.rpi -t verdaccio:rpi .',
|
||||||
|
},
|
||||||
|
engines: { node: '>=4.6.1', npm: '>=2.15.9' },
|
||||||
|
preferGlobal: true,
|
||||||
|
publishConfig: { registry: 'https://registry.verdaccio.org' },
|
||||||
|
license: 'WTFPL',
|
||||||
|
contributors: [
|
||||||
|
{
|
||||||
|
name: '030',
|
||||||
|
email: 'test1@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/4ef03c2bf8d8689527903212d96fb45b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User NPM',
|
||||||
|
email: 'test2@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/a5a236ba477ee98908600c40cda74f4a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User NPM',
|
||||||
|
email: 'test3@test.comu',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/41a61049006855759bd6ec82ef0543a0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Alex Vernacchia',
|
||||||
|
email: 'tes4@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/06975001f7f2be7052bcf978700c6112',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Alexander Makarenko',
|
||||||
|
email: 'test5@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/d9acfc4ed4e49a436738ff26a722dce4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Alexandre-io',
|
||||||
|
email: 'test6@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/2e095c7cfd278f72825d0fed6e12e3b1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Aram Drevekenin',
|
||||||
|
email: 'test7@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/371edff6d79c39bb9e36bde39d41a4b0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bart Dubois',
|
||||||
|
email: 'test8@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/4acf72b14fcb459286c988c4523bafc8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Barthélemy Vessemont',
|
||||||
|
email: 'test9@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/322cd2fad528a55c4351ec76d85ef525',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brandon Nicholls',
|
||||||
|
email: 'test10@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/2d3b462f08f214ed459967aa7ef206f7',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Bren Norris',
|
||||||
|
email: 'test11@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/465a42204a22efada0f15b46a7cdad3a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brett Trotter',
|
||||||
|
email: 'test12@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/27a54519dcbe64c6d705f3cc4854595a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Brian Peacock',
|
||||||
|
email: 'test13@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/3dd3d627330e7e048c13a7480f19842e',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cedric Darne',
|
||||||
|
email: 'test14@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/0a617cebc6539940d7956c86e86c72a6',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chad Killingsworth',
|
||||||
|
email: 'test15@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/a5825b2d69311e559e28a535e5f0d483',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chris Breneman',
|
||||||
|
email: 'test16@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/3c5c3edef955c93edac672cbad04d7cd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Cody Droz',
|
||||||
|
email: 'test17@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/b762ce4d14acfece36e783b1592d882b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Daniel Rodríguez Rivero',
|
||||||
|
email: 'test18@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/ac7f548c31e8a002cfa41bd4c71e222d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Denis Babineau',
|
||||||
|
email: 'test19@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/ee5a522e067759ba0403824ecebeab4d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Emmanuel Narh',
|
||||||
|
email: 'test20@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/93a84a6120969fd181785ff9de834f0a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fabio Poloni',
|
||||||
|
email: 'test21@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/f9a05677360e5f52fcca6e1af9b0f2ee',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Facundo Chambó',
|
||||||
|
email: 'test22@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/ec9e7c590ba4081c25fcf197f90a4ea0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Guilherme Bernal',
|
||||||
|
email: 'test23@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/e5d55dcf2495618e8b9f8778f8353ee0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Jakub Jirutka',
|
||||||
|
email: 'test24@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/061bdb74aa4a543108658b277a257b4b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'James Newell',
|
||||||
|
email: 'test25@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/825190aaae6ec7fd95085e1fb6f261d2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Jan Vansteenkiste',
|
||||||
|
email: 'test26@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/41835625a324201c796a0a0cffe4796b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Jannis Achstetter',
|
||||||
|
email: 'test27@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/92d1cce007b032f4a63c6df764f18030',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Jeremy Moritz',
|
||||||
|
email: 'test28@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/008127e8f10293f43e62de3b7b3520e1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John Gozde',
|
||||||
|
email: 'test29@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/3e8927c60cb043a56fdd6531cfcaddbc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Jon de la Motte',
|
||||||
|
email: 'test30@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/126c1ea4fdb20bbb85c3ff735b7b0964',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Joseph Gentle',
|
||||||
|
email: 'test31@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/484f0b8ba8b7cc43db0be8f910a91254',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'José De Paz',
|
||||||
|
email: 'test32@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/2532122835f5ebf1642b707ae088c895',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Juan Carlos Picado',
|
||||||
|
email: 'test33@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/c676605ff39f9c7a43f5518a8ce54e12',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Juan Carlos Picado',
|
||||||
|
email: 'test34@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/fba48015a688c38cc84e5b55b07858c0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User NPM',
|
||||||
|
email: 'test35@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/fba48015a688c38cc84e5b55b07858c0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User NPM @nickname',
|
||||||
|
email: 'test36@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/fba48015a688c38cc84e5b55b07858c0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kalman Speier',
|
||||||
|
email: 'test37@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/272806ba17639e2fbf811e51eb8bfb99',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Keyvan Fatehi',
|
||||||
|
email: 'test38@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/22735d1ba5765955914eb2d597dfaab5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kody J. Peterson',
|
||||||
|
email: 'test39@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/918a15afc52e9b0a67b2651191b23d04',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Madison Grubb',
|
||||||
|
email: 'test40@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/73b84fdf661c11d48d3370bfa197162b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Manuel de Brito Fontes',
|
||||||
|
email: 'test41@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/8798ca0a499428e5e8f25d3614ac8b6e',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mark Doeswijk',
|
||||||
|
email: 'test42@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/0d70ebd6c46dc01502bfab5f8c2d2bc5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Meeeeow',
|
||||||
|
email: 'test43@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/baa061890d7b352ba121082272419a8a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Meeeeow',
|
||||||
|
email: 'test44@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/12a36e093451d4c0f75d4240960ce29b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Michael Arnel',
|
||||||
|
email: 'test45@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/5f9a5ed24c63609d52651258f6dd8c12',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Michael Crowe',
|
||||||
|
email: 'test46@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/eec9ee62019852da28a3bc91c57907f9',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Miguel Mejias',
|
||||||
|
email: 'test47@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/7289a01fedfdb9ddf855ee4dd4d41ae2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Miroslav Bajtoš',
|
||||||
|
email: 'test48@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/b4d8831300713259f74aea79f842ca57',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nate Ziarek',
|
||||||
|
email: 'test49@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/6442023756294fd43aa518bbe5cc6dcc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Nick',
|
||||||
|
email: 'test50@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/8a810f12c9624ea2092852fe7c19f1ee',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Piotr Synowiec',
|
||||||
|
email: 'test51@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/87028f33a3e1e5b4201c371abddf93e2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Rafael Cesar',
|
||||||
|
email: 'test52@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/204ed93fa5be7e2f9f299ad8bca6431f',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Robert Ewald',
|
||||||
|
email: 'test53@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/ec2166ce419f78fb354f128b01a4a44d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Robert Groh',
|
||||||
|
email: 'test54@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/565ccb5374a3e0e31a75f11da2eb57aa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Robin Persson',
|
||||||
|
email: 'test55@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/99da46e4d59664134b176869340f464b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Romain Lai-King',
|
||||||
|
email: 'test56@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/69d0370c58399d0e0bbd15ccabfe1ec5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ryan Graham',
|
||||||
|
email: 'test57@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/8bd1dd86bbf8705a5a702b86a2f3a390',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ryan Graham',
|
||||||
|
email: 'test58@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/e272ab422c1c629e9be26cba8b6c0166',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sam Day',
|
||||||
|
email: 'test59@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/1886554b0562a0eeeb78a4d1f27917ea',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Tarun Garg',
|
||||||
|
email: 'test60@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/185e200c3451cfbe341f0e758626303a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Thomas Cort',
|
||||||
|
email: 'test61@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/120d2921c33c1bd8dedfce67a28dcc63',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Tom Vincent',
|
||||||
|
email: 'test62@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/fb0c7faeda7f5d5632182a3d80381bfa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Trent Earl',
|
||||||
|
email: 'test63@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/1e30abe66d21824b89c28d05e5b57d84',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Yannick Croissant',
|
||||||
|
email: 'test64@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/1e619ddb2a180222dd3d9f0348e65b9b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Yannick Galatol',
|
||||||
|
email: 'test65@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/2f624f92326fef845bb2c07b392b7e48',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cklein',
|
||||||
|
email: 'test66@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/f8288370380881cf3afc5a92a63d652d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'danielo515',
|
||||||
|
email: 'test67@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/ac7f548c31e8a002cfa41bd4c71e222d',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'jmwilkinson',
|
||||||
|
email: 'test68@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/3b99683f0a4c26a8906ecbe7968a4ade',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nickname',
|
||||||
|
email: 'test69@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/fba48015a688c38cc84e5b55b07858c0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nickname',
|
||||||
|
email: 'test70@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/047ba1e853d20459e531619af5493c56',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'maxlaverse',
|
||||||
|
email: 'test71@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/74324a2900906c45949a8c5cee6d0730',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'saheba',
|
||||||
|
email: 'test72@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/77644c51856cab149e0f550c5f0c6ed8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'steve-p-com',
|
||||||
|
email: 'test73@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/bef1821d3036b8b9242c4999826c1c3c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'trent.earl',
|
||||||
|
email: 'test74@test.local',
|
||||||
|
avatar: 'https://www.gravatar.com/avatar/f84b8ae496f7c988dce5a71d773e75bb',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
readmeFilename: 'README.md',
|
||||||
|
gitHead: '567dbe327819ed30afb96906f8d43f19740e2e3d',
|
||||||
|
bugs: { url: 'https://github.com/verdaccio/verdaccio/issues' },
|
||||||
|
homepage: 'https://github.com/verdaccio/verdaccio#readme',
|
||||||
|
_id: 'verdaccio@2.7.1',
|
||||||
|
_shasum: '958c919180e7f2ed6775f48d4ec64bd8de2a14df',
|
||||||
|
_from: '.',
|
||||||
|
_npmVersion: '3.10.10',
|
||||||
|
_nodeVersion: '6.9.5',
|
||||||
|
_npmUser: {},
|
||||||
|
dist: {
|
||||||
|
shasum: '958c919180e7f2ed6775f48d4ec64bd8de2a14df',
|
||||||
|
tarball: 'https://registry.verdaccio.org/verdaccio/-/verdaccio-2.7.1.tgz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue