Compare commits

...

16 commits

Author SHA1 Message Date
Brendan Kellam
2be9217ba7
Merge 7341a49407 into d63f3cf9d9 2025-12-11 19:21:26 +08:00
Brendan Kellam
d63f3cf9d9
chore(web): Improve error messages for file loading errors (#665)
Some checks failed
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Has been cancelled
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Has been cancelled
Update Roadmap Released / update (push) Has been cancelled
Publish to ghcr / merge (push) Has been cancelled
2025-12-05 11:58:19 -08:00
Cade 🐀
3d85a0595c
fix: add support for anyuid to Dockerfile (#658)
Some checks are pending
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Waiting to run
Publish to ghcr / merge (push) Blocked by required conditions
Update Roadmap Released / update (push) Waiting to run
* fix: add support for anyuid to Dockerfile

* changelog

---------

Co-authored-by: Cade Schlaefli <cade.schlaefli@mouser.com>
Co-authored-by: Brendan Kellam <bshizzle1234@gmail.com>
2025-12-04 22:29:23 -08:00
Brian Phillips
84cf524d84
Add GHES support to the review agent (#611)
* add support for GHES to the review agent

* fix throttling types

---------

Co-authored-by: Brendan Kellam <bshizzle1234@gmail.com>
2025-12-04 22:08:24 -08:00
bkellam
7c72578765 sourcebot v4.10.2
Some checks are pending
Update Roadmap Released / update (push) Waiting to run
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Waiting to run
Publish to ghcr / merge (push) Blocked by required conditions
2025-12-04 10:41:41 -08:00
Brendan Kellam
483b433aab
fix(web): Respect disable telemetry flag for web server side events (#657)
* fix

* changelog
2025-12-04 10:32:32 -08:00
Brendan Kellam
bcca1d6d7d
chore(web): Fix mistake of upgrading to a breaking version of next (#656)
Some checks are pending
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Waiting to run
Publish to ghcr / merge (push) Blocked by required conditions
Update Roadmap Released / update (push) Waiting to run
2025-12-03 17:12:10 -08:00
bkellam
0e88eecc30 release @sourcebot/mcp v1.0.11
Some checks are pending
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Waiting to run
Publish to ghcr / merge (push) Blocked by required conditions
Update Roadmap Released / update (push) Waiting to run
2025-12-03 16:10:51 -08:00
bkellam
a4685e34ab sourcebot v4.10.1 2025-12-03 16:05:53 -08:00
Brendan Kellam
76dc2f5a12
chore(web): Server side search telemetry (#652) 2025-12-03 16:04:36 -08:00
Brendan Kellam
7fc068f8b2
fix(web): Fix CVE 2025-55182 (#654) 2025-12-03 15:59:43 -08:00
bkellam
91caf129ed chore: add default PostHog token in env.server.ts for development scenarios
Some checks failed
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Has been cancelled
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Has been cancelled
Update Roadmap Released / update (push) Has been cancelled
Publish to ghcr / merge (push) Has been cancelled
2025-12-01 20:18:23 -08:00
Brendan Kellam
92578881df
chore(web): Scope code nav to current repository by default (#647)
Some checks are pending
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Waiting to run
Publish to ghcr / merge (push) Blocked by required conditions
Update Roadmap Released / update (push) Waiting to run
2025-11-30 18:53:09 -08:00
Brendan Kellam
28986f4355
chore(web): Bake PostHog token into build 2025-11-30 18:29:01 -08:00
Adam
41a6eb48a0
Shrink Docker image size by ~1/3 by removing unnecessary ops (#642)
Some checks failed
Publish to ghcr / build (linux/amd64, blacksmith-4vcpu-ubuntu-2404) (push) Has been cancelled
Publish to ghcr / build (linux/arm64, blacksmith-8vcpu-ubuntu-2204-arm) (push) Has been cancelled
Update Roadmap Released / update (push) Has been cancelled
Publish to ghcr / merge (push) Has been cancelled
* Remove duplicate copy, chown on copy

* Add Dockerfile syntax

* Revert entrypoint changes to avoid errors in some non-root cases
2025-11-29 12:43:12 -08:00
bkellam
7341a49407 fix 2025-08-19 11:13:30 -07:00
48 changed files with 886 additions and 616 deletions

View file

@ -21,7 +21,7 @@ AUTH_URL="http://localhost:3000"
DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot) DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot)
SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
# CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exists) CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exists)
# Email # Email
# EMAIL_FROM_ADDRESS="" # The from address for transactional emails. # EMAIL_FROM_ADDRESS="" # The from address for transactional emails.
@ -29,7 +29,6 @@ SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
# PostHog # PostHog
# POSTHOG_PAPIK="" # POSTHOG_PAPIK=""
# NEXT_PUBLIC_POSTHOG_PAPIK=""
# Sentry # Sentry
# SENTRY_BACKEND_DSN="" # SENTRY_BACKEND_DSN=""

View file

@ -55,7 +55,6 @@ jobs:
${{ env.IMAGE_PATH }}:latest ${{ env.IMAGE_PATH }}:latest
build-args: | build-args: |
NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }} NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }}
NEXT_PUBLIC_POSTHOG_PAPIK=${{ vars.NEXT_PUBLIC_POSTHOG_PAPIK }}
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT }} NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }} NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }} NEXT_PUBLIC_SENTRY_WEBAPP_DSN=${{ vars.NEXT_PUBLIC_SENTRY_WEBAPP_DSN }}

View file

@ -77,7 +77,6 @@ jobs:
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 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: | build-args: |
NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }} NEXT_PUBLIC_SOURCEBOT_VERSION=${{ github.ref_name }}
NEXT_PUBLIC_POSTHOG_PAPIK=${{ vars.NEXT_PUBLIC_POSTHOG_PAPIK }}
- name: Export digest - name: Export digest
run: | run: |

View file

@ -7,14 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Fixed
- Fixed review agent so that it works with GHES instances [#611](https://github.com/sourcebot-dev/sourcebot/pull/611)
### Added
- Added support for arbitrary user IDs required for OpenShift. [#658](https://github.com/sourcebot-dev/sourcebot/pull/658)
### Updated
- Improved error messages in file source api. [#665](https://github.com/sourcebot-dev/sourcebot/pull/665)
## [4.10.2] - 2025-12-04
### Fixed
- Fixed issue where the disable telemetry flag was not being respected for web server telemetry. [#657](https://github.com/sourcebot-dev/sourcebot/pull/657)
## [4.10.1] - 2025-12-03
### Added ### Added
- Added `ALWAYS_INDEX_FILE_PATTERNS` environment variable to allow specifying a comma seperated list of glob patterns matching file paths that should always be indexed, regardless of size or # of trigrams. [#631](https://github.com/sourcebot-dev/sourcebot/pull/631) - Added `ALWAYS_INDEX_FILE_PATTERNS` environment variable to allow specifying a comma seperated list of glob patterns matching file paths that should always be indexed, regardless of size or # of trigrams. [#631](https://github.com/sourcebot-dev/sourcebot/pull/631)
- Added button to explore menu to toggle cross-repository search. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647)
- Added server side telemetry for search metrics. [#652](https://github.com/sourcebot-dev/sourcebot/pull/652)
### Fixed ### Fixed
- Fixed issue where single quotes could not be used in search queries. [#629](https://github.com/sourcebot-dev/sourcebot/pull/629) - Fixed issue where single quotes could not be used in search queries. [#629](https://github.com/sourcebot-dev/sourcebot/pull/629)
- Fixed issue where files with special characters would fail to load. [#636](https://github.com/sourcebot-dev/sourcebot/issues/636) - Fixed issue where files with special characters would fail to load. [#636](https://github.com/sourcebot-dev/sourcebot/issues/636)
- Fixed Ask performance issues. [#632](https://github.com/sourcebot-dev/sourcebot/pull/632) - Fixed Ask performance issues. [#632](https://github.com/sourcebot-dev/sourcebot/pull/632)
- Fixed regression where creating a new Ask thread when unauthenticated would result in a 404. [#641](https://github.com/sourcebot-dev/sourcebot/pull/641) - Fixed regression where creating a new Ask thread when unauthenticated would result in a 404. [#641](https://github.com/sourcebot-dev/sourcebot/pull/641)
- Updated react and next package versions to fix CVE 2025-55182. [#654](https://github.com/sourcebot-dev/sourcebot/pull/654)
### Changed
- Changed the default behaviour for code nav to scope references & definitions search to the current repository. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647)
## [4.10.0] - 2025-11-24 ## [4.10.0] - 2025-11-24

View file

@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1
# ------ Global scope variables ------ # ------ Global scope variables ------
# Set of global build arguments. # Set of global build arguments.
@ -8,11 +9,6 @@
# @see: https://docs.docker.com/build/building/variables/#scoping # @see: https://docs.docker.com/build/building/variables/#scoping
ARG NEXT_PUBLIC_SOURCEBOT_VERSION ARG NEXT_PUBLIC_SOURCEBOT_VERSION
# PAPIK = Project API Key
# Note that this key does not need to be kept secret, so it's not
# necessary to use Docker build secrets here.
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
ARG NEXT_PUBLIC_POSTHOG_PAPIK
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
@ -57,8 +53,6 @@ ENV SKIP_ENV_VALIDATION=1
# ----------- # -----------
ARG NEXT_PUBLIC_SOURCEBOT_VERSION ARG NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION
ARG NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$NEXT_PUBLIC_POSTHOG_PAPIK
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT ARG NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT
@ -154,8 +148,6 @@ FROM node-alpine AS runner
# ----------- # -----------
ARG NEXT_PUBLIC_SOURCEBOT_VERSION ARG NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION ENV NEXT_PUBLIC_SOURCEBOT_VERSION=$NEXT_PUBLIC_SOURCEBOT_VERSION
ARG NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$NEXT_PUBLIC_POSTHOG_PAPIK
ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
@ -178,6 +170,12 @@ ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot
ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db ENV DATABASE_DATA_DIR=$DATA_CACHE_DIR/db
ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis
ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem
# PAPIK = Project API Key
# Note that this key does not need to be kept secret, so it's not
# necessary to use Docker build secrets here.
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
# @note: this is also declared in the shared env.server.ts file.
ENV POSTHOG_PAPIK=phc_lLPuFFi5LH6c94eFJcqvYVFwiJffVcV6HD8U4a1OnRW
# Valid values are: debug, info, warn, error # Valid values are: debug, info, warn, error
ENV SOURCEBOT_LOG_LEVEL=info ENV SOURCEBOT_LOG_LEVEL=info
@ -197,6 +195,7 @@ RUN addgroup -g $GID sourcebot && \
adduser -D -u $UID -h /app -S sourcebot && \ adduser -D -u $UID -h /app -S sourcebot && \
adduser sourcebot postgres && \ adduser sourcebot postgres && \
adduser sourcebot redis && \ adduser sourcebot redis && \
chown -R sourcebot /app && \
adduser sourcebot node && \ adduser sourcebot node && \
mkdir /var/log/sourcebot && \ mkdir /var/log/sourcebot && \
chown sourcebot /var/log/sourcebot chown sourcebot /var/log/sourcebot
@ -220,22 +219,23 @@ COPY --from=zoekt-builder \
/cmd/zoekt-index \ /cmd/zoekt-index \
/usr/local/bin/ /usr/local/bin/
RUN chown -R sourcebot:sourcebot /app
# Copy zoekt proto files (needed for gRPC client at runtime) # Copy zoekt proto files (needed for gRPC client at runtime)
COPY vendor/zoekt/grpc/protos /app/vendor/zoekt/grpc/protos COPY --chown=sourcebot:sourcebot vendor/zoekt/grpc/protos /app/vendor/zoekt/grpc/protos
# Copy all of the things # Copy all of the things
COPY --from=web-builder /app/packages/web/public ./packages/web/public COPY --chown=sourcebot:sourcebot --from=web-builder /app/packages/web/public ./packages/web/public
COPY --from=web-builder /app/packages/web/.next/standalone ./ COPY --chown=sourcebot:sourcebot --from=web-builder /app/packages/web/.next/standalone ./
COPY --from=web-builder /app/packages/web/.next/static ./packages/web/.next/static COPY --chown=sourcebot:sourcebot --from=web-builder /app/packages/web/.next/static ./packages/web/.next/static
COPY --from=backend-builder /app/node_modules ./node_modules COPY --chown=sourcebot:sourcebot --from=backend-builder /app/node_modules ./node_modules
COPY --from=backend-builder /app/packages/backend ./packages/backend COPY --chown=sourcebot:sourcebot --from=backend-builder /app/packages/backend ./packages/backend
COPY --from=shared-libs-builder /app/node_modules ./node_modules COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/db ./packages/db
COPY --from=shared-libs-builder /app/packages/db ./packages/db COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/schemas ./packages/schemas
COPY --from=shared-libs-builder /app/packages/schemas ./packages/schemas COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/shared ./packages/shared
COPY --from=shared-libs-builder /app/packages/shared ./packages/shared COPY --chown=sourcebot:sourcebot --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLanguage
COPY --from=shared-libs-builder /app/packages/queryLanguage ./packages/queryLanguage
# Fixes git "dubious ownership" issues when the volume is mounted with different permissions to the container. # Fixes git "dubious ownership" issues when the volume is mounted with different permissions to the container.
RUN git config --global safe.directory "*" RUN git config --global safe.directory "*"
@ -246,9 +246,11 @@ RUN mkdir -p /run/postgresql && \
chmod 775 /run/postgresql chmod 775 /run/postgresql
# Make app directory accessible to both root and sourcebot user # Make app directory accessible to both root and sourcebot user
RUN chown -R sourcebot:sourcebot /app RUN chown -R sourcebot /app \
&& chgrp -R 0 /app \
&& chmod -R g=u /app
# Make data directory accessible to both root and sourcebot user # Make data directory accessible to both root and sourcebot user
RUN chown -R sourcebot:sourcebot /data RUN chown -R sourcebot /data
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY prefix-output.sh ./prefix-output.sh COPY prefix-output.sh ./prefix-output.sh

View file

@ -21,6 +21,7 @@ import LicenseKeyRequired from '/snippets/license-key-required.mdx'
| **Go to definition** | Clicking the "go to definition" button in the popover or clicking the symbol name navigates to the symbol's definition. | | **Go to definition** | Clicking the "go to definition" button in the popover or clicking the symbol name navigates to the symbol's definition. |
| **Find references** | Clicking the "find all references" button in the popover lists all references in the explore panel. | | **Find references** | Clicking the "find all references" button in the popover lists all references in the explore panel. |
| **Explore panel** | Lists all references and definitions for the symbol selected in the popover. | | **Explore panel** | Lists all references and definitions for the symbol selected in the popover. |
| **Cross-repository navigation** | You can search across all repositories by clicking the globe icon in the explore panel. By default, references and definitions are scoped to the repository where the symbol is being resolved. |
## How does it work? ## How does it work?

View file

@ -66,12 +66,6 @@ fi
echo -e "\e[34m[Info] Sourcebot version: $NEXT_PUBLIC_SOURCEBOT_VERSION\e[0m" echo -e "\e[34m[Info] Sourcebot version: $NEXT_PUBLIC_SOURCEBOT_VERSION\e[0m"
# If we don't have a PostHog key, then we need to disable telemetry.
if [ -z "$NEXT_PUBLIC_POSTHOG_PAPIK" ]; then
echo -e "\e[33m[Warning] NEXT_PUBLIC_POSTHOG_PAPIK was not set. Setting SOURCEBOT_TELEMETRY_DISABLED.\e[0m"
export SOURCEBOT_TELEMETRY_DISABLED=true
fi
if [ -n "$SOURCEBOT_TELEMETRY_DISABLED" ]; then if [ -n "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
# Validate that SOURCEBOT_TELEMETRY_DISABLED is either "true" or "false" # Validate that SOURCEBOT_TELEMETRY_DISABLED is either "true" or "false"
if [ "$SOURCEBOT_TELEMETRY_DISABLED" != "true" ] && [ "$SOURCEBOT_TELEMETRY_DISABLED" != "false" ]; then if [ "$SOURCEBOT_TELEMETRY_DISABLED" != "true" ] && [ "$SOURCEBOT_TELEMETRY_DISABLED" != "false" ]; then
@ -159,7 +153,7 @@ if [ ! -f "$FIRST_RUN_FILE" ]; then
# (if telemetry is enabled) # (if telemetry is enabled)
if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then
if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{ if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{
"api_key": "'"$NEXT_PUBLIC_POSTHOG_PAPIK"'", "api_key": "'"$POSTHOG_PAPIK"'",
"event": "install", "event": "install",
"distinct_id": "'"$SOURCEBOT_INSTALL_ID"'", "distinct_id": "'"$SOURCEBOT_INSTALL_ID"'",
"properties": { "properties": {
@ -179,7 +173,7 @@ else
if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then if [ "$SOURCEBOT_TELEMETRY_DISABLED" = "false" ]; then
if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{ if ! ( curl -L --output /dev/null --silent --fail --header "Content-Type: application/json" -d '{
"api_key": "'"$NEXT_PUBLIC_POSTHOG_PAPIK"'", "api_key": "'"$POSTHOG_PAPIK"'",
"event": "upgrade", "event": "upgrade",
"distinct_id": "'"$SOURCEBOT_INSTALL_ID"'", "distinct_id": "'"$SOURCEBOT_INSTALL_ID"'",
"properties": { "properties": {

View file

@ -5,9 +5,9 @@ import { PosthogEvent, PosthogEventMap } from './posthogEvents.js';
let posthog: PostHog | undefined = undefined; let posthog: PostHog | undefined = undefined;
if (clientEnv.NEXT_PUBLIC_POSTHOG_PAPIK) { if (env.POSTHOG_PAPIK) {
posthog = new PostHog( posthog = new PostHog(
clientEnv.NEXT_PUBLIC_POSTHOG_PAPIK, env.POSTHOG_PAPIK,
{ {
host: "https://us.i.posthog.com", host: "https://us.i.posthog.com",
} }

View file

@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.0.11] - 2025-12-03
### Changed
- Updated API client to match the latest Sourcebot release. [#652](https://github.com/sourcebot-dev/sourcebot/pull/652)
## [1.0.10] - 2025-11-24 ## [1.0.10] - 2025-11-24
### Changed ### Changed

View file

@ -1,6 +1,6 @@
{ {
"name": "@sourcebot/mcp", "name": "@sourcebot/mcp",
"version": "1.0.10", "version": "1.0.11",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View file

@ -8,7 +8,6 @@ export const search = async (request: SearchRequest): Promise<SearchResponse | S
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Org-Domain': '~',
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {}) ...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
}, },
body: JSON.stringify(request) body: JSON.stringify(request)
@ -26,7 +25,6 @@ export const listRepos = async (): Promise<ListRepositoriesResponse | ServiceErr
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Org-Domain': '~',
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {}) ...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
}, },
}).then(response => response.json()); }).then(response => response.json());
@ -43,7 +41,6 @@ export const getFileSource = async (request: FileSourceRequest): Promise<FileSou
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Org-Domain': '~',
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {}) ...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
}, },
body: JSON.stringify(request) body: JSON.stringify(request)

View file

@ -76,6 +76,7 @@ server.tool(
contextLines: env.DEFAULT_CONTEXT_LINES, contextLines: env.DEFAULT_CONTEXT_LINES,
isRegexEnabled: true, isRegexEnabled: true,
isCaseSensitivityEnabled: caseSensitive, isCaseSensitivityEnabled: caseSensitive,
source: 'mcp'
}); });
if (isServiceError(response)) { if (isServiceError(response)) {

View file

@ -31,6 +31,7 @@ export const searchOptionsSchema = z.object({
export const searchRequestSchema = z.object({ export const searchRequestSchema = z.object({
query: z.string(), // The zoekt query to execute. query: z.string(), // The zoekt query to execute.
source: z.string().optional(), // The source of the search request.
...searchOptionsSchema.shape, ...searchOptionsSchema.shape,
}); });

View file

@ -7,7 +7,6 @@ export const env = createEnv({
client: { client: {
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(SOURCEBOT_CLOUD_ENVIRONMENT).optional(), NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(SOURCEBOT_CLOUD_ENVIRONMENT).optional(),
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default("unknown"), NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default("unknown"),
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),
NEXT_PUBLIC_SENTRY_BACKEND_DSN: z.string().optional(), NEXT_PUBLIC_SENTRY_BACKEND_DSN: z.string().optional(),
NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().optional(), NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().optional(),
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: z.string().optional(), NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: z.string().optional(),
@ -16,7 +15,6 @@ export const env = createEnv({
runtimeEnvStrict: { runtimeEnvStrict: {
NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT, NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT,
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
NEXT_PUBLIC_SENTRY_BACKEND_DSN: process.env.NEXT_PUBLIC_SENTRY_BACKEND_DSN, NEXT_PUBLIC_SENTRY_BACKEND_DSN: process.env.NEXT_PUBLIC_SENTRY_BACKEND_DSN,
NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT, NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,

View file

@ -120,6 +120,8 @@ export const env = createEnv({
CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(Number.MAX_SAFE_INTEGER), CONFIG_MAX_REPOS_NO_TOKEN: numberSchema.default(Number.MAX_SAFE_INTEGER),
NODE_ENV: z.enum(["development", "test", "production"]), NODE_ENV: z.enum(["development", "test", "production"]),
SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'), SOURCEBOT_TELEMETRY_DISABLED: booleanSchema.default('false'),
// @note: this is also declared in the Dockerfile.
POSTHOG_PAPIK: z.string().default("phc_lLPuFFi5LH6c94eFJcqvYVFwiJffVcV6HD8U4a1OnRW"),
// Database variables // Database variables
// Either DATABASE_URL or DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, and DATABASE_NAME must be set. // Either DATABASE_URL or DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, and DATABASE_NAME must be set.

View file

@ -147,7 +147,7 @@
"langfuse-vercel": "^3.38.4", "langfuse-vercel": "^3.38.4",
"lucide-react": "^0.517.0", "lucide-react": "^0.517.0",
"micromatch": "^4.0.8", "micromatch": "^4.0.8",
"next": "15.5.0", "next": "^15.5.7",
"next-auth": "^5.0.0-beta.30", "next-auth": "^5.0.0-beta.30",
"next-navigation-guard": "^0.2.0", "next-navigation-guard": "^0.2.0",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
@ -156,11 +156,12 @@
"openai": "^4.98.0", "openai": "^4.98.0",
"parse-diff": "^0.11.1", "parse-diff": "^0.11.1",
"posthog-js": "^1.161.5", "posthog-js": "^1.161.5",
"posthog-node": "^5.15.0",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"psl": "^1.15.0", "psl": "^1.15.0",
"react": "19.1.1", "react": "^19.2.1",
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "19.1.1", "react-dom": "^19.2.1",
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"react-hotkeys-hook": "^4.5.1", "react-hotkeys-hook": "^4.5.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
@ -196,8 +197,8 @@
"@types/node": "^20", "@types/node": "^20",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/psl": "^1.1.3", "@types/psl": "^1.1.3",
"@types/react": "19.1.10", "@types/react": "19.2.1",
"@types/react-dom": "19.1.7", "@types/react-dom": "19.2.1",
"@typescript-eslint/eslint-plugin": "^8.40.0", "@typescript-eslint/eslint-plugin": "^8.40.0",
"@typescript-eslint/parser": "^8.40.0", "@typescript-eslint/parser": "^8.40.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -217,7 +218,7 @@
"vitest-mock-extended": "^3.1.0" "vitest-mock-extended": "^3.1.0"
}, },
"resolutions": { "resolutions": {
"@types/react": "19.1.10", "@types/react": "19.2.1",
"@types/react-dom": "19.1.7" "@types/react-dom": "19.2.1"
} }
} }

View file

@ -38,8 +38,8 @@ const auditService = getAuditService();
/** /**
* "Service Error Wrapper". * "Service Error Wrapper".
* *
* Captures any thrown exceptions and converts them to a unexpected * Captures any thrown exceptions, logs them to the console and Sentry,
* service error. Also logs them with Sentry. * and returns a generic unexpected service error.
*/ */
export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> => { export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> => {
try { try {
@ -52,10 +52,6 @@ export const sew = async <T>(fn: () => Promise<T>): Promise<T | ServiceError> =>
return e.serviceError; return e.serviceError;
} }
if (e instanceof Error) {
return unexpectedError(e.message);
}
return unexpectedError(`An unexpected error occurred. Please try again later.`); return unexpectedError(`An unexpected error occurred. Please try again later.`);
} }
} }

View file

@ -22,8 +22,12 @@ export const CodePreviewPanel = async ({ path, repoName, revisionName }: CodePre
getRepoInfoByName(repoName), getRepoInfoByName(repoName),
]); ]);
if (isServiceError(fileSourceResponse) || isServiceError(repoInfoResponse)) { if (isServiceError(fileSourceResponse)) {
return <div>Error loading file source</div> return <div>Error loading file source: {fileSourceResponse.message}</div>
}
if (isServiceError(repoInfoResponse)) {
return <div>Error loading repo info: {repoInfoResponse.message}</div>
} }
const codeHostInfo = getCodeHostInfoForRepo({ const codeHostInfo = getCodeHostInfoForRepo({

View file

@ -3,7 +3,6 @@
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup"; import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension"; import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo";
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement"; import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension"; import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme"; import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
@ -11,14 +10,10 @@ import { useKeymapExtension } from "@/hooks/useKeymapExtension";
import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam";
import { search } from "@codemirror/search"; import { search } from "@codemirror/search";
import CodeMirror, { EditorSelection, EditorView, ReactCodeMirrorRef, SelectionRange, ViewUpdate } from "@uiw/react-codemirror"; import CodeMirror, { EditorSelection, EditorView, ReactCodeMirrorRef, SelectionRange, ViewUpdate } from "@uiw/react-codemirror";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { EditorContextMenu } from "../../../components/editorContextMenu"; import { EditorContextMenu } from "../../../components/editorContextMenu";
import { useBrowseNavigation } from "../../hooks/useBrowseNavigation";
import { BrowseHighlightRange, HIGHLIGHT_RANGE_QUERY_PARAM } from "../../hooks/utils"; import { BrowseHighlightRange, HIGHLIGHT_RANGE_QUERY_PARAM } from "../../hooks/utils";
import { useBrowseState } from "../../hooks/useBrowseState";
import { rangeHighlightingExtension } from "./rangeHighlightingExtension"; import { rangeHighlightingExtension } from "./rangeHighlightingExtension";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { createAuditAction } from "@/ee/features/audit/actions";
interface PureCodePreviewPanelProps { interface PureCodePreviewPanelProps {
path: string; path: string;
@ -40,9 +35,6 @@ export const PureCodePreviewPanel = ({
const [currentSelection, setCurrentSelection] = useState<SelectionRange>(); const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
const keymapExtension = useKeymapExtension(editorRef?.view); const keymapExtension = useKeymapExtension(editorRef?.view);
const hasCodeNavEntitlement = useHasEntitlement("code-nav"); const hasCodeNavEntitlement = useHasEntitlement("code-nav");
const { updateBrowseState } = useBrowseState();
const { navigateToPath } = useBrowseNavigation();
const captureEvent = useCaptureEvent();
const highlightRangeQuery = useNonEmptyQueryParam(HIGHLIGHT_RANGE_QUERY_PARAM); const highlightRangeQuery = useNonEmptyQueryParam(HIGHLIGHT_RANGE_QUERY_PARAM);
const highlightRange = useMemo((): BrowseHighlightRange | undefined => { const highlightRange = useMemo((): BrowseHighlightRange | undefined => {
@ -88,7 +80,6 @@ export const PureCodePreviewPanel = ({
} }
} }
} }
}, [highlightRangeQuery]); }, [highlightRangeQuery]);
const extensions = useMemo(() => { const extensions = useMemo(() => {
@ -116,90 +107,31 @@ export const PureCodePreviewPanel = ({
// Scroll the highlighted range into view. // Scroll the highlighted range into view.
useEffect(() => { useEffect(() => {
if (!highlightRange || !editorRef || !editorRef.state) { if (!highlightRange || !editorRef || !editorRef.state || !editorRef.view) {
return; return;
} }
const doc = editorRef.state.doc; const doc = editorRef.state.doc;
const { start, end } = highlightRange; const { start, end } = highlightRange;
const selection = EditorSelection.range(
doc.line(start.lineNumber).from, const from = doc.line(start.lineNumber).from;
doc.line(end.lineNumber).from, const to = doc.line(end.lineNumber).to;
); const selection = EditorSelection.range(from, to);
// When the selection is in view, we don't want to perform any scrolling
// as it could be jarring for the user. If it is not in view, scroll to the
// center of the viewport.
const viewport = editorRef.view.viewport;
const isInView = from >= viewport.from && to <= viewport.to;
const scrollStrategy = isInView ? "nearest" : "center";
editorRef.view?.dispatch({ editorRef.view?.dispatch({
effects: [ effects: [
EditorView.scrollIntoView(selection, { y: "center" }), EditorView.scrollIntoView(selection, { y: scrollStrategy }),
] ]
}); });
}, [editorRef, highlightRange]); }, [editorRef, highlightRange]);
const onFindReferences = useCallback((symbolName: string) => {
captureEvent('wa_find_references_pressed', {
source: 'browse',
});
createAuditAction({
action: "user.performed_find_references",
metadata: {
message: symbolName,
},
})
updateBrowseState({
selectedSymbolInfo: {
repoName,
symbolName,
revisionName,
language,
},
isBottomPanelCollapsed: false,
activeExploreMenuTab: "references",
})
}, [captureEvent, updateBrowseState, repoName, revisionName, language]);
// If we resolve multiple matches, instead of navigating to the first match, we should
// instead popup the bottom sheet with the list of matches.
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
captureEvent('wa_goto_definition_pressed', {
source: 'browse',
});
createAuditAction({
action: "user.performed_goto_definition",
metadata: {
message: symbolName,
},
})
if (symbolDefinitions.length === 0) {
return;
}
if (symbolDefinitions.length === 1) {
const symbolDefinition = symbolDefinitions[0];
const { fileName, repoName } = symbolDefinition;
navigateToPath({
repoName,
revisionName,
path: fileName,
pathType: 'blob',
highlightRange: symbolDefinition.range,
})
} else {
updateBrowseState({
selectedSymbolInfo: {
symbolName,
repoName,
revisionName,
language,
},
activeExploreMenuTab: "definitions",
isBottomPanelCollapsed: false,
})
}
}, [captureEvent, navigateToPath, revisionName, updateBrowseState, repoName, language]);
const theme = useCodeMirrorTheme(); const theme = useCodeMirrorTheme();
return ( return (
@ -223,11 +155,12 @@ export const PureCodePreviewPanel = ({
)} )}
{editorRef && hasCodeNavEntitlement && ( {editorRef && hasCodeNavEntitlement && (
<SymbolHoverPopup <SymbolHoverPopup
source="preview"
editorRef={editorRef} editorRef={editorRef}
revisionName={revisionName} revisionName={revisionName}
language={language} language={language}
onFindReferences={onFindReferences} fileName={path}
onGotoDefinition={onGotoDefinition} repoName={repoName}
/> />
)} )}
</CodeMirror> </CodeMirror>

View file

@ -10,6 +10,7 @@ import { useBrowseParams } from "./hooks/useBrowseParams";
import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog"; import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog";
import { useDomain } from "@/hooks/useDomain"; import { useDomain } from "@/hooks/useDomain";
import { SearchBar } from "../components/searchBar"; import { SearchBar } from "../components/searchBar";
import escapeStringRegexp from "escape-string-regexp";
interface LayoutProps { interface LayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -30,7 +31,7 @@ export default function Layout({
<SearchBar <SearchBar
size="sm" size="sm"
defaults={{ defaults={{
query: `repo:${repoName}${revisionName ? ` rev:${revisionName}` : ''} `, query: `repo:^${escapeStringRegexp(repoName)}$${revisionName ? ` rev:${revisionName}` : ''} `,
}} }}
className="w-full" className="w-full"
/> />

View file

@ -55,6 +55,7 @@ export const useSuggestionsData = ({
query: `file:${suggestionQuery}`, query: `file:${suggestionQuery}`,
matches: 15, matches: 15,
contextLines: 1, contextLines: 1,
source: 'search-bar-file-suggestions'
}), }),
select: (data): Suggestion[] => { select: (data): Suggestion[] => {
if (isServiceError(data)) { if (isServiceError(data)) {
@ -75,6 +76,7 @@ export const useSuggestionsData = ({
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`, query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
matches: 15, matches: 15,
contextLines: 1, contextLines: 1,
source: 'search-bar-symbol-suggestions'
}), }),
select: (data): Suggestion[] => { select: (data): Suggestion[] => {
if (isServiceError(data)) { if (isServiceError(data)) {

View file

@ -1,12 +1,16 @@
'use client'; 'use client';
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu"; import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
import { SearchResultChunk } from "@/features/search"; import { SearchResultChunk } from "@/features/search";
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme"; import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
import { useKeymapExtension } from "@/hooks/useKeymapExtension"; import { useKeymapExtension } from "@/hooks/useKeymapExtension";
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension"; import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension";
import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension"; import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension";
import { search } from "@codemirror/search"; import { search } from "@codemirror/search";
@ -16,13 +20,6 @@ import { Scrollbar } from "@radix-ui/react-scroll-area";
import CodeMirror, { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror'; import CodeMirror, { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror';
import { ArrowDown, ArrowUp } from "lucide-react"; import { ArrowDown, ArrowUp } from "lucide-react";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo";
import { createAuditAction } from "@/ee/features/audit/actions";
import useCaptureEvent from "@/hooks/useCaptureEvent";
export interface CodePreviewFile { export interface CodePreviewFile {
content: string; content: string;
@ -59,8 +56,6 @@ export const CodePreview = ({
const languageExtension = useCodeMirrorLanguageExtension(file?.language ?? '', editorRef?.view); const languageExtension = useCodeMirrorLanguageExtension(file?.language ?? '', editorRef?.view);
const [currentSelection, setCurrentSelection] = useState<SelectionRange>(); const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
const captureEvent = useCaptureEvent();
const extensions = useMemo(() => { const extensions = useMemo(() => {
return [ return [
keymapExtension, keymapExtension,
@ -115,81 +110,6 @@ export const CodePreview = ({
onSelectedMatchIndexChange((prev) => prev + 1); onSelectedMatchIndexChange((prev) => prev + 1);
}, [onSelectedMatchIndexChange]); }, [onSelectedMatchIndexChange]);
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
captureEvent('wa_goto_definition_pressed', {
source: 'preview',
});
createAuditAction({
action: "user.performed_goto_definition",
metadata: {
message: symbolName,
},
})
if (symbolDefinitions.length === 0) {
return;
}
if (symbolDefinitions.length === 1) {
const symbolDefinition = symbolDefinitions[0];
const { fileName, repoName } = symbolDefinition;
navigateToPath({
repoName,
revisionName: file.revision,
path: fileName,
pathType: 'blob',
highlightRange: symbolDefinition.range,
})
} else {
navigateToPath({
repoName,
revisionName: file.revision,
path: file.filepath,
pathType: 'blob',
setBrowseState: {
selectedSymbolInfo: {
symbolName,
repoName,
revisionName: file.revision,
language: file.language,
},
activeExploreMenuTab: "definitions",
isBottomPanelCollapsed: false,
}
});
}
}, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName]);
const onFindReferences = useCallback((symbolName: string) => {
captureEvent('wa_find_references_pressed', {
source: 'preview',
});
createAuditAction({
action: "user.performed_find_references",
metadata: {
message: symbolName,
},
})
navigateToPath({
repoName,
revisionName: file.revision,
path: file.filepath,
pathType: 'blob',
setBrowseState: {
selectedSymbolInfo: {
repoName,
symbolName,
revisionName: file.revision,
language: file.language,
},
activeExploreMenuTab: "references",
isBottomPanelCollapsed: false,
}
})
}, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName]);
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="flex flex-row bg-accent items-center justify-between pr-3 py-0.5 mt-7"> <div className="flex flex-row bg-accent items-center justify-between pr-3 py-0.5 mt-7">
@ -218,7 +138,7 @@ export const CodePreview = ({
}} }}
title={file.filepath} title={file.filepath}
> >
{file.filepath} <span>{file.filepath}</span>
</span> </span>
</div> </div>
@ -286,11 +206,12 @@ export const CodePreview = ({
{editorRef && hasCodeNavEntitlement && ( {editorRef && hasCodeNavEntitlement && (
<SymbolHoverPopup <SymbolHoverPopup
source="preview"
editorRef={editorRef} editorRef={editorRef}
language={file.language} language={file.language}
revisionName={file.revision} revisionName={file.revision}
onFindReferences={onFindReferences} fileName={file.filepath}
onGotoDefinition={onGotoDefinition} repoName={repoName}
/> />
)} )}
</CodeMirror> </CodeMirror>

View file

@ -52,7 +52,7 @@ export const Entry = ({
<div className="overflow-hidden flex-1 min-w-0"> <div className="overflow-hidden flex-1 min-w-0">
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<p className="overflow-hidden text-ellipsis whitespace-nowrap truncate-start">{displayName}</p> <p className="overflow-hidden text-ellipsis whitespace-nowrap truncate-start"><span>{displayName}</span></p>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="right" className="max-w-sm"> <TooltipContent side="right" className="max-w-sm">
<p className="font-mono text-sm break-all whitespace-pre-wrap">{displayName}</p> <p className="font-mono text-sm break-all whitespace-pre-wrap">{displayName}</p>

View file

@ -129,7 +129,8 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
whole, whole,
isRegexEnabled, isRegexEnabled,
isCaseSensitivityEnabled, isCaseSensitivityEnabled,
}), source: 'sourcebot-web-client'
} satisfies SearchRequest),
signal: abortControllerRef.current.signal, signal: abortControllerRef.current.signal,
}); });

View file

@ -4,6 +4,7 @@ import { search, searchRequestSchema } from "@/features/search";
import { isServiceError } from "@/lib/utils"; import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server"; import { NextRequest } from "next/server";
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError"; import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
import { captureEvent } from "@/lib/posthog";
export const POST = async (request: NextRequest) => { export const POST = async (request: NextRequest) => {
const body = await request.json(); const body = await request.json();
@ -16,9 +17,15 @@ export const POST = async (request: NextRequest) => {
const { const {
query, query,
source,
...options ...options
} = parsed.data; } = parsed.data;
await captureEvent('api_code_search_request', {
source: source ?? 'unknown',
type: 'blocking',
});
const response = await search({ const response = await search({
queryType: 'string', queryType: 'string',
query, query,
@ -28,5 +35,6 @@ export const POST = async (request: NextRequest) => {
if (isServiceError(response)) { if (isServiceError(response)) {
return serviceErrorResponse(response); return serviceErrorResponse(response);
} }
return Response.json(response); return Response.json(response);
} }

View file

@ -1,6 +1,7 @@
'use server'; 'use server';
import { streamSearch, searchRequestSchema } from '@/features/search'; import { streamSearch, searchRequestSchema } from '@/features/search';
import { captureEvent } from '@/lib/posthog';
import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError'; import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError';
import { isServiceError } from '@/lib/utils'; import { isServiceError } from '@/lib/utils';
import { NextRequest } from 'next/server'; import { NextRequest } from 'next/server';
@ -15,9 +16,15 @@ export const POST = async (request: NextRequest) => {
const { const {
query, query,
source,
...options ...options
} = parsed.data; } = parsed.data;
await captureEvent('api_code_search_request', {
source: source ?? 'unknown',
type: 'streamed',
});
const stream = await streamSearch({ const stream = await streamSearch({
queryType: 'string', queryType: 'string',
query, query,

View file

@ -6,28 +6,32 @@ import { WebhookEventDefinition} from "@octokit/webhooks/types";
import { EndpointDefaults } from "@octokit/types"; import { EndpointDefaults } from "@octokit/types";
import { env } from "@sourcebot/shared"; import { env } from "@sourcebot/shared";
import { processGitHubPullRequest } from "@/features/agents/review-agent/app"; import { processGitHubPullRequest } from "@/features/agents/review-agent/app";
import { throttling } from "@octokit/plugin-throttling"; import { throttling, type ThrottlingOptions } from "@octokit/plugin-throttling";
import fs from "fs"; import fs from "fs";
import { GitHubPullRequest } from "@/features/agents/review-agent/types"; import { GitHubPullRequest } from "@/features/agents/review-agent/types";
import { createLogger } from "@sourcebot/shared"; import { createLogger } from "@sourcebot/shared";
const logger = createLogger('github-webhook'); const logger = createLogger('github-webhook');
let githubApp: App | undefined; const DEFAULT_GITHUB_API_BASE_URL = "https://api.github.com";
type GitHubAppBaseOptions = Omit<ConstructorParameters<typeof App>[0], "Octokit"> & { throttle: ThrottlingOptions };
let githubAppBaseOptions: GitHubAppBaseOptions | undefined;
const githubAppCache = new Map<string, App>();
if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET && env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH) { if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET && env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH) {
try { try {
const privateKey = fs.readFileSync(env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH, "utf8"); const privateKey = fs.readFileSync(env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH, "utf8");
const throttledOctokit = Octokit.plugin(throttling); githubAppBaseOptions = {
githubApp = new App({
appId: env.GITHUB_REVIEW_AGENT_APP_ID, appId: env.GITHUB_REVIEW_AGENT_APP_ID,
privateKey: privateKey, privateKey,
webhooks: { webhooks: {
secret: env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET, secret: env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET,
}, },
Octokit: throttledOctokit,
throttle: { throttle: {
onRateLimit: (retryAfter: number, options: Required<EndpointDefaults>, octokit: Octokit, retryCount: number) => { enabled: true,
onRateLimit: (retryAfter, _options, _octokit, retryCount) => {
if (retryCount > 3) { if (retryCount > 3) {
logger.warn(`Rate limit exceeded: ${retryAfter} seconds`); logger.warn(`Rate limit exceeded: ${retryAfter} seconds`);
return false; return false;
@ -35,13 +39,55 @@ if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET
return true; return true;
}, },
onSecondaryRateLimit: (_retryAfter, options) => {
// no retries on secondary rate limits
logger.warn(`SecondaryRateLimit detected for ${options.method} ${options.url}`);
} }
}); },
};
} catch (error) { } catch (error) {
logger.error(`Error initializing GitHub app: ${error}`); logger.error(`Error initializing GitHub app: ${error}`);
} }
} }
const normalizeGithubApiBaseUrl = (baseUrl?: string) => {
if (!baseUrl) {
return DEFAULT_GITHUB_API_BASE_URL;
}
return baseUrl.replace(/\/+$/, "");
};
const resolveGithubApiBaseUrl = (headers: Record<string, string>) => {
const enterpriseHost = headers["x-github-enterprise-host"];
if (enterpriseHost) {
return normalizeGithubApiBaseUrl(`https://${enterpriseHost}/api/v3`);
}
return DEFAULT_GITHUB_API_BASE_URL;
};
const getGithubAppForBaseUrl = (baseUrl: string) => {
if (!githubAppBaseOptions) {
return undefined;
}
const normalizedBaseUrl = normalizeGithubApiBaseUrl(baseUrl);
const cachedApp = githubAppCache.get(normalizedBaseUrl);
if (cachedApp) {
return cachedApp;
}
const OctokitWithBaseUrl = Octokit.plugin(throttling).defaults({ baseUrl: normalizedBaseUrl });
const app = new App({
...githubAppBaseOptions,
Octokit: OctokitWithBaseUrl,
});
githubAppCache.set(normalizedBaseUrl, app);
return app;
};
function isPullRequestEvent(eventHeader: string, payload: unknown): payload is WebhookEventDefinition<"pull-request-opened"> | WebhookEventDefinition<"pull-request-synchronize"> { function isPullRequestEvent(eventHeader: string, payload: unknown): payload is WebhookEventDefinition<"pull-request-opened"> | WebhookEventDefinition<"pull-request-synchronize"> {
return eventHeader === "pull_request" && typeof payload === "object" && payload !== null && "action" in payload && typeof payload.action === "string" && (payload.action === "opened" || payload.action === "synchronize"); return eventHeader === "pull_request" && typeof payload === "object" && payload !== null && "action" in payload && typeof payload.action === "string" && (payload.action === "opened" || payload.action === "synchronize");
} }
@ -52,12 +98,16 @@ function isIssueCommentEvent(eventHeader: string, payload: unknown): payload is
export const POST = async (request: NextRequest) => { export const POST = async (request: NextRequest) => {
const body = await request.json(); const body = await request.json();
const headers = Object.fromEntries(request.headers.entries()); const headers = Object.fromEntries(Array.from(request.headers.entries(), ([key, value]) => [key.toLowerCase(), value]));
const githubEvent = headers['x-github-event'] || headers['X-GitHub-Event']; const githubEvent = headers['x-github-event'];
if (githubEvent) { if (githubEvent) {
logger.info('GitHub event received:', githubEvent); logger.info('GitHub event received:', githubEvent);
const githubApiBaseUrl = resolveGithubApiBaseUrl(headers);
logger.debug('Using GitHub API base URL for event', { githubApiBaseUrl });
const githubApp = getGithubAppForBaseUrl(githubApiBaseUrl);
if (!githubApp) { if (!githubApp) {
logger.warn('Received GitHub webhook event but GitHub app env vars are not set'); logger.warn('Received GitHub webhook event but GitHub app env vars are not set');
return Response.json({ status: 'ok' }); return Response.json({ status: 'ok' });

View file

@ -1,13 +1,15 @@
import { cn } from '@/lib/utils'
import React from 'react' import React from 'react'
interface KeyboardShortcutHintProps { interface KeyboardShortcutHintProps {
shortcut: string shortcut: string
label?: string label?: string
className?: string
} }
export function KeyboardShortcutHint({ shortcut, label }: KeyboardShortcutHintProps) { export function KeyboardShortcutHint({ shortcut, label, className }: KeyboardShortcutHintProps) {
return ( return (
<div className="inline-flex items-center" aria-label={label || `Keyboard shortcut: ${shortcut}`}> <div className={cn("inline-flex items-center", className)} aria-label={label || `Keyboard shortcut: ${shortcut}`}>
<kbd <kbd
className="px-2 py-1 font-semibold font-sans border rounded-md" className="px-2 py-1 font-semibold font-sans border rounded-md"
style={{ style={{

View file

@ -313,6 +313,11 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.truncate-start > * {
direction: ltr;
unicode-bidi: embed;
}
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;

View file

@ -37,7 +37,12 @@ export default function RootLayout({
<Toaster /> <Toaster />
<SessionProvider> <SessionProvider>
<PlanProvider entitlements={getEntitlements()}> <PlanProvider entitlements={getEntitlements()}>
<PostHogProvider disabled={env.SOURCEBOT_TELEMETRY_DISABLED === "true"}> <PostHogProvider
isDisabled={env.SOURCEBOT_TELEMETRY_DISABLED === "true"}
// @note: the posthog api key doesn't need to be kept secret,
// so we are safe to send it to the client.
posthogApiKey={env.POSTHOG_PAPIK}
>
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
defaultTheme="system" defaultTheme="system"

View file

@ -32,16 +32,16 @@ function PostHogPageView() {
interface PostHogProviderProps { interface PostHogProviderProps {
children: React.ReactNode children: React.ReactNode
disabled: boolean isDisabled: boolean
posthogApiKey: string
} }
export function PostHogProvider({ children, disabled }: PostHogProviderProps) { export function PostHogProvider({ children, isDisabled, posthogApiKey }: PostHogProviderProps) {
const { data: session } = useSession(); const { data: session } = useSession();
useEffect(() => { useEffect(() => {
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) { if (!isDisabled) {
console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`); posthog.init(posthogApiKey, {
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
// @see next.config.mjs for path rewrites to the "/ingest" route. // @see next.config.mjs for path rewrites to the "/ingest" route.
api_host: "/ingest", api_host: "/ingest",
person_profiles: 'identified_only', person_profiles: 'identified_only',
@ -66,7 +66,7 @@ export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
} else { } else {
console.debug("PostHog telemetry disabled"); console.debug("PostHog telemetry disabled");
} }
}, [disabled]); }, [isDisabled, posthogApiKey]);
useEffect(() => { useEffect(() => {
if (!session) { if (!session) {

View file

@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const toggleVariants = cva( const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2", "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2 cursor-pointer",
{ {
variants: { variants: {
variant: { variant: {
@ -16,9 +16,7 @@ const toggleVariants = cva(
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground", "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
}, },
size: { size: {
default: "h-10 px-3 min-w-10", default: "h-7 w-7 min-w-7 p-0",
sm: "h-9 px-2.5 min-w-9",
lg: "h-11 px-5 min-w-11",
}, },
}, },
defaultVariants: { defaultVariants: {

View file

@ -1,19 +1,23 @@
'use client'; 'use client';
import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState"; import { useBrowseState } from "@/app/[domain]/browse/hooks/useBrowseState";
import { findSearchBasedSymbolReferences, findSearchBasedSymbolDefinitions} from "@/app/api/(client)/client"; import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "@/app/api/(client)/client";
import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle"; import { AnimatedResizableHandle } from "@/components/ui/animatedResizableHandle";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; import { ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
import { Toggle } from "@/components/ui/toggle";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useDomain } from "@/hooks/useDomain"; import { useDomain } from "@/hooks/useDomain";
import { unwrapServiceError } from "@/lib/utils"; import { measure, unwrapServiceError } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import clsx from "clsx"; import clsx from "clsx";
import { Loader2 } from "lucide-react"; import { GlobeIcon, Loader2 } from "lucide-react";
import { useMemo } from "react"; import { useMemo, useState } from "react";
import { VscSymbolMisc } from "react-icons/vsc"; import { VscSymbolMisc } from "react-icons/vsc";
import { ReferenceList } from "./referenceList"; import { ReferenceList } from "./referenceList";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
import { useHotkeys } from "react-hotkeys-hook";
import useCaptureEvent from "@/hooks/useCaptureEvent";
interface ExploreMenuProps { interface ExploreMenuProps {
selectedSymbolInfo: { selectedSymbolInfo: {
@ -27,27 +31,39 @@ interface ExploreMenuProps {
export const ExploreMenu = ({ export const ExploreMenu = ({
selectedSymbolInfo, selectedSymbolInfo,
}: ExploreMenuProps) => { }: ExploreMenuProps) => {
const domain = useDomain(); const domain = useDomain();
const captureEvent = useCaptureEvent();
const { const {
state: { activeExploreMenuTab }, state: { activeExploreMenuTab },
updateBrowseState, updateBrowseState,
} = useBrowseState(); } = useBrowseState();
const [isGlobalSearchEnabled, setIsGlobalSearchEnabled] = useState(false);
const { const {
data: referencesResponse, data: referencesResponse,
isError: isReferencesResponseError, isError: isReferencesResponseError,
isPending: isReferencesResponsePending, isPending: isReferencesResponsePending,
isLoading: isReferencesResponseLoading, isLoading: isReferencesResponseLoading,
} = useQuery({ } = useQuery({
queryKey: ["references", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain], queryKey: ["references", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain, isGlobalSearchEnabled],
queryFn: () => unwrapServiceError( queryFn: async () => {
const response = await measure(() => unwrapServiceError(
findSearchBasedSymbolReferences({ findSearchBasedSymbolReferences({
symbolName: selectedSymbolInfo.symbolName, symbolName: selectedSymbolInfo.symbolName,
language: selectedSymbolInfo.language, language: selectedSymbolInfo.language,
revisionName: selectedSymbolInfo.revisionName, revisionName: selectedSymbolInfo.revisionName,
repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName
}) })
), ), 'findSearchBasedSymbolReferences', false);
captureEvent('wa_explore_menu_references_loaded', {
durationMs: response.durationMs,
isGlobalSearchEnabled,
})
return response.data;
}
}); });
const { const {
@ -56,14 +72,32 @@ export const ExploreMenu = ({
isPending: isDefinitionsResponsePending, isPending: isDefinitionsResponsePending,
isLoading: isDefinitionsResponseLoading, isLoading: isDefinitionsResponseLoading,
} = useQuery({ } = useQuery({
queryKey: ["definitions", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain], queryKey: ["definitions", selectedSymbolInfo.symbolName, selectedSymbolInfo.repoName, selectedSymbolInfo.revisionName, selectedSymbolInfo.language, domain, isGlobalSearchEnabled],
queryFn: () => unwrapServiceError( queryFn: async () => {
const response = await measure(() => unwrapServiceError(
findSearchBasedSymbolDefinitions({ findSearchBasedSymbolDefinitions({
symbolName: selectedSymbolInfo.symbolName, symbolName: selectedSymbolInfo.symbolName,
language: selectedSymbolInfo.language, language: selectedSymbolInfo.language,
revisionName: selectedSymbolInfo.revisionName, revisionName: selectedSymbolInfo.revisionName,
repoName: isGlobalSearchEnabled ? undefined : selectedSymbolInfo.repoName
}) })
), ), 'findSearchBasedSymbolDefinitions', false);
captureEvent('wa_explore_menu_definitions_loaded', {
durationMs: response.durationMs,
isGlobalSearchEnabled,
})
return response.data;
}
});
useHotkeys('shift+a', () => {
setIsGlobalSearchEnabled(prev => !prev);
}, {
enableOnFormTags: true,
enableOnContentEditable: true,
description: "Search all repositories",
}); });
const isPending = isReferencesResponsePending || isDefinitionsResponsePending; const isPending = isReferencesResponsePending || isDefinitionsResponsePending;
@ -98,8 +132,11 @@ export const ExploreMenu = ({
<ResizablePanel <ResizablePanel
minSize={10} minSize={10}
maxSize={20} maxSize={20}
className="flex flex-col h-full"
> >
<div className="flex flex-col p-2"> <div className="flex flex-col p-2">
<div className="flex flex-row items-center justify-between">
<Tooltip <Tooltip
delayDuration={100} delayDuration={100}
> >
@ -116,11 +153,31 @@ export const ExploreMenu = ({
</TooltipTrigger> </TooltipTrigger>
<TooltipContent <TooltipContent
side="top" side="top"
align="start" align="center"
> >
Symbol references and definitions found using a best-guess search heuristic. Symbol references and definitions found using a best-guess search heuristic.
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Toggle
pressed={isGlobalSearchEnabled}
onPressedChange={setIsGlobalSearchEnabled}
>
<GlobeIcon className="w-4 h-4" />
</Toggle>
</span>
</TooltipTrigger>
<TooltipContent side="top" align="center">
Search all repositories
<KeyboardShortcutHint
shortcut="⇧ A"
className="ml-2"
/>
</TooltipContent>
</Tooltip>
</div>
<div className="flex flex-col gap-1 mt-4"> <div className="flex flex-col gap-1 mt-4">
<Entry <Entry
name="References" name="References"

View file

@ -1,42 +1,50 @@
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
import { useToast } from "@/components/hooks/use-toast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { LoadingButton } from "@/components/ui/loading-button"; import { LoadingButton } from "@/components/ui/loading-button";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { createAuditAction } from "@/ee/features/audit/actions";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react"; import { computePosition, flip, offset, shift, VirtualElement } from "@floating-ui/react";
import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { SymbolDefinition, useHoveredOverSymbolInfo } from "./useHoveredOverSymbolInfo";
import { SymbolDefinitionPreview } from "./symbolDefinitionPreview";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { useHotkeys } from "react-hotkeys-hook"; import { useHotkeys } from "react-hotkeys-hook";
import { useToast } from "@/components/hooks/use-toast"; import { SymbolDefinitionPreview } from "./symbolDefinitionPreview";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useHoveredOverSymbolInfo } from "./useHoveredOverSymbolInfo";
import { KeyboardShortcutHint } from "@/app/components/keyboardShortcutHint";
interface SymbolHoverPopupProps { interface SymbolHoverPopupProps {
editorRef: ReactCodeMirrorRef; editorRef: ReactCodeMirrorRef;
language: string; language: string;
revisionName: string; revisionName: string;
onFindReferences: (symbolName: string) => void; repoName: string;
onGotoDefinition: (symbolName: string, symbolDefinitions: SymbolDefinition[]) => void; fileName: string;
source: 'browse' | 'preview' | 'chat';
} }
export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
editorRef, editorRef,
revisionName, revisionName,
language, language,
onFindReferences, repoName,
onGotoDefinition: _onGotoDefinition, fileName,
source,
}) => { }) => {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [isSticky, setIsSticky] = useState(false); const [isSticky, setIsSticky] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
const { navigateToPath } = useBrowseNavigation();
const captureEvent = useCaptureEvent();
const symbolInfo = useHoveredOverSymbolInfo({ const symbolInfo = useHoveredOverSymbolInfo({
editorRef, editorRef,
isSticky, isSticky,
revisionName, revisionName,
language, language,
repoName,
}); });
// Positions the popup relative to the symbol // Positions the popup relative to the symbol
@ -77,13 +85,118 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
} }
}, [symbolInfo, editorRef]); }, [symbolInfo, editorRef]);
// Multiple symbol definitions can exist for the same symbol, but we can only navigate
// and display a preview of one. If the symbol definition exists in the current file,
// then we use that one, otherwise we fallback to the first definition in the list.
const previewedSymbolDefinition = useMemo(() => {
if (!symbolInfo?.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0) {
return undefined;
}
const matchingDefinition = symbolInfo.symbolDefinitions.find(
(definition) => (
definition.fileName === fileName && definition.repoName === repoName
)
);
if (matchingDefinition) {
return matchingDefinition;
}
return symbolInfo.symbolDefinitions[0];
}, [fileName, repoName, symbolInfo?.symbolDefinitions]);
const onGotoDefinition = useCallback(() => { const onGotoDefinition = useCallback(() => {
if (!symbolInfo || !symbolInfo.symbolDefinitions) { if (
!symbolInfo ||
!symbolInfo.symbolDefinitions ||
!previewedSymbolDefinition
) {
return; return;
} }
_onGotoDefinition(symbolInfo.symbolName, symbolInfo.symbolDefinitions); captureEvent('wa_goto_definition_pressed', {
}, [symbolInfo, _onGotoDefinition]); source,
});
createAuditAction({
action: "user.performed_goto_definition",
metadata: {
message: symbolInfo.symbolName,
},
});
const {
fileName,
repoName,
revisionName,
language,
range: highlightRange,
} = previewedSymbolDefinition;
navigateToPath({
// Always navigate to the preview symbol definition.
repoName,
revisionName,
path: fileName,
pathType: 'blob',
highlightRange,
// If there are multiple definitions, we should open the Explore panel with the definitions.
...(symbolInfo.symbolDefinitions.length > 1 ? {
setBrowseState: {
selectedSymbolInfo: {
symbolName: symbolInfo.symbolName,
repoName,
revisionName,
language,
},
activeExploreMenuTab: "definitions",
isBottomPanelCollapsed: false,
}
} : {}),
});
}, [
captureEvent,
previewedSymbolDefinition,
navigateToPath,
source,
symbolInfo
]);
const onFindReferences = useCallback(() => {
if (!symbolInfo) {
return;
}
captureEvent('wa_find_references_pressed', {
source,
});
createAuditAction({
action: "user.performed_find_references",
metadata: {
message: symbolInfo.symbolName,
},
})
navigateToPath({
repoName,
revisionName,
path: fileName,
pathType: 'blob',
highlightRange: symbolInfo.range,
setBrowseState: {
selectedSymbolInfo: {
symbolName: symbolInfo.symbolName,
repoName,
revisionName,
language,
},
activeExploreMenuTab: "references",
isBottomPanelCollapsed: false,
}
})
}, [captureEvent, fileName, language, navigateToPath, repoName, revisionName, source, symbolInfo]);
// @todo: We should probably make the behaviour s.t., the ctrl / cmd key needs to be held // @todo: We should probably make the behaviour s.t., the ctrl / cmd key needs to be held
// down to navigate to the definition. We should also only show the underline when the key // down to navigate to the definition. We should also only show the underline when the key
@ -100,9 +213,7 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
}, [symbolInfo, onGotoDefinition]); }, [symbolInfo, onGotoDefinition]);
useHotkeys('alt+shift+f12', () => { useHotkeys('alt+shift+f12', () => {
if (symbolInfo?.symbolName) { onFindReferences();
onFindReferences(symbolInfo.symbolName);
}
}, { }, {
enableOnFormTags: true, enableOnFormTags: true,
enableOnContentEditable: true, enableOnContentEditable: true,
@ -147,9 +258,9 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
<Loader2 className="w-4 h-4 animate-spin" /> <Loader2 className="w-4 h-4 animate-spin" />
Loading... Loading...
</div> </div>
) : symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 0 ? ( ) : previewedSymbolDefinition ? (
<SymbolDefinitionPreview <SymbolDefinitionPreview
symbolDefinition={symbolInfo.symbolDefinitions[0]} symbolDefinition={previewedSymbolDefinition}
/> />
) : ( ) : (
<p className="text-sm font-medium text-muted-foreground">No hover info found</p> <p className="text-sm font-medium text-muted-foreground">No hover info found</p>
@ -160,13 +271,13 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<LoadingButton <LoadingButton
loading={symbolInfo.isSymbolDefinitionsLoading} loading={symbolInfo.isSymbolDefinitionsLoading}
disabled={!symbolInfo.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0} disabled={!previewedSymbolDefinition}
variant="outline" variant="outline"
size="sm" size="sm"
onClick={onGotoDefinition} onClick={onGotoDefinition}
> >
{ {
!symbolInfo.isSymbolDefinitionsLoading && (!symbolInfo.symbolDefinitions || symbolInfo.symbolDefinitions.length === 0) ? !symbolInfo.isSymbolDefinitionsLoading && !previewedSymbolDefinition ?
"No definition found" : "No definition found" :
`Go to ${symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 1 ? "definitions" : "definition"}` `Go to ${symbolInfo.symbolDefinitions && symbolInfo.symbolDefinitions.length > 1 ? "definitions" : "definition"}`
} }
@ -186,7 +297,7 @@ export const SymbolHoverPopup: React.FC<SymbolHoverPopupProps> = ({
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => onFindReferences(symbolInfo.symbolName)} onClick={onFindReferences}
> >
Find references Find references
</Button> </Button>

View file

@ -1,7 +1,8 @@
import { findSearchBasedSymbolDefinitions } from "@/app/api/(client)/client"; import { findSearchBasedSymbolDefinitions } from "@/app/api/(client)/client";
import { SourceRange } from "@/features/search"; import { SourceRange } from "@/features/search";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { useDomain } from "@/hooks/useDomain"; import { useDomain } from "@/hooks/useDomain";
import { unwrapServiceError } from "@/lib/utils"; import { measure, unwrapServiceError } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { ReactCodeMirrorRef } from "@uiw/react-codemirror"; import { ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@ -12,6 +13,7 @@ interface UseHoveredOverSymbolInfoProps {
isSticky: boolean; isSticky: boolean;
revisionName: string; revisionName: string;
language: string; language: string;
repoName: string;
} }
export type SymbolDefinition = { export type SymbolDefinition = {
@ -19,12 +21,14 @@ export type SymbolDefinition = {
language: string; language: string;
fileName: string; fileName: string;
repoName: string; repoName: string;
revisionName: string;
range: SourceRange; range: SourceRange;
} }
interface HoveredOverSymbolInfo { interface HoveredOverSymbolInfo {
element: HTMLElement; element: HTMLElement;
symbolName: string; symbolName: string;
range: SourceRange;
isSymbolDefinitionsLoading: boolean; isSymbolDefinitionsLoading: boolean;
symbolDefinitions?: SymbolDefinition[]; symbolDefinitions?: SymbolDefinition[];
} }
@ -37,6 +41,7 @@ export const useHoveredOverSymbolInfo = ({
isSticky, isSticky,
revisionName, revisionName,
language, language,
repoName,
}: UseHoveredOverSymbolInfoProps): HoveredOverSymbolInfo | undefined => { }: UseHoveredOverSymbolInfoProps): HoveredOverSymbolInfo | undefined => {
const mouseOverTimerRef = useRef<NodeJS.Timeout | null>(null); const mouseOverTimerRef = useRef<NodeJS.Timeout | null>(null);
const mouseOutTimerRef = useRef<NodeJS.Timeout | null>(null); const mouseOutTimerRef = useRef<NodeJS.Timeout | null>(null);
@ -49,15 +54,26 @@ export const useHoveredOverSymbolInfo = ({
return (symbolElement && symbolElement.textContent) ?? undefined; return (symbolElement && symbolElement.textContent) ?? undefined;
}, [symbolElement]); }, [symbolElement]);
const captureEvent = useCaptureEvent();
const { data: symbolDefinitions, isLoading: isSymbolDefinitionsLoading } = useQuery({ const { data: symbolDefinitions, isLoading: isSymbolDefinitionsLoading } = useQuery({
queryKey: ["definitions", symbolName, revisionName, language, domain], queryKey: ["definitions", symbolName, revisionName, language, domain, repoName],
queryFn: () => unwrapServiceError( queryFn: async () => {
const response = await measure(() => unwrapServiceError(
findSearchBasedSymbolDefinitions({ findSearchBasedSymbolDefinitions({
symbolName: symbolName!, symbolName: symbolName!,
language, language,
revisionName, revisionName,
repoName,
}) })
), ), 'findSearchBasedSymbolDefinitions', false);
captureEvent('wa_symbol_hover_popup_definitions_loaded', {
durationMs: response.durationMs,
});
return response.data;
},
select: ((data) => { select: ((data) => {
return data.files.flatMap((file) => { return data.files.flatMap((file) => {
return file.matches.map((match) => { return file.matches.map((match) => {
@ -66,6 +82,7 @@ export const useHoveredOverSymbolInfo = ({
language: file.language, language: file.language,
fileName: file.fileName, fileName: file.fileName,
repoName: file.repository, repoName: file.repository,
revisionName: revisionName,
range: match.range, range: match.range,
} }
}) })
@ -122,17 +139,64 @@ export const useHoveredOverSymbolInfo = ({
}; };
}, [editorRef, domain, clearTimers]); }, [editorRef, domain, clearTimers]);
// Extract the highlight range of the symbolElement from the editor view.
const highlightRange = useMemo((): SourceRange | undefined => {
if (!symbolElement || !editorRef.view) {
return undefined;
}
const view = editorRef.view;
const rect = symbolElement.getBoundingClientRect();
// Get the start position (left edge, middle vertically)
const startPos = view.posAtCoords({
x: rect.left,
y: rect.top + rect.height / 2,
});
// Get the end position (right edge, middle vertically)
const endPos = view.posAtCoords({
x: rect.right,
y: rect.top + rect.height / 2,
});
if (startPos === null || endPos === null) {
return undefined;
}
// Convert CodeMirror positions to SourceRange format
const startLine = view.state.doc.lineAt(startPos);
const endLine = view.state.doc.lineAt(endPos);
const startColumn = startPos - startLine.from + 1; // 1-based column
const endColumn = endPos - endLine.from + 1; // 1-based column
return {
start: {
byteOffset: startPos, // 0-based byte offset
lineNumber: startLine.number, // 1-based line number
column: startColumn, // 1-based column
},
end: {
byteOffset: endPos, // 0-based byte offset
lineNumber: endLine.number, // 1-based line number
column: endColumn, // 1-based column
},
};
}, [symbolElement, editorRef.view]);
if (!isVisible && !isSticky) { if (!isVisible && !isSticky) {
return undefined; return undefined;
} }
if (!symbolElement || !symbolName) { if (!symbolElement || !symbolName || !highlightRange) {
return undefined; return undefined;
} }
return { return {
element: symbolElement, element: symbolElement,
symbolName, symbolName,
range: highlightRange,
isSymbolDefinitionsLoading: isSymbolDefinitionsLoading, isSymbolDefinitionsLoading: isSymbolDefinitionsLoading,
symbolDefinitions, symbolDefinitions,
}; };

View file

@ -47,6 +47,7 @@ export const useSuggestionsData = ({
query: query.join(' '), query: query.join(' '),
matches: 10, matches: 10,
contextLines: 1, contextLines: 1,
source: 'chat-file-suggestions'
})) }))
}, },
select: (data): FileSuggestion[] => { select: (data): FileSuggestion[] => {

View file

@ -1,10 +1,8 @@
'use client'; 'use client';
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
import { PathHeader } from "@/app/[domain]/components/pathHeader"; import { PathHeader } from "@/app/[domain]/components/pathHeader";
import { SymbolHoverPopup } from '@/ee/features/codeNav/components/symbolHoverPopup'; import { SymbolHoverPopup } from '@/ee/features/codeNav/components/symbolHoverPopup';
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension"; import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
import { SymbolDefinition } from '@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo';
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement"; import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension"; import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme"; import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
@ -12,15 +10,13 @@ import { useKeymapExtension } from "@/hooks/useKeymapExtension";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Range } from "@codemirror/state"; import { Range } from "@codemirror/state";
import { Decoration, DecorationSet, EditorView } from '@codemirror/view'; import { Decoration, DecorationSet, EditorView } from '@codemirror/view';
import { CodeHostType } from "@sourcebot/db";
import CodeMirror, { ReactCodeMirrorRef, StateField } from '@uiw/react-codemirror'; import CodeMirror, { ReactCodeMirrorRef, StateField } from '@uiw/react-codemirror';
import isEqual from "fast-deep-equal/react";
import { ChevronDown, ChevronRight } from "lucide-react"; import { ChevronDown, ChevronRight } from "lucide-react";
import { forwardRef, memo, Ref, useCallback, useImperativeHandle, useMemo, useState } from "react"; import { forwardRef, memo, Ref, useCallback, useImperativeHandle, useMemo, useState } from "react";
import { FileReference } from "../../types"; import { FileReference } from "../../types";
import { createCodeFoldingExtension } from "./codeFoldingExtension"; import { createCodeFoldingExtension } from "./codeFoldingExtension";
import useCaptureEvent from "@/hooks/useCaptureEvent";
import { CodeHostType } from "@sourcebot/db";
import { createAuditAction } from "@/ee/features/audit/actions";
import isEqual from "fast-deep-equal/react";
const lineDecoration = Decoration.line({ const lineDecoration = Decoration.line({
attributes: { class: "cm-range-border-radius chat-lineHighlight" }, attributes: { class: "cm-range-border-radius chat-lineHighlight" },
@ -74,7 +70,6 @@ const ReferencedFileSourceListItem = ({
}: ReferencedFileSourceListItemProps, forwardedRef: Ref<ReactCodeMirrorRef>) => { }: ReferencedFileSourceListItemProps, forwardedRef: Ref<ReactCodeMirrorRef>) => {
const theme = useCodeMirrorTheme(); const theme = useCodeMirrorTheme();
const [editorRef, setEditorRef] = useState<ReactCodeMirrorRef | null>(null); const [editorRef, setEditorRef] = useState<ReactCodeMirrorRef | null>(null);
const captureEvent = useCaptureEvent();
useImperativeHandle( useImperativeHandle(
forwardedRef, forwardedRef,
@ -84,7 +79,6 @@ const ReferencedFileSourceListItem = ({
const hasCodeNavEntitlement = useHasEntitlement("code-nav"); const hasCodeNavEntitlement = useHasEntitlement("code-nav");
const languageExtension = useCodeMirrorLanguageExtension(language, editorRef?.view); const languageExtension = useCodeMirrorLanguageExtension(language, editorRef?.view);
const { navigateToPath } = useBrowseNavigation();
const getReferenceAtPos = useCallback((x: number, y: number, view: EditorView): FileReference | undefined => { const getReferenceAtPos = useCallback((x: number, y: number, view: EditorView): FileReference | undefined => {
const pos = view.posAtCoords({ x, y }); const pos = view.posAtCoords({ x, y });
@ -217,83 +211,6 @@ const ReferencedFileSourceListItem = ({
codeFoldingExtension, codeFoldingExtension,
]); ]);
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
if (symbolDefinitions.length === 0) {
return;
}
captureEvent('wa_goto_definition_pressed', {
source: 'chat',
});
createAuditAction({
action: "user.performed_goto_definition",
metadata: {
message: symbolName,
},
});
if (symbolDefinitions.length === 1) {
const symbolDefinition = symbolDefinitions[0];
const { fileName, repoName } = symbolDefinition;
navigateToPath({
repoName,
revisionName: revision,
path: fileName,
pathType: 'blob',
highlightRange: symbolDefinition.range,
})
} else {
navigateToPath({
repoName,
revisionName: revision,
path: fileName,
pathType: 'blob',
setBrowseState: {
selectedSymbolInfo: {
symbolName,
repoName,
revisionName: revision,
language: language,
},
activeExploreMenuTab: "definitions",
isBottomPanelCollapsed: false,
}
});
}
}, [captureEvent, navigateToPath, revision, repoName, fileName, language]);
const onFindReferences = useCallback((symbolName: string) => {
captureEvent('wa_find_references_pressed', {
source: 'chat',
});
createAuditAction({
action: "user.performed_find_references",
metadata: {
message: symbolName,
},
});
navigateToPath({
repoName,
revisionName: revision,
path: fileName,
pathType: 'blob',
setBrowseState: {
selectedSymbolInfo: {
symbolName,
repoName,
revisionName: revision,
language: language,
},
activeExploreMenuTab: "references",
isBottomPanelCollapsed: false,
}
})
}, [captureEvent, fileName, language, navigateToPath, repoName, revision]);
const ExpandCollapseIcon = useMemo(() => { const ExpandCollapseIcon = useMemo(() => {
return isExpanded ? ChevronDown : ChevronRight; return isExpanded ? ChevronDown : ChevronRight;
}, [isExpanded]); }, [isExpanded]);
@ -341,11 +258,12 @@ const ReferencedFileSourceListItem = ({
> >
{editorRef && hasCodeNavEntitlement && ( {editorRef && hasCodeNavEntitlement && (
<SymbolHoverPopup <SymbolHoverPopup
source="chat"
editorRef={editorRef} editorRef={editorRef}
revisionName={revision} revisionName={revision}
language={language} language={language}
onFindReferences={onFindReferences} repoName={repoName}
onGotoDefinition={onGotoDefinition} fileName={fileName}
/> />
)} )}
</CodeMirror> </CodeMirror>

View file

@ -30,7 +30,7 @@ export const FileListItem = ({
pathType: 'blob', pathType: 'blob',
})} })}
> >
{path} <span>{path}</span>
</Link> </Link>
</div> </div>
) )

View file

@ -26,8 +26,9 @@ export const findSymbolReferencesTool = tool({
inputSchema: z.object({ inputSchema: z.object({
symbol: z.string().describe("The symbol to find references to"), symbol: z.string().describe("The symbol to find references to"),
language: z.string().describe("The programming language of the symbol"), language: z.string().describe("The programming language of the symbol"),
repository: z.string().describe("The repository to scope the search to").optional(),
}), }),
execute: async ({ symbol, language }) => { execute: async ({ symbol, language, repository }) => {
// @todo: make revision configurable. // @todo: make revision configurable.
const revision = "HEAD"; const revision = "HEAD";
@ -35,6 +36,7 @@ export const findSymbolReferencesTool = tool({
symbolName: symbol, symbolName: symbol,
language, language,
revisionName: "HEAD", revisionName: "HEAD",
repoName: repository,
}); });
if (isServiceError(response)) { if (isServiceError(response)) {
@ -63,8 +65,9 @@ export const findSymbolDefinitionsTool = tool({
inputSchema: z.object({ inputSchema: z.object({
symbol: z.string().describe("The symbol to find definitions of"), symbol: z.string().describe("The symbol to find definitions of"),
language: z.string().describe("The programming language of the symbol"), language: z.string().describe("The programming language of the symbol"),
repository: z.string().describe("The repository to scope the search to").optional(),
}), }),
execute: async ({ symbol, language }) => { execute: async ({ symbol, language, repository }) => {
// @todo: make revision configurable. // @todo: make revision configurable.
const revision = "HEAD"; const revision = "HEAD";
@ -72,6 +75,7 @@ export const findSymbolDefinitionsTool = tool({
symbolName: symbol, symbolName: symbol,
language, language,
revisionName: revision, revisionName: revision,
repoName: repository,
}); });
if (isServiceError(response)) { if (isServiceError(response)) {

View file

@ -8,6 +8,7 @@ import { withOptionalAuthV2 } from "@/withAuthV2";
import { SearchResponse } from "../search/types"; import { SearchResponse } from "../search/types";
import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types"; import { FindRelatedSymbolsRequest, FindRelatedSymbolsResponse } from "./types";
import { QueryIR } from '../search/ir'; import { QueryIR } from '../search/ir';
import escapeStringRegexp from "escape-string-regexp";
// The maximum number of matches to return from the search API. // The maximum number of matches to return from the search API.
const MAX_REFERENCE_COUNT = 1000; const MAX_REFERENCE_COUNT = 1000;
@ -18,6 +19,7 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR
symbolName, symbolName,
language, language,
revisionName = "HEAD", revisionName = "HEAD",
repoName,
} = props; } = props;
const languageFilter = getExpandedLanguageFilter(language); const languageFilter = getExpandedLanguageFilter(language);
@ -40,6 +42,11 @@ export const findSearchBasedSymbolReferences = async (props: FindRelatedSymbolsR
} }
}, },
languageFilter, languageFilter,
...(repoName ? [{
repo: {
regexp: `^${escapeStringRegexp(repoName)}$`,
}
}]: [])
] ]
} }
} }
@ -67,6 +74,7 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols
symbolName, symbolName,
language, language,
revisionName = "HEAD", revisionName = "HEAD",
repoName
} = props; } = props;
const languageFilter = getExpandedLanguageFilter(language); const languageFilter = getExpandedLanguageFilter(language);
@ -93,6 +101,11 @@ export const findSearchBasedSymbolDefinitions = async (props: FindRelatedSymbols
} }
}, },
languageFilter, languageFilter,
...(repoName ? [{
repo: {
regexp: `^${escapeStringRegexp(repoName)}$`,
}
}]: [])
] ]
} }
} }

View file

@ -4,7 +4,16 @@ import { rangeSchema, repositoryInfoSchema } from "../search/types";
export const findRelatedSymbolsRequestSchema = z.object({ export const findRelatedSymbolsRequestSchema = z.object({
symbolName: z.string(), symbolName: z.string(),
language: z.string(), language: z.string(),
/**
* Optional revision name to scope search to.
* If not provided, the search will be scoped to HEAD.
*/
revisionName: z.string().optional(), revisionName: z.string().optional(),
/**
* Optional repository name to scope search to.
* If not provided, the search will be across all repositories.
*/
repoName: z.string().optional(),
}); });
export type FindRelatedSymbolsRequest = z.infer<typeof findRelatedSymbolsRequestSchema>; export type FindRelatedSymbolsRequest = z.infer<typeof findRelatedSymbolsRequestSchema>;

View file

@ -6,6 +6,7 @@ import { search } from "./searchApi";
import { sew } from "@/actions"; import { sew } from "@/actions";
import { withOptionalAuthV2 } from "@/withAuthV2"; import { withOptionalAuthV2 } from "@/withAuthV2";
import { QueryIR } from './ir'; import { QueryIR } from './ir';
import escapeStringRegexp from "escape-string-regexp";
// @todo (bkellam) #574 : We should really be using `git show <hash>:<path>` to fetch file contents here. // @todo (bkellam) #574 : We should really be using `git show <hash>:<path>` to fetch file contents here.
// This will allow us to support permalinks to files at a specific revision that may not be indexed // This will allow us to support permalinks to files at a specific revision that may not be indexed
@ -18,7 +19,7 @@ export const getFileSource = async ({ fileName, repository, branch }: FileSource
children: [ children: [
{ {
repo: { repo: {
regexp: `^${repository}$`, regexp: `^${escapeStringRegexp(repository)}$`,
}, },
}, },
{ {

View file

@ -2,13 +2,12 @@ import { sew } from "@/actions";
import { getRepoPermissionFilterForUser } from "@/prisma"; import { getRepoPermissionFilterForUser } from "@/prisma";
import { withOptionalAuthV2 } from "@/withAuthV2"; import { withOptionalAuthV2 } from "@/withAuthV2";
import { PrismaClient, UserWithAccounts } from "@sourcebot/db"; import { PrismaClient, UserWithAccounts } from "@sourcebot/db";
import { createLogger, env, hasEntitlement } from "@sourcebot/shared"; import { env, hasEntitlement } from "@sourcebot/shared";
import { QueryIR } from './ir'; import { QueryIR } from './ir';
import { parseQuerySyntaxIntoIR } from './parser'; import { parseQuerySyntaxIntoIR } from './parser';
import { SearchOptions } from "./types"; import { SearchOptions } from "./types";
import { createZoektSearchRequest, zoektSearch, zoektStreamSearch } from './zoektSearcher'; import { createZoektSearchRequest, zoektSearch, zoektStreamSearch } from './zoektSearcher';
const logger = createLogger("searchApi");
type QueryStringSearchRequest = { type QueryStringSearchRequest = {
queryType: 'string'; queryType: 'string';

View file

@ -94,6 +94,7 @@ export type SearchOptions = z.infer<typeof searchOptionsSchema>;
export const searchRequestSchema = z.object({ export const searchRequestSchema = z.object({
query: z.string(), // The zoekt query to execute. query: z.string(), // The zoekt query to execute.
source: z.string().optional(), // The source of the search request.
...searchOptionsSchema.shape, ...searchOptionsSchema.shape,
}); });
export type SearchRequest = z.infer<typeof searchRequestSchema>; export type SearchRequest = z.infer<typeof searchRequestSchema>;

View file

@ -0,0 +1,69 @@
import { PostHog } from 'posthog-node'
import { env } from '@sourcebot/shared'
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import * as Sentry from "@sentry/nextjs";
import { PosthogEvent, PosthogEventMap } from './posthogEvents';
import { cookies } from 'next/headers';
/**
* @note: This is a subset of the properties stored in the
* ph_phc_<id>_posthog cookie.
*/
export type PostHogCookie = {
distinct_id: string;
}
const isPostHogCookie = (cookie: unknown): cookie is PostHogCookie => {
return typeof cookie === 'object' &&
cookie !== null &&
'distinct_id' in cookie;
}
/**
* Attempts to retrieve the PostHog cookie from the given cookie store, returning
* undefined if the cookie is not found or is invalid.
*/
const getPostHogCookie = (cookieStore: Pick<RequestCookies, 'get'>): PostHogCookie | undefined => {
const phCookieKey = `ph_${env.POSTHOG_PAPIK}_posthog`;
const cookie = cookieStore.get(phCookieKey);
if (!cookie) {
return undefined;
}
const parsedCookie = (() => {
try {
return JSON.parse(cookie.value);
} catch (e) {
Sentry.captureException(e);
return null;
}
})();
if (isPostHogCookie(parsedCookie)) {
return parsedCookie;
}
return undefined;
}
export async function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E]) {
if (env.SOURCEBOT_TELEMETRY_DISABLED === 'true') {
return;
}
const cookieStore = await cookies();
const cookie = getPostHogCookie(cookieStore);
const posthog = new PostHog(env.POSTHOG_PAPIK, {
host: 'https://us.i.posthog.com',
flushAt: 1,
flushInterval: 0
})
posthog.capture({
event,
properties,
distinctId: cookie?.distinct_id ?? '',
});
}

View file

@ -273,15 +273,6 @@ export type PosthogEventMap = {
wa_api_key_created: {}, wa_api_key_created: {},
wa_api_key_creation_fail: {}, wa_api_key_creation_fail: {},
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
wa_goto_definition_pressed: {
source: 'chat' | 'browse' | 'preview',
},
wa_find_references_pressed: {
source: 'chat' | 'browse' | 'preview',
},
//////////////////////////////////////////////////////////////////
wa_explore_menu_reference_clicked: {},
//////////////////////////////////////////////////////////////////
wa_chat_feedback_submitted: { wa_chat_feedback_submitted: {
feedback: 'like' | 'dislike', feedback: 'like' | 'dislike',
chatId: string, chatId: string,
@ -302,5 +293,31 @@ export type PosthogEventMap = {
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
wa_github_star_toast_displayed: {}, wa_github_star_toast_displayed: {},
wa_github_star_toast_clicked: {}, wa_github_star_toast_clicked: {},
//////////////////////////////////////////////////////////////////
wa_goto_definition_pressed: {
source: 'chat' | 'browse' | 'preview',
},
wa_find_references_pressed: {
source: 'chat' | 'browse' | 'preview',
},
wa_symbol_hover_popup_definitions_loaded: {
durationMs: number,
},
wa_explore_menu_reference_clicked: {},
wa_explore_menu_references_loaded: {
durationMs: number,
// Whether or not the user is searching all repositories.
isGlobalSearchEnabled: boolean,
},
wa_explore_menu_definitions_loaded: {
durationMs: number,
// Whether or not the user is searching all repositories.
isGlobalSearchEnabled: boolean,
},
//////////////////////////////////////////////////////////////////
api_code_search_request: {
source: string;
type: 'streamed' | 'blocking';
},
} }
export type PosthogEvent = keyof PosthogEventMap; export type PosthogEvent = keyof PosthogEventMap;

View file

@ -35,7 +35,8 @@
"next-env.d.ts", "next-env.d.ts",
"**/*.ts", "**/*.ts",
"**/*.tsx", "**/*.tsx",
".next/types/**/*.ts" ".next/types/**/*.ts",
".next/dev/types/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"

421
yarn.lock
View file

@ -1649,12 +1649,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@emnapi/runtime@npm:^1.4.4": "@emnapi/runtime@npm:^1.7.0":
version: 1.4.5 version: 1.7.1
resolution: "@emnapi/runtime@npm:1.4.5" resolution: "@emnapi/runtime@npm:1.7.1"
dependencies: dependencies:
tslib: "npm:^2.4.0" tslib: "npm:^2.4.0"
checksum: 10c0/37a0278be5ac81e918efe36f1449875cbafba947039c53c65a1f8fc238001b866446fc66041513b286baaff5d6f9bec667f5164b3ca481373a8d9cb65bfc984b checksum: 10c0/26b851cd3e93877d8732a985a2ebf5152325bbacc6204ef5336a47359dedcc23faeb08cdfcb8bb389b5401b3e894b882bc1a1e55b4b7c1ed1e67c991a760ddd5
languageName: node languageName: node
linkType: hard linkType: hard
@ -2426,6 +2426,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/colour@npm:^1.0.0":
version: 1.0.0
resolution: "@img/colour@npm:1.0.0"
checksum: 10c0/02261719c1e0d7aa5a2d585981954f2ac126f0c432400aa1a01b925aa2c41417b7695da8544ee04fd29eba7ecea8eaf9b8bef06f19dc8faba78f94eeac40667d
languageName: node
linkType: hard
"@img/sharp-darwin-arm64@npm:0.33.5": "@img/sharp-darwin-arm64@npm:0.33.5":
version: 0.33.5 version: 0.33.5
resolution: "@img/sharp-darwin-arm64@npm:0.33.5" resolution: "@img/sharp-darwin-arm64@npm:0.33.5"
@ -2438,11 +2445,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-darwin-arm64@npm:0.34.3": "@img/sharp-darwin-arm64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-darwin-arm64@npm:0.34.3" resolution: "@img/sharp-darwin-arm64@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0" "@img/sharp-libvips-darwin-arm64": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-darwin-arm64": "@img/sharp-libvips-darwin-arm64":
optional: true optional: true
@ -2462,11 +2469,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-darwin-x64@npm:0.34.3": "@img/sharp-darwin-x64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-darwin-x64@npm:0.34.3" resolution: "@img/sharp-darwin-x64@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-darwin-x64": "npm:1.2.0" "@img/sharp-libvips-darwin-x64": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-darwin-x64": "@img/sharp-libvips-darwin-x64":
optional: true optional: true
@ -2481,9 +2488,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-darwin-arm64@npm:1.2.0": "@img/sharp-libvips-darwin-arm64@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.0" resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4"
conditions: os=darwin & cpu=arm64 conditions: os=darwin & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
@ -2495,9 +2502,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-darwin-x64@npm:1.2.0": "@img/sharp-libvips-darwin-x64@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.0" resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4"
conditions: os=darwin & cpu=x64 conditions: os=darwin & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
@ -2509,9 +2516,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linux-arm64@npm:1.2.0": "@img/sharp-libvips-linux-arm64@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.0" resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4"
conditions: os=linux & cpu=arm64 & libc=glibc conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
@ -2523,20 +2530,27 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linux-arm@npm:1.2.0": "@img/sharp-libvips-linux-arm@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-linux-arm@npm:1.2.0" resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4"
conditions: os=linux & cpu=arm & libc=glibc conditions: os=linux & cpu=arm & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linux-ppc64@npm:1.2.0": "@img/sharp-libvips-linux-ppc64@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.0" resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4"
conditions: os=linux & cpu=ppc64 & libc=glibc conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linux-riscv64@npm:1.2.4":
version: 1.2.4
resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4"
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.0.4": "@img/sharp-libvips-linux-s390x@npm:1.0.4":
version: 1.0.4 version: 1.0.4
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4" resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4"
@ -2544,9 +2558,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.2.0": "@img/sharp-libvips-linux-s390x@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.0" resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4"
conditions: os=linux & cpu=s390x & libc=glibc conditions: os=linux & cpu=s390x & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
@ -2558,9 +2572,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linux-x64@npm:1.2.0": "@img/sharp-libvips-linux-x64@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-linux-x64@npm:1.2.0" resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4"
conditions: os=linux & cpu=x64 & libc=glibc conditions: os=linux & cpu=x64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
@ -2572,9 +2586,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0": "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0" resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4"
conditions: os=linux & cpu=arm64 & libc=musl conditions: os=linux & cpu=arm64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
@ -2586,9 +2600,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.0": "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4":
version: 1.2.0 version: 1.2.4
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.0" resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4"
conditions: os=linux & cpu=x64 & libc=musl conditions: os=linux & cpu=x64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
@ -2605,11 +2619,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linux-arm64@npm:0.34.3": "@img/sharp-linux-arm64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-linux-arm64@npm:0.34.3" resolution: "@img/sharp-linux-arm64@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-linux-arm64": "npm:1.2.0" "@img/sharp-libvips-linux-arm64": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-linux-arm64": "@img/sharp-libvips-linux-arm64":
optional: true optional: true
@ -2629,11 +2643,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linux-arm@npm:0.34.3": "@img/sharp-linux-arm@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-linux-arm@npm:0.34.3" resolution: "@img/sharp-linux-arm@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-linux-arm": "npm:1.2.0" "@img/sharp-libvips-linux-arm": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-linux-arm": "@img/sharp-libvips-linux-arm":
optional: true optional: true
@ -2641,11 +2655,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linux-ppc64@npm:0.34.3": "@img/sharp-linux-ppc64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-linux-ppc64@npm:0.34.3" resolution: "@img/sharp-linux-ppc64@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0" "@img/sharp-libvips-linux-ppc64": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-linux-ppc64": "@img/sharp-libvips-linux-ppc64":
optional: true optional: true
@ -2653,6 +2667,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linux-riscv64@npm:0.34.5":
version: 0.34.5
resolution: "@img/sharp-linux-riscv64@npm:0.34.5"
dependencies:
"@img/sharp-libvips-linux-riscv64": "npm:1.2.4"
dependenciesMeta:
"@img/sharp-libvips-linux-riscv64":
optional: true
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.33.5": "@img/sharp-linux-s390x@npm:0.33.5":
version: 0.33.5 version: 0.33.5
resolution: "@img/sharp-linux-s390x@npm:0.33.5" resolution: "@img/sharp-linux-s390x@npm:0.33.5"
@ -2665,11 +2691,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linux-s390x@npm:0.34.3": "@img/sharp-linux-s390x@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-linux-s390x@npm:0.34.3" resolution: "@img/sharp-linux-s390x@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-linux-s390x": "npm:1.2.0" "@img/sharp-libvips-linux-s390x": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-linux-s390x": "@img/sharp-libvips-linux-s390x":
optional: true optional: true
@ -2689,11 +2715,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linux-x64@npm:0.34.3": "@img/sharp-linux-x64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-linux-x64@npm:0.34.3" resolution: "@img/sharp-linux-x64@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-linux-x64": "npm:1.2.0" "@img/sharp-libvips-linux-x64": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-linux-x64": "@img/sharp-libvips-linux-x64":
optional: true optional: true
@ -2713,11 +2739,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.34.3": "@img/sharp-linuxmusl-arm64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.3" resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0" "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-linuxmusl-arm64": "@img/sharp-libvips-linuxmusl-arm64":
optional: true optional: true
@ -2737,11 +2763,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.34.3": "@img/sharp-linuxmusl-x64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.3" resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0" "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4"
dependenciesMeta: dependenciesMeta:
"@img/sharp-libvips-linuxmusl-x64": "@img/sharp-libvips-linuxmusl-x64":
optional: true optional: true
@ -2758,18 +2784,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-wasm32@npm:0.34.3": "@img/sharp-wasm32@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-wasm32@npm:0.34.3" resolution: "@img/sharp-wasm32@npm:0.34.5"
dependencies: dependencies:
"@emnapi/runtime": "npm:^1.4.4" "@emnapi/runtime": "npm:^1.7.0"
conditions: cpu=wasm32 conditions: cpu=wasm32
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-win32-arm64@npm:0.34.3": "@img/sharp-win32-arm64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-win32-arm64@npm:0.34.3" resolution: "@img/sharp-win32-arm64@npm:0.34.5"
conditions: os=win32 & cpu=arm64 conditions: os=win32 & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
@ -2781,9 +2807,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-win32-ia32@npm:0.34.3": "@img/sharp-win32-ia32@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-win32-ia32@npm:0.34.3" resolution: "@img/sharp-win32-ia32@npm:0.34.5"
conditions: os=win32 & cpu=ia32 conditions: os=win32 & cpu=ia32
languageName: node languageName: node
linkType: hard linkType: hard
@ -2795,9 +2821,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@img/sharp-win32-x64@npm:0.34.3": "@img/sharp-win32-x64@npm:0.34.5":
version: 0.34.3 version: 0.34.5
resolution: "@img/sharp-win32-x64@npm:0.34.3" resolution: "@img/sharp-win32-x64@npm:0.34.5"
conditions: os=win32 & cpu=x64 conditions: os=win32 & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
@ -3268,10 +3294,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/env@npm:15.5.0": "@next/env@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/env@npm:15.5.0" resolution: "@next/env@npm:15.5.7"
checksum: 10c0/13ed931688c26b764499840022c9855763fb583e728cf30933b2ff71d3ca681eb71611ec4d7580ecb45197c18a1a4498ba5a61cb60edc6e18966103620d11bad checksum: 10c0/f92d99e5fa3516c6b7699abafd9bd813f5c1889dd257ab098f1b71f93137f5e4f49792e22f6dddf8a59efcb134e8e84277c983ff88607b2a42aac651bfde78ea
languageName: node languageName: node
linkType: hard linkType: hard
@ -3291,9 +3317,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-darwin-arm64@npm:15.5.0": "@next/swc-darwin-arm64@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-darwin-arm64@npm:15.5.0" resolution: "@next/swc-darwin-arm64@npm:15.5.7"
conditions: os=darwin & cpu=arm64 conditions: os=darwin & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
@ -3305,9 +3331,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-darwin-x64@npm:15.5.0": "@next/swc-darwin-x64@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-darwin-x64@npm:15.5.0" resolution: "@next/swc-darwin-x64@npm:15.5.7"
conditions: os=darwin & cpu=x64 conditions: os=darwin & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
@ -3319,9 +3345,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-arm64-gnu@npm:15.5.0": "@next/swc-linux-arm64-gnu@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-linux-arm64-gnu@npm:15.5.0" resolution: "@next/swc-linux-arm64-gnu@npm:15.5.7"
conditions: os=linux & cpu=arm64 & libc=glibc conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
@ -3333,9 +3359,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-arm64-musl@npm:15.5.0": "@next/swc-linux-arm64-musl@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-linux-arm64-musl@npm:15.5.0" resolution: "@next/swc-linux-arm64-musl@npm:15.5.7"
conditions: os=linux & cpu=arm64 & libc=musl conditions: os=linux & cpu=arm64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
@ -3347,9 +3373,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-x64-gnu@npm:15.5.0": "@next/swc-linux-x64-gnu@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-linux-x64-gnu@npm:15.5.0" resolution: "@next/swc-linux-x64-gnu@npm:15.5.7"
conditions: os=linux & cpu=x64 & libc=glibc conditions: os=linux & cpu=x64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
@ -3361,9 +3387,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-x64-musl@npm:15.5.0": "@next/swc-linux-x64-musl@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-linux-x64-musl@npm:15.5.0" resolution: "@next/swc-linux-x64-musl@npm:15.5.7"
conditions: os=linux & cpu=x64 & libc=musl conditions: os=linux & cpu=x64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
@ -3375,9 +3401,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-win32-arm64-msvc@npm:15.5.0": "@next/swc-win32-arm64-msvc@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-win32-arm64-msvc@npm:15.5.0" resolution: "@next/swc-win32-arm64-msvc@npm:15.5.7"
conditions: os=win32 & cpu=arm64 conditions: os=win32 & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
@ -3396,9 +3422,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-win32-x64-msvc@npm:15.5.0": "@next/swc-win32-x64-msvc@npm:15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "@next/swc-win32-x64-msvc@npm:15.5.0" resolution: "@next/swc-win32-x64-msvc@npm:15.5.7"
conditions: os=win32 & cpu=x64 conditions: os=win32 & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
@ -4573,6 +4599,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@posthog/core@npm:1.6.0":
version: 1.6.0
resolution: "@posthog/core@npm:1.6.0"
dependencies:
cross-spawn: "npm:^7.0.6"
checksum: 10c0/28aa907bb21b18587bc5f47c44349ebc834b37d9a4cedb1a18d7b673d4d7cdad2120dda80deceaee707b2f52333e9a08a8e591e1fc4066521ce05e820b76309f
languageName: node
linkType: hard
"@prisma/client@npm:6.2.1": "@prisma/client@npm:6.2.1":
version: 6.2.1 version: 6.2.1
resolution: "@prisma/client@npm:6.2.1" resolution: "@prisma/client@npm:6.2.1"
@ -8155,8 +8190,8 @@ __metadata:
"@types/node": "npm:^20" "@types/node": "npm:^20"
"@types/nodemailer": "npm:^6.4.17" "@types/nodemailer": "npm:^6.4.17"
"@types/psl": "npm:^1.1.3" "@types/psl": "npm:^1.1.3"
"@types/react": "npm:19.1.10" "@types/react": "npm:19.2.1"
"@types/react-dom": "npm:19.1.7" "@types/react-dom": "npm:19.2.1"
"@typescript-eslint/eslint-plugin": "npm:^8.40.0" "@typescript-eslint/eslint-plugin": "npm:^8.40.0"
"@typescript-eslint/parser": "npm:^8.40.0" "@typescript-eslint/parser": "npm:^8.40.0"
"@uidotdev/usehooks": "npm:^2.4.1" "@uidotdev/usehooks": "npm:^2.4.1"
@ -8208,7 +8243,7 @@ __metadata:
langfuse-vercel: "npm:^3.38.4" langfuse-vercel: "npm:^3.38.4"
lucide-react: "npm:^0.517.0" lucide-react: "npm:^0.517.0"
micromatch: "npm:^4.0.8" micromatch: "npm:^4.0.8"
next: "npm:15.5.0" next: "npm:^15.5.7"
next-auth: "npm:^5.0.0-beta.30" next-auth: "npm:^5.0.0-beta.30"
next-navigation-guard: "npm:^0.2.0" next-navigation-guard: "npm:^0.2.0"
next-themes: "npm:^0.3.0" next-themes: "npm:^0.3.0"
@ -8219,11 +8254,12 @@ __metadata:
parse-diff: "npm:^0.11.1" parse-diff: "npm:^0.11.1"
postcss: "npm:^8" postcss: "npm:^8"
posthog-js: "npm:^1.161.5" posthog-js: "npm:^1.161.5"
posthog-node: "npm:^5.15.0"
pretty-bytes: "npm:^6.1.1" pretty-bytes: "npm:^6.1.1"
psl: "npm:^1.15.0" psl: "npm:^1.15.0"
react: "npm:19.1.1" react: "npm:^19.2.1"
react-device-detect: "npm:^2.2.3" react-device-detect: "npm:^2.2.3"
react-dom: "npm:19.1.1" react-dom: "npm:^19.2.1"
react-email: "npm:3.0.3" react-email: "npm:3.0.3"
react-hook-form: "npm:^7.53.0" react-hook-form: "npm:^7.53.0"
react-hotkeys-hook: "npm:^4.5.1" react-hotkeys-hook: "npm:^4.5.1"
@ -8874,21 +8910,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react-dom@npm:19.1.7": "@types/react-dom@npm:19.2.1":
version: 19.1.7 version: 19.2.1
resolution: "@types/react-dom@npm:19.1.7" resolution: "@types/react-dom@npm:19.2.1"
peerDependencies: peerDependencies:
"@types/react": ^19.0.0 "@types/react": ^19.2.0
checksum: 10c0/8db5751c1567552fe4e1ece9f5823b682f2994ec8d30ed34ba0ef984e3c8ace1435f8be93d02f55c350147e78ac8c4dbcd8ed2c3b6a60f575bc5374f588c51c9 checksum: 10c0/0dbbc5b7ecd74681bfac95a413133b26118a70b8840748277abafa47e5c7a037beae6a660e6a21fb53f5cbdb0b2d33e117ea7bbd976a888c298392a8a96bc68f
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react@npm:19.1.10": "@types/react@npm:19.2.1":
version: 19.1.10 version: 19.2.1
resolution: "@types/react@npm:19.1.10" resolution: "@types/react@npm:19.2.1"
dependencies: dependencies:
csstype: "npm:^3.0.2" csstype: "npm:^3.0.2"
checksum: 10c0/fb583deacd0a815e2775dc1b9f764532d8cacb748ddd2c2914805a46c257ce6c237b4078f44009692074db212ab61a390301c6470f07f5aa5bfdeb78a2acfda1 checksum: 10c0/c44881c275da91156ce02986ab1f59c9724db256f4850d3937c9acea561a6ab1fe1028f7a1fc4da3a2c1bcb00de29e238922e8c6d42a727ef2e6e0cd40b3db9f
languageName: node languageName: node
linkType: hard linkType: hard
@ -11310,10 +11346,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"detect-libc@npm:^2.0.4": "detect-libc@npm:^2.1.2":
version: 2.0.4 version: 2.1.2
resolution: "detect-libc@npm:2.0.4" resolution: "detect-libc@npm:2.1.2"
checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4
languageName: node languageName: node
linkType: hard linkType: hard
@ -16142,19 +16178,19 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"next@npm:15.5.0": "next@npm:^15.5.7":
version: 15.5.0 version: 15.5.7
resolution: "next@npm:15.5.0" resolution: "next@npm:15.5.7"
dependencies: dependencies:
"@next/env": "npm:15.5.0" "@next/env": "npm:15.5.7"
"@next/swc-darwin-arm64": "npm:15.5.0" "@next/swc-darwin-arm64": "npm:15.5.7"
"@next/swc-darwin-x64": "npm:15.5.0" "@next/swc-darwin-x64": "npm:15.5.7"
"@next/swc-linux-arm64-gnu": "npm:15.5.0" "@next/swc-linux-arm64-gnu": "npm:15.5.7"
"@next/swc-linux-arm64-musl": "npm:15.5.0" "@next/swc-linux-arm64-musl": "npm:15.5.7"
"@next/swc-linux-x64-gnu": "npm:15.5.0" "@next/swc-linux-x64-gnu": "npm:15.5.7"
"@next/swc-linux-x64-musl": "npm:15.5.0" "@next/swc-linux-x64-musl": "npm:15.5.7"
"@next/swc-win32-arm64-msvc": "npm:15.5.0" "@next/swc-win32-arm64-msvc": "npm:15.5.7"
"@next/swc-win32-x64-msvc": "npm:15.5.0" "@next/swc-win32-x64-msvc": "npm:15.5.7"
"@swc/helpers": "npm:0.5.15" "@swc/helpers": "npm:0.5.15"
caniuse-lite: "npm:^1.0.30001579" caniuse-lite: "npm:^1.0.30001579"
postcss: "npm:8.4.31" postcss: "npm:8.4.31"
@ -16197,7 +16233,7 @@ __metadata:
optional: true optional: true
bin: bin:
next: dist/bin/next next: dist/bin/next
checksum: 10c0/8691787562666713e9ee8bc3edad646328d9cea85d5c4c10bd05a978afa25bc9927bbfd50acc69ceb268296741b5f71e78fa0213fe65b731a55e73b0f5807c24 checksum: 10c0/baf5b9f42416c478702b3894479b3d7862bc4abf18afe0e43b7fc7ed35567b8dc6cb76cd94906505bab9013cb8d0f3370cdc0451c01ec15ae5a638d37b5ba7c7
languageName: node languageName: node
linkType: hard linkType: hard
@ -17209,6 +17245,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"posthog-node@npm:^5.15.0":
version: 5.15.0
resolution: "posthog-node@npm:5.15.0"
dependencies:
"@posthog/core": "npm:1.6.0"
checksum: 10c0/7db929453cefc9b2d0017e1f7c9ffe7e6ecd2a495ee9861bd5e8f3873f72fa29a318dd7bccf10eb15639e1860aab396d4be502af88afba96ed15ac8b46d57e9d
languageName: node
linkType: hard
"preact-render-to-string@npm:6.5.11": "preact-render-to-string@npm:6.5.11":
version: 6.5.11 version: 6.5.11
resolution: "preact-render-to-string@npm:6.5.11" resolution: "preact-render-to-string@npm:6.5.11"
@ -17539,14 +17584,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-dom@npm:19.1.1": "react-dom@npm:^19.2.1":
version: 19.1.1 version: 19.2.1
resolution: "react-dom@npm:19.1.1" resolution: "react-dom@npm:19.2.1"
dependencies: dependencies:
scheduler: "npm:^0.26.0" scheduler: "npm:^0.27.0"
peerDependencies: peerDependencies:
react: ^19.1.1 react: ^19.2.1
checksum: 10c0/8c91198510521299c56e4e8d5e3a4508b2734fb5e52f29eeac33811de64e76fe586ad32c32182e2e84e070d98df67125da346c3360013357228172dbcd20bcdd checksum: 10c0/e56b6b3d72314df580ca800b70a69a21c6372703c8f45d9b5451ca6519faefb2496d76ffa9c5adb94136d2bbf2fd303d0dfc208a2cd77ede3132877471af9470
languageName: node languageName: node
linkType: hard linkType: hard
@ -17763,10 +17808,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react@npm:19.1.1": "react@npm:^19.2.1":
version: 19.1.1 version: 19.2.1
resolution: "react@npm:19.1.1" resolution: "react@npm:19.2.1"
checksum: 10c0/8c9769a2dfd02e603af6445058325e6c8a24b47b185d0e461f66a6454765ddcaecb3f0a90184836c68bb509f3c38248359edbc42f0d07c23eb500a5c30c87b4e checksum: 10c0/2b5eaf407abb3db84090434c20d6c5a8e447ab7abcd8fe9eaf1ddc299babcf31284ee9db7ea5671d21c85ac5298bd632fa1a7da1ed78d5b368a537f5e1cd5d62
languageName: node languageName: node
linkType: hard linkType: hard
@ -18490,10 +18535,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"scheduler@npm:^0.26.0": "scheduler@npm:^0.27.0":
version: 0.26.0 version: 0.27.0
resolution: "scheduler@npm:0.26.0" resolution: "scheduler@npm:0.27.0"
checksum: 10c0/5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356 checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452
languageName: node languageName: node
linkType: hard linkType: hard
@ -18542,12 +18587,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"semver@npm:^7.7.2": "semver@npm:^7.7.3":
version: 7.7.2 version: 7.7.3
resolution: "semver@npm:7.7.2" resolution: "semver@npm:7.7.3"
bin: bin:
semver: bin/semver.js semver: bin/semver.js
checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e
languageName: node languageName: node
linkType: hard linkType: hard
@ -18745,34 +18790,36 @@ __metadata:
linkType: hard linkType: hard
"sharp@npm:^0.34.3": "sharp@npm:^0.34.3":
version: 0.34.3 version: 0.34.5
resolution: "sharp@npm:0.34.3" resolution: "sharp@npm:0.34.5"
dependencies: dependencies:
"@img/sharp-darwin-arm64": "npm:0.34.3" "@img/colour": "npm:^1.0.0"
"@img/sharp-darwin-x64": "npm:0.34.3" "@img/sharp-darwin-arm64": "npm:0.34.5"
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0" "@img/sharp-darwin-x64": "npm:0.34.5"
"@img/sharp-libvips-darwin-x64": "npm:1.2.0" "@img/sharp-libvips-darwin-arm64": "npm:1.2.4"
"@img/sharp-libvips-linux-arm": "npm:1.2.0" "@img/sharp-libvips-darwin-x64": "npm:1.2.4"
"@img/sharp-libvips-linux-arm64": "npm:1.2.0" "@img/sharp-libvips-linux-arm": "npm:1.2.4"
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0" "@img/sharp-libvips-linux-arm64": "npm:1.2.4"
"@img/sharp-libvips-linux-s390x": "npm:1.2.0" "@img/sharp-libvips-linux-ppc64": "npm:1.2.4"
"@img/sharp-libvips-linux-x64": "npm:1.2.0" "@img/sharp-libvips-linux-riscv64": "npm:1.2.4"
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0" "@img/sharp-libvips-linux-s390x": "npm:1.2.4"
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0" "@img/sharp-libvips-linux-x64": "npm:1.2.4"
"@img/sharp-linux-arm": "npm:0.34.3" "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4"
"@img/sharp-linux-arm64": "npm:0.34.3" "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4"
"@img/sharp-linux-ppc64": "npm:0.34.3" "@img/sharp-linux-arm": "npm:0.34.5"
"@img/sharp-linux-s390x": "npm:0.34.3" "@img/sharp-linux-arm64": "npm:0.34.5"
"@img/sharp-linux-x64": "npm:0.34.3" "@img/sharp-linux-ppc64": "npm:0.34.5"
"@img/sharp-linuxmusl-arm64": "npm:0.34.3" "@img/sharp-linux-riscv64": "npm:0.34.5"
"@img/sharp-linuxmusl-x64": "npm:0.34.3" "@img/sharp-linux-s390x": "npm:0.34.5"
"@img/sharp-wasm32": "npm:0.34.3" "@img/sharp-linux-x64": "npm:0.34.5"
"@img/sharp-win32-arm64": "npm:0.34.3" "@img/sharp-linuxmusl-arm64": "npm:0.34.5"
"@img/sharp-win32-ia32": "npm:0.34.3" "@img/sharp-linuxmusl-x64": "npm:0.34.5"
"@img/sharp-win32-x64": "npm:0.34.3" "@img/sharp-wasm32": "npm:0.34.5"
color: "npm:^4.2.3" "@img/sharp-win32-arm64": "npm:0.34.5"
detect-libc: "npm:^2.0.4" "@img/sharp-win32-ia32": "npm:0.34.5"
semver: "npm:^7.7.2" "@img/sharp-win32-x64": "npm:0.34.5"
detect-libc: "npm:^2.1.2"
semver: "npm:^7.7.3"
dependenciesMeta: dependenciesMeta:
"@img/sharp-darwin-arm64": "@img/sharp-darwin-arm64":
optional: true optional: true
@ -18788,6 +18835,8 @@ __metadata:
optional: true optional: true
"@img/sharp-libvips-linux-ppc64": "@img/sharp-libvips-linux-ppc64":
optional: true optional: true
"@img/sharp-libvips-linux-riscv64":
optional: true
"@img/sharp-libvips-linux-s390x": "@img/sharp-libvips-linux-s390x":
optional: true optional: true
"@img/sharp-libvips-linux-x64": "@img/sharp-libvips-linux-x64":
@ -18802,6 +18851,8 @@ __metadata:
optional: true optional: true
"@img/sharp-linux-ppc64": "@img/sharp-linux-ppc64":
optional: true optional: true
"@img/sharp-linux-riscv64":
optional: true
"@img/sharp-linux-s390x": "@img/sharp-linux-s390x":
optional: true optional: true
"@img/sharp-linux-x64": "@img/sharp-linux-x64":
@ -18818,7 +18869,7 @@ __metadata:
optional: true optional: true
"@img/sharp-win32-x64": "@img/sharp-win32-x64":
optional: true optional: true
checksum: 10c0/df9e6645e3db6ed298a0ac956ba74e468c367fc038b547936fbdddc6a29fce9af40413acbef73b3716291530760f311a20e45c8983f20ee5ea69dd2f21464a2b checksum: 10c0/fd79e29df0597a7d5704b8461c51f944ead91a5243691697be6e8243b966402beda53ddc6f0a53b96ea3cb8221f0b244aa588114d3ebf8734fb4aefd41ab802f
languageName: node languageName: node
linkType: hard linkType: hard