Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/opencode/bin/altimate
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,35 @@ const fs = require("fs")
const path = require("path")
const os = require("os")

// Find the nearest node_modules directory from a starting path.
// The Bun compiled binary resolves external packages (e.g. NAPI native
// modules like @altimateai/altimate-core) via NODE_PATH because its virtual
// filesystem (/$bunfs/root/) has no node_modules.
function findNodeModules(startDir) {
let current = startDir
for (;;) {
const modules = path.join(current, "node_modules")
if (fs.existsSync(modules)) return modules
const parent = path.dirname(current)
if (parent === current) return undefined
current = parent
}
}

function run(target) {
// 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already fixed in commit c7e41b4fs.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.

const modules = findNodeModules(targetDir)
if (modules) {
const sep = process.platform === "win32" ? ";" : ":"
env.NODE_PATH = env.NODE_PATH ? modules + sep + env.NODE_PATH : modules
}

const result = childProcess.spawnSync(target, process.argv.slice(2), {
stdio: "inherit",
env,
})
if (result.error) {
console.error(result.error.message)
Expand Down
26 changes: 26 additions & 0 deletions packages/opencode/bin/altimate-code
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,35 @@ const fs = require("fs")
const path = require("path")
const os = require("os")

// Find the nearest node_modules directory from a starting path.
// The Bun compiled binary resolves external packages (e.g. NAPI native
// modules like @altimateai/altimate-core) via NODE_PATH because its virtual
// filesystem (/$bunfs/root/) has no node_modules.
function findNodeModules(startDir) {
let current = startDir
for (;;) {
const modules = path.join(current, "node_modules")
if (fs.existsSync(modules)) return modules
const parent = path.dirname(current)
if (parent === current) return undefined
current = parent
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

function run(target) {
// 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)))
const modules = findNodeModules(targetDir)
if (modules) {
const sep = process.platform === "win32" ? ";" : ":"
env.NODE_PATH = env.NODE_PATH ? modules + sep + env.NODE_PATH : modules
}

const result = childProcess.spawnSync(target, process.argv.slice(2), {
stdio: "inherit",
env,
})
if (result.error) {
console.error(result.error.message)
Expand Down
25 changes: 17 additions & 8 deletions packages/opencode/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,26 @@ for (const item of targets) {
tsconfig: "./tsconfig.json",
plugins: [solidPlugin],
sourcemap: "external",
// Packages excluded from the compiled binary — loaded lazily at runtime.
// NOTE: @altimateai/altimate-core is intentionally NOT external — it's a
// napi binary that must be bundled for the CLI to work out of the box.
// Packages excluded from the compiled binary — resolved from node_modules
// 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.
//
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

// IMPORTANT: Without code splitting, Bun inlines dynamic import() targets
// into the main chunk. Any external require() in those targets will fail
// at startup — not when the import() is called. Only mark packages as
// external when they truly cannot be bundled (e.g. NAPI native addons).
external: [
// 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.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

// The JS loader dynamically require()s platform-specific .node binaries
// (e.g. @altimateai/altimate-core-darwin-arm64).
// Must be installed as a dependency of the published wrapper package.
"@altimateai/altimate-core",
// Database drivers — native addons, users install on demand per warehouse
"pg", "snowflake-sdk", "@google-cloud/bigquery", "@databricks/sql",
"mysql2", "mssql", "oracledb", "duckdb", "better-sqlite3",
// Optional infra packages
"keytar", "ssh2", "dockerode", "yaml",
// Optional infra packages — native addons or heavy optional deps
"keytar", "ssh2", "dockerode",
],
compile: {
autoloadBunfig: false,
Expand Down
9 changes: 9 additions & 0 deletions packages/opencode/script/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { fileURLToPath } from "url"
const dir = fileURLToPath(new URL("..", import.meta.url))
process.chdir(dir)

// NAPI native modules that must be installed alongside the CLI binary.
// 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"],
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

}

const binaries: Record<string, string> = {}
for (const filepath of new Bun.Glob("**/package.json").scanSync({ cwd: "./dist" })) {
const pkg = await Bun.file(`./dist/${filepath}`).json()
Expand Down Expand Up @@ -34,6 +41,7 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
},
version: version,
license: pkg.license,
dependencies: runtimeDependencies,
optionalDependencies: binaries,
},
null,
Expand Down Expand Up @@ -81,6 +89,7 @@ try {
},
version: version,
license: pkg.license,
dependencies: runtimeDependencies,
optionalDependencies: binaries,
},
null,
Expand Down
Loading