0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00

feat(connector): add kakao connector (#1826)

* feat(connector): add kakao connector

* fix(connector): kakao when user profile is null bug fix

* chore(connector): update kakao connector readme

* chore(connector): delete changelog

* chore(connector): delete unused lib

* test(connector): update test

* fix: pnpm lock

* fix: fix test

Co-authored-by: wangsijie <wangsijie@silverhand.io>
This commit is contained in:
Kyungyoon Kim 2022-08-31 11:59:59 +09:00 committed by GitHub
parent d952d8660d
commit 1f9e820eb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 621 additions and 1 deletions

View file

@ -0,0 +1,54 @@
# Kakao Connector
The Kakao connector provides a succinct way for your application to use Kakaos OAuth 2.0 authentication system.
**Table of contents**
- [Set up a project in the Kakao Devlopers Console](#set-up-a-project-in-the-kakao-devlopers-console)
- [Configure Kakao Login](#configure-kakao-login)
- [Activate Kakao Login](#activate-kakao-login)
- [Privacy Setting](#privacy-setting)
- [Security Setting (Optional)](#security-setting-optional)
- [Configure Logto](#configure-logto)
- [Config types](#config-types)
- [clientId](#clientid)
- [clientSecret](#clientseceret)
## Set up a project in the Kakao Devlopers Console
- Visit the [Kakao Developers Console](https://developers.kakao.com/console/app) and sign in with your Kakao account.
- Click the **Add an application** to create new project or choose exist project.
## Configure Kakao Login
### Activate Kakao Login
- Click the **Product Settings -> Kakao Login** from the menu.
- Turn on `Kakao Login Activation`
- Add below URL into `Redirect URI`
- `http(s)://YOUR_URL/callback/kakao-universal`
- (Please replace `YOUR_URL` with your `Logto` URL, and choose `http` or `https` on your situation.)
### Privacy Setting
- Click the **Product Settings -> Kakao Login -> Consent Item** from the menu.
- Change state of `Nickname`, `Profile image`, and `Email` to **Required consent** (You might not able to change `Email` to **Required consent** because of your project setting.)
### Security Setting (Optional)
- Click the **Product Settings -> Kakao Login -> Security** from the menu.
- Click the `Client secret code` to generate secret code.
- Change `Activation state` to Enable. (If you enable it, `secret code` is necessary.)
## Configure Logto
### Config types
| Name | Type |
|--------------|---------|
| clientId | string |
| clientSecret | string? |
#### clientId
`clientId` is `REST API key` of your project.
(You can find it from `summary` of your project from Kakao developers console.)
#### clientSeceret
`clientSecret` is `Secret Code` of your project.
(Please check [Security Setting (Optional)](#security-setting-optional))

View file

@ -0,0 +1,4 @@
{
"clientId": "<client-id>",
"clientSecret": "<client-secret>"
}

View file

@ -0,0 +1,7 @@
import { Config, merge } from '@silverhand/jest-config';
const config: Config.InitialOptions = merge({
setupFilesAfterEnv: ['jest-matcher-specific-error'],
});
export default config;

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 256 256">
<path fill="#FFE812"
d="M256 236c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20V20C0 8.954 8.954 0 20 0h216c11.046 0 20 8.954 20 20v216z"/>
<path d="M128 36C70.562 36 24 72.713 24 118c0 29.279 19.466 54.97 48.748 69.477-1.593 5.494-10.237 35.344-10.581 37.689 0 0-.207 1.762.934 2.434s2.483.15 2.483.15c3.272-.457 37.943-24.811 43.944-29.04 5.995.849 12.168 1.29 18.472 1.29 57.438 0 104-36.712 104-82 0-45.287-46.562-82-104-82z"/>
<path fill="#FFE812"
d="M70.5 146.625c-3.309 0-6-2.57-6-5.73V105.25h-9.362c-3.247 0-5.888-2.636-5.888-5.875s2.642-5.875 5.888-5.875h30.724c3.247 0 5.888 2.636 5.888 5.875s-2.642 5.875-5.888 5.875H76.5v35.645c0 3.16-2.691 5.73-6 5.73zM123.112 146.547c-2.502 0-4.416-1.016-4.993-2.65l-2.971-7.778-18.296-.001-2.973 7.783c-.575 1.631-2.488 2.646-4.99 2.646a9.155 9.155 0 0 1-3.814-.828c-1.654-.763-3.244-2.861-1.422-8.52l14.352-37.776c1.011-2.873 4.082-5.833 7.99-5.922 3.919.088 6.99 3.049 8.003 5.928l14.346 37.759c1.826 5.672.236 7.771-1.418 8.532a9.176 9.176 0 0 1-3.814.827c-.001 0 0 0 0 0zm-11.119-21.056L106 108.466l-5.993 17.025h11.986zM138 145.75c-3.171 0-5.75-2.468-5.75-5.5V99.5c0-3.309 2.748-6 6.125-6s6.125 2.691 6.125 6v35.25h12.75c3.171 0 5.75 2.468 5.75 5.5s-2.579 5.5-5.75 5.5H138zM171.334 146.547c-3.309 0-6-2.691-6-6V99.5c0-3.309 2.691-6 6-6s6 2.691 6 6v12.896l16.74-16.74c.861-.861 2.044-1.335 3.328-1.335 1.498 0 3.002.646 4.129 1.772 1.051 1.05 1.678 2.401 1.764 3.804.087 1.415-.384 2.712-1.324 3.653l-13.673 13.671 14.769 19.566a5.951 5.951 0 0 1 1.152 4.445 5.956 5.956 0 0 1-2.328 3.957 5.94 5.94 0 0 1-3.609 1.211 5.953 5.953 0 0 1-4.793-2.385l-14.071-18.644-2.082 2.082v13.091a6.01 6.01 0 0 1-6.002 6.003z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,54 @@
{
"name": "@logto/connector-kakao",
"version": "1.0.0-beta.6",
"description": "Kakao connector implementation.",
"main": "./lib/index.js",
"exports": "./lib/index.js",
"author": "Kyungyoon Kim. <ruddbs5302@gmail.com>",
"license": "MPL-2.0",
"private": true,
"files": [
"lib",
"docs",
"logo.svg",
"README.md"
],
"scripts": {
"precommit": "lint-staged",
"build": "rm -rf lib/ && tsc -p tsconfig.build.json",
"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": "jest",
"test:coverage": "jest --coverage --silent",
"prepack": "pnpm build"
},
"dependencies": {
"@logto/connector-core": "^1.0.0-beta.5",
"@silverhand/essentials": "^1.2.0",
"@silverhand/jest-config": "1.0.0-rc.3",
"got": "^11.8.2",
"zod": "^3.14.3"
},
"devDependencies": {
"@jest/types": "^28.1.3",
"@silverhand/eslint-config": "1.0.0-rc.2",
"@silverhand/ts-config": "1.0.0-rc.2",
"@types/jest": "^28.1.6",
"@types/node": "^16.3.1",
"eslint": "^8.21.0",
"jest": "^28.1.3",
"jest-matcher-specific-error": "^1.0.0",
"lint-staged": "^13.0.0",
"nock": "^13.2.2",
"prettier": "^2.7.1",
"typescript": "^4.7.4"
},
"engines": {
"node": "^16.0.0"
},
"eslintConfig": {
"extends": "@silverhand"
},
"prettier": "@silverhand/eslint-config/.prettierrc"
}

View file

@ -0,0 +1,30 @@
import { ConnectorMetadata, ConnectorPlatform, ConnectorType } from '@logto/connector-core';
export const authorizationEndpoint = 'https://kauth.kakao.com/oauth/authorize';
export const accessTokenEndpoint = 'https://kauth.kakao.com/oauth/token';
export const userInfoEndpoint = 'https://kapi.kakao.com/v2/user/me';
export const defaultMetadata: ConnectorMetadata = {
id: 'kakao-universal',
target: 'kakao',
type: ConnectorType.Social,
platform: ConnectorPlatform.Universal,
name: {
en: 'Kakao',
'zh-CN': 'Kakao',
'tr-TR': 'Kakao',
'ko-KR': '카카오',
},
logo: './logo.svg',
logoDark: null,
description: {
en: 'Kakao is the most famous social network servcie provider in South Korea',
'zh-CN': 'Kakao is the most famous social network servcie provider in South Korea',
'tr-TR': 'Kakao is the most famous social network servcie provider in South Korea',
'ko-KR': '카카오는 한국에서 가장 유명한 SNS 서비스 제공자 입니다.',
},
readme: './README.md',
configTemplate: './docs/config-template.json',
};
export const defaultTimeout = 5000;

View file

@ -0,0 +1,138 @@
import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-core';
import nock from 'nock';
import createConnector, { getAccessToken } from '.';
import { accessTokenEndpoint, authorizationEndpoint, userInfoEndpoint } from './constant';
import { mockedConfig } from './mock';
const getConfig = jest.fn().mockResolvedValue(mockedConfig);
describe('kakao connector', () => {
describe('getAuthorizationUri', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should get a valid authorizationUri with redirectUri and state', async () => {
const connector = await createConnector({ getConfig });
const authorizationUri = await connector.getAuthorizationUri({
state: 'some_state',
redirectUri: 'http://localhost:3000/callback',
});
expect(authorizationUri).toEqual(
`${authorizationEndpoint}?client_id=%3Cclient-id%3E&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback&response_type=code&state=some_state`
);
});
});
describe('getAccessToken', () => {
afterEach(() => {
nock.cleanAll();
jest.clearAllMocks();
});
it('should get an accessToken by exchanging with code', async () => {
nock(accessTokenEndpoint).post('').reply(200, {
access_token: 'access_token',
scope: 'scope',
token_type: 'token_type',
});
const { accessToken } = await getAccessToken(mockedConfig, {
code: 'code',
redirectUri: 'dummyRedirectUri',
});
expect(accessToken).toEqual('access_token');
});
it('throws SocialAuthCodeInvalid error if accessToken not found in response', async () => {
nock(accessTokenEndpoint)
.post('')
.reply(200, { access_token: '', scope: 'scope', token_type: 'token_type' });
await expect(
getAccessToken(mockedConfig, { code: 'code', redirectUri: 'dummyRedirectUri' })
).rejects.toMatchError(new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
});
});
describe('getUserInfo', () => {
beforeEach(() => {
nock(accessTokenEndpoint).post('').reply(200, {
access_token: 'access_token',
scope: 'scope',
token_type: 'token_type',
});
});
afterEach(() => {
nock.cleanAll();
jest.clearAllMocks();
});
it('should get valid SocialUserInfo', async () => {
nock(userInfoEndpoint)
.post('')
.reply(200, {
id: 1_234_567_890,
kakao_account: {
is_email_valid: true,
email: 'ruddbs5302@gmail.com',
profile: {
nickname: 'pemassi',
profile_image_url: 'https://github.com/images/error/octocat_happy.gif',
is_default_image: false,
},
},
});
const connector = await createConnector({ getConfig });
const socialUserInfo = await connector.getUserInfo({
code: 'code',
redirectUri: 'redirectUri',
});
expect(socialUserInfo).toMatchObject({
id: '1234567890',
avatar: 'https://github.com/images/error/octocat_happy.gif',
name: 'pemassi',
email: 'ruddbs5302@gmail.com',
});
});
it('throws SocialAccessTokenInvalid error if remote response code is 401', async () => {
nock(userInfoEndpoint).post('').reply(401);
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({ code: 'code', redirectUri: '' })).rejects.toMatchError(
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
);
});
it('throws General error', async () => {
nock(userInfoEndpoint).post('').reply(200, {
sub: '1234567890',
name: 'monalisa octocat',
given_name: 'monalisa',
family_name: 'octocat',
picture: 'https://github.com/images/error/octocat_happy.gif',
email: 'octocat@google.com',
email_verified: true,
locale: 'en',
});
const connector = await createConnector({ getConfig });
await expect(
connector.getUserInfo({
error: 'general_error',
error_description: 'General error encountered.',
})
).rejects.toMatchError(
new ConnectorError(
ConnectorErrorCodes.General,
'{"error":"general_error","error_description":"General error encountered."}'
)
);
});
it('throws unrecognized error', async () => {
nock(userInfoEndpoint).post('').reply(500);
const connector = await createConnector({ getConfig });
await expect(connector.getUserInfo({ code: 'code', redirectUri: '' })).rejects.toThrow();
});
});
});

View file

@ -0,0 +1,156 @@
/**
* The Implementation of OpenID Connect of Kakao.
* https://developers.kakao.com/docs/latest/en/kakaologin/rest-api
*/
import {
ConnectorError,
ConnectorErrorCodes,
CreateConnector,
GetAuthorizationUri,
GetConnectorConfig,
GetUserInfo,
SocialConnector,
validateConfig,
} from '@logto/connector-core';
import { assert, conditional } from '@silverhand/essentials';
import got, { HTTPError } from 'got';
import {
accessTokenEndpoint,
authorizationEndpoint,
defaultMetadata,
defaultTimeout,
userInfoEndpoint,
} from './constant';
import {
accessTokenResponseGuard,
authResponseGuard,
KakaoConfig,
kakaoConfigGuard,
userInfoResponseGuard,
} from './types';
const getAuthorizationUri =
(getConfig: GetConnectorConfig): GetAuthorizationUri =>
async ({ state, redirectUri }) => {
const config = await getConfig(defaultMetadata.id);
validateConfig<KakaoConfig>(config, kakaoConfigGuard);
const queryParameters = new URLSearchParams({
client_id: config.clientId,
redirect_uri: redirectUri,
response_type: 'code',
state,
});
return `${authorizationEndpoint}?${queryParameters.toString()}`;
};
export const getAccessToken = async (
config: KakaoConfig,
codeObject: { code: string; redirectUri: string }
) => {
const { code, redirectUri } = codeObject;
const { clientId, clientSecret } = config;
// NoteNeed to decodeURIComponent on code
// https://stackoverflow.com/questions/51058256/google-api-node-js-invalid-grant-malformed-auth-code
const httpResponse = await got.post(accessTokenEndpoint, {
form: {
code: decodeURIComponent(code),
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
},
timeout: defaultTimeout,
});
const result = accessTokenResponseGuard.safeParse(JSON.parse(httpResponse.body));
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error.message);
}
const { access_token: accessToken } = result.data;
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
return { accessToken };
};
const getUserInfo =
(getConfig: GetConnectorConfig): GetUserInfo =>
// eslint-disable-next-line complexity
async (data) => {
const { code, redirectUri } = await authorizationCallbackHandler(data);
const config = await getConfig(defaultMetadata.id);
validateConfig<KakaoConfig>(config, kakaoConfigGuard);
const { accessToken } = await getAccessToken(config, { code, redirectUri });
try {
const httpResponse = await got.post(userInfoEndpoint, {
headers: {
authorization: `Bearer ${accessToken}`,
},
timeout: defaultTimeout,
});
const result = userInfoResponseGuard.safeParse(JSON.parse(httpResponse.body));
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error.message);
}
const { id, kakao_account } = result.data;
const { is_email_valid, email, profile } = kakao_account ?? {
is_email_valid: null,
profile: null,
email: null,
};
return {
id: id.toString(),
avatar: conditional(profile && !profile.is_default_image && profile.profile_image_url),
email: conditional(is_email_valid && email),
name: conditional(profile?.nickname),
};
} catch (error: unknown) {
return getUserInfoErrorHandler(error);
}
};
const authorizationCallbackHandler = async (parameterObject: unknown) => {
const result = authResponseGuard.safeParse(parameterObject);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));
}
return result.data;
};
const getUserInfoErrorHandler = (error: unknown) => {
if (error instanceof HTTPError) {
const { statusCode, body: rawBody } = error.response;
if (statusCode === 401) {
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
}
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(rawBody));
}
throw error;
};
const createKakaoConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
return {
metadata: defaultMetadata,
configGuard: kakaoConfigGuard,
getAuthorizationUri: getAuthorizationUri(getConfig),
getUserInfo: getUserInfo(getConfig),
};
};
export default createKakaoConnector;

View file

@ -0,0 +1,4 @@
export const mockedConfig = {
clientId: '<client-id>',
clientSecret: '<client-secret>',
};

View file

@ -0,0 +1,40 @@
import { z } from 'zod';
export const kakaoConfigGuard = z.object({
clientId: z.string(),
clientSecret: z.string().optional(),
});
export type KakaoConfig = z.infer<typeof kakaoConfigGuard>;
export const accessTokenResponseGuard = z.object({
access_token: z.string(),
scope: z.string().optional(),
token_type: z.string(),
});
export type AccessTokenResponse = z.infer<typeof accessTokenResponseGuard>;
export const userInfoResponseGuard = z.object({
id: z.number(),
kakao_account: z
.object({
is_email_valid: z.boolean().optional(),
email: z.string().optional(),
profile: z
.object({
nickname: z.string().optional(),
profile_image_url: z.string().optional(),
is_default_image: z.boolean().optional(),
})
.optional(),
})
.optional(),
});
export type UserInfoResponse = z.infer<typeof userInfoResponseGuard>;
export const authResponseGuard = z.object({
code: z.string(),
redirectUri: z.string(),
});

View file

@ -0,0 +1,12 @@
{
"extends": "@silverhand/ts-config/tsconfig.base",
"compilerOptions": {
"outDir": "lib",
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
}

View file

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.base",
"include": [
"src"
],
"exclude": [
"src/**/*.test.ts"
]
}

View file

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.base",
"compilerOptions": {
"types": [
"node",
"jest",
"jest-matcher-specific-error"
]
},
"include": [
"src",
"jest.config.ts"
]
}

