0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat: return and store connector raw data

This commit is contained in:
Gao Sun 2024-03-19 14:05:42 +08:00
parent d3d0f5133b
commit 57d97a4df8
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
47 changed files with 1070 additions and 154 deletions

View file

@ -0,0 +1,8 @@
---
"@logto/connector-kit": major
---
update `SocialUserInfo` and `GetUserInfo` types
- Added `rawData?: Json` to `SocialUserInfo`
- `GetUserInfo` now does not accept unknown keys in the return object, since the raw data is now stored in `SocialUserInfo`

View file

@ -0,0 +1,7 @@
---
"@logto/connector-kit": major
---
guard results of `parseJson` and `parseJsonObject`
Now `parseJson` and `parseJsonObject` are type safe.

View file

@ -0,0 +1,21 @@
---
"@logto/connector-alipay-native": minor
"@logto/connector-wechat-native": minor
"@logto/connector-mock-social": minor
"@logto/connector-alipay-web": minor
"@logto/connector-feishu-web": minor
"@logto/connector-wechat-web": minor
"@logto/connector-facebook": minor
"@logto/connector-azuread": minor
"@logto/connector-discord": minor
"@logto/connector-github": minor
"@logto/connector-google": minor
"@logto/connector-oauth": minor
"@logto/connector-apple": minor
"@logto/connector-kakao": minor
"@logto/connector-naver": minor
"@logto/connector-wecom": minor
"@logto/connector-oidc": minor
---
return and store social connector raw data

View file

@ -0,0 +1,5 @@
---
"@logto/connector-kit": minor
---
add `jsonGuard()` and `jsonObjectGuard()`

View file

@ -12,7 +12,7 @@ tasks:
cd packages/core
pnpm build
cd -
pnpm connectors:build
pnpm connectors build
pnpm cli connector link
command: |
gp ports await 5432

View file

@ -15,7 +15,7 @@
"cli": "logto",
"changeset": "changeset",
"alteration": "logto db alt",
"connectors:build": "pnpm -r --filter \"./packages/connectors/connector-*\" build",
"connectors": "pnpm -r --filter \"./packages/connectors/connector-*\"",
"//": "# `changeset version` won't run version lifecycle scripts, see https://github.com/changesets/changesets/issues/860",
"ci:version": "changeset version && pnpm -r version",
"ci:build": "pnpm -r build",

View file

@ -156,10 +156,23 @@ describe('getUserInfo', () => {
sign: '<signature>',
});
const connector = await createConnector({ getConfig });
const { id, name, avatar } = await connector.getUserInfo({ auth_code: 'code' }, jest.fn());
const { id, name, avatar, rawData } = await connector.getUserInfo(
{ auth_code: 'code' },
jest.fn()
);
expect(id).toEqual('2088000000000000');
expect(name).toEqual('PlayboyEric');
expect(avatar).toEqual('https://www.alipay.com/xxx.jpg');
expect(rawData).toEqual({
alipay_user_info_share_response: {
code: '10000',
msg: 'Success',
user_id: '2088000000000000',
nick_name: 'PlayboyEric',
avatar: 'https://www.alipay.com/xxx.jpg',
},
sign: '<signature>',
});
});
it('should throw SocialAccessTokenInvalid with code 20001', async () => {

View file

@ -131,8 +131,8 @@ const getUserInfo =
searchParams: signedSearchParameters,
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -148,7 +148,7 @@ const getUserInfo =
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse);
}
return { id, avatar, name };
return { id, avatar, name, rawData };
};
const errorHandler: ErrorHandler = ({ code, msg, sub_code, sub_msg }) => {

View file

@ -142,10 +142,23 @@ describe('getUserInfo', () => {
sign: '<signature>',
});
const connector = await createConnector({ getConfig });
const { id, name, avatar } = await connector.getUserInfo({ auth_code: 'code' }, jest.fn());
const { id, name, avatar, rawData } = await connector.getUserInfo(
{ auth_code: 'code' },
jest.fn()
);
expect(id).toEqual('2088000000000000');
expect(name).toEqual('PlayboyEric');
expect(avatar).toEqual('https://www.alipay.com/xxx.jpg');
expect(rawData).toEqual({
alipay_user_info_share_response: {
code: '10000',
msg: 'Success',
user_id: '2088000000000000',
nick_name: 'PlayboyEric',
avatar: 'https://www.alipay.com/xxx.jpg',
},
sign: '<signature>',
});
});
it('throw General error if auth_code not provided in input', async () => {

View file

@ -130,10 +130,8 @@ const getUserInfo =
searchParams: signedSearchParameters,
timeout: { request: defaultTimeout },
});
const { body: rawBody } = httpResponse;
const result = userInfoResponseGuard.safeParse(parseJson(rawBody));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -149,7 +147,7 @@ const getUserInfo =
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse);
}
return { id, avatar, name };
return { id, avatar, name, rawData };
};
const errorHandler: ErrorHandler = ({ code, msg, sub_code, sub_msg }) => {

View file

@ -60,7 +60,11 @@ describe('getUserInfo', () => {
}));
const connector = await createConnector({ getConfig });
const userInfo = await connector.getUserInfo({ id_token: 'idToken' }, jest.fn());
expect(userInfo).toEqual({ id: userId, email: 'foo@bar.com' });
expect(userInfo).toEqual({
id: userId,
email: 'foo@bar.com',
rawData: { id_token: 'idToken' },
});
});
it('should ignore unverified email', async () => {
@ -69,7 +73,7 @@ describe('getUserInfo', () => {
}));
const connector = await createConnector({ getConfig });
const userInfo = await connector.getUserInfo({ id_token: 'idToken' }, jest.fn());
expect(userInfo).toEqual({ id: 'userId' });
expect(userInfo).toEqual({ id: 'userId', rawData: { id_token: 'idToken' } });
});
it('should get user info from the `user` field', async () => {
@ -89,7 +93,18 @@ describe('getUserInfo', () => {
jest.fn()
);
// Should use info from `user` field first
expect(userInfo).toEqual({ id: userId, email: 'foo2@bar.com', name: 'foo bar' });
expect(userInfo).toEqual({
id: userId,
email: 'foo2@bar.com',
name: 'foo bar',
rawData: {
id_token: 'idToken',
user: JSON.stringify({
email: 'foo2@bar.com',
name: { firstName: 'foo', lastName: 'bar' },
}),
},
});
});
it('should throw if id token is missing', async () => {

View file

@ -12,6 +12,7 @@ import {
ConnectorErrorCodes,
validateConfig,
ConnectorType,
jsonGuard,
} from '@logto/connector-kit';
import { generateStandardId } from '@logto/shared/universal';
import { createRemoteJWKSet, jwtVerify } from 'jose';
@ -112,6 +113,7 @@ const getUserInfo =
user?.email ??
(payload.email && payload.email_verified === true ? String(payload.email) : undefined),
name: [user?.name?.firstName, user?.name?.lastName].filter(Boolean).join(' ') || undefined,
rawData: jsonGuard.parse(data),
};
} catch {
throw new ConnectorError(ConnectorErrorCodes.SocialIdTokenInvalid);

View file

@ -4,8 +4,8 @@
"description": "Microsoft Azure AD connector implementation.",
"author": "Mobilist Inc. <info@mobilist.com.tr>",
"dependencies": {
"@logto/connector-kit": "workspace:^2.1.0",
"@azure/msal-node": "^2.0.0"
"@azure/msal-node": "^2.0.0",
"@logto/connector-kit": "workspace:^2.1.0"
},
"main": "./lib/index.js",
"module": "./lib/index.js",
@ -25,9 +25,8 @@
"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",
"test": "vitest src",
"test:ci": "pnpm run test --silent --coverage",
"prepublishOnly": "pnpm build"
},
"engines": {
@ -48,5 +47,9 @@
"prettier": "@silverhand/eslint-config/.prettierrc",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@vitest/coverage-v8": "^1.4.0",
"vitest": "^1.4.0"
}
}

