diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json
index 5dce84485..4160bb7ce 100644
--- a/packages/hoppscotch-common/locales/en.json
+++ b/packages/hoppscotch-common/locales/en.json
@@ -315,7 +315,8 @@
"proxy_error": "Proxy error",
"script_fail": "Could not execute pre-request script",
"something_went_wrong": "Something went wrong",
- "test_script_fail": "Could not execute post-request script"
+ "test_script_fail": "Could not execute post-request script",
+ "reading_files": "Error while reading one or more files."
},
"export": {
"as_json": "Export as JSON",
@@ -413,7 +414,10 @@
"json_description": "Import collections from a Hoppscotch Collections JSON file",
"postman_environment": "Postman Environment",
"postman_environment_description": "Import Postman Environment from a JSON file",
- "title": "Import"
+ "title": "Import",
+ "file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported",
+ "file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.",
+ "success": "Successfully imported"
},
"inspections": {
"description": "Inspect possible errors",
diff --git a/packages/hoppscotch-common/src/components/collections/ImportExport.vue b/packages/hoppscotch-common/src/components/collections/ImportExport.vue
index 64ae1d361..4a520576c 100644
--- a/packages/hoppscotch-common/src/components/collections/ImportExport.vue
+++ b/packages/hoppscotch-common/src/components/collections/ImportExport.vue
@@ -263,7 +263,7 @@ const HoppOpenAPIImporter: ImporterOrExporter = {
step: UrlSource({
caption: "import.from_url",
onImportFromURL: async (content) => {
- const res = await hoppOpenAPIImporter(content)()
+ const res = await hoppOpenAPIImporter([content])()
if (E.isRight(res)) {
handleImportToStore(res.right)
diff --git a/packages/hoppscotch-common/src/components/collections/MyCollections.vue b/packages/hoppscotch-common/src/components/collections/MyCollections.vue
index b376f2c5d..810549cd3 100644
--- a/packages/hoppscotch-common/src/components/collections/MyCollections.vue
+++ b/packages/hoppscotch-common/src/components/collections/MyCollections.vue
@@ -694,7 +694,7 @@ class MyCollectionsAdapter implements SmartTreeAdapter {
let target = collections[indexPaths.shift() as number]
while (indexPaths.length > 0)
- target = target.folders[indexPaths.shift() as number]
+ target = target?.folders[indexPaths.shift() as number]
return target !== undefined ? target : null
}
diff --git a/packages/hoppscotch-common/src/components/environments/ImportExport.vue b/packages/hoppscotch-common/src/components/environments/ImportExport.vue
index 56c2f3869..fc97315f4 100644
--- a/packages/hoppscotch-common/src/components/environments/ImportExport.vue
+++ b/packages/hoppscotch-common/src/components/environments/ImportExport.vue
@@ -133,7 +133,7 @@ const PostmanEnvironmentsImport: ImporterOrExporter = {
return
}
- handleImportToStore([res.right])
+ handleImportToStore(res.right)
platform.analytics?.logEvent({
type: "HOPP_IMPORT_ENVIRONMENT",
@@ -166,19 +166,14 @@ const insomniaEnvironmentsImport: ImporterOrExporter = {
return
}
- const globalEnvIndex = res.right.findIndex(
+ const globalEnvs = res.right.filter(
(env) => env.name === "Base Environment"
)
+ const otherEnvs = res.right.filter(
+ (env) => env.name !== "Base Environment"
+ )
- const globalEnv =
- globalEnvIndex !== -1 ? res.right[globalEnvIndex] : undefined
-
- // remove the global env from the environments array to prevent it from being imported twice
- if (globalEnvIndex !== -1) {
- res.right.splice(globalEnvIndex, 1)
- }
-
- handleImportToStore(res.right, globalEnv)
+ handleImportToStore(otherEnvs, globalEnvs)
platform.analytics?.logEvent({
type: "HOPP_IMPORT_ENVIRONMENT",
@@ -340,14 +335,14 @@ const showImportFailedError = () => {
const handleImportToStore = async (
environments: Environment[],
- globalEnv?: NonSecretEnvironment
+ globalEnvs: NonSecretEnvironment[] = []
) => {
- // if there's a global env, add them to the store
- if (globalEnv) {
- globalEnv.variables.forEach(({ key, value, secret }) =>
+ // Add global envs to the store
+ globalEnvs.forEach(({ variables }) => {
+ variables.forEach(({ key, value, secret }) => {
addGlobalEnvVariable({ key, value, secret })
- )
- }
+ })
+ })
if (props.environmentType === "MY_ENV") {
appendEnvironments(environments)
diff --git a/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/FileImport.vue b/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/FileImport.vue
index 97c373257..4750b1a42 100644
--- a/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/FileImport.vue
+++ b/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/FileImport.vue
@@ -13,6 +13,7 @@
{{ t(`${caption}`) }}
+
@@ -23,15 +24,30 @@
type="file"
class="p-4 cursor-pointer transition file:transition file:cursor-pointer text-secondary hover:text-secondaryDark file:mr-2 file:py-2 file:px-4 file:rounded file:border-0 file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
:accept="acceptedFileTypes"
+ multiple
@change="onFileChange"
/>
+
+
+ {{
+ t("import.file_size_limit_exceeded_warning_multiple_files", {
+ files:
+ importFilesCount === 1 ? "file" : `${importFilesCount} files`,
+ })
+ }}
+
+
+
+ {{ t("import.file_size_limit_exceeded_warning_single_file") }}
+
+
@@ -51,16 +67,30 @@ defineProps<{
const t = useI18n()
const toast = useToast()
+const ALLOWED_FILE_SIZE_LIMIT = 10 // 10 MB
+
+const importFilesCount = ref(0)
+
const hasFile = ref(false)
-const fileContent = ref("")
+const showFileSizeLimitExceededWarning = ref(false)
+const fileContent = ref([])
const inputChooseFileToImportFrom = ref()
const emit = defineEmits<{
- (e: "importFromFile", content: string): void
+ (e: "importFromFile", content: string[]): void
}>()
-const onFileChange = () => {
+const onFileChange = async () => {
+ // Reset the state on entering the handler to avoid any stale state
+ if (showFileSizeLimitExceededWarning.value) {
+ showFileSizeLimitExceededWarning.value = false
+ }
+
+ if (importFilesCount.value) {
+ importFilesCount.value = 0
+ }
+
const inputFileToImport = inputChooseFileToImportFrom.value
if (!inputFileToImport) {
@@ -69,27 +99,52 @@ const onFileChange = () => {
}
if (!inputFileToImport.files || inputFileToImport.files.length === 0) {
- inputChooseFileToImportFrom.value[0].value = ""
+ inputChooseFileToImportFrom.value = ""
hasFile.value = false
toast.show(t("action.choose_file").toString())
return
}
- const reader = new FileReader()
+ const readerPromises: Promise[] = []
- reader.onload = ({ target }) => {
- const content = target!.result as string | null
- if (!content) {
- hasFile.value = false
- toast.show(t("action.choose_file").toString())
- return
+ let totalFileSize = 0
+
+ for (let i = 0; i < inputFileToImport.files.length; i++) {
+ const file = inputFileToImport.files[i]
+
+ totalFileSize += file.size / 1024 / 1024
+
+ if (totalFileSize > ALLOWED_FILE_SIZE_LIMIT) {
+ showFileSizeLimitExceededWarning.value = true
+ break
}
- fileContent.value = content
+ const reader = new FileReader()
- hasFile.value = !!content?.length
+ readerPromises.push(
+ new Promise((resolve, reject) => {
+ reader.onload = () => resolve(reader.result as string | null)
+ reader.onerror = reject
+ reader.readAsText(file)
+ })
+ )
}
- reader.readAsText(inputFileToImport.files[0])
+ importFilesCount.value = readerPromises.length
+
+ const results = await Promise.allSettled(readerPromises)
+
+ const contentsArr = results
+ .filter((result) => result.status === "fulfilled")
+ .map((result) => (result as { value: string | null }).value)
+ .filter(Boolean) as string[]
+
+ const errors = results.filter((result) => result.status === "rejected")
+ if (errors.length) {
+ toast.error(t("error.reading_files"))
+ }
+
+ fileContent.value = contentsArr
+ hasFile.value = contentsArr.length > 0
}
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/hopp.ts b/packages/hoppscotch-common/src/helpers/import-export/import/hopp.ts
index bdcb37518..723b2af77 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/hopp.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/hopp.ts
@@ -2,6 +2,7 @@ import { pipe, flow } from "fp-ts/function"
import * as TE from "fp-ts/TaskEither"
import * as O from "fp-ts/Option"
import * as RA from "fp-ts/ReadonlyArray"
+import * as A from "fp-ts/Array"
import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
import { isPlainObject as _isPlainObject } from "lodash-es"
@@ -9,11 +10,13 @@ import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { safeParseJSON } from "~/helpers/functional/json"
import { translateToNewGQLCollection } from "@hoppscotch/data"
-export const hoppRESTImporter = (content: string) =>
+export const hoppRESTImporter = (content: string[]) =>
pipe(
- safeParseJSON(content),
+ content,
+ A.traverse(O.Applicative)((str) => safeParseJSON(str, true)),
O.chain(
flow(
+ A.flatten,
makeCollectionsArray,
RA.map(validateCollection),
O.sequenceArray,
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/hoppEnv.ts b/packages/hoppscotch-common/src/helpers/import-export/import/hoppEnv.ts
index e4d8c6efa..dbc349f11 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/hoppEnv.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/hoppEnv.ts
@@ -8,17 +8,35 @@ import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { Environment } from "@hoppscotch/data"
import { z } from "zod"
-export const hoppEnvImporter = (content: string) => {
- const parsedContent = safeParseJSON(content, true)
+export const hoppEnvImporter = (contents: string[]) => {
+ const parsedContents = contents.map((str) => safeParseJSON(str, true))
- // parse json from the environments string
- if (O.isNone(parsedContent)) {
+ if (parsedContents.some((parsed) => O.isNone(parsed))) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
}
+ const parsedValues = parsedContents.flatMap((content) => {
+ const unwrappedContent = O.toNullable(content) as Environment[] | null
+
+ if (unwrappedContent) {
+ return unwrappedContent.map((contentEntry) => {
+ return {
+ ...contentEntry,
+ variables: contentEntry.variables?.map((valueEntry) => ({
+ ...valueEntry,
+ ...("value" in valueEntry
+ ? { value: String(valueEntry.value) }
+ : {}),
+ })),
+ }
+ })
+ }
+ return null
+ })
+
const validationResult = z
.array(entityReference(Environment))
- .safeParse(parsedContent.value)
+ .safeParse(parsedValues)
if (!validationResult.success) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/hoppGql.ts b/packages/hoppscotch-common/src/helpers/import-export/import/hoppGql.ts
index 70e710263..545794439 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/hoppGql.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/hoppGql.ts
@@ -3,10 +3,10 @@ import * as E from "fp-ts/Either"
// TODO: add zod validation
export const hoppGqlCollectionsImporter = (
- content: string
+ contents: string[]
): E.Either<"INVALID_JSON", HoppCollection[]> => {
return E.tryCatch(
- () => JSON.parse(content) as HoppCollection[],
+ () => contents.flatMap((content) => JSON.parse(content)),
() => "INVALID_JSON"
)
}
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/FileSource.ts b/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/FileSource.ts
index b40755fcc..bae1e2a4f 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/FileSource.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/FileSource.ts
@@ -6,7 +6,7 @@ import { v4 as uuidv4 } from "uuid"
export function FileSource(metadata: {
acceptedFileTypes: string
caption: string
- onImportFromFile: (content: string) => any | Promise
+ onImportFromFile: (content: string[]) => any | Promise
}) {
const stepID = uuidv4()
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/GistSource.ts b/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/GistSource.ts
index ff97bb45f..2c2c845b9 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/GistSource.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/import-sources/GistSource.ts
@@ -10,14 +10,14 @@ import { v4 as uuidv4 } from "uuid"
export function GistSource(metadata: {
caption: string
onImportFromGist: (
- importResult: E.Either
+ importResult: E.Either
) => any | Promise
}) {
const stepID = uuidv4()
return defineStep(stepID, UrlImport, () => ({
caption: metadata.caption,
- onImportFromURL: (gistResponse) => {
+ onImportFromURL: (gistResponse: Record) => {
const fileSchema = z.object({
files: z.record(z.object({ content: z.string() })),
})
@@ -29,9 +29,11 @@ export function GistSource(metadata: {
return
}
- const content = Object.values(parseResult.data.files)[0].content
+ const contents = Object.values(parseResult.data.files).map(
+ ({ content }) => content
+ )
- metadata.onImportFromGist(E.right(content))
+ metadata.onImportFromGist(E.right(contents))
},
fetchLogic: fetchGistFromUrl,
}))
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/insomnia.ts b/packages/hoppscotch-common/src/helpers/import-export/import/insomnia.ts
index e6aaf7f0a..473afb9f9 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/insomnia.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/insomnia.ts
@@ -1,19 +1,21 @@
-import { convert, ImportRequest } from "insomnia-importers"
-import { pipe } from "fp-ts/function"
import {
+ HoppCollection,
HoppRESTAuth,
HoppRESTHeader,
HoppRESTParam,
HoppRESTReqBody,
HoppRESTRequest,
knownContentTypes,
- makeRESTRequest,
- HoppCollection,
makeCollection,
+ makeRESTRequest,
} from "@hoppscotch/data"
+
import * as A from "fp-ts/Array"
-import * as TO from "fp-ts/TaskOption"
import * as TE from "fp-ts/TaskEither"
+import * as TO from "fp-ts/TaskOption"
+import { pipe } from "fp-ts/function"
+import { ImportRequest, convert } from "insomnia-importers"
+
import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { replaceInsomniaTemplating } from "./insomniaEnv"
@@ -203,15 +205,18 @@ const getHoppFolder = (
headers: [],
})
-const getHoppCollections = (doc: InsomniaDoc) =>
- getFoldersIn(null, doc.data.resources).map((f) =>
- getHoppFolder(f, doc.data.resources)
- )
+const getHoppCollections = (docs: InsomniaDoc[]) => {
+ return docs.flatMap((doc) => {
+ return getFoldersIn(null, doc.data.resources).map((f) =>
+ getHoppFolder(f, doc.data.resources)
+ )
+ })
+}
-export const hoppInsomniaImporter = (fileContent: string) =>
+export const hoppInsomniaImporter = (fileContents: string[]) =>
pipe(
- fileContent,
- parseInsomniaDoc,
+ fileContents,
+ A.traverse(TO.ApplicativeSeq)(parseInsomniaDoc),
TO.map(getHoppCollections),
TE.fromTaskOption(() => IMPORTER_INVALID_FILE_FORMAT)
)
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/insomniaEnv.ts b/packages/hoppscotch-common/src/helpers/import-export/import/insomniaEnv.ts
index ba3ce36c2..f92c358fd 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/insomniaEnv.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/insomniaEnv.ts
@@ -29,33 +29,36 @@ export const replaceInsomniaTemplating = (expression: string) => {
return expression.replaceAll(regex, "<<$1>>")
}
-export const insomniaEnvImporter = (content: string) => {
- const parsedContent = safeParseJSONOrYAML(content)
-
- if (O.isNone(parsedContent)) {
+export const insomniaEnvImporter = (contents: string[]) => {
+ const parsedContents = contents.map((str) => safeParseJSONOrYAML(str))
+ if (parsedContents.some((parsed) => O.isNone(parsed))) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
}
- const validationResult = insomniaResourcesSchema.safeParse(
- parsedContent.value
- )
+ const parsedValues = parsedContents.map((parsed) => O.toNullable(parsed))
+
+ const validationResult = z
+ .array(insomniaResourcesSchema)
+ .safeParse(parsedValues)
if (!validationResult.success) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
}
- const insomniaEnvs = validationResult.data.resources
- .filter((resource) => resource._type === "environment")
- .map((envResource) => {
- const envResourceData = envResource.data as Record
- const stringifiedData: Record = {}
+ const insomniaEnvs = validationResult.data.flatMap(({ resources }) => {
+ return resources
+ .filter((resource) => resource._type === "environment")
+ .map((envResource) => {
+ const envResourceData = envResource.data as Record
+ const stringifiedData: Record = {}
- Object.keys(envResourceData).forEach((key) => {
- stringifiedData[key] = String(envResourceData[key])
+ Object.keys(envResourceData).forEach((key) => {
+ stringifiedData[key] = String(envResourceData[key])
+ })
+
+ return { ...envResource, data: stringifiedData }
})
-
- return { ...envResource, data: stringifiedData }
- })
+ })
const environments: NonSecretEnvironment[] = []
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts b/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts
index 29600acf7..5cf267001 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts
@@ -584,24 +584,28 @@ const convertPathToHoppReqs = (
RA.toArray
)
-const convertOpenApiDocToHopp = (
- doc: OpenAPI.Document
+const convertOpenApiDocsToHopp = (
+ docs: OpenAPI.Document[]
): TE.TaskEither => {
- const name = doc.info.title
+ const collections = docs.map((doc) => {
+ const name = doc.info.title
- const paths = Object.entries(doc.paths ?? {})
- .map(([pathName, pathObj]) => convertPathToHoppReqs(doc, pathName, pathObj))
- .flat()
+ const paths = Object.entries(doc.paths ?? {})
+ .map(([pathName, pathObj]) =>
+ convertPathToHoppReqs(doc, pathName, pathObj)
+ )
+ .flat()
- return TE.of([
- makeCollection({
+ return makeCollection({
name,
folders: [],
requests: paths,
auth: { authType: "inherit", authActive: true },
headers: [],
- }),
- ])
+ })
+ })
+
+ return TE.of(collections)
}
const parseOpenAPIDocContent = (str: string) =>
@@ -614,29 +618,49 @@ const parseOpenAPIDocContent = (str: string) =>
)
)
-export const hoppOpenAPIImporter = (fileContent: string) =>
+export const hoppOpenAPIImporter = (fileContents: string[]) =>
pipe(
// See if we can parse JSON properly
- fileContent,
- parseOpenAPIDocContent,
- TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT),
+ fileContents,
+ A.traverse(O.Applicative)(parseOpenAPIDocContent),
+ TE.fromOption(() => {
+ return IMPORTER_INVALID_FILE_FORMAT
+ }),
// Try validating, else the importer is invalid file format
- TE.chainW((obj) =>
- pipe(
+ TE.chainW((docArr) => {
+ return pipe(
TE.tryCatch(
- () => SwaggerParser.validate(obj),
+ async () => {
+ const resultDoc = []
+
+ for (const docObj of docArr) {
+ const validatedDoc = await SwaggerParser.validate(docObj)
+ resultDoc.push(validatedDoc)
+ }
+
+ return resultDoc
+ },
() => IMPORTER_INVALID_FILE_FORMAT
)
)
- ),
+ }),
// Deference the references
- TE.chainW((obj) =>
+ TE.chainW((docArr) =>
pipe(
TE.tryCatch(
- () => SwaggerParser.dereference(obj),
+ async () => {
+ const resultDoc = []
+
+ for (const docObj of docArr) {
+ const validatedDoc = await SwaggerParser.dereference(docObj)
+ resultDoc.push(validatedDoc)
+ }
+
+ return resultDoc
+ },
() => OPENAPI_DEREF_ERROR
)
)
),
- TE.chainW(convertOpenApiDocToHopp)
+ TE.chainW(convertOpenApiDocsToHopp)
)
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts b/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts
index ed16bf3af..bb9603dc0 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts
@@ -55,7 +55,11 @@ const readPMCollection = (def: string) =>
pipe(
def,
safeParseJSON,
- O.chain((data) => O.tryCatch(() => new PMCollection(data)))
+ O.chain((data) =>
+ O.tryCatch(() => {
+ return new PMCollection(data)
+ })
+ )
)
const getHoppReqHeaders = (item: Item): HoppRESTHeader[] =>
@@ -296,15 +300,17 @@ const getHoppFolder = (ig: ItemGroup- ): HoppCollection =>
headers: [],
})
-export const getHoppCollection = (coll: PMCollection) => getHoppFolder(coll)
+export const getHoppCollections = (collections: PMCollection[]) => {
+ return collections.map(getHoppFolder)
+}
-export const hoppPostmanImporter = (fileContent: string) =>
+export const hoppPostmanImporter = (fileContents: string[]) =>
pipe(
// Try reading
- fileContent,
- readPMCollection,
+ fileContents,
+ A.traverse(O.Applicative)(readPMCollection),
- O.map(flow(getHoppCollection, A.of)),
+ O.map(flow(getHoppCollections)),
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
)
diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/postmanEnv.ts b/packages/hoppscotch-common/src/helpers/import-export/import/postmanEnv.ts
index 428db3c6d..15176c37c 100644
--- a/packages/hoppscotch-common/src/helpers/import-export/import/postmanEnv.ts
+++ b/packages/hoppscotch-common/src/helpers/import-export/import/postmanEnv.ts
@@ -1,12 +1,11 @@
-import * as TE from "fp-ts/TaskEither"
-import * as O from "fp-ts/Option"
-
-import { IMPORTER_INVALID_FILE_FORMAT } from "."
-import { safeParseJSON } from "~/helpers/functional/json"
-
-import { z } from "zod"
import { Environment } from "@hoppscotch/data"
+import * as O from "fp-ts/Option"
+import * as TE from "fp-ts/TaskEither"
import { uniqueId } from "lodash-es"
+import { z } from "zod"
+
+import { safeParseJSON } from "~/helpers/functional/json"
+import { IMPORTER_INVALID_FILE_FORMAT } from "."
const postmanEnvSchema = z.object({
name: z.string(),
@@ -18,32 +17,44 @@ const postmanEnvSchema = z.object({
),
})
-export const postmanEnvImporter = (content: string) => {
- const parsedContent = safeParseJSON(content)
+type PostmanEnv = z.infer
- // parse json from the environments string
- if (O.isNone(parsedContent)) {
+export const postmanEnvImporter = (contents: string[]) => {
+ const parsedContents = contents.map((str) => safeParseJSON(str, true))
+ if (parsedContents.some((parsed) => O.isNone(parsed))) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
}
- const validationResult = postmanEnvSchema.safeParse(parsedContent.value)
+ const parsedValues = parsedContents.flatMap((parsed) => {
+ const unwrappedEntry = O.toNullable(parsed) as PostmanEnv[] | null
+
+ if (unwrappedEntry) {
+ return unwrappedEntry.map((entry) => ({
+ ...entry,
+ values: entry.values?.map((valueEntry) => ({
+ ...valueEntry,
+ value: String(valueEntry.value),
+ })),
+ }))
+ }
+ return null
+ })
+
+ const validationResult = z.array(postmanEnvSchema).safeParse(parsedValues)
if (!validationResult.success) {
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
}
- const postmanEnv = validationResult.data
-
- const environment: Environment = {
- id: uniqueId(),
- v: 1,
- name: postmanEnv.name,
- variables: [],
- }
-
- postmanEnv.values.forEach(({ key, value }) =>
- environment.variables.push({ key, value, secret: false })
+ // Convert `values` to `variables` to match the format expected by the system
+ const environments: Environment[] = validationResult.data.map(
+ ({ name, values }) => ({
+ id: uniqueId(),
+ v: 1,
+ name,
+ variables: values.map((entires) => ({ ...entires, secret: false })),
+ })
)
- return TE.right(environment)
+ return TE.right(environments)
}
diff --git a/packages/hoppscotch-common/src/newstore/collections.ts b/packages/hoppscotch-common/src/newstore/collections.ts
index 91cdbb364..3c021809a 100644
--- a/packages/hoppscotch-common/src/newstore/collections.ts
+++ b/packages/hoppscotch-common/src/newstore/collections.ts
@@ -61,7 +61,7 @@ export function navigateToFolderWithIndexPath(
let target = collections[indexPaths.shift() as number]
- while (indexPaths.length > 0)
+ while (indexPaths.length > 0 && target)
target = target.folders[indexPaths.shift() as number]
return target !== undefined ? target : null