Skip to content

Commit 1969017

Browse files
committed
wip
1 parent 85addd1 commit 1969017

File tree

6 files changed

+309
-168
lines changed

6 files changed

+309
-168
lines changed

src/app/Console/Commands/Upgrade/Step.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ public function fix(StepResult $result): StepResult
4141
return StepResult::skipped('No automatic fix available.');
4242
}
4343

44+
/**
45+
* Provide optional choices for automatic fixes. When empty, a yes/no confirmation is shown.
46+
*
47+
* @return array<int|string, mixed>
48+
*/
49+
public function fixOptions(StepResult $result): array
50+
{
51+
return [];
52+
}
53+
54+
public function selectFixOption(?string $option): void
55+
{
56+
}
57+
4458
public function isBlocking(): bool
4559
{
4660
return false;

src/app/Console/Commands/Upgrade/Support/ConfigFilesHelper.php

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ConfigFilesHelper
1111
protected string $packageRoot;
1212
protected bool $publishedIsFile = false;
1313
protected bool $packageIsFile = false;
14+
protected ?string $defaultConfigFile = null;
1415
private array $configFiles = [];
1516

1617
public function __construct(
@@ -24,6 +25,11 @@ public function __construct(
2425
$this->initializeConfigFiles();
2526
}
2627

28+
public function setDefaultConfigFile(string $configFile): void
29+
{
30+
$this->defaultConfigFile = $configFile;
31+
}
32+
2733
protected function initializeConfigFiles(): void
2834
{
2935
if ($this->configFiles !== []) {
@@ -60,13 +66,139 @@ public function readPublishedFile(string $path): ?string
6066
return $this->context->readFile($this->context->relativePath($absolutePath));
6167
}
6268

69+
public function configKeyHasValue(string $key, mixed $expectedValue, ?string $path = null): bool
70+
{
71+
$target = $this->resolveConfigFileArgument($path);
72+
73+
if ($target === null) {
74+
return false;
75+
}
76+
77+
$config = $this->loadPublishedConfig($target);
78+
79+
if ($config === null) {
80+
return false;
81+
}
82+
83+
$current = $this->getConfigValueByKey($config, $key);
84+
85+
if ($current === null) {
86+
return false;
87+
}
88+
89+
if (is_array($current)) {
90+
if (is_array($expectedValue)) {
91+
foreach ($expectedValue as $value) {
92+
if (! in_array($value, $current, true)) {
93+
return false;
94+
}
95+
}
96+
97+
return true;
98+
}
99+
100+
return in_array($expectedValue, $current, true);
101+
}
102+
103+
return $current === $expectedValue;
104+
}
105+
63106
public function writePublishedFile(string $path, string $contents): bool
64107
{
65108
$absolutePath = $this->resolvePublishedPath($path);
66109

67110
return $this->context->writeFile($this->context->relativePath($absolutePath), $contents);
68111
}
69112

113+
public function updateConfigKeyValue(string $key, mixed $newValue, ?string $path = null): bool
114+
{
115+
$target = $this->resolveConfigFileArgument($path);
116+
117+
if ($target === null) {
118+
return false;
119+
}
120+
121+
if (is_string($newValue) && $this->configKeyHasValue($key, $newValue, $target)) {
122+
return false;
123+
}
124+
125+
$contents = $this->readPublishedFile($target);
126+
127+
if ($contents === null) {
128+
return false;
129+
}
130+
131+
$changed = false;
132+
$pattern = '/(?P<prefix>(["\"])'.preg_quote($key, '/').'\2\s*=>\s*)(?P<value>(?:[^,\r\n\/]|\/(?!\/))+)(?P<suffix>,?[ \t]*(?:\/\/[^\r\n]*)?)/';
133+
134+
$updated = preg_replace_callback(
135+
$pattern,
136+
function (array $matches) use ($newValue, &$changed) {
137+
$existing = trim($matches['value']);
138+
$quote = $existing[0] ?? null;
139+
140+
if (is_string($newValue)) {
141+
$preferredQuote = ($quote === "'" || $quote === '"') ? $quote : "'";
142+
$replacement = $this->exportStringValue($newValue, $preferredQuote);
143+
} else {
144+
$replacement = $this->exportValue($newValue);
145+
}
146+
147+
if ($replacement === $existing) {
148+
return $matches[0];
149+
}
150+
151+
$changed = true;
152+
153+
return $matches['prefix'].$replacement.$matches['suffix'];
154+
},
155+
$contents,
156+
1
157+
);
158+
159+
if ($updated === null || ! $changed) {
160+
return false;
161+
}
162+
163+
return $this->writePublishedFile($target, $updated);
164+
}
165+
166+
public function commentOutConfigValue(string $valueExpression, ?string $path = null): bool
167+
{
168+
$target = $this->resolveConfigFileArgument($path);
169+
170+
if ($target === null) {
171+
return false;
172+
}
173+
174+
$contents = $this->readPublishedFile($target);
175+
176+
if ($contents === null) {
177+
return false;
178+
}
179+
180+
$pattern = '~^[\t ]*'.preg_quote($valueExpression, '~').'([\t ]*,?[\t ]*)\r?$~m';
181+
182+
$updated = preg_replace_callback(
183+
$pattern,
184+
function (array $matches) use ($valueExpression) {
185+
$position = strpos($matches[0], $valueExpression);
186+
$indentation = $position === false ? '' : substr($matches[0], 0, $position);
187+
188+
return $indentation.'// '.$valueExpression.$matches[1];
189+
},
190+
$contents,
191+
1,
192+
$count
193+
);
194+
195+
if ($updated === null || $count === 0) {
196+
return false;
197+
}
198+
199+
return $this->writePublishedFile($target, $updated);
200+
}
201+
70202
public function loadPublishedConfig(string $path): ?array
71203
{
72204
return $this->loadConfigArray($this->resolvePublishedPath($path));
@@ -824,6 +956,49 @@ protected function normalizePath(string $path): string
824956
return rtrim(str_replace('\\', '/', $path), '/');
825957
}
826958

959+
protected function resolveConfigFileArgument(?string $path): ?string
960+
{
961+
if ($path !== null) {
962+
return $path;
963+
}
964+
965+
return $this->defaultConfigFile;
966+
}
967+
968+
protected function getConfigValueByKey(array $config, string $key): mixed
969+
{
970+
if ($key === '') {
971+
return null;
972+
}
973+
974+
$segments = explode('.', $key);
975+
$current = $config;
976+
977+
foreach ($segments as $segment) {
978+
if (! is_array($current) || ! array_key_exists($segment, $current)) {
979+
return null;
980+
}
981+
982+
$current = $current[$segment];
983+
}
984+
985+
return $current;
986+
}
987+
988+
protected function exportStringValue(string $value, string $quote): string
989+
{
990+
if ($quote === '"') {
991+
return '"'.addcslashes($value, "\\\"$").'"';
992+
}
993+
994+
return '\'' . addcslashes($value, "\\'") . '\'';
995+
}
996+
997+
protected function exportValue(mixed $value): string
998+
{
999+
return var_export($value, true);
1000+
}
1001+
8271002
protected function calculateMissingKeys(array $packageKeys, array $publishedKeys): array
8281003
{
8291004
$missingKeys = array_values(array_diff($packageKeys, $publishedKeys));

src/app/Console/Commands/Upgrade/UpgradeCommand.php

Lines changed: 118 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,32 +92,72 @@ public function handle(): int
9292
}
9393

9494
if ($this->shouldOfferFix($step, $result)) {
95-
$question = trim($step->fixMessage($result));
96-
$question = $question !== '' ? $question : 'Apply automatic fix?';
97-
$applyFix = $this->confirm(' '.$question, true);
98-
99-
if ($applyFix) {
100-
$this->progressBlock('Applying automatic fix');
101-
$fixResult = $step->fix($result);
102-
$this->closeProgressBlock(strtoupper($fixResult->status->label()), $fixResult->status->color());
103-
$this->printResultDetails($fixResult);
104-
105-
if (! $fixResult->status->isFailure()) {
106-
$this->progressBlock('Re-running '.$step->title());
107-
108-
try {
109-
$result = $step->run();
110-
} catch (\Throwable $exception) {
111-
$result = StepResult::failure(
112-
$exception->getMessage(),
113-
[
114-
'Step: '.$stepClass,
115-
]
116-
);
95+
$options = $step->fixOptions($result);
96+
97+
if (! empty($options)) {
98+
[$choiceMap, $defaultLabel] = $this->normalizeFixOptions($options);
99+
100+
if (! empty($choiceMap)) {
101+
$question = trim($step->fixMessage($result));
102+
$question = $question !== '' ? $question : 'Select an automatic fix option';
103+
$selectedLabel = $this->choice(' '.$question, array_keys($choiceMap), $defaultLabel);
104+
$selectedOption = $choiceMap[$selectedLabel] ?? null;
105+
106+
if ($selectedOption !== null && $selectedOption !== '') {
107+
$step->selectFixOption((string) $selectedOption);
108+
109+
$this->progressBlock('Applying automatic fix');
110+
$fixResult = $step->fix($result);
111+
$this->closeProgressBlock(strtoupper($fixResult->status->label()), $fixResult->status->color());
112+
$this->printResultDetails($fixResult);
113+
114+
if (! $fixResult->status->isFailure()) {
115+
$this->progressBlock('Re-running '.$step->title());
116+
117+
try {
118+
$result = $step->run();
119+
} catch (\Throwable $exception) {
120+
$result = StepResult::failure(
121+
$exception->getMessage(),
122+
[
123+
'Step: '.$stepClass,
124+
]
125+
);
126+
}
127+
128+
$this->closeProgressBlock(strtoupper($result->status->label()), $result->status->color());
129+
$this->printResultDetails($result);
130+
}
131+
}
132+
}
133+
} else {
134+
$question = trim($step->fixMessage($result));
135+
$question = $question !== '' ? $question : 'Apply automatic fix?';
136+
$applyFix = $this->confirm(' '.$question, true);
137+
138+
if ($applyFix) {
139+
$this->progressBlock('Applying automatic fix');
140+
$fixResult = $step->fix($result);
141+
$this->closeProgressBlock(strtoupper($fixResult->status->label()), $fixResult->status->color());
142+
$this->printResultDetails($fixResult);
143+
144+
if (! $fixResult->status->isFailure()) {
145+
$this->progressBlock('Re-running '.$step->title());
146+
147+
try {
148+
$result = $step->run();
149+
} catch (\Throwable $exception) {
150+
$result = StepResult::failure(
151+
$exception->getMessage(),
152+
[
153+
'Step: '.$stepClass,
154+
]
155+
);
156+
}
157+
158+
$this->closeProgressBlock(strtoupper($result->status->label()), $result->status->color());
159+
$this->printResultDetails($result);
117160
}
118-
119-
$this->closeProgressBlock(strtoupper($result->status->label()), $result->status->color());
120-
$this->printResultDetails($result);
121161
}
122162
}
123163
}
@@ -131,7 +171,11 @@ public function handle(): int
131171

132172
$expectedVersionInstalled = $this->hasExpectedBackpackVersion($context, $config);
133173

134-
return $this->outputSummary($descriptor['label'], $results, $expectedVersionInstalled, $config);
174+
$this->outputSummary($descriptor['label'], $results, $expectedVersionInstalled, $config);
175+
176+
$this->note('The script has only updated what could be automated. '.PHP_EOL.' Please run composer update to finish Step 1, then go back to the Upgrade Guide and follow all other steps, to make sure your admin panel is correctly upgraded: https://backpackforlaravel.com/docs/7.x/upgrade-guide#step-2');
177+
178+
return Command::SUCCESS;
135179
}
136180

137181
protected function outputSummary(
@@ -248,6 +292,54 @@ protected function shouldOfferFix(Step $step, StepResult $result): bool
248292
return $step->canFix($result);
249293
}
250294

295+
/**
296+
* @param array<int|string, mixed> $options
297+
* @return array{0: array<string, string|null>, 1: string|null}
298+
*/
299+
protected function normalizeFixOptions(array $options): array
300+
{
301+
$choices = [];
302+
$defaultLabel = null;
303+
304+
foreach ($options as $key => $option) {
305+
$label = null;
306+
$value = null;
307+
$isDefault = false;
308+
309+
if (is_array($option)) {
310+
$label = $option['label'] ?? null;
311+
$value = $option['key'] ?? (is_string($key) ? $key : null);
312+
$isDefault = (bool) ($option['default'] ?? false);
313+
} else {
314+
$label = (string) $option;
315+
$value = is_string($key) ? $key : $label;
316+
}
317+
318+
if (! is_string($label)) {
319+
continue;
320+
}
321+
322+
$label = trim($label);
323+
324+
if ($label === '') {
325+
continue;
326+
}
327+
328+
$value = $value === null ? null : (string) $value;
329+
$choices[$label] = $value;
330+
331+
if ($isDefault && $defaultLabel === null) {
332+
$defaultLabel = $label;
333+
}
334+
}
335+
336+
if ($defaultLabel === null && ! empty($choices)) {
337+
$defaultLabel = array_key_first($choices);
338+
}
339+
340+
return [$choices, $defaultLabel];
341+
}
342+
251343
protected function resolveConfigForDescriptor(array $descriptor): UpgradeConfigInterface
252344
{
253345
$descriptorKey = $descriptor['key'] ?? null;

0 commit comments

Comments
 (0)