;
+};
+
+export type RequestReport = {
+ path: string;
+ tests: TestReport[];
+ errors: HoppCLIError[];
+ result: boolean;
+};
diff --git a/packages/hoppscotch-cli/src/types/response.ts b/packages/hoppscotch-cli/src/types/response.ts
new file mode 100644
index 000000000..6f54a228a
--- /dev/null
+++ b/packages/hoppscotch-cli/src/types/response.ts
@@ -0,0 +1,28 @@
+import { TestReport } from "../interfaces/response";
+import { HoppEnvs } from "./request";
+
+/**
+ * The expectation failed (fail) or errored (error)
+ */
+export type ExpectResult = {
+ status: "pass" | "fail" | "error";
+ message: string;
+};
+
+export type TestMetrics = {
+ /**
+ * Total passed and failed test-cases.
+ */
+ tests: { failing: number; passing: number };
+
+ /**
+ * Total test-blocks/test-suites passed & failed, calculated
+ * based on test-cases failed/passed with in each test-block.
+ */
+ testSuites: { failing: number; passing: number };
+};
+
+export type TestRunnerRes = {
+ envs: HoppEnvs;
+ testsReport: TestReport[];
+};
diff --git a/packages/hoppscotch-cli/src/utils/checks.ts b/packages/hoppscotch-cli/src/utils/checks.ts
new file mode 100644
index 000000000..658195f47
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/checks.ts
@@ -0,0 +1,155 @@
+import fs from "fs/promises";
+import { join } from "path";
+import { pipe } from "fp-ts/function";
+import {
+ HoppCollection,
+ HoppRESTRequest,
+ isHoppRESTRequest,
+} from "@hoppscotch/data";
+import * as A from "fp-ts/Array";
+import * as S from "fp-ts/string";
+import * as TE from "fp-ts/TaskEither";
+import { error, HoppCLIError, HoppErrnoException } from "../types/errors";
+import { CommanderError } from "commander";
+
+/**
+ * Determines whether an object has a property with given name.
+ * @param target Object to be checked for given property.
+ * @param prop Property to be checked in target object.
+ * @returns True, if property exists in target object; False, otherwise.
+ */
+export const hasProperty = (
+ target: object,
+ prop: P
+): target is Record
=> 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 the given file path exists and is of JSON type.
+ * @param path The input file path to check.
+ * @returns Absolute path for valid file path OR HoppCLIError in case of error.
+ */
+export const checkFilePath = (
+ path: string
+): TE.TaskEither =>
+ pipe(
+ path,
+
+ /**
+ * Check the path type and returns string if passes else HoppCLIError.
+ */
+ TE.fromPredicate(S.isString, () => error({ code: "NO_FILE_PATH" })),
+
+ /**
+ * Trying to access given file path.
+ * If successfully accessed, we return the path from predicate step.
+ * Else return HoppCLIError with code FILE_NOT_FOUND.
+ */
+ TE.chainFirstW(
+ TE.tryCatchK(
+ () => pipe(path, join, fs.access),
+ () => error({ code: "FILE_NOT_FOUND", path: path })
+ )
+ ),
+
+ /**
+ * On successfully accessing given file path, we map file path to
+ * absolute path and return abs file path if file is JSON type.
+ */
+ TE.map(join),
+ TE.chainW(
+ TE.fromPredicate(S.endsWith(".json"), (absPath) =>
+ error({ code: "FILE_NOT_JSON", path: absPath })
+ )
+ )
+ );
+
+/**
+ * Checks if given error data is of type HoppCLIError, based on existence
+ * of code property.
+ * @param error Error data to check.
+ * @returns True, if unknown error validates to be HoppCLIError;
+ * False, otherwise.
+ */
+export const isHoppCLIError = (error: unknown): error is HoppCLIError => {
+ return (
+ !!error &&
+ typeof error === "object" &&
+ hasProperty(error, "code") &&
+ typeof error.code === "string"
+ );
+};
+
+/**
+ * Checks if given error data is of type HoppErrnoException, based on existence
+ * of name property.
+ * @param error Error data to check.
+ * @returns True, if unknown error validates to be HoppErrnoException;
+ * False, otherwise.
+ */
+export const isHoppErrnoException = (
+ error: unknown
+): error is HoppErrnoException => {
+ return (
+ !!error &&
+ typeof error === "object" &&
+ hasProperty(error, "name") &&
+ typeof error.name === "string"
+ );
+};
+
+/**
+ * Check whether given unknown error is instance of commander-error and
+ * has zero exit code (which we consider as safe error).
+ * @param error Error data to check.
+ * @returns True, if error data validates to be safe-commander-error;
+ * False, otherwise.
+ */
+export const isSafeCommanderError = (
+ error: unknown
+): error is CommanderError => {
+ return error instanceof CommanderError && error.exitCode === 0;
+};
diff --git a/packages/hoppscotch-cli/src/utils/collections.ts b/packages/hoppscotch-cli/src/utils/collections.ts
new file mode 100644
index 000000000..69b6e92a1
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/collections.ts
@@ -0,0 +1,129 @@
+import * as T from "fp-ts/Task";
+import * as A from "fp-ts/Array";
+import { pipe } from "fp-ts/function";
+import { bold } from "chalk";
+import { log } from "console";
+import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
+import { HoppEnvs, CollectionStack, RequestReport } from "../types/request";
+import { preProcessRequest, processRequest } from "./request";
+import { exceptionColors } from "./getters";
+import { TestReport } from "../interfaces/response";
+import {
+ printErrorsReport,
+ printFailedTestsReport,
+ printTestsMetrics,
+} from "./display";
+const { WARN, FAIL } = exceptionColors;
+
+/**
+ * Processes each requests within collections to prints details of subsequent requests,
+ * tests and to display complete errors-report, failed-tests-report and test-metrics.
+ * @param collections Array of hopp-collection with hopp-requests to be processed.
+ * @returns List of report for each processed request.
+ */
+export const collectionsRunner =
+ (collections: HoppCollection[]): T.Task =>
+ async () => {
+ const envs: HoppEnvs = { global: [], selected: [] };
+ const requestsReport: RequestReport[] = [];
+ const collectionStack: CollectionStack[] = getCollectionStack(collections);
+
+ while (collectionStack.length) {
+ // Pop out top-most collection from stack to be processed.
+ const { collection, path } = collectionStack.pop();
+
+ // Processing each request in collection
+ for (const request of collection.requests) {
+ const _request = preProcessRequest(request);
+ const requestPath = `${path}/${_request.name}`;
+
+ // Request processing initiated message.
+ log(WARN(`\nRunning: ${bold(requestPath)}`));
+
+ // Processing current request.
+ const result = await processRequest(_request, envs, requestPath)();
+
+ // Updating global & selected envs with new envs from processed-request output.
+ const { global, selected } = result.envs;
+ envs.global = global;
+ envs.selected = selected;
+
+ // Storing current request's report.
+ const requestReport = result.report;
+ requestsReport.push(requestReport);
+ }
+
+ // Pushing remaining folders realted collection to stack.
+ for (const folder of collection.folders) {
+ collectionStack.push({
+ path: `${path}/${folder.name}`,
+ collection: folder,
+ });
+ }
+ }
+
+ return requestsReport;
+ };
+
+/**
+ * Transforms collections to generate collection-stack which describes each collection's
+ * path within collection & the collection itself.
+ * @param collections Hopp-collection objects to be mapped to collection-stack type.
+ * @returns Mapped collections to collection-stack.
+ */
+const getCollectionStack = (
+ collections: HoppCollection[]
+): CollectionStack[] =>
+ pipe(
+ collections,
+ A.map(
+ (collection) => { collection, path: collection.name }
+ )
+ );
+
+/**
+ * Prints collection-runner-report using test-metrics data in table format.
+ * @param requestsReport Provides data for each request-report which includes
+ * failed-tests-report, errors
+ * @returns True, if collection runner executed without any errors or failed test-cases.
+ * False, if errors occured or test-cases failed.
+ */
+export const collectionsRunnerResult = (
+ requestsReport: RequestReport[]
+): boolean => {
+ const testsReport: TestReport[] = [];
+ let finalResult = true;
+
+ // Printing requests-report details of failed-tests and errors
+ for (const requestReport of requestsReport) {
+ const { path, tests, errors, result } = requestReport;
+
+ finalResult = finalResult && result;
+
+ printFailedTestsReport(path, tests);
+
+ printErrorsReport(path, errors);
+
+ testsReport.push.apply(testsReport, tests);
+ }
+
+ printTestsMetrics(testsReport);
+
+ return finalResult;
+};
+
+/**
+ * Exiting hopp cli process with appropriate exit code depending on
+ * collections-runner result.
+ * If result is true, we exit the cli process with code 0.
+ * Else, exit with code 1.
+ * @param result Boolean defining the collections-runner result.
+ */
+export const collectionsRunnerExit = (result: boolean) => {
+ if (!result) {
+ const EXIT_MSG = FAIL(`\nExited with code 1`);
+ process.stdout.write(EXIT_MSG);
+ process.exit(1);
+ }
+ process.exit(0);
+};
diff --git a/packages/hoppscotch-cli/src/utils/constants.ts b/packages/hoppscotch-cli/src/utils/constants.ts
new file mode 100644
index 000000000..ce717a52d
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/constants.ts
@@ -0,0 +1,7 @@
+import { ResponseErrorPair } from "../interfaces/response";
+
+export const responseErrors: ResponseErrorPair = {
+ 501: "REQUEST NOT SUPPORTED",
+ 408: "NETWORK TIMEOUT",
+ 400: "BAD REQUEST",
+} as const;
diff --git a/packages/hoppscotch-cli/src/utils/display.ts b/packages/hoppscotch-cli/src/utils/display.ts
new file mode 100644
index 000000000..2c22ff0a1
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/display.ts
@@ -0,0 +1,145 @@
+import { bold } from "chalk";
+import { groupEnd, group, log } from "console";
+import { handleError } from "../handlers/error";
+import { RequestConfig } from "../interfaces/request";
+import { RequestRunnerResponse, TestReport } from "../interfaces/response";
+import { HoppCLIError } from "../types/errors";
+import { exceptionColors, getColorStatusCode } from "./getters";
+import {
+ getFailedExpectedResults,
+ getFailedTestsReport,
+ getTestMetrics,
+} from "./test";
+const { FAIL, SUCCESS, BG_INFO } = exceptionColors;
+
+/**
+ * Prints test-suites in pretty-way describing each test-suites failed/passed
+ * status.
+ * @param testsReport Providing details of each test-suites with tests-report.
+ */
+export const printTestSuitesReport = (testsReport: TestReport[]) => {
+ group();
+ for (const testReport of testsReport) {
+ const { failing, descriptor } = testReport;
+
+ if (failing > 0) {
+ log(`${FAIL("✖")} ${descriptor}`);
+ } else {
+ log(`${SUCCESS("✔")} ${descriptor}`);
+ }
+ }
+ groupEnd();
+};
+
+/**
+ * Prints total number of test-cases and test-suites passed/failed.
+ * @param testsReport Provides testSuites and testCases metrics.
+ */
+export const printTestsMetrics = (testsReport: TestReport[]) => {
+ const { testSuites, tests } = getTestMetrics(testsReport);
+
+ const failedTestCasesOut = FAIL(`${tests.failing} failing`);
+ const passedTestCasesOut = SUCCESS(`${tests.passing} passing`);
+ const testCasesOut = `Test Cases: ${failedTestCasesOut} ${passedTestCasesOut}\n`;
+
+ const failedTestSuitesOut = FAIL(`${testSuites.failing} failing`);
+ const passedTestSuitesOut = SUCCESS(`${testSuites.passing} passing`);
+ const testSuitesOut = `Test Suites: ${failedTestSuitesOut} ${passedTestSuitesOut}\n`;
+
+ const message = `\n${testCasesOut}${testSuitesOut}`;
+ process.stdout.write(message);
+};
+
+/**
+ * Prints details of each reported error for a request with error code.
+ * @param path Request's path in collection for which errors occured.
+ * @param errorsReport List of errors reported.
+ */
+export const printErrorsReport = (
+ path: string,
+ errorsReport: HoppCLIError[]
+) => {
+ if (errorsReport.length > 0) {
+ const REPORTED_ERRORS_TITLE = FAIL(`\n${bold(path)} reported errors:`);
+
+ group(REPORTED_ERRORS_TITLE);
+ for (const errorReport of errorsReport) {
+ handleError(errorReport);
+ }
+ groupEnd();
+ }
+};
+
+/**
+ * Prints details of each failed tests for given request's path.
+ * @param path Request's path in collection for which tests-failed.
+ * @param testsReport Overall tests-report including failed-tests-report.
+ */
+export const printFailedTestsReport = (
+ path: string,
+ testsReport: TestReport[]
+) => {
+ const failedTestsReport = getFailedTestsReport(testsReport);
+
+ // Only printing test-reports with failing test-cases.
+ if (failedTestsReport.length > 0) {
+ const FAILED_TESTS_PATH = FAIL(`\n${bold(path)} failed tests:`);
+ group(FAILED_TESTS_PATH);
+
+ for (const failedTestReport of failedTestsReport) {
+ const { descriptor, expectResults } = failedTestReport;
+ const failedExpectResults = getFailedExpectedResults(expectResults);
+
+ // Only printing failed expected-results.
+ if (failedExpectResults.length > 0) {
+ group("⦁", descriptor);
+
+ for (const failedExpectResult of failedExpectResults) {
+ log(FAIL("-"), failedExpectResult.message);
+ }
+
+ groupEnd();
+ }
+ }
+
+ groupEnd();
+ }
+};
+
+/**
+ * Provides methods for printing request-runner's state messages.
+ */
+export const printRequestRunner = {
+ // Request-runner starting message.
+ start: (requestConfig: RequestConfig) => {
+ const METHOD = BG_INFO(` ${requestConfig.method} `);
+ const ENDPOINT = requestConfig.url;
+
+ process.stdout.write(`${METHOD} ${ENDPOINT}`);
+ },
+
+ // Prints response's status, when request-runner executes successfully.
+ success: (requestResponse: RequestRunnerResponse) => {
+ const { status, statusText } = requestResponse;
+ const statusMsg = getColorStatusCode(status, statusText);
+
+ process.stdout.write(` ${statusMsg}\n`);
+ },
+
+ // Prints error message, when request-runner fails to execute.
+ fail: () => log(FAIL(" ERROR\n⚠ Error running request.")),
+};
+
+/**
+ * Provides methods for printing test-runner's state messages.
+ */
+export const printTestRunner = {
+ fail: () => log(FAIL("⚠ Error running test-script.")),
+};
+
+/**
+ * Provides methods for printing pre-request-runner's state messages.
+ */
+export const printPreRequestRunner = {
+ fail: () => log(FAIL("⚠ Error running pre-request-script.")),
+};
diff --git a/packages/hoppscotch-cli/src/utils/functions/array.ts b/packages/hoppscotch-cli/src/utils/functions/array.ts
new file mode 100644
index 000000000..4a5a07280
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/functions/array.ts
@@ -0,0 +1,37 @@
+import { clone } from "lodash";
+
+/**
+ * Sorts the array based on the sort func.
+ * NOTE: Creates a new array, if you don't need ref
+ * to original array, use `arrayUnsafeSort` for better perf
+ * @param sortFunc Sort function to sort against
+ */
+export const arraySort =
+ (sortFunc: (a: T, b: T) => number) =>
+ (arr: T[]) => {
+ const newArr = clone(arr);
+
+ newArr.sort(sortFunc);
+
+ return newArr;
+ };
+
+/**
+ * Equivalent to `Array.prototype.flatMap`.
+ * @param mapFunc The map function.
+ * @returns Array formed by applying given mapFunc.
+ */
+export const arrayFlatMap =
+ (mapFunc: (value: T, index: number, arr: T[]) => U[]) =>
+ (arr: T[]) =>
+ arr.flatMap(mapFunc);
+
+export const tupleToRecord = <
+ KeyType extends string | number | symbol,
+ ValueType
+>(
+ tuples: [KeyType, ValueType][]
+): Record =>
+ tuples.length > 0
+ ? (Object.assign as any)(...tuples.map(([key, val]) => ({ [key]: val })))
+ : {};
diff --git a/packages/hoppscotch-cli/src/utils/getters.ts b/packages/hoppscotch-cli/src/utils/getters.ts
new file mode 100644
index 000000000..514d2a314
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/getters.ts
@@ -0,0 +1,113 @@
+import {
+ HoppRESTHeader,
+ Environment,
+ parseTemplateStringE,
+ HoppRESTParam,
+} from "@hoppscotch/data";
+import chalk from "chalk";
+import { pipe } from "fp-ts/function";
+import * as A from "fp-ts/Array";
+import * as E from "fp-ts/Either";
+import * as S from "fp-ts/string";
+import * as O from "fp-ts/Option";
+import { error } from "../types/errors";
+
+/**
+ * Generates template string (status + statusText) with specific color unicodes
+ * based on type of status.
+ * @param status Status code of a HTTP response.
+ * @param statusText Status text of a HTTP response.
+ * @returns Template string with related color unicodes.
+ */
+export const getColorStatusCode = (
+ status: number | string,
+ statusText: string
+): string => {
+ const statusCode = `${status == 0 ? "Error" : status} : ${statusText}`;
+
+ if (status.toString().startsWith("2")) {
+ return chalk.greenBright(statusCode);
+ } else if (status.toString().startsWith("3")) {
+ return chalk.yellowBright(statusCode);
+ }
+
+ return chalk.redBright(statusCode);
+};
+
+/**
+ * Replaces all template-string with their effective ENV values to generate effective
+ * request headers/parameters meta-data.
+ * @param metaData Headers/parameters on which ENVs will be applied.
+ * @param environment Provides ENV variables for parsing template-string.
+ * @returns Active, non-empty-key, parsed headers/parameters pairs.
+ */
+export const getEffectiveFinalMetaData = (
+ metaData: HoppRESTHeader[] | HoppRESTParam[],
+ environment: Environment
+) =>
+ pipe(
+ metaData,
+
+ /**
+ * Selecting only non-empty and active pairs.
+ */
+ A.filter(({ key, active }) => !S.isEmpty(key) && active),
+ A.map(({ key, value }) => ({
+ active: true,
+ key: parseTemplateStringE(key, environment.variables),
+ value: parseTemplateStringE(value, environment.variables),
+ })),
+ E.fromPredicate(
+ /**
+ * Check if every key-value is right either. Else return HoppCLIError with
+ * appropriate reason.
+ */
+ A.every(({ key, value }) => E.isRight(key) && E.isRight(value)),
+ (reason) => error({ code: "PARSING_ERROR", data: reason })
+ ),
+ E.map(
+ /**
+ * Filtering and mapping only right-eithers for each key-value as [string, string].
+ */
+ A.filterMap(({ key, value }) =>
+ E.isRight(key) && E.isRight(value)
+ ? O.some({ active: true, key: key.right, value: value.right })
+ : O.none
+ )
+ )
+ );
+
+/**
+ * Reduces array of HoppRESTParam or HoppRESTHeader to unique key-value
+ * pair.
+ * @param metaData Array of meta-data to reduce.
+ * @returns Object with unique key-value pair.
+ */
+export const getMetaDataPairs = (
+ metaData: HoppRESTParam[] | HoppRESTHeader[]
+) =>
+ pipe(
+ metaData,
+
+ // Excluding non-active & empty key request meta-data.
+ A.filter(({ active, key }) => active && !S.isEmpty(key)),
+
+ // Reducing array of request-meta-data to key-value pair object.
+ A.reduce(>{}, (target, { key, value }) =>
+ Object.assign(target, { [`${key}`]: value })
+ )
+ );
+
+/**
+ * Object providing aliases for chalk color properties based on exceptions.
+ */
+export const exceptionColors = {
+ WARN: chalk.yellow,
+ INFO: chalk.blue,
+ FAIL: chalk.red,
+ SUCCESS: chalk.green,
+ BG_WARN: chalk.bgYellow,
+ BG_FAIL: chalk.bgRed,
+ BG_INFO: chalk.bgBlue,
+ BG_SUCCESS: chalk.bgGreen,
+};
diff --git a/packages/hoppscotch-cli/src/utils/mutators.ts b/packages/hoppscotch-cli/src/utils/mutators.ts
new file mode 100644
index 000000000..eb145d881
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/mutators.ts
@@ -0,0 +1,80 @@
+import fs from "fs/promises";
+import * as E from "fp-ts/Either";
+import * as TE from "fp-ts/TaskEither";
+import * as A from "fp-ts/Array";
+import * as J from "fp-ts/Json";
+import { pipe } from "fp-ts/function";
+import { FormDataEntry } from "../types/request";
+import { error, HoppCLIError } from "../types/errors";
+import { isRESTCollection, isHoppErrnoException } from "./checks";
+import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
+
+/**
+ * Parses array of FormDataEntry to FormData.
+ * @param values Array of FormDataEntry.
+ * @returns FormData with key-value pair from FormDataEntry.
+ */
+export const toFormData = (values: FormDataEntry[]) => {
+ const formData = new FormData();
+
+ values.forEach(({ key, value }) => formData.append(key, value));
+
+ return formData;
+};
+
+/**
+ * Parses provided error message to maintain hopp-error messages.
+ * @param e Custom error data.
+ * @returns Parsed error message without extra spaces.
+ */
+export const parseErrorMessage = (e: unknown) => {
+ let msg: string;
+ if (isHoppErrnoException(e)) {
+ msg = e.message.replace(e.code! + ":", "").replace("error:", "");
+ } else if (typeof e === "string") {
+ msg = e;
+ } else {
+ msg = JSON.stringify(e);
+ }
+ return msg.replace(/\n+$|\s{2,}/g, "").trim();
+};
+
+/**
+ * Parses collection json file for given path:context.path, and validates
+ * the parsed collectiona array.
+ * @param path Collection json file path.
+ * @returns For successful parsing we get array of HoppCollection,
+ */
+export const parseCollectionData = (
+ path: string
+): TE.TaskEither[]> =>
+ pipe(
+ // Trying to read give collection json path.
+ TE.tryCatch(
+ () => pipe(path, fs.readFile),
+ (reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) })
+ ),
+
+ // Checking if parsed file data is array.
+ TE.chainEitherKW((data) =>
+ pipe(
+ data.toString(),
+ J.parse,
+ E.map((jsonData) => (Array.isArray(jsonData) ? jsonData : [jsonData])),
+ E.mapLeft((e) =>
+ error({ code: "MALFORMED_COLLECTION", path, data: E.toError(e) })
+ )
+ )
+ ),
+
+ // Validating collections to be HoppRESTCollection.
+ TE.chainW(
+ TE.fromPredicate(A.every(isRESTCollection), () =>
+ error({
+ code: "MALFORMED_COLLECTION",
+ path,
+ data: "Please check the collection data.",
+ })
+ )
+ )
+ );
diff --git a/packages/hoppscotch-cli/src/utils/pre-request.ts b/packages/hoppscotch-cli/src/utils/pre-request.ts
new file mode 100644
index 000000000..fa2bc76bb
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/pre-request.ts
@@ -0,0 +1,268 @@
+import {
+ Environment,
+ HoppRESTRequest,
+ parseBodyEnvVariablesE,
+ parseRawKeyValueEntriesE,
+ parseTemplateString,
+ parseTemplateStringE,
+} from "@hoppscotch/data";
+import { runPreRequestScript } from "@hoppscotch/js-sandbox";
+import { flow, pipe } from "fp-ts/function";
+import * as TE from "fp-ts/TaskEither";
+import * as E from "fp-ts/Either";
+import * as RA from "fp-ts/ReadonlyArray";
+import * as A from "fp-ts/Array";
+import * as O from "fp-ts/Option";
+import * as S from "fp-ts/string";
+import qs from "qs";
+import { EffectiveHoppRESTRequest } from "../interfaces/request";
+import { error, HoppCLIError } from "../types/errors";
+import { HoppEnvs } from "../types/request";
+import { isHoppCLIError } from "./checks";
+import { tupleToRecord, arraySort, arrayFlatMap } from "./functions/array";
+import { toFormData } from "./mutators";
+import { getEffectiveFinalMetaData } from "./getters";
+
+/**
+ * Runs pre-request-script runner over given request which extracts set ENVs and
+ * applies them on current request to generate updated request.
+ * @param request HoppRESTRequest to be converted to EffectiveHoppRESTRequest.
+ * @param envs Environment variables related to request.
+ * @returns EffectiveHoppRESTRequest that includes parsed ENV variables with in
+ * request OR HoppCLIError with error code and related information.
+ */
+export const preRequestScriptRunner = (
+ request: HoppRESTRequest,
+ envs: HoppEnvs
+): TE.TaskEither =>
+ pipe(
+ TE.of(request),
+ TE.chain(({ preRequestScript }) =>
+ runPreRequestScript(preRequestScript, envs)
+ ),
+ TE.map(
+ ({ selected, global }) =>
+ { name: "Env", variables: [...selected, ...global] }
+ ),
+ TE.chainEitherKW((env) => getEffectiveRESTRequest(request, env)),
+ TE.mapLeft((reason) =>
+ isHoppCLIError(reason)
+ ? reason
+ : error({
+ code: "PRE_REQUEST_SCRIPT_ERROR",
+ data: reason,
+ })
+ )
+ );
+
+/**
+ * Outputs an executable request format with environment variables applied
+ *
+ * @param request The request to source from
+ * @param environment The environment to apply
+ *
+ * @returns An object with extra fields defining a complete request
+ */
+export function getEffectiveRESTRequest(
+ request: HoppRESTRequest,
+ environment: Environment
+): E.Either {
+ const envVariables = environment.variables;
+
+ // Parsing final headers with applied ENVs.
+ const _effectiveFinalHeaders = getEffectiveFinalMetaData(
+ request.headers,
+ environment
+ );
+ if (E.isLeft(_effectiveFinalHeaders)) {
+ return _effectiveFinalHeaders;
+ }
+ const effectiveFinalHeaders = _effectiveFinalHeaders.right;
+
+ // Parsing final parameters with applied ENVs.
+ const _effectiveFinalParams = getEffectiveFinalMetaData(
+ request.params,
+ environment
+ );
+ if (E.isLeft(_effectiveFinalParams)) {
+ return _effectiveFinalParams;
+ }
+ const effectiveFinalParams = _effectiveFinalParams.right;
+
+ // Authentication
+ if (request.auth.authActive) {
+ // TODO: Support a better b64 implementation than btoa ?
+ if (request.auth.authType === "basic") {
+ const username = parseTemplateString(request.auth.username, envVariables);
+ const password = parseTemplateString(request.auth.password, envVariables);
+
+ effectiveFinalHeaders.push({
+ active: true,
+ key: "Authorization",
+ value: `Basic ${btoa(`${username}:${password}`)}`,
+ });
+ } else if (
+ request.auth.authType === "bearer" ||
+ request.auth.authType === "oauth-2"
+ ) {
+ effectiveFinalHeaders.push({
+ active: true,
+ key: "Authorization",
+ value: `Bearer ${parseTemplateString(
+ request.auth.token,
+ envVariables
+ )}`,
+ });
+ } else if (request.auth.authType === "api-key") {
+ const { key, value, addTo } = request.auth;
+ if (addTo === "Headers") {
+ effectiveFinalHeaders.push({
+ active: true,
+ key: parseTemplateString(key, envVariables),
+ value: parseTemplateString(value, envVariables),
+ });
+ } else if (addTo === "Query params") {
+ effectiveFinalParams.push({
+ active: true,
+ key: parseTemplateString(key, envVariables),
+ value: parseTemplateString(value, envVariables),
+ });
+ }
+ }
+ }
+
+ // Parsing final-body with applied ENVs.
+ const _effectiveFinalBody = getFinalBodyFromRequest(request, envVariables);
+ if (E.isLeft(_effectiveFinalBody)) {
+ return _effectiveFinalBody;
+ }
+ const effectiveFinalBody = _effectiveFinalBody.right;
+
+ if (request.body.contentType)
+ effectiveFinalHeaders.push({
+ active: true,
+ key: "content-type",
+ value: request.body.contentType,
+ });
+
+ // Parsing final-endpoint with applied ENVs.
+ const _effectiveFinalURL = parseTemplateStringE(
+ request.endpoint,
+ envVariables
+ );
+ if (E.isLeft(_effectiveFinalURL)) {
+ return E.left(
+ error({
+ code: "PARSING_ERROR",
+ data: `${request.endpoint} (${_effectiveFinalURL.left})`,
+ })
+ );
+ }
+ const effectiveFinalURL = _effectiveFinalURL.right;
+
+ return E.right({
+ ...request,
+ effectiveFinalURL,
+ effectiveFinalHeaders,
+ effectiveFinalParams,
+ effectiveFinalBody,
+ });
+}
+
+/**
+ * Replaces template variables in request's body from the given set of ENVs,
+ * to generate final request body without any template variables.
+ * @param request Provides request's body, on which ENVs has to be applied.
+ * @param envVariables Provides set of key-value pairs (environment variables),
+ * used to parse-out template variables.
+ * @returns Final request body without any template variables as value.
+ * Or, HoppCLIError in case of error while parsing.
+ */
+function getFinalBodyFromRequest(
+ request: HoppRESTRequest,
+ envVariables: Environment["variables"]
+): E.Either {
+ if (request.body.contentType === null) {
+ return E.right(null);
+ }
+
+ if (request.body.contentType === "application/x-www-form-urlencoded") {
+ return pipe(
+ request.body.body,
+ parseRawKeyValueEntriesE,
+ E.map(
+ flow(
+ RA.toArray,
+
+ /**
+ * Filtering out empty keys and non-active pairs.
+ */
+ A.filter(({ active, key }) => active && !S.isEmpty(key)),
+
+ /**
+ * Mapping each key-value to template-string-parser with either on array,
+ * which will be resolved in further steps.
+ */
+ A.map(({ key, value }) => [
+ parseTemplateStringE(key, envVariables),
+ parseTemplateStringE(value, envVariables),
+ ]),
+
+ /**
+ * Filtering and mapping only right-eithers for each key-value as [string, string].
+ */
+ A.filterMap(([key, value]) =>
+ E.isRight(key) && E.isRight(value)
+ ? O.some([key.right, value.right] as [string, string])
+ : O.none
+ ),
+ tupleToRecord,
+ qs.stringify
+ )
+ ),
+ E.mapLeft((e) => error({ code: "PARSING_ERROR", data: e.message }))
+ );
+ }
+
+ if (request.body.contentType === "multipart/form-data") {
+ return pipe(
+ request.body.body,
+ A.filter((x) => x.key !== "" && x.active), // Remove empty keys
+
+ // Sort files down
+ arraySort((a, b) => {
+ if (a.isFile) return 1;
+ if (b.isFile) return -1;
+ return 0;
+ }),
+
+ // FormData allows only a single blob in an entry,
+ // we split array blobs into separate entries (FormData will then join them together during exec)
+ arrayFlatMap((x) =>
+ x.isFile
+ ? x.value.map((v) => ({
+ key: parseTemplateString(x.key, envVariables),
+ value: v as string | Blob,
+ }))
+ : [
+ {
+ key: parseTemplateString(x.key, envVariables),
+ value: parseTemplateString(x.value, envVariables),
+ },
+ ]
+ ),
+ toFormData,
+ E.right
+ );
+ }
+
+ return pipe(
+ parseBodyEnvVariablesE(request.body.body, envVariables),
+ E.mapLeft((e) =>
+ error({
+ code: "PARSING_ERROR",
+ data: `${request.body.body} (${e})`,
+ })
+ )
+ );
+}
diff --git a/packages/hoppscotch-cli/src/utils/request.ts b/packages/hoppscotch-cli/src/utils/request.ts
new file mode 100644
index 000000000..ece74eb70
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/request.ts
@@ -0,0 +1,321 @@
+import axios, { Method } from "axios";
+import { URL } from "url";
+import * as S from "fp-ts/string";
+import * as A from "fp-ts/Array";
+import * as T from "fp-ts/Task";
+import * as E from "fp-ts/Either";
+import * as TE from "fp-ts/TaskEither";
+import { HoppRESTRequest } from "@hoppscotch/data";
+import { responseErrors } from "./constants";
+import { getMetaDataPairs } from "./getters";
+import { testRunner, getTestScriptParams, hasFailedTestCases } from "./test";
+import { RequestConfig, EffectiveHoppRESTRequest } from "../interfaces/request";
+import { RequestRunnerResponse } from "../interfaces/response";
+import { preRequestScriptRunner } from "./pre-request";
+import { HoppEnvs, RequestReport } from "../types/request";
+import {
+ printPreRequestRunner,
+ printRequestRunner,
+ printTestRunner,
+ printTestSuitesReport,
+} from "./display";
+import { error, HoppCLIError } from "../types/errors";
+
+// !NOTE: The `config.supported` checks are temporary until OAuth2 and Multipart Forms are supported
+
+/**
+ * Transforms given request data to request-config used by request-runner to
+ * perform HTTP request.
+ * @param req Effective request data with parsed ENVs.
+ * @returns Request config with data realted to HTTP request.
+ */
+export const createRequest = (req: EffectiveHoppRESTRequest): RequestConfig => {
+ const config: RequestConfig = {
+ supported: true,
+ };
+ const { finalBody, finalEndpoint, finalHeaders, finalParams } = getRequest;
+ const reqParams = finalParams(req);
+ const reqHeaders = finalHeaders(req);
+ config.url = finalEndpoint(req);
+ config.method = req.method as Method;
+ config.params = getMetaDataPairs(reqParams);
+ config.headers = getMetaDataPairs(reqHeaders);
+ if (req.auth.authActive) {
+ switch (req.auth.authType) {
+ case "oauth-2": {
+ // TODO: OAuth2 Request Parsing
+ // !NOTE: Temporary `config.supported` check
+ config.supported = false;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ if (req.body.contentType) {
+ config.headers["Content-Type"] = req.body.contentType;
+ switch (req.body.contentType) {
+ case "multipart/form-data": {
+ // TODO: Parse Multipart Form Data
+ // !NOTE: Temporary `config.supported` check
+ config.supported = false;
+ break;
+ }
+ default: {
+ config.data = finalBody(req);
+ break;
+ }
+ }
+ }
+
+ return config;
+};
+
+/**
+ * Performs http request using axios with given requestConfig axios
+ * parameters.
+ * @param requestConfig The axios request config.
+ * @returns If successfully ran, we get runner-response including HTTP response data.
+ * Else, HoppCLIError with appropriate error code & data.
+ */
+export const requestRunner =
+ (
+ requestConfig: RequestConfig
+ ): TE.TaskEither =>
+ async () => {
+ try {
+ // NOTE: Temporary parsing check for request endpoint.
+ requestConfig.url = new URL(requestConfig.url ?? "").toString();
+
+ let status: number;
+ const baseResponse = await axios(requestConfig);
+ const { config } = baseResponse;
+ const runnerResponse: RequestRunnerResponse = {
+ ...baseResponse,
+ endpoint: getRequest.endpoint(config.url),
+ method: getRequest.method(config.method),
+ body: baseResponse.data,
+ };
+
+ // !NOTE: Temporary `config.supported` check
+ if ((config as RequestConfig).supported === false) {
+ status = 501;
+ runnerResponse.status = status;
+ runnerResponse.statusText = responseErrors[status];
+ }
+
+ return E.right(runnerResponse);
+ } catch (e) {
+ let status: number;
+ const runnerResponse: RequestRunnerResponse = {
+ endpoint: "",
+ method: "GET",
+ body: {},
+ statusText: responseErrors[400],
+ status: 400,
+ headers: [],
+ };
+
+ if (axios.isAxiosError(e)) {
+ runnerResponse.endpoint = e.config.url ?? "";
+
+ if (e.response) {
+ const { data, status, statusText, headers } = e.response;
+ runnerResponse.body = data;
+ runnerResponse.statusText = statusText;
+ runnerResponse.status = status;
+ runnerResponse.headers = headers;
+ } else if ((e.config as RequestConfig).supported === false) {
+ status = 501;
+ runnerResponse.status = status;
+ runnerResponse.statusText = responseErrors[status];
+ } else if (e.request) {
+ return E.left(error({ code: "REQUEST_ERROR", data: E.toError(e) }));
+ }
+
+ return E.right(runnerResponse);
+ }
+
+ return E.left(error({ code: "REQUEST_ERROR", data: E.toError(e) }));
+ }
+ };
+
+/**
+ * Getter object methods for request-runner.
+ */
+const getRequest = {
+ method: (value: string | undefined) =>
+ value ? (value.toUpperCase() as Method) : "GET",
+
+ endpoint: (value: string | undefined): string => (value ? value : ""),
+
+ finalEndpoint: (req: EffectiveHoppRESTRequest): string =>
+ S.isEmpty(req.effectiveFinalURL) ? req.endpoint : req.effectiveFinalURL,
+
+ finalHeaders: (req: EffectiveHoppRESTRequest) =>
+ A.isNonEmpty(req.effectiveFinalHeaders)
+ ? req.effectiveFinalHeaders
+ : req.headers,
+
+ finalParams: (req: EffectiveHoppRESTRequest) =>
+ A.isNonEmpty(req.effectiveFinalParams)
+ ? req.effectiveFinalParams
+ : req.params,
+
+ finalBody: (req: EffectiveHoppRESTRequest) =>
+ req.effectiveFinalBody ? req.effectiveFinalBody : req.body.body,
+};
+
+/**
+ * Processes given request, which includes executing pre-request-script,
+ * running request & executing test-script.
+ * @param request Request to be processed.
+ * @param envs Global + selected envs used by requests with in collection.
+ * @returns Updated envs and current request's report.
+ */
+export const processRequest =
+ (
+ request: HoppRESTRequest,
+ envs: HoppEnvs,
+ path: string
+ ): T.Task<{ envs: HoppEnvs; report: RequestReport }> =>
+ async () => {
+ // Initialising updatedEnvs with given parameter envs, will eventually get updated.
+ const result = {
+ envs: envs,
+ report: {},
+ };
+
+ // Initial value for current request's report with default values for properties.
+ const report: RequestReport = {
+ path: path,
+ tests: [],
+ errors: [],
+ result: true,
+ };
+
+ // Initial value for effective-request with default values for properties.
+ let effectiveRequest = {
+ ...request,
+ effectiveFinalBody: null,
+ effectiveFinalHeaders: [],
+ effectiveFinalParams: [],
+ effectiveFinalURL: "",
+ };
+
+ // Executing pre-request-script
+ const preRequestRes = await preRequestScriptRunner(request, envs)();
+ if (E.isLeft(preRequestRes)) {
+ printPreRequestRunner.fail();
+
+ // Updating report for errors & current result
+ report.errors.push(preRequestRes.left);
+ report.result = report.result && false;
+ } else {
+ // Updating effective-request
+ effectiveRequest = preRequestRes.right;
+ }
+
+ // Creating request-config for request-runner.
+ const requestConfig = createRequest(effectiveRequest);
+
+ printRequestRunner.start(requestConfig);
+
+ // Default value for request-runner's response.
+ let _requestRunnerRes: RequestRunnerResponse = {
+ endpoint: "",
+ method: "GET",
+ headers: [],
+ status: 400,
+ statusText: "",
+ body: Object(null),
+ };
+ // Executing request-runner.
+ const requestRunnerRes = await requestRunner(requestConfig)();
+ if (E.isLeft(requestRunnerRes)) {
+ // Updating report for errors & current result
+ report.errors.push(requestRunnerRes.left);
+ report.result = report.result && false;
+
+ printRequestRunner.fail();
+ } else {
+ _requestRunnerRes = requestRunnerRes.right;
+ printRequestRunner.success(_requestRunnerRes);
+ }
+
+ // Extracting test-script-runner parameters.
+ const testScriptParams = getTestScriptParams(
+ _requestRunnerRes,
+ request,
+ envs
+ );
+
+ // Executing test-runner.
+ const testRunnerRes = await testRunner(testScriptParams)();
+ if (E.isLeft(testRunnerRes)) {
+ printTestRunner.fail();
+
+ // Updating report with current errors & result.
+ report.errors.push(testRunnerRes.left);
+ report.result = report.result && false;
+ } else {
+ const { envs, testsReport } = testRunnerRes.right;
+ const _hasFailedTestCases = hasFailedTestCases(testsReport);
+
+ // Updating report with current tests & result.
+ report.tests = testsReport;
+ report.result = report.result && _hasFailedTestCases;
+
+ // Updating resulting envs from test-runner.
+ result.envs = envs;
+
+ printTestSuitesReport(testsReport);
+ }
+
+ result.report = report;
+
+ return result;
+ };
+
+/**
+ * Generates new request without any missing/invalid data using
+ * current request object.
+ * @param request Hopp rest request to be processed.
+ * @returns Updated request object free of invalid/missing data.
+ */
+export const preProcessRequest = (
+ request: HoppRESTRequest
+): HoppRESTRequest => {
+ const tempRequest = Object.assign({}, request);
+ if (!tempRequest.v) {
+ tempRequest.v = "1";
+ }
+ if (!tempRequest.name) {
+ tempRequest.name = "Untitled Request";
+ }
+ if (!tempRequest.method) {
+ tempRequest.method = "GET";
+ }
+ if (!tempRequest.endpoint) {
+ tempRequest.endpoint = "";
+ }
+ if (!tempRequest.params) {
+ tempRequest.params = [];
+ }
+ if (!tempRequest.headers) {
+ tempRequest.headers = [];
+ }
+ if (!tempRequest.preRequestScript) {
+ tempRequest.preRequestScript = "";
+ }
+ if (!tempRequest.testScript) {
+ tempRequest.testScript = "";
+ }
+ if (!tempRequest.auth) {
+ tempRequest.auth = { authActive: false, authType: "none" };
+ }
+ if (!tempRequest.body) {
+ tempRequest.body = { contentType: null, body: null };
+ }
+ return tempRequest;
+};
diff --git a/packages/hoppscotch-cli/src/utils/test.ts b/packages/hoppscotch-cli/src/utils/test.ts
new file mode 100644
index 000000000..22a034bcd
--- /dev/null
+++ b/packages/hoppscotch-cli/src/utils/test.ts
@@ -0,0 +1,197 @@
+import { HoppRESTRequest } from "@hoppscotch/data";
+import { execTestScript, TestDescriptor } from "@hoppscotch/js-sandbox";
+import { flow, pipe } from "fp-ts/function";
+import * as RA from "fp-ts/ReadonlyArray";
+import * as A from "fp-ts/Array";
+import * as TE from "fp-ts/TaskEither";
+import * as T from "fp-ts/Task";
+import {
+ RequestRunnerResponse,
+ TestReport,
+ TestScriptParams,
+} from "../interfaces/response";
+import { error, HoppCLIError } from "../types/errors";
+import { HoppEnvs } from "../types/request";
+import { ExpectResult, TestMetrics, TestRunnerRes } from "../types/response";
+
+/**
+ * Executes test script and runs testDescriptorParser to generate test-report using
+ * expected-results, test-status & test-descriptor.
+ * @param testScriptData Parameters related to test-script function.
+ * @returns If executes successfully, we get TestRunnerRes(updated ENVs + test-reports).
+ * Else, HoppCLIError with appropriate code & data.
+ */
+export const testRunner = (
+ testScriptData: TestScriptParams
+): TE.TaskEither =>
+ pipe(
+ /**
+ * Executing test-script.
+ */
+ TE.of(testScriptData),
+ TE.chain(({ testScript, response, envs }) =>
+ execTestScript(testScript, envs, response)
+ ),
+
+ /**
+ * Recursively parsing test-results using test-descriptor-parser
+ * to generate test-reports.
+ */
+ TE.chainTaskK(({ envs, tests }) =>
+ pipe(
+ tests,
+ A.map(testDescriptorParser),
+ T.sequenceArray,
+ T.map(
+ flow(
+ RA.flatten,
+ RA.toArray,
+ (testsReport) => { envs, testsReport }
+ )
+ )
+ )
+ ),
+ TE.mapLeft((e) =>
+ error({
+ code: "TEST_SCRIPT_ERROR",
+ data: e,
+ })
+ )
+ );
+
+/**
+ * Recursive function to parse test-descriptor from nested-children and
+ * generate tests-report.
+ * @param testDescriptor Object with details of test-descriptor.
+ * @returns Flattened array of TestReport parsed from TestDescriptor.
+ */
+export const testDescriptorParser = (
+ testDescriptor: TestDescriptor
+): T.Task =>
+ pipe(
+ /**
+ * Generate single TestReport from given testDescriptor.
+ */
+ testDescriptor,
+ ({ expectResults, descriptor }) =>
+ A.isNonEmpty(expectResults)
+ ? pipe(
+ expectResults,
+ A.reduce({ failing: 0, passing: 0 }, (prev, { status }) =>
+ /**
+ * Incrementing number of passed test-cases if status is "pass",
+ * else, incrementing number of failed test-cases.
+ */
+ status === "pass"
+ ? { failing: prev.failing, passing: prev.passing + 1 }
+ : { failing: prev.failing + 1, passing: prev.passing }
+ ),
+ ({ failing, passing }) =>
+ {
+ failing,
+ passing,
+ descriptor,
+ expectResults,
+ },
+ Array.of
+ )
+ : [],
+ T.of,
+
+ /**
+ * Recursive call to testDescriptorParser on testDescriptor's children.
+ * The result is concated with previous testReport.
+ */
+ T.chain((testReport) =>
+ pipe(
+ testDescriptor.children,
+ A.map(testDescriptorParser),
+ T.sequenceArray,
+ T.map(flow(RA.flatten, RA.toArray, A.concat(testReport)))
+ )
+ )
+ );
+
+/**
+ * Extracts parameter object from request-runner's response, request and envs
+ * for test-runner.
+ * @param reqRunnerRes Provides response data.
+ * @param request Provides test-script data.
+ * @param envs Current ENVs state with-in collections-runner.
+ * @returns Object to be passed as parameter for test-runner
+ */
+export const getTestScriptParams = (
+ reqRunnerRes: RequestRunnerResponse,
+ request: HoppRESTRequest,
+ envs: HoppEnvs
+) => {
+ const testScriptParams: TestScriptParams = {
+ testScript: request.testScript,
+ response: {
+ body: reqRunnerRes.body,
+ status: reqRunnerRes.status,
+ headers: reqRunnerRes.headers,
+ },
+ envs: envs,
+ };
+ return testScriptParams;
+};
+
+/**
+ * Combines quantitative details (test-cases passed/failed) of each test-report
+ * to generate TestMetrics object with total test-cases & total test-suites.
+ * @param testsReport Contains details of each test-report (failed/passed test-cases).
+ * @returns Object containing details of total test-cases passed/failed and
+ * total test-suites passed/failed.
+ */
+export const getTestMetrics = (testsReport: TestReport[]): TestMetrics =>
+ testsReport.reduce(
+ ({ testSuites, tests }, testReport) => ({
+ tests: {
+ failing: tests.failing + testReport.failing,
+ passing: tests.passing + testReport.passing,
+ },
+ testSuites: {
+ failing: testSuites.failing + (testReport.failing > 0 ? 1 : 0),
+ passing: testSuites.passing + (testReport.failing === 0 ? 1 : 0),
+ },
+ }),
+ {
+ tests: { failing: 0, passing: 0 },
+ testSuites: { failing: 0, passing: 0 },
+ }
+ );
+
+/**
+ * Filters tests-report containing atleast one or more failed test-cases.
+ * @param testsReport Provides "failing" test-cases data.
+ * @returns Tests report with one or more test-cases failing.
+ */
+export const getFailedTestsReport = (testsReport: TestReport[]) =>
+ pipe(
+ testsReport,
+ A.filter(({ failing }) => failing > 0)
+ );
+
+/**
+ * Filters expected-results containing which has status as "fail" or "error".
+ * @param expectResults Provides "status" data for each expected result.
+ * @returns Expected results with "fail" or "error" status.
+ */
+export const getFailedExpectedResults = (expectResults: ExpectResult[]) =>
+ pipe(
+ expectResults,
+ A.filter(({ status }) => status !== "pass")
+ );
+
+/**
+ * Checks if any of the tests-report have failed test-cases.
+ * @param testsReport Provides "failing" test-cases data.
+ * @returns True, if one or more failed test-cases found.
+ * False, if all test-cases passed.
+ */
+export const hasFailedTestCases = (testsReport: TestReport[]) =>
+ pipe(
+ testsReport,
+ A.every(({ failing }) => failing === 0)
+ );
diff --git a/packages/hoppscotch-cli/tsconfig.json b/packages/hoppscotch-cli/tsconfig.json
new file mode 100644
index 000000000..53d804a35
--- /dev/null
+++ b/packages/hoppscotch-cli/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES6",
+ "module": "commonjs",
+ "outDir": ".",
+ "rootDir": ".",
+ "strict": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "composite": true
+ },
+ "files": ["package.json"]
+}
diff --git a/packages/hoppscotch-cli/tsup.config.ts b/packages/hoppscotch-cli/tsup.config.ts
new file mode 100644
index 000000000..f3ac47715
--- /dev/null
+++ b/packages/hoppscotch-cli/tsup.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: [ "./src/index.ts" ],
+ outDir: "./dist/",
+ format: ["cjs"],
+ platform: "node",
+ sourcemap: true,
+ bundle: true,
+ target: "node12",
+ skipNodeModulesBundle: false,
+ esbuildOptions(options) {
+ options.bundle = true
+ },
+ noExternal: [
+ /\w+/
+ ],
+ clean: true,
+});
diff --git a/packages/hoppscotch-js-sandbox/jest.config.js b/packages/hoppscotch-js-sandbox/jest.config.js
index d183c8a9d..6fe7cc21a 100644
--- a/packages/hoppscotch-js-sandbox/jest.config.js
+++ b/packages/hoppscotch-js-sandbox/jest.config.js
@@ -1,4 +1,4 @@
-export default {
+module.exports = {
preset: "ts-jest",
testEnvironment: "node",
collectCoverage: true,
diff --git a/packages/hoppscotch-js-sandbox/package.json b/packages/hoppscotch-js-sandbox/package.json
index 9ebf9de92..9aa81af68 100644
--- a/packages/hoppscotch-js-sandbox/package.json
+++ b/packages/hoppscotch-js-sandbox/package.json
@@ -3,19 +3,25 @@
"version": "2.0.0",
"description": "JavaScript sandboxes for running external scripts used by Hoppscotch clients",
"main": "./lib/index.js",
+ "module": "./lib/index.mjs",
+ "type": "commonjs",
+ "exports": {
+ ".": {
+ "require": "./lib/index.js",
+ "default": "./lib/index.mjs"
+ }
+ },
"types": "./lib/",
- "type": "module",
"engines": {
"node": ">=14",
"pnpm": ">=3"
},
"scripts": {
- "demo": "esrun src/demo.ts",
"lint": "eslint --ext .ts,.js --ignore-path .gitignore .",
"lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .",
- "test": "npx jest",
- "build": "npx tsc",
- "clean": "npx tsc --build --clean",
+ "test": "pnpx jest",
+ "build": "pnpx tsup",
+ "clean": "pnpx tsc --build --clean",
"postinstall": "pnpm run build",
"prepublish": "pnpm run build",
"do-lint": "pnpm run lint",
@@ -37,7 +43,8 @@
"@hoppscotch/data": "workspace:^0.4.0",
"fp-ts": "^2.11.9",
"lodash": "^4.17.21",
- "quickjs-emscripten": "^0.15.0"
+ "quickjs-emscripten": "^0.15.0",
+ "tsup": "^5.11.13"
},
"devDependencies": {
"@digitak/esrun": "^3.1.2",
diff --git a/packages/hoppscotch-js-sandbox/src/demo.ts b/packages/hoppscotch-js-sandbox/src/demo.ts
deleted file mode 100644
index 2652010f8..000000000
--- a/packages/hoppscotch-js-sandbox/src/demo.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { runTestScript } from "./index"
-import { TestResponse } from "./test-runner"
-
-const dummyResponse: TestResponse = {
- status: 200,
- body: "hoi",
- headers: [],
-}
-// eslint-disable-next-line prettier/prettier
-;(async () => {
- console.dir(
- await runTestScript(
- `
- pw.test("Arithmetic operations and toBe", () => {
- const size = 500 + 500;
- pw.expect(size).toBe(1000);
- pw.expect(size - 500).toBe(500);
- pw.expect(size * 4).toBe(4000);
- pw.expect(size / 4).toBe(250);
- });
- pw.test("toBeLevelxxx", () => {
- pw.expect(200).toBeLevel2xx();
- pw.expect(204).toBeLevel2xx();
- pw.expect(300).not.toBeLevel2xx();
- pw.expect(300).toBeLevel3xx();
- pw.expect(304).toBeLevel3xx();
- pw.expect(204).not.toBeLevel3xx();
- pw.expect(401).toBeLevel4xx();
- pw.expect(404).toBeLevel4xx();
- pw.expect(204).not.toBeLevel4xx();
- pw.expect(501).toBeLevel5xx();
- pw.expect(504).toBeLevel5xx();
- pw.expect(204).not.toBeLevel5xx();
- });
- pw.test("toBeType", () => {
- pw.expect("hello").toBeType("string");
- pw.expect(10).toBeType("number");
- pw.expect(true).toBeType("boolean");
- pw.expect("deffonotanumber").not.toBeType("number");
- });
- pw.test("toHaveLength", () => {
- const arr = [1, 2, 3];
- pw.expect(arr).toHaveLength(3);
- pw.expect(arr).not.toHaveLength(4);
- });
- `,
- dummyResponse
- ),
- {
- depth: 100,
- }
- )
-})()
diff --git a/packages/hoppscotch-js-sandbox/src/index.ts b/packages/hoppscotch-js-sandbox/src/index.ts
index 766d31f1d..5d4449e34 100644
--- a/packages/hoppscotch-js-sandbox/src/index.ts
+++ b/packages/hoppscotch-js-sandbox/src/index.ts
@@ -3,11 +3,14 @@ import * as TE from "fp-ts/TaskEither"
import { execPreRequestScript } from "./preRequest"
import {
execTestScript,
- TestResponse,
+ TestResponse as _TestResponse,
TestDescriptor as _TestDescriptor,
TestResult,
} from "./test-runner"
+export * from "./test-runner"
+
+export type TestResponse = _TestResponse
export type TestDescriptor = _TestDescriptor
export type SandboxTestResult = TestResult & { tests: TestDescriptor }
diff --git a/packages/hoppscotch-js-sandbox/tsconfig.json b/packages/hoppscotch-js-sandbox/tsconfig.json
index 8eb23a6d1..ff918da2d 100644
--- a/packages/hoppscotch-js-sandbox/tsconfig.json
+++ b/packages/hoppscotch-js-sandbox/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "ES6",
- "module": "ESNext",
+ "module": "CommonJS",
"moduleResolution": "Node",
"skipLibCheck": true,
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
@@ -19,5 +19,5 @@
"sourceMap": true
},
"include": ["./src", "./src/global.d.ts"],
- "exclude": ["node_modules", "./src/__tests__", "./src/demo.ts"]
+ "exclude": ["node_modules", "./src/__tests__"]
}
diff --git a/packages/hoppscotch-js-sandbox/tsup.config.ts b/packages/hoppscotch-js-sandbox/tsup.config.ts
new file mode 100644
index 000000000..483eeabea
--- /dev/null
+++ b/packages/hoppscotch-js-sandbox/tsup.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from "tsup"
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "./lib/",
+ format: ["esm", "cjs"],
+ dts: true,
+ splitting: true,
+ sourcemap: true,
+ clean: true,
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 469c49320..f2aba61c2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -343,6 +343,42 @@ importers:
vue-template-babel-compiler: 1.1.3
worker-loader: 3.0.8
+ packages/hoppscotch-cli:
+ specifiers:
+ '@hoppscotch/data': workspace:^0.4.0
+ '@hoppscotch/js-sandbox': workspace:^2.0.0
+ '@swc/core': ^1.2.160
+ '@types/axios': ^0.14.0
+ '@types/chalk': ^2.2.0
+ '@types/commander': ^2.12.2
+ axios: ^0.21.4
+ chalk: ^4.1.1
+ commander: ^8.0.0
+ esm: ^3.2.25
+ fp-ts: ^2.11.3
+ lodash: ^4.17.21
+ prettier: ^2.5.1
+ qs: ^6.10.3
+ tsup: ^5.11.13
+ typescript: ^4.3.5
+ devDependencies:
+ '@hoppscotch/data': link:../hoppscotch-data
+ '@hoppscotch/js-sandbox': link:../hoppscotch-js-sandbox
+ '@swc/core': 1.2.160
+ '@types/axios': 0.14.0
+ '@types/chalk': 2.2.0
+ '@types/commander': 2.12.2
+ axios: 0.21.4
+ chalk: 4.1.2
+ commander: 8.3.0
+ esm: 3.2.25
+ fp-ts: 2.11.9
+ lodash: 4.17.21
+ prettier: 2.6.0
+ qs: 6.10.3
+ tsup: 5.12.1_typescript@4.6.2
+ typescript: 4.6.2
+
packages/hoppscotch-data:
specifiers:
'@types/lodash': ^4.14.180
@@ -380,12 +416,14 @@ importers:
prettier: ^2.6.0
quickjs-emscripten: ^0.15.0
ts-jest: ^27.1.3
+ tsup: ^5.11.13
typescript: ^4.6.2
dependencies:
'@hoppscotch/data': link:../hoppscotch-data
fp-ts: 2.11.9
lodash: 4.17.21
quickjs-emscripten: 0.15.0
+ tsup: 5.12.1_typescript@4.6.2
devDependencies:
'@digitak/esrun': 3.1.2
'@relmify/jest-fp-ts': 2.0.0_fp-ts@2.11.9+io-ts@2.2.16
@@ -4063,11 +4101,11 @@ packages:
ufo: 0.7.11
dev: false
- /@nuxt/kit-edge/3.0.0-27465767.70f067a:
- resolution: {integrity: sha512-3WJfEKf7ymS+lDnDi7TEIqigWyQoCDLwdYGplpRnjFBALGIbMhnv7x4LGgYroB0RT6QS84k6HgTiYaZf5OERuw==}
+ /@nuxt/kit-edge/3.0.0-27470397.9ebea90:
+ resolution: {integrity: sha512-mBUqr6uAqXBurjf63m3/AtaLlKnXMy2OguW4nCismxE30/fz0LtF/MndKcX7SrfeOcpsnrOYvIUhgLheS0Lhpg==}
engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0}
dependencies:
- '@nuxt/schema': /@nuxt/schema-edge/3.0.0-27465767.70f067a
+ '@nuxt/schema': /@nuxt/schema-edge/3.0.0-27470397.9ebea90
c12: 0.2.4
consola: 2.15.3
defu: 6.0.0
@@ -4077,13 +4115,13 @@ packages:
jiti: 1.13.0
knitwork: 0.1.1
lodash.template: 4.5.0
- mlly: 0.4.3
+ mlly: 0.5.1
pathe: 0.2.0
pkg-types: 0.3.2
scule: 0.2.1
semver: 7.3.5
- unctx: 1.0.2
- unimport: 0.1.1
+ unctx: 1.1.3
+ unimport: 0.1.3
untyped: 0.4.3
transitivePeerDependencies:
- esbuild
@@ -4115,8 +4153,8 @@ packages:
- encoding
dev: false
- /@nuxt/schema-edge/3.0.0-27465767.70f067a:
- resolution: {integrity: sha512-S9RzCddYD2fyjUJYUiiNRwOdwjQjoPARm/4zXYx7sXnIOKvY7P84RgtzTNKDqlTilTY6j0NGShiqn/F9sC3mlw==}
+ /@nuxt/schema-edge/3.0.0-27470397.9ebea90:
+ resolution: {integrity: sha512-NTVqtIozgUW83uRLoEufjCulfkcZKPjrUXIuVNMmJwkkpO1SxJt4jY2uVTU8ahi8uG+iETkERcMbGi8NA86dtw==}
engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0}
dependencies:
c12: 0.2.4
@@ -4128,7 +4166,7 @@ packages:
scule: 0.2.1
std-env: 3.0.1
ufo: 0.8.1
- unimport: 0.1.1
+ unimport: 0.1.3
transitivePeerDependencies:
- esbuild
- rollup
@@ -4848,6 +4886,143 @@ packages:
resolution: {integrity: sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==}
dev: false
+ /@swc/core-android-arm-eabi/1.2.160:
+ resolution: {integrity: sha512-VzFP7tYgvpkUhd8wgyNtERqvoPBBDretyMFxAxPe2SxClaBs9Ka95PdiPPZalRq+vFCb/dFxD8Vhz+XO16Kpjg==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-android-arm64/1.2.160:
+ resolution: {integrity: sha512-m+xqQaa7TqW3Vm9MUvITtdU8OlAc/9yT+TgOS4l8WlfFI87IDnLLfinKKEp+xfKwzYDdIsh+sC+jdGdIBTMB+Q==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-darwin-arm64/1.2.160:
+ resolution: {integrity: sha512-9bG70KYKvjNf7tZtjOu1h4kDZPtoidZptIXPGSHuUgJ1BbSJYpfRR5xAmq4k37+GqOjIPJp4+lSGQPa2HfejpA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-darwin-x64/1.2.160:
+ resolution: {integrity: sha512-+b4HdKAVf/XPZ9DjgG2axGLbquPEuYwEP3zeWgbWn0s0FYQ7WTFxznf3YrTJE9MYadJeCOs3U80E2xVAtRRS9Q==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-freebsd-x64/1.2.160:
+ resolution: {integrity: sha512-E5agJwv+RVMoZ8FQIPSO5wLPDQx6jqcMpV207EB3pPaxPWGe4n3DH3vcibHp80RACDNdiaqo5lBeBnGJI4ithw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-arm-gnueabihf/1.2.160:
+ resolution: {integrity: sha512-uCttZRNx+lWVhCYGC6/pGUej08g1SQc5am6R9NVFh111goytcdlPnC4jV8oWzq2QhDWkkKxLoP2CZOytzI4+0w==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-arm64-gnu/1.2.160:
+ resolution: {integrity: sha512-sB18roiv8m/zsY6tXLSrbUls0eKkSkxOEF0ennXVEtz97rMJ+WWnkOc8gI+rUpj3MHbVAIxyDNyyZU4cH5g1jQ==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-arm64-musl/1.2.160:
+ resolution: {integrity: sha512-PJ7Ukb+BRR3pGYcUag8qRWOB11eByc5YLx/xAMSc3bRmaYW/oj6s8k+1DYiR//BAuNQdf14MpMFzDuWiDEUh7A==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-x64-gnu/1.2.160:
+ resolution: {integrity: sha512-wVh8Q86xz3t0y5zoUryWQ64bFG/YxdcykBgaog8lU9xkFb1KSqVRE9ia7aKA12/ZtAfpJZLRBleZxBAcaCg9FQ==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-linux-x64-musl/1.2.160:
+ resolution: {integrity: sha512-AnWdarl9WWuDdbc2AX1w76W1jaekSCokxRrWdSGUgQytaZRtybKZEgThvJCQDrSlYQD4XDOhhVRCurTvy4JsfQ==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-win32-arm64-msvc/1.2.160:
+ resolution: {integrity: sha512-ScL27mZRTwEIqBIv9RY34nQvyBvhosiM5Lus4dCFmS71flPcAEv7hJgy4GE3YUQV0ryGNK9NaO43H8sAyNwKVQ==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-win32-ia32-msvc/1.2.160:
+ resolution: {integrity: sha512-e75zbWlhlyrd5HdrYzELa6OlZxgyaVpJj+c9xMD95HcdklVbmsyt1vuqRxMyqaZUDLyehwwCDRX/ZeDme//M/A==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core-win32-x64-msvc/1.2.160:
+ resolution: {integrity: sha512-GAYT+WzYQY4sr17S21yJh4flJp/sQ62mAs6RfN89p7jIWgm0Bl/SooRl6ocsftTlnZm7K7QC8zmQVeNCWDCLPw==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@swc/core/1.2.160:
+ resolution: {integrity: sha512-nXoC7HA+aY7AtBPsiqGXocoRLAzzA7MV+InWQtILN7Uru4hB9+rLnLCPc3zSdg7pgnxJLa1tHup1Rz7Vv6TcIQ==}
+ engines: {node: '>=10'}
+ hasBin: true
+ optionalDependencies:
+ '@swc/core-android-arm-eabi': 1.2.160
+ '@swc/core-android-arm64': 1.2.160
+ '@swc/core-darwin-arm64': 1.2.160
+ '@swc/core-darwin-x64': 1.2.160
+ '@swc/core-freebsd-x64': 1.2.160
+ '@swc/core-linux-arm-gnueabihf': 1.2.160
+ '@swc/core-linux-arm64-gnu': 1.2.160
+ '@swc/core-linux-arm64-musl': 1.2.160
+ '@swc/core-linux-x64-gnu': 1.2.160
+ '@swc/core-linux-x64-musl': 1.2.160
+ '@swc/core-win32-arm64-msvc': 1.2.160
+ '@swc/core-win32-ia32-msvc': 1.2.160
+ '@swc/core-win32-x64-msvc': 1.2.160
+ dev: true
+
/@szmarczak/http-timer/1.1.2:
resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==}
engines: {node: '>=6'}
@@ -4917,6 +5092,15 @@ packages:
postcss: 7.0.39
dev: true
+ /@types/axios/0.14.0:
+ resolution: {integrity: sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=}
+ deprecated: This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!
+ dependencies:
+ axios: 0.26.1
+ transitivePeerDependencies:
+ - debug
+ dev: true
+
/@types/babel__core/7.1.14:
resolution: {integrity: sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==}
dependencies:
@@ -4985,6 +5169,13 @@ packages:
'@types/responselike': 1.0.0
dev: true
+ /@types/chalk/2.2.0:
+ resolution: {integrity: sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw==}
+ deprecated: This is a stub types definition for chalk (https://github.com/chalk/chalk). chalk provides its own type definitions, so you don't need @types/chalk installed!
+ dependencies:
+ chalk: 4.1.2
+ dev: true
+
/@types/clean-css/4.2.5:
resolution: {integrity: sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw==}
dependencies:
@@ -4998,6 +5189,13 @@ packages:
'@types/tern': 0.23.4
dev: true
+ /@types/commander/2.12.2:
+ resolution: {integrity: sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==}
+ deprecated: This is a stub types definition for commander (https://github.com/tj/commander.js). commander provides its own type definitions, so you don't need @types/commander installed!
+ dependencies:
+ commander: 8.3.0
+ dev: true
+
/@types/component-emitter/1.2.11:
resolution: {integrity: sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==}
dev: false
@@ -6338,7 +6536,6 @@ packages:
/any-promise/1.3.0:
resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=}
- dev: true
/anymatch/2.0.0:
resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==}
@@ -6545,7 +6742,6 @@ packages:
follow-redirects: 1.14.9
transitivePeerDependencies:
- debug
- dev: false
/axios/0.26.1:
resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==}
@@ -6553,7 +6749,6 @@ packages:
follow-redirects: 1.14.9
transitivePeerDependencies:
- debug
- dev: false
/babel-code-frame/6.26.0:
resolution: {integrity: sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=}
@@ -7211,7 +7406,6 @@ packages:
dependencies:
esbuild: 0.14.26
load-tsconfig: 0.2.3
- dev: true
/bytes/3.0.0:
resolution: {integrity: sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=}
@@ -7233,7 +7427,6 @@ packages:
/cac/6.7.12:
resolution: {integrity: sha512-rM7E2ygtMkJqD9c7WnFU6fruFcN3xe4FM5yUmgxhZzIKJk4uHl9U/fhwdajGFQbQuv43FAUo1Fe8gX/oIKDeSA==}
engines: {node: '>=8'}
- dev: true
/cacache/12.0.4:
resolution: {integrity: sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==}
@@ -7825,7 +8018,6 @@ packages:
/commander/8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
- dev: false
/common-tags/1.8.2:
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
@@ -9158,7 +9350,6 @@ packages:
cpu: [x64]
os: [android]
requiresBuild: true
- dev: true
optional: true
/esbuild-android-arm64/0.14.26:
@@ -9167,7 +9358,6 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
- dev: true
optional: true
/esbuild-darwin-64/0.14.26:
@@ -9176,7 +9366,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
- dev: true
optional: true
/esbuild-darwin-arm64/0.14.26:
@@ -9185,7 +9374,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
- dev: true
optional: true
/esbuild-freebsd-64/0.14.26:
@@ -9194,7 +9382,6 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
- dev: true
optional: true
/esbuild-freebsd-arm64/0.14.26:
@@ -9203,7 +9390,6 @@ packages:
cpu: [arm64]
os: [freebsd]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-32/0.14.26:
@@ -9212,7 +9398,6 @@ packages:
cpu: [ia32]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-64/0.14.26:
@@ -9221,7 +9406,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-arm/0.14.26:
@@ -9230,7 +9414,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-arm64/0.14.26:
@@ -9239,7 +9422,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-mips64le/0.14.26:
@@ -9248,7 +9430,6 @@ packages:
cpu: [mips64el]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-ppc64le/0.14.26:
@@ -9257,7 +9438,6 @@ packages:
cpu: [ppc64]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-riscv64/0.14.26:
@@ -9266,7 +9446,6 @@ packages:
cpu: [riscv64]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-linux-s390x/0.14.26:
@@ -9275,7 +9454,6 @@ packages:
cpu: [s390x]
os: [linux]
requiresBuild: true
- dev: true
optional: true
/esbuild-netbsd-64/0.14.26:
@@ -9284,7 +9462,6 @@ packages:
cpu: [x64]
os: [netbsd]
requiresBuild: true
- dev: true
optional: true
/esbuild-openbsd-64/0.14.26:
@@ -9293,7 +9470,6 @@ packages:
cpu: [x64]
os: [openbsd]
requiresBuild: true
- dev: true
optional: true
/esbuild-sunos-64/0.14.26:
@@ -9302,7 +9478,6 @@ packages:
cpu: [x64]
os: [sunos]
requiresBuild: true
- dev: true
optional: true
/esbuild-windows-32/0.14.26:
@@ -9311,7 +9486,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
- dev: true
optional: true
/esbuild-windows-64/0.14.26:
@@ -9320,7 +9494,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
- dev: true
optional: true
/esbuild-windows-arm64/0.14.26:
@@ -9329,7 +9502,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
- dev: true
optional: true
/esbuild/0.12.29:
@@ -9364,7 +9536,6 @@ packages:
esbuild-windows-32: 0.14.26
esbuild-windows-64: 0.14.26
esbuild-windows-arm64: 0.14.26
- dev: true
/escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
@@ -9721,6 +9892,11 @@ packages:
- supports-color
dev: true
+ /esm/3.2.25:
+ resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
+ engines: {node: '>=6'}
+ dev: true
+
/espree/9.3.1:
resolution: {integrity: sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -10218,7 +10394,6 @@ packages:
peerDependenciesMeta:
debug:
optional: true
- dev: false
/for-in/1.0.2:
resolution: {integrity: sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=}
@@ -10293,7 +10468,6 @@ packages:
/fp-ts/2.11.9:
resolution: {integrity: sha512-GhYlNKkCOfdjp71ocdtyaQGoqCswEoWDJLRr+2jClnBBq2dnSOtd6QxmJdALq8UhfqCyZZ0f0lxadU4OhwY9nw==}
- dev: false
/fragment-cache/0.2.1:
resolution: {integrity: sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=}
@@ -10576,7 +10750,6 @@ packages:
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
- dev: true
/glob/7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
@@ -12516,7 +12689,6 @@ packages:
/joycon/3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
- dev: true
/js-base64/2.6.4:
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
@@ -12985,7 +13157,6 @@ packages:
/load-tsconfig/0.2.3:
resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- dev: true
/loader-runner/2.4.0:
resolution: {integrity: sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==}
@@ -13707,6 +13878,13 @@ packages:
resolution: {integrity: sha512-xezyv7hnfFPuiDS3AiJuWs0OxlvooS++3L2lURvmh/1n7UG4O2Ehz9UkwWgg3wyLEPKGVfJLlr2DjjTCl9UJTg==}
dev: true
+ /mlly/0.5.1:
+ resolution: {integrity: sha512-0axKqxbYyQvaAfi6BNqDluCJqg6RkjdsdFxSDoiP6l5HplSTr3ie5Vkxvw9U9ogdj65x56amTnAn+xSoP727Rg==}
+ dependencies:
+ pathe: 0.2.0
+ pkg-types: 0.3.2
+ dev: true
+
/mocha/9.2.2:
resolution: {integrity: sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==}
engines: {node: '>= 12.0.0'}
@@ -13786,7 +13964,6 @@ packages:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
- dev: true
/nan/2.15.0:
resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==}
@@ -14067,7 +14244,7 @@ packages:
/nuxt-windicss/2.2.8:
resolution: {integrity: sha512-l0mONjhsxhkDa/XVLbQZIaA2+xxo+IBCWyieR7vq1Rl4BDRghNXYPMi8H04qwNND8R0cQQNQYUmECxjUg1LlAQ==}
dependencies:
- '@nuxt/kit': /@nuxt/kit-edge/3.0.0-27465767.70f067a
+ '@nuxt/kit': /@nuxt/kit-edge/3.0.0-27470397.9ebea90
'@windicss/plugin-utils': 1.8.3
consola: 2.15.3
defu: 5.0.1
@@ -14676,7 +14853,6 @@ packages:
/pirates/4.0.5:
resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==}
engines: {node: '>= 6'}
- dev: true
/pkg-dir/3.0.0:
resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
@@ -14966,7 +15142,6 @@ packages:
dependencies:
lilconfig: 2.0.4
yaml: 1.10.2
- dev: true
/postcss-loader/3.0.0:
resolution: {integrity: sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==}
@@ -15703,7 +15878,6 @@ packages:
engines: {node: '>=0.6'}
dependencies:
side-channel: 1.0.4
- dev: false
/query-string/4.3.4:
resolution: {integrity: sha1-u7aTucqRXCMlFbIosaArYJBD2+s=}
@@ -16250,7 +16424,6 @@ packages:
hasBin: true
optionalDependencies:
fsevents: 2.3.2
- dev: true
/run-async/2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
@@ -17424,7 +17597,6 @@ packages:
mz: 2.7.0
pirates: 4.0.5
ts-interface-checker: 0.1.13
- dev: true
/supports-color/2.0.0:
resolution: {integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=}
@@ -17726,13 +17898,11 @@ packages:
engines: {node: '>=0.8'}
dependencies:
thenify: 3.3.1
- dev: true
/thenify/3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
dependencies:
any-promise: 1.3.0
- dev: true
/thread-loader/3.0.4_webpack@4.46.0:
resolution: {integrity: sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA==}
@@ -17905,7 +18075,6 @@ packages:
/tree-kill/1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
- dev: true
/trim-newlines/3.0.1:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
@@ -17924,7 +18093,6 @@ packages:
/ts-interface-checker/0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
- dev: true
/ts-jest/27.1.3_60149d457e34ffba7d4e845dde6a1263:
resolution: {integrity: sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==}
@@ -18124,6 +18292,34 @@ packages:
- ts-node
dev: true
+ /tsup/5.12.1_typescript@4.6.2:
+ resolution: {integrity: sha512-vI7E4T6+6n5guQ9UKUOkQmzd1n4V9abGK71lbnzJMLJspbkNby5zlwWvgvHafLdYCb1WXpjFuqqmNLjBA0Wz3g==}
+ hasBin: true
+ peerDependencies:
+ typescript: ^4.1.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ bundle-require: 3.0.4_esbuild@0.14.26
+ cac: 6.7.12
+ chokidar: 3.5.3
+ debug: 4.3.3
+ esbuild: 0.14.26
+ execa: 5.1.1
+ globby: 11.1.0
+ joycon: 3.1.1
+ postcss-load-config: 3.1.3
+ resolve-from: 5.0.0
+ rollup: 2.70.1
+ source-map: 0.7.3
+ sucrase: 3.20.3
+ tree-kill: 1.2.2
+ typescript: 4.6.2
+ transitivePeerDependencies:
+ - supports-color
+ - ts-node
+
/tsutils/3.21.0_typescript@4.6.2:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
@@ -18244,8 +18440,18 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
- /unctx/1.0.2:
- resolution: {integrity: sha512-qxRfnQZWJqkg180JeOCJEvtjj5/7wnWVqkNHln8muY5/z8kMWBFqikFBPwIPCQrZJ+jtaSWkVHJkuHUAXls6zw==}
+ /unctx/1.1.3:
+ resolution: {integrity: sha512-x3sI4ueuHw05zQgbzfpzF9XO+zw0C7sPCPoTRIgVPAXr76HALqcV97cJDEa5Nj+WCAl7V2rgSZR/p4uM78gO2g==}
+ dependencies:
+ acorn: 8.7.0
+ estree-walker: 2.0.2
+ magic-string: 0.26.1
+ unplugin: 0.5.2
+ transitivePeerDependencies:
+ - esbuild
+ - rollup
+ - vite
+ - webpack
dev: true
/undici/4.12.1:
@@ -18285,8 +18491,8 @@ packages:
engines: {node: '>= 0.4.12'}
dev: true
- /unimport/0.1.1:
- resolution: {integrity: sha512-M3DEUx4idjPlAHyncuhvwemOYalbrl4gkxnaokrYVTSiFiu+WqM6kEybwMahRhNhVP4NLBs8QN/64BT2ujrYsA==}
+ /unimport/0.1.3:
+ resolution: {integrity: sha512-P21Psvf8rqgbx3pLpxvmOlTnwUU3bkRiou8hAijGfbuq5ioUILsffT48aTyIYDsYowoWoYtamn9d1IDxXOV37Q==}
dependencies:
'@rollup/pluginutils': 4.2.0
escape-string-regexp: 5.0.0
@@ -18427,6 +18633,28 @@ packages:
webpack-virtual-modules: 0.4.3
dev: true
+ /unplugin/0.5.2:
+ resolution: {integrity: sha512-3SPYtus/56cxyD4jfjrnqCvb6jPxvdqJNaRXnEaG2BhNEMaoygu/39AG+LwKmiIUzj4XHyitcfZ7scGlWfEigA==}
+ peerDependencies:
+ esbuild: '>=0.13'
+ rollup: ^2.50.0
+ vite: ^2.3.0
+ webpack: 4 || 5
+ peerDependenciesMeta:
+ esbuild:
+ optional: true
+ rollup:
+ optional: true
+ vite:
+ optional: true
+ webpack:
+ optional: true
+ dependencies:
+ chokidar: 3.5.3
+ webpack-sources: 3.2.3
+ webpack-virtual-modules: 0.4.3
+ dev: true
+
/unquote/1.1.1:
resolution: {integrity: sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=}
@@ -18997,6 +19225,11 @@ packages:
source-map: 0.6.1
dev: false
+ /webpack-sources/3.2.3:
+ resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
+ engines: {node: '>=10.13.0'}
+ dev: true
+
/webpack-virtual-modules/0.4.3:
resolution: {integrity: sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==}