0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

Merge pull request #4863 from logto-io/gao-swagger-org-apis

refactor(core): add swagger data for org apis
This commit is contained in:
Gao Sun 2023-11-14 11:16:09 +08:00 committed by GitHub
commit 5de7772186
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 732 additions and 124 deletions

View file

@ -9,7 +9,7 @@ ENV PUPPETEER_SKIP_DOWNLOAD=true
### Install toolchain ### ### Install toolchain ###
RUN npm add --location=global pnpm@^8.0.0 RUN npm add --location=global pnpm@^8.0.0
# https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#node-gyp-alpine # https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#node-gyp-alpine
RUN apk add --no-cache python3 make g++ RUN apk add --no-cache python3 make g++ rsync
COPY . . COPY . .

View file

@ -12,12 +12,12 @@
}, },
"scripts": { "scripts": {
"precommit": "lint-staged", "precommit": "lint-staged",
"copyfiles": "copyfiles -u 1 src/routes/**/*.openapi.json build/", "copy:apidocs": "rsync -a -m --include '*/' --include '*.openapi.json' --exclude '*' src/routes/ build/routes/",
"build": "rm -rf build/ && tsc -p tsconfig.build.json && pnpm run copyfiles", "build": "rm -rf build/ && tsc -p tsconfig.build.json && pnpm run copy:apidocs",
"build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap && pnpm run copyfiles", "build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap && pnpm run copy:apidocs",
"lint": "eslint --ext .ts src", "lint": "eslint --ext .ts src",
"lint:report": "pnpm lint --format json --output-file report.json", "lint:report": "pnpm lint --format json --output-file report.json",
"dev": "rm -rf build/ && pnpm run copyfiles && nodemon", "dev": "rm -rf build/ && pnpm run copy:apidocs && nodemon",
"start": "NODE_ENV=production node .", "start": "NODE_ENV=production node .",
"test:only": "NODE_OPTIONS=\"--experimental-vm-modules --max_old_space_size=4096\" jest --logHeapUsage", "test:only": "NODE_OPTIONS=\"--experimental-vm-modules --max_old_space_size=4096\" jest --logHeapUsage",
"test": "pnpm build:test && pnpm test:only", "test": "pnpm build:test && pnpm test:only",
@ -80,8 +80,8 @@
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"redis": "^4.6.5", "redis": "^4.6.5",
"roarr": "^7.11.0", "roarr": "^7.11.0",
"semver": "^7.3.8",
"samlify": "2.8.10", "samlify": "2.8.10",
"semver": "^7.3.8",
"slonik": "^30.0.0", "slonik": "^30.0.0",
"slonik-interceptor-preset": "^1.2.10", "slonik-interceptor-preset": "^1.2.10",
"slonik-sql-tag-raw": "^1.1.4", "slonik-sql-tag-raw": "^1.1.4",
@ -109,7 +109,6 @@
"@types/semver": "^7.3.12", "@types/semver": "^7.3.12",
"@types/sinon": "^10.0.13", "@types/sinon": "^10.0.13",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"copyfiles": "^2.4.1",
"eslint": "^8.44.0", "eslint": "^8.44.0",
"jest": "^29.5.0", "jest": "^29.5.0",
"jest-matcher-specific-error": "^1.0.0", "jest-matcher-specific-error": "^1.0.0",

View file

@ -0,0 +1,291 @@
{
"tags": [{
"name": "Organizations",
"description": "Organization is a concept that brings together multiple identities (mostly users). Logto supports multiple organizations, and each organization can have multiple users.\n\nEvery organization shares the same set (organization template) of roles and permissions. Each user can have different roles in different organizations."
}],
"paths": {
"/api/organizations": {
"post": {
"summary": "Create a new organization",
"description": "Create a new organization with the given data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"description": "The name of the organization."
},
"description": {
"description": "The description of the organization."
}
}
}
}
}
},
"responses": {
"201": {
"description": "The organization was created successfully."
}
}
},
"get": {
"summary": "Get organizations",
"description": "Get organizations that match the given query with pagination.",
"parameters": [
{
"name": "q",
"in": "query",
"description": "The query to filter organizations. It can be a partial ID or name.\n\nIf not provided, all organizations will be returned."
},
{
"name": "showFeatured",
"in": "query",
"description": "Whether to show featured users in the organization. Featured users are randomly selected from the organization members.\n\nIf not provided, `featuredUsers` will not be included in the response."
}
],
"responses": {
"200": {
"description": "A list of organizations."
}
}
}
},
"/api/organizations/{id}": {
"get": {
"summary": "Get organization by ID",
"description": "Get organization details by ID.",
"responses": {
"200": {
"description": "Details of the organization."
}
}
},
"patch": {
"summary": "Update organization by ID",
"description": "Update organization details by ID with the given data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"description": "The updated name of the organization."
},
"description": {
"description": "The updated description of the organization."
}
}
}
}
}
},
"responses": {
"200": {
"description": "The organization was updated successfully."
}
}
},
"delete": {
"summary": "Delete organization by ID",
"description": "Delete organization by ID.",
"responses": {
"204": {
"description": "The organization was deleted successfully."
}
}
}
},
"/api/organizations/{id}/users": {
"post": {
"summary": "Add user members to organization",
"description": "Add users as members to the specified organization with the given user IDs.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"userIds": {
"description": "An array of user IDs to be added to the organization."
}
}
}
}
}
},
"responses": {
"201": {
"description": "Users were added to the organization successfully."
},
"422": {
"description": "At least one of the IDs provided is not valid. For example, the organization ID or user ID does not exist; or the user is already a member of the organization."
}
}
},
"put": {
"summary": "Replace organization user members",
"description": "Replace all user members for the specified organization with the given users. This effectively removing all existing user memberships in the organization and adding the new users as members.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"userIds": {
"description": "An array of user IDs to replace existing users."
}
}
}
}
}
},
"responses": {
"204": {
"description": "Successfully replaced all users for the organization."
},
"422": {
"description": "At least one of the IDs provided is not valid. For example, the organization ID or user ID does not exist."
}
}
},
"get": {
"summary": "Get organization user members",
"description": "Get users that are members of the specified organization for the given query with pagination.",
"parameters": [
{
"name": "q",
"in": "query",
"description": "The query to filter users. It will match multiple fields of users, including ID, name, username, email, and phone number.\n\nIf not provided, all users will be returned."
}
],
"responses": {
"200": {
"description": "A list of users that are members of the organization."
}
}
}
},
"/api/organizations/{id}/users/{userId}": {
"delete": {
"summary": "Remove user member from organization",
"description": "Remove a user's membership from the specified organization.",
"responses": {
"204": {
"description": "The user was removed from the organization members successfully."
},
"404": {
"description": "The user is not a member of the organization."
}
}
}
},
"/api/organizations/{id}/users/roles": {
"post": {
"summary": "Assign roles to organization user members",
"description": "Assign roles to user members of the specified organization with the given data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"userIds": {
"description": "An array of user IDs to assign roles."
},
"organizationRoleIds": {
"description": "An array of organization role IDs to assign."
}
}
}
}
}
},
"responses": {
"201": {
"description": "Roles were assigned to organization users successfully."
},
"422": {
"description": "At least one of the IDs provided is not valid. For example, the organization ID, user ID, or organization role ID does not exist; the user is not a member of the organization; or the user already has the role."
}
}
}
},
"/api/organizations/{id}/users/{userId}/roles": {
"get": {
"summary": "Get roles for a user in an organization",
"description": "Get roles assigned to a user in the specified organization with pagination.",
"responses": {
"200": {
"description": "A list of roles assigned to the user."
},
"422": {
"description": "The user is not a member of the organization."
}
}
},
"put": {
"summary": "Update roles for a user in an organization",
"description": "Update roles assigned to a user in the specified organization with the provided data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"organizationRoleIds": {
"description": "An array of organization role IDs to update for the user."
}
}
}
}
}
},
"responses": {
"204": {
"description": "Roles were updated for the user successfully."
},
"422": {
"description": "The user is not a member of the organization; or at least one of the IDs provided is not valid. For example, the organization ID or organization role ID does not exist."
}
}
},
"post": {
"summary": "Assign roles to a user in an organization",
"description": "Assign roles to a user in the specified organization with the provided data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"organizationRoleIds": {
"description": "An array of organization role IDs to assign to the user."
}
}
}
}
}
},
"responses": {
"201": {
"description": "Roles were assigned to the user successfully."
},
"422": {
"description": "The user is not a member of the organization; or at least one of the IDs provided is not valid. For example, the organization ID or organization role ID does not exist; or the user already has the role."
}
}
}
},
"/api/organizations/{id}/users/{userId}/roles/{roleId}": {
"delete": {
"summary": "Remove a role from a user in an organization",
"description": "Remove a role assignment from a user in the specified organization.",
"responses": {
"204": {
"description": "The role was removed from the user successfully."
},
"404": {
"description": "The user is not a member of the organization; or the user does not have the role."
}
}
}
}
}
}

View file

@ -0,0 +1,167 @@
{
"tags": [{
"name": "Organization roles",
"description": "Organization roles are used to define a set of organization scopes that can be assigned to users. Every organization role is a part of the organization template.\n\nOrganization roles will only be meaningful within an organization context. For example, a user may have an `admin` role for organization A, but not for organization B."
}],
"paths": {
"/api/organization-roles": {
"get": {
"summary": "Get organization roles",
"description": "Get organization roles with pagination.",
"responses": {
"200": {
"description": "A list of organization roles."
}
}
},
"post": {
"summary": "Create a new organization role",
"description": "Create a new organization role with the given data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"description": "The name of the organization role. It must be unique within the organization template."
},
"description": {
"description": "The description of the organization role."
}
}
}
}
}
},
"responses": {
"201": {
"description": "The organization role was created successfully."
},
"422": {
"description": "The organization role name is already in use."
}
}
}
},
"/api/organization-roles/{id}": {
"get": {
"summary": "Get organization role by ID",
"description": "Get organization role details by ID.",
"responses": {
"200": {
"description": "Details of the organization role."
}
}
},
"patch": {
"summary": "Update organization role by ID",
"description": "Update organization role details by ID with the given data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"description": "The updated name of the organization role. It must be unique within the organization template."
},
"description": {
"description": "The updated description of the organization role."
}
}
}
}
}
},
"responses": {
"200": {
"description": "The organization role was updated successfully."
},
"422": {
"description": "The organization role name is already in use."
}
}
},
"delete": {
"summary": "Delete organization role by ID",
"description": "Delete organization role by ID.",
"responses": {
"204": {
"description": "The organization role was deleted successfully."
}
}
}
},
"/api/organization-roles/{id}/scopes": {
"get": {
"summary": "Get organization role scopes",
"description": "Get all organization scopes that are assigned to the specified organization role.",
"responses": {
"200": {
"description": "A list of organization scopes."
}
}
},
"post": {
"summary": "Assign organization scopes to organization role",
"description": "Assign organization scopes to the specified organization role",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"organizationScopeIds": {
"description": "An array of organization scope IDs to be assigned."
}
}
}
}
}
},
"responses": {
"201": {
"description": "Organization scopes were assigned successfully."
},
"422": {
"description": "At least one of the IDs provided is invalid. For example, the organization scope ID does not exist; or the organization scope has already been assigned to the organization role."
}
}
},
"put": {
"summary": "Replace organization scopes for organization role",
"description": "Replace all organization scopes that are assigned to the specified organization role with the given organization scopes. This effectively removes all existing organization scope assignments and replaces them with the new ones.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"organizationScopeIds": {
"description": "An array of organization scope IDs to replace existing scopes."
}
}
}
}
}
},
"responses": {
"204": {
"description": "Organization scopes were replaced successfully."
},
"422": {
"description": "At least one of the IDs provided is invalid. For example, the organization scope ID does not exist."
}
}
}
},
"/api/organization-roles/{id}/scopes/{organizationScopeId}": {
"delete": {
"summary": "Remove organization scope",
"description": "Remove a organization scope assignment from the specified organization role.",
"responses": {
"204": {
"description": "Organization scope assignment was removed successfully."
}
}
}
}
}
}

