0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor(core): align next() usage and add prod mode for ui proxy

This commit is contained in:
Gao Sun 2021-07-30 02:21:47 +08:00
parent eff73ffaa2
commit e5d49504ac
No known key found for this signature in database
GPG key ID: 0F0EFA2E36639F31
13 changed files with 146 additions and 61 deletions

View file

@ -10,7 +10,8 @@
"precommit": "lint-staged", "precommit": "lint-staged",
"build": "rm -rf build/ && tsc", "build": "rm -rf build/ && tsc",
"lint": "eslint --format pretty --ext .ts src", "lint": "eslint --format pretty --ext .ts src",
"dev": "rm -rf build/ && tsc-watch --preserveWatchOutput --onSuccess \"node ./build/index.js\"" "dev": "rm -rf build/ && tsc-watch --preserveWatchOutput --onSuccess \"node ./build/index.js\"",
"start": "NODE_ENV=production node build/index.js"
}, },
"dependencies": { "dependencies": {
"@logto/essentials": "^1.1.0-rc.2", "@logto/essentials": "^1.1.0-rc.2",
@ -29,6 +30,7 @@
"koa-mount": "^4.0.0", "koa-mount": "^4.0.0",
"koa-proxies": "^0.12.1", "koa-proxies": "^0.12.1",
"koa-router": "^10.0.0", "koa-router": "^10.0.0",
"koa-static": "^5.0.0",
"lodash.pick": "^4.4.0", "lodash.pick": "^4.4.0",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"nanoid": "^3.1.23", "nanoid": "^3.1.23",
@ -44,6 +46,7 @@
"@types/koa-logger": "^3.1.1", "@types/koa-logger": "^3.1.1",
"@types/koa-mount": "^4.0.0", "@types/koa-mount": "^4.0.0",
"@types/koa-router": "^7.4.2", "@types/koa-router": "^7.4.2",
"@types/koa-static": "^4.0.2",
"@types/lodash.pick": "^4.4.6", "@types/lodash.pick": "^4.4.6",
"@types/node": "^16.3.1", "@types/node": "^16.3.1",
"@types/oidc-provider": "^7.4.1", "@types/oidc-provider": "^7.4.1",

View file

@ -9,5 +9,7 @@ export const routes = Object.freeze({
}, },
}); });
export const isProduction = getEnv('NODE_ENV') === 'production';
export const port = Number(getEnv('PORT', '3001')); export const port = Number(getEnv('PORT', '3001'));
export const oidcIssuer = getEnv('OIDC_ISSUER', `http://localhost:${port}/oidc`); export const oidcIssuer = getEnv('OIDC_ISSUER', `http://localhost:${port}/oidc`);
export const mountedApps = Object.freeze(['api', 'oidc']);

View file

@ -1,12 +1,12 @@
declare module 'koa-body' { declare module 'koa-body' {
import { IKoaBodyOptions } from 'node_modules/koa-body'; import { IKoaBodyOptions } from 'node_modules/koa-body';
import { Middleware } from 'koa'; import { MiddlewareType } from 'koa';
declare function koaBody< declare function koaBody<
StateT = Record<string, unknown>, StateT = Record<string, unknown>,
ContextT = Record<string, unknown>, ContextT = Record<string, unknown>,
ResponseBodyT = any ResponseBodyT = any
>(options?: IKoaBodyOptions): Middleware<StateT, ContextT, ResponseBodyT>; >(options?: IKoaBodyOptions): MiddlewareType<StateT, ContextT, ResponseBodyT>;
export = koaBody; export = koaBody;
} }

13
packages/core/src/include.d/koa.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
import { DefaultState, DefaultContext, ParameterizedContext, Next } from 'koa';
declare module 'koa' {
// Have to do this patch since `compose.Middleware` returns `any`.
export type KoaNext<T> = () => Promise<T>;
export type KoaMiddleware<T, R> = (context: T, next: KoaNext<R>) => Promise<void>;
export type MiddlewareType<
StateT = DefaultState,
ContextT = DefaultContext,
ResponseBodyT = any,
NextT = void
> = KoaMiddleware<ParameterizedContext<StateT, ContextT, ResponseBodyT>, NextT>;
}

View file

