From 1b1a09c675a2f7d8e4028a43b455d76d8d15b7ab Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Fri, 14 Jul 2023 16:43:38 +0530 Subject: [PATCH] feat: updated frontend docker containers to allow for runtime environment variable definitions --- .env.example | 16 +- .../src/admin/admin.service.ts | 2 +- .../src/auth/auth.service.ts | 14 +- packages/hoppscotch-common/meta.ts | 8 +- .../src/components/http/Request.vue | 2 +- .../src/components/profile/Shortcode.vue | 2 +- .../src/helpers/backend/GQLClient.ts | 4 +- .../src/helpers/strategies/AxiosStrategy.ts | 163 ++++++++++++++++++ .../hoppscotch-common/src/modules/sentry.ts | 12 +- packages/hoppscotch-selfhost-web/index.html | 3 + packages/hoppscotch-selfhost-web/meta.ts | 8 +- packages/hoppscotch-selfhost-web/package.json | 2 + packages/hoppscotch-selfhost-web/prod_run.mjs | 17 ++ .../src/platform/auth.ts | 18 +- .../hoppscotch-selfhost-web/vite.config.ts | 12 +- packages/hoppscotch-sh-admin/index.html | 5 +- packages/hoppscotch-sh-admin/package.json | 3 + packages/hoppscotch-sh-admin/prod_run.mjs | 18 ++ .../hoppscotch-sh-admin/src/components.d.ts | 23 +++ .../hoppscotch-sh-admin/src/helpers/auth.ts | 26 +-- packages/hoppscotch-sh-admin/src/main.ts | 2 +- packages/hoppscotch-sh-admin/vite.config.ts | 8 +- pnpm-lock.yaml | 152 +++++++++++++++- prod.Dockerfile | 16 +- 24 files changed, 466 insertions(+), 70 deletions(-) create mode 100644 packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts create mode 100755 packages/hoppscotch-selfhost-web/prod_run.mjs create mode 100755 packages/hoppscotch-sh-admin/prod_run.mjs diff --git a/.env.example b/.env.example index 56a4e0016..df60d41e1 100644 --- a/.env.example +++ b/.env.example @@ -47,15 +47,15 @@ RATE_LIMIT_MAX=100 # Max requests per IP # Base URLs -VITE_BASE_URL=http://localhost:3000 -VITE_SHORTCODE_BASE_URL=http://localhost:3000 -VITE_ADMIN_URL=http://localhost:3100 +APP_BASE_URL=http://localhost:3000 +APP_SHORTCODE_BASE_URL=http://localhost:3000 +APP_ADMIN_URL=http://localhost:3100 # Backend URLs -VITE_BACKEND_GQL_URL=http://localhost:3170/graphql -VITE_BACKEND_WS_URL=ws://localhost:3170/graphql -VITE_BACKEND_API_URL=http://localhost:3170/v1 +APP_BACKEND_GQL_URL=http://localhost:3170/graphql +APP_BACKEND_WS_URL=ws://localhost:3170/graphql +APP_BACKEND_API_URL=http://localhost:3170/v1 # Terms Of Service And Privacy Policy Links (Optional) -VITE_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms -VITE_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy +APP_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms +APP_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy diff --git a/packages/hoppscotch-backend/src/admin/admin.service.ts b/packages/hoppscotch-backend/src/admin/admin.service.ts index 1ac4102ae..9de865243 100644 --- a/packages/hoppscotch-backend/src/admin/admin.service.ts +++ b/packages/hoppscotch-backend/src/admin/admin.service.ts @@ -77,7 +77,7 @@ export class AdminService { template: 'code-your-own', variables: { inviteeEmail: inviteeEmail, - magicLink: `${process.env.VITE_BASE_URL}`, + magicLink: `${process.env.APP_BASE_URL}`, }, }); } catch (e) { diff --git a/packages/hoppscotch-backend/src/auth/auth.service.ts b/packages/hoppscotch-backend/src/auth/auth.service.ts index 63e59ac2a..9482962d7 100644 --- a/packages/hoppscotch-backend/src/auth/auth.service.ts +++ b/packages/hoppscotch-backend/src/auth/auth.service.ts @@ -95,9 +95,9 @@ export class AuthService { */ private async generateRefreshToken(userUid: string) { const refreshTokenPayload: RefreshTokenPayload = { - iss: process.env.VITE_BASE_URL, + iss: process.env.APP_BASE_URL, sub: userUid, - aud: [process.env.VITE_BASE_URL], + aud: [process.env.APP_BASE_URL], }; const refreshToken = await this.jwtService.sign(refreshTokenPayload, { @@ -127,9 +127,9 @@ export class AuthService { */ async generateAuthTokens(userUid: string) { const accessTokenPayload: AccessTokenPayload = { - iss: process.env.VITE_BASE_URL, + iss: process.env.APP_BASE_URL, sub: userUid, - aud: [process.env.VITE_BASE_URL], + aud: [process.env.APP_BASE_URL], }; const refreshToken = await this.generateRefreshToken(userUid); @@ -218,14 +218,14 @@ export class AuthService { let url: string; switch (origin) { case Origin.ADMIN: - url = process.env.VITE_ADMIN_URL; + url = process.env.APP_ADMIN_URL; break; case Origin.APP: - url = process.env.VITE_BASE_URL; + url = process.env.APP_BASE_URL; break; default: // if origin is invalid by default set URL to Hoppscotch-App - url = process.env.VITE_BASE_URL; + url = process.env.APP_BASE_URL; } await this.mailerService.sendEmail(email, { diff --git a/packages/hoppscotch-common/meta.ts b/packages/hoppscotch-common/meta.ts index f7c58dd36..e0a7e2fff 100644 --- a/packages/hoppscotch-common/meta.ts +++ b/packages/hoppscotch-common/meta.ts @@ -36,7 +36,7 @@ export const META_TAGS = (env: Record): 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): 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): 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): 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", diff --git a/packages/hoppscotch-common/src/components/http/Request.vue b/packages/hoppscotch-common/src/components/http/Request.vue index 39f99e373..762dbea7b 100644 --- a/packages/hoppscotch-common/src/components/http/Request.vue +++ b/packages/hoppscotch-common/src/components/http/Request.vue @@ -485,7 +485,7 @@ const copyRequest = async () => { const copyShareLink = (shareLink: string) => { const link = `${ - import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh" + import.meta.env.APP_SHORTCODE_BASE_URL ?? "https://hopp.sh" }/r${shareLink}` if (navigator.share) { const time = new Date().toLocaleTimeString() diff --git a/packages/hoppscotch-common/src/components/profile/Shortcode.vue b/packages/hoppscotch-common/src/components/profile/Shortcode.vue index 64b507a4f..82a092a7f 100644 --- a/packages/hoppscotch-common/src/components/profile/Shortcode.vue +++ b/packages/hoppscotch-common/src/components/profile/Shortcode.vue @@ -106,7 +106,7 @@ const requestLabelColor = computed(() => const dateStamp = computed(() => shortDateTime(props.shortcode.createdOn)) const shortcodeBaseURL = - import.meta.env.VITE_SHORTCODE_BASE_URL ?? "https://hopp.sh" + import.meta.env.APP_SHORTCODE_BASE_URL ?? "https://hopp.sh" const copyShortcode = (codeID: string) => { copyToClipboard(`${shortcodeBaseURL}/r/${codeID}`) diff --git a/packages/hoppscotch-common/src/helpers/backend/GQLClient.ts b/packages/hoppscotch-common/src/helpers/backend/GQLClient.ts index 15a1c420a..dc5948411 100644 --- a/packages/hoppscotch-common/src/helpers/backend/GQLClient.ts +++ b/packages/hoppscotch-common/src/helpers/backend/GQLClient.ts @@ -28,9 +28,9 @@ import { platform } from "~/platform" // TODO: Implement caching const BACKEND_GQL_URL = - import.meta.env.VITE_BACKEND_GQL_URL ?? "https://api.hoppscotch.io/graphql" + import.meta.env.APP_BACKEND_GQL_URL ?? "https://api.hoppscotch.io/graphql" const BACKEND_WS_URL = - import.meta.env.VITE_BACKEND_WS_URL ?? "wss://api.hoppscotch.io/graphql" + import.meta.env.APP_BACKEND_WS_URL ?? "wss://api.hoppscotch.io/graphql" type GQLOpType = "query" | "mutation" | "subscription" /** diff --git a/packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts b/packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts new file mode 100644 index 000000000..8c8bce728 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/strategies/AxiosStrategy.ts @@ -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.APP_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 + ? { + "multipart-part-key": multipartKey, + } + : {} + ) + ), + + // 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 diff --git a/packages/hoppscotch-common/src/modules/sentry.ts b/packages/hoppscotch-common/src/modules/sentry.ts index bf8a096de..cee8a39c9 100644 --- a/packages/hoppscotch-common/src/modules/sentry.ts +++ b/packages/hoppscotch-common/src/modules/sentry.ts @@ -52,17 +52,17 @@ function initSentry(dsn: string, router: Router, app: App) { Sentry.init({ app, dsn, - release: import.meta.env.VITE_SENTRY_RELEASE_TAG ?? undefined, + release: import.meta.env.APP_SENTRY_RELEASE_TAG ?? undefined, environment: APP_IS_IN_DEV_MODE ? "dev" - : import.meta.env.VITE_SENTRY_ENVIRONMENT, + : import.meta.env.APP_SENTRY_ENVIRONMENT, integrations: [ new BrowserTracing({ routingInstrumentation: Sentry.vueRouterInstrumentation( getInstrumentationVueRouter(router) ), // TODO: We may want to limit this later on - tracingOrigins: [new URL(import.meta.env.VITE_BACKEND_GQL_URL).origin], + tracingOrigins: [new URL(import.meta.env.APP_BACKEND_GQL_URL).origin], }), ], tracesSampleRate: 0.8, @@ -175,7 +175,7 @@ function subscribeForAppDataTags() { export default { onRouterInit(app, router) { - if (!import.meta.env.VITE_SENTRY_DSN) { + if (!import.meta.env.APP_SENTRY_DSN) { console.log( "Sentry tracing is not enabled because 'VITE_SENTRY_DSN' env is not defined" ) @@ -183,14 +183,14 @@ export default { } if (settingsStore.value.TELEMETRY_ENABLED) { - initSentry(import.meta.env.VITE_SENTRY_DSN, router, app) + initSentry(import.meta.env.APP_SENTRY_DSN, router, app) } settingsStore.subject$.subscribe(({ TELEMETRY_ENABLED }) => { if (!TELEMETRY_ENABLED && sentryActive) { deinitSentry() } else if (TELEMETRY_ENABLED && !sentryActive) { - initSentry(import.meta.env.VITE_SENTRY_DSN!, router, app) + initSentry(import.meta.env.APP_SENTRY_DSN!, router, app) } }) diff --git a/packages/hoppscotch-selfhost-web/index.html b/packages/hoppscotch-selfhost-web/index.html index 5cff33f3b..6d4ee078d 100644 --- a/packages/hoppscotch-selfhost-web/index.html +++ b/packages/hoppscotch-selfhost-web/index.html @@ -1,6 +1,9 @@ + Hoppscotch • Open source API development ecosystem diff --git a/packages/hoppscotch-selfhost-web/meta.ts b/packages/hoppscotch-selfhost-web/meta.ts index f7c58dd36..e0a7e2fff 100644 --- a/packages/hoppscotch-selfhost-web/meta.ts +++ b/packages/hoppscotch-selfhost-web/meta.ts @@ -36,7 +36,7 @@ export const META_TAGS = (env: Record): 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): 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): 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): 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", diff --git a/packages/hoppscotch-selfhost-web/package.json b/packages/hoppscotch-selfhost-web/package.json index dd988c05d..d38ba4e1c 100644 --- a/packages/hoppscotch-selfhost-web/package.json +++ b/packages/hoppscotch-selfhost-web/package.json @@ -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" } } diff --git a/packages/hoppscotch-selfhost-web/prod_run.mjs b/packages/hoppscotch-selfhost-web/prod_run.mjs new file mode 100755 index 000000000..f9a4713b7 --- /dev/null +++ b/packages/hoppscotch-selfhost-web/prod_run.mjs @@ -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("APP_")) + .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") diff --git a/packages/hoppscotch-selfhost-web/src/platform/auth.ts b/packages/hoppscotch-selfhost-web/src/platform/auth.ts index e95925cb0..11324267d 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/auth.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/auth.ts @@ -17,23 +17,21 @@ const currentUser$ = new BehaviorSubject(null) export const probableUser$ = new BehaviorSubject(null) async function logout() { - await axios.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/logout`, { + await axios.get(`${import.meta.env.APP_BACKEND_API_URL}/auth/logout`, { withCredentials: true, }) } async function signInUserWithGithubFB() { - window.location.href = `${import.meta.env.VITE_BACKEND_API_URL}/auth/github` + window.location.href = `${import.meta.env.APP_BACKEND_API_URL}/auth/github` } async function signInUserWithGoogleFB() { - window.location.href = `${import.meta.env.VITE_BACKEND_API_URL}/auth/google` + window.location.href = `${import.meta.env.APP_BACKEND_API_URL}/auth/google` } async function signInUserWithMicrosoftFB() { - window.location.href = `${ - import.meta.env.VITE_BACKEND_API_URL - }/auth/microsoft` + window.location.href = `${import.meta.env.APP_BACKEND_API_URL}/auth/microsoft` } async function getInitialUserDetails() { @@ -53,7 +51,7 @@ async function getInitialUserDetails() { message: string }> }>( - `${import.meta.env.VITE_BACKEND_GQL_URL}`, + `${import.meta.env.APP_BACKEND_GQL_URL}`, { query: `query Me { me { @@ -147,7 +145,7 @@ async function setInitialUser() { async function refreshToken() { const res = await axios.get( - `${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`, + `${import.meta.env.APP_BACKEND_API_URL}/auth/refresh`, { withCredentials: true, } @@ -166,7 +164,7 @@ async function refreshToken() { async function sendMagicLink(email: string) { const res = await axios.post( - `${import.meta.env.VITE_BACKEND_API_URL}/auth/signin`, + `${import.meta.env.APP_BACKEND_API_URL}/auth/signin`, { email, }, @@ -284,7 +282,7 @@ export const def: AuthPlatformDef = { const deviceIdentifier = getLocalConfig("deviceIdentifier") await axios.post( - `${import.meta.env.VITE_BACKEND_API_URL}/auth/verify`, + `${import.meta.env.APP_BACKEND_API_URL}/auth/verify`, { token: token, deviceIdentifier, diff --git a/packages/hoppscotch-selfhost-web/vite.config.ts b/packages/hoppscotch-selfhost-web/vite.config.ts index fa7fc9679..1b9b060e4 100644 --- a/packages/hoppscotch-selfhost-web/vite.config.ts +++ b/packages/hoppscotch-selfhost-web/vite.config.ts @@ -17,8 +17,9 @@ 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, "../../"), ["APP_"]) export default defineConfig({ envDir: path.resolve(__dirname, "../../"), @@ -78,14 +79,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 +241,9 @@ export default defineConfig({ modernPolyfills: ["es.string.replace-all"], renderLegacyChunks: false, }), + ImportMetaEnv.vite({ + example: "../../.env.example", + env: "../../.env", + }), ], }) diff --git a/packages/hoppscotch-sh-admin/index.html b/packages/hoppscotch-sh-admin/index.html index 65e692c2f..7def0497b 100644 --- a/packages/hoppscotch-sh-admin/index.html +++ b/packages/hoppscotch-sh-admin/index.html @@ -2,6 +2,9 @@ + @@ -14,4 +17,4 @@ - \ No newline at end of file + diff --git a/packages/hoppscotch-sh-admin/package.json b/packages/hoppscotch-sh-admin/package.json index 359f173dd..b742382ba 100644 --- a/packages/hoppscotch-sh-admin/package.json +++ b/packages/hoppscotch-sh-admin/package.json @@ -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", diff --git a/packages/hoppscotch-sh-admin/prod_run.mjs b/packages/hoppscotch-sh-admin/prod_run.mjs new file mode 100755 index 000000000..da7297098 --- /dev/null +++ b/packages/hoppscotch-sh-admin/prod_run.mjs @@ -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("APP_")) + .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") diff --git a/packages/hoppscotch-sh-admin/src/components.d.ts b/packages/hoppscotch-sh-admin/src/components.d.ts index 0ad19898b..568d8e230 100644 --- a/packages/hoppscotch-sh-admin/src/components.d.ts +++ b/packages/hoppscotch-sh-admin/src/components.d.ts @@ -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'] } } diff --git a/packages/hoppscotch-sh-admin/src/helpers/auth.ts b/packages/hoppscotch-sh-admin/src/helpers/auth.ts index ba7385dbe..8aba7f3c0 100644 --- a/packages/hoppscotch-sh-admin/src/helpers/auth.ts +++ b/packages/hoppscotch-sh-admin/src/helpers/auth.ts @@ -55,27 +55,27 @@ const currentUser$ = new BehaviorSubject(null); export const probableUser$ = new BehaviorSubject(null); async function logout() { - await axios.get(`${import.meta.env.VITE_BACKEND_API_URL}/auth/logout`, { + await axios.get(`${import.meta.env.APP_BACKEND_API_URL}/auth/logout`, { withCredentials: true, }); } async function signInUserWithGithubFB() { window.location.href = `${ - import.meta.env.VITE_BACKEND_API_URL - }/auth/github?redirect_uri=${import.meta.env.VITE_ADMIN_URL}`; + import.meta.env.APP_BACKEND_API_URL + }/auth/github?redirect_uri=${import.meta.env.APP_ADMIN_URL}`; } async function signInUserWithGoogleFB() { window.location.href = `${ - import.meta.env.VITE_BACKEND_API_URL - }/auth/google?redirect_uri=${import.meta.env.VITE_ADMIN_URL}`; + import.meta.env.APP_BACKEND_API_URL + }/auth/google?redirect_uri=${import.meta.env.APP_ADMIN_URL}`; } async function signInUserWithMicrosoftFB() { window.location.href = `${ - import.meta.env.VITE_BACKEND_API_URL - }/auth/microsoft?redirect_uri=${import.meta.env.VITE_ADMIN_URL}`; + import.meta.env.APP_BACKEND_API_URL + }/auth/microsoft?redirect_uri=${import.meta.env.APP_ADMIN_URL}`; } async function getInitialUserDetails() { @@ -95,7 +95,7 @@ async function getInitialUserDetails() { message: string; }>; }>( - `${import.meta.env.VITE_BACKEND_GQL_URL}`, + `${import.meta.env.APP_BACKEND_GQL_URL}`, { query: `query Me { me { @@ -189,7 +189,7 @@ async function setInitialUser() { async function refreshToken() { const res = await axios.get( - `${import.meta.env.VITE_BACKEND_API_URL}/auth/refresh`, + `${import.meta.env.APP_BACKEND_API_URL}/auth/refresh`, { withCredentials: true, } @@ -208,7 +208,7 @@ async function refreshToken() { async function elevateUser() { const res = await axios.get( - `${import.meta.env.VITE_BACKEND_API_URL}/auth/verify/admin`, + `${import.meta.env.APP_BACKEND_API_URL}/auth/verify/admin`, { withCredentials: true, } @@ -219,7 +219,7 @@ async function elevateUser() { async function sendMagicLink(email: string) { const res = await axios.post( - `${import.meta.env.VITE_BACKEND_API_URL}/auth/signin?origin=admin`, + `${import.meta.env.APP_BACKEND_API_URL}/auth/signin?origin=admin`, { email, }, @@ -337,7 +337,7 @@ export const auth = { const deviceIdentifier = getLocalConfig('deviceIdentifier'); await axios.post( - `${import.meta.env.VITE_BACKEND_API_URL}/auth/verify`, + `${import.meta.env.APP_BACKEND_API_URL}/auth/verify`, { token: token, deviceIdentifier, @@ -383,7 +383,7 @@ export const auth = { await this.signInWithEmailLink(deviceIdentifier, window.location.href); removeLocalConfig('deviceIdentifier'); - window.location.href = import.meta.env.VITE_ADMIN_URL; + window.location.href = import.meta.env.APP_ADMIN_URL; } }, }; diff --git a/packages/hoppscotch-sh-admin/src/main.ts b/packages/hoppscotch-sh-admin/src/main.ts index 4fdccd817..85257a6e8 100644 --- a/packages/hoppscotch-sh-admin/src/main.ts +++ b/packages/hoppscotch-sh-admin/src/main.ts @@ -20,7 +20,7 @@ import { auth } from './helpers/auth'; const app = createApp(App).use( urql, createClient({ - url: import.meta.env.VITE_BACKEND_GQL_URL, + url: import.meta.env.APP_BACKEND_GQL_URL, requestPolicy: 'network-only', fetchOptions: () => { return { diff --git a/packages/hoppscotch-sh-admin/vite.config.ts b/packages/hoppscotch-sh-admin/vite.config.ts index b80a21275..e09e3d50e 100644 --- a/packages/hoppscotch-sh-admin/vite.config.ts +++ b/packages/hoppscotch-sh-admin/vite.config.ts @@ -10,10 +10,10 @@ 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, '../../'), server: { port: 3100, }, @@ -85,7 +85,11 @@ export default defineConfig({ variables: ["variable-full"], }, ], - }, + } + }), + ImportMetaEnv.vite({ + example: "../../.env.example", + env: "../../.env", }), ], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c208738f0..8baf63ed0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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) @@ -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) @@ -6017,6 +6032,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 +6264,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 +7335,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} @@ -11479,6 +11552,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 +12416,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==} @@ -14047,6 +14123,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'} @@ -14373,6 +14456,17 @@ packages: /glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + /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==} dependencies: @@ -15871,6 +15965,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'} @@ -17560,6 +17662,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 +18008,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 +18033,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 +19202,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==} @@ -20439,6 +20555,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 @@ -22360,6 +22480,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: @@ -22772,10 +22900,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'} @@ -23892,7 +24029,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==} @@ -24438,6 +24574,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==} diff --git a/prod.Dockerfile b/prod.Dockerfile index b33dd20fe..93ce72bc2 100644 --- a/prod.Dockerfile +++ b/prod.Dockerfile @@ -2,9 +2,12 @@ FROM node:18 as base_builder WORKDIR /usr/src/app -COPY . . RUN npm install -g pnpm -RUN pnpm install --force +COPY pnpm-lock.yaml . +RUN pnpm fetch + +COPY . . +RUN pnpm install --force --offline FROM base_builder as backend WORKDIR /usr/src/app/packages/hoppscotch-backend @@ -19,9 +22,13 @@ RUN pnpm run generate FROM caddy:2-alpine as frontend 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 @@ -29,6 +36,11 @@ 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"] +