Add PostHog telemetry support

This commit is contained in:
bkellam 2024-09-16 21:37:34 -07:00
parent 9a8d17b4c0
commit 3ef9be2b6b
18 changed files with 317 additions and 43 deletions

View file

@ -7,4 +7,5 @@ README.md
!.next/static !.next/static
!.next/standalone !.next/standalone
.git .git
.sourcebot .sourcebot
.env.local

4
.env Normal file
View file

@ -0,0 +1,4 @@
NEXT_PUBLIC_POSTHOG_KEY=phc_VFn4CkEGHRdlVyOOw8mfkoj1DKVoG6y1007EClvzAnS
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
NEXT_PUBLIC_POSTHOG_ASSET_HOST=https://us-assets.i.posthog.com
NEXT_PUBLIC_POSTHOG_UI_HOST=https://us.posthog.com

View file

@ -22,6 +22,8 @@ RUN yarn config set network-timeout 1200000
RUN yarn --frozen-lockfile RUN yarn --frozen-lockfile
COPY . . COPY . .
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED
RUN yarn run build RUN yarn run build
# ------ Runner ------ # ------ Runner ------
@ -33,6 +35,9 @@ ENV DATA_DIR=/data
ENV CONFIG_PATH=$DATA_DIR/config.json ENV CONFIG_PATH=$DATA_DIR/config.json
ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot ENV DATA_CACHE_DIR=$DATA_DIR/.sourcebot
# Sourcebot collects anonymous usage data using [PostHog](https://posthog.com/). Uncomment this line to disable.
# ENV SOURCEBOT_TELEMETRY_DISABLED=1
# Configure dependencies # Configure dependencies
RUN apk add --no-cache git ca-certificates bind-tools tini jansson wget supervisor RUN apk add --no-cache git ca-certificates bind-tools tini jansson wget supervisor

View file

@ -65,20 +65,26 @@ zoekt will now index your repositories (at `HEAD`). By default, it will re-index
4. Go to `http://localhost:3000` - once a index has been created, you should get results. 4. Go to `http://localhost:3000` - once a index has been created, you should get results.
## Building Sourcebot ## Building Sourcebot
TODO TODO
## GitLab ## Disabling Telemetry
By default, Sourcebot collects anonymous usage data using [PostHog](https://posthog.com/). You can disable this by setting the environment variable `SOURCEBOT_TELEMETRY_DISABLED` to `1` in the docker run command. Example:
```sh
docker run -e SOURCEBOT_TELEMETRY_DISABLED=1 ...stuff... ghcr.io/taqlaai/sourcebot:main
```
# GitLab
TODO TODO
## BitBucket # BitBucket
TODO TODO
### Todos # Todos
- Add instructions on using GitLab and BitBucket - Add instructions on using GitLab and BitBucket
- Add instructions on building Sourcebot locally - Add instructions on building Sourcebot locally

View file

@ -1,7 +1,6 @@
#!/bin/sh #!/bin/sh
set -e set -e
# Check if CONFIG_PATH is set # Check if CONFIG_PATH is set
if [ -z "$CONFIG_PATH" ]; then if [ -z "$CONFIG_PATH" ]; then
echo "\e[33mWarning: CONFIG_PATH environment variable is not set.\e[0m" echo "\e[33mWarning: CONFIG_PATH environment variable is not set.\e[0m"
@ -35,4 +34,17 @@ else
echo -e "\e[33mWarning: GitLab repositories will not be indexed since GITLAB_TOKEN was not set. If you are not using GitLab, disregard.\e[0m" echo -e "\e[33mWarning: GitLab repositories will not be indexed since GITLAB_TOKEN was not set. If you are not using GitLab, disregard.\e[0m"
fi fi
# Update nextjs public env variables w/o requiring a rebuild.
# @see: https://phase.dev/blog/nextjs-public-runtime-variables/
# Infer NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED if it is not set
if [ -z "$NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED" ] && [ ! -z "$SOURCEBOT_TELEMETRY_DISABLED" ]; then
export NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED="$SOURCEBOT_TELEMETRY_DISABLED"
fi
find /app/public /app/.next -type f -name "*.js" |
while read file; do
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED|${NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED}|g" "$file"
done
exec supervisord -c /etc/supervisor/conf.d/supervisord.conf exec supervisord -c /etc/supervisor/conf.d/supervisord.conf

View file

@ -1,6 +1,26 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: "standalone" output: "standalone",
// @see : https://posthog.com/docs/advanced/proxy/nextjs
async rewrites() {
return [
{
source: "/ingest/static/:path*",
destination: `${process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST}/static/:path*`,
},
{
source: "/ingest/:path*",
destination: `${process.env.NEXT_PUBLIC_POSTHOG_HOST}/:path*`,
},
{
source: "/ingest/decide",
destination: `${process.env.NEXT_PUBLIC_POSTHOG_HOST}/decide`,
},
];
},
// This is required to support PostHog trailing slash API requests
skipTrailingSlashRedirect: true,
}; };
export default nextConfig; export default nextConfig;

View file

@ -27,17 +27,20 @@
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@uiw/react-codemirror": "^4.23.0", "@uiw/react-codemirror": "^4.23.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"client-only": "^0.0.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"http-status-codes": "^2.3.0", "http-status-codes": "^2.3.0",
"lucide-react": "^0.435.0", "lucide-react": "^0.435.0",
"next": "14.2.6", "next": "14.2.6",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"posthog-js": "^1.161.5",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"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-resizable-panels": "^2.1.1", "react-resizable-panels": "^2.1.1",
"server-only": "^0.0.1",
"sharp": "^0.33.5", "sharp": "^0.33.5",
"tailwind-merge": "^2.5.2", "tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

View file

@ -4,9 +4,15 @@ import "./globals.css";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
import { Suspense } from "react"; import { Suspense } from "react";
import { QueryClientProvider } from "./queryClientProvider"; import { QueryClientProvider } from "./queryClientProvider";
import { PHProvider } from "./posthogProvider";
import dynamic from "next/dynamic";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
const PostHogPageView = dynamic(() => import('./posthogPageView'), {
ssr: false,
})
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Sourcebot", title: "Sourcebot",
description: "Sourcebot", description: "Sourcebot",
@ -24,22 +30,25 @@ export default function RootLayout({
suppressHydrationWarning suppressHydrationWarning
> >
<body className={inter.className}> <body className={inter.className}>
<ThemeProvider <PHProvider>
attribute="class" <PostHogPageView />
defaultTheme="system" <ThemeProvider
enableSystem attribute="class"
disableTransitionOnChange defaultTheme="system"
> enableSystem
<QueryClientProvider> disableTransitionOnChange
{/* >
@todo : ideally we don't wrap everything in a suspense boundary. <QueryClientProvider>
@see : https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout {/*
*/} @todo : ideally we don't wrap everything in a suspense boundary.
<Suspense> @see : https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
{children} */}
</Suspense> <Suspense>
</QueryClientProvider> {children}
</ThemeProvider> </Suspense>
</QueryClientProvider>
</ThemeProvider>
</PHProvider>
</body> </body>
</html> </html>
); );

View file

@ -0,0 +1,28 @@
'use client'
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { usePostHog } from 'posthog-js/react';
export default function PostHogPageView(): null {
const pathname = usePathname();
const searchParams = useSearchParams();
const posthog = usePostHog();
useEffect(() => {
// Track pageviews
if (pathname && posthog) {
let url = window.origin + pathname
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`
}
posthog.capture(
'$pageview',
{
'$current_url': url,
}
)
}
}, [pathname, searchParams, posthog])
return null
}

View file

@ -0,0 +1,25 @@
'use client'
import { NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_UI_HOST, NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED } from '@/lib/environment.client'
import posthog from 'posthog-js'
import { PostHogProvider } from 'posthog-js/react'
if (typeof window !== 'undefined') {
if (!NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED) {
posthog.init(NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: "/ingest",
ui_host: NEXT_PUBLIC_POSTHOG_UI_HOST,
person_profiles: 'identified_only',
capture_pageview: false, // Disable automatic pageview capture, as we capture manually
});
} else {
console.log("PostHog telemetry disabled");
}
}
export function PHProvider({
children,
}: {
children: React.ReactNode
}) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>
}

View file

@ -13,7 +13,7 @@ import { SymbolIcon } from "@radix-ui/react-icons";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import logoDark from "../../../public/sb_logo_dark.png"; import logoDark from "../../../public/sb_logo_dark.png";
import logoLight from "../../../public/sb_logo_light.png"; import logoLight from "../../../public/sb_logo_light.png";
import { fetchFileSource, search } from "../api/(client)/client"; import { fetchFileSource, search } from "../api/(client)/client";
@ -21,6 +21,7 @@ import { SearchBar } from "../searchBar";
import { SettingsDropdown } from "../settingsDropdown"; import { SettingsDropdown } from "../settingsDropdown";
import { CodePreviewFile, CodePreviewPanel } from "./codePreviewPanel"; import { CodePreviewFile, CodePreviewPanel } from "./codePreviewPanel";
import { SearchResultsPanel } from "./searchResultsPanel"; import { SearchResultsPanel } from "./searchResultsPanel";
import useCaptureEvent from "@/hooks/useCaptureEvent";
const DEFAULT_NUM_RESULTS = 100; const DEFAULT_NUM_RESULTS = 100;
@ -33,6 +34,8 @@ export default function SearchPage() {
const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); const [selectedMatchIndex, setSelectedMatchIndex] = useState(0);
const [selectedFile, setSelectedFile] = useState<SearchResultFile | undefined>(undefined); const [selectedFile, setSelectedFile] = useState<SearchResultFile | undefined>(undefined);
const captureEvent = useCaptureEvent();
const { data: searchResponse, isLoading } = useQuery({ const { data: searchResponse, isLoading } = useQuery({
queryKey: ["search", searchQuery, numResults], queryKey: ["search", searchQuery, numResults],
queryFn: () => search({ queryFn: () => search({
@ -40,8 +43,41 @@ export default function SearchPage() {
numResults, numResults,
}), }),
enabled: searchQuery.length > 0, enabled: searchQuery.length > 0,
refetchOnWindowFocus: false,
}); });
useEffect(() => {
if (!searchResponse) {
return;
}
const fileLanguages = searchResponse.Result.Files?.map(file => file.Language) || [];
captureEvent("search_finished", {
contentBytesLoaded: searchResponse.Result.ContentBytesLoaded,
indexBytesLoaded: searchResponse.Result.IndexBytesLoaded,
crashes: searchResponse.Result.Crashes,
durationMs: searchResponse.Result.Duration / 1000000,
fileCount: searchResponse.Result.FileCount,
shardFilesConsidered: searchResponse.Result.ShardFilesConsidered,
filesConsidered: searchResponse.Result.FilesConsidered,
filesLoaded: searchResponse.Result.FilesLoaded,
filesSkipped: searchResponse.Result.FilesSkipped,
shardsScanned: searchResponse.Result.ShardsScanned,
shardsSkipped: searchResponse.Result.ShardsSkipped,
shardsSkippedFilter: searchResponse.Result.ShardsSkippedFilter,
matchCount: searchResponse.Result.MatchCount,
ngramMatches: searchResponse.Result.NgramMatches,
ngramLookups: searchResponse.Result.NgramLookups,
wait: searchResponse.Result.Wait,
matchTreeConstruction: searchResponse.Result.MatchTreeConstruction,
matchTreeSearch: searchResponse.Result.MatchTreeSearch,
regexpsConsidered: searchResponse.Result.RegexpsConsidered,
flushReason: searchResponse.Result.FlushReason,
fileLanguages,
});
}, [captureEvent, searchResponse]);
const { fileMatches, searchDurationMs } = useMemo((): { fileMatches: SearchResultFile[], searchDurationMs: number } => { const { fileMatches, searchDurationMs } = useMemo((): { fileMatches: SearchResultFile[], searchDurationMs: number } => {
if (!searchResponse) { if (!searchResponse) {
return { return {
@ -104,7 +140,7 @@ export default function SearchPage() {
onClick={() => { onClick={() => {
const url = createPathWithQueryParams('/search', const url = createPathWithQueryParams('/search',
["query", searchQuery], ["query", searchQuery],
["numResults", `${numResults*2}`], ["numResults", `${numResults * 2}`],
) )
router.push(url); router.push(url);
}} }}

View file

@ -0,0 +1,26 @@
'use client';
import { CaptureOptions } from "posthog-js";
import posthog from "posthog-js";
import { PosthogEvent, PosthogEventMap } from "../lib/posthogEvents";
export function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E], options?: CaptureOptions) {
if(!options) {
options = {};
}
options.send_instantly = true;
posthog.capture(event, properties, options);
}
/**
* Captures a distinct action as a event and forwards it to the event service
* (i.e., PostHog).
*
* @returns A callback for capturing events.
* @see: https://posthog.com/docs/libraries/js#capturing-events
*/
const useCaptureEvent = () => {
return captureEvent;
}
export default useCaptureEvent;

View file

@ -0,0 +1,9 @@
import 'client-only';
import { getEnv, getEnvBoolean } from "./utils";
export const NEXT_PUBLIC_POSTHOG_KEY = getEnv(process.env.NEXT_PUBLIC_POSTHOG_KEY);
export const NEXT_PUBLIC_POSTHOG_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_HOST);
export const NEXT_PUBLIC_POSTHOG_UI_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_UI_HOST);
export const NEXT_PUBLIC_POSTHOG_ASSET_HOST = getEnv(process.env.NEXT_PUBLIC_POSTHOG_ASSET_HOST);
export const NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED = getEnvBoolean(process.env.NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED, false);

View file

@ -1,14 +1,8 @@
import 'server-only';
const getEnv = (env: string | undefined, defaultValue = '') => { import { getEnv, getEnvNumber } from "./utils";
return env ?? defaultValue;
}
const getEnvNumber = (env: string | undefined, defaultValue: number = 0) => {
return Number(env) ?? defaultValue;
}
export const ZOEKT_WEBSERVER_URL = getEnv(process.env.ZOEKT_WEBSERVER_URL, "http://localhost:6070"); export const ZOEKT_WEBSERVER_URL = getEnv(process.env.ZOEKT_WEBSERVER_URL, "http://localhost:6070");
export const SHARD_MAX_MATCH_COUNT = getEnvNumber(process.env.SHARD_MAX_MATCH_COUNT, 10000); export const SHARD_MAX_MATCH_COUNT = getEnvNumber(process.env.SHARD_MAX_MATCH_COUNT, 10000);
export const TOTAL_MAX_MATCH_COUNT = getEnvNumber(process.env.TOTAL_MAX_MATCH_COUNT, 100000); export const TOTAL_MAX_MATCH_COUNT = getEnvNumber(process.env.TOTAL_MAX_MATCH_COUNT, 100000);
export const NODE_ENV = process.env.NODE_ENV; export const NODE_ENV = process.env.NODE_ENV;

29
src/lib/posthogEvents.ts Normal file
View file

@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-empty-object-type */
export type PosthogEventMap = {
search_finished: {
contentBytesLoaded: number,
indexBytesLoaded: number,
crashes: number,
durationMs: number,
fileCount: number,
shardFilesConsidered: number,
filesConsidered: number,
filesLoaded: number,
filesSkipped: number,
shardsScanned: number,
shardsSkipped: number,
shardsSkippedFilter: number,
matchCount: number,
ngramMatches: number,
ngramLookups: number,
wait: number,
matchTreeConstruction: number,
matchTreeSearch: number,
regexpsConsidered: number,
flushReason: number,
fileLanguages: string[]
}
}
export type PosthogEvent = keyof PosthogEventMap;

View file

@ -30,11 +30,34 @@ const rangeSchema = z.object({
End: locationSchema, End: locationSchema,
}); });
// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L350
export const searchResponseStats = {
ContentBytesLoaded: z.number(),
IndexBytesLoaded: z.number(),
Crashes: z.number(),
Duration: z.number(),
FileCount: z.number(),
ShardFilesConsidered: z.number(),
FilesConsidered: z.number(),
FilesLoaded: z.number(),
FilesSkipped: z.number(),
ShardsScanned: z.number(),
ShardsSkipped: z.number(),
ShardsSkippedFilter: z.number(),
MatchCount: z.number(),
NgramMatches: z.number(),
NgramLookups: z.number(),
Wait: z.number(),
MatchTreeConstruction: z.number(),
MatchTreeSearch: z.number(),
RegexpsConsidered: z.number(),
FlushReason: z.number(),
}
// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L497
export const searchResponseSchema = z.object({ export const searchResponseSchema = z.object({
Result: z.object({ Result: z.object({
Duration: z.number(), ...searchResponseStats,
FileCount: z.number(),
MatchCount: z.number(),
Files: z.array(z.object({ Files: z.array(z.object({
FileName: z.string(), FileName: z.string(),
Repository: z.string(), Repository: z.string(),
@ -72,7 +95,7 @@ export const fileSourceResponseSchema = z.object({
export type ListRepositoriesResponse = z.infer<typeof listRepositoriesResponseSchema>; export type ListRepositoriesResponse = z.infer<typeof listRepositoriesResponseSchema>;
// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L728 // @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L728
export const statsSchema = z.object({ const repoStatsSchema = z.object({
Repos: z.number(), Repos: z.number(),
Shards: z.number(), Shards: z.number(),
Documents: z.number(), Documents: z.number(),
@ -84,7 +107,7 @@ export const statsSchema = z.object({
}); });
// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L716 // @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L716
export const indexMetadataSchema = z.object({ const indexMetadataSchema = z.object({
IndexFormatVersion: z.number(), IndexFormatVersion: z.number(),
IndexFeatureVersion: z.number(), IndexFeatureVersion: z.number(),
IndexMinReaderVersion: z.number(), IndexMinReaderVersion: z.number(),
@ -96,7 +119,7 @@ export const indexMetadataSchema = z.object({
}); });
// @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L555 // @see : https://github.com/TaqlaAI/zoekt/blob/3780e68cdb537d5a7ed2c84d9b3784f80c7c5d04/api.go#L555
export const repositorySchema = z.object({ const repositorySchema = z.object({
Name: z.string(), Name: z.string(),
URL: z.string(), URL: z.string(),
Source: z.string(), Source: z.string(),
@ -121,8 +144,8 @@ export const listRepositoriesResponseSchema = z.object({
Repos: z.array(z.object({ Repos: z.array(z.object({
Repository: repositorySchema, Repository: repositorySchema,
IndexMetadata: indexMetadataSchema, IndexMetadata: indexMetadataSchema,
Stats: statsSchema, Stats: repoStatsSchema,
})), })),
Stats: statsSchema, Stats: repoStatsSchema,
}) })
}); });

View file

@ -81,3 +81,18 @@ export const isServiceError = (data: unknown): data is ServiceError => {
'errorCode' in data && 'errorCode' in data &&
'message' in data; 'message' in data;
} }
export const getEnv = (env: string | undefined, defaultValue = '') => {
return env ?? defaultValue;
}
export const getEnvNumber = (env: string | undefined, defaultValue: number = 0) => {
return Number(env) ?? defaultValue;
}
export const getEnvBoolean = (env: string | undefined, defaultValue: boolean) => {
if (!env) {
return defaultValue;
}
return env === 'true' || env === '1';
}

View file

@ -1268,7 +1268,7 @@ class-variance-authority@^0.7.0:
dependencies: dependencies:
clsx "2.0.0" clsx "2.0.0"
client-only@0.0.1: client-only@0.0.1, client-only@^0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
@ -1897,6 +1897,11 @@ fastq@^1.6.0:
dependencies: dependencies:
reusify "^1.0.4" reusify "^1.0.4"
fflate@^0.4.8:
version "0.4.8"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
file-entry-cache@^6.0.1: file-entry-cache@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@ -2901,6 +2906,20 @@ postcss@^8, postcss@^8.4.23:
picocolors "^1.0.1" picocolors "^1.0.1"
source-map-js "^1.2.0" source-map-js "^1.2.0"
posthog-js@^1.161.5:
version "1.161.5"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.161.5.tgz#3c07acf622c0719cd8e0e78ab4b0f3e85914c7ef"
integrity sha512-KGkb12grSQvGRauH6z+AUB83c4dgWqzmJFDjyMXarWRafaLN80HzjN1jk806x27HvdDXi21jtwiXekioWzEQ9g==
dependencies:
fflate "^0.4.8"
preact "^10.19.3"
web-vitals "^4.0.1"
preact@^10.19.3:
version "10.24.0"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.0.tgz#bd8139bee35aafede3c6de96d2453982610dfeef"
integrity sha512-aK8Cf+jkfyuZ0ZZRG9FbYqwmEiGQ4y/PUO4SuTWoyWL244nZZh7bd5h2APd4rSNDYTBNghg1L+5iJN3Skxtbsw==
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -3113,6 +3132,11 @@ semver@^7.5.4, semver@^7.6.0, semver@^7.6.3:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
server-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/server-only/-/server-only-0.0.1.tgz#0f366bb6afb618c37c9255a314535dc412cd1c9e"
integrity sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==
set-function-length@^1.2.1: set-function-length@^1.2.1:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
@ -3572,6 +3596,11 @@ w3c-keyname@^2.2.4:
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
web-vitals@^4.0.1:
version "4.2.3"
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.3.tgz#270c4baecfbc6ec6fc15da1989e465e5f9b94fb7"
integrity sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q==
which-boxed-primitive@^1.0.2: which-boxed-primitive@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"