@ -3,21 +3,22 @@ import Router from 'koa-router';
import { Provider } from 'oidc-provider'; import { Provider } from 'oidc-provider';
import signInRoutes from '@/routes/sign-in'; import signInRoutes from '@/routes/sign-in';
import registerRoutes from '@/routes/register'; import registerRoutes from '@/routes/register';
import uiProxy from '@/proxies/ui';
import swaggerRoutes from '@/routes/swagger'; import swaggerRoutes from '@/routes/swagger';
import mount from 'koa-mount';
const createRouter = (provider: Provider): Router => { const createRouter = (provider: Provider): Router => {
const router = new Router({ prefix: '/api' }); const router = new Router();
router.use(signInRoutes(provider)); signInRoutes(router, provider);
router.use(registerRoutes()); registerRoutes(router);
router.use(swaggerRoutes()); swaggerRoutes(router);
return router; return router;
}; };
export default function initRouter(app: Koa, provider: Provider): Router { export default function initRouter(app: Koa, provider: Provider) {
const router = createRouter(provider); const router = createRouter(provider);
app.use(router.routes()).use(uiProxy()).use(router.allowedMethods()); const apisApp = new Koa().use(router.routes()).use(router.allowedMethods());
return router;
app.use(mount('/api', apisApp));
} }

View file

@ -3,8 +3,9 @@ import koaLogger from 'koa-logger';
import koaErrorHandler from '@/middleware/koa-error-handler'; import koaErrorHandler from '@/middleware/koa-error-handler';
import { port } from '@/consts'; import { port } from '@/consts';
import koaUIProxy from '@/middleware/koa-ui-proxy';
import initOidc from './oidc'; import initOidc from './oidc';
import initRouter from './router'; import initRouter from './apis';
export default async function initApp(app: Koa): Promise<void> { export default async function initApp(app: Koa): Promise<void> {
app.use(koaErrorHandler()); app.use(koaErrorHandler());
@ -13,6 +14,8 @@ export default async function initApp(app: Koa): Promise<void> {
const provider = await initOidc(app); const provider = await initOidc(app);
initRouter(app, provider); initRouter(app, provider);
app.use(koaUIProxy());
app.listen(port, () => { app.listen(port, () => {
console.log(`App is listening on port ${port}`); console.log(`App is listening on port ${port}`);
}); });

View file

@ -1,6 +1,6 @@
import RequestError from '@/errors/RequestError'; import RequestError from '@/errors/RequestError';
import { has } from '@logto/essentials'; import { has } from '@logto/essentials';
import { Middleware } from 'koa'; import { MiddlewareType } from 'koa';
import koaBody from 'koa-body'; import koaBody from 'koa-body';
import { IMiddleware, IRouterParamContext } from 'koa-router'; import { IMiddleware, IRouterParamContext } from 'koa-router';
import { ZodType } from 'zod'; import { ZodType } from 'zod';
@ -51,12 +51,12 @@ export default function koaGuard<
query, query,
body, body,
params, params,
}: GuardConfig<GuardQueryT, GuardBodyT, GuardParametersT>): Middleware< }: GuardConfig<GuardQueryT, GuardBodyT, GuardParametersT>): MiddlewareType<
StateT, StateT,
WithGuarded<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>, WithGuarded<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>,
ResponseBodyT ResponseBodyT
> { > {
const guard: Middleware< const guard: MiddlewareType<
StateT, StateT,
WithGuarded<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>, WithGuarded<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>,
ResponseBodyT ResponseBodyT
@ -72,24 +72,21 @@ export default function koaGuard<
throw new RequestError('guard.invalid_input', error); throw new RequestError('guard.invalid_input', error);
} }
await next(); return next();
}; };
const guardMiddleware: WithGuardConfig< const guardMiddleware: WithGuardConfig<
Middleware< MiddlewareType<
StateT, StateT,
WithGuarded<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>, WithGuarded<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>,
ResponseBodyT ResponseBodyT
> >
> = async function (ctx, next) { > = async function (ctx, next) {
if (body) { if (body) {
await koaBody<StateT, ContextT>()(ctx, async () => { return koaBody<StateT, ContextT>()(ctx, async () => guard(ctx, next));
await guard(ctx, next);
});
return;
} }
await guard(ctx, next); return guard(ctx, next);
}; };
guardMiddleware.config = { query, body, params }; guardMiddleware.config = { query, body, params };

