Skip to content

Build envio CLI as NAPI native module instead of binary#1011

Closed
DZakh wants to merge 11 commits intomainfrom
claude/refactor-cli-to-core-GtECj
Closed

Build envio CLI as NAPI native module instead of binary#1011
DZakh wants to merge 11 commits intomainfrom
claude/refactor-cli-to-core-GtECj

Conversation

@DZakh
Copy link
Member

@DZakh DZakh commented Mar 4, 2026

Summary

This PR refactors the envio CLI build system to compile as a NAPI native module (envio.node) instead of a standalone binary. This enables the CLI to be invoked from JavaScript/Node.js while maintaining the same Rust implementation.

Key Changes

  • New NAPI binding (packages/cli/src/napi.rs): Exposes a run(argv: Vec<String>) function that executes the CLI with given arguments, wrapping clap parsing and tokio runtime initialization.

  • JavaScript wrapper (packages/envio/core.js): Loads the platform-specific native module with fallback logic:

    • First tries to load from installed platform package (envio-{os}-{arch})
    • Falls back to local development build (./envio.node)
    • Provides clear error messages for unsupported platforms
  • CLI entry point (packages/envio/bin/envio.js): Executable script that imports and calls the native run() function, allowing pnpm envio to work as before.

  • Build configuration updates:

    • Cargo.toml: Added napi feature flag and cdylib crate type for native module compilation
    • Build commands changed from cargo build --bins to cargo build --lib --features napi
    • Platform packages now contain envio.node instead of bin/envio
  • CI/CD updates:

    • GitHub Actions workflows updated to build native modules instead of binaries
    • Platform package structure simplified (no bin/ directory needed)
    • Test scenarios now use pnpm envio instead of direct binary paths
  • Package configuration:

    • packages/envio/package.json: Added bin field pointing to bin/envio.js
    • Platform packages list envio.node as main entry point
    • Updated file inclusion lists to include new JS/TS wrapper files

Implementation Details

  • The NAPI module uses tokio::runtime::Builder to execute async CLI commands from synchronous JavaScript context
  • Error handling converts Rust errors to NAPI errors with descriptive messages
  • Platform detection uses Node.js process.platform and process.arch with mapping to Rust target triples
  • Development workflow unchanged: cargo build still works locally, with the module loaded from ./envio.node

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh

Summary by CodeRabbit

  • New Features

    • CLI is now exposed via a native Node.js module with a lightweight JavaScript wrapper and TypeScript typings; clearer runtime errors when loading platform artifacts.
    • Environment variable override to locate the package at runtime (ENVIO_PKG_DIR).
  • Chores

    • Build, packaging, verification, and publish flows updated to produce/expect platform-specific native module artifacts.
    • Tests and e2e tooling now invoke the CLI via the package manager entrypoint.
    • Removed executable-bit handling for downloaded artifacts; native module files are ignored in development.

Replace the standalone Rust binary distribution with a napi-rs native
module (.node file) loaded directly by Node.js. This eliminates the
need for a separate binary and allows the CLI to be invoked as a JS
bin entry point (`envio` command via npm/pnpm).

Key changes:
- Add napi/napi-derive deps behind optional `napi` feature flag
- Add src/napi.rs with `run(argv)` function wrapping the executor
- Add core.js/core.d.ts ESM loader that resolves platform .node file
- Add bin/envio.js entry point to envio package
- Update build scripts to ship envio.node instead of bin/envio binary
- Update CI workflows to build cdylib with --features napi
- Update verify-artifact to expect new file structure

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Converts envio distribution from standalone Rust CLI binaries to a Node.js N‑API native module. CI, build, packaging, tests, and dev tooling now produce and consume platform-specific envio.node artifacts with a JS wrapper (bin/envio.js) invoked via the envio bin entry.

Changes

