feat: import environments from insomnia (#3625)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
@@ -392,7 +392,8 @@
|
||||
"hoppscotch_environment": "Hoppscotch Environment",
|
||||
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file",
|
||||
"postman_environment": "Postman Environment",
|
||||
"postman_environment_description": "Import Postman Environment JSON file",
|
||||
"postman_environment_description": "Import Postman Environment from a JSON file",
|
||||
"insomnia_environment_description": "Import Insomnia Environment from a JSON/YAML file",
|
||||
"environments_from_gist": "Import From Gist",
|
||||
"environments_from_gist_description": "Import Hoppscotch Environments From Gist",
|
||||
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist"
|
||||
@@ -968,4 +969,4 @@
|
||||
"team": "Team Workspace",
|
||||
"title": "Workspaces"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,22 @@ import { GistSource } from "~/helpers/import-export/import/import-sources/GistSo
|
||||
import { hoppEnvImporter } from "~/helpers/import-export/import/hoppEnv"
|
||||
|
||||
import * as E from "fp-ts/Either"
|
||||
import { appendEnvironments, environments$ } from "~/newstore/environments"
|
||||
import {
|
||||
appendEnvironments,
|
||||
addGlobalEnvVariable,
|
||||
environments$,
|
||||
} from "~/newstore/environments"
|
||||
|
||||
import { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||
import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql"
|
||||
import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv"
|
||||
import { insomniaEnvImporter } from "~/helpers/import-export/import/insomniaEnv"
|
||||
|
||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||
import IconPostman from "~icons/hopp/postman"
|
||||
import IconInsomnia from "~icons/hopp/insomnia"
|
||||
import IconUser from "~icons/lucide/user"
|
||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
||||
import { computed } from "vue"
|
||||
@@ -136,6 +142,51 @@ const PostmanEnvironmentsImport: ImporterOrExporter = {
|
||||
}),
|
||||
}
|
||||
|
||||
const insomniaEnvironmentsImport: ImporterOrExporter = {
|
||||
metadata: {
|
||||
id: "import.from_insomnia",
|
||||
name: "import.from_insomnia",
|
||||
icon: IconInsomnia,
|
||||
title: "import.from_json",
|
||||
applicableTo: ["personal-workspace", "team-workspace"],
|
||||
disabled: false,
|
||||
},
|
||||
component: FileSource({
|
||||
acceptedFileTypes: "application/json",
|
||||
caption: "import.insomnia_environment_description",
|
||||
onImportFromFile: async (environments) => {
|
||||
const res = await insomniaEnvImporter(environments)()
|
||||
|
||||
if (E.isLeft(res)) {
|
||||
showImportFailedError()
|
||||
return
|
||||
}
|
||||
|
||||
const globalEnvIndex = res.right.findIndex(
|
||||
(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)
|
||||
|
||||
platform.analytics?.logEvent({
|
||||
type: "HOPP_IMPORT_ENVIRONMENT",
|
||||
platform: "rest",
|
||||
workspaceType: isTeamEnvironment.value ? "team" : "personal",
|
||||
})
|
||||
|
||||
emit("hide-modal")
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
const EnvironmentsImportFromGIST: ImporterOrExporter = {
|
||||
metadata: {
|
||||
id: "import.environments_from_gist",
|
||||
@@ -255,6 +306,7 @@ const importerModules = [
|
||||
HoppEnvironmentsImport,
|
||||
EnvironmentsImportFromGIST,
|
||||
PostmanEnvironmentsImport,
|
||||
insomniaEnvironmentsImport,
|
||||
]
|
||||
|
||||
const exporterModules = computed(() => {
|
||||
@@ -271,7 +323,17 @@ const showImportFailedError = () => {
|
||||
toast.error(t("import.failed").toString())
|
||||
}
|
||||
|
||||
const handleImportToStore = async (environments: Environment[]) => {
|
||||
const handleImportToStore = async (
|
||||
environments: Environment[],
|
||||
globalEnv?: Environment
|
||||
) => {
|
||||
// if there's a global env, add them to the store
|
||||
if (globalEnv) {
|
||||
globalEnv.variables.forEach(({ key, value }) => {
|
||||
addGlobalEnvVariable({ key, value })
|
||||
})
|
||||
}
|
||||
|
||||
if (props.environmentType === "MY_ENV") {
|
||||
appendEnvironments(environments)
|
||||
toast.success(t("state.file_imported"))
|
||||
|
||||
16
packages/hoppscotch-common/src/helpers/functional/yaml.ts
Normal file
16
packages/hoppscotch-common/src/helpers/functional/yaml.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import yaml from "js-yaml"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { safeParseJSON } from "./json"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
export const safeParseYAML = (str: string) => O.tryCatch(() => yaml.load(str))
|
||||
|
||||
export const safeParseJSONOrYAML = (str: string) =>
|
||||
pipe(
|
||||
str,
|
||||
safeParseJSON,
|
||||
O.match(
|
||||
() => safeParseYAML(str),
|
||||
(data) => O.of(data)
|
||||
)
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
import { convert, ImportRequest } from "insomnia-importers"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import {
|
||||
HoppRESTAuth,
|
||||
HoppRESTHeader,
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
makeCollection,
|
||||
} from "@hoppscotch/data"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as S from "fp-ts/string"
|
||||
import * as TO from "fp-ts/TaskOption"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { replaceInsomniaTemplating } from "./insomniaEnv"
|
||||
|
||||
// TODO: Insomnia allows custom prefixes for Bearer token auth, Hoppscotch doesn't. We just ignore the prefix for now
|
||||
|
||||
@@ -32,10 +32,8 @@ type InsomniaRequestResource = ImportRequest & { _type: "request" }
|
||||
const parseInsomniaDoc = (content: string) =>
|
||||
TO.tryCatch(() => convert(content))
|
||||
|
||||
const replaceVarTemplating = flow(
|
||||
S.replace(/{{\s*/g, "<<"),
|
||||
S.replace(/\s*}}/g, ">>")
|
||||
)
|
||||
const replaceVarTemplating = (expression: string) =>
|
||||
replaceInsomniaTemplating(expression)
|
||||
|
||||
const getFoldersIn = (
|
||||
folder: InsomniaFolderResource | null,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as O from "fp-ts/Option"
|
||||
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
|
||||
import { z } from "zod"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
|
||||
|
||||
const insomniaResourcesSchema = z.object({
|
||||
resources: z.array(
|
||||
z
|
||||
.object({
|
||||
_type: z.string(),
|
||||
})
|
||||
.passthrough()
|
||||
),
|
||||
})
|
||||
|
||||
const insomniaEnvSchema = z.object({
|
||||
_type: z.literal("environment"),
|
||||
name: z.string(),
|
||||
data: z.record(z.string()),
|
||||
})
|
||||
|
||||
export const replaceInsomniaTemplating = (expression: string) => {
|
||||
const regex = /\{\{ _\.([^}]+) \}\}/g
|
||||
return expression.replaceAll(regex, "<<$1>>")
|
||||
}
|
||||
|
||||
export const insomniaEnvImporter = (content: string) => {
|
||||
const parsedContent = safeParseJSONOrYAML(content)
|
||||
|
||||
if (O.isNone(parsedContent)) {
|
||||
return TE.left(IMPORTER_INVALID_FILE_FORMAT)
|
||||
}
|
||||
|
||||
const validationResult = insomniaResourcesSchema.safeParse(
|
||||
parsedContent.value
|
||||
)
|
||||
|
||||
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<string, unknown>
|
||||
const stringifiedData: Record<string, string> = {}
|
||||
|
||||
Object.keys(envResourceData).forEach((key) => {
|
||||
stringifiedData[key] = String(envResourceData[key])
|
||||
})
|
||||
|
||||
return { ...envResource, data: stringifiedData }
|
||||
})
|
||||
|
||||
const environments: Environment[] = []
|
||||
|
||||
insomniaEnvs.forEach((insomniaEnv) => {
|
||||
const parsedInsomniaEnv = insomniaEnvSchema.safeParse(insomniaEnv)
|
||||
|
||||
if (parsedInsomniaEnv.success) {
|
||||
const environment: Environment = {
|
||||
name: parsedInsomniaEnv.data.name,
|
||||
variables: Object.entries(parsedInsomniaEnv.data.data).map(
|
||||
([key, value]) => ({ key, value })
|
||||
),
|
||||
}
|
||||
|
||||
environments.push(environment)
|
||||
}
|
||||
})
|
||||
|
||||
const processedEnvironments = environments.map((env) => ({
|
||||
...env,
|
||||
variables: env.variables.map((variable) => ({
|
||||
...variable,
|
||||
value: replaceInsomniaTemplating(variable.value),
|
||||
})),
|
||||
}))
|
||||
|
||||
return TE.right(processedEnvironments)
|
||||
}
|
||||
Reference in New Issue
Block a user