Compare commits

...

16 Commits

Author SHA1 Message Date
Mir Arif Hasan
5fba746f89 fix: skip parameter in findMany 2023-08-26 01:35:44 +05:30
Andrew Bastin
dfb281bcf7 chore: update prod.Dockerfile to add step for the backend container to not copy .env in 2023-08-25 21:03:00 +05:30
Andrew Bastin
c62482e81f fix: login component in app not respecting allowed auth provider ids 2023-08-25 19:13:03 +05:30
Anwarul Islam
886847ab7b fix: corrections for spotlight searchers (#3275) 2023-08-25 01:44:29 +05:30
Nivedin
a268cab11e fix: context menu bugs (#3279) 2023-08-25 00:27:03 +05:30
Nivedin
e9509b9fa1 fix: tab right click rename bug (#3286) 2023-08-25 00:20:08 +05:30
Liyas Thomas
8db452089c fix: icons inside tooltip (#3283) 2023-08-24 23:55:22 +05:30
Andrew Bastin
a1764023f3 fix: sh-admin not properly loading in env variables 2023-08-24 20:41:46 +05:30
Andrew Bastin
b08b63dc73 feat: cleaner save context handling for graphql (#3282)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com>
2023-08-24 19:07:17 +05:30
Nivedin
a9a4ebf595 fix: autocomplete bug (#3285)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com>
2023-08-24 18:46:58 +05:30
Andrew Bastin
a8e279db28 fix: codegen breaking 2023-08-24 15:03:50 +05:30
Andrew Bastin
d09a3e9237 fix: import-meta-env crashes while using dev mode 2023-08-24 09:43:52 +05:30
Andrew Bastin
efa40cf6ea feat: container registry friendly docker images and all-in-one container (#3193)
Co-authored-by: Balu Babu <balub997@gmail.com>
2023-08-24 00:01:28 +05:30
Akash K
1a3d9f18ab fix: issues in displaying the suggestions menu on EnvInput (#3280) 2023-08-23 21:18:48 +05:30
Akash K
653ccd3240 fix: vertical scroll not working on codemirror instance when lines are not wrapped (#3276)
Co-authored-by: Liyas Thomas <liyascthomas@gmail.com>
2023-08-23 18:14:12 +05:30
Anwarul Islam
c0806cfd07 chore: removed unnecessary dependencies from hoppscotch-ui (#3077) 2023-08-23 18:13:19 +05:30
71 changed files with 1640 additions and 683 deletions

View File

@@ -1 +1,2 @@
*/**/node_modules
node_modules
**/*/node_modules

11
aio.Caddyfile Normal file
View File

@@ -0,0 +1,11 @@
:3000 {
try_files {path} /
root * /site/selfhost-web
file_server
}
:3100 {
try_files {path} /
root * /site/sh-admin
file_server
}

72
aio_run.mjs Normal file
View File

@@ -0,0 +1,72 @@
#!/usr/local/bin/node
// @ts-check
import { execSync, spawn } from "child_process"
import fs from "fs"
import process from "process"
function runChildProcessWithPrefix(command, args, prefix) {
const childProcess = spawn(command, args);
childProcess.stdout.on('data', (data) => {
const output = data.toString().trim().split('\n');
output.forEach((line) => {
console.log(`${prefix} | ${line}`);
});
});
childProcess.stderr.on('data', (data) => {
const error = data.toString().trim().split('\n');
error.forEach((line) => {
console.error(`${prefix} | ${line}`);
});
});
childProcess.on('close', (code) => {
console.log(`${prefix} Child process exited with code ${code}`);
});
childProcess.on('error', (stuff) => {
console.log("error")
console.log(stuff)
})
return childProcess
}
const envFileContent = Object.entries(process.env)
.filter(([env]) => env.startsWith("VITE_"))
.map(([env, val]) => `${env}=${
(val.startsWith("\"") && val.endsWith("\""))
? val
: `"${val}"`
}`)
.join("\n")
fs.writeFileSync("build.env", envFileContent)
execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
fs.rmSync("build.env")
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server")
caddyProcess.on("exit", (code) => {
console.log(`Exiting process because Caddy Server exited with code ${code}`)
process.exit(code)
})
backendProcess.on("exit", (code) => {
console.log(`Exiting process because Backend Server exited with code ${code}`)
process.exit(code)
})
process.on('SIGINT', () => {
console.log("SIGINT received, exiting...")
caddyProcess.kill("SIGINT")
backendProcess.kill("SIGINT")
process.exit(0)
})

View File

@@ -7,6 +7,103 @@ services:
# This service runs the backend app in the port 3170
hoppscotch-backend:
container_name: hoppscotch-backend
build:
dockerfile: prod.Dockerfile
context: .
target: backend
env_file:
- ./.env
restart: always
environment:
# Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well)
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
- PORT=3170
volumes:
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
# - ./packages/hoppscotch-backend/:/usr/src/app
- /usr/src/app/node_modules/
depends_on:
hoppscotch-db:
condition: service_healthy
ports:
- "3170:3170"
# The main hoppscotch app. This will be hosted at port 3000
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
# the SH admin dashboard server at packages/hoppscotch-selfhost-web/Caddyfile
hoppscotch-app:
container_name: hoppscotch-app
build:
dockerfile: prod.Dockerfile
context: .
target: app
env_file:
- ./.env
depends_on:
- hoppscotch-backend
ports:
- "3000:8080"
# The Self Host dashboard for managing the app. This will be hosted at port 3100
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
# the SH admin dashboard server at packages/hoppscotch-sh-admin/Caddyfile
hoppscotch-sh-admin:
container_name: hoppscotch-sh-admin
build:
dockerfile: prod.Dockerfile
context: .
target: sh_admin
env_file:
- ./.env
depends_on:
- hoppscotch-backend
ports:
- "3100:8080"
# The service that spins up all 3 services at once in one container
hoppscotch-aio:
container_name: hoppscotch-aio
build:
dockerfile: prod.Dockerfile
context: .
target: aio
env_file:
- ./.env
depends_on:
hoppscotch-db:
condition: service_healthy
ports:
- "3000:3000"
- "3100:3100"
- "3170:3170"
# The preset DB service, you can delete/comment the below lines if
# you are using an external postgres instance
# This will be exposed at port 5432
hoppscotch-db:
image: postgres:15
ports:
- "5432:5432"
user: postgres
environment:
# The default user defined by the docker image
POSTGRES_USER: postgres
# NOTE: Please UPDATE THIS PASSWORD!
POSTGRES_PASSWORD: testpass
POSTGRES_DB: hoppscotch
healthcheck:
test:
[
"CMD-SHELL",
"sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"
]
interval: 5s
timeout: 5s
retries: 10
# All the services listed below are deprececated
hoppscotch-old-backend:
container_name: hoppscotch-old-backend
build:
dockerfile: packages/hoppscotch-backend/Dockerfile
context: .
@@ -28,54 +125,26 @@ services:
ports:
- "3170:3000"
# The main hoppscotch app. This will be hosted at port 3000
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
# the SH admin dashboard server at packages/hoppscotch-selfhost-web/Caddyfile
hoppscotch-app:
container_name: hoppscotch-app
hoppscotch-old-app:
container_name: hoppscotch-old-app
build:
dockerfile: packages/hoppscotch-selfhost-web/Dockerfile
context: .
env_file:
- ./.env
depends_on:
- hoppscotch-backend
- hoppscotch-old-backend
ports:
- "3000:8080"
# The Self Host dashboard for managing the app. This will be hosted at port 3100
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
# the SH admin dashboard server at packages/hoppscotch-sh-admin/Caddyfile
hoppscotch-sh-admin:
container_name: hoppscotch-sh-admin
hoppscotch-old-sh-admin:
container_name: hoppscotch-old-sh-admin
build:
dockerfile: packages/hoppscotch-sh-admin/Dockerfile
context: .
env_file:
- ./.env
depends_on:
- hoppscotch-backend
- hoppscotch-old-backend
ports:
- "3100:8080"
# The preset DB service, you can delete/comment the below lines if
# you are using an external postgres instance
# This will be exposed at port 5432
hoppscotch-db:
image: postgres:15
ports:
- "5432:5432"
user: postgres
environment:
# The default user defined by the docker image
POSTGRES_USER: postgres
# NOTE: Please UPDATE THIS PASSWORD!
POSTGRES_PASSWORD: testpass
POSTGRES_DB: hoppscotch
healthcheck:
test: ["CMD-SHELL", "sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"]
interval: 5s
timeout: 5s
retries: 10

14
healthcheck.sh Normal file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
curlCheck() {
if ! curl -s --head "$1" | head -n 1 | grep -q "HTTP/1.[01] [23].."; then
echo "URL request failed!"
exit 1
else
echo "URL request succeeded!"
fi
}
curlCheck "http://localhost:3000"
curlCheck "http://localhost:3100"
curlCheck "http://localhost:3170/ping"

View File

@@ -33,7 +33,7 @@
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.2.1",
"@nestjs/throttler": "^4.0.0",
"@prisma/client": "^4.7.1",
"@prisma/client": "^4.16.2",
"apollo-server-express": "^3.11.1",
"apollo-server-plugin-base": "^3.7.1",
"argon2": "^0.30.3",
@@ -57,7 +57,7 @@
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"passport-microsoft": "^1.0.0",
"prisma": "^4.7.1",
"prisma": "^4.16.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.6.0"

View File

@@ -5,7 +5,7 @@ datasource db {
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-1.1.x"]
binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x"]
}
model Team {

View File

@@ -0,0 +1,9 @@
import { Controller, Get } from '@nestjs/common';
@Controller('ping')
export class AppController {
@Get()
ping(): string {
return 'Success';
}
}

View File

@@ -19,6 +19,7 @@ import { UserCollectionModule } from './user-collection/user-collection.module';
import { ShortcodeModule } from './shortcode/shortcode.module';
import { COOKIES_NOT_FOUND } from './errors';
import { ThrottlerModule } from '@nestjs/throttler';
import { AppController } from './app.controller';
@Module({
imports: [
@@ -81,5 +82,6 @@ import { ThrottlerModule } from '@nestjs/throttler';
ShortcodeModule,
],
providers: [GQLComplexityPlugin],
controllers: [AppController],
})
export class AppModule {}

View File

@@ -150,7 +150,7 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
orderBy: {
createdOn: 'desc',
},
skip: 1,
skip: args.cursor ? 1 : 0,
take: args.take,
cursor: args.cursor ? { id: args.cursor } : undefined,
});

View File

@@ -24,6 +24,8 @@ beforeEach(() => {
mockPubSub.publish.mockClear();
});
const date = new Date();
describe('UserHistoryService', () => {
describe('fetchUserHistory', () => {
test('Should return a list of users REST history if exists', async () => {
@@ -400,7 +402,7 @@ describe('UserHistoryService', () => {
request: [{}],
responseMetadata: [{}],
reqType: ReqType.REST,
executedOn: new Date(),
executedOn: date,
isStarred: false,
});
@@ -410,7 +412,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]),
reqType: ReqType.REST,
executedOn: new Date(),
executedOn: date,
isStarred: false,
};

View File

@@ -166,12 +166,6 @@ a {
@apply truncate;
@apply sm:inline-flex;
}
.env-icon {
@apply transition;
@apply inline-flex;
@apply items-center;
}
}
.tippy-svg-arrow {
@@ -332,7 +326,7 @@ pre.ace_editor {
@apply after:font-icon;
@apply after:text-current;
@apply after:right-3;
@apply after:content-["\e313"];
@apply after:content-["\e5cf"];
@apply after:text-lg;
}
@@ -487,6 +481,10 @@ pre.ace_editor {
}
}
.cm-scroller {
@apply overscroll-y-auto;
}
.cm-editor {
.cm-line::selection {
@apply bg-accentDark #{!important};
@@ -576,9 +574,9 @@ details[open] summary .indicator {
}
.gql-operation-not-highlight {
opacity: 0.5;
@apply opacity-50;
}
.gql-operation-highlight {
opacity: 1;
@apply opacity-100;
}

View File

@@ -159,8 +159,8 @@
},
"context_menu": {
"set_environment_variable": "Set as variable",
"add_parameter": "Add to parameter",
"open_link_in_new_tab": "Open link in new tab"
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab"
},
"count": {
"header": "Header {count}",
@@ -185,7 +185,6 @@
"folder": "Folder is empty",
"headers": "This request does not have any headers",
"history": "History is empty",
"history_suggestions": "History does not have any matching entries",
"invites": "Invite list is empty",
"members": "Team is empty",
"parameters": "This request does not have any parameters",
@@ -195,7 +194,6 @@
"schema": "Connect to a GraphQL endpoint to view schema",
"shortcodes": "Shortcodes are empty",
"subscription": "Subscriptions are empty",
"suggestions": "No matching suggestions found",
"team_name": "Team name empty",
"teams": "You don't belong to any teams",
"tests": "There are no tests for this request"
@@ -608,7 +606,7 @@
"delete_method": "Select DELETE method",
"get_method": "Select GET method",
"head_method": "Select HEAD method",
"rename": "Rename Current Request",
"rename": "Rename Request",
"import_curl": "Import cURL",
"show_code": "Generate code snippet",
"method": "Method",
@@ -651,11 +649,11 @@
},
"spotlight": {
"general": {
"help_menu": "Open help and support menu",
"help_menu": "Help and support",
"chat": "Chat with support",
"open_docs": "Read Documentation",
"open_keybindings": "Open keyboard shortcuts",
"social": "Social links and GitHub",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"miscellaneous": {
@@ -663,15 +661,18 @@
"title": "Miscellaneous"
},
"request": {
"tab_parameters": "Open parameters tab",
"tab_body": "Open body tab",
"tab_headers": "Open headers tab",
"tab_authorization": "Open authorization tab",
"tab_pre_request_script": "Open pre-request script tab",
"tab_tests": "Open tests tab"
"switch_to": "Switch to",
"select_method": "Select method",
"save_as_new": "Save as new request",
"tab_parameters": "Parameters tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_authorization": "Authorization tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_tests": "Tests tab"
},
"response": {
"copy": "Copy response as JSON",
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
@@ -694,6 +695,7 @@
"title": "Teams"
},
"tab": {
"duplicate": "Duplicate tab",
"close_current": "Close current tab",
"close_others": "Close other tabs",
"new_tab": "Open a new tab",
@@ -705,9 +707,7 @@
"interface": "Interface",
"interceptor": "Interceptor"
},
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"install_extension": "Install Browser Extension",
"settings": {
"theme": {
"black": "Black Mode",
@@ -721,8 +721,7 @@
"size_lg": "Change to Large"
},
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"install_extension": "Install Browser Extension"
"change_language": "Change Language"
}
},
"sse": {

View File

@@ -29,6 +29,7 @@ declare module 'vue' {
AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default']
AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default']
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
AppSupport: typeof import('./components/app/Support.vue')['default']
@@ -80,6 +81,7 @@ declare module 'vue' {
GraphqlResponse: typeof import('./components/graphql/Response.vue')['default']
GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default']
GraphqlSubscriptionLog: typeof import('./components/graphql/SubscriptionLog.vue')['default']
GraphqlTabHead: typeof import('./components/graphql/TabHead.vue')['default']
GraphqlType: typeof import('./components/graphql/Type.vue')['default']
GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default']
GraphqlVariable: typeof import('./components/graphql/Variable.vue')['default']
@@ -89,7 +91,7 @@ declare module 'vue' {
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartAutoComplete: typeof import("@hoppscotch/ui")["HoppSmartAutoComplete"]
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
@@ -151,6 +153,7 @@ declare module 'vue' {
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideRss: typeof import('~icons/lucide/rss')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
IconLucideVerified: typeof import('~icons/lucide/verified')['default']

View File

@@ -1,7 +1,6 @@
<template>
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
<AppShare :show="showShare" @hide-modal="showShare = false" />
<AppSocial :show="showSocial" @hide-modal="showSocial = false" />
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
<HoppSmartConfirmModal
@@ -27,7 +26,6 @@ const t = useI18n()
const showShortcuts = ref(false)
const showShare = ref(false)
const showSocial = ref(false)
const showLogin = ref(false)
const confirmRemove = ref(false)
@@ -60,10 +58,6 @@ defineActionHandler("modals.share.toggle", () => {
showShare.value = !showShare.value
})
defineActionHandler("modals.social.toggle", () => {
showSocial.value = !showSocial.value
})
defineActionHandler("modals.login.toggle", () => {
showLogin.value = !showLogin.value
})

View File

@@ -1,135 +0,0 @@
<template>
<HoppSmartModal
v-if="show"
dialog
:title="t('app.social_links')"
@close="hideModal"
>
<template #body>
<div class="flex flex-col space-y-2">
<div class="grid grid-cols-3 gap-4">
<a
v-for="(platform, index) in platforms"
:key="`platform-${index}`"
:href="platform.link"
target="_blank"
class="social-link"
tabindex="0"
>
<component :is="platform.icon" class="w-6 h-6" />
<span class="mt-3">
{{ platform.name }}
</span>
</a>
<button class="social-link" @click="copyAppLink">
<component :is="copyIcon" class="w-6 h-6 text-xl" />
<span class="mt-3">
{{ t("app.copy") }}
</span>
</button>
</div>
</div>
</template>
<template #footer>
<p class="text-secondaryLight">
{{ t("app.social_description") }}
</p>
</template>
</HoppSmartModal>
</template>
<script setup lang="ts">
import { useI18n } from "@composables/i18n"
import { useToast } from "@composables/toast"
import { refAutoReset } from "@vueuse/core"
import { copyToClipboard } from "~/helpers/utils/clipboard"
import IconFacebook from "~icons/brands/facebook"
import IconLinkedIn from "~icons/brands/linkedin"
import IconReddit from "~icons/brands/reddit"
import IconTwitter from "~icons/brands/twitter"
import IconCheck from "~icons/lucide/check"
import IconCopy from "~icons/lucide/copy"
import IconGitHub from "~icons/lucide/github"
const t = useI18n()
const toast = useToast()
defineProps<{
show: boolean
}>()
const emit = defineEmits<{
(e: "hide-modal"): void
}>()
const url = "https://hoppscotch.io"
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
IconCopy,
1000
)
const platforms = [
{
name: "GitHub",
icon: IconGitHub,
link: `https://hoppscotch.io/github`,
},
{
name: "Twitter",
icon: IconTwitter,
link: `https://twitter.com/hoppscotch_io`,
},
{
name: "Facebook",
icon: IconFacebook,
link: `https://www.facebook.com/hoppscotch.io`,
},
{
name: "Reddit",
icon: IconReddit,
link: `https://www.reddit.com/r/hoppscotch`,
},
{
name: "LinkedIn",
icon: IconLinkedIn,
link: `https://www.linkedin.com/company/hoppscotch/`,
},
]
const copyAppLink = () => {
copyToClipboard(url)
copyIcon.value = IconCheck
toast.success(`${t("state.copied_to_clipboard")}`)
}
const hideModal = () => {
emit("hide-modal")
}
</script>
<style lang="scss" scoped>
.social-link {
@apply border border-dividerLight;
@apply rounded;
@apply flex-col flex;
@apply p-4;
@apply items-center;
@apply justify-center;
@apply font-semibold;
@apply hover: (bg-primaryLight text-secondaryDark);
@apply focus: outline-none;
@apply focus-visible: border-divider;
svg {
@apply opacity-80;
}
&:hover {
svg {
@apply opacity-100;
}
}
}
</style>

View File

@@ -0,0 +1,3 @@
<template>
<IconLucideCheckCircle class="text-accent" />
</template>

View File

@@ -111,6 +111,7 @@ import {
SwitchWorkspaceSpotlightSearcherService,
WorkspaceSpotlightSearcherService,
} from "~/services/spotlight/searchers/workspace.searcher"
import { InterceptorSpotlightSearcherService } from "~/services/spotlight/searchers/interceptor.searcher"
const t = useI18n()
@@ -138,6 +139,7 @@ useService(EnvironmentsSpotlightSearcherService)
useService(SwitchEnvSpotlightSearcherService)
useService(WorkspaceSpotlightSearcherService)
useService(SwitchWorkspaceSpotlightSearcherService)
useService(InterceptorSpotlightSearcherService)
const search = ref("")
@@ -264,4 +266,3 @@ function newUseArrowKeysForNavigation() {
return { selectedEntry }
}
</script>
~/services/spotlight/searchers/workspace.searcher

View File

@@ -37,6 +37,7 @@
@click="
emit('add-request', {
path: `${collectionIndex}`,
index: collection.requests.length,
})
"
/>
@@ -219,6 +220,7 @@ import {
moveGraphqlRequest,
} from "~/newstore/collections"
import { Picked } from "~/helpers/types/HoppPicked"
import { getTabsRefTo } from "~/helpers/graphql/tab"
const props = defineProps({
picked: { type: Object, default: null },
@@ -293,6 +295,22 @@ const removeCollection = () => {
emit("select", null)
}
const possibleTabs = getTabsRefTo((tab) => {
const ctx = tab.document.saveContext
if (!ctx) return false
return (
ctx.originLocation === "user-collection" &&
ctx.folderPath.startsWith(props.collectionIndex.toString())
)
})
for (const tab of possibleTabs) {
tab.value.document.saveContext = undefined
tab.value.document.isDirty = true
}
removeGraphqlCollection(props.collectionIndex, props.collection.id)
toast.success(`${t("state.deleted")}`)
}

View File

@@ -34,7 +34,12 @@
:icon="IconFilePlus"
:title="t('request.new')"
class="hidden group-hover:inline-flex"
@click="emit('add-request', { path: folderPath })"
@click="
emit('add-request', {
path: folderPath,
index: folder.requests.length,
})
"
/>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
@@ -198,6 +203,7 @@ import { useI18n } from "@composables/i18n"
import { useColorMode } from "@composables/theming"
import { removeGraphqlFolder, moveGraphqlRequest } from "~/newstore/collections"
import { computed, ref } from "vue"
import { getTabsRefTo } from "~/helpers/graphql/tab"
const toast = useToast()
const t = useI18n()
@@ -249,10 +255,8 @@ const collectionIcon = computed(() => {
const pick = () => {
emit("select", {
picked: {
pickedType: "gql-my-folder",
folderPath: props.folderPath,
},
pickedType: "gql-my-folder",
folderPath: props.folderPath,
})
}
@@ -273,6 +277,22 @@ const removeFolder = () => {
emit("select", { picked: null })
}
const possibleTabs = getTabsRefTo((tab) => {
const ctx = tab.document.saveContext
if (!ctx) return false
return (
ctx.originLocation === "user-collection" &&
ctx.folderPath.startsWith(props.folderPath)
)
})
for (const tab of possibleTabs) {
tab.value.document.saveContext = undefined
tab.value.document.isDirty = true
}
removeGraphqlFolder(props.folderPath, props.folder.id)
toast.success(t("state.deleted"))
}

View File

@@ -20,22 +20,28 @@
/>
</span>
<span
class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
class="flex items-center flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
@click="selectRequest()"
>
<span class="truncate" :class="{ 'text-accent': isSelected }">
{{ request.name }}
</span>
<span
v-if="isActive"
v-tippy="{ theme: 'tooltip' }"
class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
:title="`${t('collection.request_in_use')}`"
>
<span
class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
>
</span>
<span
class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
></span>
</span>
</span>
<div class="flex">
<HoppButtonSecondary
v-if="!saveRequest"
v-tippy="{ theme: 'tooltip' }"
:icon="IconRotateCCW"
:title="t('action.restore')"
class="hidden group-hover:inline-flex"
@click="selectRequest()"
/>
<span>
<tippy
ref="options"
@@ -121,7 +127,6 @@
<script setup lang="ts">
import IconCheckCircle from "~icons/lucide/check-circle"
import IconFile from "~icons/lucide/file"
import IconRotateCCW from "~icons/lucide/rotate-ccw"
import IconMoreVertical from "~icons/lucide/more-vertical"
import IconEdit from "~icons/lucide/edit"
import IconCopy from "~icons/lucide/copy"
@@ -132,7 +137,12 @@ import { useToast } from "@composables/toast"
import { HoppGQLRequest, makeGQLRequest } from "@hoppscotch/data"
import { cloneDeep } from "lodash-es"
import { removeGraphqlRequest } from "~/newstore/collections"
import { createNewTab } from "~/helpers/graphql/tab"
import {
createNewTab,
getTabRefWithSaveContext,
currentTabID,
currentActiveTab,
} from "~/helpers/graphql/tab"
// Template refs
const tippyActions = ref<any | null>(null)
@@ -154,6 +164,18 @@ const props = defineProps({
requestIndex: { type: Number, default: null },
})
const isActive = computed(() => {
const saveCtx = currentActiveTab.value?.document.saveContext
if (!saveCtx) return false
return (
saveCtx.originLocation === "user-collection" &&
saveCtx.folderPath === props.folderPath &&
saveCtx.requestIndex === props.requestIndex
)
})
// TODO: Better types please
const emit = defineEmits(["select", "edit-request", "duplicate-request"])
@@ -179,7 +201,24 @@ const selectRequest = () => {
if (props.saveRequest) {
pick()
} else {
const possibleTab = getTabRefWithSaveContext({
originLocation: "user-collection",
folderPath: props.folderPath,
requestIndex: props.requestIndex,
})
// Switch to that request if that request is open
if (possibleTab) {
currentTabID.value = possibleTab.value.id
return
}
createNewTab({
saveContext: {
originLocation: "user-collection",
folderPath: props.folderPath,
requestIndex: props.requestIndex,
},
request: cloneDeep(
makeGQLRequest({
name: props.request.name,
@@ -213,6 +252,18 @@ const removeRequest = () => {
emit("select", null)
}
// Detach the request from any of the tabs
const possibleTab = getTabRefWithSaveContext({
originLocation: "user-collection",
folderPath: props.folderPath,
requestIndex: props.requestIndex,
})
if (possibleTab) {
possibleTab.value.document.saveContext = undefined
possibleTab.value.document.isDirty = true
}
removeGraphqlRequest(props.folderPath, props.requestIndex, props.request.id)
toast.success(`${t("state.deleted")}`)
}

View File

@@ -265,7 +265,7 @@ export default defineComponent({
this.$data.editingCollectionIndex = collectionIndex
this.displayModalEdit(true)
},
onAddRequest({ name, path }) {
onAddRequest({ name, path, index }) {
const newRequest = {
...currentActiveTab.value.document.request,
name,
@@ -274,6 +274,11 @@ export default defineComponent({
saveGraphqlRequestAs(path, newRequest)
createNewTab({
saveContext: {
originLocation: "user-collection",
folderPath: path,
requestIndex: index,
},
request: newRequest,
isDirty: false,
})

View File

@@ -239,6 +239,7 @@ import {
resetTeamRequestsContext,
} from "~/helpers/collection/collection"
import { currentReorderingStatus$ } from "~/newstore/reordering"
import { defineActionHandler } from "~/helpers/actions"
const t = useI18n()
const toast = useToast()
@@ -2067,4 +2068,8 @@ const getErrorMessage = (err: GQLError<string>) => {
}
}
}
defineActionHandler("collection.new", () => {
displayModalAdd(true)
})
</script>

View File

@@ -34,6 +34,13 @@
@hide-modal="displayModalNew(false)"
/>
</div>
<HoppSmartConfirmModal
:show="showConfirmRemoveEnvModal"
:title="t('confirm.remove_team')"
@hide-modal="showConfirmRemoveEnvModal = false"
@resolve="removeSelectedEnvironment()"
/>
</template>
<script setup lang="ts">
@@ -44,6 +51,7 @@ import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
import { useReadonlyStream, useStream } from "@composables/stream"
import { useI18n } from "~/composables/i18n"
import {
getSelectedEnvironmentIndex,
globalEnv$,
selectedEnvironmentIndex$,
setSelectedEnvironmentIndex,
@@ -54,8 +62,15 @@ import { workspaceStatus$ } from "~/newstore/workspace"
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
import { useLocalState } from "~/newstore/localstate"
import { onLoggedIn } from "~/composables/auth"
import { pipe } from "fp-ts/function"
import * as TE from "fp-ts/TaskEither"
import { GQLError } from "~/helpers/backend/GQLClient"
import { deleteEnvironment } from "~/newstore/environments"
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
import { useToast } from "~/composables/toast"
const t = useI18n()
const toast = useToast()
type EnvironmentType = "my-environments" | "team-environments"
@@ -168,6 +183,7 @@ watch(
}
)
const showConfirmRemoveEnvModal = ref(false)
const showModalNew = ref(false)
const showModalDetails = ref(false)
const action = ref<"new" | "edit">("edit")
@@ -194,6 +210,30 @@ const editEnvironment = (environmentIndex: "Global") => {
displayModalEdit(true)
}
const removeSelectedEnvironment = () => {
const selectedEnvIndex = getSelectedEnvironmentIndex()
if (selectedEnvIndex?.type === "NO_ENV_SELECTED") return
if (selectedEnvIndex?.type === "MY_ENV") {
deleteEnvironment(selectedEnvIndex.index)
toast.success(`${t("state.deleted")}`)
}
if (selectedEnvIndex?.type === "TEAM_ENV") {
pipe(
deleteTeamEnvironment(selectedEnvIndex.teamEnvID),
TE.match(
(err: GQLError<string>) => {
console.error(err)
},
() => {
toast.success(`${t("team_environment.deleted")}`)
}
)
)()
}
}
const resetSelectedData = () => {
editingEnvironmentIndex.value = null
}
@@ -203,6 +243,10 @@ defineActionHandler("modals.environment.new", () => {
showModalDetails.value = true
})
defineActionHandler("modals.environment.delete-selected", () => {
showConfirmRemoveEnvModal.value = true
})
defineActionHandler(
"modals.my.environment.edit",
({ envName, variableName }) => {

View File

@@ -312,8 +312,10 @@ const authProviders: AuthProviderItem[] = [
},
]
const allowedAuthProvidersIDsString: string | undefined = import.meta.env
.VITE_ALLOWED_AUTH_PROVIDERS
// Do not format the `import.meta.env.VITE_ALLOWED_AUTH_PROVIDERS` call into multiple lines!
// prettier-ignore
const allowedAuthProvidersIDsString =
import.meta.env.VITE_ALLOWED_AUTH_PROVIDERS
const allowedAuthProvidersIDs = allowedAuthProvidersIDsString
? allowedAuthProvidersIDsString.split(",")

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex flex-col flex-1">
<div
class="sticky z-10 flex items-center justify-between flex-shrink-0 pl-4 overflow-x-auto border-b bg-primary border-dividerLight"
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
>
<span class="flex items-center">
<label class="font-semibold truncate text-secondaryLight">

View File

@@ -1,6 +1,6 @@
<template>
<div
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight"
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
>
<label class="font-semibold text-secondaryLight">
{{ t("tab.headers") }}

View File

@@ -1,6 +1,6 @@
<template>
<div
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight gqlRunQuery"
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
>
<label class="font-semibold text-secondaryLight">
{{ t("request.query") }}

View File

@@ -2,7 +2,7 @@
<div class="flex flex-col flex-1 h-full">
<HoppSmartTabs
v-model="selectedOptionTab"
styles="sticky bg-primary z-10"
styles="sticky top-0 bg-primary z-10 border-b-0"
:render-inactive-tabs="true"
>
<HoppSmartTab
@@ -67,6 +67,7 @@ import {
} from "~/helpers/graphql/connection"
import { useService } from "dioc/vue"
import { InterceptorService } from "~/services/interceptor.service"
import { editGraphqlRequest } from "~/newstore/collections"
type OptionTabs = "query" | "headers" | "variables" | "authorization"
const selectedOptionTab = ref<OptionTabs>("query")
@@ -180,12 +181,29 @@ const hideRequestModal = () => {
showSaveRequestModal.value = false
}
const saveRequest = () => {
showSaveRequestModal.value = true
if (
currentActiveTab.value.document.saveContext &&
currentActiveTab.value.document.saveContext.originLocation ===
"user-collection"
) {
editGraphqlRequest(
currentActiveTab.value.document.saveContext.folderPath,
currentActiveTab.value.document.saveContext.requestIndex,
currentActiveTab.value.document.request
)
currentActiveTab.value.document.isDirty = false
} else {
showSaveRequestModal.value = true
}
}
const clearGQLQuery = () => {
request.value.query = ""
}
defineActionHandler("request.send-cancel", runQuery)
defineActionHandler("request.save", saveRequest)
defineActionHandler("request.save-as", () => {
showSaveRequestModal.value = true
})
defineActionHandler("request.reset", clearGQLQuery)
</script>

View File

@@ -129,9 +129,7 @@ const downloadResponse = (str: string) => {
}
defineActionHandler("response.file.download", () =>
downloadResponse.bind(responseString.value)
)
defineActionHandler("response.copy", () =>
copyResponse.bind(responseString.value)
downloadResponse(responseString.value)
)
defineActionHandler("response.copy", () => copyResponse(responseString.value))
</script>

View File

@@ -0,0 +1,118 @@
<template>
<div
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
:title="tab.document.request.name"
class="truncate px-2 flex items-center"
@dblclick="emit('open-rename-modal')"
@contextmenu.prevent="options?.tippy?.show()"
@click.middle="emit('close-tab')"
>
<tippy
ref="options"
trigger="manual"
interactive
theme="popover"
:on-shown="() => tippyActions!.focus()"
>
<span class="leading-8 px-2 truncate">
{{ tab.document.request.name }}
</span>
<template #content="{ hide }">
<div
ref="tippyActions"
class="flex flex-col focus:outline-none"
tabindex="0"
@keyup.r="renameAction?.$el.click()"
@keyup.d="duplicateAction?.$el.click()"
@keyup.w="closeAction?.$el.click()"
@keyup.x="closeOthersAction?.$el.click()"
@keyup.escape="hide()"
>
<HoppSmartItem
ref="renameAction"
:icon="IconFileEdit"
:label="t('request.rename')"
:shortcut="['R']"
@click="
() => {
emit('open-rename-modal')
hide()
}
"
/>
<HoppSmartItem
ref="duplicateAction"
:icon="IconCopy"
:label="t('tab.duplicate')"
:shortcut="['D']"
@click="
() => {
emit('duplicate-tab')
hide()
}
"
/>
<HoppSmartItem
v-if="isRemovable"
ref="closeAction"
:icon="IconXCircle"
:label="t('tab.close')"
:shortcut="['W']"
@click="
() => {
emit('close-tab')
hide()
}
"
/>
<HoppSmartItem
v-if="isRemovable"
ref="closeOthersAction"
:icon="IconXSquare"
:label="t('tab.close_others')"
:shortcut="['X']"
@click="
() => {
emit('close-other-tabs')
hide()
}
"
/>
</div>
</template>
</tippy>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
import { TippyComponent } from "vue-tippy"
import { useI18n } from "~/composables/i18n"
import IconXCircle from "~icons/lucide/x-circle"
import IconXSquare from "~icons/lucide/x-square"
import IconFileEdit from "~icons/lucide/file-edit"
import IconCopy from "~icons/lucide/copy"
import { HoppGQLTab } from "~/helpers/graphql/tab"
const t = useI18n()
defineProps<{
tab: HoppGQLTab
isRemovable: boolean
}>()
const emit = defineEmits<{
(event: "open-rename-modal"): void
(event: "close-tab"): void
(event: "close-other-tabs"): void
(event: "duplicate-tab"): void
}>()
const tippyActions = ref<TippyComponent | null>(null)
const options = ref<TippyComponent | null>(null)
const renameAction = ref<HTMLButtonElement | null>(null)
const closeAction = ref<HTMLButtonElement | null>(null)
const closeOthersAction = ref<HTMLButtonElement | null>(null)
const duplicateAction = ref<HTMLButtonElement | null>(null)
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="sticky z-10 flex items-center justify-between pl-4 border-b bg-primary border-dividerLight"
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
>
<label class="font-semibold text-secondaryLight">
{{ t("request.variables") }}

View File

@@ -56,13 +56,7 @@
:inspection-results="tabResults"
@paste="onPasteUrl($event)"
@enter="newSendRequest"
>
<template #empty>
<span>
{{ t("empty.history_suggestions") }}
</span>
</template>
</SmartEnvInput>
/>
</div>
</div>
<div class="flex mt-2 sm:mt-0">

View File

@@ -4,7 +4,7 @@
:title="tab.document.request.name"
class="truncate px-2 flex items-center"
@dblclick="emit('open-rename-modal')"
@contextmenu.prevent="options?.tippy.show()"
@contextmenu.prevent="options?.tippy?.show()"
@click.middle="emit('close-tab')"
>
<span

View File

@@ -1,5 +1,5 @@
<template>
<div class="autocomplete-wrapper">
<div ref="autoCompleteWrapper" class="autocomplete-wrapper">
<div
class="absolute inset-0 flex flex-1 divide-x divide-dividerLight overflow-x-auto"
>
@@ -18,7 +18,9 @@
/>
</div>
<ul
v-if="showSuggestionPopover && autoCompleteSource"
v-if="
showSuggestionPopover && autoCompleteSource && suggestions.length > 0
"
ref="suggestionsMenu"
class="suggestions"
>
@@ -39,20 +41,12 @@
<span class="ml-2 truncate">to select</span>
</div>
</li>
<li v-if="suggestions.length === 0" class="pointer-events-none">
<div v-if="slots.empty" class="truncate py-0.5">
<slot name="empty"></slot>
</div>
<span v-else class="truncate py-0.5">
{{ t("empty.suggestions") }}
</span>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, nextTick, computed, Ref, useSlots } from "vue"
import { ref, onMounted, watch, nextTick, computed, Ref } from "vue"
import {
EditorView,
placeholder as placeholderExt,
@@ -69,7 +63,6 @@ import { HoppReactiveEnvPlugin } from "~/helpers/editor/extensions/HoppEnvironme
import { useReadonlyStream } from "@composables/stream"
import { AggregateEnvironment, aggregateEnvs$ } from "~/newstore/environments"
import { platform } from "~/platform"
import { useI18n } from "~/composables/i18n"
import { onClickOutside, useDebounceFn } from "@vueuse/core"
import { InspectorResult } from "~/services/inspection"
import { invokeAction } from "~/helpers/actions"
@@ -111,10 +104,6 @@ const emit = defineEmits<{
(e: "click", ev: any): void
}>()
const slots = useSlots()
const t = useI18n()
const cachedValue = ref(props.modelValue)
const view = ref<EditorView>()
@@ -125,8 +114,9 @@ const currentSuggestionIndex = ref(-1)
const showSuggestionPopover = ref(false)
const suggestionsMenu = ref<any | null>(null)
const autoCompleteWrapper = ref<any | null>(null)
onClickOutside(suggestionsMenu, () => {
onClickOutside(autoCompleteWrapper, () => {
showSuggestionPopover.value = false
})

View File

@@ -7,6 +7,7 @@ import { BehaviorSubject } from "rxjs"
import { HoppRESTDocument } from "./rest/document"
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
import { RequestOptionTabs } from "~/components/http/RequestOptions.vue"
import { HoppGQLSaveContext } from "./graphql/document"
export type HoppAction =
| "contextmenu.open" // Send/Cancel a Hoppscotch Request
@@ -25,14 +26,15 @@ export type HoppAction =
| "request.method.delete" // Select DELETE Method
| "request.import-curl" // Import cURL
| "request.show-code" // Show generated code
| "collection.new" // Create root collection
| "flyouts.chat.open" // Shows the keybinds flyout
| "flyouts.keybinds.toggle" // Shows the keybinds flyout
| "modals.search.toggle" // Shows the search modal
| "modals.support.toggle" // Shows the support modal
| "modals.share.toggle" // Shows the share modal
| "modals.social.toggle" // Shows the social links modal
| "modals.environment.add" // Show add environment modal via context menu
| "modals.environment.new" // Add new environment
| "modals.environment.delete-selected" // Delete Selected Environment
| "modals.my.environment.edit" // Edit current personal environment
| "modals.team.environment.edit" // Edit current team environment
| "modals.team.new" // Add new team
@@ -107,8 +109,13 @@ type HoppActionArgsMap = {
tab: RequestOptionTabs
}
"request.duplicate-tab": {
tabID: string
}
"gql.request.open": {
request: HoppGQLRequest
saveContext?: HoppGQLSaveContext
}
"modals.environment.add": {
envName: string

View File

@@ -17,6 +17,9 @@ import {
getSelectedEnvironmentType,
} from "~/newstore/environments"
import { invokeAction } from "~/helpers/actions"
import IconUser from "~icons/lucide/user?raw"
import IconUsers from "~icons/lucide/users?raw"
import IconEdit from "~icons/lucide/edit?raw"
const HOPP_ENVIRONMENT_REGEX = /(<<[a-zA-Z0-9-_]+>>)/g
@@ -71,14 +74,14 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
const selectedEnvType = getSelectedEnvironmentType()
const envTypeIcon = `<span class="inline-flex items-center opacity-65 -mx-0.5 text-base font-icon">${
selectedEnvType === "TEAM_ENV" ? "group" : "person"
const envTypeIcon = `<span class="inline-flex items-center justify-center my-1">${
selectedEnvType === "TEAM_ENV" ? IconUsers : IconUser
}</span>`
const appendEditAction = (tooltip: HTMLElement) => {
const editIcon = document.createElement("span")
const editIcon = document.createElement("button")
editIcon.className =
"ml-2 cursor-pointer env-icon text-accent hover:text-accentDark"
"ml-2 cursor-pointer text-accent hover:text-accentDark"
editIcon.addEventListener("click", () => {
const isPersonalEnv =
envName === "Global" || selectedEnvType !== "TEAM_ENV"
@@ -88,7 +91,7 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
variableName: parsedEnvKey,
})
})
editIcon.innerHTML = `<span class="inline-flex items-center px-1 -mx-1 text-base font-icon">edit</span>`
editIcon.innerHTML = `<span class="inline-flex items-center justify-center my-1">${IconEdit}</span>`
tooltip.appendChild(editIcon)
}
@@ -103,7 +106,7 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
const kbd = document.createElement("kbd")
const icon = document.createElement("span")
icon.innerHTML = envTypeIcon
icon.className = "mr-2 env-icon"
icon.className = "mr-2"
kbd.textContent = finalEnv
tooltipContainer.appendChild(icon)
tooltipContainer.appendChild(document.createTextNode(`${envName} `))

View File

@@ -197,3 +197,30 @@ export function getTabsRefTo(func: (tab: HoppGQLTab) => boolean) {
.filter(func)
.map((tab) => getTabRef(tab.id))
}
export function closeOtherTabs(tabID: string) {
if (!tabMap.has(tabID)) {
console.warn(
`The tab to close other tabs does not exist (tab id: ${tabID})`
)
return
}
tabOrdering.value = [tabID]
tabMap.forEach((_, id) => {
if (id !== tabID) tabMap.delete(id)
})
currentTabID.value = tabID
}
export function getDirtyTabsCount() {
let count = 0
for (const tab of tabMap.values()) {
if (tab.document.isDirty) count++
}
return count
}

View File

@@ -1,4 +1,4 @@
import { HTTPSnippet } from "httpsnippet"
import * as HTTPSnippet from "httpsnippet"
import { HoppRESTRequest } from "@hoppscotch/data"
import * as O from "fp-ts/Option"
import * as E from "fp-ts/Either"
@@ -208,7 +208,10 @@ export const generateCode = (
}).convert(codegenInfo.lang, codegenInfo.mode, {
indent: " ",
}),
(e) => e
(e) => {
console.error(e)
return e
}
),
// Only allow string output to pass through, else none

View File

@@ -0,0 +1,163 @@
import axios, { AxiosRequestConfig } from "axios"
import { v4 } from "uuid"
import { pipe } from "fp-ts/function"
import * as TE from "fp-ts/TaskEither"
import { cloneDeep } from "lodash-es"
import { NetworkResponse, NetworkStrategy } from "../network"
import { decodeB64StringToArrayBuffer } from "../utils/b64"
import { settingsStore } from "~/newstore/settings"
let cancelSource = axios.CancelToken.source()
type ProxyHeaders = {
"multipart-part-key"?: string
}
type ProxyPayloadType = FormData | (AxiosRequestConfig & { wantsBinary: true })
export const cancelRunningAxiosRequest = () => {
cancelSource.cancel()
// Create a new cancel token
cancelSource = axios.CancelToken.source()
}
const getProxyPayload = (
req: AxiosRequestConfig,
multipartKey: string | null
) => {
let payload: ProxyPayloadType = {
...req,
wantsBinary: true,
accessToken: import.meta.env.VITE_PROXYSCOTCH_ACCESS_TOKEN ?? "",
}
if (payload.data instanceof FormData) {
const formData = payload.data
payload.data = ""
formData.append(multipartKey!, JSON.stringify(payload))
payload = formData
}
return payload
}
const preProcessRequest = (req: AxiosRequestConfig): AxiosRequestConfig => {
const reqClone = cloneDeep(req)
// If the parameters are URLSearchParams, inject them to URL instead
// This prevents issues of marshalling the URLSearchParams to the proxy
if (reqClone.params instanceof URLSearchParams) {
try {
const url = new URL(reqClone.url ?? "")
for (const [key, value] of reqClone.params.entries()) {
url.searchParams.append(key, value)
}
reqClone.url = url.toString()
} catch (e) {
// making this a non-empty block, so we can make the linter happy.
// we should probably use, allowEmptyCatch, or take the time to do something with the caught errors :)
}
reqClone.params = {}
}
return reqClone
}
const axiosWithProxy: NetworkStrategy = (req) =>
pipe(
TE.Do,
TE.bind("processedReq", () => TE.of(preProcessRequest(req))),
// If the request has FormData, the proxy needs a key
TE.bind("multipartKey", ({ processedReq }) =>
TE.of(
processedReq.data instanceof FormData
? `proxyRequestData-${v4()}`
: null
)
),
// Build headers to send
TE.bind("headers", ({ processedReq, multipartKey }) =>
TE.of(
processedReq.data instanceof FormData
? <ProxyHeaders>{
"multipart-part-key": multipartKey,
}
: <ProxyHeaders>{}
)
),
// Create payload
TE.bind("payload", ({ processedReq, multipartKey }) =>
TE.of(getProxyPayload(processedReq, multipartKey))
),
// Run the proxy request
TE.chain(({ payload, headers }) =>
TE.tryCatch(
() =>
axios.post(
settingsStore.value.PROXY_URL || "https://proxy.hoppscotch.io",
payload,
{
headers,
cancelToken: cancelSource.token,
}
),
(reason) =>
axios.isCancel(reason)
? "cancellation" // Convert cancellation errors into cancellation strings
: reason
)
),
// Check success predicate
TE.chain(
TE.fromPredicate(
({ data }) => data.success,
({ data }) => data.data.message || "Proxy Error"
)
),
// Process Base64
TE.chain(({ data }) => {
if (data.isBinary) {
data.data = decodeB64StringToArrayBuffer(data.data)
}
return TE.of(data)
})
)
const axiosWithoutProxy: NetworkStrategy = (req) =>
pipe(
TE.tryCatch(
() =>
axios({
...req,
cancelToken: (cancelSource && cancelSource.token) || "",
responseType: "arraybuffer",
}),
(e) => (axios.isCancel(e) ? "cancellation" : (e as any))
),
TE.orElse((e) =>
e !== "cancellation" && e.response
? TE.right(e.response as NetworkResponse)
: TE.left(e)
)
)
const axiosStrategy: NetworkStrategy = (req) =>
pipe(
req,
settingsStore.value.PROXY_ENABLED ? axiosWithProxy : axiosWithoutProxy
)
export default axiosStrategy

View File

@@ -427,6 +427,10 @@ export function getCurrentEnvironment(): Environment {
}
}
export function getSelectedEnvironmentIndex() {
return environmentsStore.value.selectedEnvironmentIndex
}
export function getSelectedEnvironmentType() {
return environmentsStore.value.selectedEnvironmentIndex.type
}

View File

@@ -21,15 +21,14 @@
:close-visibility="'hover'"
>
<template #tabhead>
<div
v-tippy="{ theme: 'tooltip', delay: [500, 20] }"
:title="tab.document.request.name"
class="truncate px-2"
>
<span class="leading-8 px-2">
{{ tab.document.request.name }}
</span>
</div>
<GraphqlTabHead
:tab="tab"
:is-removable="tabs.length > 1"
@open-rename-modal="openReqRenameModal(tab)"
@close-tab="removeTab(tab.id)"
@close-other-tabs="closeOtherTabsAction(tab.id)"
@duplicate-tab="duplicateTab(tab)"
/>
</template>
<template #suffix>
@@ -59,7 +58,12 @@
<GraphqlSidebar />
</template>
</AppPaneLayout>
<CollectionsEditRequest
v-model="editReqModalReqName"
:show="showRenamingReqNameModalForTabID !== undefined"
@submit="renameReqName"
@hide-modal="showRenamingReqNameModalForTabID = undefined"
/>
<HoppSmartConfirmModal
:show="confirmingCloseForTabID !== null"
:confirm="t('modal.close_unsaved_tab')"
@@ -67,6 +71,13 @@
@hide-modal="onCloseConfirm"
@resolve="onResolveConfirm"
/>
<HoppSmartConfirmModal
:show="confirmingCloseAllTabs"
:confirm="t('modal.close_unsaved_tab')"
:title="t('confirm.close_unsaved_tabs', { count: unsavedTabsCount })"
@hide-modal="confirmingCloseAllTabs = false"
@resolve="onResolveConfirmCloseAllTabs"
/>
</div>
</template>
@@ -80,10 +91,12 @@ import { connection, disconnect } from "~/helpers/graphql/connection"
import { getDefaultGQLRequest } from "~/helpers/graphql/default"
import {
HoppGQLTab,
closeOtherTabs,
closeTab,
createNewTab,
currentTabID,
getActiveTabs,
getDirtyTabsCount,
getTabRef,
updateTab,
updateTabOrdering,
@@ -142,6 +155,27 @@ const onResolveConfirm = () => {
}
}
const confirmingCloseAllTabs = ref(false)
const unsavedTabsCount = ref(0)
const exceptedTabID = ref<string | null>(null)
const closeOtherTabsAction = (tabID: string) => {
const dirtyTabCount = getDirtyTabsCount()
// If there are dirty tabs, show the confirm modal
if (dirtyTabCount > 0) {
confirmingCloseAllTabs.value = true
unsavedTabsCount.value = dirtyTabCount
exceptedTabID.value = tabID
} else {
closeOtherTabs(tabID)
}
}
const onResolveConfirmCloseAllTabs = () => {
if (exceptedTabID.value) closeOtherTabs(exceptedTabID.value)
confirmingCloseAllTabs.value = false
}
const onTabUpdate = (tab: HoppGQLTab) => {
updateTab(tab)
}
@@ -152,8 +186,34 @@ onBeforeUnmount(() => {
}
})
defineActionHandler("gql.request.open", ({ request }) => {
const editReqModalReqName = ref("")
const showRenamingReqNameModalForTabID = ref<string>()
const openReqRenameModal = (tab: HoppGQLTab) => {
editReqModalReqName.value = tab.document.request.name
showRenamingReqNameModalForTabID.value = tab.id
}
const renameReqName = () => {
const tab = getTabRef(showRenamingReqNameModalForTabID.value!)
if (tab.value) {
tab.value.document.request.name = editReqModalReqName.value
updateTab(tab.value)
}
showRenamingReqNameModalForTabID.value = undefined
}
const duplicateTab = (tab: HoppGQLTab) => {
const newTab = createNewTab({
request: tab.document.request,
isDirty: true,
})
currentTabID.value = newTab.id
}
defineActionHandler("gql.request.open", ({ request, saveContext }) => {
createNewTab({
saveContext,
request: request,
isDirty: false,
})

View File

@@ -150,6 +150,7 @@ const showRenamingReqNameModal = ref(false)
const reqName = ref<string>("")
const unsavedTabsCount = ref(0)
const exceptedTabID = ref<string | null>(null)
const renameTabID = ref<string | null>(null)
const t = useI18n()
const toast = useToast()
@@ -257,6 +258,7 @@ const openReqRenameModal = (tabID?: string) => {
if (tabID) {
const tab = getTabRef(tabID)
reqName.value = tab.value.document.request.name
renameTabID.value = tabID
} else {
reqName.value = currentActiveTab.value.document.request.name
}
@@ -264,7 +266,7 @@ const openReqRenameModal = (tabID?: string) => {
}
const renameReqName = () => {
const tab = getTabRef(currentTabID.value)
const tab = getTabRef(renameTabID.value ?? currentTabID.value)
if (tab.value) {
tab.value.document.request.name = reqName.value
updateTab(tab.value)
@@ -459,6 +461,9 @@ defineActionHandler("rest.request.open", ({ doc }) => {
})
defineActionHandler("rest.request.rename", openReqRenameModal)
defineActionHandler("request.duplicate-tab", ({ tabID }) => {
duplicateTab(tabID)
})
const inspectionService = useService(InspectionService)
useService(HeaderInspectorService)

View File

@@ -114,7 +114,7 @@ export class ParameterMenuService extends Service implements ContextMenu {
id: "environment",
text: {
type: "text",
text: this.t("context_menu.add_parameter"),
text: this.t("context_menu.add_parameters"),
},
icon: markRaw(IconArrowDownRight),
action: () => {

View File

@@ -70,7 +70,7 @@ export class URLMenuService extends Service implements ContextMenu {
id: "link-tab",
text: {
type: "text",
text: this.t("context_menu.open_link_in_new_tab"),
text: this.t("context_menu.open_request_in_new_tab"),
},
icon: markRaw(IconCopyPlus),
action: () => {

View File

@@ -1,5 +1,6 @@
import { Service } from "dioc"
import {
SpotlightResultTextType,
SpotlightSearcher,
SpotlightSearcherResult,
SpotlightSearcherSessionState,
@@ -26,6 +27,7 @@ import {
} from "@hoppscotch/data"
import { hoppWorkspaceStore } from "~/newstore/workspace"
import { changeWorkspace } from "~/newstore/workspace"
import { invokeAction } from "~/helpers/actions"
/**
* A spotlight searcher that searches through the user's collections
@@ -143,6 +145,13 @@ export class CollectionsSpotlightSearcherService
},
})
if (pageCategory === "rest" || pageCategory === "graphql") {
minisearch.add({
id: `create-collection`,
name: this.t("collection.new"),
})
}
if (pageCategory === "rest") {
this.loadRESTDocsIntoMinisearch(minisearch)
} else if (pageCategory === "graphql") {
@@ -153,6 +162,11 @@ export class CollectionsSpotlightSearcherService
const scopeHandle = effectScope()
const newCollectionText: SpotlightResultTextType<any> = {
type: "text",
text: this.t("collection.new"),
}
scopeHandle.run(() => {
watch(query, (query) => {
if (pageCategory === "other") {
@@ -165,28 +179,34 @@ export class CollectionsSpotlightSearcherService
results.value = searchResults.map((result) => ({
id: result.id,
text: {
type: "custom",
component: markRaw(RESTRequestSpotlightEntry),
componentProps: {
folderPath: result.id.split("rest-")[1],
},
},
text:
result.id === "create-collection"
? newCollectionText
: {
type: "custom",
component: markRaw(RESTRequestSpotlightEntry),
componentProps: {
folderPath: result.id.split("rest-")[1],
},
},
icon: markRaw(IconFolder),
score: result.score,
}))
} else {
} else if (pageCategory === "graphql") {
const searchResults = minisearch.search(query).slice(0, 10)
results.value = searchResults.map((result) => ({
id: result.id,
text: {
type: "custom",
component: markRaw(GQLRequestSpotlightEntry),
componentProps: {
folderPath: result.id.split("gql-")[1],
},
},
text:
result.id === "create-collection"
? newCollectionText
: {
type: "custom",
component: markRaw(GQLRequestSpotlightEntry),
componentProps: {
folderPath: result.id.split("gql-")[1],
},
},
icon: markRaw(IconFolder),
score: result.score,
}))
@@ -256,6 +276,8 @@ export class CollectionsSpotlightSearcherService
}
public onResultSelect(result: SpotlightSearcherResult): void {
if (result.id === "create-collection") return invokeAction("collection.new")
const [type, path] = result.id.split("-")
if (type === "rest") {
@@ -304,13 +326,15 @@ export class CollectionsSpotlightSearcherService
if (!req) return
createNewGQLTab(
{
request: req,
isDirty: false,
createNewGQLTab({
saveContext: {
originLocation: "user-collection",
folderPath: folderPath.join("/"),
requestIndex: reqIndex,
},
true
)
request: req,
isDirty: false,
})
}
}
}

View File

@@ -21,30 +21,29 @@ import {
StaticSpotlightSearcherService,
} from "./base/static.searcher"
import IconEdit from "~icons/lucide/edit"
import IconTrash2 from "~icons/lucide/trash-2"
import IconCopy from "~icons/lucide/copy"
import IconEdit from "~icons/lucide/edit"
import IconLayers from "~icons/lucide/layers"
import IconTrash2 from "~icons/lucide/trash-2"
import { Service } from "dioc"
import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function"
import { cloneDeep } from "lodash-es"
import MiniSearch from "minisearch"
import { map } from "rxjs"
import { useStreamStatic } from "~/composables/stream"
import { GQLError } from "~/helpers/backend/GQLClient"
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
import {
createEnvironment,
currentEnvironment$,
deleteEnvironment,
duplicateEnvironment,
environmentsStore,
getGlobalVariables,
selectedEnvironmentIndex$,
setSelectedEnvironmentIndex,
} from "~/newstore/environments"
import { pipe } from "fp-ts/function"
import * as TE from "fp-ts/TaskEither"
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
import { GQLError } from "~/helpers/backend/GQLClient"
import { cloneDeep } from "lodash-es"
import { Service } from "dioc"
import MiniSearch from "minisearch"
import { map } from "rxjs"
type Doc = {
text: string
@@ -188,29 +187,6 @@ export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearche
}
}
removeSelectedEnvironment = () => {
if (this.selectedEnvIndex.value?.type === "NO_ENV_SELECTED") return
if (this.selectedEnvIndex.value?.type === "MY_ENV") {
deleteEnvironment(this.selectedEnvIndex.value.index)
// this.toast.success(`${t("state.deleted")}`)
}
if (this.selectedEnvIndex.value?.type === "TEAM_ENV") {
pipe(
deleteTeamEnvironment(this.selectedEnvIndex.value.teamEnvID),
TE.match(
(err: GQLError<string>) => {
console.error(err)
},
() => {
// this.toast.success(`${this.t("team_environment.deleted")}`)
}
)
)()
}
}
public onDocSelected(id: string): void {
switch (id) {
case "new_environment":
@@ -229,7 +205,7 @@ export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearche
})
break
case "delete_selected_env":
this.removeSelectedEnvironment()
invokeAction(`modals.environment.delete-selected`)
break
case "duplicate_selected_env":
this.duplicateSelectedEnv()

View File

@@ -7,14 +7,17 @@ import {
StaticSpotlightSearcherService,
} from "./base/static.searcher"
import IconLinkedIn from "~icons/brands/linkedin"
import IconTwitter from "~icons/brands/twitter"
import IconBook from "~icons/lucide/book"
import IconGithub from "~icons/lucide/github"
import IconDiscord from "~icons/lucide/link"
import IconGitHub from "~icons/lucide/github"
import IconLifeBuoy from "~icons/lucide/life-buoy"
import IconMessageCircle from "~icons/lucide/message-circle"
import IconZap from "~icons/lucide/zap"
type Doc = {
text: string
text: string | string[]
alternates: string[]
icon: object | Component
}
@@ -56,10 +59,25 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ
alternates: ["key", "shortcuts", "binding"],
icon: markRaw(IconZap),
},
social_links: {
text: this.t("spotlight.general.social"),
alternates: ["social", "github", "binding"],
icon: markRaw(IconGithub),
link_github: {
text: [this.t("spotlight.general.social"), "GitHub"],
alternates: ["social", "github", "link"],
icon: markRaw(IconGitHub),
},
link_twitter: {
text: [this.t("spotlight.general.social"), "Twitter"],
alternates: ["social", "twitter", "link"],
icon: markRaw(IconTwitter),
},
link_discord: {
text: [this.t("spotlight.general.social"), "Discord"],
alternates: ["social", "discord", "link"],
icon: markRaw(IconDiscord),
},
link_linkedin: {
text: [this.t("spotlight.general.social"), "LinkedIn"],
alternates: ["social", "linkedin", "link"],
icon: markRaw(IconLinkedIn),
},
})
@@ -87,8 +105,7 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ
}
}
private openDocs() {
const url = "https://docs.hoppscotch.io"
private openURL(url: string) {
window.open(url, "_blank")
}
@@ -101,13 +118,22 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ
invokeAction("flyouts.chat.open")
break
case "open_docs":
this.openDocs()
this.openURL("https://docs.hoppscotch.io")
break
case "open_keybindings":
invokeAction("flyouts.keybinds.toggle")
break
case "social_links":
invokeAction("modals.social.toggle")
case "link_github":
this.openURL("https://hoppscotch.io/github")
break
case "link_twitter":
this.openURL("https://twitter.com/hoppscotch_io")
break
case "link_discord":
this.openURL("https://hoppscotch.io/discord")
break
case "link_linkedin":
this.openURL("https://www.linkedin.com/company/hoppscotch/")
break
}
}

View File

@@ -0,0 +1,126 @@
import { Ref, computed, effectScope, markRaw, ref, unref, watch } from "vue"
import { getI18n } from "~/modules/i18n"
import {
SpotlightSearcher,
SpotlightSearcherResult,
SpotlightSearcherSessionState,
SpotlightService,
} from ".."
import { Service } from "dioc"
import { useService } from "dioc/vue"
import MiniSearch from "minisearch"
import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue"
import { InterceptorService } from "~/services/interceptor.service"
import IconCircle from "~icons/lucide/circle"
/**
* This searcher is responsible for searching through the interceptor.
* And switching between them.
*/
export class InterceptorSpotlightSearcherService
extends Service
implements SpotlightSearcher
{
public static readonly ID = "INTERCEPTOR_SPOTLIGHT_SEARCHER_SERVICE"
private t = getI18n()
public searcherID = "interceptor"
public searcherSectionTitle = this.t("settings.interceptor")
private readonly spotlight = this.bind(SpotlightService)
constructor() {
super()
this.spotlight.registerSearcher(this)
}
private interceptorService = useService(InterceptorService)
createSearchSession(
query: Readonly<Ref<string>>
): [Ref<SpotlightSearcherSessionState>, () => void] {
const loading = ref(false)
const results = ref<SpotlightSearcherResult[]>([])
const minisearch = new MiniSearch({
fields: ["name", "alternates"],
storeFields: ["name"],
})
const interceptorSelection = this.interceptorService
.currentInterceptorID as Ref<string>
const interceptors = this.interceptorService.availableInterceptors
minisearch.addAll(
interceptors.value.map((entry) => {
let id = `interceptor-${entry.interceptorID}`
if (entry.interceptorID === interceptorSelection.value) {
id += "-selected"
}
const name = unref(entry.name(this.t))
return {
id,
name,
alternates: ["interceptor", "change", name],
}
})
)
const scopeHandle = effectScope()
scopeHandle.run(() => {
watch(
[query],
([query]) => {
results.value = minisearch
.search(query, {
prefix: true,
fuzzy: true,
boost: {
reltime: 2,
},
weights: {
fuzzy: 0.2,
prefix: 0.8,
},
})
.map((x) => {
return {
id: x.id,
icon: markRaw(
x.id.endsWith("-selected") ? IconCheckCircle : IconCircle
),
score: x.score,
text: {
type: "text",
text: [this.t("spotlight.section.interceptor"), x.name],
},
}
})
},
{ immediate: true }
)
})
const onSessionEnd = () => {
scopeHandle.stop()
minisearch.removeAll()
}
const resultObj = computed<SpotlightSearcherSessionState>(() => ({
loading: loading.value,
results: results.value,
}))
return [resultObj, onSessionEnd]
}
onResultSelect(result: SpotlightSearcherResult): void {
const selectedInterceptor = result.id.split("-")[1]
this.interceptorService.currentInterceptorID.value = selectedInterceptor
}
}

View File

@@ -1,4 +1,4 @@
import { Component, markRaw, reactive } from "vue"
import { Component, computed, markRaw, reactive } from "vue"
import { invokeAction } from "~/helpers/actions"
import { getI18n } from "~/modules/i18n"
import { SpotlightSearcherResult, SpotlightService } from ".."
@@ -7,12 +7,11 @@ import {
StaticSpotlightSearcherService,
} from "./base/static.searcher"
import { useRoute } from "vue-router"
import { RequestOptionTabs } from "~/components/http/RequestOptions.vue"
import { currentActiveTab } from "~/helpers/rest/tab"
import IconWindow from "~icons/lucide/app-window"
import IconCheck from "~icons/lucide/check"
import IconChevronLeft from "~icons/lucide/chevron-left"
import IconChevronRight from "~icons/lucide/chevron-right"
import IconCheckCircle from "~icons/lucide/check-circle"
import IconCode2 from "~icons/lucide/code-2"
import IconCopy from "~icons/lucide/copy"
import IconFileCode from "~icons/lucide/file-code"
@@ -25,6 +24,7 @@ type Doc = {
text: string | string[]
alternates: string[]
icon: object | Component
excludeFromSearch?: boolean
}
/**
@@ -43,116 +43,160 @@ export class RequestSpotlightSearcherService extends StaticSpotlightSearcherServ
private readonly spotlight = this.bind(SpotlightService)
private route = useRoute()
private isRESTPage = computed(() => this.route.name === "index")
private isGQLPage = computed(() => this.route.name === "graphql")
private documents: Record<string, Doc> = reactive({
send_request: {
text: this.t("shortcut.request.send_request"),
alternates: ["request", "send"],
icon: markRaw(IconPlay),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
save_to_collections: {
text: [
this.t("request.save_as"),
this.t("shortcut.request.save_to_collections"),
],
text: this.t("spotlight.request.save_as_new"),
alternates: ["save", "collections"],
icon: markRaw(IconSave),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
save_request: {
text: this.t("shortcut.request.save_request"),
alternates: ["save", "request"],
icon: markRaw(IconSave),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
rename_request: {
text: this.t("shortcut.request.rename"),
alternates: ["rename", "request"],
icon: markRaw(IconRename),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
copy_request_link: {
text: this.t("shortcut.request.copy_request_link"),
alternates: ["copy", "link"],
icon: markRaw(IconCopy),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
reset_request: {
text: this.t("shortcut.request.reset_request"),
alternates: ["reset", "request"],
icon: markRaw(IconRotateCCW),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
import_curl: {
text: this.t("shortcut.request.import_curl"),
alternates: ["import", "curl"],
icon: markRaw(IconFileCode),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
show_code: {
text: this.t("shortcut.request.show_code"),
alternates: ["show", "code"],
icon: markRaw(IconCode2),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
// Change request method
next_method: {
text: this.t("shortcut.request.next_method"),
alternates: ["next", "method"],
icon: markRaw(IconChevronRight),
},
previous_method: {
text: this.t("shortcut.request.previous_method"),
alternates: ["previous", "method"],
icon: markRaw(IconChevronLeft),
},
get_method: {
text: this.t("shortcut.request.get_method"),
text: [this.t("spotlight.request.select_method"), "GET"],
alternates: ["get", "method"],
icon: markRaw(IconCheck),
icon: markRaw(IconCheckCircle),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
head_method: {
text: this.t("shortcut.request.head_method"),
text: [this.t("spotlight.request.select_method"), "HEAD"],
alternates: ["head", "method"],
icon: markRaw(IconCheck),
icon: markRaw(IconCheckCircle),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
post_method: {
text: this.t("shortcut.request.post_method"),
text: [this.t("spotlight.request.select_method"), "POST"],
alternates: ["post", "method"],
icon: markRaw(IconCheck),
icon: markRaw(IconCheckCircle),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
put_method: {
text: this.t("shortcut.request.put_method"),
text: [this.t("spotlight.request.select_method"), "PUT"],
alternates: ["put", "method"],
icon: markRaw(IconCheck),
icon: markRaw(IconCheckCircle),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
delete_method: {
text: this.t("shortcut.request.delete_method"),
text: [this.t("spotlight.request.select_method"), "DELETE"],
alternates: ["delete", "method"],
icon: markRaw(IconCheck),
icon: markRaw(IconCheckCircle),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
// Change sub tabs
tab_parameters: {
text: this.t("spotlight.request.tab_parameters"),
text: [
this.t("spotlight.request.switch_to"),
this.t("spotlight.request.tab_parameters"),
],
alternates: ["parameters", "tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
tab_body: {
text: this.t("spotlight.request.tab_body"),
text: [
this.t("spotlight.request.switch_to"),
this.t("spotlight.request.tab_body"),
],
alternates: ["body", "tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
tab_headers: {
text: this.t("spotlight.request.tab_headers"),
text: [
this.t("spotlight.request.switch_to"),
this.t("spotlight.request.tab_headers"),
],
alternates: ["headers", "tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
tab_authorization: {
text: this.t("spotlight.request.tab_authorization"),
text: [
this.t("spotlight.request.switch_to"),
this.t("spotlight.request.tab_authorization"),
],
alternates: ["authorization", "tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(
() => !this.isRESTPage.value ?? !this.isGQLPage.value
),
},
tab_pre_request_script: {
text: this.t("spotlight.request.tab_pre_request_script"),
text: [
this.t("spotlight.request.switch_to"),
this.t("spotlight.request.tab_pre_request_script"),
],
alternates: ["pre-request", "script", "tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
tab_tests: {
text: this.t("spotlight.request.tab_tests"),
text: [
this.t("spotlight.request.switch_to"),
this.t("spotlight.request.tab_tests"),
],
alternates: ["tests", "tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(() => !this.isRESTPage.value),
},
})
@@ -209,12 +253,6 @@ export class RequestSpotlightSearcherService extends StaticSpotlightSearcherServ
case "reset_request":
invokeAction("request.reset")
break
case "next_method":
invokeAction("request.method.next")
break
case "previous_method":
invokeAction("request.method.prev")
break
case "get_method":
invokeAction("request.method.get")
break

View File

@@ -10,11 +10,10 @@ import {
} from "./base/static.searcher"
import IconCloud from "~icons/lucide/cloud"
import IconGlobe from "~icons/lucide/globe"
import IconMonitor from "~icons/lucide/monitor"
import IconMoon from "~icons/lucide/moon"
import IconSun from "~icons/lucide/sun"
import IconGlobe from "~icons/lucide/globe"
import IconShieldCheck from "~icons/lucide/shield-check"
import IconType from "~icons/lucide/type"
type Doc = {
@@ -128,22 +127,6 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer
alternates: ["language", "change language"],
icon: markRaw(IconGlobe),
},
change_interceptor: {
text: [
this.t("spotlight.section.interceptor"),
this.t("spotlight.settings.change_interceptor"),
],
alternates: ["interceptor", "change interceptor"],
icon: markRaw(IconShieldCheck),
},
install_ext: {
text: [
this.t("spotlight.section.interceptor"),
this.t("spotlight.settings.install_extension"),
],
alternates: ["install extension", "extension", "interceptor"],
icon: markRaw(IconShieldCheck),
},
})
constructor() {
@@ -174,27 +157,12 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer
applySetting("BG_COLOR", theme)
}
installExtension() {
const url = navigator.userAgent.includes("Firefox")
? "https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
: "https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
window.open(url, "_blank")
}
public onDocSelected(id: string): void {
switch (id) {
case "change_interceptor":
invokeAction("navigation.jump.settings")
break
case "change_lang":
invokeAction("navigation.jump.settings")
break
case "install_ext":
this.installExtension()
break
// theme actions
case "theme_system":
invokeAction("settings.theme.system")

View File

@@ -1,4 +1,4 @@
import { Component, markRaw, reactive } from "vue"
import { Component, computed, markRaw, reactive } from "vue"
import { getI18n } from "~/modules/i18n"
import { SpotlightSearcherResult, SpotlightService } from ".."
import {
@@ -6,19 +6,23 @@ import {
StaticSpotlightSearcherService,
} from "./base/static.searcher"
import { useRoute } from "vue-router"
import { getDefaultRESTRequest } from "~/helpers/rest/default"
import {
closeOtherTabs,
closeTab,
createNewTab,
currentTabID,
getActiveTabs,
} from "~/helpers/rest/tab"
import IconWindow from "~icons/lucide/app-window"
import { getDefaultRESTRequest } from "~/helpers/rest/default"
import { invokeAction } from "~/helpers/actions"
type Doc = {
text: string
alternates: string[]
icon: object | Component
excludeFromSearch?: boolean
}
/**
@@ -37,21 +41,39 @@ export class TabSpotlightSearcherService extends StaticSpotlightSearcherService<
private readonly spotlight = this.bind(SpotlightService)
private route = useRoute()
private showAction = computed(
() => this.route.name === "index" ?? this.route.name === "graphql"
)
private documents: Record<string, Doc> = reactive({
duplicate_tab: {
text: this.t("spotlight.tab.duplicate"),
alternates: ["tab", "duplicate", "duplicate tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(() => !this.showAction.value),
},
close_current_tab: {
text: this.t("spotlight.tab.close_current"),
alternates: ["tab", "close", "close tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(
() => !this.showAction.value ?? getActiveTabs().value.length === 1
),
},
close_other_tabs: {
text: this.t("spotlight.tab.close_others"),
alternates: ["tab", "close", "close all"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(
() => !this.showAction.value ?? getActiveTabs().value.length < 2
),
},
open_new_tab: {
text: this.t("spotlight.tab.new_tab"),
alternates: ["tab", "new", "open tab"],
icon: markRaw(IconWindow),
excludeFromSearch: computed(() => !this.showAction.value),
},
})
@@ -80,6 +102,10 @@ export class TabSpotlightSearcherService extends StaticSpotlightSearcherService<
}
public onDocSelected(id: string): void {
if (id === "duplicate_tab")
invokeAction("request.duplicate-tab", {
tabID: currentTabID.value,
})
if (id === "close_current_tab") closeTab(currentTabID.value)
if (id === "close_other_tabs") closeOtherTabs(currentTabID.value)
if (id === "open_new_tab")

View File

@@ -8,7 +8,7 @@ import {
ref,
watch,
} from "vue"
import { activeActions$, invokeAction } from "~/helpers/actions"
import { invokeAction } from "~/helpers/actions"
import { getI18n } from "~/modules/i18n"
import {
SpotlightSearcher,
@@ -24,6 +24,7 @@ import {
import { Service } from "dioc"
import * as E from "fp-ts/Either"
import MiniSearch from "minisearch"
import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue"
import { useStreamStatic } from "~/composables/stream"
import { runGQLQuery } from "~/helpers/backend/GQLClient"
import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql"
@@ -36,7 +37,7 @@ import IconUserPlus from "~icons/lucide/user-plus"
import IconUsers from "~icons/lucide/users"
type Doc = {
text: string
text: string | string[]
alternates: string[]
icon: object | Component
excludeFromSearch?: boolean
@@ -66,14 +67,6 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe
}
)[0]
private activeActions = useStreamStatic(activeActions$, [], () => {
/* noop */
})[0]
private isLoggedInUser = computed(() =>
this.activeActions.value.includes("user.logout")
)
private isTeamSelected = computed(
() =>
this.workspace.value.type === "team" &&
@@ -82,31 +75,33 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe
private documents: Record<string, Doc> = reactive({
new_team: {
text: this.t("spotlight.workspace.new"),
text: [this.t("team.title"), this.t("spotlight.workspace.new")],
alternates: ["new", "team", "workspace"],
icon: markRaw(IconUsers),
excludeFromSearch: computed(() => !this.isLoggedInUser.value),
},
edit_team: {
text: this.t("spotlight.workspace.edit"),
text: [this.t("team.title"), this.t("spotlight.workspace.edit")],
alternates: ["edit", "team", "workspace"],
icon: markRaw(IconEdit),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
},
invite_members: {
text: this.t("spotlight.workspace.invite"),
text: [this.t("team.title"), this.t("spotlight.workspace.invite")],
alternates: ["invite", "members", "workspace"],
icon: markRaw(IconUserPlus),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
},
delete_team: {
text: this.t("spotlight.workspace.delete"),
text: [this.t("team.title"), this.t("spotlight.workspace.delete")],
alternates: ["delete", "team", "workspace"],
icon: markRaw(IconTrash2),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
},
switch_to_personal: {
text: this.t("spotlight.workspace.switch_to_personal"),
text: [
this.t("team.title"),
this.t("spotlight.workspace.switch_to_personal"),
],
alternates: ["switch", "team", "workspace", "personal"],
icon: markRaw(IconUser),
excludeFromSearch: computed(() => !this.isTeamSelected.value),
@@ -145,8 +140,13 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe
}
public onDocSelected(id: string): void {
if (id === "new_team") invokeAction(`modals.team.new`)
else if (id === "edit_team") invokeAction(`modals.team.edit`)
if (id === "new_team") {
if (platform.auth.getCurrentUser()) {
invokeAction(`modals.team.new`)
} else {
invokeAction(`modals.login.toggle`)
}
} else if (id === "edit_team") invokeAction(`modals.team.edit`)
else if (id === "invite_members") invokeAction(`modals.team.invite`)
else if (id === "delete_team") this.deleteTeam()
else if (id === "switch_to_personal")
@@ -197,6 +197,14 @@ export class SwitchWorkspaceSpotlightSearcherService
})
}
private workspace = useStreamStatic(
workspaceStatus$,
{ type: "personal" },
() => {
/* noop */
}
)[0]
createSearchSession(
query: Readonly<Ref<string>>
): [Ref<SpotlightSearcherSessionState>, () => void] {
@@ -211,8 +219,16 @@ export class SwitchWorkspaceSpotlightSearcherService
this.fetchMyTeams().then((teams) => {
minisearch.addAll(
teams.map((entry) => {
let id = `workspace-${entry.id}`
// if id matches add -selected to it
if (
this.workspace.value.type === "team" &&
this.workspace.value.teamID === entry.id
) {
id += "-selected"
}
return {
id: `workspace-${entry.id}`,
id,
name: entry.name,
alternates: ["team", "workspace", "change", "switch"],
}
@@ -241,7 +257,9 @@ export class SwitchWorkspaceSpotlightSearcherService
.map((x) => {
return {
id: x.id,
icon: markRaw(IconUsers),
icon: markRaw(
x.id.endsWith("-selected") ? IconCheckCircle : IconUsers
),
score: x.score,
text: {
type: "text",

View File

@@ -1,6 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script>
globalThis.import_meta_env = JSON.parse('"import_meta_env_placeholder"')
</script>
<title>Hoppscotch • Open source API development ecosystem</title>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />

View File

@@ -36,7 +36,7 @@ export const META_TAGS = (env: Record<string, string>): IHTMLTag[] => [
},
{
name: "image",
content: `${env.VITE_BASE_URL}/banner.png`,
content: `${env.APP_BASE_URL}/banner.png`,
},
// Open Graph tags
{
@@ -49,7 +49,7 @@ export const META_TAGS = (env: Record<string, string>): IHTMLTag[] => [
},
{
name: "og:image",
content: `${env.VITE_BASE_URL}/banner.png`,
content: `${env.APP_BASE_URL}/banner.png`,
},
// Twitter tags
{
@@ -74,7 +74,7 @@ export const META_TAGS = (env: Record<string, string>): IHTMLTag[] => [
},
{
name: "twitter:image",
content: `${env.VITE_BASE_URL}/banner.png`,
content: `${env.APP_BASE_URL}/banner.png`,
},
// Add to homescreen for Chrome on Android. Fallback for PWA (handled by nuxt)
{
@@ -84,7 +84,7 @@ export const META_TAGS = (env: Record<string, string>): IHTMLTag[] => [
// Windows phone tile icon
{
name: "msapplication-TileImage",
content: `${env.VITE_BASE_URL}/icon.png`,
content: `${env.APP_BASE_URL}/icon.png`,
},
{
name: "msapplication-TileColor",

View File

@@ -29,6 +29,7 @@
"@hoppscotch/common": "workspace:^",
"@hoppscotch/data": "workspace:^",
"axios": "^1.4.0",
"@import-meta-env/unplugin": "^0.4.8",
"buffer": "^6.0.3",
"fp-ts": "^2.16.1",
"process": "^0.11.10",
@@ -74,6 +75,7 @@
"vite-plugin-windicss": "^1.9.1",
"vitest": "^0.34.2",
"vue-tsc": "^1.8.8",
"vite-plugin-fonts": "^0.6.0",
"windicss": "^3.5.6"
}
}

View File

@@ -0,0 +1,17 @@
#!/usr/local/bin/node
import { execSync } from "child_process"
import fs from "fs"
const envFileContent = Object.entries(process.env)
.filter(([env]) => env.startsWith("VITE_"))
.map(
([env, val]) =>
`${env}=${val.startsWith('"') && val.endsWith('"') ? val : `"${val}"`}`
)
.join("\n")
fs.writeFileSync("build.env", envFileContent)
execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
fs.rmSync("build.env")

View File

@@ -17,10 +17,12 @@ import { FileSystemIconLoader } from "unplugin-icons/loaders"
import * as path from "path"
import Unfonts from "unplugin-fonts/vite"
import legacy from "@vitejs/plugin-legacy"
import ImportMetaEnv from "@import-meta-env/unplugin"
const ENV = loadEnv("development", path.resolve(__dirname, "../../"))
const ENV = loadEnv("development", path.resolve(__dirname, "../../"), ["VITE_"])
export default defineConfig({
envPrefix: process.env.HOPP_ALLOW_RUNTIME_ENV ? "VITE_BUILDTIME_" : "VITE_",
envDir: path.resolve(__dirname, "../../"),
// TODO: Migrate @hoppscotch/data to full ESM
define: {
@@ -65,6 +67,7 @@ export default defineConfig({
"@lib": path.resolve(__dirname, "./src/lib"),
stream: "stream-browserify",
util: "util",
querystring: "qs",
},
dedupe: ["vue"],
},
@@ -78,14 +81,15 @@ export default defineConfig({
routeStyle: "nuxt",
dirs: "../hoppscotch-common/src/pages",
importMode: "async",
onRoutesGenerated: (routes) =>
onRoutesGenerated(routes) {
generateSitemap({
routes,
nuxtStyle: true,
allowRobots: true,
dest: ".sitemap-gen",
hostname: ENV.VITE_BASE_URL,
}),
})
},
}),
StaticCopy({
targets: [
@@ -239,5 +243,11 @@ export default defineConfig({
modernPolyfills: ["es.string.replace-all"],
renderLegacyChunks: false,
}),
process.env.HOPP_ALLOW_RUNTIME_ENV
? ImportMetaEnv.vite({
example: "../../.env.example",
env: "../../.env",
})
: [],
],
})

View File

@@ -166,12 +166,6 @@ a {
@apply truncate;
@apply sm:inline-flex;
}
.env-icon {
@apply transition;
@apply inline-flex;
@apply items-center;
}
}
.tippy-svg-arrow {
@@ -332,7 +326,7 @@ pre.ace_editor {
@apply after:font-icon;
@apply after:text-current;
@apply after:right-3;
@apply after:content-["\e313"];
@apply after:content-["\e5cf"];
@apply after:text-lg;
}
@@ -487,6 +481,10 @@ pre.ace_editor {
}
}
.cm-scroller {
@apply overscroll-y-auto;
}
.cm-editor {
.cm-line::selection {
@apply bg-accentDark #{!important};
@@ -574,3 +572,11 @@ details[open] summary .indicator {
@apply rounded;
@apply border-0;
}
.gql-operation-not-highlight {
@apply opacity-50;
}
.gql-operation-highlight {
@apply opacity-100;
}

View File

@@ -2,6 +2,9 @@
<html lang="en" data-font-size="large">
<head>
<script>
globalThis.import_meta_env = JSON.parse('"import_meta_env_placeholder"')
</script>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -14,4 +17,4 @@
<script type="module" src="/src/main.ts"></script>
</body>
</html>
</html>

View File

@@ -51,9 +51,12 @@
"@graphql-codegen/typescript-document-nodes": "3.0.0",
"@graphql-codegen/typescript-operations": "3.0.0",
"@graphql-codegen/urql-introspection": "2.2.1",
"@import-meta-env/cli": "^0.6.3",
"@import-meta-env/unplugin": "^0.4.8",
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
"@vitejs/plugin-vue": "^3.1.0",
"@vue/compiler-sfc": "^3.2.6",
"dotenv": "^16.0.3",
"graphql-tag": "^2.12.6",
"npm-run-all": "^4.1.5",
"sass": "^1.57.1",

View File

@@ -0,0 +1,18 @@
#!/usr/local/bin/node
import { execSync } from "child_process"
import fs from "fs"
const envFileContent = Object.entries(process.env)
.filter(([env]) => env.startsWith("VITE_"))
.map(([env, val]) => `${env}=${
(val.startsWith("\"") && val.endsWith("\""))
? val
: `"${val}"`
}`)
.join("\n")
fs.writeFileSync("build.env", envFileContent)
execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
fs.rmSync("build.env")

View File

@@ -39,5 +39,28 @@ declare module '@vue/runtime-core' {
Tippy: typeof import('vue-tippy')['Tippy'];
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default'];
UsersTable: typeof import('./components/users/Table.vue')['default'];
AppHeader: typeof import('./components/app/Header.vue')['default']
AppLogin: typeof import('./components/app/Login.vue')['default']
AppLogout: typeof import('./components/app/Logout.vue')['default']
AppModal: typeof import('./components/app/Modal.vue')['default']
AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
AppToast: typeof import('./components/app/Toast.vue')['default']
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default']
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
TeamsDetails: typeof import('./components/teams/Details.vue')['default']
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
TeamsMembers: typeof import('./components/teams/Members.vue')['default']
TeamsPendingInvites: typeof import('./components/teams/PendingInvites.vue')['default']
TeamsTable: typeof import('./components/teams/Table.vue')['default']
Tippy: typeof import('vue-tippy')['Tippy']
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default']
UsersTable: typeof import('./components/users/Table.vue')['default']
}
}

View File

@@ -10,10 +10,15 @@ import Pages from 'vite-plugin-pages';
import Layouts from 'vite-plugin-vue-layouts';
import VueI18n from '@intlify/vite-plugin-vue-i18n';
import path from 'path';
import ImportMetaEnv from "@import-meta-env/unplugin"
// https://vitejs.dev/config/
export default defineConfig({
envDir: path.resolve(__dirname, '../../'),
envPrefix:
process.env.HOPP_ALLOW_RUNTIME_ENV
? "VITE_BUILDTIME_"
: "VITE_",
envDir: path.resolve(__dirname, "../.."),
server: {
port: 3100,
},
@@ -85,7 +90,13 @@ export default defineConfig({
variables: ["variable-full"],
},
],
},
}
}),
process.env.HOPP_ALLOW_RUNTIME_ENV
? ImportMetaEnv.vite({
example: "../../.env.example",
env: "../../.env",
})
: [],
],
});

View File

@@ -14,8 +14,7 @@
"do-build-ui": "pnpm run story:build"
},
"peerDependencies": {
"vue": "^3.2.25",
"vue-router": "^4.0.16"
"vue": "^3.2.25"
},
"dependencies": {
"@fontsource-variable/inter": "^5.0.5",
@@ -25,25 +24,10 @@
"@lezer/highlight": "^1.0.0",
"@vitejs/plugin-legacy": "^2.3.0",
"@vueuse/core": "^8.7.5",
"@vueuse/head": "^0.7.9",
"acorn-walk": "^8.2.0",
"esprima": "^4.0.1",
"events": "^3.3.0",
"fp-ts": "^2.12.1",
"globalthis": "^1.0.3",
"lodash-es": "^4.17.21",
"path": "^0.12.7",
"rxjs": "^7.5.5",
"splitpanes": "^3.1.1",
"tern": "^0.24.3",
"timers": "^0.1.1",
"tippy.js": "^6.3.7",
"url": "^0.11.0",
"util": "^0.12.4",
"vite-plugin-eslint": "^1.8.1",
"vue-github-button": "^3.0.3",
"vue-router": "^4.0.16",
"vue-tippy": "6.0.0-alpha.58",
"vuedraggable-es": "^4.1.1"
},
"devDependencies": {
@@ -61,12 +45,10 @@
"@vue/compiler-sfc": "^3.2.39",
"@vue/eslint-config-typescript": "^11.0.1",
"@vue/runtime-core": "^3.2.39",
"cross-env": "^7.0.3",
"eslint": "^8.24.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.5.1",
"histoire": "^0.12.4",
"npm-run-all": "^4.1.5",
"rollup-plugin-polyfill-node": "^0.10.1",
"sass": "^1.53.0",
"typescript": "^4.5.4",
@@ -85,8 +67,6 @@
"vite-plugin-vue-layouts": "^0.7.0",
"vite-plugin-windicss": "^1.8.8",
"vue": "^3.2.25",
"vue-loader": "^16.8.3",
"vue-router": "^4.0.16",
"vue-tsc": "^0.38.2",
"windicss": "^3.5.6"
},

View File

@@ -166,12 +166,6 @@ a {
@apply truncate;
@apply sm:inline-flex;
}
.env-icon {
@apply transition;
@apply inline-flex;
@apply items-center;
}
}
.tippy-svg-arrow {
@@ -332,7 +326,7 @@ pre.ace_editor {
@apply after:font-icon;
@apply after:text-current;
@apply after:right-3;
@apply after:content-["\e313"];
@apply after:content-["\e5cf"];
@apply after:text-lg;
}
@@ -487,6 +481,10 @@ pre.ace_editor {
}
}
.cm-scroller {
@apply overscroll-y-auto;
}
.cm-editor {
.cm-line::selection {
@apply bg-accentDark #{!important};
@@ -574,3 +572,11 @@ details[open] summary .indicator {
@apply rounded;
@apply border-0;
}
.gql-operation-not-highlight {
@apply opacity-50;
}
.gql-operation-highlight {
@apply opacity-100;
}

View File

@@ -57,7 +57,7 @@ const emit = defineEmits<{
@apply font-icon;
@apply mr-2;
@apply transition;
@apply content-["\e876"];
@apply content-["\e5ca"];
}
}

View File

@@ -23,14 +23,7 @@
"@workers/*": ["./src/workers/*"],
"@functional/*": ["./src/helpers/functional/*"]
},
"types": [
"vite/client",
"unplugin-icons/types/vue",
"unplugin-fonts/client",
"vite-plugin-pages/client",
"vite-plugin-vue-layouts/client",
"vite-plugin-pwa/client"
]
"types": ["vite/client", "unplugin-icons/types/vue"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@@ -11,7 +11,8 @@ export default defineConfig({
vue(),
dts({
insertTypesEntry: true,
outDir: ["dist"],
skipDiagnostics: true,
outputDir: ["dist"],
}),
WindiCSS({
root: path.resolve(__dirname),
@@ -48,7 +49,7 @@ export default defineConfig({
fileName: (format, entry) => `${entry}.${format}.js`,
},
rollupOptions: {
external: ["vue", "vue-router"],
external: ["vue"],
output: {
exports: "named",
},

384
pnpm-lock.yaml generated
View File

@@ -107,8 +107,8 @@ importers:
specifier: ^4.0.0
version: 4.0.0(@nestjs/common@9.2.1)(@nestjs/core@9.2.1)(reflect-metadata@0.1.13)
'@prisma/client':
specifier: ^4.7.1
version: 4.8.1(prisma@4.8.1)
specifier: ^4.16.2
version: 4.16.2(prisma@4.16.2)
apollo-server-express:
specifier: ^3.11.1
version: 3.11.1(express@4.18.2)(graphql@15.8.0)
@@ -179,8 +179,8 @@ importers:
specifier: ^1.0.0
version: 1.0.0
prisma:
specifier: ^4.7.1
version: 4.8.1
specifier: ^4.16.2
version: 4.16.2
reflect-metadata:
specifier: ^0.1.13
version: 0.1.13
@@ -881,6 +881,9 @@ importers:
'@hoppscotch/data':
specifier: workspace:^
version: link:../hoppscotch-data
'@import-meta-env/unplugin':
specifier: ^0.4.8
version: 0.4.8(@import-meta-env/cli@0.6.3)(dotenv@16.3.1)
axios:
specifier: ^1.4.0
version: 1.4.0
@@ -984,6 +987,9 @@ importers:
vite:
specifier: ^4.4.9
version: 4.4.9(@types/node@17.0.45)(sass@1.53.0)(terser@5.19.2)
vite-plugin-fonts:
specifier: ^0.6.0
version: 0.6.0(vite@4.4.9)
vite-plugin-html-config:
specifier: ^1.0.11
version: 1.0.11(vite@4.4.9)
@@ -1091,7 +1097,7 @@ importers:
version: 0.14.9(@vue/compiler-sfc@3.2.45)(vite@3.2.4)
unplugin-vue-components:
specifier: ^0.21.0
version: 0.21.0(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(vue@3.2.45)(webpack@5.88.2)
version: 0.21.0(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(vue@3.2.45)
vue:
specifier: ^3.2.6
version: 3.2.45
@@ -1129,6 +1135,12 @@ importers:
'@graphql-codegen/urql-introspection':
specifier: 2.2.1
version: 2.2.1(graphql@16.6.0)
'@import-meta-env/cli':
specifier: ^0.6.3
version: 0.6.3(@import-meta-env/unplugin@0.4.8)(dotenv@16.3.1)
'@import-meta-env/unplugin':
specifier: ^0.4.8
version: 0.4.8(@import-meta-env/cli@0.6.3)(dotenv@16.3.1)
'@intlify/vite-plugin-vue-i18n':
specifier: ^7.0.0
version: 7.0.0(vite@3.2.4)(vue-i18n@9.2.2)
@@ -1138,6 +1150,9 @@ importers:
'@vue/compiler-sfc':
specifier: ^3.2.6
version: 3.2.45
dotenv:
specifier: ^16.0.3
version: 16.3.1
graphql-tag:
specifier: ^2.12.6
version: 2.12.6(graphql@16.6.0)
@@ -1198,63 +1213,18 @@ importers:
'@vueuse/core':
specifier: ^8.7.5
version: 8.7.5(vue@3.2.45)
'@vueuse/head':
specifier: ^0.7.9
version: 0.7.9(vue@3.2.45)
acorn-walk:
specifier: ^8.2.0
version: 8.2.0
esprima:
specifier: ^4.0.1
version: 4.0.1
events:
specifier: ^3.3.0
version: 3.3.0
fp-ts:
specifier: ^2.12.1
version: 2.13.1
globalthis:
specifier: ^1.0.3
version: 1.0.3
lodash-es:
specifier: ^4.17.21
version: 4.17.21
path:
specifier: ^0.12.7
version: 0.12.7
rxjs:
specifier: ^7.5.5
version: 7.6.0
splitpanes:
specifier: ^3.1.1
version: 3.1.1
tern:
specifier: ^0.24.3
version: 0.24.3
timers:
specifier: ^0.1.1
version: 0.1.1
tippy.js:
specifier: ^6.3.7
version: 6.3.7
url:
specifier: ^0.11.0
version: 0.11.0
util:
specifier: ^0.12.4
version: 0.12.4
vite-plugin-eslint:
specifier: ^1.8.1
version: 1.8.1(eslint@8.29.0)(vite@3.2.4)
vue-github-button:
specifier: ^3.0.3
version: 3.0.3
vue-router:
specifier: ^4.0.16
version: 4.1.0(vue@3.2.45)
vue-tippy:
specifier: 6.0.0-alpha.58
version: 6.0.0-alpha.58(vue@3.2.45)
vuedraggable-es:
specifier: ^4.1.1
version: 4.1.1(vue@3.2.45)
@@ -1301,9 +1271,6 @@ importers:
'@vue/runtime-core':
specifier: ^3.2.39
version: 3.2.45
cross-env:
specifier: ^7.0.3
version: 7.0.3
eslint:
specifier: ^8.24.0
version: 8.29.0
@@ -1316,9 +1283,6 @@ importers:
histoire:
specifier: ^0.12.4
version: 0.12.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.19.2)(vite@3.2.4)
npm-run-all:
specifier: ^4.1.5
version: 4.1.5
rollup-plugin-polyfill-node:
specifier: ^0.10.1
version: 0.10.1(rollup@2.79.1)
@@ -1336,7 +1300,7 @@ importers:
version: 0.15.3(@vue/compiler-sfc@3.2.45)
unplugin-vue-components:
specifier: ^0.21.0
version: 0.21.0(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(vue@3.2.45)(webpack@5.88.2)
version: 0.21.0(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(vue@3.2.45)
vite:
specifier: ^3.2.3
version: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.19.2)
@@ -1373,9 +1337,6 @@ importers:
vue:
specifier: ^3.2.25
version: 3.2.45
vue-loader:
specifier: ^16.8.3
version: 16.8.3(@vue/compiler-sfc@3.2.45)(vue@3.2.45)(webpack@5.88.2)
vue-tsc:
specifier: ^0.38.2
version: 0.38.2(typescript@4.9.3)
@@ -6017,6 +5978,47 @@ packages:
- supports-color
dev: true
/@import-meta-env/cli@0.6.3(@import-meta-env/unplugin@0.4.8)(dotenv@16.3.1):
resolution: {integrity: sha512-R0tAmEpNXjqvFgWpnPkODAKaBUKQzCUVzb7DDsOxe1OQxz32ja4W1jdtZikapkh4ZjGnJ7S+kCjKTOfh8qSUOQ==}
engines: {node: '>= 14'}
hasBin: true
peerDependencies:
'@import-meta-env/babel': ^0.4.3
'@import-meta-env/swc': ^0.4.5
'@import-meta-env/unplugin': ^0.4.8
dotenv: ^11.0.0 || ^12.0.4 || ^13.0.1 || ^14.3.2 || ^15.0.1 || ^16.0.0
peerDependenciesMeta:
'@import-meta-env/babel':
optional: true
'@import-meta-env/swc':
optional: true
'@import-meta-env/unplugin':
optional: true
dependencies:
'@import-meta-env/unplugin': 0.4.8(@import-meta-env/cli@0.6.3)(dotenv@16.3.1)
commander: 10.0.1
dotenv: 16.3.1
glob: 10.2.7
picocolors: 1.0.0
serialize-javascript: 6.0.1
/@import-meta-env/unplugin@0.4.8(@import-meta-env/cli@0.6.3)(dotenv@16.3.1):
resolution: {integrity: sha512-NMyC8BzR8+cl7Ht6vZPMqYeR60zxEwB5Yo/OsM5DSaLpD/L04jqQQIxeQDmg5/jRqi+OPc7KPTBhYccjSByNpA==}
engines: {node: '>= 14'}
peerDependencies:
'@import-meta-env/cli': ^0.5.1 || ^0.6.0
dotenv: ^11.0.0 || ^12.0.4 || ^13.0.1 || ^14.3.2 || ^15.0.1 || ^16.0.0
peerDependenciesMeta:
'@import-meta-env/cli':
optional: true
dependencies:
'@import-meta-env/cli': 0.6.3(@import-meta-env/unplugin@0.4.8)(dotenv@16.3.1)
dotenv: 16.3.1
magic-string: 0.30.2
object-hash: 3.0.0
picocolors: 1.0.0
unplugin: 1.2.0
/@intlify/bundle-utils@3.4.0(vue-i18n@9.2.2):
resolution: {integrity: sha512-2UQkqiSAOSPEHMGWlybqWm4G2K0X+FyYho5AwXz6QklSX1EY5EDmOSxZmwscn2qmKBnp6OYsme5kUrnN9xrWzQ==}
engines: {node: '>= 12'}
@@ -6208,6 +6210,17 @@ packages:
dev: false
optional: true
/@isaacs/cliui@8.0.2:
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
dependencies:
string-width: 5.1.2
string-width-cjs: /string-width@4.2.3
strip-ansi: 7.1.0
strip-ansi-cjs: /strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: /wrap-ansi@7.0.0
/@istanbuljs/load-nyc-config@1.1.0:
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
engines: {node: '>=8'}
@@ -7268,6 +7281,12 @@ packages:
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
engines: {node: '>=10'}
/@pkgjs/parseargs@0.11.0:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
requiresBuild: true
optional: true
/@pkgr/utils@2.4.2:
resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -7288,8 +7307,8 @@ packages:
resolution: {integrity: sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==}
dev: false
/@prisma/client@4.8.1(prisma@4.8.1):
resolution: {integrity: sha512-d4xhZhETmeXK/yZ7K0KcVOzEfI5YKGGEr4F5SBV04/MU4ncN/HcE28sy3e4Yt8UFW0ZuImKFQJE+9rWt9WbGSQ==}
/@prisma/client@4.16.2(prisma@4.16.2):
resolution: {integrity: sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==}
engines: {node: '>=14.17'}
requiresBuild: true
peerDependencies:
@@ -7298,16 +7317,16 @@ packages:
prisma:
optional: true
dependencies:
'@prisma/engines-version': 4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe
prisma: 4.8.1
'@prisma/engines-version': 4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81
prisma: 4.16.2
dev: false
/@prisma/engines-version@4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe:
resolution: {integrity: sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw==}
/@prisma/engines-version@4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81:
resolution: {integrity: sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==}
dev: false
/@prisma/engines@4.8.1:
resolution: {integrity: sha512-93tctjNXcIS+i/e552IO6tqw17sX8liivv8WX9lDMCpEEe3ci+nT9F+1oHtAafqruXLepKF80i/D20Mm+ESlOw==}
/@prisma/engines@4.16.2:
resolution: {integrity: sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==}
requiresBuild: true
dev: false
@@ -7981,6 +8000,7 @@ packages:
dependencies:
'@types/eslint': 8.4.10
'@types/estree': 0.0.51
dev: true
/@types/eslint@8.4.10:
resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==}
@@ -7997,6 +8017,7 @@ packages:
/@types/estree@1.0.1:
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
dev: true
/@types/express-serve-static-core@4.17.31:
resolution: {integrity: sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==}
@@ -9658,14 +9679,6 @@ packages:
- vue
dev: false
/@vueuse/head@0.7.9(vue@3.2.45):
resolution: {integrity: sha512-5wnRiH2XIUSLLXJDLDDTcpvAg5QXgTIVZl46AU7to/T91KHsdBLHSE4WhRO7kP0jbkAhlxnx64E29cQtwBrMjg==}
peerDependencies:
vue: '>=3'
dependencies:
vue: 3.2.45
dev: false
/@vueuse/head@1.3.1(vue@3.3.4):
resolution: {integrity: sha512-XCcHGfDzkGlHS7KIPJVYN//L7jpfASLsN7MUE19ndHVQLnPIDxqFLDl7IROsY81PKzawVAUe4OYVWcGixseWxA==}
peerDependencies:
@@ -9735,6 +9748,7 @@ packages:
dependencies:
'@webassemblyjs/helper-numbers': 1.11.6
'@webassemblyjs/helper-wasm-bytecode': 1.11.6
dev: true
/@webassemblyjs/floating-point-hex-parser@1.11.1:
resolution: {integrity: sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==}
@@ -9742,6 +9756,7 @@ packages:
/@webassemblyjs/floating-point-hex-parser@1.11.6:
resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==}
dev: true
/@webassemblyjs/helper-api-error@1.11.1:
resolution: {integrity: sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==}
@@ -9749,6 +9764,7 @@ packages:
/@webassemblyjs/helper-api-error@1.11.6:
resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==}
dev: true
/@webassemblyjs/helper-buffer@1.11.1:
resolution: {integrity: sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==}
@@ -9756,6 +9772,7 @@ packages:
/@webassemblyjs/helper-buffer@1.11.6:
resolution: {integrity: sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==}
dev: true
/@webassemblyjs/helper-numbers@1.11.1:
resolution: {integrity: sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==}
@@ -9771,6 +9788,7 @@ packages:
'@webassemblyjs/floating-point-hex-parser': 1.11.6
'@webassemblyjs/helper-api-error': 1.11.6
'@xtuc/long': 4.2.2
dev: true
/@webassemblyjs/helper-wasm-bytecode@1.11.1:
resolution: {integrity: sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==}
@@ -9778,6 +9796,7 @@ packages:
/@webassemblyjs/helper-wasm-bytecode@1.11.6:
resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==}
dev: true
/@webassemblyjs/helper-wasm-section@1.11.1:
resolution: {integrity: sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==}
@@ -9795,6 +9814,7 @@ packages:
'@webassemblyjs/helper-buffer': 1.11.6
'@webassemblyjs/helper-wasm-bytecode': 1.11.6
'@webassemblyjs/wasm-gen': 1.11.6
dev: true
/@webassemblyjs/ieee754@1.11.1:
resolution: {integrity: sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==}
@@ -9806,6 +9826,7 @@ packages:
resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==}
dependencies:
'@xtuc/ieee754': 1.2.0
dev: true
/@webassemblyjs/leb128@1.11.1:
resolution: {integrity: sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==}
@@ -9817,6 +9838,7 @@ packages:
resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==}
dependencies:
'@xtuc/long': 4.2.2
dev: true
/@webassemblyjs/utf8@1.11.1:
resolution: {integrity: sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==}
@@ -9824,6 +9846,7 @@ packages:
/@webassemblyjs/utf8@1.11.6:
resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==}
dev: true
/@webassemblyjs/wasm-edit@1.11.1:
resolution: {integrity: sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==}
@@ -9849,6 +9872,7 @@ packages:
'@webassemblyjs/wasm-opt': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6
'@webassemblyjs/wast-printer': 1.11.6
dev: true
/@webassemblyjs/wasm-gen@1.11.1:
resolution: {integrity: sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==}
@@ -9868,6 +9892,7 @@ packages:
'@webassemblyjs/ieee754': 1.11.6
'@webassemblyjs/leb128': 1.11.6
'@webassemblyjs/utf8': 1.11.6
dev: true
/@webassemblyjs/wasm-opt@1.11.1:
resolution: {integrity: sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==}
@@ -9885,6 +9910,7 @@ packages:
'@webassemblyjs/helper-buffer': 1.11.6
'@webassemblyjs/wasm-gen': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6
dev: true
/@webassemblyjs/wasm-parser@1.11.1:
resolution: {integrity: sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==}
@@ -9906,6 +9932,7 @@ packages:
'@webassemblyjs/ieee754': 1.11.6
'@webassemblyjs/leb128': 1.11.6
'@webassemblyjs/utf8': 1.11.6
dev: true
/@webassemblyjs/wast-printer@1.11.1:
resolution: {integrity: sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==}
@@ -9919,6 +9946,7 @@ packages:
dependencies:
'@webassemblyjs/ast': 1.11.6
'@xtuc/long': 4.2.2
dev: true
/@wessberg/stringutil@1.0.19:
resolution: {integrity: sha512-9AZHVXWlpN8Cn9k5BC/O0Dzb9E9xfEMXzYrNunwvkUTvuK7xgQPVRZpLo+jWCOZ5r8oBa8NIrHuPEu1hzbb6bg==}
@@ -10075,9 +10103,11 @@ packages:
/@xtuc/ieee754@1.2.0:
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
dev: true
/@xtuc/long@4.2.2:
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
dev: true
/JSONStream@1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
@@ -10136,6 +10166,7 @@ packages:
acorn: ^8
dependencies:
acorn: 8.10.0
dev: true
/acorn-jsx@5.3.2(acorn@7.4.1):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -10256,6 +10287,7 @@ packages:
ajv: ^6.9.1
dependencies:
ajv: 6.12.6
dev: true
/ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -10884,10 +10916,6 @@ packages:
engines: {node: '>=0.6'}
dev: true
/big.js@5.2.2:
resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
dev: true
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
@@ -11340,6 +11368,7 @@ packages:
/chrome-trace-event@1.0.3:
resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==}
engines: {node: '>=6.0'}
dev: true
/ci-info@3.3.2:
resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==}
@@ -11479,6 +11508,10 @@ packages:
dependencies:
delayed-stream: 1.0.0
/commander@10.0.1:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@@ -12339,7 +12372,6 @@ packages:
/dotenv@16.3.1:
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'}
dev: true
/dset@3.1.2:
resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==}
@@ -12413,11 +12445,6 @@ packages:
/emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
/emojis-list@3.0.0:
resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==}
engines: {node: '>= 4'}
dev: true
/encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
@@ -12536,6 +12563,7 @@ packages:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.1
dev: true
/entities@2.1.0:
resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==}
@@ -12654,6 +12682,7 @@ packages:
/es-module-lexer@1.3.0:
resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==}
dev: true
/es-set-tostringtag@2.0.1:
resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==}
@@ -13301,6 +13330,7 @@ packages:
dependencies:
esrecurse: 4.3.0
estraverse: 4.3.0
dev: true
/eslint-scope@7.1.1:
resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==}
@@ -14047,6 +14077,13 @@ packages:
dependencies:
is-callable: 1.2.7
/foreground-child@3.1.1:
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
engines: {node: '>=14'}
dependencies:
cross-spawn: 7.0.3
signal-exit: 4.1.0
/fork-ts-checker-webpack-plugin@7.2.13(typescript@4.8.4)(webpack@5.74.0):
resolution: {integrity: sha512-fR3WRkOb4bQdWB/y7ssDUlVdrclvwtyCUIHCfivAoYxq9dF7XfrDKbMdZIfwJ7hxIAqkYSGeU7lLJE6xrxIBdg==}
engines: {node: '>=12.13.0', yarn: '>=1.0.0'}
@@ -14354,10 +14391,6 @@ packages:
through2: 4.0.2
dev: true
/github-buttons@2.21.1:
resolution: {integrity: sha512-n9bCQ8sj+5oX1YH5NeyWGbAclRDtHEhMBzqw2ctsWpdEHOwVgfruRu0VIVy01Ah10dd/iFajMHYU71L7IBWBOw==}
dev: false
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -14372,6 +14405,18 @@ packages:
/glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
dev: true
/glob@10.2.7:
resolution: {integrity: sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
dependencies:
foreground-child: 3.1.1
jackspeak: 2.3.0
minimatch: 9.0.3
minipass: 6.0.2
path-scurry: 1.10.1
/glob@7.1.6:
resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
@@ -14877,10 +14922,6 @@ packages:
dependencies:
function-bind: 1.1.1
/hash-sum@2.0.0:
resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==}
dev: true
/he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
@@ -15871,6 +15912,14 @@ packages:
resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==}
engines: {node: '>=6'}
/jackspeak@2.3.0:
resolution: {integrity: sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg==}
engines: {node: '>=14'}
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
/jake@10.8.5:
resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==}
engines: {node: '>=10'}
@@ -16887,6 +16936,7 @@ packages:
'@types/node': 18.16.17
merge-stream: 2.0.0
supports-color: 8.1.1
dev: true
/jest-worker@29.4.1:
resolution: {integrity: sha512-O9doU/S1EBe+yp/mstQ0VpPwpv0Clgn68TkNwGxL6/usX/KUW9Arnn4ag8C3jc6qHcXznhsT5Na1liYzAsuAbQ==}
@@ -17424,14 +17474,6 @@ packages:
/loader-runner@4.3.0:
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
engines: {node: '>=6.11.5'}
/loader-utils@2.0.4:
resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==}
engines: {node: '>=8.9.0'}
dependencies:
big.js: 5.2.2
emojis-list: 3.0.0
json5: 2.2.3
dev: true
/local-pkg@0.4.2:
@@ -17560,6 +17602,10 @@ packages:
dependencies:
tslib: 2.6.2
/lru-cache@10.0.1:
resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
engines: {node: 14 || >=16.14}
/lru-cache@4.1.5:
resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==}
dependencies:
@@ -17902,7 +17948,6 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
dependencies:
brace-expansion: 2.0.1
dev: true
/minimist-options@4.1.0:
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
@@ -17928,6 +17973,10 @@ packages:
dependencies:
yallist: 4.0.0
/minipass@6.0.2:
resolution: {integrity: sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==}
engines: {node: '>=16 || 14 >=14.17'}
/minisearch@6.1.0:
resolution: {integrity: sha512-PNxA/X8pWk+TiqPbsoIYH0GQ5Di7m6326/lwU/S4mlo4wGQddIcf/V//1f9TB0V4j59b57b+HZxt8h3iMROGvg==}
dev: false
@@ -19093,6 +19142,13 @@ packages:
path-root-regex: 0.1.2
dev: true
/path-scurry@1.10.1:
resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
engines: {node: '>=16 || 14 >=14.17'}
dependencies:
lru-cache: 10.0.1
minipass: 6.0.2
/path-to-regexp@0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
@@ -19396,13 +19452,13 @@ packages:
- supports-color
dev: false
/prisma@4.8.1:
resolution: {integrity: sha512-ZMLnSjwulIeYfaU1O6/LF6PEJzxN5par5weykxMykS9Z6ara/j76JH3Yo2AH3bgJbPN4Z6NeCK9s5fDkzf33cg==}
/prisma@4.16.2:
resolution: {integrity: sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==}
engines: {node: '>=14.17'}
hasBin: true
requiresBuild: true
dependencies:
'@prisma/engines': 4.8.1
'@prisma/engines': 4.16.2
dev: false
/process-nextick-args@2.0.1:
@@ -19564,10 +19620,6 @@ packages:
once: 1.4.0
dev: true
/punycode@1.3.2:
resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==}
dev: false
/punycode@1.4.1:
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
@@ -19608,12 +19660,6 @@ packages:
dependencies:
side-channel: 1.0.4
/querystring@0.2.0:
resolution: {integrity: sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=}
engines: {node: '>=0.4.x'}
deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
dev: false
/querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
dev: true
@@ -19652,6 +19698,7 @@ packages:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
safe-buffer: 5.2.1
dev: true
/range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
@@ -20252,6 +20299,7 @@ packages:
'@types/json-schema': 7.0.12
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: true
/scuid@1.1.0:
resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==}
@@ -20349,6 +20397,7 @@ packages:
resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==}
dependencies:
randombytes: 2.1.0
dev: true
/serve-static@1.15.0:
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
@@ -20439,6 +20488,10 @@ packages:
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
/signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
/signedsource@1.0.0:
resolution: {integrity: sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==}
dev: true
@@ -20698,10 +20751,6 @@ packages:
through: 2.3.8
dev: false
/splitpanes@3.1.1:
resolution: {integrity: sha512-VUkxDJfIGSvTM/fm/+OSrx8ha9URwE/9B8FPvfzoBuAxVELIHBWpsfnJXIXv77zVwuex//QQL4kTU9SDBPeHjA==}
dev: false
/splitpanes@3.1.5:
resolution: {integrity: sha512-r3Mq2ITFQ5a2VXLOy4/Sb2Ptp7OfEO8YIbhVJqJXoFc9hc5nTXXkCvtVDjIGbvC0vdE7tse+xTM9BMjsszP6bw==}
dev: false
@@ -21086,6 +21135,7 @@ packages:
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
dev: true
/supports-color@9.2.2:
resolution: {integrity: sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA==}
@@ -21169,6 +21219,7 @@ packages:
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
dev: true
/tar@6.1.13:
resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==}
@@ -21241,7 +21292,7 @@ packages:
webpack: 5.74.0
dev: true
/terser-webpack-plugin@5.3.9(esbuild@0.19.2)(webpack@5.88.2):
/terser-webpack-plugin@5.3.9(webpack@5.88.2):
resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==}
engines: {node: '>= 10.13.0'}
peerDependencies:
@@ -21258,12 +21309,12 @@ packages:
optional: true
dependencies:
'@jridgewell/trace-mapping': 0.3.19
esbuild: 0.19.2
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.1
terser: 5.19.2
webpack: 5.88.2(esbuild@0.19.2)
webpack: 5.88.2
dev: true
/terser@5.14.1:
resolution: {integrity: sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==}
@@ -21591,7 +21642,7 @@ packages:
micromatch: 4.0.5
semver: 7.3.8
typescript: 4.9.3
webpack: 5.88.2(esbuild@0.19.2)
webpack: 5.88.2
dev: true
/ts-log@2.2.4:
@@ -22244,7 +22295,7 @@ packages:
- supports-color
dev: true
/unplugin-vue-components@0.21.0(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(vue@3.2.45)(webpack@5.88.2):
/unplugin-vue-components@0.21.0(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(vue@3.2.45):
resolution: {integrity: sha512-U7uOMNmRJ2eAv9CNjP8QRvxs6nAe3FVQUEIUphC1FGguBp3BWSLgGAcSHaX2nQy0gFoDY2mLF2M52W/t/eDaKg==}
engines: {node: '>=14'}
peerDependencies:
@@ -22263,7 +22314,7 @@ packages:
magic-string: 0.26.7
minimatch: 5.1.6
resolve: 1.22.4
unplugin: 0.7.1(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(webpack@5.88.2)
unplugin: 0.7.1(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)
vue: 3.2.45
transitivePeerDependencies:
- esbuild
@@ -22301,7 +22352,7 @@ packages:
- supports-color
dev: true
/unplugin@0.7.1(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4)(webpack@5.88.2):
/unplugin@0.7.1(esbuild@0.19.2)(rollup@2.79.1)(vite@3.2.4):
resolution: {integrity: sha512-Z6hNDXDNh9aimMkPU1mEjtk+2ova8gh0y7rJeJdGH1vWZOHwF2lLQiQ/R97rv9ymmzEQXsR2fyMet72T8jy6ew==}
peerDependencies:
esbuild: '>=0.13'
@@ -22323,7 +22374,6 @@ packages:
esbuild: 0.19.2
rollup: 2.79.1
vite: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.19.2)
webpack: 5.88.2(esbuild@0.19.2)
webpack-sources: 3.2.3
webpack-virtual-modules: 0.4.4
@@ -22360,6 +22410,14 @@ packages:
webpack-virtual-modules: 0.5.0
dev: true
/unplugin@1.2.0:
resolution: {integrity: sha512-7lJXQY4CxOK4jZyVskZuuNBqBSOlxezKqBpfQEpH+Odk2Ban3moKAlvzs9rZuZoZp6/1FEhvY9TZXav2FRhaBg==}
dependencies:
acorn: 8.10.0
chokidar: 3.5.3
webpack-sources: 3.2.3
webpack-virtual-modules: 0.5.0
/unplugin@1.4.0:
resolution: {integrity: sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==}
dependencies:
@@ -22435,13 +22493,6 @@ packages:
requires-port: 1.0.0
dev: true
/url@0.11.0:
resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==}
dependencies:
punycode: 1.3.2
querystring: 0.2.0
dev: false
/url@0.11.1:
resolution: {integrity: sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==}
dependencies:
@@ -22472,17 +22523,6 @@ packages:
inherits: 2.0.3
dev: false
/util@0.12.4:
resolution: {integrity: sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==}
dependencies:
inherits: 2.0.4
is-arguments: 1.1.1
is-generator-function: 1.0.10
is-typed-array: 1.1.12
safe-buffer: 5.2.1
which-typed-array: 1.1.11
dev: false
/util@0.12.5:
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
dependencies:
@@ -22772,10 +22812,19 @@ packages:
peerDependencies:
vite: ^2.0.0 || ^3.0.0
dependencies:
fast-glob: 3.2.12
fast-glob: 3.3.1
vite: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.19.2)
dev: true
/vite-plugin-fonts@0.6.0(vite@4.4.9):
resolution: {integrity: sha512-dV6nnLEju8k5EmvlBH6egxkVZ+rgc5zWsJr9+cNRXBMEDnpRGHcZPI260UEDNg2yB99wSTNER2eduEvZFbMIGw==}
peerDependencies:
vite: ^2.0.0 || ^3.0.0
dependencies:
fast-glob: 3.3.1
vite: 4.4.9(@types/node@17.0.45)(sass@1.53.0)(terser@5.19.2)
dev: true
/vite-plugin-html-config@1.0.10(vite@3.2.4):
resolution: {integrity: sha512-qJCVKC/mR4BIy4EG7AHQ3nGo1BF+3fOjVIka0kXKQMlxT12dl9G5YKmjhLohDzySijOb03R2PzYiAdavwKkqQQ==}
engines: {node: '>=12.0.0'}
@@ -23586,12 +23635,6 @@ packages:
- supports-color
dev: true
/vue-github-button@3.0.3:
resolution: {integrity: sha512-O2Kv5HxRMn1qqgt2sSy8N7y2C6WGOeICzgGj6y+JFcnLjorTTzNR8vY5abiPOYDiW03WZ/9hUIn7Gm9CG9pIuA==}
dependencies:
github-buttons: 2.21.1
dev: false
/vue-i18n@9.2.2(vue@3.2.45):
resolution: {integrity: sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==}
engines: {node: '>= 14'}
@@ -23616,26 +23659,6 @@ packages:
'@vue/devtools-api': 6.2.1
vue: 3.3.4
/vue-loader@16.8.3(@vue/compiler-sfc@3.2.45)(vue@3.2.45)(webpack@5.88.2):
resolution: {integrity: sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==}
peerDependencies:
'@vue/compiler-sfc': ^3.0.8
vue: ^3.2.13
webpack: ^4.1.0 || ^5.0.0-0
peerDependenciesMeta:
'@vue/compiler-sfc':
optional: true
vue:
optional: true
dependencies:
'@vue/compiler-sfc': 3.2.45
chalk: 4.1.2
hash-sum: 2.0.0
loader-utils: 2.0.4
vue: 3.2.45
webpack: 5.88.2(esbuild@0.19.2)
dev: true
/vue-pdf-embed@1.1.6(vue@3.3.4):
resolution: {integrity: sha512-CRQIw8OxiD6H1n8KT2zVWbp/00fA3PgSV/JYJ0Ut+FdC1jHrRDHNBj3BvaRVwZFZg3EJ8LLjyEDYxWWUMOjrDw==}
peerDependencies:
@@ -23807,6 +23830,7 @@ packages:
dependencies:
glob-to-regexp: 0.4.1
graceful-fs: 4.2.10
dev: true
/wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
@@ -23892,7 +23916,6 @@ packages:
/webpack-virtual-modules@0.5.0:
resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
dev: true
/webpack@5.74.0:
resolution: {integrity: sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==}
@@ -23934,7 +23957,7 @@ packages:
- uglify-js
dev: true
/webpack@5.88.2(esbuild@0.19.2):
/webpack@5.88.2:
resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==}
engines: {node: '>=10.13.0'}
hasBin: true
@@ -23965,13 +23988,14 @@ packages:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
terser-webpack-plugin: 5.3.9(esbuild@0.19.2)(webpack@5.88.2)
terser-webpack-plugin: 5.3.9(webpack@5.88.2)
watchpack: 2.4.0
webpack-sources: 3.2.3
transitivePeerDependencies:
- '@swc/core'
- esbuild
- uglify-js
dev: true
/whatwg-encoding@1.0.5:
resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==}
@@ -24438,6 +24462,14 @@ packages:
string-width: 4.2.3
strip-ansi: 6.0.1
/wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
dependencies:
ansi-styles: 6.1.0
string-width: 5.1.2
strip-ansi: 7.1.0
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}

69
prod.Dockerfile Normal file
View File

@@ -0,0 +1,69 @@
FROM node:18-alpine3.16 as base_builder
WORKDIR /usr/src/app
ENV HOPP_ALLOW_RUNTIME_ENV=true
RUN npm install -g pnpm
COPY pnpm-lock.yaml .
RUN pnpm fetch
COPY . .
RUN pnpm install -f --offline
FROM base_builder as backend
WORKDIR /usr/src/app/packages/hoppscotch-backend
RUN pnpm exec prisma generate
RUN pnpm run build
# Remove the env file to avoid backend copying it in and using it
RUN rm "../../.env"
ENV PRODUCTION="true"
ENV PORT=3170
ENV APP_PORT=${PORT}
ENV DB_URL=${DATABASE_URL}
CMD ["pnpm", "run", "start:prod"]
EXPOSE 3170
FROM base_builder as fe_builder
WORKDIR /usr/src/app/packages/hoppscotch-selfhost-web
RUN pnpm run generate
FROM caddy:2-alpine as app
WORKDIR /site
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-sh-admin/prod_run.mjs /usr
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/Caddyfile /etc/caddy/Caddyfile
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/dist/ .
RUN apk add nodejs npm
RUN npm install -g @import-meta-env/cli
EXPOSE 8080
CMD ["/bin/sh", "-c", "node /usr/prod_run.mjs && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile"]
FROM base_builder as sh_admin_builder
WORKDIR /usr/src/app/packages/hoppscotch-sh-admin
RUN pnpm run build
FROM caddy:2-alpine as sh_admin
WORKDIR /site
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/prod_run.mjs /usr
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/Caddyfile /etc/caddy/Caddyfile
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist/ .
RUN apk add nodejs npm
RUN npm install -g @import-meta-env/cli
EXPOSE 8080
CMD ["/bin/sh", "-c", "node /usr/prod_run.mjs && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile"]
FROM backend as aio
RUN apk add caddy tini
RUN npm install -g @import-meta-env/cli
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/dist /site/selfhost-web
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist /site/sh-admin
COPY aio.Caddyfile /etc/caddy/Caddyfile
ENTRYPOINT [ "tini", "--" ]
RUN apk --no-cache add curl
COPY --chmod=755 healthcheck.sh .
HEALTHCHECK --interval=2s CMD /bin/sh ./healthcheck.sh
CMD ["node", "/usr/src/app/aio_run.mjs"]
EXPOSE 3170
EXPOSE 3000
EXPOSE 3100