Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/workflows/_build-cloud.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# Internal reusable workflow for building a non-OSS ("cloud") Docker image and
# pushing it to Amazon ECR.
name: Build Cloud Image

on:
workflow_call:
inputs:
environment:
description: "GitHub Environment supplying the Sentry vars/secrets. Also scopes the OIDC subject used to assume the ECR push role."
required: true
type: string
git_ref:
description: "Git ref to checkout and build"
required: true
type: string
docker_tags:
description: "Docker tags configuration for docker/metadata-action"
required: true
type: string
aws_region:
description: "Region the ECR repository lives in"
required: false
type: string
default: us-west-1

jobs:
build:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
permissions:
contents: read
# Required to request the OIDC token that assumes the AWS role.
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.git_ref }}
submodules: "true"
fetch-depth: 0
Comment thread
brendan-kellam marked this conversation as resolved.
# Nothing after checkout talks to the remote, so don't leave the token
# behind in the workspace's .git/config.
persist-credentials: false

- name: Resolve build commit SHA
id: commit
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Validate environment configuration
env:
ENVIRONMENT: ${{ inputs.environment }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
AWS_ECR_ROLE_ARN: ${{ vars.AWS_ECR_ROLE_ARN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT: ${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT: ${{ vars.SENTRY_BACKEND_PROJECT }}
run: |
missing=0
for name in SENTRY_AUTH_TOKEN AWS_ECR_ROLE_ARN \
NEXT_PUBLIC_SENTRY_ENVIRONMENT \
NEXT_PUBLIC_SENTRY_WEBAPP_DSN \
NEXT_PUBLIC_SENTRY_BACKEND_DSN \
SENTRY_ORG SENTRY_WEBAPP_PROJECT SENTRY_BACKEND_PROJECT; do
if [ -z "${!name}" ]; then
echo "::error::${name} is not set on the '${ENVIRONMENT}' environment (or the repository)."
missing=1
else
echo "ok: ${name}"
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Refusing to build: the image would ship without Sentry wiring."
exit 1
fi

- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ECR_ROLE_ARN }}
aws-region: ${{ inputs.aws_region }}

- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2

# Each environment publishes to its own registry (see CicdStack), so a
# staging build can never overwrite a prod tag.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ steps.ecr.outputs.registry }}/sourcebot-${{ inputs.environment }}
tags: ${{ inputs.docker_tags }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push Docker image
Comment thread
brendan-kellam marked this conversation as resolved.
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# SENTRY_RELEASE is the commit SHA rather than SOURCEBOT_VERSION so that
# every prod build gets a distinct release (prod tracks `main`, where the
# version only moves on a tagged release). packages/backend/src/instrument.ts
# reports NEXT_PUBLIC_BUILD_COMMIT_SHA as its release to match; the webapp
# gets SENTRY_RELEASE injected into its bundle by withSentryConfig.
build-args: |
NEXT_PUBLIC_BUILD_COMMIT_SHA=${{ steps.commit.outputs.sha }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}
NEXT_PUBLIC_SENTRY_BACKEND_DSN=${{ vars.NEXT_PUBLIC_SENTRY_BACKEND_DSN }}
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY=${{ vars.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY }}
NEXT_PUBLIC_LANGFUSE_BASE_URL=${{ vars.NEXT_PUBLIC_LANGFUSE_BASE_URL }}
SENTRY_ORG=${{ vars.SENTRY_ORG }}
SENTRY_WEBAPP_PROJECT=${{ vars.SENTRY_WEBAPP_PROJECT }}
SENTRY_BACKEND_PROJECT=${{ vars.SENTRY_BACKEND_PROJECT }}
SENTRY_RELEASE=${{ steps.commit.outputs.sha }}
# Passed as a secret, not a build-arg: build args are recorded in layer
# metadata that `mode=max` exports to the cache. @see: Dockerfile
secrets: |
sentry_auth_token=${{ secrets.SENTRY_AUTH_TOKEN }}
# Cache scope is per-environment, and distinct from the OSS build's
# (which is keyed on platform alone). Sharing a scope would let a build
# that never sees SENTRY_AUTH_TOKEN restore layers from one that did.
cache-from: type=gha,scope=cloud-${{ inputs.environment }}-amd64
cache-to: type=gha,mode=max,scope=cloud-${{ inputs.environment }}-amd64

- name: Summarize
env:
ENVIRONMENT: ${{ inputs.environment }}
COMMIT_SHA: ${{ steps.commit.outputs.sha }}
TAGS: ${{ steps.meta.outputs.tags }}
run: |
{
echo "### Pushed to ECR"
echo
echo "| | |"
echo "|---|---|"
echo "| Environment | \`${ENVIRONMENT}\` |"
echo "| Commit | \`${COMMIT_SHA}\` |"
echo "| Sentry release | \`${COMMIT_SHA}\` |"
echo
echo '```'
echo "$TAGS"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
28 changes: 28 additions & 0 deletions .github/workflows/release-cloud-prod.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
name: Release Sourcebot (Cloud - Production)

permissions:
contents: read
id-token: write

on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:

concurrency:
group: release-cloud-prod-${{ github.ref }}
cancel-in-progress: false
Comment thread
brendan-kellam marked this conversation as resolved.

jobs:
build:
uses: ./.github/workflows/_build-cloud.yml
with:
environment: prod
git_ref: ${{ github.ref }}
docker_tags: |
type=raw,value=main,enable=${{ github.ref == 'refs/heads/main' }}
type=sha,format=long,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern=v{{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
secrets: inherit
22 changes: 5 additions & 17 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -61,19 +61,12 @@ ENV NEXT_PUBLIC_LANGFUSE_BASE_URL=$NEXT_PUBLIC_LANGFUSE_BASE_URL
ARG NEXT_PUBLIC_BUILD_COMMIT_SHA
ENV NEXT_PUBLIC_BUILD_COMMIT_SHA=$NEXT_PUBLIC_BUILD_COMMIT_SHA

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
# @see : next.config.mjs
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_WEBAPP_PROJECT
ENV SENTRY_WEBAPP_PROJECT=$SENTRY_WEBAPP_PROJECT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
# -----------

RUN apk add --no-cache libc6-compat
Expand All@@ -92,7 +85,9 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/web install

ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn workspace @sourcebot/web build

RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
yarn workspace @sourcebot/web build
Comment thread
cursor[bot] marked this conversation as resolved.
ENV SKIP_ENV_VALIDATION=0
# ------------------------------

Expand All@@ -101,16 +96,10 @@ FROM node-alpine AS backend-builder
ENV SKIP_ENV_VALIDATION=1
# -----------

# To upload source maps to Sentry, we need to set the following build-time args.
# It's important that we don't set these for oss builds, otherwise the Sentry
# auth token will be exposed.
ARG SENTRY_ORG
ENV SENTRY_ORG=$SENTRY_ORG
ARG SENTRY_BACKEND_PROJECT
ENV SENTRY_BACKEND_PROJECT=$SENTRY_BACKEND_PROJECT
# SMUAT = Source Map Upload Auth Token
ARG SENTRY_SMUAT
ENV SENTRY_SMUAT=$SENTRY_SMUAT
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
# -----------
Expand All@@ -129,11 +118,10 @@ COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLang
RUN yarn workspace @sourcebot/backend install
RUN yarn workspace @sourcebot/backend build

# Upload source maps to Sentry if we have the necessary build-time args.
RUN if [ -n "$SENTRY_SMUAT" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_BACKEND_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
apk add --no-cache curl; \
curl -sL https://sentry.io/get-cli/ | sh; \
sentry-cli login --auth-token $SENTRY_SMUAT; \
sentry-cli sourcemaps inject --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
sentry-cli sourcemaps upload --org $SENTRY_ORG --project $SENTRY_BACKEND_PROJECT --release $SENTRY_RELEASE ./packages/backend/dist; \
fi
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,10 @@ const logger = createLogger('instrument');
if (!!env.NEXT_PUBLIC_SENTRY_BACKEND_DSN && !!env.NEXT_PUBLIC_SENTRY_ENVIRONMENT) {
Sentry.init({
dsn: env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
// Must match the release our source maps are uploaded under, which the
// Dockerfile sets from SENTRY_RELEASE (the build's commit SHA). Falls back
// to the version for builds that don't pass a commit SHA.
release: env.NEXT_PUBLIC_BUILD_COMMIT_SHA ?? SOURCEBOT_VERSION,
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
});
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/next.config.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,8 +146,8 @@ export default withSentryConfig(nextConfig, {
// For all available options, see:
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_WEBAPP_PROJECT,
authToken: process.env.SENTRY_SMUAT,
release: process.env.SENTRY_RELEASE,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.SENTRY_RELEASE },

// Only print logs for uploading source maps in CI
silent: !process.env.CI,
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
// This file configures the initialization of Sentry on the client.
// The config you add here will be used whenever a users loads a page in their browser.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
//
// Must be named `instrumentation-client.ts`. Next.js loads this file itself, whereas
// `sentry.client.config.ts` is only picked up by @sentry/nextjs' webpack plugin,
// which never runs since `next build` defaults to Turbopack.

import * as Sentry from "@sentry/nextjs";

Expand All@@ -9,9 +13,15 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

Comment thread
cursor[bot] marked this conversation as resolved.
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
} else {
console.debug("[client] Sentry was not initialized");
}

// Instruments App Router client-side navigations as spans. Next.js only reads this
// export from `instrumentation-client.ts`. A no-op when Sentry is uninitialized.
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
4 changes: 2 additions & 2 deletions packages/web/src/instrumentation.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,11 +19,11 @@ export async function register() {
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
await import('./sentry.edge.config');
}

if (process.env.NEXT_RUNTIME === 'nodejs') {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ if (!!process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN && !!process.env.NEXT_PUBLIC_SEN
dsn: process.env.NEXT_PUBLIC_SENTRY_WEBAPP_DSN,
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,

tracesSampleRate: 1.0,

// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});
Expand Down
Loading