Skip to content

Commit d57a05c

Browse files
committed
[#2014] Allow to select modules in the installer.
1 parent f440455 commit d57a05c

84 files changed

Lines changed: 2749 additions & 206 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,17 @@ DRUPAL_THEME=your_site_theme
6262
DRUPAL_MAINTENANCE_THEME=your_site_theme
6363

6464
# Stage file proxy origin.
65+
#;< MODULE_SHIELD
6566
#
6667
# If using Shield, the HTTP authentication credentials will be automatically
6768
# added to the origin URL.
69+
#;> MODULE_SHIELD
6870
DRUPAL_STAGE_FILE_PROXY_ORIGIN=https://www.your-site-domain.example
6971

72+
#;< MODULE_SHIELD
7073
# Shield message.
7174
DRUPAL_SHIELD_PRINT="Restricted access."
75+
#;> MODULE_SHIELD
7276

7377
#;< SERVICE_REDIS
7478
# Enable Redis integration.

.vortex/docs/static/img/installer.json

Lines changed: 166 additions & 176 deletions
Large diffs are not rendered by default.

.vortex/docs/static/img/installer.svg

Lines changed: 1 addition & 1 deletion
Loading

.vortex/installer/composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
},
1919
"require": {
2020
"php": ">=8.3",
21-
"alexskrypnyk/file": "^0.13.1",
21+
"alexskrypnyk/file": "^0.14",
2222
"alexskrypnyk/str2name": "^1.4",
2323
"composer/composer": "^2.9.1",
2424
"cweagans/composer-patches": "^2",
@@ -88,10 +88,10 @@
8888
"lint": [
8989
"phpcs",
9090
"phpstan",
91-
"rector --clear-cache --dry-run"
91+
"rector --dry-run"
9292
],
9393
"lint-fix": [
94-
"rector --clear-cache",
94+
"rector",
9595
"phpcbf"
9696
],
9797
"reset": "rm -Rf vendor vendor-bin",

.vortex/installer/composer.lock

Lines changed: 8 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.vortex/installer/src/Prompts/Handlers/ModulePrefix.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class ModulePrefix extends AbstractHandler {
1313
* {@inheritdoc}
1414
*/
1515
public function label(): string {
16-
return 'Module prefix';
16+
return 'Custom modules prefix';
1717
}
1818

1919
/**
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\VortexInstaller\Prompts\Handlers;
6+
7+
use DrevOps\VortexInstaller\Utils\File;
8+
use DrevOps\VortexInstaller\Utils\JsonManipulator;
9+
10+
class Modules extends AbstractHandler {
11+
12+
/**
13+
* {@inheritdoc}
14+
*/
15+
public function label(): string {
16+
return 'Modules';
17+
}
18+
19+
/**
20+
* {@inheritdoc}
21+
*/
22+
public function hint(array $responses): ?string {
23+
return 'Use ⬆, ⬇ and Space bar to select one or more modules.';
24+
}
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
public function options(array $responses): ?array {
30+
return static::getAvailableModules();
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function default(array $responses): null|string|bool|array {
37+
// Default to all modules selected (meaning none will be removed).
38+
return array_keys(static::getAvailableModules());
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function discover(): null|string|bool|array {
45+
if (!$this->isInstalled()) {
46+
return NULL;
47+
}
48+
49+
$composer_file = $this->dstDir . '/composer.json';
50+
$discovered_modules = $this->getModulesFromComposerFile($composer_file);
51+
52+
if ($discovered_modules === NULL) {
53+
return NULL;
54+
}
55+
56+
// Filter discovered modules to only include those in our available list.
57+
$available_modules = array_keys(static::getAvailableModules());
58+
$modules = array_intersect($discovered_modules, $available_modules);
59+
60+
sort($modules);
61+
62+
return $modules;
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function process(): void {
69+
$selected_modules = $this->getResponseAsArray();
70+
$all_modules = static::getAvailableModules();
71+
72+
$t = $this->tmpDir;
73+
$w = $this->webroot;
74+
75+
// Process each module that was NOT selected (remove them).
76+
foreach (array_keys($all_modules) as $module_name) {
77+
if (!in_array($module_name, $selected_modules)) {
78+
// Remove from composer.json.
79+
$pattern = '/\s*"drupal\/' . preg_quote($module_name, '/') . '":\s*"[^\"]+",?\n/';
80+
File::replaceContentInFile($t . '/composer.json', $pattern, "\n");
81+
82+
// Remove module from settings file.
83+
File::remove($t . '/' . $w . '/sites/default/includes/modules/settings.' . $module_name . '.php');
84+
85+
// Remove module from the provision example file.
86+
File::replaceContentCallbackInFile($t . '/scripts/custom/provision-10-example.sh', function (string $content) use ($module_name): string {
87+
$pattern = '/^(\s*)(drush\s+pm:install.*\b' . preg_quote($module_name, '/') . '\b.*)$/m';
88+
$content = preg_replace_callback($pattern, function (array $matches) use ($module_name): string {
89+
$indent = $matches[1];
90+
$line = $matches[2];
91+
$line = preg_replace('/\s+\b' . preg_quote($module_name, '/') . '\b/', '', $line);
92+
$line = preg_replace('/\s{2,}/', ' ', $line) ?? $line;
93+
return $indent . $line;
94+
}, $content);
95+
return $content ?? '';
96+
});
97+
98+
// Remove module from the Behat tests.
99+
File::remove($t . '/tests/behat/features/' . $module_name . '.feature');
100+
101+
// Remove module from the config tests.
102+
$pattern = '/\s*\$config\[\'' . preg_quote($module_name, '/') . '\..*;(\r?\n)?/';
103+
File::removeLine($t . '/tests/phpunit/Drupal/EnvironmentSettingsTest.php', $pattern);
104+
105+
// Remove module tokens.
106+
File::removeTokenAsync('MODULE_' . strtoupper($module_name));
107+
}
108+
}
109+
110+
if (count($selected_modules) === 0) {
111+
File::removeTokenAsync('MODULE');
112+
}
113+
}
114+
115+
/**
116+
* Get the full list of available Drupal contributed modules.
117+
*
118+
* This list excludes Drupal core modules and service modules.
119+
*
120+
* @return array<string, string>
121+
* Array of module machine names as keys and labels as values.
122+
*/
123+
public static function getAvailableModules(): array {
124+
return [
125+
'admin_toolbar' => 'Admin toolbar',
126+
'coffee' => 'Coffee',
127+
'config_split' => 'Config split',
128+
'config_update' => 'Config update',
129+
'environment_indicator' => 'Environment indicator',
130+
'pathauto' => 'Pathauto',
131+
'redirect' => 'Redirect',
132+
'robotstxt' => 'Robots.txt',
133+
'seckit' => 'Seckit',
134+
'shield' => 'Shield',
135+
'stage_file_proxy' => 'Stage file proxy',
136+
];
137+
}
138+
139+
/**
140+
* Extract Drupal contributed module names from a composer.json file.
141+
*
142+
* @param string $composer_file
143+
* Path to the composer.json file.
144+
*
145+
* @return array|null
146+
* Array of module machine names (without drupal/ prefix), or NULL on error.
147+
*/
148+
protected function getModulesFromComposerFile(string $composer_file): ?array {
149+
if (!file_exists($composer_file)) {
150+
return NULL;
151+
}
152+
153+
$cj = JsonManipulator::fromFile($composer_file);
154+
155+
if (!$cj instanceof JsonManipulator) {
156+
return NULL;
157+
}
158+
159+
$require = $cj->getProperty('require');
160+
161+
if (!is_array($require)) {
162+
return NULL;
163+
}
164+
165+
$modules = [];
166+
foreach (array_keys($require) as $package) {
167+
// Only include drupal/* packages, excluding core packages.
168+
if (str_starts_with((string) $package, 'drupal/') && !str_starts_with((string) $package, 'drupal/core-')) {
169+
// Extract module name (remove drupal/ prefix).
170+
$module_name = substr((string) $package, 7);
171+
$modules[] = $module_name;
172+
}
173+
}
174+
175+
return $modules;
176+
}
177+
178+
}

.vortex/installer/src/Prompts/PromptManager.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use DrevOps\VortexInstaller\Prompts\Handlers\LabelMergeConflictsPr;
2323
use DrevOps\VortexInstaller\Prompts\Handlers\MachineName;
2424
use DrevOps\VortexInstaller\Prompts\Handlers\ModulePrefix;
25+
use DrevOps\VortexInstaller\Prompts\Handlers\Modules;
2526
use DrevOps\VortexInstaller\Prompts\Handlers\Name;
2627
use DrevOps\VortexInstaller\Prompts\Handlers\NotificationChannels;
2728
use DrevOps\VortexInstaller\Prompts\Handlers\Org;
@@ -64,7 +65,7 @@ class PromptManager {
6465
*
6566
* Used to display the progress of the prompts.
6667
*/
67-
const TOTAL_RESPONSES = 27;
68+
const TOTAL_RESPONSES = 28;
6869

6970
/**
7071
* Array of responses.
@@ -135,6 +136,7 @@ public function runPrompts(): void {
135136
fn($r, $pr, $n): string => text(...$this->args(ProfileCustom::class)),
136137
ProfileCustom::id()
137138
)
139+
->add(fn(array $r, $pr, $n): array => multiselect(...$this->args(Modules::class, NULL, $r)), Modules::id())
138140
->add(fn(array $r, $pr, $n): string => text(...$this->args(ModulePrefix::class, NULL, $r)), ModulePrefix::id())
139141
->add(
140142
fn(array $r, $pr, $n): string => $this->resolveOrPrompt(Theme::id(), $r, fn(): int|string => select(...$this->args(Theme::class))),
@@ -277,6 +279,7 @@ public function runProcessors(): void {
277279
Services::id(),
278280
Timezone::id(),
279281
CodeProvider::id(),
282+
Modules::id(),
280283
Starter::id(),
281284
ProfileCustom::id(),
282285
Profile::id(),
@@ -368,6 +371,7 @@ public function getResponsesSummary(): array {
368371

369372
$values['Drupal'] = Tui::LIST_SECTION_TITLE;
370373
$values['Starter'] = $responses[Starter::id()];
374+
$values['Modules'] = Converter::toList($responses[Modules::id()], ', ');
371375
$values['Webroot'] = $responses[Webroot::id()];
372376
$values['Profile'] = $responses[Profile::id()];
373377
$values['Module prefix'] = $responses[ModulePrefix::id()];
@@ -543,7 +547,7 @@ private function args(string $handler_class, mixed $default_override = NULL, arr
543547
}
544548

545549
$options = $handler->options($responses);
546-
if (is_array($options) && $options !== []) {
550+
if (is_array($options)) {
547551
$args['options'] = $options;
548552
$args['scroll'] = 10;
549553
}

.vortex/installer/src/Utils/JsonManipulator.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@ public static function fromFile(string $composer_json): ?self {
2929
// @codeCoverageIgnoreEnd
3030
}
3131

32-
return new self($contents);
32+
try {
33+
$instance = new self($contents);
34+
}
35+
catch (\Exception) {
36+
// Invalid JSON.
37+
return NULL;
38+
}
39+
40+
return $instance;
3341
}
3442

3543
/**

.vortex/installer/tests/Fixtures/install/_baseline/scripts/custom/provision-10-example.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,9 @@ note "Environment: ${environment}"
3434
if echo "${environment}" | grep -q -e dev -e stage -e ci -e local; then
3535
note "Running example operations in non-production environment."
3636

37-
# Set site name.
3837
task "Setting site name."
3938
drush php:eval "\Drupal::service('config.factory')->getEditable('system.site')->set('name', 'star wars')->save();"
4039

41-
# Enable contrib modules.
4240
task "Installing contrib modules."
4341
drush pm:install admin_toolbar coffee config_split config_update media environment_indicator pathauto redirect robotstxt shield stage_file_proxy
4442

0 commit comments

Comments
 (0)