View file

@ -1,13 +1,76 @@
import type { GetConnectorConfig } from '@logto/connector-kit';
import nock from 'nock';
import { vi, describe, beforeAll, it, expect } from 'vitest';
import { graphAPIEndpoint } from './constant.js';
import createConnector from './index.js';
const { jest } = import.meta;
vi.mock('@azure/msal-node', async () => ({
ConfidentialClientApplication: class {
async acquireTokenByCode() {
return {
accessToken: 'accessToken',
scopes: ['scopes'],
tokenType: 'tokenType',
};
}
},
}));
const getConnectorConfig = jest.fn() as GetConnectorConfig;
const getConnectorConfig = vi.fn().mockResolvedValue({
clientId: 'clientId',
clientSecret: 'clientSecret',
cloudInstance: 'https://login.microsoftonline.com',
tenantId: 'tenantId',
});
describe('Azure AD connector', () => {
it('init without exploding', () => {
expect(async () => createConnector({ getConfig: getConnectorConfig })).not.toThrow();
});
});
describe('getUserInfo', () => {
beforeAll(async () => {
const graphMeUrl = new URL(graphAPIEndpoint);
nock(graphMeUrl.origin).get(graphMeUrl.pathname).reply(200, {
id: 'id',
displayName: 'displayName',
mail: 'mail',
userPrincipalName: 'userPrincipalName',
});
});
it('should get user info from graph api', async () => {
const connector = await createConnector({ getConfig: getConnectorConfig });
const userInfo = await connector.getUserInfo(
{ code: 'code', redirectUri: 'redirectUri' },
vi.fn()
);
expect(userInfo).toEqual({
id: 'id',
name: 'displayName',
email: 'mail',
rawData: {
id: 'id',
displayName: 'displayName',
mail: 'mail',
userPrincipalName: 'userPrincipalName',
},
});
});
it('should throw if graph api response has no id', async () => {
const graphMeUrl = new URL(graphAPIEndpoint);
nock(graphMeUrl.origin).get(graphMeUrl.pathname).reply(200, {
displayName: 'displayName',
mail: 'mail',
userPrincipalName: 'userPrincipalName',
});
const connector = await createConnector({ getConfig: getConnectorConfig });
const userInfo = connector.getUserInfo({ code: 'code', redirectUri: 'redirectUri' }, vi.fn());
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
await expect(userInfo).rejects.toThrow(expect.objectContaining({ code: 'invalid_response' }));
});
});

View file

@ -3,7 +3,7 @@ import { got, HTTPError } from 'got';
import path from 'node:path';
import type { AuthorizationCodeRequest, AuthorizationUrlRequest } from '@azure/msal-node';
import { ConfidentialClientApplication, CryptoProvider } from '@azure/msal-node';
import { ConfidentialClientApplication } from '@azure/msal-node';
import type {
GetAuthorizationUri,
GetUserInfo,
@ -31,10 +31,6 @@ import {
// eslint-disable-next-line @silverhand/fp/no-let
let authCodeRequest: AuthorizationCodeRequest;
// This `cryptoProvider` seems not used.
// Temporarily keep this as this is a refactor, which should not change the logics.
const cryptoProvider = new CryptoProvider();
const getAuthorizationUri =
(getConfig: GetConnectorConfig): GetAuthorizationUri =>
async ({ state, redirectUri }) => {
@ -85,7 +81,6 @@ const getAccessToken = async (config: AzureADConfig, code: string, redirectUri:
});
const authResult = await clientApplication.acquireTokenByCode(codeRequest);
const result = accessTokenResponseGuard.safeParse(authResult);
if (!result.success) {
@ -117,8 +112,8 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -130,6 +125,7 @@ const getUserInfo =
id,
email: conditional(mail),
name: conditional(displayName),
rawData,
};
} catch (error: unknown) {
if (error instanceof HTTPError) {

View file

@ -101,11 +101,18 @@ describe('Discord connector', () => {
},
jest.fn()
);
expect(socialUserInfo).toMatchObject({
expect(socialUserInfo).toStrictEqual({
id: '1234567890',
name: 'Whumpus',
avatar: 'https://cdn.discordapp.com/avatars/1234567890/avatar_id',
email: 'whumpus@discord.com',
rawData: {
id: '1234567890',
username: 'Whumpus',
avatar: 'avatar_id',
email: 'whumpus@discord.com',
verified: true,
},
});
});

View file

@ -102,8 +102,8 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -124,7 +124,7 @@ const getUserInfo =
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, userInfoResult.error);
}
return userInfoResult.data;
return { ...userInfoResult.data, rawData };
} catch (error: unknown) {
if (error instanceof HTTPError) {
const { statusCode, body: rawBody } = error.response;

View file

@ -132,11 +132,17 @@ describe('Facebook connector', () => {
},
jest.fn()
);
expect(socialUserInfo).toMatchObject({
expect(socialUserInfo).toStrictEqual({
id: '1234567890',
avatar,
name: 'monalisa octocat',
email: 'octocat@facebook.com',
rawData: {
id: '1234567890',
name: 'monalisa octocat',
email: 'octocat@facebook.com',
picture: { data: { url: avatar } },
},
});
});

View file

@ -105,8 +105,8 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -119,6 +119,7 @@ const getUserInfo =
avatar: picture?.data.url,
email,
name,
rawData,
};
} catch (error: unknown) {
if (error instanceof HTTPError) {

View file

@ -112,7 +112,7 @@ describe('getUserInfo', () => {
const accessTokenUrl = new URL(accessTokenEndpoint);
it('should get userInfo with accessToken', async () => {
nock(userInfoUrl.origin).get(userInfoUrl.pathname).query(true).once().reply(200, {
const jsonResponse = Object.freeze({
sub: 'ou_caecc734c2e3328a62489fe0648c4b98779515d3',
name: '李雷',
picture: 'https://www.feishu.cn/avatar',
@ -129,9 +129,10 @@ describe('getUserInfo', () => {
employee_no: '111222333',
mobile: '+86130xxxx0000',
});
nock(userInfoUrl.origin).get(userInfoUrl.pathname).query(true).once().reply(200, jsonResponse);
const connector = await createConnector({ getConfig });
const { id, name, avatar } = await connector.getUserInfo(
const { id, name, avatar, rawData } = await connector.getUserInfo(
{
code: 'code',
redirectUri: 'http://localhost:3000',
@ -141,6 +142,7 @@ describe('getUserInfo', () => {
expect(id).toEqual('ou_caecc734c2e3328a62489fe0648c4b98779515d3');
expect(name).toEqual('李雷');
expect(avatar).toEqual('www.feishu.cn/avatar/icon');
expect(rawData).toEqual(jsonResponse);
});
it('throw General error if code not provided in input', async () => {

View file

@ -13,6 +13,7 @@ import {
ConnectorErrorCodes,
ConnectorPlatform,
ConnectorType,
jsonGuard,
validateConfig,
} from '@logto/connector-kit';
@ -153,6 +154,7 @@ export function getUserInfo(getConfig: GetConnectorConfig): GetUserInfo {
email: conditional(email),
userId: conditional(user_id),
phone: conditional(mobile),
rawData: jsonGuard.parse(response.body),
};
} catch (error: unknown) {
if (error instanceof ConnectorError) {

View file

@ -91,14 +91,22 @@ describe('getUserInfo', () => {
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
name: 'monalisa octocat',
email: 'octocat@github.com',
foo: 'bar',
});
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo({ code: 'code' }, jest.fn());
expect(socialUserInfo).toMatchObject({
expect(socialUserInfo).toStrictEqual({
id: '1',
avatar: 'https://github.com/images/error/octocat_happy.gif',
name: 'monalisa octocat',
email: 'octocat@github.com',
rawData: {
id: 1,
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
name: 'monalisa octocat',
email: 'octocat@github.com',
foo: 'bar',
},
});
});
@ -113,6 +121,12 @@ describe('getUserInfo', () => {
const socialUserInfo = await connector.getUserInfo({ code: 'code' }, jest.fn());
expect(socialUserInfo).toMatchObject({
id: '1',
rawData: {
id: 1,
avatar_url: null,
name: null,
email: null,
},
});
});

View file

@ -117,8 +117,8 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -131,6 +131,7 @@ const getUserInfo =
avatar: conditional(avatar),
email: conditional(email),
name: conditional(name),
rawData,
};
} catch (error: unknown) {
if (error instanceof HTTPError) {

View file

@ -79,7 +79,7 @@ describe('google connector', () => {
});
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpoint).post('').reply(200, {
const jsonResponse = Object.freeze({
sub: '1234567890',
name: 'monalisa octocat',
given_name: 'monalisa',
@ -89,6 +89,7 @@ describe('google connector', () => {
email_verified: true,
locale: 'en',
});
nock(userInfoEndpoint).post('').reply(200, jsonResponse);
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo(
{
@ -97,11 +98,12 @@ describe('google connector', () => {
},
jest.fn()
);
expect(socialUserInfo).toMatchObject({
expect(socialUserInfo).toStrictEqual({
id: '1234567890',
avatar: 'https://github.com/images/error/octocat_happy.gif',
name: 'monalisa octocat',
email: 'octocat@google.com',
rawData: jsonResponse,
});
});

View file

@ -101,8 +101,8 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -115,6 +115,7 @@ const getUserInfo =
avatar,
email: conditional(email_verified && email),
name,
rawData,
};
} catch (error: unknown) {
return getUserInfoErrorHandler(error);

View file

@ -79,9 +79,7 @@ describe('kakao connector', () => {
});
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpoint)
.post('')
.reply(200, {
const jsonResponse = Object.freeze({
id: 1_234_567_890,
kakao_account: {
is_email_valid: true,
@ -93,6 +91,7 @@ describe('kakao connector', () => {
},
},
});
nock(userInfoEndpoint).post('').reply(200, jsonResponse);
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo(
{
@ -101,11 +100,12 @@ describe('kakao connector', () => {
},
jest.fn()
);
expect(socialUserInfo).toMatchObject({
expect(socialUserInfo).toStrictEqual({
id: '1234567890',
avatar: 'https://github.com/images/error/octocat_happy.gif',
name: 'pemassi',
email: 'ruddbs5302@gmail.com',
rawData: jsonResponse,
});
});

View file

@ -99,8 +99,8 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -118,6 +118,7 @@ const getUserInfo =
avatar: conditional(profile && !profile.is_default_image && profile.profile_image_url),
email: conditional(is_email_valid && email),
name: conditional(profile?.nickname),
rawData,
};
} catch (error: unknown) {
return getUserInfoErrorHandler(error);

View file

@ -7,7 +7,12 @@ import type {
CreateConnector,
SocialConnector,
} from '@logto/connector-kit';
import { ConnectorError, ConnectorErrorCodes, ConnectorType } from '@logto/connector-kit';
import {
ConnectorError,
ConnectorErrorCodes,
ConnectorType,
jsonGuard,
} from '@logto/connector-kit';
import { defaultMetadata } from './constant.js';
import { mockSocialConfigGuard } from './types.js';
@ -35,6 +40,7 @@ const getUserInfo: GetUserInfo = async (data) => {
return {
id: userId ?? `mock-social-sub-${randomUUID()}`,
...rest,
rawData: jsonGuard.parse(data),
};
};

View file

@ -79,9 +79,7 @@ describe('naver connector', () => {
});
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpoint)
.post('')
.reply(200, {
const jsonResponse = Object.freeze({
resultcode: '00',
message: 'success',
response: {
@ -95,6 +93,7 @@ describe('naver connector', () => {
birthday: '10-01',
},
});
nock(userInfoEndpoint).post('').reply(200, jsonResponse);
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo(
{
@ -108,6 +107,7 @@ describe('naver connector', () => {
avatar: 'https://ssl.pstatic.net/static/pwe/address/nodata_33x33.gif',
name: 'OpenAPI',
email: 'openapi@naver.com',
rawData: jsonResponse,
});
});

View file

@ -99,8 +99,8 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -114,6 +114,7 @@ const getUserInfo =
avatar: conditional(profile_image),
email: conditional(email),
name: conditional(nickname),
rawData,
};
} catch (error: unknown) {
return getUserInfoErrorHandler(error);

View file

@ -59,6 +59,7 @@ describe('getUserInfo', () => {
const userInfoEndpointUrl = new URL(mockConfig.userInfoEndpoint);
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(true).reply(200, {
sub: userId,
foo: 'bar',
});
const connector = await createConnector({ getConfig });
const userInfo = await connector.getUserInfo(
@ -67,6 +68,6 @@ describe('getUserInfo', () => {
return { redirectUri: 'http://localhost:3001/callback' };
})
);
expect(userInfo).toEqual({ id: userId });
expect(userInfo).toStrictEqual({ id: userId, rawData: { sub: userId, foo: 'bar' } });
});
});

View file

@ -71,8 +71,9 @@ const getUserInfo =
},
timeout: { request: defaultTimeout },
});
const rawData = parseJsonObject(httpResponse.body);
return userProfileMapping(parseJsonObject(httpResponse.body), parsedConfig.profileMap);
return { ...userProfileMapping(rawData, parsedConfig.profileMap), rawData };
} catch (error: unknown) {
if (error instanceof HTTPError) {
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(error.response.body));

View file

@ -69,6 +69,6 @@ describe('getUserInfo', () => {
return { nonce: 'nonce', redirectUri: 'http://localhost:3001/callback' };
})
);
expect(userInfo).toEqual({ id: userId });
expect(userInfo).toMatchObject({ id: userId, rawData: { sub: userId, nonce: 'nonce' } });
});
});

View file

@ -14,6 +14,7 @@ import {
ConnectorErrorCodes,
validateConfig,
ConnectorType,
jsonGuard,
} from '@logto/connector-kit';
import { generateStandardId } from '@logto/shared/universal';
import { createRemoteJWKSet, jwtVerify } from 'jose';
@ -137,6 +138,7 @@ const getUserInfo =
avatar: conditional(picture),
email: conditional(email_verified && email),
phone: conditional(phone_verified && phone),
rawData: jsonGuard.parse(payload),
};
} catch (error: unknown) {
if (error instanceof HTTPError) {

View file

@ -139,17 +139,22 @@ describe('getUserInfo', () => {
const parameters = new URLSearchParams({ access_token: 'access_token', openid: 'openid' });
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(0, {
const jsonResponse = Object.freeze({
unionid: 'this_is_an_arbitrary_wechat_union_id',
headimgurl: 'https://github.com/images/error/octocat_happy.gif',
nickname: 'wechat bot',
});
nock(userInfoEndpointUrl.origin)
.get(userInfoEndpointUrl.pathname)
.query(parameters)
.reply(0, jsonResponse);
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo({ code: 'code' }, jest.fn());
expect(socialUserInfo).toMatchObject({
id: 'this_is_an_arbitrary_wechat_union_id',
avatar: 'https://github.com/images/error/octocat_happy.gif',
name: 'wechat bot',
rawData: jsonResponse,
});
});

View file

@ -100,8 +100,8 @@ const getUserInfo =
searchParams: { access_token: accessToken, openid },
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -114,7 +114,7 @@ const getUserInfo =
// 'errmsg' and 'errcode' turn to non-empty values or empty values at the same time. Hence, if 'errmsg' is non-empty then 'errcode' should be non-empty.
userInfoResponseMessageParser(result.data);
return { id: unionid ?? openid, avatar: headimgurl, name: nickname };
return { id: unionid ?? openid, avatar: headimgurl, name: nickname, rawData };
} catch (error: unknown) {
return getUserInfoErrorHandler(error);
}

View file

@ -130,11 +130,15 @@ describe('getUserInfo', () => {
const parameters = new URLSearchParams({ access_token: 'access_token', openid: 'openid' });
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(0, {
const jsonResponse = Object.freeze({
unionid: 'this_is_an_arbitrary_wechat_union_id',
headimgurl: 'https://github.com/images/error/octocat_happy.gif',
nickname: 'wechat bot',
});
nock(userInfoEndpointUrl.origin)
.get(userInfoEndpointUrl.pathname)
.query(parameters)
.reply(0, jsonResponse);
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo(
{
@ -146,6 +150,7 @@ describe('getUserInfo', () => {
id: 'this_is_an_arbitrary_wechat_union_id',
avatar: 'https://github.com/images/error/octocat_happy.gif',
name: 'wechat bot',
rawData: jsonResponse,
});
});

View file

@ -101,8 +101,8 @@ const getUserInfo =
searchParams: { access_token: accessToken, openid },
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -115,7 +115,7 @@ const getUserInfo =
// 'errmsg' and 'errcode' turn to non-empty values or empty values at the same time. Hence, if 'errmsg' is non-empty then 'errcode' should be non-empty.
userInfoResponseMessageParser(result.data);
return { id: unionid ?? openid, avatar: headimgurl, name: nickname };
return { id: unionid ?? openid, avatar: headimgurl, name: nickname, rawData };
} catch (error: unknown) {
return getUserInfoErrorHandler(error);
}

View file

@ -135,9 +135,14 @@ describe('getUserInfo', () => {
const parameters = new URLSearchParams({ access_token: 'access_token', code: 'code' });
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpointUrl.origin).get(userInfoEndpointUrl.pathname).query(parameters).reply(0, {
const jsonResponse = Object.freeze({
userid: 'wecom_id',
foo: 'bar',
});
nock(userInfoEndpointUrl.origin)
.get(userInfoEndpointUrl.pathname)
.query(parameters)
.reply(0, jsonResponse);
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo(
{
@ -149,6 +154,7 @@ describe('getUserInfo', () => {
id: 'wecom_id',
avatar: '',
name: 'wecom_id',
rawData: jsonResponse,
});
});

View file

@ -106,8 +106,8 @@ const getUserInfo =
searchParams: { access_token: accessToken, code },
timeout: { request: defaultTimeout },
});
const result = userInfoResponseGuard.safeParse(parseJson(httpResponse.body));
const rawData = parseJson(httpResponse.body);
const result = userInfoResponseGuard.safeParse(rawData);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
@ -118,15 +118,18 @@ const getUserInfo =
errorResponseHandler(result.data);
//
const { userid, openid } = result.data;
const id = userid ?? openid;
if (userid) {
return { id: userid, avatar: '', name: userid };
}
if (openid) {
return { id: openid, avatar: '', name: openid };
}
if (!id) {
throw new Error('Both userid and openid are undefined or null.');
// Both userid and openid are null
}
return {
id,
avatar: '',
name: id,
rawData,
};
} catch (error: unknown) {
return getUserInfoErrorHandler(error);
}

View file

@ -1,8 +1,8 @@
import {
type CreateSsoConnector,
SignInIdentifier,
demoAppApplicationId,
SsoProviderName,
demoAppApplicationId,
} from '@logto/schemas';
import { appendPath, getEnv } from '@silverhand/essentials';
@ -17,7 +17,7 @@ export const demoAppUrl = appendPath(new URL(logtoUrl), 'demo-app');
export const discoveryUrl = `${logtoUrl}/oidc/.well-known/openid-configuration`;
export const demoAppRedirectUri = `${logtoUrl}/${demoAppApplicationId}`;
export const demoAppRedirectUri = appendPath(new URL(logtoUrl), demoAppApplicationId).href;
export const adminConsoleRedirectUri = `${logtoConsoleUrl}/console/callback`;
export const signUpIdentifiers = {

View file

@ -193,6 +193,13 @@ describe('admin console user management', () => {
details: {
id: socialUserId,
email: socialUserEmail,
rawData: {
code,
email: socialUserEmail,
redirectUri,
state,
userId: socialUserId,
},
},
});
@ -202,6 +209,13 @@ describe('admin console user management', () => {
expect(updatedIdentity).toHaveProperty(mockSocialConnectorTarget);
expect(updatedIdentity[mockSocialConnectorTarget]).toMatchObject({
userId: anotherSocialUserId,
rawData: {
code,
email: socialUserEmail,
redirectUri,
state,
userId: anotherSocialUserId,
},
});
const updatedIdentities = await putUserIdentity(userId, socialTarget, socialIdentity);

View file

@ -1,6 +1,9 @@
import { ConnectorType, InteractionEvent, SignInIdentifier } from '@logto/schemas';
import { mockSocialConnectorId } from '#src/__mocks__/connectors-mock.js';
import {
mockSocialConnectorId,
mockSocialConnectorTarget,
} from '#src/__mocks__/connectors-mock.js';
import {
createSocialAuthorizationUri,
putInteraction,
@ -131,8 +134,24 @@ describe('Social Identifier Interactions', () => {
const uid = await processSession(client, redirectTo);
const { primaryEmail } = await getUser(uid);
const { primaryEmail, identities } = await getUser(uid);
expect(primaryEmail).toBe(socialEmail);
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
expect(identities[mockSocialConnectorTarget]).toStrictEqual({
details: {
email: expect.any(String),
id: expect.any(String),
rawData: {
code: 'auth_code_foo',
email: expect.any(String),
redirectUri: 'http://foo.dev/callback',
state: 'foo_state',
userId: expect.any(String),
},
},
userId: expect.any(String),
});
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
await logoutClient(client);
await deleteUser(uid);

View file

@ -1,6 +1,3 @@
import type { Json } from '@withtyped/server';
import { z } from 'zod';
export * from './custom-domain.js';
export * from './hooks.js';
export * from './logs.js';
@ -15,17 +12,8 @@ export * from './applications.js';
export {
configurableConnectorMetadataGuard,
type ConfigurableConnectorMetadata,
jsonGuard,
jsonObjectGuard,
} from '@logto/connector-kit';
export type { Json, JsonObject } from '@withtyped/server';
/* === Commonly Used === */
// Copied from https://github.com/colinhacks/zod#json-type
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
export const jsonGuard: z.ZodType<Json> = z.lazy(() =>
z.union([literalSchema, z.array(jsonGuard), z.record(jsonGuard)])
);
export const jsonObjectGuard = z.record(jsonGuard);

View file

@ -1,3 +1,4 @@
import { type Json, type JsonObject } from '@withtyped/server';
import type { ZodType, ZodTypeDef } from 'zod';
import {
@ -5,6 +6,8 @@ import {
ConnectorErrorCodes,
type SendMessagePayload,
ConnectorType,
jsonGuard,
jsonObjectGuard,
} from './types/index.js';
export * from './types/index.js';
@ -24,38 +27,24 @@ export const parseJson = (
jsonString: string,
errorCode: ConnectorErrorCodes = ConnectorErrorCodes.InvalidResponse,
errorPayload?: unknown
): unknown => {
): Json => {
try {
return JSON.parse(jsonString);
return jsonGuard.parse(JSON.parse(jsonString));
} catch {
throw new ConnectorError(errorCode, errorPayload ?? jsonString);
}
};
const isRecordOrArray = (parsed: unknown): parsed is Record<string, unknown> | unknown[] => {
if (Array.isArray(parsed)) {
return true;
export const parseJsonObject = (
...[jsonString, errorCode = ConnectorErrorCodes.InvalidResponse, errorPayload]: Parameters<
typeof parseJson
>
): JsonObject => {
try {
return jsonObjectGuard.parse(JSON.parse(jsonString));
} catch {
throw new ConnectorError(errorCode, errorPayload ?? jsonString);
}
if (!(parsed !== null && typeof parsed === 'object')) {
return false;
}
if (Object.getOwnPropertySymbols(parsed).length > 0) {
return false;
}
return true;
};
export const parseJsonObject = (...args: Parameters<typeof parseJson>) => {
const parsed = parseJson(...args);
if (!isRecordOrArray(parsed)) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, parsed);
}
return parsed;
};
/** @deprecated Use {@link mockConnectorFilePaths} instead. */

View file

@ -1,4 +1,5 @@
// MARK: Social connector
import { type Json } from '@withtyped/server';
import { z } from 'zod';
import { type BaseConnector, type ConnectorType } from './foundation.js';
@ -22,20 +23,38 @@ export type GetAuthorizationUri = (
setSession: SetSession
) => Promise<string>;
// Copied from https://github.com/colinhacks/zod#json-type
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
export const jsonGuard: z.ZodType<Json> = z.lazy(() =>
z.union([literalSchema, z.array(jsonGuard), z.record(jsonGuard)])
);
export const jsonObjectGuard = z.record(jsonGuard);
/**
* Normalized social user info that can be used in the system. The raw data returned from the
* social provider is also included in the `rawData` field.
*/
export type SocialUserInfo = {
id: string;
email?: string;
phone?: string;
name?: string;
avatar?: string;
rawData?: Json;
};
export const socialUserInfoGuard = z.object({
id: z.string(),
email: z.string().optional(),
phone: z.string().optional(),
name: z.string().optional(),
avatar: z.string().optional(),
});
rawData: jsonGuard.optional(),
}) satisfies z.ZodType<SocialUserInfo>;
export type SocialUserInfo = z.infer<typeof socialUserInfoGuard>;
export type GetUserInfo = (
data: unknown,
getSession: GetSession
) => Promise<SocialUserInfo & Record<string, string | boolean | number | undefined>>;
export type GetUserInfo = (data: unknown, getSession: GetSession) => Promise<SocialUserInfo>;
export const connectorSessionGuard = z
.object({

View file

@ -786,6 +786,9 @@ importers:
'@types/supertest':
specifier: ^6.0.0
version: 6.0.1
'@vitest/coverage-v8':
specifier: ^1.4.0
version: 1.4.0(vitest@1.4.0)
eslint:
specifier: ^8.44.0
version: 8.44.0
@ -816,6 +819,9 @@ importers:
typescript:
specifier: ^5.3.3
version: 5.3.3
vitest:
specifier: ^1.4.0
version: 1.4.0(@types/node@20.11.20)
packages/connectors/connector-discord:
dependencies:
@ -4274,6 +4280,14 @@ packages:
'@jridgewell/trace-mapping': 0.3.18
dev: true
/@ampproject/remapping@2.3.0:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
dev: true
/@apidevtools/json-schema-ref-parser@9.0.6:
resolution: {integrity: sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==}
dependencies:
@ -6264,6 +6278,16 @@ packages:
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-string-parser@7.23.4:
resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-identifier@7.22.20:
resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-identifier@7.22.5:
resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==}
engines: {node: '>=6.9.0'}
@ -6302,6 +6326,14 @@ packages:
'@babel/types': 7.20.2
dev: true
/@babel/parser@7.24.0:
resolution: {integrity: sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.24.0
dev: true
/@babel/plugin-proposal-object-rest-spread@7.12.1(@babel/core@7.12.9):
resolution: {integrity: sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==}
peerDependencies:
@ -6525,6 +6557,15 @@ packages:
to-fast-properties: 2.0.0
dev: true
/@babel/types@7.24.0:
resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.23.4
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
dev: true
/@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
@ -7007,6 +7048,213 @@ packages:
postcss-selector-parser: 6.0.15
dev: true
/@esbuild/aix-ppc64@0.19.12:
resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm64@0.19.12:
resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm@0.19.12:
resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64@0.19.12:
resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64@0.19.12:
resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64@0.19.12:
resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64@0.19.12:
resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64@0.19.12:
resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64@0.19.12:
resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm@0.19.12:
resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32@0.19.12:
resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64@0.19.12:
resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el@0.19.12:
resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64@0.19.12:
resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64@0.19.12:
resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x@0.19.12:
resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64@0.19.12:
resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64@0.19.12:
resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64@0.19.12:
resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64@0.19.12:
resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64@0.19.12:
resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32@0.19.12:
resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64@0.19.12:
resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.44.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -7510,6 +7758,15 @@ packages:
'@jridgewell/trace-mapping': 0.3.18
dev: true
/@jridgewell/gen-mapping@0.3.5:
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.2.1
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.25
dev: true
/@jridgewell/resolve-uri@3.1.0:
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
engines: {node: '>=6.0.0'}
@ -7520,6 +7777,11 @@ packages:
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/set-array@1.2.1:
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/source-map@0.3.5:
resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
dependencies:
@ -7542,6 +7804,13 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@jridgewell/trace-mapping@0.3.25:
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@jridgewell/trace-mapping@0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
@ -10573,6 +10842,69 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
/@vitest/coverage-v8@1.4.0(vitest@1.4.0):
resolution: {integrity: sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==}
peerDependencies:
vitest: 1.4.0
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 0.2.3
debug: 4.3.4
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.4
istanbul-reports: 3.1.7
magic-string: 0.30.7
magicast: 0.3.3
picocolors: 1.0.0
std-env: 3.7.0
strip-literal: 2.0.0
test-exclude: 6.0.0
v8-to-istanbul: 9.2.0
vitest: 1.4.0(@types/node@20.11.20)
transitivePeerDependencies:
- supports-color
dev: true
/@vitest/expect@1.4.0:
resolution: {integrity: sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==}
dependencies:
'@vitest/spy': 1.4.0
'@vitest/utils': 1.4.0
chai: 4.4.1
dev: true
/@vitest/runner@1.4.0:
resolution: {integrity: sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==}
dependencies:
'@vitest/utils': 1.4.0
p-limit: 5.0.0
pathe: 1.1.2
dev: true
/@vitest/snapshot@1.4.0:
resolution: {integrity: sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==}
dependencies:
magic-string: 0.30.7
pathe: 1.1.2
pretty-format: 29.7.0
dev: true
/@vitest/spy@1.4.0:
resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==}
dependencies:
tinyspy: 2.2.1
dev: true
/@vitest/utils@1.4.0:
resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==}
dependencies:
diff-sequences: 29.6.3
estree-walker: 3.0.3
loupe: 2.3.7
pretty-format: 29.7.0
dev: true
/@withtyped/client@0.8.4(zod@3.22.4):
resolution: {integrity: sha512-oX19xZRjUASZLWUzQW4LKhLsDgtwFfWU7mgReKiY/tddwvHLoakLqz1o1MvRDuz+EOoYrQM+VpkwJeMsnbNT1w==}
dependencies:
@ -10659,12 +10991,23 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
/acorn-walk@8.3.2:
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
engines: {node: '>=0.4.0'}
dev: true
/acorn@8.10.0:
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/acorn@8.11.3:
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
@ -10907,6 +11250,10 @@ packages:
tslib: 2.6.2
dev: false
/assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true
/ast-types-flow@0.0.7:
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
dev: true
@ -11233,6 +11580,11 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
dev: true
/cache-content-type@1.0.1:
resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==}
engines: {node: '>= 6.0.0'}
@ -11350,6 +11702,19 @@ packages:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
dev: true
/chai@4.4.1:
resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==}
engines: {node: '>=4'}
dependencies:
assertion-error: 1.1.0
check-error: 1.0.3
deep-eql: 4.1.3
get-func-name: 2.0.2
loupe: 2.3.7
pathval: 1.1.1
type-detect: 4.0.8
dev: true
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@ -11398,6 +11763,12 @@ packages:
/chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
/check-error@1.0.3:
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
dependencies:
get-func-name: 2.0.2
dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
@ -12163,6 +12534,13 @@ packages:
optional: true
dev: true
/deep-eql@4.1.3:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
engines: {node: '>=6'}
dependencies:
type-detect: 4.0.8
dev: true
/deep-equal@1.0.1:
resolution: {integrity: sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=}
@ -12574,6 +12952,37 @@ packages:
is-symbol: 1.0.4
dev: true
/esbuild@0.19.12:
resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/aix-ppc64': 0.19.12
'@esbuild/android-arm': 0.19.12
'@esbuild/android-arm64': 0.19.12
'@esbuild/android-x64': 0.19.12
'@esbuild/darwin-arm64': 0.19.12
'@esbuild/darwin-x64': 0.19.12
'@esbuild/freebsd-arm64': 0.19.12
'@esbuild/freebsd-x64': 0.19.12
'@esbuild/linux-arm': 0.19.12
'@esbuild/linux-arm64': 0.19.12
'@esbuild/linux-ia32': 0.19.12
'@esbuild/linux-loong64': 0.19.12
'@esbuild/linux-mips64el': 0.19.12
'@esbuild/linux-ppc64': 0.19.12
'@esbuild/linux-riscv64': 0.19.12
'@esbuild/linux-s390x': 0.19.12
'@esbuild/linux-x64': 0.19.12
'@esbuild/netbsd-x64': 0.19.12
'@esbuild/openbsd-x64': 0.19.12
'@esbuild/sunos-x64': 0.19.12
'@esbuild/win32-arm64': 0.19.12
'@esbuild/win32-ia32': 0.19.12
'@esbuild/win32-x64': 0.19.12
dev: true
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
@ -13104,6 +13513,12 @@ packages:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
dependencies:
'@types/estree': 1.0.5
dev: true
/esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@ -13680,6 +14095,10 @@ packages:
engines: {node: '>=18'}
dev: false
/get-func-name@2.0.2:
resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
dev: true
/get-intrinsic@1.1.3:
resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==}
dependencies:
@ -14893,6 +15312,11 @@ packages:
engines: {node: '>=8'}
dev: true
/istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
dev: true
/istanbul-lib-instrument@5.2.1:
resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
engines: {node: '>=8'}
@ -14928,6 +15352,15 @@ packages:
supports-color: 7.2.0
dev: true
/istanbul-lib-report@3.0.1:
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
engines: {node: '>=10'}
dependencies:
istanbul-lib-coverage: 3.2.2
make-dir: 4.0.0
supports-color: 7.2.0
dev: true
/istanbul-lib-source-maps@4.0.1:
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
engines: {node: '>=10'}
@ -14939,6 +15372,17 @@ packages:
- supports-color
dev: true
/istanbul-lib-source-maps@5.0.4:
resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==}
engines: {node: '>=10'}
dependencies:
'@jridgewell/trace-mapping': 0.3.25
debug: 4.3.4
istanbul-lib-coverage: 3.2.2
transitivePeerDependencies:
- supports-color
dev: true
/istanbul-reports@3.1.5:
resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==}
engines: {node: '>=8'}
@ -14947,6 +15391,14 @@ packages:
istanbul-lib-report: 3.0.0
dev: true
/istanbul-reports@3.1.7:
resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
engines: {node: '>=8'}
dependencies:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
dev: true
/jest-changed-files@29.7.0:
resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -15711,6 +16163,10 @@ packages:
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
/js-tokens@8.0.3:
resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==}
dev: true
/js-types@1.0.0:
resolution: {integrity: sha512-bfwqBW9cC/Lp7xcRpug7YrXm0IVw+T9e3g4mCYnv0Pjr3zIzU9PCQElYU9oSGAWzXlbdl9X5SAMPejO9sxkeUw==}
engines: {node: '>=0.10.0'}
@ -16298,6 +16754,14 @@ packages:
engines: {node: '>= 12.13.0'}
dev: true
/local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
dependencies:
mlly: 1.6.1
pkg-types: 1.0.3
dev: true
/locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@ -16441,6 +16905,12 @@ packages:
dependencies:
js-tokens: 4.0.0
/loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
dependencies:
get-func-name: 2.0.2
dev: true
/lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies:
@ -16498,6 +16968,14 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/magicast@0.3.3:
resolution: {integrity: sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==}
dependencies:
'@babel/parser': 7.24.0
'@babel/types': 7.24.0
source-map-js: 1.0.2
dev: true
/make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
@ -16505,6 +16983,13 @@ packages:
semver: 6.3.1
dev: true
/make-dir@4.0.0:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
dependencies:
semver: 7.6.0
dev: true
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
@ -17160,6 +17645,15 @@ packages:
hasBin: true
dev: false
/mlly@1.6.1:
resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==}
dependencies:
acorn: 8.11.3
pathe: 1.1.2
pkg-types: 1.0.3
ufo: 1.5.2
dev: true
/module-details-from-path@1.0.3:
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
dev: false
@ -17753,7 +18247,6 @@ packages:
engines: {node: '>=18'}
dependencies:
yocto-queue: 1.0.0
dev: false
/p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
@ -17972,6 +18465,14 @@ packages:
engines: {node: '>=8'}
dev: true
/pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
dev: true
/pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
/pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
dev: true
@ -18133,6 +18634,14 @@ packages:
find-up: 5.0.0
dev: true
/pkg-types@1.0.3:
resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
dependencies:
jsonc-parser: 3.2.0
mlly: 1.6.1
pathe: 1.1.2
dev: true
/pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@ -19647,6 +20156,10 @@ packages:
get-intrinsic: 1.1.3
object-inspect: 1.12.2
/siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
dev: true
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@ -19887,6 +20400,10 @@ packages:
escape-string-regexp: 2.0.0
dev: true
/stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
dev: true
/state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
dev: true
@ -19904,6 +20421,10 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/std-env@3.7.0:
resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
dev: true
/stdin-discarder@0.2.2:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
@ -20069,6 +20590,12 @@ packages:
engines: {node: '>=8'}
dev: true
/strip-literal@2.0.0:
resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==}
dependencies:
js-tokens: 8.0.3
dev: true
/strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
dev: false
@ -20548,10 +21075,24 @@ packages:
globrex: 0.1.2
dev: true
/tinybench@2.6.0:
resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==}
dev: true
/tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
dev: true
/tinypool@0.8.2:
resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==}
engines: {node: '>=14.0.0'}
dev: true
/tinyspy@2.2.1:
resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
engines: {node: '>=14.0.0'}
dev: true
/titleize@4.0.0:
resolution: {integrity: sha512-ZgUJ1K83rhdu7uh7EHAC2BgY5DzoX8V5rTvoWI4vFysggi6YjLe5gUXABPWAU7VkvGP7P/0YiWq+dcPeYDsf1g==}
engines: {node: '>=18'}
@ -20829,6 +21370,10 @@ packages:
resolution: {integrity: sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg==}
dev: true
/ufo@1.5.2:
resolution: {integrity: sha512-eiutMaL0J2MKdhcOM1tUy13pIrYnyR87fEd8STJQFrrAwImwvlXkxlZEjaKah8r2viPohld08lt73QfLG1NxMg==}
dev: true
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@ -21059,6 +21604,15 @@ packages:
convert-source-map: 1.9.0
dev: true
/v8-to-istanbul@9.2.0:
resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
engines: {node: '>=10.12.0'}
dependencies:
'@jridgewell/trace-mapping': 0.3.18
'@types/istanbul-lib-coverage': 2.0.4
convert-source-map: 2.0.0
dev: true
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies:
@ -21105,6 +21659,119 @@ packages:
vfile-message: 4.0.2
dev: true
/vite-node@1.4.0(@types/node@20.11.20):
resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
dependencies:
cac: 6.7.14
debug: 4.3.4
pathe: 1.1.2
picocolors: 1.0.0
vite: 5.1.6(@types/node@20.11.20)
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- sass
- stylus
- sugarss
- supports-color
- terser
dev: true
/vite@5.1.6(@types/node@20.11.20):
resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
dependencies:
'@types/node': 20.11.20
esbuild: 0.19.12
postcss: 8.4.35
rollup: 4.12.0
optionalDependencies:
fsevents: 2.3.3
dev: true
/vitest@1.4.0(@types/node@20.11.20):
resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/node': ^18.0.0 || >=20.0.0
'@vitest/browser': 1.4.0
'@vitest/ui': 1.4.0
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@types/node':
optional: true
'@vitest/browser':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
dependencies:
'@types/node': 20.11.20
'@vitest/expect': 1.4.0
'@vitest/runner': 1.4.0
'@vitest/snapshot': 1.4.0
'@vitest/spy': 1.4.0
'@vitest/utils': 1.4.0
acorn-walk: 8.3.2
chai: 4.4.1
debug: 4.3.4
execa: 8.0.1
local-pkg: 0.5.0
magic-string: 0.30.7
pathe: 1.1.2
picocolors: 1.0.0
std-env: 3.7.0
strip-literal: 2.0.0
tinybench: 2.6.0
tinypool: 0.8.2
vite: 5.1.6(@types/node@20.11.20)
vite-node: 1.4.0(@types/node@20.11.20)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
- lightningcss
- sass
- stylus
- sugarss
- supports-color
- terser
dev: true
/void-elements@3.1.0:
resolution: {integrity: sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=}
engines: {node: '>=0.10.0'}
@ -21241,6 +21908,15 @@ packages:
isexe: 2.0.0
dev: true
/why-is-node-running@2.2.2:
resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
engines: {node: '>=8'}
hasBin: true
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
dev: true
/word-wrap@1.2.3:
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
engines: {node: '>=0.10.0'}