Skip to main content

van_init/
lib.rs

1use anyhow::{bail, Context, Result};
2use console::style;
3use dialoguer::Input;
4use std::fs;
5use std::path::{Path, PathBuf};
6use van_context::config::VanConfig;
7
8/// Run the interactive `van init` command.
9pub fn run(name: Option<String>) -> Result<()> {
10    println!();
11    println!(
12        "  {}",
13        style("Van - Create a new project").bold().cyan()
14    );
15    println!();
16
17    // Prompt for project name if not provided
18    let project_name = match name {
19        Some(n) => n,
20        None => Input::new()
21            .with_prompt(format!("  {}", style("Project name").bold()))
22            .interact_text()
23            .context("Failed to read project name")?,
24    };
25
26    // Validate project name
27    if project_name.is_empty() {
28        bail!("Project name cannot be empty");
29    }
30    if project_name
31        .chars()
32        .any(|c| !c.is_alphanumeric() && c != '-' && c != '_')
33    {
34        bail!("Project name can only contain alphanumeric characters, hyphens, and underscores");
35    }
36
37    let project_dir = PathBuf::from(&project_name);
38
39    // Check if directory already exists
40    if project_dir.exists() {
41        bail!("Directory '{}' already exists", project_name);
42    }
43
44    // Scaffold the project
45    println!();
46    println!(
47        "  {} {}",
48        style("Scaffolding project in").dim(),
49        style(format!("./{project_name}/")).dim().bold()
50    );
51    println!();
52
53    let files =
54        scaffold_project(&project_dir, &project_name).context("Failed to scaffold project")?;
55
56    // Print created files
57    for file in &files {
58        println!("  {}  {}", style("+").green().bold(), style(file).dim());
59    }
60
61    // Done message
62    println!();
63    println!(
64        "  {} Project created successfully.",
65        style("Done.").green().bold()
66    );
67    println!();
68    println!("  Now run:");
69    println!();
70    println!("    {}  {}", style("cd").cyan(), project_name);
71    println!("    {}", style("van dev").cyan());
72    println!();
73
74    Ok(())
75}
76
77/// Scaffold a new Van project with starter files.
78pub fn scaffold_project(project_dir: &Path, name: &str) -> Result<Vec<String>> {
79    let mut created_files = Vec::new();
80
81    // Create directory structure
82    let dirs = [
83        "src/pages",
84        "src/components",
85        "src/layouts",
86        "src/assets",
87        "mock",
88    ];
89    for dir in &dirs {
90        fs::create_dir_all(project_dir.join(dir))
91            .with_context(|| format!("Failed to create directory: {dir}"))?;
92    }
93
94    // package.json
95    let config = VanConfig::new(name);
96    let config_path = project_dir.join("package.json");
97    fs::write(&config_path, config.to_json_pretty()?)?;
98    created_files.push("package.json".into());
99
100    // src/pages/index.van
101    fs::write(
102        project_dir.join("src/pages/index.van"),
103        include_str!("templates/pages/index.van"),
104    )?;
105    created_files.push("src/pages/index.van".into());
106
107    // src/components/hello.van
108    fs::write(
109        project_dir.join("src/components/hello.van"),
110        include_str!("templates/components/hello.van"),
111    )?;
112    created_files.push("src/components/hello.van".into());
113
114    // src/layouts/default.van
115    fs::write(
116        project_dir.join("src/layouts/default.van"),
117        include_str!("templates/layouts/default.van"),
118    )?;
119    created_files.push("src/layouts/default.van".into());
120
121    // mock/index.json
122    fs::write(
123        project_dir.join("mock/index.json"),
124        include_str!("templates/mock/index.json"),
125    )?;
126    created_files.push("mock/index.json".into());
127
128    // .gitignore
129    fs::write(
130        project_dir.join(".gitignore"),
131        "dist/\nnode_modules/\n.van/\n",
132    )?;
133    created_files.push(".gitignore".into());
134
135    Ok(created_files)
136}