@@ -26,15 +26,30 @@ export class ConfigGenerator {
2626 showProgress : boolean = true
2727 ) : Promise < void > {
2828 // Handle output directory path
29- // Always normalize the path, whether absolute or relative
30- // This avoids the need for process.cwd() which can fail in CI
31- if ( ! outputDir ) {
32- outputDir = '.' ;
29+ // Resolve to absolute path immediately while we can
30+ // This prevents issues when the current directory is deleted during execution
31+ let resolvedOutputDir = outputDir || '.' ;
32+
33+ // Resolve to absolute path early, before any async operations
34+ // Use a try-catch in case process.cwd() fails
35+ let absoluteOutputDir : string ;
36+ try {
37+ absoluteOutputDir = path . resolve ( resolvedOutputDir ) ;
38+ } catch {
39+ // If we can't resolve (e.g., cwd deleted), use the path as-is if absolute
40+ // or throw a clear error if it's relative
41+ if ( path . isAbsolute ( resolvedOutputDir ) ) {
42+ absoluteOutputDir = path . normalize ( resolvedOutputDir ) ;
43+ } else {
44+ throw new Error (
45+ `Cannot resolve relative path '${ resolvedOutputDir } ' - current directory may not exist. ` +
46+ 'Please provide an absolute path or ensure the current directory is valid.'
47+ ) ;
48+ }
3349 }
3450
35- // Normalize the path - this works for both absolute and relative paths
36- // and doesn't require process.cwd()
37- outputDir = path . normalize ( outputDir ) ;
51+ // Use absoluteOutputDir from here on
52+ resolvedOutputDir = absoluteOutputDir ;
3853
3954 // Validate input configurations
4055 try {
@@ -79,34 +94,15 @@ export class ConfigGenerator {
7994 if ( spinner ) spinner . start ( steps [ currentStep ] ) ;
8095
8196 // Create directories
82- // For absolute outputDir paths, join directly
83- // For relative paths, createSafeDirectory handles validation
84- let claudeDir : string ;
85- let agentsDir : string ;
86- let commandsDir : string ;
87- let hooksDir : string ;
88-
89- if ( path . isAbsolute ( outputDir ) ) {
90- // For absolute paths (e.g., test temp directories), join directly
91- claudeDir = path . join ( outputDir , '.claude' ) ;
92- agentsDir = path . join ( outputDir , '.claude' , 'agents' ) ;
93- commandsDir = path . join ( outputDir , '.claude' , 'commands' ) ;
94- hooksDir = path . join ( outputDir , '.claude' , 'hooks' ) ;
95- } else {
96- // For relative paths, construct the full paths
97- claudeDir = path . join ( outputDir , '.claude' ) ;
98- agentsDir = path . join ( outputDir , '.claude' , 'agents' ) ;
99- commandsDir = path . join ( outputDir , '.claude' , 'commands' ) ;
100- hooksDir = path . join ( outputDir , '.claude' , 'hooks' ) ;
101- }
102-
103- // Create all directories
104- await fs . mkdir ( claudeDir , { recursive : true } ) ;
105- await fs . mkdir ( agentsDir , { recursive : true } ) ;
106- await fs . mkdir ( commandsDir , { recursive : true } ) ;
107- await fs . mkdir ( hooksDir , { recursive : true } ) ;
108-
109- await fs . mkdir ( outputDir , { recursive : true } ) ;
97+ // Since resolvedOutputDir is now always absolute, we can safely join paths
98+ const claudeDir = path . join ( resolvedOutputDir , '.claude' ) ;
99+ const agentsDir = path . join ( claudeDir , 'agents' ) ;
100+ const commandsDir = path . join ( claudeDir , 'commands' ) ;
101+ const hooksDir = path . join ( claudeDir , 'hooks' ) ;
102+
103+ // Create all directories with absolute paths
104+ // Create parent directory first to ensure it exists
105+ await fs . mkdir ( resolvedOutputDir , { recursive : true } ) ;
110106 await fs . mkdir ( claudeDir , { recursive : true } ) ;
111107 await fs . mkdir ( agentsDir , { recursive : true } ) ;
112108 await fs . mkdir ( commandsDir , { recursive : true } ) ;
@@ -126,8 +122,8 @@ export class ConfigGenerator {
126122
127123 if ( claudeMdConfigs . length > 0 ) {
128124 const mergedClaudeMd = this . configMerger . merge ( claudeMdConfigs ) ;
129- // Write directly to outputDir without additional path validation since outputDir is already validated
130- const claudeMdPath = path . join ( outputDir , 'CLAUDE.md' ) ;
125+ // Write directly to resolvedOutputDir without additional path validation since resolvedOutputDir is already validated
126+ const claudeMdPath = path . join ( resolvedOutputDir , 'CLAUDE.md' ) ;
131127 await fs . writeFile ( claudeMdPath , mergedClaudeMd ) ;
132128 }
133129 if ( spinner ) spinner . succeed ( steps [ currentStep ] ) ;
@@ -144,9 +140,7 @@ export class ConfigGenerator {
144140 const agentName = typeof validatedAgent . name === 'string' ? validatedAgent . name : 'unknown' ;
145141 const filename = `${ PathValidator . validateFilename ( agentName . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 - ] / g, '-' ) ) } .md` ;
146142 const content = this . componentMerger . generateAgentFile ( validatedAgent ) ;
147- // Ensure directory exists before writing
148- await fs . mkdir ( agentsDir , { recursive : true } ) ;
149- // Write directly to agentsDir
143+ // Write directly to agentsDir (already created above)
150144 const agentPath = path . join ( agentsDir , filename ) ;
151145 await fs . writeFile ( agentPath , content ) ;
152146 }
@@ -166,9 +160,7 @@ export class ConfigGenerator {
166160 typeof validatedCommand . name === 'string' ? validatedCommand . name : 'unknown' ;
167161 const filename = `${ PathValidator . validateFilename ( commandName . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 - ] / g, '-' ) ) } .md` ;
168162 const content = this . componentMerger . generateCommandFile ( validatedCommand ) ;
169- // Ensure directory exists before writing
170- await fs . mkdir ( commandsDir , { recursive : true } ) ;
171- // Write directly to commandsDir
163+ // Write directly to commandsDir (already created above)
172164 const commandPath = path . join ( commandsDir , filename ) ;
173165 await fs . writeFile ( commandPath , content ) ;
174166 }
@@ -181,8 +173,7 @@ export class ConfigGenerator {
181173 const hookGroups = parsedConfigs . map ( c => c . parsed . hooks ) ;
182174 const mergedHooks = this . componentMerger . mergeHooks ( hookGroups ) ;
183175
184- // Ensure hooks directory exists
185- await fs . mkdir ( hooksDir , { recursive : true } ) ;
176+ // Process hooks (directory already created above)
186177 for ( const hook of mergedHooks ) {
187178 // Validate and sanitize hook data
188179 const validatedHook = InputValidator . validateHook ( hook ) ;
@@ -221,7 +212,7 @@ export class ConfigGenerator {
221212 const settingsPath = await PathValidator . createSafeFilePath (
222213 'settings.json' ,
223214 '.claude' ,
224- outputDir
215+ resolvedOutputDir
225216 ) ;
226217
227218 await fs . writeFile ( settingsPath , JSON . stringify ( validatedSettings , null , 2 ) ) ;
@@ -232,7 +223,7 @@ export class ConfigGenerator {
232223 // Validate the generated structure
233224 currentStep ++ ;
234225 if ( spinner ) spinner . start ( steps [ currentStep ] ) ;
235- const validation = await this . validateGeneratedStructure ( outputDir ) ;
226+ const validation = await this . validateGeneratedStructure ( resolvedOutputDir ) ;
236227 if ( ! validation . valid ) {
237228 if ( spinner ) spinner . warn ( `${ steps [ currentStep ] } - ${ validation . errors . length } warnings` ) ;
238229 validation . errors . forEach ( error => console . warn ( chalk . yellow ( ` - ${ error } ` ) ) ) ;
0 commit comments