Compare commits

..

17 Commits

Author SHA1 Message Date
Andrew Bastin
0d78e6d209 Merge pull request #2243 from RishabhAgarwal-2001/2087-openapi 2022-06-29 22:14:42 +05:30
Andrew Bastin
172e459872 Merge branch 'orphan-pr/2243' into 2087-openapi 2022-06-29 22:14:14 +05:30
Rishabh Agarwal
5233c36904 refactor: comments address 2022-05-17 15:24:36 +05:30
Rishabh Agarwal
6417ece710 Merge branch '2087-openapi' of https://github.com/RishabhAgarwal-2001/hoppscotch into 2087-openapi 2022-05-17 14:42:02 +05:30
Andrew Bastin
a3d92c862c refactor: rename to prettyPrintJSON and add jsdoc 2022-05-03 18:05:39 +05:30
Rishabh Agarwal
f84b67ec39 feat: handle oneof and anyof 2022-05-03 18:04:34 +05:30
Rishabh Agarwal
3afd2c1cf2 refactor: address comments 2022-05-03 18:04:34 +05:30
Rishabh Agarwal
ed49c0b72c refactor: examplev2 created and lintfix 2022-05-03 18:04:33 +05:30
Rishabh Agarwal
bc2f81ff25 feat: v3 requests example working 2022-05-03 18:04:33 +05:30
Rishabh Agarwal
721a201e7a refactor: removed unecessary const 2022-05-03 18:04:33 +05:30
Rishabh Agarwal
b0071e6859 feat: oasv2 example generation complete 2022-05-03 18:04:33 +05:30
Rishabh Agarwal
3a41d79c2f feat: handle oneof and anyof 2022-04-27 23:48:26 +05:30
Rishabh Agarwal
2fd9eb0767 refactor: address comments 2022-04-18 19:29:45 +05:30
Rishabh Agarwal
2aef9c5691 refactor: examplev2 created and lintfix 2022-04-18 15:45:52 +05:30
Rishabh Agarwal
14e9d19ae6 feat: v3 requests example working 2022-04-15 12:06:51 +05:30
Rishabh Agarwal
a04b634089 refactor: removed unecessary const 2022-04-08 11:15:39 +05:30
Rishabh Agarwal
2244d38439 feat: oasv2 example generation complete 2022-04-08 11:06:25 +05:30
63 changed files with 1032 additions and 1973 deletions

View File

