Add additional telemetry (#63)

This commit is contained in:
Brendan Kellam 2024-11-09 16:40:07 -08:00 committed by GitHub
parent 379976d458
commit adba96a8c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 229 additions and 14 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- Added additional telemetry events. ([#63](https://github.com/sourcebot-dev/sourcebot/pull/63))
## [2.4.0] - 2024-11-06 ## [2.4.0] - 2024-11-06
### Added ### Added

View file

@ -55,13 +55,14 @@ RUN echo "Sourcebot Version: $SOURCEBOT_VERSION"
ENV SOURCEBOT_LOG_LEVEL=info ENV SOURCEBOT_LOG_LEVEL=info
# @note: This is also set in .env # @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. # Sourcebot collects anonymous usage data using [PostHog](https://posthog.com/). Uncomment this line to disable.
# ENV SOURCEBOT_TELEMETRY_DISABLED=1 # ENV SOURCEBOT_TELEMETRY_DISABLED=1
# Configure dependencies # 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 # Configure zoekt
COPY vendor/zoekt/install-ctags-alpine.sh . COPY vendor/zoekt/install-ctags-alpine.sh .

View file

@ -15,23 +15,47 @@ fi
# In order to detect if this is the first run, we create a `.installed` file in # In order to detect if this is the first run, we create a `.installed` file in
# the cache directory. # the cache directory.
FIRST_RUN_FILE="$DATA_CACHE_DIR/.installed" FIRST_RUN_FILE="$DATA_CACHE_DIR/.installedv2"
if [ ! -f "$FIRST_RUN_FILE" ]; then if [ ! -f "$FIRST_RUN_FILE" ]; then
touch "$FIRST_RUN_FILE" touch "$FIRST_RUN_FILE"
export SOURCEBOT_INSTALL_ID=$(uuidgen)
# If this is our first run, send a `install` event to PostHog # If this is our first run, send a `install` event to PostHog
# (if telemetry is enabled) # (if telemetry is enabled)
if [ -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then if [ -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
curl -L -s --header "Content-Type: application/json" -d '{ curl -L -s --header "Content-Type: application/json" -d '{
"api_key": "'"$NEXT_PUBLIC_POSTHOG_KEY"'", "api_key": "'"$POSTHOG_KEY"'",
"event": "install", "event": "install",
"distinct_id": "'"$(uuidgen)"'", "distinct_id": "'"$SOURCEBOT_INSTALL_ID"'",
"properties": { "properties": {
"sourcebot_version": "'"$SOURCEBOT_VERSION"'" "sourcebot_version": "'"$SOURCEBOT_VERSION"'"
} }
}' https://us.i.posthog.com/capture/ > /dev/null }' https://us.i.posthog.com/capture/ > /dev/null
fi 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
fi
echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL_ID\"}" > "$FIRST_RUN_FILE"
# Fallback to sample config if a config does not exist # Fallback to sample config if a config does not exist
if echo "$CONFIG_PATH" | grep -qE '^https?://'; then if echo "$CONFIG_PATH" | grep -qE '^https?://'; then

4
packages/backend/.env Normal file
View file

@ -0,0 +1,4 @@
POSTHOG_HOST=https://us.i.posthog.com
# @note: This is also set in the Dockerfile
POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS

View file

@ -1 +1,2 @@
dist/ dist/
!.env

View file

@ -24,9 +24,11 @@
"@octokit/rest": "^21.0.2", "@octokit/rest": "^21.0.2",
"argparse": "^2.0.1", "argparse": "^2.0.1",
"cross-fetch": "^4.0.0", "cross-fetch": "^4.0.0",
"dotenv": "^16.4.5",
"gitea-js": "^1.22.0", "gitea-js": "^1.22.0",
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"micromatch": "^4.0.8", "micromatch": "^4.0.8",
"posthog-node": "^4.2.1",
"simple-git": "^3.27.0", "simple-git": "^3.27.0",
"strip-json-comments": "^5.0.1", "strip-json-comments": "^5.0.1",
"winston": "^3.15.0" "winston": "^3.15.0"

View file

@ -1,6 +1,23 @@
import dotenv from 'dotenv';
export const getEnv = (env: string | undefined, defaultValue = '') => { export const getEnv = (env: string | undefined, defaultValue = '') => {
return env ?? 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_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);

View file

@ -47,6 +47,7 @@ export const getGiteaReposFromConfig = async (config: GiteaConfig, ctx: AppConte
return { return {
vcs: 'git', vcs: 'git',
codeHost: 'gitea',
name: repo.full_name!, name: repo.full_name!,
id: repoId, id: repoId,
cloneUrl: cloneUrl.toString(), cloneUrl: cloneUrl.toString(),

View file

@ -72,6 +72,7 @@ export const getGitHubReposFromConfig = async (config: GitHubConfig, signal: Abo
return { return {
vcs: 'git', vcs: 'git',
codeHost: 'github',
name: repo.full_name, name: repo.full_name,
id: repoId, id: repoId,
cloneUrl: cloneUrl.toString(), cloneUrl: cloneUrl.toString(),

View file

@ -75,6 +75,7 @@ export const getGitLabReposFromConfig = async (config: GitLabConfig, ctx: AppCon
return { return {
vcs: 'git', vcs: 'git',
codeHost: 'gitlab',
name: project.path_with_namespace, name: project.path_with_namespace,
id: repoId, id: repoId,
cloneUrl: cloneUrl.toString(), cloneUrl: cloneUrl.toString(),

View file

@ -1,6 +1,6 @@
import { ArgumentParser } from "argparse"; import { ArgumentParser } from "argparse";
import { mkdir, readFile } from 'fs/promises'; import { mkdir, readFile } from 'fs/promises';
import { existsSync, watch, FSWatcher } from 'fs'; import { existsSync, watch } from 'fs';
import path from 'path'; import path from 'path';
import { SourcebotConfigurationSchema } from "./schemas/v2.js"; import { SourcebotConfigurationSchema } from "./schemas/v2.js";
import { getGitHubReposFromConfig } from "./github.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 stripJsonComments from 'strip-json-comments';
import { indexGitRepository, indexLocalRepository } from "./zoekt.js"; import { indexGitRepository, indexLocalRepository } from "./zoekt.js";
import { getLocalRepoFromConfig, initLocalRepoFileWatchers } from "./local.js"; import { getLocalRepoFromConfig, initLocalRepoFileWatchers } from "./local.js";
import { captureEvent } from "./posthog.js";
const logger = createLogger('main'); const logger = createLogger('main');
@ -28,15 +29,19 @@ type Arguments = {
} }
const syncGitRepository = async (repo: GitRepository, ctx: AppContext) => { const syncGitRepository = async (repo: GitRepository, ctx: AppContext) => {
let fetchDuration_s: number | undefined = undefined;
let cloneDuration_s: number | undefined = undefined;
if (existsSync(repo.path)) { if (existsSync(repo.path)) {
logger.info(`Fetching ${repo.id}...`); logger.info(`Fetching ${repo.id}...`);
const { durationMs } = await measure(() => fetchRepository(repo, ({ method, stage , progress}) => { const { durationMs } = await measure(() => fetchRepository(repo, ({ method, stage , progress}) => {
logger.info(`git.${method} ${stage} stage ${progress}% complete for ${repo.id}`) logger.info(`git.${method} ${stage} stage ${progress}% complete for ${repo.id}`)
})); }));
fetchDuration_s = durationMs / 1000;
process.stdout.write('\n'); process.stdout.write('\n');
logger.info(`Fetched ${repo.id} in ${durationMs / 1000}s`); logger.info(`Fetched ${repo.id} in ${fetchDuration_s}s`);
} else { } else {
logger.info(`Cloning ${repo.id}...`); 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 }) => { const { durationMs } = await measure(() => cloneRepository(repo, ({ method, stage, progress }) => {
logger.info(`git.${method} ${stage} stage ${progress}% complete for ${repo.id}`) logger.info(`git.${method} ${stage} stage ${progress}% complete for ${repo.id}`)
})); }));
cloneDuration_s = durationMs / 1000;
process.stdout.write('\n'); 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}...`); logger.info(`Indexing ${repo.id}...`);
const { durationMs } = await measure(() => indexGitRepository(repo, ctx)); 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) => { const syncLocalRepository = async (repo: LocalRepository, ctx: AppContext, signal?: AbortSignal) => {
logger.info(`Indexing ${repo.id}...`); logger.info(`Indexing ${repo.id}...`);
const { durationMs } = await measure(() => indexLocalRepository(repo, ctx, signal)); 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) => { export const isRepoReindxingRequired = (previous: Repository, current: Repository) => {
@ -172,6 +189,11 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal,
}, db); }, db);
} else { } else {
await createRepository(newRepo, db); 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 { try {
let indexDuration_s: number | undefined;
let fetchDuration_s: number | undefined;
let cloneDuration_s: number | undefined;
if (repo.vcs === 'git') { 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') { } 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) { } catch (err: any) {
// @todo : better error handling here.. // @todo : better error handling here..
logger.error(err); logger.error(err);
@ -312,5 +350,6 @@ const syncConfig = async (configPath: string, db: Database, signal: AbortSignal,
} }
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
} }
})(); })();

View file

@ -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<E extends PosthogEvent>(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();

View file

@ -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;

View file

@ -8,6 +8,7 @@ interface BaseRepository {
lastIndexedDate?: string; lastIndexedDate?: string;
isFork?: boolean; isFork?: boolean;
isArchived?: boolean; isArchived?: boolean;
codeHost?: string;
} }
export interface GitRepository extends BaseRepository { export interface GitRepository extends BaseRepository {

View file

@ -1747,6 +1747,11 @@ async@^3.2.3:
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== 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: available-typed-arrays@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" 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" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59"
integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== 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: axobject-query@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" 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" color "^3.1.3"
text-hex "1.0.x" 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: commander@^4.0.0:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" 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" has-property-descriptors "^1.0.0"
object-keys "^1.1.1" 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: detect-libc@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
@ -2156,6 +2182,11 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" 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: duplexer@~0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" 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" resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== 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: for-each@^0.3.3:
version "0.3.3" version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" 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" cross-spawn "^7.0.0"
signal-exit "^4.0.1" 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: from@~0:
version "0.1.7" version "0.1.7"
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" 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" braces "^3.0.3"
picomatch "^2.3.1" 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: minimatch@9.0.3:
version "9.0.3" version "9.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" 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" preact "^10.19.3"
web-vitals "^4.0.1" 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: preact@^10.19.3:
version "10.24.2" version "10.24.2"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.2.tgz#42179771d3b06e7adb884e3f8127ddd3d99b78f6" 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" object-assign "^4.1.1"
react-is "^16.13.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: ps-tree@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd" resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd"
@ -4136,6 +4206,11 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" 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: safe-array-concat@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb"