diff --git a/.changeset/config.json b/.changeset/config.json index a408d2fa2..99aa3618b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -12,13 +12,6 @@ "@logto/integration-tests", "@logto/ui" ]], - "//": "Ignore other release group members, only keep the major one.", - "ignore": [ - "@logto/create", - "@logto/console", - "@logto/integration-tests", - "@logto/ui" - ], "linked": [[ "@logto/phrases", "@logto/phrases-ui", diff --git a/.changeset/fifty-balloons-taste.md b/.changeset/fifty-balloons-taste.md new file mode 100644 index 000000000..8534ffa27 --- /dev/null +++ b/.changeset/fifty-balloons-taste.md @@ -0,0 +1,6 @@ +--- +"@logto/phrases": minor +"@logto/phrases-ui": minor +--- + +Add German language diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..0b939e470 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,18 @@ +{ + "mode": "pre", + "tag": "beta", + "initialVersions": { + "@logto/cli": "1.0.0-beta.12", + "@logto/console": "1.0.0-beta.12", + "@logto/core": "1.0.0-beta.12", + "@logto/create": "1.0.0-beta.12", + "@logto/demo-app": "1.0.0-beta.12", + "@logto/integration-tests": "1.0.0-beta.12", + "@logto/phrases": "1.0.0-beta.12", + "@logto/phrases-ui": "1.0.0-beta.12", + "@logto/schemas": "1.0.0-beta.12", + "@logto/shared": "1.0.0-beta.12", + "@logto/ui": "1.0.0-beta.12" + }, + "changesets": [] +} diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 110ce31e5..165c7139b 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -38,9 +38,9 @@ jobs: strategy: matrix: - os: [ubuntu-latest] + node_version: [16, 18] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -55,6 +55,7 @@ jobs: - name: Setup Node and pnpm uses: silverhand-io/actions-node-pnpm-run-steps@v2 with: + node-version: ${{ matrix.node_version }} run-install: false # Setup integration test @@ -63,6 +64,9 @@ jobs: cd tests pnpm i pnpm prepack + # Install Chromium + cd packages/integration-tests/node_modules/puppeteer + pnpm postinstall # Setup environment - name: Setup Postgres diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dffec17c5..af5765f0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,11 +47,17 @@ jobs: main-test: runs-on: ubuntu-latest + strategy: + matrix: + node_version: [16, 18] + steps: - uses: actions/checkout@v3 - name: Setup Node and pnpm uses: silverhand-io/actions-node-pnpm-run-steps@v2 + with: + node-version: ${{ matrix.node_version }} - name: Prepack run: pnpm prepack diff --git a/Dockerfile b/Dockerfile index 81641a922..5301427af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,14 @@ FROM node:16-alpine as builder WORKDIR /etc/logto ENV CI=true -COPY . . # Install toolchain -RUN npm add --location=global pnpm@^7.2.1 +RUN npm add --location=global pnpm@^7.14.0 # https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#node-gyp-alpine RUN apk add --no-cache python3 make g++ +COPY . . + # Install dependencies and build RUN pnpm i RUN pnpm -r build diff --git a/package.json b/package.json index 291afbab8..d4e93b575 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ ] }, "engines": { - "node": ">=14.15.0", - "pnpm": ">=6" + "node": "^16.13.0 || ^18.12.0", + "pnpm": "^7.14.0" }, "alias": { "html-parse-stringify": "html-parse-stringify/dist/html-parse-stringify.module.js", diff --git a/packages/cli/package.json b/packages/cli/package.json index e96ac48b6..a34330050 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,7 +34,7 @@ "prepack": "pnpm build" }, "engines": { - "node": "^16.0.0" + "node": "^16.13.0 || ^18.12.0" }, "bugs": { "url": "https://github.com/logto-io/logto/issues" @@ -47,7 +47,7 @@ "decamelize": "^5.0.0", "dotenv": "^16.0.0", "fs-extra": "^10.1.0", - "got": "^11.8.2", + "got": "^11.8.5", "hpagent": "^1.0.0", "inquirer": "^8.2.2", "nanoid": "^3.3.4", diff --git a/packages/cli/src/commands/install/utils.ts b/packages/cli/src/commands/install/utils.ts index e21d7ee0e..529d562f8 100644 --- a/packages/cli/src/commands/install/utils.ts +++ b/packages/cli/src/commands/install/utils.ts @@ -27,16 +27,17 @@ export const defaultPath = path.join(os.homedir(), 'logto'); const pgRequired = new semver.SemVer('14.0.0'); export const validateNodeVersion = () => { - const required = new semver.SemVer('16.0.0'); + const required = [new semver.SemVer('16.13.0'), new semver.SemVer('18.12.0')]; + const requiredVersionString = required.map((version) => '^' + version.version).join(' || '); const current = new semver.SemVer(execSync('node -v', { encoding: 'utf8', stdio: 'pipe' })); - if (required.compare(current) > 0) { - log.error(`Logto requires NodeJS >=${required.version}, but ${current.version} found.`); + if (required.every((version) => version.major !== current.major)) { + log.error(`Logto requires NodeJS ${requiredVersionString}, but ${current.version} found.`); } - if (current.major > required.major) { + if (required.some((version) => version.major === current.major && version.compare(current) > 0)) { log.warn( - `Logto is tested under NodeJS ^${required.version}, but version ${current.version} found.` + `Logto is tested under NodeJS ${requiredVersionString}, but version ${current.version} found.` ); } }; diff --git a/packages/console/package.json b/packages/console/package.json index 227b9bbbf..fb0b30302 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -22,7 +22,7 @@ "@logto/language-kit": "1.0.0-beta.20", "@logto/phrases": "workspace:^", "@logto/phrases-ui": "workspace:^", - "@logto/react": "1.0.0-beta.11", + "@logto/react": "1.0.0-beta.12", "@logto/schemas": "workspace:^", "@mdx-js/react": "^1.6.22", "@parcel/core": "2.7.0", @@ -50,6 +50,7 @@ "csstype": "^3.0.11", "dayjs": "^1.10.5", "deep-object-diff": "^1.1.7", + "date-fns": "^2.29.3", "deepmerge": "^4.2.2", "dnd-core": "^16.0.0", "eslint": "^8.21.0", @@ -88,6 +89,17 @@ "typescript": "^4.7.4", "zod": "^3.19.1" }, + "engines": { + "node": "^16.13.0 || ^18.12.0" + }, + "//": "https://github.com/parcel-bundler/parcel/issues/7636", + "targets": { + "default": { + "engines": { + "browsers": "defaults" + } + } + }, "alias": { "@/*": "./src/$1", "@mdx/components/*": "./src/mdx-components/$1" diff --git a/packages/console/src/assets/avatars/avatar-002.png b/packages/console/src/assets/avatars/avatar-002.png index fb2862be3..46dea241d 100644 Binary files a/packages/console/src/assets/avatars/avatar-002.png and b/packages/console/src/assets/avatars/avatar-002.png differ diff --git a/packages/console/src/assets/avatars/avatar-005.png b/packages/console/src/assets/avatars/avatar-005.png index 591d3274b..9013d8cf8 100644 Binary files a/packages/console/src/assets/avatars/avatar-005.png and b/packages/console/src/assets/avatars/avatar-005.png differ diff --git a/packages/console/src/assets/avatars/avatar-010.png b/packages/console/src/assets/avatars/avatar-010.png deleted file mode 100644 index 46dea241d..000000000 Binary files a/packages/console/src/assets/avatars/avatar-010.png and /dev/null differ diff --git a/packages/console/src/components/DateTime/index.tsx b/packages/console/src/components/DateTime/index.tsx index 0bc850197..4c20a2473 100644 --- a/packages/console/src/components/DateTime/index.tsx +++ b/packages/console/src/components/DateTime/index.tsx @@ -1,18 +1,18 @@ import type { Nullable } from '@silverhand/essentials'; -import dayjs from 'dayjs'; +import { isValid } from 'date-fns'; type Props = { children: Nullable; }; const DateTime = ({ children }: Props) => { - const date = dayjs(children); + const date = children && new Date(children); - if (!children || !date.isValid()) { + if (!date || !isValid(date)) { return -; } - return {date.toDate().toLocaleDateString()}; + return {date.toLocaleDateString()}; }; export default DateTime; diff --git a/packages/console/src/consts/avatars.ts b/packages/console/src/consts/avatars.ts index ce6258de9..32b79b63c 100644 --- a/packages/console/src/consts/avatars.ts +++ b/packages/console/src/consts/avatars.ts @@ -7,7 +7,6 @@ import avatar006 from '@/assets/avatars/avatar-006.png'; import avatar007 from '@/assets/avatars/avatar-007.png'; import avatar008 from '@/assets/avatars/avatar-008.png'; import avatar009 from '@/assets/avatars/avatar-009.png'; -import avatar010 from '@/assets/avatars/avatar-010.png'; export const Avatars = [ avatar001, @@ -19,7 +18,6 @@ export const Avatars = [ avatar007, avatar008, avatar009, - avatar010, ]; export const generateAvatarPlaceHolderById = (id: string) => diff --git a/packages/console/src/pages/AuditLogDetails/index.tsx b/packages/console/src/pages/AuditLogDetails/index.tsx index 1a5c8f7ad..173c49b6c 100644 --- a/packages/console/src/pages/AuditLogDetails/index.tsx +++ b/packages/console/src/pages/AuditLogDetails/index.tsx @@ -1,6 +1,5 @@ import type { LogDto, User } from '@logto/schemas'; import classNames from 'classnames'; -import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; import { useLocation, useParams } from 'react-router-dom'; import useSWR from 'swr'; @@ -85,7 +84,7 @@ const AuditLogDetails = () => {
{t('log_details.time')}
-
{dayjs(data.createdAt).toDate().toLocaleString()}
+
{new Date(data.createdAt).toLocaleString()}
diff --git a/packages/console/src/pages/Dashboard/index.tsx b/packages/console/src/pages/Dashboard/index.tsx index db727d9e8..4d4c8500b 100644 --- a/packages/console/src/pages/Dashboard/index.tsx +++ b/packages/console/src/pages/Dashboard/index.tsx @@ -1,4 +1,4 @@ -import dayjs from 'dayjs'; +import { format } from 'date-fns'; import type { ChangeEventHandler } from 'react'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -36,7 +36,7 @@ const tickFormatter = new Intl.NumberFormat('en-US', { }); const Dashboard = () => { - const [date, setDate] = useState(dayjs().format('YYYY-MM-DD')); + const [date, setDate] = useState(format(Date.now(), 'yyyy-MM-dd')); const { data: totalData, error: totalError } = useSWR( '/api/dashboard/users/total' ); diff --git a/packages/console/src/pages/SignInExperience/components/Preview.tsx b/packages/console/src/pages/SignInExperience/components/Preview.tsx index 137e467ec..dfee63676 100644 --- a/packages/console/src/pages/SignInExperience/components/Preview.tsx +++ b/packages/console/src/pages/SignInExperience/components/Preview.tsx @@ -4,7 +4,7 @@ import type { ConnectorResponse, ConnectorMetadata, SignInExperience } from '@lo import { AppearanceMode } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import classNames from 'classnames'; -import dayjs from 'dayjs'; +import { format } from 'date-fns'; import { useEffect, useMemo, useState, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import useSWR from 'swr'; @@ -195,7 +195,7 @@ const Preview = ({ signInExperience, className }: Props) => {
{platform !== 'desktopWeb' && (
-
{dayjs().format('HH:mm')}
+
{format(Date.now(), 'HH:mm')}
)} diff --git a/packages/core/package.json b/packages/core/package.json index 40a987b77..0feb7462d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -30,7 +30,7 @@ "@silverhand/essentials": "^1.3.0", "chalk": "^4", "clean-deep": "^3.4.0", - "dayjs": "^1.10.5", + "date-fns": "^2.29.3", "debug": "^4.3.4", "decamelize": "^5.0.0", "deepmerge": "^4.2.2", @@ -38,7 +38,7 @@ "etag": "^1.8.1", "find-up": "^5.0.0", "fs-extra": "^10.1.0", - "got": "^11.8.2", + "got": "^11.8.5", "hash-wasm": "^4.9.0", "i18next": "^21.8.16", "iconv-lite": "0.6.3", @@ -55,7 +55,7 @@ "lodash.pick": "^4.4.0", "module-alias": "^2.2.2", "nanoid": "^3.1.23", - "oidc-provider": "^7.11.3", + "oidc-provider": "^7.13.0", "p-retry": "^4.6.1", "query-string": "^7.0.1", "roarr": "^7.11.0", @@ -84,7 +84,7 @@ "@types/koa-send": "^4.1.3", "@types/lodash.pick": "^4.4.6", "@types/node": "^16.0.0", - "@types/oidc-provider": "^7.11.1", + "@types/oidc-provider": "^7.12.0", "@types/supertest": "^2.0.11", "copyfiles": "^2.4.1", "eslint": "^8.21.0", @@ -100,7 +100,7 @@ "typescript": "^4.7.4" }, "engines": { - "node": "^16.0.0" + "node": "^16.13.0 || ^18.12.0" }, "_moduleAliases": { "@": "./build" diff --git a/packages/core/src/env-set/parameters.ts b/packages/core/src/env-set/parameters.ts index bd7157a04..d4fad5e6b 100644 --- a/packages/core/src/env-set/parameters.ts +++ b/packages/core/src/env-set/parameters.ts @@ -1,2 +1,4 @@ -export const isTrue = (value: string) => - ['1', 'true', 'y', 'yes', 'yep', 'yeah'].includes(value.toLowerCase()); +export const isTrue = (value?: string) => + // We need to leverage the native type guard + // eslint-disable-next-line no-implicit-coercion + !!value && ['1', 'true', 'y', 'yes', 'yep', 'yeah'].includes(value.toLowerCase()); diff --git a/packages/core/src/middleware/koa-guard.ts b/packages/core/src/middleware/koa-guard.ts index af8efe14a..45ad13c74 100644 --- a/packages/core/src/middleware/koa-guard.ts +++ b/packages/core/src/middleware/koa-guard.ts @@ -3,7 +3,7 @@ import { has } from '@silverhand/essentials'; import type { MiddlewareType } from 'koa'; import koaBody from 'koa-body'; import type { IMiddleware, IRouterParamContext } from 'koa-router'; -import type { ZodType } from 'zod'; +import type { ZodType, ZodTypeDef } from 'zod'; import envSet from '@/env-set'; import RequestError from '@/errors/RequestError'; @@ -48,7 +48,7 @@ export const isGuardMiddleware = ( ): function_ is WithGuardConfig => function_.name === 'guardMiddleware' && has(function_, 'config'); -const tryParse = ( +const tryParse = ( type: 'query' | 'body' | 'params', guard: Optional>, data: unknown diff --git a/packages/core/src/oidc/adapter.test.ts b/packages/core/src/oidc/adapter.test.ts index 7cb9c54c4..8ee40b08e 100644 --- a/packages/core/src/oidc/adapter.test.ts +++ b/packages/core/src/oidc/adapter.test.ts @@ -35,10 +35,9 @@ jest.mock('@logto/shared', () => ({ const now = Date.now(); jest.mock( - 'dayjs', - // eslint-disable-next-line unicorn/consistent-function-scoping - jest.fn(() => () => ({ - add: jest.fn((delta: number) => new Date(now + delta * 1000)), + 'date-fns', + jest.fn(() => ({ + addSeconds: jest.fn((_: Date, seconds: number) => new Date(now + seconds * 1000)), })) ); diff --git a/packages/core/src/oidc/adapter.ts b/packages/core/src/oidc/adapter.ts index 5c8426268..dc401c715 100644 --- a/packages/core/src/oidc/adapter.ts +++ b/packages/core/src/oidc/adapter.ts @@ -1,7 +1,7 @@ import type { CreateApplication, OidcClientMetadata } from '@logto/schemas'; import { ApplicationType } from '@logto/schemas'; import { adminConsoleApplicationId, demoAppApplicationId } from '@logto/schemas/lib/seeds'; -import dayjs from 'dayjs'; +import { addSeconds } from 'date-fns'; import type { AdapterFactory, AllClientMetadata } from 'oidc-provider'; import snakecaseKeys from 'snakecase-keys'; @@ -99,7 +99,7 @@ export default function postgresAdapter(modelName: string): ReturnType findPayloadById(modelName, id), findByUserCode: async (userCode) => findPayloadByPayloadField(modelName, 'userCode', userCode), diff --git a/packages/core/src/queries/oidc-model-instance.ts b/packages/core/src/queries/oidc-model-instance.ts index fccbe0058..d7dedfba6 100644 --- a/packages/core/src/queries/oidc-model-instance.ts +++ b/packages/core/src/queries/oidc-model-instance.ts @@ -7,7 +7,7 @@ import { OidcModelInstances } from '@logto/schemas'; import { convertToIdentifiers, convertToTimestamp } from '@logto/shared'; import type { Nullable } from '@silverhand/essentials'; import { conditional } from '@silverhand/essentials'; -import dayjs from 'dayjs'; +import { addSeconds, isBefore } from 'date-fns'; import type { ValueExpression } from 'slonik'; import { sql } from 'slonik'; @@ -30,7 +30,7 @@ const isConsumed = (modelName: string, consumedAt: Nullable): boolean => return Boolean(consumedAt); } - return dayjs(consumedAt).add(refreshTokenReuseInterval, 'seconds').isBefore(dayjs()); + return isBefore(addSeconds(consumedAt, refreshTokenReuseInterval), Date.now()); }; const withConsumed = ( diff --git a/packages/core/src/queries/user.test.ts b/packages/core/src/queries/user.test.ts index 22dbb24fd..6eb556874 100644 --- a/packages/core/src/queries/user.test.ts +++ b/packages/core/src/queries/user.test.ts @@ -240,7 +240,7 @@ describe('user query', () => { const expectSql = sql` select count(*) from ${table} - where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${fields.username} like $3 or ${fields.name} like $4 + where ${fields.primaryEmail} ilike $1 or ${fields.primaryPhone} ilike $2 or ${fields.username} ilike $3 or ${fields.name} ilike $4 `; mockQuery.mockImplementationOnce(async (sql, values) => { @@ -259,7 +259,7 @@ describe('user query', () => { select count(*) from ${table} where not (${fields.roleNames}::jsonb?$1) - and (${fields.primaryEmail} like $2 or ${fields.primaryPhone} like $3 or ${fields.username} like $4 or ${fields.name} like $5) + and (${fields.primaryEmail} ilike $2 or ${fields.primaryPhone} ilike $3 or ${fields.username} ilike $4 or ${fields.name} ilike $5) `; mockQuery.mockImplementationOnce(async (sql, values) => { @@ -278,6 +278,24 @@ describe('user query', () => { await expect(countUsers(search, true)).resolves.toEqual(dbvalue); }); + it('countUsers with isCaseSensitive', async () => { + const search = 'foo'; + const expectSql = sql` + select count(*) + from ${table} + where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${fields.username} like $3 or ${fields.name} like $4 + `; + + mockQuery.mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`]); + + return createMockQueryResult([dbvalue]); + }); + + await expect(countUsers(search, undefined, true)).resolves.toEqual(dbvalue); + }); + it('findUsers', async () => { const search = 'foo'; const limit = 100; @@ -285,9 +303,9 @@ describe('user query', () => { const expectSql = sql` select ${sql.join(Object.values(fields), sql`,`)} from ${table} - where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${ + where ${fields.primaryEmail} ilike $1 or ${fields.primaryPhone} ilike $2 or ${ fields.username - } like $3 or ${fields.name} like $4 + } ilike $3 or ${fields.name} ilike $4 limit $5 offset $6 `; @@ -317,9 +335,9 @@ describe('user query', () => { select ${sql.join(Object.values(fields), sql`,`)} from ${table} where not (${fields.roleNames}::jsonb?$1) - and (${fields.primaryEmail} like $2 or ${fields.primaryPhone} like $3 or ${ + and (${fields.primaryEmail} ilike $2 or ${fields.primaryPhone} ilike $3 or ${ fields.username - } like $4 or ${fields.name} like $5) + } ilike $4 or ${fields.name} ilike $5) limit $6 offset $7 `; @@ -342,6 +360,37 @@ describe('user query', () => { await expect(findUsers(limit, offset, search, true)).resolves.toEqual([dbvalue]); }); + it('findUsers with isCaseSensitive', async () => { + const search = 'foo'; + const limit = 100; + const offset = 1; + const expectSql = sql` + select ${sql.join(Object.values(fields), sql`,`)} + from ${table} + where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${ + fields.username + } like $3 or ${fields.name} like $4 + limit $5 + offset $6 + `; + + mockQuery.mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([ + `%${search}%`, + `%${search}%`, + `%${search}%`, + `%${search}%`, + limit, + offset, + ]); + + return createMockQueryResult([dbvalue]); + }); + + await expect(findUsers(limit, offset, search, undefined, true)).resolves.toEqual([dbvalue]); + }); + it('updateUserById', async () => { const username = 'Joe'; const id = 'foo'; diff --git a/packages/core/src/queries/user.ts b/packages/core/src/queries/user.ts index 67b1bb8d2..6a4e70881 100644 --- a/packages/core/src/queries/user.ts +++ b/packages/core/src/queries/user.ts @@ -84,44 +84,64 @@ export const hasUserWithIdentity = async (target: string, userId: string) => ` ); -const buildUserSearchConditionSql = (search: string) => { +const buildUserSearchConditionSql = (search: string, isCaseSensitive = false) => { const searchFields = [fields.primaryEmail, fields.primaryPhone, fields.username, fields.name]; - const conditions = searchFields.map((filedName) => sql`${filedName} like ${'%' + search + '%'}`); - return sql`${sql.join(conditions, sql` or `)}`; + return sql`${sql.join( + searchFields.map( + (filedName) => + sql`${filedName} ${isCaseSensitive ? sql`like` : sql`ilike`} ${'%' + search + '%'}` + ), + sql` or ` + )}`; }; -const buildUserConditions = (search?: string, hideAdminUser?: boolean) => { +const buildUserConditions = ( + search?: string, + hideAdminUser?: boolean, + isCaseSensitive?: boolean +) => { if (hideAdminUser) { return sql` where not (${fields.roleNames}::jsonb?${UserRole.Admin}) - ${conditionalSql(search, (search) => sql`and (${buildUserSearchConditionSql(search)})`)} + ${conditionalSql( + search, + (search) => sql`and (${buildUserSearchConditionSql(search, isCaseSensitive)})` + )} `; } return sql` - ${conditionalSql(search, (search) => sql`where ${buildUserSearchConditionSql(search)}`)} + ${conditionalSql( + search, + (search) => sql`where ${buildUserSearchConditionSql(search, isCaseSensitive)}` + )} `; }; -export const countUsers = async (search?: string, hideAdminUser?: boolean) => +export const countUsers = async ( + search?: string, + hideAdminUser?: boolean, + isCaseSensitive?: boolean +) => envSet.pool.one<{ count: number }>(sql` select count(*) from ${table} - ${buildUserConditions(search, hideAdminUser)} + ${buildUserConditions(search, hideAdminUser, isCaseSensitive)} `); export const findUsers = async ( limit: number, offset: number, search?: string, - hideAdminUser?: boolean + hideAdminUser?: boolean, + isCaseSensitive?: boolean ) => envSet.pool.any( sql` select ${sql.join(Object.values(fields), sql`,`)} from ${table} - ${buildUserConditions(search, hideAdminUser)} + ${buildUserConditions(search, hideAdminUser, isCaseSensitive)} limit ${limit} offset ${offset} ` diff --git a/packages/core/src/routes/admin-user.ts b/packages/core/src/routes/admin-user.ts index c62653a69..7e3e4a2f4 100644 --- a/packages/core/src/routes/admin-user.ts +++ b/packages/core/src/routes/admin-user.ts @@ -4,6 +4,7 @@ import { has } from '@silverhand/essentials'; import pick from 'lodash.pick'; import { literal, object, string } from 'zod'; +import { isTrue } from '@/env-set/parameters'; import RequestError from '@/errors/RequestError'; import { encryptUserPassword, generateUserId, insertUser } from '@/lib/user'; import koaGuard from '@/middleware/koa-guard'; @@ -27,18 +28,24 @@ export default function adminUserRoutes(router: T) { '/users', koaPagination(), koaGuard({ - query: object({ search: string().optional(), hideAdminUser: literal('true').optional() }), + query: object({ + search: string().optional(), + // Use `.transform()` once the type issue fixed + hideAdminUser: string().optional(), + isCaseSensitive: string().optional(), + }), }), async (ctx, next) => { const { limit, offset } = ctx.pagination; const { - query: { search, hideAdminUser: _hideAdminUser }, + query: { search, hideAdminUser: _hideAdminUser, isCaseSensitive: _isCaseSensitive }, } = ctx.guard; - const hideAdminUser = _hideAdminUser === 'true'; + const hideAdminUser = isTrue(_hideAdminUser); + const isCaseSensitive = isTrue(_isCaseSensitive); const [{ count }, users] = await Promise.all([ - countUsers(search, hideAdminUser), - findUsers(limit, offset, search, hideAdminUser), + countUsers(search, hideAdminUser, isCaseSensitive), + findUsers(limit, offset, search, hideAdminUser, isCaseSensitive), ]); ctx.pagination.totalCount = count; diff --git a/packages/core/src/routes/custom-phrase.test.ts b/packages/core/src/routes/custom-phrase.test.ts index e137ea7b1..007df147b 100644 --- a/packages/core/src/routes/custom-phrase.test.ts +++ b/packages/core/src/routes/custom-phrase.test.ts @@ -40,13 +40,13 @@ jest.mock('@/queries/custom-phrase', () => ({ upsertCustomPhrase: async (customPhrase: CustomPhrase) => upsertCustomPhrase(customPhrase), })); -const isValidStructure = jest.fn( +const isStrictlyPartial = jest.fn( (fullTranslation: Translation, partialTranslation: Partial) => true ); jest.mock('@/utils/translation', () => ({ - isValidStructure: (fullTranslation: Translation, partialTranslation: Translation) => - isValidStructure(fullTranslation, partialTranslation), + isStrictlyPartial: (fullTranslation: Translation, partialTranslation: Translation) => + isStrictlyPartial(fullTranslation, partialTranslation), })); const mockFallbackLanguage = trTrTag; @@ -130,13 +130,13 @@ describe('customPhraseRoutes', () => { }); }); - it('should call isValidStructure', async () => { + it('should call isStrictlyPartial', async () => { await customPhraseRequest.put(`/custom-phrases/${mockLanguageTag}`).send(translation); - expect(isValidStructure).toBeCalledWith(en.translation, translation); + expect(isStrictlyPartial).toBeCalledWith(en.translation, translation); }); it('should fail when the input translation structure is invalid', async () => { - isValidStructure.mockReturnValueOnce(false); + isStrictlyPartial.mockReturnValueOnce(false); const response = await customPhraseRequest .put(`/custom-phrases/${mockLanguageTag}`) .send(translation); diff --git a/packages/core/src/routes/custom-phrase.ts b/packages/core/src/routes/custom-phrase.ts index c32404d89..95c344f94 100644 --- a/packages/core/src/routes/custom-phrase.ts +++ b/packages/core/src/routes/custom-phrase.ts @@ -15,7 +15,7 @@ import { } from '@/queries/custom-phrase'; import { findDefaultSignInExperience } from '@/queries/sign-in-experience'; import assertThat from '@/utils/assert-that'; -import { isValidStructure } from '@/utils/translation'; +import { isStrictlyPartial } from '@/utils/translation'; import type { AuthedRouter } from './types'; @@ -70,7 +70,7 @@ export default function customPhraseRoutes(router: T) { const translation = cleanDeepTranslation(body); assertThat( - isValidStructure(resource.en.translation, translation), + isStrictlyPartial(resource.en.translation, translation), new RequestError('localization.invalid_translation_structure') ); diff --git a/packages/core/src/routes/dashboard.test.ts b/packages/core/src/routes/dashboard.test.ts index 89d75af4d..7e960ab5b 100644 --- a/packages/core/src/routes/dashboard.test.ts +++ b/packages/core/src/routes/dashboard.test.ts @@ -1,4 +1,8 @@ -import dayjs from 'dayjs'; +// The FP version works better for `format()` +/* eslint-disable import/no-duplicates */ +import { endOfDay, subDays } from 'date-fns'; +import { format } from 'date-fns/fp'; +/* eslint-enable import/no-duplicates */ import dashboardRoutes from '@/routes/dashboard'; import { createRequester } from '@/utils/test-utils'; @@ -8,6 +12,7 @@ const countUsers = jest.fn(async () => ({ count: totalUserCount })); const getDailyNewUserCountsByTimeInterval = jest.fn( async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts ); +const formatToQueryDate = format('yyyy-MM-dd'); jest.mock('@/queries/user', () => ({ countUsers: async () => countUsers(), @@ -83,8 +88,8 @@ describe('dashboardRoutes', () => { it('should call getDailyNewUserCountsByTimeInterval with the time interval (14 days ago 23:59:59.999, today 23:59:59.999]', async () => { await logRequest.get('/dashboard/users/new'); expect(getDailyNewUserCountsByTimeInterval).toHaveBeenCalledWith( - dayjs().endOf('day').subtract(14, 'day').valueOf(), - dayjs().endOf('day').valueOf() + subDays(endOfDay(Date.now()), 14).valueOf(), + endOfDay(Date.now()).valueOf() ); }); @@ -105,10 +110,10 @@ describe('dashboardRoutes', () => { }); describe('GET /dashboard/users/active', () => { - const mockToday = '2022-05-30'; + const mockToday = new Date(2022, 4, 30); beforeEach(() => { - jest.useFakeTimers().setSystemTime(new Date(mockToday)); + jest.useFakeTimers().setSystemTime(mockToday); }); it('should fail when the parameter `date` does not match the date regex', async () => { @@ -117,44 +122,44 @@ describe('dashboardRoutes', () => { }); it('should call getDailyActiveUserCountsByTimeInterval with the time interval (2022-05-31, 2022-06-30] when the parameter `date` is 2022-06-30', async () => { - const targetDate = '2022-06-30'; - await logRequest.get(`/dashboard/users/active?date=${targetDate}`); + const targetDate = new Date(2022, 5, 30); + await logRequest.get(`/dashboard/users/active?date=${formatToQueryDate(targetDate)}`); expect(getDailyActiveUserCountsByTimeInterval).toHaveBeenCalledWith( - dayjs('2022-05-31').endOf('day').valueOf(), - dayjs(targetDate).endOf('day').valueOf() + endOfDay(new Date(2022, 4, 31)).valueOf(), + endOfDay(targetDate).valueOf() ); }); it('should call getDailyActiveUserCountsByTimeInterval with the time interval (30 days ago, tomorrow] when there is no parameter `date`', async () => { await logRequest.get('/dashboard/users/active'); expect(getDailyActiveUserCountsByTimeInterval).toHaveBeenCalledWith( - dayjs('2022-04-30').endOf('day').valueOf(), - dayjs(mockToday).endOf('day').valueOf() + endOfDay(new Date(2022, 3, 30)).valueOf(), + endOfDay(mockToday).valueOf() ); }); it('should call countActiveUsersByTimeInterval with correct parameters when the parameter `date` is 2022-06-30', async () => { - const targetDate = '2022-06-30'; - await logRequest.get(`/dashboard/users/active?date=${targetDate}`); + const targetDate = new Date(2022, 5, 30); + await logRequest.get(`/dashboard/users/active?date=${formatToQueryDate(targetDate)}`); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 1, - dayjs('2022-06-16').endOf('day').valueOf(), - dayjs('2022-06-23').endOf('day').valueOf() + endOfDay(new Date(2022, 5, 16)).valueOf(), + endOfDay(new Date(2022, 5, 23)).valueOf() ); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 2, - dayjs('2022-06-23').endOf('day').valueOf(), - dayjs(targetDate).endOf('day').valueOf() + endOfDay(new Date(2022, 5, 23)).valueOf(), + endOfDay(targetDate).valueOf() ); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 3, - dayjs('2022-05-01').endOf('day').valueOf(), - dayjs('2022-05-31').endOf('day').valueOf() + endOfDay(new Date(2022, 4, 1)).valueOf(), + endOfDay(new Date(2022, 4, 31)).valueOf() ); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 4, - dayjs('2022-05-31').endOf('day').valueOf(), - dayjs(targetDate).endOf('day').valueOf() + endOfDay(new Date(2022, 4, 31)).valueOf(), + endOfDay(targetDate).valueOf() ); }); @@ -162,23 +167,23 @@ describe('dashboardRoutes', () => { await logRequest.get('/dashboard/users/active'); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 1, - dayjs('2022-05-16').endOf('day').valueOf(), - dayjs('2022-05-23').endOf('day').valueOf() + endOfDay(new Date(2022, 4, 16)).valueOf(), + endOfDay(new Date(2022, 4, 23)).valueOf() ); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 2, - dayjs('2022-05-23').endOf('day').valueOf(), - dayjs(mockToday).endOf('day').valueOf() + endOfDay(new Date(2022, 4, 23)).valueOf(), + endOfDay(mockToday).valueOf() ); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 3, - dayjs('2022-03-31').endOf('day').valueOf(), - dayjs('2022-04-30').endOf('day').valueOf() + endOfDay(new Date(2022, 2, 31)).valueOf(), + endOfDay(new Date(2022, 3, 30)).valueOf() ); expect(countActiveUsersByTimeInterval).toHaveBeenNthCalledWith( 4, - dayjs('2022-04-30').endOf('day').valueOf(), - dayjs(mockToday).endOf('day').valueOf() + endOfDay(new Date(2022, 3, 30)).valueOf(), + endOfDay(mockToday).valueOf() ); }); diff --git a/packages/core/src/routes/dashboard.ts b/packages/core/src/routes/dashboard.ts index c708fb060..e89247929 100644 --- a/packages/core/src/routes/dashboard.ts +++ b/packages/core/src/routes/dashboard.ts @@ -1,6 +1,5 @@ import { dateRegex } from '@logto/core-kit'; -import type { Dayjs } from 'dayjs'; -import dayjs from 'dayjs'; +import { endOfDay, format, subDays } from 'date-fns'; import { object, string } from 'zod'; import koaGuard from '@/middleware/koa-guard'; @@ -12,11 +11,11 @@ import { countUsers, getDailyNewUserCountsByTimeInterval } from '@/queries/user' import type { AuthedRouter } from './types'; -const getDateString = (day: Dayjs) => day.format('YYYY-MM-DD'); +const getDateString = (date: Date | number) => format(date, 'yyyy-MM-dd'); const indices = (length: number) => [...Array.from({ length }).keys()]; -const lastTimestampOfDay = (day: Dayjs) => day.endOf('day').valueOf(); +const getEndOfDayTimestamp = (date: Date | number) => endOfDay(date).valueOf(); export default function dashboardRoutes(router: T) { router.get('/dashboard/users/total', async (ctx, next) => { @@ -27,11 +26,11 @@ export default function dashboardRoutes(router: T) { }); router.get('/dashboard/users/new', async (ctx, next) => { - const today = dayjs(); + const today = Date.now(); const dailyNewUserCounts = await getDailyNewUserCountsByTimeInterval( // (14 days ago 23:59:59.999, today 23:59:59.999] - lastTimestampOfDay(today.subtract(14, 'day')), - lastTimestampOfDay(today) + getEndOfDayTimestamp(subDays(today, 14)), + getEndOfDayTimestamp(today) ); const last14DaysNewUserCounts = new Map( @@ -39,15 +38,15 @@ export default function dashboardRoutes(router: T) { ); const todayNewUserCount = last14DaysNewUserCounts.get(getDateString(today)) ?? 0; - const yesterday = today.subtract(1, 'day'); + const yesterday = subDays(today, 1); const yesterdayNewUserCount = last14DaysNewUserCounts.get(getDateString(yesterday)) ?? 0; const todayDelta = todayNewUserCount - yesterdayNewUserCount; const last7DaysNewUserCount = indices(7) - .map((index) => getDateString(today.subtract(index, 'day'))) + .map((index) => getDateString(subDays(today, index))) .reduce((sum, date) => sum + (last14DaysNewUserCounts.get(date) ?? 0), 0); const newUserCountFrom13DaysAgoTo7DaysAgo = indices(7) - .map((index) => getDateString(today.subtract(7 + index, 'day'))) + .map((index) => getDateString(subDays(today, index + 7))) .reduce((sum, date) => sum + (last14DaysNewUserCounts.get(date) ?? 0), 0); const last7DaysDelta = last7DaysNewUserCount - newUserCountFrom13DaysAgoTo7DaysAgo; @@ -75,7 +74,7 @@ export default function dashboardRoutes(router: T) { query: { date }, } = ctx.guard; - const targetDay = date ? dayjs(date) : dayjs(); // Defaults to today + const targetDay = date ? new Date(date) : new Date(); // Defaults to today const [ // DAU: Daily Active User last30DauCounts, @@ -88,39 +87,39 @@ export default function dashboardRoutes(router: T) { ] = await Promise.all([ getDailyActiveUserCountsByTimeInterval( // (30 days ago 23:59:59.999, target day 23:59:59.999] - lastTimestampOfDay(targetDay.subtract(30, 'day')), - lastTimestampOfDay(targetDay) + getEndOfDayTimestamp(subDays(targetDay, 30)), + getEndOfDayTimestamp(targetDay) ), countActiveUsersByTimeInterval( // (14 days ago 23:59:59.999, 7 days ago 23:59:59.999] - lastTimestampOfDay(targetDay.subtract(14, 'day')), - lastTimestampOfDay(targetDay.subtract(7, 'day')) + getEndOfDayTimestamp(subDays(targetDay, 14)), + getEndOfDayTimestamp(subDays(targetDay, 7)) ), countActiveUsersByTimeInterval( // (7 days ago 23:59:59.999, target day 23:59:59.999] - lastTimestampOfDay(targetDay.subtract(7, 'day')), - lastTimestampOfDay(targetDay) + getEndOfDayTimestamp(subDays(targetDay, 7)), + getEndOfDayTimestamp(targetDay) ), countActiveUsersByTimeInterval( // (60 days ago 23:59:59.999, 30 days ago 23:59:59.999] - lastTimestampOfDay(targetDay.subtract(60, 'day')), - lastTimestampOfDay(targetDay.subtract(30, 'day')) + getEndOfDayTimestamp(subDays(targetDay, 60)), + getEndOfDayTimestamp(subDays(targetDay, 30)) ), countActiveUsersByTimeInterval( // (30 days ago 23:59:59.999, target day 23:59:59.999] - lastTimestampOfDay(targetDay.subtract(30, 'day')), - lastTimestampOfDay(targetDay) + getEndOfDayTimestamp(subDays(targetDay, 30)), + getEndOfDayTimestamp(targetDay) ), ]); - const previousDate = getDateString(targetDay.subtract(1, 'day')); + const previousDate = getDateString(subDays(targetDay, 1)); const targetDate = getDateString(targetDay); const previousDAU = last30DauCounts.find(({ date }) => date === previousDate)?.count ?? 0; const dau = last30DauCounts.find(({ date }) => date === targetDate)?.count ?? 0; const dauCurve = indices(30).map((index) => { - const dateString = getDateString(targetDay.subtract(29 - index, 'day')); + const dateString = getDateString(subDays(targetDay, 29 - index)); const count = last30DauCounts.find(({ date }) => date === dateString)?.count ?? 0; return { date: dateString, count }; diff --git a/packages/core/src/routes/session/continue.test.ts b/packages/core/src/routes/session/continue.test.ts index 12a0f6b52..3d2af5dea 100644 --- a/packages/core/src/routes/session/continue.test.ts +++ b/packages/core/src/routes/session/continue.test.ts @@ -1,4 +1,4 @@ -import dayjs from 'dayjs'; +import { addDays, subSeconds } from 'date-fns'; import { Provider } from 'oidc-provider'; import { mockUser } from '@/__mocks__'; @@ -6,6 +6,7 @@ import { createRequester } from '@/utils/test-utils'; import continueRoutes, { continueRoute } from './continue'; +const getTomorrowIsoString = () => addDays(Date.now(), 1).toISOString(); const getVerificationStorageFromInteraction = jest.fn(); const checkRequiredProfile = jest.fn(); @@ -68,7 +69,7 @@ describe('session -> continueRoutes', () => { result: { continueSignIn: { userId: mockUser.id, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -100,7 +101,7 @@ describe('session -> continueRoutes', () => { result: { continueSignIn: { userId: mockUser.id, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -136,7 +137,7 @@ describe('session -> continueRoutes', () => { result: { continueSignIn: { userId: mockUser.id, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -167,7 +168,7 @@ describe('session -> continueRoutes', () => { result: { continueSignIn: { userId: mockUser.id, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -214,7 +215,7 @@ describe('session -> continueRoutes', () => { result: { continueSignIn: { userId: mockUser.id, - expiresAt: dayjs().subtract(1, 'second').toISOString(), + expiresAt: subSeconds(Date.now(), 1).toISOString(), }, }, }); diff --git a/packages/core/src/routes/session/forgot-password.test.ts b/packages/core/src/routes/session/forgot-password.test.ts index 35b5e7a0b..e517b6068 100644 --- a/packages/core/src/routes/session/forgot-password.test.ts +++ b/packages/core/src/routes/session/forgot-password.test.ts @@ -1,6 +1,6 @@ import type { User } from '@logto/schemas'; import { PasscodeType } from '@logto/schemas'; -import dayjs from 'dayjs'; +import { addDays, subDays } from 'date-fns'; import { Provider } from 'oidc-provider'; import { mockPasswordEncrypted, mockSignInExperience, mockUserWithPassword } from '@/__mocks__'; @@ -16,6 +16,8 @@ const encryptUserPassword = jest.fn(async (password: string) => ({ const findUserById = jest.fn(async (): Promise => mockUserWithPassword); const updateUserById = jest.fn(async (..._args: unknown[]) => ({ userId: 'id' })); const findDefaultSignInExperience = jest.fn(async () => mockSignInExperience); +const getYesterdayDate = () => subDays(Date.now(), 1); +const getTomorrowDate = () => addDays(Date.now(), 1); jest.mock('@/lib/user', () => ({ ...jest.requireActual('@/lib/user'), @@ -89,7 +91,7 @@ describe('session -> forgotPasswordRoutes', () => { result: { verification: { userId: 'id', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowDate().toISOString(), flow: PasscodeType.ForgotPassword, }, }, @@ -110,7 +112,7 @@ describe('session -> forgotPasswordRoutes', () => { interactionDetails.mockResolvedValueOnce({ result: { verification: { - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowDate().toISOString(), flow: PasscodeType.ForgotPassword, }, }, @@ -126,7 +128,7 @@ describe('session -> forgotPasswordRoutes', () => { result: { verification: { userId: 'id', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowDate().toISOString(), flow: PasscodeType.SignIn, }, }, @@ -170,7 +172,7 @@ describe('session -> forgotPasswordRoutes', () => { result: { verification: { userId: 'id', - expiresAt: dayjs().subtract(1, 'day').toISOString(), + expiresAt: getYesterdayDate().toISOString(), flow: PasscodeType.ForgotPassword, }, }, @@ -186,7 +188,7 @@ describe('session -> forgotPasswordRoutes', () => { result: { verification: { userId: 'id', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowDate().toISOString(), flow: PasscodeType.ForgotPassword, }, }, @@ -203,7 +205,7 @@ describe('session -> forgotPasswordRoutes', () => { result: { verification: { userId: 'id', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowDate().toISOString(), flow: PasscodeType.ForgotPassword, }, }, diff --git a/packages/core/src/routes/session/passwordless.test.ts b/packages/core/src/routes/session/passwordless.test.ts index 372fdc87e..47dc04c74 100644 --- a/packages/core/src/routes/session/passwordless.test.ts +++ b/packages/core/src/routes/session/passwordless.test.ts @@ -1,7 +1,7 @@ /* eslint-disable max-lines */ import type { User } from '@logto/schemas'; import { PasscodeType, SignInIdentifier, SignUpIdentifier } from '@logto/schemas'; -import dayjs from 'dayjs'; +import { addDays, addSeconds, subDays } from 'date-fns'; import { Provider } from 'oidc-provider'; import { mockSignInExperience, mockSignInMethod, mockUser } from '@/__mocks__'; @@ -25,6 +25,7 @@ const findDefaultSignInExperience = jest.fn(async () => ({ verify: true, }, })); +const getTomorrowIsoString = () => addDays(Date.now(), 1).toISOString(); jest.mock('@/lib/user', () => ({ generateUserId: () => 'user1', @@ -203,7 +204,7 @@ describe('session -> passwordlessRoutes', () => { verification: { flow: PasscodeType.SignIn, phone: '13000000000', - expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(), + expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(), }, }) ); @@ -227,7 +228,7 @@ describe('session -> passwordlessRoutes', () => { verification: { flow: PasscodeType.Register, phone: '13000000000', - expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(), + expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(), }, }) ); @@ -251,7 +252,7 @@ describe('session -> passwordlessRoutes', () => { expect.objectContaining({ verification: { userId: mockUser.id, - expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(), + expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(), flow: PasscodeType.ForgotPassword, }, }) @@ -300,7 +301,7 @@ describe('session -> passwordlessRoutes', () => { verification: { flow: PasscodeType.SignIn, email: 'a@a.com', - expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(), + expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(), }, }) ); @@ -323,7 +324,7 @@ describe('session -> passwordlessRoutes', () => { verification: { flow: PasscodeType.Register, email: 'a@a.com', - expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(), + expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(), }, }) ); @@ -347,7 +348,7 @@ describe('session -> passwordlessRoutes', () => { expect.objectContaining({ verification: { userId: mockUser.id, - expiresAt: dayjs(fakeTime).add(verificationTimeout, 'second').toISOString(), + expiresAt: addSeconds(fakeTime, verificationTimeout).toISOString(), flow: PasscodeType.ForgotPassword, }, }) @@ -379,7 +380,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000000', flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -403,7 +404,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000000', flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -424,7 +425,7 @@ describe('session -> passwordlessRoutes', () => { result: { verification: { phone: '13000000000', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -438,7 +439,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000000', flow: PasscodeType.ForgotPassword, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -466,7 +467,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000000', flow: PasscodeType.SignIn, - expiresAt: dayjs().subtract(1, 'day').toISOString(), + expiresAt: subDays(Date.now(), 1).toISOString(), }, }, }); @@ -480,7 +481,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'XX@foo', flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -494,7 +495,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000001', flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -542,7 +543,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'a@a.com', flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -566,7 +567,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'a@a.com', flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -589,7 +590,7 @@ describe('session -> passwordlessRoutes', () => { result: { verification: { email: 'a@a.com', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -603,7 +604,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'a@a.com', flow: PasscodeType.ForgotPassword, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -616,7 +617,7 @@ describe('session -> passwordlessRoutes', () => { result: { verification: { flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -630,7 +631,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'b@a.com', flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -677,7 +678,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000001', flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -699,7 +700,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000001', flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -720,7 +721,7 @@ describe('session -> passwordlessRoutes', () => { result: { verification: { phone: '13000000001', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -734,7 +735,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000001', flow: PasscodeType.ForgotPassword, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -747,7 +748,7 @@ describe('session -> passwordlessRoutes', () => { result: { verification: { flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -761,7 +762,7 @@ describe('session -> passwordlessRoutes', () => { verification: { phone: '13000000000', flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -805,7 +806,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'b@a.com', flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -827,7 +828,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'b@a.com', flow: PasscodeType.SignIn, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -848,7 +849,7 @@ describe('session -> passwordlessRoutes', () => { result: { verification: { email: 'b@a.com', - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -862,7 +863,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'b@a.com', flow: PasscodeType.ForgotPassword, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -875,7 +876,7 @@ describe('session -> passwordlessRoutes', () => { result: { verification: { flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); @@ -889,7 +890,7 @@ describe('session -> passwordlessRoutes', () => { verification: { email: 'a@a.com', flow: PasscodeType.Register, - expiresAt: dayjs().add(1, 'day').toISOString(), + expiresAt: getTomorrowIsoString(), }, }, }); diff --git a/packages/core/src/routes/session/utils.ts b/packages/core/src/routes/session/utils.ts index 9f90e78a8..0a0273f66 100644 --- a/packages/core/src/routes/session/utils.ts +++ b/packages/core/src/routes/session/utils.ts @@ -8,7 +8,7 @@ import type { } from '@logto/schemas'; import { SignUpIdentifier, logTypeGuard } from '@logto/schemas'; import type { Nullable, Truthy } from '@silverhand/essentials'; -import dayjs from 'dayjs'; +import { addSeconds, isAfter, isValid } from 'date-fns'; import type { Context } from 'koa'; import type { Provider } from 'oidc-provider'; import type { ZodType } from 'zod'; @@ -73,8 +73,9 @@ export const getVerificationStorageFromInteraction = async { + const parsed = new Date(expiresAt); assertThat( - dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()), + isValid(parsed) && isAfter(parsed, Date.now()), new RequestError({ code: 'session.verification_expired', status: 401 }) ); }; @@ -88,7 +89,7 @@ export const assignVerificationResult = async ( ) => { const verification: VerificationStorage = { ...verificationData, - expiresAt: dayjs().add(verificationTimeout, 'second').toISOString(), + expiresAt: addSeconds(Date.now(), verificationTimeout).toISOString(), }; await provider.interactionResult(ctx.req, ctx.res, { @@ -116,7 +117,7 @@ export const assignContinueSignInResult = async ( await provider.interactionResult(ctx.req, ctx.res, { continueSignIn: { ...payload, - expiresAt: dayjs().add(continueSignInTimeout, 'second').toISOString(), + expiresAt: addSeconds(Date.now(), continueSignInTimeout).toISOString(), }, }); }; @@ -142,8 +143,9 @@ export const getContinueSignInResult = async ( const { expiresAt, ...rest } = signInResult.data.continueSignIn; + const parsed = new Date(expiresAt); assertThat( - dayjs(expiresAt).isValid() && dayjs(expiresAt).isAfter(dayjs()), + isValid(parsed) && isAfter(parsed, Date.now()), new RequestError({ code: 'session.unauthorized', status: 401 }) ); diff --git a/packages/core/src/utils/translation.test.ts b/packages/core/src/utils/translation.test.ts index c492cb099..d9bb27671 100644 --- a/packages/core/src/utils/translation.test.ts +++ b/packages/core/src/utils/translation.test.ts @@ -1,7 +1,7 @@ import en from '@logto/phrases-ui/lib/locales/en'; import fr from '@logto/phrases-ui/lib/locales/fr'; -import { isValidStructure } from '@/utils/translation'; +import { isStrictlyPartial } from '@/utils/translation'; const customizedFrTranslation = { secondary: { @@ -10,15 +10,15 @@ const customizedFrTranslation = { }, }; -describe('isValidStructure', () => { +describe('isStrictlyPartial', () => { it('should be true when its structure is valid', () => { - expect(isValidStructure(en.translation, fr.translation)).toBeTruthy(); - expect(isValidStructure(en.translation, customizedFrTranslation)).toBeTruthy(); + expect(isStrictlyPartial(en.translation, fr.translation)).toBeTruthy(); + expect(isStrictlyPartial(en.translation, customizedFrTranslation)).toBeTruthy(); }); it('should be true when the structure is partial and the existing key-value pairs are correct', () => { expect( - isValidStructure(en.translation, { + isStrictlyPartial(en.translation, { secondary: { sign_in_with: 'Se connecter avec {{methods, list(type: disjunction;)}}', // Missing 'secondary.social_bind_with' key-value pair @@ -29,7 +29,7 @@ describe('isValidStructure', () => { it('should be false when there is an unexpected key-value pair', () => { expect( - isValidStructure(en.translation, { + isStrictlyPartial(en.translation, { secondary: { sign_in_with: 'Se connecter avec {{methods, list(type: disjunction;)}}', social_bind_with: diff --git a/packages/core/src/utils/translation.ts b/packages/core/src/utils/translation.ts index 4801531d7..73932c99d 100644 --- a/packages/core/src/utils/translation.ts +++ b/packages/core/src/utils/translation.ts @@ -1,38 +1,25 @@ import type { Translation } from '@logto/schemas'; -// LOG-4385: Refactor me -// eslint-disable-next-line complexity -export const isValidStructure = (fullTranslation: Translation, partialTranslation: Translation) => { - const fullKeys = new Set(Object.keys(fullTranslation)); - const partialKeys = Object.keys(partialTranslation); - - if (fullKeys.size === 0 || partialKeys.length === 0) { - return true; - } - - if (partialKeys.some((key) => !fullKeys.has(key))) { - return false; - } - - for (const [key, value] of Object.entries(fullTranslation)) { - const targetValue = partialTranslation[key]; - - if (targetValue === undefined) { - continue; - } - - if (typeof value === 'string') { - if (typeof targetValue === 'string') { - continue; - } +/** + * @param fullTranslation The translation with full keys + * @param partialTranslation The translation to check + * @returns If the flatten keys of `partialTranslation` is a subset of `fullTranslation` + */ +export const isStrictlyPartial = ( + fullTranslation: Translation, + partialTranslation: Translation +): boolean => { + return Object.entries(partialTranslation).every(([key, value]) => { + const fullValue = fullTranslation[key]; + if (!fullValue) { return false; } - if (typeof targetValue === 'string' || !isValidStructure(value, targetValue)) { - return false; + if (typeof fullValue === 'object' && typeof value === 'object') { + return isStrictlyPartial(fullValue, value); } - } - return true; + return typeof fullValue === typeof value; + }); }; diff --git a/packages/create/package.json b/packages/create/package.json index a950e218f..82161724c 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -11,7 +11,7 @@ }, "scripts": {}, "engines": { - "node": "^16.0.0" + "node": "^16.13.0 || ^18.12.0" }, "dependencies": { "@logto/cli": "workspace:^" diff --git a/packages/demo-app/package.json b/packages/demo-app/package.json index 3aa49eb91..8e310b50b 100644 --- a/packages/demo-app/package.json +++ b/packages/demo-app/package.json @@ -20,7 +20,7 @@ "@logto/core-kit": "1.0.0-beta.20", "@logto/language-kit": "1.0.0-beta.20", "@logto/phrases": "workspace:^", - "@logto/react": "1.0.0-beta.11", + "@logto/react": "1.0.0-beta.12", "@logto/schemas": "workspace:^", "@parcel/core": "2.7.0", "@parcel/transformer-sass": "2.7.0", @@ -44,6 +44,17 @@ "stylelint": "^14.9.1", "typescript": "^4.7.4" }, + "engines": { + "node": "^16.13.0 || ^18.12.0" + }, + "//": "https://github.com/parcel-bundler/parcel/issues/7636", + "targets": { + "default": { + "engines": { + "browsers": "defaults" + } + } + }, "alias": { "@/*": "./src/$1" }, diff --git a/packages/integration-tests/jest.config.ts b/packages/integration-tests/jest.config.ts index e397c3752..d9955574a 100644 --- a/packages/integration-tests/jest.config.ts +++ b/packages/integration-tests/jest.config.ts @@ -1,7 +1,7 @@ -import { merge, Config } from '@silverhand/jest-config'; +import type { Config } from '@silverhand/jest-config'; +import { merge } from '@silverhand/jest-config'; const config: Config.InitialOptions = merge({ - testEnvironment: 'jsdom', setupFilesAfterEnv: ['/jest.setup.js'], }); diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index ae31215c1..b0bfe6af6 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -15,7 +15,8 @@ }, "devDependencies": { "@jest/types": "^29.1.2", - "@logto/node": "1.0.0-beta.11", + "@logto/js": "1.0.0-beta.11", + "@logto/node": "1.0.0-beta.12", "@logto/schemas": "workspace:^", "@peculiar/webcrypto": "^1.3.3", "@silverhand/eslint-config": "1.3.0", @@ -27,18 +28,21 @@ "@types/node": "^16.0.0", "dotenv": "^16.0.0", "eslint": "^8.21.0", - "got": "^11.8.2", + "got": "^11.8.5", "jest": "^29.1.2", "jest-puppeteer": "^6.1.1", "node-fetch": "^2.6.7", "openapi-schema-validator": "^12.0.0", "openapi-types": "^12.0.0", "prettier": "^2.7.1", - "puppeteer": "^18.0.0", + "puppeteer": "^19.0.0", "text-encoder": "^0.0.4", "ts-node": "^10.9.1", "typescript": "^4.7.4" }, + "engines": { + "node": "^16.13.0 || ^18.12.0" + }, "eslintConfig": { "extends": "@silverhand" }, diff --git a/packages/integration-tests/src/client/index.ts b/packages/integration-tests/src/client/index.ts index 75dfe7047..e5129e839 100644 --- a/packages/integration-tests/src/client/index.ts +++ b/packages/integration-tests/src/client/index.ts @@ -10,7 +10,7 @@ import { extractCookie } from '@/utils'; import { MemoryStorage } from './storage'; -const defaultConfig = { +export const defaultConfig = { endpoint: logtoUrl, appId: demoAppApplicationId, persistAccessToken: false, @@ -18,8 +18,8 @@ const defaultConfig = { export default class MockClient { public interactionCookie?: string; - private navigateUrl?: string; + private navigateUrl?: string; private readonly storage: MemoryStorage; private readonly logto: LogtoClient; @@ -88,6 +88,10 @@ export default class MockClient { return this.logto.getAccessToken(resource); } + public async getRefreshToken() { + return this.logto.getRefreshToken(); + } + public async signOut(postSignOutRedirectUri?: string) { return this.logto.signOut(postSignOutRedirectUri); } diff --git a/packages/integration-tests/src/include.d/node-fetch.d.ts b/packages/integration-tests/src/include.d/node-fetch.d.ts new file mode 100644 index 000000000..dd1a8efbe --- /dev/null +++ b/packages/integration-tests/src/include.d/node-fetch.d.ts @@ -0,0 +1,4 @@ +declare module 'node-fetch' { + const nodeFetch: typeof fetch; + export = nodeFetch; +} diff --git a/packages/integration-tests/tests/api/get-access-token.test.ts b/packages/integration-tests/tests/api/get-access-token.test.ts index 94e10e720..da220cdf4 100644 --- a/packages/integration-tests/tests/api/get-access-token.test.ts +++ b/packages/integration-tests/tests/api/get-access-token.test.ts @@ -1,8 +1,13 @@ +import path from 'path'; + +import { fetchTokenByRefreshToken } from '@logto/js'; import { managementResource } from '@logto/schemas/lib/seeds'; import { assert } from '@silverhand/essentials'; +import fetch from 'node-fetch'; import { signInWithUsernameAndPassword } from '@/api'; -import MockClient from '@/client'; +import MockClient, { defaultConfig } from '@/client'; +import { logtoUrl } from '@/constants'; import { createUserByAdmin } from '@/helpers'; import { generateUsername, generatePassword } from '@/utils'; @@ -36,4 +41,41 @@ describe('get access token', () => { // Request for invalid resource should throw void expect(client.getAccessToken('api.foo.com')).rejects.toThrow(); }); + + it('sign-in and get multiple Access Token by the same Refresh Token within refreshTokenReuseInterval', async () => { + const client = new MockClient({ resources: [managementResource.indicator] }); + await client.initSession(); + assert(client.interactionCookie, new Error('Session not found')); + + const { redirectTo } = await signInWithUsernameAndPassword( + username, + password, + client.interactionCookie + ); + + await client.processSession(redirectTo); + assert(client.isAuthenticated, new Error('Sign in get get access token failed')); + + const refreshToken = await client.getRefreshToken(); + assert(refreshToken, new Error('No Refresh Token found')); + + const getAccessTokenByRefreshToken = async () => + fetchTokenByRefreshToken( + { + clientId: defaultConfig.appId, + tokenEndpoint: path.join(logtoUrl, '/oidc/token'), + refreshToken, + resource: managementResource.indicator, + }, + async (...args: Parameters): Promise => { + const response = await fetch(...args); + assert(response.ok, new Error('Request error')); + + return response.json(); + } + ); + + // Allow to use the same refresh token to fetch access token within short time period + await Promise.all([getAccessTokenByRefreshToken(), getAccessTokenByRefreshToken()]); + }); }); diff --git a/packages/phrases-ui/package.json b/packages/phrases-ui/package.json index df0a50a77..0b690b2a2 100644 --- a/packages/phrases-ui/package.json +++ b/packages/phrases-ui/package.json @@ -44,6 +44,9 @@ "prettier": "^2.7.1", "typescript": "^4.7.4" }, + "engines": { + "node": "^16.13.0 || ^18.12.0" + }, "eslintConfig": { "extends": "@silverhand" }, diff --git a/packages/phrases-ui/src/index.ts b/packages/phrases-ui/src/index.ts index 792000a91..247666d59 100644 --- a/packages/phrases-ui/src/index.ts +++ b/packages/phrases-ui/src/index.ts @@ -4,6 +4,7 @@ import { languages } from '@logto/language-kit'; import type { NormalizeKeyPaths } from '@silverhand/essentials'; import { z } from 'zod'; +import de from './locales/de'; import en from './locales/en'; import fr from './locales/fr'; import ko from './locales/ko'; @@ -16,7 +17,7 @@ export type { LocalePhrase } from './types'; export type I18nKey = NormalizeKeyPaths; -export const builtInLanguages = ['en', 'fr', 'pt-PT', 'zh-CN', 'ko', 'tr-TR'] as const; +export const builtInLanguages = ['de', 'en', 'fr', 'ko', 'pt-PT', 'tr-TR', 'zh-CN'] as const; export const builtInLanguageOptions = builtInLanguages.map((languageTag) => ({ value: languageTag, @@ -30,12 +31,13 @@ export type BuiltInLanguageTag = z.infer; export type Resource = Record; const resource: Resource = { + de, en, fr, - 'pt-PT': ptPT, - 'zh-CN': zhCN, ko, + 'pt-PT': ptPT, 'tr-TR': trTR, + 'zh-CN': zhCN, }; export const getDefaultLanguageTag = (language: string): LanguageTag => diff --git a/packages/phrases-ui/src/locales/de.ts b/packages/phrases-ui/src/locales/de.ts new file mode 100644 index 000000000..8e02cfc5b --- /dev/null +++ b/packages/phrases-ui/src/locales/de.ts @@ -0,0 +1,99 @@ +import type { LocalePhrase } from '../types'; + +const translation = { + input: { + username: 'Benutzername', + password: 'Passwort', + email: 'Email', + phone_number: 'Telefonnummer', + confirm_password: 'Passwort bestätigen', + }, + secondary: { + sign_in_with: 'Anmelden mit {{methods, list(type: disjunction;)}}', + register_with: 'Create account with {{methods, list(type: disjunction;)}}', // UNTRANSLATED + social_bind_with: + 'Besitzt du schon ein Konto? Melde dich an, um {{methods, list(type: disjunction;)}} mit deiner Identität zu verbinden.', + }, + action: { + sign_in: 'Anmelden', + continue: 'Weiter', + create_account: 'Konto erstellen', + create: 'Erstellen', + enter_passcode: 'Bestätigungscode eingeben', + confirm: 'Bestätigen', + cancel: 'Abbrechen', + save_password: 'Speichern', + bind: 'Mit {{address}} verknüpfen', + back: 'Gehe zurück', + nav_back: 'Zurück', + agree: 'Zustimmen', + got_it: 'Alles klar', + sign_in_with: 'Mit {{name}} anmelden', + forgot_password: 'Passwort vergessen?', + switch_to: 'Zu {{method}} wechseln', + sign_in_via_passcode: 'Sign in via verification code', // UNTRANSLATED + sign_in_via_password: 'Sign in via password', // UNTRANSLATED + }, + description: { + email: 'Email', + phone_number: 'Telefonnummer', + reminder: 'Erinnerung', + not_found: '404 Nicht gefunden', + agree_with_terms: 'Ich akzeptiere die ', + agree_with_terms_modal: 'Bitte akzeptiere die .', + terms_of_use: 'Nutzungsbedingungen', + create_account: 'Konto erstellen', + or: 'oder', + enter_passcode: 'Der Bestätigungscode wurde an deine {{address}} gesendet', + passcode_sent: 'Der Bestätigungscode wurde erneut gesendet', + resend_after_seconds: 'Nach {{seconds}} Sekunden erneut senden', + resend_passcode: 'Bestätigungscode erneut senden', + continue_with: 'Weiter mit', + create_account_id_exists: + 'Das Konto mit {{type}} {{value}} existiert bereits, möchtest du dich anmelden?', + sign_in_id_does_not_exists: + 'Das Konto mit {{type}} {{value}} existiert nicht, möchtest du ein neues Konto erstellen?', + sign_in_id_does_not_exists_alert: 'The account with {{type}} {{value}} does not exist.', // UNTRANSLATED + create_account_id_exists_alert: 'The account with {{type}} {{value}} already exists', // UNTRANSLATED + forgot_password_id_does_not_exits: 'Das Konto mit {{type}} {{value}} existiert nicht.', + bind_account_title: 'Konto verknüpfen', + social_create_account: 'Kein Konto? Du kannst ein neues Konto erstellen und es verknüpfen.', + social_bind_account: + 'Besitzt du schon ein Konto? Melde dich an, um die Identität zu verknüpfen.', + social_bind_with_existing: 'Wir haben ein Konto gefunden, das du verknüpfen kannst.', + reset_password: 'Passwort zurücksetzen', + reset_password_description_email: + 'Gib die Email Adresse deines Kontos ein und wir senden dir einen Bestätigungscode um dein Passwort zurückzusetzen.', + reset_password_description_sms: + 'Gib die Telefonnummer deines Kontos ein und wir senden dir einen Bestätigungscode um dein Passwort zurückzusetzen.', + new_password: 'Neues Passwort', + password_changed: 'Passwort geändert', + no_account: "Don't have an account?", // UNTRANSLATED + have_account: 'Already have an account?', // UNTRANSLATED + enter_password: 'Enter Password', // UNTRANSLATED + enter_password_for: 'Enter the password of {{method}} {{value}}', // UNTRANSLATED + }, + error: { + username_password_mismatch: 'Benutzername oder Passwort ist falsch', + username_required: 'Benutzername ist erforderlich', + password_required: 'Passwort ist erforderlich', + username_exists: 'Benutzername existiert bereits', + username_should_not_start_with_number: 'Benutzername darf nicht mit einer Zahl beginnen', + username_valid_charset: 'Benutzername darf nur Buchstaben, Zahlen und Unterstriche enthalten', + invalid_email: 'Die Email ist ungültig', + invalid_phone: 'Die Telefonnummer ist ungültig', + password_min_length: 'Passwort muss mindestens {{min}} Zeichen lang sein', + passwords_do_not_match: 'Passwörter stimmen nicht überein', + invalid_passcode: 'Der Bestätigungscode ist ungültig', + invalid_connector_auth: 'Die Autorisierung ist ungültig', + invalid_connector_request: 'Connector Daten sind ungültig', + unknown: 'Unbekannter Fehler. Versuche es später noch einmal.', + invalid_session: 'Die Sitzung ist ungültig. Bitte melde dich erneut an.', + }, +}; + +const de: LocalePhrase = Object.freeze({ + translation, +}); + +export default de; diff --git a/packages/phrases/package.json b/packages/phrases/package.json index f79eef4b7..0eef2125d 100644 --- a/packages/phrases/package.json +++ b/packages/phrases/package.json @@ -25,7 +25,7 @@ "prepack": "pnpm build" }, "engines": { - "node": "^16.0.0" + "node": "^16.13.0 || ^18.12.0" }, "bugs": { "url": "https://github.com/logto-io/logto/issues" diff --git a/packages/phrases/src/index.ts b/packages/phrases/src/index.ts index 33f9efeaf..e8a821620 100644 --- a/packages/phrases/src/index.ts +++ b/packages/phrases/src/index.ts @@ -4,6 +4,7 @@ import { languages } from '@logto/language-kit'; import type { NormalizeKeyPaths } from '@silverhand/essentials'; import { z } from 'zod'; +import de from './locales/de'; import en from './locales/en'; import fr from './locales/fr'; import ko from './locales/ko'; @@ -16,7 +17,7 @@ export type { LocalPhrase } from './types'; export type I18nKey = NormalizeKeyPaths; -export const builtInLanguages = ['en', 'fr', 'pt-PT', 'zh-CN', 'ko', 'tr-TR'] as const; +export const builtInLanguages = ['de', 'en', 'fr', 'ko', 'pt-PT', 'tr-TR', 'zh-CN'] as const; export const builtInLanguageOptions = builtInLanguages.map((languageTag) => ({ value: languageTag, @@ -42,12 +43,13 @@ export const isBuiltInLanguageTag = (language: string): language is BuiltInLangu export type Resource = Record; const resource: Resource = { + de, en, fr, - 'pt-PT': ptPT, - 'zh-CN': zhCN, ko, + 'pt-PT': ptPT, 'tr-TR': trTR, + 'zh-CN': zhCN, }; export default resource; diff --git a/packages/phrases/src/locales/de/errors.ts b/packages/phrases/src/locales/de/errors.ts new file mode 100644 index 000000000..48c9e4ec6 --- /dev/null +++ b/packages/phrases/src/locales/de/errors.ts @@ -0,0 +1,152 @@ +const errors = { + auth: { + authorization_header_missing: 'Autorisierungs-Header fehlt.', + authorization_token_type_not_supported: 'Autorisierungs-Typ wird nicht unterstützt.', + unauthorized: 'Unautorisiert. Bitte überprüfe deine Zugangsdaten.', + forbidden: 'Verboten. Bitte überprüfe deine Rollen und Berechtigungen.', + expected_role_not_found: + 'Erwartete Rolle nicht gefunden. Bitte überprüfe deine Rollen und Berechtigungen.', + jwt_sub_missing: '`sub` fehlt in JWT.', + }, + guard: { + invalid_input: 'Die Anfrage {{type}} ist ungültig.', + invalid_pagination: 'Die Paginierung der Anfrage ist ungültig.', + }, + oidc: { + aborted: 'Der Endnutzer hat die Interaktion abgebrochen.', + invalid_scope: 'Scope {{scope}} wird nicht unterstützt.', + invalid_scope_plural: 'Scopes {{scopes}} werden nicht unterstützt.', + invalid_token: 'Ungültiger Token übermittelt.', + invalid_client_metadata: 'Ungültige Client Metadaten übermittelt.', + insufficient_scope: 'Access token fehlen angefragte scope {{scopes}}.', + invalid_request: 'Anfrage ist ungültig.', + invalid_grant: 'Grant request ist ungültig.', + invalid_redirect_uri: + '`redirect_uri` stimmt nicht mit den registrierten `redirect_uris` des Clients überein.', + access_denied: 'Zugang verweigert.', + invalid_target: 'Ungültiger resource indicator.', + unsupported_grant_type: 'Nicht unterstützter `grant_type` angefragt.', + unsupported_response_mode: 'Nicht unterstützter `response_mode` angefragt.', + unsupported_response_type: 'Nicht unterstützter `response_type` angefragt.', + provider_error: 'OIDC interner Fehler: {{message}}.', + }, + user: { + username_exists_register: 'Der Benutzername wurde registriert.', + email_exists_register: 'Die E-Mail wurde registriert.', + phone_exists_register: 'Die Telefonnummer wurde registriert.', + invalid_email: 'Ungültige E-Mail.', + invalid_phone: 'Ungültige Telefonnummer.', + email_not_exists: 'Die E-Mail wurde noch nicht registriert.', + phone_not_exists: 'Die Telefonnummer wurde noch nicht registriert.', + identity_not_exists: 'Die Identität wurde noch nicht registriert.', + identity_exists: 'Die Identität wurde registriert.', + invalid_role_names: 'Rollennamen ({{roleNames}}) sind ungültig', + cannot_delete_self: 'Du kannst dich nicht selbst löschen.', + sign_up_method_not_enabled: 'This sign up method is not enabled.', // UNTRANSLATED + sign_in_method_not_enabled: 'This sign in method is not enabled.', // UNTRANSLATED + same_password: 'Das neue Passwort muss sich vom alten unterscheiden.', + require_password: 'You need to set a password before sign in.', // UNTRANSLATED + password_exists: 'Your password has been set.', // UNTRANSLATED + require_username: 'You need to set a username before sign in.', // UNTRANSLATED + username_exists: 'Your username has been set.', // UNTRANSLATED + require_email: 'You need to set an email before sign in.', // UNTRANSLATED + email_exists: 'Your email has been set.', // UNTRANSLATED + require_sms: 'You need to set a phone before sign in.', // UNTRANSLATED + sms_exists: 'Your phone has been set.', // UNTRANSLATED + require_email_or_sms: 'You need to set a phone or email before sign in.', // UNTRANSLATED + }, + password: { + unsupported_encryption_method: 'Die Verschlüsselungsmethode {{name}} wird nicht unterstützt.', + pepper_not_found: 'Password pepper not found. Please check your core envs.', + }, + session: { + not_found: 'Sitzung nicht gefunden. Bitte melde dich erneut an.', + invalid_credentials: 'Ungültige Zugangsdaten. Überprüfe deine Eingaben.', + invalid_sign_in_method: 'Aktuelle Anmeldemethode ist ungültig.', + invalid_connector_id: 'Connector mit ID {{connectorId}} wurde nicht gefunden.', + insufficient_info: 'Unzureichende Informationen für die Anmeldung.', + connector_id_mismatch: 'Connector ID stimmt nicht mit Sitzung überein.', + connector_session_not_found: 'Connector Sitzung nicht gefunden. Bitte melde dich erneut an.', + verification_session_not_found: + 'Die Verifizierung war nicht erfolgreich. Starte die Verifizierung neu und versuche es erneut.', + verification_expired: + 'Die Verbindung wurde unterbrochen. Verifiziere erneut, um die Sicherheit deines Kontos zu gewährleisten.', + unauthorized: 'Bitte melde dich erst an.', + unsupported_prompt_name: 'Nicht unterstützter prompt Name.', + forgot_password_not_enabled: 'Forgot password is not enabled.', + }, + connector: { + // UNTRANSLATED + general: 'An unexpected error occurred in connector.{{errorDescription}}', + not_found: 'Cannot find any available connector for type: {{type}}.', + not_enabled: 'The connector is not enabled.', + invalid_metadata: "The connector's metadata is invalid.", + invalid_config_guard: "The connector's config guard is invalid.", + unexpected_type: "The connector's type is unexpected.", + invalid_request_parameters: 'The request is with wrong input parameter(s).', + insufficient_request_parameters: 'The request might miss some input parameters.', + invalid_config: "The connector's config is invalid.", + invalid_response: "The connector's response is invalid.", + template_not_found: 'Unable to find correct template in connector config.', + not_implemented: '{{method}}: has not been implemented yet.', + social_invalid_access_token: "The connector's access token is invalid.", + invalid_auth_code: "The connector's auth code is invalid.", + social_invalid_id_token: "The connector's id token is invalid.", + authorization_failed: "The user's authorization process is unsuccessful.", + social_auth_code_invalid: 'Unable to get access token, please check authorization code.', + more_than_one_sms: 'The number of SMS connectors is larger then 1.', + more_than_one_email: 'The number of Email connectors is larger then 1.', + db_connector_type_mismatch: 'There is a connector in the DB that does not match the type.', + }, + passcode: { + phone_email_empty: 'Telefonnummer oder E-Mail darf nicht leer sein.', + not_found: 'Passcode nicht gefunden. Bitte sende erst einen Passcode.', + phone_mismatch: + 'Telefonnummer stimmt nicht mit Passcode überein. Frage einen neuen Passcode an.', + email_mismatch: 'E-Mail stimmt nicht mit Passcode überein. Frage einen neuen Passcode an.', + code_mismatch: 'Ungültiger Passcode.', + expired: 'Passcode ist abgelaufen. Frage einen neuen Passcode an.', + exceed_max_try: 'Passcode wurde zu oft versucht. Frage einen neuen Passcode an.', + }, + sign_in_experiences: { + empty_content_url_of_terms_of_use: + 'Leere "Nutzungsbedingungen" URL. Bitte füge die URL hinzu, wenn "Nutzungsbedingungen" aktiviert ist.', + empty_logo: 'Bitte füge eine Logo URL hinzu.', + empty_slogan: + 'Leerer Branding-Slogan. Bitte füge einen Branding-Slogan hinzu, wenn ein UI-Stil ausgewählt wird, der den Slogan enthält.', + empty_social_connectors: + 'Leere Social Connectors. Bitte füge aktivierte Social Connectoren hinzu, wenn Social Anmeldung aktiviert ist.', + enabled_connector_not_found: 'Aktivierter {{type}} Connector nicht gefunden.', + not_one_and_only_one_primary_sign_in_method: + 'Es darf nur eine primäre Anmeldemethode geben. Bitte überprüfe deine Eingabe.', + username_requires_password: 'Must enable set a password for username sign up identifier.', // UNTRANSLATED + passwordless_requires_verify: 'Must enable verify for email/phone sign up identifier.', // UNTRANSLATED + miss_sign_up_identifier_in_sign_in: 'Sign in methods must contain the sign up identifier.', // UNTRANSLATED + password_sign_in_must_be_enabled: + 'Password sign in must be enabled when set a password is required in sign up.', // UNTRANSLATED + code_sign_in_must_be_enabled: + 'Verification code sign in must be enabled when set a password is not required in sign up.', // UNTRANSLATED + unsupported_default_language: 'Die Sprache - {{language}} wird momentan nicht unterstützt.', + }, + localization: { + cannot_delete_default_language: + '{{languageTag}} ist die Standard-Sprache und kann nicht gelöscht werden.', + invalid_translation_structure: 'Ungültige Übersetzungsstruktur. Bitte überprüfe deine Eingabe.', + }, + swagger: { + invalid_zod_type: 'Ungültiger Zod Typ. Überprüfe deine route guard Konfiguration.', + not_supported_zod_type_for_params: + 'Nicht unterstützter Zod Typ für diese Parameter. Überprüfe deine route guard Konfiguration.', + }, + entity: { + create_failed: 'Fehler beim erstellen von {{name}}.', + not_exists: '{{name}} existiert nicht.', + not_exists_with_id: '{{name}} mit ID `{{id}}` existiert nicht.', + not_found: 'Die Ressource wurde nicht gefunden.', + }, + log: { + invalid_type: 'Der Log Typ ist ungültig.', + }, +}; + +export default errors; diff --git a/packages/phrases/src/locales/de/index.ts b/packages/phrases/src/locales/de/index.ts new file mode 100644 index 000000000..10ea8ed97 --- /dev/null +++ b/packages/phrases/src/locales/de/index.ts @@ -0,0 +1,10 @@ +import type { LocalPhrase } from '../../types'; +import errors from './errors'; +import translation from './translation'; + +const de: LocalPhrase = Object.freeze({ + translation, + errors, +}); + +export default de; diff --git a/packages/phrases/src/locales/de/translation/admin-console/api-resource-details.ts b/packages/phrases/src/locales/de/translation/admin-console/api-resource-details.ts new file mode 100644 index 000000000..900414932 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/api-resource-details.ts @@ -0,0 +1,11 @@ +const api_resource_details = { + back_to_api_resources: 'Zurück zu API Ressourcen', + token_expiration_time_in_seconds: 'Token Ablaufzeit (in Sekunden)', + token_expiration_time_in_seconds_placeholder: 'Gib die Ablaufzeit des Tokens ein', + delete_description: + 'Diese Aktion kann nicht rückgängig gemacht werden. Die API Ressource wird permanent gelöscht. Bitte gib den API Ressourcennamen {{name}} zur Bestätigung ein.', + enter_your_api_resource_name: 'Gib einen API Ressourcennamen ein', + api_resource_deleted: 'Die API Ressource {{name}} wurde erfolgreich gelöscht', +}; + +export default api_resource_details; diff --git a/packages/phrases/src/locales/de/translation/admin-console/api-resources.ts b/packages/phrases/src/locales/de/translation/admin-console/api-resources.ts new file mode 100644 index 000000000..b15b5094d --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/api-resources.ts @@ -0,0 +1,14 @@ +const api_resources = { + title: 'API Ressourcen', + subtitle: 'Lege APIs an, die du in deinen autorisierten Anwendungen verwenden kannst', + create: 'Erstelle API Ressource', + api_name: 'API Name', + api_name_placeholder: 'Gib einen API Namen ein', + api_identifier: 'API Identifikator', + api_identifier_tip: + 'Der eindeutige Identifikator der API Ressource muss eine absolute URI ohne Fragmentbezeichner (#) sein. Entspricht dem Ressourcen Parameter in OAuth 2.0.', + api_resource_created: 'Die API Ressource {{name}} wurde erfolgreich angelegt', + api_identifier_placeholder: 'https://dein-api-identifikator/', +}; + +export default api_resources; diff --git a/packages/phrases/src/locales/de/translation/admin-console/application-details.ts b/packages/phrases/src/locales/de/translation/admin-console/application-details.ts new file mode 100644 index 000000000..1311893ab --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/application-details.ts @@ -0,0 +1,44 @@ +const application_details = { + back_to_applications: 'Zurück zu Anwendungen', + check_guide: 'Zur Anleitung', + advanced_settings: 'Erweiterte Einstellungen', + application_name: 'Anwendungsname', + application_name_placeholder: 'Meine App', + description: 'Beschreibung', + description_placeholder: 'Gib eine Beschreibung ein', + authorization_endpoint: 'Autorisierungs-Endpoint', + authorization_endpoint_tip: + 'Der Endpoint, der für die Authentifizierung und Autorisierung via OpenID Connect verwendet wird.', + application_id: 'App ID', + application_secret: 'App Geheimnis', + redirect_uri: 'Umleitungs-URI', + redirect_uris: 'Umleitungs-URIs', + redirect_uri_placeholder: 'https://deine.website.de/app', + redirect_uri_placeholder_native: 'io.logto://callback', + redirect_uri_tip: + 'URI zu der der Benutzer nach der Anmeldung (egal ob erfolgreich oder nicht) weitergeleitet wird. See OpenID Connect AuthRequest for more info.', + post_sign_out_redirect_uri: 'Post Sign-out Umleitungs-URI', + post_sign_out_redirect_uris: 'Post Sign-out Umleitungs-URIs', + post_sign_out_redirect_uri_placeholder: 'https://deine.website.de/home', + post_sign_out_redirect_uri_tip: + 'URI zu der der Benutzer nach dem Abmelden weitergeleitet wird (optional). Hat bei einigen Anwendungstypen keine Auswirkungen.', + cors_allowed_origins: 'CORS allowed origins', + cors_allowed_origins_placeholder: 'https://your.website.de', + cors_allowed_origins_tip: + 'Es sind standardmäßig alle Umleitungs-URI Origins erlaubt. Normalerweise ist dieses Feld nicht erforderlich.', + add_another: 'Weitere hinzufügen', + id_token_expiration: 'ID Token Ablaufzeit', + refresh_token_expiration: 'Refresh Token Ablaufzeit', + token_endpoint: 'Token Endpoint', + user_info_endpoint: 'Benutzerinformations-Endpoint', + enable_admin_access: 'Admin-Zugang aktivieren', + enable_admin_access_label: + 'Zugang zur Management API aktivieren oder deaktivieren. Falls aktiviert, können access tokens verwendet werden, um die Management API im Namen der Anwendung aufzurufen.', + delete_description: + 'Diese Aktion kann nicht rückgängig gemacht werden. Die Anwendung wird permanent gelöscht. Bitte gib den Anwendungsnamen {{name}} zur Bestätigung ein.', + enter_your_application_name: 'Gib einen Anwendungsnamen ein', + application_deleted: 'Anwendung {{name}} wurde erfolgreich gelöscht', + redirect_uri_required: 'Gebe mindestens eine Umleitungs-URI an', +}; + +export default application_details; diff --git a/packages/phrases/src/locales/de/translation/admin-console/applications.ts b/packages/phrases/src/locales/de/translation/admin-console/applications.ts new file mode 100644 index 000000000..a5539a1ad --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/applications.ts @@ -0,0 +1,50 @@ +const applications = { + title: 'Anwendungen', + subtitle: + 'Richte eine native, Single Page oder herkömmliche Anwendung ein, die Logto zur Authentifizierung nutzt.', + create: 'Anwendung erstellen', + application_name: 'Anwendungsname', + application_name_placeholder: 'Meine App', + application_description: 'Anwendungsbeschreibung', + application_description_placeholder: 'Gib eine Beschreibung ein', + select_application_type: 'Wähle einen Anwendungstyp', + no_application_type_selected: 'Du hast noch keinen Anwendungstyp ausgewählt', + application_created: + 'Die Anwendung {{name}} wurde erfolgreich erstellt! \nKonfiguriere jetzt die Anwendung.', + app_id: 'App ID', + type: { + native: { + title: 'Native App', + subtitle: 'Eine Anwendung, die in einer nativen Umgebung läuft', + description: 'z.B. iOS app, Android app', + }, + spa: { + title: 'Single Page App', + subtitle: + 'Eine Anwendung, die in einem Webbrowser ausgeführt wird und Daten dynamisch an Ort und Stelle aktualisiert', + description: 'z.B. React DOM app, Vue app', + }, + traditional: { + title: 'Herkömmliche Website', + subtitle: 'Eine Anwendung, die Seiten allein durch den Webserver rendert und aktualisiert', + description: 'z.B. Next.js, PHP', + }, + machine_to_machine: { + title: 'Machine to Machine', + subtitle: 'Eine Anwendung (normalerweise ein Dienst), die direkt mit Ressourcen kommuniziert', + description: 'z.B. Backend Dienst', + }, + }, + guide: { + get_sample_file: 'Zum Beispielprojekt', + header_description: + 'Folge der Schritt-für-Schritt-Anleitung, um die Anwendung zu integrieren, oder klick auf die rechte Schaltfläche, um unser Beispielprojekt zu erhalten', + title: 'Die Anwendung wurde erfolgreich erstellt', + subtitle: + 'Folge nun den folgenden Schritten, um deine App-Einstellungen abzuschließen. Bitte wähle den SDK-Typ aus, um fortzufahren.', + description_by_sdk: + 'Diese Schnellstart-Anleitung zeigt, wie man Logto in die {{sdk}} App integriert', + }, +}; + +export default applications; diff --git a/packages/phrases/src/locales/de/translation/admin-console/connector-details.ts b/packages/phrases/src/locales/de/translation/admin-console/connector-details.ts new file mode 100644 index 000000000..4a67369ad --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/connector-details.ts @@ -0,0 +1,22 @@ +const connector_details = { + back_to_connectors: 'Zurück zu Connectoren', + check_readme: 'Zur README', + save_error_empty_config: 'Bitte fülle die Konfiguration aus', + send: 'Senden', + send_error_invalid_format: 'Ungültige Eingabe', + edit_config_label: 'Gib deine JSON-Konfiguration ein', + test_email_sender: 'Teste den E-Mail Connector', + test_sms_sender: 'Teste den SMS Connector', + test_email_placeholder: 'Gib eine Test-E-Mail ein', + test_sms_placeholder: 'Gib eine Test-Telefonnummer ein', + test_message_sent: 'Testnachricht wurde gesendet!', + test_sender_description: 'Wenn dein JSON richtig konfiguriert ist, erhältst du eine Nachricht.', + options_change_email: 'E-Mail Connector bearbeiten', + options_change_sms: 'SMS Connector bearbeiten', + connector_deleted: 'Der Connector wurde erfolgreich gelöscht', + type_email: 'E-Mail connector', + type_sms: 'SMS connector', + type_social: 'Social connector', +}; + +export default connector_details; diff --git a/packages/phrases/src/locales/de/translation/admin-console/connectors.ts b/packages/phrases/src/locales/de/translation/admin-console/connectors.ts new file mode 100644 index 000000000..63c7b3117 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/connectors.ts @@ -0,0 +1,39 @@ +const connectors = { + title: 'Connectoren', + subtitle: 'Richte Connectoren ein um passwortlose und Social Anmeldung zu aktivieren', + create: 'Social Connector erstellen', + config_sie_notice: 'You’ve set up connectors. Make sure to configure it in {{link}}.', // UNTRANSLATED + config_sie_link_text: 'sign in experience', // UNTRANSLATED + tab_email_sms: 'E-Mail und SMS Connectoren', + tab_social: 'Social Connectoren', + connector_name: 'Connectorname', + connector_type: 'Typ', + connector_status: 'Anmeldeoberfläche', + connector_status_in_use: 'In Benutzung', + connector_status_not_in_use: 'Nicht in Benutzung', + social_connector_eg: 'z.B. Google, Facebook, Github', + save_and_done: 'Speichern und fertigstellen', + type: { + email: 'E-Mail Connector', + sms: 'SMS Connector', + social: 'Social Connector', + }, + setup_title: { + email: 'E-Mail Connector einrichten', + sms: 'SMS Connector einrichten', + social: 'Social Connector erstellen', + }, + guide: { + subtitle: 'Eine Schritt-für-Schritt-Anleitung zur Konfiguration deines Connectors', + }, + platform: { + universal: 'Universal', + web: 'Web', + native: 'Nativ', + }, + add_multi_platform: ' unterstützt mehrere Plattformen, wähle eine Plattform aus, um fortzufahren', + drawer_title: 'Connector Anleitung', + drawer_subtitle: 'Folge den Anweisungen, um deinen Connector zu integrieren', +}; + +export default connectors; diff --git a/packages/phrases/src/locales/de/translation/admin-console/contact.ts b/packages/phrases/src/locales/de/translation/admin-console/contact.ts new file mode 100644 index 000000000..f73339731 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/contact.ts @@ -0,0 +1,22 @@ +const contact = { + title: 'Kontakt', + description: + 'Tritt unserer Community bei, um Feedback zu geben, um Hilfe zu bitten und deine Gedanken mit anderen Entwicklern zu teilen', + discord: { + title: 'Discord channel', + description: 'Tritt unserem öffentlichen Kanal bei, um mit anderen Entwicklern zu chatten', + button: 'Beitreten', + }, + github: { + title: 'GitHub', + description: 'Erstelle ein Issue bei GitHub', + button: 'Öffnen', + }, + email: { + title: 'E-Mail senden', + description: 'Schick uns eine E-Mail für weitere Informationen und Hilfe', + button: 'Senden', + }, +}; + +export default contact; diff --git a/packages/phrases/src/locales/de/translation/admin-console/dashboard.ts b/packages/phrases/src/locales/de/translation/admin-console/dashboard.ts new file mode 100644 index 000000000..733900244 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/dashboard.ts @@ -0,0 +1,22 @@ +const dashboard = { + title: 'Dashboard', + description: 'Verschaffe dir einen Überblick über die Leistung deiner App', + total_users: 'Gesamtzahl der Benutzer', + total_users_tip: 'Gesamtzahl der Benutzer', + new_users_today: 'Neue Benutzer heute', + new_users_today_tip: 'Neue Benutzer, die sich heute in deinen Anwendungen registriert haben', + new_users_7_days: 'Neue Benutzer in den letzten 7 Tagen', + new_users_7_days_tip: + 'Neue Benutzer, die sich in den letzten 7 Tagen in deinen Anwendungen registriert haben', + daily_active_users: 'Täglich aktive Benutzer', + daily_active_users_tip: + 'Die Anzahl der einzelnen Benutzer, die heute Token in deinen Anwendungen ausgetauscht haben', + weekly_active_users: 'Wöchentlich aktive Benutzer', + weekly_active_users_tip: + 'Die Anzahl der einzelnen Benutzer, die in den letzten 7 Tagen Token in deinen Anwendungen ausgetauscht haben', + monthly_active_users: 'Monatlich aktive Benutzer', + monthly_active_users_tip: + 'Die Anzahl der einzelnen Benutzer, die in den letzten 30 Tagen Token in deinen Anwendungen ausgetauscht haben', +}; + +export default dashboard; diff --git a/packages/phrases/src/locales/de/translation/admin-console/errors.ts b/packages/phrases/src/locales/de/translation/admin-console/errors.ts new file mode 100644 index 000000000..ef7ff49c4 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/errors.ts @@ -0,0 +1,21 @@ +const errors = { + something_went_wrong: 'Ups, da ist etwas schief gelaufen.', + page_not_found: 'Seite nicht gefunden', + unknown_server_error: 'Unbekannter Serverfehler', + empty: 'Keine Daten verfügbar', + missing_total_number: 'Total-Number wurde nicht in Response Headern gefunden', + invalid_uri_format: 'Ungültiges URI-Format', + invalid_origin_format: 'Ungültiges URI Origin-Format', + invalid_json_format: 'Ungültiges JSON-Format', + invalid_error_message_format: 'Ungültiges Fehlermeldung-Format.', + required_field_missing: 'Bitte fülle {{field}} aus', + required_field_missing_plural: 'Mindestens ein {{field}} muss ausgefüllt sein', + more_details: 'Mehr Details', + username_pattern_error: + 'Der Benutzername sollte nur Buchstaben, Zahlen oder Unterstriche enthalten und nicht mit einer Zahl beginnen.', + password_pattern_error: 'Das Passwort muss aus mindestens 6 Zeichen lang sein', + insecure_contexts: 'Unsichere Kontexte (nicht-HTTPS) werden nicht unterstützt.', + unexpected_error: 'Ein unerwarteter Fehler ist aufgetreten', +}; + +export default errors; diff --git a/packages/phrases/src/locales/de/translation/admin-console/general.ts b/packages/phrases/src/locales/de/translation/admin-console/general.ts new file mode 100644 index 000000000..070a3c097 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/general.ts @@ -0,0 +1,43 @@ +const general = { + placeholder: 'Platzhalter', + skip: 'Überspringen', + next: 'Weiter', + retry: 'Erneut versuchen', + done: 'Fertig', + search: 'Suche', + search_placeholder: 'Suchen', + clear_result: 'Ergebnisse löschen', + save: 'Speichern', + save_changes: 'Änderungen speichern', + saved: 'Gespeichert!', + loading: 'Lade...', + redirecting: 'Weiterleiten...', + add: 'Hinzufügen', + added: 'Hinzugefügt', + cancel: 'Abbrechen', + confirm: 'Bestätigen', + check_out: 'Ansehen', + create: 'Erstellen', + set_up: 'Einrichten', + customize: 'Anpassen', + enable: 'Aktivieren', + reminder: 'Erinnerung', + delete: 'Löschen', + more_options: 'MEHR OPTIONEN', + close: 'Schließen', + copy: 'Kopieren', + copying: 'Kopiere', + copied: 'Kopiert', + required: 'Erforderlich', + add_another: '+ Weitere hinzufügen', + deletion_confirmation: 'Willst du {{title}} wirklich löschen?', + settings_nav: 'Einstellungen', + unsaved_changes_warning: + 'Du hast ungespeicherte Änderungen. Willst du diese Seite wirklich verlassen?', + leave_page: 'Seite verlassen', + stay_on_page: 'Auf Seite bleiben', + type_to_search: 'Tippe um zu suchen', + got_it: 'Got it', // UNTRANSLATED +}; + +export default general; diff --git a/packages/phrases/src/locales/de/translation/admin-console/get-started.ts b/packages/phrases/src/locales/de/translation/admin-console/get-started.ts new file mode 100644 index 000000000..8897b4703 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/get-started.ts @@ -0,0 +1,29 @@ +const get_started = { + progress: 'Erste Schritte: {{completed}}/{{total}}', + progress_dropdown_title: 'Was du machen kannst...', + title: 'Wie willst du mit Logto loslegen?', + subtitle_part1: 'Ein paar Dinge, die du tun kannst, um schnell von Logto zu profitieren', + subtitle_part2: 'Ich bin fertig mit der Einrichtung.', + hide_this: 'Ausblenden', + confirm_message: + 'Bist du sicher, dass du diese Seite ausblenden willst? Diese Aktion kann nicht rückgängig gemacht werden.', + card1_title: 'Zur Demo', + card1_subtitle: 'Probiere die Logto-Anmeldung jetzt aus, um zu sehen, wie sie funktioniert', + card2_title: 'Erste Anwendung erstellen und integrieren', + card2_subtitle: + 'Richte eine native, Single Page oder herkömmliche Anwendung ein, die Logto zur Authentifizierung nutzt.', + card3_title: 'Anmeldeoberfläche anpassen', + card3_subtitle: + 'Passe die Benutzeroberfläche für die Anmeldung an deine Marke an und zeige eine Vorschau in Echtzeit an', + card4_title: 'SMS- und E-Mail-Verbindung einrichten', + card4_subtitle: + 'Probiere die passwortlose Anmeldung mit Telefonnummer oder E-Mail aus, um ein sicheres und reibungsloses Kundenerlebnis zu ermöglichen.', + card5_title: 'Social Connector hinzufügen', + card5_subtitle: + 'Lass deine Kunden sich mit einem Klick mit ihren sozialen Identitäten bei deiner App anmelden', + card6_title: 'Weitere Informationen', + card6_subtitle: + 'Schau dir unsere schrittweisen, szenariobasierten Dokumentationen ohne langweilige Konzepte an', +}; + +export default get_started; diff --git a/packages/phrases/src/locales/de/translation/admin-console/index.ts b/packages/phrases/src/locales/de/translation/admin-console/index.ts new file mode 100644 index 000000000..2964cba88 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/index.ts @@ -0,0 +1,52 @@ +import api_resource_details from './api-resource-details'; +import api_resources from './api-resources'; +import application_details from './application-details'; +import applications from './applications'; +import connector_details from './connector-details'; +import connectors from './connectors'; +import contact from './contact'; +import dashboard from './dashboard'; +import errors from './errors'; +import general from './general'; +import get_started from './get-started'; +import log_details from './log-details'; +import logs from './logs'; +import session_expired from './session-expired'; +import settings from './settings'; +import sign_in_exp from './sign-in-exp'; +import tab_sections from './tab-sections'; +import tabs from './tabs'; +import user_details from './user-details'; +import users from './users'; +import welcome from './welcome'; + +const admin_console = { + title: 'Admin Konsole', + sign_out: 'Abmelden', + profile: 'Profil', + admin_user: 'Admin', + system_app: 'System', + general, + errors, + tab_sections, + tabs, + applications, + application_details, + api_resources, + api_resource_details, + connectors, + connector_details, + get_started, + users, + user_details, + contact, + sign_in_exp, + settings, + dashboard, + logs, + log_details, + session_expired, + welcome, +}; + +export default admin_console; diff --git a/packages/phrases/src/locales/de/translation/admin-console/log-details.ts b/packages/phrases/src/locales/de/translation/admin-console/log-details.ts new file mode 100644 index 000000000..056530189 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/log-details.ts @@ -0,0 +1,17 @@ +const log_details = { + back_to_logs: 'Zurück zu Audit Logs', + back_to_user: 'Zurück zu {{name}}', + success: 'Erfolgreich', + failed: 'Fehlgeschlagen', + event_type: 'Event Typ', + application: 'Anwendung', + ip_address: 'IP Adresse', + user: 'Benutzer', + log_id: 'Log ID', + time: 'Zeit', + user_agent: 'User agent', + tab_details: 'Details', + raw_data: 'Rohe Daten', +}; + +export default log_details; diff --git a/packages/phrases/src/locales/de/translation/admin-console/logs.ts b/packages/phrases/src/locales/de/translation/admin-console/logs.ts new file mode 100644 index 000000000..7c548f676 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/logs.ts @@ -0,0 +1,12 @@ +const logs = { + title: 'Audit Logs', + subtitle: + 'Anzeige der Log Daten von Authentifizierungsereignissen, die von Admins und Benutzern stammen', + event: 'Event', + user: 'Benutzer', + application: 'Anwendung', + time: 'Zeit', + filter_by: 'Filter nach', +}; + +export default logs; diff --git a/packages/phrases/src/locales/de/translation/admin-console/session-expired.ts b/packages/phrases/src/locales/de/translation/admin-console/session-expired.ts new file mode 100644 index 000000000..f1052d50e --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/session-expired.ts @@ -0,0 +1,8 @@ +const session_expired = { + title: 'Sitzung abgelaufen', + subtitle: + 'Deine Sitzung ist möglicherweise abgelaufen und deine Verbindung wurde unterbrochen. Klicke auf die Schaltfläche unten, um dich erneut an der Admin Konsole anzumelden.', + button: 'Erneut anmelden', +}; + +export default session_expired; diff --git a/packages/phrases/src/locales/de/translation/admin-console/settings.ts b/packages/phrases/src/locales/de/translation/admin-console/settings.ts new file mode 100644 index 000000000..e20d8050e --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/settings.ts @@ -0,0 +1,27 @@ +const settings = { + title: 'Einstellungen', + description: 'Verwalte die globalen Einstellungen', + tabs: { + general: 'Allgemein', + }, + custom_domain: 'Benutzerdefinierte Domain', + language: 'Sprache', + appearance: 'Darstellung', + appearance_system: 'Synchonisiere mit Systemeinstellungen', + appearance_light: 'Hell', + appearance_dark: 'Dunkel', + saved: 'Gespeichert!', + change_password: 'Passwort ändern', + change_password_description: + 'Du kannst das Passwort für dieses Konto ändern. Du verwendest den aktuellen Benutzernamen mit dem neuen Passwort, um dich in der Admin Konsole anzumelden.', + change_modal_title: 'Account Password ändern', + change_modal_description: + 'Du verwendest den aktuellen Benutzernamen mit dem neuen Passwort, um dich in der Admin Konsole anzumelden.', + new_password: 'Neues Passwort', + new_password_placeholder: 'Gib ein neues Passwort ein', + confirm_password: 'Passwort bestätigen', + confirm_password_placeholder: 'Bestätige das neue Passwort', + password_changed: 'Passwort geändert!', +}; + +export default settings; diff --git a/packages/phrases/src/locales/de/translation/admin-console/sign-in-exp.ts b/packages/phrases/src/locales/de/translation/admin-console/sign-in-exp.ts new file mode 100644 index 000000000..a5cca7104 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/sign-in-exp.ts @@ -0,0 +1,187 @@ +const sign_in_exp = { + title: 'Anmeldeoberfläche', + description: + 'Passe die Benutzeroberfläche für die Anmeldung an deine Marke an und zeige eine Vorschau in Echtzeit an', + tabs: { + branding: 'Branding', + methods: 'Anmeldemethoden', + sign_up_and_sign_in: 'Sign up and Sign in', // UNTRANSLATED + others: 'Andere', + }, + welcome: { + title: + 'Dies ist das erste Mal, dass du deine Anmeldeoberfläche anpasst. Diese Anleitung hilft dir, alle notwendigen Einstellungen vorzunehmen und schnell loszulegen.', + get_started: 'Erste Schritte', + apply_remind: + 'Bitte beachte, dass die Anmeldeoberfläche für alle Anwendungen unter diesem Konto gilt.', + got_it: 'Alles klar', + }, + sign_up_and_sign_in: { + identifiers: 'Sign up identifiers', // UNTRANSLATED + identifiers_email: 'Email address', // UNTRANSLATED + identifiers_sms: 'Phone number', // UNTRANSLATED + identifiers_username: 'Username', // UNTRANSLATED + identifiers_email_or_sms: 'Email address or phone number', // UNTRANSLATED + identifiers_none: 'None', // UNTRANSLATED + and: 'and', // UNTRANSLATED + or: 'or', // UNTRANSLATED + sign_up: { + title: 'SIGN UP', // UNTRANSLATED + sign_up_identifier: 'Sign up identifier', // UNTRANSLATED + sign_up_authentication: 'Sign up authentication', // UNTRANSLATED + set_a_password_option: 'Set a password', // UNTRANSLATED + verify_at_sign_up_option: 'Verify at sign up', // UNTRANSLATED + social_only_creation_description: '(This apply to social only account creation)', // UNTRANSLATED + }, + sign_in: { + title: 'SIGN IN', // UNTRANSLATED + sign_in_identifier_and_auth: 'Sign in identifier and authentication', // UNTRANSLATED + description: + 'Users can use any one of the selected ways to sign in. Drag and drop to define identifier priority regarding the sign in flow. You can also define the password or verification code priority.', // UNTRANSLATED + add_sign_in_method: 'Add Sign-in Method', // UNTRANSLATED + password_auth: 'Password', // UNTRANSLATED + verification_code_auth: 'Verification code', // UNTRANSLATED + auth_swap_tip: 'Swap to change the priority', // UNTRANSLATED + }, + social_sign_in: { + title: 'SOCIAL SIGN IN', // UNTRANSLATED + social_sign_in: 'Social sign in', // UNTRANSLATED + description: + 'Users may need to enter required identifier when register through social accounts. This was defined by your sign up identifier.', // UNTRANSLATED + add_social_connector: 'Add Social Connector', // UNTRANSLATED + set_up_hint: { + not_in_list: 'Not in the list?', // UNTRANSLATED + set_up_more: 'Set up more', // UNTRANSLATED + go_to: 'social connectors or go to “Connectors” section.', // UNTRANSLATED + }, + }, + }, + color: { + title: 'FARBE', + primary_color: 'Markenfarbe', + dark_primary_color: 'Markenfarbe (Dunkler Modus)', + dark_mode: 'Aktiviere Dunklen Modus', + dark_mode_description: + 'Deine App erhält einen automatisch generierten Dunklen Modus, der auf deiner Markenfarbe und dem Logto-Algorithmus basiert. Du kannst diesen nach Belieben anpassen.', + dark_mode_reset_tip: 'Neuberechnung der Farbe des dunklen Modus basierend auf der Markenfarbe.', + reset: 'Neuberechnen', + }, + branding: { + title: 'BRANDING', + ui_style: 'Stil', + styles: { + logo_slogan: 'App logo mit Slogan', + logo: 'Nur App logo', + }, + logo_image_url: 'App logo URL', + logo_image_url_placeholder: 'https://dein.cdn.domain/logo.png', + dark_logo_image_url: 'App logo URL (Dunkler Modus)', + dark_logo_image_url_placeholder: 'https://dein.cdn.domain/logo-dark.png', + slogan: 'Slogan', + slogan_placeholder: 'Entfessle deine Kreativität', + }, + sign_in_methods: { + title: 'ANMELDEMETHODEN', + primary: 'Primäre Anmeldemethode', + enable_secondary: 'Aktiviere sekundäre Anmeldemethoden', + enable_secondary_description: + 'Sobald sie aktiviert ist, unterstützt deine App neben der primären Anmeldemethode noch weitere Anmeldemethoden. ', + methods: 'Anmeldemethode', + methods_sms: 'SMS Anmeldung', + methods_email: 'E-Mail Anmeldung', + methods_social: 'Social Anmeldung', + methods_username: 'Benutzername-und-Passwort Anmeldung', + methods_primary_tag: '(Primär)', + define_social_methods: 'Definiere die unterstützten Social Anmeldemethoden', + transfer: { + title: 'Social Connectoren', + footer: { + not_in_list: 'Nicht in der Liste?', + set_up_more: 'Mehr Social Connectoren einrichten', + go_to: 'oder "Connectoren" aufrufen.', + }, + }, + }, + others: { + terms_of_use: { + title: 'NUTZUNGSBEDINGUNGEN', + enable: 'Aktiviere Nutzungsbedingungen', + description: 'Füge die rechtlichen Vereinbarungen für die Nutzung deines Produkts hinzu', + terms_of_use: 'Nutzungsbedingungen', + terms_of_use_placeholder: 'https://beispiel.de/nutzungsbedingungen', + terms_of_use_tip: 'URL zu den Nutzungsbedingungen', + }, + languages: { + title: 'SPRACHEN', + enable_auto_detect: 'Aktiviere automatische Spracherkennung', + description: + 'Deine Software erkennt die Sprach-Einstellung des Nutzers und schaltet auf die lokale Sprache um. Du kannst neue Sprachen hinzufügen, indem du die Benutzeroberfläche vom Englischen in eine andere Sprache übersetzt.', + manage_language: 'Sprachen verwalten', + default_language: 'Standard-Sprache', + default_language_description_auto: + 'Die Standardsprache wird verwendet, wenn die erkannte Benutzersprache nicht in der aktuellen Sprachbibliothek enthalten ist.', + default_language_description_fixed: + 'Wenn die automatische Erkennung deaktiviert ist, ist die Standardsprache die einzige Sprache, die deine Software anzeigt. Schalte die automatische Erkennung ein um weitere Sprachen anzuzeigen.', + }, + manage_language: { + title: 'Sprachen verwalten', + subtitle: + 'Erweitere die Anmeldeoberfläche durch neue Sprachen und Übersetzungen. Deine Übersetzung kann als Standard-Sprache verwendet werden.', + add_language: 'Sprache hinzufügen', + logto_provided: 'Von Logto bereitgestellt', + key: 'Schlüssel', + logto_source_values: 'Logto Übersetzungen', + custom_values: 'Benutzerdefinierte Übersetzungen', + clear_all_tip: 'Alle benutzerdefinierten Übersetzungen löschen', + unsaved_description: + 'Wenn du diese Seite verlässt, ohne zu speichern, werden die Änderungen nicht gespeichert.', + deletion_tip: 'Sprache löschen', + deletion_title: 'Willst du diese Sprache wirklich löschen?', + deletion_description: + 'Nach dem Löschen können deine Benutzer diese Sprache nicht mehr nutzen.', + default_language_deletion_title: 'Die Standardsprache kann nicht gelöscht werden.', + default_language_deletion_description: + '{{language}} ist als Standardsprache eingestellt und kann nicht gelöscht werden. ', + got_it: 'Alles klar', + }, + authentication: { + title: 'AUTHENTIFIZIERUNG', + enable_create_account: 'Aktiviere Registrierung', + enable_create_account_description: + 'Aktiviere oder deaktiviere Konto Registrierung. Wenn diese Funktion deaktiviert ist, können deine Kunden keine Konten über die Anmeldeoberfläche erstellen, aber du kannst immer noch Benutzer in der Admin Konsole hinzufügen.', + enable_user_registration: 'Enable user registration', // UNTRANSLATED + enable_user_registration_description: + 'Enable or disallow user registration. Once disabled, users can still be added in the admin console but users can no longer establish accounts through the sign-in UI.', // UNTRANSLATED + }, + }, + setup_warning: { + no_connector: '', + no_connector_sms: + 'Du hast noch keinen SMS Connector eingerichtet. Deine Anmeldung wird erst freigeschaltet, wenn du die Einstellungen abgeschlossen hast. ', + no_connector_email: + 'Du hast noch keinen E-Mail Connector eingerichtet. Deine Anmeldung wird erst freigeschaltet, wenn du die Einstellungen abgeschlossen hast. ', + no_connector_social: + 'Du hast noch keinen Social Connector eingerichtet. Deine Anmeldung wird erst freigeschaltet, wenn du die Einstellungen abgeschlossen hast. ', + no_added_social_connector: + 'Du hast jetzt ein paar Social Connectoren eingerichtet. Füge jetzt einige zu deinem Anmeldeerlebnis hinzu.', + }, + save_alert: { + description: + 'Du änderst die Anmeldemethoden. Das wird sich auf einige deiner Benutzer auswirken. Bist du sicher, dass du das tun willst?', + before: 'Vorher', + after: 'Nachher', + sign_up: 'Sign up', // UNTRANSLATED + sign_in: 'Sign in', // UNTRANSLATED + social: 'Social', // UNTRANSLATED + }, + preview: { + title: 'Vorschau', + dark: 'Dunkel', + light: 'Hell', + native: 'Nativ', + desktop_web: 'Desktop Web', + mobile_web: 'Mobil Web', + }, +}; + +export default sign_in_exp; diff --git a/packages/phrases/src/locales/de/translation/admin-console/tab-sections.ts b/packages/phrases/src/locales/de/translation/admin-console/tab-sections.ts new file mode 100644 index 000000000..e5cfecc8c --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/tab-sections.ts @@ -0,0 +1,8 @@ +const tab_sections = { + overview: 'Übersicht', + resource_management: 'Ressourcenverwaltung', + user_management: 'Benutzerverwaltung', + help_and_support: 'Hilfe und Support', +}; + +export default tab_sections; diff --git a/packages/phrases/src/locales/de/translation/admin-console/tabs.ts b/packages/phrases/src/locales/de/translation/admin-console/tabs.ts new file mode 100644 index 000000000..4cd526fb3 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/tabs.ts @@ -0,0 +1,15 @@ +const tabs = { + get_started: 'Erste Schritte', + dashboard: 'Dashboard', + applications: 'Anwendungen', + api_resources: 'API Ressourcen', + sign_in_experience: 'Anmeldeoberfläche', + connectors: 'Connectoren', + users: 'Benutzerverwaltung', + audit_logs: 'Audit Logs', + docs: 'Dokumentation', + contact_us: 'Kontakt', + settings: 'Einstellungen', +}; + +export default tabs; diff --git a/packages/phrases/src/locales/de/translation/admin-console/user-details.ts b/packages/phrases/src/locales/de/translation/admin-console/user-details.ts new file mode 100644 index 000000000..e0643d761 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/user-details.ts @@ -0,0 +1,41 @@ +const user_details = { + back_to_users: 'Zurück zur Benutzerverwaltung', + created_title: 'Der Benutzer wurde erfolgreich erstellt', + created_guide: 'Sende dem Benutzer folgende Anmeldeinformationen', + created_username: 'Benutzername:', + created_password: 'Passwort:', + menu_delete: 'Löschen', + delete_description: + 'Diese Aktion kann nicht rückgängig gemacht werden. Der Benutzer wird permanent gelöscht.', + deleted: 'Der Benutzer wurde erfolgreich gelöscht', + reset_password: { + reset_password: 'Passwort zurücksetzen', + title: 'Willst du das Passwort wirklich zurücksetzen?', + content: + 'Diese Aktion kann nicht rückgängig gemacht werden. Das Anmeldeinformationen werden zurückgesetzt.', + congratulations: 'Der Benutzer wurde erfolgreich zurückgesetzt', + new_password: 'Neues Passwort:', + }, + tab_logs: 'Benutzer-Logs', + field_email: 'Primäre E-Mail', + field_phone: 'Primäre Telefonnummer', + field_username: 'Benutzername', + field_name: 'Name', + field_avatar: 'Profilbild URL', + field_avatar_placeholder: 'https://dein.cdn.domain/profilbild.png', + field_custom_data: 'Benutzerdefinierte Daten', + field_custom_data_tip: + 'Zusätzliche Benutzerinformationen, die nicht in den vordefinierten Benutzereigenschaften aufgeführt sind, wie z. B. die vom Benutzer bevorzugte Farbe und Sprache.', + field_connectors: 'Social Connections', + custom_data_invalid: 'Benutzerdefinierte Daten müssen ein gültiges JSON-Objekt sein.', + connectors: { + connectors: 'Connectoren', + user_id: 'Benutzer ID', + remove: 'Löschen', + not_connected: 'Der Nutzer ist nicht mit einem Social Connector verbunden', + deletion_confirmation: + 'Du entfernst die bestehende Identität. Bist du sicher, dass du das tun willst?', + }, +}; + +export default user_details; diff --git a/packages/phrases/src/locales/de/translation/admin-console/users.ts b/packages/phrases/src/locales/de/translation/admin-console/users.ts new file mode 100644 index 000000000..0407bfc42 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/users.ts @@ -0,0 +1,15 @@ +const users = { + title: 'Benutzerverwaltung', + subtitle: + 'Verwalten von Benutzeridentitäten, einschließlich des Anlegens von Benutzern, Bearbeiten von Benutzerinformationen, Anzeigen von Benutzer-Logs, Zurücksetzen von Passwörtern und Löschen von Benutzern', + create: 'Benutzer hinzufügen', + user_name: 'Benutzer', + application_name: 'Anwendungsname', + latest_sign_in: 'Letzte Anmeldung', + create_form_username: 'Benutzername', + create_form_password: 'Passwort', + create_form_name: 'Name', + unnamed: 'Unbenannt', +}; + +export default users; diff --git a/packages/phrases/src/locales/de/translation/admin-console/welcome.ts b/packages/phrases/src/locales/de/translation/admin-console/welcome.ts new file mode 100644 index 000000000..d07d47bf4 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/admin-console/welcome.ts @@ -0,0 +1,8 @@ +const welcome = { + title: 'Willkommen in der Admin Konsole', + description: + 'Die Admin-Konsole ist eine Web-App, mit der du Logto verwalten kannst, ohne programmieren zu müssen. Legen wir zunächst ein Konto an. Mit diesem Konto kannst du Logto selbst oder im Namen deines Unternehmens verwalten.', + create_account: 'Konto erstellen', +}; + +export default welcome; diff --git a/packages/phrases/src/locales/de/translation/demo-app.ts b/packages/phrases/src/locales/de/translation/demo-app.ts new file mode 100644 index 000000000..18d473be0 --- /dev/null +++ b/packages/phrases/src/locales/de/translation/demo-app.ts @@ -0,0 +1,15 @@ +const demo_app = { + notification: + 'Nutze dein existierendes Admin Konto oder erstelle ein neues Konto um dich in die Demo App einzuloggen.', + title: 'Du hast dich erfolgreich in der Demo App angemeldet!', + subtitle: 'Here is your log in information:', + username: 'Benutzername: ', + user_id: 'Benutzer ID: ', + sign_out: 'Aus der Demo App ausloggen', + continue_explore: 'Oder weiter zum Entdecken', + customize_sign_in_experience: 'Anmeldeoberfläche anpassen', + enable_passwordless: 'Passwordless einschalten', + add_social_connector: 'Social Connector hinzufügen', +}; + +export default demo_app; diff --git a/packages/phrases/src/locales/de/translation/index.ts b/packages/phrases/src/locales/de/translation/index.ts new file mode 100644 index 000000000..4d123d04e --- /dev/null +++ b/packages/phrases/src/locales/de/translation/index.ts @@ -0,0 +1,9 @@ +import admin_console from './admin-console'; +import demo_app from './demo-app'; + +const translation = { + admin_console, + demo_app, +}; + +export default translation; diff --git a/packages/schemas/package.json b/packages/schemas/package.json index c561267c8..91258e648 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -27,7 +27,7 @@ "test:ci": "jest" }, "engines": { - "node": "^16.0.0" + "node": "^16.13.0 || ^18.12.0" }, "devDependencies": { "@silverhand/eslint-config": "1.3.0", diff --git a/packages/shared/package.json b/packages/shared/package.json index c44c5ab15..a5599b39f 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -33,7 +33,7 @@ "typescript": "^4.7.4" }, "engines": { - "node": "^16.0.0" + "node": "^16.13.0 || ^18.12.0" }, "eslintConfig": { "extends": "@silverhand", @@ -45,7 +45,6 @@ "dependencies": { "@logto/schemas": "workspace:^", "@silverhand/essentials": "^1.3.0", - "dayjs": "^1.10.5", "find-up": "^5.0.0", "nanoid": "^3.3.4", "slonik": "^30.0.0" diff --git a/packages/shared/src/database/utils.test.ts b/packages/shared/src/database/utils.test.ts index 396fd981b..79653f147 100644 --- a/packages/shared/src/database/utils.test.ts +++ b/packages/shared/src/database/utils.test.ts @@ -1,4 +1,3 @@ -import dayjs from 'dayjs'; import { sql } from 'slonik'; import { SqlToken } from 'slonik/dist/src/tokens.js'; @@ -124,7 +123,7 @@ describe('convertToTimestamp()', () => { }); it('converts to sql per time parameter', () => { - const time = dayjs(123_123_123); + const time = new Date(123_123_123); expect(convertToTimestamp(time)).toEqual({ sql: 'to_timestamp($1)', diff --git a/packages/shared/src/database/utils.ts b/packages/shared/src/database/utils.ts index d18afdcf1..910e8e9b2 100644 --- a/packages/shared/src/database/utils.ts +++ b/packages/shared/src/database/utils.ts @@ -1,7 +1,6 @@ import type { SchemaValuePrimitive, SchemaValue } from '@logto/schemas'; import type { Falsy } from '@silverhand/essentials'; import { notFalsy } from '@silverhand/essentials'; -import dayjs from 'dayjs'; import type { SqlSqlToken, SqlToken, QueryResult, IdentifierSqlToken } from 'slonik'; import { sql } from 'slonik'; @@ -76,7 +75,8 @@ export const convertToIdentifiers = ({ table, fields }: T, with }; }; -export const convertToTimestamp = (time = dayjs()) => sql`to_timestamp(${time.valueOf() / 1000})`; +export const convertToTimestamp = (time = new Date()) => + sql`to_timestamp(${time.valueOf() / 1000})`; export const manyRows = async (query: Promise>): Promise => { const { rows } = await query; diff --git a/packages/ui/package.json b/packages/ui/package.json index 32e260f53..3f45dfbe5 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -46,7 +46,7 @@ "i18next": "^21.8.16", "i18next-browser-languagedetector": "^6.1.4", "jest": "^29.1.2", - "jest-environment-jsdom": "^28.1.3", + "jest-environment-jsdom": "^29.0.0", "jest-transformer-svg": "^2.0.0", "js-base64": "^3.7.2", "ky": "^0.31.0", @@ -69,6 +69,17 @@ "typescript": "^4.7.4", "use-debounced-loader": "^0.1.1" }, + "engines": { + "node": "^16.13.0 || ^18.12.0" + }, + "//": "https://github.com/parcel-bundler/parcel/issues/7636", + "targets": { + "default": { + "engines": { + "browsers": "defaults" + } + } + }, "alias": { "@/*": "./src/$1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02d300564..523a7ff83 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,7 +41,7 @@ importers: dotenv: ^16.0.0 eslint: ^8.21.0 fs-extra: ^10.1.0 - got: ^11.8.2 + got: ^11.8.5 hpagent: ^1.0.0 inquirer: ^8.2.2 jest: ^29.1.2 @@ -69,7 +69,7 @@ importers: decamelize: 5.0.1 dotenv: 16.0.0 fs-extra: 10.1.0 - got: 11.8.3 + got: 11.8.5 hpagent: 1.0.0 inquirer: 8.2.2 nanoid: 3.3.4 @@ -109,7 +109,7 @@ importers: '@logto/language-kit': 1.0.0-beta.20 '@logto/phrases': workspace:^ '@logto/phrases-ui': workspace:^ - '@logto/react': 1.0.0-beta.11 + '@logto/react': 1.0.0-beta.12 '@logto/schemas': workspace:^ '@mdx-js/react': ^1.6.22 '@parcel/core': 2.7.0 @@ -135,6 +135,7 @@ importers: clean-deep: ^3.4.0 cross-env: ^7.0.3 csstype: ^3.0.11 + date-fns: ^2.29.3 dayjs: ^1.10.5 deep-object-diff: ^1.1.7 deepmerge: ^4.2.2 @@ -180,7 +181,7 @@ importers: '@logto/language-kit': 1.0.0-beta.20 '@logto/phrases': link:../phrases '@logto/phrases-ui': link:../phrases-ui - '@logto/react': 1.0.0-beta.11_react@18.2.0 + '@logto/react': 1.0.0-beta.12_react@18.2.0 '@logto/schemas': link:../schemas '@mdx-js/react': 1.6.22_react@18.2.0 '@parcel/core': 2.7.0 @@ -206,7 +207,8 @@ importers: clean-deep: 3.4.0 cross-env: 7.0.3 csstype: 3.0.11 - dayjs: 1.10.7 + date-fns: 2.29.3 + dayjs: 1.11.6 deep-object-diff: 1.1.7 deepmerge: 4.2.2 dnd-core: 16.0.0 @@ -274,12 +276,12 @@ importers: '@types/koa-send': ^4.1.3 '@types/lodash.pick': ^4.4.6 '@types/node': ^16.0.0 - '@types/oidc-provider': ^7.11.1 + '@types/oidc-provider': ^7.12.0 '@types/supertest': ^2.0.11 chalk: ^4 clean-deep: ^3.4.0 copyfiles: ^2.4.1 - dayjs: ^1.10.5 + date-fns: ^2.29.3 debug: ^4.3.4 decamelize: ^5.0.0 deepmerge: ^4.2.2 @@ -288,7 +290,7 @@ importers: etag: ^1.8.1 find-up: ^5.0.0 fs-extra: ^10.1.0 - got: ^11.8.2 + got: ^11.8.5 hash-wasm: ^4.9.0 http-errors: ^1.6.3 i18next: ^21.8.16 @@ -311,7 +313,7 @@ importers: nanoid: ^3.1.23 nock: ^13.2.2 nodemon: ^2.0.19 - oidc-provider: ^7.11.3 + oidc-provider: ^7.13.0 openapi-types: ^12.0.0 p-retry: ^4.6.1 prettier: ^2.7.1 @@ -337,7 +339,7 @@ importers: '@silverhand/essentials': 1.3.0 chalk: 4.1.2 clean-deep: 3.4.0 - dayjs: 1.10.7 + date-fns: 2.29.3 debug: 4.3.4 decamelize: 5.0.1 deepmerge: 4.2.2 @@ -345,7 +347,7 @@ importers: etag: 1.8.1 find-up: 5.0.0 fs-extra: 10.1.0 - got: 11.8.3 + got: 11.8.5 hash-wasm: 4.9.0 i18next: 21.8.16 iconv-lite: 0.6.3 @@ -362,7 +364,7 @@ importers: lodash.pick: 4.4.0 module-alias: 2.2.2 nanoid: 3.1.30 - oidc-provider: 7.11.3 + oidc-provider: 7.13.0 p-retry: 4.6.1 query-string: 7.0.1 roarr: 7.11.0 @@ -390,7 +392,7 @@ importers: '@types/koa-send': 4.1.3 '@types/lodash.pick': 4.4.6 '@types/node': 16.11.12 - '@types/oidc-provider': 7.11.1 + '@types/oidc-provider': 7.12.0 '@types/supertest': 2.0.11 copyfiles: 2.4.1 eslint: 8.21.0 @@ -416,7 +418,7 @@ importers: '@logto/core-kit': 1.0.0-beta.20 '@logto/language-kit': 1.0.0-beta.20 '@logto/phrases': workspace:^ - '@logto/react': 1.0.0-beta.11 + '@logto/react': 1.0.0-beta.12 '@logto/schemas': workspace:^ '@parcel/core': 2.7.0 '@parcel/transformer-sass': 2.7.0 @@ -443,7 +445,7 @@ importers: '@logto/core-kit': 1.0.0-beta.20 '@logto/language-kit': 1.0.0-beta.20 '@logto/phrases': link:../phrases - '@logto/react': 1.0.0-beta.11_react@18.2.0 + '@logto/react': 1.0.0-beta.12_react@18.2.0 '@logto/schemas': link:../schemas '@parcel/core': 2.7.0 '@parcel/transformer-sass': 2.7.0_@parcel+core@2.7.0 @@ -470,7 +472,8 @@ importers: packages/integration-tests: specifiers: '@jest/types': ^29.1.2 - '@logto/node': 1.0.0-beta.11 + '@logto/js': 1.0.0-beta.11 + '@logto/node': 1.0.0-beta.12 '@logto/schemas': workspace:^ '@peculiar/webcrypto': ^1.3.3 '@silverhand/eslint-config': 1.3.0 @@ -482,20 +485,21 @@ importers: '@types/node': ^16.0.0 dotenv: ^16.0.0 eslint: ^8.21.0 - got: ^11.8.2 + got: ^11.8.5 jest: ^29.1.2 jest-puppeteer: ^6.1.1 node-fetch: ^2.6.7 openapi-schema-validator: ^12.0.0 openapi-types: ^12.0.0 prettier: ^2.7.1 - puppeteer: ^18.0.0 + puppeteer: ^19.0.0 text-encoder: ^0.0.4 ts-node: ^10.9.1 typescript: ^4.7.4 devDependencies: '@jest/types': 29.1.2 - '@logto/node': 1.0.0-beta.11 + '@logto/js': 1.0.0-beta.11 + '@logto/node': 1.0.0-beta.12 '@logto/schemas': link:../schemas '@peculiar/webcrypto': 1.3.3 '@silverhand/eslint-config': 1.3.0_swk2g7ygmfleszo5c33j4vooni @@ -507,14 +511,14 @@ importers: '@types/node': 16.11.12 dotenv: 16.0.0 eslint: 8.21.0 - got: 11.8.3 + got: 11.8.5 jest: 29.1.2_k5ytkvaprncdyzidqqws5bqksq - jest-puppeteer: 6.1.1_puppeteer@18.0.0 + jest-puppeteer: 6.1.1_puppeteer@19.2.2 node-fetch: 2.6.7 openapi-schema-validator: 12.0.0 openapi-types: 12.0.0 prettier: 2.7.1 - puppeteer: 18.0.0 + puppeteer: 19.2.2 text-encoder: 0.0.4 ts-node: 10.9.1_ccwudyfw5se7hgalwgkzhn2yp4 typescript: 4.7.4 @@ -631,7 +635,6 @@ importers: '@silverhand/ts-config': 1.2.1 '@types/jest': ^29.1.2 '@types/node': ^16.0.0 - dayjs: ^1.10.5 eslint: ^8.21.0 find-up: ^5.0.0 jest: ^29.1.2 @@ -643,7 +646,6 @@ importers: dependencies: '@logto/schemas': link:../schemas '@silverhand/essentials': 1.3.0 - dayjs: 1.10.7 find-up: 5.0.0 nanoid: 3.3.4 slonik: 30.1.2 @@ -691,7 +693,7 @@ importers: i18next: ^21.8.16 i18next-browser-languagedetector: ^6.1.4 jest: ^29.1.2 - jest-environment-jsdom: ^28.1.3 + jest-environment-jsdom: ^29.0.0 jest-transformer-svg: ^2.0.0 js-base64: ^3.7.2 ky: ^0.31.0 @@ -744,7 +746,7 @@ importers: i18next: 21.8.16 i18next-browser-languagedetector: 6.1.4 jest: 29.1.2 - jest-environment-jsdom: 28.1.3 + jest-environment-jsdom: 29.2.2 jest-transformer-svg: 2.0.0_jest@29.1.2+react@18.2.0 js-base64: 3.7.2 ky: 0.31.0 @@ -836,7 +838,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.1.2 - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 '@babel/generator': 7.17.9 '@babel/helper-compilation-targets': 7.17.7_@babel+core@7.17.9 '@babel/helper-module-transforms': 7.17.7 @@ -1850,11 +1852,11 @@ packages: resolution: {integrity: sha512-ujEBCcYs82BTmRxqfHMQggSlkUZP63AE5YEaTPj7eFyJOzukkTorstOUC7L6nE3w5SYadGVAnTsQ/ZjTGL0qYQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 chalk: 4.1.2 - jest-message-util: 29.1.2 - jest-util: 29.1.2 + jest-message-util: 29.2.1 + jest-util: 29.2.1 slash: 3.0.0 dev: true @@ -1871,15 +1873,15 @@ packages: '@jest/reporters': 29.1.2 '@jest/test-result': 29.1.2 '@jest/transform': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.5.0 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.0.0 - jest-config: 29.1.2_@types+node@16.11.65 + jest-config: 29.1.2_@types+node@17.0.23 jest-haste-map: 29.1.2 jest-message-util: 29.1.2 jest-regex-util: 29.0.0 @@ -1888,7 +1890,7 @@ packages: jest-runner: 29.1.2 jest-runtime: 29.1.2 jest-snapshot: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 jest-watcher: 29.1.2 micromatch: 4.0.5 @@ -1913,15 +1915,15 @@ packages: '@jest/reporters': 29.1.2 '@jest/test-result': 29.1.2 '@jest/transform': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.5.0 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.0.0 - jest-config: 29.1.2_7fnffnpg6qxg3uvt2vx7t77ic4 + jest-config: 29.1.2_hvivgrlmkyd4vgu6rkkmg6acly jest-haste-map: 29.1.2 jest-message-util: 29.1.2 jest-regex-util: 29.0.0 @@ -1930,7 +1932,7 @@ packages: jest-runner: 29.1.2 jest-runtime: 29.1.2 jest-snapshot: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 jest-watcher: 29.1.2 micromatch: 4.0.5 @@ -1952,24 +1954,14 @@ packages: jest-mock: 27.5.1 dev: true - /@jest/environment/28.1.3: - resolution: {integrity: sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@jest/fake-timers': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 17.0.23 - jest-mock: 28.1.3 - dev: true - - /@jest/environment/29.1.2: - resolution: {integrity: sha512-rG7xZ2UeOfvOVzoLIJ0ZmvPl4tBEQ2n73CZJSlzUjPw4or1oSWC0s0Rk0ZX+pIBJ04aVr6hLWFn1DFtrnf8MhQ==} + /@jest/environment/29.2.2: + resolution: {integrity: sha512-OWn+Vhu0I1yxuGBJEFFekMYc8aGBGrY4rt47SOh/IFaI+D7ZHCk7pKRiSoZ2/Ml7b0Ony3ydmEHRx/tEOC7H1A==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/fake-timers': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 - jest-mock: 29.1.2 + '@jest/fake-timers': 29.2.2 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 + jest-mock: 29.2.2 dev: true /@jest/expect-utils/29.1.2: @@ -2001,38 +1993,26 @@ packages: jest-util: 27.5.1 dev: true - /@jest/fake-timers/28.1.3: - resolution: {integrity: sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@jest/types': 28.1.3 - '@sinonjs/fake-timers': 9.1.2 - '@types/node': 17.0.23 - jest-message-util: 28.1.3 - jest-mock: 28.1.3 - jest-util: 28.1.3 - dev: true - - /@jest/fake-timers/29.1.2: - resolution: {integrity: sha512-GppaEqS+QQYegedxVMpCe2xCXxxeYwQ7RsNx55zc8f+1q1qevkZGKequfTASI7ejmg9WwI+SJCrHe9X11bLL9Q==} + /@jest/fake-timers/29.2.2: + resolution: {integrity: sha512-nqaW3y2aSyZDl7zQ7t1XogsxeavNpH6kkdq+EpXncIDvAkjvFD7hmhcIs1nWloengEWUoWqkqSA6MSbf9w6DgA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@sinonjs/fake-timers': 9.1.2 - '@types/node': 16.11.65 - jest-message-util: 29.1.2 - jest-mock: 29.1.2 - jest-util: 29.1.2 + '@types/node': 17.0.23 + jest-message-util: 29.2.1 + jest-mock: 29.2.2 + jest-util: 29.2.1 dev: true /@jest/globals/29.1.2: resolution: {integrity: sha512-uMgfERpJYoQmykAd0ffyMq8wignN4SvLUG6orJQRe9WAlTRc9cdpCaE/29qurXixYJVZWUqIBXhSk8v5xN1V9g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.1.2 + '@jest/environment': 29.2.2 '@jest/expect': 29.1.2 - '@jest/types': 29.1.2 - jest-mock: 29.1.2 + '@jest/types': 29.2.1 + jest-mock: 29.2.2 transitivePeerDependencies: - supports-color dev: true @@ -2050,9 +2030,9 @@ packages: '@jest/console': 29.1.2 '@jest/test-result': 29.1.2 '@jest/transform': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@jridgewell/trace-mapping': 0.3.16 - '@types/node': 16.11.65 + '@types/node': 17.0.23 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -2063,8 +2043,8 @@ packages: istanbul-lib-report: 3.0.0 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 - jest-message-util: 29.1.2 - jest-util: 29.1.2 + jest-message-util: 29.2.1 + jest-util: 29.2.1 jest-worker: 29.1.2 slash: 3.0.0 string-length: 4.0.2 @@ -2075,13 +2055,6 @@ packages: - supports-color dev: true - /@jest/schemas/28.1.3: - resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@sinclair/typebox': 0.24.26 - dev: true - /@jest/schemas/29.0.0: resolution: {integrity: sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2103,7 +2076,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.1 dev: true @@ -2123,7 +2096,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.19.3 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@jridgewell/trace-mapping': 0.3.16 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 @@ -2132,7 +2105,7 @@ packages: graceful-fs: 4.2.10 jest-haste-map: 29.1.2 jest-regex-util: 29.0.0 - jest-util: 29.1.2 + jest-util: 29.2.1 micromatch: 4.0.5 pirates: 4.0.5 slash: 3.0.0 @@ -2145,27 +2118,27 @@ packages: resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@types/istanbul-lib-coverage': 2.0.3 + '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 '@types/node': 17.0.23 '@types/yargs': 16.0.4 chalk: 4.1.2 dev: true - /@jest/types/28.1.3: - resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@jest/types/29.1.2: + resolution: {integrity: sha512-DcXGtoTykQB5jiwCmVr8H4vdg2OJhQex3qPkG+ISyDO7xQXbt/4R6dowcRyPemRnkH7JoHvZuxPBdlq+9JxFCg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/schemas': 28.1.3 - '@types/istanbul-lib-coverage': 2.0.3 + '@jest/schemas': 29.0.0 + '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 '@types/node': 17.0.23 '@types/yargs': 17.0.13 chalk: 4.1.2 dev: true - /@jest/types/29.1.2: - resolution: {integrity: sha512-DcXGtoTykQB5jiwCmVr8H4vdg2OJhQex3qPkG+ISyDO7xQXbt/4R6dowcRyPemRnkH7JoHvZuxPBdlq+9JxFCg==} + /@jest/types/29.2.1: + resolution: {integrity: sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.0.0 @@ -2235,8 +2208,8 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /@koa/cors/3.1.0: - resolution: {integrity: sha512-7ulRC1da/rBa6kj6P4g2aJfnET3z8Uf3SWu60cjbtxTA5g8lxRdX/Bd2P92EagGwwAhANeNw8T8if99rJliR6Q==} + /@koa/cors/3.4.3: + resolution: {integrity: sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==} engines: {node: '>= 8.0.0'} dependencies: vary: 1.1.2 @@ -2300,16 +2273,16 @@ packages: dev: true optional: true - /@logto/browser/1.0.0-beta.11: - resolution: {integrity: sha512-Ofdj5UqLzwoW66XGnff+1U6Lix90qI2Q30+TISJz6soCm97oqJdWpY2SRlMAfnvDobsFPqAxyCAz49Cg16h50Q==} + /@logto/browser/1.0.0-beta.12: + resolution: {integrity: sha512-ImlZ/+fcBEhz8F3QqhQU1HXYjJwA+3hJIqGzcYDUeLvAeMIY7gP1N/RtXauacaeh+F0UfpoKeoD45Q3EedHOwA==} dependencies: - '@logto/client': 1.0.0-beta.11 + '@logto/client': 1.0.0-beta.12 '@silverhand/essentials': 1.3.0 js-base64: 3.7.2 dev: true - /@logto/client/1.0.0-beta.11: - resolution: {integrity: sha512-7Nl+53JPgB0wjMU9zJH6SrOj4OKn0Kl9U1cumt/IHSYuaDyV7TgvR/HaaeSZuZ0JxbMg66Riee87Qx9Tv46k6A==} + /@logto/client/1.0.0-beta.12: + resolution: {integrity: sha512-9u5uHqKYv7uq85lfX+EkytYnks0fQexO/vRt6/kz74DC5CqFdJPYHu4njbJyAYB8rCQoiB/Bbw1RGIC5AyRWLA==} dependencies: '@logto/core-kit': 1.0.0-beta.20 '@logto/js': 1.0.0-beta.11 @@ -2365,10 +2338,10 @@ packages: dependencies: zod: 3.19.1 - /@logto/node/1.0.0-beta.11: - resolution: {integrity: sha512-nsa9RtrzBRmLMJNeNzutJ72bLwZpGUr4lhBAze7YXJi6o9ujvZOLgdvZAZeoSdxxBhTilwbDWNXmj8n3Ndavaw==} + /@logto/node/1.0.0-beta.12: + resolution: {integrity: sha512-SA2H2JH5S5mXZyBPR79Z9U4zYZDXHijqYpreDdSd5abjMu0g+rq00Ht80TwYby2vCj/a4GV/OUew7jzGh3lwlw==} dependencies: - '@logto/client': 1.0.0-beta.11 + '@logto/client': 1.0.0-beta.12 '@silverhand/essentials': 1.3.0 js-base64: 3.7.2 node-fetch: 2.6.7 @@ -2376,12 +2349,12 @@ packages: - encoding dev: true - /@logto/react/1.0.0-beta.11_react@18.2.0: - resolution: {integrity: sha512-W9L1QrJml4tHlrUkmdaJRsj4ln+SgwGAeyF+QhmKBq5CoSR+EauE63ySLhPn2KFVrc3m/sioejJIVy+1sUe21w==} + /@logto/react/1.0.0-beta.12_react@18.2.0: + resolution: {integrity: sha512-8LOAZx7+zs+Uyr/ciudtjiiXVimxrydodRscQGFx+PQcbie/efVOijwbLrHDrBzB4cjmgSvdFnBH/2z9Tpiz0g==} peerDependencies: react: '>=16.8.0 || ^18.0.0' dependencies: - '@logto/browser': 1.0.0-beta.11 + '@logto/browser': 1.0.0-beta.12 '@silverhand/essentials': 1.3.0 react: 18.2.0 dev: true @@ -3533,10 +3506,6 @@ packages: typescript: 4.7.4 dev: true - /@sinclair/typebox/0.24.26: - resolution: {integrity: sha512-1ZVIyyS1NXDRVT8GjWD5jULjhDyM3IsIHef2VGUMdnWOlX2tkPjyEX/7K0TGSH2S8EaPhp1ylFdjSjUGQ+gecg==} - dev: true - /@sinclair/typebox/0.24.46: resolution: {integrity: sha512-ng4ut1z2MCBhK/NwDVwIQp3pAUOCs/KNaW3cBxdFB2xTDrOuo1xuNmpr/9HHFhxqIvHrs1NTH3KJg6q+JSy1Kw==} dev: true @@ -3909,7 +3878,7 @@ packages: /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 16.11.65 + '@types/node': 17.0.23 dev: true /@types/hast/2.3.4: @@ -3950,10 +3919,6 @@ packages: ci-info: 3.5.0 dev: true - /@types/istanbul-lib-coverage/2.0.3: - resolution: {integrity: sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==} - dev: true - /@types/istanbul-lib-coverage/2.0.4: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} dev: true @@ -3961,7 +3926,7 @@ packages: /@types/istanbul-lib-report/3.0.0: resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} dependencies: - '@types/istanbul-lib-coverage': 2.0.3 + '@types/istanbul-lib-coverage': 2.0.4 dev: true /@types/istanbul-reports/3.0.1: @@ -3989,12 +3954,12 @@ packages: resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} dev: true - /@types/jsdom/16.2.15: - resolution: {integrity: sha512-nwF87yjBKuX/roqGYerZZM0Nv1pZDMAT5YhOHYeM/72Fic+VEqJh4nyoqoapzJnW3pUlfxPY5FhgsJtM+dRnQQ==} + /@types/jsdom/20.0.0: + resolution: {integrity: sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==} dependencies: '@types/node': 17.0.23 - '@types/parse5': 6.0.3 '@types/tough-cookie': 4.0.2 + parse5: 7.1.1 dev: true /@types/json-schema/7.0.11: @@ -4119,10 +4084,6 @@ packages: resolution: {integrity: sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==} dev: true - /@types/node/16.11.65: - resolution: {integrity: sha512-Vfz7wGMOr4jbQGiQHVJm8VjeQwM9Ya7mHe9LtQ264/Epf5n1KiZShOFqk++nBzw6a/ubgYdB9Od7P+MH/LjoWw==} - dev: true - /@types/node/17.0.23: resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==} @@ -4130,8 +4091,8 @@ packages: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true - /@types/oidc-provider/7.11.1: - resolution: {integrity: sha512-qimKqQ5HmQE2HKzhTvkLC0CQcOrQZv9+VAWEBb4EL+zqIZjE4X0PiCL0177eo6kitWrIjTQhhiLc2R3luA1AlQ==} + /@types/oidc-provider/7.12.0: + resolution: {integrity: sha512-PQtsWdbjzq/AARiNu2WjPnnbiCF78BCYgDOdKW0VYsjrbDa9HN8dALt7bSIJ0O67ti1l0pYDk8UCpmUu8rPAUQ==} dependencies: '@types/koa': 2.13.4 dev: true @@ -4143,10 +4104,6 @@ packages: resolution: {integrity: sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==} dev: true - /@types/parse5/6.0.3: - resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} - dev: true - /@types/pluralize/0.0.29: resolution: {integrity: sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==} dev: true @@ -4459,7 +4416,7 @@ packages: resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==} engines: {node: '>= 0.6'} dependencies: - mime-types: 2.1.34 + mime-types: 2.1.35 negotiator: 0.6.2 /accepts/1.3.8: @@ -4470,11 +4427,11 @@ packages: negotiator: 0.6.3 dev: true - /acorn-globals/6.0.0: - resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} + /acorn-globals/7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 + acorn: 8.8.0 + acorn-walk: 8.2.0 dev: true /acorn-jsx/5.3.2_acorn@8.8.0: @@ -4485,28 +4442,11 @@ packages: acorn: 8.8.0 dev: true - /acorn-walk/7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} - dev: true - /acorn-walk/8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} dev: true - /acorn/7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /acorn/8.7.0: - resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - /acorn/8.8.0: resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} engines: {node: '>=0.4.0'} @@ -4716,8 +4656,8 @@ packages: hasBin: true dev: true - /async/0.9.2: - resolution: {integrity: sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==} + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: false /asynckit/0.4.0: @@ -4909,10 +4849,6 @@ packages: wcwidth: 1.0.1 dev: true - /browser-process-hrtime/1.0.0: - resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} - dev: true - /browserslist/4.20.3: resolution: {integrity: sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4989,7 +4925,7 @@ packages: resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} engines: {node: '>= 6.0.0'} dependencies: - mime-types: 2.1.34 + mime-types: 2.1.35 ylru: 1.2.1 /cacheable-lookup/5.0.4: @@ -5145,10 +5081,6 @@ packages: engines: {node: '>=6.0'} dev: true - /ci-info/3.3.2: - resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==} - dev: true - /ci-info/3.5.0: resolution: {integrity: sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==} dev: true @@ -5731,8 +5663,13 @@ packages: whatwg-url: 11.0.0 dev: true - /dayjs/1.10.7: - resolution: {integrity: sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==} + /date-fns/2.29.3: + resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + engines: {node: '>=0.11'} + + /dayjs/1.11.6: + resolution: {integrity: sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==} + dev: true /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -5813,8 +5750,8 @@ packages: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} dev: true - /decimal.js/10.3.1: - resolution: {integrity: sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==} + /decimal.js/10.4.2: + resolution: {integrity: sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==} dev: true /decode-named-character-reference/1.0.1: @@ -5936,8 +5873,8 @@ packages: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} dev: false - /devtools-protocol/0.0.1036444: - resolution: {integrity: sha512-0y4f/T8H9lsESV9kKP1HDUXgHxCdniFeJh6Erq+FbdOEvp/Ydp9t8kcAAM5gOd17pMrTDlFWntoHtzzeTUWKNw==} + /devtools-protocol/0.0.1056733: + resolution: {integrity: sha512-CmTu6SQx2g3TbZzDCAV58+LTxVdKplS7xip0g5oDXpZ+isr0rv5dDP8ToyVRywzPHkCCPKgKgScEcwz4uPWDIA==} dev: true /dezalgo/1.0.3: @@ -6067,12 +6004,12 @@ packages: /ee-first/1.1.1: resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} - /ejs/3.1.6: - resolution: {integrity: sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==} + /ejs/3.1.8: + resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} engines: {node: '>=0.10.0'} hasBin: true dependencies: - jake: 10.8.2 + jake: 10.8.5 dev: false /electron-to-chromium/1.4.141: @@ -6132,6 +6069,11 @@ packages: engines: {node: '>=0.12'} dev: true + /entities/4.4.0: + resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + engines: {node: '>=0.12'} + dev: true + /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -6776,7 +6718,7 @@ packages: jest-get-type: 29.0.0 jest-matcher-utils: 29.1.2 jest-message-util: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 dev: true /extend/3.0.2: @@ -7349,8 +7291,8 @@ packages: csstype: 3.0.11 dev: true - /got/11.8.3: - resolution: {integrity: sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==} + /got/11.8.5: + resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==} engines: {node: '>=10.19.0'} dependencies: '@sindresorhus/is': 4.2.0 @@ -8306,12 +8248,13 @@ packages: istanbul-lib-report: 3.0.0 dev: true - /jake/10.8.2: - resolution: {integrity: sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==} + /jake/10.8.5: + resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} + engines: {node: '>=10'} hasBin: true dependencies: - async: 0.9.2 - chalk: 2.4.2 + async: 3.2.4 + chalk: 4.1.2 filelist: 1.0.2 minimatch: 3.1.2 dev: false @@ -8328,23 +8271,23 @@ packages: resolution: {integrity: sha512-ajQOdxY6mT9GtnfJRZBRYS7toNIJayiiyjDyoZcnvPRUPwJ58JX0ci0PKAKUo2C1RyzlHw0jabjLGKksO42JGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.1.2 + '@jest/environment': 29.2.2 '@jest/expect': 29.1.2 '@jest/test-result': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 is-generator-fn: 2.1.0 jest-each: 29.1.2 jest-matcher-utils: 29.1.2 - jest-message-util: 29.1.2 + jest-message-util: 29.2.1 jest-runtime: 29.1.2 jest-snapshot: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 p-limit: 3.1.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 slash: 3.0.0 stack-utils: 2.0.5 transitivePeerDependencies: @@ -8363,13 +8306,13 @@ packages: dependencies: '@jest/core': 29.1.2 '@jest/test-result': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 jest-config: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 prompts: 2.4.2 yargs: 17.6.0 @@ -8391,13 +8334,13 @@ packages: dependencies: '@jest/core': 29.1.2 '@jest/test-result': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 jest-config: 29.1.2_@types+node@16.11.12 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 prompts: 2.4.2 yargs: 17.6.0 @@ -8419,13 +8362,13 @@ packages: dependencies: '@jest/core': 29.1.2_ts-node@10.9.1 '@jest/test-result': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 jest-config: 29.1.2_k5ytkvaprncdyzidqqws5bqksq - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 prompts: 2.4.2 yargs: 17.6.0 @@ -8449,7 +8392,7 @@ packages: dependencies: '@babel/core': 7.19.3 '@jest/test-sequencer': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 babel-jest: 29.1.2_@babel+core@7.19.3 chalk: 4.1.2 ci-info: 3.5.0 @@ -8462,57 +8405,17 @@ packages: jest-regex-util: 29.0.0 jest-resolve: 29.1.2 jest-runner: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /jest-config/29.1.2_7fnffnpg6qxg3uvt2vx7t77ic4: - resolution: {integrity: sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.19.3 - '@jest/test-sequencer': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 - babel-jest: 29.1.2_@babel+core@7.19.3 - chalk: 4.1.2 - ci-info: 3.5.0 - deepmerge: 4.2.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-circus: 29.1.2 - jest-environment-node: 29.1.2 - jest-get-type: 29.0.0 - jest-regex-util: 29.0.0 - jest-resolve: 29.1.2 - jest-runner: 29.1.2 - jest-util: 29.1.2 - jest-validate: 29.1.2 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.1.2 - slash: 3.0.0 - strip-json-comments: 3.1.1 - ts-node: 10.9.1_ccwudyfw5se7hgalwgkzhn2yp4 - transitivePeerDependencies: - - supports-color - dev: true - /jest-config/29.1.2_@types+node@16.11.12: resolution: {integrity: sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8527,7 +8430,7 @@ packages: dependencies: '@babel/core': 7.19.3 '@jest/test-sequencer': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@types/node': 16.11.12 babel-jest: 29.1.2_@babel+core@7.19.3 chalk: 4.1.2 @@ -8541,18 +8444,18 @@ packages: jest-regex-util: 29.0.0 jest-resolve: 29.1.2 jest-runner: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /jest-config/29.1.2_@types+node@16.11.65: + /jest-config/29.1.2_@types+node@17.0.23: resolution: {integrity: sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -8566,8 +8469,8 @@ packages: dependencies: '@babel/core': 7.19.3 '@jest/test-sequencer': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 babel-jest: 29.1.2_@babel+core@7.19.3 chalk: 4.1.2 ci-info: 3.5.0 @@ -8580,17 +8483,57 @@ packages: jest-regex-util: 29.0.0 jest-resolve: 29.1.2 jest-runner: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 slash: 3.0.0 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true + /jest-config/29.1.2_hvivgrlmkyd4vgu6rkkmg6acly: + resolution: {integrity: sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.19.3 + '@jest/test-sequencer': 29.1.2 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 + babel-jest: 29.1.2_@babel+core@7.19.3 + chalk: 4.1.2 + ci-info: 3.5.0 + deepmerge: 4.2.2 + glob: 7.2.3 + graceful-fs: 4.2.10 + jest-circus: 29.1.2 + jest-environment-node: 29.1.2 + jest-get-type: 29.0.0 + jest-regex-util: 29.0.0 + jest-resolve: 29.1.2 + jest-runner: 29.1.2 + jest-util: 29.2.1 + jest-validate: 29.1.2 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.2.1 + slash: 3.0.0 + strip-json-comments: 3.1.1 + ts-node: 10.9.1_ccwudyfw5se7hgalwgkzhn2yp4 + transitivePeerDependencies: + - supports-color + dev: true + /jest-config/29.1.2_k5ytkvaprncdyzidqqws5bqksq: resolution: {integrity: sha512-EC3Zi86HJUOz+2YWQcJYQXlf0zuBhJoeyxLM6vb6qJsVmpP7KcCP1JnyF0iaqTaXdBP8Rlwsvs7hnKWQWWLwwA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8605,7 +8548,7 @@ packages: dependencies: '@babel/core': 7.19.3 '@jest/test-sequencer': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@types/node': 16.11.12 babel-jest: 29.1.2_@babel+core@7.19.3 chalk: 4.1.2 @@ -8619,11 +8562,11 @@ packages: jest-regex-util: 29.0.0 jest-resolve: 29.1.2 jest-runner: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 slash: 3.0.0 strip-json-comments: 3.1.1 ts-node: 10.9.1_ccwudyfw5se7hgalwgkzhn2yp4 @@ -8667,28 +8610,32 @@ packages: resolution: {integrity: sha512-AmTQp9b2etNeEwMyr4jc0Ql/LIX/dhbgP21gHAizya2X6rUspHn2gysMXaj6iwWuOJ2sYRgP8c1P4cXswgvS1A==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 chalk: 4.1.2 jest-get-type: 29.0.0 - jest-util: 29.1.2 - pretty-format: 29.1.2 + jest-util: 29.2.1 + pretty-format: 29.2.1 dev: true - /jest-environment-jsdom/28.1.3: - resolution: {integrity: sha512-HnlGUmZRdxfCByd3GM2F100DgQOajUBzEitjGqIREcb45kGjZvRrKUdlaF6escXBdcXNl0OBh+1ZrfeZT3GnAg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /jest-environment-jsdom/29.2.2: + resolution: {integrity: sha512-5mNtTcky1+RYv9kxkwMwt7fkzyX4EJUarV7iI+NQLigpV4Hz4sgfOdP4kOpCHXbkRWErV7tgXoXLm2CKtucr+A==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true dependencies: - '@jest/environment': 28.1.3 - '@jest/fake-timers': 28.1.3 - '@jest/types': 28.1.3 - '@types/jsdom': 16.2.15 + '@jest/environment': 29.2.2 + '@jest/fake-timers': 29.2.2 + '@jest/types': 29.2.1 + '@types/jsdom': 20.0.0 '@types/node': 17.0.23 - jest-mock: 28.1.3 - jest-util: 28.1.3 - jsdom: 19.0.0 + jest-mock: 29.2.2 + jest-util: 29.2.1 + jsdom: 20.0.2 transitivePeerDependencies: - bufferutil - - canvas - supports-color - utf-8-validate dev: true @@ -8709,12 +8656,12 @@ packages: resolution: {integrity: sha512-C59yVbdpY8682u6k/lh8SUMDJPbOyCHOTgLVVi1USWFxtNV+J8fyIwzkg+RJIVI30EKhKiAGNxYaFr3z6eyNhQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.1.2 - '@jest/fake-timers': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 - jest-mock: 29.1.2 - jest-util: 29.1.2 + '@jest/environment': 29.2.2 + '@jest/fake-timers': 29.2.2 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 + jest-mock: 29.2.2 + jest-util: 29.2.1 dev: true /jest-environment-puppeteer/6.1.1: @@ -8739,14 +8686,14 @@ packages: resolution: {integrity: sha512-xSjbY8/BF11Jh3hGSPfYTa/qBFrm3TPM7WU8pU93m2gqzORVLkHFWvuZmFsTEBPRKndfewXhMOuzJNHyJIZGsw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@types/graceful-fs': 4.1.5 - '@types/node': 16.11.65 + '@types/node': 17.0.23 anymatch: 3.1.2 fb-watchman: 2.0.2 graceful-fs: 4.2.10 jest-regex-util: 29.0.0 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-worker: 29.1.2 micromatch: 4.0.5 walker: 1.0.8 @@ -8759,7 +8706,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.0.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 dev: true /jest-matcher-specific-error/1.0.0: @@ -8780,7 +8727,7 @@ packages: resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 '@jest/types': 27.5.1 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -8791,27 +8738,12 @@ packages: stack-utils: 2.0.5 dev: true - /jest-message-util/28.1.3: - resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@babel/code-frame': 7.18.6 - '@jest/types': 28.1.3 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - pretty-format: 28.1.3 - slash: 3.0.0 - stack-utils: 2.0.5 - dev: true - /jest-message-util/29.1.2: resolution: {integrity: sha512-9oJ2Os+Qh6IlxLpmvshVbGUiSkZVc2FK+uGOm6tghafnB2RyjKAxMZhtxThRMxfX1J1SOMhTn9oK3/MutRWQJQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/code-frame': 7.18.6 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@types/stack-utils': 2.0.1 chalk: 4.1.2 graceful-fs: 4.2.10 @@ -8821,6 +8753,21 @@ packages: stack-utils: 2.0.5 dev: true + /jest-message-util/29.2.1: + resolution: {integrity: sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.18.6 + '@jest/types': 29.2.1 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.10 + micromatch: 4.0.5 + pretty-format: 29.2.1 + slash: 3.0.0 + stack-utils: 2.0.5 + dev: true + /jest-mock/27.5.1: resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -8829,21 +8776,13 @@ packages: '@types/node': 17.0.23 dev: true - /jest-mock/28.1.3: - resolution: {integrity: sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@jest/types': 28.1.3 - '@types/node': 17.0.23 - dev: true - - /jest-mock/29.1.2: - resolution: {integrity: sha512-PFDAdjjWbjPUtQPkQufvniXIS3N9Tv7tbibePEjIIprzjgo0qQlyUiVMrT4vL8FaSJo1QXifQUOuPH3HQC/aMA==} + /jest-mock/29.2.2: + resolution: {integrity: sha512-1leySQxNAnivvbcx0sCB37itu8f4OX2S/+gxLAV4Z62shT4r4dTG9tACDywUAEZoLSr36aYUTsVp3WKwWt4PMQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.1.2 - '@types/node': 16.11.65 - jest-util: 29.1.2 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 + jest-util: 29.2.1 dev: true /jest-pnp-resolver/1.2.2_jest-resolve@29.1.2: @@ -8858,14 +8797,14 @@ packages: jest-resolve: 29.1.2 dev: true - /jest-puppeteer/6.1.1_puppeteer@18.0.0: + /jest-puppeteer/6.1.1_puppeteer@19.2.2: resolution: {integrity: sha512-cBOszleUpyipDMNYmcmH3x+687x03ZvOVz7W8X5y5TgD+j4MK+BcumwGdE1YwVS21kPLjJUu1pIdEzEDuFEBfA==} peerDependencies: puppeteer: '>= 1.5.0' dependencies: expect-puppeteer: 6.1.1 jest-environment-puppeteer: 6.1.1 - puppeteer: 18.0.0 + puppeteer: 19.2.2 transitivePeerDependencies: - debug - supports-color @@ -8894,7 +8833,7 @@ packages: graceful-fs: 4.2.10 jest-haste-map: 29.1.2 jest-pnp-resolver: 1.2.2_jest-resolve@29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-validate: 29.1.2 resolve: 1.22.1 resolve.exports: 1.1.0 @@ -8906,11 +8845,11 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.1.2 - '@jest/environment': 29.1.2 + '@jest/environment': 29.2.2 '@jest/test-result': 29.1.2 '@jest/transform': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 chalk: 4.1.2 emittery: 0.10.2 graceful-fs: 4.2.10 @@ -8918,10 +8857,10 @@ packages: jest-environment-node: 29.1.2 jest-haste-map: 29.1.2 jest-leak-detector: 29.1.2 - jest-message-util: 29.1.2 + jest-message-util: 29.2.1 jest-resolve: 29.1.2 jest-runtime: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 jest-watcher: 29.1.2 jest-worker: 29.1.2 p-limit: 3.1.0 @@ -8934,26 +8873,26 @@ packages: resolution: {integrity: sha512-jr8VJLIf+cYc+8hbrpt412n5jX3tiXmpPSYTGnwcvNemY+EOuLNiYnHJ3Kp25rkaAcTWOEI4ZdOIQcwYcXIAZw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/environment': 29.1.2 - '@jest/fake-timers': 29.1.2 + '@jest/environment': 29.2.2 + '@jest/fake-timers': 29.2.2 '@jest/globals': 29.1.2 '@jest/source-map': 29.0.0 '@jest/test-result': 29.1.2 '@jest/transform': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 glob: 7.2.3 graceful-fs: 4.2.10 jest-haste-map: 29.1.2 - jest-message-util: 29.1.2 - jest-mock: 29.1.2 + jest-message-util: 29.2.1 + jest-mock: 29.2.2 jest-regex-util: 29.0.0 jest-resolve: 29.1.2 jest-snapshot: 29.1.2 - jest-util: 29.1.2 + jest-util: 29.2.1 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: @@ -8972,7 +8911,7 @@ packages: '@babel/types': 7.19.4 '@jest/expect-utils': 29.1.2 '@jest/transform': 29.1.2 - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 '@types/babel__traverse': 7.18.2 '@types/prettier': 2.7.1 babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.3 @@ -8983,10 +8922,10 @@ packages: jest-get-type: 29.0.0 jest-haste-map: 29.1.2 jest-matcher-utils: 29.1.2 - jest-message-util: 29.1.2 - jest-util: 29.1.2 + jest-message-util: 29.2.1 + jest-util: 29.2.1 natural-compare: 1.4.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 semver: 7.3.8 transitivePeerDependencies: - supports-color @@ -9013,29 +8952,17 @@ packages: '@jest/types': 27.5.1 '@types/node': 17.0.23 chalk: 4.1.2 - ci-info: 3.3.2 - graceful-fs: 4.2.10 - picomatch: 2.3.1 - dev: true - - /jest-util/28.1.3: - resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@jest/types': 28.1.3 - '@types/node': 17.0.23 - chalk: 4.1.2 ci-info: 3.5.0 graceful-fs: 4.2.10 picomatch: 2.3.1 dev: true - /jest-util/29.1.2: - resolution: {integrity: sha512-vPCk9F353i0Ymx3WQq3+a4lZ07NXu9Ca8wya6o4Fe4/aO1e1awMMprZ3woPFpKwghEOW+UXgd15vVotuNN9ONQ==} + /jest-util/29.2.1: + resolution: {integrity: sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 chalk: 4.1.2 ci-info: 3.5.0 graceful-fs: 4.2.10 @@ -9046,12 +8973,12 @@ packages: resolution: {integrity: sha512-k71pOslNlV8fVyI+mEySy2pq9KdXdgZtm7NHrBX8LghJayc3wWZH0Yr0mtYNGaCU4F1OLPXRkwZR0dBm/ClshA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.1.2 + '@jest/types': 29.2.1 camelcase: 6.2.1 chalk: 4.1.2 jest-get-type: 29.0.0 leven: 3.1.0 - pretty-format: 29.1.2 + pretty-format: 29.2.1 dev: true /jest-watcher/29.1.2: @@ -9059,12 +8986,12 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.1.2 - '@jest/types': 29.1.2 - '@types/node': 16.11.65 + '@jest/types': 29.2.1 + '@types/node': 17.0.23 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 - jest-util: 29.1.2 + jest-util: 29.2.1 string-length: 4.0.2 dev: true @@ -9072,8 +8999,8 @@ packages: resolution: {integrity: sha512-AdTZJxKjTSPHbXT/AIOjQVmoFx0LHFcVabWu0sxI7PAy7rFf8c0upyvgBKgguVXdM4vY74JdwkyD4hSmpTW8jA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 16.11.65 - jest-util: 29.1.2 + '@types/node': 17.0.23 + jest-util: 29.2.1 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -9148,6 +9075,10 @@ packages: '@sideway/pinpoint': 2.0.0 dev: true + /jose/4.10.4: + resolution: {integrity: sha512-eBH77Xs9Yc/oTDvukhAEDVMijhekPuNktXJL4tUlB22jqKP1k48v5nmsUmc8feoJPsxB3HsfEt2LbVSoz+1mng==} + dev: false + /jose/4.6.0: resolution: {integrity: sha512-0hNAkhMBNi4soKSAX4zYOFV+aqJlEz/4j4fregvasJzEVtjDChvWqRjPvHwLqr5hx28Ayr6bsOs1Kuj87V0O8w==} @@ -9177,9 +9108,9 @@ packages: dependencies: argparse: 2.0.1 - /jsdom/19.0.0: - resolution: {integrity: sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==} - engines: {node: '>=12'} + /jsdom/20.0.2: + resolution: {integrity: sha512-AHWa+QO/cgRg4N+DsmHg1Y7xnz+8KU3EflM0LVDTdmrYOc1WWTSkOjtpUveQH+1Bqd5rtcVnb/DuxV/UjDO4rA==} + engines: {node: '>=14'} peerDependencies: canvas: ^2.5.0 peerDependenciesMeta: @@ -9188,11 +9119,11 @@ packages: dependencies: abab: 2.0.6 acorn: 8.8.0 - acorn-globals: 6.0.0 + acorn-globals: 7.0.1 cssom: 0.5.0 cssstyle: 2.3.0 data-urls: 3.0.2 - decimal.js: 10.3.1 + decimal.js: 10.4.2 domexception: 4.0.0 escodegen: 2.0.0 form-data: 4.0.0 @@ -9200,18 +9131,17 @@ packages: http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.1 - parse5: 6.0.1 - saxes: 5.0.1 + nwsapi: 2.2.2 + parse5: 7.1.1 + saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 4.0.0 - w3c-hr-time: 1.0.2 + tough-cookie: 4.1.2 w3c-xmlserializer: 3.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 - whatwg-url: 10.0.0 - ws: 8.8.1 + whatwg-url: 11.0.0 + ws: 8.10.0 xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil @@ -10237,20 +10167,10 @@ packages: picomatch: 2.3.1 dev: true - /mime-db/1.51.0: - resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} - engines: {node: '>= 0.6'} - /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - /mime-types/2.1.34: - resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.51.0 - /mime-types/2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} @@ -10641,8 +10561,8 @@ packages: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} dev: true - /nwsapi/2.2.1: - resolution: {integrity: sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==} + /nwsapi/2.2.2: + resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} dev: true /obj-props/1.4.0: @@ -10655,8 +10575,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /object-hash/2.2.0: - resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + /object-hash/3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} dev: false @@ -10714,25 +10634,25 @@ packages: /obuf/1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - /oidc-provider/7.11.3: - resolution: {integrity: sha512-lk90N1+tXi4JX2/kdukMlSxummBTY78AFCX8meHev8pcxbCBjQqJwW2oKaujnTGxYIuDeUYjNI9zh0ur+oFu8g==} - engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0} + /oidc-provider/7.13.0: + resolution: {integrity: sha512-S8Ar9XrU3Gwe4XQMAw3bdfmE8bse9DIcJ2UNgHXcS77XeQuGsuotwbMQ/3fBNes5MnWc+nEgJMjzx5TQgqIheA==} + engines: {node: 12 || 14 || 16 || 18} dependencies: - '@koa/cors': 3.1.0 + '@koa/cors': 3.4.3 cacheable-lookup: 6.0.4 debug: 4.3.4 - ejs: 3.1.6 - got: 11.8.3 - jose: 4.6.0 + ejs: 3.1.8 + got: 11.8.5 + jose: 4.10.4 jsesc: 3.0.2 koa: 2.13.4 koa-compose: 4.1.0 nanoid: 3.3.4 - object-hash: 2.2.0 + object-hash: 3.0.0 oidc-token-hash: 5.0.1 paseto: 2.1.3 quick-lru: 5.1.1 - raw-body: 2.4.3 + raw-body: 2.5.1 optionalDependencies: paseto3: /paseto/3.1.1 transitivePeerDependencies: @@ -11022,6 +10942,12 @@ packages: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} dev: true + /parse5/7.1.1: + resolution: {integrity: sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==} + dependencies: + entities: 4.4.0 + dev: true + /parseurl/1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -11507,18 +11433,17 @@ packages: react-is: 17.0.2 dev: true - /pretty-format/28.1.3: - resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /pretty-format/29.1.2: + resolution: {integrity: sha512-CGJ6VVGXVRP2o2Dorl4mAwwvDWT25luIsYhkyVQW32E4nL+TgW939J7LlKT/npq5Cpq6j3s+sy+13yk7xYpBmg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/schemas': 28.1.3 - ansi-regex: 5.0.1 + '@jest/schemas': 29.0.0 ansi-styles: 5.2.0 react-is: 18.2.0 dev: true - /pretty-format/29.1.2: - resolution: {integrity: sha512-CGJ6VVGXVRP2o2Dorl4mAwwvDWT25luIsYhkyVQW32E4nL+TgW939J7LlKT/npq5Cpq6j3s+sy+13yk7xYpBmg==} + /pretty-format/29.2.1: + resolution: {integrity: sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.0.0 @@ -11621,22 +11546,38 @@ packages: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} - /puppeteer/18.0.0: - resolution: {integrity: sha512-g87rnbVwRbIJF3J2399jq7bic2TSJXxv6ysaPRi61pnwHf7eVm97YO3fEIldgVKWO/nxND192YWfhaeKjkcfOg==} + /puppeteer-core/19.2.2: + resolution: {integrity: sha512-faojf+1pZ/tHXSr4x1q+9MVd9FrL3rpdbC0w7qN7MNClMoLuCvMbpR4vzcjoiJYgclt1n+SOPUOmHQViTw6frw==} engines: {node: '>=14.1.0'} - requiresBuild: true dependencies: cross-fetch: 3.1.5 debug: 4.3.4 - devtools-protocol: 0.0.1036444 + devtools-protocol: 0.0.1056733 extract-zip: 2.0.1 https-proxy-agent: 5.0.1 - progress: 2.0.3 proxy-from-env: 1.1.0 rimraf: 3.0.2 tar-fs: 2.1.1 unbzip2-stream: 1.4.3 - ws: 8.8.1 + ws: 8.10.0 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: true + + /puppeteer/19.2.2: + resolution: {integrity: sha512-m1T5Mog5qu5+dMBptWYTn6pXRdnFbydbVUCthqwbfd8/kOiMlzZBR9ywjX79LpvI1Sj+/z8+FKeIsjnMul8ZYA==} + engines: {node: '>=14.1.0'} + requiresBuild: true + dependencies: + cosmiconfig: 7.0.1 + devtools-protocol: 0.0.1056733 + https-proxy-agent: 5.0.1 + progress: 2.0.3 + proxy-from-env: 1.1.0 + puppeteer-core: 19.2.2 transitivePeerDependencies: - bufferutil - encoding @@ -11688,6 +11629,10 @@ packages: strict-uri-encode: 2.0.0 dev: false + /querystringify/2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -11716,6 +11661,16 @@ packages: unpipe: 1.0.0 dev: false + /raw-body/2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + /react-animate-height/3.0.4_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-k+mBS8yCzpFp+7BdrHsL5bXd6CO/2bYO2SvRGKfxK+Ss3nzplAJLlgnd6Zhcxe/avdpy/CgcziicFj7pIHgG5g==} engines: {node: '>= 12.0.0'} @@ -12252,8 +12207,7 @@ packages: dev: true /requires-port/1.0.0: - resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=} - dev: false + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} /resolve-alpn/1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -12431,9 +12385,9 @@ packages: source-map-js: 1.0.2 dev: true - /saxes/5.0.1: - resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} - engines: {node: '>=10'} + /saxes/6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} dependencies: xmlchars: 2.2.0 dev: true @@ -13456,13 +13410,14 @@ packages: nopt: 1.0.10 dev: true - /tough-cookie/4.0.0: - resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==} + /tough-cookie/4.1.2: + resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==} engines: {node: '>=6'} dependencies: psl: 1.9.0 punycode: 2.1.1 - universalify: 0.1.2 + universalify: 0.2.0 + url-parse: 1.5.10 dev: true /tr46/0.0.3: @@ -13532,7 +13487,7 @@ packages: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 jest: 29.1.2_k5ytkvaprncdyzidqqws5bqksq - jest-util: 29.1.2 + jest-util: 29.2.1 json5: 2.2.1 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -13561,7 +13516,7 @@ packages: '@tsconfig/node14': 1.0.1 '@tsconfig/node16': 1.0.2 '@types/node': 17.0.23 - acorn: 8.7.0 + acorn: 8.8.0 acorn-walk: 8.2.0 arg: 4.1.3 create-require: 1.1.1 @@ -13891,6 +13846,11 @@ packages: engines: {node: '>= 4.0.0'} dev: true + /universalify/0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + dev: true + /universalify/2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} @@ -13921,6 +13881,13 @@ packages: dependencies: punycode: 2.1.1 + /url-parse/1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + /use-debounced-loader/0.1.1_react@18.2.0: resolution: {integrity: sha512-FbY/ynor7wZV55v1EvvAvu8CvSoEKT1azS2zFb/aLlL0vySbqTM7x9fIcaOJN++E52mVINNDe2VmWWd+Q00S+A==} engines: {node: '>=10'} @@ -14025,12 +13992,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /w3c-hr-time/1.0.2: - resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} - dependencies: - browser-process-hrtime: 1.0.0 - dev: true - /w3c-xmlserializer/3.0.0: resolution: {integrity: sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==} engines: {node: '>=12'} @@ -14111,14 +14072,6 @@ packages: engines: {node: '>=12'} dev: true - /whatwg-url/10.0.0: - resolution: {integrity: sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==} - engines: {node: '>=12'} - dependencies: - tr46: 3.0.0 - webidl-conversions: 7.0.0 - dev: true - /whatwg-url/11.0.0: resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} engines: {node: '>=12'} @@ -14218,8 +14171,8 @@ packages: signal-exit: 3.0.7 dev: true - /ws/8.8.1: - resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==} + /ws/8.10.0: + resolution: {integrity: sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1