mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(core): add oidc config to the response of application apis (#536)
This commit is contained in:
parent
2f22a81a8f
commit
d238168ebf
11 changed files with 119 additions and 53 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { Application } from '@logto/schemas';
|
import { Application, ApplicationDTO } from '@logto/schemas';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -31,26 +31,16 @@ import { noSpaceRegex } from '@/utilities/regex';
|
||||||
import DeleteForm from './components/DeleteForm';
|
import DeleteForm from './components/DeleteForm';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
// TODO LOG-1908: OidcConfig in Application Details
|
|
||||||
type OidcConfig = {
|
|
||||||
authorization_endpoint: string;
|
|
||||||
userinfo_endpoint: string;
|
|
||||||
token_endpoint: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ApplicationDetails = () => {
|
const ApplicationDetails = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
const { data, error, mutate } = useSWR<Application, RequestError>(
|
const { data, error, mutate } = useSWR<ApplicationDTO, RequestError>(
|
||||||
id && `/api/applications/${id}`
|
id && `/api/applications/${id}`
|
||||||
);
|
);
|
||||||
// TODO LOG-1908: OidcConfig in Application Details
|
|
||||||
const { data: oidcConfig, error: fetchOidcConfigError } = useSWR<OidcConfig, RequestError>(
|
const isLoading = !data && !error;
|
||||||
'/oidc/.well-known/openid-configuration'
|
|
||||||
);
|
|
||||||
const isLoading = !data && !error && !oidcConfig && !fetchOidcConfigError;
|
|
||||||
|
|
||||||
const [isReadmeOpen, setIsReadmeOpen] = useState(false);
|
const [isReadmeOpen, setIsReadmeOpen] = useState(false);
|
||||||
|
|
||||||
|
@ -90,14 +80,14 @@ const ApplicationDetails = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.json<Application>();
|
.json<ApplicationDTO>();
|
||||||
void mutate(updatedApplication);
|
void mutate(updatedApplication);
|
||||||
toast.success(t('application_details.save_success'));
|
toast.success(t('application_details.save_success'));
|
||||||
});
|
});
|
||||||
|
|
||||||
const isAdvancedSettings = location.pathname.includes('advanced-settings');
|
const isAdvancedSettings = location.pathname.includes('advanced-settings');
|
||||||
|
|
||||||
const SettingsPage = oidcConfig && (
|
const SettingsPage = data && (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
isRequired
|
isRequired
|
||||||
|
@ -113,7 +103,10 @@ const ApplicationDetails = () => {
|
||||||
title="admin_console.application_details.authorization_endpoint"
|
title="admin_console.application_details.authorization_endpoint"
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
>
|
>
|
||||||
<CopyToClipboard className={styles.textField} value={oidcConfig.authorization_endpoint} />
|
<CopyToClipboard
|
||||||
|
className={styles.textField}
|
||||||
|
value={data.oidcConfig.authorizationEndpoint}
|
||||||
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField
|
<FormField
|
||||||
isRequired
|
isRequired
|
||||||
|
@ -170,13 +163,13 @@ const ApplicationDetails = () => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const AdvancedSettingsPage = oidcConfig && (
|
const AdvancedSettingsPage = data && (
|
||||||
<>
|
<>
|
||||||
<FormField title="admin_console.application_details.token_endpoint">
|
<FormField title="admin_console.application_details.token_endpoint">
|
||||||
<CopyToClipboard className={styles.textField} value={oidcConfig.token_endpoint} />
|
<CopyToClipboard className={styles.textField} value={data.oidcConfig.tokenEndpoint} />
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField title="admin_console.application_details.user_info_endpoint">
|
<FormField title="admin_console.application_details.user_info_endpoint">
|
||||||
<CopyToClipboard className={styles.textField} value={oidcConfig.userinfo_endpoint} />
|
<CopyToClipboard className={styles.textField} value={data.oidcConfig.userinfoEndpoint} />
|
||||||
</FormField>
|
</FormField>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -191,7 +184,7 @@ const ApplicationDetails = () => {
|
||||||
/>
|
/>
|
||||||
{isLoading && <div>loading</div>}
|
{isLoading && <div>loading</div>}
|
||||||
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
{error && <div>{`error occurred: ${error.body.message}`}</div>}
|
||||||
{data && oidcConfig && (
|
{data && (
|
||||||
<>
|
<>
|
||||||
<Card className={styles.header}>
|
<Card className={styles.header}>
|
||||||
<ImagePlaceholder size={76} borderRadius={16} />
|
<ImagePlaceholder size={76} borderRadius={16} />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Application } from '@logto/schemas';
|
import { ApplicationDTO } from '@logto/schemas';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -33,7 +33,7 @@ const Applications = () => {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
const [query, setQuery] = useSearchParams();
|
const [query, setQuery] = useSearchParams();
|
||||||
const pageIndex = Number(query.get('page') ?? '1');
|
const pageIndex = Number(query.get('page') ?? '1');
|
||||||
const { data, error, mutate } = useSWR<[Application[], number], RequestError>(
|
const { data, error, mutate } = useSWR<[ApplicationDTO[], number], RequestError>(
|
||||||
`/api/applications?page=${pageIndex}&page_size=${pageSize}`
|
`/api/applications?page=${pageIndex}&page_size=${pageSize}`
|
||||||
);
|
);
|
||||||
const isLoading = !data && !error;
|
const isLoading = !data && !error;
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"@logto/phrases": "^0.1.0",
|
"@logto/phrases": "^0.1.0",
|
||||||
"@logto/schemas": "^0.1.0",
|
"@logto/schemas": "^0.1.0",
|
||||||
"@silverhand/essentials": "^1.1.0",
|
"@silverhand/essentials": "^1.1.0",
|
||||||
|
"camelcase-keys": "^7.0.2",
|
||||||
"dayjs": "^1.10.5",
|
"dayjs": "^1.10.5",
|
||||||
"decamelize": "^5.0.0",
|
"decamelize": "^5.0.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
|
|
|
@ -1,20 +1,29 @@
|
||||||
import {
|
import {
|
||||||
Application,
|
Application,
|
||||||
|
ApplicationDTO,
|
||||||
ApplicationType,
|
ApplicationType,
|
||||||
Passcode,
|
Passcode,
|
||||||
PasscodeType,
|
PasscodeType,
|
||||||
Resource,
|
Resource,
|
||||||
Role,
|
Role,
|
||||||
Setting,
|
Setting,
|
||||||
|
SnakeCaseOidcConfig,
|
||||||
UserLog,
|
UserLog,
|
||||||
UserLogResult,
|
UserLogResult,
|
||||||
UserLogType,
|
UserLogType,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
|
import camelcaseKeys from 'camelcase-keys';
|
||||||
|
|
||||||
export * from './connector';
|
export * from './connector';
|
||||||
export * from './sign-in-experience';
|
export * from './sign-in-experience';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
|
||||||
|
export const mockOidcConfig: SnakeCaseOidcConfig = {
|
||||||
|
authorization_endpoint: 'https://logto.dev/oidc/auth',
|
||||||
|
userinfo_endpoint: 'https://logto.dev/oidc/userinfo',
|
||||||
|
token_endpoint: 'https://logto.dev/oidc/token',
|
||||||
|
};
|
||||||
|
|
||||||
export const mockApplication: Application = {
|
export const mockApplication: Application = {
|
||||||
id: 'foo',
|
id: 'foo',
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
|
@ -32,6 +41,11 @@ export const mockApplication: Application = {
|
||||||
createdAt: 1_645_334_775_356,
|
createdAt: 1_645_334_775_356,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mockApplicationDTO: ApplicationDTO = {
|
||||||
|
...mockApplication,
|
||||||
|
oidcConfig: camelcaseKeys(mockOidcConfig),
|
||||||
|
};
|
||||||
|
|
||||||
export const mockResource: Resource = {
|
export const mockResource: Resource = {
|
||||||
id: 'logto_api',
|
id: 'logto_api',
|
||||||
name: 'management api',
|
name: 'management api',
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { Application, CreateApplication, ApplicationType } from '@logto/schemas';
|
import { Application, CreateApplication, ApplicationType } from '@logto/schemas';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
import { mockApplication } from '@/__mocks__';
|
import { mockApplicationDTO, mockOidcConfig } from '@/__mocks__';
|
||||||
|
import { port } from '@/env/consts';
|
||||||
import { findApplicationById } from '@/queries/application';
|
import { findApplicationById } from '@/queries/application';
|
||||||
import { createRequester } from '@/utils/test-utils';
|
import { createRequester } from '@/utils/test-utils';
|
||||||
|
|
||||||
|
@ -8,22 +10,22 @@ import applicationRoutes from './application';
|
||||||
|
|
||||||
jest.mock('@/queries/application', () => ({
|
jest.mock('@/queries/application', () => ({
|
||||||
findTotalNumberOfApplications: jest.fn(async () => ({ count: 10 })),
|
findTotalNumberOfApplications: jest.fn(async () => ({ count: 10 })),
|
||||||
findAllApplications: jest.fn(async () => [mockApplication]),
|
findAllApplications: jest.fn(async () => [mockApplicationDTO]),
|
||||||
findApplicationById: jest.fn(async () => mockApplication),
|
findApplicationById: jest.fn(async () => mockApplicationDTO),
|
||||||
deleteApplicationById: jest.fn(),
|
deleteApplicationById: jest.fn(),
|
||||||
insertApplication: jest.fn(
|
insertApplication: jest.fn(
|
||||||
async (body: CreateApplication): Promise<Application> => ({
|
async (body: CreateApplication): Promise<Application> => ({
|
||||||
...mockApplication,
|
...mockApplicationDTO,
|
||||||
...body,
|
...body,
|
||||||
oidcClientMetadata: {
|
oidcClientMetadata: {
|
||||||
...mockApplication.oidcClientMetadata,
|
...mockApplicationDTO.oidcClientMetadata,
|
||||||
...body.oidcClientMetadata,
|
...body.oidcClientMetadata,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
updateApplicationById: jest.fn(
|
updateApplicationById: jest.fn(
|
||||||
async (_, data: Partial<CreateApplication>): Promise<Application> => ({
|
async (_, data: Partial<CreateApplication>): Promise<Application> => ({
|
||||||
...mockApplication,
|
...mockApplicationDTO,
|
||||||
...data,
|
...data,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
@ -43,10 +45,15 @@ const customClientMetadata = {
|
||||||
describe('application route', () => {
|
describe('application route', () => {
|
||||||
const applicationRequest = createRequester({ authedRoutes: applicationRoutes });
|
const applicationRequest = createRequester({ authedRoutes: applicationRoutes });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const discoveryUrl = `http://localhost:${port}/oidc/.well-known/openid-configuration`;
|
||||||
|
nock(discoveryUrl).get('').reply(200, mockOidcConfig);
|
||||||
|
});
|
||||||
|
|
||||||
it('GET /applications', async () => {
|
it('GET /applications', async () => {
|
||||||
const response = await applicationRequest.get('/applications');
|
const response = await applicationRequest.get('/applications');
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
expect(response.body).toEqual([mockApplication]);
|
expect(response.body).toEqual([mockApplicationDTO]);
|
||||||
expect(response.header).toHaveProperty('total-number', '10');
|
expect(response.header).toHaveProperty('total-number', '10');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,7 +67,7 @@ describe('application route', () => {
|
||||||
|
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
expect(response.body).toEqual({
|
expect(response.body).toEqual({
|
||||||
...mockApplication,
|
...mockApplicationDTO,
|
||||||
id: 'randomId',
|
id: 'randomId',
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
@ -98,7 +105,7 @@ describe('application route', () => {
|
||||||
const response = await applicationRequest.get('/applications/foo');
|
const response = await applicationRequest.get('/applications/foo');
|
||||||
|
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
expect(response.body).toEqual(mockApplication);
|
expect(response.body).toEqual(mockApplicationDTO);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('PATCH /applications/:applicationId', async () => {
|
it('PATCH /applications/:applicationId', async () => {
|
||||||
|
@ -109,7 +116,7 @@ describe('application route', () => {
|
||||||
.send({ name, customClientMetadata });
|
.send({ name, customClientMetadata });
|
||||||
|
|
||||||
expect(response.status).toEqual(200);
|
expect(response.status).toEqual(200);
|
||||||
expect(response.body).toEqual({ ...mockApplication, name, customClientMetadata });
|
expect(response.body).toEqual({ ...mockApplicationDTO, name, customClientMetadata });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('PATCH /applications/:applicationId expect to throw with invalid properties', async () => {
|
it('PATCH /applications/:applicationId expect to throw with invalid properties', async () => {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { Applications } from '@logto/schemas';
|
import { Applications, SnakeCaseOidcConfig } from '@logto/schemas';
|
||||||
|
import camelcaseKeys from 'camelcase-keys';
|
||||||
|
import got from 'got';
|
||||||
import { object, string } from 'zod';
|
import { object, string } from 'zod';
|
||||||
|
|
||||||
|
import { port } from '@/env/consts';
|
||||||
import koaGuard from '@/middleware/koa-guard';
|
import koaGuard from '@/middleware/koa-guard';
|
||||||
import koaPagination from '@/middleware/koa-pagination';
|
import koaPagination from '@/middleware/koa-pagination';
|
||||||
import { buildOidcClientMetadata } from '@/oidc/utils';
|
import { buildOidcClientMetadata } from '@/oidc/utils';
|
||||||
|
@ -18,18 +21,24 @@ import { AuthedRouter } from './types';
|
||||||
|
|
||||||
const applicationId = buildIdGenerator(21);
|
const applicationId = buildIdGenerator(21);
|
||||||
|
|
||||||
|
const discoveryUrl = `http://localhost:${port}/oidc/.well-known/openid-configuration`;
|
||||||
|
|
||||||
export default function applicationRoutes<T extends AuthedRouter>(router: T) {
|
export default function applicationRoutes<T extends AuthedRouter>(router: T) {
|
||||||
router.get('/applications', koaPagination(), async (ctx, next) => {
|
router.get('/applications', koaPagination(), async (ctx, next) => {
|
||||||
const { limit, offset } = ctx.pagination;
|
const { limit, offset } = ctx.pagination;
|
||||||
|
|
||||||
const [{ count }, applications] = await Promise.all([
|
const [{ count }, applications, oidcConfig] = await Promise.all([
|
||||||
findTotalNumberOfApplications(),
|
findTotalNumberOfApplications(),
|
||||||
findAllApplications(limit, offset),
|
findAllApplications(limit, offset),
|
||||||
|
got(discoveryUrl).json<SnakeCaseOidcConfig>(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Return totalCount to pagination middleware
|
// Return totalCount to pagination middleware
|
||||||
ctx.pagination.totalCount = count;
|
ctx.pagination.totalCount = count;
|
||||||
ctx.body = applications;
|
ctx.body = applications.map((application) => ({
|
||||||
|
...application,
|
||||||
|
oidcConfig: camelcaseKeys(oidcConfig),
|
||||||
|
}));
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
@ -45,13 +54,21 @@ export default function applicationRoutes<T extends AuthedRouter>(router: T) {
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const { name, type, oidcClientMetadata, customClientMetadata } = ctx.guard.body;
|
const { name, type, oidcClientMetadata, customClientMetadata } = ctx.guard.body;
|
||||||
|
|
||||||
ctx.body = await insertApplication({
|
const [application, oidcConfig] = await Promise.all([
|
||||||
|
insertApplication({
|
||||||
id: applicationId(),
|
id: applicationId(),
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
oidcClientMetadata: buildOidcClientMetadata(oidcClientMetadata),
|
oidcClientMetadata: buildOidcClientMetadata(oidcClientMetadata),
|
||||||
customClientMetadata,
|
customClientMetadata,
|
||||||
});
|
}),
|
||||||
|
got(discoveryUrl).json<SnakeCaseOidcConfig>(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
...application,
|
||||||
|
oidcConfig: camelcaseKeys(oidcConfig),
|
||||||
|
};
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -67,7 +84,15 @@ export default function applicationRoutes<T extends AuthedRouter>(router: T) {
|
||||||
params: { id },
|
params: { id },
|
||||||
} = ctx.guard;
|
} = ctx.guard;
|
||||||
|
|
||||||
ctx.body = await findApplicationById(id);
|
const [application, oidcConfig] = await Promise.all([
|
||||||
|
findApplicationById(id),
|
||||||
|
got(discoveryUrl).json<SnakeCaseOidcConfig>(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
...application,
|
||||||
|
oidcConfig: camelcaseKeys(oidcConfig),
|
||||||
|
};
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -85,9 +110,17 @@ export default function applicationRoutes<T extends AuthedRouter>(router: T) {
|
||||||
body,
|
body,
|
||||||
} = ctx.guard;
|
} = ctx.guard;
|
||||||
|
|
||||||
ctx.body = await updateApplicationById(id, {
|
const [application, oidcConfig] = await Promise.all([
|
||||||
|
updateApplicationById(id, {
|
||||||
...body,
|
...body,
|
||||||
});
|
}),
|
||||||
|
got(discoveryUrl).json<SnakeCaseOidcConfig>(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
...application,
|
||||||
|
oidcConfig: camelcaseKeys(oidcConfig),
|
||||||
|
};
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@silverhand/eslint-config": "^0.10.2",
|
"@silverhand/eslint-config": "^0.10.2",
|
||||||
"@silverhand/essentials": "^1.1.0",
|
"@silverhand/essentials": "^1.1.6",
|
||||||
"@silverhand/ts-config": "^0.10.2",
|
"@silverhand/ts-config": "^0.10.2",
|
||||||
"@types/lodash.uniq": "^4.5.6",
|
"@types/lodash.uniq": "^4.5.6",
|
||||||
"@types/node": "14",
|
"@types/node": "14",
|
||||||
|
|
6
packages/schemas/src/types/application.ts
Normal file
6
packages/schemas/src/types/application.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { Application } from '../db-entries';
|
||||||
|
import { OidcConfig } from './oidc-config';
|
||||||
|
|
||||||
|
export interface ApplicationDTO extends Application {
|
||||||
|
oidcConfig: OidcConfig;
|
||||||
|
}
|
|
@ -1,2 +1,4 @@
|
||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './connector';
|
export * from './connector';
|
||||||
|
export * from './oidc-config';
|
||||||
|
export * from './application';
|
||||||
|
|
9
packages/schemas/src/types/oidc-config.ts
Normal file
9
packages/schemas/src/types/oidc-config.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { KeysToCamelCase } from '@silverhand/essentials';
|
||||||
|
|
||||||
|
export type SnakeCaseOidcConfig = {
|
||||||
|
authorization_endpoint: string;
|
||||||
|
userinfo_endpoint: string;
|
||||||
|
token_endpoint: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OidcConfig = KeysToCamelCase<SnakeCaseOidcConfig>;
|
|
@ -165,6 +165,7 @@ importers:
|
||||||
'@types/node': ^16.3.1
|
'@types/node': ^16.3.1
|
||||||
'@types/oidc-provider': ^7.8.0
|
'@types/oidc-provider': ^7.8.0
|
||||||
'@types/supertest': ^2.0.11
|
'@types/supertest': ^2.0.11
|
||||||
|
camelcase-keys: ^7.0.2
|
||||||
copyfiles: ^2.4.1
|
copyfiles: ^2.4.1
|
||||||
dayjs: ^1.10.5
|
dayjs: ^1.10.5
|
||||||
decamelize: ^5.0.0
|
decamelize: ^5.0.0
|
||||||
|
@ -206,6 +207,7 @@ importers:
|
||||||
'@logto/phrases': link:../phrases
|
'@logto/phrases': link:../phrases
|
||||||
'@logto/schemas': link:../schemas
|
'@logto/schemas': link:../schemas
|
||||||
'@silverhand/essentials': 1.1.2
|
'@silverhand/essentials': 1.1.2
|
||||||
|
camelcase-keys: 7.0.2
|
||||||
dayjs: 1.10.7
|
dayjs: 1.10.7
|
||||||
decamelize: 5.0.1
|
decamelize: 5.0.1
|
||||||
dotenv: 10.0.0
|
dotenv: 10.0.0
|
||||||
|
@ -315,7 +317,7 @@ importers:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@logto/phrases': ^0.1.0
|
'@logto/phrases': ^0.1.0
|
||||||
'@silverhand/eslint-config': ^0.10.2
|
'@silverhand/eslint-config': ^0.10.2
|
||||||
'@silverhand/essentials': ^1.1.0
|
'@silverhand/essentials': ^1.1.6
|
||||||
'@silverhand/ts-config': ^0.10.2
|
'@silverhand/ts-config': ^0.10.2
|
||||||
'@types/lodash.uniq': ^4.5.6
|
'@types/lodash.uniq': ^4.5.6
|
||||||
'@types/node': '14'
|
'@types/node': '14'
|
||||||
|
@ -334,7 +336,7 @@ importers:
|
||||||
zod: 3.14.3
|
zod: 3.14.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@silverhand/eslint-config': 0.10.2_3a533fa6cc3da0cf8525ef55d41c4384
|
'@silverhand/eslint-config': 0.10.2_3a533fa6cc3da0cf8525ef55d41c4384
|
||||||
'@silverhand/essentials': 1.1.2
|
'@silverhand/essentials': 1.1.7
|
||||||
'@silverhand/ts-config': 0.10.2_typescript@4.6.2
|
'@silverhand/ts-config': 0.10.2_typescript@4.6.2
|
||||||
'@types/lodash.uniq': 4.5.6
|
'@types/lodash.uniq': 4.5.6
|
||||||
'@types/node': 14.18.0
|
'@types/node': 14.18.0
|
||||||
|
@ -5260,6 +5262,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.orderby: 4.6.0
|
lodash.orderby: 4.6.0
|
||||||
lodash.pick: 4.4.0
|
lodash.pick: 4.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@silverhand/essentials/1.1.4:
|
/@silverhand/essentials/1.1.4:
|
||||||
resolution: {integrity: sha512-5pHjIz42CjILcqGWhmfP7/RCbmlWIWmj0H3RMJDGW3QKZyNkWawG6gKwtEQ75N0MoZOzXjNE4HD4DK3moPa5sg==}
|
resolution: {integrity: sha512-5pHjIz42CjILcqGWhmfP7/RCbmlWIWmj0H3RMJDGW3QKZyNkWawG6gKwtEQ75N0MoZOzXjNE4HD4DK3moPa5sg==}
|
||||||
|
@ -5283,7 +5286,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.orderby: 4.6.0
|
lodash.orderby: 4.6.0
|
||||||
lodash.pick: 4.4.0
|
lodash.pick: 4.4.0
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@silverhand/ts-config-react/0.10.3_typescript@4.6.2:
|
/@silverhand/ts-config-react/0.10.3_typescript@4.6.2:
|
||||||
resolution: {integrity: sha512-xGOwcw1HTixfP3PSSdJT3leGnlUV0dLna9xp58bDDLul7UCnIn+PNp1VNJxUZ/HvtKbV4ZSYdGsGE6Xqmwn7Ag==}
|
resolution: {integrity: sha512-xGOwcw1HTixfP3PSSdJT3leGnlUV0dLna9xp58bDDLul7UCnIn+PNp1VNJxUZ/HvtKbV4ZSYdGsGE6Xqmwn7Ag==}
|
||||||
|
@ -7374,7 +7376,6 @@ packages:
|
||||||
/camelcase/6.3.0:
|
/camelcase/6.3.0:
|
||||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/caniuse-api/3.0.0:
|
/caniuse-api/3.0.0:
|
||||||
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
|
resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
|
||||||
|
@ -11898,7 +11899,7 @@ packages:
|
||||||
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 27.5.1
|
'@jest/types': 27.5.1
|
||||||
camelcase: 6.2.1
|
camelcase: 6.3.0
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
jest-get-type: 27.5.1
|
jest-get-type: 27.5.1
|
||||||
leven: 3.1.0
|
leven: 3.1.0
|
||||||
|
|
Loading…
Reference in a new issue