diff --git a/monorepo/DevTools/src/MonorepoDevToolsServiceProvider.php b/monorepo/DevTools/src/MonorepoDevToolsServiceProvider.php index 0ca2d214b15..39e6292436c 100644 --- a/monorepo/DevTools/src/MonorepoDevToolsServiceProvider.php +++ b/monorepo/DevTools/src/MonorepoDevToolsServiceProvider.php @@ -17,6 +17,7 @@ public function boot(): void { $this->commands([ MonorepoReleaseCommand::class, + RefactorConfigCommand::class, ]); } } diff --git a/monorepo/DevTools/src/RefactorConfigCommand.php b/monorepo/DevTools/src/RefactorConfigCommand.php new file mode 100644 index 00000000000..927b26d5a26 --- /dev/null +++ b/monorepo/DevTools/src/RefactorConfigCommand.php @@ -0,0 +1,187 @@ +argument('format'); + if (! in_array($format, self::SUPPORTED_FORMATS)) { + $this->error('Invalid format. Supported formats: '.implode(', ', self::SUPPORTED_FORMATS)); + + return 1; + } + + $this->gray(" > Migrating configuration to $format"); + + return match ($format) { + 'yaml' => $this->migrateToYaml(), + }; + } + + protected function migrateToYaml(): int + { + $this->ensureYamlConfigDoesNotExist(); + + $config = $this->getConfigDiff(); + + if (empty($config)) { + $this->warn("You don't seem to have any configuration to migrate."); + + return 0; + } + + $serializedConfig = $this->serializePhpData($config); + $yaml = $this->dumpConfigToYaml($serializedConfig); + + file_put_contents(Hyde::path('hyde.yml'), $yaml); + + $this->info('All done!'); + + return 0; + } + + protected function ensureYamlConfigDoesNotExist(): void + { + if (file_exists(Hyde::path('hyde.yml')) || file_exists(Hyde::path('hyde.yaml'))) { + throw new RuntimeException('Configuration already exists in YAML format.'); + } + } + + protected function getConfigDiff(): array + { + $config = config('hyde'); + $default = require Hyde::vendorPath('config/hyde.php'); + + return $this->diffConfig($config, $default); + } + + protected function dumpConfigToYaml(array $config): string + { + return Yaml::dump($config, 16, 4, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); + } + + /** + * @param array $config + * @return array + */ + protected function serializePhpData(array $config): array + { + return collect($config)->mapWithKeys(function ($value, $key) { + if (is_array($value)) { + return [$key => $this->serializePhpData($value)]; + } + + return $this->serializePhpValue($value, $key); + })->toArray(); + } + + /** + * @param mixed $value + * @param string|int $key + * @return array + */ + protected function serializePhpValue(mixed $value, string|int $key): array + { + if ($value instanceof Feature) { + return [$key => Str::kebab($value->name)]; + } + + if (is_string($key) && str_starts_with($key, 'Hyde\Pages\\')) { + return [Str::kebab(substr($key, 11)) => $value]; + } + + if ($value instanceof MetadataElementContract) { + return [$key => $value->__toString()]; + } + + if ($value instanceof PostAuthor) { + return [$key => $this->serializePostAuthor($value)]; + } + + return [$key => $value]; + } + + protected function serializePostAuthor(PostAuthor $author): array + { + return [ + 'username' => $author->username, + 'name' => $author->name, + 'website' => $author->website, + ]; + } + + /** + * @param array $config + * @param array $default + * @return array + */ + protected function diffConfig(array $config, array $default): array + { + $diff = []; + + foreach ($config as $key => $value) { + if (! isset($default[$key]) || $value != $default[$key]) { + $diff[$key] = $value; + } + } + + return $this->arrayFilterRecurse($diff); + } + + /** + * @param array $input + * @return array + */ + protected function arrayFilterRecurse(array $input): array + { + foreach ($input as $key => &$value) { + if (is_array($value)) { + $value = $this->arrayFilterRecurse($value); + if (empty($value)) { + unset($input[$key]); + } + } elseif (blank($value)) { + unset($input[$key]); + } + } + + return $input; + } +}