Compare commits

...

16 Commits

Author SHA1 Message Date
Chika
eea7109fe6 next.config.js: use rewrites only for development 2022-07-17 10:27:13 +03:00
Chika
ab0670ddf8 html: add htmlfor for elements 2022-07-17 10:22:11 +03:00
Chika
e6f6639366 css: add fonts to project 2022-07-17 09:36:06 +03:00
Chika
89c6e47a85 css: use normalize npm package 2022-07-17 09:34:20 +03:00
Chika
458307508b http: speed up loading page 2022-07-16 22:20:40 +03:00
Chika
72e0cbb0f3 graphql: fix apollo http errors on client 2022-07-16 20:27:44 +03:00
Chika
ed5f096afa authorization: fix auth url is undefined 2022-07-16 19:12:43 +03:00
Chika
b9d9c9d7bc apollo: fix access graphql url on server 2022-07-16 18:13:06 +03:00
Chika
a780656f29 process/payments: run validation immediately 2022-07-16 17:53:53 +03:00
Chika
090217039e page/index: fix data is undefined 2022-07-16 15:28:33 +03:00
Chika
e607176770 msw: revert auth url 2022-07-16 15:24:26 +03:00
Chika
6d9a26a54d env: move URL_GET_USER to env.local 2022-07-16 15:12:21 +03:00
Chika
a0cc238508 build: not check types/graphql.ts 2022-07-16 14:19:09 +03:00
Chika
225c0e5a7b Dockerfile: add new ARGs 2022-07-16 14:12:59 +03:00
Chika
e0fc9077b4 remove .env files 2022-07-16 11:56:16 +03:00
Chika
fcf521eb8c build: add Dockerfile & changes 2022-07-16 11:48:13 +03:00
42 changed files with 220 additions and 125 deletions

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git

4
.env
View File

@ -1,6 +1,2 @@
####### USERS ########
USERS_SUPER=["akalinina","vchikalkin"]
####### URLS ########
URL_GET_USER=http://auth_service/auth/user
NEXT_PUBLIC_URL_CRM_GRAPHQL_PROXY=/api/crmgraphql

View File

@ -1,8 +0,0 @@
####### Colors ########
NEXT_PUBLIC_COLOR_PRIMARY=#1C01A9
NEXT_PUBLIC_COLOR_SECONDARY=#3A0185
NEXT_PUBLIC_COLOR_TERTIARTY=#580161
####### ICONS ########
NEXT_PUBLIC_FAVICON=favicon.prod.ico

View File

@ -1,4 +0,0 @@
####### Colors ########
NEXT_PUBLIC_COLOR_PRIMARY=#BF3676
NEXT_PUBLIC_COLOR_SECONDARY=#FD4047
NEXT_PUBLIC_COLOR_TERTIARTY=#FF9112

View File