Cohort / File(s) Summary
CI Workflows
\.github/workflows/build-platforms.yml, \.github/workflows/build_and_verify.yml, \.github/workflows/publish.yml, \.github/actions/prepare-envio-artifacts/action.yml
Build and publish steps updated to produce a N‑API native module (envio.node) instead of a CLI executable; removed chmod +x; test invocations switched to pnpm envio; Node pinned to v24 in workflows.
Rust / N‑API Integration
packages/cli/Cargo.toml, packages/cli/src/lib.rs, packages/cli/src/napi.rs, packages/cli/build.rs
Adds [lib] crate-type = ["lib","cdylib"], optional napi deps (v3) and feature, build.rs calls napi_build::setup(), and introduces a feature‑gated napi::run(argv) entry point.
Packaging & Verification
packages/build-envio/build-platform-package.ts, packages/build-envio/src/build-artifact.ts, packages/build-envio/src/verify-artifact.ts
Packaging now targets envio.node; package.json generation/files updated to include envio.node, core.js, core.d.ts, and bin; verifier now expects a bin field and the new files.
JS Wrapper & Public API
packages/envio/core.js, packages/envio/core.d.ts, packages/envio/bin/envio.js, packages/envio/package.json, .gitignore
Adds core.js loader that resolves optional platform package or local envio.node and exports run; adds TypeScript declarations, a CLI JS wrapper bin/envio.js, bin entry, and ignores *.node.
E2E & Dev Config
packages/e2e-tests/src/config.ts, .pnpmfile.cjs
E2E fallback now uses pnpm envio (JS wrapper) instead of CI artifact; comments updated from “binary” to “native module”; pnpmfile comments adjusted.
Build output handling
packages/build-envio/src/build-artifact.ts (dist copy list)
Dist assets expanded to include core.js, core.d.ts, and bin; bin preserved/rewritten to point to JS wrapper (bin/envio.js) rather than a native executable.
CLI config resolution
packages/cli/src/config_parsing/system_config.rs
Adds ENVIO_PKG_DIR override and expands dev-mode discovery (search from executable and cwd) for resolving package directory and version.
Project script
package.json
Adds build:native script to build cargo lib with napi feature and copy platform library to packages/envio/envio.node.

Sequence Diagram

sequenceDiagram
    participant CI as Build System
    participant Cargo as Cargo (Rust)
    participant Pack as Packager
    participant NPM as npm/pnpm
    participant App as Application
    participant Loader as Node.js Loader
    participant Native as Native Module (envio.node)
    participant CLI as Rust CLI Logic

    rect rgba(100,150,255,0.5)
        Note over CI,Cargo: Old flow — binary distribution
        CI->>Cargo: cargo build --release --bins
        Cargo-->>CI: bin/envio (executable)
        CI->>Pack: package CLI binary
        Pack->>NPM: publish with bin/envio
        App->>NPM: install
        App->>App: execute ./bin/envio
    end

    rect rgba(150,200,100,0.5)
        Note over CI,Native: New flow — N‑API native module
        CI->>Cargo: cargo build --release --lib --features napi --target
        Cargo-->>CI: libenvio.so / libenvio.dylib
        CI->>CI: rename -> envio.node
        CI->>Pack: package envio.node + core.js/core.d.ts/bin
        Pack->>NPM: publish platform package / optional deps
        App->>NPM: install
        App->>Loader: import 'envio' / require
        Loader->>Native: load envio.node
        Native->>CLI: invoke run(argv) via N‑API
        CLI-->>Native: result
        Native-->>App: exported run executes
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • JonoPrest
  • JasoonS

Poem

🐰 A carrot-fueled build hopped on the road,
From Rusty bin to tiny .node.
JS wraps the hop, N‑API hums,
Envoy of cargo now softly drums,
Hooray — envio leaps, with joyful code! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Build envio CLI as NAPI native module instead of binary' accurately describes the main change: converting the envio CLI from a standalone binary to a NAPI native module.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/refactor-cli-to-core-GtECj

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…iles

