diff --git a/packages/connector-aliyun-dm/docs/config-template.md b/packages/connector-aliyun-dm/docs/config-template.md index e561f6b27..16e332592 100644 --- a/packages/connector-aliyun-dm/docs/config-template.md +++ b/packages/connector-aliyun-dm/docs/config-template.md @@ -6,17 +6,17 @@ "fromAlias": "", "templates": [ { - "usageType": "SIGN_IN", + "usageType": "SignIn", "subject": "", "content": "" }, { - "usageType": "REGISTER", + "usageType": "Register", "subject": "", "content": "" }, { - "usageType": "TEST", + "usageType": "Test", "subject": "", "content": "" } diff --git a/packages/connector-aliyun-sms/docs/config-template.md b/packages/connector-aliyun-sms/docs/config-template.md index 7d8dcda07..dbd2ebdc7 100644 --- a/packages/connector-aliyun-sms/docs/config-template.md +++ b/packages/connector-aliyun-sms/docs/config-template.md @@ -6,7 +6,7 @@ "templates": [ { "type": 0, - "usageType": "SIGN_IN", + "usageType": "SignIn", "code": "", "name": "", "content": "", @@ -14,7 +14,7 @@ }, { "type": 0, - "usageType": "REGISTER", + "usageType": "Register", "code": "", "name": "", "content": "", @@ -22,7 +22,7 @@ }, { "type": 0, - "usageType": "TEST", + "usageType": "Test", "code": "", "name": "", "content": "", diff --git a/packages/connector-sendgrid-mail/README.md b/packages/connector-sendgrid-mail/README.md new file mode 100644 index 000000000..8cd8b3871 --- /dev/null +++ b/packages/connector-sendgrid-mail/README.md @@ -0,0 +1,2 @@ +### SendGrid Mail README +placeholder diff --git a/packages/connector-sendgrid-mail/docs/config-template.md b/packages/connector-sendgrid-mail/docs/config-template.md new file mode 100644 index 000000000..b1c410264 --- /dev/null +++ b/packages/connector-sendgrid-mail/docs/config-template.md @@ -0,0 +1,26 @@ +```json +{ + "apiKey": "", + "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}}.", + } + ] +} +``` diff --git a/packages/connector-sendgrid-mail/jest.config.ts b/packages/connector-sendgrid-mail/jest.config.ts new file mode 100644 index 000000000..431024545 --- /dev/null +++ b/packages/connector-sendgrid-mail/jest.config.ts @@ -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; diff --git a/packages/connector-sendgrid-mail/package.json b/packages/connector-sendgrid-mail/package.json new file mode 100644 index 000000000..194b12f8f --- /dev/null +++ b/packages/connector-sendgrid-mail/package.json @@ -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. ", + "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" +} diff --git a/packages/connector-sendgrid-mail/src/constant.ts b/packages/connector-sendgrid-mail/src/constant.ts new file mode 100644 index 000000000..79d40116e --- /dev/null +++ b/packages/connector-sendgrid-mail/src/constant.ts @@ -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), +}; diff --git a/packages/connector-sendgrid-mail/src/index.test.ts b/packages/connector-sendgrid-mail/src/index.test.ts new file mode 100644 index 000000000..eb4132555 --- /dev/null +++ b/packages/connector-sendgrid-mail/src/index.test.ts @@ -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; + +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(); + }); +}); diff --git a/packages/connector-sendgrid-mail/src/index.ts b/packages/connector-sendgrid-mail/src/index.ts new file mode 100644 index 000000000..675310d6c --- /dev/null +++ b/packages/connector-sendgrid-mail/src/index.ts @@ -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; + + constructor(getConnectorConfig: GetConnectorConfig) { + 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> = 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>(); + }; +} diff --git a/packages/connector-sendgrid-mail/src/mock.ts b/packages/connector-sendgrid-mail/src/mock.ts new file mode 100644 index 000000000..238befc38 --- /dev/null +++ b/packages/connector-sendgrid-mail/src/mock.ts @@ -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}}.', + }, + ], +}; diff --git a/packages/connector-sendgrid-mail/src/types.ts b/packages/connector-sendgrid-mail/src/types.ts new file mode 100644 index 000000000..a21b1dfe0 --- /dev/null +++ b/packages/connector-sendgrid-mail/src/types.ts @@ -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; + substitutions?: Record; + dynamic_template_data?: Record; + custom_args?: Record; + 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; // 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; + +/** + * @doc https://docs.sendgrid.com/api-reference/mail-send/mail-send#responses + */ +type HelpObject = Record; // Helper text or docs for troubleshooting + +type ErrorObject = { message: string; field?: Nullable; help?: HelpObject }; + +export type SendEmailResponse = { errors: ErrorObject; id?: string }; diff --git a/packages/connector-sendgrid-mail/tsconfig.base.json b/packages/connector-sendgrid-mail/tsconfig.base.json new file mode 100644 index 000000000..848a915f7 --- /dev/null +++ b/packages/connector-sendgrid-mail/tsconfig.base.json @@ -0,0 +1,10 @@ +{ + "extends": "@silverhand/ts-config/tsconfig.base", + "compilerOptions": { + "outDir": "lib", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + } +} diff --git a/packages/connector-sendgrid-mail/tsconfig.build.json b/packages/connector-sendgrid-mail/tsconfig.build.json new file mode 100644 index 000000000..d42923dd3 --- /dev/null +++ b/packages/connector-sendgrid-mail/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.base", + "include": ["src"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/connector-sendgrid-mail/tsconfig.json b/packages/connector-sendgrid-mail/tsconfig.json new file mode 100644 index 000000000..20354364a --- /dev/null +++ b/packages/connector-sendgrid-mail/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.base", + "compilerOptions": { + "types": ["node", "jest", "jest-matcher-specific-error"] + }, + "include": ["src", "jest.config.ts"] +} diff --git a/packages/connector-sendgrid-mail/tsconfig.test.json b/packages/connector-sendgrid-mail/tsconfig.test.json new file mode 100644 index 000000000..98c16f367 --- /dev/null +++ b/packages/connector-sendgrid-mail/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "isolatedModules": false + } +} diff --git a/packages/connector-types/src/index.ts b/packages/connector-types/src/index.ts index 16d24b9a4..78ef23fd9 100644 --- a/packages/connector-types/src/index.ts +++ b/packages/connector-types/src/index.ts @@ -56,7 +56,7 @@ export type EmailMessageTypes = { Test: Record; }; -type SmsMessageTypes = EmailMessageTypes; +export type SmsMessageTypes = EmailMessageTypes; export type EmailSendMessageFunction = ( address: string, diff --git a/packages/core/package.json b/packages/core/package.json index 3d1cde54d..16645e504 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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", diff --git a/packages/core/src/connectors/index.test.ts b/packages/core/src/connectors/index.test.ts index 4cc7cc907..fa9c0f80c 100644 --- a/packages/core/src/connectors/index.test.ts +++ b/packages/core/src/connectors/index.test.ts @@ -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 () => { diff --git a/packages/core/src/connectors/index.ts b/packages/core/src/connectors/index.ts index 79fe99998..1df3aa383 100644 --- a/packages/core/src/connectors/index.ts +++ b/packages/core/src/connectors/index.ts @@ -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), ]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50fa100e0..1ea59d7e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: