From b945e72c235fb3c292e585a26b128808e5ac53e8 Mon Sep 17 00:00:00 2001 From: David Alsh Date: Sat, 2 Mar 2024 13:28:36 +1100 Subject: [PATCH] added cli args --- crates/mach/src/args.rs | 32 --- crates/mach/src/cmd/build/config.rs | 260 ++++++++++++++++++++++++- crates/mach/src/cmd/build/main.rs | 126 +++++++++++- crates/mach/src/cmd/version/config.rs | 4 - crates/mach/src/cmd/version/main.rs | 41 +++- crates/mach/src/cmd/version/mod.rs | 2 - crates/mach/src/config.rs | 243 ----------------------- crates/mach/src/main.rs | 12 +- npm/mach/_scripts/postinstall/bin.bash | 7 +- 9 files changed, 429 insertions(+), 298 deletions(-) delete mode 100644 crates/mach/src/args.rs delete mode 100644 crates/mach/src/cmd/version/config.rs delete mode 100644 crates/mach/src/config.rs diff --git a/crates/mach/src/args.rs b/crates/mach/src/args.rs deleted file mode 100644 index 9cbb3f4e..00000000 --- a/crates/mach/src/args.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::path::PathBuf; - -use clap::Parser; - -/// Simple program to greet a person -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -pub struct Args { - /// Input file to build - pub entry: Vec, - - /// Output folder - #[arg(short = 'o', long = "dist", default_value = "dist")] - pub out_folder: Option, - - /// Enable optimization - #[arg( - short = 'z', - long = "optimize", - env = "MACH_OPTIMIZE", - default_value = "true" - )] - pub optimize: Option, - - /// How many threads to use for compilation - #[arg(short = 't', long = "threads", env = "MACH_THREADS")] - pub threads: Option, - - /// How many Node.js workers to spawn for plugins - #[arg(long = "node-workers", env = "MACH_NODE_WORKERS", default_value = "1")] - pub node_workers: Option, -} diff --git a/crates/mach/src/cmd/build/config.rs b/crates/mach/src/cmd/build/config.rs index 84ffb7ca..e7b12b7f 100644 --- a/crates/mach/src/cmd/build/config.rs +++ b/crates/mach/src/cmd/build/config.rs @@ -1,4 +1,262 @@ +use std::path::PathBuf; +use std::collections::HashMap; +use std::fs; +use std::time::SystemTime; + use clap::Parser; +use normalize_path::NormalizePath; + +use crate::platform::public::Config; +use crate::platform::public::Machrc; + +type FileIndex = HashMap>; #[derive(Parser, Debug)] -pub struct BuildCommand {} \ No newline at end of file +pub struct BuildCommand { + /// Input file to build + pub entry: Vec, + + /// Output folder + #[arg(short = 'o', long = "dist", default_value = "dist")] + pub out_folder: PathBuf, + + /// Disable optimizations + #[arg(long = "no-optimize")] + pub no_optimize: bool, + + /// How many threads to use for compilation + #[arg(short = 't', long = "threads", env = "MACH_THREADS")] + pub threads: Option, + + /// How many Node.js workers to spawn for plugins + #[arg(long = "node-workers", env = "MACH_NODE_WORKERS", default_value = "1")] + pub node_workers: Option, +} + +pub fn parse_config(command: BuildCommand) -> Result { + let start_time = SystemTime::now(); + + // Ignore multiple entries for now + let entry_point = get_absolute_path(&command.entry[0].clone()); + + // Find these points of interest + let file_index = find_file_by_name( + &entry_point, + &["package.json", ".machrc", "pnpm-workspace.yaml"], + ); + + let machrc = parse_machrc(&file_index).expect("Cannot parse .machrc"); + + let mut node_workers = command.node_workers.unwrap(); + if !machrc.engines.contains(&"node".to_string()) { + node_workers = 0; + } + + // Project root is the location of the first package.json + let package_json_path = file_index + .get("package.json") + .unwrap() + .get(0) + .unwrap() + .clone(); + let package_json = parse_json_file(&package_json_path).expect("Cannot parse package.json"); + + let project_root = package_json_path.parent().unwrap().to_path_buf(); + + let dist_dir = { + let dist_dir_arg = command.out_folder; + if dist_dir_arg.is_absolute() { + dist_dir_arg + } else { + project_root.join(dist_dir_arg).normalize() + } + }; + + let threads = { + if let Some(threads) = command.threads { + threads + } else { + num_cpus::get() + } + }; + + let env = { + let mut vars = HashMap::::new(); + for (k, v) in std::env::vars() { + vars.insert(k, v); + } + vars + }; + + return Ok(Config { + start_time, + entry_point, + dist_dir, + // TODO + workspace_root: None, + // TODO + workspace_kind: None, + project_root, + package_json, + machrc, + threads, + node_workers, + optimize: !command.no_optimize, + env, + }); +} + +fn parse_machrc(file_index: &FileIndex) -> Result { + let Some(mach_configs) = file_index.get(".machrc") else { + return Ok(Machrc::default()); + }; + + if mach_configs.len() == 0 { + return Ok(Machrc::default()); + } + + let file_path = mach_configs[0].clone(); + + let mut mach_config = Machrc { + is_default: false, + file_path, + engines: vec!["mach".to_string()], + resolvers: None, + transformers: None, + }; + + let Ok(json_file) = fs::read_to_string(&mach_config.file_path) else { + return Err("Unable to read file".to_string()); + }; + + let Ok(json) = serde_json::from_str::(&json_file) else { + return Err("Unable to parse json".to_string()); + }; + + if json_file.contains("\"node:") { + mach_config.engines.push("node".to_string()); + } + + if let Some(resolvers_value) = json.get("resolvers") { + let mut resolvers = Vec::::new(); + let Some(resolvers_values) = resolvers_value.as_array() else { + return Err("'resolvers' should be array".to_string()); + }; + for resolver_value in resolvers_values { + let Some(resolver_value) = resolver_value.as_str() else { + return Err("'resolvers[n]' should be string".to_string()); + }; + resolvers.push(resolver_value.to_string()); + } + mach_config.resolvers = Some(resolvers); + }; + + if let Some(transformers_value) = json.get("transformers") { + let mut transformers = HashMap::>::new(); + let Some(transformers_value) = transformers_value.as_object() else { + return Err("'transformers' should be object".to_string()); + }; + for (key, value) in transformers_value { + let mut values = Vec::::new(); + let Some(value) = value.as_array() else { + return Err("'transformers[key]' should be array".to_string()); + }; + for value in value { + let Some(value) = value.as_str() else { + return Err("'transformers[key][n]' should be string".to_string()); + }; + values.push(value.to_string()); + } + transformers.insert(key.clone(), values); + } + mach_config.transformers = Some(transformers); + } + + return Ok(mach_config); +} + +fn parse_json_file(target: &PathBuf) -> Result { + let Ok(json_file) = fs::read_to_string(target) else { + return Err("Unable to read file".to_string()); + }; + let Ok(json) = serde_json::from_str::(&json_file) else { + return Err("Unable to parse json".to_string()); + }; + return Ok(json); +} + +// fn parse_yaml_file(target: &PathBuf) -> Result { +// let Ok(yaml_file) = fs::read_to_string(target) else { +// return Err("Unable to read file".to_string()); +// }; +// let Ok(yaml) = serde_yaml::from_str::(&yaml_file) else { +// return Err("Unable to parse json".to_string()); +// }; +// return Ok(yaml); +// } + +fn find_file_by_name( + start_path: &PathBuf, + targets: &[&str], +) -> FileIndex { + let mut found = FileIndex::new(); + for target in targets { + found.insert(target.to_string(), vec![]); + } + + let mut current_test = start_path.clone(); + + let mut paths_to_test = Vec::::new(); + paths_to_test.push(current_test.clone()); + while current_test.pop() { + paths_to_test.push(current_test.clone()) + } + paths_to_test.reverse(); + + while let Some(path) = paths_to_test.pop() { + if path.is_file() { + let Some(file_name) = path.file_name() else { + continue; + }; + let file_name = file_name.to_str().unwrap(); + + for target in targets { + let target = target.to_string(); + if file_name == target { + found.get_mut(&target).unwrap().push(path.join(target)); + continue; + } + } + } else if path.is_dir() { + let Ok(ls) = path.read_dir() else { + panic!("failed to ls"); + }; + + for item in ls { + let Ok(item) = item else { + panic!("failed to ls"); + }; + + let file_name = item.file_name(); + let file_name = file_name.to_str().unwrap(); + + for target in targets { + let target = target.to_string(); + if file_name == target { + found.get_mut(&target).unwrap().push(path.join(target)); + continue; + } + } + } + } + } + return found; +} + +fn get_absolute_path(target: &PathBuf) -> PathBuf { + let mut file_path = PathBuf::from(&target); + if !file_path.is_absolute() { + file_path = std::env::current_dir().unwrap().join(file_path); + } + return file_path.normalize(); +} diff --git a/crates/mach/src/cmd/build/main.rs b/crates/mach/src/cmd/build/main.rs index 647c10b5..90f530cd 100644 --- a/crates/mach/src/cmd/build/main.rs +++ b/crates/mach/src/cmd/build/main.rs @@ -1,5 +1,127 @@ -use super::BuildCommand; +use std::sync::Arc; + +use crate::platform::adapters::node_js::NodeAdapter; +use crate::platform::bundling::bundle; +use crate::platform::emit::emit; +use crate::platform::packaging::package; +use crate::platform::plugins::load_plugins; +use crate::platform::public::AssetGraph; +use crate::platform::public::AssetMap; +use crate::platform::public::BundleGraph; +use crate::platform::public::Bundles; +use crate::platform::public::Config; +use crate::platform::public::DependencyMap; +use crate::platform::public::Packages; +use crate::platform::transformation::transform; + +use super::{parse_config, BuildCommand}; + +async fn main_async(config: Config) { + // Bundle state + let mut asset_map = AssetMap::new(); + let mut dependency_map = DependencyMap::new(); + let mut asset_graph = AssetGraph::new(); + let mut bundles = Bundles::new(); + let mut bundle_graph = BundleGraph::new(); + let mut packages = Packages::new(); + + // Adapters + let node_adapter = Arc::new(NodeAdapter::new(config.node_workers).await); + + // TODO move this into a "reporter" plugin + println!("Entry: {}", config.entry_point.to_str().unwrap()); + println!("Root: {}", config.project_root.to_str().unwrap()); + if !&config.machrc.is_default { + println!( + "Mach Config: {}", + config.machrc.file_path.to_str().unwrap() + ); + } else { + println!("Mach Config: Default"); + } + println!("Out Dir: {}", config.dist_dir.to_str().unwrap()); + println!("Optimize: {}", config.optimize); + println!("Threads: {}", config.threads); + println!("Node Workers: {}", config.node_workers); + + // Initialize plugins + let Ok(plugins) = load_plugins(&config.machrc, node_adapter.clone()).await else { + panic!("Unable to initialize plugins"); + }; + + // This phase reads source files and transforms them. New imports + // are discovered as files are parsed, looping until no more imports exist + if let Err(err) = transform( + &config, + &mut asset_map, + &mut dependency_map, + &mut asset_graph, + &plugins, + ) + .await + { + println!("Transformation Error"); + println!("{}", err); + return; + } + + println!("Assets: {}", asset_map.len()); + + // dbg!(&asset_map); + // dbg!(&asset_graph); + // dbg!(&dependency_map); + + if let Err(err) = bundle( + &config, + &mut asset_map, + &mut dependency_map, + &mut asset_graph, + &mut bundles, + &mut bundle_graph, + ) { + println!("Bundling Error"); + println!("{}", err); + return; + } + + // dbg!(&bundles); + // dbg!(&bundle_graph); + + if let Err(err) = package( + &config, + &mut asset_map, + &mut dependency_map, + &mut asset_graph, + &mut bundles, + &mut bundle_graph, + &mut packages, + ) { + println!("Packaging Error"); + println!("{}", err); + return; + } + + // dbg!(&packages); + + if let Err(err) = emit(&config, &mut bundles, &mut packages) { + println!("Packaging Error"); + println!("{}", err); + return; + } + + println!( + "Finished in: {:.3}s", + config.start_time.elapsed().unwrap().as_nanos() as f64 / 1_000_000 as f64 / 1000 as f64 + ); +} + pub fn main(command: BuildCommand) { - println!("running build"); + let config = parse_config(command).expect("Failed to init config"); + tokio::runtime::Builder::new_multi_thread() + .worker_threads(config.threads) + .enable_all() + .build() + .unwrap() + .block_on(main_async(config)); } diff --git a/crates/mach/src/cmd/version/config.rs b/crates/mach/src/cmd/version/config.rs deleted file mode 100644 index 68f95a36..00000000 --- a/crates/mach/src/cmd/version/config.rs +++ /dev/null @@ -1,4 +0,0 @@ -use clap::Parser; - -#[derive(Parser, Debug)] -pub struct VersionCommand {} diff --git a/crates/mach/src/cmd/version/main.rs b/crates/mach/src/cmd/version/main.rs index 704980e7..c1db4edd 100644 --- a/crates/mach/src/cmd/version/main.rs +++ b/crates/mach/src/cmd/version/main.rs @@ -1,6 +1,8 @@ use std::fs; +use std::path::PathBuf; -use super::VersionCommand; +use clap::Parser; +use normalize_path::NormalizePath; #[allow(non_upper_case_globals)] const color_red: &str = "\x1B[31m"; @@ -15,7 +17,21 @@ const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); const VERSION: &str = env!("CARGO_PKG_VERSION"); const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY"); -pub fn main(_command: VersionCommand) { +#[derive(Parser, Debug)] +pub struct VersionCommand {} + +pub fn main() { + let exe_path = std::env::current_exe().expect("Cannot find executable path"); + let possible_package_json = exe_path.join("../../../package.json").normalize(); + let mut npm_version = None::; + if possible_package_json.exists() { + let package_json = parse_json_file(&possible_package_json).expect("package.json malformed"); + if let Some(version) = package_json.get("version") { + let version = version.as_str().expect("package.json#version is invalid type"); + npm_version = Some(version.to_string()); + } + } + print!(r#"{color_red}{style_bold}"#); println!(r#"___ ___ _ "#); println!(r#"| \/ | | | "#); @@ -25,7 +41,22 @@ pub fn main(_command: VersionCommand) { println!(r#"\_| |_/\__,_|\___|_| |_|"#); print!(r#"{color_reset}{style_reset}"#); println!(r#""#); - println!(r#"{style_bold}Description{style_reset} {DESCRIPTION}"#); - println!(r#"{style_bold}Repository{style_reset} {REPOSITORY}"#); - println!(r#"{style_bold}Version{style_reset} {VERSION}"#); + println!(r#"{style_bold}Description{style_reset} {DESCRIPTION}"#); + println!(r#"{style_bold}Repository{style_reset} {REPOSITORY}"#); + if let Some(npm_version) = npm_version { + println!(r#"{style_bold}NPM Version{style_reset} {npm_version}"#); + println!(r#"{style_bold}Bin Version{style_reset} {VERSION}"#); + } else { + println!(r#"{style_bold}Version{style_reset} {VERSION}"#); + } } + +fn parse_json_file(target: &PathBuf) -> Result { + let Ok(json_file) = fs::read_to_string(target) else { + return Err("Unable to read file".to_string()); + }; + let Ok(json) = serde_json::from_str::(&json_file) else { + return Err("Unable to parse json".to_string()); + }; + return Ok(json); +} \ No newline at end of file diff --git a/crates/mach/src/cmd/version/mod.rs b/crates/mach/src/cmd/version/mod.rs index 70b2ebac..d890082b 100644 --- a/crates/mach/src/cmd/version/mod.rs +++ b/crates/mach/src/cmd/version/mod.rs @@ -1,5 +1,3 @@ mod main; -mod config; pub use crate::cmd::version::main::*; -pub use crate::cmd::version::config::*; diff --git a/crates/mach/src/config.rs b/crates/mach/src/config.rs deleted file mode 100644 index e1e82ca4..00000000 --- a/crates/mach/src/config.rs +++ /dev/null @@ -1,243 +0,0 @@ -use std::collections::HashMap; -use std::fs; -use std::path::PathBuf; -use std::time::SystemTime; - -use clap::Parser; -use normalize_path::NormalizePath; - -use crate::args::Args; -use crate::platform::public::Config; -use crate::platform::public::Machrc; - -type FileIndex = HashMap>; - -pub fn parse_config() -> Result { - let start_time = SystemTime::now(); - - let args = Args::parse(); - - // Ignore multiple entries for now - let entry_point = get_absolute_path(&args.entry[0].clone()); - - // Find these points of interest - let file_index = find_file_by_name( - &entry_point, - &["package.json", ".machrc", "pnpm-workspace.yaml"], - ); - - let machrc = parse_machrc(&file_index).expect("Cannot parse .machrc"); - - let mut node_workers = args.node_workers.unwrap(); - if !machrc.engines.contains(&"node".to_string()) { - node_workers = 0; - } - - // Project root is the location of the first package.json - let package_json_path = file_index - .get("package.json") - .unwrap() - .get(0) - .unwrap() - .clone(); - let package_json = parse_json_file(&package_json_path).expect("Cannot parse package.json"); - - let project_root = package_json_path.parent().unwrap().to_path_buf(); - - let dist_dir = { - let dist_dir_arg = args.out_folder.unwrap(); - if dist_dir_arg.is_absolute() { - dist_dir_arg - } else { - project_root.join(dist_dir_arg).normalize() - } - }; - - let threads = { - if let Some(threads) = args.threads { - threads - } else { - num_cpus::get() - } - }; - - let env = { - let mut vars = HashMap::::new(); - for (k, v) in std::env::vars() { - vars.insert(k, v); - } - vars - }; - - return Ok(Config { - start_time, - entry_point, - dist_dir, - // TODO - workspace_root: None, - // TODO - workspace_kind: None, - project_root, - package_json, - machrc, - threads, - node_workers, - optimize: args.optimize.unwrap(), - env, - }); -} - -fn parse_machrc(file_index: &FileIndex) -> Result { - let Some(mach_configs) = file_index.get(".machrc") else { - return Ok(Machrc::default()); - }; - - if mach_configs.len() == 0 { - return Ok(Machrc::default()); - } - - let file_path = mach_configs[0].clone(); - - let mut mach_config = Machrc { - is_default: false, - file_path, - engines: vec!["mach".to_string()], - resolvers: None, - transformers: None, - }; - - let Ok(json_file) = fs::read_to_string(&mach_config.file_path) else { - return Err("Unable to read file".to_string()); - }; - - let Ok(json) = serde_json::from_str::(&json_file) else { - return Err("Unable to parse json".to_string()); - }; - - if json_file.contains("\"node:") { - mach_config.engines.push("node".to_string()); - } - - if let Some(resolvers_value) = json.get("resolvers") { - let mut resolvers = Vec::::new(); - let Some(resolvers_values) = resolvers_value.as_array() else { - return Err("'resolvers' should be array".to_string()); - }; - for resolver_value in resolvers_values { - let Some(resolver_value) = resolver_value.as_str() else { - return Err("'resolvers[n]' should be string".to_string()); - }; - resolvers.push(resolver_value.to_string()); - } - mach_config.resolvers = Some(resolvers); - }; - - if let Some(transformers_value) = json.get("transformers") { - let mut transformers = HashMap::>::new(); - let Some(transformers_value) = transformers_value.as_object() else { - return Err("'transformers' should be object".to_string()); - }; - for (key, value) in transformers_value { - let mut values = Vec::::new(); - let Some(value) = value.as_array() else { - return Err("'transformers[key]' should be array".to_string()); - }; - for value in value { - let Some(value) = value.as_str() else { - return Err("'transformers[key][n]' should be string".to_string()); - }; - values.push(value.to_string()); - } - transformers.insert(key.clone(), values); - } - mach_config.transformers = Some(transformers); - } - - return Ok(mach_config); -} - -fn parse_json_file(target: &PathBuf) -> Result { - let Ok(json_file) = fs::read_to_string(target) else { - return Err("Unable to read file".to_string()); - }; - let Ok(json) = serde_json::from_str::(&json_file) else { - return Err("Unable to parse json".to_string()); - }; - return Ok(json); -} - -// fn parse_yaml_file(target: &PathBuf) -> Result { -// let Ok(yaml_file) = fs::read_to_string(target) else { -// return Err("Unable to read file".to_string()); -// }; -// let Ok(yaml) = serde_yaml::from_str::(&yaml_file) else { -// return Err("Unable to parse json".to_string()); -// }; -// return Ok(yaml); -// } - -fn find_file_by_name( - start_path: &PathBuf, - targets: &[&str], -) -> FileIndex { - let mut found = FileIndex::new(); - for target in targets { - found.insert(target.to_string(), vec![]); - } - - let mut current_test = start_path.clone(); - - let mut paths_to_test = Vec::::new(); - paths_to_test.push(current_test.clone()); - while current_test.pop() { - paths_to_test.push(current_test.clone()) - } - paths_to_test.reverse(); - - while let Some(path) = paths_to_test.pop() { - if path.is_file() { - let Some(file_name) = path.file_name() else { - continue; - }; - let file_name = file_name.to_str().unwrap(); - - for target in targets { - let target = target.to_string(); - if file_name == target { - found.get_mut(&target).unwrap().push(path.join(target)); - continue; - } - } - } else if path.is_dir() { - let Ok(ls) = path.read_dir() else { - panic!("failed to ls"); - }; - - for item in ls { - let Ok(item) = item else { - panic!("failed to ls"); - }; - - let file_name = item.file_name(); - let file_name = file_name.to_str().unwrap(); - - for target in targets { - let target = target.to_string(); - if file_name == target { - found.get_mut(&target).unwrap().push(path.join(target)); - continue; - } - } - } - } - } - return found; -} - -fn get_absolute_path(target: &PathBuf) -> PathBuf { - let mut file_path = PathBuf::from(&target); - if !file_path.is_absolute() { - file_path = std::env::current_dir().unwrap().join(file_path); - } - return file_path.normalize(); -} diff --git a/crates/mach/src/main.rs b/crates/mach/src/main.rs index 06e94395..7062864a 100644 --- a/crates/mach/src/main.rs +++ b/crates/mach/src/main.rs @@ -1,4 +1,6 @@ mod cmd; +mod platform; +mod kit; use clap::Subcommand; use clap::Parser; @@ -9,16 +11,20 @@ use cmd::version::VersionCommand; #[derive(Debug, Subcommand)] pub enum CommandType { + /// Build a project Build(BuildCommand), + /// Start a web server and reload when changes are detected Dev(DevCommand), + /// Build and rebuild when changes are detected Watch(WatchCommand), + /// Print version information Version(VersionCommand), } #[derive(Parser, Debug)] struct Commands { #[clap(subcommand)] - command: CommandType + command: CommandType, } fn main() { @@ -34,8 +40,8 @@ fn main() { CommandType::Watch(command) => { cmd::watch::main(command); }, - CommandType::Version(command) => { - cmd::version::main(command); + CommandType::Version(_) => { + cmd::version::main(); }, } } \ No newline at end of file diff --git a/npm/mach/_scripts/postinstall/bin.bash b/npm/mach/_scripts/postinstall/bin.bash index 4ec82aaf..c54129da 100644 --- a/npm/mach/_scripts/postinstall/bin.bash +++ b/npm/mach/_scripts/postinstall/bin.bash @@ -8,9 +8,4 @@ if [ "$MACH_BINARY_PATH" != "" ]; then $MACH_BINARY_PATH $@ fi -if [[ "$@" = *"--version"* ]]; then - node -e "console.log('NPM Package:', JSON.parse(require('node:fs').readFileSync(require('node:path').join('${TARGET_PATH}', 'package.json'))).version)" - echo "Mach Binary: $($BIN_PATH --version)" -else - $BIN_PATH $@ -fi +$BIN_PATH $@