2020namespace FastForward \DevTools \Console \Command ;
2121
2222use FastForward \DevTools \Console \Command \Traits \LogsCommandResults ;
23- use Composer \Command \BaseCommand ;
24- use Composer \Factory ;
25- use Composer \Json \JsonManipulator ;
2623use FastForward \DevTools \Composer \Json \ComposerJsonInterface ;
2724use FastForward \DevTools \Console \Input \HasJsonOption ;
2825use FastForward \DevTools \Filesystem \FilesystemInterface ;
3229use Psr \Log \LogLevel ;
3330use Symfony \Component \Config \FileLocatorInterface ;
3431use Symfony \Component \Console \Attribute \AsCommand ;
32+ use Symfony \Component \Console \Command \Command ;
3533use Symfony \Component \Console \Input \InputInterface ;
3634use Symfony \Component \Console \Input \InputOption ;
3735use Symfony \Component \Console \Output \OutputInterface ;
36+ use Symfony \Component \Console \Question \ConfirmationQuestion ;
37+ use Symfony \Component \Console \Style \SymfonyStyle ;
3838use Symfony \Component \Filesystem \Path ;
3939
40+ use function Safe \json_decode ;
41+ use function Safe \json_encode ;
4042use function Safe \getcwd ;
4143
4244/**
4648 name: 'update-composer-json ' ,
4749 description: 'Updates composer.json with Fast Forward dev-tools scripts and metadata. '
4850)]
49- final class UpdateComposerJsonCommand extends BaseCommand implements LoggerAwareCommandInterface
51+ final class UpdateComposerJsonCommand extends Command implements LoggerAwareCommandInterface
5052{
5153 use HasJsonOption;
5254 use LogsCommandResults;
@@ -59,13 +61,15 @@ final class UpdateComposerJsonCommand extends BaseCommand implements LoggerAware
5961 * @param FileLocatorInterface $fileLocator the locator used to resolve packaged configuration files
6062 * @param FileDiffer $fileDiffer
6163 * @param LoggerInterface $logger the output-aware logger
64+ * @param SymfonyStyle $io
6265 */
6366 public function __construct (
6467 private readonly ComposerJsonInterface $ composer ,
6568 private readonly FilesystemInterface $ filesystem ,
6669 private readonly FileLocatorInterface $ fileLocator ,
6770 private readonly FileDiffer $ fileDiffer ,
6871 private readonly LoggerInterface $ logger ,
72+ private readonly SymfonyStyle $ io ,
6973 ) {
7074 parent ::__construct ();
7175 }
@@ -86,7 +90,7 @@ protected function configure(): void
8690 shortcut: 'f ' ,
8791 mode: InputOption::VALUE_OPTIONAL ,
8892 description: 'Path to the composer.json file to update. ' ,
89- default: Factory:: getComposerFile () ,
93+ default: ' composer.json ' ,
9094 )
9195 ->addOption (
9296 name: 'dry-run ' ,
@@ -132,22 +136,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
132136 }
133137
134138 $ currentContents = $ this ->filesystem ->readFile ($ file );
135- $ manipulator = new JsonManipulator ($ currentContents );
136- $ grumphpConfig = DevToolsPathResolver::getPackagePath ('grumphp.yml ' );
137-
138- foreach ($ this ->scripts () as $ name => $ command ) {
139- $ manipulator ->addSubNode ('scripts ' , $ name , $ command );
140- }
141-
142- if ('' === $ this ->composer ->getReadme () && $ this ->filesystem ->exists ('README.md ' , \dirname ($ file ))) {
143- $ manipulator ->addProperty ('readme ' , 'README.md ' );
144- }
145-
146- $ manipulator ->addSubNode ('extra ' , 'grumphp ' , [
147- 'config-default-path ' => Path::makeRelative ($ grumphpConfig , getcwd ()),
148- ], true );
149-
150- $ updatedContents = $ manipulator ->getContents ();
139+ $ updatedContents = $ this ->updatedComposerJsonContents ($ currentContents , $ file );
151140 $ comparison = $ this ->fileDiffer ->diffContents (
152141 'generated dev-tools composer.json configuration ' ,
153142 $ file ,
@@ -205,8 +194,68 @@ protected function execute(InputInterface $input, OutputInterface $output): int
205194 */
206195 private function shouldUpdateComposerJson (string $ file ): bool
207196 {
208- return $ this ->getIO ()
209- ->askConfirmation (\sprintf ('Update managed file %s? [y/N] ' , $ file ), false );
197+ $ confirmationMessage = \sprintf (
198+ 'composer.json file %s has changes. Do you want to update it with the new dev-tools configuration? ' ,
199+ $ file ,
200+ );
201+
202+ $ confirmation = new ConfirmationQuestion ($ confirmationMessage , false );
203+
204+ return $ this ->io ->askQuestion ($ confirmation );
205+ }
206+
207+ /**
208+ * Builds the managed composer.json payload.
209+ *
210+ * @param string $currentContents the current composer.json file contents
211+ * @param string $file the path being updated, used to resolve local README checks
212+ *
213+ * @return string the composer.json payload with managed sections applied
214+ */
215+ private function updatedComposerJsonContents (string $ currentContents , string $ file ): string
216+ {
217+ $ composerJsonData = json_decode ($ currentContents , true , 512 , \JSON_THROW_ON_ERROR );
218+
219+ if (! \is_array ($ composerJsonData )) {
220+ $ composerJsonData = [];
221+ }
222+
223+ $ scripts = $ composerJsonData ['scripts ' ] ?? [];
224+ if (! \is_array ($ scripts )) {
225+ $ scripts = [];
226+ }
227+
228+ foreach ($ this ->scripts () as $ name => $ command ) {
229+ $ scripts [$ name ] = $ command ;
230+ }
231+
232+ $ composerJsonData ['scripts ' ] = $ scripts ;
233+
234+ if ('' === $ this ->composer ->getReadme () && $ this ->filesystem ->exists ('README.md ' , \dirname ($ file ))) {
235+ if (! isset ($ composerJsonData ['readme ' ])) {
236+ $ composerJsonData ['readme ' ] = 'README.md ' ;
237+ }
238+ }
239+
240+ $ extra = $ composerJsonData ['extra ' ] ?? [];
241+ if (! \is_array ($ extra )) {
242+ $ extra = [];
243+ }
244+
245+ $ grumphpConfig = DevToolsPathResolver::getPackagePath ('grumphp.yml ' );
246+ $ grumphpExtra = $ extra ['grumphp ' ] ?? [];
247+ if (! \is_array ($ grumphpExtra )) {
248+ $ grumphpExtra = [];
249+ }
250+
251+ $ grumphpExtra ['config-default-path ' ] = Path::makeRelative ($ grumphpConfig , getcwd ());
252+ $ extra ['grumphp ' ] = $ grumphpExtra ;
253+ $ composerJsonData ['extra ' ] = $ extra ;
254+
255+ return json_encode (
256+ $ composerJsonData ,
257+ \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE
258+ ) . "\n" ;
210259 }
211260
212261 /**
0 commit comments