fix(windows): .exe glob in .gitignore + .exe extension resolution in find-browse (#1554)

bun build --compile on Windows appends .exe to the output filename,
producing browse.exe instead of browse. find-browse's existsSync probe
only checked the bare path and returned null on Windows even when the
binary was correctly built. .gitignore similarly only excluded the
bare bin/gstack-global-discover path, leaving the .exe variant
tracked.

This commit:
- .gitignore: changes `bin/gstack-global-discover` →
  `bin/gstack-global-discover*` so the Windows .exe variant is ignored
- browse/src/find-browse.ts: adds isExecutable + findExecutable helpers
  that fall back to .exe/.cmd/.bat probing on Windows, mirroring the
  same helper already in make-pdf/src/browseClient.ts and pdftotext.ts

Contributed by @Mike-E-Log via #1554.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-05-18 21:00:33 -07:00
parent 8a24e896f1
commit c4516f642c
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 35 additions and 4 deletions

2
.gitignore vendored
View File

@ -4,7 +4,7 @@ dist/
browse/dist/
design/dist/
make-pdf/dist/
bin/gstack-global-discover
bin/gstack-global-discover*
.gstack/
.claude/skills/
.claude/scheduled_tasks.lock

View File

@ -5,7 +5,7 @@
* Outputs the absolute path to the browse binary on stdout, or exits 1 if not found.
*/
import { existsSync } from 'fs';
import { accessSync, constants } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
@ -24,6 +24,35 @@ function getGitRoot(): string | null {
}
}
// Probe a path for executability. accessSync(X_OK) checks the executable
// bit on Linux/macOS and degrades to an existence check on Windows (no
// true execute bit). Mirrors make-pdf/src/browseClient.ts:159 /
// make-pdf/src/pdftotext.ts:117.
function isExecutable(p: string): boolean {
try {
accessSync(p, constants.X_OK);
return true;
} catch {
return false;
}
}
// Resolve a bare binary path to the actual file on disk. On Windows, `bun
// build --compile` appends `.exe` to the output filename, so `browse` on
// disk is actually `browse.exe`. After a bare-path probe, try the Windows
// extensions. Linux/macOS behavior is unchanged. Mirrors the helper in
// make-pdf/src/browseClient.ts:89 and make-pdf/src/pdftotext.ts:52.
function findExecutable(base: string): string | null {
if (isExecutable(base)) return base;
if (process.platform === 'win32') {
for (const ext of ['.exe', '.cmd', '.bat']) {
const withExt = base + ext;
if (isExecutable(withExt)) return withExt;
}
}
return null;
}
export function locateBinary(): string | null {
const root = getGitRoot();
const home = homedir();
@ -33,14 +62,16 @@ export function locateBinary(): string | null {
if (root) {
for (const m of markers) {
const local = join(root, m, 'skills', 'gstack', 'browse', 'dist', 'browse');
if (existsSync(local)) return local;
const found = findExecutable(local);
if (found) return found;
}
}
// Global fallback
for (const m of markers) {
const global = join(home, m, 'skills', 'gstack', 'browse', 'dist', 'browse');
if (existsSync(global)) return global;
const found = findExecutable(global);
if (found) return found;
}
return null;