@@ -33,32 +33,33 @@ fn main() -> Result<()> {
3333
3434 // Load config
3535 let config = Config :: load ( ) ?;
36+ let refresh = cli. refresh ;
3637
3738 match cli. command {
3839 Commands :: Env { packages, export, json } => {
39- cmd_env ( & config, & packages, export, json) ?;
40+ cmd_env ( & config, & packages, export, json, refresh ) ?;
4041 }
4142 Commands :: Run { packages, env_vars, command } => {
42- cmd_run ( & config, & packages, & env_vars, & command) ?;
43+ cmd_run ( & config, & packages, & env_vars, & command, refresh ) ?;
4344 }
4445 Commands :: Shell { packages, shell } => {
45- cmd_shell ( & config, & packages, shell) ?;
46+ cmd_shell ( & config, & packages, shell, refresh ) ?;
4647 }
4748 Commands :: List { package } => {
48- cmd_list ( & config, package) ?;
49+ cmd_list ( & config, package, refresh ) ?;
4950 }
5051 Commands :: Info { package } => {
51- cmd_info ( & config, & package) ?;
52+ cmd_info ( & config, & package, refresh ) ?;
5253 }
53- Commands :: Validate { package } => {
54- cmd_validate ( & config, package) ?;
54+ Commands :: Validate { package, strict } => {
55+ cmd_validate ( & config, package, strict , refresh ) ?;
5556 }
5657 Commands :: Lock { packages, update : _ } => {
57- cmd_lock ( & config, & packages) ?;
58+ cmd_lock ( & config, & packages, refresh ) ?;
5859 }
5960 Commands :: Context { action } => match action {
6061 ContextAction :: Save { packages, output } => {
61- cmd_context_save ( & config, & packages, & output) ?;
62+ cmd_context_save ( & config, & packages, & output, refresh ) ?;
6263 }
6364 ContextAction :: Show { file, json, export } => {
6465 cmd_context_show ( & file, json, export) ?;
@@ -77,7 +78,7 @@ fn main() -> Result<()> {
7778 Cli :: print_completions ( shell) ;
7879 }
7980 Commands :: Wrap { packages, dir, shell } => {
80- cmd_wrap ( & config, & packages, & dir, & shell) ?;
81+ cmd_wrap ( & config, & packages, & dir, & shell, refresh ) ?;
8182 }
8283 Commands :: Publish { target, path, flat } => {
8384 cmd_publish ( & target, path. as_deref ( ) , flat) ?;
@@ -88,8 +89,14 @@ fn main() -> Result<()> {
8889}
8990
9091/// Resolve packages and print environment
91- fn cmd_env ( config : & Config , packages : & [ String ] , export : bool , json : bool ) -> Result < ( ) > {
92- let resolver = Resolver :: new ( config) ?;
92+ fn cmd_env (
93+ config : & Config ,
94+ packages : & [ String ] ,
95+ export : bool ,
96+ json : bool ,
97+ refresh : bool ,
98+ ) -> Result < ( ) > {
99+ let resolver = Resolver :: new ( config, refresh) ?;
93100 let resolved = resolver. resolve ( packages) ?;
94101 let env = resolved. environment ( ) ;
95102
@@ -114,13 +121,14 @@ fn cmd_run(
114121 packages : & [ String ] ,
115122 env_vars : & [ String ] ,
116123 command : & [ String ] ,
124+ refresh : bool ,
117125) -> Result < ( ) > {
118126 use std:: process:: Command ;
119127
120128 // Pre-resolve hooks
121129 Config :: run_hooks ( & config. hooks . pre_resolve , & std:: env:: vars ( ) . collect ( ) ) ?;
122130
123- let resolver = Resolver :: new ( config) ?;
131+ let resolver = Resolver :: new ( config, refresh ) ?;
124132 let resolved = resolver. resolve ( packages) ?;
125133 let mut env = resolved. environment ( ) ;
126134
@@ -138,23 +146,17 @@ fn cmd_run(
138146 anyhow:: bail!( "No command specified" ) ;
139147 }
140148
141- // Resolve command alias. A command value may include baked-in
142- // arguments (e.g. `nukex: ${NUKE}/Nuke --nukex`) or whitespace from
143- // a script launcher (e.g. `usdview: python3.14 ~/USD/bin/usdview`).
144- // Tokenize with POSIX shell rules so the first token is the program
145- // and the rest are prepended to user args. Tilde-expand each token
146- // individually — Package::expand_env_value only expands a leading
147- // `~/` so tokens after whitespace would otherwise stay literal.
149+ // Resolve command alias. A command value may be a bare path (possibly
150+ // containing spaces, e.g. `/Applications/Houdini 20/bin/hython`), or
151+ // include baked-in arguments (e.g. `nukex: ${NUKE}/Nuke --nukex`), or
152+ // whitespace from a script launcher (e.g. `python3.14 ~/USD/bin/usdview`).
148153 let commands_map = resolved. commands ( ) ;
149154 let resolved_cmd = commands_map
150155 . get ( & command[ 0 ] )
151156 . cloned ( )
152157 . unwrap_or_else ( || command[ 0 ] . clone ( ) ) ;
153- let mut tokens: Vec < String > = shell_words:: split ( & resolved_cmd)
154- . with_context ( || format ! ( "Failed to parse command alias: {:?}" , resolved_cmd) ) ?
155- . into_iter ( )
156- . map ( |t| shellexpand:: tilde ( & t) . into_owned ( ) )
157- . collect ( ) ;
158+ let mut tokens = package:: tokenize_command ( & resolved_cmd)
159+ . with_context ( || format ! ( "Failed to parse command alias: {:?}" , resolved_cmd) ) ?;
158160 if tokens. is_empty ( ) {
159161 anyhow:: bail!(
160162 "Command alias for {:?} resolved to an empty string" ,
@@ -180,8 +182,13 @@ fn cmd_run(
180182}
181183
182184/// Start interactive shell with resolved environment
183- fn cmd_shell ( config : & Config , packages : & [ String ] , shell : Option < String > ) -> Result < ( ) > {
184- let resolver = Resolver :: new ( config) ?;
185+ fn cmd_shell (
186+ config : & Config ,
187+ packages : & [ String ] ,
188+ shell : Option < String > ,
189+ refresh : bool ,
190+ ) -> Result < ( ) > {
191+ let resolver = Resolver :: new ( config, refresh) ?;
185192 let resolved = resolver. resolve ( packages) ?;
186193 let env = resolved. environment ( ) ;
187194
@@ -195,8 +202,8 @@ fn cmd_shell(config: &Config, packages: &[String], shell: Option<String>) -> Res
195202}
196203
197204/// List available packages
198- fn cmd_list ( config : & Config , package : Option < String > ) -> Result < ( ) > {
199- let resolver = Resolver :: new ( config) ?;
205+ fn cmd_list ( config : & Config , package : Option < String > , refresh : bool ) -> Result < ( ) > {
206+ let resolver = Resolver :: new ( config, refresh ) ?;
200207
201208 if let Some ( name) = package {
202209 // List versions of specific package
@@ -217,8 +224,8 @@ fn cmd_list(config: &Config, package: Option<String>) -> Result<()> {
217224}
218225
219226/// Show package info
220- fn cmd_info ( config : & Config , package : & str ) -> Result < ( ) > {
221- let resolver = Resolver :: new ( config) ?;
227+ fn cmd_info ( config : & Config , package : & str , refresh : bool ) -> Result < ( ) > {
228+ let resolver = Resolver :: new ( config, refresh ) ?;
222229 let pkg = resolver. get_package ( package) ?;
223230
224231 println ! ( "Name: {}" , pkg. name) ;
@@ -248,9 +255,18 @@ fn cmd_info(config: &Config, package: &str) -> Result<()> {
248255 Ok ( ( ) )
249256}
250257
251- /// Validate package definitions
252- fn cmd_validate ( config : & Config , package : Option < String > ) -> Result < ( ) > {
253- let resolver = Resolver :: new ( config) ?;
258+ /// Validate package definitions.
259+ ///
260+ /// Dependency problems are always fatal. Command-target problems
261+ /// (missing / non-executable files) are reported as warnings unless
262+ /// `strict` is set, in which case they fail validation too.
263+ fn cmd_validate (
264+ config : & Config ,
265+ package : Option < String > ,
266+ strict : bool ,
267+ refresh : bool ,
268+ ) -> Result < ( ) > {
269+ let resolver = Resolver :: new ( config, refresh) ?;
254270
255271 let packages = if let Some ( name) = package {
256272 vec ! [ name]
@@ -259,10 +275,27 @@ fn cmd_validate(config: &Config, package: Option<String>) -> Result<()> {
259275 } ;
260276
261277 let mut errors = 0 ;
278+ let mut warnings = 0 ;
262279
263280 for pkg_name in packages {
264- match resolver. validate_package ( & pkg_name) {
265- Ok ( ( ) ) => println ! ( "✓ {}" , pkg_name) ,
281+ let report = resolver. validate_package_report ( & pkg_name) ;
282+ match report {
283+ Ok ( cmd_problems) => {
284+ if cmd_problems. is_empty ( ) {
285+ println ! ( "✓ {}" , pkg_name) ;
286+ } else {
287+ let label = if strict { "✗" } else { "!" } ;
288+ println ! ( "{} {}: command problems:" , label, pkg_name) ;
289+ for p in & cmd_problems {
290+ println ! ( " - {}" , p) ;
291+ }
292+ if strict {
293+ errors += 1 ;
294+ } else {
295+ warnings += 1 ;
296+ }
297+ }
298+ }
266299 Err ( e) => {
267300 println ! ( "✗ {}: {}" , pkg_name, e) ;
268301 errors += 1 ;
@@ -274,7 +307,14 @@ fn cmd_validate(config: &Config, package: Option<String>) -> Result<()> {
274307 anyhow:: bail!( "{} package(s) failed validation" , errors) ;
275308 }
276309
277- println ! ( "\n All packages valid!" ) ;
310+ if warnings > 0 {
311+ println ! (
312+ "\n All dependencies resolve ({} package(s) with command warnings — use --strict to fail on these)." ,
313+ warnings
314+ ) ;
315+ } else {
316+ println ! ( "\n All packages valid!" ) ;
317+ }
278318 Ok ( ( ) )
279319}
280320
@@ -283,9 +323,9 @@ fn cmd_validate(config: &Config, package: Option<String>) -> Result<()> {
283323// ---------------------------------------------------------------------------
284324
285325/// Resolve packages and write pinned versions to `anvil.lock`.
286- fn cmd_lock ( config : & Config , packages : & [ String ] ) -> Result < ( ) > {
326+ fn cmd_lock ( config : & Config , packages : & [ String ] , refresh : bool ) -> Result < ( ) > {
287327 // Always resolve fresh (ignore existing lockfile).
288- let resolver = Resolver :: new_unlocked ( config) ?;
328+ let resolver = Resolver :: new_unlocked ( config, refresh ) ?;
289329 let resolved = resolver. resolve ( packages) ?;
290330
291331 let mut pins = std:: collections:: HashMap :: new ( ) ;
@@ -314,8 +354,13 @@ fn cmd_lock(config: &Config, packages: &[String]) -> Result<()> {
314354// ---------------------------------------------------------------------------
315355
316356/// Resolve packages and save the full environment to a context file.
317- fn cmd_context_save ( config : & Config , packages : & [ String ] , output : & str ) -> Result < ( ) > {
318- let resolver = Resolver :: new ( config) ?;
357+ fn cmd_context_save (
358+ config : & Config ,
359+ packages : & [ String ] ,
360+ output : & str ,
361+ refresh : bool ,
362+ ) -> Result < ( ) > {
363+ let resolver = Resolver :: new ( config, refresh) ?;
319364 let resolved = resolver. resolve ( packages) ?;
320365 let env = resolved. environment ( ) ;
321366
@@ -416,7 +461,7 @@ fn cmd_init(name: &str, version: &str, flat: bool) -> Result<()> {
416461 let template = format ! (
417462 r#"name: {name}
418463version: "{version}"
419- description: TODO
464+ # description: one-line summary
420465
421466# requires:
422467# - python-3.11
@@ -459,8 +504,14 @@ environment:
459504// ---------------------------------------------------------------------------
460505
461506/// Generate wrapper scripts for all commands defined by the resolved packages.
462- fn cmd_wrap ( config : & Config , packages : & [ String ] , dir : & str , wrapper_shell : & str ) -> Result < ( ) > {
463- let resolver = Resolver :: new ( config) ?;
507+ fn cmd_wrap (
508+ config : & Config ,
509+ packages : & [ String ] ,
510+ dir : & str ,
511+ wrapper_shell : & str ,
512+ refresh : bool ,
513+ ) -> Result < ( ) > {
514+ let resolver = Resolver :: new ( config, refresh) ?;
464515 let resolved = resolver. resolve ( packages) ?;
465516 let commands = resolved. commands ( ) ;
466517
0 commit comments