View file

@ -0,0 +1,95 @@
{
"tags": [{
"name": "Organization scopes",
"description": "Organization scopes (permissions) are used to define actions that can be performed on a organization. Every organization scope is a part of the organization template.\n\nOrganization scopes will only be meaningful within an organization context. For example, a user may have a `read` scope for organization A, but not for organization B."
}],
"paths": {
"/api/organization-scopes": {
"get": {
"summary": "Get organization scopes",
"description": "Get organization scopes that match with pagination.",
"responses": {
"200": {
"description": "A list of organization scopes."
}
}
},
"post": {
"summary": "Create a new organization scope",
"description": "Create a new organization scope with the given data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"description": "The name of the organization scope. It must be unique within the organization template."
},
"description": {
"description": "The description of the organization scope."
}
}
}
}
}
},
"responses": {
"201": {
"description": "The organization scope was created successfully."
},
"422": {
"description": "The organization scope name is already in use."
}
}
}
},
"/api/organization-scopes/{id}": {
"get": {
"summary": "Get organization scope by ID",
"description": "Get organization scope details by ID.",
"responses": {
"200": {
"description": "The organization scope data for the given ID."
}
}
},
"patch": {
"summary": "Update organization scope by ID",
"description": "Update organization scope details by ID with the given data.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"name": {
"description": "The updated name of the organization scope. It must be unique within the organization template."
},
"description": {
"description": "The updated description of the organization scope."
}
}
}
}
}
},
"responses": {
"200": {
"description": "The organization scope was updated successfully."
},
"422": {
"description": "The organization scope name is already in use."
}
}
},
"delete": {
"summary": "Delete organization scope by ID",
"description": "Delete organization scope by ID.",
"responses": {
"204": {
"description": "The organization scope was deleted successfully."
}
}
}
}
}
}

