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