diff --git a/packages/connector-alipay/src/README.md b/packages/connector-alipay/README.md similarity index 100% rename from packages/connector-alipay/src/README.md rename to packages/connector-alipay/README.md diff --git a/packages/connector-alipay/src/config-template.md b/packages/connector-alipay/docs/config-template.md similarity index 88% rename from packages/connector-alipay/src/config-template.md rename to packages/connector-alipay/docs/config-template.md index 8ccabf50b..4b4c74fc5 100644 --- a/packages/connector-alipay/src/config-template.md +++ b/packages/connector-alipay/docs/config-template.md @@ -1,5 +1,7 @@ +```json { "appId": "", "signType": "", "privateKey": "" } +``` diff --git a/packages/connector-alipay/package.json b/packages/connector-alipay/package.json index bd9356807..91aec254c 100644 --- a/packages/connector-alipay/package.json +++ b/packages/connector-alipay/package.json @@ -2,18 +2,22 @@ "name": "@logto/connector-alipay", "version": "0.1.0", "description": "Alipay implementation.", - "main": "lib/index.js", - "author": "Logto Team", + "main": "./lib/index.js", + "exports": "./lib/index.js", + "author": "Silverhand Inc. ", "license": "MPL-2.0", + "files": [ + "lib", + "docs" + ], "private": true, "scripts": { "preinstall": "npx only-allow pnpm", "precommit": "lint-staged", - "copyfiles": "copyfiles -u 1 src/**/*.md lib", - "build": "rm -rf lib/ && tsc -p tsconfig.build.json && pnpm run copyfiles", + "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/ && pnpm run copyfiles && tsc-watch -p tsconfig.build.json --preserveWatchOutput --onSuccess \"node ./lib/index.js\"", + "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" @@ -39,7 +43,6 @@ "@types/lodash.pick": "^4.4.6", "@types/node": "^16.3.1", "@types/supertest": "^2.0.11", - "copyfiles": "^2.4.1", "eslint": "^8.10.0", "jest": "^27.5.1", "jest-matcher-specific-error": "^1.0.0", diff --git a/packages/connector-alipay/src/constant.ts b/packages/connector-alipay/src/constant.ts index 591e76918..326c638f1 100644 --- a/packages/connector-alipay/src/constant.ts +++ b/packages/connector-alipay/src/constant.ts @@ -17,8 +17,8 @@ export const alipaySigningAlgorithms = ['RSA', 'RSA2'] as const; // eslint-disable-next-line unicorn/prefer-module const currentPath = __dirname; -const pathToReadmeFile = path.join(currentPath, 'README.md'); -const pathToConfigTemplate = path.join(currentPath, 'config-template.md'); +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.'; @@ -29,7 +29,6 @@ export const defaultMetadata: ConnectorMetadata = { en: 'Sign In with Alipay', 'zh-CN': '支付宝登录', }, - // TODO: add the real logo URL (LOG-1823) logo: './logo.png', description: { en: 'Sign In with Alipay', diff --git a/packages/connector-alipay/src/index.test.ts b/packages/connector-alipay/src/index.test.ts index 4afdeaa25..841631d2d 100644 --- a/packages/connector-alipay/src/index.test.ts +++ b/packages/connector-alipay/src/index.test.ts @@ -74,7 +74,6 @@ describe('getAccessToken', () => { }); const response = await alipayMethods.getAccessToken('code'); - console.log(response); const { accessToken } = response; expect(accessToken).toEqual('access_token'); }); diff --git a/packages/connector-aliyun-dm/README.md b/packages/connector-aliyun-dm/README.md new file mode 100644 index 000000000..339f0b4de --- /dev/null +++ b/packages/connector-aliyun-dm/README.md @@ -0,0 +1,2 @@ +### Aliyun DM README +placeholder diff --git a/packages/connector-aliyun-dm/docs/config-template.md b/packages/connector-aliyun-dm/docs/config-template.md new file mode 100644 index 000000000..e561f6b27 --- /dev/null +++ b/packages/connector-aliyun-dm/docs/config-template.md @@ -0,0 +1,25 @@ +```json +{ + "accessKeyId": "", + "accessKeySecret": "", + "accountName": "", + "fromAlias": "", + "templates": [ + { + "usageType": "SIGN_IN", + "subject": "", + "content": "" + }, + { + "usageType": "REGISTER", + "subject": "", + "content": "" + }, + { + "usageType": "TEST", + "subject": "", + "content": "" + } + ] +} +``` diff --git a/packages/connector-aliyun-dm/jest.config.ts b/packages/connector-aliyun-dm/jest.config.ts new file mode 100644 index 000000000..d8ceb361c --- /dev/null +++ b/packages/connector-aliyun-dm/jest.config.ts @@ -0,0 +1,8 @@ +import { Config, merge } from '@logto/jest-config'; + +const config: Config.InitialOptions = merge({ + testEnvironment: 'node', + setupFilesAfterEnv: ['jest-matcher-specific-error'], +}); + +export default config; diff --git a/packages/connector-aliyun-dm/package.json b/packages/connector-aliyun-dm/package.json new file mode 100644 index 000000000..7672aea38 --- /dev/null +++ b/packages/connector-aliyun-dm/package.json @@ -0,0 +1,55 @@ +{ + "name": "@logto/connector-aliyun-dm", + "version": "0.1.0", + "description": "Aliyun DM connector implementation.", + "main": "./lib/index.js", + "exports": "./lib/index.js", + "author": "Silverhand Inc. ", + "license": "MPL-2.0", + "files": [ + "lib", + "docs" + ], + "private": true, + "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/jest-config": "^0.1.0", + "@logto/shared": "^0.1.0", + "@silverhand/essentials": "^1.1.0", + "got": "^11.8.2", + "zod": "^3.14.3" + }, + "devDependencies": { + "@jest/types": "^27.5.1", + "@silverhand/eslint-config": "^0.10.2", + "@silverhand/ts-config": "^0.10.2", + "@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": "^11.1.1", + "prettier": "^2.3.2", + "ts-jest": "^27.1.1", + "tsc-watch": "^4.4.0", + "typescript": "^4.6.2" + }, + "engines": { + "node": "^16.0.0" + }, + "eslintConfig": { + "extends": "@silverhand" + }, + "prettier": "@silverhand/eslint-config/.prettierrc" +} diff --git a/packages/connector-aliyun-dm/src/constant.ts b/packages/connector-aliyun-dm/src/constant.ts new file mode 100644 index 000000000..f6d7752aa --- /dev/null +++ b/packages/connector-aliyun-dm/src/constant.ts @@ -0,0 +1,53 @@ +import path from 'path'; + +import { ConnectorType, ConnectorMetadata } from '@logto/connector-types'; +import { getFileContents } from '@logto/shared'; + +/** + * @doc https://help.aliyun.com/document_detail/29444.html + */ +export interface SingleSendMail { + AccountName: string; + AddressType: '0' | '1'; + ClickTrace?: '0' | '1'; + FromAlias?: string; + HtmlBody?: string; + ReplyToAddress: 'true' | 'false'; + Subject: string; + TagName?: string; + TextBody?: string; + ToAddress: string; +} + +export const endpoint = 'https://dm.aliyuncs.com/'; + +export const staticConfigs = { + Format: 'json', + SignatureMethod: 'HMAC-SHA1', + SignatureVersion: '1.0', + Version: '2015-11-23', +}; + +// 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 = { + id: 'aliyun-dm', + type: ConnectorType.Email, + name: { + en: 'Aliyun Direct Mail', + 'zh-CN': '阿里云邮件推送', + }, + logo: './logo.png', + description: { + en: 'A simple and efficient email service to help you send transactional notifications and batch email.', + 'zh-CN': + '邮件推送(DirectMail)是款简单高效的电子邮件群发服务,构建在阿里云基础之上,帮您快速、精准地实现事务邮件、通知邮件和批量邮件的发送。', + }, + readme: getFileContents(pathToReadmeFile, readmeContentFallback), + configTemplate: getFileContents(pathToConfigTemplate, configTemplateFallback), +}; diff --git a/packages/connector-aliyun-dm/src/index.test.ts b/packages/connector-aliyun-dm/src/index.test.ts new file mode 100644 index 000000000..974e87178 --- /dev/null +++ b/packages/connector-aliyun-dm/src/index.test.ts @@ -0,0 +1,58 @@ +import { GetConnectorConfig } from '@logto/connector-types'; + +import { AliyunDmConnector, AliyunDmConfig } from '.'; +import { mockedConfig } from './mock'; +import { singleSendMail } from './single-send-mail'; + +const getConnectorConfig = jest.fn() as GetConnectorConfig; + +const aliyunDmMethods = new AliyunDmConnector(getConnectorConfig); + +jest.mock('./single-send-mail'); + +beforeAll(() => { + jest.spyOn(aliyunDmMethods, 'getConfig').mockResolvedValue(mockedConfig); +}); + +describe('validateConfig()', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should pass on valid config', async () => { + await expect( + aliyunDmMethods.validateConfig({ + accessKeyId: 'accessKeyId', + accessKeySecret: 'accessKeySecret', + accountName: 'accountName', + templates: [], + }) + ).resolves.not.toThrow(); + }); + + it('throws if config is invalid', async () => { + await expect(aliyunDmMethods.validateConfig({})).rejects.toThrow(); + }); +}); + +describe('sendMessage()', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should call singleSendMail() and replace code in content', async () => { + await aliyunDmMethods.sendMessage('to@email.com', 'SignIn', { code: '1234' }); + expect(singleSendMail).toHaveBeenCalledWith( + expect.objectContaining({ + HtmlBody: 'Your code is 1234, 1234 is your code', + }), + expect.anything() + ); + }); + + it('throws if template is missing', async () => { + await expect( + aliyunDmMethods.sendMessage('to@email.com', 'Register', { code: '1234' }) + ).rejects.toThrow(); + }); +}); diff --git a/packages/connector-aliyun-dm/src/index.ts b/packages/connector-aliyun-dm/src/index.ts new file mode 100644 index 000000000..3af1dc5a4 --- /dev/null +++ b/packages/connector-aliyun-dm/src/index.ts @@ -0,0 +1,90 @@ +import { + ConnectorError, + ConnectorErrorCodes, + ConnectorMetadata, + EmailSendMessageFunction, + ValidateConfig, + EmailConnector, + GetConnectorConfig, +} from '@logto/connector-types'; +import { assert } from '@silverhand/essentials'; +import { Response } from 'got'; +import { z } from 'zod'; + +import { defaultMetadata } from './constant'; +import { singleSendMail } from './single-send-mail'; +import { SendEmailResponse } from './utils'; + +/** + * 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(), + subject: z.string(), + content: z.string(), // With variable {{code}}, support HTML +}); + +const configGuard = z.object({ + accessKeyId: z.string(), + accessKeySecret: z.string(), + accountName: z.string(), + fromAlias: z.string().optional(), + templates: z.array(templateGuard), +}); + +export type AliyunDmConfig = z.infer; + +export class AliyunDmConnector implements EmailConnector { + public metadata: ConnectorMetadata = defaultMetadata; + + public readonly getConfig: GetConnectorConfig; + + constructor(getConnectorConfig: GetConnectorConfig) { + this.getConfig = getConnectorConfig; + } + + public validateConfig: ValidateConfig = async (config: unknown) => { + const result = configGuard.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.id); + await this.validateConfig(config); + const { accessKeyId, accessKeySecret, accountName, fromAlias, templates } = config; + const template = templates.find((template) => template.usageType === type); + + assert( + template, + new ConnectorError( + ConnectorErrorCodes.TemplateNotFound, + `Cannot find template for type: ${type}` + ) + ); + + return singleSendMail( + { + AccessKeyId: accessKeyId, + AccountName: accountName, + ReplyToAddress: 'false', + AddressType: '1', + ToAddress: address, + FromAlias: fromAlias, + Subject: template.subject, + HtmlBody: + typeof data.code === 'string' + ? template.content.replace(/{{code}}/g, data.code) + : template.content, + }, + accessKeySecret + ); + }; +} diff --git a/packages/connector-aliyun-dm/src/mock.ts b/packages/connector-aliyun-dm/src/mock.ts new file mode 100644 index 000000000..82b93aefa --- /dev/null +++ b/packages/connector-aliyun-dm/src/mock.ts @@ -0,0 +1,29 @@ +export const mockedParameters = { + AccessKeyId: 'testid', + AccountName: "", + Action: 'SingleSendMail', + AddressType: '1', + Format: 'XML', + HtmlBody: '4', + RegionId: 'cn-hangzhou', + ReplyToAddress: 'true', + SignatureMethod: 'HMAC-SHA1', + SignatureVersion: '1.0', + Subject: '3', + TagName: '2', + ToAddress: '1@test.com', + Version: '2015-11-23', +}; + +export const mockedConfig = { + accessKeyId: 'accessKeyId', + accessKeySecret: 'accessKeySecret', + accountName: 'accountName', + templates: [ + { + usageType: 'SignIn', + content: 'Your code is {{code}}, {{code}} is your code', + subject: 'subject', + }, + ], +}; diff --git a/packages/connector-aliyun-dm/src/single-send-mail.test.ts b/packages/connector-aliyun-dm/src/single-send-mail.test.ts new file mode 100644 index 000000000..46ea1e74a --- /dev/null +++ b/packages/connector-aliyun-dm/src/single-send-mail.test.ts @@ -0,0 +1,26 @@ +import { singleSendMail } from './single-send-mail'; +import { request } from './utils'; + +jest.mock('./utils'); + +describe('singleSendMail', () => { + it('should call request with action SingleSendMail', async () => { + await singleSendMail( + { + AccessKeyId: '', + AccountName: 'noreply@example.com', + AddressType: '1', + FromAlias: 'CompanyName', + HtmlBody: 'test from logto', + ReplyToAddress: 'false', + Subject: 'test', + ToAddress: 'user@example.com', + }, + '' + ); + const calledData = (request as jest.MockedFunction).mock.calls[0]; + expect(calledData).not.toBeUndefined(); + const payload = calledData?.[1]; + expect(payload).toHaveProperty('Action', 'SingleSendMail'); + }); +}); diff --git a/packages/connector-aliyun-dm/src/single-send-mail.ts b/packages/connector-aliyun-dm/src/single-send-mail.ts new file mode 100644 index 000000000..a002f4546 --- /dev/null +++ b/packages/connector-aliyun-dm/src/single-send-mail.ts @@ -0,0 +1,18 @@ +import { Response } from 'got'; + +import { SingleSendMail, endpoint, staticConfigs } from './constant'; +import { PublicParameters, request, SendEmailResponse } from './utils'; + +/** + * @doc https://help.aliyun.com/document_detail/29444.html + */ +export const singleSendMail = async ( + parameters: PublicParameters & SingleSendMail, + accessKeySecret: string +): Promise> => { + return request( + endpoint, + { Action: 'SingleSendMail', ...staticConfigs, ...parameters }, + accessKeySecret + ); +}; diff --git a/packages/connector-aliyun-dm/src/utils.test.ts b/packages/connector-aliyun-dm/src/utils.test.ts new file mode 100644 index 000000000..ec1a40a62 --- /dev/null +++ b/packages/connector-aliyun-dm/src/utils.test.ts @@ -0,0 +1,32 @@ +import got from 'got'; + +import { mockedParameters } from './mock'; +import { getSignature, request } from './utils'; + +jest.mock('got'); + +describe('getSignature', () => { + it('should get valid signature', () => { + const parameters = { + ...mockedParameters, + SignatureNonce: 'c1b2c332-4cfb-4a0f-b8cc-ebe622aa0a5c', + Timestamp: '2016-10-20T06:27:56Z', + }; + const signature = getSignature(parameters, 'testsecret', 'POST'); + expect(signature).toEqual('llJfXJjBW3OacrVgxxsITgYaYm0='); + }); +}); + +describe('request', () => { + it('should call axios.post with extended params', async () => { + const parameters = mockedParameters; + await request('http://test.endpoint.com', parameters, 'testsecret'); + const calledData = (got.post as jest.MockedFunction).mock.calls[0]; + expect(calledData).not.toBeUndefined(); + const payload = calledData?.[0].form as URLSearchParams; + expect(payload.get('AccessKeyId')).toEqual('testid'); + expect(payload.get('Timestamp')).not.toBeNull(); + expect(payload.get('SignatureNonce')).not.toBeNull(); + expect(payload.get('Signature')).not.toBeNull(); + }); +}); diff --git a/packages/connector-aliyun-dm/src/utils.ts b/packages/connector-aliyun-dm/src/utils.ts new file mode 100644 index 000000000..62d32b488 --- /dev/null +++ b/packages/connector-aliyun-dm/src/utils.ts @@ -0,0 +1,79 @@ +import { createHmac } from 'crypto'; + +import got from 'got'; + +export type { Response } from 'got'; +export type SendEmailResponse = { EnvId: string; RequestId: string }; + +// Aliyun has special escape rules. +// https://help.aliyun.com/document_detail/29442.html +const escaper = (string_: string) => + encodeURIComponent(string_) + .replace(/\*/g, '%2A') + .replace(/'/g, '%27') + .replace(/!/g, '%21') + .replace(/"/g, '%22') + .replace(/\(/g, '%28') + .replace(/\)/g, '%29') + .replace(/\+/, '%2B'); + +export const getSignature = ( + parameters: Record, + secret: string, + method: string +) => { + const canonicalizedQuery = Object.keys(parameters) + .map((key) => { + const value = parameters[key]; + + return value === undefined ? '' : `${escaper(key)}=${escaper(value)}`; + }) + .filter(Boolean) + .slice() + .sort() + .join('&'); + + const stringToSign = `${method.toUpperCase()}&${escaper('/')}&${escaper(canonicalizedQuery)}`; + + return createHmac('sha1', `${secret}&`).update(stringToSign).digest('base64'); +}; + +export interface PublicParameters { + AccessKeyId: string; + Format?: string; // 'json' or 'xml', default: 'json' + RegionId?: string; // 'cn-hangzhou' | 'ap-southeast-1' | 'ap-southeast-2' + Signature?: string; + SignatureMethod?: string; + SignatureNonce?: string; + SignatureVersion?: string; + Timestamp?: string; + Version?: string; +} + +export const request = async ( + url: string, + parameters: PublicParameters & Record, + accessKeySecret: string +) => { + const finalParameters: Record = { + ...parameters, + SignatureNonce: String(Math.random()), + Timestamp: new Date().toISOString(), + }; + const signature = getSignature(finalParameters, accessKeySecret, 'POST'); + + const payload = new URLSearchParams(); + + for (const [key, value] of Object.entries(finalParameters)) { + payload.append(key, value); + } + payload.append('Signature', signature); + + return got.post({ + url, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + form: payload, + }); +}; diff --git a/packages/connector-aliyun-dm/tsconfig.base.json b/packages/connector-aliyun-dm/tsconfig.base.json new file mode 100644 index 000000000..848a915f7 --- /dev/null +++ b/packages/connector-aliyun-dm/tsconfig.base.json @@ -0,0 +1,10 @@ +{ + "extends": "@silverhand/ts-config/tsconfig.base", + "compilerOptions": { + "outDir": "lib", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + } +} diff --git a/packages/connector-aliyun-dm/tsconfig.build.json b/packages/connector-aliyun-dm/tsconfig.build.json new file mode 100644 index 000000000..d42923dd3 --- /dev/null +++ b/packages/connector-aliyun-dm/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.base", + "include": ["src"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/connector-aliyun-dm/tsconfig.json b/packages/connector-aliyun-dm/tsconfig.json new file mode 100644 index 000000000..20354364a --- /dev/null +++ b/packages/connector-aliyun-dm/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-aliyun-dm/tsconfig.test.json b/packages/connector-aliyun-dm/tsconfig.test.json new file mode 100644 index 000000000..98c16f367 --- /dev/null +++ b/packages/connector-aliyun-dm/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 b5ce77494..8f488ea02 100644 --- a/packages/connector-types/src/index.ts +++ b/packages/connector-types/src/index.ts @@ -50,17 +50,15 @@ export type EmailMessageTypes = { type SmsMessageTypes = EmailMessageTypes; -export type SendEmailResponse = { EnvId: string; RequestId: string }; - export type SendSmsResponse = { BizId: string; Code: string; Message: string; RequestId: string }; -export type EmailSendMessageFunction> = ( +export type EmailSendMessageFunction = ( address: string, type: keyof EmailMessageTypes, payload: EmailMessageTypes[typeof type] ) => Promise; -export type SmsSendMessageFunction> = ( +export type SmsSendMessageFunction = ( phone: string, type: keyof SmsMessageTypes, payload: SmsMessageTypes[typeof type] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 376648738..2fc979409 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,7 +32,6 @@ importers: '@types/lodash.pick': ^4.4.6 '@types/node': ^16.3.1 '@types/supertest': ^2.0.11 - copyfiles: ^2.4.1 dayjs: ^1.10.5 eslint: ^8.10.0 got: ^11.8.2 @@ -69,7 +68,6 @@ importers: '@types/lodash.pick': 4.4.6 '@types/node': 16.11.12 '@types/supertest': 2.0.11 - copyfiles: 2.4.1 eslint: 8.10.0 jest: 27.5.1 jest-matcher-specific-error: 1.0.0 @@ -81,6 +79,49 @@ importers: tsc-watch: 4.5.0_typescript@4.6.3 typescript: 4.6.3 + packages/connector-aliyun-dm: + specifiers: + '@jest/types': ^27.5.1 + '@logto/connector-types': ^0.1.0 + '@logto/jest-config': ^0.1.0 + '@logto/shared': ^0.1.0 + '@silverhand/eslint-config': ^0.10.2 + '@silverhand/essentials': ^1.1.0 + '@silverhand/ts-config': ^0.10.2 + '@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: ^11.1.1 + prettier: ^2.3.2 + ts-jest: ^27.1.1 + tsc-watch: ^4.4.0 + typescript: ^4.6.2 + zod: ^3.14.3 + dependencies: + '@logto/connector-types': link:../connector-types + '@logto/jest-config': link:../jest-config + '@logto/shared': link:../shared + '@silverhand/essentials': 1.1.7 + got: 11.8.3 + zod: 3.14.3 + devDependencies: + '@jest/types': 27.5.1 + '@silverhand/eslint-config': 0.10.2_bbe1a6794670f389df81805f22999709 + '@silverhand/ts-config': 0.10.2_typescript@4.6.3 + '@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: 11.2.6 + prettier: 2.5.1 + ts-jest: 27.1.1_9985e1834e803358b7be1e6ce5ca0eea + tsc-watch: 4.6.2_typescript@4.6.3 + typescript: 4.6.3 + packages/connector-types: specifiers: '@jest/types': ^27.5.1 @@ -19021,6 +19062,21 @@ packages: typescript: 4.6.3 dev: true + /tsc-watch/4.6.2_typescript@4.6.3: + resolution: {integrity: sha512-eHWzZGkPmzXVGQKbqQgf3BFpGiZZw1jQ29ZOJeaSe8JfyUvphbd221NfXmmsJUGGPGA/nnaSS01tXipUcyxAxg==} + 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.3 + dev: true + /tsc-watch/5.0.3_typescript@4.6.2: resolution: {integrity: sha512-Hz2UawwELMSLOf0xHvAFc7anLeMw62cMVXr1flYmhRuOhOyOljwmb1l/O60ZwRyy1k7N1iC1mrn1QYM2zITfuw==} engines: {node: '>=8.17.0'}