@ -19,6 +19,7 @@ const defaultLinkProps: ComponentProps<typeof Link> = {
const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
selectLead: {
render: () => {
const elementName = 'selectLead';
const title = titles.selectLead;
const valueName = map.selectLead;
const Component = components.selectLead;
@ -26,7 +27,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
const builder = builders.selectLead;
const Element = builder(Component, {
elementName: 'selectLead',
elementName,
valueName,
});
@ -36,9 +37,13 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
});
return (
<Container key="selectLead">
<Head title={title} addon={<LinkComponent {...defaultLinkProps} />} />
<Element {...props} />
<Container key={elementName}>
<Head
htmlFor={elementName}
title={title}
addon={<LinkComponent {...defaultLinkProps} />}
/>
<Element {...props} id={elementName} />
</Container>
);
},
@ -46,6 +51,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
selectOpportunity: {
render: () => {
const elementName = 'selectOpportunity';
const title = titles.selectOpportunity;
const valueName = map.selectOpportunity;
const Component = components.selectOpportunity;
@ -53,7 +59,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
const builder = builders.selectOpportunity;
const Element = builder(Component, {
elementName: 'selectOpportunity',
elementName,
valueName,
});
@ -63,9 +69,13 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
});
return (
<Container key="selectOpportunity">
<Head title={title} addon={<LinkComponent {...defaultLinkProps} />} />
<Element {...props} />
<Container key={elementName}>
<Head
htmlFor={elementName}
title={title}
addon={<LinkComponent {...defaultLinkProps} />}
/>
<Element {...props} id={elementName} />
</Container>
);
},
@ -73,6 +83,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
selectQuote: {
render: () => {
const elementName = 'selectQuote';
const title = titles.selectQuote;
const valueName = map.selectQuote;
const Component = components.selectQuote;
@ -80,7 +91,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
const builder = builders.selectQuote;
const Element = builder(Component, {
elementName: 'selectQuote',
elementName,
valueName,
});
@ -90,9 +101,13 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
});
return (
<Container key="selectQuote">
<Head title={title} addon={<LinkComponent {...defaultLinkProps} />} />
<Element {...props} />
<Container key={elementName}>
<Head
htmlFor={elementName}
title={title}
addon={<LinkComponent {...defaultLinkProps} />}
/>
<Element {...props} id={elementName} />
</Container>
);
},
@ -100,6 +115,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
tbxVehicleTaxInYear: {
render: () => {
const elementName = 'tbxVehicleTaxInYear';
const title = titles.tbxVehicleTaxInYear;
const valueName = map.tbxVehicleTaxInYear;
const Component = components.tbxVehicleTaxInYear;
@ -107,15 +123,15 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
const builder = builders.tbxVehicleTaxInYear;
const Element = builder(Component, {
elementName: 'tbxVehicleTaxInYear',
elementName,
valueName,
});
return (
<Tooltip title="Без учета налога на роскошь" placement="topLeft">
<Container>
<Head title={title} />
<Element {...props} />
<Head htmlFor={elementName} title={title} />
<Element {...props} id={elementName} />
</Container>
</Tooltip>
);
@ -124,6 +140,7 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
selectHighSeasonStart: {
render: () => {
const elementName = 'selectHighSeasonStart';
const title = titles.selectHighSeasonStart;
const valueName = map.selectHighSeasonStart;
const Component = components.selectHighSeasonStart;
@ -131,15 +148,15 @@ const overrideRender: Partial<Record<keyof typeof map, RenderProps>> = {
const builder = builders.selectHighSeasonStart;
const Element = builder(Component, {
elementName: 'selectHighSeasonStart',
elementName,
valueName,
});
return (
<Tooltip title="С какого платежа начинается полный высокий сезон" placement="topLeft">
<Container>
<Head title={title} />
<Element {...props} />
<Head htmlFor={elementName} title={title} />
<Element {...props} id={elementName} />
</Container>
</Tooltip>
);

View File

@ -21,8 +21,8 @@ const render = Object.keys(map).reduce((acc, elementName) => {
acc[elementName] = {
render: () => (
<Container key={elementName}>
<Head title={title} />
<Element {...props} />
<Head title={title} htmlFor={elementName} />
<Element {...props} id={elementName} />
</Container>
),
};

View File

@ -3,7 +3,7 @@ import styled from 'styled-components';
import { Flex } from 'UIKit/grid';
import { min } from 'UIKit/mq';
const ElementTitle = styled.h5`
const ElementTitle = styled.label`
color: rgba(0, 0, 0, 0.75);
font-weight: 600;
font-size: 13px;
@ -18,10 +18,18 @@ const ElementTitle = styled.h5`
}
`;
export function Head({ title, addon }: { title: string; addon?: ReactNode }) {
export function Head({
title,
addon,
htmlFor,
}: {
title: string;
addon?: ReactNode;
htmlFor: string;
}) {
return (
<Flex flexDirection={['row']} justifyContent={['space-between']} alignItems={['center']}>
<ElementTitle>{title}</ElementTitle>
<ElementTitle htmlFor={htmlFor}>{title}</ElementTitle>
{addon}
</Flex>
);

65
Dockerfile Normal file
View File

@ -0,0 +1,65 @@
# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# If using npm with a `package-lock.json` comment out above and use below instead
# COPY package.json package-lock.json ./
# RUN npm ci
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG NEXT_PUBLIC_URL_CRM_GRAPHQL_PROXY
ARG NEXT_PUBLIC_COLOR_PRIMARY
ARG NEXT_PUBLIC_COLOR_SECONDARY
ARG NEXT_PUBLIC_COLOR_TERTIARTY
ARG NEXT_PUBLIC_FAVICON
ARG NEXT_TELEMETRY_DISABLED
ARG NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT
ARG NEXT_PUBLIC_URL_GET_USER
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]

View File

@ -3,7 +3,7 @@ module.exports = {
client: {
service: {
name: 'crmgraphql',
// url: process.env.URL_CRM_GRAPHQL_DIRECT,
// url: process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT,
localSchemaFile: './graphql/crm.schema.graphql',
},
excludes: ['graphql/**/*'],

View File

@ -7,7 +7,7 @@ let apolloClient;
// prettier-ignore
const uri = typeof window === 'undefined'
? process.env.URL_CRM_GRAPHQL_DIRECT
? process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT
: process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_PROXY;
function createApolloClient() {

View File

@ -20,7 +20,7 @@ const users = {
};
export const handlers = [
rest.get('http://auth_service/auth/user', (req, res, ctx) => {
return res(ctx.json(users.akalinina));
rest.get('http://auth_service/user', (req, res, ctx) => {
return res(ctx.json(users.vchikalkin));
}),
];

View File

@ -1,3 +1,4 @@
/* eslint-disable operator-linebreak */
/* eslint-disable @typescript-eslint/no-var-requires */
const withPlugins = require('next-compose-plugins');
const withLess = require('next-with-less');
@ -5,6 +6,7 @@ const withGraphQL = require('next-plugin-graphql');
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
swcMinify: true,
reactStrictMode: true,
eslint: {
@ -14,14 +16,6 @@ const nextConfig = {
styledComponents: true,
},
pageExtensions: ['tsx', 'jsx'],
async rewrites() {
return [
{
source: process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_PROXY,
destination: process.env.URL_CRM_GRAPHQL_DIRECT,
},
];
},
experimental: {
modularizeImports: {
'lodash-es': {
@ -30,6 +24,16 @@ const nextConfig = {
},
},
},
rewrites:
process.env.NODE_ENV === 'development' &&
async function rewrites() {
return [
{
source: process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_PROXY,
destination: process.env.NEXT_PUBLIC_URL_CRM_GRAPHQL_DIRECT,
},
];
},
};
const plugins = [

View File

@ -30,6 +30,7 @@
"next-compose-plugins": "^2.2.1",
"next-plugin-graphql": "^0.0.2",
"next-with-less": "^2.0.5",
"normalize.css": "^8.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rebass": "^4.0.7",

View File

@ -6,10 +6,12 @@ import { useApollo } from 'apollo/hooks';
import Layout from 'Components/Layout';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import 'normalize.css';
import StoreProvider from 'stores/Provider';
import { ThemeProvider } from 'styled-components';
import { GlobalStyle } from 'UIKit/colors';
import theme from 'UIKit/theme';
import '../styles/fonts.css';
import '../styles/globals.css';
import ruRU from 'antd/lib/locale/ru_RU';

View File

@ -7,6 +7,7 @@ export default class MyDocument extends Document {
const originalRenderPage = ctx.renderPage;
try {
// prettier-ignore
ctx.renderPage = () => originalRenderPage({
enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
});
@ -32,27 +33,9 @@ export default class MyDocument extends Document {
<Html lang="ru" translate="no">
<Head>
<meta charSet="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin="true" />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
/>
<link
rel="icon"
href={process.env.NEXT_PUBLIC_FAVICON || 'favicon.ico'}
crossOrigin="use-credentials"
/>
<meta name="theme-color" content="#000000" />
<meta name="description" content="Лизинговый калькулятор Эволюция" />
<link rel="icon" href={process.env.NEXT_PUBLIC_FAVICON} crossOrigin="use-credentials" />
<link rel="apple-touch-icon" href="logo-100.png" crossOrigin="use-credentials" />
</Head>
<body>

View File

@ -3,16 +3,13 @@ import { useApolloClient } from '@apollo/client';
import initializeApollo from 'apollo/client';
import * as Calculation from 'Components/Calculation';
import Output from 'Components/Output';
import defaultOptions from 'config/default-options';
import * as InsuranceTableConfig from 'config/tables/insurance-table';
import { merge } from 'lodash-es';
import type { GetServerSideProps } from 'next';
import Head from 'next/head';
import * as agentsReactions from 'process/agents/reactions';
import * as calculateReactions from 'process/calculate/reactions';
import { getCRMData } from 'process/init/get-data';
import { getInsuranceData, getMainData, getOwnerData } from 'process/init/get-data';
import * as leadOpportunityReactions from 'process/lead-opportunity/reactions';
import paymentsReactions from 'process/payments/reactions';
import { useEffect } from 'react';
import { fetchUser } from 'services/user';
import { useStore } from 'stores/hooks';
import styled from 'styled-components';
@ -43,22 +40,30 @@ const Grid = styled(Box)`
}
`;
function Home() {
const store = useStore();
const apolloClient = useApolloClient();
/**
* add reactions to store
*/
setTimeout(() => {
function injectReactions(store, apolloClient) {
leadOpportunityReactions.common(store, apolloClient);
leadOpportunityReactions.urls(store, apolloClient);
paymentsReactions(store, apolloClient);
calculateReactions.validation(store, apolloClient);
agentsReactions.common(store, apolloClient);
}
function Home() {
const store = useStore();
const apolloClient = useApolloClient();
useEffect(() => {
getMainData(apolloClient).then(({ options }) => {
store.$calculation.$options.setManyOptions(options);
});
getInsuranceData(apolloClient).then(({ tables }) => {
store.$tables.insurance.setManyRowOptions(tables.insurance);
});
injectReactions(store, apolloClient);
}, []);
return (
<Grid>
<Head>
@ -71,7 +76,7 @@ function Home() {
);
}
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
export const getServerSideProps = async ({ req }) => {
const { cookie = '' } = req.headers;
const user = await fetchUser({
@ -81,18 +86,14 @@ export const getServerSideProps: GetServerSideProps = async ({ req }) => {
});
const apolloClient = initializeApollo();
const { options, tables } = await getCRMData(apolloClient, user);
const { options: ownerOptions } = await getOwnerData(apolloClient, user);
return {
props: {
user,
calculation: {
options: merge(defaultOptions, options),
},
tables: {
insurance: {
options: merge(InsuranceTableConfig.defaultOptions, tables.insurance),
},
options: ownerOptions,
},
initialApolloState: apolloClient.cache.extract(),

View File

@ -5,7 +5,6 @@ import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { getDomainName } from 'services/user/tools';
import type { User } from 'services/user/types';
import { normalizeOptions } from 'tools/entity';
import type { GetAddproductTypes } from './__generated__/GetAddproductTypes';
import type { GetInsuranceData } from './__generated__/GetInsuranceData';
import type { GetMainOptions } from './__generated__/GetMainOptions';
@ -133,42 +132,28 @@ const QUERY_GET_INSURANCE_DATA = gql`
}
`;
export async function getCRMData(apolloClient: ApolloClient<NormalizedCache>, user: User) {
const {
data: { selectLead, selectOpportunity },
} = await apolloClient.query<GetOwnerData, GetOwnerDataVariables>({
export async function getOwnerData(apolloClient: ApolloClient<NormalizedCache>, user: User) {
const { data: ownerData } = await apolloClient.query<GetOwnerData, GetOwnerDataVariables>({
query: QUERY_GET_OWNER_DATA,
variables: {
domainname: getDomainName(user),
},
});
return {
options: ownerData,
};
}
export async function getMainData(apolloClient: ApolloClient<object>) {
// prettier-ignore
const { data: options } = await apolloClient.query<GetMainOptions>({
const { data: mainOptions } = await apolloClient.query<GetMainOptions>({
query: QUERY_GET_MAIN_OPTIONS,
variables: {
currentDate: dayjs().utc().toISOString()
}
});
const {
data: { kasko, osago, fingap },
} = await apolloClient.query<GetInsuranceData>({
query: QUERY_GET_INSURANCE_DATA,
});
const insuranceData = {
osago: {
insuranceCompany: normalizeOptions(osago),
},
kasko: {
insuranceCompany: normalizeOptions(kasko),
},
fingap: {
insuranceCompany: normalizeOptions(fingap),
},
};
const { data: subsidies } = await apolloClient.query<GetSubsidies>({
query: QUERY_GET_SUBSIDIES,
variables: {
@ -230,9 +215,7 @@ export async function getCRMData(apolloClient: ApolloClient<NormalizedCache>, us
return {
options: {
selectLead,
selectOpportunity,
...options,
...mainOptions,
selectSubsidy,
selectImportProgram,
selectRegionRegistration,
@ -244,8 +227,29 @@ export async function getCRMData(apolloClient: ApolloClient<NormalizedCache>, us
selectTracker,
selectInsNSIB,
},
};
}
export async function getInsuranceData(apolloClient: ApolloClient<object>) {
const { data: insuranceData } = await apolloClient.query<GetInsuranceData>({
query: QUERY_GET_INSURANCE_DATA,
});
const insurance = {
osago: {
insuranceCompany: insuranceData.osago,
},
kasko: {
insuranceCompany: insuranceData.kasko,
},
fingap: {
insuranceCompany: insuranceData.fingap,
},
};
return {
tables: {
insurance: insuranceData,
insurance,
},
};
}

View File

@ -41,6 +41,9 @@ export default function paymentsReactions(store: RootStore, apolloClient: Apollo
} else {
removeError();
}
},
{
fireImmediately: true,
}
);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,7 @@ import type { User } from './types';
export async function fetchUser(config: AxiosRequestConfig) {
const user = await axios
.get<User>(process.env.URL_GET_USER || '', config)
.get<User>(process.env.NEXT_PUBLIC_URL_GET_USER || '', config)
.then((res) => love(res.data));
return user;

View File

@ -3,6 +3,7 @@
import type { Elements, ElementsTypes } from 'Components/Calculation/config/map/values';
import defaultOptions from 'config/default-options';
import type { BaseOption } from 'Elements/types';
import { merge } from 'lodash-es';
import { makeAutoObservable } from 'mobx';
import type RootStore from 'stores/root';
import type { CalculationOptions } from './types';
@ -17,7 +18,7 @@ export default class OptionsStore {
}
hydrate = (initialOptions: CalculationOptions) => {
this.options = initialOptions;
this.options = merge(defaultOptions, initialOptions);
};
getOptions<T extends Elements>(elementName: T) {

View File

@ -56,10 +56,14 @@ export default class InsuranceTable {
return this.options[key];
}
setRowOptions = (key: Insurance.Keys, rowOptions: Partial<Insurance.RowOptions>) => {
setRowOptions = (key: Insurance.Keys, rowOptions: Insurance.RowOptions) => {
mergeWith(this.options[key], rowOptions);
};
setManyRowOptions = (options: Partial<Record<Insurance.Keys, Insurance.RowOptions>>) => {
mergeWith(this.options, options);
};
getRowStatuses(key: Insurance.Keys) {
return this.statuses[key];
}

6
styles/fonts.css Normal file
View File

@ -0,0 +1,6 @@
@font-face {
font-family: Montserrat;
src: url('../public/fonts/Montserrat/Montserrat-Medium.ttf');
font-weight: 500;
font-style: normal;
}

View File

@ -19,5 +19,5 @@
},
"files": ["types/graphql.d.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
"exclude": ["node_modules", "**/__generated__"]
"exclude": ["node_modules", "**/__generated__", "./types/graphql.ts"]
}

View File

@ -4380,6 +4380,11 @@ normalize-url@^6.1.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
normalize.css@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3"
integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"