ACMS-5823: Minor bugfixes, deprecate some classes and provided feature for flexible file operations.#98
ACMS-5823: Minor bugfixes, deprecate some classes and provided feature for flexible file operations.#98vishalkhode1 wants to merge 1 commit intodevelopfrom
Conversation
…e to override settings.php file paths.
|
Can we get test coverage for filesystem/operation classes. |
There was a problem hiding this comment.
Pull request overview
This PR refactors settings generation into a configurable “file operations” pipeline (copy/append/prepend) driven by defaults, composer.json overrides, and a new runtime PreSettingsFileGenerateEvent. It also introduces centralized placeholder/variable resolution, sets a consistent ${drs.root} config value, updates Drush commands for correct site context, and deprecates legacy filesystem/config helpers.
Changes:
- Introduce a new filesystem abstraction + operation model (Copy/Append/Prepend) and refactor
Settings::generate()to execute declarative operations with placeholder resolution and event-based customization. - Add
ConfigResolverand ensure${drs.root}is set in default configs for placeholder usage. - Update Drush commands/tests/docs for new APIs and deprecations.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/src/unit/SettingsUnitTest.php | Removes older unit test file (replaced by updated tests). |
| tests/src/unit/SettingsTest.php | Updates unit tests to new Settings initialization/config pattern and adds deprecation test. |
| tests/src/Functional/SettingsFileTest.php | Updates functional settings generation fixture to new Settings + DefaultConfig flow and asserts config dir creation. |
| tests/src/Functional/Helpers/FilesystemTest.php | Marks deprecated helper filesystem tests to ignore deprecations. |
| tests/src/Functional/Helpers/EnvironmentDetectorTest.php | Switches test file operations to Symfony filesystem/mkdir instead of deprecated helper. |
| tests/src/Functional/Drush/Traits/SiteUriTraitTest.php | Updates file copy usage to Symfony filesystem API. |
| tests/src/Functional/Config/SettingsConfigTest.php | Ignores deprecations for deprecated SettingsConfig tests. |
| tests/src/Functional/Config/DefaultDrushConfigTest.php | Updates expectations to new exported config shape including drs.root. |
| tests/src/Functional/Config/DefaultConfigTest.php | Updates expectations to include drs.root. |
| src/Settings.php | Major refactor: operations loading/merging, event hook, placeholder resolution, execution + permission handling. |
| src/Helpers/Filesystem.php | Deprecates legacy helper filesystem and triggers deprecation warning on construction. |
| src/Filesystem/StatefulDirectoryCreator.php | New permission/rollback helper for operation preparation and post-processing. |
| src/Filesystem/Operation/PrependOperation.php | New prepend operation implementation. |
| src/Filesystem/Operation/OperationType.php | New enum describing operation types and destructiveness. |
| src/Filesystem/Operation/OperationStatus.php | New enum for operation results. |
| src/Filesystem/Operation/OperationResult.php | New result object for success/skip/failure outcomes. |
| src/Filesystem/Operation/OperationKey.php | New enum for supported payload keys + schema metadata. |
| src/Filesystem/Operation/OperationFactoryInterface.php | Defines factory contract for operation instantiation. |
| src/Filesystem/Operation/OperationFactory.php | Concrete factory mapping enum types to operation classes. |
| src/Filesystem/Operation/FileOperationInterface.php | Interface for executable file operations. |
| src/Filesystem/Operation/FileOperationHandler.php | Core mapping normalization/validation and operation object creation. |
| src/Filesystem/Operation/CopyOperation.php | New copy operation with overwrite and placeholder-resolution options. |
| src/Filesystem/Operation/BaseOperation.php | Shared base for operation execution/validation/content resolution. |
| src/Filesystem/Operation/AppendOperation.php | New append operation implementation. |
| src/Filesystem/FilesystemInterface.php | New filesystem contract decoupling from Symfony directly. |
| src/Filesystem/Filesystem.php | Symfony filesystem adapter implementing the new contract. |
| src/Exceptions/InvalidMappingException.php | New exception for invalid operation mappings. |
| src/Event/PreSettingsFileGenerateEvent.php | New event allowing runtime operation mutation. |
| src/Drush/Commands/SettingsDrushCommands.php | Updates settings init to use new Settings API + hook manager; adjusts post-hook behavior. |
| src/Drush/Commands/MultisiteDrushCommands.php | Updates multisite command to extend base command and use new Settings initialization. |
| src/Drush/Commands/HooksDrushCommands.php | Adds post-hook to apply write permissions in CI fixture scenarios. |
| src/Drush/Commands/BaseDrushCommands.php | Centralizes site context initialization using --uri and config initializer. |
| src/Config/SettingsConfig.php | Deprecates legacy SettingsConfig and rewires methods through ConfigResolver. |
| src/Config/DefaultDrushConfig.php | Adds drs.root and simplifies exported config fields. |
| src/Config/DefaultConfig.php | Adds drs.root to default config. |
| src/Config/ConfigResolver.php | New centralized placeholder + boolean-string resolution. |
| src/Config/ConfigInitializer.php | Docblock typing tweak for addConfig() param. |
| examples/example-drush-command/src/Drush/Commands/ExampleDrushCommands.php | Demonstrates altering operations via the new event hook. |
| docs/how-to-use-operations.md | Adds comprehensive documentation for operations schema and customization. |
| README.md | Adds high-level documentation link and minor formatting tweaks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| } | ||
| if (!is_writable($path)) { | ||
| $this->trackState($path); | ||
| $this->fileSystem->chmod($path, 0755); |
| private function handleOperationResults(StatefulDirectoryCreator $stateful, array $results): void { | ||
| if ($results['success']) { | ||
| $stateful->lockPath(array_keys($results['success'])); | ||
| } | ||
| if ($results['skip']) { | ||
| $stateful->rollbackPath(array_keys($results['skip'])); | ||
| } | ||
| if ($results['failed']) { | ||
| $stateful->restorePath(); | ||
| throw new \Exception(implode(PHP_EOL, $results['failed'])); | ||
| } |
| private function readJsonFile(string $path): array { | ||
| assert( | ||
| file_exists($path) && is_readable($path), | ||
| sprintf("The JSON file must exist and be readable. Path: '%s'.", $path), | ||
| ); | ||
| return json_decode( | ||
| file_get_contents($path), | ||
| TRUE, | ||
| 512, | ||
| JSON_THROW_ON_ERROR, | ||
| ); |
| if ($destination_exists) { | ||
| $source_content = $this->getFilesystem()->readFile($source); | ||
| $destination_content = $this->getFilesystem()->readFile($destination); | ||
| if ($placeholder && str_contains($destination_content, $resolved_content)) { | ||
| return new OperationResult( | ||
| OperationStatus::Skipped, | ||
| sprintf('Resolved content contains the destination content - %s', $source), | ||
| $this, | ||
| ); | ||
| } | ||
| if (!$placeholder && $source_content === $destination_content) { | ||
| return new OperationResult( | ||
| OperationStatus::Skipped, | ||
| sprintf('Destination already matches source - %s', $destination), | ||
| $this, | ||
| ); | ||
| } | ||
| } |
| $path = $root . DIRECTORY_SEPARATOR . "sites" . DIRECTORY_SEPARATOR . $uri; | ||
| $file_system = new Filesystem(); | ||
| $currentPerms = fileperms($path) & 0777; | ||
| $file_system->chmod($path, $currentPerms | 0222, 0o000, TRUE); |
| $project_json_file = $project . '/composer.json'; | ||
| $json = $this->readJsonFile($project_json_file); | ||
| $project_operations = $json['extra']['drupal-recommended-settings']['operations'] ?? []; | ||
| if (!$project_operations) { | ||
| $project_operations_file = $json['extra']['drupal-recommended-settings']['operations-file'] ?? []; | ||
| if ($project_operations_file) { | ||
| $project_operations = $this->readJsonFile($project_operations_file); | ||
| } | ||
| } |
| public function tearDown(): void { | ||
| $this->fileSystem->chmod($this->drupalRoot, 0777, 0o000, TRUE); | ||
| $this->fileSystem->remove($this->drupalRoot . '/docroot'); | ||
| $this->fileSystem->remove($this->drupalRoot . '/config'); | ||
| } |
| private function resolveString(string $data): string { | ||
| $value = $this->expandString($data); | ||
| return is_string($value) ? $this->convertBooleanString($value) : $value; | ||
| } |
| public function handle(array $operation_mappings): array { | ||
| $normalized_operations = []; | ||
| foreach ($operation_mappings as $destination => $operation_config) { | ||
| if ($this->isSkippedOperation($operation_config, $destination)) { | ||
| continue; | ||
| } | ||
| $this->validateDestination($destination); | ||
| array_push( | ||
| $normalized_operations, | ||
| ...$this->normalizeSingleOperation($destination, $operation_config) | ||
| ); | ||
| } | ||
| return $normalized_operations; | ||
| } |
| private function addDrsSettings(array $operations): array { | ||
| $settings_key = "\${docroot}/sites/\${site}/settings.php"; | ||
| $settings_payload = $operations[$settings_key] ?? NULL; | ||
| assert( | ||
| array_key_exists($settings_key, $operations) && (is_string($settings_payload) || is_array($settings_payload) && !empty($settings_payload)), | ||
| "The `settings.php` file operation must be included in the operations." | ||
| ); | ||
| if (is_string($settings_payload)) { | ||
| $operations[$settings_key] = [ | ||
| "copy" => $settings_payload, | ||
| ]; | ||
| } | ||
| $this->fileSystem->dumpFile($file, $contents); | ||
| $operations[$settings_key]["append"][] = [ | ||
| "content" => PHP_EOL . $this->drsRequireLine . PHP_EOL, | ||
| ]; | ||
| $operations[$settings_key]["append"][] = [ | ||
| "content" => $this->settingsWarning . PHP_EOL, | ||
| ]; |
🚀 Major Overhaul: Flexible File Operations
This PR provides a comprehensive feature to configuration and file operation customization, focusing on flexibility, maintainability, and user empowerment.
✨ Features
Custom File Operations
🔧 Enable user-defined file operations (
copy,append,prepend) via:composer.jsonstatic operations mapping.PreSettingsFileGenerateEventfor runtime control📝 Supports advanced options: placeholder resolution, conditional overwrites, and more.
Centralized Config Resolution
🆕 Introduced
ConfigResolverclass for robust variable and placeholder resolution.Consistent Plugin Root Path
📁 Ensures
drs.rootis set in bothDefaultDrushConfigfor use as a placeholder.🛠️ Major Changes
🔒 After generating settings files, all are set to read-only (mirroring Drupal’s behavior).
🐞 Minor Fixes
salt.txtcommand is now skipped ifdrs:init:settingsthrows an exception.urioption, ensuring proper site context.\Acquia\Drupal\RecommendedSettings\Helpers\Filesystem➡️ Deprecated: Use
Acquia\Drupal\RecommendedSettings\Filesystem\Filesysteminstead.Acquia\Drupal\RecommendedSettings\Config\SettingsConfig➡️ Deprecated: Use
Acquia\Drupal\RecommendedSettings\Config\ConfigResolver.📚 Documentation & Examples
docs/how-to-use-operations.mdwith new operations format, default behaviors, and customization methods.README.mdto introduce and link to file operations customization.examples/example-drush-commandfor dynamic file operation alteration via events.Checklist: