Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .vortex/docs/content/updating-vortex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Specifically check if any environment variables were added or changed.
It may be a good idea to commit changes in smaller chunks to make it easier
to review and revert if necessary.

Where the update replaced a file your project had changed, `.logs/vortex-update.md` records the replaced change alongside the change the new version brings. Work through it to re-apply your changes, then delete the entries you have resolved. The file is not tracked in Git.

Your `composer.json` has most likely deviated from the **Vortex** version,
so review the package version changes carefully. We recommend temporarily
reverting the changes to `composer.json` and `composer.lock` to preserve
Expand Down
10 changes: 8 additions & 2 deletions .vortex/installer/src/Command/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$version = $this->getRepositoryDownloader()->download($this->artifact, $this->config->get(Config::TMP), $release_prefix);
$this->config->set(Config::VERSION, $version);
$this->fileManager->snapshotTemplate();
$this->fileManager->snapshotPreviousTemplate($this->getRepositoryDownloader(), $this->artifact);
$this->fileManager->snapshotPreviousTemplate(
$this->getRepositoryDownloader(),
$this->artifact,
function (string $dir, string $ref): void {
$this->promptManager->renderAsInstalled($dir, $ref);
},
);
return $version;
},
hint: fn(): string => sprintf('Downloading from "%s" repository at ref "%s"', $this->artifact->getRepo(), $this->artifact->getRef()),
Expand Down Expand Up @@ -262,7 +268,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::FAILURE;
}

$this->presenter->footer();
$this->presenter->footer($this->fileManager->getRegistryFile());

$should_build = TRUE;
$requested_build = (bool) $this->config->get(Config::BUILD_NOW);
Expand Down
16 changes: 15 additions & 1 deletion .vortex/installer/src/Prompts/InstallerPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DrevOps\VortexInstaller\Downloader\Artifact;
use DrevOps\VortexInstaller\Prompts\Handlers\Starter;
use DrevOps\VortexInstaller\Utils\Config;
use DrevOps\VortexInstaller\Utils\File;
use DrevOps\VortexInstaller\Utils\Strings;
use DrevOps\VortexInstaller\Utils\Tui;
use Symfony\Component\Process\ExecutableFinder;
Expand Down Expand Up @@ -122,13 +123,26 @@ public function header(Artifact $artifact, string $version): void {
Tui::box($content, $title);
}

public function footer(): void {
/**
* Display the footer after the installation finished.
*
* @param string|null $registry_file
* Path of the registry of project changes the update replaced, or NULL
* when the update replaced none.
*/
public function footer(?string $registry_file = NULL): void {
$output = '';
$prefix = ' ';

if ($this->config->isVortexProject()) {
$title = 'Finished updating Vortex';
$output .= 'Please review the changes and commit the required files.';

if ($registry_file !== NULL) {
$output .= PHP_EOL . PHP_EOL;
$output .= 'Project changes replaced by this update are recorded in:' . PHP_EOL;
$output .= $prefix . File::toRelative($registry_file, (string) $this->config->getDestination()) . PHP_EOL;
}
}
else {
$title = 'Finished installing Vortex';
Expand Down
81 changes: 76 additions & 5 deletions .vortex/installer/src/Prompts/PromptManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ class PromptManager {
*/
protected array $responses = [];

/**
* Responses describing the destination as the handlers found it.
*
* Collected while the prompts run, so each value is the one discovery
* produced with the same preceding responses in context.
*/
protected array $discoveredResponses = [];

/**
* Current response index.
*
Expand Down Expand Up @@ -247,6 +255,40 @@ public function runPrompts(): void {
// Filter out elements with numeric keys returned by intro() calls.
$responses = array_filter($responses, fn($key): bool => !is_numeric($key), ARRAY_FILTER_USE_KEY);

if ($this->config->getNoInteraction()) {
Tui::output()->setVerbosity($original_verbosity);
}

$this->responses = $this->normalizeResponses($responses);

// A conditional prompt this run skips never reaches args(), so its handler
// is asked directly. Otherwise the answer describing the destination would
// be replaced by the one describing this run.
foreach ($this->handlers as $id => $handler) {
if (!isset($this->discoveredResponses[$id])) {
$discovered = $handler->discover();

if ($discovered !== NULL) {
$this->discoveredResponses[$id] = $discovered;
}
}
}

// Discovery covers only the handlers that read the destination, so the
// collected answers fill the rest.
$this->discoveredResponses = $this->normalizeResponses(array_replace($responses, $this->discoveredResponses));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

/**
* Fold internal answers into the responses they qualify.
*
* @param array $responses
* Raw responses keyed by handler ID.
*
* @return array
* The responses with internal answers merged and removed.
*/
protected function normalizeResponses(array $responses): array {
if (isset($responses[Profile::id()]) && $responses[Profile::id()] === Profile::CUSTOM && isset($responses[ProfileCustom::id()])) {
$responses[Profile::id()] = $responses[ProfileCustom::id()];
}
Expand All @@ -270,11 +312,7 @@ public function runPrompts(): void {
$responses[Starter::id()] = Starter::LOAD_DATABASE_DEMO;
}

if ($this->config->getNoInteraction()) {
Tui::output()->setVerbosity($original_verbosity);
}

$this->responses = $responses;
return $responses;
}

/**
Expand Down Expand Up @@ -348,6 +386,31 @@ public function runProcessors(): void {
File::runDirectoryTasks($this->config->get(Config::TMP));
}

/**
* Render a template download as the destination has it installed.
*
* The answers come from discovery against the destination rather than from
* the choices this run collected, so the render reproduces the project's
* current configuration even where this run changes it. That is what makes
* the result comparable to the project's own files.
*
* @param string $dir
* Directory holding an unprocessed template download.
* @param string $version
* Version to stamp into the rendered content.
*/
public function renderAsInstalled(string $dir, string $version): void {
$config = clone $this->config;
$config->set(Config::TMP, $dir, TRUE);
$config->set(Config::VERSION, $version, TRUE);

// Handlers bind to the directory they are constructed with, so rendering
// into a directory other than this run's staging copy needs its own set.
$manager = new self($config);
$manager->responses = $this->discoveredResponses;
$manager->runProcessors();
}

/**
* Run all post-build processors.
*
Expand Down Expand Up @@ -659,6 +722,10 @@ protected function args(string $handler_class, mixed $default_override = NULL, a
$default_from_prompts = $this->promptOverrides[$id] ?? NULL;
$default_from_discovery = $handler->discover();

if ($default_from_discovery !== NULL) {
$this->discoveredResponses[$id] = $default_from_discovery;
}

if ($default_from_prompts !== NULL) {
$default = $default_from_prompts;
}
Expand Down Expand Up @@ -703,6 +770,10 @@ protected function resolveOrPrompt(string $handler_id, array $r, callable $promp
Tui::success($message);
}

// A resolved value is read from the destination, so it stands in for
// discovery for handlers that never reach a prompt.
$this->discoveredResponses[$handler_id] = $resolved;

return $resolved;
}

Expand Down
Loading