View file

@ -153,7 +153,7 @@ export default class SchemaRouter<
koaGuard({ koaGuard({
body: schema.createGuard.omit({ id: true }), body: schema.createGuard.omit({ id: true }),
response: schema.guard, response: schema.guard,
status: [201], status: [201], // TODO: 409/422 for conflict?
}), }),
async (ctx, next) => { async (ctx, next) => {
// eslint-disable-next-line no-restricted-syntax -- `.omit()` doesn't play well with generics // eslint-disable-next-line no-restricted-syntax -- `.omit()` doesn't play well with generics
@ -189,7 +189,7 @@ export default class SchemaRouter<
params: z.object({ id: z.string().min(1) }), params: z.object({ id: z.string().min(1) }),
body: schema.updateGuard, body: schema.updateGuard,
response: schema.guard, response: schema.guard,
status: [200, 404], status: [200, 404], // TODO: 409/422 for conflict?
}), }),
async (ctx, next) => { async (ctx, next) => {
ctx.body = await queries.updateById(ctx.guard.params.id, ctx.guard.body); ctx.body = await queries.updateById(ctx.guard.params.id, ctx.guard.body);

View file

@ -91,6 +91,26 @@ describe('organization role APIs', () => {
}); });
}); });
it('should fail when try to update an organization role with a name that already exists', async () => {
const [role1, role2] = await Promise.all([
roleApi.create({ name: 'test' + randomId() }),
roleApi.create({ name: 'test' + randomId() }),
]);
const response = await roleApi
.update(role1.id, {
name: role2.name,
})
.catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject(
expect.objectContaining({
code: 'entity.unique_integrity_violation',
})
);
});
it('should be able to delete organization role', async () => { it('should be able to delete organization role', async () => {
const createdRole = await roleApi.create({ name: 'test' + randomId() }); const createdRole = await roleApi.create({ name: 'test' + randomId() });
await roleApi.delete(createdRole.id); await roleApi.delete(createdRole.id);

View file

@ -87,6 +87,26 @@ describe('organization scope APIs', () => {
}); });
}); });
it('should fail when try to update an organization scope with a name that already exists', async () => {
const [scope1, scope2] = await Promise.all([
scopeApi.create({ name: 'test' + randomId() }),
scopeApi.create({ name: 'test' + randomId() }),
]);
const response = await scopeApi
.update(scope2.id, {
name: scope1.name,
})
.catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject(
expect.objectContaining({
code: 'entity.unique_integrity_violation',
})
);
});
it('should be able to delete organization scope', async () => { it('should be able to delete organization scope', async () => {
const createdScope = await scopeApi.create({ name: 'test' + randomId() }); const createdScope = await scopeApi.create({ name: 'test' + randomId() });
await scopeApi.delete(createdScope.id); await scopeApi.delete(createdScope.id);

View file

@ -3371,15 +3371,12 @@ importers:
'@types/supertest': '@types/supertest':
specifier: ^2.0.11 specifier: ^2.0.11
version: 2.0.11 version: 2.0.11
copyfiles:
specifier: ^2.4.1
version: 2.4.1
eslint: eslint:
specifier: ^8.44.0 specifier: ^8.44.0
version: 8.44.0 version: 8.44.0
jest: jest:
specifier: ^29.5.0 specifier: ^29.5.0
version: 29.5.0(@types/node@18.11.18)(ts-node@10.9.1) version: 29.5.0(@types/node@18.11.18)
jest-matcher-specific-error: jest-matcher-specific-error:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0 version: 1.0.0
@ -7037,6 +7034,48 @@ packages:
slash: 3.0.0 slash: 3.0.0
dev: true dev: true
/@jest/core@29.5.0:
resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
'@jest/console': 29.5.0
'@jest/reporters': 29.5.0
'@jest/test-result': 29.5.0
'@jest/transform': 29.5.0
'@jest/types': 29.5.0
'@types/node': 18.11.18
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.8.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.5.0
jest-config: 29.5.0(@types/node@18.11.18)
jest-haste-map: 29.5.0
jest-message-util: 29.5.0
jest-regex-util: 29.4.3
jest-resolve: 29.5.0
jest-resolve-dependencies: 29.5.0
jest-runner: 29.5.0
jest-runtime: 29.5.0
jest-snapshot: 29.5.0
jest-util: 29.5.0
jest-validate: 29.5.0
jest-watcher: 29.5.0
micromatch: 4.0.5
pretty-format: 29.5.0
slash: 3.0.0
strip-ansi: 6.0.1
transitivePeerDependencies:
- supports-color
- ts-node
dev: true
/@jest/core@29.5.0(ts-node@10.9.1): /@jest/core@29.5.0(ts-node@10.9.1):
resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==} resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -11247,14 +11286,6 @@ packages:
strip-ansi: 6.0.1 strip-ansi: 6.0.1
wrap-ansi: 6.2.0 wrap-ansi: 6.2.0
/cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
dev: true
/cliui@8.0.1: /cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -11488,28 +11519,11 @@ packages:
depd: 2.0.0 depd: 2.0.0
keygrip: 1.1.0 keygrip: 1.1.0
/copyfiles@2.4.1:
resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==}
hasBin: true
dependencies:
glob: 7.2.0
minimatch: 3.1.2
mkdirp: 1.0.4
noms: 0.0.0
through2: 2.0.5
untildify: 4.0.0
yargs: 16.2.0
dev: true
/core-js@3.21.1: /core-js@3.21.1:
resolution: {integrity: sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==} resolution: {integrity: sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==}
requiresBuild: true requiresBuild: true
dev: false dev: false
/core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true
/cosmiconfig-typescript-loader@4.3.0(@types/node@18.11.18)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.0.2): /cosmiconfig-typescript-loader@4.3.0(@types/node@18.11.18)(cosmiconfig@8.2.0)(ts-node@10.9.1)(typescript@5.0.2):
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'} engines: {node: '>=12', npm: '>=6'}
@ -13491,17 +13505,6 @@ packages:
is-glob: 4.0.3 is-glob: 4.0.3
dev: true dev: true
/glob@7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/glob@7.2.3: /glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies: dependencies:
@ -14589,10 +14592,6 @@ packages:
/isarray@0.0.1: /isarray@0.0.1:
resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
/isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
dev: true
/isexe@2.0.0: /isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true dev: true
@ -14682,6 +14681,34 @@ packages:
- supports-color - supports-color
dev: true dev: true
/jest-cli@29.5.0(@types/node@18.11.18):
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
'@jest/core': 29.5.0
'@jest/test-result': 29.5.0
'@jest/types': 29.5.0
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
import-local: 3.1.0
jest-config: 29.5.0(@types/node@18.11.18)
jest-util: 29.5.0
jest-validate: 29.5.0
prompts: 2.4.2
yargs: 17.7.2
transitivePeerDependencies:
- '@types/node'
- supports-color
- ts-node
dev: true
/jest-cli@29.5.0(@types/node@18.11.18)(ts-node@10.9.1): /jest-cli@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -14710,6 +14737,45 @@ packages:
- ts-node - ts-node
dev: true dev: true
/jest-config@29.5.0(@types/node@18.11.18):
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
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.20.2
'@jest/test-sequencer': 29.5.0
'@jest/types': 29.5.0
'@types/node': 18.11.18
babel-jest: 29.5.0(@babel/core@7.20.2)
chalk: 4.1.2
ci-info: 3.8.0
deepmerge: 4.3.1
glob: 7.2.3
graceful-fs: 4.2.11
jest-circus: 29.5.0
jest-environment-node: 29.5.0
jest-get-type: 29.4.3
jest-regex-util: 29.4.3
jest-resolve: 29.5.0
jest-runner: 29.5.0
jest-util: 29.5.0
jest-validate: 29.5.0
micromatch: 4.0.5
parse-json: 5.2.0
pretty-format: 29.5.0
slash: 3.0.0
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
dev: true
/jest-config@29.5.0(@types/node@18.11.18)(ts-node@10.9.1): /jest-config@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -15121,6 +15187,26 @@ packages:
supports-color: 8.1.1 supports-color: 8.1.1
dev: true dev: true
/jest@29.5.0(@types/node@18.11.18):
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
node-notifier:
optional: true
dependencies:
'@jest/core': 29.5.0
'@jest/types': 29.5.0
import-local: 3.1.0
jest-cli: 29.5.0(@types/node@18.11.18)
transitivePeerDependencies:
- '@types/node'
- supports-color
- ts-node
dev: true
/jest@29.5.0(@types/node@18.11.18)(ts-node@10.9.1): /jest@29.5.0(@types/node@18.11.18)(ts-node@10.9.1):
resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -16622,6 +16708,7 @@ packages:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
dev: false
/module-details-from-path@1.0.3: /module-details-from-path@1.0.3:
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
@ -16858,13 +16945,6 @@ packages:
undefsafe: 2.0.5 undefsafe: 2.0.5
dev: true dev: true
/noms@0.0.0:
resolution: {integrity: sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=}
dependencies:
inherits: 2.0.4
readable-stream: 1.0.34
dev: true
/nopt@1.0.10: /nopt@1.0.10:
resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==}
hasBin: true hasBin: true
@ -17857,10 +17937,6 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true
/process@0.11.10: /process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'} engines: {node: '>= 0.6.0'}
@ -18492,27 +18568,6 @@ packages:
strip-bom: 3.0.0 strip-bom: 3.0.0
dev: true dev: true
/readable-stream@1.0.34:
resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==}
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 0.0.1
string_decoder: 0.10.31
dev: true
/readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 1.0.0
process-nextick-args: 2.0.1
safe-buffer: 5.1.2
string_decoder: 1.1.1
util-deprecate: 1.0.2
dev: true
/readable-stream@3.6.2: /readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -18934,10 +18989,6 @@ packages:
dependencies: dependencies:
tslib: 2.5.0 tslib: 2.5.0
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: true
/safe-buffer@5.2.1: /safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@ -19584,16 +19635,6 @@ packages:
es-abstract: 1.20.4 es-abstract: 1.20.4
dev: true dev: true
/string_decoder@0.10.31:
resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
dev: true
/string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
dependencies:
safe-buffer: 5.1.2
dev: true
/string_decoder@1.3.0: /string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies: dependencies:
@ -20131,13 +20172,6 @@ packages:
engines: {node: '>=0.2.6'} engines: {node: '>=0.2.6'}
dev: false dev: false
/through2@2.0.5:
resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
dependencies:
readable-stream: 2.3.8
xtend: 4.0.2
dev: true
/through2@3.0.2: /through2@3.0.2:
resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==}
dependencies: dependencies:
@ -20599,11 +20633,6 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
dev: true
/update-browserslist-db@1.0.10(browserslist@4.21.4): /update-browserslist-db@1.0.10(browserslist@4.21.4):
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
hasBin: true hasBin: true
@ -21044,19 +21073,6 @@ packages:
y18n: 4.0.3 y18n: 4.0.3
yargs-parser: 18.1.3 yargs-parser: 18.1.3
/yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
dependencies:
cliui: 7.0.4
escalade: 3.1.1
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 20.2.9
dev: true
/yargs@17.6.0: /yargs@17.6.0:
resolution: {integrity: sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==} resolution: {integrity: sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==}
engines: {node: '>=12'} engines: {node: '>=12'}