@@ -8,7 +8,7 @@ use std::{
88} ;
99use stellar_xdr:: curr:: ScSpecEntry ;
1010
11- use super :: generate;
11+ use super :: { generate, validate_npm_package_name } ;
1212
1313static PROJECT_DIR : Dir < ' _ > = include_dir ! ( "$CARGO_MANIFEST_DIR/src/project_template" ) ;
1414
@@ -52,6 +52,14 @@ impl Project {
5252 network_passphrase : Option < & str > ,
5353 spec : & [ ScSpecEntry ] ,
5454 ) -> std:: io:: Result < ( ) > {
55+ validate_npm_package_name ( contract_name) . map_err ( |e| {
56+ std:: io:: Error :: new (
57+ std:: io:: ErrorKind :: InvalidInput ,
58+ format ! (
59+ "output directory name '{contract_name}' is not a valid npm package name: {e}"
60+ ) ,
61+ )
62+ } ) ?;
5563 self . replace_placeholder_patterns ( contract_name, contract_id, rpc_url, network_passphrase) ?;
5664 self . append_index_ts ( spec, contract_id, network_passphrase)
5765 }
@@ -87,7 +95,12 @@ impl Project {
8795 ) ,
8896 ] ;
8997 let root: & Path = self . as_ref ( ) ;
90- [ "package.json" , "README.md" , "src/index.ts" ]
98+
99+ // Handle package.json with proper JSON serialization
100+ replace_package_json ( root, contract_name) ?;
101+
102+ // Handle non-JSON files with string replacement
103+ [ "README.md" , "src/index.ts" ]
91104 . into_iter ( )
92105 . try_for_each ( |file_name| {
93106 let file = & root. join ( file_name) ;
@@ -139,6 +152,38 @@ impl Project {
139152 }
140153}
141154
155+ fn replace_package_json ( root : & Path , contract_name : & str ) -> std:: io:: Result < ( ) > {
156+ let file = root. join ( "package.json" ) ;
157+ let contents = fs:: read_to_string ( & file) ?;
158+ let mut json: serde_json:: Value = serde_json:: from_str ( & contents) . map_err ( |e| {
159+ std:: io:: Error :: new (
160+ std:: io:: ErrorKind :: InvalidData ,
161+ format ! ( "failed to parse package.json template: {e}" ) ,
162+ )
163+ } ) ?;
164+
165+ if let Some ( obj) = json. as_object_mut ( ) {
166+ obj. insert (
167+ "name" . to_string ( ) ,
168+ serde_json:: Value :: String ( contract_name. to_string ( ) ) ,
169+ ) ;
170+ } else {
171+ return Err ( std:: io:: Error :: new (
172+ std:: io:: ErrorKind :: InvalidData ,
173+ "package.json template must be a JSON object" ,
174+ ) ) ;
175+ }
176+
177+ let serialized = serde_json:: to_string_pretty ( & json) . map_err ( |e| {
178+ std:: io:: Error :: new (
179+ std:: io:: ErrorKind :: InvalidData ,
180+ format ! ( "failed to serialize package.json: {e}" ) ,
181+ )
182+ } ) ?;
183+ // Append trailing newline to match standard formatting
184+ fs:: write ( & file, format ! ( "{serialized}\n " ) )
185+ }
186+
142187#[ cfg( test) ]
143188mod test {
144189 use temp_dir:: TempDir ;
@@ -189,6 +234,51 @@ mod test {
189234 println ! ( "Updated Snapshot!" ) ;
190235 }
191236
237+ #[ test]
238+ fn test_package_json_name_is_set_correctly ( ) {
239+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
240+ let _project = init ( temp_dir. path ( ) ) . unwrap ( ) ;
241+ let pkg_json_path = temp_dir. path ( ) . join ( "package.json" ) ;
242+ let contents = fs:: read_to_string ( & pkg_json_path) . unwrap ( ) ;
243+ let json: serde_json:: Value = serde_json:: from_str ( & contents) . unwrap ( ) ;
244+ assert_eq ! ( json[ "name" ] , "test_custom_types" ) ;
245+ let obj = json. as_object ( ) . unwrap ( ) ;
246+ let expected_keys = [
247+ "version" ,
248+ "name" ,
249+ "type" ,
250+ "exports" ,
251+ "typings" ,
252+ "scripts" ,
253+ "dependencies" ,
254+ "devDependencies" ,
255+ ] ;
256+ for key in expected_keys {
257+ assert ! (
258+ obj. contains_key( key) ,
259+ "missing expected key in package.json: {key}"
260+ ) ;
261+ }
262+ }
263+
264+ #[ test]
265+ fn test_init_rejects_invalid_contract_name ( ) {
266+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
267+ let p: Project = temp_dir. path ( ) . to_path_buf ( ) . try_into ( ) . unwrap ( ) ;
268+ let spec = soroban_spec:: read:: from_wasm ( EXAMPLE_WASM ) . unwrap ( ) ;
269+ let result = p. init (
270+ r#"foo","optionalDependencies":{"evil":"1"},"z":""# ,
271+ Some ( "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE" ) ,
272+ Some ( "https://rpc-futurenet.stellar.org:443" ) ,
273+ Some ( "Test SDF Future Network ; October 2022" ) ,
274+ & spec,
275+ ) ;
276+ assert ! ( result. is_err( ) ) ;
277+ let err = result. unwrap_err ( ) ;
278+ assert_eq ! ( err. kind( ) , std:: io:: ErrorKind :: InvalidInput ) ;
279+ assert ! ( err. to_string( ) . contains( "not a valid npm package name" ) ) ;
280+ }
281+
192282 fn assert_dirs_equal < P : AsRef < Path > > ( dir1 : P , dir2 : P ) {
193283 let walker1 = WalkDir :: new ( & dir1) ;
194284 let walker2 = WalkDir :: new ( & dir2) ;
0 commit comments