View file

@ -0,0 +1,41 @@
import fs from 'fs';
import { Middleware } from 'koa';
import proxy from 'koa-proxies';
import serveStatic from 'koa-static';
import { IRouterParamContext } from 'koa-router';
import { isProduction, mountedApps } from '@/consts';
const PATH_TO_UI_DIST = '../ui/build/public';
const uiDistFiles = fs.readdirSync(PATH_TO_UI_DIST);
export default function koaUIProxy<
StateT,
ContextT extends IRouterParamContext,
ResponseBodyT
>(): Middleware<StateT, ContextT, ResponseBodyT> {
const developmentProxy = proxy('*', {
target: 'http://localhost:5000',
changeOrigin: true,
logs: true,
});
const staticProxy = serveStatic(PATH_TO_UI_DIST);
return async (context, next) => {
// Route has been handled by one of mounted apps
if (mountedApps.some((app) => context.request.path.startsWith(`/${app}`))) {
return next();
}
if (!isProduction) {
await developmentProxy(context, next);
return next();
}
if (!uiDistFiles.some((file) => context.request.path.startsWith(`/${file}`))) {
context.request.path = '/';
}
await staticProxy(context, next);
return next();
};
}

View file

@ -1,10 +0,0 @@
import proxy from 'koa-proxies';
// CAUTION: this is for testing only
export default function uiProxy() {
return proxy(/^\/(?!api|oidc).*$/, {
target: 'http://localhost:5000',
changeOrigin: true,
logs: true,
});
}

View file

@ -22,9 +22,7 @@ const generateUserId = async (maxRetries = 500) => {
throw new Error('Cannot generate user ID in reasonable retries'); throw new Error('Cannot generate user ID in reasonable retries');
}; };
export default function registerRoutes() { export default function registerRoutes(router: Router) {
const router = new Router();
router.post( router.post(
'/register', '/register',
koaGuard({ koaGuard({
@ -33,7 +31,7 @@ export default function registerRoutes() {
password: string().min(6), password: string().min(6),
}), }),
}), }),
async (ctx) => { async (ctx, next) => {
const { username, password } = ctx.guard.body; const { username, password } = ctx.guard.body;
if (await hasUser(username)) { if (await hasUser(username)) {
@ -59,8 +57,7 @@ export default function registerRoutes() {
}); });
ctx.body = { id }; ctx.body = { id };
return next();
} }
); );
return router.routes();
} }

View file

@ -9,13 +9,11 @@ import koaGuard from '@/middleware/koa-guard';
import RequestError from '@/errors/RequestError'; import RequestError from '@/errors/RequestError';
import { LogtoErrorCode } from '@logto/phrases'; import { LogtoErrorCode } from '@logto/phrases';
export default function signInRoutes(provider: Provider) { export default function signInRoutes(router: Router, provider: Provider) {
const router = new Router();
router.post( router.post(
'/sign-in', '/sign-in',
koaGuard({ body: object({ username: string().optional(), password: string().optional() }) }), koaGuard({ body: object({ username: string().optional(), password: string().optional() }) }),
async (ctx) => { async (ctx, next) => {
const { const {
prompt: { name }, prompt: { name },
} = await provider.interactionDetails(ctx.req, ctx.res); } = await provider.interactionDetails(ctx.req, ctx.res);
@ -60,10 +58,12 @@ export default function signInRoutes(provider: Provider) {
} else { } else {
throw new Error(`Prompt not supported: ${name}`); throw new Error(`Prompt not supported: ${name}`);
} }
return next();
} }
); );
router.post('/sign-in/consent', async (ctx) => { router.post('/sign-in/consent', async (ctx, next) => {
const { session, grantId, params, prompt } = await provider.interactionDetails( const { session, grantId, params, prompt } = await provider.interactionDetails(
ctx.req, ctx.req,
ctx.res ctx.res
@ -95,16 +95,17 @@ export default function signInRoutes(provider: Provider) {
{ mergeWithLastSubmission: true } { mergeWithLastSubmission: true }
); );
ctx.body = { redirectTo }; ctx.body = { redirectTo };
return next();
}); });
router.post('/sign-in/abort', async (ctx) => { router.post('/sign-in/abort', async (ctx, next) => {
await provider.interactionDetails(ctx.req, ctx.res); await provider.interactionDetails(ctx.req, ctx.res);
const error: LogtoErrorCode = 'oidc.aborted'; const error: LogtoErrorCode = 'oidc.aborted';
const redirectTo = await provider.interactionResult(ctx.req, ctx.res, { const redirectTo = await provider.interactionResult(ctx.req, ctx.res, {
error, error,
}); });
ctx.body = { redirectTo }; ctx.body = { redirectTo };
return next();
}); });
return router.routes();
} }

View file

@ -4,10 +4,8 @@ import { isGuardMiddleware, WithGuardConfig } from '@/middleware/koa-guard';
import { toTitle } from '@/utils/string'; import { toTitle } from '@/utils/string';
import { zodTypeToSwagger } from '@/utils/zod'; import { zodTypeToSwagger } from '@/utils/zod';
export default function swaggerRoutes() { export default function swaggerRoutes(router: Router) {
const router = new Router(); router.get('/swagger.json', async (ctx, next) => {
router.get('/swagger.json', async (ctx) => {
const routes = ctx.router.stack.map(({ path, stack, methods }) => { const routes = ctx.router.stack.map(({ path, stack, methods }) => {
const guard = stack.find((function_): function_ is WithGuardConfig<IMiddleware> => const guard = stack.find((function_): function_ is WithGuardConfig<IMiddleware> =>
isGuardMiddleware(function_) isGuardMiddleware(function_)
@ -17,16 +15,15 @@ export default function swaggerRoutes() {
const paths = Object.fromEntries( const paths = Object.fromEntries(
routes.map<[string, OpenAPIV3.PathItemObject]>(({ path, methods, guard }) => { routes.map<[string, OpenAPIV3.PathItemObject]>(({ path, methods, guard }) => {
const trimmedPath = path.slice(4);
const body = guard?.config.body; const body = guard?.config.body;
return [ return [
trimmedPath, `/api${path}`,
Object.fromEntries( Object.fromEntries(
methods.map<[string, OpenAPIV3.OperationObject]>((method) => [ methods.map<[string, OpenAPIV3.OperationObject]>((method) => [
method.toLowerCase(), method.toLowerCase(),
{ {
tags: [toTitle(trimmedPath.split('/')[1] ?? 'General')], tags: [toTitle(path.split('/')[1] ?? 'General')],
requestBody: body && { requestBody: body && {
required: true, required: true,
content: { content: {
@ -57,7 +54,7 @@ export default function swaggerRoutes() {
}; };
ctx.body = document; ctx.body = document;
});
return router.routes(); return next();
});
} }

View file

@ -27,6 +27,7 @@ importers:
'@types/koa-logger': ^3.1.1 '@types/koa-logger': ^3.1.1
'@types/koa-mount': ^4.0.0 '@types/koa-mount': ^4.0.0
'@types/koa-router': ^7.4.2 '@types/koa-router': ^7.4.2
'@types/koa-static': ^4.0.2
'@types/lodash.pick': ^4.4.6 '@types/lodash.pick': ^4.4.6
'@types/node': ^16.3.1 '@types/node': ^16.3.1
'@types/oidc-provider': ^7.4.1 '@types/oidc-provider': ^7.4.1
@ -45,6 +46,7 @@ importers:
koa-mount: ^4.0.0 koa-mount: ^4.0.0
koa-proxies: ^0.12.1 koa-proxies: ^0.12.1
koa-router: ^10.0.0 koa-router: ^10.0.0
koa-static: ^5.0.0
lint-staged: ^11.1.1 lint-staged: ^11.1.1
lodash.pick: ^4.4.0 lodash.pick: ^4.4.0
module-alias: ^2.2.2 module-alias: ^2.2.2
@ -74,6 +76,7 @@ importers:
koa-mount: 4.0.0 koa-mount: 4.0.0
koa-proxies: 0.12.1_koa@2.13.1 koa-proxies: 0.12.1_koa@2.13.1
koa-router: 10.0.0 koa-router: 10.0.0
koa-static: 5.0.0
lodash.pick: 4.4.0 lodash.pick: 4.4.0
module-alias: 2.2.2 module-alias: 2.2.2
nanoid: 3.1.23 nanoid: 3.1.23
@ -88,6 +91,7 @@ importers:
'@types/koa-logger': 3.1.1 '@types/koa-logger': 3.1.1
'@types/koa-mount': 4.0.0 '@types/koa-mount': 4.0.0
'@types/koa-router': 7.4.4 '@types/koa-router': 7.4.4
'@types/koa-static': 4.0.2
'@types/lodash.pick': 4.4.6 '@types/lodash.pick': 4.4.6
'@types/node': 16.4.6 '@types/node': 16.4.6
'@types/oidc-provider': 7.4.2 '@types/oidc-provider': 7.4.2
@ -3142,6 +3146,19 @@ packages:
'@types/koa': 2.13.4 '@types/koa': 2.13.4
dev: true dev: true
/@types/koa-send/4.1.3:
resolution: {integrity: sha512-daaTqPZlgjIJycSTNjKpHYuKhXYP30atFc1pBcy6HHqB9+vcymDgYTguPdx9tO4HMOqNyz6bz/zqpxt5eLR+VA==}
dependencies:
'@types/koa': 2.13.4
dev: true
/@types/koa-static/4.0.2:
resolution: {integrity: sha512-ns/zHg+K6XVPMuohjpOlpkR1WLa4VJ9czgUP9bxkCDn0JZBtUWbD/wKDZzPGDclkQK1bpAEScufCHOy8cbfL0w==}
dependencies:
'@types/koa': 2.13.4
'@types/koa-send': 4.1.3
dev: true
/@types/koa/2.13.4: /@types/koa/2.13.4:
resolution: {integrity: sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==} resolution: {integrity: sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==}
dependencies: dependencies:
@ -5832,7 +5849,6 @@ packages:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
dev: true
/debug/4.3.2: /debug/4.3.2:
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
@ -8029,7 +8045,6 @@ packages:
inherits: 2.0.3 inherits: 2.0.3
setprototypeof: 1.1.0 setprototypeof: 1.1.0
statuses: 1.5.0 statuses: 1.5.0
dev: true
/http-errors/1.7.2: /http-errors/1.7.2:
resolution: {integrity: sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==} resolution: {integrity: sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==}
@ -8325,7 +8340,6 @@ packages:
/inherits/2.0.3: /inherits/2.0.3:
resolution: {integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=} resolution: {integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=}
dev: true
/inherits/2.0.4: /inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
@ -9664,6 +9678,27 @@ packages:
- supports-color - supports-color
dev: false dev: false
/koa-send/5.0.1:
resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==}
engines: {node: '>= 8'}
dependencies:
debug: 4.3.2
http-errors: 1.8.0
resolve-path: 1.4.0
transitivePeerDependencies:
- supports-color
dev: false
/koa-static/5.0.0:
resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==}
engines: {node: '>= 7.6.0'}
dependencies:
debug: 3.2.7
koa-send: 5.0.1
transitivePeerDependencies:
- supports-color
dev: false
/koa/2.13.1: /koa/2.13.1:
resolution: {integrity: sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==} resolution: {integrity: sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w==}
engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4}
@ -10556,7 +10591,6 @@ packages:
/ms/2.1.3: /ms/2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/multi-fork/0.0.2: /multi-fork/0.0.2:
resolution: {integrity: sha1-gFiuxGFBJMftqhWBm4juiJ0+tOA=} resolution: {integrity: sha1-gFiuxGFBJMftqhWBm4juiJ0+tOA=}
@ -11514,7 +11548,6 @@ packages:
/path-is-absolute/1.0.1: /path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/path-is-inside/1.0.2: /path-is-inside/1.0.2:
resolution: {integrity: sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=} resolution: {integrity: sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=}
@ -13206,6 +13239,14 @@ packages:
global-dirs: 0.1.1 global-dirs: 0.1.1
dev: true dev: true
/resolve-path/1.4.0:
resolution: {integrity: sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=}
engines: {node: '>= 0.8'}
dependencies:
http-errors: 1.6.3
path-is-absolute: 1.0.1
dev: false
/resolve-pathname/3.0.0: /resolve-pathname/3.0.0:
resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
dev: false dev: false
@ -13632,7 +13673,6 @@ packages:
/setprototypeof/1.1.0: /setprototypeof/1.1.0:
resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==}
dev: true
/setprototypeof/1.1.1: /setprototypeof/1.1.1:
resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==}