Add WASI build support and npm package#3
Conversation
WalkthroughThis change adds WASI (WebAssembly System Interface) support and npm packaging to Regula. New files include a shell build script that vendors dependencies with patches, then compiles a WASI-compatible WebAssembly binary. A Node.js CLI entry point boots the WASM module under WASI, while an index.js wrapper exposes programmatic API functions. Multiple dependency patches enable WASI compatibility (terminal stubs, errno handling, file watcher stubs). Package.json, .npmignore, and .gitignore configure npm distribution. Test fixtures and harnesses validate parity between WASI and native builds. Documentation restructures to emphasize fork identity and WASI/npm usage. Sequence Diagram(s)sequenceDiagram
participant User as Node.js User
participant CLI as cli.js
participant WASM as regula.wasm
participant WASI as WASI Runtime
participant FS as File System
User->>CLI: node cli.js [args]
CLI->>CLI: Create WASI instance
CLI->>CLI: Read regula.wasm
CLI->>WASM: Instantiate with WASI imports
WASM->>WASI: Initialize (get env, args, fd:0/1/2)
WASI->>FS: Preopen "/" (read paths)
WASM->>WASM: Execute Regula logic
WASM->>FS: Read input files
WASM->>WASM: Analyze & validate
WASM->>WASI: Write output (stdout)
WASI->>CLI: Return exit code
CLI->>User: Print results, exit
sequenceDiagram
participant Caller as Caller (Node.js)
participant IndexJS as index.js
participant ChildProcess as Child Process
participant CLI as cli.js
participant WASM as regula.wasm
Caller->>IndexJS: runRegula(paths, options)
IndexJS->>IndexJS: Normalize paths & build args
IndexJS->>ChildProcess: spawn "node cli.js"
ChildProcess->>CLI: Execute with args
CLI->>WASM: Boot WASI module
WASM->>WASM: Run analysis
WASM->>ChildProcess: Output JSON
ChildProcess->>IndexJS: Return stdout/stderr/code
IndexJS->>IndexJS: Parse JSON stdout
IndexJS->>Caller: Return parsed results
Poem
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
This adds the ability to build regula as a WASI (WebAssembly System Interface) binary that can be run via Node.js 18+. New files: - build-wasi.sh: Build script that applies vendor patches and compiles to WASI - patches/: WASI-compatible vendor patches (applied only during WASI build) - package.json: npm package configuration (regula-wasi) - index.js: Programmatic API (runRegula, validate functions) - cli.js: CLI wrapper for the WASM binary - test/: Test suite including WASI/native parity tests Usage: - Build WASI: ./build-wasi.sh - Build native: go build -mod vendor (unchanged) - Run tests: npm test && npm run test:wasi The WASI build produces identical output to the native binary. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add LICENSE to published files - Update description to note it's a fork - Point repository URL to nonfx/regula Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2a94fbe to
d25cd7e
Compare
- Update build-wasi.sh to run go mod vendor first - Add -ldflags for stripped builds - Skip patches that don't apply (go-systemd removed) - Update fsnotify patch for new package structure Requires Go 1.25+ for WASM linking of large binaries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Document this as a maintained fork of fugue/regula - List security patches and dependency upgrades - Add WASI build documentation - Add npm installation and usage instructions - Update available commands and options Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@cli.js`:
- Around line 13-18: The WASI instance currently preopens the entire host
filesystem via preopens: { "/": "/" } in the new WASI(...) call; change that to
only expose the current working directory by replacing the preopens mapping with
a single subtree like preopens: { ".": process.cwd() } (i.e., update the new
WASI(...) creation in cli.js so the WASI constructor maps only process.cwd()
instead of "/").
In `@index.js`:
- Around line 12-26: The function runRegula documents options.format but always
forces JSON; update argument construction in runRegula so args uses
options.format when provided (fall back to "json" if not), i.e., replace the
hardcoded ["run","--format","json"] with logic that sets "--format" to
options.format or "json" before pushing it into args; reference runRegula, the
args array, and options.format to locate and change the code.
In `@patches/coreos/go-systemd/journal/journal.go`:
- Around line 72-82: Enabled() currently calls net.Dial("unixgram",
journalSocket) but discards the resulting connection, leaking a file descriptor
on each probe; update Enabled() to capture the returned connection (e.g., conn,
err := net.Dial(...)), check err as now, and ensure the connection is closed
(conn.Close()) before returning (use a defer immediately after a successful dial
or an explicit Close prior to return) so repeated calls don't exhaust FDs; keep
the existing checks involving onceConn.Do(initConn) and unixConnPtr unchanged.
- Around line 120-137: The temp file fallback currently drops large journal
entries because the ancillary-FD send using conn.WriteMsgUnix is commented out;
replace that silent noop with a build-tagged helper (e.g., sendWithFD or sendFD)
and call it where the commented code is: create sendWithFD_unix.go (//go:build
!wasip1) that constructs syscall.UnixRights(int(file.Fd())) and calls
conn.WriteMsgUnix to send the FD and returns any error, and create
sendWithFD_wasip1.go (//go:build wasip1) that returns a clear "unsupported on
wasip1" error; update the code around tempFd/conn.WriteMsgUnix to call this
helper and propagate its error instead of returning nil so large entries are
either sent or fail explicitly.
In `@patches/fsnotify/fsnotify/backend_other.go`:
- Around line 22-69: The Close, Add and Remove methods on Watcher currently
return nil on unsupported platforms and should instead return the same
unsupported-backend error used by NewWatcher so misuse doesn't silently succeed;
update Watcher.Close, Watcher.Add and Watcher.Remove to return the identical
error value (the one NewWatcher returns when the backend is unsupported) rather
than nil, preserving signatures and without adding platform-specific behavior.
In `@patches/sirupsen/logrus/terminal_check_notappengine.go`:
- Around line 11-17: The call to isTerminal(int(v.Fd())) in checkIfTerminal is
undefined; add a concrete implementation or delegate to the standard term
package. Import "golang.org/x/term" (or "golang.org/x/crypto/ssh/terminal" if
you prefer) and replace/implement isTerminal to call term.IsTerminal(fd) (or add
a local isTerminal wrapper that calls term.IsTerminal) so checkIfTerminal
compiles; ensure the import is added and referenced functions are
checkIfTerminal and isTerminal (and types io.Writer/os.File) so the linker can
resolve the symbol.
In `@test/test-wasi.js`:
- Around line 178-187: The test named "Exit codes match for failures" currently
only compares failure detection; update it to also assert the exit codes from
runWasi and runBinary are equal and non-zero when failures are present: after
computing wasiHasFailures and binaryHasFailures, add assertions comparing
wasi.exitCode and binary.exitCode (e.g., assert.strictEqual(wasi.exitCode,
binary.exitCode)) and assert that when wasiHasFailures is true the exit code is
!== 0 (and similarly for binary) so both the equality and non-zero behavior are
checked for the wasi and binary results returned by runWasi and runBinary.
- Around line 36-49: runWasi and runBinary currently always resolve even when
the child process fails, so tests can pass by comparing stdout alone; update the
helpers or tests so failures are surfaced: modify runWasi and runBinary to
reject when the execFile callback reports an error (use error and error.code) so
callers receive a rejected promise on non-zero exit, or alternatively keep the
helpers but add explicit assertions in success tests (e.g., Test 10) to assert
result.exitCode === 0 before checking stdout/JSON; locate the functions runWasi
and runBinary and adjust their execFile callback behavior or add exitCode === 0
assertions in tests that expect success.
🧹 Nitpick comments (4)
patches/sirupsen/logrus/terminal_check_appengine.go (1)
10-12: Consider returningfalseinstead oftrue.Always returning
truemeans WASI outputs will include terminal escape codes even when piped/redirected. Returningfalsewould be safer - no terminal formatting when terminal state is unknown.♻️ Proposed fix
func checkIfTerminal(w io.Writer) bool { - return true + return false }build-wasi.sh (1)
1-6: Harden script safety checks.
Considerset -euo pipefailand a quick vendor dir check for clearer failures.♻️ Suggested change
-#!/bin/bash -set -e +#!/bin/bash +set -euo pipefail + +if [[ ! -d vendor ]]; then + echo "vendor/ not found; run 'go mod vendor' first." + exit 1 +fipatches/chzyer/readline/term_wasip1.go (1)
31-37: Consider signaling “no terminal” instead of a fixed size.
Returning(80,24,nil)may trigger TTY paths. If terminal I/O is unsupported in WASI, returning an error (or 0,0) is safer.♻️ Suggested change
- return 80, 24, nil // Returning a standard terminal size of 80x24 + return 0, 0, errors.New("unsupported on WASI")test/test.js (1)
29-33: Useprocess.execPathfor CLI runs.
Guarantees same Node version across environments; avoids PATH surprises.♻️ Suggested change
- execFile("node", [cliPath, ...args], { maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => { + execFile(process.execPath, [cliPath, ...args], { maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => { resolve({ error, stdout, stderr, exitCode: error?.code || 0 }); });
| const wasi = new WASI({ | ||
| version: "preview1", | ||
| args: ["regula", ...args], | ||
| env: process.env, | ||
| preopens: { "/": "/" }, | ||
| returnOnExit: true, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
WASI preopens security best practices restrict filesystem access
💡 Result:
WASI preopens: security best practices to restrict filesystem access
- Default-deny: don’t preopen anything unless you must. WASI modules have no filesystem access unless the host explicitly supplies preopened directory handles. (docs.rs)
- Follow least privilege on scope (which directories). Preopen the smallest directory subtree the module needs (avoid broad roots like
/, your home dir, or project root) because the module can access the whole contents of each preopened directory. (docs.wasmtime.dev) - Follow least privilege on rights (read vs write). When you preopen, set directory and file permissions to read-only unless writes are required; WASI hosts (e.g., Wasmtime) expose separate dir/file permission masks for this. (docs.wasmtime.dev)
- Rely on the capability boundary—don’t “escape-proof” with path string checks alone. WASI’s model is: libc resolves absolute paths by translating them into a (preopened dir fd + relative path) and then using
openat-style operations; access fails if no matching preopen capability exists. (sources.debian.org) - Assume
..traversal is blocked, but still keep preopens tight. Wasmtime notes that WASI prevents traversing outside a preopened directory (e.g., using..). This is a safety net, not a reason to preopen large trees. (docs.rs) - Use dedicated “scratch” directories for writes. If a module needs temporary output, preopen a newly created empty directory (or per-tenant/per-request directory) rather than granting write access to existing host paths. (This follows directly from preopens granting access to the directory contents.) (docs.wasmtime.dev)
- Minimize extra WASI surface area that comes with filesystem access. Security advisories show that some issues are only reachable when the guest can open additional file descriptors—commonly possible when a filesystem directory is preopened—so avoid preopens for untrusted modules unless required, and keep runtimes updated. (advisories.gitlab.com)
If you tell me your runtime/host (Wasmtime, Wasmer, Node, browsers, etc.) and whether you’re using WASI Preview1 or WASI 0.2/components, I can translate this into concrete configuration examples (CLI flags / embedding code) for your environment.
Citations:
- 1: https://docs.rs/wasmtime-wasi/latest/wasmtime_wasi/struct.WasiCtxBuilder.html?utm_source=openai
- 2: https://docs.wasmtime.dev/c-api/wasi_8h.html?utm_source=openai
- 3: https://docs.wasmtime.dev/c-api/wasi_8h.html?utm_source=openai
- 4: https://sources.debian.org/src/wasi-libc/0.0~git20241209.574b88d-2/libc-bottom-half/README.md?utm_source=openai
- 5: https://docs.rs/wasmtime-wasi/latest/wasmtime_wasi/struct.WasiCtxBuilder.html?utm_source=openai
- 6: https://docs.wasmtime.dev/c-api/wasi_8h.html?utm_source=openai
- 7: https://advisories.gitlab.com/pkg/cargo/wasmtime/CVE-2025-53901/?utm_source=openai
🏁 Script executed:
fd cli.jsRepository: nonfx/regula
Length of output: 62
🏁 Script executed:
cat -n cli.jsRepository: nonfx/regula
Length of output: 1081
Restrict WASI preopens to limit filesystem access.
Line 17 maps / to /, exposing the entire host filesystem. WASI best practices recommend preopening only the smallest directory subtree needed. For a CLI tool, process.cwd() is appropriate—it limits access to where the user runs the command.
Suggested change
-const wasi = new WASI({
+const preopenDir = process.cwd();
+const wasi = new WASI({
version: "preview1",
args: ["regula", ...args],
env: process.env,
- preopens: { "/": "/" },
+ preopens: { "/": preopenDir },
returnOnExit: true,
});📝 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.
| const wasi = new WASI({ | |
| version: "preview1", | |
| args: ["regula", ...args], | |
| env: process.env, | |
| preopens: { "/": "/" }, | |
| returnOnExit: true, | |
| const preopenDir = process.cwd(); | |
| const wasi = new WASI({ | |
| version: "preview1", | |
| args: ["regula", ...args], | |
| env: process.env, | |
| preopens: { "/": preopenDir }, | |
| returnOnExit: true, | |
| }); |
🤖 Prompt for AI Agents
In `@cli.js` around lines 13 - 18, The WASI instance currently preopens the entire
host filesystem via preopens: { "/": "/" } in the new WASI(...) call; change
that to only expose the current working directory by replacing the preopens
mapping with a single subtree like preopens: { ".": process.cwd() } (i.e.,
update the new WASI(...) creation in cli.js so the WASI constructor maps only
process.cwd() instead of "/").
| * @param {string} options.inputType - Input type: auto, tf, tf-plan, cfn, k8s, arm | ||
| * @param {string} options.format - Output format: json, text, table, sarif, junit, tap | ||
| * @param {string[]} options.include - Additional rego files to include | ||
| * @param {string[]} options.only - Only run specific rules | ||
| * @param {string[]} options.exclude - Exclude specific rules | ||
| * @returns {Promise<Object>} - Regula output | ||
| */ | ||
| export async function runRegula(paths, options = {}) { | ||
| const pathArray = Array.isArray(paths) ? paths : [paths]; | ||
|
|
||
| const args = ["run", "--format", "json"]; | ||
|
|
||
| if (options.inputType) { | ||
| args.push("--input-type", options.inputType); | ||
| } |
There was a problem hiding this comment.
options.format is documented but ignored.
options.format is advertised yet args always uses JSON, so callers can’t request other formats. Either honor the option or drop it from the public docs.
✅ Minimal fix
- const args = ["run", "--format", "json"];
+ const args = ["run", "--format", options.format ?? "json"];🤖 Prompt for AI Agents
In `@index.js` around lines 12 - 26, The function runRegula documents
options.format but always forces JSON; update argument construction in runRegula
so args uses options.format when provided (fall back to "json" if not), i.e.,
replace the hardcoded ["run","--format","json"] with logic that sets "--format"
to options.format or "json" before pushing it into args; reference runRegula,
the args array, and options.format to locate and change the code.
| // Enabled checks whether the local systemd journal is available for logging. | ||
| func Enabled() bool { | ||
| onceConn.Do(initConn) | ||
|
|
||
| if (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) == nil { | ||
| return false | ||
| } | ||
|
|
||
| if _, err := net.Dial("unixgram", journalSocket); err != nil { | ||
| return false | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f "journal.go" patches/Repository: nonfx/regula
Length of output: 100
🏁 Script executed:
cat -n "patches/coreos/go-systemd/journal/journal.go" | sed -n '65,90p'Repository: nonfx/regula
Length of output: 908
🏁 Script executed:
cat -n "patches/coreos/go-systemd/journal/journal.go" | head -100Repository: nonfx/regula
Length of output: 3532
🏁 Script executed:
cat -n "patches/coreos/go-systemd/journal/journal.go" | sed -n '100,200p'Repository: nonfx/regula
Length of output: 3585
🏁 Script executed:
rg -n "func initConn" "patches/coreos/go-systemd/journal/journal.go" -A 20Repository: nonfx/regula
Length of output: 353
Enabled() leaks a file descriptor on every check.
Line 80 dials unixgram for probe but never closes—repeated Enabled() calls exhaust FDs. Capture and close before returning.
Suggested fix
if _, err := net.Dial("unixgram", journalSocket); err != nil {
return false
}
+conn, err := net.Dial("unixgram", journalSocket)
+if err != nil {
+ return false
+}
+_ = conn.Close()📝 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.
| // Enabled checks whether the local systemd journal is available for logging. | |
| func Enabled() bool { | |
| onceConn.Do(initConn) | |
| if (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) == nil { | |
| return false | |
| } | |
| if _, err := net.Dial("unixgram", journalSocket); err != nil { | |
| return false | |
| } | |
| // Enabled checks whether the local systemd journal is available for logging. | |
| func Enabled() bool { | |
| onceConn.Do(initConn) | |
| if (*net.UnixConn)(atomic.LoadPointer(&unixConnPtr)) == nil { | |
| return false | |
| } | |
| conn, err := net.Dial("unixgram", journalSocket) | |
| if err != nil { | |
| return false | |
| } | |
| _ = conn.Close() |
🤖 Prompt for AI Agents
In `@patches/coreos/go-systemd/journal/journal.go` around lines 72 - 82, Enabled()
currently calls net.Dial("unixgram", journalSocket) but discards the resulting
connection, leaking a file descriptor on each probe; update Enabled() to capture
the returned connection (e.g., conn, err := net.Dial(...)), check err as now,
and ensure the connection is closed (conn.Close()) before returning (use a defer
immediately after a successful dial or an explicit Close prior to return) so
repeated calls don't exhaust FDs; keep the existing checks involving
onceConn.Do(initConn) and unixConnPtr unchanged.
| // Large log entry, send it via tempfile and ancillary-fd. | ||
| file, err := tempFd() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer file.Close() | ||
| _, err = io.Copy(file, data) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // @nonfx - Disabled for wasip1 compilation | ||
| // rights := syscall.UnixRights(int(file.Fd())) | ||
| // _, _, err = conn.WriteMsgUnix([]byte{}, rights, socketAddr) | ||
| // if err != nil { | ||
| // return err | ||
| // } | ||
|
|
||
| return nil |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -path "*coreos/go-systemd/journal/journal.go" -type fRepository: nonfx/regula
Length of output: 102
🏁 Script executed:
cat -n ./patches/coreos/go-systemd/journal/journal.go | sed -n '100,150p'Repository: nonfx/regula
Length of output: 1719
🏁 Script executed:
# Check the function signature and broader context
cat -n ./patches/coreos/go-systemd/journal/journal.go | sed -n '75,110p'Repository: nonfx/regula
Length of output: 1450
Large journal entries are silently dropped.
When WriteMsgUnix hits EMSGSIZE/ENOBUFS, you create a temp file but the ancillary-FD send is commented out, then return nil. That's silent data loss. Use build-tagged helpers: real send on non-WASI, explicit error on WASI.
✅ Directional fix (helper + build tags)
- // `@nonfx` - Disabled for wasip1 compilation
- // rights := syscall.UnixRights(int(file.Fd()))
- // _, _, err = conn.WriteMsgUnix([]byte{}, rights, socketAddr)
- // if err != nil {
- // return err
- // }
+ if err := sendWithFD(conn, socketAddr, file); err != nil {
+ return err
+ }Then add:
sendWithFD_unix.go(//go:build !wasip1): usessyscall.UnixRights+WriteMsgUnixsendWithFD_wasip1.go(//go:build wasip1): returns a clear "unsupported" error
🤖 Prompt for AI Agents
In `@patches/coreos/go-systemd/journal/journal.go` around lines 120 - 137, The
temp file fallback currently drops large journal entries because the
ancillary-FD send using conn.WriteMsgUnix is commented out; replace that silent
noop with a build-tagged helper (e.g., sendWithFD or sendFD) and call it where
the commented code is: create sendWithFD_unix.go (//go:build !wasip1) that
constructs syscall.UnixRights(int(file.Fd())) and calls conn.WriteMsgUnix to
send the FD and returns any error, and create sendWithFD_wasip1.go (//go:build
wasip1) that returns a clear "unsupported on wasip1" error; update the code
around tempFd/conn.WriteMsgUnix to call this helper and propagate its error
instead of returning nil so large entries are either sent or fail explicitly.
| func checkIfTerminal(w io.Writer) bool { | ||
| switch v := w.(type) { | ||
| case *os.File: | ||
| return isTerminal(int(v.Fd())) | ||
| default: | ||
| return false | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Search for isTerminal function definition
rg -n 'func isTerminal' patches/Repository: nonfx/regula
Length of output: 38
🏁 Script executed:
# Check the file structure in logrus patches
ls -la patches/sirupsen/logrus/ | head -20Repository: nonfx/regula
Length of output: 319
🏁 Script executed:
# Get full content of the file in question
cat -n patches/sirupsen/logrus/terminal_check_notappengine.goRepository: nonfx/regula
Length of output: 492
🏁 Script executed:
# Search more broadly for isTerminal
rg -n 'isTerminal' patches/Repository: nonfx/regula
Length of output: 146
🏁 Script executed:
# Check what's in the entire logrus directory including hidden files
find patches/sirupsen/logrus/ -type f -name "*.go"Repository: nonfx/regula
Length of output: 162
🏁 Script executed:
# Look at the appengine version for comparison
cat -n patches/sirupsen/logrus/terminal_check_appengine.goRepository: nonfx/regula
Length of output: 288
🏁 Script executed:
# Search the entire repo for isTerminal definition
rg -n 'func isTerminal' .Repository: nonfx/regula
Length of output: 38
🏁 Script executed:
# Check if there's an upstream logrus directory (non-patched)
find . -type d -name logrus | head -10Repository: nonfx/regula
Length of output: 81
🏁 Script executed:
# Look for any terminal_check files outside patches
find . -name "terminal_check*.go" -type fRepository: nonfx/regula
Length of output: 166
🏁 Script executed:
# Check if there are any imports or dependencies that might provide isTerminal
rg -n 'import|isTerminal' patches/sirupsen/logrus/terminal_check_notappengine.goRepository: nonfx/regula
Length of output: 102
🏁 Script executed:
# Search for terminal_check files in the whole repo to understand the patch structure
find . -path ./patches -prune -o -name "terminal_check*.go" -type f -printRepository: nonfx/regula
Length of output: 38
🏁 Script executed:
# Look at git history or any comments that might explain the patch
head -50 patches/sirupsen/logrus/terminal_check_notappengine.goRepository: nonfx/regula
Length of output: 366
isTerminal function is undefined – code won't compile.
This file calls isTerminal(int(v.Fd())) on line 14 but the function is not defined anywhere in the codebase. No imports bring it in either. The patch is incomplete – either the function definition is missing from the patches, or this needs additional files from upstream logrus to work.
🧰 Tools
🪛 GitHub Check: Test with Go 1.24.11
[failure] 14-14:
undefined: isTerminal
🤖 Prompt for AI Agents
In `@patches/sirupsen/logrus/terminal_check_notappengine.go` around lines 11 - 17,
The call to isTerminal(int(v.Fd())) in checkIfTerminal is undefined; add a
concrete implementation or delegate to the standard term package. Import
"golang.org/x/term" (or "golang.org/x/crypto/ssh/terminal" if you prefer) and
replace/implement isTerminal to call term.IsTerminal(fd) (or add a local
isTerminal wrapper that calls term.IsTerminal) so checkIfTerminal compiles;
ensure the import is added and referenced functions are checkIfTerminal and
isTerminal (and types io.Writer/os.File) so the linker can resolve the symbol.
| function runWasi(args) { | ||
| return new Promise((resolve, reject) => { | ||
| execFile("node", [cliPath, ...args], { maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => { | ||
| resolve({ error, stdout, stderr, exitCode: error?.code || 0 }); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| function runBinary(args) { | ||
| return new Promise((resolve, reject) => { | ||
| execFile(binaryPath, args, { maxBuffer: 50 * 1024 * 1024 }, (error, stdout, stderr) => { | ||
| resolve({ error, stdout, stderr, exitCode: error?.code || 0 }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the structure of the test file
wc -l test/test-wasi.jsRepository: nonfx/regula
Length of output: 77
🏁 Script executed:
# Read the full test file to understand the test structure and assertions
cat -n test/test-wasi.jsRepository: nonfx/regula
Length of output: 8429
🏁 Script executed:
# Let's also check if there are other test files that might provide context
git ls-files test/Repository: nonfx/regula
Length of output: 135
🏁 Script executed:
# Check if exitCode is asserted anywhere in the test file
rg "exitCode" test/test-wasi.js -A 2 -B 2Repository: nonfx/regula
Length of output: 510
🏁 Script executed:
# Double-check: are there any other error handling patterns or exitCode checks elsewhere?
rg "\.error|exitCode|assert" test/test-wasi.js | grep -E "(error|exitCode)"Repository: nonfx/regula
Length of output: 239
Add exitCode assertions—tests can pass with both binaries failing identically.
runWasi and runBinary always resolve, never reject. Tests mostly compare stdout only, so missing/invalid binaries pass silently. Add exitCode === 0 checks in success tests, or reject on error in the helpers.
Test 10's comment claims "Exit codes match" but only checks JSON parse results, not actual exit codes.
Minimal fix (shared helper)
+function assertOk(result, label) {
+ assert.strictEqual(result.exitCode, 0, `${label} exit code should be 0`);
+ assert.ifError(result.error);
+}Apply in success cases, e.g.:
- const wasi = await runWasi(["version"]);
- const binary = await runBinary(["version"]);
+ const wasi = await runWasi(["version"]);
+ const binary = await runBinary(["version"]);
+ assertOk(wasi, "wasi");
+ assertOk(binary, "binary");🤖 Prompt for AI Agents
In `@test/test-wasi.js` around lines 36 - 49, runWasi and runBinary currently
always resolve even when the child process fails, so tests can pass by comparing
stdout alone; update the helpers or tests so failures are surfaced: modify
runWasi and runBinary to reject when the execFile callback reports an error (use
error and error.code) so callers receive a rejected promise on non-zero exit, or
alternatively keep the helpers but add explicit assertions in success tests
(e.g., Test 10) to assert result.exitCode === 0 before checking stdout/JSON;
locate the functions runWasi and runBinary and adjust their execFile callback
behavior or add exitCode === 0 assertions in tests that expect success.
| // Test 10: Exit codes match | ||
| await test("Exit codes match for failures", async () => { | ||
| const wasi = await runWasi(["run", "--format", "json", join(fixturesDir, "insecure.tf")]); | ||
| const binary = await runBinary(["run", "--format", "json", join(fixturesDir, "insecure.tf")]); | ||
|
|
||
| // Both should have non-zero exit for files with violations | ||
| const wasiHasFailures = JSON.parse(wasi.stdout).summary.rule_results.FAIL > 0; | ||
| const binaryHasFailures = JSON.parse(binary.stdout).summary.rule_results.FAIL > 0; | ||
|
|
||
| assert.strictEqual(wasiHasFailures, binaryHasFailures, "Both should detect failures"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n test/test-wasi.js | sed -n '170,190p'Repository: nonfx/regula
Length of output: 1207
🏁 Script executed:
# Find the runWasi and runBinary function implementations
rg -A 10 "function runWasi|const runWasi" test/test-wasi.jsRepository: nonfx/regula
Length of output: 478
🏁 Script executed:
# Also check for runBinary
rg -A 10 "function runBinary|const runBinary" test/test-wasi.jsRepository: nonfx/regula
Length of output: 377
Test doesn't actually compare exit codes despite its name.
The test is called "Exit codes match for failures" but only checks if both detect failures. Add the exit code assertion.
assert.strictEqual(wasiHasFailures, binaryHasFailures, "Both should detect failures");
+ assert.strictEqual(wasi.exitCode, binary.exitCode, "Exit codes should match");📝 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.
| // Test 10: Exit codes match | |
| await test("Exit codes match for failures", async () => { | |
| const wasi = await runWasi(["run", "--format", "json", join(fixturesDir, "insecure.tf")]); | |
| const binary = await runBinary(["run", "--format", "json", join(fixturesDir, "insecure.tf")]); | |
| // Both should have non-zero exit for files with violations | |
| const wasiHasFailures = JSON.parse(wasi.stdout).summary.rule_results.FAIL > 0; | |
| const binaryHasFailures = JSON.parse(binary.stdout).summary.rule_results.FAIL > 0; | |
| assert.strictEqual(wasiHasFailures, binaryHasFailures, "Both should detect failures"); | |
| // Test 10: Exit codes match | |
| await test("Exit codes match for failures", async () => { | |
| const wasi = await runWasi(["run", "--format", "json", join(fixturesDir, "insecure.tf")]); | |
| const binary = await runBinary(["run", "--format", "json", join(fixturesDir, "insecure.tf")]); | |
| // Both should have non-zero exit for files with violations | |
| const wasiHasFailures = JSON.parse(wasi.stdout).summary.rule_results.FAIL > 0; | |
| const binaryHasFailures = JSON.parse(binary.stdout).summary.rule_results.FAIL > 0; | |
| assert.strictEqual(wasiHasFailures, binaryHasFailures, "Both should detect failures"); | |
| assert.strictEqual(wasi.exitCode, binary.exitCode, "Exit codes should match"); |
🤖 Prompt for AI Agents
In `@test/test-wasi.js` around lines 178 - 187, The test named "Exit codes match
for failures" currently only compares failure detection; update it to also
assert the exit codes from runWasi and runBinary are equal and non-zero when
failures are present: after computing wasiHasFailures and binaryHasFailures, add
assertions comparing wasi.exitCode and binary.exitCode (e.g.,
assert.strictEqual(wasi.exitCode, binary.exitCode)) and assert that when
wasiHasFailures is true the exit code is !== 0 (and similarly for binary) so
both the equality and non-zero behavior are checked for the wasi and binary
results returned by runWasi and runBinary.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@README.md`:
- Around line 155-170: The fenced code block under the "Available Commands"
section (the block starting with "regula [command]") is missing a language tag
which markdownlint flags; update the opening triple-backticks to include a
language (e.g., change ``` to ```text) so the block becomes ```text followed by
the existing "regula [command]" content and the closing ```; no other content
change is needed.
| ``` | ||
| regula [command] | ||
|
|
||
| Once you've built the binary, execute the following to run tests: | ||
| Commands: | ||
| run Evaluate rules against infrastructure as code | ||
| test Run OPA test with Regula | ||
| repl Start an interactive session for testing rules | ||
| init Create a new Regula configuration file | ||
| show Show debug information | ||
| version Print version information | ||
| completion Generate shell autocompletion script | ||
|
|
||
| Flags: | ||
| -h, --help Help for regula | ||
| -v, --verbose Verbose output | ||
| ``` |
There was a problem hiding this comment.
Add a language to the fenced code block.
The command block under “Available Commands” is missing a language tag; markdownlint will flag it.
Suggested fix
-```
+```text
regula [command]
Commands:
run Evaluate rules against infrastructure as code
test Run OPA test with Regula
repl Start an interactive session for testing rules
init Create a new Regula configuration file
show Show debug information
version Print version information
completion Generate shell autocompletion script
Flags:
-h, --help Help for regula
-v, --verbose Verbose output📝 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.
| ``` | |
| regula [command] | |
| Once you've built the binary, execute the following to run tests: | |
| Commands: | |
| run Evaluate rules against infrastructure as code | |
| test Run OPA test with Regula | |
| repl Start an interactive session for testing rules | |
| init Create a new Regula configuration file | |
| show Show debug information | |
| version Print version information | |
| completion Generate shell autocompletion script | |
| Flags: | |
| -h, --help Help for regula | |
| -v, --verbose Verbose output | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
[warning] 155-155: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@README.md` around lines 155 - 170, The fenced code block under the "Available
Commands" section (the block starting with "regula [command]") is missing a
language tag which markdownlint flags; update the opening triple-backticks to
include a language (e.g., change ``` to ```text) so the block becomes ```text
followed by the existing "regula [command]" content and the closing ```; no
other content change is needed.
|
@codrabbitai review |
- fsnotify: Return ErrNotSupported instead of nil for unsupported methods - cli.js: Add comment explaining why preopens '/' is necessary - index.js: Remove misleading format option from docs, add noBuiltIns/noIgnore/varFiles options - Remove unused patches (go-systemd, terraform.patch) - Update build script to remove go-systemd reference Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@README.md`:
- Around line 1-27: Update the README's stated versions: change the OPA line
that currently reads "OPA upgraded from v0.45.1 to v1.12.2 (latest)" to "OPA
upgraded from v0.45.1 to v1.11.0" (remove the "(latest)" tag), and update the Go
line from "Go upgraded to 1.24.11 with stdlib CVE fixes" to "Go upgraded to
1.25.6 with stdlib CVE fixes" so the documentation matches the correct target
versions.
Summary
regula-wasi) that runs via Node.js 18+New Files
build-wasi.shpatches/package.jsonindex.jsrunRegula,validatefunctions)cli.jstest/Usage
Test Results
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation
Tests