diff --git a/pgml-apps/cargo-pgml-components/Cargo.lock b/pgml-apps/cargo-pgml-components/Cargo.lock index fa54e9722..c02281263 100644 --- a/pgml-apps/cargo-pgml-components/Cargo.lock +++ b/pgml-apps/cargo-pgml-components/Cargo.lock @@ -79,7 +79,7 @@ checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "cargo-pgml-components" -version = "0.1.7" +version = "0.1.8" dependencies = [ "anyhow", "clap", diff --git a/pgml-apps/cargo-pgml-components/Cargo.toml b/pgml-apps/cargo-pgml-components/Cargo.toml index 84be9f5b5..ed7e37973 100644 --- a/pgml-apps/cargo-pgml-components/Cargo.toml +++ b/pgml-apps/cargo-pgml-components/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-pgml-components" -version = "0.1.7" +version = "0.1.8" edition = "2021" authors = ["PostgresML "] license = "MIT" diff --git a/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs b/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs index 90b733122..0c9bde514 100644 --- a/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs +++ b/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs @@ -7,7 +7,8 @@ use std::process::Command; use convert_case::{Case, Casing}; -use crate::util::{execute_command, info, unwrap_or_exit, warn}; +use crate::frontend::tools::execute_with_nvm; +use crate::util::{info, unwrap_or_exit, warn}; /// The name of the JS file that imports all other JS files /// created in the modules. @@ -99,7 +100,7 @@ pub fn bundle() { assemble_modules(); // Bundle JavaScript. - unwrap_or_exit!(execute_command( + unwrap_or_exit!(execute_with_nvm( Command::new(JS_COMPILER) .arg(MODULES_FILE) .arg("--file") diff --git a/pgml-apps/cargo-pgml-components/src/frontend/nvm.sh b/pgml-apps/cargo-pgml-components/src/frontend/nvm.sh new file mode 100644 index 000000000..067872416 --- /dev/null +++ b/pgml-apps/cargo-pgml-components/src/frontend/nvm.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm +[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion + +${@} diff --git a/pgml-apps/cargo-pgml-components/src/frontend/sass.rs b/pgml-apps/cargo-pgml-components/src/frontend/sass.rs index dc74cd5d2..c12ba643d 100644 --- a/pgml-apps/cargo-pgml-components/src/frontend/sass.rs +++ b/pgml-apps/cargo-pgml-components/src/frontend/sass.rs @@ -5,7 +5,8 @@ use std::fs::{copy, read_to_string, remove_file, File}; use std::io::Write; use std::process::Command; -use crate::util::{execute_command, info, unwrap_or_exit, warn}; +use crate::frontend::tools::execute_with_nvm; +use crate::util::{info, unwrap_or_exit, warn}; /// The name of the SASS file that imports all other SASS files /// created in the modules. @@ -77,7 +78,7 @@ pub fn bundle() { cleanup_old_bundles(); // Build Sass. - unwrap_or_exit!(execute_command( + unwrap_or_exit!(execute_with_nvm( Command::new(SASS_COMPILER).arg(SASS_FILE).arg(CSS_FILE), )); diff --git a/pgml-apps/cargo-pgml-components/src/frontend/tools.rs b/pgml-apps/cargo-pgml-components/src/frontend/tools.rs index b6b2e785c..c3f19f79d 100644 --- a/pgml-apps/cargo-pgml-components/src/frontend/tools.rs +++ b/pgml-apps/cargo-pgml-components/src/frontend/tools.rs @@ -1,29 +1,102 @@ //! Tools required by us to build stuff. -use crate::util::{error, execute_command, unwrap_or_exit, warn}; +use crate::util::{debug1, error, execute_command, unwrap_or_exit, warn}; +use std::fs::File; +use std::io::Write; use std::process::{exit, Command}; /// Required tools. static TOOLS: &[&str] = &["sass", "rollup"]; +static NVM_EXEC: &'static str = "/tmp/pgml-components-nvm.sh"; +static NVM_SOURCE: &'static str = "https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh"; +static NVM_SOURCE_DOWNLOADED: &'static str = "/tmp/pgml-components-nvm-source.sh"; /// Install any missing tools. pub fn install() { - if let Err(err) = execute_command(Command::new("node").arg("--version")) { - error("Node is not installed. Install it with nvm or your system package manager."); - debug!("{}", err); - exit(1); - } + install_nvm_entrypoint(); + debug!("installed node entrypoint"); + install_node(); + debug!("installed node"); for tool in TOOLS { - match execute_command(Command::new(tool).arg("--version")) { + match execute_with_nvm(Command::new(tool).arg("--version")) { Ok(_) => (), Err(err) => { - debug!("{}", err); + debug1!(err); warn(&format!("installing {}", tool)); - unwrap_or_exit!(execute_command( + unwrap_or_exit!(execute_with_nvm( Command::new("npm").arg("install").arg("-g").arg(tool) )); } } } } + +/// Execute a command making sure that nvm is available. +pub fn execute_with_nvm(command: &mut Command) -> std::io::Result { + let mut cmd = Command::new(NVM_EXEC); + cmd.arg(command.get_program()); + for arg in command.get_args() { + cmd.arg(arg); + } + execute_command(&mut cmd) +} + +/// Install the nvm entrypoint we provide into /tmp +fn install_nvm_entrypoint() { + let mut file = unwrap_or_exit!(File::create(NVM_EXEC)); + unwrap_or_exit!(writeln!(&mut file, "{}", include_str!("nvm.sh"))); + drop(file); + + unwrap_or_exit!(execute_command( + Command::new("chmod").arg("+x").arg(NVM_EXEC) + )); +} + +/// Install node using nvm +fn install_node() { + debug!("installing node"); + // Node is already installed. + if let Ok(_) = execute_with_nvm(Command::new("node").arg("--version")) { + debug!("node is available"); + return; + } + + warn("installing node using nvm"); + + debug!("node is not available"); + + if let Err(err) = execute_command(Command::new("nvm").arg("--version")) { + debug!("nvm is not available"); + debug1!(err); + // Install Node Version Manager. + if let Err(err) = execute_command( + Command::new("curl") + .arg("-Ls") + .arg(NVM_SOURCE) + .arg("-o") + .arg(NVM_SOURCE_DOWNLOADED), + ) { + debug!("curl is not available"); + error("couldn't not download nvm from Github, please do so manually before proceeding"); + debug1!(err); + exit(1); + } else { + if let Err(err) = execute_command(Command::new("bash").arg(NVM_SOURCE_DOWNLOADED)) { + error("couldn't install nvm, please do so manually before proceeding"); + debug1!(err); + exit(1); + } else { + warn("installed nvm"); + } + } + } + + if let Err(err) = execute_with_nvm(Command::new("nvm").arg("install").arg("stable")) { + error("couldn't install Node, please do so manually before proceeding"); + debug1!(err); + exit(1); + } else { + warn("installed node") + } +} diff --git a/pgml-apps/cargo-pgml-components/src/main.rs b/pgml-apps/cargo-pgml-components/src/main.rs index 6f2a57a6d..5a47b1caf 100644 --- a/pgml-apps/cargo-pgml-components/src/main.rs +++ b/pgml-apps/cargo-pgml-components/src/main.rs @@ -2,7 +2,7 @@ use clap::{Args, Parser, Subcommand}; use std::env::{current_dir, set_current_dir}; -use std::fs::{create_dir_all}; +use std::fs::create_dir_all; use std::path::Path; #[macro_use] @@ -62,17 +62,23 @@ fn main() { let cli = Cli::parse(); match cli.subcomand { - CargoSubcommands::PgmlComponents(pgml_commands) => match pgml_commands.command { - Commands::Bundle {} => bundle(pgml_commands.project_path), - Commands::Add(command) => match command { - AddCommands::Component { name } => crate::frontend::components::add(&name, pgml_commands.overwrite), - }, - }, + CargoSubcommands::PgmlComponents(pgml_commands) => { + validate_project(pgml_commands.project_path); + match pgml_commands.command { + Commands::Bundle {} => bundle(), + Commands::Add(command) => match command { + AddCommands::Component { name } => { + crate::frontend::components::add(&name, pgml_commands.overwrite) + } + }, + } + } } } -/// Bundle SASS and JavaScript into neat bundle files. -fn bundle(project_path: Option) { +fn validate_project(project_path: Option) { + debug!("validating project directory"); + // Validate that the required project paths exist. let cwd = if let Some(project_path) = project_path { project_path @@ -92,9 +98,13 @@ fn bundle(project_path: Option) { } unwrap_or_exit!(set_current_dir(path)); +} + +/// Bundle SASS and JavaScript into neat bundle files. +fn bundle() { frontend::sass::bundle(); frontend::javascript::bundle(); frontend::components::update_modules(); - info("Bundle complete"); + info("bundle complete"); } diff --git a/pgml-apps/cargo-pgml-components/src/util.rs b/pgml-apps/cargo-pgml-components/src/util.rs index 7205c4a9b..ec3d4aa3d 100644 --- a/pgml-apps/cargo-pgml-components/src/util.rs +++ b/pgml-apps/cargo-pgml-components/src/util.rs @@ -1,8 +1,8 @@ use owo_colors::OwoColorize; use std::fs::File; -use std::io::Write; +use std::io::{ErrorKind, Write}; use std::path::Path; -use std::process::{exit, Command}; +use std::process::Command; macro_rules! unwrap_or_exit { ($i:expr) => { @@ -17,6 +17,13 @@ macro_rules! unwrap_or_exit { }; } +macro_rules! debug1 { + ($e:expr) => { + debug!("{}:{}:{} {}", file!(), line!(), column!(), $e); + }; +} + +pub(crate) use debug1; pub(crate) use unwrap_or_exit; pub fn info(value: &str) { @@ -43,12 +50,14 @@ pub fn execute_command(command: &mut Command) -> std::io::Result { let stdout = String::from_utf8_lossy(&output.stderr).to_string(); if !output.status.success() { - error!( + let error = String::from_utf8_lossy(&output.stderr).to_string(); + debug!( "{} failed: {}", command.get_program().to_str().unwrap(), - String::from_utf8_lossy(&output.stderr).to_string(), + error, ); - exit(1); + + return Err(std::io::Error::new(ErrorKind::Other, error)); } if !stderr.is_empty() {