-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(core): bypass globby in stdin mode to avoid hang on macOS #931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -173,48 +173,127 @@ export const searchFiles = async ( | |
| } | ||
|
|
||
| // Start with configured include patterns | ||
| let includePatterns = config.include.map((pattern) => escapeGlobPattern(pattern)); | ||
| const includePatterns = config.include.map((pattern) => escapeGlobPattern(pattern)); | ||
|
|
||
| // In stdin mode (explicitFiles provided), bypass globby for explicit files to avoid hang | ||
| // when .gitignore is both an ignore rules source and a match target | ||
| let filePaths: string[] = []; | ||
|
|
||
| // If explicit files are provided, add them to include patterns | ||
| if (explicitFiles) { | ||
| const relativePaths = explicitFiles.map((filePath) => { | ||
| const relativePath = path.relative(rootDir, filePath); | ||
| // Escape the path to handle special characters | ||
| return escapeGlobPattern(relativePath); | ||
| }); | ||
| includePatterns = [...includePatterns, ...relativePaths]; | ||
| } | ||
| logger.debug('Stdin mode: processing explicit files separately from globby'); | ||
|
|
||
| // In stdin mode, we need to manually read .gitignore files since we're not using globby's ignoreFiles | ||
| const allIgnorePatterns = [...adjustedIgnorePatterns]; | ||
|
|
||
| if (config.ignore.useGitignore) { | ||
| // Read .gitignore from root directory | ||
| const gitignorePath = path.join(rootDir, '.gitignore'); | ||
| try { | ||
| const gitignoreContent = await fs.readFile(gitignorePath, 'utf8'); | ||
| const gitignorePatterns = parseIgnoreContent(gitignoreContent); | ||
| allIgnorePatterns.push(...gitignorePatterns); | ||
| logger.trace('Loaded .gitignore patterns:', gitignorePatterns); | ||
| } catch (error) { | ||
| // .gitignore might not exist, which is fine | ||
| logger.trace( | ||
| 'No .gitignore found or could not read:', | ||
| error instanceof Error ? error.message : String(error), | ||
| ); | ||
| } | ||
|
|
||
| // If no include patterns at all, default to all files | ||
| if (includePatterns.length === 0) { | ||
| includePatterns = ['**/*']; | ||
| } | ||
| // Read .repomixignore from root directory | ||
| const repomixignorePath = path.join(rootDir, '.repomixignore'); | ||
| try { | ||
| const repomixignoreContent = await fs.readFile(repomixignorePath, 'utf8'); | ||
| const repomixignorePatterns = parseIgnoreContent(repomixignoreContent); | ||
| allIgnorePatterns.push(...repomixignorePatterns); | ||
| logger.trace('Loaded .repomixignore patterns:', repomixignorePatterns); | ||
| } catch (error) { | ||
| // .repomixignore might not exist, which is fine | ||
| logger.trace( | ||
| 'No .repomixignore found or could not read:', | ||
| error instanceof Error ? error.message : String(error), | ||
|
Comment on lines
+188
to
+215
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for reading and parsing if (config.ignore.useGitignore) {
const readIgnoreFile = async (fileName: string) => {
try {
const content = await fs.readFile(path.join(rootDir, fileName), 'utf8');
const patterns = parseIgnoreContent(content);
allIgnorePatterns.push(...patterns);
logger.trace(`Loaded ${fileName} patterns:`, patterns);
} catch (error) {
// Ignore file might not exist, which is fine
logger.trace(
`No ${fileName} found or could not read:`,
error instanceof Error ? error.message : String(error),
);
}
};
await readIgnoreFile('.gitignore');
await readIgnoreFile('.repomixignore');
} |
||
| ); | ||
| } | ||
| } | ||
|
Comment on lines
+188
to
+218
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Always load In stdin mode we now skip - if (config.ignore.useGitignore) {
- // Read .gitignore from root directory
- const gitignorePath = path.join(rootDir, '.gitignore');
- try {
- const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
- const gitignorePatterns = parseIgnoreContent(gitignoreContent);
- allIgnorePatterns.push(...gitignorePatterns);
- logger.trace('Loaded .gitignore patterns:', gitignorePatterns);
- } catch (error) {
- // .gitignore might not exist, which is fine
- logger.trace(
- 'No .gitignore found or could not read:',
- error instanceof Error ? error.message : String(error),
- );
- }
-
- // Read .repomixignore from root directory
- const repomixignorePath = path.join(rootDir, '.repomixignore');
- try {
- const repomixignoreContent = await fs.readFile(repomixignorePath, 'utf8');
- const repomixignorePatterns = parseIgnoreContent(repomixignoreContent);
- allIgnorePatterns.push(...repomixignorePatterns);
- logger.trace('Loaded .repomixignore patterns:', repomixignorePatterns);
- } catch (error) {
- // .repomixignore might not exist, which is fine
- logger.trace(
- 'No .repomixignore found or could not read:',
- error instanceof Error ? error.message : String(error),
- );
- }
- }
+ const loadIgnoreFile = async (fileName: string, enabled: boolean) => {
+ if (!enabled) return;
+ const filePath = path.join(rootDir, fileName);
+ try {
+ const content = await fs.readFile(filePath, 'utf8');
+ const patterns = parseIgnoreContent(content).map(normalizeGlobPattern);
+ allIgnorePatterns.push(...patterns);
+ logger.trace(`Loaded ${fileName} patterns:`, patterns);
+ } catch (error) {
+ logger.trace(
+ `No ${fileName} found or could not read:`,
+ error instanceof Error ? error.message : String(error),
+ );
+ }
+ };
+
+ await loadIgnoreFile('.gitignore', config.ignore.useGitignore);
+ await loadIgnoreFile('.repomixignore', true);
🤖 Prompt for AI Agents |
||
|
|
||
| logger.trace('Include patterns with explicit files:', includePatterns); | ||
|
|
||
| const filePaths = await globby(includePatterns, { | ||
| cwd: rootDir, | ||
| ignore: [...adjustedIgnorePatterns], | ||
| ignoreFiles: [...ignoreFilePatterns], | ||
| onlyFiles: true, | ||
| absolute: false, | ||
| dot: true, | ||
| followSymbolicLinks: false, | ||
| }).catch((error: unknown) => { | ||
| // Handle EPERM errors specifically | ||
| const code = (error as NodeJS.ErrnoException | { code?: string })?.code; | ||
| if (code === 'EPERM' || code === 'EACCES') { | ||
| throw new PermissionError( | ||
| `Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`, | ||
| rootDir, | ||
| // 1. Run globby only for includePatterns (if any) | ||
| const globbyPatterns = includePatterns.length > 0 ? includePatterns : []; | ||
| const globbyResults = | ||
| globbyPatterns.length > 0 | ||
| ? await globby(globbyPatterns, { | ||
| cwd: rootDir, | ||
| ignore: [...adjustedIgnorePatterns], | ||
| ignoreFiles: [...ignoreFilePatterns], | ||
| onlyFiles: true, | ||
| absolute: false, | ||
| dot: true, | ||
| followSymbolicLinks: false, | ||
| }).catch((error: unknown) => { | ||
| const code = (error as NodeJS.ErrnoException | { code?: string })?.code; | ||
| if (code === 'EPERM' || code === 'EACCES') { | ||
| throw new PermissionError( | ||
| `Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`, | ||
| rootDir, | ||
| ); | ||
| } | ||
| throw error; | ||
| }) | ||
| : []; | ||
|
|
||
| logger.trace('Globby results for includePatterns:', globbyResults); | ||
|
|
||
| // 2. Convert explicit files to relative paths | ||
| const relativePaths = explicitFiles.map((filePath) => path.relative(rootDir, filePath)); | ||
|
|
||
| logger.trace('Explicit files (relative):', relativePaths); | ||
|
|
||
| // 3. Filter explicit files using ignore patterns (manually with minimatch) | ||
| const filteredExplicitFiles = relativePaths.filter((filePath) => { | ||
| // Check if file matches any ignore pattern | ||
| const shouldIgnore = allIgnorePatterns.some( | ||
| (pattern) => minimatch(filePath, pattern) || minimatch(`${filePath}/`, pattern), | ||
| ); | ||
| } | ||
| throw error; | ||
| }); | ||
| return !shouldIgnore; | ||
| }); | ||
|
|
||
| logger.trace('Filtered explicit files:', filteredExplicitFiles); | ||
|
|
||
| // 4. Merge globby results and filtered explicit files, removing duplicates | ||
| filePaths = [...new Set([...globbyResults, ...filteredExplicitFiles])]; | ||
|
|
||
| logger.trace('Merged file paths:', filePaths); | ||
| } else { | ||
| // Normal mode: use globby with all patterns | ||
| const patterns = includePatterns.length > 0 ? includePatterns : ['**/*']; | ||
|
Comment on lines
+220
to
+268
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normalize explicit paths to POSIX separators before merging On Windows, - const globbyResults =
- globbyPatterns.length > 0
- ? await globby(globbyPatterns, {
+ const toPosix = (value: string) => value.replace(/\\/g, '/');
+ const globbyResults =
+ globbyPatterns.length > 0
+ ? (
+ await globby(globbyPatterns, {
cwd: rootDir,
ignore: [...adjustedIgnorePatterns],
ignoreFiles: [...ignoreFilePatterns],
onlyFiles: true,
absolute: false,
dot: true,
followSymbolicLinks: false,
}).catch((error: unknown) => {
const code = (error as NodeJS.ErrnoException | { code?: string })?.code;
if (code === 'EPERM' || code === 'EACCES') {
throw new PermissionError(
`Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`,
rootDir,
);
}
throw error;
})
- : [];
+ ).map(toPosix)
+ : [];
...
- const relativePaths = explicitFiles.map((filePath) => path.relative(rootDir, filePath));
+ const relativePaths = explicitFiles.map((filePath) => toPosix(path.relative(rootDir, filePath)));🤖 Prompt for AI Agents |
||
|
|
||
| logger.trace('Include patterns:', patterns); | ||
|
|
||
| filePaths = await globby(patterns, { | ||
| cwd: rootDir, | ||
| ignore: [...adjustedIgnorePatterns], | ||
| ignoreFiles: [...ignoreFilePatterns], | ||
| onlyFiles: true, | ||
| absolute: false, | ||
| dot: true, | ||
| followSymbolicLinks: false, | ||
| }).catch((error: unknown) => { | ||
| const code = (error as NodeJS.ErrnoException | { code?: string })?.code; | ||
| if (code === 'EPERM' || code === 'EACCES') { | ||
| throw new PermissionError( | ||
| `Permission denied while scanning directory. Please check folder access permissions for your terminal app. path: ${rootDir}`, | ||
| rootDir, | ||
| ); | ||
|
Comment on lines
+269
to
+286
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| } | ||
| throw error; | ||
| }); | ||
| } | ||
|
|
||
| let emptyDirPaths: string[] = []; | ||
| if (config.output.includeEmptyDirectories) { | ||
| const directories = await globby(includePatterns, { | ||
| if (config.output.includeEmptyDirectories && !explicitFiles) { | ||
| // Note: empty directory detection is only supported in normal mode, not in stdin mode | ||
| const patterns = includePatterns.length > 0 ? includePatterns : ['**/*']; | ||
| const directories = await globby(patterns, { | ||
| cwd: rootDir, | ||
| ignore: [...adjustedIgnorePatterns], | ||
| ignoreFiles: [...ignoreFilePatterns], | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normalize patterns loaded from ignore files
Lines like
build/ortmp/are extremely common in.gitignore, but when we feed them straight intominimatchthey no longer behave like git (they won't excludebuild/index.js). We already normalize config-derived patterns; we need to apply the samenormalizeGlobPatternto patterns pulled from.gitignore/.repomixignorebefore pushing them intoallIgnorePatterns, otherwise stdin-mode files slip through those ignores.🤖 Prompt for AI Agents