package jstest import ( "fmt" "os" "os/exec" "path/filepath" "sync" "testing" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/repo" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" "github.com/go-json-experiment/json" ) const loaderScript = `import script from "./script.mjs"; process.stdout.write(JSON.stringify(await script(...process.argv.slice(2))));` var getNodeExeOnce = sync.OnceValue(func() string { const exeName = "node" exe, err := exec.LookPath(exeName) if err != nil { return "" } return exe }) // EvalNodeScript imports a Node.js script that default-exports a single function, // calls it with the provided arguments, and unmarshals the JSON-stringified // awaited return value into T. func EvalNodeScript[T any](t testing.TB, script string, dir string, args ...string) (result T, err error) { return evalNodeScript[T](t, script, loaderScript, dir, args...) } // EvalNodeScriptWithTS is like EvalNodeScript, but provides the TypeScript // library to the script as the first argument. func EvalNodeScriptWithTS[T any](t testing.TB, script string, dir string, args ...string) (result T, err error) { if dir == "" { dir = t.TempDir() } tsSrc := tspath.NormalizePath(filepath.Join(repo.RootPath, "node_modules/typescript/lib/typescript.js")) if tsSrc[0] == '/' { tsSrc = "file://" + tsSrc } else { tsSrc = "file:///" + tsSrc } tsLoaderScript := fmt.Sprintf(`import script from "./script.mjs"; import * as ts from "%s"; process.stdout.write(JSON.stringify(await script(ts, ...process.argv.slice(2))));`, tsSrc) return evalNodeScript[T](t, script, tsLoaderScript, dir, args...) } func SkipIfNoNodeJS(t testing.TB) { t.Helper() if getNodeExeOnce() == "" { t.Skip("Node.js not found") } } func evalNodeScript[T any](t testing.TB, script string, loader string, dir string, args ...string) (result T, err error) { t.Helper() exe := getNodeExe(t) scriptPath := dir + "/script.mjs" if err = os.WriteFile(scriptPath, []byte(script), 0o644); err != nil { return result, err } loaderPath := dir + "/loader.mjs" if err = os.WriteFile(loaderPath, []byte(loader), 0o644); err != nil { return result, err } execArgs := make([]string, 0, 1+len(args)) execArgs = append(execArgs, loaderPath) execArgs = append(execArgs, args...) execCmd := exec.Command(exe, execArgs...) execCmd.Dir = dir output, err := execCmd.CombinedOutput() if err != nil { return result, fmt.Errorf("failed to run node: %w\n%s", err, output) } if err = json.Unmarshal(output, &result); err != nil { return result, fmt.Errorf("failed to unmarshal JSON output: %w", err) } return result, nil } func getNodeExe(t testing.TB) string { if exe := getNodeExeOnce(); exe != "" { return exe } t.Fatal("Node.js not found") return "" }