feat(rtl): nodertl — pure-Go Node.js bridge (NODE_EVAL)
Lets PRG use the npm ecosystem with NO C/CGO/Harbour native — faithful to
Five (C→Go). NODE_EVAL(cJs[,cWorkdir[,cInput]]) shells out to `node -e`
(pure os/exec), resolving node from PATH or common Homebrew/usr paths;
user data passes via env FIVE_NODE_INPUT (no code injection); returns
{ok,out,err} JSON. The Go binary stays pure Go and only spawns node when
this RTL is used.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
86
hbrtl_ext/nodertl/nodertl.go
Normal file
86
hbrtl_ext/nodertl/nodertl.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// hbrtl_ext/nodertl — 순수 Go 로 Node.js 서브프로세스를 호출해 npm 생태계를
|
||||
// PRG 에서 쓰게 하는 fivenode 런타임 RTL. C/CGO/Harbour 네이티브 한 줄 없이
|
||||
// (Five = C→Go 재구현 철학 유지) Node 의 전체 라이브러리 생태계에 접근한다.
|
||||
//
|
||||
// Five(Go 바이너리) ──exec──▶ node -e <js> ──require──▶ npm 모듈
|
||||
//
|
||||
// PRG surface:
|
||||
// NODE_EVAL(cJs [, cWorkdir [, cInput]]) -> cJson
|
||||
// cJs : 실행할 JS (결과를 process.stdout 으로 출력)
|
||||
// cWorkdir : require() 해석 기준 디렉터리(node_modules 위치). 비우면
|
||||
// 환경변수 SOLMADE_NODE_DIR 사용.
|
||||
// cInput : 사용자 입력. JS 에 코드로 박지 말고 env FIVE_NODE_INPUT 으로
|
||||
// 전달된다(코드 인젝션 방지). JS 에서 process.env.FIVE_NODE_INPUT 로 읽음.
|
||||
// 반환: {"ok":bool,"out":string,"err":string} JSON 문자열.
|
||||
package nodertl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"five/hbrt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
hbrt.HB_FUNC("NODE_EVAL", nodeEval)
|
||||
}
|
||||
|
||||
// nodeBin: PATH 에 node 가 없어도(예: launchd 의 좁은 PATH) 흔한 위치를 폴백.
|
||||
func nodeBin() string {
|
||||
if p, err := exec.LookPath("node"); err == nil {
|
||||
return p
|
||||
}
|
||||
for _, p := range []string{"/opt/homebrew/bin/node", "/usr/local/bin/node", "/usr/bin/node"} {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func resJSON(ok bool, out, errs string) string {
|
||||
b, _ := json.Marshal(map[string]interface{}{"ok": ok, "out": out, "err": errs})
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func nodeEval(ctx *hbrt.HBContext) {
|
||||
if ctx.PCount() < 1 || !ctx.IsChar(1) {
|
||||
ctx.RetC(resJSON(false, "", "NODE_EVAL(cJs[,cWorkdir[,cInput]]) — missing js"))
|
||||
return
|
||||
}
|
||||
js := ctx.ParC(1)
|
||||
|
||||
workdir := ""
|
||||
if ctx.PCount() >= 2 && ctx.IsChar(2) {
|
||||
workdir = ctx.ParC(2)
|
||||
}
|
||||
if workdir == "" {
|
||||
workdir = os.Getenv("SOLMADE_NODE_DIR")
|
||||
}
|
||||
|
||||
input := ""
|
||||
if ctx.PCount() >= 3 && ctx.IsChar(3) {
|
||||
input = ctx.ParC(3)
|
||||
}
|
||||
|
||||
bin := nodeBin()
|
||||
if bin == "" {
|
||||
ctx.RetC(resJSON(false, "", "node not found (install node; or set PATH)"))
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command(bin, "-e", js)
|
||||
if workdir != "" {
|
||||
cmd.Dir = workdir
|
||||
}
|
||||
cmd.Env = append(os.Environ(), "FIVE_NODE_INPUT="+input)
|
||||
|
||||
var out, errb bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &errb
|
||||
runErr := cmd.Run()
|
||||
|
||||
ctx.RetC(resJSON(runErr == nil, out.String(), errb.String()))
|
||||
}
|
||||
Reference in New Issue
Block a user