Compare commits

..

2 Commits

Author SHA1 Message Date
nivedin
fa42fc1538 fix: update spelling 2024-03-20 15:25:34 +05:30
nivedin
0f4168d12c feat: add github enterprise auth option 2024-03-20 14:44:44 +05:30
61 changed files with 440 additions and 1494 deletions

View File

@@ -1,14 +1,40 @@
# THIS IS NOT TO BE USED FOR PERSONAL DEPLOYMENTS! # Docker Compose config used for internal test and QA deployments
# Internal Docker Compose Image used for internal testing deployments # This just spins up the AIO container along with an attached DB to the standard HTTP ports with subpath access mode
# TODO: Add Healthcheck for the AIO container
version: "3.7" version: "3.7"
services: services:
# The service that spins up all 3 services at once in one container
hoppscotch-aio:
container_name: hoppscotch-aio
restart: unless-stopped
build:
dockerfile: prod.Dockerfile
context: .
target: aio
environment:
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch
- ENABLE_SUBPATH_BASED_ACCESS=true
depends_on:
hoppscotch-db:
condition: service_healthy
ports:
- "3080:80"
# The preset DB service, you can delete/comment the below lines if
# you are using an external postgres instance
# This will be exposed at port 5432
hoppscotch-db: hoppscotch-db:
image: postgres:15 image: postgres:15
ports:
- "5432:5432"
user: postgres user: postgres
environment: environment:
# The default user defined by the docker image
POSTGRES_USER: postgres POSTGRES_USER: postgres
# NOTE: Please UPDATE THIS PASSWORD!
POSTGRES_PASSWORD: testpass POSTGRES_PASSWORD: testpass
POSTGRES_DB: hoppscotch POSTGRES_DB: hoppscotch
healthcheck: healthcheck:
@@ -20,29 +46,3 @@ services:
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 10 retries: 10
hoppscotch-aio:
container_name: hoppscotch-aio
build:
dockerfile: prod.Dockerfile
context: .
target: aio
environment:
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch
- ENABLE_SUBPATH_BASED_ACCESS=true
env_file:
- ./.env
depends_on:
hoppscotch-db:
condition: service_healthy
command: ["sh", "-c", "pnpm exec prisma migrate deploy && node /usr/src/app/aio_run.mjs"]
healthcheck:
test:
- CMD
- curl
- '-f'
- 'http://localhost:80'
interval: 2s
timeout: 10s
retries: 30

View File

@@ -9,10 +9,6 @@ curlCheck() {
fi fi
} }
if [ "$ENABLE_SUBPATH_BASED_ACCESS" = "true" ]; then curlCheck "http://localhost:3000"
curlCheck "http://localhost:80/backend/ping" curlCheck "http://localhost:3100"
else curlCheck "http://localhost:3170/ping"
curlCheck "http://localhost:3000"
curlCheck "http://localhost:3100"
curlCheck "http://localhost:3170/ping"
fi

View File

@@ -84,12 +84,6 @@ export const USER_ALREADY_INVITED = 'admin/user_already_invited' as const;
*/ */
export const USER_UPDATE_FAILED = 'user/update_failed' as const; export const USER_UPDATE_FAILED = 'user/update_failed' as const;
/**
* User display name validation failure
* (UserService)
*/
export const USER_SHORT_DISPLAY_NAME = 'user/short_display_name' as const;
/** /**
* User deletion failure * User deletion failure
* (UserService) * (UserService)
@@ -756,8 +750,3 @@ export const DATABASE_TABLE_NOT_EXIST =
* (InfraConfigService) * (InfraConfigService)
*/ */
export const POSTHOG_CLIENT_NOT_INITIALIZED = 'posthog/client_not_initialized'; export const POSTHOG_CLIENT_NOT_INITIALIZED = 'posthog/client_not_initialized';
/**
* Inputs supplied are invalid
*/
export const INVALID_PARAMS = 'invalid_parameters' as const;

View File

@@ -1,11 +1,4 @@
import { import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
Controller,
Get,
HttpStatus,
Param,
Query,
UseGuards,
} from '@nestjs/common';
import { TeamCollectionService } from './team-collection.service'; import { TeamCollectionService } from './team-collection.service';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard'; import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
@@ -14,8 +7,6 @@ import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorat
import { TeamMemberRole } from '@prisma/client'; import { TeamMemberRole } from '@prisma/client';
import { RESTTeamMemberGuard } from 'src/team/guards/rest-team-member.guard'; import { RESTTeamMemberGuard } from 'src/team/guards/rest-team-member.guard';
import { throwHTTPErr } from 'src/utils'; import { throwHTTPErr } from 'src/utils';
import { RESTError } from 'src/types/RESTError';
import { INVALID_PARAMS } from 'src/errors';
@UseGuards(ThrottlerBehindProxyGuard) @UseGuards(ThrottlerBehindProxyGuard)
@Controller({ path: 'team-collection', version: '1' }) @Controller({ path: 'team-collection', version: '1' })
@@ -35,15 +26,8 @@ export class TeamCollectionController {
@Query('take') take: string, @Query('take') take: string,
@Query('skip') skip: string, @Query('skip') skip: string,
) { ) {
if (!teamID || !searchQuery) {
return <RESTError>{
message: INVALID_PARAMS,
statusCode: HttpStatus.BAD_REQUEST,
};
}
const res = await this.teamCollectionService.searchByTitle( const res = await this.teamCollectionService.searchByTitle(
searchQuery.trim(), searchQuery,
teamID, teamID,
parseInt(take), parseInt(take),
parseInt(skip), parseInt(skip),

View File

@@ -58,29 +58,6 @@ export class UserResolver {
if (E.isLeft(updatedUser)) throwErr(updatedUser.left); if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
return updatedUser.right; return updatedUser.right;
} }
@Mutation(() => User, {
description: 'Update a users display name',
})
@UseGuards(GqlAuthGuard)
async updateDisplayName(
@GqlUser() user: AuthUser,
@Args({
name: 'updatedDisplayName',
description: 'New name of user',
type: () => String,
})
updatedDisplayName: string,
) {
const updatedUser = await this.userService.updateUserDisplayName(
user.uid,
updatedDisplayName,
);
if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
return updatedUser.right;
}
@Mutation(() => Boolean, { @Mutation(() => Boolean, {
description: 'Delete an user account', description: 'Delete an user account',
}) })

View File

@@ -1,9 +1,4 @@
import { import { JSON_INVALID, USERS_NOT_FOUND, USER_NOT_FOUND } from 'src/errors';
JSON_INVALID,
USERS_NOT_FOUND,
USER_NOT_FOUND,
USER_SHORT_DISPLAY_NAME,
} from 'src/errors';
import { mockDeep, mockReset } from 'jest-mock-extended'; import { mockDeep, mockReset } from 'jest-mock-extended';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
@@ -485,14 +480,6 @@ describe('UserService', () => {
); );
expect(result).toEqualLeft(USER_NOT_FOUND); expect(result).toEqualLeft(USER_NOT_FOUND);
}); });
test('should resolve left and error when short display name is passed', async () => {
const newDisplayName = '';
const result = await userService.updateUserDisplayName(
user.uid,
newDisplayName,
);
expect(result).toEqualLeft(USER_SHORT_DISPLAY_NAME);
});
}); });
describe('fetchAllUsers', () => { describe('fetchAllUsers', () => {

View File

@@ -8,11 +8,7 @@ import * as T from 'fp-ts/Task';
import * as A from 'fp-ts/Array'; import * as A from 'fp-ts/Array';
import { pipe, constVoid } from 'fp-ts/function'; import { pipe, constVoid } from 'fp-ts/function';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
import { import { USERS_NOT_FOUND, USER_NOT_FOUND } from 'src/errors';
USERS_NOT_FOUND,
USER_NOT_FOUND,
USER_SHORT_DISPLAY_NAME,
} from 'src/errors';
import { SessionType, User } from './user.model'; import { SessionType, User } from './user.model';
import { USER_UPDATE_FAILED } from 'src/errors'; import { USER_UPDATE_FAILED } from 'src/errors';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
@@ -295,10 +291,6 @@ export class UserService {
* @returns a Either of User or error * @returns a Either of User or error
*/ */
async updateUserDisplayName(userUID: string, displayName: string) { async updateUserDisplayName(userUID: string, displayName: string) {
if (!displayName || displayName.length === 0) {
return E.left(USER_SHORT_DISPLAY_NAME);
}
try { try {
const dbUpdatedUser = await this.prisma.user.update({ const dbUpdatedUser = await this.prisma.user.update({
where: { uid: userUID }, where: { uid: userUID },

View File

@@ -20,7 +20,7 @@ describe("Test `hopp test <file>` command:", () => {
const out = getErrorCode(stderr); const out = getErrorCode(stderr);
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT"); expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
}); });
}); })
describe("Supplied collection export file validations", () => { describe("Supplied collection export file validations", () => {
test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => { test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => {
@@ -66,43 +66,6 @@ describe("Test `hopp test <file>` command:", () => {
}); });
}); });
describe("Versioned entities", () => {
describe("Collections & Requests", () => {
const testFixtures = [
{ fileName: "coll-v1-req-v0.json", collVersion: 1, reqVersion: 0 },
{ fileName: "coll-v1-req-v1.json", collVersion: 1, reqVersion: 1 },
{ fileName: "coll-v2-req-v2.json", collVersion: 2, reqVersion: 2 },
{ fileName: "coll-v2-req-v3.json", collVersion: 2, reqVersion: 3 },
];
testFixtures.forEach(({ collVersion, fileName, reqVersion }) => {
test(`Successfully processes a supplied collection export file where the collection is based on the "v${collVersion}" schema and the request following the "v${reqVersion}" schema`, async () => {
const args = `test ${getTestJsonFilePath(fileName, "collection")}`;
const { error } = await runCLI(args);
expect(error).toBeNull();
});
});
});
describe("Environments", () => {
const testFixtures = [
{ fileName: "env-v0.json", version: 0 },
{ fileName: "env-v1.json", version: 1 },
];
testFixtures.forEach(({ fileName, version }) => {
test(`Successfully processes the supplied collection and environment export files where the environment is based on the "v${version}" schema`, async () => {
const ENV_PATH = getTestJsonFilePath(fileName, "environment");
const args = `test ${getTestJsonFilePath("sample-coll.json", "collection")} --env ${ENV_PATH}`;
const { error } = await runCLI(args);
expect(error).toBeNull();
});
});
});
});
test("Successfully processes a supplied collection export file of the expected format", async () => { test("Successfully processes a supplied collection export file of the expected format", async () => {
const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`; const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -112,8 +75,7 @@ describe("Test `hopp test <file>` command:", () => {
test("Successfully inherits headers and authorization set at the root collection", async () => { test("Successfully inherits headers and authorization set at the root collection", async () => {
const args = `test ${getTestJsonFilePath( const args = `test ${getTestJsonFilePath(
"collection-level-headers-auth-coll.json", "collection-level-headers-auth-coll.json", "collection"
"collection"
)}`; )}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -122,8 +84,7 @@ describe("Test `hopp test <file>` command:", () => {
test("Persists environment variables set in the pre-request script for consumption in the test script", async () => { test("Persists environment variables set in the pre-request script for consumption in the test script", async () => {
const args = `test ${getTestJsonFilePath( const args = `test ${getTestJsonFilePath(
"pre-req-script-env-var-persistence-coll.json", "pre-req-script-env-var-persistence-coll.json", "collection"
"collection"
)}`; )}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -145,8 +106,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => { test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => {
const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath( const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath(
"notjson-coll.txt", "notjson-coll.txt", "collection"
"collection"
)}`; )}`;
const { stderr } = await runCLI(args); const { stderr } = await runCLI(args);
@@ -163,10 +123,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => { test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => {
const ENV_PATH = getTestJsonFilePath( const ENV_PATH = getTestJsonFilePath("malformed-envs.json", "environment");
"malformed-envs.json",
"environment"
);
const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`; const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
const { stderr } = await runCLI(args); const { stderr } = await runCLI(args);
@@ -185,10 +142,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Successfully resolves values from the supplied environment export file", async () => { test("Successfully resolves values from the supplied environment export file", async () => {
const TESTS_PATH = getTestJsonFilePath( const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
"env-flag-tests-coll.json",
"collection"
);
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
const args = `test ${TESTS_PATH} --env ${ENV_PATH}`; const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
@@ -197,14 +151,8 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Successfully resolves environment variables referenced in the request body", async () => { test("Successfully resolves environment variables referenced in the request body", async () => {
const COLL_PATH = getTestJsonFilePath( const COLL_PATH = getTestJsonFilePath("req-body-env-vars-coll.json", "collection");
"req-body-env-vars-coll.json", const ENVS_PATH = getTestJsonFilePath("req-body-env-vars-envs.json", "environment");
"collection"
);
const ENVS_PATH = getTestJsonFilePath(
"req-body-env-vars-envs.json",
"environment"
);
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
const { error } = await runCLI(args); const { error } = await runCLI(args);
@@ -212,10 +160,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
test("Works with shorth `-e` flag", async () => { test("Works with shorth `-e` flag", async () => {
const TESTS_PATH = getTestJsonFilePath( const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
"env-flag-tests-coll.json",
"collection"
);
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment"); const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
const args = `test ${TESTS_PATH} -e ${ENV_PATH}`; const args = `test ${TESTS_PATH} -e ${ENV_PATH}`;
@@ -238,10 +183,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
secretHeaderValue: "secret-header-value", secretHeaderValue: "secret-header-value",
}; };
const COLL_PATH = getTestJsonFilePath( const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
"secret-envs-coll.json",
"collection"
);
const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment"); const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment");
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
@@ -255,14 +197,8 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
// Prefers values specified in the environment export file over values set in the system environment // Prefers values specified in the environment export file over values set in the system environment
test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => { test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => {
const COLL_PATH = getTestJsonFilePath( const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
"secret-envs-coll.json", const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
"collection"
);
const ENVS_PATH = getTestJsonFilePath(
"secret-supplied-values-envs.json",
"environment"
);
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
const { error, stdout } = await runCLI(args); const { error, stdout } = await runCLI(args);
@@ -276,13 +212,9 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
// Values set from the scripting context takes the highest precedence // Values set from the scripting context takes the highest precedence
test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => { test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => {
const COLL_PATH = getTestJsonFilePath( const COLL_PATH = getTestJsonFilePath(
"secret-envs-persistence-coll.json", "secret-envs-persistence-coll.json", "collection"
"collection"
);
const ENVS_PATH = getTestJsonFilePath(
"secret-supplied-values-envs.json",
"environment"
); );
const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
const { error, stdout } = await runCLI(args); const { error, stdout } = await runCLI(args);
@@ -295,12 +227,10 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => { test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => {
const COLL_PATH = getTestJsonFilePath( const COLL_PATH = getTestJsonFilePath(
"secret-envs-persistence-scripting-coll.json", "secret-envs-persistence-scripting-coll.json", "collection"
"collection"
); );
const ENVS_PATH = getTestJsonFilePath( const ENVS_PATH = getTestJsonFilePath(
"secret-envs-persistence-scripting-envs.json", "secret-envs-persistence-scripting-envs.json", "environment"
"environment"
); );
const args = `test ${COLL_PATH} --env ${ENVS_PATH}`; const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;

View File

@@ -0,0 +1,84 @@
import { isRESTCollection } from "../../../utils/checks";
describe("isRESTCollection", () => {
test("Undefined collection value.", () => {
expect(isRESTCollection(undefined)).toBeFalsy();
});
test("Invalid id value.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: 1,
})
).toBeFalsy();
});
test("Invalid requests value.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: null,
})
).toBeFalsy();
});
test("Invalid folders value.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [],
folders: undefined,
})
).toBeFalsy();
});
test("Invalid RESTCollection(s) in folders.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [],
folders: [
{
v: 1,
name: "test1",
id: "2",
requests: undefined,
folders: [],
},
],
})
).toBeFalsy();
});
test("Invalid HoppRESTRequest(s) in requests.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [{}],
folders: [],
})
).toBeFalsy();
});
test("Valid RESTCollection.", () => {
expect(
isRESTCollection({
v: 1,
name: "test",
id: "1",
requests: [],
folders: [],
})
).toBeTruthy();
});
});

View File

@@ -1,27 +0,0 @@
{
"v": 1,
"name": "coll-v1",
"folders": [],
"requests": [
{
"url": "https://httpbin.org",
"path": "/get",
"headers": [
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false },
{ "key": "Authorization", "value": "Bearer token123", "active": true }
],
"params": [
{ "key": "key", "value": "value", "active": true },
{ "key": "inactive-key", "value": "inactive-param", "active": false }
],
"name": "req-v0",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"contentType": "application/json",
"body": "",
"auth": "Bearer Token",
"bearerToken": "token123"
}
]
}

View File

@@ -1,48 +0,0 @@
{
"v": 1,
"name": "coll-v1",
"folders": [],
"requests": [
{
"v": "1",
"endpoint": "https://httpbin.org/get",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v1",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
}
}
]
}

View File

@@ -1,54 +0,0 @@
{
"v": 2,
"name": "coll-v2",
"folders": [],
"requests": [
{
"v": "2",
"endpoint": "https://httpbin.org/get",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v2",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}

View File

@@ -1,54 +0,0 @@
{
"v": 2,
"name": "coll-v2",
"folders": [],
"requests": [
{
"v": "3",
"endpoint": "https://httpbin.org/get",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v3",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}

View File

@@ -1,18 +1,18 @@
[ [
{ {
"v": 2, "v": 1,
"name": "CollectionA", "name": "CollectionA",
"folders": [ "folders": [
{ {
"v": 2, "v": 1,
"name": "FolderA", "name": "FolderA",
"folders": [ "folders": [
{ {
"v": 2, "v": 1,
"name": "FolderB", "name": "FolderB",
"folders": [ "folders": [
{ {
"v": 2, "v": 1,
"name": "FolderC", "name": "FolderC",
"folders": [], "folders": [],
"requests": [ "requests": [
@@ -153,11 +153,11 @@
} }
}, },
{ {
"v": 2, "v": 1,
"name": "CollectionB", "name": "CollectionB",
"folders": [ "folders": [
{ {
"v": 2, "v": 1,
"name": "FolderA", "name": "FolderA",
"folders": [], "folders": [],
"requests": [ "requests": [

View File

@@ -1,26 +0,0 @@
{
"v": 1,
"name": "tests",
"folders": [],
"requests": [
{
"v": "2",
"endpoint": "<<baseURL>>",
"name": "",
"params": [],
"headers": [],
"method": "GET",
"auth": {
"authType": "none",
"authActive": true
},
"preRequestScript": "",
"testScript": "// Check status code is 200\npw.test(\"Status code is 200\", ()=> {\n pw.expect(pw.response.status).toBe(200);\n});\n\n// Check JSON response property\npw.test(\"Check JSON response property\", ()=> {\n pw.expect(pw.response.body.method).toBe(\"GET\");\n pw.expect(pw.response.body.headers).toBeType(\"object\");\n});",
"body": {
"contentType": null,
"body": null
},
"requestVariables": []
}
]
}

View File

@@ -1,9 +0,0 @@
{
"name": "env-v0",
"variables": [
{
"key": "baseURL",
"value": "https://echo.hoppscotch.io"
}
]
}

View File

@@ -1,10 +0,0 @@
{
"name": "env-v0",
"variables": [
{
"key": "baseURL",
"value": "https://echo.hoppscotch.io",
"secret": false
}
]
}

View File

@@ -47,7 +47,7 @@ export async function parseEnvsData(path: string) {
if (HoppEnvKeyPairResult.success) { if (HoppEnvKeyPairResult.success) {
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) { for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) {
envPairs.push({ key, value, secret: false }); envPairs.push({ key, value });
} }
} else if (HoppEnvExportObjectResult.type === "ok") { } else if (HoppEnvExportObjectResult.type === "ok") {
envPairs.push(...HoppEnvExportObjectResult.value.variables); envPairs.push(...HoppEnvExportObjectResult.value.variables);

View File

@@ -1,3 +1,5 @@
import { HoppCollection, isHoppRESTRequest } from "@hoppscotch/data";
import * as A from "fp-ts/Array";
import { CommanderError } from "commander"; import { CommanderError } from "commander";
import { HoppCLIError, HoppErrnoException } from "../types/errors"; import { HoppCLIError, HoppErrnoException } from "../types/errors";
@@ -12,6 +14,48 @@ export const hasProperty = <P extends PropertyKey>(
prop: P prop: P
): target is Record<P, unknown> => prop in target; ): target is Record<P, unknown> => prop in target;
/**
* Typeguard to check valid Hoppscotch REST Collection.
* @param param The object to be checked.
* @returns True, if unknown parameter is valid Hoppscotch REST Collection;
* False, otherwise.
*/
export const isRESTCollection = (param: unknown): param is HoppCollection => {
if (!!param && typeof param === "object") {
if (!hasProperty(param, "v") || typeof param.v !== "number") {
return false;
}
if (!hasProperty(param, "name") || typeof param.name !== "string") {
return false;
}
if (hasProperty(param, "id") && typeof param.id !== "string") {
return false;
}
if (!hasProperty(param, "requests") || !Array.isArray(param.requests)) {
return false;
} else {
// Checks each requests array to be valid HoppRESTRequest.
const checkRequests = A.every(isHoppRESTRequest)(param.requests);
if (!checkRequests) {
return false;
}
}
if (!hasProperty(param, "folders") || !Array.isArray(param.folders)) {
return false;
} else {
// Checks each folder to be valid REST collection.
const checkFolders = A.every(isRESTCollection)(param.folders);
if (!checkFolders) {
return false;
}
}
return true;
}
return false;
};
/** /**
* Checks if given error data is of type HoppCLIError, based on existence * Checks if given error data is of type HoppCLIError, based on existence
* of code property. * of code property.

View File

@@ -1,11 +1,8 @@
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
import fs from "fs/promises"; import fs from "fs/promises";
import { entityReference } from "verzod";
import { z } from "zod";
import { error } from "../types/errors";
import { FormDataEntry } from "../types/request"; import { FormDataEntry } from "../types/request";
import { isHoppErrnoException } from "./checks"; import { error } from "../types/errors";
import { isRESTCollection, isHoppErrnoException } from "./checks";
import { HoppCollection } from "@hoppscotch/data";
/** /**
* Parses array of FormDataEntry to FormData. * Parses array of FormDataEntry to FormData.
@@ -70,11 +67,7 @@ export async function parseCollectionData(
? contents ? contents
: [contents]; : [contents];
const collectionSchemaParsedResult = z if (maybeArrayOfCollections.some((x) => !isRESTCollection(x))) {
.array(entityReference(HoppCollection))
.safeParse(maybeArrayOfCollections);
if (!collectionSchemaParsedResult.success) {
throw error({ throw error({
code: "MALFORMED_COLLECTION", code: "MALFORMED_COLLECTION",
path, path,
@@ -82,22 +75,5 @@ export async function parseCollectionData(
}); });
} }
return collectionSchemaParsedResult.data.map((collection) => { return maybeArrayOfCollections as HoppCollection[];
const requestSchemaParsedResult = z
.array(entityReference(HoppRESTRequest))
.safeParse(collection.requests);
if (!requestSchemaParsedResult.success) {
throw error({
code: "MALFORMED_COLLECTION",
path,
data: "Please check the collection data.",
});
}
return {
...collection,
requests: requestSchemaParsedResult.data,
};
});
} }

View File

@@ -10,9 +10,6 @@ module.exports = {
parserOptions: { parserOptions: {
sourceType: "module", sourceType: "module",
requireConfigFile: false, requireConfigFile: false,
ecmaFeatures: {
jsx: false,
},
}, },
extends: [ extends: [
"@vue/typescript/recommended", "@vue/typescript/recommended",

View File

@@ -27,7 +27,6 @@
"hide_secret": "Hide secret", "hide_secret": "Hide secret",
"label": "Label", "label": "Label",
"learn_more": "Learn more", "learn_more": "Learn more",
"download_here": "Download here",
"less": "Less", "less": "Less",
"more": "More", "more": "More",
"new": "New", "new": "New",
@@ -315,7 +314,6 @@
"danger_zone": "Danger zone", "danger_zone": "Danger zone",
"delete_account": "Your account is currently an owner in these workspaces:", "delete_account": "Your account is currently an owner in these workspaces:",
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these workspaces before you can delete your account.", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these workspaces before you can delete your account.",
"empty_profile_name": "Profile name cannot be empty",
"empty_req_name": "Empty Request Name", "empty_req_name": "Empty Request Name",
"f12_details": "(F12 for details)", "f12_details": "(F12 for details)",
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again", "gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
@@ -335,7 +333,6 @@
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"please_install_extension": "Please install the extension and add origin to the extension.", "please_install_extension": "Please install the extension and add origin to the extension.",
"proxy_error": "Proxy error", "proxy_error": "Proxy error",
"same_profile_name": "Updated profile name is same as the current profile name",
"script_fail": "Could not execute pre-request script", "script_fail": "Could not execute pre-request script",
"something_went_wrong": "Something went wrong", "something_went_wrong": "Something went wrong",
"test_script_fail": "Could not execute post-request script", "test_script_fail": "Could not execute post-request script",
@@ -451,7 +448,7 @@
"not_found": "Environment variable “{environment}” not found." "not_found": "Environment variable “{environment}” not found."
}, },
"header": { "header": {
"cookie": "The browser doesn't allow Hoppscotch to set Cookie Headers. Please use Authorization Headers instead. However, our Hoppscotch Desktop App is live now and supports Cookies." "cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
}, },
"response": { "response": {
"401_error": "Please check your authentication credentials.", "401_error": "Please check your authentication credentials.",

View File

@@ -127,8 +127,8 @@
"@types/splitpanes": "2.2.6", "@types/splitpanes": "2.2.6",
"@types/uuid": "9.0.7", "@types/uuid": "9.0.7",
"@types/yargs-parser": "21.0.3", "@types/yargs-parser": "21.0.3",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "6.13.2",
"@vitejs/plugin-vue": "4.5.1", "@vitejs/plugin-vue": "4.5.1",
"@vue/compiler-sfc": "3.3.10", "@vue/compiler-sfc": "3.3.10",
"@vue/eslint-config-typescript": "12.0.0", "@vue/eslint-config-typescript": "12.0.0",
@@ -136,9 +136,9 @@
"autoprefixer": "10.4.16", "autoprefixer": "10.4.16",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"dotenv": "16.3.1", "dotenv": "16.3.1",
"eslint": "8.57.0", "eslint": "8.55.0",
"eslint-plugin-prettier": "5.1.3", "eslint-plugin-prettier": "5.0.1",
"eslint-plugin-vue": "9.24.0", "eslint-plugin-vue": "9.19.2",
"glob": "10.3.10", "glob": "10.3.10",
"npm-run-all": "4.1.5", "npm-run-all": "4.1.5",
"openapi-types": "12.1.3", "openapi-types": "12.1.3",

View File

@@ -65,20 +65,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { import { HoppCollection, HoppRESTAuth, HoppRESTHeaders } from "@hoppscotch/data"
GQLHeader,
HoppCollection,
HoppGQLAuth,
HoppRESTAuth,
HoppRESTHeaders,
} from "@hoppscotch/data"
import { useVModel } from "@vueuse/core"
import { useService } from "dioc/vue"
import { clone } from "lodash-es" import { clone } from "lodash-es"
import { ref, watch } from "vue"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties" import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { PersistenceService } from "~/services/persistence" import { PersistenceService } from "~/services/persistence"
import { useService } from "dioc/vue"
import { ref, watch } from "vue"
import { useVModel } from "@vueuse/core"
const persistenceService = useService(PersistenceService) const persistenceService = useService(PersistenceService)
const t = useI18n() const t = useI18n()
@@ -90,9 +84,6 @@ export type EditingProperties = {
inheritedProperties?: HoppInheritedProperty inheritedProperties?: HoppInheritedProperty
} }
type HoppCollectionAuth = HoppRESTAuth | HoppGQLAuth
type HoppCollectionHeaders = HoppRESTHeaders | GQLHeader[]
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
show: boolean show: boolean
@@ -118,8 +109,8 @@ const emit = defineEmits<{
}>() }>()
const editableCollection = ref<{ const editableCollection = ref<{
headers: HoppCollectionHeaders headers: HoppRESTHeaders
auth: HoppCollectionAuth auth: HoppRESTAuth
}>({ }>({
headers: [], headers: [],
auth: { auth: {
@@ -131,16 +122,15 @@ const editableCollection = ref<{
watch( watch(
editableCollection, editableCollection,
(updatedEditableCollection) => { (updatedEditableCollection) => {
if (props.show && props.editingProperties) { if (props.show) {
const unsavedCollectionProperties: EditingProperties = {
collection: updatedEditableCollection,
isRootCollection: props.editingProperties?.isRootCollection ?? false,
path: props.editingProperties?.path,
inheritedProperties: props.editingProperties?.inheritedProperties,
}
persistenceService.setLocalConfig( persistenceService.setLocalConfig(
"unsaved_collection_properties", "unsaved_collection_properties",
JSON.stringify(unsavedCollectionProperties) JSON.stringify(<EditingProperties>{
collection: updatedEditableCollection,
isRootCollection: props.editingProperties?.isRootCollection,
path: props.editingProperties?.path,
inheritedProperties: props.editingProperties?.inheritedProperties,
})
) )
} }
}, },
@@ -156,10 +146,10 @@ watch(
(show) => { (show) => {
if (show && props.editingProperties?.collection) { if (show && props.editingProperties?.collection) {
editableCollection.value.auth = clone( editableCollection.value.auth = clone(
props.editingProperties.collection.auth as HoppCollectionAuth props.editingProperties.collection.auth as HoppRESTAuth
) )
editableCollection.value.headers = clone( editableCollection.value.headers = clone(
props.editingProperties.collection.headers as HoppCollectionHeaders props.editingProperties.collection.headers as HoppRESTHeaders
) )
} else { } else {
editableCollection.value = { editableCollection.value = {

View File

@@ -180,6 +180,7 @@ import { GQLTabService } from "~/services/tab/graphql"
import { computed } from "vue" import { computed } from "vue"
import { import {
HoppCollection, HoppCollection,
HoppGQLAuth,
HoppGQLRequest, HoppGQLRequest,
makeGQLRequest, makeGQLRequest,
} from "@hoppscotch/data" } from "@hoppscotch/data"
@@ -225,7 +226,7 @@ const editingRequest = ref<HoppGQLRequest | null>(null)
const editingRequestIndex = ref<number | null>(null) const editingRequestIndex = ref<number | null>(null)
const editingProperties = ref<{ const editingProperties = ref<{
collection: Partial<HoppCollection> | null collection: HoppCollection | null
isRootCollection: boolean isRootCollection: boolean
path: string path: string
inheritedProperties?: HoppInheritedProperty inheritedProperties?: HoppInheritedProperty
@@ -264,9 +265,8 @@ onMounted(() => {
) )
if (unsavedCollectionPropertiesString) { if (unsavedCollectionPropertiesString) {
const unsavedCollectionProperties: EditingProperties = JSON.parse( const unsavedCollectionProperties: EditingProperties<"GraphQL"> =
unsavedCollectionPropertiesString JSON.parse(unsavedCollectionPropertiesString)
)
const auth = unsavedCollectionProperties.collection?.auth const auth = unsavedCollectionProperties.collection?.auth
@@ -610,7 +610,7 @@ const editProperties = ({
if (collectionIndex === null || collection === null) return if (collectionIndex === null || collection === null) return
const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder const parentIndex = collectionIndex.split("/").slice(0, -1).join("/") // remove last folder to get parent folder
let inheritedProperties = undefined let inheritedProperties = {}
if (parentIndex) { if (parentIndex) {
const { auth, headers } = cascadeParentCollectionForHeaderAuth( const { auth, headers } = cascadeParentCollectionForHeaderAuth(
@@ -621,7 +621,7 @@ const editProperties = ({
inheritedProperties = { inheritedProperties = {
auth, auth,
headers, headers,
} } as HoppInheritedProperty
} }
editingProperties.value = { editingProperties.value = {
@@ -635,15 +635,11 @@ const editProperties = ({
} }
const setCollectionProperties = (newCollection: { const setCollectionProperties = (newCollection: {
collection: Partial<HoppCollection> | null collection: HoppCollection
path: string path: string
isRootCollection: boolean isRootCollection: boolean
}) => { }) => {
const { collection, path, isRootCollection } = newCollection const { collection, path, isRootCollection } = newCollection
if (!collection) {
return
}
if (isRootCollection) { if (isRootCollection) {
editGraphqlCollection(parseInt(path), collection) editGraphqlCollection(parseInt(path), collection)
} else { } else {

View File

@@ -381,7 +381,7 @@ watch(
const selectedTeamID = collectionsType.value.selectedTeam?.id const selectedTeamID = collectionsType.value.selectedTeam?.id
selectedTeamID && selectedTeamID &&
debouncedSearch(newFilterText, selectedTeamID)?.catch(() => {}) debouncedSearch(newFilterText, selectedTeamID)?.catch((_) => {})
} }
}, },
{ {
@@ -414,11 +414,14 @@ onMounted(() => {
) )
if (unsavedCollectionPropertiesString) { if (unsavedCollectionPropertiesString) {
const unsavedCollectionProperties: EditingProperties = JSON.parse( const unsavedCollectionProperties: EditingProperties<"REST"> = JSON.parse(
unsavedCollectionPropertiesString unsavedCollectionPropertiesString
) )
const auth = unsavedCollectionProperties.collection?.auth // casting because the type `EditingProperties["collection"]["auth"] and the usage in Properties.vue is different. there it's casted as an any.
// FUTURE-TODO: look into this
// @ts-expect-error because of the above reason
const auth = unsavedCollectionProperties.collection?.auth as HoppRESTAuth
if (auth?.authType === "oauth-2") { if (auth?.authType === "oauth-2") {
const grantTypeInfo = auth.grantTypeInfo const grantTypeInfo = auth.grantTypeInfo

View File

@@ -299,7 +299,7 @@ const selectOAuth2AuthType = () => {
? existingGrantTypeInfo ? existingGrantTypeInfo
: defaultGrantTypeInfo : defaultGrantTypeInfo
auth.value = { auth.value = <HoppGQLAuth>{
...auth.value, ...auth.value,
authType: "oauth-2", authType: "oauth-2",
addTo: "HEADERS", addTo: "HEADERS",

View File

@@ -307,7 +307,7 @@ const selectOAuth2AuthType = () => {
? existingGrantTypeInfo ? existingGrantTypeInfo
: defaultGrantTypeInfo : defaultGrantTypeInfo
auth.value = { auth.value = <HoppRESTAuth>{
...auth.value, ...auth.value,
authType: "oauth-2", authType: "oauth-2",
addTo: "HEADERS", addTo: "HEADERS",

View File

@@ -98,7 +98,6 @@ import { RESTTabService } from "~/services/tab/rest"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { useNestedSetting } from "~/composables/settings" import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings" import { toggleNestedSetting } from "~/newstore/settings"
import { EditorView } from "@codemirror/view"
const t = useI18n() const t = useI18n()
@@ -125,7 +124,6 @@ useCodemirror(
linter: null, linter: null,
completer: null, completer: null,
environmentHighlights: false, environmentHighlights: false,
onInit: (view: EditorView) => view.focus(),
}) })
) )

View File

@@ -913,16 +913,14 @@ const generateOAuthToken = async () => {
if ( if (
grantTypesInvolvingRedirect.includes(auth.value.grantTypeInfo.grantType) grantTypesInvolvingRedirect.includes(auth.value.grantTypeInfo.grantType)
) { ) {
const authConfig: PersistedOAuthConfig = {
source: props.source,
context: props.isCollectionProperty
? { type: "collection-properties", metadata: {} }
: { type: "request-tab", metadata: {} },
grant_type: auth.value.grantTypeInfo.grantType,
}
persistenceService.setLocalConfig( persistenceService.setLocalConfig(
"oauth_temp_config", "oauth_temp_config",
JSON.stringify(authConfig) JSON.stringify(<PersistedOAuthConfig>{
source: props.source,
context: props.isCollectionProperty
? { type: "collection-properties", metadata: {} }
: { type: "request-tab" },
})
) )
} }

View File

@@ -273,10 +273,6 @@ const loading = computed(
) )
onLoggedIn(() => { onLoggedIn(() => {
if (adapter.isInitialized()) {
return
}
try { try {
// wait for a bit to let the auth token to be set // wait for a bit to let the auth token to be set
// because in some race conditions, the token is not set this fixes that // because in some race conditions, the token is not set this fixes that

View File

@@ -20,7 +20,7 @@
: '' : ''
" "
> >
<div class="p-4 truncate"> <div class="p-4">
<label <label
class="font-semibold text-secondaryDark" class="font-semibold text-secondaryDark"
:class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }" :class="{ 'cursor-pointer': compact && team.myRole === 'OWNER' }"
@@ -131,7 +131,6 @@
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmRemove" :show="confirmRemove"
:title="t('confirm.remove_team')" :title="t('confirm.remove_team')"
:loading-state="loading"
@hide-modal="confirmRemove = false" @hide-modal="confirmRemove = false"
@resolve="deleteTeam()" @resolve="deleteTeam()"
/> />
@@ -162,8 +161,6 @@ import IconMoreVertical from "~icons/lucide/more-vertical"
import IconUserX from "~icons/lucide/user-x" import IconUserX from "~icons/lucide/user-x"
import IconUserPlus from "~icons/lucide/user-plus" import IconUserPlus from "~icons/lucide/user-plus"
import IconTrash2 from "~icons/lucide/trash-2" import IconTrash2 from "~icons/lucide/trash-2"
import { useService } from "dioc/vue"
import { WorkspaceService } from "~/services/workspace.service"
const t = useI18n() const t = useI18n()
@@ -176,7 +173,6 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(e: "edit-team"): void (e: "edit-team"): void
(e: "invite-team"): void (e: "invite-team"): void
(e: "refetch-teams"): void
}>() }>()
const toast = useToast() const toast = useToast()
@@ -184,12 +180,7 @@ const toast = useToast()
const confirmRemove = ref(false) const confirmRemove = ref(false)
const confirmExit = ref(false) const confirmExit = ref(false)
const loading = ref(false)
const workspaceService = useService(WorkspaceService)
const deleteTeam = () => { const deleteTeam = () => {
loading.value = true
pipe( pipe(
backendDeleteTeam(props.teamID), backendDeleteTeam(props.teamID),
TE.match( TE.match(
@@ -197,25 +188,9 @@ const deleteTeam = () => {
// TODO: Better errors ? We know the possible errors now // TODO: Better errors ? We know the possible errors now
toast.error(`${t("error.something_went_wrong")}`) toast.error(`${t("error.something_went_wrong")}`)
console.error(err) console.error(err)
loading.value = false
confirmRemove.value = false
}, },
() => { () => {
toast.success(`${t("team.deleted")}`) toast.success(`${t("team.deleted")}`)
loading.value = false
emit("refetch-teams")
const currentWorkspace = workspaceService.currentWorkspace.value
// If the current workspace is the deleted workspace, change the workspace to personal
if (
currentWorkspace.type === "team" &&
currentWorkspace.teamID === props.teamID
) {
workspaceService.changeWorkspace({ type: "personal" })
}
confirmRemove.value = false
} }
) )
)() // Tasks (and TEs) are lazy, so call the function returned )() // Tasks (and TEs) are lazy, so call the function returned

View File

@@ -4,7 +4,6 @@
<HoppButtonSecondary <HoppButtonSecondary
:label="`${t('team.create_new')}`" :label="`${t('team.create_new')}`"
outline outline
:icon="IconPlus"
@click="displayModalAdd(true)" @click="displayModalAdd(true)"
/> />
<div v-if="loading" class="flex flex-col items-center justify-center"> <div v-if="loading" class="flex flex-col items-center justify-center">
@@ -17,6 +16,13 @@
:alt="`${t('empty.teams')}`" :alt="`${t('empty.teams')}`"
:text="`${t('empty.teams')}`" :text="`${t('empty.teams')}`"
> >
<template #body>
<HoppButtonSecondary
:label="`${t('team.create_new')}`"
filled
@click="displayModalAdd(true)"
/>
</template>
</HoppSmartPlaceholder> </HoppSmartPlaceholder>
<div <div
v-else-if="!loading" v-else-if="!loading"
@@ -33,7 +39,6 @@
:compact="modal" :compact="modal"
@edit-team="editTeam(team, team.id)" @edit-team="editTeam(team, team.id)"
@invite-team="inviteTeam(team, team.id)" @invite-team="inviteTeam(team, team.id)"
@refetch-teams="refetchTeams"
/> />
</div> </div>
<div v-if="!loading && adapterError" class="flex flex-col items-center"> <div v-if="!loading && adapterError" class="flex flex-col items-center">
@@ -71,7 +76,6 @@ import { useReadonlyStream } from "@composables/stream"
import { useColorMode } from "@composables/theming" import { useColorMode } from "@composables/theming"
import { WorkspaceService } from "~/services/workspace.service" import { WorkspaceService } from "~/services/workspace.service"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import IconPlus from "~icons/lucide/plus"
const t = useI18n() const t = useI18n()

View File

@@ -68,9 +68,6 @@ type CodeMirrorOptions = {
// callback on editor update // callback on editor update
onUpdate?: (view: ViewUpdate) => void onUpdate?: (view: ViewUpdate) => void
// callback on view initialization
onInit?: (view: EditorView) => void
} }
const hoppCompleterExt = (completer: Completer): Extension => { const hoppCompleterExt = (completer: Completer): Extension => {
@@ -211,9 +208,7 @@ export function useCodemirror(
el: Ref<any | null>, el: Ref<any | null>,
value: Ref<string | undefined>, value: Ref<string | undefined>,
options: CodeMirrorOptions options: CodeMirrorOptions
): { ): { cursor: Ref<{ line: number; ch: number }> } {
cursor: Ref<{ line: number; ch: number }>
} {
const { subscribeToStream } = useStreamSubscriber() const { subscribeToStream } = useStreamSubscriber()
// Set default value for contextMenuEnabled if not provided // Set default value for contextMenuEnabled if not provided
@@ -388,8 +383,6 @@ export function useCodemirror(
extensions, extensions,
}), }),
}) })
options.onInit?.(view.value)
} }
onMounted(() => { onMounted(() => {

View File

@@ -868,38 +868,6 @@ const samples = [
requestVariables: [], requestVariables: [],
}), }),
}, },
{
command: `curl --location 'https://api.example.net/id/1164/requests' \
--header 'Accept: application/vnd.test-data.v2.1+json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'data={"type":"test","typeId":"101"}' \
--data-urlencode 'data2={"type":"test2","typeId":"123"}'`,
response: makeRESTRequest({
method: "POST",
name: "Untitled",
endpoint: "https://api.example.net/id/1164/requests",
auth: {
authType: "inherit",
authActive: true,
},
body: {
contentType: "application/x-www-form-urlencoded",
body: `data: {"type":"test","typeId":"101"}
data2: {"type":"test2","typeId":"123"}`,
},
params: [],
headers: [
{
active: true,
key: "Accept",
value: "application/vnd.test-data.v2.1+json",
},
],
preRequestScript: "",
testScript: "",
requestVariables: [],
}),
},
] ]
describe("Parse curl command to Hopp REST Request", () => { describe("Parse curl command to Hopp REST Request", () => {

View File

@@ -33,27 +33,7 @@ export const parseCurlCommand = (curlCommand: string) => {
// const compressed = !!parsedArguments.compressed // const compressed = !!parsedArguments.compressed
curlCommand = preProcessCurlCommand(curlCommand) curlCommand = preProcessCurlCommand(curlCommand)
const parsedArguments = parser(curlCommand)
const args: parser.Arguments = parser(curlCommand)
const parsedArguments = pipe(
args,
O.fromPredicate(
(args) =>
objHasProperty("dataUrlencode", "string")(args) ||
objHasProperty("dataUrlencode", "object")(args)
),
O.map((args) => {
const urlEncodedData: string[] = Array.isArray(args.dataUrlencode)
? args.dataUrlencode
: [args.dataUrlencode]
const data = A.map(decodeURIComponent)(urlEncodedData)
return { ...args, d: data }
}),
O.getOrElse(() => args)
)
const headerObject = getHeaders(parsedArguments) const headerObject = getHeaders(parsedArguments)
const { headers } = headerObject const { headers } = headerObject

View File

@@ -1,19 +1,15 @@
import { import { pipe, flow } from "fp-ts/function"
HoppCollection, import * as TE from "fp-ts/TaskEither"
HoppRESTRequest,
getDefaultGQLRequest,
getDefaultRESTRequest,
translateToNewRESTCollection,
} from "@hoppscotch/data"
import * as A from "fp-ts/Array"
import * as O from "fp-ts/Option" import * as O from "fp-ts/Option"
import * as RA from "fp-ts/ReadonlyArray" import * as RA from "fp-ts/ReadonlyArray"
import * as TE from "fp-ts/TaskEither" import * as A from "fp-ts/Array"
import { flow, pipe } from "fp-ts/function" import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
import { HoppGQLRequest, translateToNewGQLCollection } from "@hoppscotch/data"
import { safeParseJSON } from "~/helpers/functional/json"
import { IMPORTER_INVALID_FILE_FORMAT } from "." import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { safeParseJSON } from "~/helpers/functional/json"
import { translateToNewGQLCollection } from "@hoppscotch/data"
import { entityReference } from "verzod"
import { z } from "zod"
export const hoppRESTImporter = (content: string[]) => export const hoppRESTImporter = (content: string[]) =>
pipe( pipe(
@@ -36,24 +32,8 @@ export const hoppRESTImporter = (content: string[]) =>
* else translate it into one. * else translate it into one.
*/ */
const validateCollection = (collection: unknown) => { const validateCollection = (collection: unknown) => {
const collectionSchemaParsedResult = HoppCollection.safeParse(collection) const result = entityReference(HoppCollection).safeParse(collection)
if (result.success) return O.some(result.data)
if (collectionSchemaParsedResult.type === "ok") {
const requests = collectionSchemaParsedResult.value.requests.map(
(request) => {
const requestSchemaParsedResult = HoppRESTRequest.safeParse(request)
return requestSchemaParsedResult.type === "ok"
? requestSchemaParsedResult.value
: getDefaultRESTRequest()
}
)
return O.some({
...collectionSchemaParsedResult.value,
requests,
})
}
return O.some(translateToNewRESTCollection(collection)) return O.some(translateToNewRESTCollection(collection))
} }
@@ -84,24 +64,9 @@ export const hoppGQLImporter = (content: string) =>
* @returns the collection if it is valid, else a translated version of the collection * @returns the collection if it is valid, else a translated version of the collection
*/ */
export const validateGQLCollection = (collection: unknown) => { export const validateGQLCollection = (collection: unknown) => {
const collectionSchemaParsedResult = HoppCollection.safeParse(collection) const result = z.array(entityReference(HoppCollection)).safeParse(collection)
if (collectionSchemaParsedResult.type === "ok") { if (result.success) return O.some(result.data)
const requests = collectionSchemaParsedResult.value.requests.map(
(request) => {
const requestSchemaParsedResult = HoppGQLRequest.safeParse(request)
return requestSchemaParsedResult.type === "ok"
? requestSchemaParsedResult.value
: getDefaultGQLRequest()
}
)
return O.some({
...collectionSchemaParsedResult.value,
requests,
})
}
return O.some(translateToNewGQLCollection(collection)) return O.some(translateToNewGQLCollection(collection))
} }

View File

@@ -1,14 +1,4 @@
import { import { ref } from "vue"
HoppRESTAuth,
HoppRESTHeader,
HoppRESTRequest,
getDefaultRESTRequest,
} from "@hoppscotch/data"
import axios from "axios"
import { Service } from "dioc"
import * as E from "fp-ts/Either"
import { Ref, ref } from "vue"
import { runGQLQuery } from "../backend/GQLClient" import { runGQLQuery } from "../backend/GQLClient"
import { import {
GetCollectionChildrenDocument, GetCollectionChildrenDocument,
@@ -17,10 +7,14 @@ import {
GetSingleRequestDocument, GetSingleRequestDocument,
} from "../backend/graphql" } from "../backend/graphql"
import { TeamCollection } from "./TeamCollection" import { TeamCollection } from "./TeamCollection"
import { HoppRESTAuth, HoppRESTHeader } from "@hoppscotch/data"
import { platform } from "~/platform" import * as E from "fp-ts/Either"
import { HoppInheritedProperty } from "../types/HoppInheritedProperties" import { HoppInheritedProperty } from "../types/HoppInheritedProperties"
import { TeamRequest } from "./TeamRequest" import { TeamRequest } from "./TeamRequest"
import { Service } from "dioc"
import axios from "axios"
import { Ref } from "vue"
type CollectionSearchMeta = { type CollectionSearchMeta = {
isSearchResult?: boolean isSearchResult?: boolean
@@ -155,21 +149,12 @@ function convertToTeamTree(
if (isAlreadyInserted) return if (isAlreadyInserted) return
if (parentCollection) { if (parentCollection) {
const requestSchemaParsedResult = HoppRESTRequest.safeParse(
request.request
)
const effectiveRequest =
requestSchemaParsedResult.type === "ok"
? requestSchemaParsedResult.value
: getDefaultRESTRequest()
parentCollection.requests = parentCollection.requests || [] parentCollection.requests = parentCollection.requests || []
parentCollection.requests.push({ parentCollection.requests.push({
id: request.id, id: request.id,
collectionID: request.collectionID, collectionID: request.collectionID,
title: request.title, title: request.title,
request: effectiveRequest, request: request.request,
}) })
} }
}) })
@@ -214,16 +199,16 @@ export class TeamSearchService extends Service {
this.searchResultsRequests = {} this.searchResultsRequests = {}
this.expandedCollections.value = [] this.expandedCollections.value = []
const axiosPlatformConfig = platform.auth.axiosPlatformConfig?.() ?? {}
try { try {
const searchResponse = await axios.get( const searchResponse = await axios.get(
`${ `${
this.endpoint this.endpoint
}/team-collection/search/${teamID}?searchQuery=${encodeURIComponent( }/team-collection/search/${teamID}?searchQuery=${encodeURIComponent(
query query
)}`, )}}`,
axiosPlatformConfig {
withCredentials: true,
}
) )
if (searchResponse.status !== 200) { if (searchResponse.status !== 200) {

View File

@@ -274,7 +274,7 @@ function getFinalBodyFromRequest(
if (request.body.contentType === "application/x-www-form-urlencoded") { if (request.body.contentType === "application/x-www-form-urlencoded") {
const parsedBodyRecord = pipe( const parsedBodyRecord = pipe(
request.body.body ?? "", request.body.body,
parseRawKeyValueEntriesE, parseRawKeyValueEntriesE,
E.map( E.map(
flow( flow(
@@ -311,7 +311,7 @@ function getFinalBodyFromRequest(
if (request.body.contentType === "multipart/form-data") { if (request.body.contentType === "multipart/form-data") {
return pipe( return pipe(
request.body.body ?? [], request.body.body,
A.filter((x) => (x.key !== "" || x.isFile) && x.active), // Remove empty keys A.filter((x) => (x.key !== "" || x.isFile) && x.active), // Remove empty keys
// Sort files down // Sort files down

View File

@@ -79,7 +79,7 @@ const importCollections = (url: unknown, type: unknown) =>
content.data, content.data,
TO.fromPredicate(isOfType("string")), TO.fromPredicate(isOfType("string")),
TE.fromTaskOption(() => IMPORTER_INVALID_FILE_FORMAT), TE.fromTaskOption(() => IMPORTER_INVALID_FILE_FORMAT),
TE.chain((data) => importer.importer([data])) TE.chain((data) => importer.importer(data))
) )
) )
) )

View File

@@ -93,13 +93,12 @@ onMounted(async () => {
// Indicates the access token generation flow originated from the modal for setting authorization/headers at the collection level // Indicates the access token generation flow originated from the modal for setting authorization/headers at the collection level
if (context?.type === "collection-properties") { if (context?.type === "collection-properties") {
// Set the access token in `localStorage` to retrieve from the modal while redirecting back // Set the access token in `localStorage` to retrieve from the modal while redirecting back
const authConfig: PersistedOAuthConfig = {
...persistedOAuthConfig,
token: tokenInfo.right.access_token,
}
persistenceService.setLocalConfig( persistenceService.setLocalConfig(
"oauth_temp_config", "oauth_temp_config",
JSON.stringify(authConfig) JSON.stringify(<PersistedOAuthConfig>{
...persistedOAuthConfig,
token: tokenInfo.right.access_token,
})
) )
toast.success(t("authorization.oauth.token_fetched_successfully")) toast.success(t("authorization.oauth.token_fetched_successfully"))

View File

@@ -210,8 +210,6 @@ import { toggleSetting } from "~/newstore/settings"
import IconVerified from "~icons/lucide/verified" import IconVerified from "~icons/lucide/verified"
import IconSettings from "~icons/lucide/settings" import IconSettings from "~icons/lucide/settings"
import * as E from "fp-ts/Either"
type ProfileTabs = "sync" | "teams" type ProfileTabs = "sync" | "teams"
const selectedProfileTab = ref<ProfileTabs>("sync") const selectedProfileTab = ref<ProfileTabs>("sync")
@@ -246,28 +244,19 @@ const displayName = ref(currentUser.value?.displayName || "")
const updatingDisplayName = ref(false) const updatingDisplayName = ref(false)
watchEffect(() => (displayName.value = currentUser.value?.displayName || "")) watchEffect(() => (displayName.value = currentUser.value?.displayName || ""))
const updateDisplayName = async () => { const updateDisplayName = () => {
if (!displayName.value) {
toast.error(`${t("error.empty_profile_name")}`)
return
}
if (currentUser.value?.displayName === displayName.value) {
toast.error(`${t("error.same_profile_name")}`)
return
}
updatingDisplayName.value = true updatingDisplayName.value = true
platform.auth
const res = await platform.auth.setDisplayName(displayName.value) .setDisplayName(displayName.value as string)
.then(() => {
if (E.isLeft(res)) { toast.success(`${t("profile.updated")}`)
toast.error(t("error.something_went_wrong")) })
} else if (E.isRight(res)) { .catch(() => {
toast.success(`${t("profile.updated")}`) toast.error(`${t("error.something_went_wrong")}`)
} })
.finally(() => {
updatingDisplayName.value = false updatingDisplayName.value = false
})
} }
const emailAddress = ref(currentUser.value?.email || "") const emailAddress = ref(currentUser.value?.email || "")

View File

@@ -3,8 +3,6 @@ import { Observable } from "rxjs"
import { Component } from "vue" import { Component } from "vue"
import { getI18n } from "~/modules/i18n" import { getI18n } from "~/modules/i18n"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { AxiosRequestConfig } from "axios"
import { GQLError } from "~/helpers/backend/GQLClient"
/** /**
* A common (and required) set of fields that describe a user. * A common (and required) set of fields that describe a user.
@@ -137,15 +135,6 @@ export type AuthPlatformDef = {
*/ */
getGQLClientOptions?: () => Partial<ClientOptions> getGQLClientOptions?: () => Partial<ClientOptions>
/**
* called by the platform to provide additional/different config options when
* sending requests with axios
* eg: SH needs to include cookies in the request, while Central doesn't and throws a cors error if it does
*
* @returns AxiosRequestConfig
*/
axiosPlatformConfig?: () => AxiosRequestConfig
/** /**
* Returns the string content that should be returned when the user selects to * Returns the string content that should be returned when the user selects to
* copy auth token from Developer Options. * copy auth token from Developer Options.
@@ -230,11 +219,9 @@ export type AuthPlatformDef = {
/** /**
* Updates the display name of the user * Updates the display name of the user
* @param name The new name to set this to. * @param name The new name to set this to.
* @returns A promise that resolves with the display name update status when the operation is complete * @returns An empty promise that is resolved when the operation is complete
*/ */
setDisplayName: ( setDisplayName: (name: string) => Promise<void>
name: string
) => Promise<E.Either<GQLError<string>, undefined>>
/** /**
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML ) * Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML )

View File

@@ -66,8 +66,8 @@ export class HeaderInspectorService extends Service implements Inspector {
index: index, index: index,
}, },
doc: { doc: {
text: this.t("action.download_here"), text: this.t("action.learn_more"),
link: "https://hoppscotch.com/download", link: "https://docs.hoppscotch.io/documentation/features/inspections",
}, },
}) })
} }

View File

@@ -169,16 +169,7 @@ export class CollectionsSpotlightSearcherService
} }
scopeHandle.run(() => { scopeHandle.run(() => {
const isPersonalWorkspace = computed(
() => this.workspaceService.currentWorkspace.value.type === "personal"
)
watch(query, (query) => { watch(query, (query) => {
if (!isPersonalWorkspace.value) {
results.value = []
return
}
if (pageCategory === "other") { if (pageCategory === "other") {
results.value = [] results.value = []
return return

View File

@@ -58,32 +58,13 @@ export class TeamsSpotlightSearcherService
(query) => { (query) => {
if (this.workspaceService.currentWorkspace.value.type === "team") { if (this.workspaceService.currentWorkspace.value.type === "team") {
const teamID = this.workspaceService.currentWorkspace.value.teamID const teamID = this.workspaceService.currentWorkspace.value.teamID
debouncedSearch(query, teamID)?.catch(() => {}) debouncedSearch(query, teamID)?.catch((_) => {})
} }
}, },
{ {
immediate: true, immediate: true,
} }
) )
// set the search section title based on the current workspace
const teamName = computed(() => {
return (
(this.workspaceService.currentWorkspace.value.type === "team" &&
this.workspaceService.currentWorkspace.value.teamName) ||
this.t("team.search_title")
)
})
watch(
teamName,
(newTeamName) => {
this.searcherSectionTitle = newTeamName
},
{
immediate: true,
}
)
}) })
const onSessionEnd = () => { const onSessionEnd = () => {

View File

@@ -10,7 +10,6 @@
"dist/*" "dist/*"
], ],
"scripts": { "scripts": {
"dev": "vite build --watch",
"build:code": "vite build", "build:code": "vite build",
"build:decl": "tsc --project tsconfig.decl.json", "build:decl": "tsc --project tsconfig.decl.json",
"build": "pnpm run build:code && pnpm run build:decl", "build": "pnpm run build:code && pnpm run build:decl",

View File

@@ -8,7 +8,8 @@ import { translateToNewRequest } from "../rest"
import { translateToGQLRequest } from "../graphql" import { translateToGQLRequest } from "../graphql"
const versionedObject = z.object({ const versionedObject = z.object({
v: z.number(), // v is a stringified number
v: z.string().regex(/^\d+$/).transform(Number),
}) })
export const HoppCollection = createVersionedEntity({ export const HoppCollection = createVersionedEntity({
@@ -25,7 +26,7 @@ export const HoppCollection = createVersionedEntity({
// For V1 we have to check the schema // For V1 we have to check the schema
const result = V1_VERSION.schema.safeParse(data) const result = V1_VERSION.schema.safeParse(data)
return result.success ? 1 : null return result.success ? 0 : null
}, },
}) })

View File

@@ -11,9 +11,6 @@ module.exports = {
parserOptions: { parserOptions: {
sourceType: "module", sourceType: "module",
requireConfigFile: false, requireConfigFile: false,
ecmaFeatures: {
jsx: false,
},
}, },
extends: [ extends: [
"@vue/typescript/recommended", "@vue/typescript/recommended",

View File

@@ -1,5 +0,0 @@
mutation UpdateUserDisplayName($updatedDisplayName: String!) {
updateDisplayName(updatedDisplayName: $updatedDisplayName) {
displayName
}
}

View File

@@ -1,12 +1,6 @@
import { runMutation } from "@hoppscotch/common/helpers/backend/GQLClient"
import axios from "axios" import axios from "axios"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import { z } from "zod" import { z } from "zod"
import {
UpdateUserDisplayNameDocument,
UpdateUserDisplayNameMutation,
UpdateUserDisplayNameMutationVariables,
} from "../../api/generated/graphql"
const expectedAllowedProvidersSchema = z.object({ const expectedAllowedProvidersSchema = z.object({
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML" // currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML"
@@ -34,12 +28,3 @@ export const getAllowedAuthProviders = async () => {
return E.left("SOMETHING_WENT_WRONG") return E.left("SOMETHING_WENT_WRONG")
} }
} }
export const updateUserDisplayName = (updatedDisplayName: string) =>
runMutation<
UpdateUserDisplayNameMutation,
UpdateUserDisplayNameMutationVariables,
""
>(UpdateUserDisplayNameDocument, {
updatedDisplayName,
})()

View File

@@ -8,8 +8,7 @@ import { PersistenceService } from "@hoppscotch/common/services/persistence"
import axios from "axios" import axios from "axios"
import { BehaviorSubject, Subject } from "rxjs" import { BehaviorSubject, Subject } from "rxjs"
import { Ref, ref, watch } from "vue" import { Ref, ref, watch } from "vue"
import { getAllowedAuthProviders, updateUserDisplayName } from "./auth.api" import { getAllowedAuthProviders } from "./auth.api"
import * as E from "fp-ts/Either"
export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>() export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>()
const currentUser$ = new BehaviorSubject<HoppUser | null>(null) const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
@@ -212,13 +211,6 @@ export const def: AuthPlatformDef = {
} }
}, },
axiosPlatformConfig() {
return {
// for including cookies in the request
withCredentials: true,
}
},
/** /**
* it is not possible for us to know if the current cookie is expired because we cannot access http-only cookies from js * it is not possible for us to know if the current cookie is expired because we cannot access http-only cookies from js
* hence just returning if the currentUser$ has a value associated with it * hence just returning if the currentUser$ has a value associated with it
@@ -318,22 +310,9 @@ export const def: AuthPlatformDef = {
async setEmailAddress(_email: string) { async setEmailAddress(_email: string) {
return return
}, },
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async setDisplayName(name: string) { async setDisplayName(name: string) {
if (!name) return E.left("USER_NAME_CANNOT_BE_EMPTY") return
if (!currentUser$.value) return E.left("NO_USER_LOGGED_IN")
const res = await updateUserDisplayName(name)
if (E.isRight(res)) {
setUser({
...currentUser$.value,
displayName: res.right.updateDisplayName.displayName ?? null,
})
return E.right(undefined)
}
return E.left(res.left)
}, },
async signOutUser() { async signOutUser() {

View File

@@ -164,14 +164,11 @@
"privacy_policy": "Privacy Policy", "privacy_policy": "Privacy Policy",
"reenter_email": "Re-enter email", "reenter_email": "Re-enter email",
"remove_admin_failure": "Failed to remove admin status!!", "remove_admin_failure": "Failed to remove admin status!!",
"remove_admin_failure_only_one_admin": "Failed to remove admin status. There should be at least one admin!!",
"remove_admin_success": "Admin status removed!!", "remove_admin_success": "Admin status removed!!",
"remove_admin_from_users_failure": "Failed to remove admin status from selected users!!", "remove_admin_from_users_failure": "Failed to remove admin status from selected users!!",
"remove_admin_from_users_success": "Admin status removed from selected users!!", "remove_admin_from_users_success": "Admin status removed from selected users!!",
"remove_admin_to_delete_user": "Remove admin privilege to delete the user!!", "remove_admin_to_delete_user": "Remove admin privilege to delete the user!!",
"remove_owner_to_delete_user": "Remove team ownership status to delete the user!!",
"remove_admin_for_deletion": "Remove admin status before attempting deletion!!", "remove_admin_for_deletion": "Remove admin status before attempting deletion!!",
"remove_owner_for_deletion": "One or more users are team owners. Update ownership before deletion!!",
"remove_invitee_failure": "Removal of invitee failed!!", "remove_invitee_failure": "Removal of invitee failed!!",
"remove_invitee_success": "Removal of invitee is successfull!!", "remove_invitee_success": "Removal of invitee is successfull!!",
"remove_member_failure": "Member couldn't be removed!!", "remove_member_failure": "Member couldn't be removed!!",
@@ -256,6 +253,7 @@
}, },
"users": { "users": {
"admin": "Admin", "admin": "Admin",
"admin_email": "Admin Email",
"admin_id": "Admin ID", "admin_id": "Admin ID",
"cancel": "Cancel", "cancel": "Cancel",
"created_on": "Created On", "created_on": "Created On",
@@ -272,7 +270,6 @@
"invalid_user": "Invalid User", "invalid_user": "Invalid User",
"invite_load_list_error": "Unable to Load Invited Users List", "invite_load_list_error": "Unable to Load Invited Users List",
"invite_user": "Invite User", "invite_user": "Invite User",
"invited_by": "Invited By",
"invited_on": "Invited On", "invited_on": "Invited On",
"invited_users": "Invited Users", "invited_users": "Invited Users",
"invitee_email": "Invitee Email", "invitee_email": "Invitee Email",

View File

@@ -51,6 +51,7 @@ declare module '@vue/runtime-core' {
UsersDetails: typeof import('./components/users/Details.vue')['default'] UsersDetails: typeof import('./components/users/Details.vue')['default']
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default'] UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default']
UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default'] UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default']
UsersTable: typeof import('./components/users/Table.vue')['default']
} }
} }

View File

@@ -8,8 +8,8 @@ export const UNAUTHORIZED = 'Unauthorized' as const;
// Sometimes the backend returns Unauthorized error message as follows: // Sometimes the backend returns Unauthorized error message as follows:
export const GRAPHQL_UNAUTHORIZED = '[GraphQL] Unauthorized' as const; export const GRAPHQL_UNAUTHORIZED = '[GraphQL] Unauthorized' as const;
export const ONLY_ONE_ADMIN_ACCOUNT_FOUND = export const DELETE_USER_FAILED_ONLY_ONE_ADMIN =
'[GraphQL] admin/only_one_admin_account_found' as const; 'admin/only_one_admin_account_found' as const;
export const ADMIN_CANNOT_BE_DELETED = export const ADMIN_CANNOT_BE_DELETED =
'admin/admin_can_not_be_deleted' as const; 'admin/admin_can_not_be_deleted' as const;
@@ -17,6 +17,3 @@ export const ADMIN_CANNOT_BE_DELETED =
// When trying to invite a user that is already invited // When trying to invite a user that is already invited
export const USER_ALREADY_INVITED = export const USER_ALREADY_INVITED =
'[GraphQL] admin/user_already_invited' as const; '[GraphQL] admin/user_already_invited' as const;
// When attempting to delete a user who is an owner of a team
export const USER_IS_OWNER = 'user/is_owner' as const;

View File

@@ -1,113 +0,0 @@
import { useToast } from '~/composables/toast';
import { getI18n } from '~/modules/i18n';
import { UserDeletionResult } from './backend/graphql';
import { ADMIN_CANNOT_BE_DELETED, USER_IS_OWNER } from './errors';
type ToastMessage = {
message: string;
state: 'success' | 'error';
};
const t = getI18n();
const toast = useToast();
const displayToastMessages = (
toastMessages: ToastMessage[],
currentIndex: number
) => {
const { message, state } = toastMessages[currentIndex];
toast[state](message, {
duration: 2000,
onComplete: () => {
if (currentIndex < toastMessages.length - 1) {
displayToastMessages(toastMessages, currentIndex + 1);
}
},
});
};
export const handleUserDeletion = (deletedUsersList: UserDeletionResult[]) => {
const uniqueErrorMessages = new Set(
deletedUsersList.map(({ errorMessage }) => errorMessage).filter(Boolean)
) as Set<string>;
const isBulkAction = deletedUsersList.length > 1;
const deletedUserIDs = deletedUsersList
.filter((user) => user.isDeleted)
.map((user) => user.userUID);
// Show the success toast based on the action type if there are no errors
if (uniqueErrorMessages.size === 0) {
toast.success(
isBulkAction
? t('state.delete_users_success')
: t('state.delete_user_success')
);
return;
}
const errMsgMap = {
[ADMIN_CANNOT_BE_DELETED]: isBulkAction
? t('state.remove_admin_for_deletion')
: t('state.remove_admin_to_delete_user'),
[USER_IS_OWNER]: isBulkAction
? t('state.remove_owner_for_deletion')
: t('state.remove_owner_to_delete_user'),
};
const errMsgMapKeys = Object.keys(errMsgMap);
const toastMessages: ToastMessage[] = [];
if (isBulkAction) {
// Indicates the actual count of users deleted (filtered via the `isDeleted` field)
const deletedUsersCount = deletedUserIDs.length;
if (isBulkAction && deletedUsersCount > 0) {
toastMessages.push({
message: t('state.delete_some_users_success', {
count: deletedUsersCount,
}),
state: 'success',
});
}
const remainingDeletionsCount = deletedUsersList.length - deletedUsersCount;
if (remainingDeletionsCount > 0) {
toastMessages.push({
message: t('state.delete_some_users_failure', {
count: remainingDeletionsCount,
}),
state: 'error',
});
}
}
uniqueErrorMessages.forEach((errorMessage) => {
if (errMsgMapKeys.includes(errorMessage)) {
toastMessages.push({
message: errMsgMap[errorMessage as keyof typeof errMsgMap],
state: 'error',
});
}
});
// Fallback for the case where the error message is not in the compiled list
if (
Array.from(uniqueErrorMessages).some(
(key) => !((key as string) in errMsgMap)
)
) {
const fallbackErrMsg = isBulkAction
? t('state.delete_users_failure')
: t('state.delete_user_failure');
toastMessages.push({
message: fallbackErrMsg,
state: 'error',
});
}
displayToastMessages(toastMessages, 0);
};

View File

@@ -1,23 +1,7 @@
import { I18n, createI18n } from 'vue-i18n'; import { createI18n } from 'vue-i18n';
import { HoppModule } from '.'; import { HoppModule } from '.';
import messages from '@intlify/unplugin-vue-i18n/messages'; import messages from '@intlify/unplugin-vue-i18n/messages';
// A reference to the i18n instance
let i18nInstance: I18n<
Record<string, unknown>,
Record<string, unknown>,
Record<string, unknown>,
string,
false
> | null = null;
/**
* Returns the i18n instance
*/
export function getI18n() {
return i18nInstance!.global.t;
}
export default <HoppModule>{ export default <HoppModule>{
onVueAppInit(app) { onVueAppInit(app) {
const i18n = createI18n({ const i18n = createI18n({
@@ -27,9 +11,6 @@ export default <HoppModule>{
legacy: false, legacy: false,
allowComposition: true, allowComposition: true,
}); });
app.use(i18n); app.use(i18n);
i18nInstance = i18n;
}, },
}; };

View File

@@ -73,7 +73,7 @@ import {
RemoveUsersByAdminDocument, RemoveUsersByAdminDocument,
UserInfoDocument, UserInfoDocument,
} from '~/helpers/backend/graphql'; } from '~/helpers/backend/graphql';
import { handleUserDeletion } from '~/helpers/userManagement'; import { ADMIN_CANNOT_BE_DELETED } from '~/helpers/errors';
const t = useI18n(); const t = useI18n();
const toast = useToast(); const toast = useToast();
@@ -210,11 +210,16 @@ const deleteUserMutation = async (id: string | null) => {
} else { } else {
const deletedUsers = result.data?.removeUsersByAdmin || []; const deletedUsers = result.data?.removeUsersByAdmin || [];
handleUserDeletion(deletedUsers); const isAdminError = deletedUsers.some(
(user) => user.errorMessage === ADMIN_CANNOT_BE_DELETED
);
isAdminError
? toast.error(t('state.delete_user_failed_only_one_admin'))
: toast.success(t('state.delete_user_success'));
} }
confirmDeletion.value = false; confirmDeletion.value = false;
deleteUserUID.value = null; deleteUserUID.value = null;
router.push('/users');
!result.error && router.push('/users');
}; };
</script> </script>

View File

@@ -20,7 +20,7 @@
/> />
</div> </div>
</div> </div>
<div class="overflow-x-auto mb-5"> <div class="overflow-x-auto">
<div class="mb-3 flex items-center justify-end"> <div class="mb-3 flex items-center justify-end">
<HoppButtonSecondary <HoppButtonSecondary
outline outline
@@ -210,7 +210,7 @@
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmUsersToAdmin" :show="confirmUsersToAdmin"
:title=" :title="
areMultipleUsersSelected AreMultipleUsersSelected
? t('state.confirm_users_to_admin') ? t('state.confirm_users_to_admin')
: t('state.confirm_user_to_admin') : t('state.confirm_user_to_admin')
" "
@@ -220,7 +220,7 @@
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmAdminsToUsers" :show="confirmAdminsToUsers"
:title=" :title="
areMultipleUsersSelectedToAdmin AreMultipleUsersSelectedToAdmin
? t('state.confirm_admins_to_users') ? t('state.confirm_admins_to_users')
: t('state.confirm_admin_to_user') : t('state.confirm_admin_to_user')
" "
@@ -230,7 +230,7 @@
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmUsersDeletion" :show="confirmUsersDeletion"
:title=" :title="
areMultipleUsersSelectedForDeletion AreMultipleUsersSelectedForDeletion
? t('state.confirm_users_deletion') ? t('state.confirm_users_deletion')
: t('state.confirm_user_deletion') : t('state.confirm_user_deletion')
" "
@@ -259,10 +259,10 @@ import {
UsersListV2Document, UsersListV2Document,
} from '~/helpers/backend/graphql'; } from '~/helpers/backend/graphql';
import { import {
ONLY_ONE_ADMIN_ACCOUNT_FOUND, ADMIN_CANNOT_BE_DELETED,
DELETE_USER_FAILED_ONLY_ONE_ADMIN,
USER_ALREADY_INVITED, USER_ALREADY_INVITED,
} from '~/helpers/errors'; } from '~/helpers/errors';
import { handleUserDeletion } from '~/helpers/userManagement';
import IconCheck from '~icons/lucide/check'; import IconCheck from '~icons/lucide/check';
import IconLeft from '~icons/lucide/chevron-left'; import IconLeft from '~icons/lucide/chevron-left';
import IconRight from '~icons/lucide/chevron-right'; import IconRight from '~icons/lucide/chevron-right';
@@ -309,10 +309,16 @@ const selectedRows = ref<UsersListQuery['infra']['allUsers']>([]);
// Ensure this variable is declared outside the debounce function // Ensure this variable is declared outside the debounce function
let debounceTimeout: ReturnType<typeof setTimeout> | null = null; let debounceTimeout: ReturnType<typeof setTimeout> | null = null;
let toastTimeout: ReturnType<typeof setTimeout> | null = null;
onUnmounted(() => { onUnmounted(() => {
if (debounceTimeout) { if (debounceTimeout) {
clearTimeout(debounceTimeout); clearTimeout(debounceTimeout);
} }
if (toastTimeout) {
clearTimeout(toastTimeout);
}
}); });
// Debounce Function // Debounce Function
@@ -456,7 +462,7 @@ const confirmUsersToAdmin = ref(false);
const usersToAdminUID = ref<string | null>(null); const usersToAdminUID = ref<string | null>(null);
const usersToAdmin = useMutation(MakeUsersAdminDocument); const usersToAdmin = useMutation(MakeUsersAdminDocument);
const areMultipleUsersSelected = computed(() => selectedRows.value.length > 1); const AreMultipleUsersSelected = computed(() => selectedRows.value.length > 1);
const confirmUserToAdmin = (id: string | null) => { const confirmUserToAdmin = (id: string | null) => {
confirmUsersToAdmin.value = true; confirmUsersToAdmin.value = true;
@@ -476,15 +482,11 @@ const makeUsersToAdmin = async (id: string | null) => {
if (result.error) { if (result.error) {
toast.error( toast.error(
areMultipleUsersSelected.value id ? t('state.admin_failure') : t('state.users_to_admin_failure')
? t('state.users_to_admin_failure')
: t('state.admin_failure')
); );
} else { } else {
toast.success( toast.success(
areMultipleUsersSelected.value id ? t('state.admin_success') : t('state.users_to_admin_success')
? t('state.users_to_admin_success')
: t('state.admin_success')
); );
usersList.value = usersList.value.map((user) => ({ usersList.value = usersList.value.map((user) => ({
...user, ...user,
@@ -512,7 +514,7 @@ const resetConfirmAdminToUser = () => {
adminsToUserUID.value = null; adminsToUserUID.value = null;
}; };
const areMultipleUsersSelectedToAdmin = computed( const AreMultipleUsersSelectedToAdmin = computed(
() => selectedRows.value.length > 1 () => selectedRows.value.length > 1
); );
@@ -522,20 +524,16 @@ const makeAdminsToUsers = async (id: string | null) => {
const variables = { userUIDs }; const variables = { userUIDs };
const result = await adminsToUser.executeMutation(variables); const result = await adminsToUser.executeMutation(variables);
if (result.error) { if (result.error) {
if (result.error.message === ONLY_ONE_ADMIN_ACCOUNT_FOUND) {
return toast.error(t('state.remove_admin_failure_only_one_admin'));
}
toast.error( toast.error(
areMultipleUsersSelected.value id
? t('state.remove_admin_from_users_failure') ? t('state.remove_admin_failure')
: t('state.remove_admin_failure') : t('state.remove_admin_from_users_failure')
); );
} else { } else {
toast.success( toast.success(
areMultipleUsersSelected.value id
? t('state.remove_admin_from_users_success') ? t('state.remove_admin_success')
: t('state.remove_admin_success') : t('state.remove_admin_from_users_success')
); );
usersList.value = usersList.value.map((user) => ({ usersList.value = usersList.value.map((user) => ({
...user, ...user,
@@ -564,7 +562,7 @@ const resetConfirmUserDeletion = () => {
deleteUserUID.value = null; deleteUserUID.value = null;
}; };
const areMultipleUsersSelectedForDeletion = computed( const AreMultipleUsersSelectedForDeletion = computed(
() => selectedRows.value.length > 1 () => selectedRows.value.length > 1
); );
@@ -574,22 +572,45 @@ const deleteUsers = async (id: string | null) => {
const result = await usersDeletion.executeMutation(variables); const result = await usersDeletion.executeMutation(variables);
if (result.error) { if (result.error) {
const errorMessage = areMultipleUsersSelected.value const errorMessage =
? t('state.delete_users_failure') result.error.message === DELETE_USER_FAILED_ONLY_ONE_ADMIN
: t('state.delete_user_failure'); ? t('state.delete_user_failed_only_one_admin')
: id
? t('state.delete_user_failure')
: t('state.delete_users_failure');
toast.error(errorMessage); toast.error(errorMessage);
} else { } else {
const deletedUsers = result.data?.removeUsersByAdmin || []; const deletedUsers = result.data?.removeUsersByAdmin || [];
const deletedUserIDs = deletedUsers const deletedIDs = deletedUsers
.filter((user) => user.isDeleted) .filter((user) => user.isDeleted)
.map((user) => user.userUID); .map((user) => user.userUID);
handleUserDeletion(deletedUsers); const isAdminError = deletedUsers.some(
(user) => user.errorMessage === ADMIN_CANNOT_BE_DELETED
);
usersList.value = usersList.value.filter( usersList.value = usersList.value.filter(
(user) => !deletedUserIDs.includes(user.uid) (user) => !deletedIDs.includes(user.uid)
); );
if (isAdminError) {
toast.success(
t('state.delete_some_users_success', { count: deletedIDs.length })
);
toast.error(
t('state.delete_some_users_failure', {
count: deletedUsers.length - deletedIDs.length,
})
);
toastTimeout = setTimeout(() => {
toast.error(t('state.remove_admin_for_deletion'));
}, 2000);
} else {
toast.success(
id ? t('state.delete_user_success') : t('state.delete_users_success')
);
}
selectedRows.value.splice(0, selectedRows.value.length); selectedRows.value.splice(0, selectedRows.value.length);
} }
confirmUsersDeletion.value = false; confirmUsersDeletion.value = false;

View File

@@ -45,7 +45,7 @@
<template #action="{ item }"> <template #action="{ item }">
<div v-if="item" class="my-1 mr-2"> <div v-if="item" class="my-1 mr-2">
<HoppButtonSecondary <HoppButtonSecondary
v-if="lgAndLarger" v-if="xlAndLarger"
:icon="IconTrash" :icon="IconTrash"
:label="t('users.revoke_invitation')" :label="t('users.revoke_invitation')"
class="text-secondaryDark bg-red-500 hover:bg-red-600" class="text-secondaryDark bg-red-500 hover:bg-red-600"
@@ -119,7 +119,7 @@ const toast = useToast();
const router = useRouter(); const router = useRouter();
const breakpoints = useBreakpoints(breakpointsTailwind); const breakpoints = useBreakpoints(breakpointsTailwind);
const lgAndLarger = breakpoints.greater('lg'); const xlAndLarger = breakpoints.greater('xl');
// Get Proper Date Formats // Get Proper Date Formats
const getCreatedDate = (date: string) => format(new Date(date), 'dd-MM-yyyy'); const getCreatedDate = (date: string) => format(new Date(date), 'dd-MM-yyyy');
@@ -130,8 +130,9 @@ const { fetching, error, data } = useQuery({ query: InvitedUsersDocument });
// Table Headings // Table Headings
const headings = [ const headings = [
{ key: 'adminUid', label: t('users.admin_id') },
{ key: 'adminEmail', label: t('users.admin_email') },
{ key: 'inviteeEmail', label: t('users.invitee_email') }, { key: 'inviteeEmail', label: t('users.invitee_email') },
{ key: 'adminEmail', label: t('users.invited_by') },
{ key: 'invitedOn', label: t('users.invited_on') }, { key: 'invitedOn', label: t('users.invited_on') },
{ key: 'action', label: 'Action' }, { key: 'action', label: 'Action' },
]; ];

565
pnpm-lock.yaml generated
View File

@@ -21,7 +21,7 @@ importers:
version: 16.2.4 version: 16.2.4
'@hoppscotch/ui': '@hoppscotch/ui':
specifier: 0.1.0 specifier: 0.1.0
version: 0.1.0(eslint@8.57.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9) version: 0.1.0(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9)
'@types/node': '@types/node':
specifier: 17.0.27 specifier: 17.0.27
version: 17.0.27 version: 17.0.27
@@ -411,7 +411,7 @@ importers:
version: link:../hoppscotch-js-sandbox version: link:../hoppscotch-js-sandbox
'@hoppscotch/ui': '@hoppscotch/ui':
specifier: 0.1.0 specifier: 0.1.0
version: 0.1.0(eslint@8.57.0)(terser@5.27.0)(vite@4.5.0)(vue@3.3.9) version: 0.1.0(eslint@8.55.0)(terser@5.27.0)(vite@4.5.0)(vue@3.3.9)
'@hoppscotch/vue-toasted': '@hoppscotch/vue-toasted':
specifier: 0.1.0 specifier: 0.1.0
version: 0.1.0(vue@3.3.9) version: 0.1.0(vue@3.3.9)
@@ -678,11 +678,11 @@ importers:
specifier: 21.0.3 specifier: 21.0.3
version: 21.0.3 version: 21.0.3
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: 7.3.1 specifier: 6.13.2
version: 7.3.1(@typescript-eslint/parser@7.3.1)(eslint@8.57.0)(typescript@5.3.2) version: 6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2)
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: 7.3.1 specifier: 6.13.2
version: 7.3.1(eslint@8.57.0)(typescript@5.3.2) version: 6.13.2(eslint@8.55.0)(typescript@5.3.2)
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: 4.5.1 specifier: 4.5.1
version: 4.5.1(vite@4.5.0)(vue@3.3.9) version: 4.5.1(vite@4.5.0)(vue@3.3.9)
@@ -691,7 +691,7 @@ importers:
version: 3.3.10 version: 3.3.10
'@vue/eslint-config-typescript': '@vue/eslint-config-typescript':
specifier: 12.0.0 specifier: 12.0.0
version: 12.0.0(eslint-plugin-vue@9.24.0)(eslint@8.57.0)(typescript@5.3.2) version: 12.0.0(eslint-plugin-vue@9.19.2)(eslint@8.55.0)(typescript@5.3.2)
'@vue/runtime-core': '@vue/runtime-core':
specifier: 3.3.10 specifier: 3.3.10
version: 3.3.10 version: 3.3.10
@@ -705,14 +705,14 @@ importers:
specifier: 16.3.1 specifier: 16.3.1
version: 16.3.1 version: 16.3.1
eslint: eslint:
specifier: 8.57.0 specifier: 8.55.0
version: 8.57.0 version: 8.55.0
eslint-plugin-prettier: eslint-plugin-prettier:
specifier: 5.1.3 specifier: 5.0.1
version: 5.1.3(eslint@8.57.0)(prettier@3.1.0) version: 5.0.1(eslint@8.55.0)(prettier@3.1.0)
eslint-plugin-vue: eslint-plugin-vue:
specifier: 9.24.0 specifier: 9.19.2
version: 9.24.0(eslint@8.57.0) version: 9.19.2(eslint@8.55.0)
glob: glob:
specifier: 10.3.10 specifier: 10.3.10
version: 10.3.10 version: 10.3.10
@@ -757,7 +757,7 @@ importers:
version: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.27.0) version: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.27.0)
vite-plugin-checker: vite-plugin-checker:
specifier: 0.6.2 specifier: 0.6.2
version: 0.6.2(eslint@8.57.0)(typescript@5.3.2)(vite@4.5.0)(vue-tsc@1.8.24) version: 0.6.2(eslint@8.55.0)(typescript@5.3.2)(vite@4.5.0)(vue-tsc@1.8.24)
vite-plugin-fonts: vite-plugin-fonts:
specifier: 0.7.0 specifier: 0.7.0
version: 0.7.0(vite@4.5.0) version: 0.7.0(vite@4.5.0)
@@ -1259,7 +1259,7 @@ importers:
version: 3.1.1(graphql@16.6.0) version: 3.1.1(graphql@16.6.0)
'@hoppscotch/ui': '@hoppscotch/ui':
specifier: 0.1.3 specifier: 0.1.3
version: 0.1.3(eslint@8.57.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9) version: 0.1.3(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9)
'@hoppscotch/vue-toasted': '@hoppscotch/vue-toasted':
specifier: 0.1.0 specifier: 0.1.0
version: 0.1.0(vue@3.3.9) version: 0.1.0(vue@3.3.9)
@@ -3997,15 +3997,6 @@ packages:
eslint: 8.55.0 eslint: 8.55.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
/@eslint-community/eslint-utils@4.4.0(eslint@8.57.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
dependencies:
eslint: 8.57.0
eslint-visitor-keys: 3.4.3
/@eslint-community/regexpp@4.10.0: /@eslint-community/regexpp@4.10.0:
resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -4052,10 +4043,6 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/@eslint/js@8.57.0:
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
/@faker-js/faker@5.5.3: /@faker-js/faker@5.5.3:
resolution: {integrity: sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==} resolution: {integrity: sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==}
dev: false dev: false
@@ -6011,6 +5998,32 @@ packages:
dependencies: dependencies:
graphql: 16.8.1 graphql: 16.8.1
/@hoppscotch/ui@0.1.0(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9):
resolution: {integrity: sha512-+4iHdfO7gRn7l3vpnPcQZbgdA+uE/K1KQX0/eUFcCWvja/C3eSM0db31MRX2cz1KYGwiezzhhVe21mIT4a0CZQ==}
engines: {node: '>=16'}
peerDependencies:
vue: 3.3.9
dependencies:
'@boringer-avatars/vue3': 0.2.1(vue@3.3.9)
'@fontsource-variable/inter': 5.0.15
'@fontsource-variable/material-symbols-rounded': 5.0.16
'@fontsource-variable/roboto-mono': 5.0.16
'@hoppscotch/vue-toasted': 0.1.0(vue@3.3.9)
'@vitejs/plugin-legacy': 2.3.0(terser@5.27.0)(vite@3.2.4)
'@vueuse/core': 8.7.5(vue@3.3.9)
fp-ts: 2.16.2
lodash-es: 4.17.21
path: 0.12.7
vite-plugin-eslint: 1.8.1(eslint@8.55.0)(vite@3.2.4)
vue: 3.3.9(typescript@4.9.5)
vuedraggable-es: 4.1.1(vue@3.3.9)
transitivePeerDependencies:
- '@vue/composition-api'
- eslint
- terser
- vite
dev: true
/@hoppscotch/ui@0.1.0(eslint@8.55.0)(terser@5.27.0)(vite@4.5.0)(vue@3.3.9): /@hoppscotch/ui@0.1.0(eslint@8.55.0)(terser@5.27.0)(vite@4.5.0)(vue@3.3.9):
resolution: {integrity: sha512-+4iHdfO7gRn7l3vpnPcQZbgdA+uE/K1KQX0/eUFcCWvja/C3eSM0db31MRX2cz1KYGwiezzhhVe21mIT4a0CZQ==} resolution: {integrity: sha512-+4iHdfO7gRn7l3vpnPcQZbgdA+uE/K1KQX0/eUFcCWvja/C3eSM0db31MRX2cz1KYGwiezzhhVe21mIT4a0CZQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -6037,59 +6050,7 @@ packages:
- vite - vite
dev: false dev: false
/@hoppscotch/ui@0.1.0(eslint@8.57.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9): /@hoppscotch/ui@0.1.3(eslint@8.55.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9):
resolution: {integrity: sha512-+4iHdfO7gRn7l3vpnPcQZbgdA+uE/K1KQX0/eUFcCWvja/C3eSM0db31MRX2cz1KYGwiezzhhVe21mIT4a0CZQ==}
engines: {node: '>=16'}
peerDependencies:
vue: 3.3.9
dependencies:
'@boringer-avatars/vue3': 0.2.1(vue@3.3.9)
'@fontsource-variable/inter': 5.0.15
'@fontsource-variable/material-symbols-rounded': 5.0.16
'@fontsource-variable/roboto-mono': 5.0.16
'@hoppscotch/vue-toasted': 0.1.0(vue@3.3.9)
'@vitejs/plugin-legacy': 2.3.0(terser@5.27.0)(vite@3.2.4)
'@vueuse/core': 8.7.5(vue@3.3.9)
fp-ts: 2.16.2
lodash-es: 4.17.21
path: 0.12.7
vite-plugin-eslint: 1.8.1(eslint@8.57.0)(vite@3.2.4)
vue: 3.3.9(typescript@4.9.5)
vuedraggable-es: 4.1.1(vue@3.3.9)
transitivePeerDependencies:
- '@vue/composition-api'
- eslint
- terser
- vite
dev: true
/@hoppscotch/ui@0.1.0(eslint@8.57.0)(terser@5.27.0)(vite@4.5.0)(vue@3.3.9):
resolution: {integrity: sha512-+4iHdfO7gRn7l3vpnPcQZbgdA+uE/K1KQX0/eUFcCWvja/C3eSM0db31MRX2cz1KYGwiezzhhVe21mIT4a0CZQ==}
engines: {node: '>=16'}
peerDependencies:
vue: 3.3.9
dependencies:
'@boringer-avatars/vue3': 0.2.1(vue@3.3.9)
'@fontsource-variable/inter': 5.0.15
'@fontsource-variable/material-symbols-rounded': 5.0.16
'@fontsource-variable/roboto-mono': 5.0.16
'@hoppscotch/vue-toasted': 0.1.0(vue@3.3.9)
'@vitejs/plugin-legacy': 2.3.0(terser@5.27.0)(vite@4.5.0)
'@vueuse/core': 8.7.5(vue@3.3.9)
fp-ts: 2.16.2
lodash-es: 4.17.21
path: 0.12.7
vite-plugin-eslint: 1.8.1(eslint@8.57.0)(vite@4.5.0)
vue: 3.3.9(typescript@5.3.2)
vuedraggable-es: 4.1.1(vue@3.3.9)
transitivePeerDependencies:
- '@vue/composition-api'
- eslint
- terser
- vite
dev: false
/@hoppscotch/ui@0.1.3(eslint@8.57.0)(terser@5.27.0)(vite@3.2.4)(vue@3.3.9):
resolution: {integrity: sha512-a1dmqqL+zS2P6cxkCBLdBtd+mD+MnCDSN63TrCPldW5W92rtqpeZ0bmGgiQlzfA2457JRktYpVCBR0Oc0J1jbA==} resolution: {integrity: sha512-a1dmqqL+zS2P6cxkCBLdBtd+mD+MnCDSN63TrCPldW5W92rtqpeZ0bmGgiQlzfA2457JRktYpVCBR0Oc0J1jbA==}
engines: {node: '>=16'} engines: {node: '>=16'}
peerDependencies: peerDependencies:
@@ -6105,7 +6066,7 @@ packages:
fp-ts: 2.16.2 fp-ts: 2.16.2
lodash-es: 4.17.21 lodash-es: 4.17.21
path: 0.12.7 path: 0.12.7
vite-plugin-eslint: 1.8.1(eslint@8.57.0)(vite@3.2.4) vite-plugin-eslint: 1.8.1(eslint@8.55.0)(vite@3.2.4)
vue: 3.3.9(typescript@4.9.3) vue: 3.3.9(typescript@4.9.3)
vuedraggable-es: 4.1.1(vue@3.3.9) vuedraggable-es: 4.1.1(vue@3.3.9)
transitivePeerDependencies: transitivePeerDependencies:
@@ -7533,11 +7494,6 @@ packages:
requiresBuild: true requiresBuild: true
optional: true optional: true
/@pkgr/core@0.1.1:
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
dev: true
/@pkgr/utils@2.4.2: /@pkgr/utils@2.4.2:
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -8850,64 +8806,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 6.13.2(eslint@8.57.0)(typescript@5.3.2)
'@typescript-eslint/scope-manager': 6.13.2
'@typescript-eslint/type-utils': 6.13.2(eslint@8.57.0)(typescript@5.3.2)
'@typescript-eslint/utils': 6.13.2(eslint@8.57.0)(typescript@5.3.2)
'@typescript-eslint/visitor-keys': 6.13.2
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.0
natural-compare: 1.4.0
semver: 7.5.4
ts-api-utils: 1.0.2(typescript@5.3.2)
typescript: 5.3.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/eslint-plugin@7.3.1(@typescript-eslint/parser@7.3.1)(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
'@typescript-eslint/parser': ^7.0.0
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.3.1(eslint@8.57.0)(typescript@5.3.2)
'@typescript-eslint/scope-manager': 7.3.1
'@typescript-eslint/type-utils': 7.3.1(eslint@8.57.0)(typescript@5.3.2)
'@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.3.2)
'@typescript-eslint/visitor-keys': 7.3.1
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.0
natural-compare: 1.4.0
semver: 7.6.0
ts-api-utils: 1.0.2(typescript@5.3.2)
typescript: 5.3.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@5.30.6(eslint@8.19.0)(typescript@4.9.5): /@typescript-eslint/parser@5.30.6(eslint@8.19.0)(typescript@4.9.5):
resolution: {integrity: sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==} resolution: {integrity: sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -8989,48 +8887,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser@6.13.2(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 6.13.2
'@typescript-eslint/types': 6.13.2
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
'@typescript-eslint/visitor-keys': 6.13.2
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
typescript: 5.3.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@7.3.1(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 7.3.1
'@typescript-eslint/types': 7.3.1
'@typescript-eslint/typescript-estree': 7.3.1(typescript@5.3.2)
'@typescript-eslint/visitor-keys': 7.3.1
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
typescript: 5.3.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/scope-manager@5.30.6: /@typescript-eslint/scope-manager@5.30.6:
resolution: {integrity: sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==} resolution: {integrity: sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -9063,14 +8919,6 @@ packages:
'@typescript-eslint/visitor-keys': 6.13.2 '@typescript-eslint/visitor-keys': 6.13.2
dev: true dev: true
/@typescript-eslint/scope-manager@7.3.1:
resolution: {integrity: sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag==}
engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
'@typescript-eslint/types': 7.3.1
'@typescript-eslint/visitor-keys': 7.3.1
dev: true
/@typescript-eslint/type-utils@5.30.6(eslint@8.19.0)(typescript@4.9.5): /@typescript-eslint/type-utils@5.30.6(eslint@8.19.0)(typescript@4.9.5):
resolution: {integrity: sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==} resolution: {integrity: sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -9150,46 +8998,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/type-utils@6.13.2(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
'@typescript-eslint/utils': 6.13.2(eslint@8.57.0)(typescript@5.3.2)
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
ts-api-utils: 1.0.2(typescript@5.3.2)
typescript: 5.3.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/type-utils@7.3.1(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 7.3.1(typescript@5.3.2)
'@typescript-eslint/utils': 7.3.1(eslint@8.57.0)(typescript@5.3.2)
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
ts-api-utils: 1.0.2(typescript@5.3.2)
typescript: 5.3.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/types@5.30.6: /@typescript-eslint/types@5.30.6:
resolution: {integrity: sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==} resolution: {integrity: sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -9210,11 +9018,6 @@ packages:
engines: {node: ^16.0.0 || >=18.0.0} engines: {node: ^16.0.0 || >=18.0.0}
dev: true dev: true
/@typescript-eslint/types@7.3.1:
resolution: {integrity: sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw==}
engines: {node: ^18.18.0 || >=20.0.0}
dev: true
/@typescript-eslint/typescript-estree@5.30.6(typescript@4.9.5): /@typescript-eslint/typescript-estree@5.30.6(typescript@4.9.5):
resolution: {integrity: sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==} resolution: {integrity: sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -9299,28 +9102,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/typescript-estree@7.3.1(typescript@5.3.2):
resolution: {integrity: sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 7.3.1
'@typescript-eslint/visitor-keys': 7.3.1
debug: 4.3.4(supports-color@9.2.2)
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.3
semver: 7.6.0
ts-api-utils: 1.0.2(typescript@5.3.2)
typescript: 5.3.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/utils@5.30.6(eslint@8.19.0)(typescript@4.9.5): /@typescript-eslint/utils@5.30.6(eslint@8.19.0)(typescript@4.9.5):
resolution: {integrity: sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==} resolution: {integrity: sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -9398,44 +9179,6 @@ packages:
- typescript - typescript
dev: true dev: true
/@typescript-eslint/utils@6.13.2(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 6.13.2
'@typescript-eslint/types': 6.13.2
'@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2)
eslint: 8.57.0
semver: 7.5.4
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@typescript-eslint/utils@7.3.1(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
eslint: ^8.56.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 7.3.1
'@typescript-eslint/types': 7.3.1
'@typescript-eslint/typescript-estree': 7.3.1(typescript@5.3.2)
eslint: 8.57.0
semver: 7.6.0
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@typescript-eslint/visitor-keys@5.30.6: /@typescript-eslint/visitor-keys@5.30.6:
resolution: {integrity: sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==} resolution: {integrity: sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -9468,14 +9211,6 @@ packages:
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
dev: true dev: true
/@typescript-eslint/visitor-keys@7.3.1:
resolution: {integrity: sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw==}
engines: {node: ^18.18.0 || >=20.0.0}
dependencies:
'@typescript-eslint/types': 7.3.1
eslint-visitor-keys: 3.4.3
dev: true
/@ungap/promise-all-settled@1.1.2: /@ungap/promise-all-settled@1.1.2:
resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==}
dev: true dev: true
@@ -9960,27 +9695,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@vue/eslint-config-typescript@12.0.0(eslint-plugin-vue@9.24.0)(eslint@8.57.0)(typescript@5.3.2):
resolution: {integrity: sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
eslint-plugin-vue: ^9.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.57.0)(typescript@5.3.2)
'@typescript-eslint/parser': 6.13.2(eslint@8.57.0)(typescript@5.3.2)
eslint: 8.57.0
eslint-plugin-vue: 9.24.0(eslint@8.57.0)
typescript: 5.3.2
vue-eslint-parser: 9.3.1(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
dev: true
/@vue/language-core@1.8.24(typescript@5.3.2): /@vue/language-core@1.8.24(typescript@5.3.2):
resolution: {integrity: sha512-2ClHvij0WlsDWryPzXJCSpPc6rusZFNoVtRZGgGGkKCmKuIREDDKmH8j+1tYyxPYyH0qL6pZ6+IHD8KIm5nWAw==} resolution: {integrity: sha512-2ClHvij0WlsDWryPzXJCSpPc6rusZFNoVtRZGgGGkKCmKuIREDDKmH8j+1tYyxPYyH0qL6pZ6+IHD8KIm5nWAw==}
peerDependencies: peerDependencies:
@@ -13440,6 +13154,26 @@ packages:
prettier-linter-helpers: 1.0.0 prettier-linter-helpers: 1.0.0
dev: true dev: true
/eslint-plugin-prettier@5.0.1(eslint@8.55.0)(prettier@3.1.0):
resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
eslint: '>=8.0.0'
eslint-config-prettier: '*'
prettier: '>=3.0.0'
peerDependenciesMeta:
'@types/eslint':
optional: true
eslint-config-prettier:
optional: true
dependencies:
eslint: 8.55.0
prettier: 3.1.0
prettier-linter-helpers: 1.0.0
synckit: 0.8.5
dev: true
/eslint-plugin-prettier@5.0.1(eslint@8.55.0)(prettier@3.2.5): /eslint-plugin-prettier@5.0.1(eslint@8.55.0)(prettier@3.2.5):
resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
@@ -13460,26 +13194,6 @@ packages:
synckit: 0.8.5 synckit: 0.8.5
dev: true dev: true
/eslint-plugin-prettier@5.1.3(eslint@8.57.0)(prettier@3.1.0):
resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
eslint: '>=8.0.0'
eslint-config-prettier: '*'
prettier: '>=3.0.0'
peerDependenciesMeta:
'@types/eslint':
optional: true
eslint-config-prettier:
optional: true
dependencies:
eslint: 8.57.0
prettier: 3.1.0
prettier-linter-helpers: 1.0.0
synckit: 0.8.8
dev: true
/eslint-plugin-vue@9.17.0(eslint@8.47.0): /eslint-plugin-vue@9.17.0(eslint@8.47.0):
resolution: {integrity: sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==} resolution: {integrity: sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}
@@ -13516,25 +13230,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/eslint-plugin-vue@9.24.0(eslint@8.57.0):
resolution: {integrity: sha512-9SkJMvF8NGMT9aQCwFc5rj8Wo1XWSMSHk36i7ZwdI614BU7sIOR28ZjuFPKp8YGymZN12BSEbiSwa7qikp+PBw==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
eslint: 8.57.0
globals: 13.24.0
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 6.0.16
semver: 7.6.0
vue-eslint-parser: 9.4.2(eslint@8.57.0)
xml-name-validator: 4.0.0
transitivePeerDependencies:
- supports-color
dev: true
/eslint-scope@5.1.1: /eslint-scope@5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
@@ -13775,52 +13470,6 @@ packages:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
/eslint@8.57.0:
resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@eslint-community/regexpp': 4.10.0
'@eslint/eslintrc': 2.1.4
'@eslint/js': 8.57.0
'@humanwhocodes/config-array': 0.11.14
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
'@ungap/structured-clone': 1.2.0
ajv: 6.12.6
chalk: 4.1.2
cross-spawn: 7.0.3
debug: 4.3.4(supports-color@9.2.2)
doctrine: 3.0.0
escape-string-regexp: 4.0.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.5.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
file-entry-cache: 6.0.1
find-up: 5.0.0
glob-parent: 6.0.2
globals: 13.24.0
graphemer: 1.4.0
ignore: 5.3.0
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
js-yaml: 4.1.0
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.3
strip-ansi: 6.0.1
text-table: 0.2.0
transitivePeerDependencies:
- supports-color
/espree@6.2.1: /espree@6.2.1:
resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==} resolution: {integrity: sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@@ -19396,14 +19045,6 @@ packages:
cssesc: 3.0.0 cssesc: 3.0.0
util-deprecate: 1.0.2 util-deprecate: 1.0.2
/postcss-selector-parser@6.0.16:
resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: true
/postcss-value-parser@4.2.0: /postcss-value-parser@4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
@@ -20571,14 +20212,6 @@ packages:
dependencies: dependencies:
lru-cache: 6.0.0 lru-cache: 6.0.0
/semver@7.6.0:
resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/send@0.18.0: /send@0.18.0:
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -21385,14 +21018,6 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: true dev: true
/synckit@0.8.8:
resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==}
engines: {node: ^14.18.0 || >=16.0.0}
dependencies:
'@pkgr/core': 0.1.1
tslib: 2.6.2
dev: true
/systemjs@6.14.2: /systemjs@6.14.2:
resolution: {integrity: sha512-1TlOwvKWdXxAY9vba+huLu99zrQURDWA8pUTYsRIYDZYQbGyK+pyEP4h4dlySsqo7ozyJBmYD20F+iUHhAltEg==} resolution: {integrity: sha512-1TlOwvKWdXxAY9vba+huLu99zrQURDWA8pUTYsRIYDZYQbGyK+pyEP4h4dlySsqo7ozyJBmYD20F+iUHhAltEg==}
@@ -22954,7 +22579,7 @@ packages:
- terser - terser
dev: true dev: true
/vite-plugin-checker@0.6.2(eslint@8.57.0)(typescript@5.3.2)(vite@4.5.0)(vue-tsc@1.8.24): /vite-plugin-checker@0.6.2(eslint@8.55.0)(typescript@5.3.2)(vite@4.5.0)(vue-tsc@1.8.24):
resolution: {integrity: sha512-YvvvQ+IjY09BX7Ab+1pjxkELQsBd4rPhWNw8WLBeFVxu/E7O+n6VYAqNsKdK/a2luFlX/sMpoWdGFfg4HvwdJQ==} resolution: {integrity: sha512-YvvvQ+IjY09BX7Ab+1pjxkELQsBd4rPhWNw8WLBeFVxu/E7O+n6VYAqNsKdK/a2luFlX/sMpoWdGFfg4HvwdJQ==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
peerDependencies: peerDependencies:
@@ -22990,7 +22615,7 @@ packages:
chalk: 4.1.2 chalk: 4.1.2
chokidar: 3.5.3 chokidar: 3.5.3
commander: 8.3.0 commander: 8.3.0
eslint: 8.57.0 eslint: 8.55.0
fast-glob: 3.3.2 fast-glob: 3.3.2
fs-extra: 11.1.1 fs-extra: 11.1.1
lodash.debounce: 4.0.8 lodash.debounce: 4.0.8
@@ -23008,7 +22633,7 @@ packages:
vue-tsc: 1.8.24(typescript@5.3.2) vue-tsc: 1.8.24(typescript@5.3.2)
dev: true dev: true
/vite-plugin-eslint@1.8.1(eslint@8.55.0)(vite@4.5.0): /vite-plugin-eslint@1.8.1(eslint@8.55.0)(vite@3.2.4):
resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==}
peerDependencies: peerDependencies:
eslint: '>=7' eslint: '>=7'
@@ -23018,22 +22643,9 @@ packages:
'@types/eslint': 8.56.2 '@types/eslint': 8.56.2
eslint: 8.55.0 eslint: 8.55.0
rollup: 2.79.1 rollup: 2.79.1
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.27.0)
dev: false
/vite-plugin-eslint@1.8.1(eslint@8.57.0)(vite@3.2.4):
resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==}
peerDependencies:
eslint: '>=7'
vite: '>=2'
dependencies:
'@rollup/pluginutils': 4.2.1
'@types/eslint': 8.56.2
eslint: 8.57.0
rollup: 2.79.1
vite: 3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0) vite: 3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0)
/vite-plugin-eslint@1.8.1(eslint@8.57.0)(vite@4.5.0): /vite-plugin-eslint@1.8.1(eslint@8.55.0)(vite@4.5.0):
resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==}
peerDependencies: peerDependencies:
eslint: '>=7' eslint: '>=7'
@@ -23041,7 +22653,7 @@ packages:
dependencies: dependencies:
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
'@types/eslint': 8.56.2 '@types/eslint': 8.56.2
eslint: 8.57.0 eslint: 8.55.0
rollup: 2.79.1 rollup: 2.79.1
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.27.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.27.0)
dev: false dev: false
@@ -23761,7 +23373,7 @@ packages:
espree: 9.6.1 espree: 9.6.1
esquery: 1.5.0 esquery: 1.5.0
lodash: 4.17.21 lodash: 4.17.21
semver: 7.6.0 semver: 7.5.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@@ -23779,43 +23391,7 @@ packages:
espree: 9.6.1 espree: 9.6.1
esquery: 1.5.0 esquery: 1.5.0
lodash: 4.17.21 lodash: 4.17.21
semver: 7.6.0 semver: 7.5.4
transitivePeerDependencies:
- supports-color
dev: true
/vue-eslint-parser@9.3.1(eslint@8.57.0):
resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '>=6.0.0'
dependencies:
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.5.0
lodash: 4.17.21
semver: 7.6.0
transitivePeerDependencies:
- supports-color
dev: true
/vue-eslint-parser@9.4.2(eslint@8.57.0):
resolution: {integrity: sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '>=6.0.0'
dependencies:
debug: 4.3.4(supports-color@9.2.2)
eslint: 8.57.0
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.5.0
lodash: 4.17.21
semver: 7.6.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@@ -24967,3 +24543,4 @@ packages:
/zod@3.22.4: /zod@3.22.4:
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
dev: false dev: false