From efa40cf6ea1e4d50b7d800faa588f7f581c08aa0 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Thu, 24 Aug 2023 00:01:28 +0530 Subject: [PATCH] feat: container registry friendly docker images and all-in-one container (#3193) Co-authored-by: Balu Babu --- .dockerignore | 3 +- aio.Caddyfile | 11 ++ aio_run.mjs | 72 +++++++ docker-compose.yml | 137 +++++++++---- healthcheck.sh | 14 ++ packages/hoppscotch-backend/package.json | 4 +- .../hoppscotch-backend/prisma/schema.prisma | 2 +- .../hoppscotch-backend/src/app.controller.ts | 9 + packages/hoppscotch-backend/src/app.module.ts | 2 + .../user-history/user-history.service.spec.ts | 6 +- .../src/helpers/strategies/AxiosStrategy.ts | 163 ++++++++++++++++ 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 ++ .../hoppscotch-selfhost-web/vite.config.ts | 13 +- 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 +++ packages/hoppscotch-sh-admin/vite.config.ts | 12 +- pnpm-lock.yaml | 182 ++++++++++++++++-- prod.Dockerfile | 67 +++++++ 23 files changed, 707 insertions(+), 69 deletions(-) create mode 100644 aio.Caddyfile create mode 100644 aio_run.mjs create mode 100644 healthcheck.sh create mode 100644 packages/hoppscotch-backend/src/app.controller.ts 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 create mode 100644 prod.Dockerfile diff --git a/.dockerignore b/.dockerignore index c2592520..93efa139 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ -*/**/node_modules +node_modules +**/*/node_modules diff --git a/aio.Caddyfile b/aio.Caddyfile new file mode 100644 index 00000000..65919f6e --- /dev/null +++ b/aio.Caddyfile @@ -0,0 +1,11 @@ +:3000 { + try_files {path} / + root * /site/selfhost-web + file_server +} + +:3100 { + try_files {path} / + root * /site/sh-admin + file_server +} diff --git a/aio_run.mjs b/aio_run.mjs new file mode 100644 index 00000000..61ac48c9 --- /dev/null +++ b/aio_run.mjs @@ -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) +}) diff --git a/docker-compose.yml b/docker-compose.yml index 8c948fcd..c9d5d668 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 - - diff --git a/healthcheck.sh b/healthcheck.sh new file mode 100644 index 00000000..87404aeb --- /dev/null +++ b/healthcheck.sh @@ -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" diff --git a/packages/hoppscotch-backend/package.json b/packages/hoppscotch-backend/package.json index e0ad0802..938e5e80 100644 --- a/packages/hoppscotch-backend/package.json +++ b/packages/hoppscotch-backend/package.json @@ -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" diff --git a/packages/hoppscotch-backend/prisma/schema.prisma b/packages/hoppscotch-backend/prisma/schema.prisma index c0946356..67feb1ff 100644 --- a/packages/hoppscotch-backend/prisma/schema.prisma +++ b/packages/hoppscotch-backend/prisma/schema.prisma @@ -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 { diff --git a/packages/hoppscotch-backend/src/app.controller.ts b/packages/hoppscotch-backend/src/app.controller.ts new file mode 100644 index 00000000..8ce570ea --- /dev/null +++ b/packages/hoppscotch-backend/src/app.controller.ts @@ -0,0 +1,9 @@ +import { Controller, Get } from '@nestjs/common'; + +@Controller('ping') +export class AppController { + @Get() + ping(): string { + return 'Success'; + } +} diff --git a/packages/hoppscotch-backend/src/app.module.ts b/packages/hoppscotch-backend/src/app.module.ts index 63a5bd57..167a8c92 100644 --- a/packages/hoppscotch-backend/src/app.module.ts +++ b/packages/hoppscotch-backend/src/app.module.ts @@ -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 {} diff --git a/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts b/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts index 3fdc261a..69314717 100644 --- a/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts +++ b/packages/hoppscotch-backend/src/user-history/user-history.service.spec.ts @@ -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, }; 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 00000000..137bc659 --- /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.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 + ? { + "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-selfhost-web/index.html b/packages/hoppscotch-selfhost-web/index.html index 5cff33f3..6d4ee078 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 f7c58dd3..e0a7e2ff 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 dd988c05..d38ba4e1 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 00000000..57a68a77 --- /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("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") diff --git a/packages/hoppscotch-selfhost-web/vite.config.ts b/packages/hoppscotch-selfhost-web/vite.config.ts index fa7fc967..e26605e0 100644 --- a/packages/hoppscotch-selfhost-web/vite.config.ts +++ b/packages/hoppscotch-selfhost-web/vite.config.ts @@ -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: { @@ -78,14 +80,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 +242,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 65e692c2..7def0497 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 359f173d..b742382b 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 00000000..9e386cb9 --- /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("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") diff --git a/packages/hoppscotch-sh-admin/src/components.d.ts b/packages/hoppscotch-sh-admin/src/components.d.ts index 0ad19898..568d8e23 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/vite.config.ts b/packages/hoppscotch-sh-admin/vite.config.ts index b80a2127..9bea4bf3 100644 --- a/packages/hoppscotch-sh-admin/vite.config.ts +++ b/packages/hoppscotch-sh-admin/vite.config.ts @@ -10,10 +10,14 @@ 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_", server: { port: 3100, }, @@ -85,7 +89,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 cf61b037..d36576bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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) @@ -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) @@ -5963,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'} @@ -6154,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'} @@ -7214,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} @@ -7234,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: @@ -7244,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 @@ -11435,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==} @@ -12295,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==} @@ -14001,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'} @@ -14324,6 +14407,17 @@ packages: 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==} dependencies: @@ -15818,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'} @@ -17500,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: @@ -17842,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==} @@ -17868,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 @@ -19033,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==} @@ -19336,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: @@ -20372,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 @@ -22290,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: @@ -22684,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'} @@ -23779,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==} @@ -24326,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==} diff --git a/prod.Dockerfile b/prod.Dockerfile new file mode 100644 index 00000000..88401ae6 --- /dev/null +++ b/prod.Dockerfile @@ -0,0 +1,67 @@ +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 +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