From 0a9797de4c08cf1a09e3365902b2502588a411eb Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 15 Nov 2024 15:24:10 -0800 Subject: [PATCH 1/5] wip on passing env-var to basePath --- Dockerfile | 5 +++++ entrypoint.sh | 19 +++++++++++++++++++ packages/web/next.config.mjs | 7 ++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 47067d3e7..0e07dd0fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,8 @@ ENV NEXT_TELEMETRY_DISABLED=1 # @see: https://phase.dev/blog/nextjs-public-runtime-variables/ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION +# @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath +ARG NEXT_PUBLIC_BASE_PATH=/BAKED_NEXT_PUBLIC_BASE_PATH RUN yarn workspace @sourcebot/web build # ------ Build Backend ------ @@ -54,6 +56,9 @@ RUN echo "Sourcebot Version: $SOURCEBOT_VERSION" # Valid values are: debug, info, warn, error ENV SOURCEBOT_LOG_LEVEL=info +# The base path of the application +ENV BASE_PATH=/ + # @note: This is also set in .env ENV POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS ENV NEXT_PUBLIC_POSTHOG_KEY=$POSTHOG_KEY diff --git a/entrypoint.sh b/entrypoint.sh index c2a0b0aa2..45b94e27e 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -89,4 +89,23 @@ while read file; do sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file" done +# @todo: document this +if [ ! -z "$BASE_PATH" ]; then + if [ "$BASE_PATH" = "/" ]; then + BASE_PATH="" + elif [[ ! "$BASE_PATH" =~ ^/ ]]; then + BASE_PATH="/$BASE_PATH" + fi + + echo -e "\e[34m[Info] BASE_PATH was set to "$BASE_PATH". Overriding default base path.\e[0m" +fi +# Always set NEXT_PUBLIC_BASE_PATH to BASE_PATH +export NEXT_PUBLIC_BASE_PATH="$BASE_PATH" + +find /app/packages/web/public /app/packages/web -type f | +while read file; do + sed -i "s|/BAKED_NEXT_PUBLIC_BASE_PATH|${NEXT_PUBLIC_BASE_PATH}|g" "$file" +done + +# Run supervisord exec supervisord -c /etc/supervisor/conf.d/supervisord.conf \ No newline at end of file diff --git a/packages/web/next.config.mjs b/packages/web/next.config.mjs index a44d1189c..ef8bcd9e1 100644 --- a/packages/web/next.config.mjs +++ b/packages/web/next.config.mjs @@ -20,7 +20,12 @@ const nextConfig = { ]; }, // This is required to support PostHog trailing slash API requests - skipTrailingSlashRedirect: true, + skipTrailingSlashRedirect: true, + + // @note: this is evaluated at build time. + ...(process.env.NEXT_PUBLIC_BASE_PATH ? { + basePath: process.env.NEXT_PUBLIC_BASE_PATH, + } : {}) }; export default nextConfig; From f3d8681a0a487fbbc6f2b5413137d8116e748b25 Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 15 Nov 2024 21:48:46 -0800 Subject: [PATCH 2/5] Add base path prefixing to client api --- entrypoint.sh | 3 +++ packages/web/src/app/api/(client)/client.ts | 9 ++++++--- packages/web/src/lib/environment.client.ts | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 45b94e27e..4affd86a8 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -96,9 +96,12 @@ if [ ! -z "$BASE_PATH" ]; then elif [[ ! "$BASE_PATH" =~ ^/ ]]; then BASE_PATH="/$BASE_PATH" fi +fi +if [ ! -z "$BASE_PATH" ]; then echo -e "\e[34m[Info] BASE_PATH was set to "$BASE_PATH". Overriding default base path.\e[0m" fi + # Always set NEXT_PUBLIC_BASE_PATH to BASE_PATH export NEXT_PUBLIC_BASE_PATH="$BASE_PATH" diff --git a/packages/web/src/app/api/(client)/client.ts b/packages/web/src/app/api/(client)/client.ts index a2345f725..f84ad7f69 100644 --- a/packages/web/src/app/api/(client)/client.ts +++ b/packages/web/src/app/api/(client)/client.ts @@ -1,8 +1,11 @@ +'use client'; + +import { NEXT_PUBLIC_BASE_PATH } from "@/lib/environment.client"; import { fileSourceResponseSchema, listRepositoriesResponseSchema, searchResponseSchema } from "@/lib/schemas"; import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "@/lib/types"; export const search = async (body: SearchRequest): Promise => { - const result = await fetch(`/api/search`, { + const result = await fetch(`${NEXT_PUBLIC_BASE_PATH}/api/search`, { method: "POST", headers: { "Content-Type": "application/json", @@ -14,7 +17,7 @@ export const search = async (body: SearchRequest): Promise => { } export const fetchFileSource = async (body: FileSourceRequest): Promise => { - const result = await fetch(`/api/source`, { + const result = await fetch(`${NEXT_PUBLIC_BASE_PATH}/api/source`, { method: "POST", headers: { "Content-Type": "application/json", @@ -26,7 +29,7 @@ export const fetchFileSource = async (body: FileSourceRequest): Promise => { - const result = await fetch('/api/repos', { + const result = await fetch(`${NEXT_PUBLIC_BASE_PATH}/api/repos`, { method: "GET", headers: { "Content-Type": "application/json", diff --git a/packages/web/src/lib/environment.client.ts b/packages/web/src/lib/environment.client.ts index fcb22b4b9..1b957005d 100644 --- a/packages/web/src/lib/environment.client.ts +++ b/packages/web/src/lib/environment.client.ts @@ -8,3 +8,4 @@ export const NEXT_PUBLIC_POSTHOG_UI_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHO export const NEXT_PUBLIC_POSTHOG_ASSET_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST); export const NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED = getEnvBoolean(process.env.NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED, false); export const NEXT_PUBLIC_SOURCEBOT_VERSION = getEnv(process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, "unknown"); +export const NEXT_PUBLIC_BASE_PATH = getEnv(process.env.NEXT_PUBLIC_BASE_PATH); From 2e09becf00c567df714e9e27c53e814a98e77444 Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 15 Nov 2024 22:05:18 -0800 Subject: [PATCH 3/5] nit: also support posthog requests --- packages/web/src/app/api/(client)/client.ts | 18 +++++++++++++++--- packages/web/src/app/posthogProvider.tsx | 6 +++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/web/src/app/api/(client)/client.ts b/packages/web/src/app/api/(client)/client.ts index f84ad7f69..cb6aaaa48 100644 --- a/packages/web/src/app/api/(client)/client.ts +++ b/packages/web/src/app/api/(client)/client.ts @@ -5,7 +5,8 @@ import { fileSourceResponseSchema, listRepositoriesResponseSchema, searchRespons import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "@/lib/types"; export const search = async (body: SearchRequest): Promise => { - const result = await fetch(`${NEXT_PUBLIC_BASE_PATH}/api/search`, { + const path = resolveServerPath("/api/search"); + const result = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json", @@ -17,7 +18,8 @@ export const search = async (body: SearchRequest): Promise => { } export const fetchFileSource = async (body: FileSourceRequest): Promise => { - const result = await fetch(`${NEXT_PUBLIC_BASE_PATH}/api/source`, { + const path = resolveServerPath("/api/source"); + const result = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json", @@ -29,7 +31,8 @@ export const fetchFileSource = async (body: FileSourceRequest): Promise => { - const result = await fetch(`${NEXT_PUBLIC_BASE_PATH}/api/repos`, { + const path = resolveServerPath("/api/repos"); + const result = await fetch(path, { method: "GET", headers: { "Content-Type": "application/json", @@ -38,3 +41,12 @@ export const getRepos = async (): Promise => { return listRepositoriesResponseSchema.parse(result); } + +/** + * Given a subpath to a api route on the server (e.g., /api/search), + * returns the full path to that route on the server, taking into account + * the base path (if any). + */ +export const resolveServerPath = (path: string) => { + return `${NEXT_PUBLIC_BASE_PATH}${path}`; +} \ No newline at end of file diff --git a/packages/web/src/app/posthogProvider.tsx b/packages/web/src/app/posthogProvider.tsx index c45736abc..1cee74f13 100644 --- a/packages/web/src/app/posthogProvider.tsx +++ b/packages/web/src/app/posthogProvider.tsx @@ -2,11 +2,15 @@ import { NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_UI_HOST, NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED } from '@/lib/environment.client' import posthog from 'posthog-js' import { PostHogProvider } from 'posthog-js/react' +import { resolveServerPath } from './api/(client)/client' if (typeof window !== 'undefined') { if (!NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED) { + // @see next.config.mjs for path rewrites to the "/ingest" route. + const posthogHostPath = resolveServerPath('/ingest'); + posthog.init(NEXT_PUBLIC_POSTHOG_KEY!, { - api_host: "/ingest", + api_host: posthogHostPath, ui_host: NEXT_PUBLIC_POSTHOG_UI_HOST, person_profiles: 'identified_only', capture_pageview: false, // Disable automatic pageview capture From 9244df45f482daea1de61008fd6d035f9a62c77a Mon Sep 17 00:00:00 2001 From: bkellam Date: Fri, 15 Nov 2024 22:42:08 -0800 Subject: [PATCH 4/5] rename BASE_PATH to DOMAIN_SUB_PATH. Also add some comments --- Dockerfile | 8 +- entrypoint.sh | 86 +++++++++++++-------- packages/web/next.config.mjs | 4 +- packages/web/src/app/api/(client)/client.ts | 4 +- packages/web/src/lib/environment.client.ts | 2 +- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0e07dd0fd..79e3b452e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ ENV NEXT_TELEMETRY_DISABLED=1 ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION # @note: leading "/" is required for the basePath property. @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath -ARG NEXT_PUBLIC_BASE_PATH=/BAKED_NEXT_PUBLIC_BASE_PATH +ARG NEXT_PUBLIC_DOMAIN_SUB_PATH=/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH RUN yarn workspace @sourcebot/web build # ------ Build Backend ------ @@ -56,8 +56,10 @@ RUN echo "Sourcebot Version: $SOURCEBOT_VERSION" # Valid values are: debug, info, warn, error ENV SOURCEBOT_LOG_LEVEL=info -# The base path of the application -ENV BASE_PATH=/ +# Configures the sub-path of the domain to serve Sourcebot from. +# For example, if DOMAIN_SUB_PATH is set to "/sb", Sourcebot +# will serve from http(s)://example.com/sb +ENV DOMAIN_SUB_PATH=/ # @note: This is also set in .env ENV POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS diff --git a/entrypoint.sh b/entrypoint.sh index 4affd86a8..c9663ef1c 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -70,45 +70,69 @@ fi echo -e "\e[34m[Info] Using config file at: '$CONFIG_PATH'.\e[0m" -# Update nextjs public env variables w/o requiring a rebuild. +# Update NextJs public env variables w/o requiring a rebuild. # @see: https://phase.dev/blog/nextjs-public-runtime-variables/ +{ + # Infer NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED if it is not set + if [ -z "$NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED" ] && [ ! -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then + export NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED="$SOURCEBOT_TELEMETRY_DISABLED" + fi -# Infer NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED if it is not set -if [ -z "$NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED" ] && [ ! -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then - export NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED="$SOURCEBOT_TELEMETRY_DISABLED" -fi + # Infer NEXT_PUBLIC_SOURCEBOT_VERSION if it is not set + if [ -z "$NEXT_PUBLIC_SOURCEBOT_VERSION" ] && [ ! -z "$SOURCEBOT_VERSION" ]; then + export NEXT_PUBLIC_SOURCEBOT_VERSION="$SOURCEBOT_VERSION" + fi -# Infer NEXT_PUBLIC_SOURCEBOT_VERSION if it is not set -if [ -z "$NEXT_PUBLIC_SOURCEBOT_VERSION" ] && [ ! -z "$SOURCEBOT_VERSION" ]; then - export NEXT_PUBLIC_SOURCEBOT_VERSION="$SOURCEBOT_VERSION" -fi + # Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values + # with their actual desired runtime value. + find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" | + while read file; do + sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED|${NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED}|g" "$file" + sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file" + done +} + + +# Update specifically NEXT_PUBLIC_DOMAIN_SUB_PATH w/o requiring a rebuild. +# Ultimately, the DOMAIN_SUB_PATH sets the `basePath` param in the next.config.mjs. +# Similar to above, we pass in a `BAKED_` sentinal value into next.config.mjs at build +# time. Unlike above, the `basePath` configuration is set in files other than just javascript +# code (e.g., manifest files, css files, etc.), so this section has subtle differences. +# +# @see: https://nextjs.org/docs/app/api-reference/next-config-js/basePath +# @see: https://phase.dev/blog/nextjs-public-runtime-variables/ +{ + if [ ! -z "$DOMAIN_SUB_PATH" ]; then + # If the sub-path is "/", this creates problems with certain replacements. For example: + # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> //_next/image (notice the double slash...) + # To get around this, we default to an empty sub-path, which is the default when no sub-path is defined. + if [ "$DOMAIN_SUB_PATH" = "/" ]; then + DOMAIN_SUB_PATH="" + + # Otherwise, we need to ensure that the sub-path starts with a slash, since this is a requirement + # for the basePath property. For example, assume DOMAIN_SUB_PATH=/bot, then: + # /BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH/_next/image -> /bot/_next/image + elif [[ ! "$DOMAIN_SUB_PATH" =~ ^/ ]]; then + DOMAIN_SUB_PATH="/$DOMAIN_SUB_PATH" + fi + fi -find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" | -while read file; do - sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED|${NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED}|g" "$file" - sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file" -done - -# @todo: document this -if [ ! -z "$BASE_PATH" ]; then - if [ "$BASE_PATH" = "/" ]; then - BASE_PATH="" - elif [[ ! "$BASE_PATH" =~ ^/ ]]; then - BASE_PATH="/$BASE_PATH" + if [ ! -z "$DOMAIN_SUB_PATH" ]; then + echo -e "\e[34m[Info] DOMAIN_SUB_PATH was set to "$DOMAIN_SUB_PATH". Overriding default path.\e[0m" fi -fi -if [ ! -z "$BASE_PATH" ]; then - echo -e "\e[34m[Info] BASE_PATH was set to "$BASE_PATH". Overriding default base path.\e[0m" -fi + # Always set NEXT_PUBLIC_DOMAIN_SUB_PATH to DOMAIN_SUB_PATH (even if it is empty!!) + export NEXT_PUBLIC_DOMAIN_SUB_PATH="$DOMAIN_SUB_PATH" -# Always set NEXT_PUBLIC_BASE_PATH to BASE_PATH -export NEXT_PUBLIC_BASE_PATH="$BASE_PATH" + # Iterate over _all_ files in the web directory, making substitutions for the `BAKED_` sentinal values + # with their actual desired runtime value. + find /app/packages/web -type f | + while read file; do + # @note: the leading "/" is required here as it is included at build time. See Dockerfile. + sed -i "s|/BAKED_NEXT_PUBLIC_DOMAIN_SUB_PATH|${NEXT_PUBLIC_DOMAIN_SUB_PATH}|g" "$file" + done +} -find /app/packages/web/public /app/packages/web -type f | -while read file; do - sed -i "s|/BAKED_NEXT_PUBLIC_BASE_PATH|${NEXT_PUBLIC_BASE_PATH}|g" "$file" -done # Run supervisord exec supervisord -c /etc/supervisor/conf.d/supervisord.conf \ No newline at end of file diff --git a/packages/web/next.config.mjs b/packages/web/next.config.mjs index ef8bcd9e1..fe7eda84b 100644 --- a/packages/web/next.config.mjs +++ b/packages/web/next.config.mjs @@ -23,8 +23,8 @@ const nextConfig = { skipTrailingSlashRedirect: true, // @note: this is evaluated at build time. - ...(process.env.NEXT_PUBLIC_BASE_PATH ? { - basePath: process.env.NEXT_PUBLIC_BASE_PATH, + ...(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH ? { + basePath: process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH, } : {}) }; diff --git a/packages/web/src/app/api/(client)/client.ts b/packages/web/src/app/api/(client)/client.ts index cb6aaaa48..840bee565 100644 --- a/packages/web/src/app/api/(client)/client.ts +++ b/packages/web/src/app/api/(client)/client.ts @@ -1,6 +1,6 @@ 'use client'; -import { NEXT_PUBLIC_BASE_PATH } from "@/lib/environment.client"; +import { NEXT_PUBLIC_DOMAIN_SUB_PATH } from "@/lib/environment.client"; import { fileSourceResponseSchema, listRepositoriesResponseSchema, searchResponseSchema } from "@/lib/schemas"; import { FileSourceRequest, FileSourceResponse, ListRepositoriesResponse, SearchRequest, SearchResponse } from "@/lib/types"; @@ -48,5 +48,5 @@ export const getRepos = async (): Promise => { * the base path (if any). */ export const resolveServerPath = (path: string) => { - return `${NEXT_PUBLIC_BASE_PATH}${path}`; + return `${NEXT_PUBLIC_DOMAIN_SUB_PATH}${path}`; } \ No newline at end of file diff --git a/packages/web/src/lib/environment.client.ts b/packages/web/src/lib/environment.client.ts index 1b957005d..85251888e 100644 --- a/packages/web/src/lib/environment.client.ts +++ b/packages/web/src/lib/environment.client.ts @@ -8,4 +8,4 @@ export const NEXT_PUBLIC_POSTHOG_UI_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHO export const NEXT_PUBLIC_POSTHOG_ASSET_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST); export const NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED = getEnvBoolean(process.env.NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED, false); export const NEXT_PUBLIC_SOURCEBOT_VERSION = getEnv(process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, "unknown"); -export const NEXT_PUBLIC_BASE_PATH = getEnv(process.env.NEXT_PUBLIC_BASE_PATH); +export const NEXT_PUBLIC_DOMAIN_SUB_PATH = getEnv(process.env.NEXT_PUBLIC_DOMAIN_SUB_PATH, ""); From b72d8f514db1742093e839bbcbb8912bedd92db1 Mon Sep 17 00:00:00 2001 From: bkellam Date: Mon, 18 Nov 2024 11:59:10 -0800 Subject: [PATCH 5/5] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f65ce85..413f15912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added `DOMAIN_SUB_PATH` environment variable to allow overriding the default domain subpath. ([#74](https://github.com/sourcebot-dev/sourcebot/pull/74)) + ## [2.4.3] - 2024-11-18 ### Changed