From 436c87294fe1a64d84c832f472d65c0c041ff211 Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 27 Feb 2023 19:13:20 -0800 Subject: [PATCH 1/9] Self-hosted implementation of `cargo-cp-artifact`: - Core functionality implemented in crates/cargo-cp-artifact as a regular crate - crates/cargo-cp-artifact also has a bin so it can be used to build the self-hosted JS package - pkgs/cargo-cp-artifact has a Neon implementation that depends on crates/cargo-cp-artifact - pkgs/cargo-cp-artifact/package.json uses `cargo run -p cargo-cp-artifact` to build itself from the monorepo --- Cargo.lock | 174 +++++++- Cargo.toml | 1 + crates/cargo-cp-artifact/Cargo.toml | 13 + crates/cargo-cp-artifact/src/artifact.rs | 23 ++ crates/cargo-cp-artifact/src/bin.rs | 12 + crates/cargo-cp-artifact/src/cargo.rs | 131 ++++++ crates/cargo-cp-artifact/src/cli.rs | 384 ++++++++++++++++++ crates/cargo-cp-artifact/src/lib.rs | 3 + package-lock.json | 46 ++- pkgs/cargo-cp-artifact/Cargo.lock | 269 ++++++++++++ pkgs/cargo-cp-artifact/Cargo.toml | 21 + .../bin/cargo-cp-artifact.js | 4 +- pkgs/cargo-cp-artifact/package-lock.json | 18 + pkgs/cargo-cp-artifact/package.json | 13 +- pkgs/cargo-cp-artifact/src/args.js | 131 ------ pkgs/cargo-cp-artifact/src/index.js | 163 -------- pkgs/cargo-cp-artifact/src/lib.rs | 24 ++ pkgs/cargo-cp-artifact/test/args.js | 132 ------ 18 files changed, 1111 insertions(+), 451 deletions(-) create mode 100644 crates/cargo-cp-artifact/Cargo.toml create mode 100644 crates/cargo-cp-artifact/src/artifact.rs create mode 100644 crates/cargo-cp-artifact/src/bin.rs create mode 100644 crates/cargo-cp-artifact/src/cargo.rs create mode 100644 crates/cargo-cp-artifact/src/cli.rs create mode 100644 crates/cargo-cp-artifact/src/lib.rs create mode 100644 pkgs/cargo-cp-artifact/Cargo.lock create mode 100644 pkgs/cargo-cp-artifact/Cargo.toml create mode 100644 pkgs/cargo-cp-artifact/package-lock.json delete mode 100644 pkgs/cargo-cp-artifact/src/args.js delete mode 100644 pkgs/cargo-cp-artifact/src/index.js create mode 100644 pkgs/cargo-cp-artifact/src/lib.rs delete mode 100644 pkgs/cargo-cp-artifact/test/args.js diff --git a/Cargo.lock b/Cargo.lock index 18d2d5070..5364056fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,54 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "camino" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-cp-artifact" +version = "0.2.0" +dependencies = [ + "cargo_metadata", +] + +[[package]] +name = "cargo-cp-artifact-adapter" +version = "0.2.0" +dependencies = [ + "cargo-cp-artifact", + "cargo_metadata", + "neon 0.10.1", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.13", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -105,7 +153,7 @@ checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.7.3", ] [[package]] @@ -154,7 +202,7 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" name = "electron-tests" version = "0.1.0" dependencies = [ - "neon", + "neon 1.0.0-alpha.2", ] [[package]] @@ -227,6 +275,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + [[package]] name = "lazy_static" version = "1.4.0" @@ -248,6 +302,16 @@ version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.7.3" @@ -298,12 +362,25 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "napi-tests" version = "0.1.0" dependencies = [ - "neon", + "neon 1.0.0-alpha.2", "num-bigint-dig", "once_cell", "tokio", ] +[[package]] +name = "neon" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" +dependencies = [ + "neon-build", + "neon-macros 0.10.1", + "neon-runtime", + "semver 0.9.0", + "smallvec", +] + [[package]] name = "neon" version = "1.0.0-alpha.2" @@ -313,18 +390,35 @@ dependencies = [ "doc-comment", "easy-cast", "getrandom", - "libloading", + "libloading 0.7.3", "linkify", - "neon-macros", + "neon-macros 1.0.0-alpha.2", "nodejs-sys", "once_cell", "psd", - "semver", + "semver 1.0.13", "smallvec", "tokio", "widestring", ] +[[package]] +name = "neon-build" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" + +[[package]] +name = "neon-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" +dependencies = [ + "quote", + "syn", + "syn-mid", +] + [[package]] name = "neon-macros" version = "1.0.0-alpha.2" @@ -334,6 +428,17 @@ dependencies = [ "syn-mid", ] +[[package]] +name = "neon-runtime" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" +dependencies = [ + "cfg-if", + "libloading 0.6.7", + "smallvec", +] + [[package]] name = "nodejs-sys" version = "0.14.0" @@ -466,9 +571,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -544,17 +649,66 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] [[package]] name = "shlex" @@ -582,9 +736,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 937e60cb3..ee1782e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,5 @@ members = [ "crates/*", "test/*", + "pkgs/cargo-cp-artifact", ] diff --git a/crates/cargo-cp-artifact/Cargo.toml b/crates/cargo-cp-artifact/Cargo.toml new file mode 100644 index 000000000..e5b973116 --- /dev/null +++ b/crates/cargo-cp-artifact/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cargo-cp-artifact" +version = "0.2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "cargo-cp-artifact" +path = "src/bin.rs" + +[dependencies] +cargo_metadata = "0.15.3" diff --git a/crates/cargo-cp-artifact/src/artifact.rs b/crates/cargo-cp-artifact/src/artifact.rs new file mode 100644 index 000000000..11736bdc7 --- /dev/null +++ b/crates/cargo-cp-artifact/src/artifact.rs @@ -0,0 +1,23 @@ +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum ArtifactKind { + Bin, + CDylib, + Dylib, +} + +impl ArtifactKind { + pub fn parse(str: &impl AsRef) -> Option { + match str.as_ref() { + "bin" => Some(ArtifactKind::Bin), + "cdylib" => Some(ArtifactKind::CDylib), + "dylib" => Some(ArtifactKind::Dylib), + _ => None, + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct Artifact { + pub kind: ArtifactKind, + pub crate_name: String, +} diff --git a/crates/cargo-cp-artifact/src/bin.rs b/crates/cargo-cp-artifact/src/bin.rs new file mode 100644 index 000000000..65798eb67 --- /dev/null +++ b/crates/cargo-cp-artifact/src/bin.rs @@ -0,0 +1,12 @@ +mod artifact; +mod cargo; +mod cli; + +use cargo::Status; + +fn main() { + // Skip the native binary name (argv[0]). + if let Status::Failure = cli::run(1) { + std::process::exit(1); + } +} diff --git a/crates/cargo-cp-artifact/src/cargo.rs b/crates/cargo-cp-artifact/src/cargo.rs new file mode 100644 index 000000000..bb8b00582 --- /dev/null +++ b/crates/cargo-cp-artifact/src/cargo.rs @@ -0,0 +1,131 @@ +use crate::artifact::{Artifact, ArtifactKind}; + +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs::{copy, create_dir_all, remove_file}; +use std::io::ErrorKind; +use std::process::{Stdio, Command}; +use std::path::{Path, PathBuf}; +use cargo_metadata::{Message, Target}; + +#[derive(Debug, PartialEq, Eq)] +pub struct CargoCommand { + pub artifacts: HashMap>, + pub command: String, + pub args: Vec, +} + +pub fn push_artifact( + map: &mut HashMap>, + kind: ArtifactKind, + crate_name: String, + output_file: String, +) { + let key = Artifact { kind, crate_name }; + + if !map.contains_key(&key) { + let _ = map.insert(key, vec![output_file]); + } else { + map.get_mut(&key).unwrap().push(output_file); + } +} + +pub enum Status { + Success, + Failure, +} + +fn is_newer(filename: &impl AsRef, output_file: &impl AsRef) -> bool { + let filename = filename.as_ref(); + let output_file = output_file.as_ref(); + + if let (Ok(meta1), Ok(meta2)) = (filename.metadata(), output_file.metadata()) { + if let (Ok(mtime1), Ok(mtime2)) = (meta1.modified(), meta2.modified()) { + return mtime1 > mtime2; + } + } + + return true; +} + +// FIXME: return Result +fn copy_artifact(_artifact: &Artifact, filename: PathBuf, output_file: PathBuf) { + if !is_newer(&filename, &output_file) { + return; + } + + if let Some(basename) = output_file.parent() { + // FIXME: panic + create_dir_all(basename).expect("Couldn't create directories for output file"); + } + + // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, + // the macOS code signing cache isn't cleared when overwriting a file. + // Deleting the file before copying works around the issue. + // + // Unfortunately, this workaround is incomplete because the file must be + // deleted from the location it is loaded. If further steps in the user's + // build process copy or move the file in place, the code signing cache + // will not be cleared. + // + // https://github.com/neon-bindings/neon/issues/911 + if output_file.extension() == Some(OsStr::new("node")) { + if let Err(err) = remove_file(&output_file) { + match err.kind() { + ErrorKind::NotFound => {} + // FIXME: panic + _ => { panic!("Couldn't overwrite {}", output_file.to_string_lossy()); } + } + } + } + + // FIXME: panic + copy(&filename, &output_file).expect("Couldn't copy file"); +} + +impl CargoCommand { + pub fn new( + artifacts: HashMap>, + command: String, + args: Vec, + ) -> Self { + Self { artifacts, command, args } + } + + pub fn exec(self) -> Status { + let mut command = Command::new(self.command) + .args(&self.args) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); // FIXME: unwrap + + let reader = std::io::BufReader::new(command.stdout.take().unwrap()); // FIXME: unwrap + for message in cargo_metadata::Message::parse_stream(reader) { + if let Message::CompilerArtifact(artifact) = message.unwrap() { // FIXME: unwrap + let Target { kind: kinds, name, .. } = artifact.target; + for (kind, filename) in kinds.iter().zip(artifact.filenames) { + if let Some(kind) = ArtifactKind::parse(kind) { + let crate_name = name.clone(); + let artifact = Artifact { kind, crate_name }; + if let Some(output_files) = self.artifacts.get(&artifact) { + for output_file in output_files { + copy_artifact( + &artifact, + filename.clone().into(), + Path::new(output_file).to_path_buf(), + ); + } + } + } + } + } + } + + // FIXME: panic + if command.wait().expect("Couldn't get cargo's exit status").success() { + Status::Success + } else { + Status::Failure + } + } +} diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs new file mode 100644 index 000000000..bb351be3d --- /dev/null +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -0,0 +1,384 @@ +use std::collections::HashMap; + +use crate::artifact::ArtifactKind; +use crate::cargo::{CargoCommand, push_artifact, Status}; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + UnexpectedArtifactKind(String), + CommandNotFound, + EnvVarNotFound, + UnexpectedOption(String), +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseError::UnexpectedArtifactKind(found) => { + write!(f, "Unexpected artifact type: {found}") + } + ParseError::CommandNotFound => { + writeln!(f, "Missing command to execute.")?; + writeln!(f, "")?; + write!(f, "cargo-cp-artifact -a cdylib my-crate index.node ")?; + write!(f, "-- cargo build --message-format=json-render-diagnostics") + } + ParseError::EnvVarNotFound => { + write!(f, "Could not find the `{NPM_ENV}` environment variable. ")?; + write!(f, "Expected to be executed from an `npm` command.") + } + ParseError::UnexpectedOption(found) => { + write!(f, "Unexpected option: {found}") + } + } + } +} + +//struct Args(std::iter::Skip); +pub struct Args(T); + +impl Args> { + fn new(skip: usize) -> Self { + Self(std::env::args().skip(skip)) + } +} + +impl<'a> Args> { + #[allow(dead_code)] + fn from_vec(v: Vec) -> Self { + Self(v.into_iter()) + } +} + +#[allow(unused_macros)] +macro_rules! args { + [$($s:literal),*] => { + Args::from_vec(vec![$($s.to_string()),*]) + } +} + +impl> Args { + fn next(&mut self) -> Result { + let Self(args) = self; + match args.next() { + Some(token) => Ok(token), + None => Err(ParseError::CommandNotFound), + } + } + + fn rest(self) -> Vec { + let Self(args) = self; + args.collect() + } + + fn get_artifact_kind(&mut self, token: &str) -> Result { + if token.len() == 3 && &token[1..2] != "-" { + validate_artifact_kind(&token[2..3]) + } else { + validate_artifact_kind(self.next()?.as_str()) + } + } + + fn parse(mut self, get_crate_name: F) -> Result + where + F: Fn() -> Result, + { + let mut artifacts = HashMap::new(); + + loop { + let token = self.next()?; + let token = token.as_str(); + + if token == "--" { + break; + } + + if token == "--artifact" || (token.len() <= 3 && token.starts_with("-a")) { + let kind = self.get_artifact_kind(token)?; + let crate_name = self.next()?; + let output_file = self.next()?; + push_artifact(&mut artifacts, kind, crate_name, output_file); + continue; + } + + if token == "--npm" || (token.len() <= 3 && token.starts_with("-n")) { + let kind = self.get_artifact_kind(token)?; + let mut crate_name = get_crate_name()?; + + if let Some((left, right)) = crate_name.split_once('/') { + // This is a namespaced package; assume the crate is the un-namespaced version + if left.starts_with("@") { + crate_name = right.to_string(); + } + } + + let output_file = self.next()?; + push_artifact(&mut artifacts, kind, crate_name, output_file); + continue; + } + + return Err(ParseError::UnexpectedOption(token.to_string())); + } + + let command = self.next()?; + + Ok(CargoCommand::new(artifacts, command, self.rest())) + } +} + +fn validate_artifact_kind(kind: &str) -> Result { + match kind { + "b" | "bin" => Ok(ArtifactKind::Bin), + "c" | "cdylib" => Ok(ArtifactKind::CDylib), + "d" | "dylib" => Ok(ArtifactKind::Dylib), + _ => Err(ParseError::UnexpectedArtifactKind(kind.to_string())), + } +} + +const NPM_ENV: &'static str = "npm_package_name"; + +fn get_crate_name_from_env() -> Result { + std::env::var(NPM_ENV).or(Err(ParseError::EnvVarNotFound)) +} + +pub fn run(skip: usize) -> Status { + match Args::new(skip).parse(get_crate_name_from_env) { + Ok(cargo) => { cargo.exec() } + Err(err) => { + eprintln!("{err}"); + Status::Failure + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::artifact::{Artifact, ArtifactKind}; + + macro_rules! assert_err { + ($actual:expr, $expected:expr, $($arg:tt)+) => { + { + match $actual { + Ok(_) => { panic!($($arg)+); } + Err(error) => { + assert_eq!(error, $expected, $($arg)+); + } + } + } + } + } + + fn get_crate_name_ok() -> Result { + Ok("my-crate".to_string()) + } + + fn get_crate_name_with_namespace() -> Result { + Ok("@my-namespace/my-crate".to_string()) + } + + fn get_crate_name_err() -> Result { + Err(ParseError::EnvVarNotFound) + } + + #[test] + fn test_invalid_artifact_type() { + let r = args!["-an", "a", "b", "--"].parse(get_crate_name_ok); + assert_err!( + r, + ParseError::UnexpectedArtifactKind("n".to_string()), + "expected artifact type parse error", + ); + } + + #[test] + fn test_missing_env_var() { + let r = args!["-nc", "a", "b", "--"].parse(get_crate_name_err); + assert_err!(r, ParseError::EnvVarNotFound, "expected env var error"); + } + + #[test] + fn test_missing_command() { + let r = args!["-ac", "a", "b"].parse(get_crate_name_ok); + assert_err!(r, ParseError::CommandNotFound, "expected command not found error"); + let r = args!["-ac", "a", "b", "--"].parse(get_crate_name_ok); + assert_err!(r, ParseError::CommandNotFound, "expected command not found error"); + } + + #[test] + fn test_invalid_option() { + let r = args!["-q"].parse(get_crate_name_ok); + assert_err!(r, ParseError::UnexpectedOption("-q".to_string()), "expected bad option error"); + } + + fn example_artifact1() -> Artifact { + Artifact { + kind: ArtifactKind::Bin, + crate_name: "my-crate".to_string(), + } + } + + fn example_artifact2() -> Artifact { + Artifact { + kind: ArtifactKind::Dylib, + crate_name: "a".to_string(), + } + } + + fn example_artifact3() -> Artifact { + Artifact { + kind: ArtifactKind::CDylib, + crate_name: "my-crate".to_string(), + } + } + + fn example_cargo_command() -> CargoCommand { + let mut artifacts = HashMap::new(); + let artifact = example_artifact1(); + artifacts.insert(artifact, vec!["my-bin".to_string()]); + + let command = "a".to_string(); + let args = vec!["b".to_string(), "c".to_string()]; + + CargoCommand { artifacts, command, args } + } + + fn example_complex_cargo_command() -> CargoCommand { + let mut artifacts = HashMap::new(); + + artifacts.insert(example_artifact1(), vec!["my-bin".to_string(), "other-copy".to_string()]); + artifacts.insert(example_artifact2(), vec!["b".to_string()]); + artifacts.insert(example_artifact3(), vec!["index.node".to_string()]); + + let command = "a".to_string(); + let args = vec!["b".to_string(), "c".to_string()]; + + CargoCommand { artifacts, command, args } + } + + #[test] + fn test_artifact_option() { + let cmd = args![ + "--artifact", + "bin", + "my-crate", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-a", + "bin", + "my-crate", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-ab", + "my-crate", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + } + + #[test] + fn test_npm_option() { + let cmd = args![ + "--npm", + "bin", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-n", + "bin", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-nb", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + } + + #[test] + fn test_namespace_removal() { + let cmd = args![ + "--npm", + "bin", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_with_namespace) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + } + + #[test] + fn test_complex_command() { + let cmd: CargoCommand = args![ + "-nb", + "my-bin", + "--artifact", + "d", + "a", + "b", + "-ac", + "my-crate", + "index.node", + "--npm", + "bin", + "other-copy", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_complex_cargo_command(), "improperly parsed: {:?}", cmd); + } +} diff --git a/crates/cargo-cp-artifact/src/lib.rs b/crates/cargo-cp-artifact/src/lib.rs new file mode 100644 index 000000000..32d453527 --- /dev/null +++ b/crates/cargo-cp-artifact/src/lib.rs @@ -0,0 +1,3 @@ +pub mod artifact; +pub mod cargo; +pub mod cli; diff --git a/package-lock.json b/package-lock.json index 9c6907b64..2a126a1db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2284,14 +2284,13 @@ } }, "pkgs/cargo-cp-artifact": { - "version": "0.1.7", + "version": "0.2.0", + "hasInstallScript": true, "license": "MIT", "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, - "devDependencies": { - "mocha": "^10.0.0" - } + "devDependencies": {} }, "pkgs/create-neon": { "version": "0.2.0", @@ -2328,6 +2327,15 @@ "playwright": "^1.23.1" } }, + "test/electron/node_modules/cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true, + "bin": { + "cargo-cp-artifact": "bin/cargo-cp-artifact.js" + } + }, "test/napi": { "name": "napi-tests", "version": "0.1.0", @@ -2338,6 +2346,15 @@ "chai": "^4.3.6", "mocha": "^10.0.0" } + }, + "test/napi/node_modules/cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true, + "bin": { + "cargo-cp-artifact": "bin/cargo-cp-artifact.js" + } } }, "dependencies": { @@ -2570,10 +2587,7 @@ "dev": true }, "cargo-cp-artifact": { - "version": "file:pkgs/cargo-cp-artifact", - "requires": { - "mocha": "^10.0.0" - } + "version": "file:pkgs/cargo-cp-artifact" }, "chai": { "version": "4.3.6", @@ -2826,6 +2840,14 @@ "cargo-cp-artifact": "^0.1.7", "electron": "^19.0.11", "playwright": "^1.23.1" + }, + "dependencies": { + "cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true + } } }, "emoji-regex": { @@ -3496,6 +3518,14 @@ "cargo-cp-artifact": "^0.1.7", "chai": "^4.3.6", "mocha": "^10.0.0" + }, + "dependencies": { + "cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true + } } }, "neo-async": { diff --git a/pkgs/cargo-cp-artifact/Cargo.lock b/pkgs/cargo-cp-artifact/Cargo.lock new file mode 100644 index 000000000..f590344a5 --- /dev/null +++ b/pkgs/cargo-cp-artifact/Cargo.lock @@ -0,0 +1,269 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "camino" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-bootstrap" +version = "0.1.0" +dependencies = [ + "cargo_metadata", +] + +[[package]] +name = "cargo-cp-artifact" +version = "0.2.0" +dependencies = [ + "cargo-bootstrap", + "cargo_metadata", + "neon", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.16", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "neon" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" +dependencies = [ + "neon-build", + "neon-macros", + "neon-runtime", + "semver 0.9.0", + "smallvec", +] + +[[package]] +name = "neon-build" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" + +[[package]] +name = "neon-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" +dependencies = [ + "quote", + "syn", + "syn-mid", +] + +[[package]] +name = "neon-runtime" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" +dependencies = [ + "cfg-if", + "libloading", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-mid" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/pkgs/cargo-cp-artifact/Cargo.toml b/pkgs/cargo-cp-artifact/Cargo.toml new file mode 100644 index 000000000..a165f9178 --- /dev/null +++ b/pkgs/cargo-cp-artifact/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cargo-cp-artifact-adapter" +version = "0.2.0" +authors = ["K.J. Valencik"] +license = "MIT" +edition = "2018" +exclude = ["index.node"] + +[lib] +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cargo-cp-artifact = { path = "../../crates/cargo-cp-artifact" } +cargo_metadata = "0.15.3" + +[dependencies.neon] +version = "0.10.1" +default-features = false +features = ["napi-6"] diff --git a/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js b/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js index 49bb916b2..59d26bdc3 100755 --- a/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js +++ b/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js @@ -1,6 +1,6 @@ #!/usr/bin/env node "use strict"; -const run = require(".."); +const run = require("..").run; -run(process.argv.slice(2), process.env); +run(); diff --git a/pkgs/cargo-cp-artifact/package-lock.json b/pkgs/cargo-cp-artifact/package-lock.json new file mode 100644 index 000000000..ac2b0cdd8 --- /dev/null +++ b/pkgs/cargo-cp-artifact/package-lock.json @@ -0,0 +1,18 @@ +{ + "name": "cargo-cp-artifact", + "version": "0.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cargo-cp-artifact", + "version": "0.2.0", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "cargo-cp-artifact": "bin/cargo-cp-artifact.js" + }, + "devDependencies": {} + } + } +} diff --git a/pkgs/cargo-cp-artifact/package.json b/pkgs/cargo-cp-artifact/package.json index 79ee7f3d9..de996fb35 100644 --- a/pkgs/cargo-cp-artifact/package.json +++ b/pkgs/cargo-cp-artifact/package.json @@ -1,17 +1,21 @@ { "name": "cargo-cp-artifact", - "version": "0.1.7", + "version": "0.2.0", "description": "Copies compiler artifacts emitted by rustc by parsing Cargo metadata", - "main": "src/index.js", + "main": "index.node", "files": [ "bin", - "src" + "index.node" ], "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, "scripts": { - "test": "mocha test" + "build": "cargo run -p cargo-cp-artifact -- -ac cargo-cp-artifact-adapter index.node -- cargo build --message-format=json-render-diagnostics", + "build-debug": "npm run build --", + "build-release": "npm run build -- --release", + "install": "npm run build-release", + "test": "cargo test" }, "repository": { "type": "git", @@ -29,6 +33,5 @@ }, "homepage": "https://github.com/neon-bindings/neon/tree/main/pkgs/cargo-cp-artifact", "devDependencies": { - "mocha": "^10.0.0" } } diff --git a/pkgs/cargo-cp-artifact/src/args.js b/pkgs/cargo-cp-artifact/src/args.js deleted file mode 100644 index 36fbd3e2a..000000000 --- a/pkgs/cargo-cp-artifact/src/args.js +++ /dev/null @@ -1,131 +0,0 @@ -"use strict"; - -class ParseError extends Error {} - -const NPM_ENV = "npm_package_name"; -const EXPECTED_COMMAND = [ - "Missing command to execute.", - [ - "cargo-cp-artifct -a cdylib my-crate index.node", - "--", - "cargo build --message-format=json-render-diagnostics", - ].join(" "), -].join("\n"); - -function validateArtifactType(artifactType) { - switch (artifactType) { - case "b": - case "bin": - return "bin"; - case "c": - case "cdylib": - return "cdylib"; - case "d": - case "dylib": - return "dylib"; - default: - } - - throw new ParseError(`Unexpected artifact type: ${artifactType}`); -} - -function getArtifactName({ artifactType, crateName }) { - return `${artifactType}:${crateName}`; -} - -function getCrateNameFromEnv(env) { - if (!env.hasOwnProperty(NPM_ENV)) { - throw new ParseError( - [ - `Could not find the \`${NPM_ENV}\` environment variable.`, - "Expected to be executed from an `npm` command.", - ].join(" ") - ); - } - - const name = env[NPM_ENV]; - const firstSlash = name.indexOf("/"); - - // This is a namespaced package; assume the crate is the un-namespaced version - if (name[0] === "@" && firstSlash > 0) { - return name.slice(firstSlash + 1); - } - - return name; -} - -function parse(argv, env) { - const artifacts = {}; - let tokens = argv; - - function getNext() { - if (!tokens.length) { - throw new ParseError(EXPECTED_COMMAND); - } - - const next = tokens[0]; - tokens = tokens.slice(1); - return next; - } - - function getArtifactType(token) { - if (token[1] !== "-" && token.length === 3) { - return validateArtifactType(token[2]); - } - - return validateArtifactType(getNext()); - } - - function pushArtifact(artifact) { - const name = getArtifactName(artifact); - - artifacts[name] = artifacts[name] || []; - artifacts[name].push(artifact.outputFile); - } - - while (tokens.length) { - const token = getNext(); - - // End of CLI arguments - if (token === "--") { - break; - } - - if ( - token === "--artifact" || - (token.length <= 3 && token.startsWith("-a")) - ) { - const artifactType = getArtifactType(token); - const crateName = getNext(); - const outputFile = getNext(); - - pushArtifact({ artifactType, crateName, outputFile }); - continue; - } - - if (token === "--npm" || (token.length <= 3 && token.startsWith("-n"))) { - const artifactType = getArtifactType(token); - const crateName = getCrateNameFromEnv(env); - const outputFile = getNext(); - - pushArtifact({ artifactType, crateName, outputFile }); - continue; - } - - throw new ParseError(`Unexpected option: ${token}`); - } - - if (!tokens.length) { - throw new ParseError(EXPECTED_COMMAND); - } - - const cmd = getNext(); - - return { - artifacts, - cmd, - args: tokens, - }; -} - -module.exports = { ParseError, getArtifactName, parse }; diff --git a/pkgs/cargo-cp-artifact/src/index.js b/pkgs/cargo-cp-artifact/src/index.js deleted file mode 100644 index 9eda8eeb4..000000000 --- a/pkgs/cargo-cp-artifact/src/index.js +++ /dev/null @@ -1,163 +0,0 @@ -"use strict"; - -const { spawn } = require("child_process"); -const { - promises: { copyFile, mkdir, stat, unlink }, -} = require("fs"); -const { dirname, extname } = require("path"); -const readline = require("readline"); - -const { ParseError, getArtifactName, parse } = require("./args"); - -function run(argv, env) { - const options = parseArgs(argv, env); - const copied = {}; - - const cp = spawn(options.cmd, options.args, { - stdio: ["inherit", "pipe", "inherit"], - }); - - const rl = readline.createInterface({ input: cp.stdout }); - - cp.on("error", (err) => { - if (options.cmd === "cargo" && err.code === "ENOENT") { - console.error(`Error: could not find the \`cargo\` executable. - -You can find instructions for installing Rust and Cargo at: - - https://www.rust-lang.org/tools/install - -`); - } else { - console.error(err); - } - process.exitCode = 1; - }); - - cp.on("exit", (code) => { - if (!process.exitCode) { - process.exitCode = code; - } - }); - - rl.on("line", (line) => { - try { - processCargoBuildLine(options, copied, line); - } catch (err) { - console.error(err); - process.exitCode = 1; - } - }); - - process.on("exit", () => { - Object.keys(options.artifacts).forEach((name) => { - if (!copied[name]) { - console.error(`Did not copy "${name}"`); - - if (!process.exitCode) { - process.exitCode = 1; - } - } - }); - }); -} - -function processCargoBuildLine(options, copied, line) { - const data = JSON.parse(line); - const { filenames, reason, target } = data; - - if (!data || reason !== "compiler-artifact" || !target) { - return; - } - - const { kind: kinds, name } = data.target; - - if (!Array.isArray(kinds) || !Array.isArray(filenames)) { - return; - } - - // `kind` and `filenames` zip up as key/value pairs - kinds.forEach((kind, i) => { - const filename = filenames[i]; - const key = getArtifactName({ artifactType: kind, crateName: name }); - const outputFiles = options.artifacts[key]; - - if (!outputFiles || !filename) { - return; - } - - Promise.all( - outputFiles.map((outputFile) => copyArtifact(filename, outputFile)) - ) - .then(() => { - copied[key] = true; - }) - .catch((err) => { - process.exitCode = 1; - console.error(err); - }); - }); -} - -async function isNewer(filename, outputFile) { - try { - const prevStats = await stat(outputFile); - const nextStats = await stat(filename); - - return nextStats.mtime > prevStats.mtime; - } catch (_err) {} - - return true; -} - -async function copyArtifact(filename, outputFile) { - if (!(await isNewer(filename, outputFile))) { - return; - } - - const outputDir = dirname(outputFile); - - // Don't try to create the current directory - if (outputDir && outputDir !== ".") { - await mkdir(outputDir, { recursive: true }); - } - - // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, - // the macOS code signing cache isn't cleared when overwriting a file. - // Deleting the file before copying works around the issue. - // - // Unfortunately, this workaround is incomplete because the file must be - // deleted from the location it is loaded. If further steps in the user's - // build process copy or move the file in place, the code signing cache - // will not be cleared. - // - // https://github.com/neon-bindings/neon/issues/911 - if (extname(outputFile) === ".node") { - try { - await unlink(outputFile); - } catch (_e) { - // Ignore errors; the file might not exist - } - } - - await copyFile(filename, outputFile); -} - -function parseArgs(argv, env) { - try { - return parse(argv, env); - } catch (err) { - if (err instanceof ParseError) { - quitError(err.message); - } else { - throw err; - } - } -} - -function quitError(msg) { - console.error(msg); - process.exit(1); -} - -module.exports = run; diff --git a/pkgs/cargo-cp-artifact/src/lib.rs b/pkgs/cargo-cp-artifact/src/lib.rs new file mode 100644 index 000000000..c9380dffc --- /dev/null +++ b/pkgs/cargo-cp-artifact/src/lib.rs @@ -0,0 +1,24 @@ +use neon::prelude::*; + +use cargo_cp_artifact::cargo::Status; +use cargo_cp_artifact::cli; + +fn run(mut cx: FunctionContext) -> JsResult { + // Skip the node binary name (argv[0]) and the script name (argv[1]). + if let Status::Failure = cli::run(2) { + cx.global() + .get::(&mut cx, "process")? + .get::(&mut cx, "exit")? + .call_with(&cx) + .arg(cx.number(1)) + .exec(&mut cx)?; + } + + Ok(cx.undefined()) +} + +#[neon::main] +fn main(mut cx: ModuleContext) -> NeonResult<()> { + cx.export_function("run", run)?; + Ok(()) +} diff --git a/pkgs/cargo-cp-artifact/test/args.js b/pkgs/cargo-cp-artifact/test/args.js deleted file mode 100644 index abb953d90..000000000 --- a/pkgs/cargo-cp-artifact/test/args.js +++ /dev/null @@ -1,132 +0,0 @@ -"use strict"; - -const assert = require("assert"); - -const { parse } = require("../src/args"); - -describe("Argument Parsing", () => { - it("throws on invalid artifact type", () => { - assert.throws(() => parse(["-an", "a", "b", "--"]), /artifact type/); - }); - - it("npm must have an environment variable", () => { - assert.throws(() => parse(["-nc", "a", "b", "--"], {}), /environment/); - }); - - it("must provide a command", () => { - assert.throws(() => parse(["-ac", "a", "b"]), /Missing command/); - assert.throws(() => parse(["-ac", "a", "b", "--"]), /Missing command/); - }); - - it("cannot provide invalid option", () => { - assert.throws(() => parse(["-q"], {}), /Unexpected option/); - }); - - it("should be able to use --artifact", () => { - const args = "bin my-crate my-bin -- a b c".split(" "); - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(["--artifact", ...args]), expected); - assert.deepStrictEqual(parse(["-a", ...args]), expected); - }); - - it("should be able to use --npm", () => { - const args = "bin my-bin -- a b c".split(" "); - const env = { - npm_package_name: "my-crate", - }; - - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(["--npm", ...args], env), expected); - assert.deepStrictEqual(parse(["-n", ...args], env), expected); - }); - - it("should be able to use short-hand for crate type with -a", () => { - const args = "-ab my-crate my-bin -- a b c".split(" "); - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(args), expected); - }); - - it("should be able to use short-hand for crate type with -n", () => { - const args = "-nb my-bin -- a b c".split(" "); - const env = { - npm_package_name: "my-crate", - }; - - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(args, env), expected); - }); - - it("should remove namespace from package name", () => { - const args = "-nc index.node -- a b c".split(" "); - const env = { - npm_package_name: "@my-namespace/my-crate", - }; - - const expected = { - artifacts: { - "cdylib:my-crate": ["index.node"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(args, env), expected); - }); - - it("should be able to provide multiple artifacts", () => { - const args = ` - -nb my-bin - --artifact d a b - -ac my-crate index.node - --npm bin other-copy - -- a b c - ` - .trim() - .split("\n") - .map((line) => line.trim()) - .join(" ") - .split(" "); - - const env = { - npm_package_name: "my-crate", - }; - - assert.deepStrictEqual(parse(args, env), { - artifacts: { - "bin:my-crate": ["my-bin", "other-copy"], - "dylib:a": ["b"], - "cdylib:my-crate": ["index.node"], - }, - cmd: "a", - args: ["b", "c"], - }); - }); -}); From 6755c77b752e863a0f9f6457be7853d548180a3a Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 27 Feb 2023 21:27:44 -0800 Subject: [PATCH 2/9] OO improvements: - Wrap HashMap in a custom CopyMap type - Move push_artifact into CopyMap::insert - Move some of CargoCommand::exec into CopyMap::copy - Move copy_artifact into Artifact::copy --- crates/cargo-cp-artifact/src/artifact.rs | 54 ++++++++++ crates/cargo-cp-artifact/src/cargo.rs | 119 ++++++++--------------- crates/cargo-cp-artifact/src/cli.rs | 22 ++--- 3 files changed, 105 insertions(+), 90 deletions(-) diff --git a/crates/cargo-cp-artifact/src/artifact.rs b/crates/cargo-cp-artifact/src/artifact.rs index 11736bdc7..1a4ec8bd0 100644 --- a/crates/cargo-cp-artifact/src/artifact.rs +++ b/crates/cargo-cp-artifact/src/artifact.rs @@ -1,3 +1,8 @@ +use std::ffi::OsStr; +use std::fs::{create_dir_all, remove_file}; +use std::io::ErrorKind; +use std::path::Path; + #[derive(Debug, PartialEq, Eq, Hash)] pub enum ArtifactKind { Bin, @@ -21,3 +26,52 @@ pub struct Artifact { pub kind: ArtifactKind, pub crate_name: String, } + +fn is_newer(from: &Path, to: &Path) -> bool { + if let (Ok(from_meta), Ok(to_meta)) = (from.metadata(), to.metadata()) { + if let (Ok(from_mtime), Ok(to_mtime)) = (from_meta.modified(), to_meta.modified()) { + return from_mtime > to_mtime; + } + } + + return true; +} + +impl Artifact { + + // FIXME: return Result + pub fn copy(&self, from: &Path, to: &Path) { + if !is_newer(from, to) { + return; + } + + if let Some(basename) = to.parent() { + // FIXME: panic + create_dir_all(basename).expect("Couldn't create directories for output file"); + } + + // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, + // the macOS code signing cache isn't cleared when overwriting a file. + // Deleting the file before copying works around the issue. + // + // Unfortunately, this workaround is incomplete because the file must be + // deleted from the location it is loaded. If further steps in the user's + // build process copy or move the file in place, the code signing cache + // will not be cleared. + // + // https://github.com/neon-bindings/neon/issues/911 + if to.extension() == Some(OsStr::new("node")) { + if let Err(err) = remove_file(to) { + match err.kind() { + ErrorKind::NotFound => {} + // FIXME: panic + _ => { panic!("Couldn't overwrite {}", to.to_string_lossy()); } + } + } + } + + // FIXME: panic + std::fs::copy(from, to).expect("Couldn't copy file"); + } + +} \ No newline at end of file diff --git a/crates/cargo-cp-artifact/src/cargo.rs b/crates/cargo-cp-artifact/src/cargo.rs index bb8b00582..cdc5ae07e 100644 --- a/crates/cargo-cp-artifact/src/cargo.rs +++ b/crates/cargo-cp-artifact/src/cargo.rs @@ -1,94 +1,64 @@ use crate::artifact::{Artifact, ArtifactKind}; use std::collections::HashMap; -use std::ffi::OsStr; -use std::fs::{copy, create_dir_all, remove_file}; -use std::io::ErrorKind; use std::process::{Stdio, Command}; -use std::path::{Path, PathBuf}; +use std::path::Path; use cargo_metadata::{Message, Target}; #[derive(Debug, PartialEq, Eq)] -pub struct CargoCommand { - pub artifacts: HashMap>, - pub command: String, - pub args: Vec, -} +pub struct CopyMap(HashMap>); -pub fn push_artifact( - map: &mut HashMap>, - kind: ArtifactKind, - crate_name: String, - output_file: String, -) { - let key = Artifact { kind, crate_name }; - - if !map.contains_key(&key) { - let _ = map.insert(key, vec![output_file]); - } else { - map.get_mut(&key).unwrap().push(output_file); +impl CopyMap { + pub fn new() -> Self { + Self(HashMap::new()) } -} -pub enum Status { - Success, - Failure, -} + pub fn add(&mut self, kind: ArtifactKind, crate_name: String, output_file: String) { + let key = Artifact { kind, crate_name }; -fn is_newer(filename: &impl AsRef, output_file: &impl AsRef) -> bool { - let filename = filename.as_ref(); - let output_file = output_file.as_ref(); - - if let (Ok(meta1), Ok(meta2)) = (filename.metadata(), output_file.metadata()) { - if let (Ok(mtime1), Ok(mtime2)) = (meta1.modified(), meta2.modified()) { - return mtime1 > mtime2; + if !self.0.contains_key(&key) { + let _ = self.0.insert(key, vec![output_file]); + } else { + self.0.get_mut(&key).unwrap().push(output_file); } } - return true; -} - -// FIXME: return Result -fn copy_artifact(_artifact: &Artifact, filename: PathBuf, output_file: PathBuf) { - if !is_newer(&filename, &output_file) { - return; + pub fn copy(&self, artifact: &Artifact, from: &Path) { + if let Some(output_files) = self.0.get(&artifact) { + for output_file in output_files { + artifact.copy(from, Path::new(output_file)); + } + } } +} - if let Some(basename) = output_file.parent() { - // FIXME: panic - create_dir_all(basename).expect("Couldn't create directories for output file"); +#[cfg(test)] +impl CopyMap { + pub fn set(&mut self, artifact: Artifact, output_files: &[&str]) { + let _ = self.0.insert( + artifact, + output_files + .iter() + .map(|s| s.to_string()) + .collect(), + ); } +} - // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, - // the macOS code signing cache isn't cleared when overwriting a file. - // Deleting the file before copying works around the issue. - // - // Unfortunately, this workaround is incomplete because the file must be - // deleted from the location it is loaded. If further steps in the user's - // build process copy or move the file in place, the code signing cache - // will not be cleared. - // - // https://github.com/neon-bindings/neon/issues/911 - if output_file.extension() == Some(OsStr::new("node")) { - if let Err(err) = remove_file(&output_file) { - match err.kind() { - ErrorKind::NotFound => {} - // FIXME: panic - _ => { panic!("Couldn't overwrite {}", output_file.to_string_lossy()); } - } - } - } +#[derive(Debug, PartialEq, Eq)] +pub struct CargoCommand { + pub artifacts: CopyMap, + pub command: String, + pub args: Vec, +} - // FIXME: panic - copy(&filename, &output_file).expect("Couldn't copy file"); +pub enum Status { + Success, + Failure, } impl CargoCommand { - pub fn new( - artifacts: HashMap>, - command: String, - args: Vec, - ) -> Self { + pub fn new(artifacts: CopyMap, command: String, args: Vec) -> Self { Self { artifacts, command, args } } @@ -104,18 +74,11 @@ impl CargoCommand { if let Message::CompilerArtifact(artifact) = message.unwrap() { // FIXME: unwrap let Target { kind: kinds, name, .. } = artifact.target; for (kind, filename) in kinds.iter().zip(artifact.filenames) { + let from = filename.into_std_path_buf(); if let Some(kind) = ArtifactKind::parse(kind) { let crate_name = name.clone(); let artifact = Artifact { kind, crate_name }; - if let Some(output_files) = self.artifacts.get(&artifact) { - for output_file in output_files { - copy_artifact( - &artifact, - filename.clone().into(), - Path::new(output_file).to_path_buf(), - ); - } - } + self.artifacts.copy(&artifact, &from); } } } diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs index bb351be3d..0255df32e 100644 --- a/crates/cargo-cp-artifact/src/cli.rs +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -1,7 +1,5 @@ -use std::collections::HashMap; - use crate::artifact::ArtifactKind; -use crate::cargo::{CargoCommand, push_artifact, Status}; +use crate::cargo::{CargoCommand, CopyMap, Status}; #[derive(Debug, PartialEq, Eq)] pub enum ParseError { @@ -83,7 +81,7 @@ impl> Args { where F: Fn() -> Result, { - let mut artifacts = HashMap::new(); + let mut artifacts = CopyMap::new(); loop { let token = self.next()?; @@ -97,7 +95,7 @@ impl> Args { let kind = self.get_artifact_kind(token)?; let crate_name = self.next()?; let output_file = self.next()?; - push_artifact(&mut artifacts, kind, crate_name, output_file); + artifacts.add(kind, crate_name, output_file); continue; } @@ -113,7 +111,7 @@ impl> Args { } let output_file = self.next()?; - push_artifact(&mut artifacts, kind, crate_name, output_file); + artifacts.add(kind, crate_name, output_file); continue; } @@ -233,9 +231,9 @@ mod test { } fn example_cargo_command() -> CargoCommand { - let mut artifacts = HashMap::new(); + let mut artifacts = CopyMap::new(); let artifact = example_artifact1(); - artifacts.insert(artifact, vec!["my-bin".to_string()]); + artifacts.set(artifact, &["my-bin"]); let command = "a".to_string(); let args = vec!["b".to_string(), "c".to_string()]; @@ -244,11 +242,11 @@ mod test { } fn example_complex_cargo_command() -> CargoCommand { - let mut artifacts = HashMap::new(); + let mut artifacts = CopyMap::new(); - artifacts.insert(example_artifact1(), vec!["my-bin".to_string(), "other-copy".to_string()]); - artifacts.insert(example_artifact2(), vec!["b".to_string()]); - artifacts.insert(example_artifact3(), vec!["index.node".to_string()]); + artifacts.set(example_artifact1(), &["my-bin", "other-copy"]); + artifacts.set(example_artifact2(), &["b"]); + artifacts.set(example_artifact3(), &["index.node"]); let command = "a".to_string(); let args = vec!["b".to_string(), "c".to_string()]; From f0f2332ce728b34e6f1d562b42032dc68a38e496 Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 27 Feb 2023 22:32:12 -0800 Subject: [PATCH 3/9] Use errors instead of panics --- crates/cargo-cp-artifact/src/artifact.rs | 37 +++++++++++++---- crates/cargo-cp-artifact/src/cargo.rs | 52 +++++++++++++++++------- crates/cargo-cp-artifact/src/cli.rs | 14 +++++-- 3 files changed, 76 insertions(+), 27 deletions(-) diff --git a/crates/cargo-cp-artifact/src/artifact.rs b/crates/cargo-cp-artifact/src/artifact.rs index 1a4ec8bd0..6ae1e329b 100644 --- a/crates/cargo-cp-artifact/src/artifact.rs +++ b/crates/cargo-cp-artifact/src/artifact.rs @@ -27,6 +27,28 @@ pub struct Artifact { pub crate_name: String, } +pub enum ArtifactError { + MkdirFailed(std::io::Error), + OverwriteFailed(std::io::Error), + CopyFailed(std::io::Error), +} + +impl std::fmt::Display for ArtifactError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArtifactError::MkdirFailed(err) => { + write!(f, "Could not create directory: {}", err) + } + ArtifactError::OverwriteFailed(err) => { + write!(f, "Could not delete .node file: {}", err) + } + ArtifactError::CopyFailed(err) => { + write!(f, "Could not copy artifact: {}", err) + } + } + } +} + fn is_newer(from: &Path, to: &Path) -> bool { if let (Ok(from_meta), Ok(to_meta)) = (from.metadata(), to.metadata()) { if let (Ok(from_mtime), Ok(to_mtime)) = (from_meta.modified(), to_meta.modified()) { @@ -39,15 +61,13 @@ fn is_newer(from: &Path, to: &Path) -> bool { impl Artifact { - // FIXME: return Result - pub fn copy(&self, from: &Path, to: &Path) { + pub fn copy(&self, from: &Path, to: &Path) -> Result<(), ArtifactError> { if !is_newer(from, to) { - return; + return Ok(()); } if let Some(basename) = to.parent() { - // FIXME: panic - create_dir_all(basename).expect("Couldn't create directories for output file"); + create_dir_all(basename).map_err(ArtifactError::MkdirFailed)?; } // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, @@ -64,14 +84,13 @@ impl Artifact { if let Err(err) = remove_file(to) { match err.kind() { ErrorKind::NotFound => {} - // FIXME: panic - _ => { panic!("Couldn't overwrite {}", to.to_string_lossy()); } + _ => { return Err(ArtifactError::OverwriteFailed(err)); } } } } - // FIXME: panic - std::fs::copy(from, to).expect("Couldn't copy file"); + std::fs::copy(from, to).map_err(ArtifactError::CopyFailed)?; + Ok(()) } } \ No newline at end of file diff --git a/crates/cargo-cp-artifact/src/cargo.rs b/crates/cargo-cp-artifact/src/cargo.rs index cdc5ae07e..adf3d93ca 100644 --- a/crates/cargo-cp-artifact/src/cargo.rs +++ b/crates/cargo-cp-artifact/src/cargo.rs @@ -1,4 +1,4 @@ -use crate::artifact::{Artifact, ArtifactKind}; +use crate::artifact::{Artifact, ArtifactError, ArtifactKind}; use std::collections::HashMap; use std::process::{Stdio, Command}; @@ -23,12 +23,13 @@ impl CopyMap { } } - pub fn copy(&self, artifact: &Artifact, from: &Path) { + pub fn copy(&self, artifact: &Artifact, from: &Path) -> Result<(), CargoError> { if let Some(output_files) = self.0.get(&artifact) { for output_file in output_files { - artifact.copy(from, Path::new(output_file)); + artifact.copy(from, Path::new(output_file)).map_err(CargoError::ArtifactCopyFailed)?; } } + Ok(()) } } @@ -57,38 +58,61 @@ pub enum Status { Failure, } +pub enum CargoError { + SpawnFailed(std::io::Error), + MessageParseFailed(std::io::Error), + CommandWaitFailed(std::io::Error), + ArtifactCopyFailed(ArtifactError), +} + +impl std::fmt::Display for CargoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CargoError::SpawnFailed(err) => { + write!(f, "Could not execute command: {}", err) + } + CargoError::MessageParseFailed(err) => { + write!(f, "Could not read command output: {}", err) + } + CargoError::CommandWaitFailed(err) => { + write!(f, "Command failed to exit: {}", err) + } + CargoError::ArtifactCopyFailed(err) => { + write!(f, "Failed to copy artifact: {}", err) + } + } + } +} + impl CargoCommand { pub fn new(artifacts: CopyMap, command: String, args: Vec) -> Self { Self { artifacts, command, args } } - pub fn exec(self) -> Status { + pub fn exec(self) -> Result<(), CargoError> { let mut command = Command::new(self.command) .args(&self.args) .stdout(Stdio::piped()) .spawn() - .unwrap(); // FIXME: unwrap + .map_err(CargoError::SpawnFailed)?; - let reader = std::io::BufReader::new(command.stdout.take().unwrap()); // FIXME: unwrap + let reader = std::io::BufReader::new(command.stdout.take().unwrap()); for message in cargo_metadata::Message::parse_stream(reader) { - if let Message::CompilerArtifact(artifact) = message.unwrap() { // FIXME: unwrap + let message = message.map_err(CargoError::MessageParseFailed)?; + if let Message::CompilerArtifact(artifact) = message { let Target { kind: kinds, name, .. } = artifact.target; for (kind, filename) in kinds.iter().zip(artifact.filenames) { let from = filename.into_std_path_buf(); if let Some(kind) = ArtifactKind::parse(kind) { let crate_name = name.clone(); let artifact = Artifact { kind, crate_name }; - self.artifacts.copy(&artifact, &from); + self.artifacts.copy(&artifact, &from)?; } } } } - // FIXME: panic - if command.wait().expect("Couldn't get cargo's exit status").success() { - Status::Success - } else { - Status::Failure - } + command.wait().map_err(CargoError::CommandWaitFailed)?; + Ok(()) } } diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs index 0255df32e..733ae2c50 100644 --- a/crates/cargo-cp-artifact/src/cli.rs +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -32,7 +32,6 @@ impl std::fmt::Display for ParseError { } } -//struct Args(std::iter::Skip); pub struct Args(T); impl Args> { @@ -140,12 +139,19 @@ fn get_crate_name_from_env() -> Result { } pub fn run(skip: usize) -> Status { - match Args::new(skip).parse(get_crate_name_from_env) { - Ok(cargo) => { cargo.exec() } + let cargo = match Args::new(skip).parse(get_crate_name_from_env) { + Ok(cargo) => cargo, Err(err) => { eprintln!("{err}"); - Status::Failure + return Status::Failure; } + }; + + if let Err(err) = cargo.exec() { + eprintln!("{err}"); + Status::Failure + } else { + Status::Success } } From 85da6c644efc14ff49437e97ef8fe648d1bc39c6 Mon Sep 17 00:00:00 2001 From: David Herman Date: Tue, 28 Feb 2023 21:06:36 -0800 Subject: [PATCH 4/9] Lints --- crates/cargo-cp-artifact/src/artifact.rs | 8 +- crates/cargo-cp-artifact/src/cargo.rs | 23 ++-- crates/cargo-cp-artifact/src/cli.rs | 135 ++++++++++------------- 3 files changed, 78 insertions(+), 88 deletions(-) diff --git a/crates/cargo-cp-artifact/src/artifact.rs b/crates/cargo-cp-artifact/src/artifact.rs index 6ae1e329b..68441453c 100644 --- a/crates/cargo-cp-artifact/src/artifact.rs +++ b/crates/cargo-cp-artifact/src/artifact.rs @@ -60,7 +60,6 @@ fn is_newer(from: &Path, to: &Path) -> bool { } impl Artifact { - pub fn copy(&self, from: &Path, to: &Path) -> Result<(), ArtifactError> { if !is_newer(from, to) { return Ok(()); @@ -84,7 +83,9 @@ impl Artifact { if let Err(err) = remove_file(to) { match err.kind() { ErrorKind::NotFound => {} - _ => { return Err(ArtifactError::OverwriteFailed(err)); } + _ => { + return Err(ArtifactError::OverwriteFailed(err)); + } } } } @@ -92,5 +93,4 @@ impl Artifact { std::fs::copy(from, to).map_err(ArtifactError::CopyFailed)?; Ok(()) } - -} \ No newline at end of file +} diff --git a/crates/cargo-cp-artifact/src/cargo.rs b/crates/cargo-cp-artifact/src/cargo.rs index adf3d93ca..bcfd61d84 100644 --- a/crates/cargo-cp-artifact/src/cargo.rs +++ b/crates/cargo-cp-artifact/src/cargo.rs @@ -1,9 +1,9 @@ use crate::artifact::{Artifact, ArtifactError, ArtifactKind}; +use cargo_metadata::{Message, Target}; use std::collections::HashMap; -use std::process::{Stdio, Command}; use std::path::Path; -use cargo_metadata::{Message, Target}; +use std::process::{Command, Stdio}; #[derive(Debug, PartialEq, Eq)] pub struct CopyMap(HashMap>); @@ -26,7 +26,9 @@ impl CopyMap { pub fn copy(&self, artifact: &Artifact, from: &Path) -> Result<(), CargoError> { if let Some(output_files) = self.0.get(&artifact) { for output_file in output_files { - artifact.copy(from, Path::new(output_file)).map_err(CargoError::ArtifactCopyFailed)?; + artifact + .copy(from, Path::new(output_file)) + .map_err(CargoError::ArtifactCopyFailed)?; } } Ok(()) @@ -38,10 +40,7 @@ impl CopyMap { pub fn set(&mut self, artifact: Artifact, output_files: &[&str]) { let _ = self.0.insert( artifact, - output_files - .iter() - .map(|s| s.to_string()) - .collect(), + output_files.iter().map(|s| s.to_string()).collect(), ); } } @@ -86,7 +85,11 @@ impl std::fmt::Display for CargoError { impl CargoCommand { pub fn new(artifacts: CopyMap, command: String, args: Vec) -> Self { - Self { artifacts, command, args } + Self { + artifacts, + command, + args, + } } pub fn exec(self) -> Result<(), CargoError> { @@ -100,7 +103,9 @@ impl CargoCommand { for message in cargo_metadata::Message::parse_stream(reader) { let message = message.map_err(CargoError::MessageParseFailed)?; if let Message::CompilerArtifact(artifact) = message { - let Target { kind: kinds, name, .. } = artifact.target; + let Target { + kind: kinds, name, .. + } = artifact.target; for (kind, filename) in kinds.iter().zip(artifact.filenames) { let from = filename.into_std_path_buf(); if let Some(kind) = ArtifactKind::parse(kind) { diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs index 733ae2c50..f3926614b 100644 --- a/crates/cargo-cp-artifact/src/cli.rs +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -81,15 +81,15 @@ impl> Args { F: Fn() -> Result, { let mut artifacts = CopyMap::new(); - + loop { let token = self.next()?; let token = token.as_str(); - + if token == "--" { break; } - + if token == "--artifact" || (token.len() <= 3 && token.starts_with("-a")) { let kind = self.get_artifact_kind(token)?; let crate_name = self.next()?; @@ -97,7 +97,7 @@ impl> Args { artifacts.add(kind, crate_name, output_file); continue; } - + if token == "--npm" || (token.len() <= 3 && token.starts_with("-n")) { let kind = self.get_artifact_kind(token)?; let mut crate_name = get_crate_name()?; @@ -113,12 +113,12 @@ impl> Args { artifacts.add(kind, crate_name, output_file); continue; } - + return Err(ParseError::UnexpectedOption(token.to_string())); } - + let command = self.next()?; - + Ok(CargoCommand::new(artifacts, command, self.rest())) } } @@ -194,25 +194,37 @@ mod test { "expected artifact type parse error", ); } - + #[test] fn test_missing_env_var() { let r = args!["-nc", "a", "b", "--"].parse(get_crate_name_err); assert_err!(r, ParseError::EnvVarNotFound, "expected env var error"); } - + #[test] fn test_missing_command() { let r = args!["-ac", "a", "b"].parse(get_crate_name_ok); - assert_err!(r, ParseError::CommandNotFound, "expected command not found error"); + assert_err!( + r, + ParseError::CommandNotFound, + "expected command not found error" + ); let r = args!["-ac", "a", "b", "--"].parse(get_crate_name_ok); - assert_err!(r, ParseError::CommandNotFound, "expected command not found error"); + assert_err!( + r, + ParseError::CommandNotFound, + "expected command not found error" + ); } - + #[test] fn test_invalid_option() { let r = args!["-q"].parse(get_crate_name_ok); - assert_err!(r, ParseError::UnexpectedOption("-q".to_string()), "expected bad option error"); + assert_err!( + r, + ParseError::UnexpectedOption("-q".to_string()), + "expected bad option error" + ); } fn example_artifact1() -> Artifact { @@ -244,7 +256,11 @@ mod test { let command = "a".to_string(); let args = vec!["b".to_string(), "c".to_string()]; - CargoCommand { artifacts, command, args } + CargoCommand { + artifacts, + command, + args, + } } fn example_complex_cargo_command() -> CargoCommand { @@ -257,7 +273,11 @@ mod test { let command = "a".to_string(); let args = vec!["b".to_string(), "c".to_string()]; - CargoCommand { artifacts, command, args } + CargoCommand { + artifacts, + command, + args, + } } #[test] @@ -271,34 +291,20 @@ mod test { "a", "b", "c" - ].parse(get_crate_name_ok) - .expect("expected successful parse"); + ] + .parse(get_crate_name_ok) + .expect("expected successful parse"); assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); - let cmd = args![ - "-a", - "bin", - "my-crate", - "my-bin", - "--", - "a", - "b", - "c" - ].parse(get_crate_name_ok) + let cmd = args!["-a", "bin", "my-crate", "my-bin", "--", "a", "b", "c"] + .parse(get_crate_name_ok) .expect("expected successful parse"); assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); - let cmd = args![ - "-ab", - "my-crate", - "my-bin", - "--", - "a", - "b", - "c" - ].parse(get_crate_name_ok) + let cmd = args!["-ab", "my-crate", "my-bin", "--", "a", "b", "c"] + .parse(get_crate_name_ok) .expect("expected successful parse"); assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); @@ -306,40 +312,20 @@ mod test { #[test] fn test_npm_option() { - let cmd = args![ - "--npm", - "bin", - "my-bin", - "--", - "a", - "b", - "c" - ].parse(get_crate_name_ok) + let cmd = args!["--npm", "bin", "my-bin", "--", "a", "b", "c"] + .parse(get_crate_name_ok) .expect("expected successful parse"); assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); - let cmd = args![ - "-n", - "bin", - "my-bin", - "--", - "a", - "b", - "c" - ].parse(get_crate_name_ok) + let cmd = args!["-n", "bin", "my-bin", "--", "a", "b", "c"] + .parse(get_crate_name_ok) .expect("expected successful parse"); assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); - let cmd = args![ - "-nb", - "my-bin", - "--", - "a", - "b", - "c" - ].parse(get_crate_name_ok) + let cmd = args!["-nb", "my-bin", "--", "a", "b", "c"] + .parse(get_crate_name_ok) .expect("expected successful parse"); assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); @@ -347,15 +333,8 @@ mod test { #[test] fn test_namespace_removal() { - let cmd = args![ - "--npm", - "bin", - "my-bin", - "--", - "a", - "b", - "c" - ].parse(get_crate_name_with_namespace) + let cmd = args!["--npm", "bin", "my-bin", "--", "a", "b", "c"] + .parse(get_crate_name_with_namespace) .expect("expected successful parse"); assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); @@ -380,9 +359,15 @@ mod test { "a", "b", "c" - ].parse(get_crate_name_ok) - .expect("expected successful parse"); - - assert_eq!(cmd, example_complex_cargo_command(), "improperly parsed: {:?}", cmd); + ] + .parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!( + cmd, + example_complex_cargo_command(), + "improperly parsed: {:?}", + cmd + ); } } From dadaae6aae1376537a9a258c37eddd1284bdd638 Mon Sep 17 00:00:00 2001 From: David Herman Date: Tue, 28 Feb 2023 21:15:17 -0800 Subject: [PATCH 5/9] Replace `allow_dead_code` with `#[cfg(test)]` --- crates/cargo-cp-artifact/src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs index f3926614b..5b2480a32 100644 --- a/crates/cargo-cp-artifact/src/cli.rs +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -40,14 +40,14 @@ impl Args> { } } +#[cfg(test)] impl<'a> Args> { - #[allow(dead_code)] fn from_vec(v: Vec) -> Self { Self(v.into_iter()) } } -#[allow(unused_macros)] +#[cfg(test)] macro_rules! args { [$($s:literal),*] => { Args::from_vec(vec![$($s.to_string()),*]) From 3cb0b1e04376b7b6ced3ae18f0ba05b5db1e4871 Mon Sep 17 00:00:00 2001 From: David Herman Date: Tue, 28 Feb 2023 21:16:22 -0800 Subject: [PATCH 6/9] Prettier --- pkgs/cargo-cp-artifact/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkgs/cargo-cp-artifact/package.json b/pkgs/cargo-cp-artifact/package.json index de996fb35..36ae2febd 100644 --- a/pkgs/cargo-cp-artifact/package.json +++ b/pkgs/cargo-cp-artifact/package.json @@ -32,6 +32,5 @@ "url": "https://github.com/neon-bindings/neon/issues" }, "homepage": "https://github.com/neon-bindings/neon/tree/main/pkgs/cargo-cp-artifact", - "devDependencies": { - } + "devDependencies": {} } From 22fd5ea24fcb0cd57e6a9b1b506d3fc7aa90e6c2 Mon Sep 17 00:00:00 2001 From: David Herman Date: Tue, 28 Feb 2023 21:20:15 -0800 Subject: [PATCH 7/9] Move test helpers into test module --- crates/cargo-cp-artifact/src/cli.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs index 5b2480a32..9eaf3ee35 100644 --- a/crates/cargo-cp-artifact/src/cli.rs +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -40,20 +40,6 @@ impl Args> { } } -#[cfg(test)] -impl<'a> Args> { - fn from_vec(v: Vec) -> Self { - Self(v.into_iter()) - } -} - -#[cfg(test)] -macro_rules! args { - [$($s:literal),*] => { - Args::from_vec(vec![$($s.to_string()),*]) - } -} - impl> Args { fn next(&mut self) -> Result { let Self(args) = self; @@ -160,6 +146,18 @@ mod test { use super::*; use crate::artifact::{Artifact, ArtifactKind}; + impl<'a> Args> { + fn from_vec(v: Vec) -> Self { + Self(v.into_iter()) + } + } + + macro_rules! args { + [$($s:literal),*] => { + Args::from_vec(vec![$($s.to_string()),*]) + } + } + macro_rules! assert_err { ($actual:expr, $expected:expr, $($arg:tt)+) => { { From 0ec5350a7ec7eebecc1f597144461716c879afe0 Mon Sep 17 00:00:00 2001 From: David Herman Date: Tue, 28 Feb 2023 21:20:49 -0800 Subject: [PATCH 8/9] Lint --- crates/cargo-cp-artifact/src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs index 9eaf3ee35..2332ee930 100644 --- a/crates/cargo-cp-artifact/src/cli.rs +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -157,7 +157,7 @@ mod test { Args::from_vec(vec![$($s.to_string()),*]) } } - + macro_rules! assert_err { ($actual:expr, $expected:expr, $($arg:tt)+) => { { From 1abff5c7be1025b8bb0444008eb33f17af619902 Mon Sep 17 00:00:00 2001 From: David Herman Date: Sat, 1 Apr 2023 16:44:52 -0700 Subject: [PATCH 9/9] experiment: a `cross-target` package for establishing cross-compilation targets --- .github/workflows/cross.yml | 38 + crates/cargo-cp-artifact/src/bin.rs | 1 + crates/cargo-cp-artifact/src/cargo.rs | 117 +- crates/cargo-cp-artifact/src/cli.rs | 115 +- crates/cargo-cp-artifact/src/copy.rs | 162 ++ crates/cargo-cp-artifact/src/lib.rs | 1 + package-lock.json | 185 +- package.json | 3 + pkgs/cargo-cp-artifact/.log/.gitignore | 1 + pkgs/cargo-cp-artifact/.log/README.md | 3 + pkgs/cargo-cp-artifact/.npmignore | 1 + pkgs/cargo-cp-artifact/package-lock.json | 46 +- pkgs/cargo-cp-artifact/package.json | 17 +- pkgs/cross-target/.mocharc.json | 5 + pkgs/cross-target/package-lock.json | 1896 +++++++++++++++++++++ pkgs/cross-target/package.json | 43 + pkgs/cross-target/src/bin/cross-target.ts | 139 ++ pkgs/cross-target/src/die.ts | 6 + pkgs/cross-target/src/node.ts | 34 + pkgs/cross-target/src/rust.ts | 163 ++ pkgs/cross-target/tsconfig.json | 21 + 21 files changed, 2805 insertions(+), 192 deletions(-) create mode 100644 .github/workflows/cross.yml create mode 100644 crates/cargo-cp-artifact/src/copy.rs create mode 100644 pkgs/cargo-cp-artifact/.log/.gitignore create mode 100644 pkgs/cargo-cp-artifact/.log/README.md create mode 100644 pkgs/cargo-cp-artifact/.npmignore create mode 100644 pkgs/cross-target/.mocharc.json create mode 100644 pkgs/cross-target/package-lock.json create mode 100644 pkgs/cross-target/package.json create mode 100644 pkgs/cross-target/src/bin/cross-target.ts create mode 100644 pkgs/cross-target/src/die.ts create mode 100644 pkgs/cross-target/src/node.ts create mode 100644 pkgs/cross-target/src/rust.ts create mode 100644 pkgs/cross-target/tsconfig.json diff --git a/.github/workflows/cross.yml b/.github/workflows/cross.yml new file mode 100644 index 000000000..2683fddc5 --- /dev/null +++ b/.github/workflows/cross.yml @@ -0,0 +1,38 @@ +name: cargo-cp-artifact + +on: + push: + # Prevent duplicate runs of this workflow on our own internal PRs. + branches: + - main + - next/* + pull_request: + types: [opened, synchronize, reopened, labeled] + branches: + - main + - next/* + +jobs: + matrix: + runs-on: ubuntu-latest + outputs: + includes: ${{ steps.define.includes }} + steps: + - name: Define Cross-Compilation Target Matrix + id: define + uses: dherman/target-matrix@v1 + with: + toolchain: rust + windows-latest: x86_64-pc-windows-msvc + macos-latest: x86_64-apple-darwin + ubuntu-latest: x86_64-unknown-linux-gnu x86_64-unknown-freebsd x86_64-unknown-openbsd + + build: + needs: matrix + runs-on: ${{ matrix.os }} + strategy: + matrix: + includes: ${{ needs.matrix.includes }} + steps: + - name: Show Target + run: echo "${{ matrix.target }}" diff --git a/crates/cargo-cp-artifact/src/bin.rs b/crates/cargo-cp-artifact/src/bin.rs index 65798eb67..a4ef6c931 100644 --- a/crates/cargo-cp-artifact/src/bin.rs +++ b/crates/cargo-cp-artifact/src/bin.rs @@ -1,6 +1,7 @@ mod artifact; mod cargo; mod cli; +mod copy; use cargo::Status; diff --git a/crates/cargo-cp-artifact/src/cargo.rs b/crates/cargo-cp-artifact/src/cargo.rs index bcfd61d84..8f1cb6037 100644 --- a/crates/cargo-cp-artifact/src/cargo.rs +++ b/crates/cargo-cp-artifact/src/cargo.rs @@ -1,67 +1,10 @@ -use crate::artifact::{Artifact, ArtifactError, ArtifactKind}; - -use cargo_metadata::{Message, Target}; -use std::collections::HashMap; -use std::path::Path; -use std::process::{Command, Stdio}; - -#[derive(Debug, PartialEq, Eq)] -pub struct CopyMap(HashMap>); - -impl CopyMap { - pub fn new() -> Self { - Self(HashMap::new()) - } - - pub fn add(&mut self, kind: ArtifactKind, crate_name: String, output_file: String) { - let key = Artifact { kind, crate_name }; - - if !self.0.contains_key(&key) { - let _ = self.0.insert(key, vec![output_file]); - } else { - self.0.get_mut(&key).unwrap().push(output_file); - } - } - - pub fn copy(&self, artifact: &Artifact, from: &Path) -> Result<(), CargoError> { - if let Some(output_files) = self.0.get(&artifact) { - for output_file in output_files { - artifact - .copy(from, Path::new(output_file)) - .map_err(CargoError::ArtifactCopyFailed)?; - } - } - Ok(()) - } -} - -#[cfg(test)] -impl CopyMap { - pub fn set(&mut self, artifact: Artifact, output_files: &[&str]) { - let _ = self.0.insert( - artifact, - output_files.iter().map(|s| s.to_string()).collect(), - ); - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct CargoCommand { - pub artifacts: CopyMap, - pub command: String, - pub args: Vec, -} - -pub enum Status { - Success, - Failure, -} +use cargo_metadata::{Message, MessageIter}; +use std::io::BufReader; +use std::process::{Command, Stdio, Child, ChildStdout}; pub enum CargoError { SpawnFailed(std::io::Error), MessageParseFailed(std::io::Error), - CommandWaitFailed(std::io::Error), - ArtifactCopyFailed(ArtifactError), } impl std::fmt::Display for CargoError { @@ -73,51 +16,37 @@ impl std::fmt::Display for CargoError { CargoError::MessageParseFailed(err) => { write!(f, "Could not read command output: {}", err) } - CargoError::CommandWaitFailed(err) => { - write!(f, "Command failed to exit: {}", err) - } - CargoError::ArtifactCopyFailed(err) => { - write!(f, "Failed to copy artifact: {}", err) - } } } } +#[derive(Debug, PartialEq, Eq)] +pub struct CargoCommand { + pub command: String, + pub args: Vec, +} + +pub enum Status { + Success, + Failure, +} + +pub type CargoStream = MessageIter>; + impl CargoCommand { - pub fn new(artifacts: CopyMap, command: String, args: Vec) -> Self { - Self { - artifacts, - command, - args, - } + pub fn new(command: String, args: Vec) -> Self { + Self { command, args } } - pub fn exec(self) -> Result<(), CargoError> { - let mut command = Command::new(self.command) + pub fn spawn(self) -> Result<(Child, CargoStream), CargoError> { + let mut child = Command::new(self.command) .args(&self.args) .stdout(Stdio::piped()) .spawn() .map_err(CargoError::SpawnFailed)?; - let reader = std::io::BufReader::new(command.stdout.take().unwrap()); - for message in cargo_metadata::Message::parse_stream(reader) { - let message = message.map_err(CargoError::MessageParseFailed)?; - if let Message::CompilerArtifact(artifact) = message { - let Target { - kind: kinds, name, .. - } = artifact.target; - for (kind, filename) in kinds.iter().zip(artifact.filenames) { - let from = filename.into_std_path_buf(); - if let Some(kind) = ArtifactKind::parse(kind) { - let crate_name = name.clone(); - let artifact = Artifact { kind, crate_name }; - self.artifacts.copy(&artifact, &from)?; - } - } - } - } - - command.wait().map_err(CargoError::CommandWaitFailed)?; - Ok(()) + let reader = BufReader::new(child.stdout.take().unwrap()); + let stream = Message::parse_stream(reader); + Ok((child, stream)) } } diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs index 2332ee930..25676bf7d 100644 --- a/crates/cargo-cp-artifact/src/cli.rs +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -1,10 +1,13 @@ use crate::artifact::ArtifactKind; -use crate::cargo::{CargoCommand, CopyMap, Status}; +use crate::cargo::Status; +use crate::copy::{CopyMap, CopyPlan}; #[derive(Debug, PartialEq, Eq)] pub enum ParseError { UnexpectedArtifactKind(String), - CommandNotFound, + MissingArtifactKind, + MissingCrateName, + MissingOutputFile, EnvVarNotFound, UnexpectedOption(String), } @@ -15,8 +18,20 @@ impl std::fmt::Display for ParseError { ParseError::UnexpectedArtifactKind(found) => { write!(f, "Unexpected artifact type: {found}") } - ParseError::CommandNotFound => { - writeln!(f, "Missing command to execute.")?; + ParseError::MissingArtifactKind => { + writeln!(f, "Missing artifact type.")?; + writeln!(f, "")?; + write!(f, "cargo-cp-artifact -a cdylib my-crate index.node ")?; + write!(f, "-- cargo build --message-format=json-render-diagnostics") + } + ParseError::MissingCrateName => { + writeln!(f, "Missing crate name.")?; + writeln!(f, "")?; + write!(f, "cargo-cp-artifact -a cdylib my-crate index.node ")?; + write!(f, "-- cargo build --message-format=json-render-diagnostics") + } + ParseError::MissingOutputFile => { + writeln!(f, "Missing output file.")?; writeln!(f, "")?; write!(f, "cargo-cp-artifact -a cdylib my-crate index.node ")?; write!(f, "-- cargo build --message-format=json-render-diagnostics") @@ -41,12 +56,9 @@ impl Args> { } impl> Args { - fn next(&mut self) -> Result { + fn next(&mut self) -> Option { let Self(args) = self; - match args.next() { - Some(token) => Ok(token), - None => Err(ParseError::CommandNotFound), - } + args.next() } fn rest(self) -> Vec { @@ -58,18 +70,24 @@ impl> Args { if token.len() == 3 && &token[1..2] != "-" { validate_artifact_kind(&token[2..3]) } else { - validate_artifact_kind(self.next()?.as_str()) + match self.next() { + Some(kind) => validate_artifact_kind(kind.as_str()), + None => Err(ParseError::MissingArtifactKind), + } } } - fn parse(mut self, get_crate_name: F) -> Result + fn parse(mut self, get_crate_name: F) -> Result where F: Fn() -> Result, { let mut artifacts = CopyMap::new(); loop { - let token = self.next()?; + let token = match self.next() { + Some(token) => token, + None => { break; } + }; let token = token.as_str(); if token == "--" { @@ -78,8 +96,8 @@ impl> Args { if token == "--artifact" || (token.len() <= 3 && token.starts_with("-a")) { let kind = self.get_artifact_kind(token)?; - let crate_name = self.next()?; - let output_file = self.next()?; + let crate_name = self.next().ok_or(ParseError::MissingCrateName)?; + let output_file = self.next().ok_or(ParseError::MissingOutputFile)?; artifacts.add(kind, crate_name, output_file); continue; } @@ -95,7 +113,7 @@ impl> Args { } } - let output_file = self.next()?; + let output_file = self.next().ok_or(ParseError::MissingOutputFile)?; artifacts.add(kind, crate_name, output_file); continue; } @@ -103,9 +121,10 @@ impl> Args { return Err(ParseError::UnexpectedOption(token.to_string())); } - let command = self.next()?; - - Ok(CargoCommand::new(artifacts, command, self.rest())) + Ok(match self.next() { + Some(command) => CopyPlan::cargo(artifacts, command, self.rest()), + None => CopyPlan::stdin(artifacts) + }) } } @@ -145,6 +164,8 @@ pub fn run(skip: usize) -> Status { mod test { use super::*; use crate::artifact::{Artifact, ArtifactKind}; + use crate::cargo::CargoCommand; + use crate::copy::{CopyAction, CopyPlan}; impl<'a> Args> { fn from_vec(v: Vec) -> Self { @@ -201,17 +222,25 @@ mod test { #[test] fn test_missing_command() { - let r = args!["-ac", "a", "b"].parse(get_crate_name_ok); - assert_err!( - r, - ParseError::CommandNotFound, - "expected command not found error" + let cmd = args!["-ac", "my-crate", "my-bin"] + .parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!( + cmd, + example_stdin(), + "expected stdin plan: {:?}", + cmd ); - let r = args!["-ac", "a", "b", "--"].parse(get_crate_name_ok); - assert_err!( - r, - ParseError::CommandNotFound, - "expected command not found error" + + let cmd = args!["-ac", "my-crate", "my-bin", "--"] + .parse(get_crate_name_ok) + .expect("expected successful parse"); + assert_eq!( + cmd, + example_stdin(), + "expected stdin plan: {:?}", + cmd ); } @@ -246,7 +275,17 @@ mod test { } } - fn example_cargo_command() -> CargoCommand { + fn example_stdin() -> CopyPlan { + let mut artifacts = CopyMap::new(); + let artifact = example_artifact3(); + artifacts.set(artifact, &["my-bin"]); + + let action = CopyAction::Stdin; + + CopyPlan { artifacts, action } + } + + fn example_cargo_command() -> CopyPlan { let mut artifacts = CopyMap::new(); let artifact = example_artifact1(); artifacts.set(artifact, &["my-bin"]); @@ -254,14 +293,15 @@ mod test { let command = "a".to_string(); let args = vec!["b".to_string(), "c".to_string()]; - CargoCommand { - artifacts, + let action = CopyAction::Cargo(CargoCommand { command, args, - } + }); + + CopyPlan { artifacts, action } } - fn example_complex_cargo_command() -> CargoCommand { + fn example_complex_cargo_command() -> CopyPlan { let mut artifacts = CopyMap::new(); artifacts.set(example_artifact1(), &["my-bin", "other-copy"]); @@ -271,11 +311,12 @@ mod test { let command = "a".to_string(); let args = vec!["b".to_string(), "c".to_string()]; - CargoCommand { - artifacts, + let action = CopyAction::Cargo(CargoCommand { command, args, - } + }); + + CopyPlan { artifacts, action } } #[test] @@ -340,7 +381,7 @@ mod test { #[test] fn test_complex_command() { - let cmd: CargoCommand = args![ + let cmd: CopyPlan = args![ "-nb", "my-bin", "--artifact", diff --git a/crates/cargo-cp-artifact/src/copy.rs b/crates/cargo-cp-artifact/src/copy.rs new file mode 100644 index 000000000..f04d43927 --- /dev/null +++ b/crates/cargo-cp-artifact/src/copy.rs @@ -0,0 +1,162 @@ +use crate::artifact::{Artifact, ArtifactError, ArtifactKind}; +use crate::cargo::{CargoCommand, CargoError, CargoStream}; + +use cargo_metadata::{Message, Target, MessageIter}; +use std::collections::HashMap; +use std::io::StdinLock; +use std::path::Path; +use std::process::Child; + +pub enum CopyError { + CargoFailed(CargoError), + ChildFailed(std::io::Error), + ArtifactCopyFailed(ArtifactError), +} + +impl From for CopyError { + fn from(value: CargoError) -> Self { + CopyError::CargoFailed(value) + } +} + +impl std::fmt::Display for CopyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CopyError::CargoFailed(err) => { + err.fmt(f) + } + CopyError::ChildFailed(err) => { + write!(f, "Command failed to exit: {}", err) + } + CopyError::ArtifactCopyFailed(err) => { + write!(f, "Failed to copy artifact: {}", err) + } + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct CopyMap(HashMap>); + +impl CopyMap { + pub fn new() -> Self { + Self(HashMap::new()) + } + + pub fn add(&mut self, kind: ArtifactKind, crate_name: String, output_file: String) { + let key = Artifact { kind, crate_name }; + + if !self.0.contains_key(&key) { + let _ = self.0.insert(key, vec![output_file]); + } else { + self.0.get_mut(&key).unwrap().push(output_file); + } + } + + pub fn copy(&self, artifact: &Artifact, from: &Path) -> Result<(), CopyError> { + if let Some(output_files) = self.0.get(&artifact) { + for output_file in output_files { + artifact + .copy(from, Path::new(output_file)) + .map_err(CopyError::ArtifactCopyFailed)?; + } + } + Ok(()) + } +} + +#[cfg(test)] +impl CopyMap { + pub fn set(&mut self, artifact: Artifact, output_files: &[&str]) { + let _ = self.0.insert( + artifact, + output_files.iter().map(|s| s.to_string()).collect(), + ); + } +} + +type StdinStream = MessageIter>; + +fn stdin_stream() -> StdinStream { + Message::parse_stream(std::io::stdin().lock()) +} + +#[derive(Debug, PartialEq, Eq)] +pub enum CopyAction { + Cargo(CargoCommand), + Stdin, +} + +impl CopyAction { + fn start(self) -> Result<(CopyStream, Option), CopyError> { + match self { + CopyAction::Cargo(command) => { + let (child, stream) = command.spawn()?; + Ok((CopyStream::Cargo(stream), Some(child))) + } + CopyAction::Stdin => { + Ok((CopyStream::Stdin(stdin_stream()), None)) + } + } + } +} +enum CopyStream { + Cargo(CargoStream), + Stdin(StdinStream), +} + +impl Iterator for CopyStream { + type Item = Result; + + fn next(&mut self) -> Option { + match self { + CopyStream::Cargo(stream) => stream.next(), + CopyStream::Stdin(stream) => stream.next(), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct CopyPlan { + pub artifacts: CopyMap, + pub action: CopyAction, +} + +impl CopyPlan { + pub fn cargo(artifacts: CopyMap, command: String, args: Vec) -> Self { + let action = CopyAction::Cargo(CargoCommand::new(command, args)); + Self { artifacts, action } + } + + pub fn stdin(artifacts: CopyMap) -> Self { + let action = CopyAction::Stdin; + Self { artifacts, action } + } + + pub fn exec(self) -> Result<(), CopyError> { + let (stream, child) = self.action.start()?; + + for message in stream { + let message = message.map_err(CargoError::MessageParseFailed)?; + if let Message::CompilerArtifact(artifact) = message { + let Target { + kind: kinds, name, .. + } = artifact.target; + for (kind, filename) in kinds.iter().zip(artifact.filenames) { + let from = filename.into_std_path_buf(); + if let Some(kind) = ArtifactKind::parse(kind) { + let crate_name = name.clone(); + let artifact = Artifact { kind, crate_name }; + self.artifacts.copy(&artifact, &from)?; + } + } + } + } + + if let Some(mut child) = child { + child.wait().map_err(CopyError::ChildFailed)?; + } + + Ok(()) + } +} diff --git a/crates/cargo-cp-artifact/src/lib.rs b/crates/cargo-cp-artifact/src/lib.rs index 32d453527..de1dd211a 100644 --- a/crates/cargo-cp-artifact/src/lib.rs +++ b/crates/cargo-cp-artifact/src/lib.rs @@ -1,3 +1,4 @@ pub mod artifact; pub mod cargo; pub mod cli; +pub mod copy; diff --git a/package-lock.json b/package-lock.json index 2a126a1db..4ad96cb89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "pkgs/*", "test/*" ], + "dependencies": { + "cross-target": "file:pkgs/cross-target" + }, "devDependencies": { "prettier": "^2.7.1" } @@ -73,11 +76,20 @@ } }, "node_modules/@types/chai": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", - "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", "dev": true }, + "node_modules/@types/find-package-json": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/find-package-json/-/find-package-json-1.2.3.tgz", + "integrity": "sha512-UCmxThB05dWlToG6ekGbW3iJLUzd7Z2uGP7R+d1ktlgOttxRGgqQcIeIaCT4pLlO2WkPg9bebqBhs4Epw4Bs8Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -101,9 +113,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", - "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==", + "version": "18.15.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz", + "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==", "dev": true }, "node_modules/@types/rimraf": { @@ -116,12 +128,6 @@ "@types/node": "*" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -306,14 +312,14 @@ "link": true }, "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", - "deep-eql": "^3.0.1", + "deep-eql": "^4.1.2", "get-func-name": "^2.0.0", "loupe": "^2.3.1", "pathval": "^1.1.1", @@ -481,6 +487,10 @@ "node": ">= 8" } }, + "node_modules/cross-target": { + "resolved": "pkgs/cross-target", + "link": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -523,15 +533,15 @@ } }, "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, "dependencies": { "type-detect": "^4.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=6" } }, "node_modules/defer-to-connect": { @@ -743,6 +753,11 @@ "node": ">=8" } }, + "node_modules/find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -1407,12 +1422,11 @@ } }, "node_modules/mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "dependencies": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", @@ -2103,9 +2117,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -2285,12 +2299,13 @@ }, "pkgs/cargo-cp-artifact": { "version": "0.2.0", - "hasInstallScript": true, "license": "MIT", "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, - "devDependencies": {} + "devDependencies": { + "cross-target": "file:../cross-target" + } }, "pkgs/create-neon": { "version": "0.2.0", @@ -2315,6 +2330,32 @@ "typescript": "^4.7.4" } }, + "pkgs/cross-target": { + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "find-package-json": "^1.2.0", + "handlebars": "^4.7.7" + }, + "bin": { + "cross-target": "dist/bin/cross-target.js" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/find-package-json": "^1.2.3", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.0", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + } + }, + "pkgs/cross-target/node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, "test/electron": { "name": "electron-tests", "version": "0.1.0", @@ -2401,11 +2442,20 @@ } }, "@types/chai": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", - "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", "dev": true }, + "@types/find-package-json": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/find-package-json/-/find-package-json-1.2.3.tgz", + "integrity": "sha512-UCmxThB05dWlToG6ekGbW3iJLUzd7Z2uGP7R+d1ktlgOttxRGgqQcIeIaCT4pLlO2WkPg9bebqBhs4Epw4Bs8Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -2429,9 +2479,9 @@ "dev": true }, "@types/node": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", - "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==", + "version": "18.15.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz", + "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==", "dev": true }, "@types/rimraf": { @@ -2444,12 +2494,6 @@ "@types/node": "*" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -2587,17 +2631,20 @@ "dev": true }, "cargo-cp-artifact": { - "version": "file:pkgs/cargo-cp-artifact" + "version": "file:pkgs/cargo-cp-artifact", + "requires": { + "cross-target": "file:../cross-target" + } }, "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", - "deep-eql": "^3.0.1", + "deep-eql": "^4.1.2", "get-func-name": "^2.0.0", "loupe": "^2.3.1", "pathval": "^1.1.1", @@ -2745,6 +2792,28 @@ "which": "^2.0.1" } }, + "cross-target": { + "version": "file:pkgs/cross-target", + "requires": { + "@types/chai": "^4.3.4", + "@types/find-package-json": "^1.2.3", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.0", + "chai": "^4.3.7", + "find-package-json": "^1.2.0", + "handlebars": "^4.7.7", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + }, + "dependencies": { + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + } + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2770,9 +2839,9 @@ } }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -2961,6 +3030,11 @@ "to-regex-range": "^5.0.1" } }, + "find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==" + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3463,12 +3537,11 @@ } }, "mocha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", - "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", @@ -3986,9 +4059,9 @@ "dev": true }, "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 35885c27b..238a89cb0 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "pkgs/*", "test/*" ], + "dependencies": { + "cross-target": "file:pkgs/cross-target" + }, "devDependencies": { "prettier": "^2.7.1" } diff --git a/pkgs/cargo-cp-artifact/.log/.gitignore b/pkgs/cargo-cp-artifact/.log/.gitignore new file mode 100644 index 000000000..397b4a762 --- /dev/null +++ b/pkgs/cargo-cp-artifact/.log/.gitignore @@ -0,0 +1 @@ +*.log diff --git a/pkgs/cargo-cp-artifact/.log/README.md b/pkgs/cargo-cp-artifact/.log/README.md new file mode 100644 index 000000000..c07570c41 --- /dev/null +++ b/pkgs/cargo-cp-artifact/.log/README.md @@ -0,0 +1,3 @@ +This directory contains automatically generated cargo build logs. +These logs are used by the build to find generated artifacts needed +to distribute precompiled Neon binaries. diff --git a/pkgs/cargo-cp-artifact/.npmignore b/pkgs/cargo-cp-artifact/.npmignore new file mode 100644 index 000000000..6ab38c85c --- /dev/null +++ b/pkgs/cargo-cp-artifact/.npmignore @@ -0,0 +1 @@ +.log diff --git a/pkgs/cargo-cp-artifact/package-lock.json b/pkgs/cargo-cp-artifact/package-lock.json index ac2b0cdd8..5ce359e83 100644 --- a/pkgs/cargo-cp-artifact/package-lock.json +++ b/pkgs/cargo-cp-artifact/package-lock.json @@ -7,12 +7,54 @@ "": { "name": "cargo-cp-artifact", "version": "0.2.0", - "hasInstallScript": true, "license": "MIT", "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, - "devDependencies": {} + "devDependencies": { + "cross-target": "file:../cross-target" + } + }, + "../cross-target": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-package-json": "^1.2.0", + "handlebars": "^4.7.7" + }, + "bin": { + "cross-target": "dist/bin/cross-target.js" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/find-package-json": "^1.2.3", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.0", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + } + }, + "node_modules/cross-target": { + "resolved": "../cross-target", + "link": true + } + }, + "dependencies": { + "cross-target": { + "version": "file:../cross-target", + "requires": { + "@types/chai": "^4.3.4", + "@types/find-package-json": "^1.2.3", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.0", + "chai": "^4.3.7", + "find-package-json": "^1.2.0", + "handlebars": "^4.7.7", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + } } } } diff --git a/pkgs/cargo-cp-artifact/package.json b/pkgs/cargo-cp-artifact/package.json index 36ae2febd..02734f3c9 100644 --- a/pkgs/cargo-cp-artifact/package.json +++ b/pkgs/cargo-cp-artifact/package.json @@ -10,11 +10,20 @@ "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, + "cross-target": { + "toolchain": "rust", + "targets": ["x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"], + "scripts": { + "build": "cross build --target {{target}} --message-format=json-render-diagnostics > .log/{{target}}.log", + "dist": "cargo run -p cargo-cp-artifact -- -ac cargo-cp-artifact-adapter npm/{{target}}/index.node < .log/{{target}}.log" + } + }, "scripts": { - "build": "cargo run -p cargo-cp-artifact -- -ac cargo-cp-artifact-adapter index.node -- cargo build --message-format=json-render-diagnostics", + "build": "cross-target --current build", "build-debug": "npm run build --", "build-release": "npm run build -- --release", - "install": "npm run build-release", + "postbuild": "cross-target --current dist", + "prepublishOnly": "cross-target --all build -- --release && cross-target --all dist", "test": "cargo test" }, "repository": { @@ -32,5 +41,7 @@ "url": "https://github.com/neon-bindings/neon/issues" }, "homepage": "https://github.com/neon-bindings/neon/tree/main/pkgs/cargo-cp-artifact", - "devDependencies": {} + "devDependencies": { + "cross-target": "file:../cross-target" + } } diff --git a/pkgs/cross-target/.mocharc.json b/pkgs/cross-target/.mocharc.json new file mode 100644 index 000000000..6d1ee8acb --- /dev/null +++ b/pkgs/cross-target/.mocharc.json @@ -0,0 +1,5 @@ +{ + "extension": ["ts"], + "spec": "dist/test/**/*.js", + "timeout": 5000 +} diff --git a/pkgs/cross-target/package-lock.json b/pkgs/cross-target/package-lock.json new file mode 100644 index 000000000..4ac8f922d --- /dev/null +++ b/pkgs/cross-target/package-lock.json @@ -0,0 +1,1896 @@ +{ + "name": "cross-target", + "version": "0.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cross-target", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "find-package-json": "^1.2.0", + "handlebars": "^4.7.7" + }, + "bin": { + "cross-target": "dist/bin/cross-target.js" + }, + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/find-package-json": "^1.2.3", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.0", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + } + }, + "node_modules/@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "node_modules/@types/find-package-json": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/find-package-json/-/find-package-json-1.2.3.tgz", + "integrity": "sha512-UCmxThB05dWlToG6ekGbW3iJLUzd7Z2uGP7R+d1ktlgOttxRGgqQcIeIaCT4pLlO2WkPg9bebqBhs4Epw4Bs8Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.15.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz", + "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@types/chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==", + "dev": true + }, + "@types/find-package-json": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/find-package-json/-/find-package-json-1.2.3.tgz", + "integrity": "sha512-UCmxThB05dWlToG6ekGbW3iJLUzd7Z2uGP7R+d1ktlgOttxRGgqQcIeIaCT4pLlO2WkPg9bebqBhs4Epw4Bs8Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, + "@types/node": { + "version": "18.15.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz", + "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/pkgs/cross-target/package.json b/pkgs/cross-target/package.json new file mode 100644 index 000000000..05cfdb1bd --- /dev/null +++ b/pkgs/cross-target/package.json @@ -0,0 +1,43 @@ +{ + "name": "cross-target", + "version": "0.2.0", + "description": "Define multiple cross-compilation targets in your package.json!", + "bin": { + "cross-target": "dist/bin/cross-target.js" + }, + "files": [ + "dist/src/**/*" + ], + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build", + "pretest": "npm run build", + "test": "mocha" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/neon-bindings/neon.git" + }, + "keywords": [ + "neon" + ], + "author": "Dave Herman ", + "license": "MIT", + "bugs": { + "url": "https://github.com/neon-bindings/neon/issues" + }, + "homepage": "https://github.com/neon-bindings/neon#readme", + "devDependencies": { + "@types/chai": "^4.3.4", + "@types/find-package-json": "^1.2.3", + "@types/mocha": "^10.0.1", + "@types/node": "^18.15.0", + "chai": "^4.3.7", + "mocha": "^10.2.0", + "typescript": "^4.9.5" + }, + "dependencies": { + "find-package-json": "^1.2.0", + "handlebars": "^4.7.7" + } +} diff --git a/pkgs/cross-target/src/bin/cross-target.ts b/pkgs/cross-target/src/bin/cross-target.ts new file mode 100644 index 000000000..32be1421c --- /dev/null +++ b/pkgs/cross-target/src/bin/cross-target.ts @@ -0,0 +1,139 @@ +#!/usr/bin/env node + +import { spawnSync } from 'child_process'; +import finder from "find-package-json"; +import handlebars from "handlebars"; +import die from "../die"; +import * as rust from '../rust'; +import * as node from '../node'; + +function help() { + console.error(` +usage: + cross-target --current script [-- args ...] + Run cross-target \`script\` from package.json for the + current device's system target, with the specified + command-line arguments. + + cross-target --all script [-- args ...] + Run cross-target \`script\` from package.json for all + targets specified in package.json, passing the specified + command-line arguments to each invocation of the script. +`.trim()); + console.error(); +} + +async function main(args: string[]) { + if (args.length < 2) { + help(); + die("not enough arguments provided"); + } + + const targetsSpec = args.shift(); + + if (targetsSpec !== "--current" && targetsSpec !== "--all") { + help(); + die("expected '--current' or '--all'"); + } + + const scriptKey = args.shift()!; + + if (args.length > 0) { + if (args.shift() !== "--") { + help(); + die("expected '--'"); + } + } + + const manifests = finder(); + const first = manifests.next(); + if (first.done) { + help(); + die("no package.json found"); + } + + const manifest: finder.PackageWithPath = first.value; + const crossTarget: unknown = manifest['cross-target'] ?? {}; + + if (!isCrossTarget(crossTarget)) { + help(); + die("invalid 'cross-target' specification"); + } + + const template = crossTarget.scripts?.[scriptKey]; + + if (!template) { + help(); + die(`script ${scriptKey} not found`); + } + + const compiled = handlebars.compile(template, { noEscape: true }); + + const targets = targetsSpec === "--current" + ? [rust.Target.current()] + : crossTarget.targets + ? crossTarget.targets.map(rust.Target.parse) + : DEFAULT_TARGETS.map(rust.Target.fromNode) + + for (const target of targets) { + const metadata = target.templateMetadata(); + const script = compiled(metadata); + console.error(`⚙️ cross-target: running "${script}" for target "${target.toString()}"`); + const result = spawnSync(script, { stdio: "inherit", shell: true }); + if (result.status !== 0) { + process.exit(result.status || 1); + } + } +} + +const DEFAULT_TARGETS: node.Target[] = [ + new node.Target('x64', 'darwin'), + new node.Target('x64', 'linux'), + new node.Target('x64', 'win32') +]; + +// Expand to support other toolchains in the future (node-gyp? clang? gcc? zig?) +type Toolchain = "rust"; + +type Target = string; + +type CrossTarget = { + toolchain: Toolchain, + targets?: Target[], + scripts?: Record, +}; + +// FIXME: use zod to clean this up +function isCrossTarget(value: unknown): value is CrossTarget { + if (typeof value !== 'object' || !value) { + return false; + } + + // Currently the only supported toolchain. + if (!('toolchain' in value) || (value.toolchain !== 'rust')) { + return false; + } + + if ('targets' in value) { + const targets = value.targets; + if (!Array.isArray(targets) || !targets.every(x => typeof x === 'string')) { + return false; + } + } + + if ('scripts' in value) { + const scripts = value.scripts; + if (!(scripts instanceof Object)) { + return false; + } + for (const key in scripts) { + if (typeof (scripts as Record)[key] !== 'string') { + return false; + } + } + } + + return true; +} + +main(process.argv.slice(2)); diff --git a/pkgs/cross-target/src/die.ts b/pkgs/cross-target/src/die.ts new file mode 100644 index 000000000..e4a5fd341 --- /dev/null +++ b/pkgs/cross-target/src/die.ts @@ -0,0 +1,6 @@ +export default function die( + message: string +): never { + console.error(`❌ ${message}`); + process.exit(1); +} diff --git a/pkgs/cross-target/src/node.ts b/pkgs/cross-target/src/node.ts new file mode 100644 index 000000000..49c877999 --- /dev/null +++ b/pkgs/cross-target/src/node.ts @@ -0,0 +1,34 @@ +export type Arch = string; + +// https://nodejs.org/api/process.html#processarch +const ARCHES: Arch[] = [ + 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', 'x64' +]; + +export type Platform = string; + +// https://nodejs.org/api/process.html#processplatform +const PLATFORMS: Platform[] = [ + 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', 'win32' +]; + +export class Target { + private _arch: Arch; + private _platform: Platform; + + constructor(arch: Arch, platform: Platform) { + if (!ARCHES.includes(arch)) { + throw new RangeError(`unsupported Node arch: ${arch}`); + } + if (!PLATFORMS.includes(platform)) { + throw new RangeError(`unsupported Node platform: ${platform}`); + } + this._arch = arch; + this._platform = platform; + } + + static current(): Target { return new Target(process.arch, process.platform); } + + arch(): Arch { return this._arch; } + platform(): Platform { return this._platform; } +} diff --git a/pkgs/cross-target/src/rust.ts b/pkgs/cross-target/src/rust.ts new file mode 100644 index 000000000..d5260a53b --- /dev/null +++ b/pkgs/cross-target/src/rust.ts @@ -0,0 +1,163 @@ +import * as node from './node'; + +export const VENDORS: string[] = [ + 'unknown', 'apple', 'pc' +]; + +export const OSES: string[] = [ + 'darwin', 'linux', 'freebsd', 'openbsd', 'windows' +]; + +export const ABIS: string[] = [ + 'gnu', 'msvc' +]; + +export class Platform { + private _vendor: string; + private _sys: string; + private _abi: string | undefined; + + constructor(vendor: string, sys: string, abi?: string) { + if (!VENDORS.includes(vendor)) { + throw new RangeError(`unsupported Rust platform vendor: ${vendor}`); + } + if (!OSES.includes(sys)) { + throw new RangeError(`unsupported Rust OS: ${sys}`); + } + if (abi && !ABIS.includes(abi)) { + throw new RangeError(`unsupported Rust ABI: ${abi}`); + } + this._vendor = vendor; + this._sys = sys; + this._abi = abi; + } + + vendor(): string { return this._vendor; } + sys(): string { return this._sys; } + abi(): string | undefined { return this._abi; } + + toString(): string { + const base = `${this._vendor}-${this._sys}`; + return this._abi + ? base + `-${this._abi}` + : base; + } + + equals(other: Platform): boolean { + return (this._vendor === other._vendor) + && (this._sys === other._sys) + && (this._abi === other._abi); + } +} + +export type Arch = string; + +export class Target { + private _arch: Arch; + private _platform: Platform; + + constructor(arch: Arch, platform: Platform) { + if (!ARCHES.includes(arch)) { + throw new RangeError(`unsupported Rust architecture: ${arch}`); + } + this._arch = arch; + this._platform = platform; + } + + static fromNode(target: node.Target): Target { + const arch: node.Arch = target.arch(); + const platform: node.Platform = target.platform(); + + const rustArch = ARCH_MAP[arch]; + if (!rustArch) { + throw new RangeError(`unsupported Node architecture: ${arch}`); + } + + const rustPlatform = PLATFORM_MAP[platform]; + if (!rustPlatform) { + throw new RangeError(`unsupported Node platform: ${platform}`); + } + + return new Target(rustArch, rustPlatform); + } + + static current(): Target { + return Target.fromNode(node.Target.current()); + } + + static parse(source: string): Target { + const parts = source.split(/-/); + + if (parts.length === 3) { + const [arch, vendor, sys] = parts; + return new Target(arch, new Platform(vendor, sys)); + } + + if (parts.length === 4) { + const [arch, vendor, sys, abi] = parts; + return new Target(arch, new Platform(vendor, sys, abi)); + } + + throw new SyntaxError(`invalid Rust target: ${source}`); + } + + toString(): string { + return `${this._arch}-${this._platform.toString()}`; + } + + equals(other: Target): boolean { + return (this._arch === other._arch) + && this._platform.equals(other._platform); + } + + templateMetadata(): unknown { + // FIXME: other metadata e.g. node arch/platform + return { + target: this.toString(), + arch: this._arch, + vendor: this._platform.vendor(), + sys: this._platform.sys(), + abi: this._platform.abi() ?? null + } + } +} + +// https://doc.rust-lang.org/nightly/rustc/platform-support.html +// https://rust-lang.github.io/rustup-components-history/ +// https://clang.llvm.org/docs/CrossCompilation.html#target-triple +export const ARCH_MAP: Record = { + 'arm64': 'aarch64', + 'ia32': 'i686', + 'ppc': 'powerpc', + 'ppc64': 'powerpc64', + 's390x': 's390x', + 'x64': 'x86_64' +}; + +export const PLATFORM_MAP: Record = { + 'darwin': new Platform('apple', 'darwin' ), + 'freebsd': new Platform('unknown', 'freebsd' ), + 'linux': new Platform('unknown', 'linux', 'gnu' ), + 'openbsd': new Platform('unknown', 'openbsd' ), + 'win32': new Platform('pc', 'windows', 'msvc') +}; + +// https://doc.rust-lang.org/nightly/rustc/platform-support.html +// https://rust-lang.github.io/rustup-components-history/ +// https://clang.llvm.org/docs/CrossCompilation.html#target-triple +export const ARCHES: Arch[] = [ + 'aarch64', + 'i386', 'i586', 'i686', 'x86_64', + 'arm', 'armv5te', 'armv7', 'armv7s', + 'armebv7r', 'armv7a', 'armv7r', + 'asmjs', + 'wasm32', + 'mips', 'mips64', 'mipsel', + 'mips64el', 'mipsisa32r6', 'mipsisa32r6el', 'mipsisa64r6', 'mipsisa64r6el', + 'powerpc', 'powerpc64', 'powerpc64le', + 'sparc', 'sparc64', 'sparcv9', + 'thumbv6m', 'thumbv7em', 'thumbv7m', + 'thumbv7neon', 'thumbv8m.base', 'thumbv8m.main', + 's390x', 'le32', 'msp430', + 'nvptx64' +]; diff --git a/pkgs/cross-target/tsconfig.json b/pkgs/cross-target/tsconfig.json new file mode 100644 index 000000000..cfd67baa2 --- /dev/null +++ b/pkgs/cross-target/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "lib": ["es6", "es7"], + "allowJs": false, + + "sourceMap": false, + "outDir": "dist", + + "esModuleInterop": true, + "strict": true, + "noImplicitReturns": true, + "allowUnreachableCode": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true + }, + "include": ["src/**/*", "dev/**/*", "test/**/*"], + "exclude": ["./node_modules/", "./dist/"] +}