Compare commits

...

16 commits

Author SHA1 Message Date
Alexander Braverman Masis
eb973bae0b
Merge 568395aba0 into c3fae1aaab 2025-10-13 14:47:56 -05:00
Brendan Kellam
c3fae1aaab
feat(web): Improved search performance on unbounded searches (#555) 2025-10-07 23:55:36 -07:00
Brendan Kellam
18ba1d2492
update demo deploy cadence (#556) 2025-10-07 23:51:52 -07:00
bkellam
8d7babc8d2 chore(worker): Change log message to debug 2025-10-07 16:38:56 -07:00
bkellam
595abc12be use blacksmith arm machine for arm builds 2025-10-07 10:21:19 -07:00
blacksmith-sh[bot]
0e8fdf0f97
Migrate workflows to Blacksmith (#554)
Co-authored-by: blacksmith-sh[bot] <157653362+blacksmith-sh[bot]@users.noreply.github.com>
2025-10-07 10:05:27 -07:00
Brendan Kellam
83c6704b01
fix: Fix git dubious ownership errors (#553) 2025-10-06 19:54:17 -07:00
Brendan Kellam
5e3e4f000a
chore(web): Remove spam "login page loaded" log (#552) 2025-10-06 15:04:41 -07:00
msukkari
623c794a75 update description in docs 2025-10-04 10:03:29 -07:00
Brendan Kellam
425a816fb6
Update README.md 2025-10-03 21:38:49 -07:00
bkellam
6a4c9220bd chore: try including platform pair in cache key 2025-10-03 21:24:14 -07:00
Brendan Kellam
eeb6b73a64
chore: Move helm chart to seperate repo (#549) 2025-10-03 15:45:36 -07:00
Andre Nogueira
9c8224e39f
Add Sourcebot Helm Chart (#370)
* feat: add helm chart

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

* feat: add sts support to use internal DB and improve values docs

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

* fix: include postgresql extra dependency

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

* fix: remove autoscaler

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

* fix: remove sts

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

* fix: add more suggestive env var example

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

* fix: add chart dependency lock

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

* fix: add host infer to the chart docs

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>

---------

Signed-off-by: Andre Nogueira <andre.nogueira@mollie.com>
2025-10-03 15:39:26 -07:00
Brendan Kellam
c10010eb99
feat(db): Support passing db connection as separate env vars (#545) 2025-10-02 12:51:39 -07:00
bkellam
d24de793f2 Add roadmap link to docs 2025-10-01 14:47:26 -07:00
Alexander Braverman Masis
568395aba0 flake.nix
Package Sourcebot with Nix, NixOS module for deployment, integration
test and microvm.
2025-09-22 09:09:42 -04:00
34 changed files with 1365 additions and 177 deletions

View file

@ -4,8 +4,6 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"
# Zoekt
ZOEKT_WEBSERVER_URL="http://localhost:6070"
# SHARD_MAX_MATCH_COUNT=10000
# TOTAL_MAX_MATCH_COUNT=100000
# The command to use for generating ctags.
CTAGS_COMMAND=ctags
# logging, strict

View file

@ -2,7 +2,7 @@ name: Deploy Demo
on:
push:
branches: ["main"]
tags: ["v*.*.*"]
workflow_dispatch:
jobs:

View file

@ -27,9 +27,9 @@ jobs:
platform: [linux/amd64, linux/arm64]
include:
- platform: linux/amd64
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
- platform: linux/arm64
runs-on: ubuntu-24.04-arm
runs-on: blacksmith-8vcpu-ubuntu-2204-arm
steps:
- name: Prepare
@ -57,8 +57,8 @@ jobs:
with:
cosign-release: "v2.2.4"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Login to GitHub Packages Docker Registry
uses: docker/login-action@v3
@ -69,12 +69,10 @@ jobs:
- name: Build Docker image
id: build
uses: docker/build-push-action@v6
uses: useblacksmith/build-push-action@v2
with:
context: .
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: ${{ matrix.platform }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true,annotation.org.opencontainers.image.description=Blazingly fast code search
build-args: |
@ -110,7 +108,7 @@ jobs:
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
merge:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
packages: write
needs:
@ -123,8 +121,8 @@ jobs:
pattern: digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Extract Docker metadata
id: meta

View file

@ -8,7 +8,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
steps:
@ -19,6 +19,6 @@ jobs:
- name: Build Docker image
id: build
uses: docker/build-push-action@v6
uses: useblacksmith/build-push-action@v2
with:
context: .

View file

@ -7,7 +7,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
steps:

View file

@ -7,7 +7,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
steps:

View file

@ -5,6 +5,9 @@
},
{
"path": "../vendor/zoekt"
},
{
"path": "../../sourcebot-helm-chart"
}
],
"settings": {

View file

@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
<!-- @NOTE: On next release, please bump the MCP pacakge as there are breaking changes in this! -->
### Fixed
- Fixed "dubious ownership" errors when cloning / fetching repos. [#553](https://github.com/sourcebot-dev/sourcebot/pull/553)
### Changed
- Remove spam "login page loaded" log. [#552](https://github.com/sourcebot-dev/sourcebot/pull/552)
- Improved search performance for unbounded search queries. [#555](https://github.com/sourcebot-dev/sourcebot/pull/555)
### Added
- Added support for passing db connection url as seperate `DATABASE_HOST`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_NAME`, and `DATABASE_ARGS` env vars. [#545](https://github.com/sourcebot-dev/sourcebot/pull/545)
## [4.7.3] - 2025-09-29
### Fixed

View file

@ -185,7 +185,6 @@ ENV DATA_DIR=/data
ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot
ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db
ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis
ENV DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot"
ENV REDIS_URL="redis://localhost:6379"
ENV SRC_TENANT_ENFORCEMENT_MODE=strict
ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem
@ -234,6 +233,9 @@ COPY --from=shared-libs-builder /app/packages/shared ./packages/shared
# Configure dependencies
RUN apk add --no-cache git ca-certificates bind-tools tini jansson wget supervisor uuidgen curl perl jq redis postgresql postgresql-contrib openssl util-linux unzip
# Fixes git "dubious ownership" issues when the volume is mounted with different permissions to the container.
RUN git config --global safe.directory "*"
# Configure the database
RUN mkdir -p /run/postgresql && \
chown -R postgres:postgres /run/postgresql && \

View file

@ -128,4 +128,3 @@ To configure Sourcebot (index your own repos, connect your LLMs, etc), check out
> Building from source is only required if you'd like to contribute. If you'd just like to use Sourcebot, we recommend checking out our self-hosting [docs](https://docs.sourcebot.dev/self-hosting/overview).
If you'd like to build from source, please checkout the `CONTRIBUTING.md` file for more information.

View file

@ -110,6 +110,11 @@
"href": "https://sourcebot.dev/changelog",
"icon": "list-check"
},
{
"anchor": "Roadmap",
"href": "https://github.com/sourcebot-dev/sourcebot/issues/459",
"icon": "map"
},
{
"anchor": "Support",
"href": "https://github.com/sourcebot-dev/sourcebot/issues/new?template=get_help.md",

View file

@ -19,7 +19,7 @@ The following environment variables allow you to configure your Sourcebot deploy
| `DATA_CACHE_DIR` | `$DATA_DIR/.sourcebot` | <p>The root data directory in which all data written to disk by Sourcebot will be located.</p> |
| `DATA_DIR` | `/data` | <p>The directory within the container to store all persistent data. Typically, this directory will be volume mapped such that data is persisted across container restarts (e.g., `docker run -v $(pwd):/data`)</p> |
| `DATABASE_DATA_DIR` | `$DATA_CACHE_DIR/db` | <p>The data directory for the default Postgres database.</p> |
| `DATABASE_URL` | `postgresql://postgres@ localhost:5432/sourcebot` | <p>Connection string of your Postgres database. By default, a Postgres database is automatically provisioned at startup within the container.</p><p>If you'd like to use a non-default schema, you can provide it as a parameter in the database url </p> |
| `DATABASE_URL` | `postgresql://postgres@ localhost:5432/sourcebot` | <p>Connection string of your Postgres database. By default, a Postgres database is automatically provisioned at startup within the container.</p><p>If you'd like to use a non-default schema, you can provide it as a parameter in the database url.</p><p>You can also use `DATABASE_HOST`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_NAME`, and `DATABASE_ARGS` to construct the database url.</p> |
| `EMAIL_FROM_ADDRESS` | `-` | <p>The email address that transactional emails will be sent from. See [this doc](/docs/configuration/transactional-emails) for more info.</p> |
| `FORCE_ENABLE_ANONYMOUS_ACCESS` | `false` | <p>When enabled, [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) to the organization will always be enabled</p>
| `REDIS_DATA_DIR` | `$DATA_CACHE_DIR/redis` | <p>The data directory for the default Redis instance.</p> |
@ -28,7 +28,6 @@ The following environment variables allow you to configure your Sourcebot deploy
| `REDIS_REMOVE_ON_FAIL` | `100` | <p>Controls how many failed jobs are allowed to remain in Redis queues</p> |
| `REPO_SYNC_RETRY_BASE_SLEEP_SECONDS` | `60` | <p>The base sleep duration (in seconds) for exponential backoff when retrying repository sync operations that fail</p> |
| `GITLAB_CLIENT_QUERY_TIMEOUT_SECONDS` | `600` | <p>The timeout duration (in seconds) for GitLab client queries</p> |
| `SHARD_MAX_MATCH_COUNT` | `10000` | <p>The maximum shard count per query</p> |
| `SMTP_CONNECTION_URL` | `-` | <p>The url to the SMTP service used for sending transactional emails. See [this doc](/docs/configuration/transactional-emails) for more info.</p> |
| `SOURCEBOT_ENCRYPTION_KEY` | Automatically generated at startup if no value is provided. Generated using `openssl rand -base64 24` | <p>Used to encrypt connection secrets and generate API keys.</p> |
| `SOURCEBOT_PUBLIC_KEY_PATH` | `/app/public.pem` | <p>Sourcebot's public key that's used to verify encrypted license key signatures.</p> |
@ -36,8 +35,6 @@ The following environment variables allow you to configure your Sourcebot deploy
| `SOURCEBOT_STRUCTURED_LOGGING_ENABLED` | `false` | <p>Enables/disable structured JSON logging. See [this doc](/docs/configuration/structured-logging) for more info.</p> |
| `SOURCEBOT_STRUCTURED_LOGGING_FILE` | - | <p>Optional file to log to if structured logging is enabled</p> |
| `SOURCEBOT_TELEMETRY_DISABLED` | `false` | <p>Enables/disables telemetry collection in Sourcebot. See [this doc](/docs/overview.mdx#telemetry) for more info.</p> |
| `TOTAL_MAX_MATCH_COUNT` | `100000` | <p>The maximum number of matches per query</p> |
| `ZOEKT_MAX_WALL_TIME_MS` | `10000` | <p>The maximum real world duration (in milliseconds) per zoekt query</p> |
### Enterprise Environment Variables
| Variable | Default | Description |

View file

@ -4,6 +4,8 @@ title: "Deployment guide"
import SupportedPlatforms from '/snippets/platform-support.mdx'
## Container deployment
The following guide will walk you through the steps to deploy Sourcebot on your own infrastructure. Sourcebot is distributed as a [single docker container](/docs/overview#architecture) that can be deployed to a k8s cluster, a VM, or any platform that supports docker.
@ -72,6 +74,117 @@ The following guide will walk you through the steps to deploy Sourcebot on your
</Step>
</Steps>
## NixOS deployment
<Note>Hit an issue? Please let us know on [GitHub discussions](https://github.com/sourcebot-dev/sourcebot/discussions/categories/support) or by [emailing us](mailto:team@sourcebot.dev).</Note>
<Steps>
<Step title="Flake.nix input">
Add the Sourcebot flake as an input to your NixOS configuration. This will allow you to use the Sourcebot container in your NixOS deployment.
```nix
inputs.sourcebot.url = "github:sourcebot-dev/sourcebot";
```
Add sourcebot module to your NixOS configuration:
```nix
nixosConfigurations.mysystem = nixpkgs.lib.nixosSystem {
modules = [
inputs.sourcebot.nixosModules.sourcebot
];
}
```
[Learn more about NixOS flakes](/docs/installation/nixos-flakes).
</Step>
<Step title="Setup Credentials">
Sourcebot requires a few secrets to be set up before it can run, and code host credentials can be managed using NixOS module too:
- [sops-nix](https://github.com/Mic92/sops-nix) example:
```nix
sops = {
secrets = {
sourcebot-auth-secret.owner = "sourcebot";
sourcebot-encryption-key.owner = "sourcebot";
sourcebot-gitlab-token.owner = "sourcebot";
};
templates = {
sourcebot-env = {
content = ''
AUTH_SECRET=${config.sops.placeholder.sourcebot-auth-secret}
SOURCEBOT_ENCRYPTION_KEY=${config.sops.placeholder.sourcebot-encryption-key}
GITLAB_EXAMPLE_TOKEN=${config.sops.placeholder.sourcebot-gitlab-token}
'';
};
};
};
```
- [agenix](https://github.com/ryantm/agenix) example:
```nix
age.secrets.sourcebot-env.file = ../secrets/sourcebot.age;
```
`sourcebot.age` file should be an environment file in the format:
```
AUTH_SECRET=your-auth-secret
SOURCEBOT_ENCRYPTION_KEY=your-encryption-key
GITLAB_EXAMPLE_TOKEN=your-gitlab-token
```
</Step>
<Step title="Enable Sourcebot">
The following NixOS configuration will enable Sourcebot and set it up to run with the provided configuration.
Additional options could be found in the [source file](../../nix/nixosModule.nix)
```nix
services.sourcebot = {
enable = true;
# envFile = config.sops.templates.sourcebot-env.path; # Uncomment if using sops-nix
# envFile = config.age.secrets.sourcebot-env.path; # Uncomment if using agenix
package = pkgs.sourcebot;
logLevel = "info";
dataDir = "/data/sourcebot";
dataCacheDir = "/data/sourcebot/cache";
configPath = "${pkgs.writeText "config" (builtins.toJSON {
"$schema" = "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json";
connections = {
github-public = {
type = "github";
repos = [
"sourcebot-dev/sourcebot"
];
};
gitlab-private = {
type = "gitlab";
url = "https://gitlab.example.com";
all = true;
token = {
env = "GITLAB_EXAMPLE_TOKEN";
};
exclude = {
forks = true;
};
};
};
settings = {
resyncConnectionIntervalMs = 1000 * 60 * 60 * 24 * 7; # 1 week
reindexIntervalMs = 1000 * 60 * 60 * 24 * 7; # 1 week
maxRepoIndexingJobConcurrency = 1000; # 8 default
maxConnectionSyncJobConcurrency = 1000; # 8 default
maxRepoGarbageCollectionJobConcurrency = 1000; # 8 default
};
})}";
};
```
</Step>
</Steps>
## Next steps
---

View file

@ -2,10 +2,11 @@
title: "Overview"
---
[Sourcebot](https://github.com/sourcebot-dev/sourcebot) is a self-hosted tool that helps you understand your codebase.
[Sourcebot](https://github.com/sourcebot-dev/sourcebot) is a platform that helps humans and agents understand your codebase:
- [Code search](/docs/features/search/overview): Search and navigate across all your repos and branches, no matter where theyre hosted
- [Ask Sourcebot](/docs/features/ask): Ask questions about your codebase and have Sourcebot provide detailed answers grounded with inline citations
- [MCP](/docs/features/mcp-server): Enrich agent context windows with code across your organization
<CardGroup>
<Card title="Deployment guide" icon="server" href="/docs/deployment-guide" horizontal="true">

View file

@ -1,6 +1,24 @@
#!/bin/sh
set -e
# Check if DATABASE_URL is not set
if [ -z "$DATABASE_URL" ]; then
# Check if the individual database variables are set and construct the URL
if [ -n "$DATABASE_HOST" ] && [ -n "$DATABASE_USERNAME" ] && [ -n "$DATABASE_PASSWORD" ] && [ -n "$DATABASE_NAME" ]; then
DATABASE_URL="postgresql://${DATABASE_USERNAME}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}"
if [ -n "$DATABASE_ARGS" ]; then
DATABASE_URL="${DATABASE_URL}?$DATABASE_ARGS"
fi
export DATABASE_URL
else
# Otherwise, fallback to a default value
DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot"
export DATABASE_URL
fi
fi
if [ "$DATABASE_URL" = "postgresql://postgres@localhost:5432/sourcebot" ]; then
DATABASE_EMBEDDED="true"
fi

133
flake.lock Normal file
View file

@ -0,0 +1,133 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"microvm": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
],
"spectrum": "spectrum"
},
"locked": {
"lastModified": 1747859546,
"narHash": "sha256-tDu6JFzM86y5L2eLAkkw5Aklzz0DwfohtcxRXw+fCHA=",
"owner": "astro",
"repo": "microvm.nix",
"rev": "91ba136db1a3dd73168639c185fa802eb1157ec1",
"type": "github"
},
"original": {
"owner": "astro",
"repo": "microvm.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1747179050,
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"microvm": "microvm",
"nixpkgs": "nixpkgs"
}
},
"spectrum": {
"flake": false,
"locked": {
"lastModified": 1746869549,
"narHash": "sha256-BKZ/yZO/qeLKh9YqVkKB6wJiDQJAZNN5rk5NsMImsWs=",
"ref": "refs/heads/main",
"rev": "d927e78530892ec8ed389e8fae5f38abee00ad87",
"revCount": 862,
"type": "git",
"url": "https://spectrum-os.org/git/spectrum"
},
"original": {
"type": "git",
"url": "https://spectrum-os.org/git/spectrum"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

89
flake.nix Normal file
View file

@ -0,0 +1,89 @@
{
description = "SourceBot - Code search and navigation tool";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
microvm.url = "github:astro/microvm.nix";
microvm.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = {
self,
nixpkgs,
flake-utils,
microvm,
}:
flake-utils.lib.eachSystemPassThrough ["x86_64-linux"] (system: {
nixosModules = rec {
default = sourcebot;
sourcebot = import ./nix/nixosModule.nix self;
};
nixosConfigurations.testing = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
({
pkgs,
lib,
...
}: {
imports = [
self.nixosModules.sourcebot
];
system.stateVersion = "25.05";
boot.isContainer = true; # stop nix flake check complaining about missing root fs
documentation.nixos.enable = false; # skip generating nixos docs
virtualisation.vmVariant = {
boot.isContainer = lib.mkForce false; # let vm variant create a virtual disk
virtualisation.graphics = false; # connect serial console to terminal
};
})
];
};
overlays.default = import ./nix/overlay.nix;
})
// flake-utils.lib.eachSystem ["x86_64-linux"] (
system: let
pkgs = import nixpkgs {
inherit system;
overlays = [self.overlays.default];
};
sourcebotSystem = nixpkgs.lib.nixosSystem {
inherit system pkgs;
modules = [
microvm.nixosModules.microvm
self.nixosModules.sourcebot
./nix/microvm.nix
];
};
in {
packages = rec {
default = sourcebot;
sourcebot = pkgs.callPackage ./nix/sourcebot.nix {};
microvm = sourcebotSystem.config.microvm.declaredRunner;
};
checks.default = pkgs.callPackage ./nix/nixosTest.nix {inherit self;};
devShells.default = pkgs.mkShell {
packages = with pkgs; [
yarn-berry
yarn-berry.yarn-berry-fetcher
openssl
yarn
bun
redis
jq
];
buildInputs = with pkgs; [
nodePackages.prisma
];
YARN_ENABLE_SCRIPTS = "false";
PRISMA_SCHEMA_ENGINE_BINARY = "${pkgs.prisma-engines}/bin/schema-engine";
PRISMA_QUERY_ENGINE_BINARY = "${pkgs.prisma-engines}/bin/query-engine";
PRISMA_QUERY_ENGINE_LIBRARY = "${pkgs.prisma-engines}/lib/libquery_engine.node";
PRISMA_FMT_BINARY = "${pkgs.prisma-engines}/bin/prisma-fmt";
};
}
);
}

64
nix/microvm.nix Normal file
View file

@ -0,0 +1,64 @@
{
pkgs,
lib,
...
}: {
microvm = {
mem = 1024;
interfaces = [
{
type = "user";
id = "sourecbot";
mac = "02:00:00:00:00:10";
}
];
forwardPorts = [
{
from = "host";
host.port = 47734;
guest.port = 7734;
}
];
shares = [
{
tag = "ro-store";
source = "${builtins.storeDir}";
mountPoint = "/nix/.ro-store";
}
];
volumes = [
{
mountPoint = "/var";
image = "sourcebot-var.img";
size = 1 * 1024; # 10GB
}
];
};
system.stateVersion = lib.trivial.release;
users.users.root.password = "";
# Enable autologin for root user
services.getty.autologinUser = "root";
services.sourcebot = {
enable = true;
logLevel = "debug";
configPath = "${pkgs.writeText "config" (builtins.toJSON {
"$schema" = "https://raw.githubusercontent.com/sourcebot-dev/sourcebot/main/schemas/v3/index.json";
connections = {
starter-connection = {
type = "github";
repos = [
"sourcebot-dev/sourcebot"
];
};
};
})}";
};
networking.firewall.enable = false;
}

201
nix/missing-hashes.json Normal file
View file

@ -0,0 +1,201 @@
{
"@esbuild/aix-ppc64@npm:0.19.11": "cfc55b56d9dd515356e1b89cf7d41945d8adef237d1e2b2d5bfaaf001bc73a9100a1bc78afbd8d9b0c4db232a4ed17a1c55fbfecb89dafe8fa04b93c3e98b242",
"@esbuild/aix-ppc64@npm:0.21.5": "1f48fea96ab4fbc2921756361bc8a0c4d0690f14dc2298a357aa3d436bcd1cd646d490e5d71c4e0fb46b9e04401bc93153d6886456dd0665b6758be643c95a16",
"@esbuild/aix-ppc64@npm:0.25.1": "6de3a364b7f79f21f66d606d4d29c72ef81c741d71ab4bb941c4eabe7b6a809b1340f4dd5c943827005e421912880741320b9617d17fce762d204cfb94175223",
"@esbuild/android-arm64@npm:0.19.11": "4d164bd5fa1b6a965aea993d13c69600a2cb902c77d32b92e8eaec28357b47c4a195aa1003400224fa0daf514e735a172d1b4ea95c723b3118af9aad7aa48b84",
"@esbuild/android-arm64@npm:0.21.5": "7a4831b0886c165ed671f0094dcd491235fe503364a261379c84e2225a3c3230a06bce1d3a02316fa8a040b0ffede56c617746dc3b5550549ae3fb07095bb20d",
"@esbuild/android-arm64@npm:0.25.1": "716c98ad3220c71cbaedcfa34aa7c877a3fae911938c3776a66600d7f65980e384799a6832a1b9aea96c6d5a8880610f54744bd0813e743c511d44056ef528d6",
"@esbuild/android-arm@npm:0.19.11": "255a097f9ba1eaae3ab045d198b8af9551018a1f769d7db38a0b9ba4a26435628023b79a2e75efb5c65b513d31b64924dac4c2cc5b77ceec23d7b596df7e3568",
"@esbuild/android-arm@npm:0.21.5": "9fa871018a9f2198f40fde2c672fcb1b9d3ab5ee602644ea4cf68c548ee2c0b6c60ad851ce85219f84886fd29757d8c49bac28ea48a2a16708a088e32dfe673a",
"@esbuild/android-arm@npm:0.25.1": "d2c9e95dd2027f6e14250a90a11136d9ce73a2157a8d104df4a9dd199d3c50cd91f25813536b4776630acb78596bd5a025976962c6d624df7594c32f2d9c1395",
"@esbuild/android-x64@npm:0.19.11": "3b0b8affdfec769c5e39f8729eba691af9742178d1e7caa46951f2ee33d29e6da19e7a7ff26a172eaa63b3ffd351c4b4cb8cd041d786f81e7edf8698b7bf1da0",
"@esbuild/android-x64@npm:0.21.5": "24e477ccdaf1437cabe8710bc052a13b975a53617094a225e39823a1c562a71ef975d860ab895c129a813302495d85387143e27068e62fc897a2ac0335e4a2f6",
"@esbuild/android-x64@npm:0.25.1": "92f1bec801b414ecdc4b73adfbb4482f4f2cfba423d33296914fa59ffdafb81facc445a7f65d1eea813c078220a0606f4be02252843bb9039ceb2755f643387c",
"@esbuild/darwin-arm64@npm:0.19.11": "9456be0fdc1e358d365c7f9eb7edcdf8f77ef1f5710778bdaa9f0e95fe4e2863cab3e0d91fa5c5af4a8db551fe8a4b99bb69d1504902064d1302b79779988c07",
"@esbuild/darwin-arm64@npm:0.21.5": "67b0e4ebc870d0babb6721328f02b0e75eca5ee8f176220fa194ac5897ce76a27aa4f6d724389a74e1517670f70e766fe03c30875fdebeee5b1f7b22e99c5a1f",
"@esbuild/darwin-arm64@npm:0.25.1": "9c4cd09c0bd8479e27e04e2397edccebae928184c463684490137d2b7ea3171051b66596db229af2ad3e6a911c1c8a9d10b8aed30d11d0aa155ac0d309fd5dd6",
"@esbuild/darwin-x64@npm:0.19.11": "44e4659456ac62bcab361bbbe67780afdbe5e019dddfa968b632cc8c42ae5b03e14b8cebbcfe73542ea2bf4b04916a4f22ca1c5922f02460def93e28749a132d",
"@esbuild/darwin-x64@npm:0.21.5": "a4b6df47edf4b1e91eeca9d7af18f538e25615e00763709a4cc24a8918e7ac93b9bfc2ef9f44086f16257d05ad99e23c1213a7a1397475d1a78f0a1e773af89a",
"@esbuild/darwin-x64@npm:0.25.1": "31b64c02be9438e175e8b343c52e0bbc7964f08a1b44e0adbfa2a1b229ccd0ad0184cdb8313272b4590298833ea0b197d82a909466caffe90105f8acb30402d0",
"@esbuild/freebsd-arm64@npm:0.19.11": "65315f2d4e7ee893619a8bfebc60cb8556f9942dba2cfe9844c4bb5c023e1d02482cb687aabe2ab318d8d15406269a667d34b106447ad79a45d53773a0972279",
"@esbuild/freebsd-arm64@npm:0.21.5": "a4cf357807f2ea445b5191b8e5488070b567e2b534dba24ce6f8f1a332598ee0b9ffa41b3b0e55cd0cf57e2b56f0f1d84413faba182b81cb43bf19edf58a7654",
"@esbuild/freebsd-arm64@npm:0.25.1": "e04018ffd760dd101a5931e0248751bdfca98e3f29be9b41051d55278907f52a3e5ab80c32168e208c2ff287510d6d5b765d3adc841bfd621af7930c10a547e6",
"@esbuild/freebsd-x64@npm:0.19.11": "6158a47c96d7aaabcf68484f10f256117738a24f33c3464ce75d142234fe5ef488595a1b25b3eb2ef6ebe5819eae7e3002f871fbb4b015774ce3f5e398858669",
"@esbuild/freebsd-x64@npm:0.21.5": "8957c1345196e5dabd7d9f290b5292161f5d9955f269051fa7873118cfb5a20c31d70771ea3560b513f879d0948ba32fba915fb1b387571c4fbbb1fbeaf2dd87",
"@esbuild/freebsd-x64@npm:0.25.1": "438215bea2dc716d2c9ffe9b921bbde06e77d96ec0c837fd79f46bc6e195aa50e5f55490228f7433df7aa47810eec5c602a9d9cf956c1794778018632b196f34",
"@esbuild/linux-arm64@npm:0.19.11": "f2be49288d2e6fb6885ff3952163e42529b73e78b164217550804ca331c61b522d6ede133ceb1025732c3581461133d7703ba2a2a40830a060b63ccce5f94022",
"@esbuild/linux-arm64@npm:0.21.5": "1b95b17ed94eb977e38ea9130e677551b7cf0ccccdb3f23a9f8b59b5d67400817c2a417e4f043295bd3f67796853da2a1b1a8ca201ffe745efb40a144dfdc99c",
"@esbuild/linux-arm64@npm:0.25.1": "c839788b6db471f144bc2627a117083c9a50402c76cde2f0e5411faaeb3a5cde4972bb7336b87de67cd0a65d4d5b00759668407a03b5d4ed3130d4984837429a",
"@esbuild/linux-arm@npm:0.19.11": "8b781b2881b5913dbbc1ab1ae256091980bbf5e0128f23639b3129f7dc0cf8b86726b4df01f0e513a22115c740818e57cc3584f4e092881acc461f623ba472c3",
"@esbuild/linux-arm@npm:0.21.5": "6bfcd098ada5e6117d028777e5cc58456c2f570157fa0a0dce30c9d05b8389b86f74bf6b862534bf6994d342946c98b6774e1820880fd289765864b668e94c17",
"@esbuild/linux-arm@npm:0.25.1": "ecf9fd9028d2166619b9a45161a987eebb6ef3dcb0159a2607ab164c58c26a15cf274e0b1088834c07ef9166349276d595fdad5c42bce8d03a55cb213d947efa",
"@esbuild/linux-ia32@npm:0.19.11": "769746be1d9e3553af93a7bb567436c079d6235da7066a7e225ee2f631f4a9400dd5a0c898c075127176ae00f39d239773bc16ab1177857b4db4db88245e6990",
"@esbuild/linux-ia32@npm:0.21.5": "73c249c9918f0c9a9268ffe14fe745f5e7564b309dcea213da08a5e4367ffdfc8df4b004c70f80269dce0f653a3280cfdd8bf9a7a616b5b60649e4faea6e69b5",
"@esbuild/linux-ia32@npm:0.25.1": "36f208e10a4b778a28a9002338872e52d24dfc18e25d3b41dc53892279c3ee842d76c7b608a30620f128d85344acac0dc86f203f5de7082a21fa2e908c96b68c",
"@esbuild/linux-loong64@npm:0.19.11": "4dea9a63ab261c0b48adca8661db6c80937a58311307d82c04576a83fbde358a14428ab3a2110017b6010e087ff28b8d31af6e91d787b8cc2b1bda9fa8347cac",
"@esbuild/linux-loong64@npm:0.21.5": "60977efe24b3b6e1461d49da07dd57c1234992b9d2e6ac7d0dedfee538321d42be25e496ffb193121d3a6c6ca6ea6722b880e95695824dcc6643a3d9426b2296",
"@esbuild/linux-loong64@npm:0.25.1": "75d95e6ee995c9f2abb202ca430685e5d58fbe9b0b5b01a69b498c9b360d309026d15bae7831de9c0c4f02e45028a92ffb169117c3b56dd1ac7ea8c6ef50628d",
"@esbuild/linux-mips64el@npm:0.19.11": "e84d6938b45c688f35cc1adb141fc10cbf49fedbc6320a981316921c57cff03ecade48df08dff74c0b052b02322cc8b5105258c0eb371c4ebaae5728c08f80fc",
"@esbuild/linux-mips64el@npm:0.21.5": "20fb6c8f6e58f66cd4351034858b2ad85bda4144576b180979305cfabed43780a71934e9f176e476c719f14e37253b231a43d46638ad232989d5f4dd72ec6b75",
"@esbuild/linux-mips64el@npm:0.25.1": "7fc22eb8a7dc9ee743113cb327eef3591bced4753f416f8dd722794a198a053ab6e120b3b13c1bb6cc89cbd02ea502187b1a3c9ebe8187407665c78995153740",
"@esbuild/linux-ppc64@npm:0.19.11": "12882f9a20795b5b395c7b10f52b3f37eb864c024cf964cb657df2337ce88681d3e3896e951917d2870c5d24cb876b6f1e3749356cd1ce0d86a90e7e4ab7edeb",
"@esbuild/linux-ppc64@npm:0.21.5": "69f2ef1d127f48bc14cec479ae1a96dbf5fea651e0a3f148486f73495d2acb91acdaa164cd80648844916f05e7f9f9665a1864dd394e7f9acf96bea70937e6b5",
"@esbuild/linux-ppc64@npm:0.25.1": "84f914a776774bf209c5f96a3708e52477e1966b689a880c0256530063bc581125b5cc04b9700f4aa892e7770ba47c5a950a4be9f1bb3ccbe60a500383602156",
"@esbuild/linux-riscv64@npm:0.19.11": "e12b8f4223a26e9a6e2198698824595daa7d05490b550b6a3d11a8904880507301e16bb4804bc25b018192a6efee07bb4a96da8b8696c6cf95898185fd14a284",
"@esbuild/linux-riscv64@npm:0.21.5": "60c749d87c0f67cc67c5cc0d82aa597b7a807bc52510a16960337433bdbc8fa9f3c46eba98080106c0971e404e2250ca11c441bb4ae5b7a7d78b4095e3a70363",
"@esbuild/linux-riscv64@npm:0.25.1": "97f47cd5695686254b58f950ca973df1fcecfebf3bd585629121a65323ffaa7f95cf821392011ee069da492b46fdff771be332c699a438cf123351610e12d621",
"@esbuild/linux-s390x@npm:0.19.11": "8272e9de0fec7b06cb5f44d95d69b95cbe6bef941368f05215c5b21b1cf6613f35fe9a05e2fe57df179974c67954edceb69b47ca8da984aab62d907764306c31",
"@esbuild/linux-s390x@npm:0.21.5": "a14ff0484b962b374fd1e4662a53f8dd8999ba39fcf891f15631dfb2802c8d18893d6e366c42d28d55885e5804b7d6252c0e3cee038c241285c9b537ef12b4ae",
"@esbuild/linux-s390x@npm:0.25.1": "10760a999d432d092a8ebe5f09752ee7e8ea77a1afa5f1adfe7f9ccb5bbf77d6e0da6dce5d1a4a7cd731d89b6075723c2bc0328f446ed129c4b63c6441ba22ae",
"@esbuild/linux-x64@npm:0.19.11": "52891f2aa5979213831b379081bb303b470750925ddfdb63ede7edb11d48d692c36e0280f4faaa59d4d5a313ba981887fec9097a91388cb36e39c508bdad7d9c",
"@esbuild/linux-x64@npm:0.21.5": "9e5663fcace9c8456e9934a9ed6e7428db4080024eef3bfeaf82d476120bd881382c958be2785463d6b44467b3d3f870d6cce09a9cb37bcef19afeb97814d674",
"@esbuild/linux-x64@npm:0.25.1": "4cdb1625726580eb42432878912d5480a0321559ef2c6425c1db55f89f3fe1c35fb03b3adec92c3f52a4db751d7535f23086b80ed7b219d1edbb254ffbe96e68",
"@esbuild/netbsd-arm64@npm:0.25.1": "f2427b094e072d2db7944b1d2a988dd9f17627976a395b941f225aec2d0565da2ea110a845eafae960af08e3eea74eb327e5eb9dbe06dc1e14f4439596c3b47c",
"@esbuild/netbsd-x64@npm:0.19.11": "a6b0afc71b586c6e3d3db35e84c606b6e587cf708f759bad5c31ee4ed1e960a2285fe0c6a92c4d4404bf9fe5ec5459a137a5fba914fc75b77fbcdb390e3e3a28",
"@esbuild/netbsd-x64@npm:0.21.5": "3cb6115c4557d653c7ad6d2be5b4ed7a688b14d85b7b7108a1a57dda0b2cca3f8ed13560fa6639da8788f860b75eb714a17cfb7ba8f967e93bdf40c9b3a1cde1",
"@esbuild/netbsd-x64@npm:0.25.1": "c8347ecc18b175923a3cbdaef61b64815ffe0cf2cc285f4034337c2df83e78a118ebfdf2ac3a46a8d8a19ce6a0e0605d27d11570d1758ab90074c99047b82fc0",
"@esbuild/openbsd-arm64@npm:0.25.1": "719812786b4f3ab4471a306c930ab1bf31e9d92f933ea2a34566f07d6269df5058e7d48408127daa197e6fdb21e30697303127d259b76b18936d534619a3eb15",
"@esbuild/openbsd-x64@npm:0.19.11": "e35f1612a882b252462c790e7ee7b6cfd231826762a0faa2593f0e3176dd76f180923174098a7636a8c7c40976d471e36d3bc6b773eb06fb025970bc9e54395a",
"@esbuild/openbsd-x64@npm:0.21.5": "1caf0b502d6e2612ffd3e62589de2b9cd48cd742818746011d437e2d5787df4984f7c17b7a536aa20f12d04e519c859d755a7b57e6db0ed277054bd9c1036e85",
"@esbuild/openbsd-x64@npm:0.25.1": "21590cbeda028e9b9f8131c54c36bab65a5d5bd5dd4e6bd9f80438045a655e9ac634bb3535a8650b694db267fe23bd2318a59cdec2fae1ba389ed1a00cc0bbe7",
"@esbuild/sunos-x64@npm:0.19.11": "1e435f24551bab27f9217fa095f9f0aa420db00bf84b244d7a2257bd846affbe812ca8577c20cf08c05910e94e38894dba733c6732af4f6d76f2e6f1c972ca8b",
"@esbuild/sunos-x64@npm:0.21.5": "676da7301c7c600bb7de2523ecf5d877128da3c125fd8136533f5be38ca15d9f800b2fbbd396ca37d44c5daa51b8124d8a4728bb18245cd2becb3191b897c45a",
"@esbuild/sunos-x64@npm:0.25.1": "e20ce3891d8717fe2cb885d92d16e9409802316eaa91071be5c7d75164d23dbc5a502be3691039051843d94192e8cf43ade61014a8bce298cfc13a8d3ddd85b0",
"@esbuild/win32-arm64@npm:0.19.11": "4dc08bcba8e7a59e76627b7ed3f6522e82580d01eedc6e826d19e0068b6a2a1504681e87d311564235d7b9a9d1f40fd3b6cb41777f962f7bc58b9622a55796cf",
"@esbuild/win32-arm64@npm:0.21.5": "9b2ab87429efd3c2697dc5c7948ea57b57757b6e4709469e773b73cd5fe4b7fda5912073f4c3bdf9d0346b8f3ae443367a63bcd51de24fb81b9f592712eb3366",
"@esbuild/win32-arm64@npm:0.25.1": "749a211eae6a47e5ceb71898df668d083bdec2ed762116fea7772824281f793aceb0487946e20ff604d7e102d1fc8538a73f15b476ca36e07f7ddfb601f6dfa1",
"@esbuild/win32-ia32@npm:0.19.11": "113663eb7c1da574d24453a0633b503e44e9961f7fbf2da4673536ae0cd1e627136c04da22741bb6ecf8c199461cec80539a0a6747499c5f3efb75cf9f94116b",
"@esbuild/win32-ia32@npm:0.21.5": "c1fe3276507d82202c464cd4809e67e6f151e29ed9de05c32d086dfe30207db15e646911ebc7f50df659891bfee292a25062792c589c2ff769be238c6b5fb8be",
"@esbuild/win32-ia32@npm:0.25.1": "bb45fd889d858678ec68114bfc398965ed8d44e46a9517fcd9f7b397101c2cf94d78938a2640f6f2a1fe65de4ae8830fd426cd21a28302bb92333913b3c16c85",
"@esbuild/win32-x64@npm:0.19.11": "b62f30ee80d032aaa5937e3046c644fa4884ada4bea32cc7a420bd1f6e827f65c85565438c6adf55335addc52eb78a8d806679d5c96f13093ef719406d2cf89c",
"@esbuild/win32-x64@npm:0.21.5": "5d7b28baa9c22684d35ec0515f6d36f8f583f26733c8e84c7f78edf17b8a7d133819267486f2fd66f20ca3a810896f11c3c81106d745040c2f07ade314846bf1",
"@esbuild/win32-x64@npm:0.25.1": "e33291b9834095e6460bd20bb15c49361758bf66d28ccffe0c06a1565211c91f668d9cfc0cbd5bd7a5def693fe7272dbe290b08d4eadba29e750c8a9c739f564",
"@img/sharp-darwin-arm64@npm:0.33.5": "bca72454845f361ae9e6464547fb1ca9353b1b9ed7bfe5bd116280d6cae8f49b5f941e760bd9f8fd4d5d5274e5e231af7ef42a645318f074a67e3c3de03d5576",
"@img/sharp-darwin-arm64@npm:0.34.3": "c5ed848f3cecaced50be6d1805cfb23586299f119a9917880c3f590b7ddd54b930e493c73480d13452503101168f92a0dc85ccc5b19b330470a5fe69e1fcf206",
"@img/sharp-darwin-x64@npm:0.33.5": "b293e20e66e37180d1251906fcd13ce20c824b84476add53df3f2a1376cbaee1288b91665ea2058db31ec05ef19b85eee9b52fbe82a7603a6c1835007a75121a",
"@img/sharp-darwin-x64@npm:0.34.3": "7e892ca052c8eb9002fb6e86e0a7893aff909e0f203f6ada7a67f4ca7e189631203189700a498ab3401280f5c042f5b08c3f7bc713192a97228864e9c4527743",
"@img/sharp-libvips-darwin-arm64@npm:1.0.4": "1711bd98c5e6d5d381d2301152747fda3e769b5671d4b3a572b383ddbc3568483b591f65bdc7899d46ff1a511f63023e42703b4cb79b4d185e48d6e8e769a6e7",
"@img/sharp-libvips-darwin-arm64@npm:1.2.0": "35d0d475da11fdfd7f5b5f7f1de4376ac04de6d6e0879833f71b2fa305b9d58c96ee464cf234060506ef9b1e2eabe5c09c302e818f8899b2ced1fcb7f85c01eb",
"@img/sharp-libvips-darwin-x64@npm:1.0.4": "3f6ebbdf1b81c1b899ef1448bdfc7e380e5194036d6fdb8b41a136c586e866fd2031f27e5c41575c8126fa2290c061e4ac2e8d8551cdabd489c2f8b617dfd418",
"@img/sharp-libvips-darwin-x64@npm:1.2.0": "e853f3b8a19d815af553b703e1d6d70abec525c61f3f19cc8a9d50d3749bc563b7f255307709b7c0836f76b110948489d3a9a3932d2fbc1775a083a56979bf56",
"@img/sharp-libvips-linux-arm64@npm:1.0.4": "0bef56e45f639cf521208dfeead61247a1ff5b968ed8d8687e346a9ff188cb3a34a90a81376d5557034bff32aaf4566d665a5a055888c2aec3e2a4a25b5060a0",
"@img/sharp-libvips-linux-arm64@npm:1.2.0": "ec53e3e62c9351e28ea0e4c26a1d8ab257ac003e8184b1757b79c2a240baecec2452e0dbca08c8227fd01c498801c428230261fdb86198e873e07347d741c3c7",
"@img/sharp-libvips-linux-arm@npm:1.0.5": "bc68be221896ac177649f2efb98ac77c39150fb5adf5abd90094cec3229122c01058fbe1932aed42d19e8ac6260ba043c807806b946830db385d50887dcde906",
"@img/sharp-libvips-linux-arm@npm:1.2.0": "a2f9822cb2ac5b315f2a02c59ebdb235f6c45251207795f83f1a92db696a9deced882dc752bdddd1eab98da5d7d1d1e9968ae142313ff09960b7ae30b760ba07",
"@img/sharp-libvips-linux-ppc64@npm:1.2.0": "e90e1c46e6020f3a96562dd526549af2fabff46a1576f2e7af003be4b5efeb6fc8caa8e303e2eeb58129c938be31e0bf067736072479f2298152230cecd5fce4",
"@img/sharp-libvips-linux-s390x@npm:1.0.4": "4d0da56b04fc135694d8c92c8d5410cbe2ea0fc6dbce9aaadea02059a628b07231b2f4560e22e98636ce26897aea8eb48c4537b6010f07ced6754dc0b6bda382",
"@img/sharp-libvips-linux-s390x@npm:1.2.0": "2cf62c8b285effd3a3ec8854bc5fd78af162459294a6d9847af33d6bdeacfbad81cb0bec68b76889b148306a8a28492b4d44e6c96b778d0879349180191d758d",
"@img/sharp-libvips-linux-x64@npm:1.0.4": "3c2c09d95b5916dff138390d6aba4c09ba6555a908d5b9eaa0576408d61eba299b013f3828e702c418d830b8f53a73e2e118619df7e5a6738c729ed7f78b6601",
"@img/sharp-libvips-linux-x64@npm:1.2.0": "ca342d07d1f0d22559f26bdcc0354f87a845ef19c69c22e37d9e23d71de15159f50d01183e8e7ea75e0ccfb6c7be9406741414a1f3534ec4aba10e51f0d7dffd",
"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4": "952819ca53ba8d9911e8803674fb71b5854d0f2bff8e947b04a45e84915ffce29f04241359d0ce3ddff874d5ab1a61b3efdac36a5c5245732ca43250e172ff60",
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0": "aa248f0c8602b0a17fdb5c1b00ce7dbb09f05b94a45ee7f226eaebfa459d4ee3fd407c62244902e15699927943c511a7fe045b575185ecc9b585987b64ae6cb1",
"@img/sharp-libvips-linuxmusl-x64@npm:1.0.4": "74c5f218ff390c67d9a95bab314b6d0bb8f2217dd55cbe2bcee2474a5fee4be9772d7924672c5e4d9dc6589f569a1d27e1562c4bb6ec1cee50f02b4799a64849",
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.0": "811071647c5129a1ead1e54b8f4672061bbbc1177d1d49127c755848e81f21bcbf5bf438c0e53118e731be5e000235904de3b5cf305053de015b56609a059371",
"@img/sharp-linux-arm64@npm:0.33.5": "68bbcd09e6598aa480818d2c8a17317b8fe7709491eae4a2a68688c14ba2d39cde5b9f7b48d701f231909b4efd8038d2ad8fd7cfd8ea8fca9adbc2d42487da45",
"@img/sharp-linux-arm64@npm:0.34.3": "3e8d53f54789b3d98dc14bf696917ff50f8c8c355aedc0dc90890ca764259bafec7390eabe262ce843eab8b74e0c128858128ce614e0b85dcba28d6b9ce3ea3f",
"@img/sharp-linux-arm@npm:0.33.5": "2d9a2c22e174fb4b4f832d98587076c7e24ce44ed1a6f1ff074335373dbd9ceaace539652a2109dfc2b4b87c95a9fa3ebb19dfec205a7962129deecfc48da818",
"@img/sharp-linux-arm@npm:0.34.3": "563c48ee129e5f3c6afed71ffba9c79a4369e7967e7b3a4a97d9b1e5aefa511873d3cd7a568ec42cd5968ea76f89ab93ee65c1363170af50559b620bfdb89d5b",
"@img/sharp-linux-ppc64@npm:0.34.3": "5973f8c522f33f826efff4052e253ec554882db54421a00f2636594e9ccc27e16dfb865f8de101678cb07b1a2d31b516ce4a88b65132ecc1101d07b6cc1bd571",
"@img/sharp-linux-s390x@npm:0.33.5": "c1d363553c699cb97abe2107ab59abe16631cef2150c4bd5214902cf444a57d7dc5dd2919b8daa8f09d49671bb6633565df0895fda2b27e29ceb0121343dc689",
"@img/sharp-linux-s390x@npm:0.34.3": "284d2fcd9cc297b27ec135b0c63d5a6170cb17be378d92c75ec19b6034e7ed5801cab04ca17d445a80b073d67c4f9fe809334f5d4edf533c2d81d66d5d882ef3",
"@img/sharp-linux-x64@npm:0.33.5": "870e897b4eaae97140515ce6c2d87c010b015bf53e9cd7182465dc3b1c032cf4e5d4d9e00ec66996220cb924be4b748cdcbcc03f0fffb378338ed4ad81d8d672",
"@img/sharp-linux-x64@npm:0.34.3": "a370c04ca6d4145e6d6529d84b133d80e88d408e4483ba168e5993e5b4c0cbd2db57d4e53aca0bfa43320b571ff8bd70c22d8d7f5fadb5e5df1ded91393ae71d",
"@img/sharp-linuxmusl-arm64@npm:0.33.5": "1f4ef8055027962e3dcc8307e7b4ea070ae2f41ae9ef3442afd0d7515223bf66201bcbdf9097d353f4e64fd572c2ac26eec9e832597fb42da8f27fad6ec4a34d",
"@img/sharp-linuxmusl-arm64@npm:0.34.3": "c63d8afda1a7d4810bdc19473b6235313d923f8763a8dfc182465b63b8e4805769a9e92a8b2136f0235e07ae1b1ab63adfa904bb833d64f0495108dfc180a475",
"@img/sharp-linuxmusl-x64@npm:0.33.5": "c82dd5576f1836947c81a2b573e1953d7b4fcdccb532473e2073024c02a999f8eb17ddb9c2fc5b0fd77a9a7a38edb9720dcfc9358e43a60e8f8eb0ea3f763bae",
"@img/sharp-linuxmusl-x64@npm:0.34.3": "9500e8f5d0ab96395a8d522920f25d4376d928ec58e88b3a5dbce5249c26efec046d20845bee8e9233fbff5287cfa979cfc079bc608342d257a6827be6cc0322",
"@img/sharp-wasm32@npm:0.33.5": "050bab055f1345c12e2fb7cc9dd69b1da3094c3e4536556992c165063f42cd36c8a26ef86c7dbfb1e44534662864f32175e848b2c9f73fbf3623a5433ce77096",
"@img/sharp-wasm32@npm:0.34.3": "840dd97d3151b2189c3d0ee0746c139cbdd0441d5d5dfc87d2dd9970fc2c5448a3815c3110c64009998d165ca6dc7b5d58811b98b2c1e4ab0a1eb5ad97275f1e",
"@img/sharp-win32-arm64@npm:0.34.3": "adc7d18a8334ca1fea09c877dc9493e75a9018f73dc63038c8ccef7535a272c0aa3c2a9867a49a223703747149fb1fe57ef8580dd6098f0f33c602a876a48e1b",
"@img/sharp-win32-ia32@npm:0.33.5": "494e67275dcab551c8310a7d19175e9d8c5cd005546ff970a9fcb6c759fae3a8bf97e68f91e2e292d6c48805ea40ece96b75ba2a49e3feb62d30de25cd833539",
"@img/sharp-win32-ia32@npm:0.34.3": "eef220adf1240429f72d9a9aadc22ccb741889785fa025396cfed41603364990b5f3841cee4d60525b22e65f36c7416297581300c873268fe2b7bb67087ada73",
"@img/sharp-win32-x64@npm:0.33.5": "df976250034aef085d37a52e975e22a6418459bc12e4d0a2ee4f2cd623275b9380a461ccd2cd8945e3b1d30a11b89afda31b5b14e58aaf308e20173f521c4ed4",
"@img/sharp-win32-x64@npm:0.34.3": "5a5d2624bea7a31393b5a89738ad22a2b020a67f5e5b9eb40063510b514e8516b0fb4e320ff9eb1d32fa2ee3b4c3a0387333d051da28d93957116cbbd63b5fe0",
"@msgpackr-extract/msgpackr-extract-darwin-arm64@npm:3.0.3": "f8d48613de17e5356d273745042bbc7c0d345e1e4e5e88ed1132a3ed9ca65db9f8b908a708383d7a58ede276c105e7c9a2887fad00d5e47892010466e5312b47",
"@msgpackr-extract/msgpackr-extract-darwin-x64@npm:3.0.3": "270f8831d7f6d61ff36e7118e09ec13baa633bb22dd57fa3a9e6af67f7036a3fd1beec4c8aad1c72639522300364cef98bc5ba2cddf67a3832f8a35b7fc45502",
"@msgpackr-extract/msgpackr-extract-linux-arm64@npm:3.0.3": "326c19865ead39017326dd8066cde30adaf87a62b968a22d5e5369691817086f681fc291be80b7c61c5496fe915f6df546f41e14ee18b896c54de5099cf147ac",
"@msgpackr-extract/msgpackr-extract-linux-arm@npm:3.0.3": "115f627bcd7c290ddfa1d16405cc1ce388be0b295a26adb1ee2d774133a77809aa0bae83775d3c11f220544e77d34c01d607037bf9dfc6aaac67329cb5fe2818",
"@msgpackr-extract/msgpackr-extract-linux-x64@npm:3.0.3": "ab74755aa2afd27ad8f5277c7cb7a0c721028ba63c3e3109458689340d76ed4c901aa2c71ae5950f3a2149ae9e835e711a12842db219864107cc0c5c1fbdc0da",
"@msgpackr-extract/msgpackr-extract-win32-x64@npm:3.0.3": "edfea4f5455f71f608c15163467b98245d46fbd6078019347418e193968431afe632a11e8729ec39755a54563d86940899e2fe8f3d124393f91855956bd1d745",
"@next/swc-darwin-arm64@npm:14.2.16": "fc2057e68dc5010a7c77b5c57aa8b97678a98d1a57007bf2bba4ecbc625ccb8c1c56dbbbd76b52cca50188608e62964d8fb0f972d7c2d9a31412aa6ca7020bca",
"@next/swc-darwin-arm64@npm:15.5.0": "7c65cc24ebad91c76d2773b2a830889cd266b1e538072425ea19787e546aeee089d032fe9618a7b64955eb6538ab602bf8a1f00bc209c5bab405ac017e156c74",
"@next/swc-darwin-x64@npm:14.2.16": "837a4f04a82685542861788aacc74a830f5c000e65c1099a9f3b93204fb54b4c47b7d5b10bef5a157f9c0d6cf75a28b022da53c2d4eb71b1e4b9d5ad35d843a7",
"@next/swc-darwin-x64@npm:15.5.0": "7e5e71e40e1e0b652d6dd99ab353e32469c1f4f54ce78f3aa1feea30d1b80221295be6ffd928b33a429bb8f130c39ccb44fd8b52871dfc9cc510a82503d2f8e5",
"@next/swc-linux-arm64-gnu@npm:14.2.16": "299a1fb742f133987fb945b9b00c438a3f5f77586576239b5b52f0ed8db14d6dcb6e32400940a0ef831dcaadfd299559d7b28328b245dde71a8ee26f78068077",
"@next/swc-linux-arm64-gnu@npm:15.5.0": "8b2e35974580368419cd8423cce6879296d701941eb9b24ce841c8a992cc737c98ec25e2f90d8644c97bbb6641930e25bc7fe8d7f366c93bd2c3ee352ac55766",
"@next/swc-linux-arm64-musl@npm:14.2.16": "a07024b271f7018700b55774d536cdca3deba796b04892b605cd01cb7d91129db19cb084b8d82456f11f98126a2a441ac3302fbd6d37bc9d3e06a63fb457684a",
"@next/swc-linux-arm64-musl@npm:15.5.0": "e3304b9abc58d1ee73c21ab92f8a3cb82b57dd6413c67cd81319e97b0d8d8ae8d5b1b9eca7ed1b3a62772391e642705efc513d7402d087aa7a2d7c606ac3a026",
"@next/swc-linux-x64-gnu@npm:14.2.16": "40e294f23bb630eb13e22cc98489689b9da68ed914120543c91a165328405e8210105b8004676bf32c109136643d31cd96cbbdd99f71c009c6b3386b1f437ead",
"@next/swc-linux-x64-gnu@npm:15.5.0": "3ed930b34fd0848b5a43c19826d96008a638a9574c6606295a78e29c9aaa09485f986ee9a8729df3b01981aab2630668425015b90c34524a0dc18425f9366e0d",
"@next/swc-linux-x64-musl@npm:14.2.16": "b8c3f4b4bb36bd823c19f74890a91d54abd1958c5c3d9da913ec35e7235a2857987f6af83370797c067bd692c293f8c0db1f614537f0e7feeba4f42cbc468e7a",
"@next/swc-linux-x64-musl@npm:15.5.0": "3078529d42046394d81cc36e817d71dcd6465dfed020aedb838af8727fa44ce2e1fdc7d9f953706d00319e971cd5c8cf1df1f043372ca3bba3bf2a421701d897",
"@next/swc-win32-arm64-msvc@npm:14.2.16": "839e55fb5e5c3208bdc7b5811b8f13d4ff52643efdd703d469149a11fcef54a1ccb855330188652eee2a2d6b65e1b76110d15c3d5643750ce9a2eac74f539ac2",
"@next/swc-win32-arm64-msvc@npm:15.5.0": "3099895bf71d88243105f0f6760a1062bbc16a92977f589c383cc6e37c0a7a508e114dc2f8dd0c93c68fcbae7eac5da2cc6419d691fd53bcf19c31e2866bde7c",
"@next/swc-win32-ia32-msvc@npm:14.2.16": "55acbf3583f3a2241f5d960db1704c8bd5680ad5adbec7dc6047969c6ce0b4745b611da3c3eba38c6a50a15eb5a2eaaadadfd30d6b387d180f374da3bdda8633",
"@next/swc-win32-x64-msvc@npm:14.2.16": "e85f92eca235e6605262c89a45c3b8ea99e8088dec4bf2fa4b16d3dce64f2f5c6653efcac7a23ac98fbb148d2cd18845309c18618d4a63062790b79ee5203561",
"@next/swc-win32-x64-msvc@npm:15.5.0": "31776e854db68a67186729b29054de4e5fba7b885f36f0392d5de937337cbf7e51479f6aaaf2535fdcdb6281c2f0ba639b4aed252e51616945aaa4e917a09352",
"@rollup/rollup-android-arm-eabi@npm:4.35.0": "0d365880d060032a95e862d72ef9086de822087d2442d0c56b8e775b35c4bd512b8f5a9a992ef2ff27c60f38fb53084e65e50dbedc599fd560c070b4927c63d1",
"@rollup/rollup-android-arm-eabi@npm:4.37.0": "137cc44b5452772bc857f3a9b113dc89a95b0d529963e91a49ac1b336302f37931da052a5d07c294da6060d07251c0251c7d363fd2be1775b59afd84a4c2ec67",
"@rollup/rollup-android-arm64@npm:4.35.0": "72aa2d3d47f1f2e99952bc871d360797c79a8f41474e0cdd8e17ba839f94523d3c83fefa5b6e9ea5296578e92cd07573c2eb824c3bad1fa6ce0aa4312c954e60",
"@rollup/rollup-android-arm64@npm:4.37.0": "a6856efb422e0e9ab62f407e0e73908fe78d42fbb2730aeeba3d8b3f034449ef248a0fb596b5af3e87f7054e00328aa4322525a375508f492dbc7c2478aa0e49",
"@rollup/rollup-darwin-arm64@npm:4.35.0": "37bf58948774b446148c6705dd07cdbdb5fa7ec9467bca92021b1d0a061f052e7ca45ddf65f6c22de15acd73b365ab6b26e0b7a870ed012f8bc375bf9bec753d",
"@rollup/rollup-darwin-arm64@npm:4.37.0": "ac29a2a8eb0e40c54a1685c868436658682293cacde10b7cbf587845902e4e95ec4b84e4eefbf7ac6a4b6330b47fe4094dbeeb90f22c0ee130ff984b8d3a1cb9",
"@rollup/rollup-darwin-x64@npm:4.35.0": "70b41ae4f14eaff3658634e79531e5666a76cc0484f6f129fff21cc41aec372676622c88ccd2aa3ccb6fbc56580dd9985e93bc90b9e3e767f936b339bc989201",
"@rollup/rollup-darwin-x64@npm:4.37.0": "037115d7f3b9ceab60df8ea12a48762a6c4051da7008a6b5d47174d9980759c392d7c6e4e79e2989744f0a16d1fb78ae97a9abd309fb1c41b07b65fab1700338",
"@rollup/rollup-freebsd-arm64@npm:4.35.0": "adefc811863f9ff9e8528055506178a3695991fc3a1dd568f98677dd64ed9b003135fd8b064ddcfa11d1515affeb7bab6daace77f8220a8e6264e91108e319c9",
"@rollup/rollup-freebsd-arm64@npm:4.37.0": "8ac2a91f628962633cf33aa462e567b1fc09b4ea09bf7a882ebb7ebf7adf4530d5f9d1b2dfa50ef64ddfa4b820e1eea324041f4c847298ddbe48d7491a944016",
"@rollup/rollup-freebsd-x64@npm:4.35.0": "c4bbee2f451ca386ac3457adb0a4ff1f73e6ba1e02c7887472bf220dd4ea7cec225c86dcc4748073af2306f8738769a8ff90d2343fb96589bac254e91157db70",
"@rollup/rollup-freebsd-x64@npm:4.37.0": "855b528e935ad21de0297112b2b73ea4ca7db94278606811f2d61011cf8f8042e4b7127f7993859320876fba585a00c5e7e11c7b50ce23af4ccca3679a69754f",
"@rollup/rollup-linux-arm-gnueabihf@npm:4.35.0": "450123c65e4e40c250c9cd4b925f01ab34be651b66c544f083e36332efc867298a4d03c4e0de4cc49ef77dda5b72bfcf8815811a392632ca266f459ca2b7d31e",
"@rollup/rollup-linux-arm-gnueabihf@npm:4.37.0": "dc126fb662d1a645ab41680033a987908ed0565b4ea9d414a9a46a61c06e9b136c010d1f28b1c34ffb101375a5279f18ee70d0d9b4ff1f2db80a54a05af3071d",
"@rollup/rollup-linux-arm-musleabihf@npm:4.35.0": "8a9107834b546a8430d438f0a8ab5c016913ae3aeceef23c21e462a7738502c1a7b0cef15751602846fa0f530ffa676d1c3e821591fbcadd31983b9169415bd0",
"@rollup/rollup-linux-arm-musleabihf@npm:4.37.0": "206cf3b319948b5d2d5ada18c698f16350c4cb64b53ad754fad91c1fe403c5fc110b3d3d2436433c5fd9ed1e1cec3535ed184902a6986f8bc0ae9770360e3c0b",
"@rollup/rollup-linux-arm64-gnu@npm:4.35.0": "844c3b1aad40fe18a69b9f6bc4d31228e6497ba95ed8fa9ad1037eb18a472a55b31e9d3bdf7d19f2035213ce4e7e73127cfcadac6141fb975f0c41bacfb360b7",
"@rollup/rollup-linux-arm64-gnu@npm:4.37.0": "f96da93f09aad5145f6fcfd69eccb6a2d407754e13f168d161dd2280991b7e4a3a514009b8a796767e5bd85ca0e4f81e2aea19781bb027d3ec59cf01768e2e1c",
"@rollup/rollup-linux-arm64-musl@npm:4.35.0": "e89bf799436ecf71db90c40a0dcd40eef9813f32c993777676bf81a548eb7d5ec4c0959d33c9c3ee8ae448731655ede2649807601d4c714276ccb295c0ec5e35",
"@rollup/rollup-linux-arm64-musl@npm:4.37.0": "c31f0c0aeefd18b59212f845aacac5a5a614ce0101db27dcc53884ae4d4e766d399d816104a10f4ee8c4c28e2045abb2d6a778c00bd30a430b3f34be4445cb1c",
"@rollup/rollup-linux-loongarch64-gnu@npm:4.35.0": "70e331016745c7688c861ad49f54c444f37570171fbd810c4116cc5335b72802d540e0f6019a0b4475b5c570f51691f313548f9c5bececa37eb759b02802f6dd",
"@rollup/rollup-linux-loongarch64-gnu@npm:4.37.0": "d69b456caaa8c12c2936a6836dc2dd3b9169ebfc2d888c904d3407fb2c53ba99a3323bfd36d447c17ff0b9e31b4709defaf72cafb32c23c773fbffdee50bc7fc",
"@rollup/rollup-linux-powerpc64le-gnu@npm:4.35.0": "73419009942cf6d86815a0a47d7e21b1dcb0d029561cc76d53d2cd1b7b9281f2a532eda7c7bfb546cc997337c9a1c68a1aabfee0330a76d249c119567b9607f5",
"@rollup/rollup-linux-powerpc64le-gnu@npm:4.37.0": "abc5b4e7960965f01948dbf7e956695643cf6ab78ef1d14f2bc34b93cbb85a9ccb9c005dfbf948a80145cd808078a34d216c9d4e801570e2ca638a563ecd0ae2",
"@rollup/rollup-linux-riscv64-gnu@npm:4.35.0": "9c182d0055b6b93008ec8958eeb715e6083c733ec1cf146151572a3111c98e777fdcb0e159c0b9edf5760c1dfcb295f8cbaba4d6115e0a4800bb53828cab228f",
"@rollup/rollup-linux-riscv64-gnu@npm:4.37.0": "a9a5fe6a24955dae476c9ab5fb22d5f807e0ff916ff1ec289c8050e6688695e9c434dfdb130282712dd3657bf883af4544ed1ebc77b68530ccb8e33af484a41e",
"@rollup/rollup-linux-riscv64-musl@npm:4.37.0": "e6cd70f709a59037f496d1086f34148ef2ba92bbf1813e07ccd52b3643822486fa95671a6ed4a3397b9ef23e3a4dea20926f9e1a26b00a06173202627cf2f0ce",
"@rollup/rollup-linux-s390x-gnu@npm:4.35.0": "5d9b6fefcf02ded2487b5e71633c2c8aacd7b6b0a8ab36367fb672995155c0a3164716ef8df859793bb7f6e8d8d1e8435a9bb4f0814bc1135fc13276abec0f79",
"@rollup/rollup-linux-s390x-gnu@npm:4.37.0": "a6704bd605db0a99a60947a6a69ad6d1dbf712f57d4bcaba24a122cc062b2513319726e8211410d5d9d2bc1face3b153c45c4c9209ead6a25f87cf6e79828864",
"@rollup/rollup-linux-x64-gnu@npm:4.35.0": "1488b698bd314b6af0ee114d7cef40c480c6de5c3d7a429f376f5ab93e0c599c442b2fad9eceec95797b189522bb455ecedb3a3f8c2787239da712403b2b3105",
"@rollup/rollup-linux-x64-gnu@npm:4.37.0": "fb0ee05c300d73fb05e5584418c41cd616aca8ccb4c4397b56ada769b4e3655223cdea2ce136c3ff3ed3efb9f74800acbd4801135afc3fbd49b29fff092ebd8c",
"@rollup/rollup-linux-x64-musl@npm:4.35.0": "1d9ac6fd0b9293e86c581c9b2233128b79ddd43e62d982e98a09950fd1892ec4aca30ad9ef356ac9807aa185c3e5888e4eae018dd24409340c300a8c28fa4a14",
"@rollup/rollup-linux-x64-musl@npm:4.37.0": "c502057b9ef77bb43df6337eb02c5bbb8f7aabb2780f3de8256048ade427d319e4d5cca4e4238c3076ddddfd9749d9854e8fbc7f6e50cec84e778da6dafe90b7",
"@rollup/rollup-win32-arm64-msvc@npm:4.35.0": "7d4174cb4976d62df0186c6eb85e1d5e7837773b2faf8da5dc2238c5d08b15a27b098c187e78e425f235b84eda666dc5797ca5f672849842ce7c1a0de673c03f",
"@rollup/rollup-win32-arm64-msvc@npm:4.37.0": "4a6a5c2fb0c520edd3f67deb773ff600698a665ac5a4ba4ac8147836aa054be0f3fab54c841f3eef2b638224dd432a62b5f48b3704f5d3dd37ec7fcfc6a70591",
"@rollup/rollup-win32-ia32-msvc@npm:4.35.0": "dca1a8f3cd91f4c025612d7ec28d624dda0e02d13ae98fcafaa53139fc94e2a9df507635d847c79176ff37dd9bb5d272578c4cdfd9af05bc1cdb78e31296669f",
"@rollup/rollup-win32-ia32-msvc@npm:4.37.0": "439f4339c26c104333b3f55a34ef1a2280b22eec165362eee1816548a0b93f0440a6e3982af9263d1a4158d665a6ecba68c64bf12a786eae69db232399598321",
"@rollup/rollup-win32-x64-msvc@npm:4.35.0": "c6d7a0cbfb489d60ea6f74a2c77c6ae8f9c9b4dae4106da59b640718c4fe7b2273d59ac07fd6d83ac95bcaf172ea908e2eb09c4bab3e0dfbc957e572ab055482",
"@rollup/rollup-win32-x64-msvc@npm:4.37.0": "0c85f06fddfc73dd4457a6c6ec841e52d0fb81179fe63754bd3c02c4795ec338685bd14ff49672ddfb3767aecf4f01f4a5da5730b0cf62be318753390027de37",
"@sentry/cli-darwin@npm:2.42.2": "3df1b8774a2dffba2a413420b573d0ddb94a51ab530cfcf719736cac37fb9b6ff835516b4b783771f003659436a00d3ee5ea201fb2510c1ea517d7256c619b50",
"@sentry/cli-darwin@npm:2.42.4": "c1a2aee566a2d35f6c39f312dd6f4a30b6fa2edc973009fedefc4dc7e36fc26e42cbaf9269e40f6aed8fcc3ab5c106293248b09a1bbec0c3a3872810c949c2bc",
"@sentry/cli-linux-arm64@npm:2.42.2": "17ab90626910c137925aa307bfb4b2f5790a756eaec9bbfa1066b1c2265ec1981ae3d7c8b2014071da9dd3a604cf150ba9a8d93a16e0da155831c7c8a524f956",
"@sentry/cli-linux-arm64@npm:2.42.4": "a9ab66366473ec445cf2daacaa934b16d2b1376a9a7eb1240a35e970c5e93e098248cadc4c2b08a9ab8a97dc3eefb70ff1e70820f15f29227ed07ab165d31e91",
"@sentry/cli-linux-arm@npm:2.42.2": "2d31e7d2603f124e2a36626e91dd8c50a0eba27386b94a7076115022e50193140d7f613850adf64740ee46102cec99845750df05430dd16ea50f01607ead6d2d",
"@sentry/cli-linux-arm@npm:2.42.4": "47ca5bd7049983c863384a318e231aa0cb7ec5f9d5cc7ac6c11f86c4540e3ce4217545eeec2f526dfce3e9580c665ea61d17b40b9cc83e2887f2318b8f527923",
"@sentry/cli-linux-i686@npm:2.42.2": "e5c52d65f507393c786373aaff873d6522f9ab26ef3cea498268ef63d053dc9f4e806c3295c22b48c5dd1f7ecf0c47bfaa0f7545424ef81a3cd51914de2af2bc",
"@sentry/cli-linux-i686@npm:2.42.4": "cd201e1beb369028601fe5c11c6126529c63c08979f01a00ac2aed88979790600a52cd150259a8fd0d9acf15e52d3da2f6fea69b083abf2e09f2293caf968989",
"@sentry/cli-linux-x64@npm:2.42.2": "62ed35b355b7673c8cc1d1542c21d1fb908022c2cc951eda47ee4a55c3e53499c1eca08b93408b547adf9ffe7bcb6d43efa50f185f6fa66155a13373ea79d5f3",
"@sentry/cli-linux-x64@npm:2.42.4": "84c1758ddb2a4c0529d06e73cacfce707711e215c16ceb028e69abe62c1e9bc4b5d410295ac75bc4399b64d76b4516df58ecebe2b46f879eb781e189537f993c",
"@sentry/cli-win32-i686@npm:2.42.2": "3876d8d3f6139d984714ed5fed8ef124d32af8f5d7cd6968adc8e910727e12eb39d85dcc6304a5e249dce7d8e93e11f8bcfde83545835d959f260bf4b1914d00",
"@sentry/cli-win32-i686@npm:2.42.4": "14207ff600dde2406c9d20c794197628e80b71ecdb14e930db67c2f3d806ac0db66303c6284bae4f108e30e357cfa894510a479ed7bbb74ca89a627bbbd4ff1b",
"@sentry/cli-win32-x64@npm:2.42.2": "16e6deb7f0faaed1a88c121463b3ef727726351ec04afe3308ff3775d26e1d7b990a3fd65b216d7fc2ac4f6967c625f14e6dbc2dab99fcc9d7a625ec80422ab8",
"@sentry/cli-win32-x64@npm:2.42.4": "3e6f5fa0bde62824ebbe427a47cba538fd5b7e19ce10b6bb0743ac0f52fc1e677f9c68a84e22fe5b995d29ba00bd1370ed172e6e2ad1732b4c07f181cb1d7d83",
"@unrs/rspack-resolver-binding-darwin-arm64@npm:1.2.2": "8bd22eb6e552a6e205f559fd2a08ab5abba0845df88fa857472a1b5eda460ba890dec1f51d4ead8d1aa33c304378642aea363a1911ad781811ec59f61ace3fea",
"@unrs/rspack-resolver-binding-darwin-x64@npm:1.2.2": "9fbeb7d55fc163ba1a8b3f834e2acdc371145a805b96e46636ff95e76eb895e144b9002fbf1d14ce54a0386e07a7293e6e559f3943f5d0afa91e51370c3191da",
"@unrs/rspack-resolver-binding-freebsd-x64@npm:1.2.2": "6391109bcce6c9a82ea283a59c120fdcad4e0adfc3403096333c5ff2c11e526401aa03f69557a3b314083aaf1cc260b5cbbd945c1177ffcd27d001ab1e212bf5",
"@unrs/rspack-resolver-binding-linux-arm-gnueabihf@npm:1.2.2": "1a97e5a33e937861933c3e1eb100f419229b6e51046bdee1b10ca178c78f26017b6d1cda2dcd64aa65c868cbdd59797e914133d509fc291678beb4349f8717db",
"@unrs/rspack-resolver-binding-linux-arm64-gnu@npm:1.2.2": "30ea0931074f679fc416a8b90d249673fd9d27a89e0df4c0a936d0ee1329145e0bbf5ff9680e30cd5549eb261833dfaa0d8018b7ea7b6ae1ece86ea4cce9e440",
"@unrs/rspack-resolver-binding-linux-arm64-musl@npm:1.2.2": "bee718ba77407238445f2d1950b9287b52fed11f71ebaabd826059ab85cffd3d174e93e2cfbeff151057abd7fb2f4dcbb583aeefd92c9b5342b715ea0aa7cee9",
"@unrs/rspack-resolver-binding-linux-x64-gnu@npm:1.2.2": "d3f4577b88c2d53372144056f8340af9cabd3b48b751a619dd662f7df2e799dcfcd53547eebb6ca1e21e847e2de74d5c692f13be5080076d09e7190904f8d701",
"@unrs/rspack-resolver-binding-linux-x64-musl@npm:1.2.2": "a07c77a1b3f513b262bed00265186d336b07bb271f93ff1998212d66e236310ee53eb618d26bd49f4d021d7f3407e2aa115cf37b28616ba750a8c54065d4db04",
"@unrs/rspack-resolver-binding-wasm32-wasi@npm:1.2.2": "ffc4938bdb6163a789f308b7df55541871dfb5750d59b1a6ac709f76aaa19eb58c6d4ab8f509b4a0e994e97592c682120e78855ff57bfd0d7cf46c008b63a992",
"@unrs/rspack-resolver-binding-win32-arm64-msvc@npm:1.2.2": "1e45426dc0d2d4cdf68b4a331ef4ab3f51c2e60d939654d7db581b32c624636545f829753ee982fd9c5c505150a4ef725aeedb890a7d99f2b4f2c4b0a0e4b18d",
"@unrs/rspack-resolver-binding-win32-x64-msvc@npm:1.2.2": "06db4f536ffe00470cc68904cce08d0f6f8305ce250d2d3b21fbf4f0c53c69074633a82edcfba57a87121595986e6636f40f087ebdfca0e9320c7c8263cdb241"
}

241
nix/nixosModule.nix Normal file
View file

@ -0,0 +1,241 @@
self: {
config,
pkgs,
lib ? pkgs.lib,
...
}:
with lib; let
cfg = config.services.sourcebot;
in {
options.services.sourcebot = {
enable = mkEnableOption "Enable SourceBot";
dataDir = mkOption {
type = types.path;
default = "/var/lib/sourcebot";
description = "Root data directory for SourceBot";
};
package = lib.mkOption {
type = types.package;
default = self.packages.${pkgs.system}.sourcebot;
description = "Package to use for nixos-cli";
};
# Override default, based on DATA_DIR
dataCacheDir = mkOption {
type = types.path;
default = "/var/cache/sourcebot";
description = "Directory for SourceBot data cache";
};
envFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Environment file for additional settings";
};
logLevel = mkOption {
type = types.enum ["debug" "info" "warn" "error"];
default = "info";
description = "SourceBot logging level";
};
authEnabled = mkOption {
type = types.bool;
default = true;
description = "Enables authentication in SourceBot";
};
telemetryDisabled = mkOption {
type = types.bool;
default = true;
description = "Disables telemetry collection in SourceBot";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Open Firwall ports for SourceBot";
};
configPath = mkOption {
type = types.str;
description = "Path to the SourceBot configuration file";
};
port = mkOption {
type = types.int;
default = 7734;
description = "TCP port for the SourceBot web server to listen on";
};
hostname = mkOption {
type = types.str;
default = "0.0.0.0";
description = "Hostname or IP address for the SourceBot web server to bind to";
};
authUrl = mkOption {
type = types.str;
default = "http://${cfg.hostname}:${toString cfg.port}";
description = "Hostname or IP address for the SourceBot web server to bind to";
};
redisPort = mkOption {
type = types.int;
default = 16379;
description = "TCP port for the SourceBot Redis server to listen on";
};
databaseUrl = mkOption {
type = types.nullOr types.str;
default = "postgresql://sourcebot@localhost:${toString config.services.postgresql.settings.port}/sourcebot";
description = "PostgreSQL connection URL for SourceBot. If not set, a local PostgreSQL server will be configured and used.";
};
};
config = mkIf cfg.enable {
# Create a dedicated system group for SourceBot
users.groups.sourcebot = {};
# Create a dedicated system user for SourceBot
users.users.sourcebot = {
isSystemUser = true;
group = "sourcebot"; # primary group
description = "Service account for SourceBot";
home = cfg.dataDir;
shell = "/run/current-system/sw/bin/false";
};
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port];
# Enable redis using the existing NixOS module
services.redis.servers.sourcebot = {
enable = true;
user = "sourcebot";
port = cfg.redisPort;
};
# Enable Postgres for SourceBot with isolated data directory and database
services.postgresql = {
enable = true;
enableTCPIP = true;
ensureDatabases = ["sourcebot"];
ensureUsers = [
{
name = "sourcebot";
ensureDBOwnership = true;
}
];
# Allow connections from any container IP addresses
authentication = mkBefore ''
local sourcebot sourcebot trust
host sourcebot sourcebot 127.0.0.1/32 trust
host sourcebot sourcebot ::1/128 trust
'';
};
# Create and own data directories
systemd.tmpfiles.rules = [
# ensure root data dir
"d ${cfg.dataDir} 0755 sourcebot sourcebot -"
# ensure cache dir
"d ${cfg.dataCacheDir} 0755 sourcebot sourcebot -"
];
# Zoekt search service
systemd.services.sourcebot-zoekt = {
description = "SourceBot Zoekt Search Service";
after = ["network.target" "redis.service"];
wants = ["redis.service"];
serviceConfig = {
# Run under the sourcebot user
User = "sourcebot";
Group = "sourcebot";
ExecStart = "${pkgs.zoekt}/bin/zoekt-webserver -index ${cfg.dataCacheDir}/index -rpc";
Restart = "on-failure";
RestartSec = "5s";
Environment = [
"DATA_DIR=${cfg.dataDir}"
"DATA_CACHE_DIR=${cfg.dataCacheDir}"
];
};
};
systemd.services.sourcebot-db-setup = {
description = "SourceBot Database setup";
after = ["network.target" "postgresql.service"];
wants = ["postgresql.service"];
serviceConfig = {
# Run under the sourcebot user
Type = "oneshot";
User = "sourcebot";
Group = "sourcebot";
ExecStart = "${pkgs.prisma}/bin/prisma migrate deploy --schema ${cfg.package}/packages/db/prisma/schema.prisma";
Environment = [
"PATH=${makeBinPath (with pkgs; [prisma openssl])}"
"DATABASE_URL=${cfg.databaseUrl}"
];
Restart = "on-failure";
RestartSec = "5s";
};
};
# Web frontend service
systemd.services.sourcebot-web = {
description = "SourceBot Web Service";
after = ["network.target" "sourcebot-zoekt.service" "sourcebot-db-setup.service"];
wants = ["sourcebot-zoekt.service" "sourcebot-db-setup.service"];
wantedBy = ["multi-user.target"];
serviceConfig = {
# Run under the sourcebot user
User = "sourcebot";
Group = "sourcebot";
Environment =
[
"DATA_DIR=${cfg.dataDir}"
"DATA_CACHE_DIR=${cfg.dataCacheDir}"
"PORT=${toString cfg.port}"
"HOSTNAME=${cfg.hostname}"
"DATABASE_URL=${cfg.databaseUrl}"
"REDIS_URL=redis://localhost:${toString cfg.redisPort}"
"CONFIG_PATH=${cfg.configPath}"
"SOURCEBOT_LOG_LEVEL=${cfg.logLevel}"
"SOURCEBOT_TENANCY_MODE=single"
"AUTH_CREDENTIALS_LOGIN_ENABLED=${boolToString cfg.authEnabled}"
"SOURCEBOT_TELEMETRY_DISABLED=${boolToString cfg.telemetryDisabled}"
"SOURCEBOT_PUBLIC_KEY_PATH=${cfg.package}/public.pem"
"AUTH_URL=http://${cfg.hostname}:${toString cfg.port}"
]
++ optional (cfg.envFile == null) [
"AUTH_SECRET=00000000000000000000000000000000000000000000"
"SOURCEBOT_ENCRYPTION_KEY=00000000000000000000000000000000"
];
EnvironmentFile = cfg.envFile;
ExecStart = "${cfg.package}/bin/sourcebot-web";
Restart = "always";
};
};
# Backend API service
systemd.services.sourcebot-backend = {
description = "SourceBot Backend Service";
after = ["network.target" "sourcebot-zoekt.service" "sourcebot-db-setup.service"];
wants = ["sourcebot-zoekt.service" "sourcebot-db-setup.service"];
wantedBy = ["multi-user.target"];
serviceConfig = {
# Run under the sourcebot user
User = "sourcebot";
Group = "sourcebot";
Environment =
[
"DATA_DIR=${cfg.dataDir}"
"DATA_CACHE_DIR=${cfg.dataCacheDir}"
"DATABASE_URL=postgresql://sourcebot@localhost:${toString config.services.postgresql.settings.port}/sourcebot"
"REDIS_URL=redis://localhost:${toString cfg.redisPort}"
"CONFIG_PATH=${cfg.configPath}"
"SOURCEBOT_LOG_LEVEL=${cfg.logLevel}"
"SOURCEBOT_TENANCY_MODE=single"
"AUTH_CREDENTIALS_LOGIN_ENABLED=${boolToString cfg.authEnabled}"
"SOURCEBOT_TELEMETRY_DISABLED=${boolToString cfg.telemetryDisabled}"
"SOURCEBOT_PUBLIC_KEY_PATH=${cfg.package}/public.pem"
"AUTH_URL=http://${cfg.hostname}:${toString cfg.port}"
]
++ optional (cfg.envFile == null) [
"AUTH_SECRET=00000000000000000000000000000000000000000000"
"SOURCEBOT_ENCRYPTION_KEY=00000000000000000000000000000000"
];
EnvironmentFile = cfg.envFile;
ExecStart = "${cfg.package}/bin/sourcebot-backend --cacheDir ${cfg.dataCacheDir}";
Restart = "on-failure";
RestartSec = "5s";
};
};
};
}

31
nix/nixosTest.nix Normal file
View file

@ -0,0 +1,31 @@
{
pkgs,
self,
}:
pkgs.testers.nixosTest {
name = "sourcebot-nixos-module-test";
nodes.machine = {config, ...}: {
virtualisation.graphics = false;
documentation.enable = false;
imports = [self.nixosModules.sourcebot];
# disables ForwardToConsole=yes:
# https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/testing/test-instrumentation.nix#L207
services.journald.extraConfig = pkgs.lib.mkForce "";
services.sourcebot = {
enable = true;
configPath = "${pkgs.writeText "config" ''
{
}
''}";
};
};
testScript = ''
start_all()
machine.wait_for_unit("sourcebot-web.service")
machine.wait_for_unit("sourcebot-backend.service")
machine.wait_for_unit("sourcebot-db-setup.service")
machine.wait_for_unit("postgresql.service")
machine.wait_for_unit("sourcebot-zoekt.service")
machine.succeed("${pkgs.curl}/bin/curl http://localhost:7734")
'';
}

12
nix/overlay.nix Normal file
View file

@ -0,0 +1,12 @@
final: prev: {
sourcebot = final.callPackage ./sourcebot.nix {};
zoekt = prev.zoekt.overrideAttrs (old: rec {
vendorHash = "sha256-laiBp+nMWEGofu7zOgfM2b8MIC+Dfw7eCLgb/5zf9oo=";
src = final.fetchFromGitHub {
owner = "sourcegraph";
repo = "zoekt";
rev = "12a2f4ad075359a09bd8a91793acb002211217aa";
hash = "sha256-JByTgJsnqLlP7hNbQumM4zqZZuj7igc2V35vw0ahCqM=";
};
});
}

116
nix/sourcebot.nix Normal file
View file

@ -0,0 +1,116 @@
{
pkgs,
lib,
}:
pkgs.stdenv.mkDerivation (finalAttrs: {
name = "sourcebot";
src = ../.;
buildInputs = with pkgs; [
nodejs
yarn
redis
openssl
];
buildPhase = ''
runHook preBuild
${lib.getExe pkgs.yarn-berry} workspaces foreach -R --from '{@sourcebot/schemas,@sourcebot/error,@sourcebot/db}' run build
${lib.getExe pkgs.yarn-berry} build:deps
${lib.getExe pkgs.yarn-berry} build
runHook postBuild
'';
missingHashes = ./missing-hashes.json;
offlineCache = pkgs.yarn-berry.fetchYarnBerryDeps {
inherit (finalAttrs) src missingHashes;
hash = "sha256-bazFpUV8Pi530wu3wzgx0uAKKBkEtmOosoTTArk888M=";
};
nativeBuildInputs = with pkgs; [
yarn-berry.yarnBerryConfigHook
yarn-berry
nodejs
nodePackages.prisma
makeWrapper
];
env = {
YARN_ENABLE_SCRIPTS = "false";
PRISMA_SCHEMA_ENGINE_BINARY = "${pkgs.prisma-engines}/bin/schema-engine";
PRISMA_QUERY_ENGINE_BINARY = "${pkgs.prisma-engines}/bin/query-engine";
PRISMA_QUERY_ENGINE_LIBRARY = "${pkgs.prisma-engines}/lib/libquery_engine.node";
PRISMA_FMT_BINARY = "${pkgs.prisma-engines}/bin/prisma-fmt";
};
installPhase = ''
runHook preInstall
cp -r packages/web/.next/standalone $out
cp -r node_modules/* $out/node_modules
mkdir -p $out/packages/web
cp -r packages/web/public $out/packages/web/public
mkdir -p $out/packages/web/.next
cp -r packages/web/.next/static $out/packages/web/.next/static
mkdir -p $out/packages/backend
cp -r packages/backend/node_modules/* $out/node_modules
cp -r packages/backend/* $out/packages/backend
cp -r packages/db/* $out/packages/db
cp -r packages/schemas/* $out/packages/schemas
cp -r packages/crypto/* $out/packages/crypto
cp -r packages/error $out/packages/error
cp -r packages/mcp $out/packages/mcp
cp -r packages/logger/* $out/packages/logger
cp -r packages/shared/* $out/packages/shared
cp -r node_modules/* $out/node_modules
mkdir -p $out/bin
cp public.pem $out/
rm -rf $out/pacakages/web/.next/cache
ln -s /var/cache/sourcebot $out/packages/web/.next/cache
# web
cat <<EOF > $out/bin/sourcebot-web
#!${pkgs.runtimeShell}
export PATH=\$PATH:${pkgs.openssl}/bin
export NODE_ENV=production
export NODE_PATH=$out/node_modules
export PRISMA_SCHEMA_ENGINE_BINARY=${pkgs.prisma-engines}/bin/schema-engine
export PRISMA_QUERY_ENGINE_BINARY=${pkgs.prisma-engines}/bin/query-engine
export PRISMA_QUERY_ENGINE_LIBRARY=${pkgs.prisma-engines}/lib/libquery_engine.node
export PRISMA_FMT_BINARY=${pkgs.prisma-engines}/bin/prisma-fmt
exec ${pkgs.nodejs}/bin/node $out/packages/web/server.js "\$@"
EOF
# backend
cat <<EOF > $out/bin/sourcebot-backend
#!${pkgs.runtimeShell}
export NODE_ENV=production
export NODE_PATH=$out/node_modules
export PRISMA_SCHEMA_ENGINE_BINARY=${pkgs.prisma-engines}/bin/schema-engine
export PRISMA_QUERY_ENGINE_BINARY=${pkgs.prisma-engines}/bin/query-engine
export PRISMA_QUERY_ENGINE_LIBRARY=${pkgs.prisma-engines}/lib/libquery_engine.node
export PRISMA_FMT_BINARY=${pkgs.prisma-engines}/bin/prisma-fmt
export PATH=${pkgs.lib.makeBinPath (with pkgs; [prisma openssl git zoekt])}
exec ${pkgs.nodejs}/bin/node $out/packages/backend/dist/index.js "\$@"
EOF
# mcp
cat <<EOF > $out/bin/sourcebot-mcp
#!${pkgs.runtimeShell}
export NODE_ENV=production
export NODE_PATH=$out/node_modules
exec ${pkgs.nodejs}/bin/node $out/packages/mcp/dist/index.js "\$@"
EOF
chmod +x $out/bin/*
runHook postInstall
'';
})

View file

@ -250,7 +250,7 @@ export class ConnectionManager {
create: repo,
})
const upsertDuration = performance.now() - upsertStart;
this.logger.info(`Upserted repo ${repo.displayName} (id: ${repo.external_id}) in ${upsertDuration}ms`);
this.logger.debug(`Upserted repo ${repo.displayName} (id: ${repo.external_id}) in ${upsertDuration}ms`);
}
const totalUpsertDuration = performance.now() - totalUpsertStart;
this.logger.info(`Upserted ${repoData.length} repos for connection ${connectionName} (id: ${job.data.connectionId}) in ${totalUpsertDuration}ms`);

View file

@ -38,32 +38,82 @@ export const repositoryInfoSchema = z.object({
name: z.string(),
displayName: z.string().optional(),
webUrl: z.string().optional(),
})
});
// Many of these fields are defined in zoekt/api.go.
export const searchStatsSchema = z.object({
// The actual number of matches returned by the search.
// This will always be less than or equal to `totalMatchCount`.
actualMatchCount: z.number(),
// The total number of matches found during the search.
totalMatchCount: z.number(),
// The duration (in nanoseconds) of the search.
duration: z.number(),
// Number of files containing a match.
fileCount: z.number(),
// Candidate files whose contents weren't examined because we
// gathered enough matches.
filesSkipped: z.number(),
// Amount of I/O for reading contents.
contentBytesLoaded: z.number(),
// Amount of I/O for reading from index.
indexBytesLoaded: z.number(),
// Number of search shards that had a crash.
crashes: z.number(),
// Number of files in shards that we considered.
shardFilesConsidered: z.number(),
// Files that we evaluated. Equivalent to files for which all
// atom matches (including negations) evaluated to true.
filesConsidered: z.number(),
// Files for which we loaded file content to verify substring matches
filesLoaded: z.number(),
// Shards that we scanned to find matches.
shardsScanned: z.number(),
// Shards that we did not process because a query was canceled.
shardsSkipped: z.number(),
// Shards that we did not process because the query was rejected by the
// ngram filter indicating it had no matches.
shardsSkippedFilter: z.number(),
// Number of candidate matches as a result of searching ngrams.
ngramMatches: z.number(),
// NgramLookups is the number of times we accessed an ngram in the index.
ngramLookups: z.number(),
// Wall clock time for queued search.
wait: z.number(),
// Aggregate wall clock time spent constructing and pruning the match tree.
// This accounts for time such as lookups in the trigram index.
matchTreeConstruction: z.number(),
// Aggregate wall clock time spent searching the match tree. This accounts
// for the bulk of search work done looking for matches.
matchTreeSearch: z.number(),
// Number of times regexp was called on files that we evaluated.
regexpsConsidered: z.number(),
// FlushReason explains why results were flushed.
flushReason: z.number(),
});
export const searchResponseSchema = z.object({
zoektStats: z.object({
// The duration (in nanoseconds) of the search.
duration: z.number(),
fileCount: z.number(),
matchCount: z.number(),
filesSkipped: z.number(),
contentBytesLoaded: z.number(),
indexBytesLoaded: z.number(),
crashes: z.number(),
shardFilesConsidered: z.number(),
filesConsidered: z.number(),
filesLoaded: z.number(),
shardsScanned: z.number(),
shardsSkipped: z.number(),
shardsSkippedFilter: z.number(),
ngramMatches: z.number(),
ngramLookups: z.number(),
wait: z.number(),
matchTreeConstruction: z.number(),
matchTreeSearch: z.number(),
regexpsConsidered: z.number(),
flushReason: z.number(),
}),
stats: searchStatsSchema,
files: z.array(z.object({
fileName: z.object({
// The name of the file
@ -90,6 +140,7 @@ export const searchResponseSchema = z.object({
})),
repositoryInfo: z.array(repositoryInfoSchema),
isBranchFilteringEnabled: z.boolean(),
isSearchExhaustive: z.boolean(),
});
enum RepoIndexingStatus {

View file

@ -21,19 +21,21 @@ import { FilterPanel } from "./components/filterPanel";
import { SearchResultsPanel } from "./components/searchResultsPanel";
import { useDomain } from "@/hooks/useDomain";
import { useToast } from "@/components/hooks/use-toast";
import { RepositoryInfo, SearchResultFile } from "@/features/search/types";
import { RepositoryInfo, SearchResultFile, SearchStats } from "@/features/search/types";
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
import { useFilteredMatches } from "./components/filterPanel/useFilterMatches";
import { Button } from "@/components/ui/button";
import { ImperativePanelHandle } from "react-resizable-panels";
import { FilterIcon } from "lucide-react";
import { AlertTriangleIcon, BugIcon, FilterIcon } from "lucide-react";
import { useHotkeys } from "react-hotkeys-hook";
import { useLocalStorage } from "@uidotdev/usehooks";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
import { SearchBar } from "../components/searchBar";
import { CodeSnippet } from "@/app/components/codeSnippet";
import { CopyIconButton } from "../components/copyIconButton";
const DEFAULT_MAX_MATCH_COUNT = 10000;
const DEFAULT_MAX_MATCH_COUNT = 500;
export default function SearchPage() {
// We need a suspense boundary here since we are accessing query params
@ -58,7 +60,12 @@ const SearchPageInternal = () => {
const _maxMatchCount = parseInt(useNonEmptyQueryParam(SearchQueryParams.matches) ?? `${DEFAULT_MAX_MATCH_COUNT}`);
const maxMatchCount = isNaN(_maxMatchCount) ? DEFAULT_MAX_MATCH_COUNT : _maxMatchCount;
const { data: searchResponse, isLoading: isSearchLoading, error } = useQuery({
const {
data: searchResponse,
isPending: isSearchPending,
isFetching: isFetching,
error
} = useQuery({
queryKey: ["search", searchQuery, maxMatchCount],
queryFn: () => measure(() => unwrapServiceError(search({
query: searchQuery,
@ -68,12 +75,12 @@ const SearchPageInternal = () => {
}, domain)), "client.search"),
select: ({ data, durationMs }) => ({
...data,
durationMs,
totalClientSearchDurationMs: durationMs,
}),
enabled: searchQuery.length > 0,
refetchOnWindowFocus: false,
retry: false,
staleTime: Infinity,
staleTime: 0,
});
useEffect(() => {
@ -109,58 +116,31 @@ const SearchPageInternal = () => {
const fileLanguages = searchResponse.files?.map(file => file.language) || [];
captureEvent("search_finished", {
durationMs: searchResponse.durationMs,
fileCount: searchResponse.zoektStats.fileCount,
matchCount: searchResponse.zoektStats.matchCount,
filesSkipped: searchResponse.zoektStats.filesSkipped,
contentBytesLoaded: searchResponse.zoektStats.contentBytesLoaded,
indexBytesLoaded: searchResponse.zoektStats.indexBytesLoaded,
crashes: searchResponse.zoektStats.crashes,
shardFilesConsidered: searchResponse.zoektStats.shardFilesConsidered,
filesConsidered: searchResponse.zoektStats.filesConsidered,
filesLoaded: searchResponse.zoektStats.filesLoaded,
shardsScanned: searchResponse.zoektStats.shardsScanned,
shardsSkipped: searchResponse.zoektStats.shardsSkipped,
shardsSkippedFilter: searchResponse.zoektStats.shardsSkippedFilter,
ngramMatches: searchResponse.zoektStats.ngramMatches,
ngramLookups: searchResponse.zoektStats.ngramLookups,
wait: searchResponse.zoektStats.wait,
matchTreeConstruction: searchResponse.zoektStats.matchTreeConstruction,
matchTreeSearch: searchResponse.zoektStats.matchTreeSearch,
regexpsConsidered: searchResponse.zoektStats.regexpsConsidered,
flushReason: searchResponse.zoektStats.flushReason,
durationMs: searchResponse.totalClientSearchDurationMs,
fileCount: searchResponse.stats.fileCount,
matchCount: searchResponse.stats.totalMatchCount,
actualMatchCount: searchResponse.stats.actualMatchCount,
filesSkipped: searchResponse.stats.filesSkipped,
contentBytesLoaded: searchResponse.stats.contentBytesLoaded,
indexBytesLoaded: searchResponse.stats.indexBytesLoaded,
crashes: searchResponse.stats.crashes,
shardFilesConsidered: searchResponse.stats.shardFilesConsidered,
filesConsidered: searchResponse.stats.filesConsidered,
filesLoaded: searchResponse.stats.filesLoaded,
shardsScanned: searchResponse.stats.shardsScanned,
shardsSkipped: searchResponse.stats.shardsSkipped,
shardsSkippedFilter: searchResponse.stats.shardsSkippedFilter,
ngramMatches: searchResponse.stats.ngramMatches,
ngramLookups: searchResponse.stats.ngramLookups,
wait: searchResponse.stats.wait,
matchTreeConstruction: searchResponse.stats.matchTreeConstruction,
matchTreeSearch: searchResponse.stats.matchTreeSearch,
regexpsConsidered: searchResponse.stats.regexpsConsidered,
flushReason: searchResponse.stats.flushReason,
fileLanguages,
});
}, [captureEvent, searchQuery, searchResponse]);
const { fileMatches, searchDurationMs, totalMatchCount, isBranchFilteringEnabled, repositoryInfo, matchCount } = useMemo(() => {
if (!searchResponse) {
return {
fileMatches: [],
searchDurationMs: 0,
totalMatchCount: 0,
isBranchFilteringEnabled: false,
repositoryInfo: {},
matchCount: 0,
};
}
return {
fileMatches: searchResponse.files ?? [],
searchDurationMs: Math.round(searchResponse.durationMs),
totalMatchCount: searchResponse.zoektStats.matchCount,
isBranchFilteringEnabled: searchResponse.isBranchFilteringEnabled,
repositoryInfo: searchResponse.repositoryInfo.reduce((acc, repo) => {
acc[repo.id] = repo;
return acc;
}, {} as Record<number, RepositoryInfo>),
matchCount: searchResponse.stats.matchCount,
}
}, [searchResponse]);
const isMoreResultsButtonVisible = useMemo(() => {
return totalMatchCount > maxMatchCount;
}, [totalMatchCount, maxMatchCount]);
const onLoadMoreResults = useCallback(() => {
const url = createPathWithQueryParams(`/${domain}/search`,
@ -183,20 +163,27 @@ const SearchPageInternal = () => {
/>
</TopBar>
{(isSearchLoading) ? (
{(isSearchPending || isFetching) ? (
<div className="flex flex-col items-center justify-center h-full gap-2">
<SymbolIcon className="h-6 w-6 animate-spin" />
<p className="font-semibold text-center">Searching...</p>
</div>
) : error ? (
<div className="flex flex-col items-center justify-center h-full gap-2">
<AlertTriangleIcon className="h-6 w-6" />
<p className="font-semibold text-center">Failed to search</p>
<p className="text-sm text-center">{error.message}</p>
</div>
) : (
<PanelGroup
fileMatches={fileMatches}
isMoreResultsButtonVisible={isMoreResultsButtonVisible}
fileMatches={searchResponse.files}
isMoreResultsButtonVisible={searchResponse.isSearchExhaustive === false}
onLoadMoreResults={onLoadMoreResults}
isBranchFilteringEnabled={isBranchFilteringEnabled}
repoInfo={repositoryInfo}
searchDurationMs={searchDurationMs}
numMatches={matchCount}
isBranchFilteringEnabled={searchResponse.isBranchFilteringEnabled}
repoInfo={searchResponse.repositoryInfo}
searchDurationMs={searchResponse.totalClientSearchDurationMs}
numMatches={searchResponse.stats.actualMatchCount}
searchStats={searchResponse.stats}
/>
)}
</div>
@ -208,9 +195,10 @@ interface PanelGroupProps {
isMoreResultsButtonVisible?: boolean;
onLoadMoreResults: () => void;
isBranchFilteringEnabled: boolean;
repoInfo: Record<number, RepositoryInfo>;
repoInfo: RepositoryInfo[];
searchDurationMs: number;
numMatches: number;
searchStats?: SearchStats;
}
const PanelGroup = ({
@ -218,9 +206,10 @@ const PanelGroup = ({
isMoreResultsButtonVisible,
onLoadMoreResults,
isBranchFilteringEnabled,
repoInfo,
searchDurationMs,
repoInfo: _repoInfo,
searchDurationMs: _searchDurationMs,
numMatches,
searchStats,
}: PanelGroupProps) => {
const [previewedFile, setPreviewedFile] = useState<SearchResultFile | undefined>(undefined);
const filteredFileMatches = useFilteredMatches(fileMatches);
@ -241,6 +230,17 @@ const PanelGroup = ({
description: "Toggle filter panel",
});
const searchDurationMs = useMemo(() => {
return Math.round(_searchDurationMs);
}, [_searchDurationMs]);
const repoInfo = useMemo(() => {
return _repoInfo.reduce((acc, repo) => {
acc[repo.id] = repo;
return acc;
}, {} as Record<number, RepositoryInfo>);
}, [_repoInfo]);
return (
<ResizablePanelGroup
direction="horizontal"
@ -297,7 +297,27 @@ const PanelGroup = ({
order={2}
>
<div className="py-1 px-2 flex flex-row items-center">
<InfoCircledIcon className="w-4 h-4 mr-2" />
<Tooltip>
<TooltipTrigger asChild>
<InfoCircledIcon className="w-4 h-4 mr-2" />
</TooltipTrigger>
<TooltipContent side="right" className="flex flex-col items-start gap-2 p-4">
<div className="flex flex-row items-center w-full">
<BugIcon className="w-4 h-4 mr-1.5" />
<p className="text-md font-medium">Search stats for nerds</p>
<CopyIconButton
onCopy={() => {
navigator.clipboard.writeText(JSON.stringify(searchStats, null, 2));
return true;
}}
className="ml-auto"
/>
</div>
<CodeSnippet renderNewlines>
{JSON.stringify(searchStats, null, 2)}
</CodeSnippet>
</TooltipContent>
</Tooltip>
{
fileMatches.length > 0 ? (
<p className="text-sm font-medium">{`[${searchDurationMs} ms] Found ${numMatches} matches in ${fileMatches.length} ${fileMatches.length > 1 ? 'files' : 'file'}`}</p>

View file

@ -1,12 +1,12 @@
import { cn } from "@/lib/utils"
export const CodeSnippet = ({ children, className, title }: { children: React.ReactNode, className?: string, title?: string }) => {
export const CodeSnippet = ({ children, className, title, renderNewlines = false }: { children: React.ReactNode, className?: string, title?: string, renderNewlines?: boolean }) => {
return (
<code
className={cn("bg-gray-100 dark:bg-gray-700 w-fit rounded-md px-2 py-0.5 font-medium font-mono", className)}
title={title}
>
{children}
{renderNewlines ? <pre>{children}</pre> : children}
</code>
)
}

View file

@ -2,13 +2,10 @@ import { auth } from "@/auth";
import { LoginForm } from "./components/loginForm";
import { redirect } from "next/navigation";
import { Footer } from "@/app/components/footer";
import { createLogger } from "@sourcebot/logger";
import { getAuthProviders } from "@/lib/authProviders";
import { getOrgFromDomain } from "@/data/org";
import { SINGLE_TENANT_ORG_DOMAIN } from "@/lib/constants";
const logger = createLogger('login-page');
interface LoginProps {
searchParams: Promise<{
callbackUrl?: string;
@ -18,10 +15,8 @@ interface LoginProps {
export default async function Login(props: LoginProps) {
const searchParams = await props.searchParams;
logger.info("Login page loaded");
const session = await auth();
if (session) {
logger.info("Session found in login page, redirecting to home");
return redirect("/");
}

View file

@ -15,9 +15,6 @@ export const env = createEnv({
server: {
// Zoekt
ZOEKT_WEBSERVER_URL: z.string().url().default("http://localhost:6070"),
SHARD_MAX_MATCH_COUNT: numberSchema.default(10000),
TOTAL_MAX_MATCH_COUNT: numberSchema.default(100000),
ZOEKT_MAX_WALL_TIME_MS: numberSchema.default(10000),
// Auth
FORCE_ENABLE_ANONYMOUS_ACCESS: booleanSchema.default('false'),

View file

@ -80,7 +80,7 @@ export const findSearchBasedSymbolDefinitions = async (
const parseRelatedSymbolsSearchResponse = (searchResult: SearchResponse) => {
const parser = searchResponseSchema.transform(async ({ files }) => ({
stats: {
matchCount: searchResult.stats.matchCount,
matchCount: searchResult.stats.actualMatchCount,
},
files: files.flatMap((file) => {
const chunks = file.chunks;

View file

@ -37,35 +37,82 @@ export const repositoryInfoSchema = z.object({
name: z.string(),
displayName: z.string().optional(),
webUrl: z.string().optional(),
})
});
// Many of these fields are defined in zoekt/api.go.
export const searchStatsSchema = z.object({
// The actual number of matches returned by the search.
// This will always be less than or equal to `totalMatchCount`.
actualMatchCount: z.number(),
// The total number of matches found during the search.
totalMatchCount: z.number(),
// The duration (in nanoseconds) of the search.
duration: z.number(),
// Number of files containing a match.
fileCount: z.number(),
// Candidate files whose contents weren't examined because we
// gathered enough matches.
filesSkipped: z.number(),
// Amount of I/O for reading contents.
contentBytesLoaded: z.number(),
// Amount of I/O for reading from index.
indexBytesLoaded: z.number(),
// Number of search shards that had a crash.
crashes: z.number(),
// Number of files in shards that we considered.
shardFilesConsidered: z.number(),
// Files that we evaluated. Equivalent to files for which all
// atom matches (including negations) evaluated to true.
filesConsidered: z.number(),
// Files for which we loaded file content to verify substring matches
filesLoaded: z.number(),
// Shards that we scanned to find matches.
shardsScanned: z.number(),
// Shards that we did not process because a query was canceled.
shardsSkipped: z.number(),
// Shards that we did not process because the query was rejected by the
// ngram filter indicating it had no matches.
shardsSkippedFilter: z.number(),
// Number of candidate matches as a result of searching ngrams.
ngramMatches: z.number(),
// NgramLookups is the number of times we accessed an ngram in the index.
ngramLookups: z.number(),
// Wall clock time for queued search.
wait: z.number(),
// Aggregate wall clock time spent constructing and pruning the match tree.
// This accounts for time such as lookups in the trigram index.
matchTreeConstruction: z.number(),
// Aggregate wall clock time spent searching the match tree. This accounts
// for the bulk of search work done looking for matches.
matchTreeSearch: z.number(),
// Number of times regexp was called on files that we evaluated.
regexpsConsidered: z.number(),
// FlushReason explains why results were flushed.
flushReason: z.number(),
});
export const searchResponseSchema = z.object({
zoektStats: z.object({
// The duration (in nanoseconds) of the search.
duration: z.number(),
fileCount: z.number(),
matchCount: z.number(),
filesSkipped: z.number(),
contentBytesLoaded: z.number(),
indexBytesLoaded: z.number(),
crashes: z.number(),
shardFilesConsidered: z.number(),
filesConsidered: z.number(),
filesLoaded: z.number(),
shardsScanned: z.number(),
shardsSkipped: z.number(),
shardsSkippedFilter: z.number(),
ngramMatches: z.number(),
ngramLookups: z.number(),
wait: z.number(),
matchTreeConstruction: z.number(),
matchTreeSearch: z.number(),
regexpsConsidered: z.number(),
flushReason: z.number(),
}),
stats: z.object({
matchCount: z.number(),
}),
stats: searchStatsSchema,
files: z.array(z.object({
fileName: z.object({
// The name of the file
@ -92,6 +139,7 @@ export const searchResponseSchema = z.object({
})),
repositoryInfo: z.array(repositoryInfoSchema),
isBranchFilteringEnabled: z.boolean(),
isSearchExhaustive: z.boolean(),
});
export const fileSourceRequestSchema = z.object({

View file

@ -151,12 +151,48 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ
// @see: https://github.com/sourcebot-dev/zoekt/blob/main/api.go#L892
opts: {
ChunkMatches: true,
// @note: Zoekt has several different ways to limit a given search. The two that
// we care about are `MaxMatchDisplayCount` and `TotalMaxMatchCount`:
// - `MaxMatchDisplayCount` truncates the number of matches AFTER performing
// a search (specifically, after collating and sorting the results). The number of
// results returned by the API will be less than or equal to this value.
//
// - `TotalMaxMatchCount` truncates the number of matches DURING a search. The results
// returned by the API the API can be less than, equal to, or greater than this value.
// Why greater? Because this value is compared _after_ a given shard has finished
// being processed, the number of matches returned by the last shard may have exceeded
// this value.
//
// Let's define two variables:
// - `actualMatchCount` : The number of matches that are returned by the API. This is
// always less than or equal to `MaxMatchDisplayCount`.
// - `totalMatchCount` : The number of matches that zoekt found before it either
// 1) found all matches or 2) hit the `TotalMaxMatchCount` limit. This number is
// not bounded and can be less than, equal to, or greater than both `TotalMaxMatchCount`
// and `MaxMatchDisplayCount`.
//
//
// Our challenge is to determine whether or not the search returned all possible matches/
// (it was exaustive) or if it was truncated. By setting the `TotalMaxMatchCount` to
// `MaxMatchDisplayCount + 1`, we can determine which of these occurred by comparing
// `totalMatchCount` to `MaxMatchDisplayCount`.
//
// if (totalMatchCount ≤ actualMatchCount):
// Search is EXHAUSTIVE (found all possible matches)
// Proof: totalMatchCount ≤ MaxMatchDisplayCount < TotalMaxMatchCount
// Therefore Zoekt stopped naturally, not due to limit
//
// if (totalMatchCount > actualMatchCount):
// Search is TRUNCATED (more matches exist)
// Proof: totalMatchCount > MaxMatchDisplayCount + 1 = TotalMaxMatchCount
// Therefore Zoekt hit the limit and stopped searching
//
MaxMatchDisplayCount: matches,
TotalMaxMatchCount: matches + 1,
NumContextLines: contextLines,
Whole: !!whole,
TotalMaxMatchCount: env.TOTAL_MAX_MATCH_COUNT,
ShardMaxMatchCount: env.SHARD_MAX_MATCH_COUNT,
MaxWallTime: env.ZOEKT_MAX_WALL_TIME_MS * 1000 * 1000, // zoekt expects a duration in nanoseconds
ShardMaxMatchCount: -1,
MaxWallTime: 0, // zoekt expects a duration in nanoseconds
}
});
@ -296,11 +332,35 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ
}
}).filter((file) => file !== undefined) ?? [];
const actualMatchCount = files.reduce(
(acc, file) =>
// Match count is the sum of the number of chunk matches and file name matches.
acc + file.chunks.reduce(
(acc, chunk) => acc + chunk.matchRanges.length,
0,
) + file.fileName.matchRanges.length,
0,
);
const totalMatchCount = Result.MatchCount;
const isSearchExhaustive = totalMatchCount <= actualMatchCount;
return {
zoektStats: {
files,
repositoryInfo: Array.from(repos.values()).map((repo) => ({
id: repo.id,
codeHostType: repo.external_codeHostType,
name: repo.name,
displayName: repo.displayName ?? undefined,
webUrl: repo.webUrl ?? undefined,
})),
isBranchFilteringEnabled,
isSearchExhaustive,
stats: {
actualMatchCount,
totalMatchCount,
duration: Result.Duration,
fileCount: Result.FileCount,
matchCount: Result.MatchCount,
filesSkipped: Result.FilesSkipped,
contentBytesLoaded: Result.ContentBytesLoaded,
indexBytesLoaded: Result.IndexBytesLoaded,
@ -318,25 +378,6 @@ export const search = async ({ query, matches, contextLines, whole }: SearchRequ
matchTreeSearch: Result.MatchTreeSearch,
regexpsConsidered: Result.RegexpsConsidered,
flushReason: Result.FlushReason,
},
files,
repositoryInfo: Array.from(repos.values()).map((repo) => ({
id: repo.id,
codeHostType: repo.external_codeHostType,
name: repo.name,
displayName: repo.displayName ?? undefined,
webUrl: repo.webUrl ?? undefined,
})),
isBranchFilteringEnabled: isBranchFilteringEnabled,
stats: {
matchCount: files.reduce(
(acc, file) =>
acc + file.chunks.reduce(
(acc, chunk) => acc + chunk.matchRanges.length,
0,
),
0,
)
}
} satisfies SearchResponse;
});

View file

@ -8,6 +8,7 @@ import {
fileSourceRequestSchema,
symbolSchema,
repositoryInfoSchema,
searchStatsSchema,
} from "./schemas";
import { z } from "zod";
@ -23,3 +24,4 @@ export type FileSourceResponse = z.infer<typeof fileSourceResponseSchema>;
export type RepositoryInfo = z.infer<typeof repositoryInfoSchema>;
export type SourceRange = z.infer<typeof rangeSchema>;
export type SearchStats = z.infer<typeof searchStatsSchema>;

View file

@ -15,6 +15,7 @@ export type PosthogEventMap = {
shardsSkipped: number,
shardsSkippedFilter: number,
matchCount: number,
actualMatchCount: number,
ngramMatches: number,
ngramLookups: number,
wait: number,