@@ -84,7 +84,7 @@
_Customized themes are synced with cloud / local session_
🔥 **PWA:** Install as a [PWA](https://web.dev/what-are-pwas/) on your device.
🔥 **PWA:** Install as a [PWA](https://developers.google.com/web/progressive-web-apps) on your device.
- Instant loading with Service Workers
- Offline support

View File

@@ -6,26 +6,21 @@
'!flex-row-reverse': SIDEBAR_ON_LEFT && mdAndLarger,
}"
:horizontal="!mdAndLarger"
@resize="setPaneEvent($event, 'vertical')"
>
<Pane
:size="PANE_MAIN_SIZE"
size="75"
min-size="65"
class="hide-scrollbar !overflow-auto flex flex-col"
>
<Splitpanes
class="smart-splitter"
:horizontal="COLUMN_LAYOUT"
@resize="setPaneEvent($event, 'horizontal')"
>
<Splitpanes class="smart-splitter" :horizontal="COLUMN_LAYOUT">
<Pane
:size="PANE_MAIN_TOP_SIZE"
:size="COLUMN_LAYOUT ? 45 : 50"
class="hide-scrollbar !overflow-auto flex flex-col"
>
<slot name="primary" />
</Pane>
<Pane
:size="PANE_MAIN_BOTTOM_SIZE"
:size="COLUMN_LAYOUT ? 65 : 50"
class="flex flex-col hide-scrollbar !overflow-auto"
>
<slot name="secondary" />
@@ -34,7 +29,7 @@
</Pane>
<Pane
v-if="SIDEBAR && hasSidebar"
:size="PANE_SIDEBAR_SIZE"
size="25"
min-size="20"
class="hide-scrollbar !overflow-auto flex flex-col"
>
@@ -47,9 +42,8 @@
import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css"
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { computed, useSlots, ref } from "@nuxtjs/composition-api"
import { computed, useSlots } from "@nuxtjs/composition-api"
import { useSetting } from "~/newstore/settings"
import { setLocalConfig, getLocalConfig } from "~/newstore/localpersistence"
const SIDEBAR_ON_LEFT = useSetting("SIDEBAR_ON_LEFT")
@@ -63,60 +57,4 @@ const SIDEBAR = useSetting("SIDEBAR")
const slots = useSlots()
const hasSidebar = computed(() => !!slots.sidebar)
const props = defineProps({
layoutId: {
type: String,
default: null,
},
})
type PaneEvent = {
max: number
min: number
size: number
}
const PANE_SIDEBAR_SIZE = ref(25)
const PANE_MAIN_SIZE = ref(75)
const PANE_MAIN_TOP_SIZE = ref(45)
const PANE_MAIN_BOTTOM_SIZE = ref(65)
if (!COLUMN_LAYOUT.value) {
PANE_MAIN_TOP_SIZE.value = 50
PANE_MAIN_BOTTOM_SIZE.value = 50
}
function setPaneEvent(event: PaneEvent[], type: "vertical" | "horizontal") {
if (!props.layoutId) return
const storageKey = `${props.layoutId}-pane-config-${type}`
setLocalConfig(storageKey, JSON.stringify(event))
}
function populatePaneEvent() {
if (!props.layoutId) return
const verticalPaneData = getPaneData("vertical")
if (verticalPaneData) {
const [mainPane, sidebarPane] = verticalPaneData
PANE_MAIN_SIZE.value = mainPane?.size
PANE_SIDEBAR_SIZE.value = sidebarPane?.size
}
const horizontalPaneData = getPaneData("horizontal")
if (horizontalPaneData) {
const [mainTopPane, mainBottomPane] = horizontalPaneData
PANE_MAIN_TOP_SIZE.value = mainTopPane?.size
PANE_MAIN_BOTTOM_SIZE.value = mainBottomPane?.size
}
}
function getPaneData(type: "vertical" | "horizontal"): PaneEvent[] | null {
const storageKey = `${props.layoutId}-pane-config-${type}`
const paneEvent = getLocalConfig(storageKey)
if (!paneEvent) return null
return JSON.parse(paneEvent)
}
populatePaneEvent()
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div v-show="show">
<div v-if="show">
<SmartTabs
:id="'collections_tab'"
v-model="selectedCollectionTab"

View File

@@ -11,7 +11,6 @@
autocomplete="off"
:placeholder="$t('action.search')"
class="py-2 pl-4 pr-2 bg-transparent"
:disabled="collectionsType.type == 'team-collections'"
/>
</div>
<CollectionsChooseType

View File

@@ -339,14 +339,7 @@ const selectRequest = () => {
confirmChange.value = false
setRestReq(props.request)
} else if (!active.value) {
// If the current request is the same as the request to be loaded in, there is no data loss
const currentReq = getRESTRequest()
if (isEqualHoppRESTRequest(currentReq, props.request)) {
setRestReq(props.request)
} else {
confirmChange.value = true
}
confirmChange.value = true
} else {
const currentReqWithNoChange = active.value.req
const currentFullReq = getRESTRequest()

View File

@@ -261,7 +261,7 @@ const active = useReadonlyStream(restSaveContext$, null)
const isSelected = computed(
() =>
props.picked &&
props.picked.pickedType === "teams-request" &&
props.picked.pickedType === "teams-collection" &&
props.picked.requestID === props.requestIndex
)
@@ -312,7 +312,7 @@ const selectRequest = () => {
if (props.saveRequest) {
emit("select", {
picked: {
pickedType: "teams-request",
pickedType: "teams-collection",
requestID: props.requestIndex,
},
})

View File

@@ -50,18 +50,18 @@
</div>
<div class="border rounded divide-y divide-dividerLight border-divider">
<div
v-for="({ id, env }, index) in vars"
:key="`variable-${id}-${index}`"
v-for="(variable, index) in vars"
:key="`variable-${index}`"
class="flex divide-x divide-dividerLight"
>
<input
v-model="env.key"
v-model="variable.key"
class="flex flex-1 px-4 py-2 bg-transparent"
:placeholder="`${t('count.variable', { count: index + 1 })}`"
:name="'param' + index"
/>
<SmartEnvInput
v-model="env.value"
v-model="variable.value"
:placeholder="`${t('count.value', { count: index + 1 })}`"
:envs="liveEnvs"
:name="'value' + index"
@@ -119,9 +119,6 @@
import clone from "lodash/clone"
import { computed, ref, watch } from "@nuxtjs/composition-api"
import * as E from "fp-ts/Either"
import * as A from "fp-ts/Array"
import * as O from "fp-ts/Option"
import { pipe, flow } from "fp-ts/function"
import { Environment, parseTemplateStringE } from "@hoppscotch/data"
import { refAutoReset } from "@vueuse/core"
import {
@@ -140,14 +137,6 @@ import {
useToast,
} from "~/helpers/utils/composables"
type EnvironmentVariable = {
id: number
env: {
key: string
value: string
}
}
const t = useI18n()
const toast = useToast()
@@ -170,12 +159,8 @@ const emit = defineEmits<{
(e: "hide-modal"): void
}>()
const idTicker = ref(0)
const name = ref<string | null>(null)
const vars = ref<EnvironmentVariable[]>([
{ id: idTicker.value++, env: { key: "", value: "" } },
])
const vars = ref([{ key: "", value: "" }])
const clearIcon = refAutoReset<"trash-2" | "check">("trash-2", 1000)
@@ -202,15 +187,15 @@ const workingEnv = computed(() => {
const envList = useReadonlyStream(environments$, []) || props.envVars()
const evnExpandError = computed(() => {
const variables = pipe(
vars.value,
A.map((e) => e.env)
)
for (const variable of vars.value) {
const result = parseTemplateStringE(variable.value.toString(), vars.value)
return pipe(
variables,
A.exists(({ value }) => E.isLeft(parseTemplateStringE(value, variables)))
)
if (E.isLeft(result)) {
console.error("error", result.left)
return true
}
}
return false
})
const liveEnvs = computed(() => {
@@ -233,38 +218,21 @@ watch(
(show) => {
if (show) {
name.value = workingEnv.value?.name ?? null
vars.value = pipe(
workingEnv.value?.variables ?? [],
A.map((e) => ({
id: idTicker.value++,
env: clone(e),
}))
)
vars.value = clone(workingEnv.value?.variables ?? [])
}
}
)
const clearContent = () => {
vars.value = [
{
id: idTicker.value++,
env: {
key: "",
value: "",
},
},
]
vars.value = []
clearIcon.value = "check"
toast.success(`${t("state.cleared")}`)
}
const addEnvironmentVariable = () => {
vars.value.push({
id: idTicker.value++,
env: {
key: "",
value: "",
},
key: "",
value: "",
})
}
@@ -278,19 +246,9 @@ const saveEnvironment = () => {
return
}
const filterdVariables = pipe(
vars.value,
A.filterMap(
flow(
O.fromPredicate((e) => e.env.key !== ""),
O.map((e) => e.env)
)
)
)
const environmentUpdated: Environment = {
name: name.value,
variables: filterdVariables,
variables: vars.value,
}
if (props.action === "new") {

View File

@@ -1,13 +1,363 @@
<template>
<div>
<HttpQueryParams />
<br />
<HttpPathVariables />
<div class="flex flex-col flex-1">
<div
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold"
>
<label class="font-semibold text-secondaryLight">
{{ t("request.parameter_list") }}
</label>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://docs.hoppscotch.io/features/parameters"
blank
:title="t('app.wiki')"
svg="help-circle"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.clear_all')"
svg="trash-2"
@click.native="clearContent()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('state.bulk_mode')"
svg="edit"
:class="{ '!text-accent': bulkMode }"
@click.native="bulkMode = !bulkMode"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('add.new')"
svg="plus"
:disabled="bulkMode"
@click.native="addParam"
/>
</div>
</div>
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-col flex-1"></div>
<div v-else>
<draggable
v-model="workingParams"
animation="250"
handle=".draggable-handle"
draggable=".draggable-content"
ghost-class="cursor-move"
chosen-class="bg-primaryLight"
drag-class="cursor-grabbing"
>
<div
v-for="(param, index) in workingParams"
:key="`param-${param.id}-${index}`"
class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
>
<span>
<ButtonSecondary
svg="grip-vertical"
class="cursor-auto text-primary hover:text-primary"
:class="{
'draggable-handle group-hover:text-secondaryLight !cursor-grab':
index !== workingParams?.length - 1,
}"
tabindex="-1"
/>
</span>
<SmartEnvInput
v-model="param.key"
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
@change="
updateParam(index, {
id: param.id,
key: $event,
value: param.value,
active: param.active,
})
"
/>
<SmartEnvInput
v-model="param.value"
:placeholder="`${t('count.value', { count: index + 1 })}`"
@change="
updateParam(index, {
id: param.id,
key: param.key,
value: $event,
active: param.active,
})
"
/>
<span>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="
param.hasOwnProperty('active')
? param.active
? t('action.turn_off')
: t('action.turn_on')
: t('action.turn_off')
"
:svg="
param.hasOwnProperty('active')
? param.active
? 'check-circle'
: 'circle'
: 'check-circle'
"
color="green"
@click.native="
updateParam(index, {
id: param.id,
key: param.key,
value: param.value,
active: param.hasOwnProperty('active')
? !param.active
: false,
})
"
/>
</span>
<span>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.remove')"
svg="trash"
color="red"
@click.native="deleteParam(index)"
/>
</span>
</div>
</draggable>
<div
v-if="workingParams.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
:src="`/images/states/${$colorMode.value}/add_files.svg`"
loading="lazy"
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
:alt="`${t('empty.parameters')}`"
/>
<span class="pb-4 text-center">{{ t("empty.parameters") }}</span>
<ButtonSecondary
:label="`${t('add.new')}`"
svg="plus"
filled
class="mb-4"
@click.native="addParam"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
/**
* TODO: Code duplication between QueryParams and Variables
*/
import { Ref, ref, watch } from "@nuxtjs/composition-api"
import { flow, pipe } from "fp-ts/function"
import * as O from "fp-ts/Option"
import * as A from "fp-ts/Array"
import * as RA from "fp-ts/ReadonlyArray"
import * as E from "fp-ts/Either"
import {
HoppRESTParam,
parseRawKeyValueEntriesE,
rawKeyValueEntriesToString,
RawKeyValueEntry,
} from "@hoppscotch/data"
import isEqual from "lodash/isEqual"
import cloneDeep from "lodash/cloneDeep"
import draggable from "vuedraggable"
import linter from "~/helpers/editor/linting/rawKeyValue"
import { useCodemirror } from "~/helpers/editor/codemirror"
import { useI18n, useToast, useStream } from "~/helpers/utils/composables"
import { restParams$, setRESTParams } from "~/newstore/RESTSession"
import { throwError } from "~/helpers/functional/error"
import { objRemoveKey } from "~/helpers/functional/object"
const t = useI18n()
const toast = useToast()
const idTicker = ref(0)
const bulkMode = ref(false)
const bulkParams = ref("")
const bulkEditor = ref<any | null>(null)
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
useCodemirror(bulkEditor, bulkParams, {
extendedEditorConfig: {
mode: "text/x-yaml",
placeholder: `${t("state.bulk_mode_placeholder")}`,
},
linter,
completer: null,
environmentHighlights: true,
})
// The functional parameters list (the parameters actually applied to the session)
const params = useStream(restParams$, [], setRESTParams) as Ref<HoppRESTParam[]>
// The UI representation of the parameters list (has the empty end param)
const workingParams = ref<Array<HoppRESTParam & { id: number }>>([
{
id: idTicker.value++,
key: "",
value: "",
active: true,
},
])
// Rule: Working Params always have last element is always an empty param
watch(workingParams, (paramsList) => {
if (paramsList.length > 0 && paramsList[paramsList.length - 1].key !== "") {
workingParams.value.push({
id: idTicker.value++,
key: "",
value: "",
active: true,
})
}
})
// Sync logic between params and working/bulk params
watch(
params,
(newParamsList) => {
// Sync should overwrite working params
const filteredWorkingParams: HoppRESTParam[] = pipe(
workingParams.value,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
const filteredBulkParams = pipe(
parseRawKeyValueEntriesE(bulkParams.value),
E.map(
flow(
RA.filter((e) => e.key !== ""),
RA.toArray
)
),
E.getOrElse(() => [] as RawKeyValueEntry[])
)
if (!isEqual(newParamsList, filteredWorkingParams)) {
workingParams.value = pipe(
newParamsList,
A.map((x) => ({ id: idTicker.value++, ...x }))
)
}
if (!isEqual(newParamsList, filteredBulkParams)) {
bulkParams.value = rawKeyValueEntriesToString(newParamsList)
}
},
{ immediate: true }
)
watch(workingParams, (newWorkingParams) => {
const fixedParams = pipe(
newWorkingParams,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
if (!isEqual(params.value, fixedParams)) {
params.value = cloneDeep(fixedParams)
}
})
watch(bulkParams, (newBulkParams) => {
const filteredBulkParams = pipe(
parseRawKeyValueEntriesE(newBulkParams),
E.map(
flow(
RA.filter((e) => e.key !== ""),
RA.toArray
)
),
E.getOrElse(() => [] as RawKeyValueEntry[])
)
if (!isEqual(params.value, filteredBulkParams)) {
params.value = filteredBulkParams
}
})
const addParam = () => {
workingParams.value.push({
id: idTicker.value++,
key: "",
value: "",
active: true,
})
}
const updateParam = (index: number, param: HoppRESTParam & { id: number }) => {
workingParams.value = workingParams.value.map((h, i) =>
i === index ? param : h
)
}
const deleteParam = (index: number) => {
const paramsBeforeDeletion = cloneDeep(workingParams.value)
if (
!(
paramsBeforeDeletion.length > 0 &&
index === paramsBeforeDeletion.length - 1
)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
workingParams.value = paramsBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingParams.value = pipe(
workingParams.value,
A.deleteAt(index),
O.getOrElseW(() => throwError("Working Params Deletion Out of Bounds"))
)
}
const clearContent = () => {
// set params list to the initial state
workingParams.value = [
{
id: idTicker.value++,
key: "",
value: "",
active: true,
},
]
bulkParams.value = ""
}
</script>

View File

@@ -1,271 +0,0 @@
<template>
<div>
<div
v-if="envExpandError"
class="w-full px-4 py-2 mb-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
>
{{ nestedVars }}
</div>
<div
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold"
>
<label class="font-semibold text-secondaryLight"> My Variables </label>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://docs.hoppscotch.io/features/#"
blank
:title="t('app.wiki')"
svg="help-circle"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.clear_all')"
svg="trash-2"
@click.native="clearContent()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('add.new')"
svg="plus"
@click.native="addVar"
/>
</div>
</div>
<div>
<draggable
v-model="workingVars"
animation="250"
handle=".draggable-handle"
draggable=".draggable-content"
ghost-class="cursor-move"
chosen-class="bg-primaryLight"
drag-class="cursor-grabbing"
>
<div
v-for="(variable, index) in workingVars"
:key="`vari-${variable.id}-${index}`"
class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
>
<span>
<ButtonSecondary
svg="grip-vertical"
class="cursor-auto text-primary hover:text-primary"
:class="{
'draggable-handle group-hover:text-secondaryLight !cursor-grab':
index !== workingVars?.length - 1,
}"
tabindex="-1"
/>
</span>
<SmartEnvInput
v-model="variable.key"
:placeholder="`${t('count.variable', { count: index + 1 })}`"
@change="
updateVar(index, {
id: variable.id,
key: $event,
value: variable.value,
})
"
/>
<SmartEnvInput
v-model="variable.value"
:placeholder="`${t('count.value', { count: index + 1 })}`"
@change="
updateVar(index, {
id: variable.id,
key: variable.key,
value: $event,
})
"
/>
<span>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.remove')"
svg="trash"
color="red"
@click.native="deleteVar(index)"
/>
</span>
</div>
</draggable>
<div
v-if="workingVars.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
:src="`/images/states/${$colorMode.value}/add_files.svg`"
loading="lazy"
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
:alt="`${t('empty.parameters')}`"
/>
<span class="pb-4 text-center">{{ emptyVars }}</span>
<ButtonSecondary
:label="`${t('add.new')}`"
svg="plus"
filled
class="mb-4"
@click.native="addVar"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, Ref, ref, watch } from "@nuxtjs/composition-api"
import { flow, pipe } from "fp-ts/function"
import * as O from "fp-ts/Option"
import * as A from "fp-ts/Array"
import { HoppRESTVar, parseMyVariablesString } from "@hoppscotch/data"
import draggable from "vuedraggable"
import cloneDeep from "lodash/cloneDeep"
import isEqual from "lodash/isEqual"
import * as E from "fp-ts/Either"
import { useI18n, useStream, useToast } from "~/helpers/utils/composables"
import { throwError } from "~/helpers/functional/error"
import { restVars$, setRESTVars } from "~/newstore/RESTSession"
import { objRemoveKey } from "~/helpers/functional/object"
const t = useI18n()
const toast = useToast()
const emptyVars: string = "Add a new variable"
const nestedVars: string = "nested variables greater than 10 levels"
const idTicker = ref(0)
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
// The functional variables list (the variables actually applied to the session)
const vars = useStream(restVars$, [], setRESTVars) as Ref<HoppRESTVar[]>
// The UI representation of the variables list (has the empty end variable)
const workingVars = ref<Array<HoppRESTVar & { id: number }>>([
{
id: idTicker.value++,
key: "",
value: "",
},
])
// Rule: Working vars always have last element is always an empty var
watch(workingVars, (varsList) => {
if (varsList.length > 0 && varsList[varsList.length - 1].key !== "") {
workingVars.value.push({
id: idTicker.value++,
key: "",
value: "",
})
}
})
// Sync logic between params and working/bulk params
watch(
vars,
(newVarsList) => {
// Sync should overwrite working params
const filteredWorkingVars: HoppRESTVar[] = pipe(
workingVars.value,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
if (!isEqual(newVarsList, filteredWorkingVars)) {
workingVars.value = pipe(
newVarsList,
A.map((x) => ({ id: idTicker.value++, ...x }))
)
}
},
{ immediate: true }
)
watch(workingVars, (newWorkingVars) => {
const fixedVars = pipe(
newWorkingVars,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
if (!isEqual(vars.value, fixedVars)) {
vars.value = cloneDeep(fixedVars)
}
})
const addVar = () => {
workingVars.value.push({
id: idTicker.value++,
key: "",
value: "",
})
}
const updateVar = (index: number, vari: HoppRESTVar & { id: number }) => {
workingVars.value = workingVars.value.map((h, i) => (i === index ? vari : h))
}
const deleteVar = (index: number) => {
const varsBeforeDeletion = cloneDeep(workingVars.value)
if (
!(varsBeforeDeletion.length > 0 && index === varsBeforeDeletion.length - 1)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
workingVars.value = varsBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingVars.value = pipe(
workingVars.value,
A.deleteAt(index),
O.getOrElseW(() => throwError("Working Params Deletion Out of Bounds"))
)
}
const envExpandError = computed(() => {
const variables = pipe(vars.value)
return pipe(
variables,
A.exists(({ value }) => E.isLeft(parseMyVariablesString(value, variables)))
)
})
const clearContent = () => {
// set params list to the initial state
workingVars.value = [
{
id: idTicker.value++,
key: "",
value: "",
},
]
}
</script>

View File

@@ -1,363 +0,0 @@
<template>
<div class="flex flex-col flex-1">
<div
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight top-upperMobileSecondaryStickyFold sm:top-upperSecondaryStickyFold"
>
<label class="font-semibold text-secondaryLight">
{{ t("request.parameter_list") }}
</label>
<div class="flex">
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://docs.hoppscotch.io/features/parameters"
blank
:title="t('app.wiki')"
svg="help-circle"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.clear_all')"
svg="trash-2"
@click.native="clearContent()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('state.bulk_mode')"
svg="edit"
:class="{ '!text-accent': bulkMode }"
@click.native="bulkMode = !bulkMode"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('add.new')"
svg="plus"
:disabled="bulkMode"
@click.native="addParam"
/>
</div>
</div>
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-col flex-1"></div>
<div v-else>
<draggable
v-model="workingParams"
animation="250"
handle=".draggable-handle"
draggable=".draggable-content"
ghost-class="cursor-move"
chosen-class="bg-primaryLight"
drag-class="cursor-grabbing"
>
<div
v-for="(param, index) in workingParams"
:key="`param-${param.id}-${index}`"
class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
>
<span>
<ButtonSecondary
svg="grip-vertical"
class="cursor-auto text-primary hover:text-primary"
:class="{
'draggable-handle group-hover:text-secondaryLight !cursor-grab':
index !== workingParams?.length - 1,
}"
tabindex="-1"
/>
</span>
<SmartEnvInput
v-model="param.key"
:placeholder="`${t('count.parameter', { count: index + 1 })}`"
@change="
updateParam(index, {
id: param.id,
key: $event,
value: param.value,
active: param.active,
})
"
/>
<SmartEnvInput
v-model="param.value"
:placeholder="`${t('count.value', { count: index + 1 })}`"
@change="
updateParam(index, {
id: param.id,
key: param.key,
value: $event,
active: param.active,
})
"
/>
<span>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="
param.hasOwnProperty('active')
? param.active
? t('action.turn_off')
: t('action.turn_on')
: t('action.turn_off')
"
:svg="
param.hasOwnProperty('active')
? param.active
? 'check-circle'
: 'circle'
: 'check-circle'
"
color="green"
@click.native="
updateParam(index, {
id: param.id,
key: param.key,
value: param.value,
active: param.hasOwnProperty('active')
? !param.active
: false,
})
"
/>
</span>
<span>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="t('action.remove')"
svg="trash"
color="red"
@click.native="deleteParam(index)"
/>
</span>
</div>
</draggable>
<div
v-if="workingParams.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
:src="`/images/states/${$colorMode.value}/add_files.svg`"
loading="lazy"
class="inline-flex flex-col object-contain object-center w-16 h-16 my-4"
:alt="`${t('empty.parameters')}`"
/>
<span class="pb-4 text-center">{{ t("empty.parameters") }}</span>
<ButtonSecondary
:label="`${t('add.new')}`"
svg="plus"
filled
class="mb-4"
@click.native="addParam"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Ref, ref, watch } from "@nuxtjs/composition-api"
import { flow, pipe } from "fp-ts/function"
import * as O from "fp-ts/Option"
import * as A from "fp-ts/Array"
import * as RA from "fp-ts/ReadonlyArray"
import * as E from "fp-ts/Either"
import {
HoppRESTParam,
parseRawKeyValueEntriesE,
rawKeyValueEntriesToString,
RawKeyValueEntry,
} from "@hoppscotch/data"
import isEqual from "lodash/isEqual"
import cloneDeep from "lodash/cloneDeep"
import draggable from "vuedraggable"
import linter from "~/helpers/editor/linting/rawKeyValue"
import { useCodemirror } from "~/helpers/editor/codemirror"
import { useI18n, useToast, useStream } from "~/helpers/utils/composables"
import { restParams$, setRESTParams } from "~/newstore/RESTSession"
import { throwError } from "~/helpers/functional/error"
import { objRemoveKey } from "~/helpers/functional/object"
const t = useI18n()
const toast = useToast()
const idTicker = ref(0)
const bulkMode = ref(false)
const bulkParams = ref("")
const bulkEditor = ref<any | null>(null)
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
useCodemirror(bulkEditor, bulkParams, {
extendedEditorConfig: {
mode: "text/x-yaml",
placeholder: `${t("state.bulk_mode_placeholder")}`,
},
linter,
completer: null,
environmentHighlights: true,
})
// The functional parameters list (the parameters actually applied to the session)
const params = useStream(restParams$, [], setRESTParams) as Ref<HoppRESTParam[]>
// The UI representation of the parameters list (has the empty end param)
const workingParams = ref<Array<HoppRESTParam & { id: number }>>([
{
id: idTicker.value++,
key: "",
value: "",
active: true,
},
])
// Rule: Working Params always have last element is always an empty param
watch(workingParams, (paramsList) => {
if (paramsList.length > 0 && paramsList[paramsList.length - 1].key !== "") {
workingParams.value.push({
id: idTicker.value++,
key: "",
value: "",
active: true,
})
}
})
// Sync logic between params and working/bulk params
watch(
params,
(newParamsList) => {
// Sync should overwrite working params
const filteredWorkingParams: HoppRESTParam[] = pipe(
workingParams.value,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
const filteredBulkParams = pipe(
parseRawKeyValueEntriesE(bulkParams.value),
E.map(
flow(
RA.filter((e) => e.key !== ""),
RA.toArray
)
),
E.getOrElse(() => [] as RawKeyValueEntry[])
)
if (!isEqual(newParamsList, filteredWorkingParams)) {
workingParams.value = pipe(
newParamsList,
A.map((x) => ({ id: idTicker.value++, ...x }))
)
}
if (!isEqual(newParamsList, filteredBulkParams)) {
bulkParams.value = rawKeyValueEntriesToString(newParamsList)
}
},
{ immediate: true }
)
watch(workingParams, (newWorkingParams) => {
const fixedParams = pipe(
newWorkingParams,
A.filterMap(
flow(
O.fromPredicate((e) => e.key !== ""),
O.map(objRemoveKey("id"))
)
)
)
if (!isEqual(params.value, fixedParams)) {
params.value = cloneDeep(fixedParams)
}
})
watch(bulkParams, (newBulkParams) => {
const filteredBulkParams = pipe(
parseRawKeyValueEntriesE(newBulkParams),
E.map(
flow(
RA.filter((e) => e.key !== ""),
RA.toArray
)
),
E.getOrElse(() => [] as RawKeyValueEntry[])
)
if (!isEqual(params.value, filteredBulkParams)) {
params.value = filteredBulkParams
}
})
const addParam = () => {
workingParams.value.push({
id: idTicker.value++,
key: "",
value: "",
active: true,
})
}
const updateParam = (index: number, param: HoppRESTParam & { id: number }) => {
workingParams.value = workingParams.value.map((h, i) =>
i === index ? param : h
)
}
const deleteParam = (index: number) => {
const paramsBeforeDeletion = cloneDeep(workingParams.value)
if (
!(
paramsBeforeDeletion.length > 0 &&
index === paramsBeforeDeletion.length - 1
)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
workingParams.value = paramsBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingParams.value = pipe(
workingParams.value,
A.deleteAt(index),
O.getOrElseW(() => throwError("Working Params Deletion Out of Bounds"))
)
}
const clearContent = () => {
// set params list to the initial state
workingParams.value = [
{
id: idTicker.value++,
key: "",
value: "",
active: true,
},
]
bulkParams.value = ""
}
</script>

View File

@@ -348,8 +348,7 @@ const newSendRequest = async () => {
const ensureMethodInEndpoint = () => {
if (
!/^http[s]?:\/\//.test(newEndpoint.value) &&
!newEndpoint.value.startsWith("<<") &&
!newEndpoint.value.startsWith("{{")
!newEndpoint.value.startsWith("<<")
) {
const domain = newEndpoint.value.split(/[/:#?]+/)[0]
if (domain === "localhost" || /([0-9]+\.)*[0-9]/.test(domain)) {

View File

@@ -7,7 +7,7 @@
<SmartTab
:id="'params'"
:label="`${$t('tab.parameters')}`"
:info="`${Number(newActiveParamsCount$) + Number(newActiveVarsCount$)}`"
:info="`${newActiveParamsCount$}`"
>
<HttpParameters />
</SmartTab>
@@ -50,7 +50,6 @@ import { useReadonlyStream } from "~/helpers/utils/composables"
import {
restActiveHeadersCount$,
restActiveParamsCount$,
restActiveVarsCount$,
usePreRequestScript,
useTestScript,
} from "~/newstore/RESTSession"
@@ -77,16 +76,6 @@ const newActiveParamsCount$ = useReadonlyStream(
null
)
const newActiveVarsCount$ = useReadonlyStream(
restActiveVarsCount$.pipe(
map((e) => {
if (e === 0) return null
return `${e}`
})
),
null
)
const newActiveHeadersCount$ = useReadonlyStream(
restActiveHeadersCount$.pipe(
map((e) => {

View File

@@ -35,13 +35,10 @@ import { EditorState, Extension } from "@codemirror/state"
import clone from "lodash/clone"
import { tooltips } from "@codemirror/tooltip"
import { history, historyKeymap } from "@codemirror/history"
import { HoppRESTVar } from "@hoppscotch/data"
import { inputTheme } from "~/helpers/editor/themes/baseTheme"
import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironment"
import { useReadonlyStream } from "~/helpers/utils/composables"
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
import { HoppReactiveVarPlugin } from "~/helpers/editor/extensions/HoppVariable"
import { restVars$ } from "~/newstore/RESTSession"
const props = withDefaults(
defineProps<{
@@ -49,7 +46,6 @@ const props = withDefaults(
placeholder: string
styles: string
envs: { key: string; value: string; source: string }[] | null
vars: { key: string; value: string }[] | null
focus: boolean
readonly: boolean
}>(),
@@ -58,7 +54,6 @@ const props = withDefaults(
placeholder: "",
styles: "",
envs: null,
vars: null,
focus: false,
readonly: false,
}
@@ -114,7 +109,6 @@ let pastedValue: string | null = null
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, []) as Ref<
AggregateEnvironment[]
>
const aggregateVars = useReadonlyStream(restVars$, []) as Ref<HoppRESTVar[]>
const envVars = computed(() =>
props.envs
@@ -126,17 +120,7 @@ const envVars = computed(() =>
: aggregateEnvs.value
)
const varVars = computed(() =>
props.vars
? props.vars.map((x) => ({
key: x.key,
value: x.value,
}))
: aggregateVars.value
)
const envTooltipPlugin = new HoppReactiveEnvPlugin(envVars, view)
const varTooltipPlugin = new HoppReactiveVarPlugin(varVars, view)
const initView = (el: any) => {
const extensions: Extension = [
@@ -162,7 +146,6 @@ const initView = (el: any) => {
position: "absolute",
}),
envTooltipPlugin,
varTooltipPlugin,
placeholderExt(props.placeholder),
EditorView.domEventHandlers({
paste(ev) {

View File

@@ -221,7 +221,7 @@ export const runGQLSubscription = <
createRequest(args.query, args.variables)
)
const sub = wonkaPipe(
wonkaPipe(
source,
subscribe((res) => {
result$.next(
@@ -256,8 +256,7 @@ export const runGQLSubscription = <
})
)
// Returns the stream and a subscription handle to unsub
return [result$, sub] as const
return result$
}
export const useGQLQuery = <DocType, DocVarType, DocErrorType extends string>(

View File

@@ -809,37 +809,6 @@ const samples = [
testScript: "",
}),
},
{
command: `curl https://example.com -d "alpha=beta&request_id=4"`,
response: makeRESTRequest({
method: "POST",
name: "Untitled request",
endpoint: "https://example.com/",
auth: {
authType: "none",
authActive: true,
},
body: {
contentType: "application/x-www-form-urlencoded",
body: rawKeyValueEntriesToString([
{
active: true,
key: "alpha",
value: "beta",
},
{
active: true,
key: "request_id",
value: "4",
},
]),
},
params: [],
headers: [],
preRequestScript: "",
testScript: "",
}),
},
]
describe("Parse curl command to Hopp REST Request", () => {

View File

@@ -93,8 +93,7 @@ export const parseCurlCommand = (curlCommand: string) => {
hasBodyBeenParsed = true
} else if (
rawContentType.includes("application/x-www-form-urlencoded") &&
!!pairs &&
Array.isArray(rawData)
!!pairs
) {
body = pairs.map((p) => p.join(": ")).join("\n") || null
contentType = "application/x-www-form-urlencoded"

View File

@@ -16,7 +16,7 @@ import {
getAggregateEnvs,
} from "~/newstore/environments"
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
const HOPP_ENVIRONMENT_REGEX = /(<<\w+>>)/g
const HOPP_ENV_HIGHLIGHT =
"cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight"
@@ -44,9 +44,8 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
let start = pos
let end = pos
while (start > from && /[a-zA-Z0-9-_]+/.test(text[start - from - 1]))
start--
while (end < to && /[a-zA-Z0-9-_]+/.test(text[end - from])) end++
while (start > from && /\w/.test(text[start - from - 1])) start--
while (end < to && /\w/.test(text[end - from])) end++
if (
(start === pos && side < 0) ||

View File

@@ -1,149 +0,0 @@
import { watch, Ref } from "@nuxtjs/composition-api"
import { Compartment } from "@codemirror/state"
import { hoverTooltip } from "@codemirror/tooltip"
import {
Decoration,
EditorView,
MatchDecorator,
ViewPlugin,
} from "@codemirror/view"
import * as E from "fp-ts/Either"
import { HoppRESTVar, parseTemplateStringE } from "@hoppscotch/data"
const HOPP_ENVIRONMENT_REGEX = /({{\w+}})/g
const HOPP_ENV_HIGHLIGHT =
"cursor-help transition rounded px-1 focus:outline-none mx-0.5 env-highlight"
const HOPP_ENV_HIGHLIGHT_FOUND =
"bg-accentDark text-accentContrast hover:bg-accent"
const HOPP_ENV_HIGHLIGHT_NOT_FOUND =
"bg-red-500 text-accentContrast hover:bg-red-600"
const cursorTooltipField = (aggregateEnvs: HoppRESTVar[]) =>
hoverTooltip(
(view, pos, side) => {
const { from, to, text } = view.state.doc.lineAt(pos)
// TODO: When Codemirror 6 allows this to work (not make the
// popups appear half of the time) use this implementation
// const wordSelection = view.state.wordAt(pos)
// if (!wordSelection) return null
// const word = view.state.doc.sliceString(
// wordSelection.from - 2,
// wordSelection.to + 2
// )
// if (!HOPP_ENVIRONMENT_REGEX.test(word)) return null
// Tracking the start and the end of the words
let start = pos
let end = pos
while (start > from && /\w/.test(text[start - from - 1])) start--
while (end < to && /\w/.test(text[end - from])) end++
if (
(start === pos && side < 0) ||
(end === pos && side > 0) ||
!HOPP_ENVIRONMENT_REGEX.test(
text.slice(start - from - 2, end - from + 2)
)
)
return null
const envValue =
aggregateEnvs.find(
(env) => env.key === text.slice(start - from, end - from)
// env.key === word.slice(wordSelection.from + 2, wordSelection.to - 2)
)?.value ?? "not found"
const result = parseTemplateStringE(envValue, aggregateEnvs)
const finalEnv = E.isLeft(result) ? "error" : result.right
return {
pos: start,
end: to,
above: true,
arrow: true,
create() {
const dom = document.createElement("span")
const xmp = document.createElement("xmp")
xmp.textContent = finalEnv
dom.appendChild(xmp)
dom.className = "tooltip-theme"
return { dom }
},
}
},
// HACK: This is a hack to fix hover tooltip not coming half of the time
// https://github.com/codemirror/tooltip/blob/765c463fc1d5afcc3ec93cee47d72606bed27e1d/src/tooltip.ts#L622
// Still doesn't fix the not showing up some of the time issue, but this is atleast more consistent
{ hoverTime: 1 } as any
)
function checkEnv(env: string, aggregateEnvs: HoppRESTVar[]) {
const className = aggregateEnvs.find(
(k: { key: string }) => k.key === env.slice(2, -2)
)
? HOPP_ENV_HIGHLIGHT_FOUND
: HOPP_ENV_HIGHLIGHT_NOT_FOUND
return Decoration.mark({
class: `${HOPP_ENV_HIGHLIGHT} ${className}`,
})
}
const getMatchDecorator = (aggregateEnvs: HoppRESTVar[]) =>
new MatchDecorator({
regexp: HOPP_ENVIRONMENT_REGEX,
decoration: (m) => checkEnv(m[0], aggregateEnvs),
})
export const environmentHighlightStyle = (aggregateEnvs: HoppRESTVar[]) => {
const decorator = getMatchDecorator(aggregateEnvs)
return ViewPlugin.define(
(view) => ({
decorations: decorator.createDeco(view),
update(u) {
this.decorations = decorator.updateDeco(u, this.decorations)
},
}),
{
decorations: (v) => v.decorations,
}
)
}
export class HoppReactiveVarPlugin {
private compartment = new Compartment()
private envs: HoppRESTVar[] = []
constructor(
envsRef: Ref<HoppRESTVar[]>,
private editorView: Ref<EditorView | undefined>
) {
watch(
envsRef,
(envs) => {
this.envs = envs
this.editorView.value?.dispatch({
effects: this.compartment.reconfigure([
cursorTooltipField(this.envs),
environmentHighlightStyle(this.envs),
]),
})
},
{ immediate: true }
)
}
get extension() {
return this.compartment.of([
cursorTooltipField(this.envs),
environmentHighlightStyle(this.envs),
])
}
}

View File

@@ -61,8 +61,6 @@ export const baseTheme = EditorView.theme({
},
".cm-panels.cm-panels-top": {
borderBottom: "1px solid var(--divider-light-color)",
top: "var(--lower-tertiary-sticky-fold) !important",
"z-index": "10",
},
".cm-panels.cm-panels-bottom": {
borderTop: "1px solid var(--divider-light-color)",
@@ -390,7 +388,5 @@ export const basicSetup: Extension = [
...completionKeymap,
...lintKeymap,
]),
search({
top: true,
}),
search(),
]

View File

@@ -9,6 +9,14 @@ import { flow } from "fp-ts/function"
export const safeParseJSON = (str: string): O.Option<object> =>
O.tryCatch(() => JSON.parse(str))
/**
* Generates a prettified JSON representation of an object
* @param obj The object to get the representation of
* @returns The prettified JSON string of the object
*/
export const prettyPrintJSON = (obj: unknown): O.Option<string> =>
O.tryCatch(() => JSON.stringify(obj, null, "\t"))
/**
* Checks if given string is a JSON string
* @param str Raw string to be checked

View File

@@ -1,11 +1,3 @@
/**
* Converts an array of key-value tuples (for e.g ["key", "value"]), into a record.
* (for eg. output -> { "key": "value" })
* NOTE: This function will discard duplicate key occurances and only keep the last occurance. If you do not want that behaviour,
* use `tupleWithSamesKeysToRecord`.
* @param tuples Array of tuples ([key, value])
* @returns A record with value corresponding to the last occurance of that key
*/
export const tupleToRecord = <
KeyType extends string | number | symbol,
ValueType
@@ -13,32 +5,5 @@ export const tupleToRecord = <
tuples: [KeyType, ValueType][]
): Record<KeyType, ValueType> =>
tuples.length > 0
? (Object.assign as any)(...tuples.map(([key, val]) => ({ [key]: val }))) // This is technically valid, but we have no way of telling TypeScript it is valid. Hence the assertion
? (Object.assign as any)(...tuples.map(([key, val]) => ({ [key]: val })))
: {}
/**
* Converts an array of key-value tuples (for e.g ["key", "value"]), into a record.
* (for eg. output -> { "key": ["value"] })
* NOTE: If you do not want the array as values (because of duplicate keys) and want to instead get the last occurance, use `tupleToRecord`
* @param tuples Array of tuples ([key, value])
* @returns A Record with values being arrays corresponding to each key occurance
*/
export const tupleWithSameKeysToRecord = <
KeyType extends string | number | symbol,
ValueType
>(
tuples: [KeyType, ValueType][]
): Record<KeyType, ValueType[]> => {
// By the end of the function we do ensure this typing, this can't be infered now though, hence the assertion
const out = {} as Record<KeyType, ValueType[]>
for (const [key, value] of tuples) {
if (!out[key]) {
out[key] = [value]
} else {
out[key].push(value)
}
}
return out
}

View File

@@ -0,0 +1,185 @@
import { OpenAPIV2 } from "openapi-types"
import * as O from "fp-ts/Option"
import { pipe, flow } from "fp-ts/function"
import * as A from "fp-ts/Array"
import { prettyPrintJSON } from "~/helpers/functional/json"
type PrimitiveSchemaType = "string" | "integer" | "number" | "boolean"
type SchemaType = "array" | "object" | PrimitiveSchemaType
type PrimitiveRequestBodyExample = number | string | boolean
type RequestBodyExample =
| { [name: string]: RequestBodyExample }
| Array<RequestBodyExample>
| PrimitiveRequestBodyExample
const getPrimitiveTypePlaceholder = (
schemaType: PrimitiveSchemaType
): PrimitiveRequestBodyExample => {
switch (schemaType) {
case "string":
return "string"
case "integer":
case "number":
return 1
case "boolean":
return true
}
}
const getSchemaTypeFromSchemaObject = (
schema: OpenAPIV2.SchemaObject
): O.Option<SchemaType> =>
pipe(
schema.type,
O.fromNullable,
O.map(
(schemaType) =>
(Array.isArray(schemaType) ? schemaType[0] : schemaType) as SchemaType
)
)
const isSchemaTypePrimitive = (
schemaType: string
): schemaType is PrimitiveSchemaType =>
["string", "integer", "number", "boolean"].includes(schemaType)
const isSchemaTypeArray = (schemaType: string): schemaType is "array" =>
schemaType === "array"
const isSchemaTypeObject = (schemaType: string): schemaType is "object" =>
schemaType === "object"
const getSampleEnumValueOrPlaceholder = (
schema: OpenAPIV2.SchemaObject
): RequestBodyExample =>
pipe(
schema.enum,
O.fromNullable,
O.map((enums) => enums[0] as RequestBodyExample),
O.altW(() =>
pipe(
schema,
getSchemaTypeFromSchemaObject,
O.filter(isSchemaTypePrimitive),
O.map(getPrimitiveTypePlaceholder)
)
),
O.getOrElseW(() => "")
)
const generateExampleArrayFromOpenAPIV2ItemsObject = (
items: OpenAPIV2.ItemsObject
): RequestBodyExample =>
// ItemsObject can not hold type "object"
// https://swagger.io/specification/v2/#itemsObject
// TODO : Handle array of objects
// https://stackoverflow.com/questions/60490974/how-to-define-an-array-of-objects-in-openapi-2-0
pipe(
items,
O.fromPredicate(
flow((items) => items.type as SchemaType, isSchemaTypePrimitive)
),
O.map(
flow(getSampleEnumValueOrPlaceholder, (arrayItem) => [
arrayItem,
arrayItem,
])
),
O.getOrElse(() =>
// If the type is not primitive, it is "array"
// items property is required if type is array
[
generateExampleArrayFromOpenAPIV2ItemsObject(
items.items as OpenAPIV2.ItemsObject
),
]
)
)
const generateRequestBodyExampleFromOpenAPIV2BodySchema = (
schema: OpenAPIV2.SchemaObject
): RequestBodyExample => {
if (schema.example) return schema.example as RequestBodyExample
const primitiveTypeExample = pipe(
schema,
O.fromPredicate(
flow(
getSchemaTypeFromSchemaObject,
O.map(isSchemaTypePrimitive),
O.getOrElseW(() => false) // No schema type found in the schema object, assume non-primitive
)
),
O.map(getSampleEnumValueOrPlaceholder) // Use enum or placeholder to populate primitive field
)
if (O.isSome(primitiveTypeExample)) return primitiveTypeExample.value
const arrayTypeExample = pipe(
schema,
O.fromPredicate(
flow(
getSchemaTypeFromSchemaObject,
O.map(isSchemaTypeArray),
O.getOrElseW(() => false) // No schema type found in the schema object, assume type to be different from array
)
),
O.map((schema) => schema.items as OpenAPIV2.ItemsObject),
O.map(generateExampleArrayFromOpenAPIV2ItemsObject)
)
if (O.isSome(arrayTypeExample)) return arrayTypeExample.value
return pipe(
schema,
O.fromPredicate(
flow(
getSchemaTypeFromSchemaObject,
O.map(isSchemaTypeObject),
O.getOrElseW(() => false)
)
),
O.chain((schema) =>
pipe(
schema.properties,
O.fromNullable,
O.map(
(properties) =>
Object.entries(properties) as [string, OpenAPIV2.SchemaObject][]
)
)
),
O.getOrElseW(() => [] as [string, OpenAPIV2.SchemaObject][]),
A.reduce(
{} as { [name: string]: RequestBodyExample },
(aggregatedExample, property) => {
const example = generateRequestBodyExampleFromOpenAPIV2BodySchema(
property[1]
)
aggregatedExample[property[0]] = example
return aggregatedExample
}
)
)
}
export const generateRequestBodyExampleFromOpenAPIV2Body = (
op: OpenAPIV2.OperationObject
): string =>
pipe(
(op.parameters ?? []) as OpenAPIV2.Parameter[],
A.findFirst((param) => param.in === "body"),
O.map(
flow(
(parameter) => parameter.schema,
generateRequestBodyExampleFromOpenAPIV2BodySchema
)
),
O.chain(prettyPrintJSON),
O.getOrElse(() => "")
)

View File

@@ -0,0 +1,109 @@
import { OpenAPIV3 } from "openapi-types"
import { pipe } from "fp-ts/function"
import * as O from "fp-ts/Option"
import { tupleToRecord } from "~/helpers/functional/record"
type SchemaType =
| OpenAPIV3.ArraySchemaObjectType
| OpenAPIV3.NonArraySchemaObjectType
type PrimitiveSchemaType = Exclude<SchemaType, "array" | "object">
type PrimitiveRequestBodyExample = string | number | boolean | null
type RequestBodyExample =
| PrimitiveRequestBodyExample
| Array<RequestBodyExample>
| { [name: string]: RequestBodyExample }
const isSchemaTypePrimitive = (
schemaType: SchemaType
): schemaType is PrimitiveSchemaType =>
!["array", "object"].includes(schemaType)
const getPrimitiveTypePlaceholder = (
primitiveType: PrimitiveSchemaType
): PrimitiveRequestBodyExample => {
switch (primitiveType) {
case "number":
return 0.0
case "integer":
return 0
case "string":
return "string"
case "boolean":
return true
}
}
// Use carefully, call only when type is primitive
// TODO(agarwal): Use Enum values, if any
const generatePrimitiveRequestBodyExample = (
schemaObject: OpenAPIV3.NonArraySchemaObject
): RequestBodyExample =>
getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType)
// Use carefully, call only when type is object
const generateObjectRequestBodyExample = (
schemaObject: OpenAPIV3.NonArraySchemaObject
): RequestBodyExample =>
pipe(
schemaObject.properties,
O.fromNullable,
O.map(Object.entries),
O.getOrElseW(() => [] as [string, OpenAPIV3.SchemaObject][]),
tupleToRecord
)
const generateArrayRequestBodyExample = (
schemaObject: OpenAPIV3.ArraySchemaObject
): RequestBodyExample => [
generateRequestBodyExampleFromSchemaObject(
schemaObject.items as OpenAPIV3.SchemaObject
),
]
const generateRequestBodyExampleFromSchemaObject = (
schemaObject: OpenAPIV3.SchemaObject
): RequestBodyExample => {
// TODO: Handle schema objects with allof
if (schemaObject.example) return schemaObject.example as RequestBodyExample
// If request body can be oneof or allof several schema, choose the first schema to generate an example
if (schemaObject.oneOf)
return generateRequestBodyExampleFromSchemaObject(
schemaObject.oneOf[0] as OpenAPIV3.SchemaObject
)
if (schemaObject.anyOf)
return generateRequestBodyExampleFromSchemaObject(
schemaObject.anyOf[0] as OpenAPIV3.SchemaObject
)
if (!schemaObject.type) return ""
if (isSchemaTypePrimitive(schemaObject.type))
return generatePrimitiveRequestBodyExample(
schemaObject as OpenAPIV3.NonArraySchemaObject
)
if (schemaObject.type === "object")
return generateObjectRequestBodyExample(
schemaObject as OpenAPIV3.NonArraySchemaObject
)
return generateArrayRequestBodyExample(
schemaObject as OpenAPIV3.ArraySchemaObject
)
}
export const generateRequestBodyExampleFromMediaObject = (
mediaObject: OpenAPIV3.MediaTypeObject
): RequestBodyExample => {
if (mediaObject.example) return mediaObject.example as RequestBodyExample
if (mediaObject.examples) return mediaObject.examples[0] as RequestBodyExample
return mediaObject.schema
? generateRequestBodyExampleFromSchemaObject(
mediaObject.schema as OpenAPIV3.SchemaObject
)
: ""
}

View File

@@ -0,0 +1,129 @@
import { OpenAPIV3_1 as OpenAPIV31 } from "openapi-types"
import { pipe } from "fp-ts/function"
import * as O from "fp-ts/Option"
import * as A from "fp-ts/Array"
type MixedArraySchemaType = (
| OpenAPIV31.ArraySchemaObjectType
| OpenAPIV31.NonArraySchemaObjectType
)[]
type SchemaType =
| OpenAPIV31.ArraySchemaObjectType
| OpenAPIV31.NonArraySchemaObjectType
| MixedArraySchemaType
type PrimitiveSchemaType = Exclude<
OpenAPIV31.NonArraySchemaObjectType,
"object"
>
type PrimitiveRequestBodyExample = string | number | boolean | null
type RequestBodyExample =
| PrimitiveRequestBodyExample
| Array<RequestBodyExample>
| { [name: string]: RequestBodyExample }
const isSchemaTypePrimitive = (
schemaType: SchemaType
): schemaType is PrimitiveSchemaType =>
!Array.isArray(schemaType) && !["array", "object"].includes(schemaType)
const getPrimitiveTypePlaceholder = (
primitiveType: PrimitiveSchemaType
): PrimitiveRequestBodyExample => {
switch (primitiveType) {
case "number":
return 0.0
case "integer":
return 0
case "string":
return "string"
case "boolean":
return true
}
return null
}
// Use carefully, the schema type should necessarily be primitive
// TODO(agarwal): Use Enum values, if any
const generatePrimitiveRequestBodyExample = (
schemaObject: OpenAPIV31.NonArraySchemaObject
): RequestBodyExample =>
getPrimitiveTypePlaceholder(schemaObject.type as PrimitiveSchemaType)
// Use carefully, the schema type should necessarily be object
const generateObjectRequestBodyExample = (
schemaObject: OpenAPIV31.NonArraySchemaObject
): RequestBodyExample =>
pipe(
schemaObject.properties,
O.fromNullable,
O.map(
(properties) =>
Object.entries(properties) as [string, OpenAPIV31.SchemaObject][]
),
O.getOrElseW(() => [] as [string, OpenAPIV31.SchemaObject][]),
A.reduce(
{} as { [name: string]: RequestBodyExample },
(aggregatedExample, property) => {
aggregatedExample[property[0]] =
generateRequestBodyExampleFromSchemaObject(property[1])
return aggregatedExample
}
)
)
// Use carefully, the schema type should necessarily be mixed array
const generateMixedArrayRequestBodyEcample = (
schemaObject: OpenAPIV31.SchemaObject
): RequestBodyExample =>
pipe(
schemaObject,
(schemaObject) => schemaObject.type as MixedArraySchemaType,
A.reduce([] as Array<RequestBodyExample>, (aggregatedExample, itemType) => {
// TODO: Figure out how to include non-primitive types as well
if (isSchemaTypePrimitive(itemType)) {
aggregatedExample.push(getPrimitiveTypePlaceholder(itemType))
}
return aggregatedExample
})
)
const generateArrayRequestBodyExample = (
schemaObject: OpenAPIV31.ArraySchemaObject
): RequestBodyExample => [
generateRequestBodyExampleFromSchemaObject(
schemaObject.items as OpenAPIV31.SchemaObject
),
]
const generateRequestBodyExampleFromSchemaObject = (
schemaObject: OpenAPIV31.SchemaObject
): RequestBodyExample => {
// TODO: Handle schema objects with oneof or anyof
if (schemaObject.example) return schemaObject.example as RequestBodyExample
if (schemaObject.examples)
return schemaObject.examples[0] as RequestBodyExample
if (!schemaObject.type) return ""
if (isSchemaTypePrimitive(schemaObject.type))
return generatePrimitiveRequestBodyExample(
schemaObject as OpenAPIV31.NonArraySchemaObject
)
if (schemaObject.type === "object")
return generateObjectRequestBodyExample(schemaObject)
if (schemaObject.type === "array")
return generateArrayRequestBodyExample(schemaObject)
return generateMixedArrayRequestBodyEcample(schemaObject)
}
export const generateRequestBodyExampleFromMediaObject = (
mediaObject: OpenAPIV31.MediaTypeObject
): RequestBodyExample => {
if (mediaObject.example) return mediaObject.example as RequestBodyExample
if (mediaObject.examples) return mediaObject.examples[0] as RequestBodyExample
return mediaObject.schema
? generateRequestBodyExampleFromSchemaObject(mediaObject.schema)
: ""
}

View File

@@ -24,8 +24,12 @@ import * as S from "fp-ts/string"
import * as O from "fp-ts/Option"
import * as TE from "fp-ts/TaskEither"
import * as RA from "fp-ts/ReadonlyArray"
import { step } from "../steps"
import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "."
import { step } from "../../steps"
import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "../"
import { generateRequestBodyExampleFromMediaObject as generateExampleV31 } from "./exampleV31"
import { generateRequestBodyExampleFromMediaObject as generateExampleV3 } from "./exampleV3"
import { generateRequestBodyExampleFromOpenAPIV2Body } from "./exampleV2"
import { prettyPrintJSON } from "~/helpers/functional/json"
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
@@ -114,8 +118,12 @@ const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => {
if (
obj !== "multipart/form-data" &&
obj !== "application/x-www-form-urlencoded"
)
return { contentType: obj as any, body: "" }
) {
return {
contentType: obj as any,
body: generateRequestBodyExampleFromOpenAPIV2Body(op),
}
}
const formDataValues = pipe(
(op.parameters ?? []) as OpenAPIV2.Parameter[],
@@ -178,7 +186,8 @@ const parseOpenAPIV3BodyFormData = (
}
const parseOpenAPIV3Body = (
op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject
op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject,
isV31Request: boolean
): HoppRESTReqBody => {
const objs = Object.entries(
(
@@ -197,11 +206,20 @@ const parseOpenAPIV3Body = (
OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject
] = objs[0]
const exampleBody = pipe(
prettyPrintJSON(
isV31Request
? generateExampleV31(media as OpenAPIV31.MediaTypeObject)
: generateExampleV3(media as OpenAPIV3.MediaTypeObject)
),
O.getOrElse(() => "")
)
return contentType in knownContentTypes
? contentType === "multipart/form-data" ||
contentType === "application/x-www-form-urlencoded"
? parseOpenAPIV3BodyFormData(contentType, media)
: { contentType: contentType as any, body: "" }
: { contentType: contentType as any, body: exampleBody }
: { contentType: null, body: null }
}
@@ -213,12 +231,20 @@ const isOpenAPIV3Operation = (
typeof doc.openapi === "string" &&
doc.openapi.startsWith("3.")
const isOpenAPIV31Operation = (
doc: OpenAPI.Document,
op: OpenAPIOperationType
): op is OpenAPIV31.OperationObject =>
objectHasProperty(doc, "openapi") &&
typeof doc.openapi === "string" &&
doc.openapi.startsWith("3.1")
const parseOpenAPIBody = (
doc: OpenAPI.Document,
op: OpenAPIOperationType
): HoppRESTReqBody =>
isOpenAPIV3Operation(doc, op)
? parseOpenAPIV3Body(op)
? parseOpenAPIV3Body(op, isOpenAPIV31Operation(doc, op))
: parseOpenAPIV2Body(op)
const resolveOpenAPIV3SecurityObj = (

View File

@@ -56,7 +56,6 @@ export const bindings: {
"alt-q": "navigation.jump.graphql",
"alt-w": "navigation.jump.realtime",
"alt-d": "navigation.jump.documentation",
"alt-m": "navigation.jump.profile",
"alt-s": "navigation.jump.settings",
}

View File

@@ -1,6 +1,5 @@
import * as E from "fp-ts/Either"
import { BehaviorSubject, Subscription } from "rxjs"
import { Subscription as WSubscription } from "wonka"
import { GQLError, runGQLQuery, runGQLSubscription } from "../backend/GQLClient"
import {
GetUserShortcodesQuery,
@@ -23,9 +22,6 @@ export default class ShortcodeListAdapter {
private myShortcodesCreated: Subscription | null
private myShortcodesRevoked: Subscription | null
private myShortcodesCreatedSub: WSubscription | null
private myShortcodesRevokedSub: WSubscription | null
constructor(deferInit: boolean = false) {
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
this.loading$ = new BehaviorSubject<boolean>(false)
@@ -37,8 +33,6 @@ export default class ShortcodeListAdapter {
this.isDispose = false
this.myShortcodesCreated = null
this.myShortcodesRevoked = null
this.myShortcodesCreatedSub = null
this.myShortcodesRevokedSub = null
if (!deferInit) this.initialize()
}
@@ -46,8 +40,6 @@ export default class ShortcodeListAdapter {
unsubscribeSubscriptions() {
this.myShortcodesCreated?.unsubscribe()
this.myShortcodesRevoked?.unsubscribe()
this.myShortcodesCreatedSub?.unsubscribe()
this.myShortcodesRevokedSub?.unsubscribe()
}
initialize() {
@@ -132,12 +124,9 @@ export default class ShortcodeListAdapter {
}
private registerSubscriptions() {
const [myShortcodeCreated$, myShortcodeCreatedSub] = runGQLSubscription({
this.myShortcodesCreated = runGQLSubscription({
query: ShortcodeCreatedDocument,
})
this.myShortcodesCreatedSub = myShortcodeCreatedSub
this.myShortcodesCreated = myShortcodeCreated$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result)) {
console.error(result.left)
throw new Error(`Shortcode Create Error ${result.left}`)
@@ -146,12 +135,9 @@ export default class ShortcodeListAdapter {
this.createShortcode(result.right.myShortcodesCreated)
})
const [myShortcodesRevoked$, myShortcodeRevokedSub] = runGQLSubscription({
this.myShortcodesRevoked = runGQLSubscription({
query: ShortcodeDeletedDocument,
})
this.myShortcodesRevokedSub = myShortcodeRevokedSub
this.myShortcodesRevoked = myShortcodesRevoked$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result)) {
console.error(result.left)
throw new Error(`Shortcode Delete Error ${result.left}`)

View File

@@ -103,7 +103,7 @@ export default [
label: "shortcut.navigation.settings",
},
{
keys: [getPlatformAlternateKey(), "M"],
keys: [getPlatformAlternateKey(), "P"],
label: "shortcut.navigation.profile",
},
],
@@ -171,7 +171,7 @@ export const spotlight = [
icon: "arrow-right",
},
{
keys: [getPlatformAlternateKey(), "M"],
keys: [getPlatformAlternateKey(), "P"],
label: "shortcut.navigation.profile",
action: "navigation.jump.profile",
icon: "arrow-right",
@@ -267,7 +267,7 @@ export const fuse = [
tags: ["settings", "jump", "page", "navigation", "account", "theme", "go"],
},
{
keys: [getPlatformAlternateKey(), "M"],
keys: [getPlatformAlternateKey(), "P"],
label: "shortcut.navigation.profile",
action: "navigation.jump.profile",
icon: "arrow-right",

View File

@@ -3,7 +3,6 @@ import { BehaviorSubject, Subscription } from "rxjs"
import { translateToNewRequest } from "@hoppscotch/data"
import pull from "lodash/pull"
import remove from "lodash/remove"
import { Subscription as WSubscription } from "wonka"
import { runGQLQuery, runGQLSubscription } from "../backend/GQLClient"
import { TeamCollection } from "./TeamCollection"
import { TeamRequest } from "./TeamRequest"
@@ -194,13 +193,6 @@ export default class NewTeamCollectionAdapter {
private teamRequestUpdated$: Subscription | null
private teamRequestDeleted$: Subscription | null
private teamCollectionAddedSub: WSubscription | null
private teamCollectionUpdatedSub: WSubscription | null
private teamCollectionRemovedSub: WSubscription | null
private teamRequestAddedSub: WSubscription | null
private teamRequestUpdatedSub: WSubscription | null
private teamRequestDeletedSub: WSubscription | null
constructor(private teamID: string | null) {
this.collections$ = new BehaviorSubject<TeamCollection[]>([])
this.loadingCollections$ = new BehaviorSubject<string[]>([])
@@ -212,13 +204,6 @@ export default class NewTeamCollectionAdapter {
this.teamRequestDeleted$ = null
this.teamRequestUpdated$ = null
this.teamCollectionAddedSub = null
this.teamCollectionUpdatedSub = null
this.teamCollectionRemovedSub = null
this.teamRequestAddedSub = null
this.teamRequestDeletedSub = null
this.teamRequestUpdatedSub = null
if (this.teamID) this.initialize()
}
@@ -243,13 +228,6 @@ export default class NewTeamCollectionAdapter {
this.teamRequestAdded$?.unsubscribe()
this.teamRequestDeleted$?.unsubscribe()
this.teamRequestUpdated$?.unsubscribe()
this.teamCollectionAddedSub?.unsubscribe()
this.teamCollectionUpdatedSub?.unsubscribe()
this.teamCollectionRemovedSub?.unsubscribe()
this.teamRequestAddedSub?.unsubscribe()
this.teamRequestDeletedSub?.unsubscribe()
this.teamRequestUpdatedSub?.unsubscribe()
}
private async initialize() {
@@ -428,16 +406,12 @@ export default class NewTeamCollectionAdapter {
private registerSubscriptions() {
if (!this.teamID) return
const [teamCollAdded$, teamCollAddedSub] = runGQLSubscription({
this.teamCollectionAdded$ = runGQLSubscription({
query: TeamCollectionAddedDocument,
variables: {
teamID: this.teamID,
},
})
this.teamCollectionAddedSub = teamCollAddedSub
this.teamCollectionAdded$ = teamCollAdded$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(`Team Collection Added Error: ${result.left}`)
@@ -452,15 +426,12 @@ export default class NewTeamCollectionAdapter {
)
})
const [teamCollUpdated$, teamCollUpdatedSub] = runGQLSubscription({
this.teamCollectionUpdated$ = runGQLSubscription({
query: TeamCollectionUpdatedDocument,
variables: {
teamID: this.teamID,
},
})
this.teamCollectionUpdatedSub = teamCollUpdatedSub
this.teamCollectionUpdated$ = teamCollUpdated$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(`Team Collection Updated Error: ${result.left}`)
@@ -470,30 +441,24 @@ export default class NewTeamCollectionAdapter {
})
})
const [teamCollRemoved$, teamCollRemovedSub] = runGQLSubscription({
this.teamCollectionRemoved$ = runGQLSubscription({
query: TeamCollectionRemovedDocument,
variables: {
teamID: this.teamID,
},
})
this.teamCollectionRemovedSub = teamCollRemovedSub
this.teamCollectionRemoved$ = teamCollRemoved$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(`Team Collection Removed Error: ${result.left}`)
this.removeCollection(result.right.teamCollectionRemoved)
})
const [teamReqAdded$, teamReqAddedSub] = runGQLSubscription({
this.teamRequestAdded$ = runGQLSubscription({
query: TeamRequestAddedDocument,
variables: {
teamID: this.teamID,
},
})
this.teamRequestAddedSub = teamReqAddedSub
this.teamRequestAdded$ = teamReqAdded$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(`Team Request Added Error: ${result.left}`)
@@ -507,15 +472,12 @@ export default class NewTeamCollectionAdapter {
})
})
const [teamReqUpdated$, teamReqUpdatedSub] = runGQLSubscription({
this.teamRequestUpdated$ = runGQLSubscription({
query: TeamRequestUpdatedDocument,
variables: {
teamID: this.teamID,
},
})
this.teamRequestUpdatedSub = teamReqUpdatedSub
this.teamRequestUpdated$ = teamReqUpdated$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(`Team Request Updated Error: ${result.left}`)
@@ -527,15 +489,12 @@ export default class NewTeamCollectionAdapter {
})
})
const [teamReqDeleted$, teamReqDeleted] = runGQLSubscription({
this.teamRequestDeleted$ = runGQLSubscription({
query: TeamRequestDeletedDocument,
variables: {
teamID: this.teamID,
},
})
this.teamRequestUpdatedSub = teamReqDeleted
this.teamRequestDeleted$ = teamReqDeleted$.subscribe((result) => {
}).subscribe((result) => {
if (E.isLeft(result))
throw new Error(`Team Request Deleted Error ${result.left}`)

View File

@@ -1,10 +1,6 @@
import * as A from "fp-ts/Array"
import * as E from "fp-ts/Either"
import * as O from "fp-ts/Option"
import * as RA from "fp-ts/ReadonlyArray"
import * as S from "fp-ts/string"
import qs from "qs"
import { flow, pipe } from "fp-ts/function"
import { pipe } from "fp-ts/function"
import { combineLatest, Observable } from "rxjs"
import { map } from "rxjs/operators"
import {
@@ -13,15 +9,14 @@ import {
HoppRESTRequest,
parseTemplateString,
parseBodyEnvVariables,
parseRawKeyValueEntries,
Environment,
HoppRESTHeader,
HoppRESTParam,
parseRawKeyValueEntriesE,
parseTemplateStringE,
} from "@hoppscotch/data"
import { arrayFlatMap, arraySort } from "../functional/array"
import { toFormData } from "../functional/formData"
import { tupleWithSameKeysToRecord } from "../functional/record"
import { tupleToRecord } from "../functional/record"
import { getGlobalVariables } from "~/newstore/environments"
export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
@@ -34,7 +29,6 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
effectiveFinalHeaders: { key: string; value: string }[]
effectiveFinalParams: { key: string; value: string }[]
effectiveFinalBody: FormData | string | null
effectiveFinalVars: { key: string; value: string }[]
}
/**
@@ -216,40 +210,25 @@ function getFinalBodyFromRequest(
}
if (request.body.contentType === "application/x-www-form-urlencoded") {
const parsedBodyRecord = pipe(
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)),
parseRawKeyValueEntries,
/**
* 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
),
tupleWithSameKeysToRecord,
(obj) => qs.stringify(obj, { indices: false })
)
)
// Filter out active
A.filter((x) => x.active),
// Convert to tuple
A.map(
({ key, value }) =>
[
parseTemplateString(key, envVariables),
parseTemplateString(value, envVariables),
] as [string, string]
),
// Tuple to Record object
tupleToRecord,
// Stringify
qs.stringify
)
return E.isRight(parsedBodyRecord) ? parsedBodyRecord.right : null
}
if (request.body.contentType === "multipart/form-data") {
@@ -319,21 +298,15 @@ export function getEffectiveRESTRequest(
value: parseTemplateString(x.value, envVariables),
}))
)
const effectiveFinalVars = request.vars
const effectiveFinalBody = getFinalBodyFromRequest(request, envVariables)
return {
...request,
effectiveFinalURL: parseTemplateString(
request.endpoint,
envVariables,
request.vars
),
effectiveFinalURL: parseTemplateString(request.endpoint, envVariables),
effectiveFinalHeaders,
effectiveFinalParams,
effectiveFinalBody,
effectiveFinalVars,
}
}

View File

@@ -143,15 +143,6 @@ export function useStreamSubscriber(): {
}
}
export function useI18nPathInfo() {
const { localePath, getRouteBaseName } = useContext() as any
return {
localePath: localePath as (x: string) => string,
getRouteBaseName: getRouteBaseName as (x?: any) => string, // Should be a route
}
}
export function useI18n() {
const {
app: { i18n },

View File

@@ -1,6 +1,5 @@
{
"action": {
"autoscroll": "自動捲動",
"cancel": "取消",
"choose_file": "選擇一個檔案",
"clear": "清除",
@@ -10,11 +9,10 @@
"delete": "刪除",
"disconnect": "斷開連線",
"dismiss": "忽略",
"dont_save": "不要儲存",
"download_file": "下載檔案",
"dont_save": "不要儲存",
"duplicate": "複製",
"edit": "編輯",
"filter_response": "篩選回應",
"go_back": "返回",
"label": "標籤",
"learn_more": "瞭解更多",
@@ -22,14 +20,11 @@
"more": "更多",
"new": "新增",
"no": "否",
"open_workspace": "開啟工作區",
"paste": "貼上",
"prettify": "美化",
"remove": "移除",
"restore": "還原",
"save": "儲存",
"scroll_to_bottom": "捲動至底部",
"scroll_to_top": "捲動至頂部",
"search": "搜尋",
"send": "傳送",
"start": "開始",
@@ -51,10 +46,10 @@
"contact_us": "聯絡我們",
"copy": "複製",
"copy_user_id": "複製使用者驗證權杖",
"developer_option": "開發者選項",
"developer_option_description": "協助開發和維護 Hoppscotch 的工具。",
"discord": "Discord",
"documentation": "幫助文件",
"developer_option": "開發者選項",
"developer_option_description": "協助開發和維護 Hoppscotch 的工具。",
"github": "GitHub",
"help": "幫助與回饋",
"home": "主頁",
@@ -169,7 +164,6 @@
"profile": "登入以檢視您的設定檔",
"protocols": "協議為空",
"schema": "連線至 GraphQL 端點",
"shortcodes": "Shortcodes 為空",
"team_name": "團隊名稱為空",
"teams": "團隊為空",
"tests": "沒有針對該請求的測試"
@@ -203,11 +197,9 @@
"invalid_link": "連結無效",
"invalid_link_description": "您點擊的連結無效或已過期。",
"json_prettify_invalid_body": "無法美化無效的請求主體,處理 JSON 語法錯誤並重試",
"json_parsing_failed": "JSON 無效",
"network_error": "似乎有網路錯誤。請再試一次。",
"network_fail": "無法傳送請求",
"no_duration": "無持續時間",
"no_results_found": "找不到結果",
"script_fail": "無法執行預請求指令碼",
"something_went_wrong": "發生了一些錯誤",
"test_script_fail": "無法執行測試指令碼"
@@ -274,19 +266,15 @@
"from_url": "從網址匯入",
"gist_url": "輸入 Gist 網址",
"json_description": "從 Hoppscotch 組合 JSON 檔匯入組合",
"title": "匯入",
"import_from_url_success": "已匯入組合",
"import_from_url_invalid_file_format": "匯入組合時發生錯誤",
"import_from_url_invalid_type": "不支援此類型。可接受的值為 'hoppscotch'、'openapi'、'postman'、'insomnia'",
"import_from_url_invalid_fetch": "無法從網址取得資料"
"title": "匯入"
},
"layout": {
"collapse_collection": "隱藏或顯示組合",
"collapse_sidebar": "隱藏或顯示側邊欄",
"column": "垂直布局",
"name": "配置",
"row": "水平布局",
"zen_mode": "專注模式"
"zen_mode": "專注模式",
"collapse_sidebar": "隱藏或顯示側邊欄",
"collapse_collection": "隱藏或顯示組合",
"name": "配置"
},
"modal": {
"collections": "組合",
@@ -343,11 +331,6 @@
"body": "請求本體",
"choose_language": "選擇語言",
"content_type": "內容類型",
"content_type_titles": {
"others": "其他",
"structured": "結構",
"text": "文字"
},
"copy_link": "複製連結",
"duration": "持續時間",
"enter_curl": "輸入 cURL",
@@ -358,9 +341,6 @@
"method": "方法",
"name": "請求名稱",
"new": "新請求",
"override": "覆寫",
"override_help": "在標頭設置 <xmp>Content-Type</xmp>",
"overriden": "已覆寫",
"parameter_list": "查詢參數",
"parameters": "參數",
"path": "路徑",
@@ -378,11 +358,12 @@
"type": "請求類型",
"url": "網址",
"variables": "變數",
"view_my_links": "檢視我的連結"
"override": "覆寫",
"override_help": "在標頭設置 <xmp>Content-Type</xmp>",
"overriden": "已覆寫"
},
"response": {
"body": "回應本體",
"filter_response_body": "篩選 JSON 回應本體 (使用 JSONPath 語法)",
"headers": "回應標頭",
"html": "HTML",
"image": "影像",
@@ -434,8 +415,6 @@
"proxy_use_toggle": "使用 Proxy 中介軟體傳送請求",
"read_the": "閱讀",
"reset_default": "重置為預設",
"short_codes": "快捷碼",
"short_codes_description": "我們為您打造的快捷碼。",
"sidebar_on_left": "左側邊欄",
"sync": "同步",
"sync_collections": "組合",
@@ -468,7 +447,7 @@
"documentation": "前往文件頁面",
"forward": "前往下一頁面",
"graphql": "前往 GraphQL 頁面",
"profile": "前往個人檔案頁面",
"profile": "Go to Profile page",
"realtime": "前往實時頁面",
"rest": "前往 REST 頁面",
"settings": "前往設定頁面",
@@ -497,15 +476,6 @@
"title": "主題"
}
},
"shortcodes":{
"actions":"操作",
"created_on": "建立於",
"deleted" : "已刪除快捷碼",
"method": "方法",
"not_found":"找不到快捷碼",
"short_code":"快捷碼",
"url": "網址"
},
"show": {
"code": "顯示程式碼",
"more": "顯示更多",
@@ -517,8 +487,7 @@
"event_name": "事件名稱",
"events": "事件",
"log": "日誌",
"url": "網址",
"connection_not_authorized": "此 SocketIO 連線未使用任何驗證。"
"url": "網址"
},
"sse": {
"event_type": "事件類型",
@@ -548,19 +517,7 @@
"loading": "正在載入……",
"none": "無",
"nothing_found": "沒有找到",
"waiting_send_request": "等待傳送請求",
"subscribed_success": "成功訂閱此主題:{topic}",
"unsubscribed_success": "成功取消訂閱此主題:{topic}",
"subscribed_failed": "無法訂閱此主題:{topic}",
"unsubscribed_failed": "無法取消訂閱此主題:{topic}",
"published_message": "已將此訊息:{message} 發布至主題:{topic}",
"published_error": "將訊息:{topic} 發布至主題:{message} 時發生錯誤",
"message_received": "訊息:{message}已抵達主題:{topic}",
"mqtt_subscription_failed": "訂閱此主題時發生錯誤:{topic}",
"connection_lost": "失去連線",
"connection_failed": "連線失敗",
"connection_error": "連線失敗",
"reconnection_error": "重新連線失敗"
"waiting_send_request": "等待傳送請求"
},
"support": {
"changelog": "閱讀更多有關最新版本的內容",

View File

@@ -4,7 +4,6 @@ import {
FormDataKeyValue,
HoppRESTHeader,
HoppRESTParam,
HoppRESTVar,
HoppRESTReqBody,
HoppRESTRequest,
RESTReqSchemaVersion,
@@ -30,7 +29,6 @@ export const getDefaultRESTRequest = (): HoppRESTRequest => ({
endpoint: "https://echo.hoppscotch.io",
name: "Untitled request",
params: [],
vars: [],
headers: [],
method: "GET",
auth: {
@@ -82,14 +80,6 @@ const dispatchers = defineDispatchers({
},
}
},
setVars(curr: RESTSession, { entries }: { entries: HoppRESTVar[] }) {
return {
request: {
...curr.request,
vars: entries,
},
}
},
addParam(curr: RESTSession, { newParam }: { newParam: HoppRESTParam }) {
return {
request: {
@@ -98,14 +88,6 @@ const dispatchers = defineDispatchers({
},
}
},
addVar(curr: RESTSession, { newVar }: { newVar: HoppRESTVar }) {
return {
request: {
...curr.request,
vars: [...curr.request.vars, newVar],
},
}
},
updateParam(
curr: RESTSession,
{ index, updatedParam }: { index: number; updatedParam: HoppRESTParam }
@@ -122,22 +104,6 @@ const dispatchers = defineDispatchers({
},
}
},
updateVar(
curr: RESTSession,
{ index, updatedVar }: { index: number; updatedVar: HoppRESTVar }
) {
const newVars = curr.request.vars.map((vari, i) => {
if (i === index) return updatedVar
else return vari
})
return {
request: {
...curr.request,
vars: newVars,
},
}
},
deleteParam(curr: RESTSession, { index }: { index: number }) {
const newParams = curr.request.params.filter((_x, i) => i !== index)
@@ -148,16 +114,6 @@ const dispatchers = defineDispatchers({
},
}
},
deleteVar(curr: RESTSession, { index }: { index: number }) {
const newVars = curr.request.vars.filter((_x, i) => i !== index)
return {
request: {
...curr.request,
vars: newVars,
},
}
},
deleteAllParams(curr: RESTSession) {
return {
request: {
@@ -417,14 +373,6 @@ export function setRESTParams(entries: HoppRESTParam[]) {
},
})
}
export function setRESTVars(entries: HoppRESTVar[]) {
restSessionStore.dispatch({
dispatcher: "setVars",
payload: {
entries,
},
})
}
export function addRESTParam(newParam: HoppRESTParam) {
restSessionStore.dispatch({
@@ -434,14 +382,6 @@ export function addRESTParam(newParam: HoppRESTParam) {
},
})
}
export function addRESTVar(newVar: HoppRESTVar) {
restSessionStore.dispatch({
dispatcher: "addVar",
payload: {
newVar,
},
})
}
export function updateRESTParam(index: number, updatedParam: HoppRESTParam) {
restSessionStore.dispatch({
@@ -452,15 +392,6 @@ export function updateRESTParam(index: number, updatedParam: HoppRESTParam) {
},
})
}
export function updateRESTVar(index: number, updatedVar: HoppRESTVar) {
restSessionStore.dispatch({
dispatcher: "updateVar",
payload: {
updatedVar,
index,
},
})
}
export function deleteRESTParam(index: number) {
restSessionStore.dispatch({
@@ -471,15 +402,6 @@ export function deleteRESTParam(index: number) {
})
}
export function deleteRESTVar(index: number) {
restSessionStore.dispatch({
dispatcher: "deleteVar",
payload: {
index,
},
})
}
export function deleteAllRESTParams() {
restSessionStore.dispatch({
dispatcher: "deleteAllParams",
@@ -670,20 +592,12 @@ export const restParams$ = restSessionStore.subject$.pipe(
distinctUntilChanged()
)
export const restVars$ = restSessionStore.subject$.pipe(
pluck("request", "vars"),
distinctUntilChanged()
)
export const restActiveParamsCount$ = restParams$.pipe(
map(
(params) =>
params.filter((x) => x.active && (x.key !== "" || x.value !== "")).length
)
)
export const restActiveVarsCount$ = restVars$.pipe(
map((vars) => vars.filter((x) => x.key !== "" || x.value !== "").length)
)
export const restMethod$ = restSessionStore.subject$.pipe(
pluck("request", "method"),

View File

@@ -315,7 +315,6 @@ completedRESTResponse$.subscribe((res) => {
method: res.req.method,
name: res.req.name,
params: res.req.params,
vars: res.req.vars,
preRequestScript: res.req.preRequestScript,
testScript: res.req.testScript,
v: res.req.v,

View File

@@ -58,7 +58,7 @@
"@codemirror/tooltip": "^0.19.16",
"@codemirror/view": "^0.19.48",
"@hoppscotch/codemirror-lang-graphql": "workspace:^0.2.0",
"@hoppscotch/data": "workspace:^0.4.3",
"@hoppscotch/data": "workspace:^0.4.2",
"@hoppscotch/js-sandbox": "workspace:^2.0.0",
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/composition-api": "^0.32.0",

View File

@@ -1,5 +1,5 @@
<template>
<AppPaneLayout layout-id="docs">
<AppPaneLayout>
<template #primary>
<div class="flex items-start justify-between p-4">
<label>

View File

@@ -1,5 +1,5 @@
<template>
<AppPaneLayout layout-id="graphql">
<AppPaneLayout>
<template #primary>
<GraphqlRequest :conn="gqlConn" />
<GraphqlRequestOptions :conn="gqlConn" />

View File

@@ -1,5 +1,5 @@
<template>
<AppPaneLayout layout-id="http">
<AppPaneLayout>
<template #primary>
<HttpRequest />
<HttpRequestOptions />

View File

@@ -13,10 +13,9 @@
<script setup lang="ts">
import { watch, ref, useRouter, useRoute } from "@nuxtjs/composition-api"
import { useI18n, useI18nPathInfo } from "~/helpers/utils/composables"
import { useI18n } from "~/helpers/utils/composables"
const t = useI18n()
const { localePath, getRouteBaseName } = useI18nPathInfo()
const router = useRouter()
const route = useRoute()
@@ -45,21 +44,17 @@ const currentTab = ref<RealtimeNavTab>("websocket")
// Update the router when the tab is updated
watch(currentTab, (newTab) => {
router.push(localePath(`/realtime/${newTab}`))
router.push(`/realtime/${newTab}`)
})
// Update the tab when router is upgrad
watch(
route,
(updateRoute) => {
const path = getRouteBaseName(updateRoute)
if (updateRoute.path === "/realtime") router.replace("/realtime/websocket")
if (path.endsWith("realtime")) {
router.replace(localePath(`/realtime/websocket`))
return
}
const destination: string | undefined = path.split("realtime-")[1]
const destination: string | undefined =
updateRoute.path.split("/realtime/")[1]
const target = REALTIME_NAVIGATION.find(
({ target }) => target === destination

View File

@@ -1,5 +1,5 @@
<template>
<AppPaneLayout layout-id="mqtt">
<AppPaneLayout>
<template #primary>
<div
class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary hide-scrollbar"

View File

@@ -1,5 +1,5 @@
<template>
<AppPaneLayout layout-id="socketio">
<AppPaneLayout>
<template #primary>
<div
class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary hide-scrollbar"

View File

@@ -1,5 +1,5 @@
<template>
<AppPaneLayout layout-id="sse">
<AppPaneLayout>
<template #primary>
<div
class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary hide-scrollbar"

View File

@@ -1,5 +1,5 @@
<template>
<AppPaneLayout layout-id="websocket">
<AppPaneLayout>
<template #primary>
<div
class="sticky top-0 z-10 flex flex-shrink-0 p-4 overflow-x-auto space-x-2 bg-primary hide-scrollbar"

View File

@@ -1,6 +1,6 @@
{
"name": "@hoppscotch/cli",
"version": "0.3.0",
"version": "0.2.1",
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io",
"main": "dist/index.js",
@@ -36,7 +36,7 @@
"license": "MIT",
"private": false,
"devDependencies": {
"@hoppscotch/data": "workspace:^0.4.3",
"@hoppscotch/data": "workspace:^0.4.2",
"@hoppscotch/js-sandbox": "workspace:^2.0.0",
"@relmify/jest-fp-ts": "^2.0.2",
"@swc/core": "^1.2.181",

View File

@@ -92,41 +92,12 @@ describe("Test 'hopp test <file> --env <file>' command:", () => {
expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
});
test("No errors occured (exit code 0).", async () => {
const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
const cmd = `node ./bin/hopp test ${TESTS_PATH} --env ${ENV_PATH}`;
const { error } = await execAsync(cmd);
// test("No errors occured (exit code 0).", async () => {
// const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
// const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
// const cmd = `node ./bin/hopp test ${TESTS_PATH} --env ${ENV_PATH}`;
// const { error } = await execAsync(cmd);
expect(error).toBeNull();
});
});
describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
const VALID_TEST_CMD = `node ./bin/hopp test ${getTestJsonFilePath(
"passes.json"
)}`;
test("No value passed to delay flag.", async () => {
const cmd = `${VALID_TEST_CMD} --delay`;
const { stdout } = await execAsync(cmd);
const out = getErrorCode(stdout);
expect(out).toBe<HoppErrorCode>("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);
expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
});
test("Valid value passed to delay flag.", async () => {
const cmd = `${VALID_TEST_CMD} --delay 1`;
const { error } = await execAsync(cmd);
expect(error).toBeNull();
});
// expect(error).toBeNull();
// });
});

View File

@@ -1,30 +0,0 @@
import { hrtime } from "process";
import { getDurationInSeconds } from "../../../utils/getters";
import { delayPromiseFunction } from "../../../utils/request";
describe("describePromiseFunction", () => {
let promiseFunc = (): Promise<number> => new Promise((resolve) => resolve(2));
beforeEach(() => {
promiseFunc = (): Promise<number> => new Promise((resolve) => resolve(2));
});
it("Should resolve the promise<number> after 2 seconds.", async () => {
const start = hrtime();
const res = await delayPromiseFunction(promiseFunc, 2000);
const end = hrtime(start);
const duration = getDurationInSeconds(end);
expect(Math.floor(duration)).toEqual(2);
expect(typeof res).toBe("number");
});
it("Should resolve the promise<number> after 4 seconds.", async () => {
const start = hrtime();
const res = await delayPromiseFunction(promiseFunc, 4000);
const end = hrtime(start);
const duration = getDurationInSeconds(end);
expect(Math.floor(duration)).toEqual(4);
expect(typeof res).toBe("number");
});
});

View File

@@ -58,12 +58,7 @@ describe("processRequest", () => {
(axios as unknown as jest.Mock).mockResolvedValue(DEFAULT_RESPONSE);
return expect(
processRequest({
request: SAMPLE_REQUEST,
envs: DEFAULT_ENVS,
path: "fake/collection/path",
delay: 0,
})()
processRequest(SAMPLE_REQUEST, DEFAULT_ENVS, "fake/collection/path")()
).resolves.toMatchObject({
report: {
result: true,
@@ -84,12 +79,7 @@ describe("processRequest", () => {
(axios as unknown as jest.Mock).mockResolvedValue(DEFAULT_RESPONSE);
return expect(
processRequest({
request: SAMPLE_REQUEST,
envs: DEFAULT_ENVS,
path: "fake/collection/path",
delay: 0,
})()
processRequest(SAMPLE_REQUEST, DEFAULT_ENVS, "fake/collection/path")()
).resolves.toMatchObject({
envs: {
selected: [{ key: "ENDPOINT", value: "https://example.com" }],
@@ -106,12 +96,7 @@ describe("processRequest", () => {
(axios as unknown as jest.Mock).mockResolvedValue(DEFAULT_RESPONSE);
return expect(
processRequest({
request: SAMPLE_REQUEST,
envs: DEFAULT_ENVS,
path: "fake/request/path",
delay: 0,
})()
processRequest(SAMPLE_REQUEST, DEFAULT_ENVS, "fake/request/path")()
).resolves.toMatchObject({
report: { result: false },
});

View File

@@ -9,14 +9,12 @@ import { handleError } from "../handlers/error";
import { parseCollectionData } from "../utils/mutators";
import { parseEnvsData } from "../options/test/env";
import { TestCmdOptions } from "../types/commands";
import { parseDelayOption } from "../options/test/delay";
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) => {

View File

@@ -50,10 +50,6 @@ program
"path to a hoppscotch collection.json file for CI testing"
)
.option("-e, --env <file_path>", "path to an environment variables json file")
.option(
"-d, --delay <delay_in_ms>",
"delay in milliseconds(ms) between consecutive requests within a collection"
)
.allowExcessArguments(false)
.allowUnknownOption(false)
.description("running hoppscotch collection.json file")

View File

@@ -1,20 +0,0 @@
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";
export const parseDelayOption = (
delay: unknown
): TE.TaskEither<HoppCLIError, number> =>
!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",
})
)
);

View File

@@ -4,7 +4,6 @@ import { HoppEnvs } from "./request";
export type CollectionRunnerParam = {
collections: HoppCollection<HoppRESTRequest>[];
envs: HoppEnvs;
delay?: number;
};
export type HoppCollectionFileExt = "json";

View File

@@ -1,6 +1,5 @@
export type TestCmdOptions = {
env: string;
delay: number;
};
export type HoppEnvFileExt = "json";

View File

@@ -26,10 +26,3 @@ export type RequestReport = {
result: boolean;
duration: { test: number; request: number; preRequest: number };
};
export type ProcessRequestParams = {
request: HoppRESTRequest;
envs: HoppEnvs;
path: string;
delay: number;
};

View File

@@ -5,12 +5,7 @@ import { bold } from "chalk";
import { log } from "console";
import round from "lodash/round";
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
import {
HoppEnvs,
CollectionStack,
RequestReport,
ProcessRequestParams,
} from "../types/request";
import { HoppEnvs, CollectionStack, RequestReport } from "../types/request";
import {
getRequestMetrics,
preProcessRequest,
@@ -46,7 +41,6 @@ export const collectionsRunner =
(param: CollectionRunnerParam): T.Task<RequestReport[]> =>
async () => {
const envs: HoppEnvs = param.envs;
const delay = param.delay ?? 0;
const requestsReport: RequestReport[] = [];
const collectionStack: CollectionStack[] = getCollectionStack(
param.collections
@@ -60,18 +54,12 @@ export const collectionsRunner =
for (const request of collection.requests) {
const _request = preProcessRequest(request);
const requestPath = `${path}/${_request.name}`;
const processRequestParams: ProcessRequestParams = {
path: requestPath,
request: _request,
envs,
delay,
};
// Request processing initiated message.
log(WARN(`\nRunning: ${bold(requestPath)}`));
// Processing current request.
const result = await processRequest(processRequestParams)();
const result = await processRequest(_request, envs, requestPath)();
// Updating global & selected envs with new envs from processed-request output.
const { global, selected } = result.envs;

View File

@@ -129,8 +129,3 @@ export const getDurationInSeconds = (
const durationInSeconds = (end[0] * 1e9 + end[1]) / 1e9;
return round(durationInSeconds, precision);
};
export const roundDuration = (
duration: number,
precision: number = DEFAULT_DURATION_PRECISION
) => round(duration, precision);

View File

@@ -12,11 +12,7 @@ import { testRunner, getTestScriptParams, hasFailedTestCases } from "./test";
import { RequestConfig, EffectiveHoppRESTRequest } from "../interfaces/request";
import { RequestRunnerResponse } from "../interfaces/response";
import { preRequestScriptRunner } from "./pre-request";
import {
HoppEnvs,
ProcessRequestParams,
RequestReport,
} from "../types/request";
import { HoppEnvs, RequestReport } from "../types/request";
import {
printPreRequestRunner,
printRequestRunner,
@@ -193,11 +189,11 @@ const getRequest = {
*/
export const processRequest =
(
params: ProcessRequestParams
request: HoppRESTRequest,
envs: HoppEnvs,
path: string
): T.Task<{ envs: HoppEnvs; report: RequestReport }> =>
async () => {
const { envs, path, request, delay } = params;
// Initialising updatedEnvs with given parameter envs, will eventually get updated.
const result = {
envs: <HoppEnvs>envs,
@@ -251,9 +247,7 @@ export const processRequest =
duration: 0,
};
// Executing request-runner.
const requestRunnerRes = await delayPromiseFunction<
E.Either<HoppCLIError, RequestRunnerResponse>
>(requestRunner(requestConfig), delay);
const requestRunnerRes = await requestRunner(requestConfig)();
if (E.isLeft(requestRunnerRes)) {
// Updating report for errors & current result
report.errors.push(requestRunnerRes.left);
@@ -364,15 +358,3 @@ export const getRequestMetrics = (
hasReqErrors ? { failed: 1, passed: 0 } : { failed: 0, passed: 1 },
(requests) => <RequestMetrics>{ requests, duration }
);
/**
* A function to execute promises with specific delay in milliseconds.
* @param func Function with promise with return type T.
* @param delay TIme in milliseconds to delay function.
* @returns Promise of type same as func.
*/
export const delayPromiseFunction = <T>(
func: () => Promise<T>,
delay: number
): Promise<T> =>
new Promise((resolve) => setTimeout(() => resolve(func()), delay));

View File

@@ -1,6 +1,6 @@
{
"name": "@hoppscotch/data",
"version": "0.4.3",
"version": "0.4.2",
"description": "Data Types, Validations and Migrations for Hoppscotch Public Data Structures",
"main": "dist/index.js",
"module": "true",

View File

@@ -9,13 +9,7 @@ export type Environment = {
}[]
}
export type Variables = {
key: string
value: string
}[]
const REGEX_ENV_VAR = /<<([^>]*)>>/g // "<<myVariable>>"
const REGEX_MY_VAR = /{{([^}]*)}}/g // "{{myVariable}}"
/**
* How much times can we expand environment variables
@@ -65,37 +59,26 @@ export const parseBodyEnvVariables = (
export function parseTemplateStringE(
str: string,
variables: Environment["variables"],
myVariables?: Variables | undefined
variables: Environment["variables"]
) {
if (!variables || !str) {
return E.right(str)
}
let result = str
let depthEnv = 0
let depthVar = 0
let depth = 0
while (result.match(REGEX_ENV_VAR) != null && depthEnv <= ENV_MAX_EXPAND_LIMIT) {
while (result.match(REGEX_ENV_VAR) != null && depth <= ENV_MAX_EXPAND_LIMIT) {
result = decodeURI(encodeURI(result)).replace(
REGEX_ENV_VAR,
(_, p1) => variables.find((x) => x.key === p1)?.value || ""
)
depthEnv++
depth++
}
if (myVariables) {
while (result.match(REGEX_MY_VAR) != null && depthVar <= ENV_MAX_EXPAND_LIMIT) {
result = decodeURI(encodeURI(result)).replace(
REGEX_MY_VAR,
(_, p1) => myVariables.find((x) => x.key === p1)?.value || ""
)
depthVar++
}
}
return depthEnv <= ENV_MAX_EXPAND_LIMIT && depthVar <= ENV_MAX_EXPAND_LIMIT ? E.right(result) : E.left(ENV_EXPAND_LOOP);
return depth > ENV_MAX_EXPAND_LIMIT
? E.left(ENV_EXPAND_LOOP)
: E.right(result)
}
/**
@@ -103,33 +86,9 @@ export function parseTemplateStringE(
*/
export const parseTemplateString = (
str: string,
variables: Environment["variables"],
myVariables?: Variables
variables: Environment["variables"]
) =>
pipe(
parseTemplateStringE(str, variables, myVariables),
parseTemplateStringE(str, variables),
E.getOrElse(() => str)
)
export function parseMyVariablesString(
str: string,
variables: Variables,
) {
if (!variables || !str) {
return E.right(str)
}
let result = str
let depthVar = 0
while (result.match(REGEX_MY_VAR) != null && depthVar <= ENV_MAX_EXPAND_LIMIT) {
result = decodeURI(encodeURI(result)).replace(
REGEX_MY_VAR,
(_, p1) => variables.find((x) => x.key === p1)?.value || ""
)
depthVar++
}
return depthVar <= ENV_MAX_EXPAND_LIMIT ? E.right(result) : E.left(ENV_EXPAND_LOOP);
}

View File

@@ -8,12 +8,6 @@ import * as E from "fp-ts/Either"
import * as P from "parser-ts/Parser"
import * as S from "parser-ts/string"
import * as C from "parser-ts/char"
import { recordUpdate } from "./utils/record"
/**
* Special characters in the Raw Key Value Grammar
*/
const SPECIAL_CHARS = ["#", ":"] as const
export type RawKeyValueEntry = {
key: string
@@ -37,31 +31,14 @@ const stringTakeUntilCharsInclusive = flow(
P.chainFirst(() => P.sat(() => true)),
)
const quotedString = pipe(
S.doubleQuotedString,
P.map((x) => JSON.parse(`"${x}"`))
)
const key = pipe(
wsSurround(quotedString),
P.alt(() =>
pipe(
stringTakeUntilChars([":", "\n"]),
P.map(Str.trim)
)
)
stringTakeUntilChars([":", "\n"]),
P.map(Str.trim)
)
const value = pipe(
wsSurround(quotedString),
P.alt(() =>
pipe(
stringTakeUntilChars(["\n"]),
P.map(Str.trim)
)
)
stringTakeUntilChars(["\n"]),
P.map(Str.trim)
)
const commented = pipe(
@@ -128,37 +105,6 @@ const tolerantFile = pipe(
/* End of Parser Definitions */
/**
* Detect whether the string needs to have escape characters in raw key value strings
* @param input The string to check against
*/
const stringNeedsEscapingForRawKVString = (input: string) => {
// If there are any of our special characters, it needs to be escaped definitely
if (SPECIAL_CHARS.some((x) => input.includes(x)))
return true
// The theory behind this impl is that if we apply JSON.stringify on a string
// it does escaping and then return a JSON string representation.
// We remove the quotes of the JSON and see if it can be matched against the input string
const stringified = JSON.stringify(input)
const y = stringified
.substring(1, stringified.length - 1)
.trim()
return y !== input
}
/**
* Applies Raw Key Value escaping (via quotes + escape chars) if needed
* @param input The input to apply escape on
* @returns If needed, the escaped string, else the input string itself
*/
const applyEscapeIfNeeded = (input: string) =>
stringNeedsEscapingForRawKVString(input)
? JSON.stringify(input)
: input
/**
* Converts Raw Key Value Entries to the file string format
* @param entries The entries array
@@ -167,13 +113,8 @@ const applyEscapeIfNeeded = (input: string) =>
export const rawKeyValueEntriesToString = (entries: RawKeyValueEntry[]) =>
pipe(
entries,
A.map(
flow(
recordUpdate("key", applyEscapeIfNeeded),
recordUpdate("value", applyEscapeIfNeeded),
({ key, value, active }) =>
active ? `${(key)}: ${value}` : `# ${key}: ${value}`
)
A.map(({ key, value, active }) =>
active ? `${key}: ${value}` : `# ${key}: ${value}`
),
stringArrayJoin("\n")
)

View File

@@ -16,11 +16,6 @@ export type HoppRESTParam = {
active: boolean
}
export type HoppRESTVar = {
key: string
value: string
}
export type HoppRESTHeader = {
key: string
value: string
@@ -56,7 +51,6 @@ export interface HoppRESTRequest {
method: string
endpoint: string
params: HoppRESTParam[]
vars: HoppRESTVar[]
headers: HoppRESTHeader[]
preRequestScript: string
testScript: string
@@ -80,10 +74,6 @@ export const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
(arr) => arr.filter((p) => p.key !== "" && p.value !== ""),
lodashIsEqualEq
),
vars: mapThenEq(
(arr) => arr.filter((v) => v.key !== "" && v.value !== ""),
lodashIsEqualEq
),
method: S.Eq,
name: S.Eq,
preRequestScript: S.Eq,
@@ -136,9 +126,6 @@ export function safelyExtractRESTRequest(
if (x.hasOwnProperty("params") && Array.isArray(x.params))
req.params = x.params // TODO: Deep nested checks
if (x.hasOwnProperty("vars") && Array.isArray(x.vars))
req.vars = x.vars // TODO: Deep nested checks
if (x.hasOwnProperty("headers") && Array.isArray(x.headers))
req.headers = x.headers // TODO: Deep nested checks
}
@@ -199,19 +186,6 @@ export function translateToNewRequest(x: any): HoppRESTRequest {
})
)
const vars: HoppRESTVar[] = (x?.vars ?? []).map(
({
key,
value,
}: {
key: string
value: string
}) => ({
key,
value,
})
)
const name = x?.name ?? "Untitled request"
const method = x?.method ?? ""
@@ -227,7 +201,6 @@ export function translateToNewRequest(x: any): HoppRESTRequest {
endpoint,
headers,
params,
vars,
method,
preRequestScript,
testScript,

View File

@@ -1,17 +0,0 @@
/**
* Modify a record value with a mapping function
* @param name The key to update
* @param func The value to update
* @returns The updated record
*/
export const recordUpdate =
<
X extends {},
K extends keyof X,
R
>(name: K, func: (input: X[K]) => R) =>
(x: X): Omit<X, K> & { [x in K]: R } => ({
...x,
[name]: func(x[name])
})

View File

@@ -40,7 +40,7 @@
"author": "Hoppscotch (support@hoppscotch.io)",
"license": "MIT",
"dependencies": {
"@hoppscotch/data": "workspace:^0.4.3",
"@hoppscotch/data": "workspace:^0.4.2",
"fp-ts": "^2.11.10",
"lodash": "^4.17.21",
"quickjs-emscripten": "^0.15.0",

376
pnpm-lock.yaml generated
View File

@@ -76,7 +76,7 @@ importers:
'@graphql-codegen/urql-introspection': ^2.1.1
'@graphql-typed-document-node/core': ^3.1.1
'@hoppscotch/codemirror-lang-graphql': workspace:^0.2.0
'@hoppscotch/data': workspace:^0.4.3
'@hoppscotch/data': workspace:^0.4.2
'@hoppscotch/js-sandbox': workspace:^2.0.0
'@nuxt/types': ^2.15.8
'@nuxt/typescript-build': ^2.1.0
@@ -357,7 +357,7 @@ importers:
packages/hoppscotch-cli:
specifiers:
'@hoppscotch/data': workspace:^0.4.3
'@hoppscotch/data': workspace:^0.4.2
'@hoppscotch/js-sandbox': workspace:^2.0.0
'@relmify/jest-fp-ts': ^2.0.2
'@swc/core': ^1.2.181
@@ -427,7 +427,7 @@ importers:
packages/hoppscotch-js-sandbox:
specifiers:
'@digitak/esrun': ^3.1.2
'@hoppscotch/data': workspace:^0.4.3
'@hoppscotch/data': workspace:^0.4.2
'@relmify/jest-fp-ts': ^2.0.1
'@types/jest': ^27.4.1
'@types/lodash': ^4.14.181
@@ -539,13 +539,6 @@ packages:
dependencies:
'@babel/highlight': 7.17.9
/@babel/code-frame/7.18.6:
resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/highlight': 7.18.6
dev: true
/@babel/compat-data/7.16.0:
resolution: {integrity: sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==}
engines: {node: '>=6.9.0'}
@@ -563,11 +556,6 @@ packages:
resolution: {integrity: sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==}
engines: {node: '>=6.9.0'}
/@babel/compat-data/7.18.8:
resolution: {integrity: sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/core/7.17.9:
resolution: {integrity: sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==}
engines: {node: '>=6.9.0'}
@@ -590,29 +578,6 @@ packages:
transitivePeerDependencies:
- supports-color
/@babel/core/7.18.10:
resolution: {integrity: sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==}
engines: {node: '>=6.9.0'}
dependencies:
'@ampproject/remapping': 2.1.2
'@babel/code-frame': 7.18.6
'@babel/generator': 7.18.12
'@babel/helper-compilation-targets': 7.18.9_@babel+core@7.18.10
'@babel/helper-module-transforms': 7.18.9
'@babel/helpers': 7.18.9
'@babel/parser': 7.18.11
'@babel/template': 7.18.10
'@babel/traverse': 7.18.11
'@babel/types': 7.18.10
convert-source-map: 1.8.0
debug: 4.3.4
gensync: 1.0.0-beta.2
json5: 2.2.1
semver: 6.3.0
transitivePeerDependencies:
- supports-color
dev: true
/@babel/generator/7.16.5:
resolution: {integrity: sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==}
engines: {node: '>=6.9.0'}
@@ -629,15 +594,6 @@ packages:
jsesc: 2.5.2
source-map: 0.5.7
/@babel/generator/7.18.12:
resolution: {integrity: sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.10
'@jridgewell/gen-mapping': 0.3.2
jsesc: 2.5.2
dev: true
/@babel/helper-annotate-as-pure/7.16.7:
resolution: {integrity: sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==}
engines: {node: '>=6.9.0'}
@@ -675,19 +631,6 @@ packages:
browserslist: 4.20.2
semver: 6.3.0
/@babel/helper-compilation-targets/7.18.9_@babel+core@7.18.10:
resolution: {integrity: sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
dependencies:
'@babel/compat-data': 7.18.8
'@babel/core': 7.18.10
'@babel/helper-validator-option': 7.18.6
browserslist: 4.20.2
semver: 6.3.0
dev: true
/@babel/helper-create-class-features-plugin/7.16.10_@babel+core@7.17.9:
resolution: {integrity: sha512-wDeej0pu3WN/ffTxMNCPW5UCiOav8IcLRxSIyp/9+IF2xJUM9h/OYjg0IJLHaL6F8oU8kqMz9nc1vryXhMsgXg==}
engines: {node: '>=6.9.0'}
@@ -773,11 +716,6 @@ packages:
dependencies:
'@babel/types': 7.17.0
/@babel/helper-environment-visitor/7.18.9:
resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-explode-assignable-expression/7.16.7:
resolution: {integrity: sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==}
engines: {node: '>=6.9.0'}
@@ -807,14 +745,6 @@ packages:
'@babel/template': 7.16.7
'@babel/types': 7.17.0
/@babel/helper-function-name/7.18.9:
resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.10
'@babel/types': 7.18.10
dev: true
/@babel/helper-get-function-arity/7.16.0:
resolution: {integrity: sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==}
engines: {node: '>=6.9.0'}
@@ -839,13 +769,6 @@ packages:
dependencies:
'@babel/types': 7.17.0
/@babel/helper-hoist-variables/7.18.6:
resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.10
dev: true
/@babel/helper-member-expression-to-functions/7.16.7:
resolution: {integrity: sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==}
engines: {node: '>=6.9.0'}
@@ -865,13 +788,6 @@ packages:
dependencies:
'@babel/types': 7.17.0
/@babel/helper-module-imports/7.18.6:
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.10
dev: true
/@babel/helper-module-transforms/7.16.7:
resolution: {integrity: sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==}
engines: {node: '>=6.9.0'}
@@ -902,22 +818,6 @@ packages:
transitivePeerDependencies:
- supports-color
/@babel/helper-module-transforms/7.18.9:
resolution: {integrity: sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-module-imports': 7.18.6
'@babel/helper-simple-access': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
'@babel/helper-validator-identifier': 7.18.6
'@babel/template': 7.18.10
'@babel/traverse': 7.18.11
'@babel/types': 7.18.10
transitivePeerDependencies:
- supports-color
dev: true
/@babel/helper-optimise-call-expression/7.16.7:
resolution: {integrity: sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==}
engines: {node: '>=6.9.0'}
@@ -962,13 +862,6 @@ packages:
dependencies:
'@babel/types': 7.17.0
/@babel/helper-simple-access/7.18.6:
resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.10
dev: true
/@babel/helper-skip-transparent-expression-wrappers/7.16.0:
resolution: {integrity: sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==}
engines: {node: '>=6.9.0'}
@@ -987,36 +880,14 @@ packages:
dependencies:
'@babel/types': 7.17.0
/@babel/helper-split-export-declaration/7.18.6:
resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.18.10
dev: true
/@babel/helper-string-parser/7.18.10:
resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-identifier/7.16.7:
resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==}
engines: {node: '>=6.9.0'}
/@babel/helper-validator-identifier/7.18.6:
resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-validator-option/7.16.7:
resolution: {integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==}
engines: {node: '>=6.9.0'}
/@babel/helper-validator-option/7.18.6:
resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-wrap-function/7.16.8:
resolution: {integrity: sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==}
engines: {node: '>=6.9.0'}
@@ -1038,17 +909,6 @@ packages:
transitivePeerDependencies:
- supports-color
/@babel/helpers/7.18.9:
resolution: {integrity: sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.18.10
'@babel/traverse': 7.18.11
'@babel/types': 7.18.10
transitivePeerDependencies:
- supports-color
dev: true
/@babel/highlight/7.17.9:
resolution: {integrity: sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==}
engines: {node: '>=6.9.0'}
@@ -1057,15 +917,6 @@ packages:
chalk: 2.4.2
js-tokens: 4.0.0
/@babel/highlight/7.18.6:
resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-validator-identifier': 7.18.6
chalk: 2.4.2
js-tokens: 4.0.0
dev: true
/@babel/parser/7.16.2:
resolution: {integrity: sha512-RUVpT0G2h6rOZwqLDTrKk7ksNv7YpAilTnYe1/Q+eDjxEceRMKVWbCsX7t8h6C1qCFi/1Y8WZjcEPBAFG27GPw==}
engines: {node: '>=6.0.0'}
@@ -1089,14 +940,6 @@ packages:
dependencies:
'@babel/types': 7.17.0
/@babel/parser/7.18.11:
resolution: {integrity: sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.18.10
dev: true
/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.16.7_@babel+core@7.17.9:
resolution: {integrity: sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==}
engines: {node: '>=6.9.0'}
@@ -2001,8 +1844,8 @@ packages:
dependencies:
regenerator-runtime: 0.13.9
/@babel/standalone/7.18.12:
resolution: {integrity: sha512-wDh3K5IUJiSMAY0MLYBFoCaj2RCZwvDz5BHn2uHat9KOsGWEVDFgFQFIOO+81Js2phFKNppLC45iOCsZVfJniw==}
/@babel/standalone/7.17.9:
resolution: {integrity: sha512-9wL9AtDlga8avxUrBvQJmhUtJWrelsUL0uV+TcP+49Sb6Pj8/bNIzQzU4dDp0NAPOvnZR/7msFIKsKoCl/W1/w==}
engines: {node: '>=6.9.0'}
dev: true
@@ -2020,15 +1863,6 @@ packages:
debug: 4.3.3
globals: 11.12.0
/@babel/template/7.18.10:
resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
'@babel/parser': 7.18.11
'@babel/types': 7.18.10
dev: true
/@babel/traverse/7.17.3:
resolution: {integrity: sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==}
engines: {node: '>=6.9.0'}
@@ -2064,24 +1898,6 @@ packages:
transitivePeerDependencies:
- supports-color
/@babel/traverse/7.18.11:
resolution: {integrity: sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.18.6
'@babel/generator': 7.18.12
'@babel/helper-environment-visitor': 7.18.9
'@babel/helper-function-name': 7.18.9
'@babel/helper-hoist-variables': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
'@babel/parser': 7.18.11
'@babel/types': 7.18.10
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
- supports-color
dev: true
/@babel/types/7.16.0:
resolution: {integrity: sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==}
engines: {node: '>=6.9.0'}
@@ -2104,15 +1920,6 @@ packages:
'@babel/helper-validator-identifier': 7.16.7
to-fast-properties: 2.0.0
/@babel/types/7.18.10:
resolution: {integrity: sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.18.10
'@babel/helper-validator-identifier': 7.18.6
to-fast-properties: 2.0.0
dev: true
/@bcoe/v8-coverage/0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
@@ -3908,34 +3715,13 @@ packages:
chalk: 4.1.2
dev: true
/@jridgewell/gen-mapping/0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.11
'@jridgewell/trace-mapping': 0.3.15
dev: true
/@jridgewell/resolve-uri/3.0.5:
resolution: {integrity: sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==}
engines: {node: '>=6.0.0'}
/@jridgewell/set-array/1.1.2:
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec/1.4.11:
resolution: {integrity: sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==}
/@jridgewell/trace-mapping/0.3.15:
resolution: {integrity: sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==}
dependencies:
'@jridgewell/resolve-uri': 3.0.5
'@jridgewell/sourcemap-codec': 1.4.11
dev: true
/@jridgewell/trace-mapping/0.3.4:
resolution: {integrity: sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==}
dependencies:
@@ -4242,12 +4028,12 @@ packages:
ufo: 0.7.11
dev: false
/@nuxt/kit-edge/3.0.0-rc.7-27670958.b0bf25c:
resolution: {integrity: sha512-4sbICutKR7fOgnva7M6QBdwwDkMFULgaTafZ1oen+av+LTsOJVdNruPjlEKLhn/6gfLmxT3cLuY61jhTz4qP7A==}
/@nuxt/kit-edge/3.0.0-rc.4-27605536.8c2c80e:
resolution: {integrity: sha512-Fu9ygT3Gi5zbthzZC5PVzaDhVUxLunF1mgfF9b7RoHaO+UoQSWI7AptRwx2jxkUHpftLZjELtDV6MW96xZiWqg==}
engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0 || ^18.0.0}
dependencies:
'@nuxt/schema': /@nuxt/schema-edge/3.0.0-rc.7-27670958.b0bf25c
c12: 0.2.9
'@nuxt/schema': /@nuxt/schema-edge/3.0.0-rc.4-27605536.8c2c80e
c12: 0.2.7
consola: 2.15.3
defu: 6.0.0
globby: 13.1.2
@@ -4256,14 +4042,14 @@ packages:
jiti: 1.14.0
knitwork: 0.1.2
lodash.template: 4.5.0
mlly: 0.5.10
pathe: 0.3.4
mlly: 0.5.3
pathe: 0.3.0
pkg-types: 0.3.3
scule: 0.3.2
scule: 0.2.1
semver: 7.3.7
unctx: 2.0.1
unimport: 0.6.7
untyped: 0.4.5
unctx: 1.1.4
unimport: 0.3.0
untyped: 0.4.4
transitivePeerDependencies:
- esbuild
- rollup
@@ -4296,20 +4082,20 @@ packages:
- encoding
dev: false
/@nuxt/schema-edge/3.0.0-rc.7-27670958.b0bf25c:
resolution: {integrity: sha512-GwZWyVPqpFWNDsPx1zwczv4DIv2ync/0xTTsec8Rnbg14W83apS9vw2GppHpcDAH7R3Hx8a8pHpeg7nPyD9uCg==}
/@nuxt/schema-edge/3.0.0-rc.4-27605536.8c2c80e:
resolution: {integrity: sha512-KOFpjN2efx9lXj84kSHhJV/XWJ8n0zztnJjjmEY3RhgBTd7mYtdI7BsYPtZ30Tz5vJGMlHrIGkLZW6c+IYAKzw==}
engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0 || ^18.0.0}
dependencies:
c12: 0.2.9
c12: 0.2.7
create-require: 1.1.1
defu: 6.0.0
jiti: 1.14.0
pathe: 0.3.4
pathe: 0.3.0
postcss-import-resolver: 2.0.0
scule: 0.3.2
scule: 0.2.1
std-env: 3.1.1
ufo: 0.8.5
unimport: 0.6.7
ufo: 0.8.4
unimport: 0.3.0
transitivePeerDependencies:
- esbuild
- rollup
@@ -6729,12 +6515,6 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
/acorn/8.8.0:
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/after/0.8.2:
resolution: {integrity: sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=}
dev: false
@@ -7716,15 +7496,15 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/c12/0.2.9:
resolution: {integrity: sha512-6jYdexgAKr+3kYoTmvC5eDtDHUg7GmFQSdeQqZzAKiPlFAN1heGUoXDbAYYwUCfefZy+WgVJbmAej5TTQpp3jA==}
/c12/0.2.7:
resolution: {integrity: sha512-ih1nuHbZ6Ltf8Wss96JH6YvKIW5+9+uLAA08LUQAoDrFPGSyvPvQv/QBIRE+dCBWOK4PcwH0ylRkSa9huI1Acw==}
dependencies:
defu: 6.0.0
dotenv: 16.0.1
dotenv: 16.0.0
gittar: 0.1.1
jiti: 1.14.0
mlly: 0.5.10
pathe: 0.3.4
mlly: 0.5.3
pathe: 0.2.0
rc9: 1.2.2
dev: true
@@ -8470,7 +8250,7 @@ packages:
lodash: ^4.17.20
marko: ^3.14.4
mote: ^0.2.0
mustache: ^3.0.0
mustache: ^4.0.1
nunjucks: ^3.2.2
plates: ~0.4.11
pug: ^3.0.0
@@ -9596,8 +9376,8 @@ packages:
engines: {node: '>=10'}
dev: true
/dotenv/16.0.1:
resolution: {integrity: sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==}
/dotenv/16.0.0:
resolution: {integrity: sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==}
engines: {node: '>=12'}
dev: true
@@ -10492,10 +10272,6 @@ packages:
/estree-walker/2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
/estree-walker/3.0.1:
resolution: {integrity: sha512-woY0RUD87WzMBUiZLx8NsYr23N5BKsOMZHhu2hoNRVh6NXGfoiT1KOL8G3UHlJAnEDGmfa5ubNA/AacfG+Kb0g==}
dev: true
/esutils/2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -13856,8 +13632,8 @@ packages:
emojis-list: 3.0.0
json5: 2.2.1
/local-pkg/0.4.2:
resolution: {integrity: sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==}
/local-pkg/0.4.1:
resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==}
engines: {node: '>=14'}
dev: true
@@ -14553,13 +14329,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
/mlly/0.5.10:
resolution: {integrity: sha512-mY6i+bwcgn0XAdZTiiBt6kyoUjLsm3Cuv0T4CchQJcq/UCSUcGPapSxc4g7whtIsUfcsJ2kGqZAdmqCF/VNC/Q==}
/mlly/0.5.3:
resolution: {integrity: sha512-im69tuLD9EJh9fc9TZRpJEFvsBcGMez7glUCWDcHWWCKzhvPmNvyaYjp/+h0qJJN/Xovrs//GzGjOOKmFw4Gog==}
dependencies:
acorn: 8.8.0
pathe: 0.3.4
pathe: 0.2.0
pkg-types: 0.3.3
ufo: 0.8.5
dev: true
/mocha/9.2.2:
@@ -14933,7 +14707,7 @@ packages:
/nuxt-windicss/2.2.11:
resolution: {integrity: sha512-xobq725D6vqpIgYOrLJ6CVlR4xLlFGwuq//gZikXKOdoVRpoK8C+NpHazPd4+f17urGQ4H0LqGBCIujTvV1V0g==}
dependencies:
'@nuxt/kit': /@nuxt/kit-edge/3.0.0-rc.7-27670958.b0bf25c
'@nuxt/kit': /@nuxt/kit-edge/3.0.0-rc.4-27605536.8c2c80e
'@windicss/plugin-utils': 1.8.4
consola: 2.15.3
defu: 6.0.0
@@ -15511,8 +15285,8 @@ packages:
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
dev: true
/pathe/0.3.4:
resolution: {integrity: sha512-YWgqEdxf36R6vcsyj0A+yT/rDRPe0wui4J9gRR7T4whjU5Lx/jZOr75ckEgTNaLVQABAwsrlzHRpIKcCdXAQ5A==}
/pathe/0.3.0:
resolution: {integrity: sha512-3vUjp552BJzCw9vqKsO5sttHkbYqqsZtH0x1PNtItgqx8BXEXzoY1SYRKcL6BTyVh4lGJGLj0tM42elUDMvcYA==}
dev: true
/pause-stream/0.0.11:
@@ -15609,8 +15383,8 @@ packages:
resolution: {integrity: sha512-6AJcCMnjUQPQv/Wk960w0TOmjhdjbeaQJoSKWRQv9N3rgkessCu6J0Ydsog/nw1MbpnxHuPzYbfOn2KmlZO1FA==}
dependencies:
jsonc-parser: 3.0.0
mlly: 0.5.10
pathe: 0.3.4
mlly: 0.5.3
pathe: 0.3.0
dev: true
/pluralize/8.0.0:
@@ -17346,11 +17120,6 @@ packages:
/scule/0.2.1:
resolution: {integrity: sha512-M9gnWtn3J0W+UhJOHmBxBTwv8mZCan5i1Himp60t6vvZcor0wr+IM0URKmIglsWJ7bRujNAVVN77fp+uZaWoKg==}
dev: false
/scule/0.3.2:
resolution: {integrity: sha512-zIvPdjOH8fv8CgrPT5eqtxHQXmPNnV/vHJYffZhE43KZkvULvpCTvOt1HPlFaCZx287INL9qaqrZg34e8NgI4g==}
dev: true
/selenium-webdriver/4.0.0-rc-1:
resolution: {integrity: sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==}
@@ -18165,7 +17934,7 @@ packages:
/strip-literal/0.4.0:
resolution: {integrity: sha512-ql/sBDoJOybTKSIOWrrh8kgUEMjXMwRAkZTD0EwiwxQH/6tTPkZvMIEjp0CRlpi6V5FMiJyvxeRkEi1KrGISoA==}
dependencies:
acorn: 8.8.0
acorn: 8.7.1
dev: true
/style-mod/4.0.0:
@@ -19270,8 +19039,8 @@ packages:
/ufo/0.8.3:
resolution: {integrity: sha512-AIkk06G21y/P+NCatfU+1qldCmI0XCszZLn8AkuKotffF3eqCvlce0KuwM7ZemLE/my0GSYADOAeM5zDYWMB+A==}
/ufo/0.8.5:
resolution: {integrity: sha512-e4+UtA5IRO+ha6hYklwj6r7BjiGMxS0O+UaSg9HbaTefg4kMkzj4tXzEBajRR+wkxf+golgAWKzLbytCUDMJAA==}
/ufo/0.8.4:
resolution: {integrity: sha512-/+BmBDe8GvlB2nIflWasLLAInjYG0bC9HRnfEpNi4sw77J2AJNnEVnTDReVrehoh825+Q/evF3THXTAweyam2g==}
dev: true
/uglify-js/3.14.3:
@@ -19293,13 +19062,13 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/unctx/2.0.1:
resolution: {integrity: sha512-4VkJKSG+lh1yYkvdI0Xd3Gm7y7PU6F0mG5SoJqCI1j2jtIaHvTLAdBfbhDjbHxT93BsRkzcaxaeBtu8W/mX1Sg==}
/unctx/1.1.4:
resolution: {integrity: sha512-fQMML+GjUpIjQa0HBrrJezo2dFpTAbQbU0/KFKw4T5wpc9deGjLHSYthdfNAo2xSWM34csI6arzedezQkqtfGw==}
dependencies:
acorn: 8.8.0
estree-walker: 3.0.1
acorn: 8.7.1
estree-walker: 2.0.2
magic-string: 0.26.2
unplugin: 0.8.1
unplugin: 0.6.3
transitivePeerDependencies:
- esbuild
- rollup
@@ -19344,19 +19113,19 @@ packages:
engines: {node: '>= 0.4.12'}
dev: true
/unimport/0.6.7:
resolution: {integrity: sha512-EMoVqDjswHkU+nD098QYHXH7Mkw7KwGDQAyeRF2lgairJnuO+wpkhIcmCqrD1OPJmsjkTbJ2tW6Ap8St0PuWZA==}
/unimport/0.3.0:
resolution: {integrity: sha512-RxvfvKBY+CyBmIuYSuBeosSiudgcVakdhVofy5mO5sJ3purQRc5yjLw0Lir7MKHnqe6XT1++8flgAvpxu1UkqQ==}
dependencies:
'@rollup/pluginutils': 4.2.1
escape-string-regexp: 5.0.0
fast-glob: 3.2.11
local-pkg: 0.4.2
local-pkg: 0.4.1
magic-string: 0.26.2
mlly: 0.5.10
pathe: 0.3.4
scule: 0.3.2
mlly: 0.5.3
pathe: 0.3.0
scule: 0.2.1
strip-literal: 0.4.0
unplugin: 0.9.0
unplugin: 0.7.0
transitivePeerDependencies:
- esbuild
- rollup
@@ -19467,12 +19236,12 @@ packages:
webpack-virtual-modules: 0.4.3
dev: false
/unplugin/0.8.1:
resolution: {integrity: sha512-o7rUZoPLG1fH4LKinWgb77gDtTE6mw/iry0Pq0Z5UPvZ9+HZ1/4+7fic7t58s8/CGkPrDpGq+RltO+DmswcR4g==}
/unplugin/0.6.3:
resolution: {integrity: sha512-CoW88FQfCW/yabVc4bLrjikN9HC8dEvMU4O7B6K2jsYMPK0l6iAnd9dpJwqGcmXJKRCU9vwSsy653qg+RK0G6A==}
peerDependencies:
esbuild: '>=0.13'
rollup: ^2.50.0
vite: ^2.3.0 || ^3.0.0-0
vite: ^2.3.0
webpack: 4 || 5
peerDependenciesMeta:
esbuild:
@@ -19484,18 +19253,17 @@ packages:
webpack:
optional: true
dependencies:
acorn: 8.8.0
chokidar: 3.5.3
webpack-sources: 3.2.3
webpack-virtual-modules: 0.4.4
webpack-virtual-modules: 0.4.3
dev: true
/unplugin/0.9.0:
resolution: {integrity: sha512-6o7q8Y9yxdPi5yCPmRuFfeNnVzGumRNZSK6hIkvZ6hd0cfigVdm0qBx/GgQ/NEjs54eUV1qTjvMYKRs9yh3rzw==}
/unplugin/0.7.0:
resolution: {integrity: sha512-OsiFrgybmqm5bGuaodvbLYhqUrvGuRHRMZDhddKEXTDbuQ1x+hR7M1WpQguXj03whVYjEYChhFo738cZH5RNig==}
peerDependencies:
esbuild: '>=0.13'
rollup: ^2.50.0
vite: ^2.3.0 || ^3.0.0-0
vite: ^2.3.0
webpack: 4 || 5
peerDependenciesMeta:
esbuild:
@@ -19507,10 +19275,10 @@ packages:
webpack:
optional: true
dependencies:
acorn: 8.8.0
acorn: 8.7.1
chokidar: 3.5.3
webpack-sources: 3.2.3
webpack-virtual-modules: 0.4.4
webpack-virtual-modules: 0.4.3
dev: true
/unquote/1.1.1:
@@ -19523,13 +19291,13 @@ packages:
has-value: 0.3.1
isobject: 3.0.1
/untyped/0.4.5:
resolution: {integrity: sha512-buq9URfOj4xAnVfu6BYNKzHZLHAzsCbHsDc/kHy66ESMqRpj00oD9qWf2M2qm0pC0DigsVxRF3uhOa5HJtrwGA==}
/untyped/0.4.4:
resolution: {integrity: sha512-sY6u8RedwfLfBis0copfU/fzROieyAndqPs8Kn2PfyzTjtA88vCk81J1b5z+8/VJc+cwfGy23/AqOCpvAbkNVw==}
dependencies:
'@babel/core': 7.18.10
'@babel/standalone': 7.18.12
'@babel/types': 7.18.10
scule: 0.3.2
'@babel/core': 7.17.9
'@babel/standalone': 7.17.9
'@babel/types': 7.17.0
scule: 0.2.1
transitivePeerDependencies:
- supports-color
dev: true
@@ -20156,10 +19924,6 @@ packages:
/webpack-virtual-modules/0.4.3:
resolution: {integrity: sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==}
/webpack-virtual-modules/0.4.4:
resolution: {integrity: sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA==}
dev: true
/webpack/4.46.0:
resolution: {integrity: sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==}
engines: {node: '>=6.11.5'}