0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-27 21:39:16 -05:00

feat(core): add tenant is suspended guard (#4379)

* feat(core): add tenant is suspended guard

add tenant is suspended guard to all management apis

* fix(core): address comments

fix function name
This commit is contained in:
simeng-li 2023-08-29 16:42:13 +08:00 committed by GitHub
parent ff6a1ffa4d
commit 9f70e7632c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 207 additions and 42 deletions

View file

@ -42,7 +42,7 @@
"@logto/shared": "workspace:^2.0.0",
"@logto/ui": "workspace:*",
"@silverhand/essentials": "^2.5.0",
"@withtyped/client": "^0.7.21",
"@withtyped/client": "^0.7.22",
"chalk": "^5.0.0",
"clean-deep": "^3.4.0",
"date-fns": "^2.29.3",
@ -82,7 +82,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
"@logto/cloud": "0.2.5-33a6965",
"@logto/cloud": "0.2.5-a3e852f",
"@silverhand/eslint-config": "4.0.1",
"@silverhand/ts-config": "4.0.0",
"@types/debug": "^4.1.7",

View file

@ -0,0 +1,84 @@
import type router from '@logto/cloud/routes';
import Client from '@withtyped/client';
import Sinon from 'sinon';
import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { CloudConnectionLibrary } from '#src/libraries/cloud-connection.js';
import { type LogtoConfigLibrary } from '#src/libraries/logto-config.js';
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
import koaTenantGuard from './koa-tenant-guard.js';
const { jest } = import.meta;
const logtoConfigs: LogtoConfigLibrary = {
getCloudConnectionData: jest.fn().mockResolvedValue({
appId: 'appId',
appSecret: 'appSecret',
resource: 'resource',
}),
getOidcConfigs: jest.fn(),
};
describe('koaTenantGuard middleware', () => {
const cloudConnection = new CloudConnectionLibrary(logtoConfigs);
const mockCloudClient = new Client<typeof router>({ baseUrl: 'http://localhost:3000' });
const getClientSpy = jest.spyOn(cloudConnection, 'getClient').mockResolvedValue(mockCloudClient);
const clientGetSpy = jest.spyOn(mockCloudClient, 'get');
const next = jest.fn();
const ctx = createContextWithRouteParameters();
it('should return directly if not in cloud', async () => {
const stub = Sinon.stub(EnvSet, 'values').value({
...EnvSet.values,
isCloud: false,
});
await expect(koaTenantGuard(cloudConnection)(ctx, next)).resolves.not.toThrow();
expect(clientGetSpy).not.toBeCalled();
expect(getClientSpy).not.toBeCalled();
stub.restore();
});
it('should reject if tenant is suspended', async () => {
const stub = Sinon.stub(EnvSet, 'values').value({
...EnvSet.values,
isCloud: true,
});
// @ts-expect-error mock returning value
clientGetSpy.mockResolvedValue({
isSuspended: true,
});
await expect(koaTenantGuard(cloudConnection)(ctx, next)).rejects.toMatchError(
new RequestError('subscription.tenant_suspended', 403)
);
expect(clientGetSpy).toBeCalledWith('/api/my/tenant');
stub.restore();
});
it('should resolve if tenant is not suspended', async () => {
const stub = Sinon.stub(EnvSet, 'values').value({
...EnvSet.values,
isCloud: true,
});
// @ts-expect-error mock returning value
clientGetSpy.mockResolvedValue({
isSuspended: false,
});
await expect(koaTenantGuard(cloudConnection)(ctx, next)).resolves.not.toThrow();
expect(clientGetSpy).toBeCalledWith('/api/my/tenant');
stub.restore();
});
});

View file

@ -0,0 +1,33 @@
import type { Middleware } from 'koa';
import { type IRouterParamContext } from 'koa-router';
import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { type CloudConnectionLibrary } from '#src/libraries/cloud-connection.js';
const getAvailableTenant = async (cloudConnection: CloudConnectionLibrary) => {
const client = await cloudConnection.getClient();
const tenant = await client.get('/api/my/tenant');
return tenant;
};
export default function koaTenantGuard<StateT, ContextT extends IRouterParamContext, BodyT>(
cloudConnection: CloudConnectionLibrary
): Middleware<StateT, ContextT, BodyT> {
return async (ctx, next) => {
const { isCloud } = EnvSet.values;
if (!isCloud) {
return next();
}
const tenant = await getAvailableTenant(cloudConnection);
if (tenant.isSuspended) {
throw new RequestError('subscription.tenant_suspended', 403);
}
await next();
};
}

View file

@ -5,6 +5,7 @@ import Router from 'koa-router';
import { EnvSet } from '#src/env-set/index.js';
import koaBodyEtag from '#src/middleware/koa-body-etag.js';
import koaCors from '#src/middleware/koa-cors.js';
import koaTenantGuard from '#src/middleware/koa-tenant-guard.js';
import type TenantContext from '#src/tenants/TenantContext.js';
import koaAuth from '../middleware/koa-auth/index.js';
@ -40,6 +41,8 @@ const createRouters = (tenant: TenantContext) => {
const managementRouter: AuthedRouter = new Router();
managementRouter.use(koaAuth(tenant.envSet, getManagementApiResourceIndicator(tenant.id)));
managementRouter.use(koaTenantGuard(tenant.cloudConnection));
applicationRoutes(managementRouter, tenant);
logtoConfigRoutes(managementRouter, tenant);
connectorRoutes(managementRouter, tenant);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Sie haben das Limit Ihres Abonnementplans erreicht.',
get_plan_failed: 'Fehler beim Abrufen des Abonnementplans für den Mandanten.',
tenant_suspended: 'Mandant ist gesperrt. Bitte kontaktieren Sie Ihren Administrator.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'You have reached the limit of your subscription plan.',
get_plan_failed: 'Unable to get subscription plan for tenant.',
tenant_suspended: 'Tenant is suspended. Please contact your administrator.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Has alcanzado el límite de tu plan de suscripción.',
get_plan_failed: 'No se pudo obtener el plan de suscripción para el inquilino.',
tenant_suspended: 'El inquilino está suspendido. Por favor, contacta con tu administrador.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: "Vous avez atteint la limite de votre plan d'abonnement.",
get_plan_failed: "Échec de l'obtention du plan d'abonnement pour le locataire.",
tenant_suspended: 'Le locataire est suspendu. Veuillez contacter votre administrateur.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Hai raggiunto il limite del tuo piano di abbonamento.',
get_plan_failed: "Impossibile ottenere il piano di abbonamento per l'inquilino.",
tenant_suspended: "L'inquilino è sospeso. Si prega di contattare l'amministratore.",
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: '定めた定期プランの上限に達しました。',
get_plan_failed: 'テナントのサブスクリプションプランを取得できませんでした。',
tenant_suspended: 'テナントが停止されています。管理者に連絡してください。',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: '당신은 구독 플랜 한도에 도달하였습니다.',
get_plan_failed: '임차인에 대한 구독 플랜을 가져올 수 없습니다.',
tenant_suspended: '임차인이 정지되었습니다. 관리자에게 문의하십시오.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Osiągnąłeś limit swojego planu subskrypcji.',
get_plan_failed: 'Nie można pobrać planu subskrypcji dla najemcy.',
tenant_suspended: 'Najemca jest zawieszony. Skontaktuj się z administratorem.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Você atingiu o limite do seu plano de assinatura.',
get_plan_failed: 'Não foi possível obter o plano de assinatura para o inquilino.',
tenant_suspended: 'O inquilino está suspenso. Por favor, entre em contato com o administrador.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Você atingiu o limite do seu plano de assinatura.',
get_plan_failed: 'Não foi possível obter o plano de assinatura para o inquilino.',
tenant_suspended: 'O inquilino está suspenso. Por favor, entre em contato com o administrador.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Вы достигли лимита вашего плана подписки.',
get_plan_failed: 'Не удалось получить план подписки для арендатора.',
tenant_suspended: 'Арендатор приостановлен. Пожалуйста, свяжитесь с администратором.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: 'Abonelik planınızın limitine ulaştınız.',
get_plan_failed: 'Abonelik planınızı almak için başarısız oldu.',
tenant_suspended: 'Kiracı askıya alındı. Lütfen yöneticinize başvurun.',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: '您已达到订阅计划的限制。',
get_plan_failed: '无法获取租户的订阅计划。',
tenant_suspended: '租户已被暂停。 请联系您的管理员。',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: '您已達到訂閱計劃的限制。',
get_plan_failed: '無法取得租戶的訂閱計劃。',
tenant_suspended: '租戶已被暫停。 請聯繫您的管理員。',
};
export default Object.freeze(subscription);

View file

@ -1,6 +1,7 @@
const subscription = {
limit_exceeded: '您已達到訂閱計劃的限制。',
get_plan_failed: '無法為租戶獲取訂閱計劃。',
tenant_suspended: '租戶已被暫停。 請聯繫您的管理員。',
};
export default Object.freeze(subscription);

View file

@ -38,13 +38,13 @@
"dependencies": {
"@logto/language-kit": "workspace:^1.0.0",
"@silverhand/essentials": "^2.5.0",
"@withtyped/client": "^0.7.21"
"@withtyped/client": "^0.7.22"
},
"optionalDependencies": {
"zod": "^3.20.2"
},
"devDependencies": {
"@logto/cloud": "0.2.5-33a6965",
"@logto/cloud": "0.2.5-a3e852f",
"@jest/types": "^29.0.3",
"@silverhand/eslint-config": "4.0.1",
"@silverhand/ts-config": "4.0.0",

106
pnpm-lock.yaml generated
View file

@ -1541,7 +1541,7 @@ importers:
version: link:../../toolkit/connector-kit
'@silverhand/essentials':
specifier: ^2.5.0
version: 2.5.0
version: 2.7.0
got:
specifier: ^13.0.0
version: 13.0.0
@ -1596,7 +1596,7 @@ importers:
version: 14.0.0
nock:
specifier: ^13.2.2
version: 13.2.2
version: 13.3.1
prettier:
specifier: ^3.0.0
version: 3.0.0
@ -3167,8 +3167,8 @@ importers:
specifier: ^2.5.0
version: 2.5.0
'@withtyped/client':
specifier: ^0.7.21
version: 0.7.21(zod@3.20.2)
specifier: ^0.7.22
version: 0.7.22(zod@3.20.2)
chalk:
specifier: ^5.0.0
version: 5.1.2
@ -3282,8 +3282,8 @@ importers:
version: 3.20.2
devDependencies:
'@logto/cloud':
specifier: 0.2.5-33a6965
version: 0.2.5-33a6965(zod@3.20.2)
specifier: 0.2.5-a3e852f
version: 0.2.5-a3e852f(zod@3.20.2)
'@silverhand/eslint-config':
specifier: 4.0.1
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
@ -3754,8 +3754,8 @@ importers:
specifier: ^2.5.0
version: 2.5.0
'@withtyped/client':
specifier: ^0.7.21
version: 0.7.21(zod@3.20.2)
specifier: ^0.7.22
version: 0.7.22(zod@3.20.2)
optionalDependencies:
zod:
specifier: ^3.20.2
@ -3765,8 +3765,8 @@ importers:
specifier: ^29.0.3
version: 29.1.2
'@logto/cloud':
specifier: 0.2.5-33a6965
version: 0.2.5-33a6965(zod@3.20.2)
specifier: 0.2.5-a3e852f
version: 0.2.5-a3e852f(zod@3.20.2)
'@silverhand/eslint-config':
specifier: 4.0.1
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
@ -7189,8 +7189,8 @@ packages:
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/source-map@0.3.5:
resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
/@jridgewell/source-map@0.3.3:
resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==}
dependencies:
'@jridgewell/gen-mapping': 0.3.2
'@jridgewell/trace-mapping': 0.3.18
@ -7305,7 +7305,7 @@ packages:
resolution: {integrity: sha512-4XsXlCC0uZHcfazV09/4YKo4koqvSzQlkPUAToTp/WHpb6h2XDOJh5/hi55LXL4zp0PCcgpErKRxFCtgXCc6WQ==}
dependencies:
'@logto/client': 2.2.0
'@silverhand/essentials': 2.8.2
'@silverhand/essentials': 2.7.0
js-base64: 3.7.5
dev: true
@ -7313,7 +7313,7 @@ packages:
resolution: {integrity: sha512-vw8xDW8k38/58Q1r592z/9JdsmUh4+LMmoVm/Nu7LbWKlT32eD3H9hZDkFK9XEHpriifhI0hP7asGWEmhrEUuQ==}
dependencies:
'@logto/js': 2.1.1
'@silverhand/essentials': 2.8.2
'@silverhand/essentials': 2.7.0
camelcase-keys: 7.0.2
jose: 4.14.4
dev: true
@ -7331,18 +7331,18 @@ packages:
resolution: {integrity: sha512-zxy9zr5swOxbzYJNYtKXofj2tSIS9565d+1pT6RSbmx3Hn+JG6uzsb75PZXW9vlmmm7p1sGZeTQ+xVzKNFPsMg==}
engines: {node: ^18.12.0}
dependencies:
'@silverhand/essentials': 2.8.2
'@silverhand/essentials': 2.7.0
'@withtyped/server': 0.12.8(zod@3.20.2)
transitivePeerDependencies:
- zod
dev: true
/@logto/cloud@0.2.5-33a6965(zod@3.20.2):
resolution: {integrity: sha512-Jawe/nWJmjBDDhNfcxOsQfWsLaUifV5az1mNwn23xwVM/6L/YAkwOj+dWi12lqQ+Vk6MLUPzycXvZLV28tVxQg==}
/@logto/cloud@0.2.5-a3e852f(zod@3.20.2):
resolution: {integrity: sha512-dIrEUW7gi477HQpNsq/HT1gdvPK2ZmVuV73u2rH9LXGEIFIVGqmmIaaK3IcOPG110jKCBhTzF0+hKsW9Y3Pjmw==}
engines: {node: ^18.12.0}
dependencies:
'@silverhand/essentials': 2.8.2
'@withtyped/server': 0.12.8(zod@3.20.2)
'@withtyped/server': 0.12.9(zod@3.20.2)
transitivePeerDependencies:
- zod
dev: true
@ -7350,7 +7350,7 @@ packages:
/@logto/js@2.1.1:
resolution: {integrity: sha512-PHikheavVK+l4ivgtzi14p184hEPgXjqQEAom1Gme1MZoopx+WlwxvHSEQBsmyvVqRtI0oiojhoU5tgYi1FKJw==}
dependencies:
'@silverhand/essentials': 2.8.2
'@silverhand/essentials': 2.7.0
camelcase-keys: 7.0.2
jose: 4.14.2
dev: true
@ -7359,7 +7359,7 @@ packages:
resolution: {integrity: sha512-joSzzAqaRKeEquRenoFrIXXkNxkJci5zSkk4afywz1P8tTcTysnV4eXaBmwXNpmDfQdtHBwRdSACZPLgeF8JiQ==}
dependencies:
'@logto/client': 2.1.0
'@silverhand/essentials': 2.8.2
'@silverhand/essentials': 2.7.0
js-base64: 3.7.5
node-fetch: 2.6.7
transitivePeerDependencies:
@ -7372,7 +7372,7 @@ packages:
react: '>=16.8.0 || ^18.0.0'
dependencies:
'@logto/browser': 2.1.0
'@silverhand/essentials': 2.8.2
'@silverhand/essentials': 2.7.0
react: 18.2.0
dev: true
@ -8745,7 +8745,7 @@ packages:
rollup:
optional: true
dependencies:
'@rollup/pluginutils': 5.0.3(rollup@3.8.0)
'@rollup/pluginutils': 5.0.2(rollup@3.8.0)
commondir: 1.0.1
estree-walker: 2.0.2
glob: 8.1.0
@ -8763,7 +8763,7 @@ packages:
rollup:
optional: true
dependencies:
'@rollup/pluginutils': 5.0.3(rollup@3.8.0)
'@rollup/pluginutils': 5.0.2(rollup@3.8.0)
rollup: 3.8.0
dev: true
@ -8776,12 +8776,12 @@ packages:
rollup:
optional: true
dependencies:
'@rollup/pluginutils': 5.0.3(rollup@3.8.0)
'@rollup/pluginutils': 5.0.2(rollup@3.8.0)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-builtin-module: 3.2.1
is-builtin-module: 3.2.0
is-module: 1.0.0
resolve: 1.22.2
resolve: 1.22.1
rollup: 3.8.0
dev: true
@ -8798,14 +8798,14 @@ packages:
tslib:
optional: true
dependencies:
'@rollup/pluginutils': 5.0.3(rollup@3.8.0)
resolve: 1.22.2
'@rollup/pluginutils': 5.0.2(rollup@3.8.0)
resolve: 1.22.1
rollup: 3.8.0
typescript: 5.0.2
dev: true
/@rollup/pluginutils@5.0.3(rollup@3.8.0):
resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==}
/@rollup/pluginutils@5.0.2(rollup@3.8.0):
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0
@ -8966,6 +8966,10 @@ packages:
resolution: {integrity: sha512-8GgVFAmbo6S0EgsjYXH4aH8a69O7SzEtPFPDpVZmJuGEt8e3ODVx0F2V4rXyC3/SzFbcb2md2gRbA+Z6aTad6g==}
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^7}
/@silverhand/essentials@2.7.0:
resolution: {integrity: sha512-F5Qo5ZNnERUURK/9F1ZIi4FBDM22aeD59Zv0VtkgIhUL9tYK9svA2Jz88NNdYBwqCPrh8ExZlpFNi+pNmXKNlQ==}
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
/@silverhand/essentials@2.8.2:
resolution: {integrity: sha512-mrXzAQ6ZGyLoKQpfEOr/LmbQL7FIurVsBaymnsQyKNV56bFYCv5M+2irsAKROtmLMgSunAU/10XiDIaEYW9tbA==}
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
@ -10059,6 +10063,16 @@ packages:
'@withtyped/shared': 0.2.2
transitivePeerDependencies:
- zod
dev: true
/@withtyped/client@0.7.22(zod@3.20.2):
resolution: {integrity: sha512-emNtcO0jc0dFWhvL7eUIRYzhTfn+JqgIvCmXb8ZUFOR8wdSSGrr9VDlm+wgQD06DEBBpmqtTHMMHTNXJdUC/Qw==}
dependencies:
'@withtyped/server': 0.12.9(zod@3.20.2)
'@withtyped/shared': 0.2.2
transitivePeerDependencies:
- zod
dev: false
/@withtyped/server@0.12.8(zod@3.20.2):
resolution: {integrity: sha512-fv9feTOKJhtlaoYM/Kbs2gSTcIXlmu4OMUFwGmK5jqdbVNIOkDBIPxtcC5ZEwevWFgOcd5OqBW+FvbjiaF27Fw==}
@ -10068,6 +10082,7 @@ packages:
'@silverhand/essentials': 2.8.2
'@withtyped/shared': 0.2.2
zod: 3.20.2
dev: true
/@withtyped/server@0.12.9(zod@3.20.2):
resolution: {integrity: sha512-K5zoV9D+WpawbghtlJKF1KOshKkBjq+gYzNRWuZk13YmFWFLcmZn+QCblNP55z9IGdcHWpTRknqb1APuicdzgA==}
@ -10077,7 +10092,6 @@ packages:
'@silverhand/essentials': 2.8.2
'@withtyped/shared': 0.2.2
zod: 3.20.2
dev: false
/@withtyped/shared@0.2.2:
resolution: {integrity: sha512-Vpcj12NqaoZ8M5Z/1kffheI9FBZEm9goed0THmgTcMKXLHjXSRbMZMp0olVxovEgaTIAydshqJOQUXKZMctIZw==}
@ -12746,8 +12760,8 @@ packages:
tslib: 2.5.0
dev: true
/filesize@10.0.12:
resolution: {integrity: sha512-6RS9gDchbn+qWmtV2uSjo5vmKizgfCQeb5jKmqx8HyzA3MoLqqyQxN+QcjkGBJt7FjJ9qFce67Auyya5rRRbpw==}
/filesize@10.0.7:
resolution: {integrity: sha512-iMRG7Qo9nayLoU3PNCiLizYtsy4W1ClrapeCwEgtiQelOAOuRJiw4QaLI+sSr8xr901dgHv+EYP2bCusGZgoiA==}
engines: {node: '>= 10.4.0'}
dev: true
@ -13843,6 +13857,13 @@ packages:
engines: {node: '>=4'}
dev: true
/is-builtin-module@3.2.0:
resolution: {integrity: sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==}
engines: {node: '>=6'}
dependencies:
builtin-modules: 3.3.0
dev: true
/is-builtin-module@3.2.1:
resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
engines: {node: '>=6'}
@ -18350,6 +18371,15 @@ packages:
engines: {node: '>=10'}
dev: true
/resolve@1.22.1:
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
hasBin: true
dependencies:
is-core-module: 2.12.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
/resolve@1.22.2:
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
hasBin: true
@ -18431,10 +18461,10 @@ packages:
dependencies:
brotli-size: 4.0.0
cli-table3: 0.6.3
filesize: 10.0.12
filesize: 10.0.7
gzip-size: 7.0.0
rollup: 3.8.0
terser: 5.19.2
terser: 5.17.7
dev: true
/rollup@3.8.0:
@ -19594,12 +19624,12 @@ packages:
engines: {node: '>=8'}
dev: true
/terser@5.19.2:
resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==}
/terser@5.17.7:
resolution: {integrity: sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==}
engines: {node: '>=10'}
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.5
'@jridgewell/source-map': 0.3.3
acorn: 8.10.0
commander: 2.20.3
source-map-support: 0.5.21