1- use anyhow:: { bail , Context , Result } ;
1+ use anyhow:: Result ;
22use clap:: Subcommand ;
3- use dialoguer:: { MultiSelect , Select } ;
43use http:: HttpAddCommand ;
54use local:: LocalAddCommand ;
65use registry:: RegistryAddCommand ;
76use spin_manifest:: {
87 manifest_from_file,
9- schema:: v2:: { AppManifest , ComponentDependencies , ComponentDependency } ,
8+ schema:: v2:: { AppManifest , ComponentDependency } ,
109} ;
1110use spin_serde:: { DependencyName , DependencyPackageName , KebabId } ;
1211use std:: { collections:: HashMap , path:: PathBuf } ;
1312use tokio:: fs;
14- use toml_edit:: DocumentMut ;
15- use wit_component:: WitPrinter ;
1613use wit_parser:: { PackageId , Resolve } ;
1714
18- const SPIN_WIT_DIRECTORY : & str = ".wit" ;
19- const SPIN_COMPONENTS_WIT_DIRECTORY : & str = "components" ;
15+ use crate :: common:: {
16+ constants:: { SPIN_COMPONENTS_WIT_DIRECTORY , SPIN_DEPS_WIT_FILE_NAME , SPIN_WIT_DIRECTORY } ,
17+ interact:: { select_multiple_prompt, select_prompt} ,
18+ manifest:: { edit_component_deps_in_manifest, get_component_ids, get_spin_manifest_path} ,
19+ wit:: {
20+ get_exported_interfaces, merge_dependecy_package, parse_component_bytes, resolve_to_wit,
21+ } ,
22+ } ;
2023
2124mod http;
2225mod local;
@@ -40,60 +43,50 @@ impl AddCommand {
4043 AddCommand :: Registry ( cmd) => cmd. get_component ( ) . await ?,
4144 } ;
4245
43- self . validate_component ( & component) ?;
46+ let ( mut resolve , main ) = parse_component_bytes ( component) ?;
4447
4548 let mut manifest = manifest_from_file ( get_spin_manifest_path ( ) ?) ?;
46- let component_ids = self . list_component_ids ( & manifest) ;
47- let selected_component = self . select_component ( & component_ids) ?;
49+ let component_ids = get_component_ids ( & manifest) ;
50+ let selected_component_index = select_prompt (
51+ "Select a component to add the dependency to" ,
52+ & component_ids,
53+ None ,
54+ ) ?;
55+ let selected_component = & component_ids[ selected_component_index] ;
4856
49- let decoded_wasm = wit_component:: decode ( & component) ?;
50- let mut resolve = decoded_wasm. resolve ( ) . clone ( ) ;
51- let main = decoded_wasm. package ( ) ;
5257 let selected_interfaces = self . select_interfaces ( & mut resolve, main) ?;
5358
5459 resolve. importize (
5560 resolve. select_world ( main, None ) ?,
5661 Some ( "dependency-world" . to_string ( ) ) ,
5762 ) ?;
5863
59- self . write_wit_to_file ( & resolve, main, & selected_component)
60- . await ?;
61- self . update_manifest ( & mut manifest, & selected_component, selected_interfaces)
62- . await ?;
64+ let component_dir = PathBuf :: from ( SPIN_WIT_DIRECTORY )
65+ . join ( SPIN_COMPONENTS_WIT_DIRECTORY )
66+ . join ( selected_component) ;
6367
64- Ok ( ( ) )
65- }
68+ let output_wit = component_dir. join ( SPIN_DEPS_WIT_FILE_NAME ) ;
6669
67- /// List all component IDs in the manifest.
68- fn list_component_ids ( & self , manifest : & AppManifest ) -> Vec < String > {
69- manifest. components . keys ( ) . map ( |k| k. to_string ( ) ) . collect ( )
70- }
70+ let base_resolve_file = if std:: fs:: exists ( & output_wit) ? {
71+ Some ( & output_wit)
72+ } else {
73+ None
74+ } ;
7175
72- /// Prompts the user to select a component from a list.
73- fn select_component ( & self , component_ids : & [ String ] ) -> Result < String > {
74- let selected_component_index = Select :: new ( )
75- . with_prompt ( "Select a component" )
76- . items ( component_ids)
77- . default ( 0 )
78- . interact ( ) ?;
76+ let ( merged_resolve, main) = merge_dependecy_package ( base_resolve_file, & resolve, main) ?;
77+ let wit_text = resolve_to_wit ( & merged_resolve, main) ?;
78+ fs:: write ( output_wit, wit_text) . await ?;
7979
80- Ok ( component_ids [ selected_component_index ] . clone ( ) )
81- }
80+ self . update_manifest ( & mut manifest , selected_component , selected_interfaces )
81+ . await ? ;
8282
83- /// Validates the WebAssembly component.
84- fn validate_component ( & self , component : & [ u8 ] ) -> Result < ( ) > {
85- let t = wasmparser:: validate ( component)
86- . context ( "Provided component does not seem to be a valid component" ) ;
87- match Result :: from ( t) {
88- Ok ( _) => Ok ( ( ) ) ,
89- Err ( e) => bail ! ( e) ,
90- }
83+ Ok ( ( ) )
9184 }
9285
9386 /// Prompts the user to select an interface to import.
9487 fn select_interfaces ( & self , resolve : & mut Resolve , main : PackageId ) -> Result < Vec < String > > {
9588 let world_id = resolve. select_world ( main, None ) ?;
96- let exported_interfaces = self . get_exported_interfaces ( resolve, world_id) ;
89+ let exported_interfaces = get_exported_interfaces ( resolve, world_id) ;
9790
9891 let mut package_interface_map: HashMap < String , Vec < String > > = HashMap :: new ( ) ;
9992 let mut selected_interfaces: Vec < String > = Vec :: new ( ) ;
@@ -106,15 +99,15 @@ impl AddCommand {
10699 . push ( interface) ;
107100 }
108101
109- let package_names: Vec < _ > = package_interface_map. keys ( ) . collect ( ) ;
102+ let package_names: Vec < _ > = package_interface_map. keys ( ) . cloned ( ) . collect ( ) ;
110103
111- let selected_package_indices = MultiSelect :: new ( )
112- . with_prompt ( "Select packages to import (use space to select, enter to confirm)" )
113- . items ( & package_names)
114- . interact ( ) ?;
104+ let selected_package_indices = select_multiple_prompt (
105+ "Select packages to import (use space to select, enter to confirm)" ,
106+ & package_names,
107+ ) ?;
115108
116109 for & package_idx in selected_package_indices. iter ( ) {
117- let package_name = package_names[ package_idx] ;
110+ let package_name = & package_names[ package_idx] ;
118111 let interfaces = package_interface_map. get ( package_name) . unwrap ( ) ;
119112 let interface_count = interfaces. len ( ) ;
120113
@@ -128,14 +121,14 @@ impl AddCommand {
128121 } ;
129122
130123 // Prompt user to select an interface
131- let selected_interface_idx = Select :: new ( )
132- . with_prompt ( format ! (
124+ let selected_interface_idx = select_prompt (
125+ & format ! (
133126 "Select one or all interfaces to import from package '{}'" ,
134127 package_name
135- ) )
136- . default ( 0 )
137- . items ( & interface_options )
138- . interact ( ) ?;
128+ ) ,
129+ & interface_options ,
130+ Some ( 0 ) ,
131+ ) ?;
139132
140133 if interface_count > 1 && selected_interface_idx == 0 {
141134 selected_interfaces. push ( package_name. clone ( ) ) ;
@@ -148,67 +141,6 @@ impl AddCommand {
148141 Ok ( selected_interfaces)
149142 }
150143
151- /// Retrieves the exported interfaces from the resolved world.
152- fn get_exported_interfaces (
153- & self ,
154- resolve : & Resolve ,
155- world_id : wit_parser:: WorldId ,
156- ) -> Vec < ( String , String ) > {
157- resolve. worlds [ world_id]
158- . exports
159- . iter ( )
160- . filter_map ( |( _k, v) | match v {
161- wit_parser:: WorldItem :: Interface { id, .. } => {
162- let i = & resolve. interfaces [ * id] ;
163- let pkg_id = i. package . unwrap ( ) ;
164- let pkg = & resolve. packages [ pkg_id] ;
165- let mut pkg_name = format ! ( "{}:{}" , pkg. name. namespace, pkg. name. name) ;
166- if let Some ( ver) = & pkg. name . version {
167- pkg_name. push_str ( & format ! ( "@{}" , ver) ) ;
168- }
169- Some ( ( pkg_name, i. name . clone ( ) . unwrap_or_default ( ) ) )
170- }
171- _ => None ,
172- } )
173- . collect ( )
174- }
175-
176- /// Writes the WIT content to the specified file.
177- async fn write_wit_to_file (
178- & self ,
179- dep_resolve : & Resolve ,
180- dep_pkg_id : PackageId ,
181- selected_component : & str ,
182- ) -> Result < ( ) > {
183- const SPIN_DEPS_WIT_FILE_NAME : & str = "deps.wit" ;
184-
185- let component_dir = PathBuf :: from ( SPIN_WIT_DIRECTORY )
186- . join ( SPIN_COMPONENTS_WIT_DIRECTORY )
187- . join ( selected_component) ;
188-
189- let output_wit = component_dir. join ( SPIN_DEPS_WIT_FILE_NAME ) ;
190- let mut resolve = Resolve :: default ( ) ;
191-
192- let deps_package_id = if std:: fs:: exists ( & output_wit) ? {
193- resolve. push_file ( & output_wit) ?
194- } else {
195- fs:: create_dir_all ( & component_dir) . await ?;
196- resolve. push_str ( "component.wit" , DEFAULT_WIT ) ?
197- } ;
198-
199- let deps_world_id = resolve. select_world ( deps_package_id, Some ( "deps" ) ) ?;
200- let dep_main_world_id = dep_resolve. select_world ( dep_pkg_id, Some ( "dependency-world" ) ) ?;
201- let remap = resolve. merge ( dep_resolve. clone ( ) ) ?;
202- let dependecy_world_id = remap. map_world ( dep_main_world_id, None ) ?;
203- resolve. merge_worlds ( dependecy_world_id, deps_world_id) ?;
204-
205- let wit_content = resolve_to_wit ( & resolve, deps_package_id) ?;
206-
207- fs:: write ( output_wit, wit_content) . await ?;
208-
209- Ok ( ( ) )
210- }
211-
212144 /// Updates the manifest file with the new component dependency.
213145 async fn update_manifest (
214146 & self ,
@@ -253,93 +185,3 @@ impl AddCommand {
253185 Ok ( ( ) )
254186 }
255187}
256-
257- /// Converts a Resolve object to WIT content.
258- fn resolve_to_wit ( resolve : & Resolve , package_id : PackageId ) -> Result < String > {
259- let mut printer = WitPrinter :: default ( ) ;
260- printer. emit_docs ( false ) ;
261-
262- let ids = resolve
263- . packages
264- . iter ( )
265- . map ( |( id, _) | id)
266- . filter ( |id| * id != package_id)
267- . collect :: < Vec < _ > > ( ) ;
268-
269- printer. print ( resolve, package_id, & ids)
270- }
271-
272- // This is a helper function to edit the dependency table in the manifest file
273- // while preserving the order of the manifest.
274- async fn edit_component_deps_in_manifest (
275- component_id : & str ,
276- component_deps : & ComponentDependencies ,
277- ) -> Result < String > {
278- let manifest_path = get_spin_manifest_path ( ) ?;
279- let manifest = fs:: read_to_string ( manifest_path) . await ?;
280- let mut doc = manifest. parse :: < DocumentMut > ( ) ?;
281-
282- let mut dependencies_table = toml_edit:: Table :: new ( ) ;
283-
284- for ( name, dep) in & component_deps. inner {
285- let dep_src = match dep {
286- ComponentDependency :: Version ( version) => {
287- let mut ver_table = toml_edit:: InlineTable :: default ( ) ;
288- ver_table. get_or_insert ( "version" , version) ;
289- toml_edit:: Value :: InlineTable ( ver_table)
290- }
291- ComponentDependency :: Package {
292- version,
293- registry,
294- package,
295- export : _,
296- } => {
297- let mut pkg_table = toml_edit:: InlineTable :: default ( ) ;
298- pkg_table. get_or_insert ( "version" , version) ;
299- if let Some ( reg) = registry. clone ( ) {
300- pkg_table. get_or_insert ( "registry" , reg. to_string ( ) ) ;
301- }
302- if let Some ( pkg) = package {
303- pkg_table. get_or_insert ( "package" , pkg) ;
304- }
305- toml_edit:: Value :: InlineTable ( pkg_table)
306- }
307- ComponentDependency :: Local { path, export : _ } => {
308- let mut local_table = toml_edit:: InlineTable :: default ( ) ;
309- local_table. get_or_insert ( "path" , path. to_str ( ) . unwrap ( ) . to_owned ( ) ) ;
310- toml_edit:: Value :: InlineTable ( local_table)
311- }
312- ComponentDependency :: HTTP {
313- url,
314- digest,
315- export : _,
316- } => {
317- let mut http_table = toml_edit:: InlineTable :: default ( ) ;
318- http_table. get_or_insert ( "url" , url) ;
319- http_table. get_or_insert ( "digest" , digest) ;
320- toml_edit:: Value :: InlineTable ( http_table)
321- }
322- } ;
323-
324- dependencies_table. insert ( & name. to_string ( ) , toml_edit:: Item :: Value ( dep_src. clone ( ) ) ) ;
325- }
326-
327- doc[ "component" ] [ component_id] [ "dependencies" ] = toml_edit:: Item :: Table ( dependencies_table) ;
328-
329- Ok ( doc. to_string ( ) )
330- }
331-
332- // TODO: Eventually bring this function with the proposed Spin functionality of searching in parent Directories.
333- fn get_spin_manifest_path ( ) -> Result < PathBuf > {
334- let manifest_path = PathBuf :: from ( "spin.toml" ) ;
335- if !manifest_path. exists ( ) {
336- bail ! ( "No spin.toml file found in the current directory" ) ;
337- }
338- Ok ( manifest_path)
339- }
340-
341- const DEFAULT_WIT : & str =
r#"package spin-deps:[email protected] ; 342-
343- world deps {
344- }
345- "# ;
0 commit comments