…ing prebuilds
Resolves #43. On Linux/Node combinations where one ast-grep grammar
package's prebuilt parser binary is missing for the host architecture
(reporter: Ubuntu 24.04 + WSL2 + Node 24, missing the cpp prebuild),
the existing loader silently failed to register every dynamic grammar,
not just the broken one.
Mechanism: registerDynamicLanguage iterates the modules it receives
and accesses the lazy libraryPath getter on each. If that getter
throws (because the prebuild lookup fails), the entire batch call
aborts atomically — none of the 13 dynamic grammars get registered.
The empty inner `catch {}` in ensureDynamicLanguages discarded the
underlying reason, and the failure surfaced only at WARN as a generic
"Failed to register dynamic ast-grep languages", with no indication
of which grammar was the culprit.
Empirically verified against @ast-grep/napi@0.40.5 in a clean Node
environment:
- Sequential register({A}); register({B}) calls REPLACE rather than
accumulate; only the last call's grammar survives. So the obvious
alternative of "register one at a time" is actively broken — it
would silently leave only the last grammar in langPackages
registered (php, since it's last in our list). On a polyglot
codebase that's a regression.
- Batch register({A, B, C}) with one entry whose libraryPath getter
throws aborts atomically — none of A/B/C end up registered.
The fix is to pre-validate each grammar's libraryPath getter inside
our own per-grammar try/catch, exclude any that throw, then make ONE
batch registerDynamicLanguage call with only the survivors:
- src/services/code-graph.ts: ensureDynamicLanguages now touches
mod.libraryPath inside the inner try/catch so a missing-prebuild
failure is contained to that one grammar. Adds module-level
loadedDynamicLanguages and failedDynamicLanguages state plus a
public getDynamicLanguageStatus() introspection API. The empty
`catch {}` becomes `catch (err) { ... }` capturing the actual
reason. Per-grammar failures and the (now-rare) catch-all log at
warn level with the underlying message.
- src/tools/graph-tools.ts: codebase_graph_status now appends an
"AST grammars: Loaded / Failed" block listing both sets, with the
failure reason for each unloaded grammar. Users see the loader
state without needing to enable debug logging.
- src/services/graph-symbols.ts: extractSymbolsAndCalls bumps its
failure log from DEBUG to WARN with one-shot dedupe per language —
one warn per language for the lifetime of the process, not one
warn per file (potentially hundreds).
- src/services/graph-imports.ts: extractImports gets the same
one-shot dedupe per language treatment for consistency.
- tests/unit/dynamic-language-loader.test.ts: nine new tests covering
the sync contract, idempotence, loaded/failed sort order, disjoint
sets, and a sanity check that at least one expected grammar
registers in the test environment.
The loader stays sync. createRequire stays. No call-site signature
changes, no test fixture rewrites. Existing 721 tests pass; with the
new file, 730 tests pass. typecheck and biome clean. CodeRabbit
review of the diff returned no findings.
Co-authored-by: X-Adam <X-Adam@users.noreply.github.com>