Skip to content

Commit a9a976f

Browse files
committed
fix(dsn): prevent silent exit during uncached DSN auto-detection (#411)
Replace Bun.Glob.scan() async iterators with readdir() in DSN detection paths. The async iterators don't ref-count the event loop, causing the process to exit with code 0 and no output while scanning is still in progress on first run (uncached). Changes: - bin.ts: Add setInterval keepalive cleared via .finally() to prevent premature event loop drain (can't use top-level await due to CJS bundle) - project-root.ts: Replace anyGlobMatches() Bun.Glob.scan() with readdir() + synchronous Bun.Glob.match() for language marker detection - env-file.ts: Replace Bun.Glob.scan('*') with readdir() in detectFromMonorepoEnvFiles() for listing monorepo package directories
1 parent 585b88a commit a9a976f

File tree

2 files changed

+32
-25
lines changed

2 files changed

+32
-25
lines changed

src/lib/dsn/env-file.ts

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* to find DSNs in individual packages/apps.
99
*/
1010

11-
import { stat } from "node:fs/promises";
11+
import { opendir } from "node:fs/promises";
1212
import { join } from "node:path";
1313
import { createDetectedDsn } from "./parser.js";
1414
import { scanSpecificFiles } from "./scanner.js";
@@ -168,30 +168,25 @@ export async function detectFromMonorepoEnvFiles(
168168
): Promise<EnvFileScanResult> {
169169
const dsns: DetectedDsn[] = [];
170170
const sourceMtimes: Record<string, number> = {};
171-
const pkgGlob = new Bun.Glob("*");
172171

173172
for (const monorepoRoot of MONOREPO_ROOTS) {
174173
const rootDir = join(cwd, monorepoRoot);
175174

175+
// Bun's opendir() may not throw on a missing directory — the error
176+
// surfaces when iterating. Wrap the full open+iterate in one try/catch.
177+
// No explicit handle.close() needed: for-await-of auto-closes the Dir
178+
// handle when the loop exits (including early return or break).
176179
try {
177-
// Scan for subdirectories (each is a potential package/app)
178-
for await (const pkgName of pkgGlob.scan({
179-
cwd: rootDir,
180-
onlyFiles: false,
181-
})) {
182-
const pkgDir = join(rootDir, pkgName);
183-
184-
// Only process directories, not files
185-
try {
186-
const stats = await stat(pkgDir);
187-
if (!stats.isDirectory()) {
188-
continue;
189-
}
190-
} catch {
180+
for await (const entry of await opendir(rootDir)) {
181+
// Skip hidden dirs (.git, .cache) and non-directories. Dirent
182+
// already carries the file type from the readdir syscall — no
183+
// extra stat() needed for the common case.
184+
if (entry.name.startsWith(".") || !entry.isDirectory()) {
191185
continue;
192186
}
193187

194-
const packagePath = `${monorepoRoot}/${pkgName}`;
188+
const pkgDir = join(rootDir, entry.name);
189+
const packagePath = `${monorepoRoot}/${entry.name}`;
195190

196191
const result = await detectDsnInPackage(pkgDir, packagePath);
197192
if (result.dsn) {

src/lib/dsn/project-root.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* Stops at: home directory or filesystem root
1414
*/
1515

16-
import { stat } from "node:fs/promises";
16+
import { opendir, stat } from "node:fs/promises";
1717
import { homedir } from "node:os";
1818
import { dirname, join, resolve } from "node:path";
1919
import { anyTrue } from "../promises.js";
@@ -187,23 +187,35 @@ function anyExists(dir: string, names: readonly string[]): Promise<boolean> {
187187

188188
/**
189189
* Check if any files matching glob patterns exist in a directory.
190-
* Runs pattern checks in parallel and resolves as soon as any finds a match.
190+
* Uses `opendir` to lazily stream directory entries and exits on first match
191+
* without reading the entire directory. Matches via synchronous
192+
* `Bun.Glob.match()` (no async I/O, event-loop safe).
191193
*
192194
* @param dir - Directory to check
193195
* @param patterns - Glob patterns to match
194196
* @returns True if any matching file exists
195197
*/
196-
function anyGlobMatches(
198+
async function anyGlobMatches(
197199
dir: string,
198200
patterns: readonly string[]
199201
): Promise<boolean> {
200-
return anyTrue(patterns, async (pattern) => {
201-
const glob = new Bun.Glob(pattern);
202-
for await (const _match of glob.scan({ cwd: dir, onlyFiles: true })) {
203-
return true;
202+
// Bun's opendir() may not throw on a missing directory — the error
203+
// surfaces when iterating. Wrap the full open+iterate in one try/catch.
204+
// No explicit handle.close() needed: for-await-of auto-closes the Dir
205+
// handle when the loop exits (including early return or break).
206+
try {
207+
for await (const entry of await opendir(dir)) {
208+
if (
209+
entry.isFile() &&
210+
patterns.some((p) => new Bun.Glob(p).match(entry.name))
211+
) {
212+
return true;
213+
}
204214
}
205215
return false;
206-
});
216+
} catch {
217+
return false;
218+
}
207219
}
208220

209221
/**

0 commit comments

Comments
 (0)