///|
pub(all) suberror InstallError {
MissingHome
InvalidSpec(String)
InvalidArgs(String)
IoError(String)
CommandFailed(cmd~ : String, code~ : Int)
ArtifactNotFound(String)
RegistryIndexNotFound(String)
RegistryIndexInvalid(String)
UnsupportedTarget(String)
} derive(Eq, Show)
///|
pub fn artifact_path(
repo_dir : String,
package_path : String,
target : Target,
) -> String {
let build_dir = join_path(
join_path(
join_path(join_path(repo_dir, "target"), Target::to_moon_arg(target)),
"release",
),
"build",
)
let package_dir = join_path(build_dir, package_path)
let file_name = package_file_name(package_path, target)
join_path(package_dir, file_name)
}
///|
pub fn install(
spec : Spec,
name : String,
target : Target,
) -> Result[Unit, InstallError] {
let home = match @sys.get_env_var("HOME") {
Some(value) => value
None => return Err(InstallError::MissingHome)
}
let root_dir = join_path(home, ".mooninstall")
let bin_dir = join_path(root_dir, "bin")
let src_root = join_path(root_dir, "src")
let owner_dir = join_path(src_root, spec.owner)
let repo_dir = join_path(owner_dir, spec.repo)
let tmp_root = join_path(join_path(root_dir, "tmp"), spec.owner)
let tmp_dir = join_path(tmp_root, spec.repo)
let version = match resolve_version(spec, home, tmp_dir) {
Ok(value) => value
Err(err) => return Err(err)
}
let zip_path = join_path(
join_path(join_path(join_path(home, ".moon"), "registry"), "cache"),
join_path(spec.owner, join_path(spec.repo, version + ".zip")),
)
let repos_dir = join_path(
tmp_dir,
join_path(".repos", join_path(spec.owner, join_path(spec.repo, version))),
)
ensure_dir_recursive(bin_dir) catch {
err =>
return Err(
InstallError::IoError("ensure bin dir \{bin_dir}: \{err.to_string()}"),
)
}
ensure_dir_recursive(owner_dir) catch {
err =>
return Err(
InstallError::IoError("ensure owner dir \{owner_dir}: \{err.to_string()}"),
)
}
match run_shell(shell_join(["rm", "-rf", repo_dir])) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
if !@fs.path_exists(zip_path) {
ensure_dir_recursive(tmp_dir) catch {
err =>
return Err(
InstallError::IoError("ensure tmp dir \{tmp_dir}: \{err.to_string()}"),
)
}
if @fs.path_exists(repos_dir) {
match run_shell(shell_join(["rm", "-rf", repos_dir])) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
}
let fetch_target = "\{spec.owner}/\{spec.repo}@\{version}"
match run_shell_in_dir(tmp_dir, ["moon", "fetch", fetch_target]) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
}
if @fs.path_exists(zip_path) {
ensure_dir_recursive(repo_dir) catch {
err =>
return Err(
InstallError::IoError("ensure repo dir \{repo_dir}: \{err.to_string()}"),
)
}
match run_shell(shell_join(["unzip", "-q", zip_path, "-d", repo_dir])) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
} else {
if !@fs.path_exists(repos_dir) {
return Err(InstallError::ArtifactNotFound(repos_dir))
}
match run_shell(shell_join(["cp", "-R", repos_dir, repo_dir])) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
}
match
run_shell_in_dir(repo_dir, [
"moon",
"build",
"--target",
Target::to_moon_arg(target),
"--release",
]) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
let mut artifact = artifact_path(repo_dir, spec.package_path, target)
if !@fs.path_exists(artifact) {
match find_artifact(repo_dir, spec.package_name, target) {
Some(found) => artifact = found
None => return Err(InstallError::ArtifactNotFound(artifact))
}
}
let alias_path = join_path(bin_dir, name)
match target {
Target::Js => {
let script = "#!/usr/bin/env bash\n" +
"node " +
shell_escape(artifact) +
" \"$@\"\n"
@fs.write_string_to_file(alias_path, script) catch {
err => return Err(InstallError::IoError(err.to_string()))
}
match run_shell(shell_join(["chmod", "+x", alias_path])) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
}
Target::Native =>
match run_shell(shell_join(["ln", "-sf", artifact, alias_path])) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
}
print_path_hint(bin_dir)
Ok(())
}
///|
pub fn uninstall(name : String) -> Result[Unit, InstallError] {
let home = match @sys.get_env_var("HOME") {
Some(value) => value
None => return Err(InstallError::MissingHome)
}
let bin_dir = join_path(join_path(home, ".mooninstall"), "bin")
let alias_path = join_path(bin_dir, name)
if @fs.path_exists(alias_path) {
@fs.remove_file(alias_path) catch {
err => return Err(InstallError::IoError(err.to_string()))
}
}
Ok(())
}
///|
fn resolve_version(
spec : Spec,
home : String,
tmp_dir : String,
) -> Result[String, InstallError] {
match spec.version {
Some(version) =>
match select_version(Some(version), version) {
Ok(value) => Ok(value)
Err(err) =>
Err(InstallError::InvalidSpec("invalid requested version: \{err.to_string()}"))
}
None => {
let index_path = join_path(
join_path(
join_path(join_path(join_path(home, ".moon"), "registry"), "index"),
"user",
),
join_path(spec.owner, spec.repo + ".index"),
)
if !@fs.path_exists(index_path) {
ensure_dir_recursive(tmp_dir) catch {
err => return Err(InstallError::IoError(err.to_string()))
}
match run_shell_in_dir(tmp_dir, ["moon", "update"]) {
Ok(_) => ()
Err(ExecError::CommandFailed(cmd~, code~)) =>
return Err(InstallError::CommandFailed(cmd~, code~))
}
}
if !@fs.path_exists(index_path) {
return Err(InstallError::RegistryIndexNotFound(index_path))
}
let content = @fs.read_file_to_string(index_path) catch {
err =>
return Err(
InstallError::IoError("read registry index: \{index_path}: \{err.to_string()}"),
)
}
match parse_registry_latest_version(content) {
Ok(latest) =>
match select_version(None, latest) {
Ok(version) => Ok(version)
Err(err) =>
Err(InstallError::InvalidSpec("invalid latest version: \{err.to_string()}"))
}
Err(_) => Err(InstallError::RegistryIndexInvalid(index_path))
}
}
}
}
///|
fn print_path_hint(bin_dir : String) -> Unit {
match @sys.get_env_var("PATH") {
Some(path) => {
if !path.contains(bin_dir) {
println("note: add to PATH: \{bin_dir}")
println(" export PATH=\"\{bin_dir}:$PATH\"")
}
}
None => {
println("note: add to PATH: \{bin_dir}")
println(" export PATH=\"\{bin_dir}:$PATH\"")
}
}
}
///|
fn package_file_name(package_path : String, target : Target) -> String {
let name = basename(package_path)
name + "." + Target::artifact_ext(target)
}
///|
fn basename(path : String) -> String {
match path.split("/").last() {
Some(name) => name.to_string()
None => path
}
}
///|
fn find_artifact(
repo_dir : String,
package_name : String,
target : Target,
) -> String? {
let build_dir = join_path(
join_path(
join_path(join_path(repo_dir, "target"), Target::to_moon_arg(target)),
"release",
),
"build",
)
let needle = package_name + "." + Target::artifact_ext(target)
find_file_recursive(build_dir, needle)
}
///|
fn find_file_recursive(base : String, file_name : String) -> String? {
if !@fs.path_exists(base) {
return None
}
try @fs.is_dir(base) catch {
_ => None
} noraise {
is_dir =>
if is_dir {
try @fs.read_dir(base) catch {
_ => None
} noraise {
entries => {
for name in entries {
let path = join_path(base, name)
match find_file_recursive(path, file_name) {
Some(found) => return Some(found)
None => ()
}
}
None
}
}
} else if basename(base) == file_name {
Some(base)
} else {
None
}
}
}