diff --git a/.github/workflows/deploy-netlify-ui.yml b/.github/workflows/deploy-netlify-ui.yml new file mode 100644 index 000000000..67ed1315b --- /dev/null +++ b/.github/workflows/deploy-netlify-ui.yml @@ -0,0 +1,41 @@ +name: Deploy to Netlify (ui) + +on: + push: + branches: [main] + # run this workflow only if an update is made to the ui package + paths: + - "packages/hoppscotch-ui/**" + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup environment + run: mv .env.example .env + + - name: Setup pnpm + uses: pnpm/action-setup@v2.2.4 + with: + version: 7 + run_install: true + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + cache: pnpm + + - name: Build site + run: pnpm run generate-ui + + # Deploy the ui site with netlify-cli + - name: Deploy to Netlify (ui) + run: npx netlify-cli deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod + env: + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }} + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} diff --git a/netlify.toml b/netlify.toml index b57404ffa..80f0ee912 100644 --- a/netlify.toml +++ b/netlify.toml @@ -10,7 +10,7 @@ [[headers]] for = "/*" [headers.values] - X-Frame-Options = "DENY" + X-Frame-Options = "SAMEORIGIN" X-XSS-Protection = "1; mode=block" [[redirects]] diff --git a/package.json b/package.json index 1a2b18bfb..92110d465 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "typecheck": "pnpm -r do-typecheck", "lintfix": "pnpm -r do-lintfix", "pre-commit": "pnpm -r do-lint && pnpm -r do-typecheck", - "test": "pnpm -r do-test" + "test": "pnpm -r do-test", + "generate-ui": "pnpm -r do-build-ui" }, "workspaces": [ "./packages/*" diff --git a/packages/hoppscotch-cli/package.json b/packages/hoppscotch-cli/package.json index c86ec635b..ed292d111 100644 --- a/packages/hoppscotch-cli/package.json +++ b/packages/hoppscotch-cli/package.json @@ -1,6 +1,6 @@ { "name": "@hoppscotch/cli", - "version": "0.3.0", + "version": "0.3.1", "description": "A CLI to run Hoppscotch test scripts in CI environments.", "homepage": "https://hoppscotch.io", "main": "dist/index.js", diff --git a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts b/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts index a1e6861a5..d471caa79 100644 --- a/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts @@ -5,42 +5,52 @@ import { execAsync, getErrorCode, getTestJsonFilePath } from "../utils"; describe("Test 'hopp test ' command:", () => { test("No collection file path provided.", async () => { const cmd = `node ./bin/hopp test`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("Collection file not found.", async () => { const cmd = `node ./bin/hopp test notfound.json`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("FILE_NOT_FOUND"); }); - test("Malformed collection file.", async () => { + test("Collection file is invalid JSON.", async () => { const cmd = `node ./bin/hopp test ${getTestJsonFilePath( "malformed-collection.json" )}`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); + + expect(out).toBe("UNKNOWN_ERROR"); + }); + + test("Malformed collection file.", async () => { + const cmd = `node ./bin/hopp test ${getTestJsonFilePath( + "malformed-collection2.json" + )}`; + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("MALFORMED_COLLECTION"); }); test("Invalid arguement.", async () => { const cmd = `node ./bin/hopp invalid-arg`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("Collection file not JSON type.", async () => { const cmd = `node ./bin/hopp test ${getTestJsonFilePath("notjson.txt")}`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_FILE_TYPE"); }); @@ -70,24 +80,24 @@ describe("Test 'hopp test --env ' command:", () => { test("No env file path provided.", async () => { const cmd = `${VALID_TEST_CMD} --env`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("ENV file not JSON type.", async () => { const cmd = `${VALID_TEST_CMD} --env ${getTestJsonFilePath("notjson.txt")}`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_FILE_TYPE"); }); test("ENV file not found.", async () => { const cmd = `${VALID_TEST_CMD} --env notfound.json`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("FILE_NOT_FOUND"); }); @@ -109,17 +119,17 @@ describe("Test 'hopp test --delay ' command:", () => { test("No value passed to delay flag.", async () => { const cmd = `${VALID_TEST_CMD} --delay`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); expect(out).toBe("INVALID_ARGUMENT"); }); test("Invalid value passed to delay flag.", async () => { const cmd = `${VALID_TEST_CMD} --delay 'NaN'`; - const { stdout } = await execAsync(cmd); - const out = getErrorCode(stdout); - + const { stderr } = await execAsync(cmd); + const out = getErrorCode(stderr); + console.log("invalid value thing", out) expect(out).toBe("INVALID_ARGUMENT"); }); diff --git a/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts deleted file mode 100644 index 2ae9ba885..000000000 --- a/packages/hoppscotch-cli/src/__tests__/functions/checks/checkFile.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { HoppCLIError } from "../../../types/errors"; -import { checkFile } from "../../../utils/checks"; - -import "@relmify/jest-fp-ts"; - -describe("checkFile", () => { - test("File doesn't exists.", () => { - return expect( - checkFile("./src/samples/this-file-not-exists.json")() - ).resolves.toSubsetEqualLeft({ - code: "FILE_NOT_FOUND", - }); - }); - - test("File not of JSON type.", () => { - return expect( - checkFile("./src/__tests__/samples/notjson.txt")() - ).resolves.toSubsetEqualLeft({ - code: "INVALID_FILE_TYPE", - }); - }); - - test("Existing JSON file.", () => { - return expect( - checkFile("./src/__tests__/samples/passes.json")() - ).resolves.toBeRight(); - }); -}); diff --git a/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts index 04f869989..4bdf11439 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/collection/collectionsRunner.spec.ts @@ -50,7 +50,7 @@ describe("collectionsRunner", () => { test("Empty HoppCollection.", () => { return expect( - collectionsRunner({ collections: [], envs: SAMPLE_ENVS })() + collectionsRunner({ collections: [], envs: SAMPLE_ENVS }) ).resolves.toStrictEqual([]); }); @@ -66,7 +66,7 @@ describe("collectionsRunner", () => { }, ], envs: SAMPLE_ENVS, - })() + }) ).resolves.toMatchObject([]); }); @@ -84,7 +84,7 @@ describe("collectionsRunner", () => { }, ], envs: SAMPLE_ENVS, - })() + }) ).resolves.toMatchObject([ { path: "collection/request", @@ -116,7 +116,7 @@ describe("collectionsRunner", () => { }, ], envs: SAMPLE_ENVS, - })() + }) ).resolves.toMatchObject([ { path: "collection/folder/request", diff --git a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts index 236858376..6159f9239 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts @@ -1,22 +1,20 @@ import { HoppCLIError } from "../../../types/errors"; import { parseCollectionData } from "../../../utils/mutators"; -import "@relmify/jest-fp-ts"; - describe("parseCollectionData", () => { test("Reading non-existing file.", () => { return expect( - parseCollectionData("./src/__tests__/samples/notexist.json")() - ).resolves.toSubsetEqualLeft({ + parseCollectionData("./src/__tests__/samples/notexist.json") + ).rejects.toMatchObject({ code: "FILE_NOT_FOUND", }); }); test("Unparseable JSON contents.", () => { return expect( - parseCollectionData("./src/__tests__/samples/malformed-collection.json")() - ).resolves.toSubsetEqualLeft({ - code: "MALFORMED_COLLECTION", + parseCollectionData("./src/__tests__/samples/malformed-collection.json") + ).rejects.toMatchObject({ + code: "UNKNOWN_ERROR", }); }); @@ -24,15 +22,15 @@ describe("parseCollectionData", () => { return expect( parseCollectionData( "./src/__tests__/samples/malformed-collection2.json" - )() - ).resolves.toSubsetEqualLeft({ + ) + ).rejects.toMatchObject({ code: "MALFORMED_COLLECTION", }); }); test("Valid HoppCollection.", () => { return expect( - parseCollectionData("./src/__tests__/samples/passes.json")() - ).resolves.toBeRight(); + parseCollectionData("./src/__tests__/samples/passes.json") + ).resolves.toBeTruthy(); }); }); diff --git a/packages/hoppscotch-cli/src/commands/test.ts b/packages/hoppscotch-cli/src/commands/test.ts index 2f8d6500c..bdc563892 100644 --- a/packages/hoppscotch-cli/src/commands/test.ts +++ b/packages/hoppscotch-cli/src/commands/test.ts @@ -1,5 +1,3 @@ -import * as TE from "fp-ts/TaskEither"; -import { pipe, flow } from "fp-ts/function"; import { collectionsRunner, collectionsRunnerExit, @@ -10,18 +8,23 @@ import { parseCollectionData } from "../utils/mutators"; import { parseEnvsData } from "../options/test/env"; import { TestCmdOptions } from "../types/commands"; import { parseDelayOption } from "../options/test/delay"; +import { HoppEnvs } from "../types/request"; +import { isHoppCLIError } from "../utils/checks"; export const test = (path: string, options: TestCmdOptions) => async () => { - await pipe( - TE.Do, - TE.bind("envs", () => parseEnvsData(options.env)), - TE.bind("collections", () => parseCollectionData(path)), - TE.bind("delay", () => parseDelayOption(options.delay)), - TE.chainTaskK(collectionsRunner), - TE.chainW(flow(collectionsRunnerResult, collectionsRunnerExit, TE.of)), - TE.mapLeft((e) => { - handleError(e); + try { + const delay = options.delay ? parseDelayOption(options.delay) : 0 + const envs = options.env ? await parseEnvsData(options.env) : { global: [], selected: [] } + const collections = await parseCollectionData(path) + + const report = await collectionsRunner({collections, envs, delay}) + const hasSucceeded = collectionsRunnerResult(report) + collectionsRunnerExit(hasSucceeded) + } catch(e) { + if(isHoppCLIError(e)) { + handleError(e) process.exit(1); - }) - )(); + } + else throw e + } }; diff --git a/packages/hoppscotch-cli/src/handlers/error.ts b/packages/hoppscotch-cli/src/handlers/error.ts index 7e640e2fa..e89b6dfbc 100644 --- a/packages/hoppscotch-cli/src/handlers/error.ts +++ b/packages/hoppscotch-cli/src/handlers/error.ts @@ -1,4 +1,3 @@ -import { log } from "console"; import * as S from "fp-ts/string"; import { HoppError, HoppErrorCode } from "../types/errors"; import { hasProperty, isSafeCommanderError } from "../utils/checks"; @@ -7,7 +6,7 @@ import { exceptionColors } from "../utils/getters"; const { BG_FAIL } = exceptionColors; /** - * Parses unknown error data and narrows it to get information realted to + * Parses unknown error data and narrows it to get information related to * error in string format. * @param e Error data to parse. * @returns Information in string format appropriately parsed, based on error type. @@ -81,6 +80,6 @@ export const handleError = (error: HoppError) => { } if (!S.isEmpty(ERROR_MSG)) { - log(ERROR_CODE, ERROR_MSG); + console.error(ERROR_CODE, ERROR_MSG); } -}; +}; \ No newline at end of file diff --git a/packages/hoppscotch-cli/src/options/test/delay.ts b/packages/hoppscotch-cli/src/options/test/delay.ts index 939d0857d..75be423ed 100644 --- a/packages/hoppscotch-cli/src/options/test/delay.ts +++ b/packages/hoppscotch-cli/src/options/test/delay.ts @@ -1,20 +1,14 @@ -import * as TE from "fp-ts/TaskEither"; -import * as S from "fp-ts/string"; -import { pipe } from "fp-ts/function"; -import { error, HoppCLIError } from "../../types/errors"; +import { error } from "../../types/errors"; -export const parseDelayOption = ( - delay: unknown -): TE.TaskEither => - !S.isString(delay) - ? TE.right(0) - : pipe( - delay, - Number, - TE.fromPredicate(Number.isSafeInteger, () => - error({ - code: "INVALID_ARGUMENT", - data: "Expected '-d, --delay' value to be number", - }) - ) - ); +export function parseDelayOption(delay: string): number { + const maybeInt = Number.parseInt(delay) + + if(!Number.isNaN(maybeInt)) { + return maybeInt + } else { + throw error({ + code: "INVALID_ARGUMENT", + data: "Expected '-d, --delay' value to be number", + }) + } +} diff --git a/packages/hoppscotch-cli/src/options/test/env.ts b/packages/hoppscotch-cli/src/options/test/env.ts index 057bb8f0c..676a77664 100644 --- a/packages/hoppscotch-cli/src/options/test/env.ts +++ b/packages/hoppscotch-cli/src/options/test/env.ts @@ -1,64 +1,27 @@ -import fs from "fs/promises"; -import { pipe } from "fp-ts/function"; -import * as TE from "fp-ts/TaskEither"; -import * as E from "fp-ts/Either"; -import * as J from "fp-ts/Json"; -import * as A from "fp-ts/Array"; -import * as S from "fp-ts/string"; -import isArray from "lodash/isArray"; -import { HoppCLIError, error } from "../../types/errors"; +import { error } from "../../types/errors"; import { HoppEnvs, HoppEnvPair } from "../../types/request"; -import { checkFile } from "../../utils/checks"; +import { readJsonFile } from "../../utils/mutators"; /** * Parses env json file for given path and validates the parsed env json object. * @param path Path of env.json file to be parsed. * @returns For successful parsing we get HoppEnvs object. */ -export const parseEnvsData = ( - path: unknown -): TE.TaskEither => - !S.isString(path) - ? TE.right({ global: [], selected: [] }) - : pipe( - // Checking if the env.json file exists or not. - checkFile(path), +export async function parseEnvsData(path: string) { + const contents = await readJsonFile(path) - // Trying to read given env json file path. - TE.chainW((checkedPath) => - TE.tryCatch( - () => fs.readFile(checkedPath), - (reason) => - error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) - ) - ), + if(!(contents && typeof contents === "object" && !Array.isArray(contents))) { + throw error({ code: "MALFORMED_ENV_FILE", path, data: null }) + } - // Trying to JSON parse the read file data and mapping the entries to HoppEnvPairs. - TE.chainEitherKW((data) => - pipe( - data.toString(), - J.parse, - E.map((jsonData) => - jsonData && typeof jsonData === "object" && !isArray(jsonData) - ? pipe( - jsonData, - Object.entries, - A.map( - ([key, value]) => - { - key, - value: S.isString(value) - ? value - : JSON.stringify(value), - } - ) - ) - : [] - ), - E.map((envPairs) => { global: [], selected: envPairs }), - E.mapLeft((e) => - error({ code: "MALFORMED_ENV_FILE", path, data: E.toError(e) }) - ) - ) - ) - ); + const envPairs: Array = [] + + for( const [key,value] of Object.entries(contents)) { + if(typeof value !== "string") { + throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} }) + } + + envPairs.push({key, value}) + } + return { global: [], selected: envPairs } +} diff --git a/packages/hoppscotch-cli/src/types/commands.ts b/packages/hoppscotch-cli/src/types/commands.ts index 459576bf0..0c8eab855 100644 --- a/packages/hoppscotch-cli/src/types/commands.ts +++ b/packages/hoppscotch-cli/src/types/commands.ts @@ -1,6 +1,6 @@ export type TestCmdOptions = { - env: string; - delay: number; + env: string | undefined; + delay: string | undefined; }; -export type HoppEnvFileExt = "json"; +export type HOPP_ENV_FILE_EXT = "json"; diff --git a/packages/hoppscotch-cli/src/utils/checks.ts b/packages/hoppscotch-cli/src/utils/checks.ts index 53764ce15..dc184b528 100644 --- a/packages/hoppscotch-cli/src/utils/checks.ts +++ b/packages/hoppscotch-cli/src/utils/checks.ts @@ -1,20 +1,11 @@ -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 * as E from "fp-ts/Either"; -import curryRight from "lodash/curryRight"; import { CommanderError } from "commander"; -import { error, HoppCLIError, HoppErrnoException } from "../types/errors"; -import { HoppCollectionFileExt } from "../types/collections"; -import { HoppEnvFileExt } from "../types/commands"; +import { HoppCLIError, HoppErrnoException } from "../types/errors"; /** * Determines whether an object has a property with given name. @@ -71,59 +62,6 @@ export const isRESTCollection = ( return false; }; -/** - * Checks if the file path matches the requried file type with of required extension. - * @param path The input file path to check. - * @param extension The required extension for input file path. - * @returns Absolute path for valid file extension OR HoppCLIError in case of error. - */ -export const checkFileExt = curryRight( - ( - path: unknown, - extension: HoppCollectionFileExt | HoppEnvFileExt - ): E.Either => - pipe( - path, - E.fromPredicate(S.isString, (_) => error({ code: "NO_FILE_PATH" })), - E.chainW( - E.fromPredicate(S.endsWith(`.${extension}`), (_) => - error({ code: "INVALID_FILE_TYPE", data: extension }) - ) - ) - ) -); - -/** - * Checks if the given file path exists and is of given type. - * @param path The input file path to check. - * @returns Absolute path for valid file path OR HoppCLIError in case of error. - */ -export const checkFile = (path: unknown): TE.TaskEither => - pipe( - path, - - // Checking if path is string. - TE.fromPredicate(S.isString, () => error({ code: "NO_FILE_PATH" })), - - /** - * After checking file path, we map file path to absolute path and check - * if file is of given extension type. - */ - TE.map(join), - TE.chainEitherK(checkFileExt("json")), - - /** - * 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((checkedPath) => - TE.tryCatchK( - () => fs.access(checkedPath), - () => error({ code: "FILE_NOT_FOUND", path: checkedPath }) - )() - ) - ); /** * Checks if given error data is of type HoppCLIError, based on existence diff --git a/packages/hoppscotch-cli/src/utils/collections.ts b/packages/hoppscotch-cli/src/utils/collections.ts index 84e3806c5..c671d7c96 100644 --- a/packages/hoppscotch-cli/src/utils/collections.ts +++ b/packages/hoppscotch-cli/src/utils/collections.ts @@ -1,4 +1,3 @@ -import * as T from "fp-ts/Task"; import * as A from "fp-ts/Array"; import { pipe } from "fp-ts/function"; import { bold } from "chalk"; @@ -43,8 +42,8 @@ const { WARN, FAIL } = exceptionColors; * @returns List of report for each processed request. */ export const collectionsRunner = - (param: CollectionRunnerParam): T.Task => - async () => { + async (param: CollectionRunnerParam): Promise => + { const envs: HoppEnvs = param.envs; const delay = param.delay ?? 0; const requestsReport: RequestReport[] = []; @@ -213,10 +212,10 @@ export const collectionsRunnerResult = ( * Else, exit with code 1. * @param result Boolean defining the collections-runner result. */ -export const collectionsRunnerExit = (result: boolean) => { +export const collectionsRunnerExit = (result: boolean): never => { if (!result) { const EXIT_MSG = FAIL(`\nExited with code 1`); - process.stdout.write(EXIT_MSG); + process.stderr.write(EXIT_MSG); process.exit(1); } process.exit(0); diff --git a/packages/hoppscotch-cli/src/utils/mutators.ts b/packages/hoppscotch-cli/src/utils/mutators.ts index 990ce4595..1015ed2de 100644 --- a/packages/hoppscotch-cli/src/utils/mutators.ts +++ b/packages/hoppscotch-cli/src/utils/mutators.ts @@ -1,12 +1,7 @@ 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, checkFile } from "./checks"; +import { error } from "../types/errors"; +import { isRESTCollection, isHoppErrnoException } from "./checks"; import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; /** @@ -39,49 +34,44 @@ export const parseErrorMessage = (e: unknown) => { return msg.replace(/\n+$|\s{2,}/g, "").trim(); }; +export async function readJsonFile(path: string): Promise { + if(!path.endsWith('.json')) { + throw error({ code: "INVALID_FILE_TYPE", data: path }) + } + + try { + await fs.access(path) + } catch (e) { + throw error({ code: "FILE_NOT_FOUND", path: path }) + } + + try { + return JSON.parse((await fs.readFile(path)).toString()) + } catch(e) { + throw error({ code: "UNKNOWN_ERROR", data: e }) + } +} + /** * 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 = ( +export async function parseCollectionData( path: string -): TE.TaskEither[]> => - pipe( - TE.of(path), +): Promise[]> { + let contents = await readJsonFile(path) - // Checking if given file path exists or not. - TE.chain(checkFile), + const maybeArrayOfCollections: unknown[] = Array.isArray(contents) ? contents : [contents] - // Trying to read give collection json path. - TE.chainW((checkedPath) => - TE.tryCatch( - () => fs.readFile(checkedPath), - (reason) => error({ code: "UNKNOWN_ERROR", data: E.toError(reason) }) - ) - ), + if(maybeArrayOfCollections.some((x) => !isRESTCollection(x))) { + throw error({ + code: "MALFORMED_COLLECTION", + path, + data: "Please check the collection data.", + }) + } - // 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.", - }) - ) - ) - ); + return maybeArrayOfCollections as HoppCollection[] +}; diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index a8b48ca9e..f28b644c1 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -391,6 +391,7 @@ "copy_link": "Copy link", "duration": "Duration", "enter_curl": "Enter cURL command", + "duplicated": "Request duplicated", "generate_code": "Generate code", "generated_code": "Generated code", "header_list": "Header List", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 710cbd7d8..3b7ae649d 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -32,7 +32,7 @@ declare module '@vue/runtime-core' { CollectionsAdd: typeof import('./components/collections/Add.vue')['default'] CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default'] CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default'] - CollectionsChooseType: typeof import('./components/collections/ChooseType.vue')['default'] + CollectionsCollection: typeof import('./components/collections/Collection.vue')['default'] CollectionsEdit: typeof import('./components/collections/Edit.vue')['default'] CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default'] CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default'] @@ -48,13 +48,11 @@ declare module '@vue/runtime-core' { CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default'] CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default'] CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default'] - CollectionsMyCollection: typeof import('./components/collections/my/Collection.vue')['default'] - CollectionsMyFolder: typeof import('./components/collections/my/Folder.vue')['default'] - CollectionsMyRequest: typeof import('./components/collections/my/Request.vue')['default'] + CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default'] + CollectionsRequest: typeof import('./components/collections/Request.vue')['default'] CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default'] - CollectionsTeamsCollection: typeof import('./components/collections/teams/Collection.vue')['default'] - CollectionsTeamsFolder: typeof import('./components/collections/teams/Folder.vue')['default'] - CollectionsTeamsRequest: typeof import('./components/collections/teams/Request.vue')['default'] + CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default'] + CollectionsTeamSelect: typeof import('./components/collections/TeamSelect.vue')['default'] Environments: typeof import('./components/environments/index.vue')['default'] EnvironmentsChooseType: typeof import('./components/environments/ChooseType.vue')['default'] EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default'] @@ -152,6 +150,8 @@ declare module '@vue/runtime-core' { SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default'] SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default'] SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default'] + SmartTree: typeof import('./components/smart/Tree.vue')['default'] + SmartTreeBranch: typeof import('./components/smart/TreeBranch.vue')['default'] SmartWindow: typeof import('./../../hoppscotch-ui/src/components/smart/Window.vue')['default'] SmartWindows: typeof import('./../../hoppscotch-ui/src/components/smart/Windows.vue')['default'] TabPrimary: typeof import('./components/tab/Primary.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/app/DeveloperOptions.vue b/packages/hoppscotch-common/src/components/app/DeveloperOptions.vue index 02d4f8ea4..f2f63346f 100644 --- a/packages/hoppscotch-common/src/components/app/DeveloperOptions.vue +++ b/packages/hoppscotch-common/src/components/app/DeveloperOptions.vue @@ -29,10 +29,7 @@ import IconCheck from "~icons/lucide/check" import { copyToClipboard } from "~/helpers/utils/clipboard" import { useI18n } from "@composables/i18n" import { useToast } from "@composables/toast" -import { useReadonlyStream } from "@composables/stream" -import { authIdToken$ } from "~/helpers/fb/auth" - -const userAuthToken = useReadonlyStream(authIdToken$, null) +import { platform } from "~/platform" const t = useI18n() @@ -53,8 +50,9 @@ const copyIcon = refAutoReset( // Copy user auth token to clipboard const copyUserAuthToken = () => { - if (userAuthToken.value) { - copyToClipboard(userAuthToken.value) + const token = platform.auth.getDevOptsBackendIDToken() + if (token) { + copyToClipboard(token) copyIcon.value = IconCheck toast.success(`${t("state.copied_to_clipboard")}`) } else { diff --git a/packages/hoppscotch-common/src/components/app/Footer.vue b/packages/hoppscotch-common/src/components/app/Footer.vue index 62ca6ea76..3c02499bb 100644 --- a/packages/hoppscotch-common/src/components/app/Footer.vue +++ b/packages/hoppscotch-common/src/components/app/Footer.vue @@ -219,7 +219,7 @@ import { showChat } from "@modules/crisp" import { useSetting } from "@composables/settings" import { useI18n } from "@composables/i18n" import { useReadonlyStream } from "@composables/stream" -import { currentUser$ } from "~/helpers/fb/auth" +import { platform } from "~/platform" import { TippyComponent } from "vue-tippy" import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils" import { invokeAction } from "@helpers/actions" @@ -236,7 +236,10 @@ const SIDEBAR_ON_LEFT = useSetting("SIDEBAR_ON_LEFT") const navigatorShare = !!navigator.share -const currentUser = useReadonlyStream(currentUser$, null) +const currentUser = useReadonlyStream( + platform.auth.getCurrentUserStream(), + platform.auth.getCurrentUser() +) watch( () => ZEN_MODE.value, diff --git a/packages/hoppscotch-common/src/components/app/Header.vue b/packages/hoppscotch-common/src/components/app/Header.vue index b89c23bad..f20e619cc 100644 --- a/packages/hoppscotch-common/src/components/app/Header.vue +++ b/packages/hoppscotch-common/src/components/app/Header.vue @@ -171,11 +171,10 @@ import IconUploadCloud from "~icons/lucide/upload-cloud" import IconUserPlus from "~icons/lucide/user-plus" import { breakpointsTailwind, useBreakpoints, useNetwork } from "@vueuse/core" import { pwaDefferedPrompt, installPWA } from "@modules/pwa" -import { probableUser$ } from "@helpers/fb/auth" +import { platform } from "~/platform" import { useI18n } from "@composables/i18n" import { useReadonlyStream } from "@composables/stream" import { invokeAction } from "@helpers/actions" -import { platform } from "~/index" const t = useI18n() @@ -194,7 +193,10 @@ const mdAndLarger = breakpoints.greater("md") const network = reactive(useNetwork()) -const currentUser = useReadonlyStream(probableUser$, null) +const currentUser = useReadonlyStream( + platform.auth.getProbableUserStream(), + platform.auth.getProbableUser() +) // Template refs const tippyActions = ref(null) diff --git a/packages/hoppscotch-common/src/components/collections/Add.vue b/packages/hoppscotch-common/src/components/collections/Add.vue index d11d0fdc0..a2f0575c0 100644 --- a/packages/hoppscotch-common/src/components/collections/Add.vue +++ b/packages/hoppscotch-common/src/components/collections/Add.vue @@ -41,47 +41,52 @@ - diff --git a/packages/hoppscotch-common/src/components/collections/AddFolder.vue b/packages/hoppscotch-common/src/components/collections/AddFolder.vue index 0a1c7f797..ba0d56fb4 100644 --- a/packages/hoppscotch-common/src/components/collections/AddFolder.vue +++ b/packages/hoppscotch-common/src/components/collections/AddFolder.vue @@ -3,7 +3,7 @@ v-if="show" dialog :title="t('folder.new')" - @close="$emit('hide-modal')" + @close="emit('hide-modal')" > - diff --git a/packages/hoppscotch-common/src/components/collections/AddRequest.vue b/packages/hoppscotch-common/src/components/collections/AddRequest.vue index 68f76c892..fba4fe5d9 100644 --- a/packages/hoppscotch-common/src/components/collections/AddRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/AddRequest.vue @@ -48,23 +48,20 @@ import { getRESTRequest } from "~/newstore/RESTSession" const toast = useToast() const t = useI18n() -const props = defineProps<{ - show: boolean - loadingState: boolean - folder?: object - folderPath?: string -}>() +const props = withDefaults( + defineProps<{ + show: boolean + loadingState: boolean + }>(), + { + show: false, + loadingState: false, + } +) const emit = defineEmits<{ - (e: "hide-modal"): void - ( - e: "add-request", - v: { - name: string - folder: object | undefined - path: string | undefined - } - ): void + (event: "hide-modal"): void + (event: "add-request", name: string): void }>() const name = ref("") @@ -79,15 +76,11 @@ watch( ) const addRequest = () => { - if (!name.value) { + if (name.value.trim() === "") { toast.error(`${t("error.empty_req_name")}`) return } - emit("add-request", { - name: name.value, - folder: props.folder, - path: props.folderPath, - }) + emit("add-request", name.value) } const hideModal = () => { diff --git a/packages/hoppscotch-common/src/components/collections/ChooseType.vue b/packages/hoppscotch-common/src/components/collections/ChooseType.vue deleted file mode 100644 index 52974141a..000000000 --- a/packages/hoppscotch-common/src/components/collections/ChooseType.vue +++ /dev/null @@ -1,162 +0,0 @@ - - - diff --git a/packages/hoppscotch-common/src/components/collections/Collection.vue b/packages/hoppscotch-common/src/components/collections/Collection.vue new file mode 100644 index 000000000..7a96c7387 --- /dev/null +++ b/packages/hoppscotch-common/src/components/collections/Collection.vue @@ -0,0 +1,252 @@ + + + diff --git a/packages/hoppscotch-common/src/components/collections/Edit.vue b/packages/hoppscotch-common/src/components/collections/Edit.vue index 803838281..65b348977 100644 --- a/packages/hoppscotch-common/src/components/collections/Edit.vue +++ b/packages/hoppscotch-common/src/components/collections/Edit.vue @@ -41,46 +41,52 @@ - diff --git a/packages/hoppscotch-common/src/components/collections/EditFolder.vue b/packages/hoppscotch-common/src/components/collections/EditFolder.vue index 659986fc3..58ec7549c 100644 --- a/packages/hoppscotch-common/src/components/collections/EditFolder.vue +++ b/packages/hoppscotch-common/src/components/collections/EditFolder.vue @@ -3,7 +3,7 @@ v-if="show" dialog :title="t('folder.edit')" - @close="$emit('hide-modal')" + @close="emit('hide-modal')" > - diff --git a/packages/hoppscotch-common/src/components/collections/EditRequest.vue b/packages/hoppscotch-common/src/components/collections/EditRequest.vue index 22ab6df3b..f08e8fbf1 100644 --- a/packages/hoppscotch-common/src/components/collections/EditRequest.vue +++ b/packages/hoppscotch-common/src/components/collections/EditRequest.vue @@ -9,13 +9,13 @@