1515use OpenForgeProject \MageForge \Service \VendorFileMapper ;
1616use Symfony \Component \Console \Input \InputArgument ;
1717use Symfony \Component \Console \Input \InputInterface ;
18+ use Symfony \Component \Console \Input \InputOption ;
1819use Symfony \Component \Console \Output \OutputInterface ;
1920
2021use function Laravel \Prompts \search ;
@@ -34,15 +35,17 @@ protected function configure(): void
3435 {
3536 $ this ->setName ('mageforge:theme:copy-from-vendor ' )
3637 ->setDescription ('Copy a file from vendor/ to a specific theme with correct path resolution ' )
37- ->setAliases (['m:t:cfv ' ])
38+ ->setAliases (['theme:copy ' ])
3839 ->addArgument ('file ' , InputArgument::REQUIRED , 'Path to the source file (vendor/...) ' )
39- ->addArgument ('theme ' , InputArgument::OPTIONAL , 'Target theme code (e.g. Magento/luma) ' );
40+ ->addArgument ('theme ' , InputArgument::OPTIONAL , 'Target theme code (e.g. Magento/luma) ' )
41+ ->addOption ('dry-run ' , null , InputOption::VALUE_NONE , 'Preview the copy operation without performing it ' );
4042 }
4143
4244 protected function executeCommand (InputInterface $ input , OutputInterface $ output ): int
4345 {
4446 try {
4547 $ sourceFileArg = $ input ->getArgument ('file ' );
48+ $ isDryRun = $ input ->getOption ('dry-run ' );
4649 $ absoluteSourcePath = $ this ->getAbsoluteSourcePath ($ sourceFileArg );
4750
4851 // Update sourceFileArg if it was normalized to relative path
@@ -57,6 +60,11 @@ protected function executeCommand(InputInterface $input, OutputInterface $output
5760 $ destinationPath = $ this ->vendorFileMapper ->mapToThemePath ($ sourceFile , $ themePath );
5861 $ absoluteDestPath = $ this ->getAbsoluteDestPath ($ destinationPath , $ rootPath );
5962
63+ if ($ isDryRun ) {
64+ $ this ->showDryRunPreview ($ sourceFile , $ absoluteDestPath , $ rootPath );
65+ return Cli::RETURN_SUCCESS ;
66+ }
67+
6068 if (!$ this ->confirmCopy ($ sourceFile , $ absoluteDestPath , $ rootPath )) {
6169 return Cli::RETURN_SUCCESS ;
6270 }
@@ -175,6 +183,29 @@ private function performCopy(string $absoluteSourcePath, string $absoluteDestPat
175183 copy ($ absoluteSourcePath , $ absoluteDestPath );
176184 }
177185
186+ private function showDryRunPreview (string $ sourceFile , string $ absoluteDestPath , string $ rootPath ): void
187+ {
188+ $ destinationDisplay = str_starts_with ($ absoluteDestPath , $ rootPath . '/ ' )
189+ ? substr ($ absoluteDestPath , strlen ($ rootPath ) + 1 )
190+ : $ absoluteDestPath ;
191+
192+ $ this ->io ->section ('Dry Run - Copy Preview ' );
193+ $ this ->io ->text ([
194+ "Source: <info> $ sourceFile</info> " ,
195+ "Target: <info> $ destinationDisplay</info> " ,
196+ "Absolute Target: <comment> $ absoluteDestPath</comment> "
197+ ]);
198+ $ this ->io ->newLine ();
199+
200+ if (file_exists ($ absoluteDestPath )) {
201+ $ this ->io ->warning ("File already exists at destination and would be overwritten! " );
202+ } else {
203+ $ this ->io ->info ("File would be created at destination. " );
204+ }
205+
206+ $ this ->io ->note ("No files were modified (dry-run mode). " );
207+ }
208+
178209 private function fixPromptEnvironment (): void
179210 {
180211 if (getenv ('DDEV_PROJECT ' )) {
0 commit comments