Skip to content

Commit fd0f825

Browse files
authored
Added '--prompts' CLI option to separate prompt answers from installer configuration. (#2435)
1 parent b797726 commit fd0f825

43 files changed

Lines changed: 632 additions & 639 deletions

Some content is hidden

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

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
use DrevOps\VortexInstaller\Runner\ExecutableFinderAwareTrait;
1717
use DrevOps\VortexInstaller\Runner\RunnerInterface;
1818
use DrevOps\VortexInstaller\Schema\AgentHelp;
19-
use DrevOps\VortexInstaller\Schema\ConfigValidator;
2019
use DrevOps\VortexInstaller\Schema\SchemaGenerator;
20+
use DrevOps\VortexInstaller\Schema\SchemaValidator;
2121
use DrevOps\VortexInstaller\Task\Task;
2222
use DrevOps\VortexInstaller\Utils\Config;
2323
use DrevOps\VortexInstaller\Utils\Env;
@@ -62,6 +62,8 @@ class InstallCommand extends Command implements CommandRunnerAwareInterface, Exe
6262

6363
const OPTION_VALIDATE = 'validate';
6464

65+
const OPTION_PROMPTS = 'prompts';
66+
6567
const OPTION_AGENT_HELP = 'agent-help';
6668

6769
/**
@@ -142,6 +144,7 @@ protected function configure(): void {
142144
$this->addOption(static::OPTION_URI, 'l', InputOption::VALUE_REQUIRED, 'Remote or local repository URI with an optional git ref set after @.');
143145
$this->addOption(static::OPTION_NO_CLEANUP, NULL, InputOption::VALUE_NONE, 'Do not remove installer after successful installation.');
144146
$this->addOption(static::OPTION_BUILD, 'b', InputOption::VALUE_NONE, 'Run auto-build after installation without prompting.');
147+
$this->addOption(static::OPTION_PROMPTS, 'p', InputOption::VALUE_REQUIRED, 'A JSON string with prompt answers or a path to a JSON file. Keys are prompt IDs from --schema.');
145148
$this->addOption(static::OPTION_SCHEMA, NULL, InputOption::VALUE_NONE, 'Output prompt schema as JSON.');
146149
$this->addOption(static::OPTION_VALIDATE, NULL, InputOption::VALUE_NONE, 'Validate config without installing.');
147150
$this->addOption(static::OPTION_AGENT_HELP, NULL, InputOption::VALUE_NONE, 'Output instructions for AI agents on how to use the installer.');
@@ -306,8 +309,8 @@ protected function handleSchema(InputInterface $input, OutputInterface $output):
306309
$config = Config::fromString('{}');
307310
$prompt_manager = new PromptManager($config);
308311

309-
$generator = new SchemaGenerator();
310-
$schema = $generator->generate($prompt_manager->getHandlers());
312+
$generator = new SchemaGenerator($prompt_manager->getHandlers());
313+
$schema = $generator->generate();
311314

312315
$output->write((string) json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
313316

@@ -318,28 +321,30 @@ protected function handleSchema(InputInterface $input, OutputInterface $output):
318321
* Handle --validate option.
319322
*/
320323
protected function handleValidate(InputInterface $input, OutputInterface $output): int {
321-
$config_option = $input->getOption(static::OPTION_CONFIG);
324+
$prompts_option = $input->getOption(static::OPTION_PROMPTS);
322325

323-
if (empty($config_option) || !is_string($config_option)) {
324-
$output->writeln('The --validate option requires --config.');
326+
if (empty($prompts_option) || !is_string($prompts_option)) {
327+
$output->writeln('The --validate option requires --prompts.');
325328

326329
return Command::FAILURE;
327330
}
328331

329-
$config_json = is_file($config_option) ? (string) file_get_contents($config_option) : $config_option;
330-
$user_config = json_decode($config_json, TRUE);
332+
$prompts_json = is_file($prompts_option) ? (string) file_get_contents($prompts_option) : $prompts_option;
333+
$decoded = json_decode($prompts_json);
331334

332-
if (!is_array($user_config)) {
333-
$output->writeln('Invalid JSON in --config.');
335+
if (!$decoded instanceof \stdClass) {
336+
$output->writeln('Invalid JSON in --prompts. Expected a JSON object.');
334337

335338
return Command::FAILURE;
336339
}
337340

341+
$user_config = json_decode($prompts_json, TRUE);
342+
338343
$config = Config::fromString('{}');
339344
$prompt_manager = new PromptManager($config);
340345

341-
$validator = new ConfigValidator();
342-
$result = $validator->validate($user_config, $prompt_manager->getHandlers());
346+
$validator = new SchemaValidator($prompt_manager->getHandlers());
347+
$result = $validator->validate($user_config);
343348

344349
$output->write((string) json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
345350

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@
4343
use DrevOps\VortexInstaller\Prompts\Handlers\Tools;
4444
use DrevOps\VortexInstaller\Prompts\Handlers\VersionScheme;
4545
use DrevOps\VortexInstaller\Prompts\Handlers\Webroot;
46+
use DrevOps\VortexInstaller\Schema\SchemaValidator;
4647
use DrevOps\VortexInstaller\Utils\Config;
4748
use DrevOps\VortexInstaller\Utils\Converter;
48-
use DrevOps\VortexInstaller\Utils\Env;
4949
use DrevOps\VortexInstaller\Utils\Tui;
5050
use Symfony\Component\Console\Output\OutputInterface;
5151
use function Laravel\Prompts\confirm;
@@ -87,6 +87,15 @@ class PromptManager {
8787
*/
8888
protected array $handlers = [];
8989

90+
/**
91+
* Prompt overrides from --prompts CLI option.
92+
*
93+
* Keyed by handler ID with validated values.
94+
*
95+
* @var array<string, mixed>
96+
*/
97+
protected array $promptOverrides = [];
98+
9099
/**
91100
* PromptManager constructor.
92101
*
@@ -97,6 +106,7 @@ public function __construct(
97106
protected Config $config,
98107
) {
99108
$this->initHandlers();
109+
$this->resolvePromptOverrides();
100110
}
101111

102112
/**
@@ -577,6 +587,39 @@ protected function initHandlers(): void {
577587
}
578588
}
579589

590+
/**
591+
* Resolve prompt overrides from --prompts CLI option.
592+
*
593+
* Reads the raw prompt array from Config, normalizes keys to handler IDs,
594+
* validates values against handler types and options, and stores the
595+
* validated overrides.
596+
*
597+
* @throws \RuntimeException
598+
* If any prompt value is invalid.
599+
*/
600+
private function resolvePromptOverrides(): void {
601+
$raw = $this->config->get(Config::PROMPTS);
602+
603+
if (!is_array($raw) || empty($raw)) {
604+
return;
605+
}
606+
607+
$validator = new SchemaValidator($this->handlers);
608+
$result = $validator->validate($raw);
609+
610+
if (!empty($result['errors'])) {
611+
$messages = array_map(fn(array $error): string => sprintf('%s: %s', $error['prompt'], $error['message']), $result['errors']);
612+
throw new \RuntimeException(sprintf('Invalid --prompts values: %s', implode('; ', $messages)));
613+
}
614+
615+
// Use the resolved values which include defaults for missing prompts.
616+
foreach ($raw as $key => $value) {
617+
if (isset($this->handlers[$key])) {
618+
$this->promptOverrides[$key] = $value;
619+
}
620+
}
621+
}
622+
580623
/**
581624
* Dispatch a prompt using the handler's type enum.
582625
*
@@ -646,22 +689,13 @@ private function args(string $handler_class, mixed $default_override = NULL, arr
646689

647690
// Find appropriate default value.
648691
$default_from_handler = $handler->default($responses);
649-
// Create the env var name.
650-
$var_name = $handler::envName();
651-
// Get from config.
652-
$config_val = $this->config->get($var_name);
653-
$default_from_config = is_null($config_val) ? NULL : $config_val;
654-
// Get from env.
655-
$env_val = Env::get($var_name);
656-
$default_from_env = is_null($env_val) ? NULL : Env::toValue($env_val);
692+
// Get from prompt overrides (--prompts CLI option).
693+
$default_from_prompts = $this->promptOverrides[$id] ?? NULL;
657694
// Get from discovery.
658695
$default_from_discovery = $this->handlers[$id]->discover();
659696

660-
if (!is_null($default_from_config)) {
661-
$default = $default_from_config;
662-
}
663-
elseif (!is_null($default_from_env)) {
664-
$default = $default_from_env;
697+
if (!is_null($default_from_prompts)) {
698+
$default = $default_from_prompts;
665699
}
666700
elseif (!is_null($default_from_discovery)) {
667701
$default = $default_from_discovery;

.vortex/installer/src/Schema/AgentHelp.php

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,40 +31,48 @@ public static function render(): string {
3131
available configuration prompts, their types, valid values, defaults, and
3232
dependencies.
3333
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.
34+
2. **Build prompt answers**: Using the schema, construct a JSON object where
35+
keys are prompt IDs (the `id` field from the schema). Set values according
36+
to the prompt types and allowed options.
3837
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.
38+
3. **Validate**: Run with `--validate --prompts='<json>'` to check your prompt
39+
answers without performing an installation. The output is a JSON object with
40+
`valid`, `errors`, `warnings`, and `resolved` fields.
4241
43-
4. **Install**: Run with `--no-interaction --config='<json>' --destination=<dir>`
44-
to perform the actual installation using your validated config.
42+
4. **Install**: Run with `--no-interaction --prompts='<json>' --destination=<dir>`
43+
to perform the actual installation using your validated prompt answers.
4544
4645
## Commands
4746
4847
```bash
4948
# Get the prompt schema
5049
php installer.php --schema
5150
52-
# Validate a config (JSON string)
53-
php installer.php --validate --config='{"name":"My Project","hosting_provider":"lagoon"}'
51+
# Validate prompt answers (JSON string)
52+
php installer.php --validate --prompts='{"name":"My Project","hosting_provider":"lagoon"}'
5453
55-
# Validate a config (JSON file)
56-
php installer.php --validate --config=config.json
54+
# Validate prompt answers (JSON file)
55+
php installer.php --validate --prompts=prompts.json
5756
5857
# Install non-interactively
59-
php installer.php --no-interaction --config='<json>' --destination=./my-project
58+
php installer.php --no-interaction --prompts='<json>' --destination=./my-project
6059
```
6160
61+
## Options
62+
63+
- `--prompts` (`-p`): JSON object of prompt answers, keyed by prompt ID.
64+
Accepts a JSON string or a path to a JSON file.
65+
- `--config` (`-c`): JSON object of installer configuration (repository, ref,
66+
and other internal settings). Not for prompt answers.
67+
- `--destination`: Target directory for installation.
68+
- `--no-interaction` (`-n`): Non-interactive mode. Prompts without answers in
69+
`--prompts` use discovered or default values.
70+
6271
## Schema Format
6372
6473
The `--schema` output contains a `prompts` array. Each prompt has:
6574
66-
- `id`: The prompt identifier (use as config key).
67-
- `env`: The environment variable name (alternative config key).
75+
- `id`: The prompt identifier (use as key in `--prompts` JSON).
6876
- `type`: One of `text`, `select`, `multiselect`, `confirm`, `suggest`.
6977
- `label`: Human-readable label.
7078
- `description`: Optional description text.
@@ -80,7 +88,7 @@ public static function render(): string {
8088
8189
- `text` / `suggest`: string value.
8290
- `select`: string value matching one of the option values.
83-
- `multiselect`: array of strings, each matching an option value.
91+
- `multiselect`: JSON array of strings, each matching an option value.
8492
- `confirm`: boolean (`true` or `false`).
8593
8694
## Dependencies
@@ -110,7 +118,7 @@ public static function render(): string {
110118
- Start with `--schema` to understand what prompts exist.
111119
- Provide values only for prompts you want to customize; defaults will be
112120
used for the rest.
113-
- Use `--validate` to check your config before installing.
121+
- Use `--validate` to check your prompt answers before installing.
114122
- The `resolved` field in validation output shows the complete config that
115123
would be used, including defaults.
116124
AGENT_HELP;

.vortex/installer/src/Schema/SchemaGenerator.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,32 @@
1616
class SchemaGenerator {
1717

1818
/**
19-
* Generate schema from handlers.
19+
* Constructor.
2020
*
2121
* @param array<string, \DrevOps\VortexInstaller\Prompts\Handlers\HandlerInterface> $handlers
2222
* An associative array of handler instances keyed by handler ID.
23+
*/
24+
public function __construct(
25+
protected array $handlers,
26+
) {
27+
}
28+
29+
/**
30+
* Generate schema from handlers.
2331
*
2432
* @return array<string, mixed>
2533
* The schema structure with a 'prompts' key.
2634
*/
27-
public function generate(array $handlers): array {
35+
public function generate(): array {
2836
$prompts = [];
2937

30-
foreach ($handlers as $id => $handler) {
38+
foreach ($this->handlers as $id => $handler) {
3139
if (in_array($id, static::getExcludedHandlers(), TRUE)) {
3240
continue;
3341
}
3442

3543
$prompts[] = [
3644
'id' => $handler::id(),
37-
'env' => $handler::envName(),
3845
'type' => $handler->type()->value,
3946
'label' => $handler->label(),
4047
'description' => $handler::description([]),

0 commit comments

Comments
 (0)