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