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
8pub 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 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 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 if project_dir.exists() {
41 bail!("Directory '{}' already exists", project_name);
42 }
43
44 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 for file in &files {
58 println!(" {} {}", style("+").green().bold(), style(file).dim());
59 }
60
61 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
77pub fn scaffold_project(project_dir: &Path, name: &str) -> Result<Vec<String>> {
79 let mut created_files = Vec::new();
80
81 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 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 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 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 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 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 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}