Skip to content

ACMS-5823: Minor bugfixes, deprecate some classes and provided feature for flexible file operations.#98

Open
vishalkhode1 wants to merge 1 commit intodevelopfrom
ACMS-5823
Open

ACMS-5823: Minor bugfixes, deprecate some classes and provided feature for flexible file operations.#98
vishalkhode1 wants to merge 1 commit intodevelopfrom
ACMS-5823

Conversation

@vishalkhode1
Copy link
Contributor

@vishalkhode1 vishalkhode1 commented Mar 5, 2026

🚀 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.json static operations mapping.
    • Subscribing to the PreSettingsFileGenerateEvent for runtime control
      📝 Supports advanced options: placeholder resolution, conditional overwrites, and more.
  • Centralized Config Resolution
    🆕 Introduced ConfigResolver class for robust variable and placeholder resolution.

  • Consistent Plugin Root Path
    📁 Ensures drs.root is set in both DefaultDrushConfig for use as a placeholder.


🛠️ Major Changes

  • Secure Settings File Permissions
    🔒 After generating settings files, all are set to read-only (mirroring Drupal’s behavior).

🐞 Minor Fixes

  • salt.txt command is now skipped if drs:init:settings throws an exception.
  • Drush command initialization now correctly determines the site subdirectory from the uri option, ensuring proper site context.

⚠️ Deprecations

  • \Acquia\Drupal\RecommendedSettings\Helpers\Filesystem
    ➡️ Deprecated: Use Acquia\Drupal\RecommendedSettings\Filesystem\Filesystem instead.
  • Acquia\Drupal\RecommendedSettings\Config\SettingsConfig
    ➡️ Deprecated: Use Acquia\Drupal\RecommendedSettings\Config\ConfigResolver.

📚 Documentation & Examples

  • Updated docs/how-to-use-operations.md with new operations format, default behaviors, and customization methods.
  • Updated README.md to introduce and link to file operations customization.
  • Added an example in examples/example-drush-command for dynamic file operation alteration via events.

Checklist:

  • All new features documented
  • Deprecations clearly marked
  • Backward compatibility maintained where possible
  • Comprehensive PHPUnit test coverage for the new feature.

@rajeshreeputra
Copy link
Contributor

Can we get test coverage for filesystem/operation classes.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ConfigResolver and 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);
Comment on lines +204 to +214
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']));
}
Comment on lines +344 to +354
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,
);
Comment on lines +69 to +86
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,
);
}
}
Comment on lines +74 to +77
$path = $root . DIRECTORY_SEPARATOR . "sites" . DIRECTORY_SEPARATOR . $uri;
$file_system = new Filesystem();
$currentPerms = fileperms($path) & 0777;
$file_system->chmod($path, $currentPerms | 0222, 0o000, TRUE);
Comment on lines +284 to 292
$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);
}
}
Comment on lines 114 to 117
public function tearDown(): void {
$this->fileSystem->chmod($this->drupalRoot, 0777, 0o000, TRUE);
$this->fileSystem->remove($this->drupalRoot . '/docroot');
$this->fileSystem->remove($this->drupalRoot . '/config');
}
Comment on lines +78 to +81
private function resolveString(string $data): string {
$value = $this->expandString($data);
return is_string($value) ? $this->convertBooleanString($value) : $value;
}
Comment on lines +57 to +70
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;
}
Comment on lines +309 to +326
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,
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants