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

feat: mailgun connector (#4281)

* feat: mailgun connector

* refactor: support png as connector logo

* chore: add changesets

* chore: fix error

* chore: translate phrases
This commit is contained in:
Gao Sun 2023-08-03 12:38:09 +08:00 committed by GitHub
parent 263ea256b9
commit 5b34338484
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 978 additions and 144 deletions

View file

@ -0,0 +1,5 @@
---
"@logto/connector-mailgun": minor
---
add Mailgun connector

View file

@ -0,0 +1,5 @@
---
"@logto/connector-sendgrid-email": patch
---
improve content

View file

@ -39,6 +39,8 @@
"stylelint", "stylelint",
"timestamptz", "timestamptz",
"topbar", "topbar",
"withtyped" "withtyped",
"sendgrid",
"mailgun",
] ]
} }

View file

@ -10,6 +10,8 @@ import type {
} from '@logto/connector-kit'; } from '@logto/connector-kit';
import { ConnectorError, ConnectorErrorCodes, ConnectorType } from '@logto/connector-kit'; import { ConnectorError, ConnectorErrorCodes, ConnectorType } from '@logto/connector-kit';
import { consoleLog } from '../utils.js';
import { notImplemented } from './consts.js'; import { notImplemented } from './consts.js';
import type { ConnectorFactory } from './types.js'; import type { ConnectorFactory } from './types.js';
@ -29,10 +31,18 @@ export function validateConnectorModule(
} }
} }
const supportedImageTypes = Object.freeze({
'.svg': 'image/svg+xml',
'.png': 'image/png',
});
const isSupportedImageType = (extension: string): extension is keyof typeof supportedImageTypes =>
Object.keys(supportedImageTypes).includes(extension);
export const readUrl = async ( export const readUrl = async (
url: string, url: string,
baseUrl: string, baseUrl: string,
type: 'text' | 'svg' type: 'text' | 'image'
): Promise<string> => { ): Promise<string> => {
if (!url) { if (!url) {
return url; return url;
@ -46,10 +56,20 @@ export const readUrl = async (
return url; return url;
} }
if (type === 'svg') { if (type === 'image') {
const data = await readFile(path.join(baseUrl, url)); const filePath = path.join(baseUrl, url);
const extension = path.extname(filePath);
return `data:image/svg+xml;base64,${data.toString('base64')}`; if (!isSupportedImageType(extension)) {
consoleLog.warn(
`[readUrl] unexpected image type: ${filePath}, only support ".svg" and ".png". Falling back to empty string.`
);
return '';
}
const data = await readFile(filePath);
return `data:${supportedImageTypes[extension]};base64,${data.toString('base64')}`;
} }
return readFile(path.join(baseUrl, url), 'utf8'); return readFile(path.join(baseUrl, url), 'utf8');
@ -61,8 +81,8 @@ export const parseMetadata = async (
): Promise<AllConnector['metadata']> => { ): Promise<AllConnector['metadata']> => {
return { return {
...metadata, ...metadata,
logo: await readUrl(metadata.logo, packagePath, 'svg'), logo: await readUrl(metadata.logo, packagePath, 'image'),
logoDark: metadata.logoDark && (await readUrl(metadata.logoDark, packagePath, 'svg')), logoDark: metadata.logoDark && (await readUrl(metadata.logoDark, packagePath, 'image')),
readme: await readUrl(metadata.readme, packagePath, 'text'), readme: await readUrl(metadata.readme, packagePath, 'text'),
configTemplate: configTemplate:
metadata.configTemplate && (await readUrl(metadata.configTemplate, packagePath, 'text')), metadata.configTemplate && (await readUrl(metadata.configTemplate, packagePath, 'text')),

View file

@ -0,0 +1 @@
# @logto/connector-mailgun

View file

@ -0,0 +1,101 @@
# Mailgun email connector
The official Logto connector for Mailgun email service.
**Table of contents**
- [Mailgun email connector](#mailgun-email-connector)
- [Prerequisites](#prerequisites)
- [Basic configuration](#basic-configuration)
- [Deliveries](#deliveries)
- [Config object](#config-object)
- [Usage types](#usage-types)
- [Content config](#content-config)
- [Example](#example)
- [Test Mailgun email connector](#test-mailgun-email-connector)
## Prerequisites
- A [Mailgun](https://www.mailgun.com/) account
- An API key from your Mailgun account, requires the permission to send messages (emails). See [Where Can I Find My API Key and SMTP Credentials?](https://help.mailgun.com/hc/en-us/articles/203380100-Where-Can-I-Find-My-API-Key-and-SMTP-Credentials-) for more information.
## Basic configuration
- Fill out the `domain` field with the domain you have registered in your Mailgun account. This value can be found in the **Domains** section of the Mailgun dashboard. The domain should be in the format `example.com`, without the `https://` or `http://` prefix.
- Fill out the `apiKey` field with the API key you have generated in your Mailgun account.
- Fill out the `from` field with the email address you want to send emails from. This email address must be registered in your Mailgun account. The email address should be in the format `Sender Name <sender@example.com>`.
## Deliveries
### Config object
The "Deliveries" section allows you to configure the content of the emails to be sent in different scenarios. It is a JSON key-value map where the key is the usage type and the value is an object containing the content config for the email to be sent.
```json
{
"<usage-type>": {
// ...
}
}
```
### Usage types
The following usage types are supported:
- `Register`: The email to be sent when a user is registering.
- `SignIn`: The email to be sent when a user is signing in.
- `ForgotPassword`: The email to be sent when a user is resetting their password.
- `Generic`: The email to be sent when a user is performing a generic action, for example, testing the email connector.
> **Note**
> If the usage type is not specified in the deliveries config, the generic email will be sent. If the generic email is not specified, the connector will return an error.
### Content config
The connector supports both direct HTML content and Mailgun template. You can use one of them for each usage type.
In both subject and content, you can use the `{{code}}` placeholder to insert the verification code.
To use direct HTML content, fill out the following fields:
- `subject`: The subject of the email to be sent.
- `replyTo`: The email address to be used as the reply-to address.
- `html`: (Required) The HTML content of the email to be sent.
- `text`: The plain text version of the email to be sent.
To use Mailgun template, fill out the following fields:
- `subject`: The subject of the email to be sent.
- `replyTo`: The email address to be used as the reply-to address.
- `template`: (Required) The name of the Mailgun template to be used.
- `variables`: The variables to be passed to the Mailgun template. Should be a JSON key-value map since it will be stringified before sending to Mailgun. Note there's no need to include the `code` variable since it will be automatically added by the connector.
### Example
The following is an example of the deliveries config:
```json
{
"Register": {
"subject": "{{code}} is your verification code",
"replyTo": "Foo <foo@bar.com>",
"html": "<h1>Welcome to Logto</h1><p>Your verification code is {{code}}.</p>",
"text": "Welcome to Logto. Your verification code is {{code}}."
},
"SignIn": {
"subject": "Welcome back to Logto",
"replyTo": "Foo <foo@bar.com>",
"template": "logto-sign-in",
"variables": {
"bar": "baz"
}
}
}
```
## Test Mailgun email connector
You can type in an email address and click on "Send" to see whether the settings can work before "Save and Done".
That's it. Don't forget to [Enable connector in sign-in experience](https://docs.logto.io/docs/tutorials/get-started/passwordless-sign-in-by-adding-connectors#enable-sms-or-email-passwordless-sign-in)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

@ -0,0 +1,51 @@
{
"name": "@logto/connector-mailgun",
"version": "1.0.0",
"description": "Mailgun connector for Logto.",
"author": "Silverhand Inc. <contact@silverhand.io>",
"dependencies": {
"@logto/connector-kit": "workspace:^1.1.0"
},
"main": "./lib/index.js",
"module": "./lib/index.js",
"exports": "./lib/index.js",
"license": "MPL-2.0",
"type": "module",
"files": [
"lib",
"docs",
"logo.svg",
"logo-dark.svg"
],
"scripts": {
"precommit": "lint-staged",
"build:test": "rm -rf lib/ && tsc -p tsconfig.test.json --sourcemap",
"build": "rm -rf lib/ && tsc -p tsconfig.build.json --noEmit && rollup -c",
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput --incremental",
"lint": "eslint --ext .ts src",
"lint:report": "pnpm lint --format json --output-file report.json",
"test:only": "NODE_OPTIONS=--experimental-vm-modules jest",
"test": "pnpm build:test && pnpm test:only",
"test:ci": "pnpm test:only --silent --coverage",
"prepublishOnly": "pnpm build"
},
"engines": {
"node": "^18.12.0"
},
"eslintConfig": {
"extends": "@silverhand",
"settings": {
"import/core-modules": [
"@silverhand/essentials",
"got",
"nock",
"snakecase-keys",
"zod"
]
}
},
"prettier": "@silverhand/eslint-config/.prettierrc",
"publishConfig": {
"access": "public"
}
}

View file

@ -0,0 +1,66 @@
import type { ConnectorMetadata } from '@logto/connector-kit';
import { ConnectorConfigFormItemType } from '@logto/connector-kit';
export const endpoint = 'https://api.sendgrid.com/v3/mail/send';
export const defaultMetadata: ConnectorMetadata = {
id: 'mailgun-email',
target: 'mailgun-email',
platform: null,
name: {
en: 'Mailgun',
},
logo: './logo.png',
logoDark: null,
description: {
en: 'Mailgun is an email delivery service for sending, receiving, and tracking emails.',
},
readme: './README.md',
formItems: [
{
key: 'domain',
label: 'Domain',
type: ConnectorConfigFormItemType.Text,
required: true,
placeholder: 'https://your-mailgun-domain.com',
},
{
key: 'apiKey',
label: 'API Key',
type: ConnectorConfigFormItemType.Text,
required: true,
placeholder: '<your-mailgun-api-key>',
},
{
key: 'from',
label: 'Email address to send from',
type: ConnectorConfigFormItemType.Text,
required: true,
placeholder: 'Sender Name <foo@example.com>',
},
{
key: 'deliveries',
label: 'Deliveries',
type: ConnectorConfigFormItemType.Json,
required: true,
defaultValue: {
SignIn: {
subject: 'Logto sign-in template {{code}}',
html: 'Your Logto sign-in verification code is {{code}}. The code will remain active for 10 minutes.',
},
Register: {
subject: 'Logto sign-up template {{code}}',
html: 'Your Logto sign-up verification code is {{code}}. The code will remain active for 10 minutes.',
},
ForgotPassword: {
subject: 'Logto reset password template {{code}}',
html: 'Your Logto reset password verification code is {{code}}. The code will remain active for 10 minutes.',
},
Generic: {
subject: 'Logto generic template {{code}}',
html: 'Your Logto generic verification code is {{code}}. The code will remain active for 10 minutes.',
},
},
},
],
};

View file

@ -0,0 +1,190 @@
import nock from 'nock';
import { VerificationCodeType } from '@logto/connector-kit';
import createMailgunConnector from './index.js';
import { type MailgunConfig } from './types.js';
const { jest } = import.meta;
const getConfig = jest.fn();
const domain = 'example.com';
const apiKey = 'apiKey';
const connector = await createMailgunConnector({
getConfig,
});
const baseConfig: Partial<MailgunConfig> = {
domain: 'example.com',
apiKey: 'apiKey',
from: 'foo@example.com',
};
/**
* Nock helper to assert request auth and body.
*
* @param expectation - The expected request body.
*/
const nockMessages = (expectation: Record<string, string | string[] | undefined>) =>
nock('https://api.mailgun.net')
.post(`/v3/${domain}/messages`)
.basicAuth({ user: 'api', pass: apiKey })
.reply((_, body, callback) => {
const params = new URLSearchParams(body);
for (const [key, value] of Object.entries(expectation)) {
if (Array.isArray(value)) {
expect(value).toEqual(params.getAll(key));
} else {
expect(params.get(key)).toBe(value);
}
}
callback(null, [200, 'OK']);
});
describe('Maligun connector', () => {
beforeEach(() => {
nock.cleanAll();
});
it('should send email with raw data', async () => {
nockMessages({
from: baseConfig.from,
to: 'bar@example.com',
subject: 'Verification code is 123456',
html: '<p>Your verification code is 123456</p>',
'h:Reply-To': 'baz@example.com',
});
getConfig.mockResolvedValue({
...baseConfig,
deliveries: {
[VerificationCodeType.Generic]: {
subject: 'Verification code is {{code}}',
html: '<p>Your verification code is {{code}}</p>',
replyTo: 'baz@example.com',
},
},
});
await connector.sendMessage({
to: 'bar@example.com',
type: VerificationCodeType.Generic,
payload: { code: '123456' },
});
});
it('should send email with template', async () => {
nockMessages({
from: 'foo@example.com',
to: 'bar@example.com',
subject: 'Verification code is 123456',
template: 'template',
'h:X-Mailgun-Variables': JSON.stringify({ foo: 'bar', code: '123456' }),
});
getConfig.mockResolvedValue({
...baseConfig,
deliveries: {
[VerificationCodeType.Generic]: {
template: 'template',
variables: { foo: 'bar' },
subject: 'Verification code is {{code}}',
},
},
});
await connector.sendMessage({
to: 'bar@example.com',
type: VerificationCodeType.Generic,
payload: {
code: '123456',
},
});
});
it('should fall back to generic template if type not found', async () => {
nockMessages({
from: 'foo@example.com',
to: 'bar@example.com',
subject: 'Verification code is 123456',
template: 'template',
'h:X-Mailgun-Variables': JSON.stringify({ foo: 'bar', code: '123456' }),
});
getConfig.mockResolvedValue({
...baseConfig,
deliveries: {
[VerificationCodeType.Generic]: {
template: 'template',
variables: { foo: 'bar' },
subject: 'Verification code is {{code}}',
},
},
});
await connector.sendMessage({
to: 'bar@example.com',
type: VerificationCodeType.ForgotPassword,
payload: {
code: '123456',
},
});
});
it('should throw error if template not found or type not supported', async () => {
getConfig.mockResolvedValue({
...baseConfig,
deliveries: {},
});
await expect(
connector.sendMessage({
to: '',
type: VerificationCodeType.Generic,
payload: {
code: '123456',
},
})
).rejects.toThrowErrorMatchingInlineSnapshot('"ConnectorError: template_not_found"');
await expect(
connector.sendMessage({
to: '',
// @ts-expect-error Invalid type
type: 'foo',
payload: {
code: '123456',
},
})
).rejects.toThrowErrorMatchingInlineSnapshot('"ConnectorError: template_not_supported"');
});
it('should throw error if mailgun returns error', async () => {
getConfig.mockResolvedValue({
...baseConfig,
deliveries: {
[VerificationCodeType.Generic]: {
template: 'template',
variables: { foo: 'bar' },
subject: 'Verification code is {{code}}',
},
},
});
nock('https://api.mailgun.net').post(`/v3/${domain}/messages`).reply(400, { message: 'error' });
await expect(
connector.sendMessage({
to: '',
type: VerificationCodeType.Generic,
payload: {
code: '123456',
},
})
).rejects.toThrowErrorMatchingInlineSnapshot(
'"ConnectorError: {"statusCode":400,"body":"{\\"message\\":\\"error\\"}"}"'
);
});
});

View file

@ -0,0 +1,102 @@
import { got, HTTPError } from 'got';
import type {
GetConnectorConfig,
SendMessageFunction,
CreateConnector,
EmailConnector,
} from '@logto/connector-kit';
import {
ConnectorError,
ConnectorErrorCodes,
ConnectorType,
validateConfig,
VerificationCodeType,
} from '@logto/connector-kit';
import { defaultMetadata } from './constant.js';
import {
type DeliveryConfig,
mailgunConfigGuard,
supportTemplateGuard,
type MailgunConfig,
} from './types.js';
const removeUndefinedKeys = (object: Record<string, unknown>) =>
Object.fromEntries(Object.entries(object).filter(([, value]) => value !== undefined));
const getDataFromDeliveryConfig = (
{ subject, replyTo, ...rest }: DeliveryConfig,
code: string
): Record<string, string | undefined> => {
const commonData = {
subject: subject?.replaceAll('{{code}}', code),
'h:Reply-To': replyTo,
};
if ('template' in rest) {
return {
...commonData,
template: rest.template,
'h:X-Mailgun-Variables': JSON.stringify({ ...rest.variables, code }),
};
}
return {
...commonData,
html: rest.html.replaceAll('{{code}}', code),
text: rest.text?.replaceAll('{{code}}', code),
};
};
const sendMessage = (getConfig: GetConnectorConfig): SendMessageFunction => {
return async ({ to, type: typeInput, payload: { code } }, inputConfig) => {
const config = inputConfig ?? (await getConfig(defaultMetadata.id));
validateConfig<MailgunConfig>(config, mailgunConfigGuard);
const { domain, apiKey, from, deliveries } = config;
const type = supportTemplateGuard.safeParse(typeInput);
if (!type.success) {
throw new ConnectorError(ConnectorErrorCodes.TemplateNotSupported);
}
const template = deliveries[type.data] ?? deliveries[VerificationCodeType.Generic];
if (!template) {
throw new ConnectorError(ConnectorErrorCodes.TemplateNotFound);
}
try {
return await got.post(`https://api.mailgun.net/v3/${domain}/messages`, {
username: 'api',
password: apiKey,
form: {
from,
to,
...removeUndefinedKeys(getDataFromDeliveryConfig(template, code)),
},
});
} catch (error) {
if (error instanceof HTTPError) {
const {
response: { body, statusCode },
} = error;
throw new ConnectorError(ConnectorErrorCodes.General, { statusCode, body });
}
throw new ConnectorError(ConnectorErrorCodes.General, error);
}
};
};
const createSendGridMailConnector: CreateConnector<EmailConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
type: ConnectorType.Email,
configGuard: mailgunConfigGuard,
sendMessage: sendMessage(getConfig),
};
};
export default createSendGridMailConnector;

View file

@ -0,0 +1,65 @@
import { VerificationCodeType } from '@logto/connector-kit';
import { mailgunConfigGuard } from './types.js';
describe('Mailgun config guard', () => {
it('should pass with valid config', () => {
const validConfig = {
domain: 'example.com',
apiKey: 'key',
from: 'from',
deliveries: {
[VerificationCodeType.SignIn]: {
html: 'html',
subject: 'subject',
},
[VerificationCodeType.Register]: {
template: 'template',
variables: {},
subject: 'subject',
},
[VerificationCodeType.ForgotPassword]: {
html: 'html',
text: 'text',
subject: 'subject',
},
[VerificationCodeType.Generic]: {
template: 'template',
variables: {},
subject: 'subject',
},
},
};
expect(() => mailgunConfigGuard.parse(validConfig)).not.toThrow();
});
it('should allow partial template config', () => {
const validConfig = {
domain: 'example.com',
apiKey: 'key',
from: 'from',
deliveries: {
[VerificationCodeType.SignIn]: {
html: 'html',
subject: 'subject',
},
},
};
expect(() => mailgunConfigGuard.parse(validConfig)).not.toThrow();
});
it('should fail with invalid config', () => {
const invalidConfig = {
domain: 'example.com',
apiKey: 'key',
from: 'from',
deliveries: {
[VerificationCodeType.ForgotPassword]: {
text: 'text',
subject: 'subject',
},
},
};
expect(() => mailgunConfigGuard.parse(invalidConfig)).toThrow();
});
});

View file

@ -0,0 +1,76 @@
import { z } from 'zod';
import { VerificationCodeType } from '@logto/connector-kit';
export const supportTemplateGuard = z.enum([
VerificationCodeType.SignIn,
VerificationCodeType.Register,
VerificationCodeType.ForgotPassword,
VerificationCodeType.Generic,
]);
type SupportTemplate = z.infer<typeof supportTemplateGuard>;
type CommonEmailConfig = {
/** Subject of the message. */
subject?: string;
/** The email address for recipients to reply to. */
replyTo?: string;
};
/** The data to send a regular message (email). */
type RawEmailConfig = CommonEmailConfig & {
/** HTML version of the message. */
html: string;
/** Text version of the message. */
text?: string;
};
/** The data to send a template message (email). */
type TemplateEmailConfig = CommonEmailConfig & {
/** The template name. */
template: string;
/** The template variables. */
variables?: Record<string, unknown>;
};
/** Config object fot a specific template type. */
export type DeliveryConfig = RawEmailConfig | TemplateEmailConfig;
const templateConfigGuard = z.union([
z.object({
html: z.string(),
text: z.string().optional(),
subject: z.string().optional(),
replyTo: z.string().optional(),
}),
z.object({
template: z.string(),
variables: z.record(z.unknown()).optional(),
subject: z.string().optional(),
replyTo: z.string().optional(),
}),
]) satisfies z.ZodType<DeliveryConfig>;
export type MailgunConfig = {
/** Mailgun domain. */
domain: string;
/** Mailgun API key. */
apiKey: string;
/** The sender of the email, in the form `Sender Name <me@samples.mailgun.org>`. */
from: string;
/**
* The template config object for each template type, while the key is the template type
* and the value is the config object.
*/
deliveries: Partial<Record<SupportTemplate, DeliveryConfig>>;
};
export const mailgunConfigGuard = z.object({
domain: z.string(),
apiKey: z.string(),
from: z.string(),
// Although the type it's expected, this guard should infer required keys. Looks like a mis-implemented in zod.
// See https://github.com/colinhacks/zod/issues/2623
deliveries: z.record(supportTemplateGuard, templateConfigGuard),
}) satisfies z.ZodType<MailgunConfig>;

View file

@ -1,10 +1,10 @@
# SendGrid mail connector # SendGrid email connector
The official Logto connector for SendGrid email service. The official Logto connector for SendGrid email service.
**Table of contents** **Table of contents**
- [SendGrid mail connector](#sendgrid-mail-connector) - [SendGrid email connector](#sendgrid-email-connector)
- [Get started](#get-started) - [Get started](#get-started)
- [Register SendGrid account](#register-sendgrid-account) - [Register SendGrid account](#register-sendgrid-account)
- [Verify senders](#verify-senders) - [Verify senders](#verify-senders)

View file

@ -8,10 +8,8 @@ export const defaultMetadata: ConnectorMetadata = {
target: 'sendgrid-mail', target: 'sendgrid-mail',
platform: null, platform: null,
name: { name: {
en: 'SendGrid Mail Service', en: 'SendGrid Email',
'zh-CN': 'SendGrid 邮件服务', 'zh-CN': 'SendGrid 邮件',
'tr-TR': 'SendGrid EMail Servisi',
ko: 'SendGrid 메일 서비스',
}, },
logo: './logo.svg', logo: './logo.svg',
logoDark: null, logoDark: null,
@ -28,21 +26,21 @@ export const defaultMetadata: ConnectorMetadata = {
label: 'API Key', label: 'API Key',
type: ConnectorConfigFormItemType.Text, type: ConnectorConfigFormItemType.Text,
required: true, required: true,
placeholder: '<api-key>', placeholder: '<your-sendgrid-api-key>',
}, },
{ {
key: 'fromEmail', key: 'fromEmail',
label: 'From Email', label: 'From Email',
type: ConnectorConfigFormItemType.Text, type: ConnectorConfigFormItemType.Text,
required: true, required: true,
placeholder: '<from-Email>', placeholder: 'foo@example.com',
}, },
{ {
key: 'fromName', key: 'fromName',
label: 'From Name', label: 'From Name',
type: ConnectorConfigFormItemType.Text, type: ConnectorConfigFormItemType.Text,
required: false, required: false,
placeholder: '<from-name>', placeholder: 'Logto',
}, },
{ {
key: 'templates', key: 'templates',

View file

@ -15,14 +15,14 @@ import {
} from '@logto/connector-kit'; } from '@logto/connector-kit';
import { defaultMetadata, endpoint } from './constant.js'; import { defaultMetadata, endpoint } from './constant.js';
import { sendGridMailConfigGuard } from './types.js';
import type { import type {
SendGridMailConfig,
EmailData, EmailData,
Personalization, Personalization,
Content, Content,
PublicParameters, PublicParameters,
SendGridMailConfig,
} from './types.js'; } from './types.js';
import { sendGridMailConfigGuard } from './types.js';
const sendMessage = const sendMessage =
(getConfig: GetConnectorConfig): SendMessageFunction => (getConfig: GetConnectorConfig): SendMessageFunction =>
@ -87,7 +87,7 @@ const sendMessage =
throw new ConnectorError(ConnectorErrorCodes.General, rawBody); throw new ConnectorError(ConnectorErrorCodes.General, rawBody);
} }
throw error; throw new ConnectorError(ConnectorErrorCodes.General, error);
} }
}; };

View file

@ -29,7 +29,13 @@ export default class RequestError extends Error {
expose = true, expose = true,
...interpolation ...interpolation
} = typeof input === 'string' ? { code: input } : input; } = typeof input === 'string' ? { code: input } : input;
const message = i18next.t<string, LogtoErrorI18nKey>(`errors:${code}`, interpolation); const message = i18next.t<string, LogtoErrorI18nKey>(`errors:${code}`, {
...interpolation,
interpolation: {
// Disable i18next escape value since it's for API response, we can show HTML tags.
escapeValue: false,
},
});
super(message); super(message);

View file

@ -19,7 +19,8 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
const { code, data } = error; const { code, data } = error;
const errorDescriptionGuard = z.object({ errorDescription: z.string() }); const errorDescriptionGuard = z.object({ errorDescription: z.string() });
const message = trySafe(() => errorDescriptionGuard.parse(data))?.errorDescription; const message =
trySafe(() => errorDescriptionGuard.parse(data))?.errorDescription ?? JSON.stringify(data);
switch (code) { switch (code) {
case ConnectorErrorCodes.InvalidMetadata: case ConnectorErrorCodes.InvalidMetadata:

View file

@ -12,6 +12,7 @@ const connector = {
invalid_response: 'Die Antwort des Connectors ist ungültig.', invalid_response: 'Die Antwort des Connectors ist ungültig.',
template_not_found: template_not_found:
'Die richtige Vorlage in der Connector-Konfiguration konnte nicht gefunden werden.', 'Die richtige Vorlage in der Connector-Konfiguration konnte nicht gefunden werden.',
template_not_supported: 'Der Connector unterstützt diesen Vorlagentyp nicht.',
rate_limit_exceeded: 'Auslöser-Rate-Limit. Bitte versuchen Sie es später erneut.', rate_limit_exceeded: 'Auslöser-Rate-Limit. Bitte versuchen Sie es später erneut.',
not_implemented: '{{method}}: wurde noch nicht implementiert.', not_implemented: '{{method}}: wurde noch nicht implementiert.',
social_invalid_access_token: 'Der Access Token des Connectors ist ungültig.', social_invalid_access_token: 'Der Access Token des Connectors ist ungültig.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: "The connector's config is invalid.", invalid_config: "The connector's config is invalid.",
invalid_response: "The connector's response is invalid.", invalid_response: "The connector's response is invalid.",
template_not_found: 'Unable to find correct template in connector config.', template_not_found: 'Unable to find correct template in connector config.',
template_not_supported: 'The connector does not support this template type.',
rate_limit_exceeded: 'Trigger rate limit. Please try again later.', rate_limit_exceeded: 'Trigger rate limit. Please try again later.',
not_implemented: '{{method}}: has not been implemented yet.', not_implemented: '{{method}}: has not been implemented yet.',
social_invalid_access_token: "The connector's access token is invalid.", social_invalid_access_token: "The connector's access token is invalid.",

View file

@ -11,6 +11,7 @@ const connector = {
invalid_response: 'La respuesta del conector es inválida.', invalid_response: 'La respuesta del conector es inválida.',
template_not_found: template_not_found:
'No se puede encontrar la plantilla correcta en la configuración del conector.', 'No se puede encontrar la plantilla correcta en la configuración del conector.',
template_not_supported: 'El conector no admite este tipo de plantilla.',
rate_limit_exceeded: 'Límite de frecuencia activado. Por favor, inténtalo de nuevo más tarde.', rate_limit_exceeded: 'Límite de frecuencia activado. Por favor, inténtalo de nuevo más tarde.',
not_implemented: '{{method}}: aún no se ha implementado.', not_implemented: '{{method}}: aún no se ha implementado.',
social_invalid_access_token: 'El token de acceso del conector es inválido.', social_invalid_access_token: 'El token de acceso del conector es inválido.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: "La configuration du connecteur n'est pas valide.", invalid_config: "La configuration du connecteur n'est pas valide.",
invalid_response: "La réponse du connecteur n'est pas valide.", invalid_response: "La réponse du connecteur n'est pas valide.",
template_not_found: 'Impossible de trouver le bon modèle dans la configuration du connecteur.', template_not_found: 'Impossible de trouver le bon modèle dans la configuration du connecteur.',
template_not_supported: 'Le connecteur ne prend pas en charge ce type de modèle.',
rate_limit_exceeded: 'Limite de taux déclenchée. Veuillez réessayer plus tard.', rate_limit_exceeded: 'Limite de taux déclenchée. Veuillez réessayer plus tard.',
not_implemented: "{{method}} : n'a pas encore été mis en œuvre.", not_implemented: "{{method}} : n'a pas encore été mis en œuvre.",
social_invalid_access_token: "Le jeton d'accès du connecteur n'est pas valide.", social_invalid_access_token: "Le jeton d'accès du connecteur n'est pas valide.",

View file

@ -11,6 +11,7 @@ const connector = {
invalid_response: 'La risposta del connettore non è valida.', invalid_response: 'La risposta del connettore non è valida.',
template_not_found: template_not_found:
'Impossibile trovare il modello corretto nella configurazione del connettore.', 'Impossibile trovare il modello corretto nella configurazione del connettore.',
template_not_supported: 'Il connettore non supporta questo tipo di modello.',
rate_limit_exceeded: 'Limite di frequenza attivata. Riprova più tardi.', rate_limit_exceeded: 'Limite di frequenza attivata. Riprova più tardi.',
not_implemented: '{{method}}: non è stato ancora implementato.', not_implemented: '{{method}}: non è stato ancora implementato.',
social_invalid_access_token: 'Il token di accesso del connettore non è valido.', social_invalid_access_token: 'Il token di accesso del connettore non è valido.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: 'コネクタの設定が無効です。', invalid_config: 'コネクタの設定が無効です。',
invalid_response: 'コネクタのレスポンスが無効です。', invalid_response: 'コネクタのレスポンスが無効です。',
template_not_found: 'コネクタ構成から正しいテンプレートを見つけることができませんでした。', template_not_found: 'コネクタ構成から正しいテンプレートを見つけることができませんでした。',
template_not_supported: 'コネクタはこのテンプレートタイプをサポートしていません。',
rate_limit_exceeded: 'トリガーレート制限。後でもう一度お試しください。', rate_limit_exceeded: 'トリガーレート制限。後でもう一度お試しください。',
not_implemented: '{{method}}:まだ実装されていません。', not_implemented: '{{method}}:まだ実装されていません。',
social_invalid_access_token: 'コネクタのアクセストークンが無効です。', social_invalid_access_token: 'コネクタのアクセストークンが無効です。',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: '연동 설정이 유효하지 않아요.', invalid_config: '연동 설정이 유효하지 않아요.',
invalid_response: '연동 응답이 유효하지 않아요.', invalid_response: '연동 응답이 유효하지 않아요.',
template_not_found: '연동 예제 설정을 찾을 수 없어요.', template_not_found: '연동 예제 설정을 찾을 수 없어요.',
template_not_supported: '연동이 이 템플릿 타입을 지원하지 않아요.',
rate_limit_exceeded: '트리거 주기 제한. 나중에 다시 시도하세요.', rate_limit_exceeded: '트리거 주기 제한. 나중에 다시 시도하세요.',
not_implemented: '{{method}}은 아직 구현되지 않았어요.', not_implemented: '{{method}}은 아직 구현되지 않았어요.',
social_invalid_access_token: '연동 서비스의 Access 토큰이 유효하지 않아요.', social_invalid_access_token: '연동 서비스의 Access 토큰이 유효하지 않아요.',

View file

@ -11,6 +11,7 @@ const connector = {
invalid_config: 'Konfiguracja łącznika jest nieprawidłowa.', invalid_config: 'Konfiguracja łącznika jest nieprawidłowa.',
invalid_response: 'Odpowiedź łącznika jest nieprawidłowa.', invalid_response: 'Odpowiedź łącznika jest nieprawidłowa.',
template_not_found: 'Nie można znaleźć poprawnego szablonu w konfiguracji łącznika.', template_not_found: 'Nie można znaleźć poprawnego szablonu w konfiguracji łącznika.',
template_not_supported: 'Łącznik nie obsługuje tego typu szablonu.',
rate_limit_exceeded: 'Ograniczenie szybkości wywołań. Spróbuj ponownie później.', rate_limit_exceeded: 'Ograniczenie szybkości wywołań. Spróbuj ponownie później.',
not_implemented: '{{method}}: jeszcze nie zaimplementowano.', not_implemented: '{{method}}: jeszcze nie zaimplementowano.',
social_invalid_access_token: 'Token dostępu łącznika jest nieprawidłowy.', social_invalid_access_token: 'Token dostępu łącznika jest nieprawidłowy.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: 'A configuração do conector é inválida.', invalid_config: 'A configuração do conector é inválida.',
invalid_response: 'A resposta do conector é inválida.', invalid_response: 'A resposta do conector é inválida.',
template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.', template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.',
template_not_supported: 'O conector não suporta esse tipo de modelo.',
rate_limit_exceeded: 'Limite de taxa de acionamento. Tente novamente mais tarde.', rate_limit_exceeded: 'Limite de taxa de acionamento. Tente novamente mais tarde.',
not_implemented: '{{method}}: ainda não foi implementado.', not_implemented: '{{method}}: ainda não foi implementado.',
social_invalid_access_token: 'O token de acesso do conector é inválido.', social_invalid_access_token: 'O token de acesso do conector é inválido.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: 'A configuração do conector é inválida.', invalid_config: 'A configuração do conector é inválida.',
invalid_response: 'A resposta do conector é inválida.', invalid_response: 'A resposta do conector é inválida.',
template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.', template_not_found: 'Não foi possível encontrar o modelo correto na configuração do conector.',
template_not_supported: 'O conector não suporta este tipo de modelo.',
rate_limit_exceeded: 'Limite de taxa de ativação. Por favor, tente novamente mais tarde.', rate_limit_exceeded: 'Limite de taxa de ativação. Por favor, tente novamente mais tarde.',
not_implemented: '{{method}}: ainda não foi implementado.', not_implemented: '{{method}}: ainda não foi implementado.',
social_invalid_access_token: 'O token de acesso do conector é inválido.', social_invalid_access_token: 'O token de acesso do conector é inválido.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: 'Конфигурация коннектора недействительна.', invalid_config: 'Конфигурация коннектора недействительна.',
invalid_response: 'Ответ коннектора недействителен.', invalid_response: 'Ответ коннектора недействителен.',
template_not_found: 'Невозможно найти правильный шаблон в конфигурации коннектора.', template_not_found: 'Невозможно найти правильный шаблон в конфигурации коннектора.',
template_not_supported: 'Коннектор не поддерживает этот тип шаблона.',
rate_limit_exceeded: 'Превышен лимит запросов. Пожалуйста, попробуйте позже.', rate_limit_exceeded: 'Превышен лимит запросов. Пожалуйста, попробуйте позже.',
not_implemented: '{{method}}: еще не реализован.', not_implemented: '{{method}}: еще не реализован.',
social_invalid_access_token: 'Токен доступа коннектора недействителен.', social_invalid_access_token: 'Токен доступа коннектора недействителен.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: 'Bağlayıcının ayarları geçersiz.', invalid_config: 'Bağlayıcının ayarları geçersiz.',
invalid_response: 'Bağlayıcının yanıtı geçersiz.', invalid_response: 'Bağlayıcının yanıtı geçersiz.',
template_not_found: 'Bağlayıcı yapılandırmasında doğru şablon bulunamıyor.', template_not_found: 'Bağlayıcı yapılandırmasında doğru şablon bulunamıyor.',
template_not_supported: 'Bağlayıcı bu şablon türünü desteklemiyor.',
rate_limit_exceeded: 'Tetikleyici oran sınırına ulaşıldı. Lütfen daha sonra tekrar deneyin.', rate_limit_exceeded: 'Tetikleyici oran sınırına ulaşıldı. Lütfen daha sonra tekrar deneyin.',
not_implemented: '{{method}}: henüz uygulanmadı.', not_implemented: '{{method}}: henüz uygulanmadı.',
social_invalid_access_token: 'Bağlayıcının erişim tokenı geçersiz.', social_invalid_access_token: 'Bağlayıcının erişim tokenı geçersiz.',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: '连接器配置错误', invalid_config: '连接器配置错误',
invalid_response: '连接器错误响应', invalid_response: '连接器错误响应',
template_not_found: '无法从连接器配置中找到对应的模板', template_not_found: '无法从连接器配置中找到对应的模板',
template_not_supported: '连接器不支持此模板类型。',
rate_limit_exceeded: '触发速率限制。请稍后再试。', rate_limit_exceeded: '触发速率限制。请稍后再试。',
not_implemented: '方法 {{method}} 尚未实现', not_implemented: '方法 {{method}} 尚未实现',
social_invalid_access_token: '当前连接器的 access_token 无效', social_invalid_access_token: '当前连接器的 access_token 无效',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: '連接器配置錯誤', invalid_config: '連接器配置錯誤',
invalid_response: '連接器錯誤響應', invalid_response: '連接器錯誤響應',
template_not_found: '無法從連接器配置中找到對應的模板', template_not_found: '無法從連接器配置中找到對應的模板',
template_not_supported: '連接器不支援此模板類型。',
rate_limit_exceeded: '觸發速率限制。請稍後再試。', rate_limit_exceeded: '觸發速率限制。請稍後再試。',
not_implemented: '方法 {{method}} 尚未實現', not_implemented: '方法 {{method}} 尚未實現',
social_invalid_access_token: '當前連接器的 access_token 無效', social_invalid_access_token: '當前連接器的 access_token 無效',

View file

@ -10,6 +10,7 @@ const connector = {
invalid_config: '連接器配置錯誤', invalid_config: '連接器配置錯誤',
invalid_response: '連接器錯誤響應', invalid_response: '連接器錯誤響應',
template_not_found: '無法從連接器配置中找到對應的模板', template_not_found: '無法從連接器配置中找到對應的模板',
template_not_supported: '連接器不支援此模板類型',
rate_limit_exceeded: '觸發速率限制。請稍後再試。', rate_limit_exceeded: '觸發速率限制。請稍後再試。',
not_implemented: '方法 {{method}} 尚未實現', not_implemented: '方法 {{method}} 尚未實現',
social_invalid_access_token: '當前連接器的 access_token 無效', social_invalid_access_token: '當前連接器的 access_token 無效',

View file

@ -48,7 +48,10 @@ export enum ConnectorErrorCodes {
InsufficientRequestParameters = 'insufficient_request_parameters', InsufficientRequestParameters = 'insufficient_request_parameters',
InvalidConfig = 'invalid_config', InvalidConfig = 'invalid_config',
InvalidResponse = 'invalid_response', InvalidResponse = 'invalid_response',
/** The template is not found for the given type. */
TemplateNotFound = 'template_not_found', TemplateNotFound = 'template_not_found',
/** The template type is not supported by the connector. */
TemplateNotSupported = 'template_not_supported',
RateLimitExceeded = 'rate_limit_exceeded', RateLimitExceeded = 'rate_limit_exceeded',
NotImplemented = 'not_implemented', NotImplemented = 'not_implemented',
SocialAuthCodeInvalid = 'social_auth_code_invalid', SocialAuthCodeInvalid = 'social_auth_code_invalid',
@ -62,7 +65,7 @@ export class ConnectorError extends Error {
public data: unknown; public data: unknown;
constructor(code: ConnectorErrorCodes, data?: unknown) { constructor(code: ConnectorErrorCodes, data?: unknown) {
const message = typeof data === 'string' ? data : 'Connector error occurred.'; const message = `ConnectorError: ${data ? JSON.stringify(data) : code}`;
super(message); super(message);
this.code = code; this.code = code;
this.data = typeof data === 'string' ? { message: data } : data; this.data = typeof data === 'string' ? { message: data } : data;
@ -233,6 +236,19 @@ export const emailServiceBrandingGuard = z
export type EmailServiceBranding = z.infer<typeof emailServiceBrandingGuard>; export type EmailServiceBranding = z.infer<typeof emailServiceBrandingGuard>;
export type SendMessagePayload = {
to: string;
type: VerificationCodeType;
payload: {
/**
* The dynamic verification code to send.
*
* @example '123456'
*/
code: string;
} & EmailServiceBranding;
};
export const sendMessagePayloadGuard = z.object({ export const sendMessagePayloadGuard = z.object({
to: z.string(), to: z.string(),
type: verificationCodeTypeGuard, type: verificationCodeTypeGuard,
@ -241,9 +257,7 @@ export const sendMessagePayloadGuard = z.object({
code: z.string(), code: z.string(),
}) })
.merge(emailServiceBrandingGuard), .merge(emailServiceBrandingGuard),
}); }) satisfies z.ZodType<SendMessagePayload>;
export type SendMessagePayload = z.infer<typeof sendMessagePayloadGuard>;
export type SendMessageFunction = (data: SendMessagePayload, config?: unknown) => Promise<unknown>; export type SendMessageFunction = (data: SendMessagePayload, config?: unknown) => Promise<unknown>;

View file

@ -1534,6 +1534,85 @@ importers:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.0.2 version: 5.0.2
packages/connectors/connector-mailgun:
dependencies:
'@logto/connector-kit':
specifier: workspace:^1.1.0
version: link:../../toolkit/connector-kit
'@silverhand/essentials':
specifier: ^2.5.0
version: 2.7.0
got:
specifier: ^13.0.0
version: 13.0.0
snakecase-keys:
specifier: ^5.4.4
version: 5.4.4
zod:
specifier: ^3.20.2
version: 3.20.2
devDependencies:
'@jest/types':
specifier: ^29.5.0
version: 29.5.0
'@rollup/plugin-commonjs':
specifier: ^25.0.0
version: 25.0.0(rollup@3.8.0)
'@rollup/plugin-json':
specifier: ^6.0.0
version: 6.0.0(rollup@3.8.0)
'@rollup/plugin-node-resolve':
specifier: ^15.0.1
version: 15.0.1(rollup@3.8.0)
'@rollup/plugin-typescript':
specifier: ^11.0.0
version: 11.0.0(rollup@3.8.0)(typescript@5.0.2)
'@silverhand/eslint-config':
specifier: 4.0.1
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
'@silverhand/ts-config':
specifier: 4.0.0
version: 4.0.0(typescript@5.0.2)
'@types/jest':
specifier: ^29.4.0
version: 29.4.0
'@types/node':
specifier: ^18.11.18
version: 18.11.18
'@types/supertest':
specifier: ^2.0.11
version: 2.0.11
eslint:
specifier: ^8.44.0
version: 8.44.0
jest:
specifier: ^29.5.0
version: 29.5.0(@types/node@18.11.18)
jest-matcher-specific-error:
specifier: ^1.0.0
version: 1.0.0
lint-staged:
specifier: ^13.0.0
version: 13.0.0
nock:
specifier: ^13.2.2
version: 13.3.1
prettier:
specifier: ^3.0.0
version: 3.0.0
rollup:
specifier: ^3.8.0
version: 3.8.0
rollup-plugin-summary:
specifier: ^2.0.0
version: 2.0.0(rollup@3.8.0)
supertest:
specifier: ^6.2.2
version: 6.2.2
typescript:
specifier: ^5.0.0
version: 5.0.2
packages/connectors/connector-mock-email: packages/connectors/connector-mock-email:
dependencies: dependencies:
'@logto/connector-kit': '@logto/connector-kit':
@ -2906,7 +2985,7 @@ importers:
version: 3.0.0 version: 3.0.0
jest: jest:
specifier: ^29.5.0 specifier: ^29.5.0
version: 29.5.0(@types/node@20.4.2)(ts-node@10.9.1) version: 29.5.0(@types/node@18.11.18)(ts-node@10.9.1)
jest-environment-jsdom: jest-environment-jsdom:
specifier: ^29.0.0 specifier: ^29.0.0
version: 29.2.2 version: 29.2.2
@ -2966,7 +3045,7 @@ importers:
version: 2.19.3(react@18.2.0) version: 2.19.3(react@18.2.0)
react-dnd: react-dnd:
specifier: ^16.0.0 specifier: ^16.0.0
version: 16.0.0(@types/node@20.4.2)(@types/react@18.0.31)(react@18.2.0) version: 16.0.0(@types/node@18.11.18)(@types/react@18.0.31)(react@18.2.0)
react-dnd-html5-backend: react-dnd-html5-backend:
specifier: ^16.0.0 specifier: ^16.0.0
version: 16.0.0 version: 16.0.0
@ -3933,7 +4012,7 @@ importers:
version: 3.0.0 version: 3.0.0
jest: jest:
specifier: ^29.5.0 specifier: ^29.5.0
version: 29.5.0(@types/node@20.4.2)(ts-node@10.9.1) version: 29.5.0(@types/node@18.11.18)(ts-node@10.9.1)
jest-environment-jsdom: jest-environment-jsdom:
specifier: ^29.0.0 specifier: ^29.0.0
version: 29.2.2 version: 29.2.2
@ -6528,15 +6607,15 @@ packages:
'@commitlint/execute-rule': 17.4.0 '@commitlint/execute-rule': 17.4.0
'@commitlint/resolve-extends': 17.4.4 '@commitlint/resolve-extends': 17.4.4
'@commitlint/types': 17.4.4 '@commitlint/types': 17.4.4
'@types/node': 20.4.2 '@types/node': 18.11.18
chalk: 4.1.2 chalk: 4.1.2
cosmiconfig: 8.2.0 cosmiconfig: 8.2.0
cosmiconfig-typescript-loader: 4.3.0(@types/node@20.4.2)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.0.2) cosmiconfig-typescript-loader: 4.3.0(@types/node@18.11.18)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.0.2)
lodash.isplainobject: 4.0.6 lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2 lodash.merge: 4.6.2
lodash.uniq: 4.5.0 lodash.uniq: 4.5.0
resolve-from: 5.0.0 resolve-from: 5.0.0
ts-node: 10.9.1(@types/node@20.4.2)(typescript@5.0.2) ts-node: 10.9.1(@types/node@18.11.18)(typescript@5.0.2)
typescript: 5.0.2 typescript: 5.0.2
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/core' - '@swc/core'
@ -6794,6 +6873,48 @@ packages:
slash: 3.0.0 slash: 3.0.0
dev: true dev: true
/@jest/core@29.5.0:
resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
'@jest/console': 29.5.0
'@jest/reporters': 29.5.0
'@jest/test-result': 29.5.0
'@jest/transform': 29.5.0
'@jest/types': 29.5.0
'@types/node': 18.11.18
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.8.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.5.0
jest-config: 29.5.0(@types/node@18.11.18)
jest-haste-map: 29.5.0
jest-message-util: 29.5.0
jest-regex-util: 29.4.3
jest-resolve: 29.5.0
jest-resolve-dependencies: 29.5.0
jest-runner: 29.5.0
jest-runtime: 29.5.0
jest-snapshot: 29.5.0
jest-util: 29.5.0
jest-validate: 29.5.0
jest-watcher: 29.5.0
micromatch: 4.0.5
pretty-format: 29.5.0
slash: 3.0.0
strip-ansi: 6.0.1
transitivePeerDependencies:
- supports-color
- ts-node
dev: true
/@jest/core@29.5.0(ts-node@10.9.1): /@jest/core@29.5.0(ts-node@10.9.1):
resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==} resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -7251,7 +7372,7 @@ packages:
/@logto/js@2.1.1: /@logto/js@2.1.1:
resolution: {integrity: sha512-PHikheavVK+l4ivgtzi14p184hEPgXjqQEAom1Gme1MZoopx+WlwxvHSEQBsmyvVqRtI0oiojhoU5tgYi1FKJw==} resolution: {integrity: sha512-PHikheavVK+l4ivgtzi14p184hEPgXjqQEAom1Gme1MZoopx+WlwxvHSEQBsmyvVqRtI0oiojhoU5tgYi1FKJw==}
dependencies: dependencies:
'@silverhand/essentials': 2.6.2 '@silverhand/essentials': 2.7.0
camelcase-keys: 7.0.2 camelcase-keys: 7.0.2
jose: 4.14.2 jose: 4.14.2
dev: true dev: true
@ -7260,7 +7381,7 @@ packages:
resolution: {integrity: sha512-joSzzAqaRKeEquRenoFrIXXkNxkJci5zSkk4afywz1P8tTcTysnV4eXaBmwXNpmDfQdtHBwRdSACZPLgeF8JiQ==} resolution: {integrity: sha512-joSzzAqaRKeEquRenoFrIXXkNxkJci5zSkk4afywz1P8tTcTysnV4eXaBmwXNpmDfQdtHBwRdSACZPLgeF8JiQ==}
dependencies: dependencies:
'@logto/client': 2.1.0 '@logto/client': 2.1.0
'@silverhand/essentials': 2.6.2 '@silverhand/essentials': 2.7.0
js-base64: 3.7.5 js-base64: 3.7.5
node-fetch: 2.6.7 node-fetch: 2.6.7
transitivePeerDependencies: transitivePeerDependencies:
@ -7273,7 +7394,7 @@ packages:
react: '>=16.8.0 || ^18.0.0' react: '>=16.8.0 || ^18.0.0'
dependencies: dependencies:
'@logto/browser': 2.1.0 '@logto/browser': 2.1.0
'@silverhand/essentials': 2.6.2 '@silverhand/essentials': 2.7.0
react: 18.2.0 react: 18.2.0
dev: true dev: true
@ -8867,11 +8988,6 @@ packages:
resolution: {integrity: sha512-8GgVFAmbo6S0EgsjYXH4aH8a69O7SzEtPFPDpVZmJuGEt8e3ODVx0F2V4rXyC3/SzFbcb2md2gRbA+Z6aTad6g==} resolution: {integrity: sha512-8GgVFAmbo6S0EgsjYXH4aH8a69O7SzEtPFPDpVZmJuGEt8e3ODVx0F2V4rXyC3/SzFbcb2md2gRbA+Z6aTad6g==}
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^7} engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^7}
/@silverhand/essentials@2.6.2:
resolution: {integrity: sha512-1b5u2BGEa14V3o8XzaE7eL+nuwmQe8c1wqSMcGvq+KAusPPZo9tV4glbfF16Xi/ohv37vUpBGJ2DNf4CfuxBLw==}
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
dev: true
/@silverhand/essentials@2.7.0: /@silverhand/essentials@2.7.0:
resolution: {integrity: sha512-F5Qo5ZNnERUURK/9F1ZIi4FBDM22aeD59Zv0VtkgIhUL9tYK9svA2Jz88NNdYBwqCPrh8ExZlpFNi+pNmXKNlQ==} resolution: {integrity: sha512-F5Qo5ZNnERUURK/9F1ZIi4FBDM22aeD59Zv0VtkgIhUL9tYK9svA2Jz88NNdYBwqCPrh8ExZlpFNi+pNmXKNlQ==}
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0} engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
@ -9602,10 +9718,6 @@ packages:
/@types/node@18.11.18: /@types/node@18.11.18:
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
/@types/node@20.4.2:
resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==}
dev: true
/@types/nodemailer@6.4.7: /@types/nodemailer@6.4.7:
resolution: {integrity: sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==} resolution: {integrity: sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==}
dependencies: dependencies:
@ -11144,7 +11256,7 @@ packages:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true dev: true
/cosmiconfig-typescript-loader@4.3.0(@types/node@20.4.2)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.0.2): /cosmiconfig-typescript-loader@4.3.0(@types/node@18.11.18)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.0.2):
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'} engines: {node: '>=12', npm: '>=6'}
peerDependencies: peerDependencies:
@ -11153,9 +11265,9 @@ packages:
ts-node: '>=10' ts-node: '>=10'
typescript: '>=3' typescript: '>=3'
dependencies: dependencies:
'@types/node': 20.4.2 '@types/node': 18.11.18
cosmiconfig: 8.2.0 cosmiconfig: 8.2.0
ts-node: 10.9.1(@types/node@20.4.2)(typescript@5.0.2) ts-node: 10.9.1(@types/node@18.11.18)(typescript@5.0.2)
typescript: 5.0.2 typescript: 5.0.2
dev: true dev: true
@ -11452,6 +11564,17 @@ packages:
/dayjs@1.11.6: /dayjs@1.11.6:
resolution: {integrity: sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==} resolution: {integrity: sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==}
/debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
dev: true
/debug@3.2.7(supports-color@5.5.0): /debug@3.2.7(supports-color@5.5.0):
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies: peerDependencies:
@ -12003,9 +12126,9 @@ packages:
/eslint-import-resolver-node@0.3.7: /eslint-import-resolver-node@0.3.7:
resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
dependencies: dependencies:
debug: 3.2.7(supports-color@5.5.0) debug: 3.2.7
is-core-module: 2.11.0 is-core-module: 2.12.1
resolve: 1.22.1 resolve: 1.22.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -12024,7 +12147,7 @@ packages:
eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.61.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.44.0) eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.61.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.44.0)
get-tsconfig: 4.5.0 get-tsconfig: 4.5.0
globby: 13.1.3 globby: 13.1.3
is-core-module: 2.11.0 is-core-module: 2.12.1
is-glob: 4.0.3 is-glob: 4.0.3
synckit: 0.8.5 synckit: 0.8.5
transitivePeerDependencies: transitivePeerDependencies:
@ -12056,7 +12179,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.61.0(eslint@8.44.0)(typescript@5.0.2) '@typescript-eslint/parser': 5.61.0(eslint@8.44.0)(typescript@5.0.2)
debug: 3.2.7(supports-color@5.5.0) debug: 3.2.7
eslint: 8.44.0 eslint: 8.44.0
eslint-import-resolver-node: 0.3.7 eslint-import-resolver-node: 0.3.7
eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.61.0)(eslint-plugin-import@2.27.5)(eslint@8.44.0) eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.61.0)(eslint-plugin-import@2.27.5)(eslint@8.44.0)
@ -12108,18 +12231,18 @@ packages:
array-includes: 3.1.6 array-includes: 3.1.6
array.prototype.flat: 1.3.1 array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1 array.prototype.flatmap: 1.3.1
debug: 3.2.7(supports-color@5.5.0) debug: 3.2.7
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.44.0 eslint: 8.44.0
eslint-import-resolver-node: 0.3.7 eslint-import-resolver-node: 0.3.7
eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.61.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.44.0) eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.61.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.44.0)
has: 1.0.3 has: 1.0.3
is-core-module: 2.11.0 is-core-module: 2.12.1
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 3.1.2 minimatch: 3.1.2
object.values: 1.1.6 object.values: 1.1.6
resolve: 1.22.1 resolve: 1.22.2
semver: 6.3.0 semver: 6.3.1
tsconfig-paths: 3.14.1 tsconfig-paths: 3.14.1
transitivePeerDependencies: transitivePeerDependencies:
- eslint-import-resolver-typescript - eslint-import-resolver-typescript
@ -12173,8 +12296,8 @@ packages:
eslint-utils: 2.1.0 eslint-utils: 2.1.0
ignore: 5.2.4 ignore: 5.2.4
minimatch: 3.1.2 minimatch: 3.1.2
resolve: 1.22.1 resolve: 1.22.2
semver: 6.3.0 semver: 6.3.1
dev: true dev: true
/eslint-plugin-prettier@5.0.0-alpha.2(eslint-config-prettier@8.8.0)(eslint@8.44.0)(prettier@3.0.0): /eslint-plugin-prettier@5.0.0-alpha.2(eslint-config-prettier@8.8.0)(eslint@8.44.0)(prettier@3.0.0):
@ -13801,12 +13924,6 @@ packages:
ci-info: 3.8.0 ci-info: 3.8.0
dev: true dev: true
/is-core-module@2.11.0:
resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
dependencies:
has: 1.0.3
dev: true
/is-core-module@2.12.1: /is-core-module@2.12.1:
resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==}
dependencies: dependencies:
@ -14150,6 +14267,34 @@ packages:
- supports-color - supports-color
dev: true dev: true
/jest-cli@29.5.0(@types/node@18.11.18):
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
'@jest/core': 29.5.0
'@jest/test-result': 29.5.0
'@jest/types': 29.5.0
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
import-local: 3.1.0
jest-config: 29.5.0(@types/node@18.11.18)
jest-util: 29.5.0
jest-validate: 29.5.0
prompts: 2.4.2
yargs: 17.7.2
transitivePeerDependencies:
- '@types/node'
- supports-color
- ts-node
dev: true
/jest-cli@29.5.0(@types/node@18.11.18)(ts-node@10.9.1): /jest-cli@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -14178,32 +14323,43 @@ packages:
- ts-node - ts-node
dev: true dev: true
/jest-cli@29.5.0(@types/node@20.4.2)(ts-node@10.9.1): /jest-config@29.5.0(@types/node@18.11.18):
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies: peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 '@types/node': '*'
ts-node: '>=9.0.0'
peerDependenciesMeta: peerDependenciesMeta:
node-notifier: '@types/node':
optional: true
ts-node:
optional: true optional: true
dependencies: dependencies:
'@jest/core': 29.5.0(ts-node@10.9.1) '@babel/core': 7.20.2
'@jest/test-result': 29.5.0 '@jest/test-sequencer': 29.5.0
'@jest/types': 29.5.0 '@jest/types': 29.5.0
'@types/node': 18.11.18
babel-jest: 29.5.0(@babel/core@7.20.2)
chalk: 4.1.2 chalk: 4.1.2
exit: 0.1.2 ci-info: 3.8.0
deepmerge: 4.3.1
glob: 7.2.3
graceful-fs: 4.2.11 graceful-fs: 4.2.11
import-local: 3.1.0 jest-circus: 29.5.0
jest-config: 29.5.0(@types/node@20.4.2)(ts-node@10.9.1) jest-environment-node: 29.5.0
jest-get-type: 29.4.3
jest-regex-util: 29.4.3
jest-resolve: 29.5.0
jest-runner: 29.5.0
jest-util: 29.5.0 jest-util: 29.5.0
jest-validate: 29.5.0 jest-validate: 29.5.0
prompts: 2.4.2 micromatch: 4.0.5
yargs: 17.7.2 parse-json: 5.2.0
pretty-format: 29.5.0
slash: 3.0.0
strip-json-comments: 3.1.1
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node'
- supports-color - supports-color
- ts-node
dev: true dev: true
/jest-config@29.5.0(@types/node@18.11.18)(ts-node@10.9.1): /jest-config@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
@ -14241,47 +14397,7 @@ packages:
pretty-format: 29.5.0 pretty-format: 29.5.0
slash: 3.0.0 slash: 3.0.0
strip-json-comments: 3.1.1 strip-json-comments: 3.1.1
ts-node: 10.9.1(@types/node@20.4.2)(typescript@5.0.2) ts-node: 10.9.1(@types/node@18.11.18)(typescript@5.0.2)
transitivePeerDependencies:
- supports-color
dev: true
/jest-config@29.5.0(@types/node@20.4.2)(ts-node@10.9.1):
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
'@types/node': '*'
ts-node: '>=9.0.0'
peerDependenciesMeta:
'@types/node':
optional: true
ts-node:
optional: true
dependencies:
'@babel/core': 7.20.2
'@jest/test-sequencer': 29.5.0
'@jest/types': 29.5.0
'@types/node': 20.4.2
babel-jest: 29.5.0(@babel/core@7.20.2)
chalk: 4.1.2
ci-info: 3.8.0
deepmerge: 4.3.1
glob: 7.2.3
graceful-fs: 4.2.11
jest-circus: 29.5.0
jest-environment-node: 29.5.0
jest-get-type: 29.4.3
jest-regex-util: 29.4.3
jest-resolve: 29.5.0
jest-runner: 29.5.0
jest-util: 29.5.0
jest-validate: 29.5.0
micromatch: 4.0.5
parse-json: 5.2.0
pretty-format: 29.5.0
slash: 3.0.0
strip-json-comments: 3.1.1
ts-node: 10.9.1(@types/node@20.4.2)(typescript@5.0.2)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -14640,7 +14756,7 @@ packages:
jest: ^28.1.0 || ^29.1.2 jest: ^28.1.0 || ^29.1.2
react: ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0
dependencies: dependencies:
jest: 29.5.0(@types/node@20.4.2)(ts-node@10.9.1) jest: 29.5.0(@types/node@18.11.18)(ts-node@10.9.1)
react: 18.2.0 react: 18.2.0
dev: true dev: true
@ -14704,6 +14820,26 @@ packages:
supports-color: 8.1.1 supports-color: 8.1.1
dev: true dev: true
/jest@29.5.0(@types/node@18.11.18):
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
'@jest/core': 29.5.0
'@jest/types': 29.5.0
import-local: 3.1.0
jest-cli: 29.5.0(@types/node@18.11.18)
transitivePeerDependencies:
- '@types/node'
- supports-color
- ts-node
dev: true
/jest@29.5.0(@types/node@18.11.18)(ts-node@10.9.1): /jest@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -14724,26 +14860,6 @@ packages:
- ts-node - ts-node
dev: true dev: true
/jest@29.5.0(@types/node@20.4.2)(ts-node@10.9.1):
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
'@jest/core': 29.5.0(ts-node@10.9.1)
'@jest/types': 29.5.0
import-local: 3.1.0
jest-cli: 29.5.0(@types/node@20.4.2)(ts-node@10.9.1)
transitivePeerDependencies:
- '@types/node'
- supports-color
- ts-node
dev: true
/joi@17.7.0: /joi@17.7.0:
resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==} resolution: {integrity: sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==}
dependencies: dependencies:
@ -15538,7 +15654,7 @@ packages:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'} engines: {node: '>=12'}
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.4.14 '@jridgewell/sourcemap-codec': 1.4.15
dev: true dev: true
/make-dir@3.1.0: /make-dir@3.1.0:
@ -17732,7 +17848,7 @@ packages:
dnd-core: 16.0.0 dnd-core: 16.0.0
dev: true dev: true
/react-dnd@16.0.0(@types/node@20.4.2)(@types/react@18.0.31)(react@18.2.0): /react-dnd@16.0.0(@types/node@18.11.18)(@types/react@18.0.31)(react@18.2.0):
resolution: {integrity: sha512-RCoeWRWhuwSoqdLaJV8N/weARLyXqsf43OC3QiBWPORIIGGovF/EqI8ckf14ca3bl6oZNI/igtxX49+IDmNDeQ==} resolution: {integrity: sha512-RCoeWRWhuwSoqdLaJV8N/weARLyXqsf43OC3QiBWPORIIGGovF/EqI8ckf14ca3bl6oZNI/igtxX49+IDmNDeQ==}
peerDependencies: peerDependencies:
'@types/hoist-non-react-statics': '>= 3.3.1' '@types/hoist-non-react-statics': '>= 3.3.1'
@ -17749,7 +17865,7 @@ packages:
dependencies: dependencies:
'@react-dnd/invariant': 4.0.0 '@react-dnd/invariant': 4.0.0
'@react-dnd/shallowequal': 4.0.0 '@react-dnd/shallowequal': 4.0.0
'@types/node': 20.4.2 '@types/node': 18.11.18
'@types/react': 18.0.31 '@types/react': 18.0.31
dnd-core: 16.0.0 dnd-core: 16.0.0
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
@ -19819,7 +19935,7 @@ packages:
resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==}
dev: true dev: true
/ts-node@10.9.1(@types/node@20.4.2)(typescript@5.0.2): /ts-node@10.9.1(@types/node@18.11.18)(typescript@5.0.2):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -19838,7 +19954,7 @@ packages:
'@tsconfig/node12': 1.0.11 '@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3 '@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4 '@tsconfig/node16': 1.0.4
'@types/node': 20.4.2 '@types/node': 18.11.18
acorn: 8.10.0 acorn: 8.10.0
acorn-walk: 8.2.0 acorn-walk: 8.2.0
arg: 4.1.3 arg: 4.1.3