0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

feat(connector-sendgrid-email): add sendgrid email connector (#850)

* feat(connector-sendgrid-mail): add SendGrid Mail connector

* feat(core): add SendGrid Mail connector to initializer and fix usageType
This commit is contained in:
Darcy Ye 2022-05-18 11:39:36 +08:00 committed by GitHub
parent 646bd36758
commit b887655827
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 523 additions and 106 deletions

View file

@ -6,17 +6,17 @@
"fromAlias": "<connector-alias>",
"templates": [
{
"usageType": "SIGN_IN",
"usageType": "SignIn",
"subject": "<sign-in-template-subject>",
"content": "<sign-in-template-content>"
},
{
"usageType": "REGISTER",
"usageType": "Register",
"subject": "<register-template-subject>",
"content": "<register-template-content>"
},
{
"usageType": "TEST",
"usageType": "Test",
"subject": "<test-template-subject>",
"content": "<test-template-content>"
}

View file

@ -6,7 +6,7 @@
"templates": [
{
"type": 0,
"usageType": "SIGN_IN",
"usageType": "SignIn",
"code": "<temporary-passcode>",
"name": "<sign-in-template-name>",
"content": "<sign-in-template-content>",
@ -14,7 +14,7 @@
},
{
"type": 0,
"usageType": "REGISTER",
"usageType": "Register",
"code": "<temporary-passcode>",
"name": "<register-template-name>",
"content": "<register-template-content>",
@ -22,7 +22,7 @@
},
{
"type": 0,
"usageType": "TEST",
"usageType": "Test",
"code": "<temporary-passcode>",
"name": "<test-template-name>",
"content": "<test-template-content>",

View file

@ -0,0 +1,2 @@
### SendGrid Mail README
placeholder

View file

@ -0,0 +1,26 @@
```json
{
"apiKey": "<api-key>",
"fromEmail": "noreply@logto.test.io",
"templates": [
{
"usageType": "SignIn",
"type": "text/plain",
"subject": "Logto SignIn Template",
"content": "This is for sign-in purposes only. Your passcode is {{code}}.",
},
{
"usageType": "Register",
"type": "text/plain",
"subject": "Logto Register Template",
"content": "This is for registering purposes only. Your passcode is {{code}}.",
},
{
"usageType": "Test",
"type": "text/plain",
"subject": "Logto Test Template",
"content": "This is for testing purposes only. Your passcode is {{code}}.",
}
]
}
```

View file

@ -0,0 +1,8 @@
import { Config, merge } from '@silverhand/jest-config';
const config: Config.InitialOptions = merge({
testEnvironment: 'node',
setupFilesAfterEnv: ['jest-matcher-specific-error'],
});
export default config;

View file

@ -0,0 +1,55 @@
{
"name": "@logto/connector-sendgrid-email",
"version": "0.1.0",
"description": "SendGrid Email Service connector implementation.",
"main": "./lib/index.js",
"exports": "./lib/index.js",
"author": "Silverhand Inc. <contact@silverhand.io>",
"license": "MPL-2.0",
"files": [
"lib",
"docs"
],
"private": false,
"scripts": {
"preinstall": "npx only-allow pnpm",
"precommit": "lint-staged",
"build": "rm -rf lib/ && tsc -p tsconfig.build.json",
"lint": "eslint --ext .ts src",
"lint:report": "pnpm lint --format json --output-file report.json",
"dev": "rm -rf lib/ && tsc-watch -p tsconfig.build.json --preserveWatchOutput --onSuccess \"node ./lib/index.js\"",
"test": "jest",
"test:coverage": "jest --coverage --silent",
"prepack": "pnpm build"
},
"dependencies": {
"@logto/connector-types": "^0.1.0",
"@logto/shared": "^0.1.0",
"@silverhand/essentials": "^1.1.6",
"@silverhand/jest-config": "^0.14.0",
"got": "^11.8.2",
"zod": "^3.14.3"
},
"devDependencies": {
"@jest/types": "^27.5.1",
"@silverhand/eslint-config": "^0.14.0",
"@silverhand/ts-config": "^0.14.0",
"@types/jest": "^27.4.1",
"@types/node": "^16.3.1",
"eslint": "^8.10.0",
"jest": "^27.5.1",
"jest-matcher-specific-error": "^1.0.0",
"lint-staged": "^12.0.0",
"prettier": "^2.3.2",
"ts-jest": "^27.1.1",
"tsc-watch": "^5.0.0",
"typescript": "^4.6.2"
},
"engines": {
"node": "^16.0.0"
},
"eslintConfig": {
"extends": "@silverhand"
},
"prettier": "@silverhand/eslint-config/.prettierrc"
}

View file

@ -0,0 +1,30 @@
import path from 'path';
import { ConnectorType, ConnectorMetadata } from '@logto/connector-types';
import { getFileContents } from '@logto/shared';
export const endpoint = 'https://api.sendgrid.com/v3/mail/send';
// eslint-disable-next-line unicorn/prefer-module
const currentPath = __dirname;
const pathToReadmeFile = path.join(currentPath, '..', 'README.md');
const pathToConfigTemplate = path.join(currentPath, '..', 'docs', 'config-template.md');
const readmeContentFallback = 'Please check README.md file directory.';
const configTemplateFallback = 'Please check config-template.md file directory.';
export const defaultMetadata: ConnectorMetadata = {
target: 'sendgrid-mail',
type: ConnectorType.Email,
platform: null,
name: {
en: 'SendGrid Mail Service',
'zh-CN': 'SendGrid 邮件服务',
},
logo: './logo.png',
description: {
en: 'Leverage the email service that customer-first brands trust for reliable inbox delivery at scale.',
'zh-CN': '客户至上品牌信任的电子邮件服务,实现大规模可靠的收件箱递送。',
},
readme: getFileContents(pathToReadmeFile, readmeContentFallback),
configTemplate: getFileContents(pathToConfigTemplate, configTemplateFallback),
};

View file

@ -0,0 +1,43 @@
import { GetConnectorConfig } from '@logto/connector-types';
import { SendGridMailConnector } from '.';
import { mockedConfig } from './mock';
import { ContextType, SendGridMailConfig } from './types';
const getConnectorConfig = jest.fn() as GetConnectorConfig<SendGridMailConfig>;
const sendGridMailMethods = new SendGridMailConnector(getConnectorConfig);
jest.mock('got');
beforeAll(() => {
jest.spyOn(sendGridMailMethods, 'getConfig').mockResolvedValue(mockedConfig);
});
describe('validateConfig()', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should pass on valid config', async () => {
await expect(
sendGridMailMethods.validateConfig({
apiKey: 'apiKey',
fromEmail: 'noreply@logto.test.io',
fromName: 'Logto Test',
templates: [
{
usageType: 'Test',
type: ContextType.Text,
subject: 'Logto Test Template',
content: 'This is for testing purposes only. Your passcode is {{code}}.',
},
],
})
).resolves.not.toThrow();
});
it('throws if config is invalid', async () => {
await expect(sendGridMailMethods.validateConfig({})).rejects.toThrow();
});
});

View file

@ -0,0 +1,90 @@
import {
ConnectorError,
ConnectorErrorCodes,
ConnectorMetadata,
EmailSendMessageFunction,
ValidateConfig,
EmailConnector,
GetConnectorConfig,
} from '@logto/connector-types';
import { assert, Nullable } from '@silverhand/essentials';
import got from 'got';
import { defaultMetadata, endpoint } from './constant';
import {
sendGridMailConfigGuard,
SendEmailResponse,
SendGridMailConfig,
EmailData,
Personalization,
Content,
PublicParameters,
} from './types';
export class SendGridMailConnector implements EmailConnector {
public metadata: ConnectorMetadata = defaultMetadata;
public readonly getConfig: GetConnectorConfig<SendGridMailConfig>;
constructor(getConnectorConfig: GetConnectorConfig<SendGridMailConfig>) {
this.getConfig = getConnectorConfig;
}
public validateConfig: ValidateConfig = async (config: unknown) => {
const result = sendGridMailConfigGuard.safeParse(config);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error.message);
}
};
public sendMessage: EmailSendMessageFunction<Nullable<SendEmailResponse>> = async (
address,
type,
data
) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
await this.validateConfig(config);
const { apiKey, fromEmail, fromName, templates } = config;
const template = templates.find((template) => template.usageType === type);
assert(
template,
new ConnectorError(
ConnectorErrorCodes.TemplateNotFound,
`Template not found for type: ${type}`
)
);
const toEmailData: EmailData[] = [{ email: address }];
const fromEmailData: EmailData = fromName
? { email: fromEmail, name: fromName }
: { email: fromEmail };
const personalizations: Personalization = { to: toEmailData };
const content: Content = {
type: template.type,
value:
typeof data.code === 'string'
? template.content.replace(/{{code}}/g, data.code)
: template.content,
};
const { subject } = template;
const parameters: PublicParameters = {
personalizations: [personalizations],
from: fromEmailData,
subject,
content: [content],
};
return got
.post(endpoint, {
headers: {
Authorization: 'Bearer ' + apiKey,
'Content-Type': 'application/json',
},
json: parameters,
})
.json<Nullable<SendEmailResponse>>();
};
}

View file

@ -0,0 +1,32 @@
import {
Content,
ContextType,
EmailData,
Personalization,
PublicParameters,
SendGridMailConfig,
} from './types';
const receivers: EmailData[] = [{ email: 'foo@logto.io' }];
const sender: EmailData = { email: 'noreply@logto.test.io', name: 'Logto Test' };
export const mockedParameters: PublicParameters = {
personalizations: [{ to: receivers }] as Personalization[],
from: sender,
subject: 'Test SendGrid Mail',
content: [{ type: 'text/plain', value: 'This is a test template.' }] as Content[],
};
export const mockedApiKey = 'apikey';
export const mockedConfig: SendGridMailConfig = {
apiKey: mockedApiKey,
fromEmail: 'noreply@logto.test.io',
templates: [
{
usageType: 'Test',
type: ContextType.Text,
subject: 'Logto Test Template',
content: 'This is for testing purposes only. Your passcode is {{code}}.',
},
],
};

View file

@ -0,0 +1,119 @@
import { Nullable } from '@silverhand/essentials';
import { z } from 'zod';
/**
* @doc https://docs.sendgrid.com/api-reference/mail-send/mail-send#body
*/
export enum ContextType {
Text = 'text/plain',
Html = 'text/html',
}
export type EmailData = {
name?: string;
email: string;
};
export type Personalization = {
to: EmailData[];
from?: EmailData;
cc?: EmailData | EmailData[];
bcc?: EmailData | EmailData[];
subject?: string;
headers?: Record<string, string>;
substitutions?: Record<string, string>;
dynamic_template_data?: Record<string, any>;
custom_args?: Record<string, string>;
sendAt?: number;
};
export type Content = {
type: ContextType;
value: string;
};
export type Attachment = {
content: string;
type: ContextType;
filename: string;
disposition: 'inline' | 'attachment';
content_id: string; // The attachment's content ID. This is used when the disposition is set to “inline” and the attachment is an image, allowing the file to be displayed within the body of your email.
};
export type Asm = {
group_id: number;
groups_to_display?: number[]; // Maxitems: 25
};
export type MailSettings = {
bypass_list_management: { enable: boolean };
bypass_spam_management: { enable: boolean };
bypass_bounce_management: { enable: boolean };
bypass_unsubscribe_management: { enable: boolean };
footer: { enable: boolean; text: string; html: string };
sandbox_mode: { enable: boolean };
};
export type TrackingSettings = {
click_tracking: { enable: boolean; enable_test: boolean };
open_tracking: { enable: boolean; substitution_tag: string };
subscription_tracking: { enable: boolean; test: string; html: string; substitution_tag: string };
ganalytics: {
enable: boolean;
utm_source: string;
utm_medium: string;
utm_campaign: string;
utm_term: string;
utm_content: string;
};
};
export type PublicParameters = {
personalizations: Personalization[];
from: EmailData;
reply_to?: EmailData;
reply_to_list?: EmailData[]; // Maxitems: 1000, uniqueItems: true
subject: string; // MinLength: 1
content: Content[];
attachments?: Attachment[];
template_id?: string; // An email template ID. The template content got here will overwrite all previous content fields.
headers?: Record<string, string>; // An object containing key/value pairs of header names and the value to substitute for them. The key/value pairs must be strings. You must ensure these are properly encoded if they contain unicode characters. These headers cannot be one of the reserved headers.
categories?: string[]; // An array of category names for this message. Each category name may not exceed 255 characters. Maxitems: 1000, uniqueItems: true
custom_args?: string; // This parameter is overridden by custom_args set at the personalizations level. Total custom_args size may not exceed 10,000 bytes.
send_at?: number; // A unix timestamp.
batch_id?: string; // An ID representing a batch of emails to be sent at the same time.
asm?: Asm; // An object allowing you to specify how to handle unsubscribes.
ip_pool_name?: string; // The IP Pool that you would like to send this email from. maxLength: 64, minLength: 2
mail_settings?: MailSettings;
tracking_settings?: TrackingSettings;
};
/**
* UsageType here is used to specify the use case of the template, can be either
* 'Register', 'SignIn', 'ForgotPassword' or 'Test'.
*/
const templateGuard = z.object({
usageType: z.string(),
type: z.nativeEnum(ContextType),
subject: z.string(),
content: z.string(), // With variable {{code}}, support HTML
});
export const sendGridMailConfigGuard = z.object({
apiKey: z.string(),
fromEmail: z.string(),
fromName: z.string().optional(),
templates: z.array(templateGuard),
});
export type SendGridMailConfig = z.infer<typeof sendGridMailConfigGuard>;
/**
* @doc https://docs.sendgrid.com/api-reference/mail-send/mail-send#responses
*/
type HelpObject = Record<string, unknown>; // Helper text or docs for troubleshooting
type ErrorObject = { message: string; field?: Nullable<string>; help?: HelpObject };
export type SendEmailResponse = { errors: ErrorObject; id?: string };

View file

@ -0,0 +1,10 @@
{
"extends": "@silverhand/ts-config/tsconfig.base",
"compilerOptions": {
"outDir": "lib",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}

View file

@ -0,0 +1,5 @@
{
"extends": "./tsconfig.base",
"include": ["src"],
"exclude": ["src/**/*.test.ts"]
}

View file

@ -0,0 +1,7 @@
{
"extends": "./tsconfig.base",
"compilerOptions": {
"types": ["node", "jest", "jest-matcher-specific-error"]
},
"include": ["src", "jest.config.ts"]
}

View file

@ -0,0 +1,6 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"isolatedModules": false
}
}

View file

@ -56,7 +56,7 @@ export type EmailMessageTypes = {
Test: Record<string, unknown>;
};
type SmsMessageTypes = EmailMessageTypes;
export type SmsMessageTypes = EmailMessageTypes;
export type EmailSendMessageFunction<T = unknown> = (
address: string,

View file

@ -26,6 +26,7 @@
"@logto/connector-facebook": "^0.1.0",
"@logto/connector-github": "^0.1.0",
"@logto/connector-google": "^0.1.0",
"@logto/connector-sendgrid-email": "^0.1.0",
"@logto/connector-types": "^0.1.0",
"@logto/connector-wechat": "^0.1.0",
"@logto/connector-wechat-native": "^0.1.0",

View file

@ -59,6 +59,14 @@ const googleConnector = {
config: {},
createdAt: 1_646_382_233_000,
};
const sendGridMailConnector = {
id: 'sendgrid-mail',
target: 'sendgrid-mail',
platform: null,
enabled: false,
config: {},
createdAt: 1_646_382_233_111,
};
const wechatConnector = {
id: 'wechat',
target: 'wechat',
@ -83,6 +91,7 @@ const connectors = [
facebookConnector,
githubConnector,
googleConnector,
sendGridMailConnector,
wechatConnector,
wechatNativeConnector,
];
@ -106,8 +115,9 @@ describe('getConnectorInstances', () => {
expect(connectorInstances[3]).toHaveProperty('connector', facebookConnector);
expect(connectorInstances[4]).toHaveProperty('connector', githubConnector);
expect(connectorInstances[5]).toHaveProperty('connector', googleConnector);
expect(connectorInstances[6]).toHaveProperty('connector', wechatConnector);
expect(connectorInstances[7]).toHaveProperty('connector', wechatNativeConnector);
expect(connectorInstances[6]).toHaveProperty('connector', sendGridMailConnector);
expect(connectorInstances[7]).toHaveProperty('connector', wechatConnector);
expect(connectorInstances[8]).toHaveProperty('connector', wechatNativeConnector);
});
test('should throw if any required connector does not exist in DB', async () => {

View file

@ -4,6 +4,7 @@ import { AliyunSmsConnector } from '@logto/connector-aliyun-sms';
import { FacebookConnector } from '@logto/connector-facebook';
import { GithubConnector } from '@logto/connector-github';
import { GoogleConnector } from '@logto/connector-google';
import { SendGridMailConnector } from '@logto/connector-sendgrid-email';
import { WeChatConnector } from '@logto/connector-wechat';
import { WeChatNativeConnector } from '@logto/connector-wechat-native';
import { nanoid } from 'nanoid';
@ -21,6 +22,7 @@ const allConnectors: IConnector[] = [
new FacebookConnector(getConnectorConfig),
new GithubConnector(getConnectorConfig),
new GoogleConnector(getConnectorConfig),
new SendGridMailConnector(getConnectorConfig),
new WeChatConnector(getConnectorConfig),
new WeChatNativeConnector(getConnectorConfig),
];

165
pnpm-lock.yaml generated
View file

@ -306,6 +306,49 @@ importers:
tsc-watch: 5.0.3_typescript@4.6.3
typescript: 4.6.3
packages/connector-sendgrid-mail:
specifiers:
'@jest/types': ^27.5.1
'@logto/connector-types': ^0.1.0
'@logto/shared': ^0.1.0
'@silverhand/eslint-config': ^0.14.0
'@silverhand/essentials': ^1.1.6
'@silverhand/jest-config': ^0.14.0
'@silverhand/ts-config': ^0.14.0
'@types/jest': ^27.4.1
'@types/node': ^16.3.1
eslint: ^8.10.0
got: ^11.8.2
jest: ^27.5.1
jest-matcher-specific-error: ^1.0.0
lint-staged: ^12.0.0
prettier: ^2.3.2
ts-jest: ^27.1.1
tsc-watch: ^5.0.0
typescript: ^4.6.2
zod: ^3.14.3
dependencies:
'@logto/connector-types': link:../connector-types
'@logto/shared': link:../shared
'@silverhand/essentials': 1.1.7
'@silverhand/jest-config': 0.14.0_53ggqi2i4rbcfjtktmjua6zili
got: 11.8.3
zod: 3.14.3
devDependencies:
'@jest/types': 27.5.1
'@silverhand/eslint-config': 0.14.0_rqoong6vegs374egqglqjbgiwm
'@silverhand/ts-config': 0.14.0_typescript@4.6.4
'@types/jest': 27.4.1
'@types/node': 16.11.12
eslint: 8.10.0
jest: 27.5.1
jest-matcher-specific-error: 1.0.0
lint-staged: 12.4.0
prettier: 2.5.1
ts-jest: 27.1.1_53ggqi2i4rbcfjtktmjua6zili
tsc-watch: 5.0.3_typescript@4.6.4
typescript: 4.6.4
packages/connector-types:
specifiers:
'@jest/types': ^27.5.1
@ -553,6 +596,7 @@ importers:
'@logto/connector-facebook': ^0.1.0
'@logto/connector-github': ^0.1.0
'@logto/connector-google': ^0.1.0
'@logto/connector-sendgrid-email': ^0.1.0
'@logto/connector-types': ^0.1.0
'@logto/connector-wechat': ^0.1.0
'@logto/connector-wechat-native': ^0.1.0
@ -623,6 +667,7 @@ importers:
'@logto/connector-facebook': link:../connector-facebook
'@logto/connector-github': link:../connector-github
'@logto/connector-google': link:../connector-google
'@logto/connector-sendgrid-email': link:../connector-sendgrid-mail
'@logto/connector-types': link:../connector-types
'@logto/connector-wechat': link:../connector-wechat
'@logto/connector-wechat-native': link:../connector-wechat-native
@ -3667,7 +3712,6 @@ packages:
pacote: 11.3.5
semver: 7.3.7
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -3836,7 +3880,6 @@ packages:
whatwg-url: 8.7.0
yargs-parser: 20.2.4
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -4034,7 +4077,6 @@ packages:
npm-registry-fetch: 9.0.0
npmlog: 4.1.2
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -4064,7 +4106,6 @@ packages:
pify: 5.0.0
read-package-json: 3.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -4199,7 +4240,6 @@ packages:
pacote: 11.3.5
semver: 7.3.7
transitivePeerDependencies:
- bluebird
- encoding
- supports-color
dev: true
@ -4498,8 +4538,6 @@ packages:
promise-retry: 2.0.1
semver: 7.3.7
which: 2.0.2
transitivePeerDependencies:
- bluebird
dev: true
/@npmcli/installed-package-contents/1.0.7:
@ -5514,7 +5552,6 @@ packages:
stylelint-config-xo-scss: 0.15.0_zhymizk4kfitko2u2d4p3qwyee
transitivePeerDependencies:
- eslint
- eslint-import-resolver-webpack
- postcss
- prettier
- supports-color
@ -5538,7 +5575,7 @@ packages:
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
eslint-plugin-consistent-default-export-name: 0.0.7
eslint-plugin-eslint-comments: 3.2.0_eslint@8.10.0
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
eslint-plugin-import: 2.25.4_eslint@8.10.0
eslint-plugin-no-use-extend-native: 0.5.0
eslint-plugin-node: 11.1.0_eslint@8.10.0
eslint-plugin-prettier: 3.4.1_6pitu4b2tqihty6rv5qeiyb35m
@ -5548,7 +5585,6 @@ packages:
pkg-dir: 4.2.0
prettier: 2.5.1
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: true
@ -5570,7 +5606,7 @@ packages:
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
eslint-plugin-consistent-default-export-name: 0.0.7
eslint-plugin-eslint-comments: 3.2.0_eslint@8.10.0
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
eslint-plugin-import: 2.25.4_eslint@8.10.0
eslint-plugin-no-use-extend-native: 0.5.0
eslint-plugin-node: 11.1.0_eslint@8.10.0
eslint-plugin-prettier: 3.4.1_6pitu4b2tqihty6rv5qeiyb35m
@ -5580,7 +5616,6 @@ packages:
pkg-dir: 4.2.0
prettier: 2.5.1
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: true
@ -5602,7 +5637,7 @@ packages:
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
eslint-plugin-consistent-default-export-name: 0.0.7
eslint-plugin-eslint-comments: 3.2.0_eslint@8.10.0
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
eslint-plugin-import: 2.25.4_eslint@8.10.0
eslint-plugin-no-use-extend-native: 0.5.0
eslint-plugin-node: 11.1.0_eslint@8.10.0
eslint-plugin-prettier: 3.4.1_6pitu4b2tqihty6rv5qeiyb35m
@ -5612,7 +5647,6 @@ packages:
pkg-dir: 4.2.0
prettier: 2.5.1
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: true
@ -5672,7 +5706,6 @@ packages:
- babel-jest
- esbuild
- typescript
dev: true
/@silverhand/jest-config/0.14.0_makj2rl6gt73u7koqro542qsmm:
resolution: {integrity: sha512-zK9wh38/RL5iinPlbcZnjEuu8VfL7lQHDXFQ6Mos+zhN3YH0nsoHD6Faq41eHhUlQ6AkVSowov4hSs28S5RlWg==}
@ -7665,8 +7698,6 @@ packages:
qs: 6.9.7
raw-body: 2.4.3
type-is: 1.6.18
transitivePeerDependencies:
- supports-color
dev: true
/bonjour-service/1.0.11:
@ -7842,8 +7873,6 @@ packages:
ssri: 8.0.1
tar: 6.1.11
unique-filename: 1.1.1
transitivePeerDependencies:
- bluebird
dev: true
/cache-content-type/1.0.1:
@ -8359,8 +8388,6 @@ packages:
on-headers: 1.0.2
safe-buffer: 5.1.2
vary: 1.1.2
transitivePeerDependencies:
- supports-color
dev: true
/concat-map/0.0.1:
@ -8936,22 +8963,12 @@ packages:
/debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.0.0
dev: true
/debug/3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
@ -9180,8 +9197,6 @@ packages:
dependencies:
address: 1.1.2
debug: 2.6.9
transitivePeerDependencies:
- supports-color
dev: true
/detect-port/1.3.0:
@ -9191,8 +9206,6 @@ packages:
dependencies:
address: 1.1.2
debug: 2.6.9
transitivePeerDependencies:
- supports-color
dev: true
/dezalgo/1.0.3:
@ -9671,8 +9684,6 @@ packages:
dependencies:
debug: 3.2.7
resolve: 1.22.0
transitivePeerDependencies:
- supports-color
dev: true
/eslint-import-resolver-typescript/2.5.0_rnagsyfcubvpoxo2ynj23pim7u:
@ -9684,7 +9695,7 @@ packages:
dependencies:
debug: 4.3.3
eslint: 8.10.0
eslint-plugin-import: 2.25.4_sidoke6kqbkbdht6nlmwbfnany
eslint-plugin-import: 2.25.4_eslint@8.10.0
glob: 7.2.0
is-glob: 4.0.3
resolve: 1.22.0
@ -9693,31 +9704,12 @@ packages:
- supports-color
dev: true
/eslint-module-utils/2.7.3_l62aq42yiamaj3cnpuf6avthf4:
/eslint-module-utils/2.7.3:
resolution: {integrity: sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint-import-resolver-node: '*'
eslint-import-resolver-typescript: '*'
eslint-import-resolver-webpack: '*'
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
eslint-import-resolver-node:
optional: true
eslint-import-resolver-typescript:
optional: true
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 5.14.0_fo4uz55zgcu432252zy2gvpvcu
debug: 3.2.7
eslint-import-resolver-node: 0.3.6
eslint-import-resolver-typescript: 2.5.0_rnagsyfcubvpoxo2ynj23pim7u
find-up: 2.1.0
transitivePeerDependencies:
- supports-color
dev: true
/eslint-plugin-consistent-default-export-name/0.0.7:
@ -9752,24 +9744,19 @@ packages:
ignore: 5.2.0
dev: true
/eslint-plugin-import/2.25.4_sidoke6kqbkbdht6nlmwbfnany:
/eslint-plugin-import/2.25.4_eslint@8.10.0:
resolution: {integrity: sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA==}
engines: {node: '>=4'}
peerDependencies:
'@typescript-eslint/parser': '*'
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
peerDependenciesMeta:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 5.14.0_fo4uz55zgcu432252zy2gvpvcu
array-includes: 3.1.4
array.prototype.flat: 1.2.5
debug: 2.6.9
doctrine: 2.1.0
eslint: 8.10.0
eslint-import-resolver-node: 0.3.6
eslint-module-utils: 2.7.3_l62aq42yiamaj3cnpuf6avthf4
eslint-module-utils: 2.7.3
has: 1.0.3
is-core-module: 2.8.1
is-glob: 4.0.3
@ -9777,10 +9764,6 @@ packages:
object.values: 1.1.5
resolve: 1.22.0
tsconfig-paths: 3.13.0
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-plugin-no-use-extend-native/0.5.0:
@ -10172,8 +10155,6 @@ packages:
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
dev: true
/extend-shallow/2.0.1:
@ -10370,8 +10351,6 @@ packages:
parseurl: 1.3.3
statuses: 1.5.0
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: true
/find-cache-dir/3.3.2:
@ -13082,7 +13061,6 @@ packages:
import-local: 3.1.0
npmlog: 4.1.2
transitivePeerDependencies:
- bluebird
- encoding
- supports-color
dev: true
@ -13117,7 +13095,6 @@ packages:
npm-package-arg: 8.1.5
npm-registry-fetch: 11.0.0
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -13131,7 +13108,6 @@ packages:
semver: 7.3.7
ssri: 8.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -13500,7 +13476,6 @@ packages:
socks-proxy-agent: 5.0.1
ssri: 8.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -13525,7 +13500,6 @@ packages:
socks-proxy-agent: 6.1.1
ssri: 8.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -14598,7 +14572,6 @@ packages:
minizlib: 2.1.2
npm-package-arg: 8.1.5
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -14615,7 +14588,6 @@ packages:
minizlib: 2.1.2
npm-package-arg: 8.1.5
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -15054,7 +15026,6 @@ packages:
ssri: 8.0.1
tar: 6.1.11
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -15447,8 +15418,6 @@ packages:
async: 2.6.3
debug: 3.2.7
mkdirp: 0.5.5
transitivePeerDependencies:
- supports-color
dev: true
/postcss-calc/8.2.4_postcss@8.4.12:
@ -16135,11 +16104,6 @@ packages:
/promise-inflight/1.0.1:
resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=}
peerDependencies:
bluebird: '*'
peerDependenciesMeta:
bluebird:
optional: true
dev: true
/promise-retry/2.0.1:
@ -16417,7 +16381,6 @@ packages:
text-table: 0.2.0
transitivePeerDependencies:
- eslint
- supports-color
- typescript
- vue-template-compiler
- webpack
@ -17512,8 +17475,6 @@ packages:
on-finished: 2.3.0
range-parser: 1.2.1
statuses: 1.5.0
transitivePeerDependencies:
- supports-color
dev: true
/serialize-error/7.0.1:
@ -17560,8 +17521,6 @@ packages:
http-errors: 1.6.3
mime-types: 2.1.35
parseurl: 1.3.3
transitivePeerDependencies:
- supports-color
dev: true
/serve-static/1.14.2:
@ -17572,8 +17531,6 @@ packages:
escape-html: 1.0.3
parseurl: 1.3.3
send: 0.17.2
transitivePeerDependencies:
- supports-color
dev: true
/set-blocking/2.0.0:
@ -18809,7 +18766,7 @@ packages:
'@types/jest': 27.4.1
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 27.5.1_ts-node@10.7.0
jest: 27.5.1
jest-util: 27.5.1
json5: 2.2.1
lodash.memoize: 4.1.2
@ -18817,7 +18774,6 @@ packages:
semver: 7.3.5
typescript: 4.6.4
yargs-parser: 20.2.9
dev: true
/ts-jest/27.1.1_makj2rl6gt73u7koqro542qsmm:
resolution: {integrity: sha512-Ds0VkB+cB+8g2JUmP/GKWndeZcCKrbe6jzolGrVWdqVUFByY/2KDHqxJ7yBSon7hDB1TA4PXxjfZ+JjzJisvgA==}
@ -19008,6 +18964,21 @@ packages:
typescript: 4.6.3
dev: true
/tsc-watch/5.0.3_typescript@4.6.4:
resolution: {integrity: sha512-Hz2UawwELMSLOf0xHvAFc7anLeMw62cMVXr1flYmhRuOhOyOljwmb1l/O60ZwRyy1k7N1iC1mrn1QYM2zITfuw==}
engines: {node: '>=8.17.0'}
hasBin: true
peerDependencies:
typescript: '*'
dependencies:
cross-spawn: 7.0.3
node-cleanup: 2.1.2
ps-tree: 1.2.0
string-argv: 0.1.2
strip-ansi: 6.0.1
typescript: 4.6.4
dev: true
/tsconfig-paths/3.13.0:
resolution: {integrity: sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw==}
dependencies: