From adba96a8c49f1ab4af127ce97b59ca95bcc9603d Mon Sep 17 00:00:00 2001 From: Brendan Kellam Date: Sat, 9 Nov 2024 16:40:07 -0800 Subject: [PATCH] Add additional telemetry (#63) --- CHANGELOG.md | 4 ++ Dockerfile | 5 +- entrypoint.sh | 32 ++++++++++-- packages/backend/.env | 4 ++ packages/backend/.gitignore | 3 +- packages/backend/package.json | 2 + packages/backend/src/environment.ts | 17 ++++++ packages/backend/src/gitea.ts | 1 + packages/backend/src/github.ts | 1 + packages/backend/src/gitlab.ts | 1 + packages/backend/src/index.ts | 53 ++++++++++++++++--- packages/backend/src/posthog.ts | 27 ++++++++++ packages/backend/src/posthogEvents.ts | 17 ++++++ packages/backend/src/types.ts | 1 + yarn.lock | 75 +++++++++++++++++++++++++++ 15 files changed, 229 insertions(+), 14 deletions(-) create mode 100644 packages/backend/.env create mode 100644 packages/backend/src/posthog.ts create mode 100644 packages/backend/src/posthogEvents.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index cc641470..e6487e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added additional telemetry events. ([#63](https://github.com/sourcebot-dev/sourcebot/pull/63)) + ## [2.4.0] - 2024-11-06 ### Added diff --git a/Dockerfile b/Dockerfile index 7e5a1955..f18fb3fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,13 +55,14 @@ RUN echo "Sourcebot Version: $SOURCEBOT_VERSION" ENV SOURCEBOT_LOG_LEVEL=info # @note: This is also set in .env -ENV NEXT_PUBLIC_POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS +ENV POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS +ENV NEXT_PUBLIC_POSTHOG_KEY=$POSTHOG_KEY # Sourcebot collects anonymous usage data using [PostHog](https://posthog.com/). Uncomment this line to disable. # ENV SOURCEBOT_TELEMETRY_DISABLED=1 # Configure dependencies -RUN apk add --no-cache git ca-certificates bind-tools tini jansson wget supervisor uuidgen curl perl +RUN apk add --no-cache git ca-certificates bind-tools tini jansson wget supervisor uuidgen curl perl jq # Configure zoekt COPY vendor/zoekt/install-ctags-alpine.sh . diff --git a/entrypoint.sh b/entrypoint.sh index 2fa3be78..c2a0b0aa 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -15,24 +15,48 @@ fi # In order to detect if this is the first run, we create a `.installed` file in # the cache directory. -FIRST_RUN_FILE="$DATA_CACHE_DIR/.installed" +FIRST_RUN_FILE="$DATA_CACHE_DIR/.installedv2" + if [ ! -f "$FIRST_RUN_FILE" ]; then touch "$FIRST_RUN_FILE" - + export SOURCEBOT_INSTALL_ID=$(uuidgen) + # If this is our first run, send a `install` event to PostHog # (if telemetry is enabled) if [ -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then curl -L -s --header "Content-Type: application/json" -d '{ - "api_key": "'"$NEXT_PUBLIC_POSTHOG_KEY"'", + "api_key": "'"$POSTHOG_KEY"'", "event": "install", - "distinct_id": "'"$(uuidgen)"'", + "distinct_id": "'"$SOURCEBOT_INSTALL_ID"'", "properties": { "sourcebot_version": "'"$SOURCEBOT_VERSION"'" } }' https://us.i.posthog.com/capture/ > /dev/null fi +else + export SOURCEBOT_INSTALL_ID=$(cat "$FIRST_RUN_FILE" | jq -r '.install_id') + PREVIOUS_VERSION=$(cat "$FIRST_RUN_FILE" | jq -r '.version') + + # If the version has changed, we assume an upgrade has occurred. + if [ "$PREVIOUS_VERSION" != "$SOURCEBOT_VERSION" ]; then + echo -e "\e[34m[Info] Upgraded from version $PREVIOUS_VERSION to $SOURCEBOT_VERSION\e[0m" + + if [ -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then + curl -L -s --header "Content-Type: application/json" -d '{ + "api_key": "'"$POSTHOG_KEY"'", + "event": "upgrade", + "distinct_id": "'"$SOURCEBOT_INSTALL_ID"'", + "properties": { + "from_version": "'"$PREVIOUS_VERSION"'", + "to_version": "'"$SOURCEBOT_VERSION"'" + } + }' https://us.i.posthog.com/capture/ > /dev/null + fi + fi fi +echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL_ID\"}" > "$FIRST_RUN_FILE" + # Fallback to sample config if a config does not exist if echo "$CONFIG_PATH" | grep -qE '^https?://'; then if ! curl --output /dev/null --silent --head --fail "$CONFIG_PATH"; then diff --git a/packages/backend/.env b/packages/backend/.env new file mode 100644 index 00000000..cdcc9711 --- /dev/null +++ b/packages/backend/.env @@ -0,0 +1,4 @@ +POSTHOG_HOST=https://us.i.posthog.com + +# @note: This is also set in the Dockerfile +POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS \ No newline at end of file diff --git a/packages/backend/.gitignore b/packages/backend/.gitignore index 77738287..9ce098c9 100644 --- a/packages/backend/.gitignore +++ b/packages/backend/.gitignore @@ -1 +1,2 @@ -dist/ \ No newline at end of file +dist/ +!.env \ No newline at end of file diff --git a/packages/backend/package.json b/packages/backend/package.json index 76207ac3..a8bed825 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -24,9 +24,11 @@ "@octokit/rest": "^21.0.2", "argparse": "^2.0.1", "cross-fetch": "^4.0.0", + "dotenv": "^16.4.5", "gitea-js": "^1.22.0", "lowdb": "^7.0.1", "micromatch": "^4.0.8", + "posthog-node": "^4.2.1", "simple-git": "^3.27.0", "strip-json-comments": "^5.0.1", "winston": "^3.15.0" diff --git a/packages/backend/src/environment.ts b/packages/backend/src/environment.ts index b1e0e43e..160816d1 100644 --- a/packages/backend/src/environment.ts +++ b/packages/backend/src/environment.ts @@ -1,6 +1,23 @@ +import dotenv from 'dotenv'; export const getEnv = (env: string | undefined, defaultValue = '') => { return env ?? defaultValue; } +export const getEnvBoolean = (env: string | undefined, defaultValue: boolean) => { + if (!env) { + return defaultValue; + } + return env === 'true' || env === '1'; +} + +dotenv.config({ + path: './.env', +}); + export const SOURCEBOT_LOG_LEVEL = getEnv(process.env.SOURCEBOT_LOG_LEVEL, 'info'); +export const SOURCEBOT_TELEMETRY_DISABLED = getEnvBoolean(process.env.SOURCEBOT_TELEMETRY_DISABLED, false); +export const SOURCEBOT_INSTALL_ID = getEnv(process.env.SOURCEBOT_INSTALL_ID, 'unknown'); +export const SOURCEBOT_VERSION = getEnv(process.env.SOURCEBOT_VERSION, 'unknown'); +export const POSTHOG_KEY = getEnv(process.env.POSTHOG_KEY); +export const POSTHOG_HOST = getEnv(process.env.POSTHOG_HOST); diff --git a/packages/backend/src/gitea.ts b/packages/backend/src/gitea.ts index f5119844..caa3051f 100644 --- a/packages/backend/src/gitea.ts +++ b/packages/backend/src/gitea.ts @@ -47,6 +47,7 @@ export const getGiteaReposFromConfig = async (config: GiteaConfig, ctx: AppConte return { vcs: 'git', + codeHost: 'gitea', name: repo.full_name!, id: repoId, cloneUrl: cloneUrl.toString(), diff --git a/packages/backend/src/github.ts b/packages/backend/src/github.ts index da813de6..d487208b 100644 --- a/packages/backend/src/github.ts +++ b/packages/backend/src/github.ts @@ -72,6 +72,7 @@ export const getGitHubReposFromConfig = async (config: GitHubConfig, signal: Abo return { vcs: 'git', + codeHost: 'github', name: repo.full_name, id: repoId, cloneUrl: cloneUrl.toString(), diff --git a/packages/backend/src/gitlab.ts b/packages/backend/src/gitlab.ts index ddb52eee..5795c32f 100644 --- a/packages/backend/src/gitlab.ts +++ b/packages/backend/src/gitlab.ts @@ -75,6 +75,7 @@ export const getGitLabReposFromConfig = async (config: GitLabConfig, ctx: AppCon return { vcs: 'git', + codeHost: 'gitlab', name: project.path_with_namespace, id: repoId, cloneUrl: cloneUrl.toString(), diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 553db78e..14c6a88b 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,6 +1,6 @@ import { ArgumentParser } from "argparse"; import { mkdir, readFile } from 'fs/promises'; -import { existsSync, watch, FSWatcher } from 'fs'; +import { existsSync, watch } from 'fs'; import path from 'path'; import { SourcebotConfigurationSchema } from "./schemas/v2.js"; import { getGitHubReposFromConfig } from "./github.js"; @@ -15,6 +15,7 @@ import { REINDEX_INTERVAL_MS, RESYNC_CONFIG_INTERVAL_MS } from "./constants.js"; import stripJsonComments from 'strip-json-comments'; import { indexGitRepository, indexLocalRepository } from "./zoekt.js"; import { getLocalRepoFromConfig, initLocalRepoFileWatchers } from "./local.js"; +import { captureEvent } from "./posthog.js"; const logger = createLogger('main'); @@ -28,15 +29,19 @@ type Arguments = { } const syncGitRepository = async (repo: GitRepository, ctx: AppContext) => { + let fetchDuration_s: number | undefined = undefined; + let cloneDuration_s: number | undefined = undefined; + if (existsSync(repo.path)) { logger.info(`Fetching ${repo.id}...`); const { durationMs } = await measure(() => fetchRepository(repo, ({ method, stage , progress}) => { logger.info(`git.${method} ${stage} stage ${progress}% complete for ${repo.id}`) })); + fetchDuration_s = durationMs / 1000; process.stdout.write('\n'); - logger.info(`Fetched ${repo.id} in ${durationMs / 1000}s`); + logger.info(`Fetched ${repo.id} in ${fetchDuration_s}s`); } else { logger.info(`Cloning ${repo.id}...`); @@ -44,20 +49,32 @@ const syncGitRepository = async (repo: GitRepository, ctx: AppContext) => { const { durationMs } = await measure(() => cloneRepository(repo, ({ method, stage, progress }) => { logger.info(`git.${method} ${stage} stage ${progress}% complete for ${repo.id}`) })); + cloneDuration_s = durationMs / 1000; process.stdout.write('\n'); - logger.info(`Cloned ${repo.id} in ${durationMs / 1000}s`); + logger.info(`Cloned ${repo.id} in ${cloneDuration_s}s`); } logger.info(`Indexing ${repo.id}...`); const { durationMs } = await measure(() => indexGitRepository(repo, ctx)); - logger.info(`Indexed ${repo.id} in ${durationMs / 1000}s`); + const indexDuration_s = durationMs / 1000; + logger.info(`Indexed ${repo.id} in ${indexDuration_s}s`); + + return { + fetchDuration_s, + cloneDuration_s, + indexDuration_s, + } } const syncLocalRepository = async (repo: LocalRepository, ctx: AppContext, signal?: AbortSignal) => { logger.info(`Indexing ${repo.id}...`); const { durationMs } = await measure(() => indexLocalRepository(repo, ctx, signal)); - logger.info(`Indexed ${repo.id} in ${durationMs / 1000}s`); + const indexDuration_s = durationMs / 1000; + logger.info(`Indexed ${repo.id} in ${indexDuration_s}s`); + return { + indexDuration_s, + } } export const isRepoReindxingRequired = (previous: Repository, current: Repository) => { @@ -172,6 +189,11 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal, }, db); } else { await createRepository(newRepo, db); + + captureEvent("repo_created", { + vcs: newRepo.vcs, + codeHost: newRepo.codeHost, + }); } } @@ -297,11 +319,27 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal, } try { + let indexDuration_s: number | undefined; + let fetchDuration_s: number | undefined; + let cloneDuration_s: number | undefined; + if (repo.vcs === 'git') { - await syncGitRepository(repo, context); + const stats = await syncGitRepository(repo, context); + indexDuration_s = stats.indexDuration_s; + fetchDuration_s = stats.fetchDuration_s; + cloneDuration_s = stats.cloneDuration_s; } else if (repo.vcs === 'local') { - await syncLocalRepository(repo, context); + const stats = await syncLocalRepository(repo, context); + indexDuration_s = stats.indexDuration_s; } + + captureEvent('repo_synced', { + vcs: repo.vcs, + codeHost: repo.codeHost, + indexDuration_s, + fetchDuration_s, + cloneDuration_s, + }); } catch (err: any) { // @todo : better error handling here.. logger.error(err); @@ -312,5 +350,6 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal, } await new Promise(resolve => setTimeout(resolve, 1000)); + } })(); diff --git a/packages/backend/src/posthog.ts b/packages/backend/src/posthog.ts new file mode 100644 index 00000000..f9bc487c --- /dev/null +++ b/packages/backend/src/posthog.ts @@ -0,0 +1,27 @@ +import { PostHog } from 'posthog-node'; +import { PosthogEvent, PosthogEventMap } from './posthogEvents.js'; +import { POSTHOG_HOST, POSTHOG_KEY, SOURCEBOT_INSTALL_ID, SOURCEBOT_TELEMETRY_DISABLED, SOURCEBOT_VERSION } from './environment.js'; + +const posthog = new PostHog( + POSTHOG_KEY, + { + host: POSTHOG_HOST, + } +); + +export function captureEvent(event: E, properties: PosthogEventMap[E]) { + if (SOURCEBOT_TELEMETRY_DISABLED) { + return; + } + + posthog.capture({ + distinctId: SOURCEBOT_INSTALL_ID, + event: event, + properties: { + ...properties, + sourcebot_version: SOURCEBOT_VERSION, + }, + }); +} + +await posthog.shutdown(); \ No newline at end of file diff --git a/packages/backend/src/posthogEvents.ts b/packages/backend/src/posthogEvents.ts new file mode 100644 index 00000000..7651a038 --- /dev/null +++ b/packages/backend/src/posthogEvents.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-empty-object-type */ + +export type PosthogEventMap = { + repo_created: { + vcs: string; + codeHost?: string; + }, + repo_synced: { + vcs: string; + codeHost?: string; + fetchDuration_s?: number; + cloneDuration_s?: number; + indexDuration_s?: number; + } +} + +export type PosthogEvent = keyof PosthogEventMap; \ No newline at end of file diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 6c81097b..7d699790 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -8,6 +8,7 @@ interface BaseRepository { lastIndexedDate?: string; isFork?: boolean; isArchived?: boolean; + codeHost?: string; } export interface GitRepository extends BaseRepository { diff --git a/yarn.lock b/yarn.lock index 3f3d29e9..624dd441 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1747,6 +1747,11 @@ async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -1759,6 +1764,15 @@ axe-core@^4.10.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== +axios@^1.7.4: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" @@ -1970,6 +1984,13 @@ colorspace@1.1.x: color "^3.1.3" text-hex "1.0.x" +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -2115,6 +2136,11 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + detect-libc@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" @@ -2156,6 +2182,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -2713,6 +2744,11 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -2728,6 +2764,15 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" @@ -3464,6 +3509,18 @@ micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + minimatch@9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" @@ -3892,6 +3949,14 @@ posthog-js@^1.161.5: preact "^10.19.3" web-vitals "^4.0.1" +posthog-node@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/posthog-node/-/posthog-node-4.2.1.tgz#c9f077116bebd06dc65a3f9ae282d10db242c660" + integrity sha512-l+fsjYEkTik3m/G0pE7gMr4qBJP84LhK779oQm6MBzhBGpd4By4qieTW+4FUAlNCyzQTynn3Nhsa50c0IELSxQ== + dependencies: + axios "^1.7.4" + rusha "^0.8.14" + preact@^10.19.3: version "10.24.2" resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.2.tgz#42179771d3b06e7adb884e3f8127ddd3d99b78f6" @@ -3926,6 +3991,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + ps-tree@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd" @@ -4136,6 +4206,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rusha@^0.8.14: + version "0.8.14" + resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.14.tgz#a977d0de9428406138b7bb90d3de5dcd024e2f68" + integrity sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA== + safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb"