| ... | ... | @@ -14,17 +14,8 @@ const WORKSPACE_CACHE_TTL_MS = 15_000; |
| 14 | 14 | const WORKSPACE_CACHE_MAX_KEYS = 4; |
| 15 | 15 | const WORKSPACE_INDEX_MAX_ENTRIES = 25_000; |
| 16 | 16 | const WORKSPACE_SCAN_READDIR_CONCURRENCY = 32; |
| 17 | | const IGNORED_DIRECTORY_NAMES = new Set([ |
| 18 | | ".git", |
| 19 | | ".convex", |
| 20 | | "node_modules", |
| 21 | | ".next", |
| 22 | | ".turbo", |
| 23 | | "dist", |
| 24 | | "build", |
| 25 | | "out", |
| 26 | | ".cache", |
| 27 | | ]); |
| 17 | const GIT_CHECK_IGNORE_MAX_STDIN_BYTES = 256 * 1024; |
| 18 | const IGNORED_DIRECTORY_NAMES = new Set([".git", "node_modules"]); |
| 28 | 19 | |
| 29 | 20 | interface WorkspaceIndex { |
| 30 | 21 | scannedAt: number; |
| ... | ... | @@ -56,7 +47,10 @@ function basenameOf(input: string): string { |
| 56 | 47 | } |
| 57 | 48 | |
| 58 | 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 | 56 | function scoreEntry(entry: ProjectEntry, query: string): number { |
| ... | ... | @@ -145,26 +139,67 @@ async function filterGitIgnoredPaths(cwd: string, relativePaths: string[]): Prom |
| 145 | 139 | return relativePaths; |
| 146 | 140 | } |
| 147 | 141 | |
| 148 | | const checkIgnore = await runProcess("git", ["check-ignore", "--no-index", "-z", "--stdin"], { |
| 149 | | cwd, |
| 150 | | allowNonZeroExit: true, |
| 151 | | timeoutMs: 20_000, |
| 152 | | maxBufferBytes: 16 * 1024 * 1024, |
| 153 | | outputMode: "truncate", |
| 154 | | stdin: `${relativePaths.join("\0")}\0`, |
| 155 | | }).catch(() => null); |
| 156 | | if (!checkIgnore) { |
| 157 | | return relativePaths; |
| 142 | const ignoredPaths = new Set<string>(); |
| 143 | let chunk: string[] = []; |
| 144 | let chunkBytes = 0; |
| 145 | |
| 146 | const flushChunk = async (): Promise<boolean> => { |
| 147 | if (chunk.length === 0) { |
| 148 | return true; |
| 149 | } |
| 150 | |
| 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. |
| 161 | | if (checkIgnore.code !== 0 && checkIgnore.code !== 1) { |
| 199 | if (!(await flushChunk())) { |
| 162 | 200 | return relativePaths; |
| 163 | 201 | } |
| 164 | 202 | |
| 165 | | const ignoredPaths = new Set( |
| 166 | | splitNullSeparatedPaths(checkIgnore.stdout, Boolean(checkIgnore.stdoutTruncated)), |
| 167 | | ); |
| 168 | 203 | if (ignoredPaths.size === 0) { |
| 169 | 204 | return relativePaths; |
| 170 | 205 | } |
| ... | ... | @@ -210,14 +245,14 @@ async function buildWorkspaceIndexFromGit(cwd: string): Promise<WorkspaceIndex | |
| 210 | 245 | } |
| 211 | 246 | |
| 212 | 247 | const directoryEntries: ProjectEntry[] = [...directorySet] |
| 213 | | .sort((left, right) => left.localeCompare(right)) |
| 248 | .toSorted((left, right) => left.localeCompare(right)) |
| 214 | 249 | .map((directoryPath) => ({ |
| 215 | 250 | path: directoryPath, |
| 216 | 251 | kind: "directory", |
| 217 | 252 | parentPath: parentPathOf(directoryPath), |
| 218 | 253 | })); |
| 219 | 254 | const fileEntries: ProjectEntry[] = [...new Set(filePaths)] |
| 220 | | .sort((left, right) => left.localeCompare(right)) |
| 255 | .toSorted((left, right) => left.localeCompare(right)) |
| 221 | 256 | .map((filePath) => ({ |
| 222 | 257 | path: filePath, |
| 223 | 258 | kind: "file", |
| ... | ... | @@ -228,8 +263,7 @@ async function buildWorkspaceIndexFromGit(cwd: string): Promise<WorkspaceIndex | |
| 228 | 263 | return { |
| 229 | 264 | scannedAt: Date.now(), |
| 230 | 265 | entries: entries.slice(0, WORKSPACE_INDEX_MAX_ENTRIES), |
| 231 | | truncated: |
| 232 | | Boolean(listedFiles.stdoutTruncated) || entries.length > WORKSPACE_INDEX_MAX_ENTRIES, |
| 266 | truncated: Boolean(listedFiles.stdoutTruncated) || entries.length > WORKSPACE_INDEX_MAX_ENTRIES, |
| 233 | 267 | }; |
| 234 | 268 | } |
| 235 | 269 | |