View file

@ -0,0 +1,7 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"isolatedModules": false,
"allowJs": true
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@logto/core",
"version": "1.0.0-beta.6",
"version": "1.0.0-beta.5",
"description": "The open source identity solution.",
"main": "build/index.js",
"author": "Silverhand Inc. <contact@silverhand.io>",
@ -34,6 +34,7 @@
"@logto/connector-twilio-sms": "^1.0.0-beta.6",
"@logto/connector-wechat-native": "^1.0.0-beta.6",
"@logto/connector-wechat-web": "^1.0.0-beta.6",
"@logto/connector-kakao": "^1.0.0-beta.6",
"@logto/phrases": "^1.0.0-beta.6",
"@logto/schemas": "^1.0.0-beta.6",
"@logto/shared": "^1.0.0-beta.6",

View file

@ -17,6 +17,7 @@ export const defaultConnectorPackages = [
'@logto/connector-twilio-sms',
'@logto/connector-wechat-web',
'@logto/connector-wechat-native',
'@logto/connector-kakao',
];
const notImplemented = () => {

View file

@ -88,6 +88,12 @@ const wechatNativeConnector = {
config: {},
createdAt: 1_646_382_233_000,
};
const kakaoConnector = {
id: 'kakao-universal',
enabled: false,
config: {},
createdAt: 1_646_382_233_000,
};
const connectors = [
alipayConnector,
@ -104,6 +110,7 @@ const connectors = [
twilioSmsConnector,
wechatConnector,
wechatNativeConnector,
kakaoConnector,
];
const findAllConnectors = jest.fn(async () => connectors);

75
pnpm-lock.yaml generated
View file

@ -422,6 +422,45 @@ importers:
prettier: 2.7.1
typescript: 4.7.4
packages/connector-kakao:
specifiers:
'@jest/types': ^28.1.3
'@logto/connector-core': ^1.0.0-beta.5
'@silverhand/eslint-config': 1.0.0-rc.2
'@silverhand/essentials': ^1.2.0
'@silverhand/jest-config': 1.0.0-rc.3
'@silverhand/ts-config': 1.0.0-rc.2
'@types/jest': ^28.1.6
'@types/node': ^16.3.1
eslint: ^8.21.0
got: ^11.8.2
jest: ^28.1.3
jest-matcher-specific-error: ^1.0.0
lint-staged: ^13.0.0
nock: ^13.2.2
prettier: ^2.7.1
typescript: ^4.7.4
zod: ^3.14.3
dependencies:
'@logto/connector-core': link:../connector-core
'@silverhand/essentials': 1.2.0
'@silverhand/jest-config': 1.0.0-rc.3_bi2kohzqnxavgozw3csgny5hju
got: 11.8.3
zod: 3.14.3
devDependencies:
'@jest/types': 28.1.3
'@silverhand/eslint-config': 1.0.0-rc.2_swk2g7ygmfleszo5c33j4vooni
'@silverhand/ts-config': 1.0.0-rc.2_typescript@4.7.4
'@types/jest': 28.1.6
'@types/node': 16.11.12
eslint: 8.21.0
jest: 28.1.3_@types+node@16.11.12
jest-matcher-specific-error: 1.0.0
lint-staged: 13.0.0
nock: 13.2.2
prettier: 2.7.1
typescript: 4.7.4
packages/connector-mock-email:
specifiers:
'@logto/connector-core': ^1.0.0-beta.6
@ -828,6 +867,7 @@ importers:
'@logto/connector-facebook': ^1.0.0-beta.6
'@logto/connector-github': ^1.0.0-beta.6
'@logto/connector-google': ^1.0.0-beta.6
'@logto/connector-kakao': ^1.0.0-beta.6
'@logto/connector-sendgrid-email': ^1.0.0-beta.6
'@logto/connector-smtp': ^1.0.0-beta.6
'@logto/connector-twilio-sms': ^1.0.0-beta.6
@ -913,6 +953,7 @@ importers:
'@logto/connector-facebook': link:../connector-facebook
'@logto/connector-github': link:../connector-github
'@logto/connector-google': link:../connector-google
'@logto/connector-kakao': link:../connector-kakao
'@logto/connector-sendgrid-email': link:../connector-sendgrid-mail
'@logto/connector-smtp': link:../connector-smtp
'@logto/connector-twilio-sms': link:../connector-twilio-sms
@ -2317,6 +2358,7 @@ packages:
pacote: 13.4.1
semver: 7.3.7
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -2347,6 +2389,7 @@ packages:
p-waterfall: 2.1.1
semver: 7.3.7
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -2487,6 +2530,7 @@ packages:
whatwg-url: 8.7.0
yargs-parser: 20.2.4
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -2684,6 +2728,7 @@ packages:
npm-registry-fetch: 9.0.0
npmlog: 4.1.2
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -2713,6 +2758,7 @@ packages:
pify: 5.0.0
read-package-json: 3.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -2751,6 +2797,7 @@ packages:
npmlog: 4.1.2
tar: 6.1.11
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -2849,6 +2896,7 @@ packages:
pacote: 13.4.1
semver: 7.3.7
transitivePeerDependencies:
- bluebird
- encoding
- supports-color
dev: true
@ -2894,6 +2942,7 @@ packages:
'@npmcli/run-script': 3.0.2
npmlog: 4.1.2
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -2995,6 +3044,7 @@ packages:
slash: 3.0.0
write-json-file: 4.3.0
transitivePeerDependencies:
- bluebird
- encoding
- supports-color
dev: true
@ -3224,6 +3274,7 @@ packages:
treeverse: 2.0.0
walk-up-path: 1.0.0
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -3259,6 +3310,8 @@ packages:
promise-retry: 2.0.1
semver: 7.3.7
which: 2.0.2
transitivePeerDependencies:
- bluebird
dev: true
/@npmcli/installed-package-contents/1.0.7:
@ -3289,6 +3342,7 @@ packages:
pacote: 13.4.1
semver: 7.3.7
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -3340,6 +3394,7 @@ packages:
node-gyp: 9.0.0
read-package-json-fast: 2.0.3
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -5981,6 +6036,8 @@ packages:
ssri: 8.0.1
tar: 6.1.11
unique-filename: 1.1.1
transitivePeerDependencies:
- bluebird
dev: true
/cacache/16.1.0:
@ -6005,6 +6062,8 @@ packages:
ssri: 9.0.1
tar: 6.1.11
unique-filename: 1.1.1
transitivePeerDependencies:
- bluebird
dev: true
/cache-content-type/1.0.1:
@ -10594,6 +10653,7 @@ packages:
import-local: 3.1.0
npmlog: 4.1.2
transitivePeerDependencies:
- bluebird
- encoding
- supports-color
dev: true
@ -10627,6 +10687,7 @@ packages:
npm-package-arg: 8.1.5
npm-registry-fetch: 11.0.0
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -10640,6 +10701,7 @@ packages:
semver: 7.3.7
ssri: 8.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -10962,6 +11024,7 @@ packages:
socks-proxy-agent: 6.1.1
ssri: 9.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -10985,6 +11048,7 @@ packages:
socks-proxy-agent: 5.0.1
ssri: 8.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -11009,6 +11073,7 @@ packages:
socks-proxy-agent: 6.1.1
ssri: 8.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -11901,6 +11966,7 @@ packages:
tar: 6.1.11
which: 2.0.2
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -12086,6 +12152,7 @@ packages:
minizlib: 2.1.2
npm-package-arg: 8.1.5
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -12101,6 +12168,7 @@ packages:
npm-package-arg: 9.0.2
proc-log: 2.0.1
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -12117,6 +12185,7 @@ packages:
minizlib: 2.1.2
npm-package-arg: 8.1.5
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -12517,6 +12586,7 @@ packages:
ssri: 9.0.1
tar: 6.1.11
transitivePeerDependencies:
- bluebird
- supports-color
dev: true
@ -13246,6 +13316,11 @@ packages:
/promise-inflight/1.0.1:
resolution: {integrity: sha1-mEcocL8igTL8vdhoEputEsPAKeM=}
peerDependencies:
bluebird: '*'
peerDependenciesMeta:
bluebird:
optional: true
dev: true
/promise-retry/2.0.1: