@@ -43,91 +43,113 @@ protected function configure(): void
4343
4444 protected function executeCommand (InputInterface $ input , OutputInterface $ output ): int
4545 {
46- $ sourceFile = $ input ->getArgument ('file ' );
47- $ themeCode = $ input ->getArgument ('theme ' );
46+ try {
47+ $ sourceFileArg = $ input ->getArgument ('file ' );
48+ $ absoluteSourcePath = $ this ->getAbsoluteSourcePath ($ sourceFileArg );
49+
50+ // Update sourceFileArg if it was normalized to relative path
51+ $ rootPath = $ this ->directoryList ->getRoot ();
52+ $ sourceFile = str_starts_with ($ absoluteSourcePath , $ rootPath . '/ ' )
53+ ? substr ($ absoluteSourcePath , strlen ($ rootPath ) + 1 )
54+ : $ sourceFileArg ;
55+
56+ $ themeCode = $ this ->getThemeCode ($ input );
57+ $ themePath = $ this ->getThemePath ($ themeCode );
58+
59+ $ destinationPath = $ this ->vendorFileMapper ->mapToThemePath ($ sourceFile , $ themePath );
60+ $ absoluteDestPath = $ this ->getAbsoluteDestPath ($ destinationPath , $ rootPath );
61+
62+ if (!$ this ->confirmCopy ($ sourceFile , $ absoluteDestPath , $ rootPath )) {
63+ return Cli::RETURN_SUCCESS ;
64+ }
65+
66+ $ this ->performCopy ($ absoluteSourcePath , $ absoluteDestPath );
67+ $ this ->io ->success ("File copied successfully. " );
68+
69+ return Cli::RETURN_SUCCESS ;
70+ } catch (\Exception $ e ) {
71+ $ this ->io ->error ($ e ->getMessage ());
72+ return Cli::RETURN_FAILURE ;
73+ }
74+ }
4875
49- // 1. Verify Source File
76+ private function getAbsoluteSourcePath (string $ sourceFile ): string
77+ {
5078 $ rootPath = $ this ->directoryList ->getRoot ();
51- // If absolute path provided
5279 if (str_starts_with ($ sourceFile , '/ ' )) {
5380 $ absoluteSourcePath = $ sourceFile ;
54- // Make relative for display/proecessing
55- if (str_starts_with ($ sourceFile , $ rootPath . '/ ' )) {
56- $ sourceFile = substr ($ sourceFile , strlen ($ rootPath ) + 1 );
57- }
5881 } else {
59- $ absoluteSourcePath = $ rootPath . '/ ' . $ sourceFile ;
82+ $ absoluteSourcePath = $ rootPath . '/ ' . $ sourceFile ;
6083 }
6184
6285 if (!file_exists ($ absoluteSourcePath )) {
63- $ this ->io ->error ("Source file not found: $ absoluteSourcePath " );
64- return Cli::RETURN_FAILURE ;
86+ throw new \RuntimeException ("Source file not found: $ absoluteSourcePath " );
6587 }
6688
67- // 2. Select Theme if missing
68- if (!$ themeCode ) {
69- $ themes = $ this ->themeList ->getAllThemes ();
70- $ options = [];
71- foreach ($ themes as $ theme ) {
72- $ options [$ theme ->getCode ()] = $ theme ->getCode ();
73- }
89+ return $ absoluteSourcePath ;
90+ }
7491
75- if (empty ($ options )) {
76- $ this ->io ->error ('No themes found to copy to. ' );
77- return Cli::RETURN_FAILURE ;
78- }
92+ private function getThemeCode (InputInterface $ input ): string
93+ {
94+ $ themeCode = $ input ->getArgument ('theme ' );
95+ if ($ themeCode ) {
96+ return $ themeCode ;
97+ }
7998
80- // Fix Environment for DDEV (Required for Laravel Prompts)
81- $ this ->fixPromptEnvironment ();
82-
83- $ themeCode = search (
84- label: 'Select target theme ' ,
85- options: fn (string $ value ) => array_filter (
86- $ options ,
87- fn ($ option ) => str_contains (strtolower ($ option ), strtolower ($ value ))
88- ),
89- placeholder: 'Search for a theme... '
90- );
99+ $ themes = $ this ->themeList ->getAllThemes ();
100+ $ options = [];
101+ foreach ($ themes as $ theme ) {
102+ $ options [$ theme ->getCode ()] = $ theme ->getCode ();
91103 }
92104
93- // 3. Resolve Theme Path
105+ if (empty ($ options )) {
106+ throw new \RuntimeException ('No themes found to copy to. ' );
107+ }
108+
109+ $ this ->fixPromptEnvironment ();
110+
111+ return search (
112+ label: 'Select target theme ' ,
113+ options: fn (string $ value ) => array_filter (
114+ $ options ,
115+ fn ($ option ) => str_contains (strtolower ($ option ), strtolower ($ value ))
116+ ),
117+ placeholder: 'Search for a theme... '
118+ );
119+ }
120+
121+ private function getThemePath (string $ themeCode ): string
122+ {
94123 $ theme = $ this ->themeList ->getThemeByCode ($ themeCode );
95124 if (!$ theme ) {
96- $ this ->io ->error ("Theme not found: $ themeCode " );
97- return Cli::RETURN_FAILURE ;
125+ throw new \RuntimeException ("Theme not found: $ themeCode " );
98126 }
99127
100- // Use ComponentRegistrar to get absolute path
101128 $ regName = $ theme ->getArea () . '/ ' . $ theme ->getCode ();
102129 $ themePath = $ this ->componentRegistrar ->getPath (ComponentRegistrar::THEME , $ regName );
103130
104131 if (!$ themePath ) {
105- // Fallback to model path if registrar fails
106- $ this ->io ->warning ("Theme path not found via ComponentRegistrar for $ regName, falling back to getFullPath() " );
107- $ themePath = $ theme ->getFullPath ();
132+ $ this ->io ->warning ("Theme path not found via ComponentRegistrar for $ regName, falling back to getFullPath() " );
133+ $ themePath = $ theme ->getFullPath ();
108134 }
109135
110- // 4. Calculate Destination
111- try {
112- $ destinationPath = $ this ->vendorFileMapper ->mapToThemePath ($ sourceFile , $ themePath );
113- } catch (\Exception $ e ) {
114- $ this ->io ->error ($ e ->getMessage ());
115- return Cli::RETURN_FAILURE ;
116- }
136+ return $ themePath ;
137+ }
117138
139+ private function getAbsoluteDestPath (string $ destinationPath , string $ rootPath ): string
140+ {
118141 if (str_starts_with ($ destinationPath , '/ ' )) {
119- $ absoluteDestPath = $ destinationPath ;
120- } else {
121- $ absoluteDestPath = $ rootPath . '/ ' . $ destinationPath ;
142+ return $ destinationPath ;
122143 }
144+ return $ rootPath . '/ ' . $ destinationPath ;
145+ }
123146
124- // Make destination relative for display if it's inside root
125- $ destinationDisplay = $ absoluteDestPath ;
126- if ( str_starts_with ($ absoluteDestPath , $ rootPath . '/ ' )) {
127- $ destinationDisplay = substr ($ absoluteDestPath , strlen ($ rootPath ) + 1 );
128- }
147+ private function confirmCopy ( string $ sourceFile , string $ absoluteDestPath , string $ rootPath ): bool
148+ {
149+ $ destinationDisplay = str_starts_with ($ absoluteDestPath , $ rootPath . '/ ' )
150+ ? substr ($ absoluteDestPath , strlen ($ rootPath ) + 1 )
151+ : $ absoluteDestPath ;
129152
130- // 5. Preview & Confirm
131153 $ this ->io ->section ('Copy Preview ' );
132154 $ this ->io ->text ([
133155 "Source: <info> $ sourceFile</info> " ,
@@ -138,31 +160,21 @@ protected function executeCommand(InputInterface $input, OutputInterface $output
138160
139161 if (file_exists ($ absoluteDestPath )) {
140162 $ this ->io ->warning ("File already exists at destination! " );
141- if (!$ this ->io ->confirm ('Overwrite existing file? ' , false )) {
142- return Cli::RETURN_SUCCESS ;
143- }
144- } else {
145- if (!$ this ->io ->confirm ('Proceed with copy? ' , true )) {
146- return Cli::RETURN_SUCCESS ;
147- }
163+ return $ this ->io ->confirm ('Overwrite existing file? ' , false );
148164 }
149165
150- // 6. Perform Copy
151- try {
152- $ directory = dirname ($ absoluteDestPath );
153- if (!is_dir ($ directory )) {
154- if (!mkdir ($ directory , 0777 , true ) && !is_dir ($ directory )) {
155- throw new \RuntimeException (sprintf ('Directory "%s" was not created ' , $ directory ));
156- }
157- }
158- copy ($ absoluteSourcePath , $ absoluteDestPath );
159- $ this ->io ->success ("File copied successfully. " );
160- } catch (\Exception $ e ) {
161- $ this ->io ->error ("Failed to copy file: " . $ e ->getMessage ());
162- return Cli::RETURN_FAILURE ;
163- }
166+ return $ this ->io ->confirm ('Proceed with copy? ' , true );
167+ }
164168
165- return Cli::RETURN_SUCCESS ;
169+ private function performCopy (string $ absoluteSourcePath , string $ absoluteDestPath ): void
170+ {
171+ $ directory = dirname ($ absoluteDestPath );
172+ if (!is_dir ($ directory )) {
173+ if (!mkdir ($ directory , 0777 , true ) && !is_dir ($ directory )) {
174+ throw new \RuntimeException (sprintf ('Directory "%s" was not created ' , $ directory ));
175+ }
176+ }
177+ copy ($ absoluteSourcePath , $ absoluteDestPath );
166178 }
167179
168180 private function fixPromptEnvironment (): void
0 commit comments