From 3a928f6d5c873642f6a9e58e308b202605b6c3ad Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Tue, 23 Dec 2025 10:31:41 +0100 Subject: [PATCH] Add create_if_missing option to add-lines configurator This option allows specifying YAML content that should be created when the target is not found in the file. The content is appended to the file followed by the regular content. Example usage: { "add-lines": [{ "file": "config/packages/ai.yaml", "position": "after_target", "target": " store:", "create_if_missing": "ai:\n store:", "content": " azuresearch:\n ..." }] } --- src/Configurator/AddLinesConfigurator.php | 14 +++- .../Configurator/AddLinesConfiguratorTest.php | 70 +++++++++++++++++++ .../Fixtures/AddLines/ai_with_store.yaml | 5 ++ .../AddLines/ai_with_store_expected.yaml | 8 +++ .../Fixtures/AddLines/ai_without_store.yaml | 4 ++ .../AddLines/ai_without_store_expected.yaml | 8 +++ 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 tests/Configurator/Fixtures/AddLines/ai_with_store.yaml create mode 100644 tests/Configurator/Fixtures/AddLines/ai_with_store_expected.yaml create mode 100644 tests/Configurator/Fixtures/AddLines/ai_without_store.yaml create mode 100644 tests/Configurator/Fixtures/AddLines/ai_without_store_expected.yaml diff --git a/src/Configurator/AddLinesConfigurator.php b/src/Configurator/AddLinesConfigurator.php index 99443575..686abfec 100644 --- a/src/Configurator/AddLinesConfigurator.php +++ b/src/Configurator/AddLinesConfigurator.php @@ -129,8 +129,9 @@ public function executeConfigure(Recipe $recipe, $config): void continue; } $target = isset($patch['target']) ? $patch['target'] : null; + $createIfMissing = isset($patch['create_if_missing']) ? $patch['create_if_missing'] : null; - $newContents = $this->getPatchedContents($file, $content, $position, $target, $warnIfMissing); + $newContents = $this->getPatchedContents($file, $content, $position, $target, $warnIfMissing, $createIfMissing); $this->fileContents[$file] = $newContents; } } @@ -164,7 +165,7 @@ public function executeUnconfigure(Recipe $recipe, $config): void } } - private function getPatchedContents(string $file, string $value, string $position, ?string $target, bool $warnIfMissing): string + private function getPatchedContents(string $file, string $value, string $position, ?string $target, bool $warnIfMissing, ?string $createIfMissing = null): string { $fileContents = $this->readFile($file); @@ -192,6 +193,15 @@ private function getPatchedContents(string $file, string $value, string $positio break; } } + + if (!$targetFound && null !== $createIfMissing) { + // Insert the create_if_missing content at the end of the file, then add the value after the target + $createIfMissingLines = explode("\n", $createIfMissing); + $lines = array_merge($lines, $createIfMissingLines); + $lines[] = $value; + $targetFound = true; + } + $fileContents = implode("\n", $lines); if (!$targetFound) { diff --git a/tests/Configurator/AddLinesConfiguratorTest.php b/tests/Configurator/AddLinesConfiguratorTest.php index 03602b16..918e97a3 100644 --- a/tests/Configurator/AddLinesConfiguratorTest.php +++ b/tests/Configurator/AddLinesConfiguratorTest.php @@ -198,6 +198,66 @@ public function testSkippedIfTargetCannotBeFound() $this->assertSame($originalContent, $this->readFile('webpack.config.js')); } + public function testCreateIfMissingCreatesTargetAndAddsContent() + { + $this->copyFixture('ai_without_store.yaml', 'config/packages/ai.yaml'); + + $this->runConfigure([ + [ + 'file' => 'config/packages/ai.yaml', + 'position' => 'after_target', + 'target' => ' store:', + 'create_if_missing' => ' store:', + 'content' => " azuresearch:\n default:\n endpoint: '%env(AZURE_SEARCH_ENDPOINT)%'", + ], + ]); + + $this->assertSame( + $this->loadFixture('ai_without_store_expected.yaml'), + $this->readFile('config/packages/ai.yaml') + ); + } + + public function testCreateIfMissingWithExistingTarget() + { + $this->copyFixture('ai_with_store.yaml', 'config/packages/ai.yaml'); + + $this->runConfigure([ + [ + 'file' => 'config/packages/ai.yaml', + 'position' => 'after_target', + 'target' => ' store:', + 'create_if_missing' => ' store:', + 'content' => " azuresearch:\n default:\n endpoint: '%env(AZURE_SEARCH_ENDPOINT)%'", + ], + ]); + + $this->assertSame( + $this->loadFixture('ai_with_store_expected.yaml'), + $this->readFile('config/packages/ai.yaml') + ); + } + + public function testCreateIfMissingNotUsedWhenOptionNotSet() + { + $this->copyFixture('ai_without_store.yaml', 'config/packages/ai.yaml'); + + $this->runConfigure([ + [ + 'file' => 'config/packages/ai.yaml', + 'position' => 'after_target', + 'target' => ' store:', + 'content' => " azuresearch:\n default:\n endpoint: '%env(AZURE_SEARCH_ENDPOINT)%'", + ], + ]); + + // Content should remain unchanged since target was not found and no create_if_missing was provided + $this->assertSame( + $this->loadFixture('ai_without_store.yaml'), + $this->readFile('config/packages/ai.yaml') + ); + } + public function testPatchIgnoredIfValueAlreadyExists() { $originalContents = <<saveFile($targetPath, $this->loadFixture($fixtureName)); + } + private function createComposerMockWithPackagesInstalled(array $packages) { $packages = array_map(fn ($package) => explode(':', $package), $packages); diff --git a/tests/Configurator/Fixtures/AddLines/ai_with_store.yaml b/tests/Configurator/Fixtures/AddLines/ai_with_store.yaml new file mode 100644 index 00000000..8d23b232 --- /dev/null +++ b/tests/Configurator/Fixtures/AddLines/ai_with_store.yaml @@ -0,0 +1,5 @@ +ai: + store: + pinecone: + default: + api_key: 'xxx' diff --git a/tests/Configurator/Fixtures/AddLines/ai_with_store_expected.yaml b/tests/Configurator/Fixtures/AddLines/ai_with_store_expected.yaml new file mode 100644 index 00000000..ad9f1263 --- /dev/null +++ b/tests/Configurator/Fixtures/AddLines/ai_with_store_expected.yaml @@ -0,0 +1,8 @@ +ai: + store: + azuresearch: + default: + endpoint: '%env(AZURE_SEARCH_ENDPOINT)%' + pinecone: + default: + api_key: 'xxx' diff --git a/tests/Configurator/Fixtures/AddLines/ai_without_store.yaml b/tests/Configurator/Fixtures/AddLines/ai_without_store.yaml new file mode 100644 index 00000000..49312f76 --- /dev/null +++ b/tests/Configurator/Fixtures/AddLines/ai_without_store.yaml @@ -0,0 +1,4 @@ +ai: + platform: + default: + api_key: '%env(OPENAI_API_KEY)%' diff --git a/tests/Configurator/Fixtures/AddLines/ai_without_store_expected.yaml b/tests/Configurator/Fixtures/AddLines/ai_without_store_expected.yaml new file mode 100644 index 00000000..f953375a --- /dev/null +++ b/tests/Configurator/Fixtures/AddLines/ai_without_store_expected.yaml @@ -0,0 +1,8 @@ +ai: + platform: + default: + api_key: '%env(OPENAI_API_KEY)%' + store: + azuresearch: + default: + endpoint: '%env(AZURE_SEARCH_ENDPOINT)%'