From 08539c680a9f6f9b5f39f991a85686ad35f2dd7c Mon Sep 17 00:00:00 2001 From: PatrickSys Date: Wed, 25 Mar 2026 23:10:40 +0100 Subject: [PATCH 1/4] feat: add server config file support for pre-registering projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reads ~/.codebase-context/config.json on startup to pre-register project roots and per-project exclude pattern overrides without requiring a connected MCP client. Config roots are additive — they survive syncKnownRoots() refreshes from client root changes. - src/server/config.ts: new module to load/parse config file - src/project-state.ts: add extraExcludePatterns field - src/index.ts: wire applyServerConfig() in main() and startHttp(), preserve configRoots in syncKnownRoots(), merge extraExcludePatterns in performIndexingOnce() - tests/server-config.test.ts: 11 unit tests covering all edge paths --- src/index.ts | 71 +++++++++++++--- src/project-state.ts | 2 + src/server/config.ts | 91 +++++++++++++++++++++ tests/server-config.test.ts | 159 ++++++++++++++++++++++++++++++++++++ 4 files changed, 311 insertions(+), 12 deletions(-) create mode 100644 src/server/config.ts create mode 100644 tests/server-config.test.ts diff --git a/src/index.ts b/src/index.ts index e3dd141..7604fff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { createServer } from './server/factory.js'; import { startHttpServer } from './server/http.js'; +import { loadServerConfig } from './server/config.js'; import { CallToolRequestSchema, ListToolsRequestSchema, @@ -46,6 +47,7 @@ import { getProjectPathFromContextResourceUri, isContextResourceUri } from './resources/uri.js'; +import { EXCLUDED_GLOB_PATTERNS } from './constants/codebase-context.js'; import { discoverProjectsWithinRoot, findNearestProjectBoundary, @@ -102,6 +104,8 @@ function resolveRootPath(): string | undefined { const primaryRootPath = resolveRootPath(); const toolNames = new Set(TOOLS.map((tool) => tool.name)); const knownRoots = new Map(); +/** Roots loaded from config file — preserved across syncKnownRoots() refreshes. */ +const configRoots = new Map(); const discoveredProjectPaths = new Map(); let clientRootsEnabled = false; const projectSourcesByKey = new Map(); @@ -337,6 +341,13 @@ function syncKnownRoots(rootEntries: Array<{ rootPath: string; label?: string }> }); } + // Always include config-registered roots — config is additive (REPO-03) + for (const [rootKey, rootEntry] of configRoots.entries()) { + if (!nextRoots.has(rootKey)) { + nextRoots.set(rootKey, rootEntry); + } + } + for (const [rootKey, existingRoot] of knownRoots.entries()) { if (!nextRoots.has(rootKey)) { removeProject(existingRoot.rootPath); @@ -1240,6 +1251,9 @@ async function performIndexingOnce( let lastLoggedProgress = { phase: '', percentage: -1 }; const indexer = new CodebaseIndexer({ rootPath: project.rootPath, + ...(project.extraExcludePatterns?.length + ? { config: { exclude: [...EXCLUDED_GLOB_PATTERNS, ...project.extraExcludePatterns] } } + : {}), incrementalOnly, onProgress: (progress) => { // Only log when phase or percentage actually changes (prevents duplicate logs) @@ -1587,7 +1601,33 @@ async function initProject( } } +async function applyServerConfig( + serverConfig: Awaited> +): Promise { + for (const proj of serverConfig?.projects ?? []) { + try { + const stats = await fs.stat(proj.root); + if (!stats.isDirectory()) { + console.error(`[config] Skipping non-directory project root: ${proj.root}`); + continue; + } + const rootKey = normalizeRootKey(proj.root); + configRoots.set(rootKey, { rootPath: proj.root }); + registerKnownRoot(proj.root); + if (proj.excludePatterns?.length) { + const project = getOrCreateProject(proj.root); + project.extraExcludePatterns = proj.excludePatterns; + } + } catch { + console.error(`[config] Skipping inaccessible project root: ${proj.root}`); + } + } +} + async function main() { + const serverConfig = await loadServerConfig(); + await applyServerConfig(serverConfig); + if (primaryRootPath) { // Validate bootstrap root path exists and is a directory when explicitly configured. try { @@ -1711,7 +1751,18 @@ export { performIndexing }; * Each connecting MCP client gets its own Server+Transport pair, * sharing the same module-level project state. */ -async function startHttp(port: number): Promise { +async function startHttp(explicitPort?: number): Promise { + const serverConfig = await loadServerConfig(); + await applyServerConfig(serverConfig); + + // Port resolution priority: CLI flag > env var > config file > built-in default (3100) + const portFromEnv = process.env.CODEBASE_CONTEXT_PORT + ? Number.parseInt(process.env.CODEBASE_CONTEXT_PORT, 10) + : undefined; + const resolvedEnvPort = portFromEnv && Number.isFinite(portFromEnv) ? portFromEnv : undefined; + const port = explicitPort ?? resolvedEnvPort ?? serverConfig?.server?.port ?? 3100; + const host = serverConfig?.server?.host ?? '127.0.0.1'; + // Validate bootstrap root the same way main() does if (primaryRootPath) { try { @@ -1730,6 +1781,7 @@ async function startHttp(port: number): Promise { name: 'codebase-context', version: PKG_VERSION, port, + host, registerHandlers, onSessionReady: (sessionServer) => { // Per-session roots change handler @@ -1803,20 +1855,15 @@ if (isDirectRun) { const httpFlag = process.argv.includes('--http') || process.env.CODEBASE_CONTEXT_HTTP === '1'; if (httpFlag) { + // Extract only the CLI flag value. Env var, config, and default + // are resolved inside startHttp() in priority order: flag > env > config > 3100. const portFlagIdx = process.argv.indexOf('--port'); const portFromFlag = portFlagIdx !== -1 ? Number.parseInt(process.argv[portFlagIdx + 1], 10) : undefined; - const portFromEnv = process.env.CODEBASE_CONTEXT_PORT - ? Number.parseInt(process.env.CODEBASE_CONTEXT_PORT, 10) - : undefined; - const port = - portFromFlag && Number.isFinite(portFromFlag) - ? portFromFlag - : portFromEnv && Number.isFinite(portFromEnv) - ? portFromEnv - : 3100; - - startHttp(port).catch((error) => { + const explicitPort = + portFromFlag && Number.isFinite(portFromFlag) ? portFromFlag : undefined; + + startHttp(explicitPort).catch((error) => { console.error('Fatal:', error); process.exit(1); }); diff --git a/src/project-state.ts b/src/project-state.ts index 6b260dc..02f070c 100644 --- a/src/project-state.ts +++ b/src/project-state.ts @@ -17,6 +17,8 @@ export interface ProjectState { autoRefresh: AutoRefreshController; initPromise?: Promise; stopWatcher?: () => void; + /** Extra glob exclusion patterns from config file — merged with EXCLUDED_GLOB_PATTERNS at index time. */ + extraExcludePatterns?: string[]; } export function makePaths(rootPath: string): ToolPaths { diff --git a/src/server/config.ts b/src/server/config.ts new file mode 100644 index 0000000..7c3a40f --- /dev/null +++ b/src/server/config.ts @@ -0,0 +1,91 @@ +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import path from 'node:path'; + +export interface ProjectConfig { + root: string; + excludePatterns?: string[]; +} + +export interface ServerConfig { + projects?: ProjectConfig[]; + server?: { port?: number; host?: string }; +} + +function expandTilde(filePath: string): string { + if (filePath === '~' || filePath.startsWith('~/') || filePath.startsWith('~\\')) { + return path.join(os.homedir(), filePath.slice(1)); + } + return filePath; +} + +export async function loadServerConfig(): Promise { + const configPath = + process.env.CODEBASE_CONTEXT_CONFIG_PATH ?? + path.join(os.homedir(), '.codebase-context', 'config.json'); + + let raw: string; + try { + raw = await fs.readFile(configPath, 'utf8'); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + return null; + } + console.error(`[config] Failed to load config: ${(err as Error).message}`); + return null; + } + + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (err) { + console.error(`[config] Failed to load config: ${(err as Error).message}`); + return null; + } + + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + return null; + } + + const config = parsed as Record; + const result: ServerConfig = {}; + + // Resolve projects + if (Array.isArray(config.projects)) { + result.projects = (config.projects as unknown[]) + .filter((p): p is Record => typeof p === 'object' && p !== null) + .map((p) => { + const rawRoot = typeof p.root === 'string' ? p.root : ''; + const resolvedRoot = path.resolve(expandTilde(rawRoot)); + const proj: ProjectConfig = { root: resolvedRoot }; + if (Array.isArray(p.excludePatterns)) { + proj.excludePatterns = p.excludePatterns.filter( + (pattern): pattern is string => typeof pattern === 'string' + ); + } + return proj; + }); + } + + // Resolve server options + if (typeof config.server === 'object' && config.server !== null) { + const srv = config.server as Record; + result.server = {}; + + if (typeof srv.host === 'string') { + result.server.host = srv.host; + } + + if (srv.port !== undefined) { + const portValue = srv.port; + const portNum = typeof portValue === 'number' ? portValue : Number(portValue); + if (Number.isInteger(portNum) && portNum > 0) { + result.server.port = portNum; + } else { + console.error(`[config] Ignoring invalid server.port: ${portValue}`); + } + } + } + + return result; +} diff --git a/tests/server-config.test.ts b/tests/server-config.test.ts new file mode 100644 index 0000000..76e75ce --- /dev/null +++ b/tests/server-config.test.ts @@ -0,0 +1,159 @@ +import { describe, it, expect, afterEach, vi } from 'vitest'; +import { promises as fs } from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { loadServerConfig } from '../src/server/config.js'; + +// Helper: write a temp config file and set CODEBASE_CONTEXT_CONFIG_PATH +async function withTempConfig(content: string, fn: (filePath: string) => Promise) { + const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ccc-config-test-')); + const filePath = path.join(tmpDir, 'config.json'); + await fs.writeFile(filePath, content, 'utf8'); + try { + await fn(filePath); + } finally { + await fs.rm(tmpDir, { recursive: true, force: true }); + } +} + +describe('loadServerConfig', () => { + afterEach(() => { + delete process.env.CODEBASE_CONTEXT_CONFIG_PATH; + vi.restoreAllMocks(); + }); + + it('returns null silently when config file does not exist (ENOENT)', async () => { + const errorSpy = vi.spyOn(console, 'error'); + process.env.CODEBASE_CONTEXT_CONFIG_PATH = '/tmp/nonexistent-ccc-config-99999.json'; + const result = await loadServerConfig(); + expect(result).toBeNull(); + expect(errorSpy).not.toHaveBeenCalled(); + }); + + it('returns null and logs to stderr on malformed JSON', async () => { + const errorSpy = vi.spyOn(console, 'error'); + await withTempConfig('{ invalid json }', async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).toBeNull(); + expect(errorSpy).toHaveBeenCalledOnce(); + expect(errorSpy.mock.calls[0][0]).toMatch(/\[config\] Failed to load config:/); + }); + }); + + it('returns null when top-level value is an array', async () => { + await withTempConfig('[]', async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).toBeNull(); + }); + }); + + it('returns null when top-level value is a string', async () => { + await withTempConfig('"just a string"', async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).toBeNull(); + }); + }); + + it('resolves ~/my-repo to an absolute path using os.homedir()', async () => { + const config = JSON.stringify({ + projects: [{ root: '~/my-repo' }] + }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).not.toBeNull(); + expect(result!.projects).toHaveLength(1); + const resolved = result!.projects![0].root; + expect(path.isAbsolute(resolved)).toBe(true); + expect(resolved).toBe(path.join(os.homedir(), 'my-repo')); + }); + }); + + it('resolves a relative path via path.resolve()', async () => { + const config = JSON.stringify({ + projects: [{ root: 'relative/path' }] + }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).not.toBeNull(); + const resolved = result!.projects![0].root; + expect(path.isAbsolute(resolved)).toBe(true); + expect(resolved).toBe(path.resolve('relative/path')); + }); + }); + + it('returns valid config for well-formed input with projects and server.port', async () => { + // Use absolute paths that are valid on all platforms + const projA = path.join(os.tmpdir(), 'ccc-test-proj-a'); + const projB = path.join(os.tmpdir(), 'ccc-test-proj-b'); + const config = JSON.stringify({ + projects: [ + { root: projA, excludePatterns: ['**/dist/**'] }, + { root: projB } + ], + server: { port: 5199, host: '0.0.0.0' } + }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).not.toBeNull(); + expect(result!.projects).toHaveLength(2); + expect(result!.projects![0].root).toBe(path.resolve(projA)); + expect(result!.projects![0].excludePatterns).toEqual(['**/dist/**']); + expect(result!.projects![1].root).toBe(path.resolve(projB)); + expect(result!.server?.port).toBe(5199); + expect(result!.server?.host).toBe('0.0.0.0'); + }); + }); + + it('drops server.port with a warning when value is 0', async () => { + const errorSpy = vi.spyOn(console, 'error'); + const config = JSON.stringify({ server: { port: 0 } }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).not.toBeNull(); + expect(result!.server?.port).toBeUndefined(); + expect(errorSpy).toHaveBeenCalledOnce(); + expect(errorSpy.mock.calls[0][0]).toMatch(/\[config\] Ignoring invalid server\.port: 0/); + }); + }); + + it('drops server.port with a warning when value is negative', async () => { + const errorSpy = vi.spyOn(console, 'error'); + const config = JSON.stringify({ server: { port: -1 } }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result!.server?.port).toBeUndefined(); + expect(errorSpy).toHaveBeenCalledOnce(); + expect(errorSpy.mock.calls[0][0]).toMatch(/\[config\] Ignoring invalid server\.port: -1/); + }); + }); + + it('drops server.port with a warning when value is a non-numeric string', async () => { + const errorSpy = vi.spyOn(console, 'error'); + const config = JSON.stringify({ server: { port: 'abc' } }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result!.server?.port).toBeUndefined(); + expect(errorSpy).toHaveBeenCalledOnce(); + expect(errorSpy.mock.calls[0][0]).toMatch(/\[config\] Ignoring invalid server\.port: abc/); + }); + }); + + it('respects CODEBASE_CONTEXT_CONFIG_PATH env var', async () => { + const config = JSON.stringify({ server: { port: 4242 } }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result).not.toBeNull(); + expect(result!.server?.port).toBe(4242); + }); + }); +}); From a844e537276ea22c3409b2f540fe585718bec650 Mon Sep 17 00:00:00 2001 From: PatrickSys Date: Wed, 25 Mar 2026 23:11:07 +0100 Subject: [PATCH 2/4] style: apply prettier formatting --- src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7604fff..aa8ddb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1860,8 +1860,7 @@ if (isDirectRun) { const portFlagIdx = process.argv.indexOf('--port'); const portFromFlag = portFlagIdx !== -1 ? Number.parseInt(process.argv[portFlagIdx + 1], 10) : undefined; - const explicitPort = - portFromFlag && Number.isFinite(portFromFlag) ? portFromFlag : undefined; + const explicitPort = portFromFlag && Number.isFinite(portFromFlag) ? portFromFlag : undefined; startHttp(explicitPort).catch((error) => { console.error('Fatal:', error); From 912e6f6af672048f2ac145c7574bac140e503a05 Mon Sep 17 00:00:00 2001 From: PatrickSys Date: Thu, 26 Mar 2026 08:11:10 +0100 Subject: [PATCH 3/4] fix(config): reject empty roots and invalid ports --- src/server/config.ts | 11 ++++++++--- tests/server-config.test.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/server/config.ts b/src/server/config.ts index 7c3a40f..3e2d83b 100644 --- a/src/server/config.ts +++ b/src/server/config.ts @@ -55,7 +55,11 @@ export async function loadServerConfig(): Promise { result.projects = (config.projects as unknown[]) .filter((p): p is Record => typeof p === 'object' && p !== null) .map((p) => { - const rawRoot = typeof p.root === 'string' ? p.root : ''; + const rawRoot = typeof p.root === 'string' ? p.root.trim() : ''; + if (!rawRoot) { + console.error('[config] Skipping project entry with missing or empty root'); + return null; + } const resolvedRoot = path.resolve(expandTilde(rawRoot)); const proj: ProjectConfig = { root: resolvedRoot }; if (Array.isArray(p.excludePatterns)) { @@ -64,7 +68,8 @@ export async function loadServerConfig(): Promise { ); } return proj; - }); + }) + .filter((project): project is ProjectConfig => project !== null); } // Resolve server options @@ -79,7 +84,7 @@ export async function loadServerConfig(): Promise { if (srv.port !== undefined) { const portValue = srv.port; const portNum = typeof portValue === 'number' ? portValue : Number(portValue); - if (Number.isInteger(portNum) && portNum > 0) { + if (Number.isInteger(portNum) && portNum > 0 && portNum <= 65535) { result.server.port = portNum; } else { console.error(`[config] Ignoring invalid server.port: ${portValue}`); diff --git a/tests/server-config.test.ts b/tests/server-config.test.ts index 76e75ce..fc54534 100644 --- a/tests/server-config.test.ts +++ b/tests/server-config.test.ts @@ -86,6 +86,28 @@ describe('loadServerConfig', () => { }); }); + it('skips project entries with missing or empty roots instead of resolving cwd', async () => { + const errorSpy = vi.spyOn(console, 'error'); + const config = JSON.stringify({ + projects: [{}, { root: ' ' }, { root: 'valid-root' }] + }); + + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + + expect(result).not.toBeNull(); + expect(result!.projects).toEqual([{ root: path.resolve('valid-root') }]); + expect(errorSpy).toHaveBeenCalledTimes(2); + expect(errorSpy.mock.calls[0][0]).toMatch( + /\[config\] Skipping project entry with missing or empty root/ + ); + expect(errorSpy.mock.calls[1][0]).toMatch( + /\[config\] Skipping project entry with missing or empty root/ + ); + }); + }); + it('returns valid config for well-formed input with projects and server.port', async () => { // Use absolute paths that are valid on all platforms const projA = path.join(os.tmpdir(), 'ccc-test-proj-a'); @@ -147,6 +169,18 @@ describe('loadServerConfig', () => { }); }); + it('drops server.port with a warning when value exceeds 65535', async () => { + const errorSpy = vi.spyOn(console, 'error'); + const config = JSON.stringify({ server: { port: 65536 } }); + await withTempConfig(config, async (filePath) => { + process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath; + const result = await loadServerConfig(); + expect(result!.server?.port).toBeUndefined(); + expect(errorSpy).toHaveBeenCalledOnce(); + expect(errorSpy.mock.calls[0][0]).toMatch(/\[config\] Ignoring invalid server\.port: 65536/); + }); + }); + it('respects CODEBASE_CONTEXT_CONFIG_PATH env var', async () => { const config = JSON.stringify({ server: { port: 4242 } }); await withTempConfig(config, async (filePath) => { From c637bf58d1614bd7c2e0089e188f5869b17ce1dd Mon Sep 17 00:00:00 2001 From: PatrickSys Date: Thu, 26 Mar 2026 08:24:14 +0100 Subject: [PATCH 4/4] fix(deps): patch picomatch audit path --- package.json | 3 +++ pnpm-lock.yaml | 35 +++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index b82603e..787a580 100644 --- a/package.json +++ b/package.json @@ -167,6 +167,9 @@ "@modelcontextprotocol/sdk>@hono/node-server": "1.19.11", "@modelcontextprotocol/sdk>express-rate-limit": "8.2.2", "@huggingface/transformers>onnxruntime-node": "1.24.2", + "micromatch>picomatch": "2.3.2", + "anymatch>picomatch": "2.3.2", + "readdirp>picomatch": "2.3.2", "minimatch": "10.2.3", "rollup": "4.59.0", "hono@<4.12.7": ">=4.12.7" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a87837..55025da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,9 @@ overrides: '@modelcontextprotocol/sdk>@hono/node-server': 1.19.11 '@modelcontextprotocol/sdk>express-rate-limit': 8.2.2 '@huggingface/transformers>onnxruntime-node': 1.24.2 + micromatch>picomatch: 2.3.2 + anymatch>picomatch: 2.3.2 + readdirp>picomatch: 2.3.2 minimatch: 10.2.3 rollup: 4.59.0 hono@<4.12.7: '>=4.12.7' @@ -1942,12 +1945,12 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pkce-challenge@5.0.1: @@ -3165,7 +3168,7 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 apache-arrow@15.0.2: dependencies: @@ -3744,9 +3747,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.4 file-entry-cache@8.0.0: dependencies: @@ -4166,7 +4169,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mime-db@1.52.0: {} @@ -4332,9 +4335,9 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - picomatch@4.0.3: {} + picomatch@4.0.4: {} pkce-challenge@5.0.1: {} @@ -4391,7 +4394,7 @@ snapshots: readdirp@3.6.0: dependencies: - picomatch: 2.3.1 + picomatch: 2.3.2 reflect.getprototypeof@1.0.10: dependencies: @@ -4711,8 +4714,8 @@ snapshots: tinyglobby@0.2.15: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tinyrainbow@3.0.3: {} @@ -4836,8 +4839,8 @@ snapshots: vite@7.3.0(@types/node@20.19.25)(tsx@4.21.0): dependencies: esbuild: 0.27.2 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 postcss: 8.5.6 rollup: 4.59.0 tinyglobby: 0.2.15 @@ -4860,7 +4863,7 @@ snapshots: magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 - picomatch: 4.0.3 + picomatch: 4.0.4 std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 1.0.2