fix: resolve Cannot find module @altimateai/altimate-core on npm install#259
fix: resolve Cannot find module @altimateai/altimate-core on npm install#259anandgupta42 merged 2 commits intomainfrom
Conversation
…install Three issues prevented `npm install -g altimate-code` from working: 1. `@altimateai/altimate-core` (NAPI-RS native module) cannot be embedded in Bun single-file executables — mark it as external and add it as a dependency in the published wrapper packages so npm installs it. 2. `@altimateai/dbt-integration` and `yaml` were external but without code splitting, Bun inlines dynamic `import()` targets into the main chunk — external `require()` fails at startup. Remove them from externals so they get bundled. 3. Bun compiled binaries resolve externals from `/$bunfs/root/` (virtual FS), not the physical binary location. The bin wrapper now sets `NODE_PATH` to the nearest `node_modules` so the binary can find external packages. Closes #258 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
anandgupta42
left a comment
There was a problem hiding this comment.
Pre-review self-commentary on key design decisions. See inline comments below.
| current = parent | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Why NODE_PATH?
Bun compiled binaries resolve external require() calls from /$bunfs/root/ — a virtual filesystem with no node_modules. Standard Node resolution (walking up parent dirs from the binary's physical location) doesn't apply.
NODE_PATH is the only mechanism that works across all Bun versions to inject additional module search paths into a compiled binary. Tested and confirmed working with Bun 1.3.10.
Alternative considered: shipping altimate-core alongside the binary in the platform package — rejected because it would require cross-compiling the NAPI addon per-platform in our CI, which the altimate-core repo already handles via its own release pipeline.
| // at runtime. Bun compiled binaries resolve externals via standard Node | ||
| // resolution from the binary's location, walking up to the wrapper | ||
| // package's node_modules. | ||
| // |
There was a problem hiding this comment.
Key insight: external ≠ lazy without code splitting.
Bun's --compile without splitting: true inlines all modules (including dynamic import() targets) into a single chunk. Any external package referenced transitively from a dynamic import will emit a top-level require() that runs at startup — before the try/catch around the import() can catch it.
This is why @altimateai/dbt-integration and yaml were crashing the binary at startup even though they were only referenced inside lazy import() calls wrapped in try/catch.
Rule of thumb for this build config: Only mark packages as external if they truly cannot be bundled (native addons like NAPI .node files or database drivers).
| // dbt integration — heavy transitive deps, loaded on first dbt operation | ||
| "@altimateai/dbt-integration", | ||
| // Database drivers — users install on demand per warehouse | ||
| // NAPI native module — cannot be embedded in Bun single-file executable. |
There was a problem hiding this comment.
Why altimate-core must be external (not bundled):
@altimateai/altimate-core is a NAPI-RS package. Its index.js is a dynamic loader that does conditional require() to find platform-specific .node binaries (e.g. @altimateai/altimate-core-darwin-arm64). Bun's docs confirm you can embed .node files with direct require('./addon.node'), but the NAPI-RS auto-generated loader uses dynamic platform detection that Bun's bundler can't statically analyze.
So the JS loader gets inlined but the native binary doesn't → runtime crash. Making it external + installing via npm deps is the correct approach.
packages/opencode/script/publish.ts
Outdated
| // These cannot be embedded in Bun's single-file executable — the JS loader | ||
| // dynamically require()s platform-specific .node binaries at runtime. | ||
| const runtimeDependencies: Record<string, string> = { | ||
| "@altimateai/altimate-core": pkg.dependencies["@altimateai/altimate-core"], |
There was a problem hiding this comment.
Version pinning: This reads the version range from packages/opencode/package.json ("^0.2.3"), ensuring the published wrapper always matches what was tested at build time. If altimate-core gets a new release, it's automatically picked up within the semver range on next npm install.
Using dependencies (not optionalDependencies) because altimate-core is mandatory — the CLI cannot function without it (all 34 native SQL analysis methods depend on it).
packages/opencode/bin/altimate
Outdated
| // Resolve NODE_PATH so the compiled Bun binary can find external packages | ||
| // installed alongside the wrapper (e.g. @altimateai/altimate-core NAPI module). | ||
| const env = { ...process.env } | ||
| const targetDir = path.dirname(path.dirname(fs.realpathSync(target))) |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Already fixed in commit c7e41b4 — fs.realpathSync(target) is now wrapped in a try-catch block. If the path doesn't exist, we fall through gracefully and let spawnSync report the error via result.error.
…ath NODE_PATH, validation Fixes from 6-model consensus code review: 1. Wrap `fs.realpathSync(target)` in try-catch — previously would crash with a stack trace if `ALTIMATE_CODE_BIN_PATH` pointed to a missing file. Now falls back gracefully to `spawnSync` error handling. (Flagged by 4/6 models) 2. Collect ALL `node_modules` paths walking upward (not just first found) and search from BOTH the binary dir AND the wrapper script dir. This handles pnpm strict layouts, hoisted monorepos, and npm flat installs. (Suggested by Gemini 3.1 Pro) 3. Move `scriptDir` initialization before `run()` to avoid ReferenceError when `ALTIMATE_CODE_BIN_PATH` is set (calls `run()` before `scriptDir` was previously defined). (Flagged by Gemini 3.1 Pro) 4. Add fail-fast validation for `@altimateai/altimate-core` dependency in `publish.ts` — prevents silent `undefined` if accidentally removed from `package.json`. (Flagged by Kimi K2.5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review findings addressed in c7e41b4A 6-model consensus code review (Claude, GPT 5.2 Codex, Gemini 3.1 Pro, Kimi K2.5, MiniMax M2.5, GLM-5) identified 4 actionable issues. All have been fixed:
Note: The inline pre-review comments from the first commit (35230ec) reference the old code and are now superseded by the fixes above. The Sentry bot finding ( |
What does this PR do?
Fixes
npm install -g altimate-codeproducingCannot find module '@altimateai/altimate-core'(and a hidden@altimateai/dbt-integrationerror behind it).Three root causes:
@altimateai/altimate-coreis a NAPI-RS native module — Bun's bundler inlines the JS loader but cannot embed.nodenative binaries. The published wrapper package didn't declare it as a dependency, so it was never installed.@altimateai/dbt-integrationandyamlwere marked as external, but without code splitting Bun inlines dynamicimport()targets into the main chunk — externalrequire()fails at startup before any try/catch can catch it.Bun compiled binaries resolve externals from
/$bunfs/root/(virtual FS), not the physical binary location — standardnode_modulesresolution doesn't work.Fixes:
bin/altimate,bin/altimate-code: SetNODE_PATHto the nearestnode_modulesbefore spawning the binaryscript/build.ts: Add@altimateai/altimate-coreto externals (NAPI can't be embedded); removedbt-integrationandyamlfrom externals (bundle them — external packages crash at startup without code splitting)script/publish.ts: Add@altimateai/altimate-coreas adependenciesentry in both scoped and unscoped wrapper packagesType of change
Issue for this PR
Closes #258
How did you verify your code works?
bun run script/build.ts --single/tmp/test-altimate-e2e2/with altimate-core installed as a dependencynode bin/altimate-code --versionprints version successfullynode bin/altimate-code --helpshows full CLI helpNODE_PATHworks: tested binary withNODE_PATHpointing tonode_modules— altimate-core resolves correctlyChecklist
🤖 Generated with Claude Code