oseda_cli/cmd/
init.rs

1/*
2npm init -y
3npm install --save-dev vite http-server
4npm install reveal.js serve vite-plugin-singlefile
5touch vite.config.js -> add the plugin, write this by hand
6
7*/
8
9use std::{
10    error::Error,
11    fs::{self},
12    process::Command,
13};
14
15use clap::Args;
16use strum::IntoEnumIterator;
17
18use crate::{config, template::Template};
19
20/// Options for the `oseda init` command
21#[derive(Args, Debug)]
22pub struct InitOptions {
23    /// Unused for now
24    #[arg(long, required = false)]
25    sentation_only: bool,
26}
27
28// embed all the static markdown template files into binary
29const MD_VITE_CONFIG_JS: &str = include_str!("../static/md-templates/vite.config.js");
30const MD_INDEX_HTML: &str = include_str!("../static/md-templates/index.html");
31const MD_MAIN_JS: &str = include_str!("../static/md-templates/main.js");
32const MD_SLIDES: &str = include_str!("../static/md-templates/slides.md");
33const MD_CUSTOM_CSS: &str = include_str!("../static/md-templates/custom.css");
34const MD_FERRIS: &[u8] = include_bytes!("../static/md-templates/ferris.png");
35
36
37// do the same with the html templates
38const HTML_VITE_CONFIG_JS: &str = include_str!("../static/html-templates/vite.config.js");
39const HTML_INDEX_HTML: &str = include_str!("../static/html-templates/index.html");
40const HTML_MAIN_JS: &str = include_str!("../static/html-templates/main.js");
41const HTML_SLIDES: &str = include_str!("../static/html-templates/slides.html");
42const HTML_CUSTOM_CSS: &str = include_str!("../static/html-templates/custom.css");
43const HTML_FERRIS: &[u8] = include_bytes!("../static/html-templates/ferris.png");
44
45
46/// Initialize an Oseda project with the provided options
47///
48/// This command will:
49/// - Run `npm init`
50/// - Install required dependencies (Vite, Reveal.js, etc)
51/// - Write config and boilerplate files
52///
53/// # Arguments
54/// * `_opts` - command-line options (this is unused rn, used later I hope)
55///
56/// # Returns
57/// * `Ok(())` if project initialization is suceeded
58/// * `Err` if any step (npm, file write, config generation etc) fails
59pub fn init(_opts: InitOptions) -> Result<(), Box<dyn Error>> {
60    let conf = config::create_conf()?;
61
62
63    let template: Template = prompt_template()?;
64
65    std::fs::create_dir_all(&conf.title)?;
66
67    let output = Command::new("npm")
68        .args(["init", "-y", "--prefix", &conf.title])
69        .current_dir(&conf.title)
70        .output()?;
71
72    // swapped to explicit check so it doesn't hang after
73    if !output.status.success() {
74        eprintln!(
75            "npm init failed: {}",
76            String::from_utf8_lossy(&output.stderr)
77        );
78        return Err("npm init failed".into());
79    }
80
81    let npm_commands = vec![
82        format!("install --save-dev vite http-server"),
83        format!("install reveal.js serve vite-plugin-singlefile"),
84        format!("install vite@5"),
85        format!("install patch-package"),
86    ];
87
88    for c in npm_commands {
89        let args: Vec<&str> = c.split(' ').collect();
90        let output = Command::new("npm")
91            .args(&args)
92            .current_dir(&conf.title)
93            .output()?;
94
95        if !output.status.success() {
96            eprintln!(
97                "npm {} failed: {}",
98                c,
99                String::from_utf8_lossy(&output.stderr)
100            );
101            return Err(format!("npm {} failed", c).into());
102        }
103        println!("Bootstrapped npm {}", c);
104
105    }
106
107    println!("Saving config file...");
108
109    config::write_config(&conf.title, &conf)?;
110
111    // 99% sure we'll only ever have to maintain these two template schemas
112    match template {
113        Template::Markdown => {
114            // fs::write(format!("{}/package.json", &conf.title), MD_PACKAGE_JSON)?;
115            fs::write(format!("{}/vite.config.js", &conf.title), MD_VITE_CONFIG_JS)?;
116            fs::write(format!("{}/index.html", &conf.title), MD_INDEX_HTML)?;
117
118            std::fs::create_dir_all(format!("{}/src", &conf.title))?;
119            fs::write(format!("{}/src/main.js", &conf.title), MD_MAIN_JS)?;
120
121            std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
122            fs::write(format!("{}/slides/slides.md", &conf.title), MD_SLIDES)?;
123
124            std::fs::create_dir_all(format!("{}/css", &conf.title))?;
125            fs::write(format!("{}/css/custom.css", &conf.title), MD_CUSTOM_CSS)?;
126
127            std::fs::create_dir_all(format!("{}/public", &conf.title))?;
128            fs::write(format!("{}/public/ferris.png", &conf.title), MD_FERRIS)?;
129        }
130        Template::HTML => {
131
132            // fs::write(format!("{}/package.json", &conf.title), HTML_PACKAGE_JSON)?;
133            fs::write(format!("{}/vite.config.js", &conf.title), HTML_VITE_CONFIG_JS)?;
134            fs::write(format!("{}/index.html", &conf.title), HTML_INDEX_HTML)?;
135
136            std::fs::create_dir_all(format!("{}/src", &conf.title))?;
137            fs::write(format!("{}/src/main.js", &conf.title), HTML_MAIN_JS)?;
138
139            std::fs::create_dir_all(format!("{}/slides", &conf.title))?;
140            fs::write(format!("{}/slides/slides.html", &conf.title), HTML_SLIDES)?;
141
142            std::fs::create_dir_all(format!("{}/css", &conf.title))?;
143            fs::write(format!("{}/css/custom.css", &conf.title), HTML_CUSTOM_CSS)?;
144
145            std::fs::create_dir_all(format!("{}/public", &conf.title))?;
146            fs::write(format!("{}/public/ferris.png", &conf.title), HTML_FERRIS)?
147        },
148    }
149
150    Ok(())
151}
152
153fn prompt_template() -> Result<Template, Box<dyn Error>> {
154    let template_opts: Vec<Template> = Template::iter().collect();
155
156    let chosen_template = inquire::Select::new("Select a template:", template_opts).prompt()?;
157
158    Ok(chosen_template)
159}