diff --git a/.changeset/itchy-impalas-remain.md b/.changeset/itchy-impalas-remain.md new file mode 100644 index 000000000..e980aa1d9 --- /dev/null +++ b/.changeset/itchy-impalas-remain.md @@ -0,0 +1,5 @@ +--- +"@logto/connector-line": minor +--- + +add Line social connector diff --git a/packages/connectors/connector-line/README.md b/packages/connectors/connector-line/README.md new file mode 100644 index 000000000..f3df59762 --- /dev/null +++ b/packages/connectors/connector-line/README.md @@ -0,0 +1,57 @@ +# Line connector + +The official Logto connector for Line social sign-in. + +**Table of contents** +- [Line connector](#line-connector) + - [Get started](#get-started) + - [Setup a Line channel](#setup-a-line-channel) + - [Configure your connector](#configure-your-connector) + - [Config types](#config-types) + - [Test Line connector](#test-line-connector) + - [Reference](#reference) + +## Get started + +The Line connector enables end-users to sign in to your application using their own Line accounts via the Line OAuth 2.0 authentication protocol. + +## Setup a Line channel + +Go to the [Line Developers](https://developers.line.biz/console/) and sign in with your Line business account. If you don’t have an account, you can register for one. + +Then, go to [this link](https://developers.line.biz/console/register/line-login/provider/) to create a channel. + +**Step 1:** Fill in the Channel Details. + +Complete the form and create the channel. + +**Step 2:** Setup callback URLs. + +Go to channel details page and find "LINE login" tab and edit the "Callback URL" field. + +In our case, this will be `${your_logto_endpoint}/callback/${connector_id}`. e.g. `https://foo.logto.app/callback/${connector_id}`. The `connector_id` can be found on the top bar of the Logto Admin Console connector details page. + +## Configure your connector + +In your Logto connector configuration, fill out the following fields with the values obtained from your App's "Auth" tab, "Application credentials" section: + +- **clientId:** Your App's Channel ID. +- **clientSecret:** Your App's Channel Secret. + +`scope` is a space-delimited list of OIDC scopes. If not provided, the default scope is `openid profile`. + +### Config types + +| Name | Type | +| ------------ | ------ | +| clientId | string | +| clientSecret | string | +| scope | string | + +## Test Line connector + +That's it! The Line connector should now be available for end-users to sign in with their Line accounts. Don't forget to [Enable the connector in the sign-in experience](https://docs.logto.io/docs/recipes/configure-connectors/social-connector/enable-social-sign-in/). + +## Reference + +- [Line Developer Documentation](https://developers.line.biz/en/docs/line-login/overview/) diff --git a/packages/connectors/connector-line/logo.svg b/packages/connectors/connector-line/logo.svg new file mode 100644 index 000000000..5de90514f --- /dev/null +++ b/packages/connectors/connector-line/logo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/connectors/connector-line/package.json b/packages/connectors/connector-line/package.json new file mode 100644 index 000000000..deefac363 --- /dev/null +++ b/packages/connectors/connector-line/package.json @@ -0,0 +1,70 @@ +{ + "name": "@logto/connector-line", + "version": "0.0.0", + "description": "Line web connector implementation.", + "author": "Silverhand Inc. ", + "dependencies": { + "@logto/connector-kit": "workspace:^4.0.0", + "@silverhand/essentials": "^2.9.1", + "ky": "^1.2.3", + "query-string": "^9.0.0", + "snakecase-keys": "^8.0.1", + "zod": "^3.23.8" + }, + "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", + "check": "tsc --noEmit", + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint --ext .ts src", + "lint:report": "pnpm lint --format json --output-file report.json", + "test": "vitest src", + "test:ci": "pnpm run test --silent --coverage", + "prepublishOnly": "pnpm build" + }, + "engines": { + "node": "^20.9.0" + }, + "eslintConfig": { + "extends": "@silverhand", + "settings": { + "import/core-modules": [ + "@silverhand/essentials", + "got", + "nock", + "snakecase-keys", + "zod" + ] + } + }, + "prettier": "@silverhand/eslint-config/.prettierrc", + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@silverhand/eslint-config": "6.0.1", + "@silverhand/ts-config": "6.0.0", + "@types/node": "^20.11.20", + "@types/supertest": "^6.0.2", + "@vitest/coverage-v8": "^2.1.8", + "eslint": "^8.56.0", + "lint-staged": "^15.0.2", + "nock": "14.0.0-beta.15", + "prettier": "^3.0.0", + "supertest": "^7.0.0", + "tsup": "^8.3.0", + "typescript": "^5.5.3", + "vitest": "^2.1.8" + } +} diff --git a/packages/connectors/connector-line/src/constant.ts b/packages/connectors/connector-line/src/constant.ts new file mode 100644 index 000000000..35d727c47 --- /dev/null +++ b/packages/connectors/connector-line/src/constant.ts @@ -0,0 +1,54 @@ +import type { ConnectorMetadata } from '@logto/connector-kit'; +import { ConnectorPlatform, ConnectorConfigFormItemType } from '@logto/connector-kit'; + +// See https://developers.line.biz/en/docs/line-login/overview/ +export const authorizationEndpoint = 'https://access.line.me/oauth2/v2.1/authorize'; +export const defaultScope = 'openid profile'; +export const accessTokenEndpoint = 'https://api.line.me/oauth2/v2.1/token'; +export const userInfoEndpoint = 'https://api.line.me/v2/profile'; + +export const defaultMetadata: ConnectorMetadata = { + id: 'line-universal', + target: 'line', + platform: ConnectorPlatform.Universal, + name: { + en: 'Line', + 'zh-CN': 'Line', + 'tr-TR': 'Line', + ko: 'Line', + }, + logo: './logo.svg', + logoDark: null, + description: { + en: 'Line is a social media platform for sharing information and connecting with friends.', + 'zh-CN': 'Line是一个分享信息和与朋友连接的社交媒体平台。', + 'tr-TR': + 'Line, bilgi paylaşma ve arkadaşlarınızla bağlantı kurma için bir sosyal medya platformudur.', + ko: 'Line은 정보 공유와 친구들과의 연결을 위한 소셜 미디어 플랫폼입니다.', + }, + readme: './README.md', + formItems: [ + { + key: 'clientId', + type: ConnectorConfigFormItemType.Text, + label: 'Client ID (Channel ID)', + required: true, + }, + { + key: 'clientSecret', + type: ConnectorConfigFormItemType.Text, + label: 'Client Secret (Channel Secret)', + required: true, + }, + { + key: 'scope', + type: ConnectorConfigFormItemType.Text, + label: 'Scope', + required: false, + description: + "The `scope` determines permissions granted by the user's authorization. If you are not sure what to enter, do not worry, just leave it blank.", + }, + ], +}; + +export const defaultTimeout = 5000; diff --git a/packages/connectors/connector-line/src/index.test.ts b/packages/connectors/connector-line/src/index.test.ts new file mode 100644 index 000000000..3d9b6e287 --- /dev/null +++ b/packages/connectors/connector-line/src/index.test.ts @@ -0,0 +1,92 @@ +import nock from 'nock'; + +import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant.js'; +import createConnector, { getAccessToken } from './index.js'; +import { mockedConfig } from './mock.js'; + +const getConfig = vi.fn().mockResolvedValue(mockedConfig); +const setSession = vi.fn(); +const getSession = vi.fn().mockResolvedValue({ + redirectUri: 'http://localhost:3000/callback', +}); + +describe('getAuthorizationUri', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should get a valid uri by redirectUri and state', async () => { + const connector = await createConnector({ getConfig }); + const authorizationUri = await connector.getAuthorizationUri( + { + state: 'some_state', + redirectUri: 'http://localhost:3000/callback', + connectorId: 'some_connector_id', + connectorFactoryId: 'some_connector_factory_id', + jti: 'some_jti', + headers: {}, + }, + setSession + ); + expect(setSession).toHaveBeenCalledWith({ + redirectUri: 'http://localhost:3000/callback', + }); + expect(authorizationUri).toEqual( + `${authorizationEndpoint}?response_type=code&client_id=%3Cclient-id%3E&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&scope=openid+profile&state=some_state` + ); + }); +}); + +describe('getAccessToken', () => { + afterEach(() => { + nock.cleanAll(); + vi.clearAllMocks(); + }); + + it('should get an accessToken by exchanging with code', async () => { + nock(accessTokenEndpoint).post('').reply(200, { + access_token: 'access_token', + }); + const { access_token } = await getAccessToken(mockedConfig, 'code', 'redirectUri'); + expect(access_token).toEqual('access_token'); + }); +}); + +describe('getUserInfo', () => { + beforeEach(() => { + nock(accessTokenEndpoint).post('').query(true).reply(200, { + access_token: 'access_token', + }); + }); + + afterEach(() => { + nock.cleanAll(); + vi.clearAllMocks(); + }); + + it('should get valid SocialUserInfo', async () => { + nock(userInfoEndpoint).get('').reply(200, { + userId: '1', + displayName: 'monalisa', + }); + const connector = await createConnector({ getConfig }); + const socialUserInfo = await connector.getUserInfo( + { code: 'code', redirectUri: 'http://localhost:3000/callback' }, + getSession + ); + expect(socialUserInfo).toStrictEqual({ + id: '1', + name: 'monalisa', + rawData: { + userId: '1', + displayName: 'monalisa', + }, + }); + }); + + it('throws unrecognized error', async () => { + nock(userInfoEndpoint).get('').reply(500); + const connector = await createConnector({ getConfig }); + await expect(connector.getUserInfo({ code: 'code' }, vi.fn())).rejects.toThrow(); + }); +}); diff --git a/packages/connectors/connector-line/src/index.ts b/packages/connectors/connector-line/src/index.ts new file mode 100644 index 000000000..8bfb7d4a0 --- /dev/null +++ b/packages/connectors/connector-line/src/index.ts @@ -0,0 +1,145 @@ +import { conditional } from '@silverhand/essentials'; + +import { + ConnectorError, + ConnectorErrorCodes, + validateConfig, + ConnectorType, + jsonGuard, +} from '@logto/connector-kit'; +import type { + GetAuthorizationUri, + GetUserInfo, + SocialConnector, + CreateConnector, + GetConnectorConfig, +} from '@logto/connector-kit'; +import ky, { HTTPError } from 'ky'; + +import { + authorizationEndpoint, + accessTokenEndpoint, + defaultMetadata, + defaultTimeout, + defaultScope, + userInfoEndpoint, +} from './constant.js'; +import type { LineConfig } from './types.js'; +import { + lineConfigGuard, + userInfoResponseGuard, + authResponseGuard, + accessTokenResponseGuard, +} from './types.js'; + +const getAuthorizationUri = + (getConfig: GetConnectorConfig): GetAuthorizationUri => + async ({ state, redirectUri }, setSession) => { + const config = await getConfig(defaultMetadata.id); + validateConfig(config, lineConfigGuard); + + await setSession({ redirectUri }); + + const queryParams = new URLSearchParams({ + response_type: 'code', + client_id: config.clientId, + redirect_uri: redirectUri, + scope: config.scope ?? defaultScope, + state, + }); + + return `${authorizationEndpoint}?${queryParams.toString()}`; + }; + +export const getAccessToken = async (config: LineConfig, code: string, redirectUri: string) => { + const response = await ky + .post(accessTokenEndpoint, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + code, + redirect_uri: redirectUri, + client_id: config.clientId, + client_secret: config.clientSecret, + }).toString(), + timeout: defaultTimeout, + }) + .json(); + + return accessTokenResponseGuard.parse(response); +}; + +const getUserInfo = + (getConfig: GetConnectorConfig): GetUserInfo => + async (data, getSession) => { + const config = await getConfig(defaultMetadata.id); + validateConfig(config, lineConfigGuard); + + const authResponseResult = authResponseGuard.safeParse(data); + + if (!authResponseResult.success) { + throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(data)); + } + + const { code } = authResponseResult.data; + const { redirectUri } = await getSession(); + + if (!redirectUri) { + throw new ConnectorError(ConnectorErrorCodes.General, { + message: 'Cannot find `redirectUri` from connector session.', + }); + } + + try { + const { access_token } = await getAccessToken(config, code, redirectUri); + + const userInfo = await ky + .get(userInfoEndpoint, { + headers: { + Authorization: `Bearer ${access_token}`, + }, + timeout: defaultTimeout, + }) + .json(); + + const userInfoResult = userInfoResponseGuard.safeParse(userInfo); + + if (!userInfoResult.success) { + throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error); + } + + const { userId, displayName } = userInfoResult.data; + + return { + id: userId, + name: conditional(displayName), + rawData: jsonGuard.parse(userInfo), + }; + } catch (error: unknown) { + if (error instanceof HTTPError) { + const { status, body: rawBody } = error.response; + + if (status === 401) { + throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid); + } + + throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody)); + } + + throw error; + } + }; + +const createLineConnector: CreateConnector = async ({ getConfig }) => { + return { + metadata: defaultMetadata, + type: ConnectorType.Social, + configGuard: lineConfigGuard, + getAuthorizationUri: getAuthorizationUri(getConfig), + getUserInfo: getUserInfo(getConfig), + }; +}; + +export default createLineConnector; diff --git a/packages/connectors/connector-line/src/mock.ts b/packages/connectors/connector-line/src/mock.ts new file mode 100644 index 000000000..a52d77ccc --- /dev/null +++ b/packages/connectors/connector-line/src/mock.ts @@ -0,0 +1,4 @@ +export const mockedConfig = { + clientId: '', + clientSecret: '', +}; diff --git a/packages/connectors/connector-line/src/types.ts b/packages/connectors/connector-line/src/types.ts new file mode 100644 index 000000000..cefe87a57 --- /dev/null +++ b/packages/connectors/connector-line/src/types.ts @@ -0,0 +1,24 @@ +import { z } from 'zod'; + +export const lineConfigGuard = z.object({ + clientId: z.string(), + clientSecret: z.string(), + scope: z.string().optional(), +}); + +export type LineConfig = z.infer; + +export const userInfoResponseGuard = z.object({ + userId: z.string(), + displayName: z.string().nullish(), +}); + +export type UserInfoResponse = z.infer; + +export const authResponseGuard = z.object({ + code: z.string(), +}); + +export const accessTokenResponseGuard = z.object({ + access_token: z.string(), +}); diff --git a/packages/connectors/connector-line/src/utils.ts b/packages/connectors/connector-line/src/utils.ts new file mode 100644 index 000000000..7ab27db7d --- /dev/null +++ b/packages/connectors/connector-line/src/utils.ts @@ -0,0 +1,12 @@ +import crypto from 'node:crypto'; + +export const generateCodeVerifier = () => { + const buffer = crypto.randomBytes(32); + return buffer.toString('base64url'); +}; + +export const generateCodeChallenge = (verifier: string) => { + const hash = crypto.createHash('sha256'); + hash.update(verifier); + return hash.digest('base64url'); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a3aef041..4e00a3522 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1440,6 +1440,67 @@ importers: specifier: ^2.1.9 version: 2.1.9(@types/node@20.12.7)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8) + packages/connectors/connector-line: + dependencies: + '@logto/connector-kit': + specifier: workspace:^4.0.0 + version: link:../../toolkit/connector-kit + '@silverhand/essentials': + specifier: ^2.9.1 + version: 2.9.2 + ky: + specifier: ^1.2.3 + version: 1.2.3 + query-string: + specifier: ^9.0.0 + version: 9.0.0 + snakecase-keys: + specifier: ^8.0.1 + version: 8.0.1 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@silverhand/eslint-config': + specifier: 6.0.1 + version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.5.3) + '@silverhand/ts-config': + specifier: 6.0.0 + version: 6.0.0(typescript@5.5.3) + '@types/node': + specifier: ^20.11.20 + version: 20.12.7 + '@types/supertest': + specifier: ^6.0.2 + version: 6.0.2 + '@vitest/coverage-v8': + specifier: ^2.1.8 + version: 2.1.9(vitest@2.1.9(@types/node@20.12.7)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8)) + eslint: + specifier: ^8.56.0 + version: 8.57.0 + lint-staged: + specifier: ^15.0.2 + version: 15.0.2 + nock: + specifier: 14.0.0-beta.15 + version: 14.0.0-beta.15 + prettier: + specifier: ^3.0.0 + version: 3.0.0 + supertest: + specifier: ^7.0.0 + version: 7.0.0 + tsup: + specifier: ^8.3.0 + version: 8.3.0(@swc/core@1.3.52(@swc/helpers@0.5.1))(jiti@1.21.0)(postcss@8.5.1)(typescript@5.5.3)(yaml@2.4.5) + typescript: + specifier: ^5.5.3 + version: 5.5.3 + vitest: + specifier: ^2.1.8 + version: 2.1.9(@types/node@20.12.7)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8) + packages/connectors/connector-linkedin: dependencies: '@logto/connector-kit': @@ -9600,10 +9661,6 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} - foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -9777,11 +9834,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.2: - resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} - engines: {node: '>=16 || 14 >=14.18'} - hasBin: true - glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true @@ -10508,10 +10560,6 @@ packages: iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} - jackspeak@3.4.0: - resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} - engines: {node: '>=14'} - jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -11585,10 +11633,6 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -11994,9 +12038,6 @@ packages: resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} engines: {node: '>= 14'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -15044,12 +15085,12 @@ snapshots: '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helpers': 7.24.4 - '@babel/parser': 7.24.8 + '@babel/parser': 7.26.3 '@babel/template': 7.24.0 '@babel/traverse': 7.24.1 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -15078,20 +15119,20 @@ snapshots: '@babel/generator@7.20.4': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@jridgewell/gen-mapping': 0.3.5 jsesc: 2.5.2 '@babel/generator@7.24.10': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 '@babel/generator@7.24.4': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 @@ -15116,34 +15157,34 @@ snapshots: '@babel/helper-environment-visitor@7.24.7': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-function-name@7.23.0': dependencies: '@babel/template': 7.24.0 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-function-name@7.24.7': dependencies: '@babel/template': 7.24.7 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-hoist-variables@7.22.5': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-hoist-variables@7.24.7': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-module-imports@7.24.3': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-module-imports@7.24.7': dependencies: '@babel/traverse': 7.24.8 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color @@ -15173,22 +15214,22 @@ snapshots: '@babel/helper-simple-access@7.22.5': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-simple-access@7.24.7': dependencies: '@babel/traverse': 7.24.8 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color '@babel/helper-split-export-declaration@7.22.6': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-split-export-declaration@7.24.7': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-string-parser@7.23.4': {} @@ -15212,14 +15253,14 @@ snapshots: dependencies: '@babel/template': 7.24.0 '@babel/traverse': 7.24.1 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color '@babel/helpers@7.24.8': dependencies: '@babel/template': 7.24.7 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/highlight@7.22.5': dependencies: @@ -15250,11 +15291,11 @@ snapshots: '@babel/parser@7.24.4': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.26.3 '@babel/parser@7.24.8': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/parser@7.26.3': dependencies: @@ -15359,20 +15400,20 @@ snapshots: '@babel/template@7.18.10': dependencies: '@babel/code-frame': 7.22.5 - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 '@babel/template@7.24.0': dependencies: '@babel/code-frame': 7.24.2 - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 '@babel/template@7.24.7': dependencies: '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 '@babel/traverse@7.24.1': dependencies: @@ -15382,9 +15423,9 @@ snapshots: '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 - debug: 4.3.4 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -15397,9 +15438,9 @@ snapshots: '@babel/helper-function-name': 7.24.7 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 - debug: 4.3.4 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -15616,7 +15657,7 @@ snapshots: '@commitlint/is-ignored@19.0.3': dependencies: '@commitlint/types': 19.0.3 - semver: 7.6.0 + semver: 7.6.3 '@commitlint/lint@19.0.3': dependencies: @@ -16411,7 +16452,7 @@ snapshots: '@types/shimmer': 1.0.2 import-in-the-middle: 1.4.2 require-in-the-middle: 7.2.0 - semver: 7.6.0 + semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: - supports-color @@ -17427,8 +17468,8 @@ snapshots: '@types/babel__core@7.1.19': dependencies: - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.2 @@ -17443,16 +17484,16 @@ snapshots: '@types/babel__generator@7.6.4': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.26.3 '@types/babel__template@7.4.1': dependencies: - '@babel/parser': 7.24.4 - '@babel/types': 7.24.0 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 '@types/babel__traverse@7.18.2': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.26.3 '@types/body-parser@1.19.2': dependencies: @@ -18844,7 +18885,7 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.18.10 - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@types/babel__core': 7.1.19 '@types/babel__traverse': 7.18.2 @@ -20728,11 +20769,6 @@ snapshots: dependencies: is-callable: 1.2.7 - foreground-child@3.1.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - foreground-child@3.3.0: dependencies: cross-spawn: 7.0.6 @@ -20934,15 +20970,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.2: - dependencies: - foreground-child: 3.1.1 - jackspeak: 3.4.0 - minimatch: 9.0.4 - minipass: 7.1.2 - package-json-from-dist: 1.0.0 - path-scurry: 1.11.1 - glob@10.4.5: dependencies: foreground-child: 3.3.0 @@ -21694,7 +21721,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.24.9 - '@babel/parser': 7.24.8 + '@babel/parser': 7.26.3 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -21746,12 +21773,6 @@ snapshots: reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 - jackspeak@3.4.0: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -22151,7 +22172,7 @@ snapshots: '@babel/generator': 7.20.4 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.24.4) '@babel/plugin-syntax-typescript': 7.18.6(@babel/core@7.24.4) - '@babel/types': 7.24.0 + '@babel/types': 7.26.3 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -22166,7 +22187,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -23488,7 +23509,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.7 - debug: 4.3.4 + debug: 4.4.0 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -23510,7 +23531,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.7 - debug: 4.3.4 + debug: 4.4.0 decode-named-character-reference: 1.0.1 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -23562,10 +23583,6 @@ snapshots: dependencies: brace-expansion: 1.1.11 - minimatch@9.0.4: - dependencies: - brace-expansion: 2.0.1 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -23729,7 +23746,7 @@ snapshots: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.13.1 - semver: 7.6.0 + semver: 7.6.3 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -23994,8 +24011,6 @@ snapshots: degenerator: 5.0.1 netmask: 2.0.2 - package-json-from-dist@1.0.0: {} - package-json-from-dist@1.0.1: {} packet-reader@1.0.0: {} @@ -25029,7 +25044,7 @@ snapshots: rimraf@5.0.5: dependencies: - glob: 10.4.2 + glob: 10.4.5 roarr@7.11.0: dependencies: