// Docker command executor for JavaScript backend
// Uses Node.js child_process via FFI
///|
/// AnyValue type for JavaScript FFI
/// Used to receive JavaScript values that will be converted to MoonBit types
priv type AnyValue
///|
/// Convert AnyValue to Int
extern "js" fn AnyValue::as_int(self : AnyValue) -> Int =
#| (self) => { return self; }
///|
/// Convert AnyValue to String
extern "js" fn AnyValue::as_string(self : AnyValue) -> String =
#| (self) => { return self; }
///|
/// Convert AnyValue to Bool
extern "js" fn AnyValue::as_bool(self : AnyValue) -> Bool =
#| (self) => { return self; }
///|
/// Execute docker command synchronously using Node.js child_process.execSync
///
/// Returns: Array[AnyValue] where [exit_status, output]
extern "js" fn exec_docker_sync(
command : String,
args : Array[String],
) -> Array[AnyValue] =
#| (command, args) => {
#| // Import Node.js child_process module
#| const { spawnSync } = require('child_process');
#|
#| // Use spawnSync to capture both stdout and stderr
#| const result = spawnSync(command, args, {
#| encoding: 'utf8',
#| maxBuffer: 10 * 1024 * 1024 // 10MB buffer
#| });
#|
#| // Combine stdout and stderr
#| const stdout = result.stdout || "";
#| const stderr = result.stderr || "";
#| const output = stdout + stderr;
#|
#| // Check exit status
#| if (result.status === 0 || result.status === null) {
#| // Success: return array [status, combined output]
#| return [0, output];
#| } else if (result.error) {
#| // Error launching process (e.g., command not found)
#| const status = result.status || 1;
#| return [status, result.error.message || output];
#| } else {
#| // Non-zero exit status
#| return [result.status, output];
#| }
#| }
///|
/// Execute docker command using JavaScript FFI
#cfg(target="js")
pub async fn exec_docker(
args : Array[String],
) -> Result[String, TestContainerError] noraise {
let command = "docker"
// Call FFI function - returns Array[AnyValue]
let result = exec_docker_sync(command, args)
// Extract values from array
let status = result[0].as_int()
let output = result[1].as_string()
// Trim output (remove trailing whitespace) and convert to String
let output_view = output.trim_end()
let output_str = "\{output_view}"
if status == 0 {
// Success: return trimmed output
Ok(output_str)
// Check if error indicates Docker CLI is not installed
} else if output_str.contains("ENOENT") ||
output_str.contains("not found") ||
output_str.contains("command not found") {
Err(
DockerCommandFailed(
"Docker CLI is not installed or not available in PATH.\n\nPlease install Docker to use this library:\n- macOS: https://docs.docker.com/desktop/install/mac-install/\n- Windows: https://docs.docker.com/desktop/install/windows-install/\n- Linux: https://docs.docker.com/desktop/install/linux-install/\n\nAfter installation, make sure 'docker' command is available in your PATH.",
),
)
} else {
// Failed: return error with details
Err(DockerCommandFailed(output_str))
}
}