npmgen_core/project/
builder.rs1use std::path::PathBuf;
2
3use super::{Author, Identity, Project, ProjectError};
4use crate::config::Config;
5
6#[derive(Debug, Clone)]
13pub struct ProjectBuilder {
14 scope: String,
15 name: String,
16 version: String,
17 git_url: String,
18 description: String,
19 author: String,
20 license: String,
21 repository: String,
22 bin: Option<String>,
23 package: Option<String>,
24 config: Config,
25 workspace_root: PathBuf,
26 target_directory: PathBuf,
27}
28
29impl ProjectBuilder {
30 pub(crate) fn new(
31 scope: impl Into<String>,
32 name: impl Into<String>,
33 version: impl Into<String>,
34 ) -> Self {
35 Self {
36 scope: scope.into(),
37 name: name.into(),
38 version: version.into(),
39 git_url: String::new(),
40 description: String::new(),
41 author: String::new(),
42 license: String::new(),
43 repository: String::new(),
44 bin: None,
45 package: None,
46 config: Config::default(),
47 workspace_root: PathBuf::from("."),
48 target_directory: PathBuf::from("target"),
49 }
50 }
51
52 pub fn git_url(mut self, git_url: impl Into<String>) -> Self {
54 self.git_url = git_url.into();
55 self
56 }
57
58 pub fn description(mut self, description: impl Into<String>) -> Self {
59 self.description = description.into();
60 self
61 }
62
63 pub fn author(mut self, author: impl Into<String>) -> Self {
65 self.author = author.into();
66 self
67 }
68
69 pub fn license(mut self, license: impl Into<String>) -> Self {
70 self.license = license.into();
71 self
72 }
73
74 pub fn repository(mut self, repository: impl Into<String>) -> Self {
76 self.repository = repository.into();
77 self
78 }
79
80 pub fn bin(mut self, bin: impl Into<String>) -> Self {
82 self.bin = Some(bin.into());
83 self
84 }
85
86 pub fn package(mut self, package: impl Into<String>) -> Self {
88 self.package = Some(package.into());
89 self
90 }
91
92 pub fn config(mut self, config: Config) -> Self {
94 self.config = config;
95 self
96 }
97
98 pub fn workspace_root(mut self, workspace_root: impl Into<PathBuf>) -> Self {
100 self.workspace_root = workspace_root.into();
101 self
102 }
103
104 pub fn target_directory(mut self, target_directory: impl Into<PathBuf>) -> Self {
106 self.target_directory = target_directory.into();
107 self
108 }
109
110 pub fn build(self) -> Result<Project, ProjectError> {
112 if self.scope.is_empty() || !self.scope.starts_with('@') {
113 return Err(ProjectError::InvalidField {
114 field: "scope",
115 reason: "must be a non-empty npm scope starting with '@'",
116 });
117 }
118 if self.name.is_empty() {
119 return Err(ProjectError::InvalidField {
120 field: "name",
121 reason: "must not be empty",
122 });
123 }
124 if self.version.is_empty() {
125 return Err(ProjectError::InvalidField {
126 field: "version",
127 reason: "must not be empty",
128 });
129 }
130
131 let bin = self.bin.unwrap_or_else(|| self.name.clone());
132 Ok(Project {
133 identity: Identity {
134 scope: self.scope,
135 name: self.name,
136 git_url: self.git_url,
137 },
138 version: self.version,
139 description: self.description,
140 author: Author::parse(&self.author),
141 license: self.license,
142 repository: self.repository,
143 bin,
144 package: self.package,
145 config: self.config,
146 workspace_root: self.workspace_root,
147 target_directory: self.target_directory,
148 })
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::ProjectBuilder;
155
156 #[test]
157 fn builds_identity_and_defaults_bin_to_name() {
158 let project = ProjectBuilder::new("@me", "tool", "1.2.3")
159 .git_url("git+https://example.test/me/tool.git")
160 .build()
161 .unwrap();
162 assert_eq!(project.package_name(), "@me/tool");
163 assert_eq!(project.version, "1.2.3");
164 assert_eq!(project.bin, "tool");
165 assert_eq!(
166 project.identity.git_url,
167 "git+https://example.test/me/tool.git"
168 );
169 }
170
171 #[test]
172 fn explicit_bin_overrides_the_name_default() {
173 let project = ProjectBuilder::new("@me", "tool", "1.2.3")
174 .bin("other")
175 .build()
176 .unwrap();
177 assert_eq!(project.bin, "other");
178 }
179
180 #[test]
181 fn rejects_empty_or_unscoped_required_fields() {
182 assert!(ProjectBuilder::new("", "tool", "1.0.0").build().is_err());
183 assert!(ProjectBuilder::new("me", "tool", "1.0.0").build().is_err());
184 assert!(ProjectBuilder::new("@me", "", "1.0.0").build().is_err());
185 assert!(ProjectBuilder::new("@me", "tool", "").build().is_err());
186 assert!(ProjectBuilder::new("@me", "tool", "1.0.0").build().is_ok());
187 }
188}