From 1908051daa2d5238f971e1551bdd2a964272a94e Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Tue, 4 Nov 2025 21:22:31 -0800 Subject: [PATCH] feat(web,worker): Environment overrides (#597) --- .env.development | 2 +- CHANGELOG.md | 1 + Dockerfile | 16 -- Makefile | 4 - docs/docs/configuration/config-file.mdx | 103 +++++++++++ .../configuration/environment-variables.mdx | 4 +- docs/docs/configuration/idp.mdx | 7 +- docs/docs/connections/ado-cloud.mdx | 2 +- docs/docs/connections/ado-server.mdx | 2 +- docs/docs/connections/bitbucket-cloud.mdx | 2 +- .../connections/bitbucket-data-center.mdx | 2 +- docs/docs/connections/gitea.mdx | 2 +- docs/docs/connections/github.mdx | 2 +- docs/docs/connections/gitlab.mdx | 2 +- .../v3/environmentOverrides.schema.mdx | 115 ++++++++++++ docs/snippets/schemas/v3/index.schema.mdx | 172 +++++++++++++++--- entrypoint.sh | 62 +++++-- package.json | 6 +- packages/backend/package.json | 6 +- packages/backend/src/azuredevops.ts | 9 +- packages/backend/src/bitbucket.ts | 4 +- packages/backend/src/configManager.ts | 2 +- packages/backend/src/connectionManager.ts | 5 +- packages/backend/src/constants.ts | 2 +- .../backend/src/ee/accountPermissionSyncer.ts | 6 +- packages/backend/src/ee/githubAppManager.ts | 11 +- .../backend/src/ee/repoPermissionSyncer.ts | 5 +- packages/backend/src/ee/syncSearchContexts.ts | 2 +- packages/backend/src/env.ts | 64 ------- packages/backend/src/gerrit.ts | 63 ++----- packages/backend/src/git.ts | 8 +- packages/backend/src/gitea.ts | 12 +- packages/backend/src/github.ts | 7 +- packages/backend/src/gitlab.ts | 14 +- packages/backend/src/index.ts | 13 +- packages/backend/src/instrument.ts | 4 +- packages/backend/src/posthog.ts | 9 +- packages/backend/src/promClient.ts | 2 +- packages/backend/src/repoCompileUtils.ts | 2 +- packages/backend/src/repoIndexManager.ts | 5 +- packages/backend/src/utils.ts | 2 +- packages/backend/src/zoekt.ts | 2 +- packages/crypto/.gitignore | 1 - packages/crypto/package.json | 20 -- packages/crypto/src/environment.ts | 13 -- packages/crypto/src/tokenUtils.ts | 30 --- packages/crypto/tsconfig.json | 24 --- packages/db/package.json | 1 - packages/db/tools/scriptRunner.ts | 13 +- .../db/tools/scripts/inject-audit-data.ts | 19 +- packages/db/tools/scripts/inject-repo-data.ts | 7 +- .../scripts/migrate-duplicate-connections.ts | 9 +- .../db/tools/scripts/test-repo-query-perf.ts | 5 +- packages/db/tools/utils.ts | 7 +- packages/error/package.json | 14 -- packages/error/src/index.ts | 17 -- packages/error/tsconfig.json | 22 --- packages/logger/.gitignore | 2 - packages/logger/package.json | 24 --- packages/logger/src/env.ts | 28 --- packages/logger/tsconfig.json | 23 --- .../src/v3/environmentOverrides.schema.ts | 114 ++++++++++++ .../src/v3/environmentOverrides.type.ts | 40 ++++ packages/schemas/src/v3/index.schema.ts | 172 +++++++++++++++--- packages/schemas/src/v3/index.type.ts | 39 ++++ packages/shared/package.json | 13 +- .../src/index.ts => shared/src/crypto.ts} | 54 +++--- packages/shared/src/db.ts | 15 ++ packages/shared/src/entitlements.ts | 11 +- packages/shared/src/env.client.ts | 27 +++ .../src/env.mjs => shared/src/env.server.ts} | 112 +++++++++--- packages/shared/src/env.ts | 21 --- packages/shared/src/index.client.ts | 6 +- packages/shared/src/index.server.ts | 24 ++- .../src/index.ts => shared/src/logger.ts} | 4 +- packages/shared/src/types.ts | 2 + packages/shared/tools/resolveEnvOverrides.ts | 30 +++ packages/web/next.config.mjs | 3 +- packages/web/package.json | 4 - packages/web/sentry.server.config.ts | 2 +- packages/web/src/actions.ts | 6 +- packages/web/src/app/[domain]/agents/page.tsx | 2 +- packages/web/src/app/[domain]/chat/page.tsx | 2 +- .../components/navigationMenu/index.tsx | 2 +- .../[domain]/components/settingsDropdown.tsx | 2 +- packages/web/src/app/[domain]/layout.tsx | 2 +- .../web/src/app/[domain]/repos/[id]/page.tsx | 2 +- .../settings/connections/[id]/page.tsx | 2 +- .../web/src/app/[domain]/settings/layout.tsx | 2 +- .../app/[domain]/settings/license/page.tsx | 2 +- .../web/src/app/[domain]/upgrade/page.tsx | 2 +- .../web/src/app/api/(server)/chat/route.ts | 2 +- .../src/app/api/(server)/ee/audit/route.ts | 2 +- .../web/src/app/api/(server)/ee/user/route.ts | 2 +- .../src/app/api/(server)/ee/users/route.ts | 2 +- .../web/src/app/api/(server)/health/route.ts | 2 +- .../web/src/app/api/(server)/stripe/route.ts | 4 +- .../web/src/app/api/(server)/version/route.ts | 2 +- .../web/src/app/api/(server)/webhook/route.ts | 4 +- .../src/app/components/authSecurityNotice.tsx | 2 +- .../components/organizationAccessSettings.tsx | 2 +- packages/web/src/app/layout.tsx | 2 +- packages/web/src/app/onboard/page.tsx | 2 +- packages/web/src/app/posthogProvider.tsx | 2 +- packages/web/src/app/signup/page.tsx | 2 +- packages/web/src/auth.ts | 2 +- packages/web/src/ee/features/audit/actions.ts | 2 +- .../web/src/ee/features/audit/auditService.ts | 2 +- packages/web/src/ee/features/audit/factory.ts | 2 +- .../web/src/ee/features/billing/actions.ts | 4 +- .../web/src/ee/features/billing/stripe.ts | 2 +- .../ee/features/permissionSyncing/actions.ts | 3 +- .../permissionSyncing/tokenRefresh.ts | 4 +- packages/web/src/ee/features/sso/sso.ts | 25 ++- .../src/features/agents/review-agent/app.ts | 4 +- .../review-agent/nodes/fetchFileContent.ts | 2 +- .../nodes/generateDiffReviewPrompt.ts | 2 +- .../review-agent/nodes/generatePrReview.ts | 2 +- .../review-agent/nodes/githubPrParser.ts | 2 +- .../review-agent/nodes/githubPushPrReviews.ts | 2 +- .../review-agent/nodes/invokeDiffReviewLlm.ts | 4 +- packages/web/src/features/chat/actions.ts | 4 +- packages/web/src/features/chat/agent.ts | 7 +- .../chat/components/chatThread/answerCard.tsx | 2 +- packages/web/src/features/chat/utils.test.ts | 2 +- packages/web/src/features/fileTree/actions.ts | 4 +- .../web/src/features/search/zoektClient.ts | 2 +- packages/web/src/hooks/useCaptureEvent.ts | 2 +- packages/web/src/initialize.ts | 4 +- packages/web/src/lib/authUtils.ts | 2 +- packages/web/src/lib/types.ts | 2 +- packages/web/src/middleware.ts | 5 - packages/web/src/prisma.ts | 16 +- packages/web/src/withAuthV2.test.ts | 14 +- packages/web/src/withAuthV2.ts | 2 +- packages/web/tsconfig.json | 3 +- schemas/v3/environmentOverrides.json | 85 +++++++++ schemas/v3/index.json | 3 + supervisord.conf | 2 +- yarn.lock | 85 ++------- 140 files changed, 1280 insertions(+), 825 deletions(-) create mode 100644 docs/snippets/schemas/v3/environmentOverrides.schema.mdx delete mode 100644 packages/backend/src/env.ts delete mode 100644 packages/crypto/.gitignore delete mode 100644 packages/crypto/package.json delete mode 100644 packages/crypto/src/environment.ts delete mode 100644 packages/crypto/src/tokenUtils.ts delete mode 100644 packages/crypto/tsconfig.json delete mode 100644 packages/error/package.json delete mode 100644 packages/error/src/index.ts delete mode 100644 packages/error/tsconfig.json delete mode 100644 packages/logger/.gitignore delete mode 100644 packages/logger/package.json delete mode 100644 packages/logger/src/env.ts delete mode 100644 packages/logger/tsconfig.json create mode 100644 packages/schemas/src/v3/environmentOverrides.schema.ts create mode 100644 packages/schemas/src/v3/environmentOverrides.type.ts rename packages/{crypto/src/index.ts => shared/src/crypto.ts} (62%) create mode 100644 packages/shared/src/db.ts create mode 100644 packages/shared/src/env.client.ts rename packages/{web/src/env.mjs => shared/src/env.server.ts} (63%) delete mode 100644 packages/shared/src/env.ts rename packages/{logger/src/index.ts => shared/src/logger.ts} (95%) create mode 100644 packages/shared/tools/resolveEnvOverrides.ts create mode 100644 schemas/v3/environmentOverrides.json diff --git a/.env.development b/.env.development index 9b5b9682..ddb981af 100644 --- a/.env.development +++ b/.env.development @@ -84,7 +84,7 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection # NEXT_PUBLIC_SOURCEBOT_VERSION= # CONFIG_MAX_REPOS_NO_TOKEN= -# NODE_ENV= +NODE_ENV=development # SOURCEBOT_TENANCY_MODE=single # NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT= diff --git a/CHANGELOG.md b/CHANGELOG.md index 73092cb2..c63625be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added - [Experimental][Sourcebot EE] Added GitLab permission syncing. [#585](https://github.com/sourcebot-dev/sourcebot/pull/585) - [Sourcebot EE] Added external identity provider config and support for multiple accounts. [#595](https://github.com/sourcebot-dev/sourcebot/pull/595) +- Added ability to configure environment variables from the config. [#597](https://github.com/sourcebot-dev/sourcebot/pull/597) ### Fixed - [ask sb] Fixed issue where reasoning tokens would appear in `text` content for openai compatible models. [#582](https://github.com/sourcebot-dev/sourcebot/pull/582) diff --git a/Dockerfile b/Dockerfile index 43d410ac..ae90c8c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,16 +42,10 @@ COPY package.json yarn.lock* .yarnrc.yml ./ COPY .yarn ./.yarn COPY ./packages/db ./packages/db COPY ./packages/schemas ./packages/schemas -COPY ./packages/crypto ./packages/crypto -COPY ./packages/error ./packages/error -COPY ./packages/logger ./packages/logger COPY ./packages/shared ./packages/shared RUN yarn workspace @sourcebot/db install RUN yarn workspace @sourcebot/schemas install -RUN yarn workspace @sourcebot/crypto install -RUN yarn workspace @sourcebot/error install -RUN yarn workspace @sourcebot/logger install RUN yarn workspace @sourcebot/shared install # ------------------------------------ @@ -97,9 +91,6 @@ COPY ./packages/web ./packages/web COPY --from=shared-libs-builder /app/node_modules ./node_modules COPY --from=shared-libs-builder /app/packages/db ./packages/db COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas -COPY --from=shared-libs-builder /app/packages/crypto ./packages/crypto -COPY --from=shared-libs-builder /app/packages/error ./packages/error -COPY --from=shared-libs-builder /app/packages/logger ./packages/logger COPY --from=shared-libs-builder /app/packages/shared ./packages/shared # Fixes arm64 timeouts @@ -138,9 +129,6 @@ COPY ./packages/backend ./packages/backend COPY --from=shared-libs-builder /app/node_modules ./node_modules COPY --from=shared-libs-builder /app/packages/db ./packages/db COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas -COPY --from=shared-libs-builder /app/packages/crypto ./packages/crypto -COPY --from=shared-libs-builder /app/packages/error ./packages/error -COPY --from=shared-libs-builder /app/packages/logger ./packages/logger COPY --from=shared-libs-builder /app/packages/shared ./packages/shared RUN yarn workspace @sourcebot/backend install RUN yarn workspace @sourcebot/backend build @@ -185,7 +173,6 @@ ENV DATA_DIR=/data ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis -ENV REDIS_URL="redis://localhost:6379" ENV SRC_TENANT_ENFORCEMENT_MODE=strict ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem @@ -225,9 +212,6 @@ COPY --from=backend-builder /app/packages/backend ./packages/backend COPY --from=shared-libs-builder /app/node_modules ./node_modules COPY --from=shared-libs-builder /app/packages/db ./packages/db COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas -COPY --from=shared-libs-builder /app/packages/crypto ./packages/crypto -COPY --from=shared-libs-builder /app/packages/error ./packages/error -COPY --from=shared-libs-builder /app/packages/logger ./packages/logger COPY --from=shared-libs-builder /app/packages/shared ./packages/shared # Configure dependencies diff --git a/Makefile b/Makefile index 538a4e5d..13be4777 100644 --- a/Makefile +++ b/Makefile @@ -28,10 +28,6 @@ clean: packages/db/dist \ packages/schemas/node_modules \ packages/schemas/dist \ - packages/crypto/node_modules \ - packages/crypto/dist \ - packages/error/node_modules \ - packages/error/dist \ packages/mcp/node_modules \ packages/mcp/dist \ packages/shared/node_modules \ diff --git a/docs/docs/configuration/config-file.mdx b/docs/docs/configuration/config-file.mdx index 58d7a1b1..9985e457 100644 --- a/docs/docs/configuration/config-file.mdx +++ b/docs/docs/configuration/config-file.mdx @@ -3,6 +3,9 @@ title: Config File sidebarTitle: Config file --- +import ConfigSchema from '/snippets/schemas/v3/index.schema.mdx' +import EnvironmentOverridesSchema from '/snippets/schemas/v3/environmentOverrides.schema.mdx' + When self-hosting Sourcebot, you **must** provide it a config file. This is done by defining a config file in a volume that's mounted to Sourcebot, and providing the path to this file in the `CONFIG_PATH` environment variable. For example: @@ -49,3 +52,103 @@ The following are settings that can be provided in your config file to modify So | `enablePublicAccess` **(deprecated)** | boolean | false | — | Use the `FORCE_ENABLE_ANONYMOUS_ACCESS` environment variable instead. | | `experiment_repoDrivenPermissionSyncIntervalMs` | number | 24 hours | 1 | Interval at which the repo permission syncer should run. | | `experiment_userDrivenPermissionSyncIntervalMs` | number | 24 hours | 1 | Interval at which the user permission syncer should run. | + +# Tokens + +Tokens are used to securely pass secrets to Sourcebot in a config file. They are used in various places, including connections, language model providers, auth providers, etc. Tokens can be passed as either environment variables or Google Cloud secrets: + + + + ```json + { + "token": { + "env": "TOKEN_NAME" + } + } + ``` + + + ```json + { + "token": { + "googleCloudSecret": "projects//secrets//versions/" + } + } + ``` + + + +# Overriding environment variables from the config + +You can override / set environment variables from the config file by using the `environmentOverrides` property. Overrides can be of type `string`, `number`, `boolean`, or a [token](/docs/configuration/config-file#tokens). Tokens are useful when you want to configure a environment variable using a Google Cloud Secret or other supported secret management service. + + + + ```jsonc + { + "environmentOverrides": { + "DATABASE_URL": { + "type": "token", + "value": { + "googleCloudSecret": "projects//secrets/postgres-connection-string/versions/latest" + } + }, + "REDIS_URL": { + "type": "token", + "value": { + "googleCloudSecret": "projects//secrets/redis-connection-string/versions/latest" + } + } + }, + } + ``` + + + + ```jsonc + { + "environmentOverrides": { + "EMAIL_FROM_ADDRESS": { + "type": "string", + "value": "hello@sourcebot.dev" + } + } + } + ``` + + + + ```jsonc + { + "environmentOverrides": { + "SOURCEBOT_CHAT_MODEL_TEMPERATURE": { + "type": "number", + "value": 0.5 + } + } + } + ``` + + + + ```jsonc + { + "environmentOverrides": { + "SOURCEBOT_TELEMETRY_DISABLED": { + "type": "boolean", + "value": false + } + } + } + ``` + + + + +**Note:** Overrides are **not** set as system environment variables, and instead are resolved at runtime on startup and stored in memory. + + +[schemas/v3/environmentOverrides.json](https://github.com/sourcebot-dev/sourcebot/blob/main/schemas/v3/environmentOverrides.json) + + + \ No newline at end of file diff --git a/docs/docs/configuration/environment-variables.mdx b/docs/docs/configuration/environment-variables.mdx index b6fba9eb..1fc78e2f 100644 --- a/docs/docs/configuration/environment-variables.mdx +++ b/docs/docs/configuration/environment-variables.mdx @@ -1,7 +1,6 @@ --- title: Environment variables sidebarTitle: Environment variables -mode: "wide" --- This page provides a detailed reference of all environment variables supported by Sourcebot. If you're just looking to get up and running, we recommend starting with the [deployment guide](/docs/deployment-guide) instead. @@ -71,3 +70,6 @@ The following environment variables allow you to configure your Sourcebot deploy | `REVIEW_AGENT_LOGGING_ENABLED` | `true` |

Enables/disables logging for the review agent. Logs are saved in `DATA_CACHE_DIR/review-agent`

| | `REVIEW_AGENT_REVIEW_COMMAND` | `review` |

The command used to trigger a code review by the review agent.

| +### Overriding environment variables from the config + +You can override environment variables from the config file by using the `environmentOverrides` property. See [this doc](/docs/configuration/config-file#overriding-environment-variables-from-the-config) for more info. \ No newline at end of file diff --git a/docs/docs/configuration/idp.mdx b/docs/docs/configuration/idp.mdx index af4f8e05..21ae756d 100644 --- a/docs/docs/configuration/idp.mdx +++ b/docs/docs/configuration/idp.mdx @@ -20,11 +20,6 @@ External identity providers can be used for [authentication](/docs/configuration "provider": "github", "purpose": "account_linking", "accountLinkingRequired": true, -/* -Secrets are provided through environment variables. Set the secret into -an env var and provide the name here to tell Sourcebot where to get -the value -*/ "clientId": { "env": "GITHUB_IDENTITY_PROVIDER_CLIENT_ID" }, @@ -45,6 +40,8 @@ the value } ``` +Secret values (such as `clientId` and `clientSecret`) can be provided as environment variables or Google Cloud secrets via [tokens](/docs/configuration/config-file#tokens). + # Supported External Identity Providers Sourcebot uses [Auth.js](https://authjs.dev/) to connect to external identity providers. If there's a provider supported by Auth.js that you don't see below, please submit a diff --git a/docs/docs/connections/ado-cloud.mdx b/docs/docs/connections/ado-cloud.mdx index 7071afb1..92bde048 100644 --- a/docs/docs/connections/ado-cloud.mdx +++ b/docs/docs/connections/ado-cloud.mdx @@ -86,7 +86,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview), Azure Devops Cloud requires you to provide a PAT in order to index your repositories. To learn how to create PAT, check out the [Azure Devops docs](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows). Sourcebot needs the `Read` access for the `Code` scope in order to find and clone your repos. -Next, provide the access token via an environment variable which is referenced in the `token` property: +Next, provide the access [token](/docs/configuration/config-file#tokens) via an environment variable which is referenced in the `token` property: diff --git a/docs/docs/connections/ado-server.mdx b/docs/docs/connections/ado-server.mdx index a62d94c2..9687f827 100644 --- a/docs/docs/connections/ado-server.mdx +++ b/docs/docs/connections/ado-server.mdx @@ -100,7 +100,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview), Azure Devops Server requires you to provide a PAT in order to index your repositories. To learn how to create PAT, check out the [Azure Devops docs](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows). Sourcebot needs the `Read` access for the `Code` scope in order to find and clone your repos. -Next, provide the access token via an environment variable which is referenced in the `token` property: +Next, provide the access [token](/docs/configuration/config-file#tokens) via an environment variable which is referenced in the `token` property: diff --git a/docs/docs/connections/bitbucket-cloud.mdx b/docs/docs/connections/bitbucket-cloud.mdx index bde7a665..56b140f8 100644 --- a/docs/docs/connections/bitbucket-cloud.mdx +++ b/docs/docs/connections/bitbucket-cloud.mdx @@ -78,7 +78,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview), ## Authenticating with Bitbucket Cloud -In order to index private repositories, you'll need to provide authentication credentials. You can do this using an `App Password` or an `Access Token` +In order to index private repositories, you'll need to provide authentication credentials via a [token](/docs/configuration/config-file#tokens). You can do this using an `App Password` or an `Access Token` diff --git a/docs/docs/connections/bitbucket-data-center.mdx b/docs/docs/connections/bitbucket-data-center.mdx index 77479e46..bfda0934 100644 --- a/docs/docs/connections/bitbucket-data-center.mdx +++ b/docs/docs/connections/bitbucket-data-center.mdx @@ -70,7 +70,7 @@ If you're not familiar with Sourcebot [connections](/docs/connections/overview), ## Authenticating with Bitbucket Data Center -In order to index private repositories, you'll need to provide an access token to Sourcebot. +In order to index private repositories, you'll need to provide an access token to Sourcebot via a [token](/docs/configuration/config-file#tokens). Create an access token for the desired scope (repo, project, or workspace). Visit the official [Bitbucket Data Center docs](https://confluence.atlassian.com/bitbucketserver/http-access-tokens-939515499.html) for more info. diff --git a/docs/docs/connections/gitea.mdx b/docs/docs/connections/gitea.mdx index 0f8505cb..0b4efe15 100644 --- a/docs/docs/connections/gitea.mdx +++ b/docs/docs/connections/gitea.mdx @@ -81,7 +81,7 @@ In order to index private repositories, you'll need to generate a Gitea access t ![Gitea Access token creation](/images/gitea_pat_creation.png) -Next, provide the access token via an environment variable which is referenced in the `token` property: +Next, provide the access token via an environment variable [token](/docs/configuration/config-file#tokens) which is referenced in the `token` property: diff --git a/docs/docs/connections/github.mdx b/docs/docs/connections/github.mdx index e87a1f6d..4d3384b4 100644 --- a/docs/docs/connections/github.mdx +++ b/docs/docs/connections/github.mdx @@ -128,7 +128,7 @@ In order to index private repositories, you'll need to generate a access token a -Next, provide the access token via an environment variable which is referenced in the `token` property: +Next, provide the access token via an environment variable [token](/docs/configuration/config-file#tokens) which is referenced in the `token` property: diff --git a/docs/docs/connections/gitlab.mdx b/docs/docs/connections/gitlab.mdx index 1b71b7ae..25603537 100644 --- a/docs/docs/connections/gitlab.mdx +++ b/docs/docs/connections/gitlab.mdx @@ -116,7 +116,7 @@ In order to index private projects, you'll need to generate a GitLab Personal Ac ![GitLab PAT Scope](/images/gitlab_pat_scopes.png) -Next, provide the PAT via an environment variable which is referenced in the `token` property: +Next, provide the PAT via an environment variable [token](/docs/configuration/config-file#tokens) which is referenced in the `token` property: diff --git a/docs/snippets/schemas/v3/environmentOverrides.schema.mdx b/docs/snippets/schemas/v3/environmentOverrides.schema.mdx new file mode 100644 index 00000000..bca6ec08 --- /dev/null +++ b/docs/snippets/schemas/v3/environmentOverrides.schema.mdx @@ -0,0 +1,115 @@ +{/* THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! */} +```json +{ + "type": "object", + "description": "Environment variable overrides.", + "title": "EnvironmentOverrides", + "not": { + "$comment": "List of environment variables that are not allowed to be overridden.", + "anyOf": [ + { + "required": [ + "CONFIG_PATH" + ] + } + ] + }, + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "token" + }, + "value": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "number" + }, + "value": { + "type": "number" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "boolean" + }, + "value": { + "type": "boolean" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + } + ] + } + } +} +``` diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx index 615c058a..413e51bd 100644 --- a/docs/snippets/schemas/v3/index.schema.mdx +++ b/docs/snippets/schemas/v3/index.schema.mdx @@ -279,25 +279,30 @@ }, "additionalProperties": false }, - "connections": { + "environmentOverrides": { "type": "object", - "description": "Defines a collection of connections from varying code hosts that Sourcebot should sync with. This is only available in single-tenancy mode.", + "description": "Environment variable overrides.", + "title": "EnvironmentOverrides", + "not": { + "$comment": "List of environment variables that are not allowed to be overridden.", + "anyOf": [ + { + "required": [ + "CONFIG_PATH" + ] + } + ] + }, "patternProperties": { "^[a-zA-Z0-9_-]+$": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConnectionConfig", "oneOf": [ { - "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "title": "GithubConnectionConfig", "properties": { "type": { - "const": "github", - "description": "GitHub Configuration" + "const": "token" }, - "token": { - "description": "A Personal Access Token (PAT).", + "value": { "anyOf": [ { "type": "object", @@ -326,6 +331,113 @@ "additionalProperties": false } ] + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "number" + }, + "value": { + "type": "number" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "boolean" + }, + "value": { + "type": "boolean" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + } + ] + } + } + }, + "connections": { + "type": "object", + "description": "Defines a collection of connections from varying code hosts that Sourcebot should sync with. This is only available in single-tenancy mode.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConnectionConfig", + "oneOf": [ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubConnectionConfig", + "properties": { + "type": { + "const": "github", + "description": "GitHub Configuration" + }, + "token": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ], + "description": "A Personal Access Token (PAT)." }, "url": { "type": "string", @@ -505,7 +617,6 @@ "description": "GitLab Configuration" }, "token": { - "description": "An authentication token.", "anyOf": [ { "type": "object", @@ -533,7 +644,8 @@ ], "additionalProperties": false } - ] + ], + "description": "An authentication token." }, "url": { "type": "string", @@ -707,7 +819,6 @@ "description": "Gitea Configuration" }, "token": { - "description": "A Personal Access Token (PAT).", "anyOf": [ { "type": "object", @@ -735,7 +846,8 @@ ], "additionalProperties": false } - ] + ], + "description": "A Personal Access Token (PAT)." }, "url": { "type": "string", @@ -974,7 +1086,6 @@ "description": "The username to use for authentication. Only needed if token is an app password." }, "token": { - "description": "An authentication token.", "anyOf": [ { "type": "object", @@ -1002,7 +1113,8 @@ ], "additionalProperties": false } - ] + ], + "description": "An authentication token." }, "url": { "type": "string", @@ -1142,7 +1254,6 @@ "description": "Azure DevOps Configuration" }, "token": { - "description": "A Personal Access Token (PAT).", "anyOf": [ { "type": "object", @@ -1170,7 +1281,8 @@ ], "additionalProperties": false } - ] + ], + "description": "A Personal Access Token (PAT)." }, "url": { "type": "string", @@ -1426,7 +1538,6 @@ "description": "Optional display name." }, "accessKeyId": { - "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", "anyOf": [ { "type": "object", @@ -1454,10 +1565,10 @@ ], "additionalProperties": false } - ] + ], + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable." }, "accessKeySecret": { - "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", "anyOf": [ { "type": "object", @@ -1485,10 +1596,10 @@ ], "additionalProperties": false } - ] + ], + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable." }, "sessionToken": { - "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable.", "anyOf": [ { "type": "object", @@ -1516,7 +1627,8 @@ ], "additionalProperties": false } - ] + ], + "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable." }, "region": { "type": "string", @@ -2855,7 +2967,6 @@ "description": "Optional display name." }, "accessKeyId": { - "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", "anyOf": [ { "type": "object", @@ -2883,10 +2994,10 @@ ], "additionalProperties": false } - ] + ], + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable." }, "accessKeySecret": { - "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", "anyOf": [ { "type": "object", @@ -2914,10 +3025,10 @@ ], "additionalProperties": false } - ] + ], + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable." }, "sessionToken": { - "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable.", "anyOf": [ { "type": "object", @@ -2945,7 +3056,8 @@ ], "additionalProperties": false } - ] + ], + "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable." }, "region": { "type": "string", diff --git a/entrypoint.sh b/entrypoint.sh index b031b326..cf90a377 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,28 +1,57 @@ #!/bin/sh + +# Exit immediately if a command fails set -e +# Disable auto-exporting of variables +set +a -# Check if DATABASE_URL is not set -if [ -z "$DATABASE_URL" ]; then - # Check if the individual database variables are set and construct the URL - if [ -n "$DATABASE_HOST" ] && [ -n "$DATABASE_USERNAME" ] && [ -n "$DATABASE_PASSWORD" ] && [ -n "$DATABASE_NAME" ]; then - DATABASE_URL="postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}" +# If a CONFIG_PATH is set, resolve the environment overrides from the config file. +# The overrides will be written into variables scopped to the current shell. This is +# required in case one of the variables used in this entrypoint is overriden (e.g., +# DATABASE_URL, REDIS_URL, etc.) +if [ -n "$CONFIG_PATH" ]; then + echo -e "\e[34m[Info] Resolving environment overrides from $CONFIG_PATH...\e[0m" - if [ -n "$DATABASE_ARGS" ]; then - DATABASE_URL="${DATABASE_URL}?$DATABASE_ARGS" - fi + set +e # Disable exist on error so we can capture EXIT_CODE + OVERRIDES_OUTPUT=$(SKIP_ENV_VALIDATION=1 yarn tool:resolve-env-overrides 2>&1) + EXIT_CODE=$? + set -e # Re-enable exit on error - export DATABASE_URL + if [ $EXIT_CODE -eq 0 ]; then + eval "$OVERRIDES_OUTPUT" else - # Otherwise, fallback to a default value - DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot" - export DATABASE_URL + echo -e "\e[31m[Error] Failed to resolve environment overrides.\e[0m" + echo "$OVERRIDES_OUTPUT" + exit 1 fi fi -if [ "$DATABASE_URL" = "postgresql://postgres@localhost:5432/sourcebot" ]; then - DATABASE_EMBEDDED="true" +# Descontruct the database URL from the individual variables if DATABASE_URL is not set +if [ -z "$DATABASE_URL" ] && [ -n "$DATABASE_HOST" ] && [ -n "$DATABASE_USERNAME" ] && [ -n "$DATABASE_PASSWORD" ] && [ -n "$DATABASE_NAME" ]; then + DATABASE_URL="postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}" + + if [ -n "$DATABASE_ARGS" ]; then + DATABASE_URL="${DATABASE_URL}?$DATABASE_ARGS" + fi fi +if [ -z "$DATABASE_URL" ]; then + echo -e "\e[34m[Info] DATABASE_URL is not set. Using embeded database.\e[0m" + export DATABASE_EMBEDDED="true" + export DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot" +else + export DATABASE_EMBEDDED="false" +fi + +if [ -z "$REDIS_URL" ]; then + echo -e "\e[34m[Info] REDIS_URL is not set. Using embeded redis.\e[0m" + export REDIS_EMBEDDED="true" + export REDIS_URL="redis://localhost:6379" +else + export REDIS_EMBEDDED="false" +fi + + echo -e "\e[34m[Info] Sourcebot version: $NEXT_PUBLIC_SOURCEBOT_VERSION\e[0m" # If we don't have a PostHog key, then we need to disable telemetry. @@ -59,7 +88,7 @@ if [ "$DATABASE_EMBEDDED" = "true" ] && [ ! -d "$DATABASE_DATA_DIR" ]; then fi # Create the redis data directory if it doesn't exist -if [ ! -d "$REDIS_DATA_DIR" ]; then +if [ "$REDIS_EMBEDDED" = "true" ] && [ ! -d "$REDIS_DATA_DIR" ]; then mkdir -p $REDIS_DATA_DIR fi @@ -149,7 +178,6 @@ fi echo "{\"version\": \"$NEXT_PUBLIC_SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL_ID\"}" > "$FIRST_RUN_FILE" - # Start the database and wait for it to be ready before starting any other service if [ "$DATABASE_EMBEDDED" = "true" ]; then su postgres -c "postgres -D $DATABASE_DATA_DIR" & @@ -171,7 +199,7 @@ fi # Run a Database migration echo -e "\e[34m[Info] Running database migration...\e[0m" -yarn workspace @sourcebot/db prisma:migrate:prod +DATABASE_URL="$DATABASE_URL" yarn workspace @sourcebot/db prisma:migrate:prod # Create the log directory mkdir -p /var/log/sourcebot diff --git a/package.json b/package.json index 7c726c7e..a70bab99 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "packages/*" ], "scripts": { - "build": "cross-env SKIP_ENV_VALIDATION=1 yarn workspaces foreach -A run build", - "test": "yarn workspaces foreach -A run test", + "build": "cross-env SKIP_ENV_VALIDATION=1 yarn workspaces foreach --all --topological run build", + "test": "yarn workspaces foreach --all --topological run test", "dev": "concurrently --kill-others --names \"zoekt,worker,web,mcp,schemas\" 'yarn dev:zoekt' 'yarn dev:backend' 'yarn dev:web' 'yarn watch:mcp' 'yarn watch:schemas'", "with-env": "cross-env PATH=\"$PWD/bin:$PATH\" dotenv -e .env.development -c --", "dev:zoekt": "yarn with-env zoekt-webserver -index .sourcebot/index -rpc", @@ -18,7 +18,7 @@ "dev:prisma:studio": "yarn with-env yarn workspace @sourcebot/db prisma:studio", "dev:prisma:migrate:reset": "yarn with-env yarn workspace @sourcebot/db prisma:migrate:reset", "dev:prisma:db:push": "yarn with-env yarn workspace @sourcebot/db prisma:db:push", - "build:deps": "yarn workspaces foreach -R --from '{@sourcebot/schemas,@sourcebot/error,@sourcebot/crypto,@sourcebot/db,@sourcebot/shared}' run build" + "build:deps": "yarn workspaces foreach --recursive --topological --from '{@sourcebot/schemas,@sourcebot/db,@sourcebot/shared}' run build" }, "devDependencies": { "concurrently": "^9.2.1", diff --git a/packages/backend/package.json b/packages/backend/package.json index 8b0659d2..5369bde1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -29,13 +29,9 @@ "@sentry/cli": "^2.42.2", "@sentry/node": "^9.3.0", "@sentry/profiling-node": "^9.3.0", - "@sourcebot/crypto": "workspace:*", "@sourcebot/db": "workspace:*", - "@sourcebot/error": "workspace:*", - "@sourcebot/logger": "workspace:*", "@sourcebot/schemas": "workspace:*", "@sourcebot/shared": "workspace:*", - "@t3-oss/env-core": "^0.12.0", "@types/express": "^5.0.0", "argparse": "^2.0.1", "azure-devops-node-api": "^15.1.1", @@ -55,6 +51,6 @@ "posthog-node": "^4.2.1", "prom-client": "^15.1.3", "simple-git": "^3.27.0", - "zod": "^3.24.3" + "zod": "^3.25.74" } } diff --git a/packages/backend/src/azuredevops.ts b/packages/backend/src/azuredevops.ts index aa0fbf55..0714276e 100644 --- a/packages/backend/src/azuredevops.ts +++ b/packages/backend/src/azuredevops.ts @@ -1,13 +1,12 @@ import { AzureDevOpsConnectionConfig } from "@sourcebot/schemas/v3/azuredevops.type"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { measure, fetchWithRetry } from "./utils.js"; import micromatch from "micromatch"; -import { BackendException, BackendError } from "@sourcebot/error"; import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js"; import * as Sentry from "@sentry/node"; import * as azdev from "azure-devops-node-api"; import { GitRepository } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { getTokenFromConfig } from "@sourcebot/crypto"; +import { getTokenFromConfig } from "@sourcebot/shared"; const logger = createLogger('azuredevops'); const AZUREDEVOPS_CLOUD_HOSTNAME = "dev.azure.com"; @@ -36,9 +35,7 @@ export const getAzureDevOpsReposFromConfig = async ( undefined; if (!token) { - const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, { - message: 'Azure DevOps requires a Personal Access Token', - }); + const e = new Error('Azure DevOps requires a Personal Access Token'); Sentry.captureException(e); throw e; } diff --git a/packages/backend/src/bitbucket.ts b/packages/backend/src/bitbucket.ts index c6fa87ff..3f0e18ff 100644 --- a/packages/backend/src/bitbucket.ts +++ b/packages/backend/src/bitbucket.ts @@ -2,7 +2,7 @@ import { createBitbucketCloudClient } from "@coderabbitai/bitbucket/cloud"; import { createBitbucketServerClient } from "@coderabbitai/bitbucket/server"; import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type"; import type { ClientOptions, ClientPathsWithMethod } from "openapi-fetch"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { measure, fetchWithRetry } from "./utils.js"; import * as Sentry from "@sentry/node"; import { @@ -11,7 +11,7 @@ import { import { SchemaRestRepository as ServerRepository } from "@coderabbitai/bitbucket/server/openapi"; import { processPromiseResults } from "./connectionUtils.js"; import { throwIfAnyFailed } from "./connectionUtils.js"; -import { getTokenFromConfig } from "@sourcebot/crypto"; +import { getTokenFromConfig } from "@sourcebot/shared"; const logger = createLogger('bitbucket'); const BITBUCKET_CLOUD_GIT = 'https://bitbucket.org'; diff --git a/packages/backend/src/configManager.ts b/packages/backend/src/configManager.ts index 14c6b0de..55dbd6ed 100644 --- a/packages/backend/src/configManager.ts +++ b/packages/backend/src/configManager.ts @@ -1,5 +1,5 @@ import { Prisma, PrismaClient } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type"; import { loadConfig } from "@sourcebot/shared"; import chokidar, { FSWatcher } from 'chokidar'; diff --git a/packages/backend/src/connectionManager.ts b/packages/backend/src/connectionManager.ts index 006a3bf2..8b54fa4d 100644 --- a/packages/backend/src/connectionManager.ts +++ b/packages/backend/src/connectionManager.ts @@ -1,11 +1,10 @@ import * as Sentry from "@sentry/node"; import { Connection, ConnectionSyncJobStatus, PrismaClient } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { ConnectionConfig } from "@sourcebot/schemas/v3/connection.type"; -import { loadConfig } from "@sourcebot/shared"; +import { loadConfig, env } from "@sourcebot/shared"; import { Job, Queue, ReservedJob, Worker } from "groupmq"; import { Redis } from 'ioredis'; -import { env } from "./env.js"; import { compileAzureDevOpsConfig, compileBitbucketConfig, compileGenericGitHostConfig, compileGerritConfig, compileGiteaConfig, compileGithubConfig, compileGitlabConfig } from "./repoCompileUtils.js"; import { Settings } from "./types.js"; import { groupmqLifecycleExceptionWrapper } from "./utils.js"; diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index f073bac5..a52d822e 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -1,5 +1,5 @@ import { CodeHostType } from "@sourcebot/db"; -import { env } from "./env.js"; +import { env } from "@sourcebot/shared"; import path from "path"; export const SINGLE_TENANT_ORG_ID = 1; diff --git a/packages/backend/src/ee/accountPermissionSyncer.ts b/packages/backend/src/ee/accountPermissionSyncer.ts index 70ff0e13..eb32ba3b 100644 --- a/packages/backend/src/ee/accountPermissionSyncer.ts +++ b/packages/backend/src/ee/accountPermissionSyncer.ts @@ -1,13 +1,11 @@ import * as Sentry from "@sentry/node"; -import { PrismaClient, AccountPermissionSyncJobStatus, Account } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; +import { PrismaClient, AccountPermissionSyncJobStatus, Account} from "@sourcebot/db"; +import { env, hasEntitlement, createLogger } from "@sourcebot/shared"; import { Job, Queue, Worker } from "bullmq"; import { Redis } from "ioredis"; import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js"; -import { env } from "../env.js"; import { createOctokitFromToken, getReposForAuthenticatedUser } from "../github.js"; import { createGitLabFromOAuthToken, getProjectsForAuthenticatedUser } from "../gitlab.js"; -import { hasEntitlement } from "@sourcebot/shared"; import { Settings } from "../types.js"; const LOG_TAG = 'user-permission-syncer'; diff --git a/packages/backend/src/ee/githubAppManager.ts b/packages/backend/src/ee/githubAppManager.ts index 892e637b..0ee50acf 100644 --- a/packages/backend/src/ee/githubAppManager.ts +++ b/packages/backend/src/ee/githubAppManager.ts @@ -1,10 +1,9 @@ -import { loadConfig } from "@sourcebot/shared"; -import { env } from "../env.js"; -import { createLogger } from "@sourcebot/logger"; -import { getTokenFromConfig } from "@sourcebot/crypto"; -import { PrismaClient } from "@sourcebot/db"; import { App } from "@octokit/app"; +import { getTokenFromConfig } from "@sourcebot/shared"; +import { PrismaClient } from "@sourcebot/db"; +import { createLogger } from "@sourcebot/shared"; import { GitHubAppConfig } from "@sourcebot/schemas/v3/index.type"; +import { env, loadConfig } from "@sourcebot/shared"; const logger = createLogger('githubAppManager'); const GITHUB_DEFAULT_DEPLOYMENT_HOSTNAME = 'github.com'; @@ -45,7 +44,7 @@ export class GithubAppManager { public async init(db: PrismaClient) { this.db = db; - const config = await loadConfig(env.CONFIG_PATH!); + const config = await loadConfig(env.CONFIG_PATH); if (!config.apps) { return; } diff --git a/packages/backend/src/ee/repoPermissionSyncer.ts b/packages/backend/src/ee/repoPermissionSyncer.ts index 2e9be5f4..078055ff 100644 --- a/packages/backend/src/ee/repoPermissionSyncer.ts +++ b/packages/backend/src/ee/repoPermissionSyncer.ts @@ -1,11 +1,10 @@ import * as Sentry from "@sentry/node"; import { PrismaClient, Repo, RepoPermissionSyncJobStatus } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; -import { hasEntitlement } from "@sourcebot/shared"; +import { createLogger } from "@sourcebot/shared"; +import { env, hasEntitlement } from "@sourcebot/shared"; import { Job, Queue, Worker } from 'bullmq'; import { Redis } from 'ioredis'; import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js"; -import { env } from "../env.js"; import { createOctokitFromToken, getRepoCollaborators, GITHUB_CLOUD_HOSTNAME } from "../github.js"; import { createGitLabFromPersonalAccessToken, getProjectMembers } from "../gitlab.js"; import { Settings } from "../types.js"; diff --git a/packages/backend/src/ee/syncSearchContexts.ts b/packages/backend/src/ee/syncSearchContexts.ts index e53679ae..8f9265a3 100644 --- a/packages/backend/src/ee/syncSearchContexts.ts +++ b/packages/backend/src/ee/syncSearchContexts.ts @@ -1,5 +1,5 @@ import micromatch from "micromatch"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { PrismaClient } from "@sourcebot/db"; import { getPlan, hasEntitlement, SOURCEBOT_SUPPORT_EMAIL } from "@sourcebot/shared"; import { SearchContext } from "@sourcebot/schemas/v3/index.type"; diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts deleted file mode 100644 index c3ea3679..00000000 --- a/packages/backend/src/env.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { createEnv } from "@t3-oss/env-core"; -import { z } from "zod"; -import dotenv from 'dotenv'; - -// Booleans are specified as 'true' or 'false' strings. -const booleanSchema = z.enum(["true", "false"]); - -// Numbers are treated as strings in .env files. -// coerce helps us convert them to numbers. -// @see: https://zod.dev/?id=coercion-for-primitives -const numberSchema = z.coerce.number(); - -dotenv.config({ - path: './.env', -}); - -dotenv.config({ - path: './.env.local', - override: true -}); - -export const env = createEnv({ - server: { - SOURCEBOT_ENCRYPTION_KEY: z.string(), - SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default("false"), - SOURCEBOT_INSTALL_ID: z.string().default("unknown"), - NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default("unknown"), - - DATA_CACHE_DIR: z.string(), - - NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(), - - FALLBACK_GITHUB_CLOUD_TOKEN: z.string().optional(), - FALLBACK_GITLAB_CLOUD_TOKEN: z.string().optional(), - FALLBACK_GITEA_CLOUD_TOKEN: z.string().optional(), - - REDIS_URL: z.string().url().default("redis://localhost:6379"), - REDIS_REMOVE_ON_COMPLETE: numberSchema.default(0), - REDIS_REMOVE_ON_FAIL: numberSchema.default(100), - - NEXT_PUBLIC_SENTRY_BACKEND_DSN: z.string().optional(), - NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().optional(), - - LOGTAIL_TOKEN: z.string().optional(), - LOGTAIL_HOST: z.string().url().optional(), - SOURCEBOT_LOG_LEVEL: z.enum(["info", "debug", "warn", "error"]).default("info"), - DEBUG_ENABLE_GROUPMQ_LOGGING: booleanSchema.default('false'), - - DATABASE_URL: z.string().url().default("postgresql://postgres:postgres@localhost:5432/postgres"), - CONFIG_PATH: z.string(), - - CONNECTION_MANAGER_UPSERT_TIMEOUT_MS: numberSchema.default(300000), - REPO_SYNC_RETRY_BASE_SLEEP_SECONDS: numberSchema.default(60), - - GITLAB_CLIENT_QUERY_TIMEOUT_SECONDS: numberSchema.default(60 * 10), - - EXPERIMENT_EE_PERMISSION_SYNC_ENABLED: booleanSchema.default('false'), - AUTH_EE_GITHUB_BASE_URL: z.string().optional(), - AUTH_EE_GITLAB_BASE_URL: z.string().default("https://gitlab.com"), - }, - runtimeEnv: process.env, - emptyStringAsUndefined: true, - skipValidation: process.env.SKIP_ENV_VALIDATION === "1", -}); \ No newline at end of file diff --git a/packages/backend/src/gerrit.ts b/packages/backend/src/gerrit.ts index ef149648..34feb208 100644 --- a/packages/backend/src/gerrit.ts +++ b/packages/backend/src/gerrit.ts @@ -1,11 +1,8 @@ +import { GerritConnectionConfig } from "@sourcebot/schemas/v3/index.type"; +import { createLogger } from '@sourcebot/shared'; import fetch from 'cross-fetch'; -import { GerritConnectionConfig } from "@sourcebot/schemas/v3/index.type" -import { createLogger } from '@sourcebot/logger'; import micromatch from "micromatch"; -import { measure, fetchWithRetry } from './utils.js'; -import { BackendError } from '@sourcebot/error'; -import { BackendException } from '@sourcebot/error'; -import * as Sentry from "@sentry/node"; +import { fetchWithRetry, measure } from './utils.js'; // https://gerrit-review.googlesource.com/Documentation/rest-api.html interface GerritProjects { @@ -39,26 +36,10 @@ export const getGerritReposFromConfig = async (config: GerritConnectionConfig): const url = config.url.endsWith('/') ? config.url : `${config.url}/`; let { durationMs, data: projects } = await measure(async () => { - try { - const fetchFn = () => fetchAllProjects(url); - return fetchWithRetry(fetchFn, `projects from ${url}`, logger); - } catch (err) { - Sentry.captureException(err); - if (err instanceof BackendException) { - throw err; - } - - logger.error(`Failed to fetch projects from ${url}`, err); - return null; - } + const fetchFn = () => fetchAllProjects(url); + return fetchWithRetry(fetchFn, `projects from ${url}`, logger); }); - if (!projects) { - const e = new Error(`Failed to fetch projects from ${url}`); - Sentry.captureException(e); - throw e; - } - // include repos by glob if specified in config if (config.projects) { projects = projects.filter((project) => { @@ -91,27 +72,9 @@ const fetchAllProjects = async (url: string): Promise => { logger.debug(`Fetching projects from Gerrit at ${endpointWithParams}`); let response: Response; - try { - response = await fetch(endpointWithParams); - if (!response.ok) { - logger.error(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`); - const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, { - status: response.status, - }); - Sentry.captureException(e); - throw e; - } - } catch (err) { - Sentry.captureException(err); - if (err instanceof BackendException) { - throw err; - } - - const status = (err as any).code; - logger.error(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${status}`); - throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, { - status: status, - }); + response = await fetch(endpointWithParams); + if (!response.ok) { + throw new Error(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`); } const text = await response.text(); @@ -151,11 +114,11 @@ const shouldExcludeProject = ({ const shouldExclude = (() => { if ([ - 'All-Projects', - 'All-Users', - 'All-Avatars', - 'All-Archived-Projects' - ].includes(project.name)) { + 'All-Projects', + 'All-Users', + 'All-Avatars', + 'All-Archived-Projects' + ].includes(project.name)) { reason = `Project is a special project.`; return true; } diff --git a/packages/backend/src/git.ts b/packages/backend/src/git.ts index dbb602e1..948b1f3b 100644 --- a/packages/backend/src/git.ts +++ b/packages/backend/src/git.ts @@ -1,8 +1,8 @@ -import { CheckRepoActions, GitConfigScope, simpleGit, SimpleGitProgressEvent } from 'simple-git'; -import { mkdir } from 'node:fs/promises'; -import { env } from './env.js'; -import { dirname, resolve } from 'node:path'; +import { env } from "@sourcebot/shared"; import { existsSync } from 'node:fs'; +import { mkdir } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; +import { CheckRepoActions, GitConfigScope, simpleGit, SimpleGitProgressEvent } from 'simple-git'; type onProgressFn = (event: SimpleGitProgressEvent) => void; diff --git a/packages/backend/src/gitea.ts b/packages/backend/src/gitea.ts index 91493b0f..23f5c520 100644 --- a/packages/backend/src/gitea.ts +++ b/packages/backend/src/gitea.ts @@ -1,13 +1,13 @@ -import { Api, giteaApi, HttpResponse, Repository as GiteaRepository } from 'gitea-js'; +import * as Sentry from "@sentry/node"; +import { getTokenFromConfig } from "@sourcebot/shared"; +import { createLogger } from '@sourcebot/shared'; import { GiteaConnectionConfig } from '@sourcebot/schemas/v3/gitea.type'; -import { measure } from './utils.js'; +import { env } from "@sourcebot/shared"; import fetch from 'cross-fetch'; -import { createLogger } from '@sourcebot/logger'; +import { Api, giteaApi, Repository as GiteaRepository, HttpResponse } from 'gitea-js'; import micromatch from 'micromatch'; import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js'; -import * as Sentry from "@sentry/node"; -import { env } from './env.js'; -import { getTokenFromConfig } from "@sourcebot/crypto"; +import { measure } from './utils.js'; const logger = createLogger('gitea'); const GITEA_CLOUD_HOSTNAME = "gitea.com"; diff --git a/packages/backend/src/github.ts b/packages/backend/src/github.ts index 5b8aaa5a..49ce1d37 100644 --- a/packages/backend/src/github.ts +++ b/packages/backend/src/github.ts @@ -1,15 +1,14 @@ import { Octokit } from "@octokit/rest"; import * as Sentry from "@sentry/node"; -import { createLogger } from "@sourcebot/logger"; +import { getTokenFromConfig } from "@sourcebot/shared"; +import { createLogger } from "@sourcebot/shared"; import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; -import { hasEntitlement } from "@sourcebot/shared"; +import { env, hasEntitlement } from "@sourcebot/shared"; import micromatch from "micromatch"; import pLimit from "p-limit"; import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js"; import { GithubAppManager } from "./ee/githubAppManager.js"; -import { env } from "./env.js"; import { fetchWithRetry, measure } from "./utils.js"; -import { getTokenFromConfig } from "@sourcebot/crypto"; export const GITHUB_CLOUD_HOSTNAME = "github.com"; diff --git a/packages/backend/src/gitlab.ts b/packages/backend/src/gitlab.ts index 6063f7bd..b461d59b 100644 --- a/packages/backend/src/gitlab.ts +++ b/packages/backend/src/gitlab.ts @@ -1,12 +1,12 @@ import { Gitlab, ProjectSchema } from "@gitbeaker/rest"; -import micromatch from "micromatch"; -import { createLogger } from "@sourcebot/logger"; -import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type" -import { measure, fetchWithRetry } from "./utils.js"; -import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js"; import * as Sentry from "@sentry/node"; -import { env } from "./env.js"; -import { getTokenFromConfig } from "@sourcebot/crypto"; +import { getTokenFromConfig } from "@sourcebot/shared"; +import { createLogger } from "@sourcebot/shared"; +import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"; +import { env } from "@sourcebot/shared"; +import micromatch from "micromatch"; +import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js"; +import { fetchWithRetry, measure } from "./utils.js"; const logger = createLogger('gitlab'); export const GITLAB_CLOUD_HOSTNAME = "gitlab.com"; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index ed66a390..6c1cbeba 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,8 +1,8 @@ import "./instrument.js"; import { PrismaClient } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; -import { getConfigSettings, hasEntitlement } from '@sourcebot/shared'; +import { createLogger } from "@sourcebot/shared"; +import { env, getConfigSettings, hasEntitlement, getDBConnectionString } from '@sourcebot/shared'; import { existsSync } from 'fs'; import { mkdir } from 'fs/promises'; import { Redis } from 'ioredis'; @@ -12,7 +12,6 @@ import { INDEX_CACHE_DIR, REPOS_CACHE_DIR } from './constants.js'; import { GithubAppManager } from "./ee/githubAppManager.js"; import { RepoPermissionSyncer } from './ee/repoPermissionSyncer.js'; import { AccountPermissionSyncer } from "./ee/accountPermissionSyncer.js"; -import { env } from "./env.js"; import { PromClient } from './promClient.js'; import { RepoIndexManager } from "./repoIndexManager.js"; @@ -29,7 +28,13 @@ if (!existsSync(indexPath)) { await mkdir(indexPath, { recursive: true }); } -const prisma = new PrismaClient(); +const prisma = new PrismaClient({ + datasources: { + db: { + url: getDBConnectionString(), + }, + }, +}); const redis = new Redis(env.REDIS_URL, { maxRetriesPerRequest: null diff --git a/packages/backend/src/instrument.ts b/packages/backend/src/instrument.ts index 926bf2ae..5e0faa28 100644 --- a/packages/backend/src/instrument.ts +++ b/packages/backend/src/instrument.ts @@ -1,6 +1,6 @@ import * as Sentry from "@sentry/node"; -import { env } from "./env.js"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; +import { env } from "@sourcebot/shared/client"; const logger = createLogger('instrument'); diff --git a/packages/backend/src/posthog.ts b/packages/backend/src/posthog.ts index 9984643b..d61ee46e 100644 --- a/packages/backend/src/posthog.ts +++ b/packages/backend/src/posthog.ts @@ -1,12 +1,13 @@ +import { env as clientEnv } from "@sourcebot/shared/client"; +import { env } from "@sourcebot/shared"; import { PostHog } from 'posthog-node'; import { PosthogEvent, PosthogEventMap } from './posthogEvents.js'; -import { env } from './env.js'; let posthog: PostHog | undefined = undefined; -if (env.NEXT_PUBLIC_POSTHOG_PAPIK) { +if (clientEnv.NEXT_PUBLIC_POSTHOG_PAPIK) { posthog = new PostHog( - env.NEXT_PUBLIC_POSTHOG_PAPIK, + clientEnv.NEXT_PUBLIC_POSTHOG_PAPIK, { host: "https://us.i.posthog.com", } @@ -23,7 +24,7 @@ export function captureEvent(event: E, properties: Posth event: event, properties: { ...properties, - sourcebot_version: env.NEXT_PUBLIC_SOURCEBOT_VERSION, + sourcebot_version: clientEnv.NEXT_PUBLIC_SOURCEBOT_VERSION, }, }); } diff --git a/packages/backend/src/promClient.ts b/packages/backend/src/promClient.ts index 67ed6f3f..2fa7718f 100644 --- a/packages/backend/src/promClient.ts +++ b/packages/backend/src/promClient.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from 'express'; import { Server } from 'http'; import client, { Registry, Counter, Gauge } from 'prom-client'; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('prometheus-client'); diff --git a/packages/backend/src/repoCompileUtils.ts b/packages/backend/src/repoCompileUtils.ts index 04a8b3b5..10c748a8 100644 --- a/packages/backend/src/repoCompileUtils.ts +++ b/packages/backend/src/repoCompileUtils.ts @@ -10,7 +10,7 @@ import { SchemaRepository as BitbucketCloudRepository } from "@coderabbitai/bitb import { CodeHostType, Prisma } from '@sourcebot/db'; import { WithRequired } from "./types.js" import { marshalBool } from "./utils.js"; -import { createLogger } from '@sourcebot/logger'; +import { createLogger } from '@sourcebot/shared'; import { BitbucketConnectionConfig, GerritConnectionConfig, GiteaConnectionConfig, GitlabConnectionConfig, GenericGitHostConnectionConfig, AzureDevOpsConnectionConfig } from '@sourcebot/schemas/v3/connection.type'; import { ProjectVisibility } from "azure-devops-node-api/interfaces/CoreInterfaces.js"; import path from 'path'; diff --git a/packages/backend/src/repoIndexManager.ts b/packages/backend/src/repoIndexManager.ts index 3b98e0e0..cccc1b90 100644 --- a/packages/backend/src/repoIndexManager.ts +++ b/packages/backend/src/repoIndexManager.ts @@ -1,14 +1,13 @@ import * as Sentry from '@sentry/node'; import { PrismaClient, Repo, RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db"; -import { createLogger, Logger } from "@sourcebot/logger"; -import { repoMetadataSchema, RepoIndexingJobMetadata, repoIndexingJobMetadataSchema, RepoMetadata } from '@sourcebot/shared'; +import { createLogger, Logger } from "@sourcebot/shared"; +import { env, RepoIndexingJobMetadata, repoIndexingJobMetadataSchema, RepoMetadata, repoMetadataSchema } from '@sourcebot/shared'; import { existsSync } from 'fs'; import { readdir, rm } from 'fs/promises'; import { Job, Queue, ReservedJob, Worker } from "groupmq"; import { Redis } from 'ioredis'; import micromatch from 'micromatch'; import { INDEX_CACHE_DIR } from './constants.js'; -import { env } from './env.js'; import { cloneRepository, fetchRepository, getBranches, getCommitHashForRefName, getTags, isPathAValidGitRepoRoot, unsetGitConfig, upsertGitConfig } from './git.js'; import { captureEvent } from './posthog.js'; import { PromClient } from './promClient.js'; diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index dd1e7dfa..e85ce766 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -2,7 +2,7 @@ import { Logger } from "winston"; import { RepoAuthCredentials, RepoWithConnections } from "./types.js"; import path from 'path'; import { Repo } from "@sourcebot/db"; -import { getTokenFromConfig } from "@sourcebot/crypto"; +import { getTokenFromConfig } from "@sourcebot/shared"; import * as Sentry from "@sentry/node"; import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig, BitbucketConnectionConfig, AzureDevOpsConnectionConfig } from '@sourcebot/schemas/v3/connection.type'; import { GithubAppManager } from "./ee/githubAppManager.js"; diff --git a/packages/backend/src/zoekt.ts b/packages/backend/src/zoekt.ts index 54ae615e..9f65f473 100644 --- a/packages/backend/src/zoekt.ts +++ b/packages/backend/src/zoekt.ts @@ -1,5 +1,5 @@ import { Repo } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { exec } from "child_process"; import { INDEX_CACHE_DIR } from "./constants.js"; import { Settings } from "./types.js"; diff --git a/packages/crypto/.gitignore b/packages/crypto/.gitignore deleted file mode 100644 index 3a8fe5ed..00000000 --- a/packages/crypto/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.env.local \ No newline at end of file diff --git a/packages/crypto/package.json b/packages/crypto/package.json deleted file mode 100644 index d25e412f..00000000 --- a/packages/crypto/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@sourcebot/crypto", - "version": "0.1.0", - "main": "dist/index.js", - "private": true, - "scripts": { - "build": "tsc", - "postinstall": "yarn build" - }, - "dependencies": { - "@google-cloud/secret-manager": "^6.1.1", - "@sourcebot/db": "*", - "@sourcebot/schemas": "*", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "@types/node": "^22.7.5", - "typescript": "^5.7.3" - } -} diff --git a/packages/crypto/src/environment.ts b/packages/crypto/src/environment.ts deleted file mode 100644 index 8efe296d..00000000 --- a/packages/crypto/src/environment.ts +++ /dev/null @@ -1,13 +0,0 @@ -import dotenv from 'dotenv'; - -export const getEnv = (env: string | undefined, defaultValue?: string) => { - return env ?? defaultValue; -} - -dotenv.config({ - path: './.env.local', - override: true -}); - -// @note: You can use https://generate-random.org/encryption-key-generator to create a new 32 byte key -export const SOURCEBOT_ENCRYPTION_KEY = getEnv(process.env.SOURCEBOT_ENCRYPTION_KEY); \ No newline at end of file diff --git a/packages/crypto/src/tokenUtils.ts b/packages/crypto/src/tokenUtils.ts deleted file mode 100644 index abefa7ef..00000000 --- a/packages/crypto/src/tokenUtils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; -import { Token } from "@sourcebot/schemas/v3/shared.type"; - -export const getTokenFromConfig = async (token: Token): Promise => { - if ('env' in token) { - const envToken = process.env[token.env]; - if (!envToken) { - throw new Error(`Environment variable ${token.env} not found.`); - } - - return envToken; - } else if ('googleCloudSecret' in token) { - try { - const client = new SecretManagerServiceClient(); - const [response] = await client.accessSecretVersion({ - name: token.googleCloudSecret, - }); - - if (!response.payload?.data) { - throw new Error(`Secret ${token.googleCloudSecret} not found.`); - } - - return response.payload.data.toString(); - } catch (error) { - throw new Error(`Failed to access Google Cloud secret ${token.googleCloudSecret}: ${error instanceof Error ? error.message : String(error)}`); - } - } else { - throw new Error('Invalid token configuration'); - } -}; \ No newline at end of file diff --git a/packages/crypto/tsconfig.json b/packages/crypto/tsconfig.json deleted file mode 100644 index b364feca..00000000 --- a/packages/crypto/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "lib": ["ES2023"], - "outDir": "dist", - "rootDir": "src", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "moduleResolution": "Node16", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "isolatedModules": true, - "resolveJsonModule": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] - } - \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json index 8dc07d69..7af80bb4 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -25,7 +25,6 @@ }, "dependencies": { "@prisma/client": "6.2.1", - "@sourcebot/logger": "workspace:*", "@types/readline-sync": "^1.4.8", "readline-sync": "^1.4.10" } diff --git a/packages/db/tools/scriptRunner.ts b/packages/db/tools/scriptRunner.ts index 499a3770..6bb61949 100644 --- a/packages/db/tools/scriptRunner.ts +++ b/packages/db/tools/scriptRunner.ts @@ -3,7 +3,6 @@ import { ArgumentParser } from "argparse"; import { migrateDuplicateConnections } from "./scripts/migrate-duplicate-connections"; import { injectAuditData } from "./scripts/inject-audit-data"; import { confirmAction } from "./utils"; -import { createLogger } from "@sourcebot/logger"; import { injectRepoData } from "./scripts/inject-repo-data"; import { testRepoQueryPerf } from "./scripts/test-repo-query-perf"; @@ -23,19 +22,17 @@ parser.add_argument("--url", { required: true, help: "Database URL" }); parser.add_argument("--script", { required: true, help: "Script to run" }); const args = parser.parse_args(); -const logger = createLogger('db-script-runner'); - (async () => { if (!(args.script in scripts)) { - logger.error("Invalid script"); + console.error("Invalid script"); process.exit(1); } const selectedScript = scripts[args.script]; - logger.info("\nTo confirm:"); - logger.info(`- Database URL: ${args.url}`); - logger.info(`- Script: ${args.script}`); + console.log("\nTo confirm:"); + console.log(`- Database URL: ${args.url}`); + console.log(`- Script: ${args.script}`); confirmAction(); @@ -45,7 +42,7 @@ const logger = createLogger('db-script-runner'); await selectedScript.run(prisma); - logger.info("\nDone."); + console.log("\nDone."); process.exit(0); })(); diff --git a/packages/db/tools/scripts/inject-audit-data.ts b/packages/db/tools/scripts/inject-audit-data.ts index 11f3e8cc..c0c202d9 100644 --- a/packages/db/tools/scripts/inject-audit-data.ts +++ b/packages/db/tools/scripts/inject-audit-data.ts @@ -1,9 +1,6 @@ import { Script } from "../scriptRunner"; import { PrismaClient } from "../../dist"; import { confirmAction } from "../utils"; -import { createLogger } from "@sourcebot/logger"; - -const logger = createLogger('inject-audit-data'); // Generate realistic audit data for analytics testing // Simulates 50 engineers with varying activity patterns @@ -17,11 +14,11 @@ export const injectAuditData: Script = { }); if (!org) { - logger.error(`Organization with id ${orgId} not found. Please create it first.`); + console.error(`Organization with id ${orgId} not found. Please create it first.`); return; } - logger.info(`Injecting audit data for organization: ${org.name} (${org.domain})`); + console.log(`Injecting audit data for organization: ${org.name} (${org.domain})`); // Generate 50 fake user IDs const userIds = Array.from({ length: 50 }, (_, i) => `user_${String(i + 1).padStart(3, '0')}`); @@ -38,7 +35,7 @@ export const injectAuditData: Script = { const startDate = new Date(); startDate.setDate(startDate.getDate() - 90); - logger.info(`Generating data from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]}`); + console.log(`Generating data from ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]}`); confirmAction(); @@ -125,9 +122,9 @@ export const injectAuditData: Script = { } } - logger.info(`\nAudit data injection complete!`); - logger.info(`Users: ${userIds.length}`); - logger.info(`Date range: ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]}`); + console.log(`\nAudit data injection complete!`); + console.log(`Users: ${userIds.length}`); + console.log(`Date range: ${startDate.toISOString().split('T')[0]} to ${endDate.toISOString().split('T')[0]}`); // Show some statistics const stats = await prisma.audit.groupBy({ @@ -136,9 +133,9 @@ export const injectAuditData: Script = { _count: { action: true } }); - logger.info('\nAction breakdown:'); + console.log('\nAction breakdown:'); stats.forEach(stat => { - logger.info(` ${stat.action}: ${stat._count.action}`); + console.log(` ${stat.action}: ${stat._count.action}`); }); }, }; \ No newline at end of file diff --git a/packages/db/tools/scripts/inject-repo-data.ts b/packages/db/tools/scripts/inject-repo-data.ts index 209880f3..bb427cbe 100644 --- a/packages/db/tools/scripts/inject-repo-data.ts +++ b/packages/db/tools/scripts/inject-repo-data.ts @@ -1,8 +1,5 @@ import { Script } from "../scriptRunner"; import { PrismaClient } from "../../dist"; -import { createLogger } from "@sourcebot/logger"; - -const logger = createLogger('inject-repo-data'); const NUM_REPOS = 100000; @@ -35,7 +32,7 @@ export const injectRepoData: Script = { }); - logger.info(`Creating ${NUM_REPOS} repos...`); + console.log(`Creating ${NUM_REPOS} repos...`); for (let i = 0; i < NUM_REPOS; i++) { await prisma.repo.create({ @@ -59,6 +56,6 @@ export const injectRepoData: Script = { }); } - logger.info(`Created ${NUM_REPOS} repos.`); + console.log(`Created ${NUM_REPOS} repos.`); } }; \ No newline at end of file diff --git a/packages/db/tools/scripts/migrate-duplicate-connections.ts b/packages/db/tools/scripts/migrate-duplicate-connections.ts index fe3fa949..7093e429 100644 --- a/packages/db/tools/scripts/migrate-duplicate-connections.ts +++ b/packages/db/tools/scripts/migrate-duplicate-connections.ts @@ -1,9 +1,6 @@ import { Script } from "../scriptRunner"; import { PrismaClient } from "../../dist"; import { confirmAction } from "../utils"; -import { createLogger } from "@sourcebot/logger"; - -const logger = createLogger('migrate-duplicate-connections'); // Handles duplicate connections by renaming them to be unique. // @see: 20250320215449_unique_connection_name_constraint_within_org @@ -18,7 +15,7 @@ export const migrateDuplicateConnections: Script = { }, })).filter(({ _count }) => _count._all > 1); - logger.info(`Found ${duplicates.reduce((acc, { _count }) => acc + _count._all, 0)} duplicate connections.`); + console.log(`Found ${duplicates.reduce((acc, { _count }) => acc + _count._all, 0)} duplicate connections.`); confirmAction(); @@ -40,7 +37,7 @@ export const migrateDuplicateConnections: Script = { const connection = connections[i]; const newName = `${name}-${i + 1}`; - logger.info(`Migrating connection with id ${connection.id} from name=${name} to name=${newName}`); + console.log(`Migrating connection with id ${connection.id} from name=${name} to name=${newName}`); await prisma.connection.update({ where: { id: connection.id }, @@ -50,6 +47,6 @@ export const migrateDuplicateConnections: Script = { } } - logger.info(`Migrated ${migrated} connections.`); + console.log(`Migrated ${migrated} connections.`); }, }; diff --git a/packages/db/tools/scripts/test-repo-query-perf.ts b/packages/db/tools/scripts/test-repo-query-perf.ts index ee07d14f..c41beaca 100644 --- a/packages/db/tools/scripts/test-repo-query-perf.ts +++ b/packages/db/tools/scripts/test-repo-query-perf.ts @@ -1,8 +1,5 @@ import { Script } from "../scriptRunner"; import { PrismaClient } from "../../dist"; -import { createLogger } from "@sourcebot/logger"; - -const logger = createLogger('test-repo-query-perf'); export const testRepoQueryPerf: Script = { run: async (prisma: PrismaClient) => { @@ -23,6 +20,6 @@ export const testRepoQueryPerf: Script = { }); const durationMs = Date.now() - start; - logger.info(`Found ${allRepos.length} repos in ${durationMs}ms`); + console.log(`Found ${allRepos.length} repos in ${durationMs}ms`); } }; \ No newline at end of file diff --git a/packages/db/tools/utils.ts b/packages/db/tools/utils.ts index a096ac9f..ed1886b4 100644 --- a/packages/db/tools/utils.ts +++ b/packages/db/tools/utils.ts @@ -1,17 +1,14 @@ import readline from 'readline-sync'; -import { createLogger } from "@sourcebot/logger"; - -const logger = createLogger('db-utils'); export const confirmAction = (message: string = "Are you sure you want to proceed? [N/y]") => { const response = readline.question(message).toLowerCase(); if (response !== 'y') { - logger.info("Aborted."); + console.log("Aborted."); process.exit(0); } } export const abort = () => { - logger.info("Aborted."); + console.log("Aborted."); process.exit(0); }; diff --git a/packages/error/package.json b/packages/error/package.json deleted file mode 100644 index 8b97d5f1..00000000 --- a/packages/error/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@sourcebot/error", - "main": "dist/index.js", - "version": "0.1.0", - "private": true, - "scripts": { - "build": "tsc", - "postinstall": "yarn build" - }, - "devDependencies": { - "@types/node": "^22.7.5", - "typescript": "^5.7.3" - } -} diff --git a/packages/error/src/index.ts b/packages/error/src/index.ts deleted file mode 100644 index f18f4005..00000000 --- a/packages/error/src/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -export enum BackendError { - CONNECTION_SYNC_SECRET_DNE = 'CONNECTION_SYNC_SECRET_DNE', - CONNECTION_SYNC_INVALID_TOKEN = 'CONNECTION_SYNC_INVALID_TOKEN', - CONNECTION_SYNC_SYSTEM_ERROR = 'CONNECTION_SYNC_SYSTEM_ERROR', - CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS = 'CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS', - CONNECTION_SYNC_CONNECTION_NOT_FOUND = 'CONNECTION_SYNC_CONNECTION_NOT_FOUND', -} - -export class BackendException extends Error { - constructor( - public readonly code: BackendError, - public readonly metadata: Record = {} - ) { - super(code); - this.name = 'BackendException'; - } -} \ No newline at end of file diff --git a/packages/error/tsconfig.json b/packages/error/tsconfig.json deleted file mode 100644 index a27277b9..00000000 --- a/packages/error/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "ES6", - "module": "CommonJS", - "lib": ["ES6"], - "outDir": "dist", - "rootDir": "src", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "moduleResolution": "node", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "isolatedModules": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/logger/.gitignore b/packages/logger/.gitignore deleted file mode 100644 index 96351007..00000000 --- a/packages/logger/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -*.tsbuildinfo \ No newline at end of file diff --git a/packages/logger/package.json b/packages/logger/package.json deleted file mode 100644 index 2e2279a3..00000000 --- a/packages/logger/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@sourcebot/logger", - "version": "0.1.0", - "main": "dist/index.js", - "type": "module", - "private": true, - "scripts": { - "build": "tsc", - "postinstall": "yarn build" - }, - "dependencies": { - "@logtail/node": "^0.5.2", - "@logtail/winston": "^0.5.2", - "@t3-oss/env-core": "^0.12.0", - "dotenv": "^16.4.5", - "triple-beam": "^1.4.1", - "winston": "^3.15.0", - "zod": "^3.24.3" - }, - "devDependencies": { - "@types/node": "^22.7.5", - "typescript": "^5.7.3" - } -} diff --git a/packages/logger/src/env.ts b/packages/logger/src/env.ts deleted file mode 100644 index 5f582e0d..00000000 --- a/packages/logger/src/env.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createEnv } from "@t3-oss/env-core"; -import { z } from "zod"; -import dotenv from 'dotenv'; - -// Booleans are specified as 'true' or 'false' strings. -const booleanSchema = z.enum(["true", "false"]); - -dotenv.config({ - path: './.env', -}); - -dotenv.config({ - path: './.env.local', - override: true -}); - -export const env = createEnv({ - server: { - SOURCEBOT_LOG_LEVEL: z.enum(["info", "debug", "warn", "error"]).default("info"), - SOURCEBOT_STRUCTURED_LOGGING_ENABLED: booleanSchema.default("false"), - SOURCEBOT_STRUCTURED_LOGGING_FILE: z.string().optional(), - LOGTAIL_TOKEN: z.string().optional(), - LOGTAIL_HOST: z.string().url().optional(), - }, - runtimeEnv: process.env, - emptyStringAsUndefined: true, - skipValidation: process.env.SKIP_ENV_VALIDATION === "1", -}); \ No newline at end of file diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json deleted file mode 100644 index 88ae91dd..00000000 --- a/packages/logger/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "lib": ["ES2023"], - "outDir": "dist", - "rootDir": "src", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "isolatedModules": true, - "resolveJsonModule": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} \ No newline at end of file diff --git a/packages/schemas/src/v3/environmentOverrides.schema.ts b/packages/schemas/src/v3/environmentOverrides.schema.ts new file mode 100644 index 00000000..49742827 --- /dev/null +++ b/packages/schemas/src/v3/environmentOverrides.schema.ts @@ -0,0 +1,114 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! +const schema = { + "type": "object", + "description": "Environment variable overrides.", + "title": "EnvironmentOverrides", + "not": { + "$comment": "List of environment variables that are not allowed to be overridden.", + "anyOf": [ + { + "required": [ + "CONFIG_PATH" + ] + } + ] + }, + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "token" + }, + "value": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "number" + }, + "value": { + "type": "number" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "boolean" + }, + "value": { + "type": "boolean" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + } + ] + } + } +} as const; +export { schema as environmentOverridesSchema }; \ No newline at end of file diff --git a/packages/schemas/src/v3/environmentOverrides.type.ts b/packages/schemas/src/v3/environmentOverrides.type.ts new file mode 100644 index 00000000..e9adfaec --- /dev/null +++ b/packages/schemas/src/v3/environmentOverrides.type.ts @@ -0,0 +1,40 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! + +/** + * Environment variable overrides. + */ +export interface EnvironmentOverrides { + /** + * This interface was referenced by `EnvironmentOverrides`'s JSON-Schema definition + * via the `patternProperty` "^[a-zA-Z0-9_-]+$". + */ + [k: string]: + | { + type: "token"; + value: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + } + | { + type: "string"; + value: string; + } + | { + type: "number"; + value: number; + } + | { + type: "boolean"; + value: boolean; + }; +} diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts index ee045243..c7bf374d 100644 --- a/packages/schemas/src/v3/index.schema.ts +++ b/packages/schemas/src/v3/index.schema.ts @@ -278,25 +278,30 @@ const schema = { }, "additionalProperties": false }, - "connections": { + "environmentOverrides": { "type": "object", - "description": "Defines a collection of connections from varying code hosts that Sourcebot should sync with. This is only available in single-tenancy mode.", + "description": "Environment variable overrides.", + "title": "EnvironmentOverrides", + "not": { + "$comment": "List of environment variables that are not allowed to be overridden.", + "anyOf": [ + { + "required": [ + "CONFIG_PATH" + ] + } + ] + }, "patternProperties": { "^[a-zA-Z0-9_-]+$": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ConnectionConfig", "oneOf": [ { - "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "title": "GithubConnectionConfig", "properties": { "type": { - "const": "github", - "description": "GitHub Configuration" + "const": "token" }, - "token": { - "description": "A Personal Access Token (PAT).", + "value": { "anyOf": [ { "type": "object", @@ -325,6 +330,113 @@ const schema = { "additionalProperties": false } ] + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "number" + }, + "value": { + "type": "number" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "boolean" + }, + "value": { + "type": "boolean" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + } + ] + } + } + }, + "connections": { + "type": "object", + "description": "Defines a collection of connections from varying code hosts that Sourcebot should sync with. This is only available in single-tenancy mode.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConnectionConfig", + "oneOf": [ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubConnectionConfig", + "properties": { + "type": { + "const": "github", + "description": "GitHub Configuration" + }, + "token": { + "anyOf": [ + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "googleCloudSecret": { + "type": "string", + "description": "The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets" + } + }, + "required": [ + "googleCloudSecret" + ], + "additionalProperties": false + } + ], + "description": "A Personal Access Token (PAT)." }, "url": { "type": "string", @@ -504,7 +616,6 @@ const schema = { "description": "GitLab Configuration" }, "token": { - "description": "An authentication token.", "anyOf": [ { "type": "object", @@ -532,7 +643,8 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "An authentication token." }, "url": { "type": "string", @@ -706,7 +818,6 @@ const schema = { "description": "Gitea Configuration" }, "token": { - "description": "A Personal Access Token (PAT).", "anyOf": [ { "type": "object", @@ -734,7 +845,8 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "A Personal Access Token (PAT)." }, "url": { "type": "string", @@ -973,7 +1085,6 @@ const schema = { "description": "The username to use for authentication. Only needed if token is an app password." }, "token": { - "description": "An authentication token.", "anyOf": [ { "type": "object", @@ -1001,7 +1112,8 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "An authentication token." }, "url": { "type": "string", @@ -1141,7 +1253,6 @@ const schema = { "description": "Azure DevOps Configuration" }, "token": { - "description": "A Personal Access Token (PAT).", "anyOf": [ { "type": "object", @@ -1169,7 +1280,8 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "A Personal Access Token (PAT)." }, "url": { "type": "string", @@ -1425,7 +1537,6 @@ const schema = { "description": "Optional display name." }, "accessKeyId": { - "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", "anyOf": [ { "type": "object", @@ -1453,10 +1564,10 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable." }, "accessKeySecret": { - "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", "anyOf": [ { "type": "object", @@ -1484,10 +1595,10 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable." }, "sessionToken": { - "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable.", "anyOf": [ { "type": "object", @@ -1515,7 +1626,8 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable." }, "region": { "type": "string", @@ -2854,7 +2966,6 @@ const schema = { "description": "Optional display name." }, "accessKeyId": { - "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable.", "anyOf": [ { "type": "object", @@ -2882,10 +2993,10 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "Optional access key ID to use with the model. Defaults to the `AWS_ACCESS_KEY_ID` environment variable." }, "accessKeySecret": { - "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable.", "anyOf": [ { "type": "object", @@ -2913,10 +3024,10 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "Optional secret access key to use with the model. Defaults to the `AWS_SECRET_ACCESS_KEY` environment variable." }, "sessionToken": { - "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable.", "anyOf": [ { "type": "object", @@ -2944,7 +3055,8 @@ const schema = { ], "additionalProperties": false } - ] + ], + "description": "Optional session token to use with the model. Defaults to the `AWS_SESSION_TOKEN` environment variable." }, "region": { "type": "string", diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts index 0e88d8ab..669a9511 100644 --- a/packages/schemas/src/v3/index.type.ts +++ b/packages/schemas/src/v3/index.type.ts @@ -44,6 +44,7 @@ export interface SourcebotConfig { contexts?: { [k: string]: SearchContext; }; + environmentOverrides?: EnvironmentOverrides; /** * Defines a collection of connections from varying code hosts that Sourcebot should sync with. This is only available in single-tenancy mode. */ @@ -159,6 +160,44 @@ export interface SearchContext { */ description?: string; } +/** + * Environment variable overrides. + */ +export interface EnvironmentOverrides { + /** + * This interface was referenced by `EnvironmentOverrides`'s JSON-Schema definition + * via the `patternProperty` "^[a-zA-Z0-9_-]+$". + */ + [k: string]: + | { + type: "token"; + value: + | { + /** + * The name of the environment variable that contains the token. + */ + env: string; + } + | { + /** + * The resource name of a Google Cloud secret. Must be in the format `projects//secrets//versions/`. See https://cloud.google.com/secret-manager/docs/creating-and-accessing-secrets + */ + googleCloudSecret: string; + }; + } + | { + type: "string"; + value: string; + } + | { + type: "number"; + value: number; + } + | { + type: "boolean"; + value: boolean; + }; +} export interface GithubConnectionConfig { /** * GitHub Configuration diff --git a/packages/shared/package.json b/packages/shared/package.json index 902a3485..23d4463b 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -6,23 +6,28 @@ "scripts": { "build": "tsc", "build:watch": "tsc-watch --preserveWatchOutput", - "postinstall": "yarn build" + "postinstall": "yarn build", + "tool:resolve-env-overrides": "tsx tools/resolveEnvOverrides.ts" }, "dependencies": { - "@sourcebot/crypto": "workspace:*", + "@google-cloud/secret-manager": "^6.1.1", + "@logtail/node": "^0.5.2", + "@logtail/winston": "^0.5.2", "@sourcebot/db": "workspace:*", - "@sourcebot/logger": "workspace:*", "@sourcebot/schemas": "workspace:*", "@t3-oss/env-core": "^0.12.0", "ajv": "^8.17.1", "micromatch": "^4.0.8", "strip-json-comments": "^5.0.1", - "zod": "^3.24.3" + "triple-beam": "^1.4.1", + "winston": "^3.15.0", + "zod": "^3.25.74" }, "devDependencies": { "@types/micromatch": "^4.0.9", "@types/node": "^22.7.5", "tsc-watch": "6.2.1", + "tsx": "^4.19.1", "typescript": "^5.7.3" }, "exports": { diff --git a/packages/crypto/src/index.ts b/packages/shared/src/crypto.ts similarity index 62% rename from packages/crypto/src/index.ts rename to packages/shared/src/crypto.ts index 8f6ca211..7a88e5ab 100644 --- a/packages/crypto/src/index.ts +++ b/packages/shared/src/crypto.ts @@ -1,6 +1,8 @@ import crypto from 'crypto'; import fs from 'fs'; -import { SOURCEBOT_ENCRYPTION_KEY } from './environment'; +import { env } from './env.server.js'; +import { Token } from '@sourcebot/schemas/v3/shared.type'; +import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; const algorithm = 'aes-256-cbc'; const ivLength = 16; // 16 bytes for CBC @@ -12,11 +14,7 @@ const generateIV = (): Buffer => { }; export function encrypt(text: string): { iv: string; encryptedData: string } { - if (!SOURCEBOT_ENCRYPTION_KEY) { - throw new Error('Encryption key is not set'); - } - - const encryptionKey = Buffer.from(SOURCEBOT_ENCRYPTION_KEY, 'ascii'); + const encryptionKey = Buffer.from(env.SOURCEBOT_ENCRYPTION_KEY, 'ascii'); const iv = generateIV(); const cipher = crypto.createCipheriv(algorithm, encryptionKey, iv); @@ -28,18 +26,10 @@ export function encrypt(text: string): { iv: string; encryptedData: string } { } export function hashSecret(text: string): string { - if (!SOURCEBOT_ENCRYPTION_KEY) { - throw new Error('Encryption key is not set'); - } - - return crypto.createHmac('sha256', SOURCEBOT_ENCRYPTION_KEY).update(text).digest('hex'); + return crypto.createHmac('sha256', env.SOURCEBOT_ENCRYPTION_KEY).update(text).digest('hex'); } export function generateApiKey(): { key: string; hash: string } { - if (!SOURCEBOT_ENCRYPTION_KEY) { - throw new Error('Encryption key is not set'); - } - const secret = crypto.randomBytes(32).toString('hex'); const hash = hashSecret(secret); @@ -50,11 +40,7 @@ export function generateApiKey(): { key: string; hash: string } { } export function decrypt(iv: string, encryptedText: string): string { - if (!SOURCEBOT_ENCRYPTION_KEY) { - throw new Error('Encryption key is not set'); - } - - const encryptionKey = Buffer.from(SOURCEBOT_ENCRYPTION_KEY, 'ascii'); + const encryptionKey = Buffer.from(env.SOURCEBOT_ENCRYPTION_KEY, 'ascii'); const ivBuffer = Buffer.from(iv, 'hex'); const encryptedBuffer = Buffer.from(encryptedText, 'hex'); @@ -92,4 +78,30 @@ export function verifySignature(data: string, signature: string, publicKeyPath: } } -export { getTokenFromConfig } from './tokenUtils.js'; \ No newline at end of file +export const getTokenFromConfig = async (token: Token): Promise => { + if ('env' in token) { + const envToken = process.env[token.env]; + if (!envToken) { + throw new Error(`Environment variable ${token.env} not found.`); + } + + return envToken; + } else if ('googleCloudSecret' in token) { + try { + const client = new SecretManagerServiceClient(); + const [response] = await client.accessSecretVersion({ + name: token.googleCloudSecret, + }); + + if (!response.payload?.data) { + throw new Error(`Secret ${token.googleCloudSecret} not found.`); + } + + return response.payload.data.toString(); + } catch (error) { + throw new Error(`Failed to access Google Cloud secret ${token.googleCloudSecret}: ${error instanceof Error ? error.message : String(error)}`); + } + } else { + throw new Error('Invalid token configuration'); + } +}; \ No newline at end of file diff --git a/packages/shared/src/db.ts b/packages/shared/src/db.ts new file mode 100644 index 00000000..b3588829 --- /dev/null +++ b/packages/shared/src/db.ts @@ -0,0 +1,15 @@ +import { env } from "./env.server.js"; + +export const getDBConnectionString = (): string | undefined => { + if (env.DATABASE_URL) { + return env.DATABASE_URL; + } + else if (env.DATABASE_HOST && env.DATABASE_USERNAME && env.DATABASE_PASSWORD && env.DATABASE_NAME) { + let databaseUrl = `postgresql://${env.DATABASE_USERNAME}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}/${env.DATABASE_NAME}`; + if (env.DATABASE_ARGS) { + databaseUrl += `?${env.DATABASE_ARGS}`; + } + + return databaseUrl; + } +} \ No newline at end of file diff --git a/packages/shared/src/entitlements.ts b/packages/shared/src/entitlements.ts index 1c0c688f..e1bba602 100644 --- a/packages/shared/src/entitlements.ts +++ b/packages/shared/src/entitlements.ts @@ -1,9 +1,10 @@ import { base64Decode } from "./utils.js"; import { z } from "zod"; -import { createLogger } from "@sourcebot/logger"; -import { verifySignature } from "@sourcebot/crypto"; -import { env } from "./env.js"; +import { createLogger } from "./logger.js"; +import { env } from "./env.server.js"; +import { env as clientEnv } from "./env.client.js"; import { SOURCEBOT_SUPPORT_EMAIL, SOURCEBOT_UNLIMITED_SEATS } from "./constants.js"; +import { verifySignature } from "./crypto.js"; const logger = createLogger('entitlements'); @@ -89,8 +90,8 @@ export const getLicenseKey = (): LicenseKeyPayload | null => { } export const getPlan = (): Plan => { - if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT) { - if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === "demo") { + if (clientEnv.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT) { + if (clientEnv.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === "demo") { return "cloud:demo"; } diff --git a/packages/shared/src/env.client.ts b/packages/shared/src/env.client.ts new file mode 100644 index 00000000..727feb59 --- /dev/null +++ b/packages/shared/src/env.client.ts @@ -0,0 +1,27 @@ +import { createEnv } from "@t3-oss/env-core"; +import { z } from "zod"; +import { SOURCEBOT_CLOUD_ENVIRONMENT } from "./constants.js"; + +export const env = createEnv({ + clientPrefix: "NEXT_PUBLIC_", + client: { + NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(SOURCEBOT_CLOUD_ENVIRONMENT).optional(), + NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default("unknown"), + NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(), + NEXT_PUBLIC_SENTRY_BACKEND_DSN: z.string().optional(), + NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().optional(), + NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: z.string().optional(), + NEXT_PUBLIC_LANGFUSE_BASE_URL: z.string().optional() + }, + runtimeEnvStrict: { + NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT, + NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, + NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK, + NEXT_PUBLIC_SENTRY_BACKEND_DSN: process.env.NEXT_PUBLIC_SENTRY_BACKEND_DSN, + NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT, + NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, + NEXT_PUBLIC_LANGFUSE_BASE_URL: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL, + }, + emptyStringAsUndefined: true, + skipValidation: process.env.SKIP_ENV_VALIDATION === "1", +}); \ No newline at end of file diff --git a/packages/web/src/env.mjs b/packages/shared/src/env.server.ts similarity index 63% rename from packages/web/src/env.mjs rename to packages/shared/src/env.server.ts index 00b6e736..87e48758 100644 --- a/packages/web/src/env.mjs +++ b/packages/shared/src/env.server.ts @@ -1,16 +1,65 @@ -import { createEnv } from "@t3-oss/env-nextjs"; +import { createEnv } from "@t3-oss/env-core"; import { z } from "zod"; -import { SOURCEBOT_CLOUD_ENVIRONMENT } from "@sourcebot/shared/client"; +import { loadConfig } from "./utils.js"; +import { tenancyModeSchema } from "./types.js"; +import { SourcebotConfig } from "@sourcebot/schemas/v3/index.type"; +import { getTokenFromConfig } from "./crypto.js"; // Booleans are specified as 'true' or 'false' strings. const booleanSchema = z.enum(["true", "false"]); -export const tenancyModeSchema = z.enum(["multi", "single"]); // Numbers are treated as strings in .env files. // coerce helps us convert them to numbers. // @see: https://zod.dev/?id=coercion-for-primitives const numberSchema = z.coerce.number(); +export const resolveEnvironmentVariableOverridesFromConfig = async (config: SourcebotConfig): Promise> => { + if (!config.environmentOverrides) { + return {}; + } + + const resolved: Record = {}; + + const start = performance.now(); + + for (const [key, override] of Object.entries(config.environmentOverrides)) { + switch (override.type) { + case 'token': + resolved[key] = await getTokenFromConfig(override.value); + break; + case 'boolean': + resolved[key] = override.value ? 'true' : 'false'; + break; + case 'number': + resolved[key] = override.value.toString(); + break; + case 'string': + resolved[key] = override.value; + break; + } + } + + const end = performance.now(); + console.debug(`resolved environment variable overrides in ${end - start}ms`); + + return resolved; +} + +// Merge process.env with environment variables resolved from config.json +const runtimeEnv = await (async () => { + const configPath = process.env.CONFIG_PATH; + if (!configPath) { + return process.env; + } + + const config = await loadConfig(configPath); + const overrides = await resolveEnvironmentVariableOverridesFromConfig(config); + return { + ...process.env, + ...overrides, + } +})(); + export const env = createEnv({ server: { // Zoekt @@ -18,7 +67,6 @@ export const env = createEnv({ // Auth FORCE_ENABLE_ANONYMOUS_ACCESS: booleanSchema.default('false'), - AUTH_SECRET: z.string(), AUTH_URL: z.string().url(), AUTH_CREDENTIALS_LOGIN_ENABLED: booleanSchema.default('true'), @@ -72,10 +120,19 @@ export const env = createEnv({ CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(Number.MAX_SAFE_INTEGER), NODE_ENV: z.enum(["development", "test", "production"]), SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'), - DATABASE_URL: z.string().url(), + + // Database variables + // Either DATABASE_URL or DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, and DATABASE_NAME must be set. + // @see: shared/src/db.ts for more details. + DATABASE_URL: z.string().url().optional(), + DATABASE_HOST: z.string().optional(), + DATABASE_USERNAME: z.string().optional(), + DATABASE_PASSWORD: z.string().optional(), + DATABASE_NAME: z.string().optional(), + DATABASE_ARGS: z.string().optional(), SOURCEBOT_TENANCY_MODE: tenancyModeSchema.default("single"), - CONFIG_PATH: z.string().optional(), + CONFIG_PATH: z.string(), // Misc UI flags SECURITY_CARD_ENABLED: booleanSchema.default('false'), @@ -137,31 +194,30 @@ export const env = createEnv({ // @NOTE: Take care to update actions.ts when changing the name of this. EXPERIMENT_SELF_SERVE_REPO_INDEXING_GITHUB_TOKEN: z.string().optional(), EXPERIMENT_EE_PERMISSION_SYNC_ENABLED: booleanSchema.default('false'), - }, - // @NOTE: Please make sure of the following: - // - Make sure you destructure all client variables in - // the `experimental__runtimeEnv` block below. - // - Update the Dockerfile to pass these variables as build-args. - client: { - // PostHog - NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(), - // Misc - NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'), + SOURCEBOT_ENCRYPTION_KEY: z.string(), + SOURCEBOT_INSTALL_ID: z.string().default("unknown"), - NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(SOURCEBOT_CLOUD_ENVIRONMENT).optional(), + FALLBACK_GITHUB_CLOUD_TOKEN: z.string().optional(), + FALLBACK_GITLAB_CLOUD_TOKEN: z.string().optional(), + FALLBACK_GITEA_CLOUD_TOKEN: z.string().optional(), - NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: z.string().optional(), - NEXT_PUBLIC_LANGFUSE_BASE_URL: z.string().optional() + REDIS_URL: z.string().url().default("redis://localhost:6379"), + REDIS_REMOVE_ON_COMPLETE: numberSchema.default(0), + REDIS_REMOVE_ON_FAIL: numberSchema.default(100), + + DEBUG_ENABLE_GROUPMQ_LOGGING: booleanSchema.default('false'), + + CONNECTION_MANAGER_UPSERT_TIMEOUT_MS: numberSchema.default(300000), + REPO_SYNC_RETRY_BASE_SLEEP_SECONDS: numberSchema.default(60), + + GITLAB_CLIENT_QUERY_TIMEOUT_SECONDS: numberSchema.default(60 * 10), + + SOURCEBOT_LOG_LEVEL: z.enum(["info", "debug", "warn", "error"]).default("info"), + SOURCEBOT_STRUCTURED_LOGGING_ENABLED: booleanSchema.default("false"), + SOURCEBOT_STRUCTURED_LOGGING_FILE: z.string().optional(), }, - // For Next.js >= 13.4.4, you only need to destructure client variables: - experimental__runtimeEnv: { - NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK, - NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, - NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT, - NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, - NEXT_PUBLIC_LANGFUSE_BASE_URL: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL, - }, - skipValidation: process.env.SKIP_ENV_VALIDATION === "1", + runtimeEnv, emptyStringAsUndefined: true, + skipValidation: process.env.SKIP_ENV_VALIDATION === "1", }); \ No newline at end of file diff --git a/packages/shared/src/env.ts b/packages/shared/src/env.ts deleted file mode 100644 index c1162923..00000000 --- a/packages/shared/src/env.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createEnv } from "@t3-oss/env-core"; -import { z } from "zod"; -import { SOURCEBOT_CLOUD_ENVIRONMENT } from "./constants.js"; - -export const env = createEnv({ - server: { - SOURCEBOT_EE_LICENSE_KEY: z.string().optional(), - SOURCEBOT_PUBLIC_KEY_PATH: z.string(), - }, - client: { - NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(SOURCEBOT_CLOUD_ENVIRONMENT).optional(), - }, - clientPrefix: "NEXT_PUBLIC_", - runtimeEnvStrict: { - SOURCEBOT_EE_LICENSE_KEY: process.env.SOURCEBOT_EE_LICENSE_KEY, - SOURCEBOT_PUBLIC_KEY_PATH: process.env.SOURCEBOT_PUBLIC_KEY_PATH, - NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT, - }, - emptyStringAsUndefined: true, - skipValidation: process.env.SKIP_ENV_VALIDATION === "1", -}); \ No newline at end of file diff --git a/packages/shared/src/index.client.ts b/packages/shared/src/index.client.ts index ca2bfad2..8d185ba6 100644 --- a/packages/shared/src/index.client.ts +++ b/packages/shared/src/index.client.ts @@ -1,2 +1,4 @@ - -export * from "./constants.js"; \ No newline at end of file +export * from "./constants.js"; +export { + env +} from "./env.client.js"; \ No newline at end of file diff --git a/packages/shared/src/index.server.ts b/packages/shared/src/index.server.ts index f3303c14..fabe608e 100644 --- a/packages/shared/src/index.server.ts +++ b/packages/shared/src/index.server.ts @@ -16,6 +16,7 @@ export type { export { repoMetadataSchema, repoIndexingJobMetadataSchema, + tenancyModeSchema, } from "./types.js"; export { base64Decode, @@ -24,4 +25,25 @@ export { isRemotePath, getConfigSettings, } from "./utils.js"; -export * from "./constants.js"; \ No newline at end of file +export * from "./constants.js"; +export { + env, + resolveEnvironmentVariableOverridesFromConfig, +} from "./env.server.js"; +export { + createLogger, +} from "./logger.js"; +export type { + Logger, +} from "./logger.js"; +export { + getTokenFromConfig, + encrypt, + decrypt, + hashSecret, + generateApiKey, + verifySignature, +} from "./crypto.js"; +export { + getDBConnectionString, +} from "./db.js"; \ No newline at end of file diff --git a/packages/logger/src/index.ts b/packages/shared/src/logger.ts similarity index 95% rename from packages/logger/src/index.ts rename to packages/shared/src/logger.ts index 635c8b3c..a3f89e2c 100644 --- a/packages/logger/src/index.ts +++ b/packages/shared/src/logger.ts @@ -2,7 +2,7 @@ import winston, { format, Logger } from 'winston'; import { Logtail } from '@logtail/node'; import { LogtailTransport } from '@logtail/winston'; import { MESSAGE } from 'triple-beam'; -import { env } from './env.js'; +import { env } from './env.server.js'; /** * Logger configuration with support for structured JSON logging. @@ -16,7 +16,7 @@ import { env } from './env.js'; * - Logs will be formatted as: "timestamp level: [label] message" */ -const { combine, colorize, timestamp, prettyPrint, errors, printf, label: labelFn, json } = format; +const { combine, colorize, timestamp, errors, printf, label: labelFn, json } = format; const datadogFormat = format((info) => { info.status = info.level.toLowerCase(); diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index a03b2e9d..0d5fb520 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -43,3 +43,5 @@ export const repoIndexingJobMetadataSchema = z.object({ }); export type RepoIndexingJobMetadata = z.infer; + +export const tenancyModeSchema = z.enum(["multi", "single"]); \ No newline at end of file diff --git a/packages/shared/tools/resolveEnvOverrides.ts b/packages/shared/tools/resolveEnvOverrides.ts new file mode 100644 index 00000000..def001c3 --- /dev/null +++ b/packages/shared/tools/resolveEnvOverrides.ts @@ -0,0 +1,30 @@ +// The following script loads the config.json file and resolves any environment variable overrides. +// It then writes then to stdout in the format of `KEY="VALUE"`. +// This is used by entrypoint.sh to set them as variables. +(async () => { + if (!process.env.CONFIG_PATH) { + console.error('CONFIG_PATH is not set'); + process.exit(1); + } + + // Silence all console logs so we don't pollute stdout. + const originalConsoleLog = console.log; + console.log = () => {}; + console.debug = () => {}; + console.info = () => {}; + console.warn = () => {}; + // console.error = () => {}; // Keep errors + + const { loadConfig } = await import("../src/utils.js"); + const { resolveEnvironmentVariableOverridesFromConfig } = await import("../src/env.server.js"); + + const config = await loadConfig(process.env.CONFIG_PATH); + const overrides = await resolveEnvironmentVariableOverridesFromConfig(config); + + for (const [key, value] of Object.entries(overrides)) { + const escapedValue = value.replace(/"/g, '\\"'); + originalConsoleLog(`${key}="${escapedValue}"`); + } + + process.exit(0); +})(); \ No newline at end of file diff --git a/packages/web/next.config.mjs b/packages/web/next.config.mjs index 2ab0b642..d344d121 100644 --- a/packages/web/next.config.mjs +++ b/packages/web/next.config.mjs @@ -1,4 +1,3 @@ -await import("./src/env.mjs"); import { withSentryConfig } from "@sentry/nextjs"; @@ -8,7 +7,7 @@ const nextConfig = { // This is required when using standalone builds. // @see: https://env.t3.gg/docs/nextjs#create-your-schema - transpilePackages: ["@t3-oss/env-nextjs", "@t3-oss/env-core"], + transpilePackages: ["@t3-oss/env-core"], // @see : https://posthog.com/docs/advanced/proxy/nextjs async rewrites() { diff --git a/packages/web/package.json b/packages/web/package.json index a7a798ab..ba3c3dbc 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -90,16 +90,12 @@ "@sentry/nextjs": "^9", "@shopify/lang-jsonc": "^1.0.0", "@sourcebot/codemirror-lang-tcl": "^1.0.12", - "@sourcebot/crypto": "workspace:*", "@sourcebot/db": "workspace:*", - "@sourcebot/error": "workspace:*", - "@sourcebot/logger": "workspace:*", "@sourcebot/schemas": "workspace:*", "@sourcebot/shared": "workspace:*", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "@stripe/react-stripe-js": "^3.1.1", "@stripe/stripe-js": "^5.6.0", - "@t3-oss/env-nextjs": "^0.12.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.53.3", "@tanstack/react-table": "^8.20.5", diff --git a/packages/web/sentry.server.config.ts b/packages/web/sentry.server.config.ts index 548160c8..c9416bc6 100644 --- a/packages/web/sentry.server.config.ts +++ b/packages/web/sentry.server.config.ts @@ -3,7 +3,7 @@ // https://docs.sentry.io/platforms/javascript/guides/nextjs/ import * as Sentry from "@sentry/nextjs"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('sentry-server-config'); diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts index 4ebfd552..e194f808 100644 --- a/packages/web/src/actions.ts +++ b/packages/web/src/actions.ts @@ -1,7 +1,7 @@ 'use server'; import { getAuditService } from "@/ee/features/audit/factory"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { addUserToOrganization, orgHasAvailability } from "@/lib/authUtils"; import { ErrorCode } from "@/lib/errorCodes"; import { notAuthenticated, notFound, orgNotFound, ServiceError, ServiceErrorException, unexpectedError } from "@/lib/serviceError"; @@ -9,9 +9,9 @@ import { getOrgMetadata, isHttpError, isServiceError } from "@/lib/utils"; import { prisma } from "@/prisma"; import { render } from "@react-email/components"; import * as Sentry from '@sentry/nextjs'; -import { generateApiKey, getTokenFromConfig, hashSecret } from "@sourcebot/crypto"; +import { generateApiKey, getTokenFromConfig, hashSecret } from "@sourcebot/shared"; import { ApiKey, ConnectionSyncJobStatus, Org, OrgRole, Prisma, RepoIndexingJobStatus, RepoIndexingJobType, StripeSubscriptionStatus } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type"; import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type"; diff --git a/packages/web/src/app/[domain]/agents/page.tsx b/packages/web/src/app/[domain]/agents/page.tsx index 1da98ff2..fd564268 100644 --- a/packages/web/src/app/[domain]/agents/page.tsx +++ b/packages/web/src/app/[domain]/agents/page.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import { NavigationMenu } from "../components/navigationMenu"; import { FaCogs } from "react-icons/fa"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; const agents = [ { diff --git a/packages/web/src/app/[domain]/chat/page.tsx b/packages/web/src/app/[domain]/chat/page.tsx index 5b7afef2..5317328d 100644 --- a/packages/web/src/app/[domain]/chat/page.tsx +++ b/packages/web/src/app/[domain]/chat/page.tsx @@ -9,7 +9,7 @@ import { RepositoryCarousel } from "../components/repositoryCarousel"; import { NavigationMenu } from "../components/navigationMenu"; import { Separator } from "@/components/ui/separator"; import { DemoCards } from "./components/demoCards"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { loadJsonFile } from "@sourcebot/shared"; import { DemoExamples, demoExamplesSchema } from "@/types"; diff --git a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx index f350fe92..66c7c36f 100644 --- a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx +++ b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx @@ -6,7 +6,7 @@ import { NavigationMenu as NavigationMenuBase } from "@/components/ui/navigation import { Separator } from "@/components/ui/separator"; import { getSubscriptionInfo } from "@/ee/features/billing/actions"; import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { ServiceErrorException } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; import { DiscordLogoIcon, GitHubLogoIcon } from "@radix-ui/react-icons"; diff --git a/packages/web/src/app/[domain]/components/settingsDropdown.tsx b/packages/web/src/app/[domain]/components/settingsDropdown.tsx index 92a5705a..ec8ad0f0 100644 --- a/packages/web/src/app/[domain]/components/settingsDropdown.tsx +++ b/packages/web/src/app/[domain]/components/settingsDropdown.tsx @@ -32,7 +32,7 @@ import { useKeymapType } from "@/hooks/useKeymapType" import { useSession } from "next-auth/react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { signOut } from "next-auth/react" -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared/client"; import posthog from "posthog-js"; import { useDomain } from "@/hooks/useDomain"; diff --git a/packages/web/src/app/[domain]/layout.tsx b/packages/web/src/app/[domain]/layout.tsx index a3079d7f..de506b40 100644 --- a/packages/web/src/app/[domain]/layout.tsx +++ b/packages/web/src/app/[domain]/layout.tsx @@ -16,7 +16,7 @@ import { getSubscriptionInfo } from "@/ee/features/billing/actions"; import { PendingApprovalCard } from "./components/pendingApproval"; import { SubmitJoinRequest } from "./components/submitJoinRequest"; import { hasEntitlement } from "@sourcebot/shared"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { GcpIapAuth } from "./components/gcpIapAuth"; import { getAnonymousAccessStatus, getMemberApprovalRequired } from "@/actions"; import { JoinOrganizationCard } from "@/app/components/joinOrganizationCard"; diff --git a/packages/web/src/app/[domain]/repos/[id]/page.tsx b/packages/web/src/app/[domain]/repos/[id]/page.tsx index a3255c04..8986f7f6 100644 --- a/packages/web/src/app/[domain]/repos/[id]/page.tsx +++ b/packages/web/src/app/[domain]/repos/[id]/page.tsx @@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Skeleton } from "@/components/ui/skeleton" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" -import { env } from "@/env.mjs" +import { env } from "@sourcebot/shared" import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants" import { ServiceErrorException } from "@/lib/serviceError" import { cn, getCodeHostInfoForRepo, isServiceError } from "@/lib/utils" diff --git a/packages/web/src/app/[domain]/settings/connections/[id]/page.tsx b/packages/web/src/app/[domain]/settings/connections/[id]/page.tsx index c36a3342..4cebd7db 100644 --- a/packages/web/src/app/[domain]/settings/connections/[id]/page.tsx +++ b/packages/web/src/app/[domain]/settings/connections/[id]/page.tsx @@ -4,7 +4,7 @@ import { DisplayDate } from "@/app/[domain]/components/DisplayDate"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"; import { notFound, ServiceErrorException } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; diff --git a/packages/web/src/app/[domain]/settings/layout.tsx b/packages/web/src/app/[domain]/settings/layout.tsx index b0aa8f5b..0b508903 100644 --- a/packages/web/src/app/[domain]/settings/layout.tsx +++ b/packages/web/src/app/[domain]/settings/layout.tsx @@ -10,8 +10,8 @@ import { getConnectionStats, getMe, getOrgAccountRequests } from "@/actions"; import { ServiceErrorException } from "@/lib/serviceError"; import { getOrgFromDomain } from "@/data/org"; import { OrgRole } from "@prisma/client"; -import { env } from "@/env.mjs"; import { hasEntitlement } from "@sourcebot/shared"; +import { env } from "@sourcebot/shared/client"; interface LayoutProps { children: React.ReactNode; diff --git a/packages/web/src/app/[domain]/settings/license/page.tsx b/packages/web/src/app/[domain]/settings/license/page.tsx index 7c6be35e..3e9df37d 100644 --- a/packages/web/src/app/[domain]/settings/license/page.tsx +++ b/packages/web/src/app/[domain]/settings/license/page.tsx @@ -4,7 +4,7 @@ import { Info, Mail } from "lucide-react"; import { getOrgMembers } from "@/actions"; import { isServiceError } from "@/lib/utils"; import { notFound, ServiceErrorException } from "@/lib/serviceError"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared/client"; interface LicensePageProps { params: Promise<{ diff --git a/packages/web/src/app/[domain]/upgrade/page.tsx b/packages/web/src/app/[domain]/upgrade/page.tsx index ed931e8d..3d4e0e30 100644 --- a/packages/web/src/app/[domain]/upgrade/page.tsx +++ b/packages/web/src/app/[domain]/upgrade/page.tsx @@ -8,7 +8,7 @@ import { isServiceError } from "@/lib/utils"; import Link from "next/link"; import { ArrowLeftIcon } from "@radix-ui/react-icons"; import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; import { getSubscriptionInfo } from "@/ee/features/billing/actions"; diff --git a/packages/web/src/app/api/(server)/chat/route.ts b/packages/web/src/app/api/(server)/chat/route.ts index 8f045305..1abc1a1c 100644 --- a/packages/web/src/app/api/(server)/chat/route.ts +++ b/packages/web/src/app/api/(server)/chat/route.ts @@ -10,7 +10,7 @@ import { prisma } from "@/prisma"; import { LanguageModelV2 as AISDKLanguageModelV2 } from "@ai-sdk/provider"; import * as Sentry from "@sentry/nextjs"; import { OrgRole } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { createUIMessageStream, createUIMessageStreamResponse, diff --git a/packages/web/src/app/api/(server)/ee/audit/route.ts b/packages/web/src/app/api/(server)/ee/audit/route.ts index 80a05e2e..c1b8c86c 100644 --- a/packages/web/src/app/api/(server)/ee/audit/route.ts +++ b/packages/web/src/app/api/(server)/ee/audit/route.ts @@ -6,7 +6,7 @@ import { isServiceError } from "@/lib/utils"; import { serviceErrorResponse } from "@/lib/serviceError"; import { StatusCodes } from "http-status-codes"; import { ErrorCode } from "@/lib/errorCodes"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { getEntitlements } from "@sourcebot/shared"; export const GET = async (request: NextRequest) => { diff --git a/packages/web/src/app/api/(server)/ee/user/route.ts b/packages/web/src/app/api/(server)/ee/user/route.ts index e308924b..539f4e7c 100644 --- a/packages/web/src/app/api/(server)/ee/user/route.ts +++ b/packages/web/src/app/api/(server)/ee/user/route.ts @@ -4,7 +4,7 @@ import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2"; import { OrgRole } from "@sourcebot/db"; import { isServiceError } from "@/lib/utils"; import { serviceErrorResponse, missingQueryParam, notFound } from "@/lib/serviceError"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { NextRequest } from "next/server"; import { StatusCodes } from "http-status-codes"; import { ErrorCode } from "@/lib/errorCodes"; diff --git a/packages/web/src/app/api/(server)/ee/users/route.ts b/packages/web/src/app/api/(server)/ee/users/route.ts index cc37c419..e77edb18 100644 --- a/packages/web/src/app/api/(server)/ee/users/route.ts +++ b/packages/web/src/app/api/(server)/ee/users/route.ts @@ -4,7 +4,7 @@ import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2"; import { OrgRole } from "@sourcebot/db"; import { isServiceError } from "@/lib/utils"; import { serviceErrorResponse } from "@/lib/serviceError"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { getAuditService } from "@/ee/features/audit/factory"; const logger = createLogger('ee-users-api'); diff --git a/packages/web/src/app/api/(server)/health/route.ts b/packages/web/src/app/api/(server)/health/route.ts index ac1a2ba1..24b69876 100644 --- a/packages/web/src/app/api/(server)/health/route.ts +++ b/packages/web/src/app/api/(server)/health/route.ts @@ -1,6 +1,6 @@ 'use server'; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('health-check'); diff --git a/packages/web/src/app/api/(server)/stripe/route.ts b/packages/web/src/app/api/(server)/stripe/route.ts index 84b67114..21286043 100644 --- a/packages/web/src/app/api/(server)/stripe/route.ts +++ b/packages/web/src/app/api/(server)/stripe/route.ts @@ -4,8 +4,8 @@ import Stripe from 'stripe'; import { prisma } from '@/prisma'; import { StripeSubscriptionStatus } from '@sourcebot/db'; import { stripeClient } from '@/ee/features/billing/stripe'; -import { env } from '@/env.mjs'; -import { createLogger } from "@sourcebot/logger"; +import { env } from '@sourcebot/shared'; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('stripe-webhook'); diff --git a/packages/web/src/app/api/(server)/version/route.ts b/packages/web/src/app/api/(server)/version/route.ts index 750955de..08008e76 100644 --- a/packages/web/src/app/api/(server)/version/route.ts +++ b/packages/web/src/app/api/(server)/version/route.ts @@ -1,4 +1,4 @@ -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared/client"; import { GetVersionResponse } from "@/lib/types"; // Note: In Next.JS 14, GET methods with no params are cached by default at build time. diff --git a/packages/web/src/app/api/(server)/webhook/route.ts b/packages/web/src/app/api/(server)/webhook/route.ts index ade6e54d..cfd01a25 100644 --- a/packages/web/src/app/api/(server)/webhook/route.ts +++ b/packages/web/src/app/api/(server)/webhook/route.ts @@ -4,12 +4,12 @@ import { NextRequest } from "next/server"; import { App, Octokit } from "octokit"; import { WebhookEventDefinition} from "@octokit/webhooks/types"; import { EndpointDefaults } from "@octokit/types"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { processGitHubPullRequest } from "@/features/agents/review-agent/app"; import { throttling } from "@octokit/plugin-throttling"; import fs from "fs"; import { GitHubPullRequest } from "@/features/agents/review-agent/types"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('github-webhook'); diff --git a/packages/web/src/app/components/authSecurityNotice.tsx b/packages/web/src/app/components/authSecurityNotice.tsx index e903383f..2bdda7a8 100644 --- a/packages/web/src/app/components/authSecurityNotice.tsx +++ b/packages/web/src/app/components/authSecurityNotice.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { useState, useEffect } from "react"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared/client"; interface AuthSecurityNoticeProps { closable?: boolean; diff --git a/packages/web/src/app/components/organizationAccessSettings.tsx b/packages/web/src/app/components/organizationAccessSettings.tsx index e240a374..70528164 100644 --- a/packages/web/src/app/components/organizationAccessSettings.tsx +++ b/packages/web/src/app/components/organizationAccessSettings.tsx @@ -6,7 +6,7 @@ import { getOrgMetadata } from "@/lib/utils" import { headers } from "next/headers" import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants" import { hasEntitlement } from "@sourcebot/shared" -import { env } from "@/env.mjs" +import { env } from "@sourcebot/shared" export async function OrganizationAccessSettings() { const org = await getOrgFromDomain(SINGLE_TENANT_ORG_DOMAIN); diff --git a/packages/web/src/app/layout.tsx b/packages/web/src/app/layout.tsx index 441e5e58..4249c00f 100644 --- a/packages/web/src/app/layout.tsx +++ b/packages/web/src/app/layout.tsx @@ -6,7 +6,7 @@ import { PostHogProvider } from "./posthogProvider"; import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import { SessionProvider } from "next-auth/react"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { PlanProvider } from "@/features/entitlements/planProvider"; import { getEntitlements } from "@sourcebot/shared"; diff --git a/packages/web/src/app/onboard/page.tsx b/packages/web/src/app/onboard/page.tsx index 33077a9b..5f22cddd 100644 --- a/packages/web/src/app/onboard/page.tsx +++ b/packages/web/src/app/onboard/page.tsx @@ -17,7 +17,7 @@ import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch"; import { redirect } from "next/navigation"; import { BetweenHorizontalStart, Brain, GitBranchIcon, LockIcon } from "lucide-react"; import { hasEntitlement } from "@sourcebot/shared"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { GcpIapAuth } from "@/app/[domain]/components/gcpIapAuth"; interface OnboardingProps { diff --git a/packages/web/src/app/posthogProvider.tsx b/packages/web/src/app/posthogProvider.tsx index 44da24ea..8f3bccc7 100644 --- a/packages/web/src/app/posthogProvider.tsx +++ b/packages/web/src/app/posthogProvider.tsx @@ -4,7 +4,7 @@ import { usePostHog } from 'posthog-js/react' import { PostHogProvider as PHProvider } from 'posthog-js/react' import { usePathname, useSearchParams } from "next/navigation" import { Suspense, useEffect } from "react" -import { env } from '@/env.mjs' +import { env } from '@sourcebot/shared/client' import { useSession } from 'next-auth/react' import { captureEvent } from '@/hooks/useCaptureEvent' diff --git a/packages/web/src/app/signup/page.tsx b/packages/web/src/app/signup/page.tsx index fd00c87d..fe84e3d9 100644 --- a/packages/web/src/app/signup/page.tsx +++ b/packages/web/src/app/signup/page.tsx @@ -2,8 +2,8 @@ import { auth } from "@/auth"; import { LoginForm } from "../login/components/loginForm"; import { redirect } from "next/navigation"; import { Footer } from "@/app/components/footer"; -import { createLogger } from "@sourcebot/logger"; import { getIdentityProviderMetadata } from "@/lib/identityProviders"; +import { createLogger } from "@sourcebot/shared"; import { getOrgFromDomain } from "@/data/org"; import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants"; diff --git a/packages/web/src/auth.ts b/packages/web/src/auth.ts index 64dab588..f61219d7 100644 --- a/packages/web/src/auth.ts +++ b/packages/web/src/auth.ts @@ -4,7 +4,7 @@ import Credentials from "next-auth/providers/credentials" import EmailProvider from "next-auth/providers/nodemailer"; import { PrismaAdapter } from "@auth/prisma-adapter" import { prisma } from "@/prisma"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { User } from '@sourcebot/db'; import 'next-auth/jwt'; import type { Provider } from "next-auth/providers"; diff --git a/packages/web/src/ee/features/audit/actions.ts b/packages/web/src/ee/features/audit/actions.ts index 57455cb0..60814946 100644 --- a/packages/web/src/ee/features/audit/actions.ts +++ b/packages/web/src/ee/features/audit/actions.ts @@ -5,7 +5,7 @@ import { ErrorCode } from "@/lib/errorCodes"; import { StatusCodes } from "http-status-codes"; import { sew, withAuth, withOrgMembership } from "@/actions"; import { OrgRole } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { ServiceError } from "@/lib/serviceError"; import { getAuditService } from "@/ee/features/audit/factory"; import { AuditEvent } from "./types"; diff --git a/packages/web/src/ee/features/audit/auditService.ts b/packages/web/src/ee/features/audit/auditService.ts index 09cc647b..a0bee39a 100644 --- a/packages/web/src/ee/features/audit/auditService.ts +++ b/packages/web/src/ee/features/audit/auditService.ts @@ -1,7 +1,7 @@ import { IAuditService, AuditEvent } from '@/ee/features/audit/types'; import { prisma } from '@/prisma'; import { Audit } from '@prisma/client'; -import { createLogger } from '@sourcebot/logger'; +import { createLogger } from '@sourcebot/shared'; const logger = createLogger('audit-service'); diff --git a/packages/web/src/ee/features/audit/factory.ts b/packages/web/src/ee/features/audit/factory.ts index 5fe7d0f4..9a2a4300 100644 --- a/packages/web/src/ee/features/audit/factory.ts +++ b/packages/web/src/ee/features/audit/factory.ts @@ -2,7 +2,7 @@ import { IAuditService } from '@/ee/features/audit/types'; import { MockAuditService } from '@/ee/features/audit/mockAuditService'; import { AuditService } from '@/ee/features/audit/auditService'; import { hasEntitlement } from '@sourcebot/shared'; -import { env } from '@/env.mjs'; +import { env } from '@sourcebot/shared'; let enterpriseService: IAuditService | undefined; diff --git a/packages/web/src/ee/features/billing/actions.ts b/packages/web/src/ee/features/billing/actions.ts index 48b11581..d66e94ee 100644 --- a/packages/web/src/ee/features/billing/actions.ts +++ b/packages/web/src/ee/features/billing/actions.ts @@ -7,12 +7,12 @@ import { prisma } from "@/prisma"; import { OrgRole } from "@sourcebot/db"; import { stripeClient } from "./stripe"; import { isServiceError } from "@/lib/utils"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { StatusCodes } from "http-status-codes"; import { ErrorCode } from "@/lib/errorCodes"; import { headers } from "next/headers"; import { getSubscriptionForOrg } from "./serverUtils"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('billing-actions'); diff --git a/packages/web/src/ee/features/billing/stripe.ts b/packages/web/src/ee/features/billing/stripe.ts index efc69668..c8ca0af7 100644 --- a/packages/web/src/ee/features/billing/stripe.ts +++ b/packages/web/src/ee/features/billing/stripe.ts @@ -1,5 +1,5 @@ import 'server-only'; -import { env } from '@/env.mjs' +import { env } from '@sourcebot/shared' import Stripe from "stripe"; import { hasEntitlement } from '@sourcebot/shared'; diff --git a/packages/web/src/ee/features/permissionSyncing/actions.ts b/packages/web/src/ee/features/permissionSyncing/actions.ts index 95667027..a52b0c3e 100644 --- a/packages/web/src/ee/features/permissionSyncing/actions.ts +++ b/packages/web/src/ee/features/permissionSyncing/actions.ts @@ -1,10 +1,9 @@ 'use server'; import { sew } from "@/actions"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger, env } from "@sourcebot/shared"; import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2"; import { loadConfig } from "@sourcebot/shared"; -import { env } from "@/env.mjs"; import { OrgRole } from "@sourcebot/db"; import { cookies } from "next/headers"; import { OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME } from "@/lib/constants"; diff --git a/packages/web/src/ee/features/permissionSyncing/tokenRefresh.ts b/packages/web/src/ee/features/permissionSyncing/tokenRefresh.ts index f6ebef06..7d2c69eb 100644 --- a/packages/web/src/ee/features/permissionSyncing/tokenRefresh.ts +++ b/packages/web/src/ee/features/permissionSyncing/tokenRefresh.ts @@ -1,7 +1,5 @@ import { loadConfig } from "@sourcebot/shared"; -import { env } from "@/env.mjs"; -import { createLogger } from "@sourcebot/logger"; -import { getTokenFromConfig } from '@sourcebot/crypto'; +import { getTokenFromConfig, createLogger, env } from "@sourcebot/shared"; import { GitHubIdentityProviderConfig, GitLabIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type"; import { LinkedAccountTokensMap } from "@/auth" const { prisma } = await import('@/prisma'); diff --git a/packages/web/src/ee/features/sso/sso.ts b/packages/web/src/ee/features/sso/sso.ts index 9a5153ff..a0fe415c 100644 --- a/packages/web/src/ee/features/sso/sso.ts +++ b/packages/web/src/ee/features/sso/sso.ts @@ -1,21 +1,18 @@ -import { env } from "@/env.mjs"; -import GitHub from "next-auth/providers/github"; -import Google from "next-auth/providers/google"; -import Okta from "next-auth/providers/okta"; -import Keycloak from "next-auth/providers/keycloak"; -import Gitlab from "next-auth/providers/gitlab"; -import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id"; +import type { IdentityProvider } from "@/auth"; +import { onCreateUser } from "@/lib/authUtils"; import { prisma } from "@/prisma"; +import { GCPIAPIdentityProviderConfig, GitHubIdentityProviderConfig, GitLabIdentityProviderConfig, GoogleIdentityProviderConfig, KeycloakIdentityProviderConfig, MicrosoftEntraIDIdentityProviderConfig, OktaIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type"; +import { createLogger, env, getTokenFromConfig, hasEntitlement, loadConfig } from "@sourcebot/shared"; import { OAuth2Client } from "google-auth-library"; -import Credentials from "next-auth/providers/credentials"; import type { User as AuthJsUser } from "next-auth"; import type { Provider } from "next-auth/providers"; -import { onCreateUser } from "@/lib/authUtils"; -import { createLogger } from "@sourcebot/logger"; -import { hasEntitlement, loadConfig } from "@sourcebot/shared"; -import { getTokenFromConfig } from "@sourcebot/crypto"; -import type { IdentityProvider } from "@/auth"; -import { GCPIAPIdentityProviderConfig, GitHubIdentityProviderConfig, GitLabIdentityProviderConfig, GoogleIdentityProviderConfig, KeycloakIdentityProviderConfig, MicrosoftEntraIDIdentityProviderConfig, OktaIdentityProviderConfig } from "@sourcebot/schemas/v3/index.type"; +import Credentials from "next-auth/providers/credentials"; +import GitHub from "next-auth/providers/github"; +import Gitlab from "next-auth/providers/gitlab"; +import Google from "next-auth/providers/google"; +import Keycloak from "next-auth/providers/keycloak"; +import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id"; +import Okta from "next-auth/providers/okta"; const logger = createLogger('web-sso'); diff --git a/packages/web/src/features/agents/review-agent/app.ts b/packages/web/src/features/agents/review-agent/app.ts index 0ea2af8f..80d5a2f3 100644 --- a/packages/web/src/features/agents/review-agent/app.ts +++ b/packages/web/src/features/agents/review-agent/app.ts @@ -2,11 +2,11 @@ import { Octokit } from "octokit"; import { generatePrReviews } from "@/features/agents/review-agent/nodes/generatePrReview"; import { githubPushPrReviews } from "@/features/agents/review-agent/nodes/githubPushPrReviews"; import { githubPrParser } from "@/features/agents/review-agent/nodes/githubPrParser"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import { GitHubPullRequest } from "@/features/agents/review-agent/types"; import path from "path"; import fs from "fs"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const rules = [ "Do NOT provide general feedback, summaries, explanations of changes, or praises for making good additions.", diff --git a/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts b/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts index 7617c959..d9903b72 100644 --- a/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts +++ b/packages/web/src/features/agents/review-agent/nodes/fetchFileContent.ts @@ -2,7 +2,7 @@ import { sourcebot_context, sourcebot_pr_payload } from "@/features/agents/revie import { getFileSource } from "@/features/search/fileSourceApi"; import { fileSourceResponseSchema } from "@/features/search/schemas"; import { isServiceError } from "@/lib/utils"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('fetch-file-content'); diff --git a/packages/web/src/features/agents/review-agent/nodes/generateDiffReviewPrompt.ts b/packages/web/src/features/agents/review-agent/nodes/generateDiffReviewPrompt.ts index 59060408..f5eda1ca 100644 --- a/packages/web/src/features/agents/review-agent/nodes/generateDiffReviewPrompt.ts +++ b/packages/web/src/features/agents/review-agent/nodes/generateDiffReviewPrompt.ts @@ -1,6 +1,6 @@ import { sourcebot_diff, sourcebot_context, sourcebot_file_diff_review_schema } from "@/features/agents/review-agent/types"; import { zodToJsonSchema } from "zod-to-json-schema"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('generate-diff-review-prompt'); diff --git a/packages/web/src/features/agents/review-agent/nodes/generatePrReview.ts b/packages/web/src/features/agents/review-agent/nodes/generatePrReview.ts index 48f6bda0..3e1fb0ed 100644 --- a/packages/web/src/features/agents/review-agent/nodes/generatePrReview.ts +++ b/packages/web/src/features/agents/review-agent/nodes/generatePrReview.ts @@ -2,7 +2,7 @@ import { sourcebot_pr_payload, sourcebot_diff_review, sourcebot_file_diff_review import { generateDiffReviewPrompt } from "@/features/agents/review-agent/nodes/generateDiffReviewPrompt"; import { invokeDiffReviewLlm } from "@/features/agents/review-agent/nodes/invokeDiffReviewLlm"; import { fetchFileContent } from "@/features/agents/review-agent/nodes/fetchFileContent"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('generate-pr-review'); diff --git a/packages/web/src/features/agents/review-agent/nodes/githubPrParser.ts b/packages/web/src/features/agents/review-agent/nodes/githubPrParser.ts index b1cee198..b633450e 100644 --- a/packages/web/src/features/agents/review-agent/nodes/githubPrParser.ts +++ b/packages/web/src/features/agents/review-agent/nodes/githubPrParser.ts @@ -2,7 +2,7 @@ import { sourcebot_pr_payload, sourcebot_file_diff, sourcebot_diff } from "@/fea import parse from "parse-diff"; import { Octokit } from "octokit"; import { GitHubPullRequest } from "@/features/agents/review-agent/types"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('github-pr-parser'); diff --git a/packages/web/src/features/agents/review-agent/nodes/githubPushPrReviews.ts b/packages/web/src/features/agents/review-agent/nodes/githubPushPrReviews.ts index e0a9e597..a6ef88d2 100644 --- a/packages/web/src/features/agents/review-agent/nodes/githubPushPrReviews.ts +++ b/packages/web/src/features/agents/review-agent/nodes/githubPushPrReviews.ts @@ -1,6 +1,6 @@ import { Octokit } from "octokit"; import { sourcebot_pr_payload, sourcebot_file_diff_review } from "@/features/agents/review-agent/types"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('github-push-pr-reviews'); diff --git a/packages/web/src/features/agents/review-agent/nodes/invokeDiffReviewLlm.ts b/packages/web/src/features/agents/review-agent/nodes/invokeDiffReviewLlm.ts index c726ba01..f3f41be8 100644 --- a/packages/web/src/features/agents/review-agent/nodes/invokeDiffReviewLlm.ts +++ b/packages/web/src/features/agents/review-agent/nodes/invokeDiffReviewLlm.ts @@ -1,8 +1,8 @@ import OpenAI from "openai"; import { sourcebot_file_diff_review, sourcebot_file_diff_review_schema } from "@/features/agents/review-agent/types"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; import fs from "fs"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; const logger = createLogger('invoke-diff-review-llm'); diff --git a/packages/web/src/features/chat/actions.ts b/packages/web/src/features/chat/actions.ts index 19ec9abd..86f86301 100644 --- a/packages/web/src/features/chat/actions.ts +++ b/packages/web/src/features/chat/actions.ts @@ -1,7 +1,6 @@ 'use server'; import { sew, withAuth, withOrgMembership } from "@/actions"; -import { env } from "@/env.mjs"; import { SOURCEBOT_GUEST_USER_ID } from "@/lib/constants"; import { ErrorCode } from "@/lib/errorCodes"; import { chatIsReadonly, notFound, ServiceError, serviceErrorResponse } from "@/lib/serviceError"; @@ -20,9 +19,8 @@ import { LanguageModelV2 as AISDKLanguageModelV2 } from "@ai-sdk/provider"; import { createXai } from '@ai-sdk/xai'; import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { createOpenRouter } from '@openrouter/ai-sdk-provider'; -import { getTokenFromConfig } from "@sourcebot/crypto"; +import { getTokenFromConfig, createLogger, env } from "@sourcebot/shared"; import { ChatVisibility, OrgRole, Prisma } from "@sourcebot/db"; -import { createLogger } from "@sourcebot/logger"; import { LanguageModel } from "@sourcebot/schemas/v3/languageModel.type"; import { Token } from "@sourcebot/schemas/v3/shared.type"; import { generateText, JSONValue, extractReasoningMiddleware, wrapLanguageModel } from "ai"; diff --git a/packages/web/src/features/chat/agent.ts b/packages/web/src/features/chat/agent.ts index 7df5a6d4..0c722c27 100644 --- a/packages/web/src/features/chat/agent.ts +++ b/packages/web/src/features/chat/agent.ts @@ -1,8 +1,9 @@ -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; +import { env as clientEnv } from "@sourcebot/shared/client"; import { getFileSource } from "@/features/search/fileSourceApi"; import { isServiceError } from "@/lib/utils"; import { ProviderOptions } from "@ai-sdk/provider-utils"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { LanguageModel, ModelMessage, StopCondition, streamText } from "ai"; import { ANSWER_TAG, FILE_REFERENCE_PREFIX, toolNames } from "./constants"; import { createCodeSearchTool, findSymbolDefinitionsTool, findSymbolReferencesTool, readFilesTool, searchReposTool, listAllReposTool } from "./tools"; @@ -140,7 +141,7 @@ export const createAgentStream = async ({ }, // Only enable langfuse traces in cloud environments. experimental_telemetry: { - isEnabled: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined, + isEnabled: clientEnv.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined, metadata: { langfuseTraceId: traceId, }, diff --git a/packages/web/src/features/chat/components/chatThread/answerCard.tsx b/packages/web/src/features/chat/components/chatThread/answerCard.tsx index d37ee67e..e3f89f62 100644 --- a/packages/web/src/features/chat/components/chatThread/answerCard.tsx +++ b/packages/web/src/features/chat/components/chatThread/answerCard.tsx @@ -17,7 +17,7 @@ import { isServiceError } from "@/lib/utils"; import { useDomain } from "@/hooks/useDomain"; import useCaptureEvent from "@/hooks/useCaptureEvent"; import { LangfuseWeb } from "langfuse"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared/client"; interface AnswerCardProps { answerText: string; diff --git a/packages/web/src/features/chat/utils.test.ts b/packages/web/src/features/chat/utils.test.ts index 3ebd4e31..698fbe42 100644 --- a/packages/web/src/features/chat/utils.test.ts +++ b/packages/web/src/features/chat/utils.test.ts @@ -4,7 +4,7 @@ import { FILE_REFERENCE_REGEX, ANSWER_TAG } from './constants'; import { SBChatMessage, SBChatMessagePart } from './types'; // Mock the env module -vi.mock('@/env.mjs', () => ({ +vi.mock('@sourcebot/shared', () => ({ env: { SOURCEBOT_CHAT_FILE_MAX_CHARACTERS: 4000, } diff --git a/packages/web/src/features/fileTree/actions.ts b/packages/web/src/features/fileTree/actions.ts index 6111ea70..a861670d 100644 --- a/packages/web/src/features/fileTree/actions.ts +++ b/packages/web/src/features/fileTree/actions.ts @@ -1,11 +1,11 @@ 'use server'; import { sew } from '@/actions'; -import { env } from '@/env.mjs'; +import { env } from '@sourcebot/shared'; import { notFound, unexpectedError } from '@/lib/serviceError'; import { withOptionalAuthV2 } from '@/withAuthV2'; import { Repo } from '@sourcebot/db'; -import { createLogger } from '@sourcebot/logger'; +import { createLogger } from '@sourcebot/shared'; import path from 'path'; import { simpleGit } from 'simple-git'; diff --git a/packages/web/src/features/search/zoektClient.ts b/packages/web/src/features/search/zoektClient.ts index 3379d2ce..bd30fcd1 100644 --- a/packages/web/src/features/search/zoektClient.ts +++ b/packages/web/src/features/search/zoektClient.ts @@ -1,4 +1,4 @@ -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared"; interface ZoektRequest { path: string, diff --git a/packages/web/src/hooks/useCaptureEvent.ts b/packages/web/src/hooks/useCaptureEvent.ts index 8dd03ffe..597f73ca 100644 --- a/packages/web/src/hooks/useCaptureEvent.ts +++ b/packages/web/src/hooks/useCaptureEvent.ts @@ -3,7 +3,7 @@ import { CaptureOptions } from "posthog-js"; import posthog from "posthog-js"; import { PosthogEvent, PosthogEventMap } from "../lib/posthogEvents"; -import { env } from "@/env.mjs"; +import { env } from "@sourcebot/shared/client"; export function captureEvent(event: E, properties: PosthogEventMap[E], options?: CaptureOptions) { if(!options) { diff --git a/packages/web/src/initialize.ts b/packages/web/src/initialize.ts index b08685b2..fa27febd 100644 --- a/packages/web/src/initialize.ts +++ b/packages/web/src/initialize.ts @@ -2,10 +2,8 @@ import { createGuestUser } from '@/lib/authUtils'; import { SOURCEBOT_SUPPORT_EMAIL } from "@/lib/constants"; import { prisma } from "@/prisma"; import { OrgRole } from '@sourcebot/db'; -import { createLogger } from "@sourcebot/logger"; -import { hasEntitlement, loadConfig } from '@sourcebot/shared'; +import { createLogger, env, hasEntitlement, loadConfig } from "@sourcebot/shared"; import { getOrgFromDomain } from './data/org'; -import { env } from './env.mjs'; import { SINGLE_TENANT_ORG_DOMAIN, SINGLE_TENANT_ORG_ID, SOURCEBOT_GUEST_USER_ID } from './lib/constants'; import { ServiceErrorException } from './lib/serviceError'; import { getOrgMetadata, isServiceError } from './lib/utils'; diff --git a/packages/web/src/lib/authUtils.ts b/packages/web/src/lib/authUtils.ts index bf7e5ea9..e11942a4 100644 --- a/packages/web/src/lib/authUtils.ts +++ b/packages/web/src/lib/authUtils.ts @@ -5,7 +5,7 @@ import { SINGLE_TENANT_ORG_ID, SOURCEBOT_GUEST_USER_EMAIL, SOURCEBOT_GUEST_USER_ import { getPlan, getSeats, hasEntitlement, SOURCEBOT_UNLIMITED_SEATS } from "@sourcebot/shared"; import { isServiceError } from "@/lib/utils"; import { orgNotFound, ServiceError, userNotFound } from "@/lib/serviceError"; -import { createLogger } from "@sourcebot/logger"; +import { createLogger } from "@sourcebot/shared"; import { getAuditService } from "@/ee/features/audit/factory"; import { StatusCodes } from "http-status-codes"; import { ErrorCode } from "./errorCodes"; diff --git a/packages/web/src/lib/types.ts b/packages/web/src/lib/types.ts index 545dbbf4..e27e7057 100644 --- a/packages/web/src/lib/types.ts +++ b/packages/web/src/lib/types.ts @@ -1,6 +1,6 @@ import { z } from "zod"; import { getReposResponseSchema, getVersionResponseSchema, repositoryQuerySchema, searchContextQuerySchema } from "./schemas"; -import { tenancyModeSchema } from "@/env.mjs"; +import { tenancyModeSchema } from "@sourcebot/shared"; export type KeymapType = "default" | "vim"; diff --git a/packages/web/src/middleware.ts b/packages/web/src/middleware.ts index 1b127f41..b59e207d 100644 --- a/packages/web/src/middleware.ts +++ b/packages/web/src/middleware.ts @@ -1,15 +1,10 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -import { env } from './env.mjs' import { SINGLE_TENANT_ORG_DOMAIN } from '@/lib/constants' export async function middleware(request: NextRequest) { const url = request.nextUrl.clone(); - if (env.SOURCEBOT_TENANCY_MODE !== 'single') { - return NextResponse.next(); - } - if ( url.pathname.startsWith('/login') || url.pathname.startsWith('/redeem') || diff --git a/packages/web/src/prisma.ts b/packages/web/src/prisma.ts index d9e488ce..0d520de7 100644 --- a/packages/web/src/prisma.ts +++ b/packages/web/src/prisma.ts @@ -1,11 +1,13 @@ import 'server-only'; -import { env } from "@/env.mjs"; +import { env, getDBConnectionString } from "@sourcebot/shared"; import { Prisma, PrismaClient } from "@sourcebot/db"; import { hasEntitlement } from "@sourcebot/shared"; // @see: https://authjs.dev/getting-started/adapters/prisma const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } +const dbConnectionString = getDBConnectionString(); + // @NOTE: In almost all cases, the userScopedPrismaClientExtension should be used // (since actions & queries are scoped to a particular user). There are some exceptions // (e.g., in initialize.ts). @@ -13,7 +15,17 @@ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient } // @todo: we can mark this as `__unsafePrisma` in the future once we've migrated // all of the actions & queries to use the userScopedPrismaClientExtension to avoid // accidental misuse. -export const prisma = globalForPrisma.prisma || new PrismaClient() +export const prisma = globalForPrisma.prisma || new PrismaClient({ + // @note: this code is evaluated at build time, and will throw exceptions if these env vars are not set. + // Here we explicitly check if the DATABASE_URL or the individual database variables are set, and only + ...(dbConnectionString !== undefined ? { + datasources: { + db: { + url: dbConnectionString, + }, + } + }: {}), +}) if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma /** diff --git a/packages/web/src/withAuthV2.test.ts b/packages/web/src/withAuthV2.test.ts index 5a1dd343..1b936005 100644 --- a/packages/web/src/withAuthV2.test.ts +++ b/packages/web/src/withAuthV2.test.ts @@ -18,18 +18,10 @@ vi.mock('./auth', () => ({ auth: mocks.auth, })); -vi.mock('@/env.mjs', () => ({ - env: {} -})); - vi.mock('next/headers', () => ({ headers: mocks.headers, })); -vi.mock('@/env.mjs', () => ({ - env: {} -})); - vi.mock('@/prisma', async () => { // @see: https://github.com/prisma/prisma/discussions/20244#discussioncomment-7976447 const actual = await vi.importActual('@/__mocks__/prisma'); @@ -38,16 +30,14 @@ vi.mock('@/prisma', async () => { }; }); -vi.mock('@sourcebot/crypto', () => ({ - hashSecret: vi.fn((secret: string) => secret), -})); - vi.mock('server-only', () => ({ default: vi.fn(), })); vi.mock('@sourcebot/shared', () => ({ hasEntitlement: mocks.hasEntitlement, + hashSecret: vi.fn((secret: string) => secret), + env: {} })); // Test utility to set the mock session diff --git a/packages/web/src/withAuthV2.ts b/packages/web/src/withAuthV2.ts index c4bf8095..65ebb054 100644 --- a/packages/web/src/withAuthV2.ts +++ b/packages/web/src/withAuthV2.ts @@ -1,5 +1,5 @@ import { prisma as __unsafePrisma, userScopedPrismaClientExtension } from "@/prisma"; -import { hashSecret } from "@sourcebot/crypto"; +import { hashSecret } from "@sourcebot/shared"; import { ApiKey, Org, OrgRole, PrismaClient, User } from "@sourcebot/db"; import { headers } from "next/headers"; import { auth } from "./auth"; diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index a41d0da0..1abfee26 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -35,8 +35,7 @@ "next-env.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts", - "src/env.mjs" + ".next/types/**/*.ts" ], "exclude": [ "node_modules" diff --git a/schemas/v3/environmentOverrides.json b/schemas/v3/environmentOverrides.json new file mode 100644 index 00000000..8def7d20 --- /dev/null +++ b/schemas/v3/environmentOverrides.json @@ -0,0 +1,85 @@ +{ + "type": "object", + "description": "Environment variable overrides.", + "title": "EnvironmentOverrides", + "not": { + "$comment": "List of environment variables that are not allowed to be overridden.", + "anyOf": [ + { + "required": [ + "CONFIG_PATH" + ] + } + ] + }, + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "const": "token" + }, + "value": { + "$ref": "./shared.json#/definitions/Token" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "number" + }, + "value": { + "type": "number" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "boolean" + }, + "value": { + "type": "boolean" + } + }, + "required": [ + "type", + "value" + ], + "additionalProperties": false + } + ] + } + } +} \ No newline at end of file diff --git a/schemas/v3/index.json b/schemas/v3/index.json index 392a2eb9..10048a34 100644 --- a/schemas/v3/index.json +++ b/schemas/v3/index.json @@ -102,6 +102,9 @@ }, "additionalProperties": false }, + "environmentOverrides": { + "$ref": "./environmentOverrides.json" + }, "connections": { "type": "object", "description": "Defines a collection of connections from varying code hosts that Sourcebot should sync with. This is only available in single-tenancy mode.", diff --git a/supervisord.conf b/supervisord.conf index eda6d430..19d30850 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -36,7 +36,7 @@ redirect_stderr=true [program:redis] command=redis-server --dir %(ENV_REDIS_DATA_DIR)s priority=10 -autostart=true +autostart=%(ENV_REDIS_EMBEDDED)s autorestart=true startretries=3 stdout_logfile=/dev/fd/1 diff --git a/yarn.lock b/yarn.lock index f6f9af62..2f19bfa9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7893,13 +7893,9 @@ __metadata: "@sentry/cli": "npm:^2.42.2" "@sentry/node": "npm:^9.3.0" "@sentry/profiling-node": "npm:^9.3.0" - "@sourcebot/crypto": "workspace:*" "@sourcebot/db": "workspace:*" - "@sourcebot/error": "workspace:*" - "@sourcebot/logger": "workspace:*" "@sourcebot/schemas": "workspace:*" "@sourcebot/shared": "workspace:*" - "@t3-oss/env-core": "npm:^0.12.0" "@types/argparse": "npm:^2.0.16" "@types/express": "npm:^5.0.0" "@types/micromatch": "npm:^4.0.9" @@ -7928,7 +7924,7 @@ __metadata: tsx: "npm:^4.19.1" typescript: "npm:^5.6.2" vitest: "npm:^2.1.9" - zod: "npm:^3.24.3" + zod: "npm:^3.25.74" languageName: unknown linkType: soft @@ -7946,25 +7942,11 @@ __metadata: languageName: node linkType: hard -"@sourcebot/crypto@workspace:*, @sourcebot/crypto@workspace:packages/crypto": - version: 0.0.0-use.local - resolution: "@sourcebot/crypto@workspace:packages/crypto" - dependencies: - "@google-cloud/secret-manager": "npm:^6.1.1" - "@sourcebot/db": "npm:*" - "@sourcebot/schemas": "npm:*" - "@types/node": "npm:^22.7.5" - dotenv: "npm:^16.4.5" - typescript: "npm:^5.7.3" - languageName: unknown - linkType: soft - -"@sourcebot/db@npm:*, @sourcebot/db@workspace:*, @sourcebot/db@workspace:packages/db": +"@sourcebot/db@workspace:*, @sourcebot/db@workspace:packages/db": version: 0.0.0-use.local resolution: "@sourcebot/db@workspace:packages/db" dependencies: "@prisma/client": "npm:6.2.1" - "@sourcebot/logger": "workspace:*" "@types/argparse": "npm:^2.0.16" "@types/readline-sync": "npm:^1.4.8" argparse: "npm:^2.0.1" @@ -7975,31 +7957,6 @@ __metadata: languageName: unknown linkType: soft -"@sourcebot/error@workspace:*, @sourcebot/error@workspace:packages/error": - version: 0.0.0-use.local - resolution: "@sourcebot/error@workspace:packages/error" - dependencies: - "@types/node": "npm:^22.7.5" - typescript: "npm:^5.7.3" - languageName: unknown - linkType: soft - -"@sourcebot/logger@workspace:*, @sourcebot/logger@workspace:packages/logger": - version: 0.0.0-use.local - resolution: "@sourcebot/logger@workspace:packages/logger" - dependencies: - "@logtail/node": "npm:^0.5.2" - "@logtail/winston": "npm:^0.5.2" - "@t3-oss/env-core": "npm:^0.12.0" - "@types/node": "npm:^22.7.5" - dotenv: "npm:^16.4.5" - triple-beam: "npm:^1.4.1" - typescript: "npm:^5.7.3" - winston: "npm:^3.15.0" - zod: "npm:^3.24.3" - languageName: unknown - linkType: soft - "@sourcebot/mcp@workspace:packages/mcp": version: 0.0.0-use.local resolution: "@sourcebot/mcp@workspace:packages/mcp" @@ -8019,7 +7976,7 @@ __metadata: languageName: unknown linkType: soft -"@sourcebot/schemas@npm:*, @sourcebot/schemas@workspace:*, @sourcebot/schemas@workspace:packages/schemas": +"@sourcebot/schemas@workspace:*, @sourcebot/schemas@workspace:packages/schemas": version: 0.0.0-use.local resolution: "@sourcebot/schemas@workspace:packages/schemas" dependencies: @@ -8036,9 +7993,10 @@ __metadata: version: 0.0.0-use.local resolution: "@sourcebot/shared@workspace:packages/shared" dependencies: - "@sourcebot/crypto": "workspace:*" + "@google-cloud/secret-manager": "npm:^6.1.1" + "@logtail/node": "npm:^0.5.2" + "@logtail/winston": "npm:^0.5.2" "@sourcebot/db": "workspace:*" - "@sourcebot/logger": "workspace:*" "@sourcebot/schemas": "workspace:*" "@t3-oss/env-core": "npm:^0.12.0" "@types/micromatch": "npm:^4.0.9" @@ -8046,9 +8004,12 @@ __metadata: ajv: "npm:^8.17.1" micromatch: "npm:^4.0.8" strip-json-comments: "npm:^5.0.1" + triple-beam: "npm:^1.4.1" tsc-watch: "npm:6.2.1" + tsx: "npm:^4.19.1" typescript: "npm:^5.7.3" - zod: "npm:^3.24.3" + winston: "npm:^3.15.0" + zod: "npm:^3.25.74" languageName: unknown linkType: soft @@ -8135,16 +8096,12 @@ __metadata: "@sentry/nextjs": "npm:^9" "@shopify/lang-jsonc": "npm:^1.0.0" "@sourcebot/codemirror-lang-tcl": "npm:^1.0.12" - "@sourcebot/crypto": "workspace:*" "@sourcebot/db": "workspace:*" - "@sourcebot/error": "workspace:*" - "@sourcebot/logger": "workspace:*" "@sourcebot/schemas": "workspace:*" "@sourcebot/shared": "workspace:*" "@ssddanbrown/codemirror-lang-twig": "npm:^1.0.0" "@stripe/react-stripe-js": "npm:^3.1.1" "@stripe/stripe-js": "npm:^5.6.0" - "@t3-oss/env-nextjs": "npm:^0.12.0" "@tailwindcss/typography": "npm:^0.5.16" "@tanstack/eslint-plugin-query": "npm:^5.74.7" "@tanstack/react-query": "npm:^5.53.3" @@ -8323,7 +8280,7 @@ __metadata: languageName: node linkType: hard -"@t3-oss/env-core@npm:0.12.0, @t3-oss/env-core@npm:^0.12.0": +"@t3-oss/env-core@npm:^0.12.0": version: 0.12.0 resolution: "@t3-oss/env-core@npm:0.12.0" peerDependencies: @@ -8360,26 +8317,6 @@ __metadata: languageName: node linkType: hard -"@t3-oss/env-nextjs@npm:^0.12.0": - version: 0.12.0 - resolution: "@t3-oss/env-nextjs@npm:0.12.0" - dependencies: - "@t3-oss/env-core": "npm:0.12.0" - peerDependencies: - typescript: ">=5.0.0" - valibot: ^1.0.0-beta.7 || ^1.0.0 - zod: ^3.24.0 - peerDependenciesMeta: - typescript: - optional: true - valibot: - optional: true - zod: - optional: true - checksum: 10c0/f39cae67353c09818b13836cf1f0ab8186058f6fd2f5e8983f43c8601f1cd6948d3a159a2f166420049080ca56e73ba69e483229f21593aba0d6d8f4cf79ef32 - languageName: node - linkType: hard - "@tailwindcss/typography@npm:^0.5.16": version: 0.5.16 resolution: "@tailwindcss/typography@npm:0.5.16"