Skip to content

Commit

Permalink
resave/all, --with-fields, & output indentation
Browse files Browse the repository at this point in the history
Resolves #15869
  • Loading branch information
brandonkelly committed Oct 10, 2024
1 parent 0c7ff1f commit 571c620
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 34 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,33 @@
- All relation fields can now be selected as field layouts’ thumbnail providers. ([#15651](https://github.com/craftcms/cms/discussions/15651))
- Added the “Markdown” field layout UI element type. ([#15674](https://github.com/craftcms/cms/pull/15674), [#15664](https://github.com/craftcms/cms/discussions/15664))
- Added `pc/*` commands as an alias of `project-config/*`.
- Added the `resave/all` command.
- Added the `--except`, `--minor-only`, and `--patch-only` options to the `update` command. ([#15829](https://github.com/craftcms/cms/pull/15829))
- Added the `--with-fields` option to all native `resave/*` commands.
- The `fields/merge` and `fields/auto-merge` commands now prompt to resave elements that include relational fields before merging them, and provide a CLI command that should be run on other environments before the changes are deployed to them. ([#15869](https://github.com/craftcms/cms/issues/15869))

### Development
- Added the `encodeUrl()` Twig function. ([#15838](https://github.com/craftcms/cms/issues/15838))
- Added support for passing aliased field handles into element queries’ `select()`/`addSelect()` methods. ([#15827](https://github.com/craftcms/cms/issues/15827))

### Extensibility
- Added `craft\base\RequestTrait::getIsWebRequest()`. ([#15690](https://github.com/craftcms/cms/pull/15690))
- Added `craft\console\Controller::output()`.
- Added `craft\console\controllers\ResaveController::hasTheFields()`.
- Added `craft\events\DefineAddressCountriesEvent`. ([#15711](https://github.com/craftcms/cms/pull/15711))
- Added `craft\filters\BasicHttpAuthLogin`. ([#15720](https://github.com/craftcms/cms/pull/15720))
- Added `craft\filters\BasicHttpAuthStatic`. ([#15720](https://github.com/craftcms/cms/pull/15720))
- Added `craft\filters\SiteFilterTrait::$enabled`. ([#15720](https://github.com/craftcms/cms/pull/15720))
- Added `craft\helpers\Console::indent()`.
- Added `craft\helpers\Console::indentStr()`.
- Added `craft\helpers\Console::outdent()`.
- Added `craft\helpers\StringHelper::firstLine()`.
- Added `craft\helpers\UrlHelper::encodeUrl()`. ([#15838](https://github.com/craftcms/cms/issues/15838))
- Added `craft\services\Addresses::EVENT_DEFINE_ADDRESS_COUNTRIES`. ([#15711](https://github.com/craftcms/cms/pull/15711))
- Added `craft\services\Addresses::getCountryList()`. ([#15711](https://github.com/craftcms/cms/pull/15711))
- Added `craft\web\View::registerCpTwigExtension()`.
- Added `craft\web\View::registerSiteTwigExtension()`.
- `craft\helpers\Console::output()` now prepends an indent to each line of the passed-in string, if `indent()` had been called prior.
- Deprecated the `enableBasicHttpAuth` config setting. `craft\filters\BasicHttpAuthLogin` should be used instead. ([#15720](https://github.com/craftcms/cms/pull/15720))
- Added the `serializeForm` event to `Craft.ElementEditor`. ([#15794](https://github.com/craftcms/cms/discussions/15794))

Expand Down
31 changes: 24 additions & 7 deletions src/console/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,24 @@ public function runAction($id, $params = []): int
return $result;
}

/**
* Prints text to STDOUT appended with a carriage return (PHP_EOL).
*
* @param string|null $string The text to print
* @return int|false The number of bytes printed or false on error
* @since 5.5.0
*/
public function output(string $string = null): int|false
{
if ($string !== null && $this->isColorEnabled()) {
$args = func_get_args();
array_shift($args);
$string = Console::ansiFormat($string, $args);
}

return Console::output($string);
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -394,22 +412,22 @@ public function passwordPrompt(array $options = []): string
$input = CliPrompt::hiddenPrompt(true);

if ($options['required'] && $input === '') {
$this->stdout($options['error'] . PHP_EOL);
$this->output($options['error']);
goto top;
}

$error = null;

if ($options['validator'] && !$options['validator']($input, $error)) {
/** @var string|null $error */
$this->stdout(($error ?? $options['error']) . PHP_EOL);
$this->output(($error ?? $options['error']));
goto top;
}

if ($options['confirm']) {
$this->stdout('Confirm: ');
if ($input !== CliPrompt::hiddenPrompt(true)) {
$this->stdout('Passwords didn\'t match, try again.' . PHP_EOL, Console::FG_RED);
$this->output('Passwords didn\'t match, try again.', Console::FG_RED);
goto top;
}
}
Expand Down Expand Up @@ -444,7 +462,7 @@ public function table(array $headers, array $data, array $options = []): void
*/
public function do(string $description, callable $action, bool $withDuration = false): void
{
$this->stdout('', Console::FG_GREY);
$this->stdout(Console::indentStr() . '', Console::FG_GREY);
$this->stdout($this->markdownToAnsi($description));
$this->stdout('', Console::FG_GREY);

Expand All @@ -455,16 +473,15 @@ public function do(string $description, callable $action, bool $withDuration = f
try {
$action();
} catch (Throwable $e) {
$this->stdout('' . PHP_EOL, Console::FG_RED, Console::BOLD);
$this->stdout(" Error: {$e->getMessage()}" . PHP_EOL, Console::FG_RED);
$this->stdout("error: {$e->getMessage()}" . PHP_EOL, Console::FG_RED);
throw $e;
}

$this->stdout('', Console::FG_GREEN, Console::BOLD);
if ($withDuration) {
$this->stdout(sprintf(' (time: %.3fs)', microtime(true) - $time), Console::FG_GREY);
}
$this->stdout(PHP_EOL);
$this->output();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/console/ControllerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public function tip(string $message): void
*/
public function warning(string $message): void
{
$this->note($message, '⚠️ ');
$this->note($message, '⚠️');
}

/**
Expand Down
103 changes: 85 additions & 18 deletions src/console/controllers/FieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use craft\base\MergeableFieldInterface;
use craft\console\Controller;
use craft\db\Table;
use craft\fields\BaseRelationField;
use craft\helpers\Console;
use craft\helpers\Db;
use craft\helpers\FileHelper;
Expand Down Expand Up @@ -74,14 +75,13 @@ public function actionMerge(string $handleA, string $handleB): int
// Make sure all the layouts either have an ID or UUID; otherwise we wouldn't know what to do with it
$unsavableLayouts = $layouts->filter(fn(FieldLayout $layout) => !$layout->id && !$layout->uid);
if ($unsavableLayouts->isNotEmpty()) {
$this->stdout(<<<EOD
$this->output(<<<EOD
These fields can’t be merged because one or both are used in a field layout(s)
that doesn’t have an `id` or `uid`:
EOD, Console::FG_RED);
$this->output();
foreach ($unsavableLayouts as $layout) {
$this->stdout(sprintf(" - %s\n", $this->layoutDescriptor($layout)), Console::FG_RED);
$this->output(sprintf(" - %s", $this->layoutDescriptor($layout)), Console::FG_RED);
}
return ExitCode::UNSPECIFIED_ERROR;
}
Expand All @@ -98,7 +98,7 @@ public function actionMerge(string $handleA, string $handleB): int
!$fieldA::isMultiInstance() ? sprintf('%s (%s)', $fieldA->name, $fieldA::displayName()) : null,
!$fieldB::isMultiInstance() ? sprintf('%s (%s)', $fieldB->name, $fieldB::displayName()) : null,
]);
$this->stdout($this->markdownToAnsi(sprintf(<<<EOD
$this->output($this->markdownToAnsi(sprintf(<<<EOD
These fields can’t be merged because %s %s support multiple instances,
and both fields are already in use by %s.
EOD,
Expand Down Expand Up @@ -126,6 +126,25 @@ public function actionMerge(string $handleA, string $handleB): int
return ExitCode::UNSPECIFIED_ERROR;
}

$mergingRelationFields = $fieldA instanceof BaseRelationField;
if ($mergingRelationFields) {
$this->warning('Merging relation fields should only be done after all elements using them have been resaved.');
if ($this->confirm('Resave them now?', true)) {
$this->do("Running `resave/all --with-fields=$handleA,$handleB`", function() use ($handleA, $handleB) {
$this->output();
Console::indent();
try {
$this->run('resave/all', [
'withFields' => [$handleA, $handleB],
]);
} finally {
Console::outdent();
$this->stdout(Console::indentStr() . ' ');
}
});
}
}

[$persistingField, $outgoingField, $outgoingLayouts] = $this->choosePersistingField(
$fieldA,
$fieldB,
Expand All @@ -135,7 +154,7 @@ public function actionMerge(string $handleA, string $handleB): int
$canMergeIntoFieldB,
);

$this->stdout("\n");
$this->output();
$this->mergeFields($persistingField, $outgoingField, $outgoingLayouts, $migrationPath);

$this->success(sprintf(<<<EOD
Expand All @@ -146,6 +165,16 @@ public function actionMerge(string $handleA, string $handleB): int
FileHelper::relativePath($migrationPath)
));

if ($mergingRelationFields) {
$this->warning(<<<MD
Be sure to run this command on other environments **before** deploying these changes:
```
php craft resave/all --with-fields=$handleA,$handleB
```
MD);
}

return ExitCode::OK;
}

Expand Down Expand Up @@ -194,18 +223,19 @@ public function actionAutoMerge(): int
}

$migrationPaths = [];
$relationFieldHandles = [];

foreach ($groups as $group) {
/** @var Collection<FieldInterface> $group */
/** @var FieldInterface $first */
$first = $group->first();

$this->stdout($this->markdownToAnsi(sprintf(
$this->output($this->markdownToAnsi(sprintf(
'**Found %s %s fields with identical settings:**',
$group->count(),
$first::displayName(),
)));
$this->stdout("\n\n");
$this->output();
$usagesByField = [];
$group = $group
->each(function(FieldInterface $field) use ($fieldsService, &$usagesByField) {
Expand All @@ -215,21 +245,46 @@ public function actionAutoMerge(): int
->sortBy(fn(FieldInterface $field) => count($usagesByField[$field->id]), SORT_NUMERIC, true)
->keyBy(fn(FieldInterface $field) => $field->handle)
->each(function(FieldInterface $field) use (&$usagesByField) {
$this->stdout($this->markdownToAnsi(sprintf(
$this->output($this->markdownToAnsi(sprintf(
" - `%s` (%s)",
$field->handle,
$this->usagesDescriptor($usagesByField[$field->id]),
)));
$this->stdout("\n");
});

$this->stdout("\n");
$this->output();

if (!$this->confirm('Merge these fields?')) {
continue;
}

$this->stdout("\n" . $this->markdownToAnsi('**Which one should persist?**') . "\n");
$this->output();

$mergingRelationFields = $group->first() instanceof BaseRelationField;
if ($mergingRelationFields) {
$handles = $group->map(fn(FieldInterface $field) => $field->handle)->values()->all();
array_push($relationFieldHandles, ...$handles);
$this->warning('Merging relation fields should only be done after all elements using them have been resaved.');
if ($this->confirm('Resave them now?', true)) {
$this->do(
sprintf('Running `resave/all --with-fields=%s`', implode(',', $handles)),
function() use ($handles) {
$this->output();
Console::indent();
try {
$this->run('resave/all', [
'withFields' => $handles,
]);
} finally {
Console::outdent();
$this->stdout(Console::indentStr() . ' ');
}
},
);
}
}

$this->output($this->markdownToAnsi('**Which one should persist?**'));

$choice = $this->select(
'Choose:',
Expand All @@ -240,17 +295,17 @@ public function actionAutoMerge(): int
$group->first()->handle,
);

$this->stdout("\n");
$this->output();
/** @var FieldInterface $persistentField */
$persistentField = $group->get($choice);

$group
->except($choice)
->each(function(FieldInterface $outgoingField) use ($persistentField, $usagesByField, &$migrationPaths) {
$this->stdout($this->markdownToAnsi("Merging `{$outgoingField->handle}` → `{$persistentField->handle}`") . "\n");
$this->output($this->markdownToAnsi("Merging `{$outgoingField->handle}` → `{$persistentField->handle}`"));
$this->mergeFields($persistentField, $outgoingField, $usagesByField[$outgoingField->id], $migrationPath);
$migrationPaths[] = $migrationPath;
$this->stdout("\n");
$this->output();
});
}

Expand All @@ -259,6 +314,16 @@ public function actionAutoMerge(): int
Fields merged. Commit the new content migrations and your project config changes,
and run `craft up` on other environments for the changes to take effect.
EOD);

if (!empty($relationFieldHandles)) {
$this->warning(sprintf(<<<MD
Be sure to run this command on other environments **before** deploying these changes:
```
php craft resave/all --with-fields=%s
```
MD, implode(',', $relationFieldHandles)));
}
} else {
$this->failure('No fields merged.');
}
Expand Down Expand Up @@ -287,12 +352,14 @@ private function choosePersistingField(
$infoA = $this->usagesDescriptor($layoutsA);
$infoB = $this->usagesDescriptor($layoutsB);

$this->stdout("\n" . $this->markdownToAnsi(<<<MD
$this->output();
$this->output($this->markdownToAnsi(<<<MD
**Which field should persist?**
- `$fieldA->handle` ($infoA)
- `$fieldB->handle` ($infoB)
MD) . "\n\n");
MD));
$this->output();

$choice = $this->select('Choose:', [
$fieldA->handle => $fieldA->name,
Expand Down Expand Up @@ -409,7 +476,7 @@ private function mergeFields(
FileHelper::writeToFile($migrationPath, $content);
});

$this->stdout(" → Running content migration …\n");
$this->output(" → Running content migration …");
Craft::$app->getContentMigrator()->migrateUp($migrationName);
}

Expand Down
Loading

0 comments on commit 571c620

Please sign in to comment.