diff --git a/Cargo.lock b/Cargo.lock index 6a468f8..e412fc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,7 +221,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -287,6 +287,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -295,7 +316,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -832,6 +853,15 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libredox" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +dependencies = [ + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1000,7 +1030,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1021,14 +1051,21 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "oseda-cli" -version = "2.4.6" +version = "2.6.0" dependencies = [ "chrono", "clap", "clap-markdown", "ctrlc", + "dirs", "inquire", "open", "reqwest", @@ -1134,6 +1171,17 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror", +] + [[package]] name = "reqwest" version = "0.12.20" @@ -1309,7 +1357,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1425,7 +1473,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.101", ] [[package]] @@ -1445,6 +1493,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -1462,7 +1521,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1499,6 +1558,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -1736,7 +1815,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -1771,7 +1850,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1838,7 +1917,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1849,7 +1928,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2070,7 +2149,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "synstructure", ] @@ -2091,7 +2170,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "synstructure", ] @@ -2131,5 +2210,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] diff --git a/Cargo.toml b/Cargo.toml index 82786ce..21ee930 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ homepage = "https://oseda.net" repository = "https://github.com/oseda-dev/oseda-cli" readme = "README.md" name = "oseda-cli" -version = "2.4.6" +version = "2.6.0" edition = "2021" [[bin]] @@ -20,6 +20,7 @@ chrono = { version = "0.4.41", features = ["serde"] } clap = { version = "4.5.38", features = ["derive"] } clap-markdown = "0.1.5" ctrlc = "3.4.7" +dirs = "6.0.0" inquire = "0.7.5" open = "5.3.3" reqwest = { version = "0.12.20", features = ["blocking"] } diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs index 73f7c96..9cb52ed 100644 --- a/src/cmd/deploy.rs +++ b/src/cmd/deploy.rs @@ -108,7 +108,6 @@ pub fn deploy(opts: DeployOptions) -> Result<(), Box> { if open::that(pull_request_url.clone()).is_err() { return Err(format!("Please visit {pull_request_url} in a browser and submit a pull-request by hand").into()); }; - } None => { println!("Error: could not get github username"); diff --git a/src/cmd/export.rs b/src/cmd/export.rs index 9687bbe..55f156c 100644 --- a/src/cmd/export.rs +++ b/src/cmd/export.rs @@ -9,7 +9,11 @@ use std::{ use clap::Args; -use crate::{cmd::run, net::kill_port}; +use crate::{ + cmd::run, + net::kill_port, + puppeteer::{is_puppeteer_chrome_installed, prompt_install_puppeteer_chrome}, +}; /// Options struct for the export subcommand #[derive(Args, Debug, Clone)] @@ -24,6 +28,7 @@ pub struct ExportOptions { /// Export the current Oseda project to a PDF file via `decktape` pub fn export(opts: ExportOptions) -> Result<(), Box> { + println!("Cleaning any existing oseda processing..."); if kill_port(opts.port).is_err() { eprintln!("Warning, could not kill value on desired port") } @@ -33,6 +38,12 @@ pub fn export(opts: ExportOptions) -> Result<(), Box> { .current_dir(".") .output()?; + // prompt for puppeteer and confirm installation + if !is_puppeteer_chrome_installed() { + prompt_install_puppeteer_chrome()?; + } + println!("Puppeteer Chrome is installed, continuing..."); + if !output.status.success() { eprintln!( "Decktape installation failure: {}", @@ -54,8 +65,8 @@ pub fn export(opts: ExportOptions) -> Result<(), Box> { let addr = format!("http://localhost:{}", opts.port); // run decktape, assuming the server has spun up by now - let export_output = Command::new("decktape") - .args(["automatic", &addr, &opts.output]) + let export_output = Command::new("npm") + .args(["exec", "decktape", "reveal", &addr, &opts.output]) .output()?; // send shutdown flag, should signal to run_with_shutdown to kill the process diff --git a/src/cmd/init.rs b/src/cmd/init.rs index 3facc6c..35e092c 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -1,4 +1,3 @@ - use std::{ error::Error, fs::{self}, diff --git a/src/lib.rs b/src/lib.rs index 4cea8fe..1ffca8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod color; pub mod config; pub mod github; pub mod net; +pub mod puppeteer; pub mod tags; pub mod template; diff --git a/src/puppeteer.rs b/src/puppeteer.rs new file mode 100644 index 0000000..9f2dd8a --- /dev/null +++ b/src/puppeteer.rs @@ -0,0 +1,68 @@ +// utils for puppeteer and chrome + +use std::{error::Error, process::Command}; + +use inquire::Confirm; +/// Checks whether headless Chrome has been installed on the users system via npx puppeteer +/// +/// # Returns +/// +/// - `bool` - Whether or not it has been installed +pub fn is_puppeteer_chrome_installed() -> bool { + //puppeteer will install into cache directory + // this is differnt on different systems + // afaik, not even specific to just OS -> so I pulled in `dirs` + let mut path = match dirs::cache_dir() { + Some(p) => p, + None => return false, + }; + + // {cache}/puppeteer/chrome + path.push("puppeteer"); + path.push("chrome"); + + if path.is_dir() { + if let Ok(entries) = std::fs::read_dir(&path) { + // if any folder present, should be installed + return entries + .filter_map(Result::ok) + .any(|entry| entry.path().is_dir()); + } + } + + false +} + +/// Prompts the user for an installation of headless Chrome via. Puppeteer, installing it if they elect to proceed +/// +/// # Returns +/// +/// - `Result<(), Box>` +/// - Ok(()) if installation proceeds as normal +/// - Err() if installation is aborted by user +pub fn prompt_install_puppeteer_chrome() -> Result<(), Box> { + println!("`oseda export` uses DeckTape to export your presentation"); + println!("DeckTape needs chromium installed (puppeteer prefered"); + println!("Puppeteer Chrome is not currently detected."); + let ans = Confirm::new("Would you like to install it and proceed?") + .with_default(false) + .prompt(); + + // let else my beloved + // gaurd clause + let Ok(true) = ans else { + return Err("DeckTape is required to proceed".into()); + }; + + println!("Installing Puppeteer Chrome... (This may take several minutes, you will only need to install this once"); + + let chrome_install_output = Command::new("npx") + .args(["puppeteer", "browsers", "install", "chrome"]) + .output()?; + + if !chrome_install_output.status.success() { + return Err("Error: could not install puppeteer chrome".into()); + } + + Ok(()) +} diff --git a/src/static/html-templates/main.js b/src/static/html-templates/main.js index 8e2b9ce..1c8ceb2 100644 --- a/src/static/html-templates/main.js +++ b/src/static/html-templates/main.js @@ -22,4 +22,8 @@ document.addEventListener("DOMContentLoaded", () => { center: false, slideNumber: true, }); + + // DeckTape needs access to window.Reveal for export to work + window.Reveal = deck + }); diff --git a/src/static/md-templates/main.js b/src/static/md-templates/main.js index 2685e6e..b447bbf 100644 --- a/src/static/md-templates/main.js +++ b/src/static/md-templates/main.js @@ -29,4 +29,8 @@ document.addEventListener("DOMContentLoaded", () => { }); deck.initialize(); + + // DeckTape needs access to window.Reveal for export to work + window.Reveal = deck + });