- Remove win32/windows mapping from core.js (no Windows platform package exists)
- Remove unused `main` field from platform package.json (core.js uses explicit path)
- Fix stale JSDoc in build-platform-package.ts ("binary" → "native module")
- Add *.node to .gitignore to prevent accidental commits of dev builds

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build_and_verify.yml:
- Line 40: The workflow currently builds the Linux binary using the musl target
("x86_64-unknown-linux-musl") but that artifact is published as the generic
"envio-linux-x64" package, breaking glibc systems; update the build step that
runs "cargo build --release --lib --features napi --target
x86_64-unknown-linux-musl" to target "x86_64-unknown-linux-gnu" instead (and
remove any musl tool/install steps), or alternatively keep the musl build and
change the published package name to "envio-linux-x64-musl" in the publish
workflow so consumers know it's musl-linked; ensure references to
"envio-linux-x64" and the build target string are updated consistently (also
consider build-platforms.yml's use of aarch64-unknown-linux-gnu as the canonical
glibc variant).

In `@packages/envio/core.js`:
- Around line 16-20: PLATFORM_MAP incorrectly signals Windows support; either
remove the 'win32' => 'windows' entry from PLATFORM_MAP so the code and
user-facing support message remain consistent for unsupported Windows, or (if
Windows should be supported) add Windows to the published build targets and
update the support message to include windows-x64/windows-arm64 and ensure
packaging/release scripts consume PLATFORM_MAP['win32'] -> 'windows'; modify the
PLATFORM_MAP constant and the user-facing support-text so both reflect the same
supported platforms.
- Around line 41-48: The current broad catch blocks around the two require()
attempts hide real native load errors; update both try/catch blocks that load
`${pkgName}/envio.node` and `./envio.node` to only swallow errors where err.code
=== 'MODULE_NOT_FOUND' (or the specific Module not found indicator) and rethrow
any other errors (e.g., ERR_DLOPEN_FAILED) so ABI/dlopen failures surface; keep
the fallback behavior only for missing-module cases and ensure you reference the
same require calls when implementing the conditional rethrow.

ℹ️ Review info
Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c03b2984-3871-4453-8313-7e567ed7eadc

📥 Commits

Reviewing files that changed from the base of the PR and between 9d9f221 and 697aa5b.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • .github/actions/prepare-envio-artifacts/action.yml
  • .github/workflows/build-platforms.yml
  • .github/workflows/build_and_verify.yml
  • .github/workflows/publish.yml
  • .pnpmfile.cjs
  • packages/build-envio/build-platform-package.ts
  • packages/build-envio/src/build-artifact.ts
  • packages/build-envio/src/verify-artifact.ts
  • packages/cli/Cargo.toml
  • packages/cli/src/lib.rs
  • packages/cli/src/napi.rs
  • packages/e2e-tests/src/config.ts
  • packages/envio/bin/envio.js
  • packages/envio/core.d.ts
  • packages/envio/core.js
  • packages/envio/package.json
💤 Files with no reviewable changes (1)
  • .github/actions/prepare-envio-artifacts/action.yml

Comment on lines +41 to +48
try {
return require(`${pkgName}/envio.node`);
} catch (_) {}

// Fallback: local build (development)
try {
return require("./envio.node");
} catch (_) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual code in the repository
cd packages/envio && wc -l core.js

Repository: enviodev/hyperindex

Length of output: 73


🏁 Script executed:

# Read the relevant section of the file
head -60 packages/envio/core.js | tail -30

Repository: enviodev/hyperindex

Length of output: 815


🌐 Web query:

Node.js native addon error codes MODULE_NOT_FOUND ERR_DLOPEN_FAILED ABI version mismatch

💡 Result:

What these errors usually mean

  • MODULE_NOT_FOUND (when requiring a native addon) often means Node can’t resolve the addon’s compiled .node binary or the binary exists but fails to load, in which case you’ll often see a nested ERR_DLOPEN_FAILED.
  • ERR_DLOPEN_FAILED means a call to process.dlopen() failed (i.e., the OS loader couldn’t load the native binary). Common causes include missing shared libs, wrong CPU/OS, or ABI mismatch. [1]
  • ABI / NODE_MODULE_VERSION mismatch means the addon was compiled against a different Node/Electron C++ module ABI than the runtime you’re using. Node tracks this as process.versions.modules and will refuse to load mismatched binaries. [2]

Quick diagnosis checklist

  1. Confirm the runtime ABI you’re actually running
    • node -p "process.versions.modules" (this is the ABI / “node_abi” value). [2]
  2. If the error mentions NODE_MODULE_VERSION X vs Y: rebuild/reinstall
    • Typical fix: delete and reinstall, then rebuild native deps:
      • rm -rf node_modules package-lock.json && npm ci
      • npm rebuild
    • The error message itself commonly recommends npm rebuild / reinstall for this situation. [3]
  3. If this is an Electron app: rebuild for Electron, not system Node
    • Use @electron/rebuild to rebuild native modules against Electron’s ABI. [4][5]
  4. Check platform/arch/libc mismatches (common on Alpine/musl)
    • Prebuilt binaries are often selected by (platform, arch, libc, node_abi). If any differs, you may get ERR_DLOPEN_FAILED or no matching binary. [6][7]
  5. Watch for “wrong ABI reported” Node builds from OS package managers
    • Some distro Node builds can report an unexpected ABI (breaking prebuild selection). Fix: install Node from official Node.js distributions instead of the OS package manager. [7][8]

If you paste your exact error block

Include:

  • the full stack trace (especially the line mentioning NODE_MODULE_VERSION ... requires ...)
  • node -v
  • whether this is Node or Electron
  • OS + CPU (uname -a, node -p "process.platform+' '+process.arch")
    …and I can tell you which of the above is the most likely root cause and the minimal fix.

Sources:
[1] Node.js error docs: ERR_DLOPEN_FAILED definition. [2] Node docs: process.versions.modules is the ABI and mismatches are refused. [3] Electron docs show the common NODE_MODULE_VERSION mismatch error and suggest rebuild/reinstall. [4][5] Electron + @electron/rebuild usage for ABI-aligned rebuilds. [6] node-pre-gyp explains node_abi / process.versions.modules and how binaries are selected. [7] Datadog docs discuss ABI-reporting issues and libc/musl causing ERR_DLOPEN_FAILED. [8] Ubuntu bug report on wrong ABI being reported.


🌐 Web query:

Node.js require error handling best practices native modules optional dependencies

💡 Result:

Best practices: handling require() failures for optional deps (esp. native modules)

1) Mark truly-optional packages as optionalDependencies

If a dependency may fail to install (common for native addons), put it in optionalDependencies so npm won’t fail the whole install on build/download errors—but your code must handle its absence at runtime. [1]

2) Use try/catch, but only swallow “missing module” errors

require() throws an error with code === 'MODULE_NOT_FOUND' when a module can’t be resolved. [2] However, the same error code can also occur for nested missing imports inside the optional package. So:

  • Only ignore the error if it refers to the top-level optional package you attempted to load.
  • Otherwise, rethrow, because it’s a real bug (broken install, missing transitive dep, bad binary, etc.).
function optionalRequire(name) {
  try {
    return require(name);
  } catch (err) {
    // Only treat as optional if *this* module wasn't found
    if (err && err.code === 'MODULE_NOT_FOUND' && err.message?.includes(`'${name}'`)) {
      return null;
    }
    throw err;
  }
}

const fast = optionalRequire('my-native-addon');
module.exports = fast ?? require('./pure-js-fallback');

3) Prefer “probe then load” when you need extra certainty

You can probe resolution with require.resolve(), which also throws MODULE_NOT_FOUND if the module can’t be found. [2] This can help you separate “not installed” from “installed but throws during initialization”:

function safeLoad(name) {
  try {
    require.resolve(name);           // existence / resolvability probe
  } catch (e) {
    if (e.code === 'MODULE_NOT_FOUND') return null;
    throw e;
  }
  // If it exists, let real errors surface:
  return require(name);
}

4) For native addons: use Node-API (N-API) to reduce load/install breakage

If you ship a native module, using Node-API (formerly N-API) is the recommended path for ABI stability across Node.js versions, reducing “compiled against a different Node version” style failures. [3][4]


Sources

  1. npm docs: optionalDependencies behavior and example try/catch pattern. [1]
  2. Node.js docs: require() / require.resolve() throwing MODULE_NOT_FOUND. [2]
  3. Node.js guide: ABI stability + rationale/guarantees for N-API. [3]
  4. Node.js docs: Node-API is ABI-stable and intended to insulate addons from engine changes. [4]

Don't swallow non-missing-module native load errors.

Lines 41-48 currently suppress all errors with broad catch (_) {} blocks, including real addon load failures (ABI mismatches, dlopen failures, missing symbols). This masks diagnostic information and makes debugging much harder.

In Node.js, require() throws MODULE_NOT_FOUND when a module can't be resolved, but throws ERR_DLOPEN_FAILED for OS loader failures and other errors when loading the binary itself. Only the former should be caught during fallback resolution; real errors need to surface.

Suggested targeted error handling
+function isModuleNotFound(err) {
+  return err && (err.code === "MODULE_NOT_FOUND" || err.code === "ERR_MODULE_NOT_FOUND");
+}
+
   // Try platform package first (installed via optionalDependencies)
   try {
     return require(`${pkgName}/envio.node`);
-  } catch (_) {}
+  } catch (err) {
+    if (!isModuleNotFound(err)) throw err;
+  }
 
   // Fallback: local build (development)
   try {
     return require("./envio.node");
-  } catch (_) {}
+  } catch (err) {
+    if (!isModuleNotFound(err)) throw err;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/envio/core.js` around lines 41 - 48, The current broad catch blocks
around the two require() attempts hide real native load errors; update both
try/catch blocks that load `${pkgName}/envio.node` and `./envio.node` to only
swallow errors where err.code === 'MODULE_NOT_FOUND' (or the specific Module not
found indicator) and rethrow any other errors (e.g., ERR_DLOPEN_FAILED) so
ABI/dlopen failures surface; keep the fallback behavior only for missing-module
cases and ensure you reference the same require calls when implementing the
conditional rethrow.

… to gnu

- Upgrade napi 2→3.8.3 and napi-derive 2→3.5.2 (latest stable)
- Fix get_envio_version() for napi: set ENVIO_PKG_DIR from JS wrapper so
  Rust can find the envio package.json (current_exe() returns Node.js)
- Add current_dir() fallback for dev-mode packages/envio discovery
- Switch linux-x64 CI build from musl to gnu (consistent with other targets,
  avoids musl+glibc cdylib issues)
- Remove unnecessary needs: build-linux-package from build-envio-package
  (they share no artifacts, can now run in parallel)

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/envio/bin/envio.js (1)

13-13: Consider whether unhandled errors need explicit handling.

If run() throws (e.g., NAPI error from Rust), the process will exit with a stack trace. This is standard CLI behavior, but if cleaner error output is desired for user-facing errors, you could wrap in try-catch:

try {
  run(process.argv.slice(2));
} catch (err) {
  console.error(err.message);
  process.exitCode = 1;
}

This is optional since the current approach is valid for CLI tools and the Rust layer likely handles error formatting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/envio/bin/envio.js` at line 13, Wrap the top-level invocation of
run(process.argv.slice(2)) in a try-catch to handle any synchronous exceptions
from run (e.g., NAPI/Rust errors); catch the error, log a concise user-facing
message (using err.message) via console.error and set process.exitCode = 1 so
the CLI exits cleanly instead of printing a full stack trace. This change should
be made around the run(...) call in envio.js to provide cleaner output for
user-facing errors while preserving current behavior for unexpected failures.
packages/cli/src/config_parsing/system_config.rs (1)

426-446: Please add regression tests for dual-start-path discovery.

Line [431] and Line [432] introduce a two-anchor search (current_exe then current_dir). Add focused tests for precedence, fallback, and failure messaging to keep this path-resolution behavior stable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/config_parsing/system_config.rs` around lines 426 - 446, Add
unit tests for the dual-anchor discovery implemented via start_paths (uses
env::current_exe() then env::current_dir()) to cover precedence, fallback, and
failure cases: (1) create temporary directories simulating an executable path
with packages/envio and a different CWD also with packages/envio and assert the
function returns the path under the current_exe anchor (i.e., start_paths[0]
wins); (2) simulate current_exe failing (make it None or return a path without
packages/envio) while current_dir contains packages/envio and assert the
function returns the current_dir-based path; and (3) simulate both anchors not
containing packages/envio and assert the function returns an Err whose message
contains "could not find packages/envio above executable or working directory".
Reference the search logic using start_paths, env::current_exe,
env::current_dir, and the candidate = d.join("packages/envio") to locate where
to call and what to assert.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build_and_verify.yml:
- Around line 174-181: Jobs that run the packaged CLI via "pnpm envio codegen"
(e.g., the scenarios-test/template-tests/e2e-test workflows and the fuel_test
job shown) must wait for the Linux package build; add an explicit job dependency
on the build-linux-package job (or the job that produces
npm-pkg-envio-linux-x64) to each of these jobs by including it in their "needs"
array so they don't start before the linux artifact is available.

In `@packages/cli/Cargo.toml`:
- Around line 12-13: The crate exposes #[napi] exports in
packages/cli/src/napi.rs but Cargo.toml lacks build-dependencies and a build
script, so N-API codegen will fail; add napi-build (napi-build = { version =
"...", optional = false } or appropriate version) under [build-dependencies] in
Cargo.toml and create a build.rs at packages/cli/build.rs that calls
napi_build::setup() so the #[napi] macros in src/napi.rs run at build time and
produce the required bindings.

---

Nitpick comments:
In `@packages/cli/src/config_parsing/system_config.rs`:
- Around line 426-446: Add unit tests for the dual-anchor discovery implemented
via start_paths (uses env::current_exe() then env::current_dir()) to cover
precedence, fallback, and failure cases: (1) create temporary directories
simulating an executable path with packages/envio and a different CWD also with
packages/envio and assert the function returns the path under the current_exe
anchor (i.e., start_paths[0] wins); (2) simulate current_exe failing (make it
None or return a path without packages/envio) while current_dir contains
packages/envio and assert the function returns the current_dir-based path; and
(3) simulate both anchors not containing packages/envio and assert the function
returns an Err whose message contains "could not find packages/envio above
executable or working directory". Reference the search logic using start_paths,
env::current_exe, env::current_dir, and the candidate = d.join("packages/envio")
to locate where to call and what to assert.

In `@packages/envio/bin/envio.js`:
- Line 13: Wrap the top-level invocation of run(process.argv.slice(2)) in a
try-catch to handle any synchronous exceptions from run (e.g., NAPI/Rust
errors); catch the error, log a concise user-facing message (using err.message)
via console.error and set process.exitCode = 1 so the CLI exits cleanly instead
of printing a full stack trace. This change should be made around the run(...)
call in envio.js to provide cleaner output for user-facing errors while
preserving current behavior for unexpected failures.

ℹ️ Review info
Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f966bd5b-48e1-496e-9333-5c85da5f9884

📥 Commits

Reviewing files that changed from the base of the PR and between 21d984c and 199dcc2.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • scenarios/test_codegen/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .github/workflows/build_and_verify.yml
  • packages/cli/Cargo.toml
  • packages/cli/src/config_parsing/system_config.rs
  • packages/envio/bin/envio.js

Comment on lines +12 to +13
napi = { version = "3", features = ["tokio_rt"], optional = true }
napi-derive = { version = "3", optional = true }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verification for napi-build wiring in packages/cli

echo "=== Checking for build.rs files ==="
fd -HI '^build.rs$' .

echo -e "\n=== Checking Cargo.toml and build.rs for napi-build references ==="
rg -n 'napi[-_]build|napi_build::setup' packages/cli/Cargo.toml packages/cli/build.rs 2>/dev/null

echo -e "\n=== Checking for #[napi] attributes in napi.rs ==="
rg -n '#\[napi\]' packages/cli/src/napi.rs 2>/dev/null

echo -e "\n=== Full Cargo.toml [build-dependencies] section ==="
rg -A 10 '\[build-dependencies\]' packages/cli/Cargo.toml 2>/dev/null

echo -e "\n=== Full packages/cli/Cargo.toml ==="
wc -l packages/cli/Cargo.toml
cat -n packages/cli/Cargo.toml

Repository: enviodev/hyperindex

Length of output: 3187


Add N-API build wiring to compile NAPI exports.

Lines 12-13 add napi/napi-derive, and line 59 enables the feature. However, packages/cli/src/napi.rs contains #[napi] exports at line 5 that require build-time codegen. Missing napi-build in [build-dependencies] and a build.rs file will cause addon compilation to fail.

Add to Cargo.toml:

Required changes
+[build-dependencies]
+napi-build = "3"

Create packages/cli/build.rs:

fn main() {
    napi_build::setup();
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
napi = { version = "3", features = ["tokio_rt"], optional = true }
napi-derive = { version = "3", optional = true }
napi = { version = "3", features = ["tokio_rt"], optional = true }
napi-derive = { version = "3", optional = true }
[build-dependencies]
napi-build = "3"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/Cargo.toml` around lines 12 - 13, The crate exposes #[napi]
exports in packages/cli/src/napi.rs but Cargo.toml lacks build-dependencies and
a build script, so N-API codegen will fail; add napi-build (napi-build = {
version = "...", optional = false } or appropriate version) under
[build-dependencies] in Cargo.toml and create a build.rs at
packages/cli/build.rs that calls napi_build::setup() so the #[napi] macros in
src/napi.rs run at build time and produce the required bindings.

claude added 3 commits March 4, 2026 07:00
…4 artifact

The prepare-envio-artifacts action downloads both npm-pkg-envio and
npm-pkg-envio-linux-x64, but test jobs only declared needs on
build-envio-package. Add build-linux-package to the needs list.

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
…al import

- Add napi-build build-dependency and build.rs calling napi_build::setup()
  for platform-specific linker flags (macOS: -undefined dynamic_lookup,
  Linux: -Wl,-z,nodelete)
- Fix bin/envio.js: use relative import ../core.js instead of self-referential
  "envio/core.js" which fails in workspace dev mode
- Set ENVIO_PKG_DIR using dirname of bin/ (the envio package root)
- Add build:native script for local dev (cargo build + copy .node)
- Clean up core.js: remove extra imports, keep standard napi loading pattern

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
Remove the two-artifact build (build-linux-package + build-envio-package)
in favor of a single build-native job that uploads just the .so file.
Test jobs download it into packages/envio/envio.node where core.js
picks it up via the local-file fallback.

- Merge build-linux-package + build-envio-package into build-native
- Simplify prepare-envio-artifacts: download .so → rename to envio.node
- Remove .pnpmfile.cjs redirect (no longer needed for CI)

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 6: The "build:native" npm script maps non-darwin platforms to ".so"
causing unclear failures on Windows; update the inline node check used in the
"build:native" script to explicitly handle process.platform === 'darwin' ->
'dylib', process.platform === 'win32' -> 'dll', and otherwise either use 'so' or
immediately throw a clear error for unsupported platforms so the script fails
fast with a descriptive message; locate the "build:native" entry and change the
node -e platform branch accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11feee35-ccf2-4b36-848b-bc0989ca0133

📥 Commits

Reviewing files that changed from the base of the PR and between 5610dad and 98dbac6.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • package.json
  • packages/cli/Cargo.toml
  • packages/cli/build.rs
  • packages/envio/bin/envio.js
  • packages/envio/core.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/cli/Cargo.toml
  • packages/envio/core.js
  • packages/envio/bin/envio.js

"private": true,
"type": "module",
"scripts": {
"build:native": "cargo build --lib --features napi && node -e \"const p=process.platform==='darwin'?'dylib':'so'; require('fs').copyFileSync('target/debug/libenvio.'+p, 'packages/envio/envio.node')\"",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Current build:native script ==="
rg -n '"build:native"' package.json -C1

echo
echo "=== Native loader platform support in core.js ==="
rg -n 'Unsupported platform|PLATFORM_MAP|darwin|linux|win32|platform' packages/envio/core.js -C2 | head -100

Repository: enviodev/hyperindex

Length of output: 1517


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check for other platform handling in build/native code ==="
rg -n 'process.platform|platform|darwin|linux|win32' package.json -C1

echo
echo "=== Verify packages/envio directory structure ==="
ls -la packages/envio/ 2>/dev/null || echo "Directory not found"

echo
echo "=== Check if there are native build configs ==="
fd -n 'Cargo.toml|build\.rs|napi\.rs|\.cargo' packages/envio 2>/dev/null || echo "No native build files found in packages/envio"

Repository: enviodev/hyperindex

Length of output: 1409


Fail fast on unsupported platforms in build:native.

Line 6 currently maps every non-darwin OS to .so. On win32, this fails later with a generic file-copy error instead of a clear unsupported-platform error. Make platform handling explicit.

Suggested patch
-    "build:native": "cargo build --lib --features napi && node -e \"const p=process.platform==='darwin'?'dylib':'so'; require('fs').copyFileSync('target/debug/libenvio.'+p, 'packages/envio/envio.node')\"",
+    "build:native": "cargo build --lib --features napi && node -e \"const os=process.platform; const ext=os==='darwin'?'dylib':os==='linux'?'so':null; if(!ext){ throw new Error('Unsupported platform for build:native: '+os); } require('fs').copyFileSync('target/debug/libenvio.'+ext, 'packages/envio/envio.node')\"",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"build:native": "cargo build --lib --features napi && node -e \"const p=process.platform==='darwin'?'dylib':'so'; require('fs').copyFileSync('target/debug/libenvio.'+p, 'packages/envio/envio.node')\"",
"build:native": "cargo build --lib --features napi && node -e \"const os=process.platform; const ext=os==='darwin'?'dylib':os==='linux'?'so':null; if(!ext){ throw new Error('Unsupported platform for build:native: '+os); } require('fs').copyFileSync('target/debug/libenvio.'+ext, 'packages/envio/envio.node')\"",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 6, The "build:native" npm script maps non-darwin
platforms to ".so" causing unclear failures on Windows; update the inline node
check used in the "build:native" script to explicitly handle process.platform
=== 'darwin' -> 'dylib', process.platform === 'win32' -> 'dll', and otherwise
either use 'so' or immediately throw a clear error for unsupported platforms so
the script fails fast with a descriptive message; locate the "build:native"
entry and change the node -e platform branch accordingly.

claude added 5 commits March 4, 2026 07:40
The napi-only build doesn't produce a cargo binary, so resolveEnvio()
fell through to `pnpm envio` which fails when cwd is outside the
workspace (template tests run in /tmp). Use absolute node path instead.

Add .claude/skills/ci/ for investigating CI failures via GitHub API.

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
When running via napi, current_exe() returns the Node binary and cwd
may be outside the workspace (e.g. template tests in /tmp/). The
existing walk-up strategy fails to find packages/envio in both cases.

Fix: when ENVIO_PKG_DIR is set (by bin/envio.js), use it directly as
the dev-mode envio path. This is the same directory the napi wrapper
already uses for module loading.

Also update ci skill to use raw GitHub API commands directly.

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
core.js now has a third fallback: if no platform package or local
envio.node is found, it locates the workspace Cargo.toml, runs
`cargo build --lib --features napi`, and copies the cdylib as
envio.node. Subsequent runs skip the build if the cdylib exists.

This means `pnpm envio dev` just works on a fresh clone without
any manual cargo build step.

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
Instead of only building when the cdylib is missing, always run
cargo build (which is a no-op at ~0.5s when nothing changed).
This gives the same auto-rebuild behavior as cargo run would,
while still exercising the production napi code path.

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
When installed via `pnpm link` or `file:` dependency, __dirname
points into node_modules/.pnpm/... which is a symlink. Use
realpathSync to resolve back to the real repo path so the
workspace root walk-up finds Cargo.toml correctly.

https://claude.ai/code/session_011KGFQXXYDMBAGr3oQSSKRh
@DZakh DZakh closed this Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants