Skip to content

Commit c77d09c

Browse files
author
nejc
committed
feat: make Laravel Updater always create PRs by default
- Change laravelplus:update to default to PR creation instead of direct merge - Replace --pr option with --direct option for direct application - Update command description to reflect PR-by-default behavior - Fix UpstreamUpgradeCommand to properly handle command integration - Add comprehensive test suite for PR creation functionality - Fix code style issues and PHPStan configuration - Update help text in UpstreamCheckCommand to show correct commands The upgrade command now: - Creates PRs by default (safer workflow) - Uses --direct flag for direct application when needed - Maintains all existing functionality and options - Has comprehensive test coverage for all scenarios All tests passing: 16/16 ✅
1 parent b997573 commit c77d09c

File tree

6 files changed

+224
-72
lines changed

6 files changed

+224
-72
lines changed

phpstan.neon

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ parameters:
66
- tests
77
reportUnmatchedIgnoredErrors: true
88
parallel:
9-
processTimeout: 300.0
10-
memoryLimitFile: 1G
9+
processTimeout: 300.0

src/Console/Commands/UpstreamCheckCommand.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ public function handle(): int
9191

9292
$this->newLine();
9393
$this->line('💡 To pull the updates, run:');
94-
$this->line(' <info>php artisan upstream:upgrade</info>');
95-
$this->line(' <info>php artisan upstream:upgrade --pr</info> (to create a PR)');
94+
$this->line(' <info>php artisan laravelplus:update</info> (creates PR by default)');
95+
$this->line(' <info>php artisan laravelplus:update --direct</info> (to apply directly)');
9696

9797
return self::SUCCESS;
9898
}

src/Console/Commands/UpstreamPrCommand.php

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ final class UpstreamPrCommand extends Command
2828

2929
protected $description = 'Create a pull request with upstream updates instead of direct merge.';
3030

31+
private readonly VersionControl $versionControl;
32+
33+
public function __construct()
34+
{
35+
parent::__construct();
36+
37+
$config = config('upstream', []);
38+
$this->versionControl = new VersionControl(
39+
bin: (string) ($config['git_binary'] ?? 'git'),
40+
cwd: (string) ($config['working_dir'] ?? base_path())
41+
);
42+
}
43+
3144
public function handle(): int
3245
{
3346
$this->displayLogo();
@@ -251,36 +264,39 @@ private function makeGitHubRequest(string $token, string $owner, string $repo, a
251264
return null;
252265
}
253266

254-
private function applyDefaultPreset(array $cfg): array
267+
/**
268+
* Apply default preset configuration if none is set.
269+
*/
270+
private function applyDefaultPreset(array $config): array
255271
{
256272
// Check if we already have a specific upstream URL configured
257-
if (! empty($cfg['upstream_url']) && $cfg['upstream_url'] !== 'https://github.com/laravel/vue-starter-kit.git') {
258-
return $cfg;
273+
if (! empty($config['upstream_url']) && $config['upstream_url'] !== 'https://github.com/laravel/vue-starter-kit.git') {
274+
return $config;
259275
}
260276

261277
// Check if we have a default preset configured
262-
$defaultPreset = $cfg['default_preset'] ?? null;
263-
if (! $defaultPreset || ! isset($cfg['presets'][$defaultPreset])) {
264-
return $cfg;
278+
$defaultPreset = $config['default_preset'] ?? null;
279+
if (! $defaultPreset || ! isset($config['presets'][$defaultPreset])) {
280+
return $config;
265281
}
266282

267283
// Apply the default preset configuration
268-
$preset = $cfg['presets'][$defaultPreset];
284+
$preset = $config['presets'][$defaultPreset];
269285

270286
// Merge preset configuration with existing config
271-
$cfg['upstream_url'] = $preset['upstream_url'];
272-
$cfg['upstream_branch'] = $preset['upstream_branch'] ?? $cfg['upstream_branch'];
287+
$config['upstream_url'] = $preset['upstream_url'];
288+
$config['upstream_branch'] = $preset['upstream_branch'] ?? $config['upstream_branch'];
273289

274290
// Merge hooks if they exist in the preset
275291
if (isset($preset['post_update'])) {
276-
$cfg['post_update'] = array_merge($cfg['post_update'], $preset['post_update']);
292+
$config['post_update'] = array_merge($config['post_update'], $preset['post_update']);
277293
}
278294

279295
if (isset($preset['pre_update'])) {
280-
$cfg['pre_update'] = array_merge($cfg['pre_update'], $preset['pre_update']);
296+
$config['pre_update'] = array_merge($config['pre_update'], $preset['pre_update']);
281297
}
282298

283-
return $cfg;
299+
return $config;
284300
}
285301

286302
private function execHooks(string $key, bool $dry): void

src/Console/Commands/UpstreamUpgradeCommand.php

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
final class UpstreamUpgradeCommand extends Command
1313
{
1414
protected $signature = 'laravelplus:update
15-
{--pr : Create a pull request instead of direct merge}
15+
{--direct : Apply changes directly without creating a pull request}
1616
{--dry-run : Show what would happen without executing}
1717
{--test : Test configuration and connectivity before upgrade}
1818
{--no-pre : Skip pre_update hooks}
@@ -26,13 +26,13 @@ final class UpstreamUpgradeCommand extends Command
2626
{--pr-title= : Override PR title}
2727
{--pr-body= : Override PR body}';
2828

29-
protected $description = 'Upgrade your project with the latest changes from upstream repository.';
29+
protected $description = 'Upgrade your project with the latest changes from upstream repository (creates PR by default).';
3030

3131
public function handle(): int
3232
{
3333
$this->displayLogo();
3434

35-
$cfg = config('upstream');
35+
$cfg = config('upstream', []);
3636

3737
// Apply default preset if no specific configuration is set
3838
$cfg = $this->applyDefaultPreset($cfg);
@@ -58,8 +58,8 @@ public function handle(): int
5858
$this->newLine();
5959
}
6060

61-
// Route to appropriate command
62-
if ($this->option('pr')) {
61+
// Route to appropriate command (default to PR creation)
62+
if (! $this->option('direct')) {
6363
return $this->createPullRequest();
6464
}
6565

@@ -122,57 +122,79 @@ private function createPullRequest(): int
122122
{
123123
$this->info('🚀 Creating pull request with upstream updates...');
124124
// Call the PR command with the same options
125-
$prCommand = new UpstreamPrCommand();
126-
// Transfer options to PR command
127-
$options = [
128-
'dry-run' => $this->option('dry-run'),
129-
'test' => false, // Already tested above
130-
'no-pre' => $this->option('no-pre'),
131-
'no-post' => $this->option('no-post'),
132-
'strategy' => $this->option('strategy'),
133-
'upstream' => $this->option('upstream'),
134-
'branch' => $this->option('branch'),
135-
'local' => $this->option('local'),
136-
'remote-name' => $this->option('remote-name'),
137-
'pr-branch' => $this->option('pr-branch'),
138-
'pr-title' => $this->option('pr-title'),
139-
'pr-body' => $this->option('pr-body'),
140-
];
141-
// Set options on the PR command
142-
foreach ($options as $key => $value) {
143-
if ($value !== null) {
144-
$prCommand->setOption($key, $value);
145-
}
125+
// Build command arguments
126+
$arguments = [];
127+
if ($this->option('dry-run')) {
128+
$arguments['--dry-run'] = true;
129+
}
130+
if ($this->option('no-pre')) {
131+
$arguments['--no-pre'] = true;
132+
}
133+
if ($this->option('no-post')) {
134+
$arguments['--no-post'] = true;
135+
}
136+
if ($this->option('strategy')) {
137+
$arguments['--strategy'] = $this->option('strategy');
138+
}
139+
if ($this->option('upstream')) {
140+
$arguments['--upstream'] = $this->option('upstream');
141+
}
142+
if ($this->option('branch')) {
143+
$arguments['--branch'] = $this->option('branch');
144+
}
145+
if ($this->option('local')) {
146+
$arguments['--local'] = $this->option('local');
147+
}
148+
if ($this->option('remote-name')) {
149+
$arguments['--remote-name'] = $this->option('remote-name');
150+
}
151+
if ($this->option('pr-branch')) {
152+
$arguments['--pr-branch'] = $this->option('pr-branch');
153+
}
154+
if ($this->option('pr-title')) {
155+
$arguments['--pr-title'] = $this->option('pr-title');
156+
}
157+
if ($this->option('pr-body')) {
158+
$arguments['--pr-body'] = $this->option('pr-body');
146159
}
147160

148-
return $prCommand->handle();
161+
// Call the PR command directly
162+
return $this->call('laravelplus:pr', $arguments);
149163
}
150164

151165
private function directUpgrade(): int
152166
{
153167
$this->info('🚀 Upgrading with upstream updates...');
154-
// Call the sync command with the same options
155-
$syncCommand = new UpstreamSyncCommand();
156-
// Transfer options to sync command
157-
$options = [
158-
'dry-run' => $this->option('dry-run'),
159-
'test' => false, // Already tested above
160-
'no-pre' => $this->option('no-pre'),
161-
'no-post' => $this->option('no-post'),
162-
'strategy' => $this->option('strategy'),
163-
'upstream' => $this->option('upstream'),
164-
'branch' => $this->option('branch'),
165-
'local' => $this->option('local'),
166-
'remote-name' => $this->option('remote-name'),
167-
];
168-
// Set options on the sync command
169-
foreach ($options as $key => $value) {
170-
if ($value !== null) {
171-
$syncCommand->setOption($key, $value);
172-
}
168+
169+
// Build command arguments for sync command
170+
$arguments = [];
171+
if ($this->option('dry-run')) {
172+
$arguments['--dry-run'] = true;
173+
}
174+
if ($this->option('no-pre')) {
175+
$arguments['--no-pre'] = true;
176+
}
177+
if ($this->option('no-post')) {
178+
$arguments['--no-post'] = true;
179+
}
180+
if ($this->option('strategy')) {
181+
$arguments['--strategy'] = $this->option('strategy');
182+
}
183+
if ($this->option('upstream')) {
184+
$arguments['--upstream'] = $this->option('upstream');
185+
}
186+
if ($this->option('branch')) {
187+
$arguments['--branch'] = $this->option('branch');
188+
}
189+
if ($this->option('local')) {
190+
$arguments['--local'] = $this->option('local');
191+
}
192+
if ($this->option('remote-name')) {
193+
$arguments['--remote-name'] = $this->option('remote-name');
173194
}
174195

175-
return $syncCommand->handle();
196+
// Call the sync command directly
197+
return $this->call('laravelplus:sync', $arguments);
176198
}
177199

178200
private function applyDefaultPreset(array $cfg): array
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use LaravelPlus\LaravelUpdater\Tests\TestCase;
6+
7+
uses(TestCase::class);
8+
9+
describe('UpstreamUpgradeCommand PR Creation', function () {
10+
it('defaults to creating PR when no options are provided', function () {
11+
$this->artisan('laravelplus:update --dry-run')
12+
->expectsOutput('🚀 Creating pull request with upstream updates...')
13+
->assertExitCode(0);
14+
});
15+
16+
it('creates PR when --direct option is not used', function () {
17+
$this->artisan('laravelplus:update --dry-run --test')
18+
->expectsOutput('🚀 Creating pull request with upstream updates...')
19+
->assertExitCode(0);
20+
});
21+
22+
it('applies changes directly when --direct option is used', function () {
23+
$this->artisan('laravelplus:update --direct --dry-run')
24+
->assertExitCode(0);
25+
});
26+
27+
it('shows correct help text for new --direct option', function () {
28+
$this->artisan('laravelplus:update --help')
29+
->expectsOutput(' --direct Apply changes directly without creating a pull request')
30+
->assertExitCode(0);
31+
});
32+
33+
it('shows updated description mentioning PR by default', function () {
34+
$this->artisan('laravelplus:update --help')
35+
->expectsOutput(' Upgrade your project with the latest changes from upstream repository (creates PR by default).')
36+
->assertExitCode(0);
37+
});
38+
39+
it('passes all options correctly to PR command when creating PR', function () {
40+
$this->artisan('laravelplus:update --dry-run --strategy=rebase --pr-title="Custom PR Title" --pr-body="Custom PR Body"')
41+
->expectsOutput('🚀 Creating pull request with upstream updates...')
42+
->assertExitCode(0);
43+
});
44+
45+
it('handles test option correctly with PR creation', function () {
46+
$this->artisan('laravelplus:update --test --dry-run')
47+
->expectsOutput('🔍 Testing upstream sync configuration...')
48+
->expectsOutput('🚀 Creating pull request with upstream updates...')
49+
->assertExitCode(0);
50+
});
51+
52+
it('handles no-pre and no-post options with PR creation', function () {
53+
$this->artisan('laravelplus:update --no-pre --no-post --dry-run')
54+
->expectsOutput('🚀 Creating pull request with upstream updates...')
55+
->assertExitCode(0);
56+
});
57+
58+
it('handles custom upstream and branch options with PR creation', function () {
59+
$this->artisan('laravelplus:update --upstream=https://github.com/test/repo.git --branch=develop --dry-run')
60+
->expectsOutput('🚀 Creating pull request with upstream updates...')
61+
->assertExitCode(0);
62+
});
63+
64+
it('handles custom PR options correctly', function () {
65+
$this->artisan('laravelplus:update --pr-branch=custom-branch --pr-title="My Custom Title" --pr-body="My Custom Body" --dry-run')
66+
->expectsOutput('🚀 Creating pull request with upstream updates...')
67+
->assertExitCode(0);
68+
});
69+
});
70+
71+
describe('UpstreamUpgradeCommand Integration', function () {
72+
it('can run check command and shows updated help text', function () {
73+
$this->artisan('laravelplus:check')
74+
->expectsOutput('🔍 Checking for upstream updates...')
75+
->assertExitCode(0);
76+
});
77+
78+
79+
it('respects custom remote name option', function () {
80+
$this->artisan('laravelplus:update --remote-name=origin --dry-run')
81+
->expectsOutput('🚀 Creating pull request with upstream updates...')
82+
->assertExitCode(0);
83+
});
84+
85+
it('handles local branch override correctly', function () {
86+
$this->artisan('laravelplus:update --local=feature-branch --dry-run')
87+
->expectsOutput('🚀 Creating pull request with upstream updates...')
88+
->assertExitCode(0);
89+
});
90+
});
91+
92+
describe('Command Options Validation', function () {
93+
it('accepts valid strategy options', function () {
94+
$this->artisan('laravelplus:update --strategy=merge --dry-run')
95+
->expectsOutput('🚀 Creating pull request with upstream updates...')
96+
->assertExitCode(0);
97+
98+
$this->artisan('laravelplus:update --strategy=rebase --dry-run')
99+
->expectsOutput('🚀 Creating pull request with upstream updates...')
100+
->assertExitCode(0);
101+
});
102+
103+
it('handles empty string options gracefully', function () {
104+
$this->artisan('laravelplus:update --upstream="" --branch="" --dry-run')
105+
->expectsOutput('🚀 Creating pull request with upstream updates...')
106+
->assertExitCode(0);
107+
});
108+
109+
it('processes multiple options together correctly', function () {
110+
$this->artisan('laravelplus:update --dry-run --test --no-pre --no-post --strategy=rebase --pr-title="Test PR"')
111+
->expectsOutput('🔍 Testing upstream sync configuration...')
112+
->expectsOutput('🚀 Creating pull request with upstream updates...')
113+
->assertExitCode(0);
114+
});
115+
});

0 commit comments

Comments
 (0)