Skip to content

Commit 2183229

Browse files
authored
Moved several methods out of the installer command class. (#2308)
1 parent f2e93fc commit 2183229

10 files changed

Lines changed: 1702 additions & 570 deletions

File tree

.vortex/installer/src/Command/InstallCommand.php

Lines changed: 28 additions & 570 deletions
Large diffs are not rendered by default.
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\VortexInstaller\Prompts;
6+
7+
use DrevOps\VortexInstaller\Downloader\Artifact;
8+
use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
9+
use DrevOps\VortexInstaller\Utils\Config;
10+
use DrevOps\VortexInstaller\Utils\Strings;
11+
use DrevOps\VortexInstaller\Utils\Tui;
12+
13+
/**
14+
* Presents installer headers, footers, and post-build messages.
15+
*
16+
* @package DrevOps\VortexInstaller\Prompts
17+
*/
18+
class InstallerPresenter {
19+
20+
const BUILD_RESULT_SUCCESS = 'success';
21+
22+
const BUILD_RESULT_SKIPPED = 'skipped';
23+
24+
const BUILD_RESULT_FAILED = 'failed';
25+
26+
/**
27+
* The prompt manager.
28+
*/
29+
protected ?PromptManager $promptManager = NULL;
30+
31+
public function __construct(
32+
protected Config $config,
33+
) {}
34+
35+
public function setPromptManager(PromptManager $prompt_manager): void {
36+
$this->promptManager = $prompt_manager;
37+
}
38+
39+
public function header(Artifact $artifact, string $version): void {
40+
$logo_large = <<<EOT
41+
42+
██╗ ██╗ ██████╗ ██████╗ ████████╗ ███████╗ ██╗ ██╗
43+
██║ ██║ ██╔═══██╗ ██╔══██╗ ╚══██╔══╝ ██╔════╝ ╚██╗██╔╝
44+
██║ ██║ ██║ ██║ ██████╔╝ ██║ █████╗ ╚███╔╝
45+
╚██╗ ██╔╝ ██║ ██║ ██╔══██╗ ██║ ██╔══╝ ██╔██╗
46+
╚████╔╝ ╚██████╔╝ ██║ ██║ ██║ ███████╗ ██╔╝ ██╗
47+
╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝
48+
49+
Drupal project template
50+
51+
by DrevOps
52+
EOT;
53+
54+
$logo_small = <<<EOT
55+
▗▖ ▗▖ ▗▄▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▖ ▗▖
56+
▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ █ ▐▌ ▝▚▞▘
57+
▐▌ ▐▌▐▌ ▐▌▐▛▀▚▖ █ ▐▛▀▀▘ ▐▌
58+
▝▚▞▘ ▝▚▄▞▘▐▌ ▐▌ █ ▐▙▄▄▖▗▞▘▝▚▖
59+
60+
Drupal project template
61+
62+
by DrevOps
63+
EOT;
64+
65+
$max_header_width = 200;
66+
67+
$logo = Tui::terminalWidth() >= 80 ? $logo_large : $logo_small;
68+
$logo = Tui::center($logo, Tui::terminalWidth($max_header_width), '');
69+
$logo = Tui::cyan($logo);
70+
71+
// Depending on how the installer is run, the version may be set to
72+
// the placeholder value or actual version (PHAR packager will replace
73+
// the placeholder with the actual version).
74+
// We need to fence the replacement below only if the version is still set
75+
// to the placeholder value.
76+
if (str_contains($version, 'vortex-installer-version')) {
77+
$version = str_replace('@vortex-installer-version@', 'development', $version);
78+
}
79+
80+
$logo .= PHP_EOL . Tui::dim(str_pad(sprintf('Installer version: %s', $version), Tui::terminalWidth($max_header_width) - 2, ' ', STR_PAD_LEFT));
81+
82+
Tui::note($logo);
83+
84+
$title = 'Welcome to the Vortex interactive installer';
85+
$content = '';
86+
87+
if ($artifact->isStable()) {
88+
$content .= 'This tool will guide you through installing the latest ' . Tui::underscore('stable') . ' version of Vortex into your project.' . PHP_EOL;
89+
}
90+
elseif ($artifact->isDevelopment()) {
91+
$content .= 'This tool will guide you through installing the latest ' . Tui::underscore('development') . ' version of Vortex into your project.' . PHP_EOL;
92+
}
93+
else {
94+
$content .= sprintf('This tool will guide you through installing a ' . Tui::underscore('custom') . ' version of Vortex into your project at commit "%s".', $artifact->getRef()) . PHP_EOL;
95+
}
96+
97+
$content .= PHP_EOL;
98+
99+
if ($this->config->isVortexProject()) {
100+
$content .= 'It looks like Vortex is already installed into this project.' . PHP_EOL;
101+
$content .= PHP_EOL;
102+
}
103+
104+
if ($this->config->getNoInteraction()) {
105+
$content .= 'Vortex installer will try to discover the settings from the environment and will install configuration relevant to your site.' . PHP_EOL;
106+
$content .= PHP_EOL;
107+
$content .= 'Existing committed files may be modified. You may need to resolve some of the changes manually.' . PHP_EOL;
108+
109+
$title = 'Welcome to the Vortex non-interactive installer';
110+
}
111+
else {
112+
$content .= 'You will be asked a few questions to tailor the configuration to your site.' . PHP_EOL;
113+
$content .= 'No changes will be made until you confirm everything at the end.' . PHP_EOL;
114+
$content .= PHP_EOL;
115+
116+
if ($this->config->isVortexProject()) {
117+
$content .= 'If you proceed, some committed files may be modified after confirmation, and you may need to resolve some of the changes manually.' . PHP_EOL;
118+
$content .= PHP_EOL;
119+
}
120+
121+
$content .= 'Press ' . Tui::yellow('Ctrl+C') . ' at any time to exit the installer.' . PHP_EOL;
122+
$content .= 'Press ' . Tui::yellow('Ctrl+U') . ' at any time to go back to the previous step.' . PHP_EOL;
123+
}
124+
125+
Tui::box($content, $title);
126+
}
127+
128+
public function footer(): void {
129+
$output = '';
130+
$prefix = ' ';
131+
132+
if ($this->config->isVortexProject()) {
133+
$title = 'Finished updating Vortex';
134+
$output .= 'Please review the changes and commit the required files.';
135+
}
136+
else {
137+
$title = 'Finished installing Vortex';
138+
139+
// Check for required tools and provide conditional instructions.
140+
$missing_tools = $this->checkRequiredTools();
141+
if (!empty($missing_tools)) {
142+
$tools_output = 'Install required tools:' . PHP_EOL;
143+
foreach ($missing_tools as $tool => $instructions) {
144+
$tools_output .= sprintf(' %s: %s', $tool, $instructions) . PHP_EOL;
145+
}
146+
$output .= Strings::wrapLines($tools_output, $prefix);
147+
$output .= PHP_EOL;
148+
}
149+
150+
$output .= 'Add and commit all files:' . PHP_EOL;
151+
$output .= $prefix . 'git add -A' . PHP_EOL;
152+
$output .= $prefix . 'git commit -m "Initial commit."' . PHP_EOL;
153+
}
154+
155+
Tui::box($output, $title);
156+
}
157+
158+
/**
159+
* Display footer after build succeeded.
160+
*/
161+
public function footerBuildSucceeded(): void {
162+
$output = '';
163+
164+
$output .= 'Get site info: ahoy info' . PHP_EOL;
165+
$output .= 'Login: ahoy login' . PHP_EOL;
166+
$output .= PHP_EOL;
167+
168+
$handler_output = $this->promptManager->runPostBuild(self::BUILD_RESULT_SUCCESS);
169+
if (!empty($handler_output)) {
170+
$output .= $handler_output;
171+
}
172+
173+
Tui::box($output, 'Site is ready');
174+
}
175+
176+
/**
177+
* Display footer after build was skipped.
178+
*/
179+
public function footerBuildSkipped(): void {
180+
$output = '';
181+
$prefix = ' ';
182+
183+
$responses = $this->promptManager->getResponses();
184+
$starter = $responses[Starter::id()] ?? Starter::LOAD_DATABASE_DEMO;
185+
$is_profile = in_array($starter, [Starter::INSTALL_PROFILE_CORE, Starter::INSTALL_PROFILE_DRUPALCMS], TRUE);
186+
187+
$output .= 'Build the site:' . PHP_EOL;
188+
if ($is_profile) {
189+
$output .= $prefix . 'VORTEX_PROVISION_TYPE=profile ahoy build' . PHP_EOL;
190+
}
191+
else {
192+
$output .= $prefix . 'ahoy build' . PHP_EOL;
193+
}
194+
$output .= PHP_EOL;
195+
196+
if ($is_profile) {
197+
$output .= 'Export database after build:' . PHP_EOL;
198+
$output .= $prefix . 'ahoy export-db db.sql' . PHP_EOL;
199+
$output .= PHP_EOL;
200+
}
201+
202+
$handler_output = $this->promptManager->runPostBuild(self::BUILD_RESULT_SKIPPED);
203+
if (!empty($handler_output)) {
204+
$output .= $handler_output;
205+
}
206+
207+
Tui::box($output, 'Ready to build');
208+
}
209+
210+
/**
211+
* Display footer after build failed.
212+
*/
213+
public function footerBuildFailed(): void {
214+
$output = '';
215+
$prefix = ' ';
216+
217+
$output .= 'Vortex was installed, but the build process failed.' . PHP_EOL;
218+
$output .= PHP_EOL;
219+
$output .= 'Troubleshooting:' . PHP_EOL;
220+
$output .= $prefix . 'Check logs:' . $prefix . $prefix . 'ahoy logs' . PHP_EOL;
221+
$output .= $prefix . 'Retry build:' . $prefix . 'ahoy build' . PHP_EOL;
222+
$output .= $prefix . 'Diagnostics:' . $prefix . 'ahoy doctor' . PHP_EOL;
223+
$output .= PHP_EOL;
224+
225+
$handler_output = $this->promptManager->runPostBuild(self::BUILD_RESULT_FAILED);
226+
if (!empty($handler_output)) {
227+
$output .= $handler_output;
228+
}
229+
230+
Tui::box($output, 'Build encountered errors');
231+
}
232+
233+
/**
234+
* Check for required development tools.
235+
*
236+
* @return array<string, string>
237+
* Array of missing tools with installation instructions.
238+
*/
239+
protected function checkRequiredTools(): array {
240+
$tools = [
241+
'docker' => [
242+
'name' => 'Docker',
243+
'command' => 'docker',
244+
'instructions' => 'https://www.docker.com/get-started',
245+
],
246+
'pygmy' => [
247+
'name' => 'Pygmy',
248+
'command' => 'pygmy',
249+
'instructions' => 'https://github.com/pygmystack/pygmy',
250+
],
251+
'ahoy' => [
252+
'name' => 'Ahoy',
253+
'command' => 'ahoy',
254+
'instructions' => 'https://github.com/ahoy-cli/ahoy',
255+
],
256+
];
257+
258+
$missing = [];
259+
260+
foreach ($tools as $tool) {
261+
// Use exec with output capture to avoid output to console.
262+
$output = [];
263+
$return_code = 0;
264+
exec(sprintf('command -v %s 2>/dev/null', $tool['command']), $output, $return_code);
265+
266+
if ($return_code !== 0) {
267+
$missing[$tool['name']] = $tool['instructions'];
268+
}
269+
}
270+
271+
return $missing;
272+
}
273+
274+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DrevOps\VortexInstaller\Schema;
6+
7+
/**
8+
* Renders AI agent instructions for the installer.
9+
*
10+
* @package DrevOps\VortexInstaller\Schema
11+
*/
12+
class AgentHelp {
13+
14+
/**
15+
* Render agent help text.
16+
*
17+
* @return string
18+
* The agent help text.
19+
*/
20+
public static function render(): string {
21+
return <<<'AGENT_HELP'
22+
# Vortex Installer - AI Agent Instructions
23+
24+
You are interacting with the Vortex installer, a CLI tool that sets up Drupal
25+
projects from the Vortex template. This guide explains how to use the installer
26+
programmatically.
27+
28+
## Workflow
29+
30+
1. **Discover prompts**: Run with `--schema` to get a JSON manifest of all
31+
available configuration prompts, their types, valid values, defaults, and
32+
dependencies.
33+
34+
2. **Build a config**: Using the schema, construct a JSON object where keys are
35+
either prompt IDs (e.g., `hosting_provider`) or environment variable names
36+
(e.g., `VORTEX_INSTALLER_PROMPT_HOSTING_PROVIDER`). Set values according to
37+
the prompt types and allowed options from the schema.
38+
39+
3. **Validate the config**: Run with `--validate --config='<json>'` to check
40+
your config without performing an installation. The output is a JSON object
41+
with `valid`, `errors`, `warnings`, and `resolved` fields.
42+
43+
4. **Install**: Run with `--no-interaction --config='<json>' --destination=<dir>`
44+
to perform the actual installation using your validated config.
45+
46+
## Commands
47+
48+
```bash
49+
# Get the prompt schema
50+
php installer.php --schema
51+
52+
# Validate a config (JSON string)
53+
php installer.php --validate --config='{"name":"My Project","hosting_provider":"lagoon"}'
54+
55+
# Validate a config (JSON file)
56+
php installer.php --validate --config=config.json
57+
58+
# Install non-interactively
59+
php installer.php --no-interaction --config='<json>' --destination=./my-project
60+
```
61+
62+
## Schema Format
63+
64+
The `--schema` output contains a `prompts` array. Each prompt has:
65+
66+
- `id`: The prompt identifier (use as config key).
67+
- `env`: The environment variable name (alternative config key).
68+
- `type`: One of `text`, `select`, `multiselect`, `confirm`, `suggest`.
69+
- `label`: Human-readable label.
70+
- `description`: Optional description text.
71+
- `options`: For `select`/`multiselect`, an array of `{value, label}` objects
72+
representing the allowed values.
73+
- `default`: The default value if not provided.
74+
- `required`: Whether the prompt requires a value.
75+
- `depends_on`: Dependency conditions. If set, this prompt only applies when
76+
the referenced prompt has one of the specified values. A `_system` key
77+
indicates a system-state dependency (not config-based).
78+
79+
## Value Types by Prompt Type
80+
81+
- `text` / `suggest`: string value.
82+
- `select`: string value matching one of the option values.
83+
- `multiselect`: array of strings, each matching an option value.
84+
- `confirm`: boolean (`true` or `false`).
85+
86+
## Dependencies
87+
88+
Some prompts depend on other prompts. For example, `hosting_project_name`
89+
depends on `hosting_provider` being `lagoon` or `acquia`. If you set
90+
`hosting_provider` to `none`, you do not need to provide `hosting_project_name`.
91+
92+
When a dependency is not met:
93+
- Omitting the dependent value is OK (it will be skipped).
94+
- Providing a value triggers a warning (it will be ignored).
95+
96+
When a dependency is met:
97+
- Required prompts must have a value or they produce an error.
98+
99+
## Validation Output
100+
101+
The `--validate` output contains:
102+
103+
- `valid`: boolean - whether the config is valid.
104+
- `errors`: array of `{prompt, message}` objects for invalid values.
105+
- `warnings`: array of `{prompt, message}` objects for ignored values.
106+
- `resolved`: object with the final merged config (your values + defaults).
107+
108+
## Tips
109+
110+
- Start with `--schema` to understand what prompts exist.
111+
- Provide values only for prompts you want to customize; defaults will be
112+
used for the rest.
113+
- Use `--validate` to check your config before installing.
114+
- The `resolved` field in validation output shows the complete config that
115+
would be used, including defaults.
116+
AGENT_HELP;
117+
}
118+
119+
}

0 commit comments

Comments
 (0)