diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml new file mode 100644 index 00000000..5d4b5fde --- /dev/null +++ b/.github/workflows/test-backend.yml @@ -0,0 +1,28 @@ +name: Test Backend + +on: + pull_request: + branches: ["main"] + + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: "true" + - name: Use Node.Js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Install + run: yarn install --frozen-lockfile + + - name: Test + run: yarn workspace @sourcebot/backend test + diff --git a/package.json b/package.json index b0bb5d74..3e0fbd87 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ ], "scripts": { "build": "yarn workspaces run build", + "test": "yarn workspace @sourcebot/backend test", "dev": "npm-run-all --print-label --parallel dev:zoekt dev:backend dev:web", "dev:zoekt": "export PATH=\"$PWD/bin:$PATH\" && zoekt-webserver -index .sourcebot/index -rpc", "dev:backend": "yarn workspace @sourcebot/backend dev:watch", diff --git a/packages/backend/package.json b/packages/backend/package.json index a8bed825..c0ae5542 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -8,7 +8,8 @@ "dev:watch": "yarn generate:types && tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --configPath ../../config.json --cacheDir ../../.sourcebot\"", "dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js", "build": "yarn generate:types && tsc", - "generate:types": "tsx tools/generateTypes.ts" + "generate:types": "tsx tools/generateTypes.ts", + "test": "vitest --config ./vitest.config.ts" }, "devDependencies": { "@types/argparse": "^2.0.16", @@ -17,7 +18,8 @@ "json-schema-to-typescript": "^15.0.2", "tsc-watch": "^6.2.0", "tsx": "^4.19.1", - "typescript": "^5.6.2" + "typescript": "^5.6.2", + "vitest": "^2.1.4" }, "dependencies": { "@gitbeaker/rest": "^40.5.1", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 14c6a88b..893d50b7 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,23 +1,11 @@ import { ArgumentParser } from "argparse"; -import { mkdir, readFile } from 'fs/promises'; -import { existsSync, watch } from 'fs'; +import { existsSync } from 'fs'; +import { mkdir } from 'fs/promises'; import path from 'path'; -import { SourcebotConfigurationSchema } from "./schemas/v2.js"; -import { getGitHubReposFromConfig } from "./github.js"; -import { getGitLabReposFromConfig } from "./gitlab.js"; -import { getGiteaReposFromConfig } from "./gitea.js"; -import { AppContext, LocalRepository, GitRepository, Repository } from "./types.js"; -import { cloneRepository, fetchRepository } from "./git.js"; -import { createLogger } from "./logger.js"; -import { createRepository, Database, loadDB, updateRepository } from './db.js'; -import { arraysEqualShallow, isRemotePath, measure } from "./utils.js"; -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"; +import { isRemotePath } from "./utils.js"; +import { AppContext } from "./types.js"; +import { main } from "./main.js" -const logger = createLogger('main'); const parser = new ArgumentParser({ description: "Sourcebot backend tool", @@ -28,328 +16,40 @@ type Arguments = { cacheDir: string; } -const syncGitRepository = async (repo: GitRepository, ctx: AppContext) => { - let fetchDuration_s: number | undefined = undefined; - let cloneDuration_s: number | undefined = undefined; +parser.add_argument("--configPath", { + help: "Path to config file", + required: true, +}); - if (existsSync(repo.path)) { - logger.info(`Fetching ${repo.id}...`); +parser.add_argument("--cacheDir", { + help: "Path to .sourcebot cache directory", + required: true, +}); +const args = parser.parse_args() as Arguments; - 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 ${fetchDuration_s}s`); - - } else { - logger.info(`Cloning ${repo.id}...`); - - 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 ${cloneDuration_s}s`); - } - - logger.info(`Indexing ${repo.id}...`); - const { durationMs } = await measure(() => indexGitRepository(repo, ctx)); - const indexDuration_s = durationMs / 1000; - logger.info(`Indexed ${repo.id} in ${indexDuration_s}s`); - - return { - fetchDuration_s, - cloneDuration_s, - indexDuration_s, - } +if (!isRemotePath(args.configPath) && !existsSync(args.configPath)) { + console.error(`Config file ${args.configPath} does not exist`); + process.exit(1); } -const syncLocalRepository = async (repo: LocalRepository, ctx: AppContext, signal?: AbortSignal) => { - logger.info(`Indexing ${repo.id}...`); - const { durationMs } = await measure(() => indexLocalRepository(repo, ctx, signal)); - const indexDuration_s = durationMs / 1000; - logger.info(`Indexed ${repo.id} in ${indexDuration_s}s`); - return { - indexDuration_s, - } +const cacheDir = args.cacheDir; +const reposPath = path.join(cacheDir, 'repos'); +const indexPath = path.join(cacheDir, 'index'); + +if (!existsSync(reposPath)) { + await mkdir(reposPath, { recursive: true }); +} +if (!existsSync(indexPath)) { + await mkdir(indexPath, { recursive: true }); } -export const isRepoReindxingRequired = (previous: Repository, current: Repository) => { - - /** - * Checks if the any of the `revisions` properties have changed. - */ - const isRevisionsChanged = () => { - if (previous.vcs !== 'git' || current.vcs !== 'git') { - return false; - } - - return ( - !arraysEqualShallow(previous.branches, current.branches) || - !arraysEqualShallow(previous.tags, current.tags) - ); - } - - /** - * Check if the `exclude.paths` property has changed. - */ - const isExcludePathsChanged = () => { - if (previous.vcs !== 'local' || current.vcs !== 'local') { - return false; - } - - return !arraysEqualShallow(previous.excludedPaths, current.excludedPaths); - } - - return ( - isRevisionsChanged() || - isExcludePathsChanged() - ) +const context: AppContext = { + indexPath, + reposPath, + cachePath: cacheDir, + configPath: args.configPath, } -const syncConfig = async (configPath: string, db: Database, signal: AbortSignal, ctx: AppContext) => { - const configContent = await (async () => { - if (isRemotePath(configPath)) { - const response = await fetch(configPath, { - signal, - }); - if (!response.ok) { - throw new Error(`Failed to fetch config file ${configPath}: ${response.statusText}`); - } - return response.text(); - } else { - return readFile(configPath, { - encoding: 'utf-8', - signal, - }); - } - })(); - - // @todo: we should validate the configuration file's structure here. - const config = JSON.parse(stripJsonComments(configContent)) as SourcebotConfigurationSchema; - - // Fetch all repositories from the config file - let configRepos: Repository[] = []; - for (const repoConfig of config.repos ?? []) { - switch (repoConfig.type) { - case 'github': { - const gitHubRepos = await getGitHubReposFromConfig(repoConfig, signal, ctx); - configRepos.push(...gitHubRepos); - break; - } - case 'gitlab': { - const gitLabRepos = await getGitLabReposFromConfig(repoConfig, ctx); - configRepos.push(...gitLabRepos); - break; - } - case 'gitea': { - const giteaRepos = await getGiteaReposFromConfig(repoConfig, ctx); - configRepos.push(...giteaRepos); - break; - } - case 'local': { - const repo = getLocalRepoFromConfig(repoConfig, ctx); - configRepos.push(repo); - break; - } - } - } - - // De-duplicate on id - configRepos.sort((a, b) => { - return a.id.localeCompare(b.id); - }); - configRepos = configRepos.filter((item, index, self) => { - if (index === 0) return true; - if (item.id === self[index - 1].id) { - logger.debug(`Duplicate repository ${item.id} found in config file.`); - return false; - } - return true; - }); - - logger.info(`Discovered ${configRepos.length} unique repositories from config.`); - - // Merge the repositories into the database - for (const newRepo of configRepos) { - if (newRepo.id in db.data.repos) { - const existingRepo = db.data.repos[newRepo.id]; - const isReindexingRequired = isRepoReindxingRequired(existingRepo, newRepo); - if (isReindexingRequired) { - logger.info(`Marking ${newRepo.id} for reindexing due to configuration change.`); - } - await updateRepository(existingRepo.id, { - ...newRepo, - ...(isReindexingRequired ? { - lastIndexedDate: undefined, - }: {}) - }, db); - } else { - await createRepository(newRepo, db); - - captureEvent("repo_created", { - vcs: newRepo.vcs, - codeHost: newRepo.codeHost, - }); - } - } - - // Find repositories that are in the database, but not in the configuration file - { - const a = configRepos.map(repo => repo.id); - const b = Object.keys(db.data.repos); - const diff = b.filter(x => !a.includes(x)); - - for (const id of diff) { - await db.update(({ repos }) => { - const repo = repos[id]; - if (repo.isStale) { - return; - } - - logger.warn(`Repository ${id} is no longer listed in the configuration file or was not found. Marking as stale.`); - repo.isStale = true; - }); - } - } -} - -(async () => { - parser.add_argument("--configPath", { - help: "Path to config file", - required: true, - }); - - parser.add_argument("--cacheDir", { - help: "Path to .sourcebot cache directory", - required: true, - }); - const args = parser.parse_args() as Arguments; - - if (!isRemotePath(args.configPath) && !existsSync(args.configPath)) { - console.error(`Config file ${args.configPath} does not exist`); - process.exit(1); - } - - const cacheDir = args.cacheDir; - const reposPath = path.join(cacheDir, 'repos'); - const indexPath = path.join(cacheDir, 'index'); - - if (!existsSync(reposPath)) { - await mkdir(reposPath, { recursive: true }); - } - if (!existsSync(indexPath)) { - await mkdir(indexPath, { recursive: true }); - } - - const context: AppContext = { - indexPath, - reposPath, - cachePath: cacheDir, - configPath: args.configPath, - } - - const db = await loadDB(context); - - let abortController = new AbortController(); - let isSyncing = false; - const _syncConfig = async () => { - if (isSyncing) { - abortController.abort(); - abortController = new AbortController(); - } - - logger.info(`Syncing configuration file ${args.configPath} ...`); - isSyncing = true; - - try { - const { durationMs } = await measure(() => syncConfig(args.configPath, db, abortController.signal, context)) - logger.info(`Synced configuration file ${args.configPath} in ${durationMs / 1000}s`); - isSyncing = false; - } catch (err: any) { - if (err.name === "AbortError") { - // @note: If we're aborting, we don't want to set isSyncing to false - // since it implies another sync is in progress. - } else { - isSyncing = false; - logger.error(`Failed to sync configuration file ${args.configPath} with error:`); - console.log(err); - } - } - - const localRepos = Object.values(db.data.repos).filter(repo => repo.vcs === 'local'); - initLocalRepoFileWatchers(localRepos, async (repo, signal) => { - logger.info(`Change detected to local repository ${repo.id}. Re-syncing...`); - await syncLocalRepository(repo, context, signal); - await db.update(({ repos }) => repos[repo.id].lastIndexedDate = new Date().toUTCString()); - }); - } - - // Re-sync on file changes if the config file is local - if (!isRemotePath(args.configPath)) { - watch(args.configPath, () => { - logger.info(`Config file ${args.configPath} changed. Re-syncing...`); - _syncConfig(); - }); - } - - // Re-sync every 24 hours - setInterval(() => { - logger.info(`Re-syncing configuration file ${args.configPath}`); - _syncConfig(); - }, RESYNC_CONFIG_INTERVAL_MS); - - // Sync immediately on startup - await _syncConfig(); - - while (true) { - const repos = db.data.repos; - - for (const [_, repo] of Object.entries(repos)) { - const lastIndexed = repo.lastIndexedDate ? new Date(repo.lastIndexedDate) : new Date(0); - - if ( - repo.isStale || - lastIndexed.getTime() > Date.now() - REINDEX_INTERVAL_MS - ) { - continue; - } - - try { - let indexDuration_s: number | undefined; - let fetchDuration_s: number | undefined; - let cloneDuration_s: number | undefined; - - if (repo.vcs === 'git') { - 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') { - 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); - continue; - } - - await db.update(({ repos }) => repos[repo.id].lastIndexedDate = new Date().toUTCString()); - } - - await new Promise(resolve => setTimeout(resolve, 1000)); - - } -})(); +main(context).finally(() => { + console.log("Shutting down..."); +}); diff --git a/packages/backend/src/main.test.ts b/packages/backend/src/main.test.ts new file mode 100644 index 00000000..2fc6946c --- /dev/null +++ b/packages/backend/src/main.test.ts @@ -0,0 +1,78 @@ +import { expect, test } from 'vitest'; +import { isRepoReindxingRequired } from './main'; +import { Repository } from './types'; + +test('isRepoReindexingRequired should return false when no changes are made', () => { + const previous: Repository = { + vcs: 'git', + name: 'test', + id: 'test', + path: '', + cloneUrl: '', + isStale: false, + branches: ['main'], + tags: ['v1.0'], + }; + const current = previous; + + expect(isRepoReindxingRequired(previous, current)).toBe(false); +}) + +test('isRepoReindexingRequired should return true when git branches change', () => { + const previous: Repository = { + vcs: 'git', + name: 'test', + id: 'test', + path: '', + cloneUrl: '', + isStale: false, + branches: ['main'], + tags: ['v1.0'], + }; + + const current: Repository = { + ...previous, + branches: ['main', 'feature'] + }; + + expect(isRepoReindxingRequired(previous, current)).toBe(true); +}); + +test('isRepoReindexingRequired should return true when git tags change', () => { + const previous: Repository = { + vcs: 'git', + name: 'test', + id: 'test', + path: '', + cloneUrl: '', + isStale: false, + branches: ['main'], + tags: ['v1.0'], + }; + + const current: Repository = { + ...previous, + tags: ['v1.0', 'v2.0'] + }; + + expect(isRepoReindxingRequired(previous, current)).toBe(true); +}); + +test('isRepoReindexingRequired should return true when local excludedPaths change', () => { + const previous: Repository = { + vcs: 'local', + name: 'test', + id: 'test', + path: '/', + isStale: false, + excludedPaths: ['node_modules'], + watch: false, + }; + + const current: Repository = { + ...previous, + excludedPaths: ['node_modules', 'dist'] + }; + + expect(isRepoReindxingRequired(previous, current)).toBe(true); +}); diff --git a/packages/backend/src/main.ts b/packages/backend/src/main.ts new file mode 100644 index 00000000..53d7c37e --- /dev/null +++ b/packages/backend/src/main.ts @@ -0,0 +1,310 @@ +import { readFile } from 'fs/promises'; +import { existsSync, watch } from 'fs'; +import { SourcebotConfigurationSchema } from "./schemas/v2.js"; +import { getGitHubReposFromConfig } from "./github.js"; +import { getGitLabReposFromConfig } from "./gitlab.js"; +import { getGiteaReposFromConfig } from "./gitea.js"; +import { AppContext, LocalRepository, GitRepository, Repository } from "./types.js"; +import { cloneRepository, fetchRepository } from "./git.js"; +import { createLogger } from "./logger.js"; +import { createRepository, Database, loadDB, updateRepository } from './db.js'; +import { arraysEqualShallow, isRemotePath, measure } from "./utils.js"; +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'); + +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 ${fetchDuration_s}s`); + + } else { + logger.info(`Cloning ${repo.id}...`); + + 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 ${cloneDuration_s}s`); + } + + logger.info(`Indexing ${repo.id}...`); + const { durationMs } = await measure(() => indexGitRepository(repo, ctx)); + 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)); + const indexDuration_s = durationMs / 1000; + logger.info(`Indexed ${repo.id} in ${indexDuration_s}s`); + return { + indexDuration_s, + } +} + +export const isRepoReindxingRequired = (previous: Repository, current: Repository) => { + + /** + * Checks if the any of the `revisions` properties have changed. + */ + const isRevisionsChanged = () => { + if (previous.vcs !== 'git' || current.vcs !== 'git') { + return false; + } + + return ( + !arraysEqualShallow(previous.branches, current.branches) || + !arraysEqualShallow(previous.tags, current.tags) + ); + } + + /** + * Check if the `exclude.paths` property has changed. + */ + const isExcludePathsChanged = () => { + if (previous.vcs !== 'local' || current.vcs !== 'local') { + return false; + } + + return !arraysEqualShallow(previous.excludedPaths, current.excludedPaths); + } + + return ( + isRevisionsChanged() || + isExcludePathsChanged() + ) +} + +const syncConfig = async (configPath: string, db: Database, signal: AbortSignal, ctx: AppContext) => { + const configContent = await (async () => { + if (isRemotePath(configPath)) { + const response = await fetch(configPath, { + signal, + }); + if (!response.ok) { + throw new Error(`Failed to fetch config file ${configPath}: ${response.statusText}`); + } + return response.text(); + } else { + return readFile(configPath, { + encoding: 'utf-8', + signal, + }); + } + })(); + + // @todo: we should validate the configuration file's structure here. + const config = JSON.parse(stripJsonComments(configContent)) as SourcebotConfigurationSchema; + + // Fetch all repositories from the config file + let configRepos: Repository[] = []; + for (const repoConfig of config.repos ?? []) { + switch (repoConfig.type) { + case 'github': { + const gitHubRepos = await getGitHubReposFromConfig(repoConfig, signal, ctx); + configRepos.push(...gitHubRepos); + break; + } + case 'gitlab': { + const gitLabRepos = await getGitLabReposFromConfig(repoConfig, ctx); + configRepos.push(...gitLabRepos); + break; + } + case 'gitea': { + const giteaRepos = await getGiteaReposFromConfig(repoConfig, ctx); + configRepos.push(...giteaRepos); + break; + } + case 'local': { + const repo = getLocalRepoFromConfig(repoConfig, ctx); + configRepos.push(repo); + break; + } + } + } + + // De-duplicate on id + configRepos.sort((a, b) => { + return a.id.localeCompare(b.id); + }); + configRepos = configRepos.filter((item, index, self) => { + if (index === 0) return true; + if (item.id === self[index - 1].id) { + logger.debug(`Duplicate repository ${item.id} found in config file.`); + return false; + } + return true; + }); + + logger.info(`Discovered ${configRepos.length} unique repositories from config.`); + + // Merge the repositories into the database + for (const newRepo of configRepos) { + if (newRepo.id in db.data.repos) { + const existingRepo = db.data.repos[newRepo.id]; + const isReindexingRequired = isRepoReindxingRequired(existingRepo, newRepo); + if (isReindexingRequired) { + logger.info(`Marking ${newRepo.id} for reindexing due to configuration change.`); + } + await updateRepository(existingRepo.id, { + ...newRepo, + ...(isReindexingRequired ? { + lastIndexedDate: undefined, + }: {}) + }, db); + } else { + await createRepository(newRepo, db); + + captureEvent("repo_created", { + vcs: newRepo.vcs, + codeHost: newRepo.codeHost, + }); + } + } + + // Find repositories that are in the database, but not in the configuration file + { + const a = configRepos.map(repo => repo.id); + const b = Object.keys(db.data.repos); + const diff = b.filter(x => !a.includes(x)); + + for (const id of diff) { + await db.update(({ repos }) => { + const repo = repos[id]; + if (repo.isStale) { + return; + } + + logger.warn(`Repository ${id} is no longer listed in the configuration file or was not found. Marking as stale.`); + repo.isStale = true; + }); + } + } +} + +export const main = async (context: AppContext) => { + const db = await loadDB(context); + + let abortController = new AbortController(); + let isSyncing = false; + const _syncConfig = async () => { + if (isSyncing) { + abortController.abort(); + abortController = new AbortController(); + } + + logger.info(`Syncing configuration file ${context.configPath} ...`); + isSyncing = true; + + try { + const { durationMs } = await measure(() => syncConfig(context.configPath, db, abortController.signal, context)) + logger.info(`Synced configuration file ${context.configPath} in ${durationMs / 1000}s`); + isSyncing = false; + } catch (err: any) { + if (err.name === "AbortError") { + // @note: If we're aborting, we don't want to set isSyncing to false + // since it implies another sync is in progress. + } else { + isSyncing = false; + logger.error(`Failed to sync configuration file ${context.configPath} with error:`); + console.log(err); + } + } + + const localRepos = Object.values(db.data.repos).filter(repo => repo.vcs === 'local'); + initLocalRepoFileWatchers(localRepos, async (repo, signal) => { + logger.info(`Change detected to local repository ${repo.id}. Re-syncing...`); + await syncLocalRepository(repo, context, signal); + await db.update(({ repos }) => repos[repo.id].lastIndexedDate = new Date().toUTCString()); + }); + } + + // Re-sync on file changes if the config file is local + if (!isRemotePath(context.configPath)) { + watch(context.configPath, () => { + logger.info(`Config file ${context.configPath} changed. Re-syncing...`); + _syncConfig(); + }); + } + + // Re-sync every 24 hours + setInterval(() => { + logger.info(`Re-syncing configuration file ${context.configPath}`); + _syncConfig(); + }, RESYNC_CONFIG_INTERVAL_MS); + + // Sync immediately on startup + await _syncConfig(); + + while (true) { + const repos = db.data.repos; + + for (const [_, repo] of Object.entries(repos)) { + const lastIndexed = repo.lastIndexedDate ? new Date(repo.lastIndexedDate) : new Date(0); + + if ( + repo.isStale || + lastIndexed.getTime() > Date.now() - REINDEX_INTERVAL_MS + ) { + continue; + } + + try { + let indexDuration_s: number | undefined; + let fetchDuration_s: number | undefined; + let cloneDuration_s: number | undefined; + + if (repo.vcs === 'git') { + 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') { + 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); + continue; + } + + await db.update(({ repos }) => repos[repo.id].lastIndexedDate = new Date().toUTCString()); + } + + await new Promise(resolve => setTimeout(resolve, 1000)); + + } +} diff --git a/packages/backend/src/utils.test.ts b/packages/backend/src/utils.test.ts new file mode 100644 index 00000000..84d77f21 --- /dev/null +++ b/packages/backend/src/utils.test.ts @@ -0,0 +1,70 @@ +import { expect, test } from 'vitest'; +import { arraysEqualShallow, isRemotePath } from './utils'; + +test('should return true for identical arrays', () => { + expect(arraysEqualShallow([1, 2, 3], [1, 2, 3])).toBe(true); +}); + +test('should return true for empty arrays', () => { + expect(arraysEqualShallow([], [])).toBe(true); +}); + +test('should return true for same array reference', () => { + const arr = [1, 2, 3]; + expect(arraysEqualShallow(arr, arr)).toBe(true); +}); + +test('should return false when one array is undefined', () => { + expect(arraysEqualShallow([1, 2, 3], undefined)).toBe(false); + expect(arraysEqualShallow(undefined, [1, 2, 3])).toBe(false); +}); + +test('should return false for arrays with different lengths', () => { + expect(arraysEqualShallow([1, 2], [1, 2, 3])).toBe(false); +}); + +test('should return true for arrays with same elements in different order', () => { + expect(arraysEqualShallow([1, 2, 3], [3, 2, 1])).toBe(true); +}); + +test('should return false for arrays with different elements', () => { + expect(arraysEqualShallow([1, 2, 3], [1, 2, 4])).toBe(false); +}); + +test('should handle arrays with string elements', () => { + expect(arraysEqualShallow(['a', 'b'], ['b', 'a'])).toBe(true); + expect(arraysEqualShallow(['a', 'b'], ['a', 'c'])).toBe(false); +}); + +test('should handle arrays with duplicate elements', () => { + expect(arraysEqualShallow([1, 1, 2], [1, 2, 1])).toBe(true); + expect(arraysEqualShallow([1, 1], [1])).toBe(false); + expect(arraysEqualShallow([1, 2, 2], [1, 1, 2])).toBe(false); +}); + +test('should not mutate the array', () => { + const a = [1, 2, 3]; + const b = [3, 2, 1]; + expect(arraysEqualShallow(a, b)).toBe(true); + expect(a[0]).toBe(1); + expect(a[1]).toBe(2); + expect(a[2]).toBe(3); + expect(b[0]).toBe(3); + expect(b[1]).toBe(2); + expect(b[2]).toBe(1); +}); + +test('isRemotePath should return true for HTTP or HTTPS URLs', () => { + expect(isRemotePath('https://example.com')).toBe(true); + expect(isRemotePath('https://github.com/repo')).toBe(true); + expect(isRemotePath('http://example.com')).toBe(true); + expect(isRemotePath('http://localhost:3000')).toBe(true); +}); + +test('isRemotePath should return false for non HTTP paths', () => { + expect(isRemotePath('/usr/local/bin')).toBe(false); + expect(isRemotePath('./relative/path')).toBe(false); + expect(isRemotePath('C:\\Windows\\System32')).toBe(false); + expect(isRemotePath('')).toBe(false); + expect(isRemotePath(' ')).toBe(false); +}); diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index 4012fe81..8ac58d78 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -75,10 +75,19 @@ export const resolvePathRelativeToConfig = (localPath: string, configPath: strin return absolutePath; } -export const arraysEqualShallow = (a?: T[], b?: T[]) => { +export const arraysEqualShallow = (a?: readonly T[], b?: readonly T[]) => { if (a === b) return true; if (a === undefined || b === undefined) return false; if (a.length !== b.length) return false; - return a.every(item => b.includes(item)) && b.every(item => a.includes(item)); + const aSorted = a.toSorted(); + const bSorted = b.toSorted(); + + for (let i = 0; i < aSorted.length; i++) { + if (aSorted[i] !== bSorted[i]) { + return false; + } + } + + return true; } \ No newline at end of file diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index a0f422f5..e0686f79 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -17,6 +17,7 @@ "pretty": true, "resolveJsonModule": true, "skipLibCheck": true, + "lib": ["ES2023"], "strict": true, "sourceMap": true, "inlineSources": true diff --git a/packages/backend/vitest.config.ts b/packages/backend/vitest.config.ts new file mode 100644 index 00000000..7c052526 --- /dev/null +++ b/packages/backend/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + watch: false, + } +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 624dd441..b8b78507 100644 --- a/yarn.lock +++ b/yarn.lock @@ -247,91 +247,181 @@ dependencies: tslib "^2.4.0" +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + "@esbuild/aix-ppc64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353" integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ== +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + "@esbuild/android-arm64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018" integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw== +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + "@esbuild/android-arm@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee" integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ== +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + "@esbuild/android-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517" integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg== +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + "@esbuild/darwin-arm64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16" integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q== +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + "@esbuild/darwin-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931" integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw== +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + "@esbuild/freebsd-arm64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc" integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA== +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + "@esbuild/freebsd-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730" integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g== +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + "@esbuild/linux-arm64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383" integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g== +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + "@esbuild/linux-arm@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771" integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ== +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + "@esbuild/linux-ia32@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333" integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ== +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + "@esbuild/linux-loong64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac" integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw== +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + "@esbuild/linux-mips64el@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6" integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q== +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + "@esbuild/linux-ppc64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96" integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw== +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + "@esbuild/linux-riscv64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7" integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA== +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + "@esbuild/linux-s390x@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f" integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw== +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + "@esbuild/linux-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24" integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + "@esbuild/netbsd-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653" @@ -342,26 +432,51 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7" integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q== +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + "@esbuild/openbsd-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273" integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA== +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + "@esbuild/sunos-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403" integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA== +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + "@esbuild/win32-arm64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2" integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A== +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + "@esbuild/win32-ia32@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac" integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ== +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + "@esbuild/win32-x64@0.23.1": version "0.23.1" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699" @@ -621,7 +736,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -1263,6 +1378,96 @@ resolved "https://registry.yarnpkg.com/@replit/codemirror-vim/-/codemirror-vim-6.2.1.tgz#6673ff4be93b7da03d303ef37d6cbfa5f647b74b" integrity sha512-qDAcGSHBYU5RrdO//qCmD8K9t6vbP327iCj/iqrkVnjbrpFhrjOt92weGXGHmTNRh16cUtkUZ7Xq7rZf+8HVow== +"@rollup/rollup-android-arm-eabi@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz#3e7eda4c0c1de6d2415343002d742ff95e38dca7" + integrity sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA== + +"@rollup/rollup-android-arm64@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz#04f679231acf7284f1f8a1f7250d0e0944865ba8" + integrity sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg== + +"@rollup/rollup-darwin-arm64@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz#ecea723041621747d0772af93b54752edf26467a" + integrity sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg== + +"@rollup/rollup-darwin-x64@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz#28e6e0687092f31e20982fc104779d48c643fc21" + integrity sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA== + +"@rollup/rollup-freebsd-arm64@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz#99e9173b8aef3d1ef086983da70413988206e530" + integrity sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g== + +"@rollup/rollup-freebsd-x64@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz#f3a1ef941f8d3c6b2b036484c69a7b2d3d9ebbd7" + integrity sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw== + +"@rollup/rollup-linux-arm-gnueabihf@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz#9ba6adcc33f26f2a0c6ee658f0bbda4de8da2f75" + integrity sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA== + +"@rollup/rollup-linux-arm-musleabihf@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz#62f2426fa9016ec884f4fa779d7b62d5ba02a41a" + integrity sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ== + +"@rollup/rollup-linux-arm64-gnu@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz#f98ec111a231d35e0c6d3404e3d80f67f9d5b9f8" + integrity sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A== + +"@rollup/rollup-linux-arm64-musl@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz#4b36ffb8359f959f2c29afd187603c53368b6723" + integrity sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw== + +"@rollup/rollup-linux-powerpc64le-gnu@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz#52f4b39e6783505d168a745b79d86474fde71680" + integrity sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA== + +"@rollup/rollup-linux-riscv64-gnu@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz#49195be7e6a7d68d482b12461e2ea914e31ff977" + integrity sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA== + +"@rollup/rollup-linux-s390x-gnu@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz#4b8d50a205eac7b46cdcb9c50d4a6ae5994c02e0" + integrity sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ== + +"@rollup/rollup-linux-x64-gnu@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz#dfcceebc5ccac7fc2db19471996026258c81b55f" + integrity sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig== + +"@rollup/rollup-linux-x64-musl@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz#192f78bad8429711d63a31dc0a7d3312e2df850e" + integrity sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ== + +"@rollup/rollup-win32-arm64-msvc@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz#f4ec076579634f780b4e5896ae7f59f3e38e0c60" + integrity sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww== + +"@rollup/rollup-win32-ia32-msvc@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz#5458eab1929827e4f805cefb90bd09ecf7eeed2b" + integrity sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg== + +"@rollup/rollup-win32-x64-msvc@4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz#93415e7e707e4b156d77c5950b983b58f4bc33f3" + integrity sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg== + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" @@ -1332,6 +1537,11 @@ resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.4.tgz#403488dc1c8d0db288270d3bbf0ce5f9c45678b4" integrity sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA== +"@types/estree@1.0.6", "@types/estree@^1.0.0": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -1550,6 +1760,65 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vitest/expect@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.4.tgz#48f4f53a01092a3bdc118cff245f79ef388bdd8e" + integrity sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA== + dependencies: + "@vitest/spy" "2.1.4" + "@vitest/utils" "2.1.4" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.4.tgz#0dc07edb9114f7f080a0181fbcdb16cd4a2d855d" + integrity sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ== + dependencies: + "@vitest/spy" "2.1.4" + estree-walker "^3.0.3" + magic-string "^0.30.12" + +"@vitest/pretty-format@2.1.4", "@vitest/pretty-format@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.4.tgz#fc31993bdc1ef5a6c1a4aa6844e7ba55658a4f9f" + integrity sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.4.tgz#f9346500bdd0be1c926daaac5d683bae87ceda2c" + integrity sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA== + dependencies: + "@vitest/utils" "2.1.4" + pathe "^1.1.2" + +"@vitest/snapshot@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.4.tgz#ef8c3f605fbc23a32773256d37d3fdfd9b23d353" + integrity sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q== + dependencies: + "@vitest/pretty-format" "2.1.4" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.4.tgz#4e90f9783437c5841a27c80f8fd84d7289a6100a" + integrity sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.4.tgz#6d67ac966647a21ce8bc497472ce230de3b64537" + integrity sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg== + dependencies: + "@vitest/pretty-format" "2.1.4" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -1737,6 +2006,11 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + ast-types-flow@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" @@ -1835,6 +2109,11 @@ busboy@1.6.0: dependencies: streamsearch "^1.1.0" +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -1861,6 +2140,17 @@ caniuse-lite@^1.0.30001579: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz#99fc5ea0d9c6e96897a104a8352604378377f949" integrity sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw== +chai@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" + integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1878,6 +2168,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + chokidar@^3.5.3: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -2082,13 +2377,18 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: +debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: ms "^2.1.3" +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + deep-equal@^2.0.5: version "2.2.3" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" @@ -2381,6 +2681,35 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + esbuild@~0.23.0: version "0.23.1" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8" @@ -2632,6 +2961,13 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -2660,6 +2996,11 @@ events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +expect-type@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75" + integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3469,6 +3810,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loupe@^3.1.0, loupe@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + lowdb@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-7.0.1.tgz#7354a684547d76206b1c730b9434604235b125e5" @@ -3486,6 +3832,13 @@ lucide-react@^0.435.0: resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.435.0.tgz#88c5cc6de61b89e42cbef309a38f100deee1bb32" integrity sha512-we5GKfzjMDw9m9SsyZJvWim9qaT+Ya5kaRS+OGFqgLqXUrPM1h+7CiMw5pKdEIoaBqfXz2pyv9TASAdpIAJs0Q== +magic-string@^0.30.12: + version "0.30.12" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" + integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + map-stream@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" @@ -3831,6 +4184,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + pause-stream@0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" @@ -3843,6 +4206,11 @@ picocolors@^1.0.0, picocolors@^1.1.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch-browser@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/picomatch-browser/-/picomatch-browser-2.2.6.tgz#e0626204575eb49f019f2f2feac24fc3b53e7a8a" @@ -3940,6 +4308,15 @@ postcss@^8, postcss@^8.4.23: picocolors "^1.1.0" source-map-js "^1.2.1" +postcss@^8.4.43: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + posthog-js@^1.161.5: version "1.166.1" resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.166.1.tgz#94524d9d8b696b8e99f7f746b69c5741d334de03" @@ -4199,6 +4576,33 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rollup@^4.20.0: + version "4.25.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.25.0.tgz#74dff4b5c2777dfc490f9711393925da50171787" + integrity sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.25.0" + "@rollup/rollup-android-arm64" "4.25.0" + "@rollup/rollup-darwin-arm64" "4.25.0" + "@rollup/rollup-darwin-x64" "4.25.0" + "@rollup/rollup-freebsd-arm64" "4.25.0" + "@rollup/rollup-freebsd-x64" "4.25.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.25.0" + "@rollup/rollup-linux-arm-musleabihf" "4.25.0" + "@rollup/rollup-linux-arm64-gnu" "4.25.0" + "@rollup/rollup-linux-arm64-musl" "4.25.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.25.0" + "@rollup/rollup-linux-riscv64-gnu" "4.25.0" + "@rollup/rollup-linux-s390x-gnu" "4.25.0" + "@rollup/rollup-linux-x64-gnu" "4.25.0" + "@rollup/rollup-linux-x64-musl" "4.25.0" + "@rollup/rollup-win32-arm64-msvc" "4.25.0" + "@rollup/rollup-win32-ia32-msvc" "4.25.0" + "@rollup/rollup-win32-x64-msvc" "4.25.0" + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -4357,6 +4761,11 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" @@ -4426,6 +4835,16 @@ stack-trace@0.0.x: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.7.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" + integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== + steno@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/steno/-/steno-4.0.2.tgz#9bd9b0ffc226a1f9436f29132c8b8e7199d22c50" @@ -4703,6 +5122,31 @@ through@2, through@~2.3, through@~2.3.1: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98" + integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ== + +tinypool@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" + integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -4888,6 +5332,53 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +vite-node@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.4.tgz#97ffb6de913fd8d42253afe441f9512e9dbdfd5c" + integrity sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg== + dependencies: + cac "^6.7.14" + debug "^4.3.7" + pathe "^1.1.2" + vite "^5.0.0" + +vite@^5.0.0: + version "5.4.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" + integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.4.tgz#ba8f4589fb639cf5a9e6af54781667312b3e8230" + integrity sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ== + dependencies: + "@vitest/expect" "2.1.4" + "@vitest/mocker" "2.1.4" + "@vitest/pretty-format" "^2.1.4" + "@vitest/runner" "2.1.4" + "@vitest/snapshot" "2.1.4" + "@vitest/spy" "2.1.4" + "@vitest/utils" "2.1.4" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" + pathe "^1.1.2" + std-env "^3.7.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.4" + why-is-node-running "^2.3.0" + w3c-keyname@^2.2.4: version "2.2.8" resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" @@ -4975,6 +5466,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + winston-transport@^4.7.0: version "4.8.0" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.8.0.tgz#a15080deaeb80338455ac52c863418c74fcf38ea"