///|
/// Resolves the current executable path before running `on_current_path`.
fn with_current_executable_path(
current_path_result : () -> Result[String, ReplaceSelfError],
on_current_path : (String) -> Result[Unit, ReplaceSelfError],
) -> Result[Unit, ReplaceSelfError] {
match current_path_result() {
Ok(current_path) => on_current_path(current_path)
Err(error) => Err(error)
}
}
///|
/// Replaces the current executable through injected dependencies.
fn replace_self_with(
platform : Platform,
new_executable : String,
current_path_result : () -> Result[String, ReplaceSelfError],
replace_action : (String) -> Result[Unit, ReplaceSelfError],
) -> Result[Unit, ReplaceSelfError] {
with_current_executable_path(current_path_result, fn(current_path) {
match validate_replacement_path(platform, new_executable, current_path) {
Ok(validated_path) => replace_action(validated_path)
Err(error) => Err(error)
}
})
}
///|
/// Deletes the current executable through injected dependencies.
fn delete_self_with(
platform : Platform,
current_path_result : () -> Result[String, ReplaceSelfError],
delete_action : () -> Result[Unit, ReplaceSelfError],
) -> Result[Unit, ReplaceSelfError] {
if platform == Unsupported {
return Err(UnsupportedPlatform(platform))
}
with_current_executable_path(current_path_result, fn(_) { delete_action() })
}
///|
/// Performs the native replace-self call.
fn replace_self_native_result(
validated_path : String,
) -> Result[Unit, ReplaceSelfError] {
unit_result_from_status(
replace_self_ffi(@utf8.encode(validated_path)),
"replace self",
"Failed to replace the current executable",
)
}
///|
/// Performs the native delete-self call.
fn delete_self_native_result() -> Result[Unit, ReplaceSelfError] {
unit_result_from_status(
delete_self_ffi(),
"delete self",
"Failed to delete the current executable",
)
}
///|
/// Replaces the currently running executable with `new_executable`.
///
/// Use this when an application has already downloaded a new binary and wants
/// the current process image on disk to be swapped for that replacement.
/// The replacement file is treated as the source of truth for the next launch
/// of the program.
///
/// # Arguments
///
/// - `new_executable`: absolute path to the replacement executable file.
/// Leading and trailing whitespace is trimmed before validation. The
/// replacement file must already exist and must not be the same path as the
/// current executable.
///
/// # Returns
///
/// Returns `Ok(())` after the replacement has completed on Unix platforms, or
/// after the replacement helper has been scheduled successfully on Windows.
/// A successful result never includes the final executable path because the
/// current executable location is always the destination.
///
/// # Platform behavior
///
/// - Linux and macOS perform the replacement immediately with an atomic rename.
/// - Windows launches a detached helper script, then completes the replacement
/// after the current process exits and the executable file is unlocked.
///
/// # Notes
///
/// - On Unix hosts, the replacement source path is moved into the current
/// executable path, so the original source path typically disappears after a
/// successful replacement.
/// - On Windows, a successful return means the replacement was only scheduled
/// successfully. Callers should exit soon after `Ok(())` so the helper can
/// take over and finish the move.
///
/// # Errors
///
/// Returns `Err(ReplaceSelfError)` when:
///
/// - `EmptyReplacementPath`: the path is empty after trimming whitespace
/// - `RelativeReplacementPath`: the path is not absolute for the current
/// platform
/// - `ReplacementMatchesCurrentExecutable`: the replacement path equals the
/// current executable path
/// - `ExecutablePathUnavailable`: the runtime cannot determine the current
/// executable path
/// - `UnsupportedPlatform`: the host platform is unsupported by the native shim
/// - `NativeFailure`: the operating system rejects the replacement request
///
/// # Example
/// ```mbt nocheck
/// match @replace_self.replace_self("/tmp/app.next") {
/// Ok(()) => ()
/// Err(error) => println(error)
/// }
/// ```
pub fn replace_self(new_executable : String) -> Result[Unit, ReplaceSelfError] {
replace_self_with(
current_platform(),
new_executable,
current_executable_path_result,
replace_self_native_result,
)
}
///|
/// Deletes the currently running executable.
///
/// Use this when a process should remove its own executable file after it has
/// finished running, such as a one-shot bootstrapper or an uninstall helper.
/// This is useful when the executable should clean up its on-disk image as part
/// of its own shutdown flow.
///
/// # Returns
///
/// Returns `Ok(())` after the file has been unlinked on Unix platforms, or
/// after the deletion helper has been scheduled successfully on Windows.
/// The function does not report whether the current process has already exited;
/// it only reports whether the delete operation or delayed delete setup
/// succeeded.
///
/// # Platform behavior
///
/// - Linux and macOS unlink the file immediately.
/// - Windows launches a detached helper script that waits for the process to
/// exit, then removes the executable file.
///
/// # Notes
///
/// - On Unix hosts, unlinking removes the directory entry immediately even
/// though the current process may continue running until it exits.
/// - On Windows, a successful return means deletion was only scheduled
/// successfully. Callers should exit soon after `Ok(())` so the helper can
/// remove the file.
///
/// # Errors
///
/// Returns `Err(ReplaceSelfError)` when:
///
/// - `ExecutablePathUnavailable`: the runtime cannot determine the current
/// executable path
/// - `UnsupportedPlatform`: the host platform is unsupported by the native shim
/// - `NativeFailure`: the operating system rejects the deletion request
///
/// # Example
/// ```mbt nocheck
/// match @replace_self.delete_self() {
/// Ok(()) => ()
/// Err(error) => println(error)
/// }
/// ```
pub fn delete_self() -> Result[Unit, ReplaceSelfError] {
delete_self_with(
current_platform(),
current_executable_path_result,
delete_self_native_result,
)
}