| ... | @@ -14,17 +14,8 @@ const WORKSPACE_CACHE_TTL_MS = 15_000; | ... | @@ -14,17 +14,8 @@ const WORKSPACE_CACHE_TTL_MS = 15_000; |
| 14 | const WORKSPACE_CACHE_MAX_KEYS = 4; | 14 | const WORKSPACE_CACHE_MAX_KEYS = 4; |
| 15 | const WORKSPACE_INDEX_MAX_ENTRIES = 25_000; | 15 | const WORKSPACE_INDEX_MAX_ENTRIES = 25_000; |
| 16 | const WORKSPACE_SCAN_READDIR_CONCURRENCY = 32; | 16 | const WORKSPACE_SCAN_READDIR_CONCURRENCY = 32; |
| 17 | const IGNORED_DIRECTORY_NAMES = new Set([ | 17 | const GIT_CHECK_IGNORE_MAX_STDIN_BYTES = 256 * 1024; |
| 18 | ".git", | 18 | const IGNORED_DIRECTORY_NAMES = new Set([".git", "node_modules"]); |
| 19 | ".convex", | | |
| 20 | "node_modules", | | |
| 21 | ".next", | | |
| 22 | ".turbo", | | |
| 23 | "dist", | | |
| 24 | "build", | | |
| 25 | "out", | | |
| 26 | ".cache", | | |
| 27 | ]); | | |
| 28 | | 19 | |
| 29 | interface WorkspaceIndex { | 20 | interface WorkspaceIndex { |
| 30 | scannedAt: number; | 21 | scannedAt: number; |
| ... | @@ -56,7 +47,10 @@ function basenameOf(input: string): string { | ... | @@ -56,7 +47,10 @@ function basenameOf(input: string): string { |
| 56 | } | 47 | } |
| 57 | | 48 | |
| 58 | function normalizeQuery(input: string): string { | 49 | function normalizeQuery(input: string): string { |
| 59 | return input.trim().replace(/^[@./]+/, "").toLowerCase(); | 50 | return input |
| | 51 | .trim() |
| | 52 | .replace(/^[@./]+/, "") |
| | 53 | .toLowerCase(); |
| 60 | } | 54 | } |
| 61 | | 55 | |
| 62 | function scoreEntry(entry: ProjectEntry, query: string): number { | 56 | function scoreEntry(entry: ProjectEntry, query: string): number { |
| ... | @@ -145,26 +139,67 @@ async function filterGitIgnoredPaths(cwd: string, relativePaths: string[]): Prom | ... | @@ -145,26 +139,67 @@ async function filterGitIgnoredPaths(cwd: string, relativePaths: string[]): Prom |
| 145 | return relativePaths; | 139 | return relativePaths; |
| 146 | } | 140 | } |
| 147 | | 141 | |
| 148 | const checkIgnore = await runProcess("git", ["check-ignore", "--no-index", "-z", "--stdin"], { | 142 | const ignoredPaths = new Set<string>(); |
| 149 | cwd, | 143 | let chunk: string[] = []; |
| 150 | allowNonZeroExit: true, | 144 | let chunkBytes = 0; |
| 151 | timeoutMs: 20_000, | 145 | |
| 152 | maxBufferBytes: 16 * 1024 * 1024, | 146 | const flushChunk = async (): Promise<boolean> => { |
| 153 | outputMode: "truncate", | 147 | if (chunk.length === 0) { |
| 154 | stdin: `${relativePaths.join("\0")}\0`, | 148 | return true; |
| 155 | }).catch(() => null); | 149 | } |
| 156 | if (!checkIgnore) { | 150 | |
| 157 | return relativePaths; | 151 | const checkIgnore = await runProcess("git", ["check-ignore", "--no-index", "-z", "--stdin"], { |
| | 152 | cwd, |
| | 153 | allowNonZeroExit: true, |
| | 154 | timeoutMs: 20_000, |
| | 155 | maxBufferBytes: 16 * 1024 * 1024, |
| | 156 | outputMode: "truncate", |
| | 157 | stdin: `${chunk.join("\0")}\0`, |
| | 158 | }).catch(() => null); |
| | 159 | chunk = []; |
| | 160 | chunkBytes = 0; |
| | 161 | |
| | 162 | if (!checkIgnore) { |
| | 163 | return false; |
| | 164 | } |
| | 165 | |
| | 166 | // git-check-ignore exits with 1 when no paths match. |
| | 167 | if (checkIgnore.code !== 0 && checkIgnore.code !== 1) { |
| | 168 | return false; |
| | 169 | } |
| | 170 | |
| | 171 | const matchedIgnoredPaths = splitNullSeparatedPaths( |
| | 172 | checkIgnore.stdout, |
| | 173 | Boolean(checkIgnore.stdoutTruncated), |
| | 174 | ); |
| | 175 | for (const ignoredPath of matchedIgnoredPaths) { |
| | 176 | ignoredPaths.add(ignoredPath); |
| | 177 | } |
| | 178 | return true; |
| | 179 | }; |
| | 180 | |
| | 181 | for (const relativePath of relativePaths) { |
| | 182 | const relativePathBytes = Buffer.byteLength(relativePath) + 1; |
| | 183 | if ( |
| | 184 | chunk.length > 0 && |
| | 185 | chunkBytes + relativePathBytes > GIT_CHECK_IGNORE_MAX_STDIN_BYTES && |
| | 186 | !(await flushChunk()) |
| | 187 | ) { |
| | 188 | return relativePaths; |
| | 189 | } |
| | 190 | |
| | 191 | chunk.push(relativePath); |
| | 192 | chunkBytes += relativePathBytes; |
| | 193 | |
| | 194 | if (chunkBytes >= GIT_CHECK_IGNORE_MAX_STDIN_BYTES && !(await flushChunk())) { |
| | 195 | return relativePaths; |
| | 196 | } |
| 158 | } | 197 | } |
| 159 | | 198 | |
| 160 | // git-check-ignore exits with 1 when no paths match. | 199 | if (!(await flushChunk())) { |
| 161 | if (checkIgnore.code !== 0 && checkIgnore.code !== 1) { | | |
| 162 | return relativePaths; | 200 | return relativePaths; |
| 163 | } | 201 | } |
| 164 | | 202 | |
| 165 | const ignoredPaths = new Set( | | |
| 166 | splitNullSeparatedPaths(checkIgnore.stdout, Boolean(checkIgnore.stdoutTruncated)), | | |
| 167 | ); | | |
| 168 | if (ignoredPaths.size === 0) { | 203 | if (ignoredPaths.size === 0) { |
| 169 | return relativePaths; | 204 | return relativePaths; |
| 170 | } | 205 | } |
| ... | @@ -210,14 +245,14 @@ async function buildWorkspaceIndexFromGit(cwd: string): Promise<WorkspaceIndex | | ... | @@ -210,14 +245,14 @@ async function buildWorkspaceIndexFromGit(cwd: string): Promise<WorkspaceIndex | |
| 210 | } | 245 | } |
| 211 | | 246 | |
| 212 | const directoryEntries: ProjectEntry[] = [...directorySet] | 247 | const directoryEntries: ProjectEntry[] = [...directorySet] |
| 213 | .sort((left, right) => left.localeCompare(right)) | 248 | .toSorted((left, right) => left.localeCompare(right)) |
| 214 | .map((directoryPath) => ({ | 249 | .map((directoryPath) => ({ |
| 215 | path: directoryPath, | 250 | path: directoryPath, |
| 216 | kind: "directory", | 251 | kind: "directory", |
| 217 | parentPath: parentPathOf(directoryPath), | 252 | parentPath: parentPathOf(directoryPath), |
| 218 | })); | 253 | })); |
| 219 | const fileEntries: ProjectEntry[] = [...new Set(filePaths)] | 254 | const fileEntries: ProjectEntry[] = [...new Set(filePaths)] |
| 220 | .sort((left, right) => left.localeCompare(right)) | 255 | .toSorted((left, right) => left.localeCompare(right)) |
| 221 | .map((filePath) => ({ | 256 | .map((filePath) => ({ |
| 222 | path: filePath, | 257 | path: filePath, |
| 223 | kind: "file", | 258 | kind: "file", |
| ... | @@ -228,8 +263,7 @@ async function buildWorkspaceIndexFromGit(cwd: string): Promise<WorkspaceIndex | | ... | @@ -228,8 +263,7 @@ async function buildWorkspaceIndexFromGit(cwd: string): Promise<WorkspaceIndex | |
| 228 | return { | 263 | return { |
| 229 | scannedAt: Date.now(), | 264 | scannedAt: Date.now(), |
| 230 | entries: entries.slice(0, WORKSPACE_INDEX_MAX_ENTRIES), | 265 | entries: entries.slice(0, WORKSPACE_INDEX_MAX_ENTRIES), |
| 231 | truncated: | 266 | truncated: Boolean(listedFiles.stdoutTruncated) || entries.length > WORKSPACE_INDEX_MAX_ENTRIES, |
| 232 | Boolean(listedFiles.stdoutTruncated) || entries.length > WORKSPACE_INDEX_MAX_ENTRIES, | | |
| 233 | }; | 267 | }; |
| 234 | } | 268 | } |
| 235 | | 269 | |