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": "Hoppscotch Environment",
|
||||||
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file",
|
"hoppscotch_environment_description": "Import Hoppscotch Environment JSON file",
|
||||||
"postman_environment": "Postman Environment",
|
"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": "Import From Gist",
|
||||||
"environments_from_gist_description": "Import Hoppscotch Environments From Gist",
|
"environments_from_gist_description": "Import Hoppscotch Environments From Gist",
|
||||||
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist"
|
"gql_collections_from_gist_description": "Import GraphQL Collections From Gist"
|
||||||
@@ -968,4 +969,4 @@
|
|||||||
"team": "Team Workspace",
|
"team": "Team Workspace",
|
||||||
"title": "Workspaces"
|
"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 { hoppEnvImporter } from "~/helpers/import-export/import/hoppEnv"
|
||||||
|
|
||||||
import * as E from "fp-ts/Either"
|
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 { createTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql"
|
import { CreateTeamEnvironmentMutation } from "~/helpers/backend/graphql"
|
||||||
import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv"
|
import { postmanEnvImporter } from "~/helpers/import-export/import/postmanEnv"
|
||||||
|
import { insomniaEnvImporter } from "~/helpers/import-export/import/insomniaEnv"
|
||||||
|
|
||||||
import IconFolderPlus from "~icons/lucide/folder-plus"
|
import IconFolderPlus from "~icons/lucide/folder-plus"
|
||||||
import IconPostman from "~icons/hopp/postman"
|
import IconPostman from "~icons/hopp/postman"
|
||||||
|
import IconInsomnia from "~icons/hopp/insomnia"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
import { initializeDownloadCollection } from "~/helpers/import-export/export"
|
||||||
import { computed } from "vue"
|
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 = {
|
const EnvironmentsImportFromGIST: ImporterOrExporter = {
|
||||||
metadata: {
|
metadata: {
|
||||||
id: "import.environments_from_gist",
|
id: "import.environments_from_gist",
|
||||||
@@ -255,6 +306,7 @@ const importerModules = [
|
|||||||
HoppEnvironmentsImport,
|
HoppEnvironmentsImport,
|
||||||
EnvironmentsImportFromGIST,
|
EnvironmentsImportFromGIST,
|
||||||
PostmanEnvironmentsImport,
|
PostmanEnvironmentsImport,
|
||||||
|
insomniaEnvironmentsImport,
|
||||||
]
|
]
|
||||||
|
|
||||||
const exporterModules = computed(() => {
|
const exporterModules = computed(() => {
|
||||||
@@ -271,7 +323,17 @@ const showImportFailedError = () => {
|
|||||||
toast.error(t("import.failed").toString())
|
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") {
|
if (props.environmentType === "MY_ENV") {
|
||||||
appendEnvironments(environments)
|
appendEnvironments(environments)
|
||||||
toast.success(t("state.file_imported"))
|
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 { convert, ImportRequest } from "insomnia-importers"
|
||||||
import { pipe, flow } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import {
|
import {
|
||||||
HoppRESTAuth,
|
HoppRESTAuth,
|
||||||
HoppRESTHeader,
|
HoppRESTHeader,
|
||||||
@@ -12,10 +12,10 @@ import {
|
|||||||
makeCollection,
|
makeCollection,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
import * as S from "fp-ts/string"
|
|
||||||
import * as TO from "fp-ts/TaskOption"
|
import * as TO from "fp-ts/TaskOption"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
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
|
// 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) =>
|
const parseInsomniaDoc = (content: string) =>
|
||||||
TO.tryCatch(() => convert(content))
|
TO.tryCatch(() => convert(content))
|
||||||
|
|
||||||
const replaceVarTemplating = flow(
|
const replaceVarTemplating = (expression: string) =>
|
||||||
S.replace(/{{\s*/g, "<<"),
|
replaceInsomniaTemplating(expression)
|
||||||
S.replace(/\s*}}/g, ">>")
|
|
||||||
)
|
|
||||||
|
|
||||||
const getFoldersIn = (
|
const getFoldersIn = (
|
||||||
folder: InsomniaFolderResource | null,
|
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