@@ -6,10 +6,14 @@ use moon_common::{
66} ;
77use moon_config:: { VcsConfig , VcsHookFormat } ;
88use moon_vcs:: VcsHookEnvironment ;
9+ use regex:: Regex ;
910use starbase_utils:: fs;
1011use std:: path:: Path ;
12+ use std:: sync:: LazyLock ;
1113use tracing:: { debug, instrument, warn} ;
1214
15+ static ARG_VAR_PATTERN : LazyLock < Regex > = LazyLock :: new ( || Regex :: new ( r"\$(ARG\d+)" ) . unwrap ( ) ) ;
16+
1317pub enum ShellType {
1418 Bash ,
1519 Pwsh ,
@@ -77,7 +81,10 @@ impl<'app> HooksGenerator<'app> {
7781
7882 self . remove_hook_files ( & hooks_dir, & state. data . hook_names ) ?;
7983
80- if hooks_dir. exists ( ) {
84+ // Only remove the directory itself when moon owns it. State from
85+ // older moon versions may reference a directory managed by another
86+ // tool (husky, lefthook, etc), which we should not delete!
87+ if ( dir == ".moon/hooks" || dir == ".config/moon/hooks" ) && hooks_dir. exists ( ) {
8188 fs:: remove_dir_all ( hooks_dir) ?;
8289 }
8390 }
@@ -203,7 +210,7 @@ impl<'app> HooksGenerator<'app> {
203210 hook_name : & str ,
204211 commands : & [ String ] ,
205212 ) -> miette:: Result < ( ) > {
206- let hook = self . format_hook_file ( commands, true ) ?;
213+ let hook = self . format_hook_file ( commands) ?;
207214
208215 // Bash only
209216 if self . is_bash_format ( ) {
@@ -224,21 +231,31 @@ impl<'app> HooksGenerator<'app> {
224231 // Create a bash hook to call the PowerShell script
225232 let bash_hook_path = env. hooks_dir . join ( hook_name) ;
226233
227- self . write_file ( & bash_hook_path, format ! (
228- "#!/bin/sh\n {} -NoLogo -NoProfile -ExecutionPolicy Bypass -File \" {}\" $1 $2 $3 $4 $5" ,
229- if matches!( self . shell, ShellType :: Pwsh ) {
230- "pwsh.exe"
231- } else {
232- "powershell.exe"
233- } ,
234- hook_path. relative_to( & env. working_dir) . unwrap( ) ,
235- ) ) ?;
234+ // Prefer a path relative from the working directory, which is
235+ // where hooks are executed from, but fall back to the absolute
236+ // path when the hooks directory is outside of it
237+ let script_path = match hook_path. relative_to ( & env. working_dir ) {
238+ Ok ( rel_path) => rel_path. to_string ( ) ,
239+ Err ( _) => hook_path. to_string_lossy ( ) . to_string ( ) ,
240+ } ;
241+
242+ self . write_file (
243+ & bash_hook_path,
244+ format ! (
245+ "#!/bin/sh\n {} -NoLogo -NoProfile -ExecutionPolicy Bypass -File \" {script_path}\" \" $@\" " ,
246+ if matches!( self . shell, ShellType :: Pwsh ) {
247+ "pwsh.exe"
248+ } else {
249+ "powershell.exe"
250+ } ,
251+ ) ,
252+ ) ?;
236253 }
237254
238255 Ok ( ( ) )
239256 }
240257
241- fn format_hook_file ( & self , commands : & [ String ] , with_header : bool ) -> miette:: Result < String > {
258+ fn format_hook_file ( & self , commands : & [ String ] ) -> miette:: Result < String > {
242259 let mut contents = vec ! [ ] ;
243260
244261 if self . is_bash_format ( ) {
@@ -270,13 +287,11 @@ impl<'app> HooksGenerator<'app> {
270287 ] ) ;
271288 }
272289
273- if with_header {
274- contents. extend ( [
275- "# Automatically generated by moon. DO NOT MODIFY!" ,
276- "# https://moonrepo.dev/docs/guides/vcs-hooks" ,
277- "" ,
278- ] ) ;
279- }
290+ contents. extend ( [
291+ "# Automatically generated by moon. DO NOT MODIFY!" ,
292+ "# https://moonrepo.dev/docs/guides/vcs-hooks" ,
293+ "" ,
294+ ] ) ;
280295
281296 for command in commands {
282297 contents. push ( command) ;
@@ -298,7 +313,11 @@ impl<'app> HooksGenerator<'app> {
298313 let mut content = contents. join ( "\n " ) ;
299314
300315 if !self . is_bash_format ( ) {
301- content = content. replace ( "$ARG" , "$env:ARG" ) ;
316+ // Only rewrite the variables that we inject (ARG0, ARG1, etc),
317+ // otherwise user variables like $ARGS would also be mangled
318+ content = ARG_VAR_PATTERN
319+ . replace_all ( & content, "$$env:$1" )
320+ . to_string ( ) ;
302321 }
303322
304323 Ok ( content)
0 commit comments