Summary
Implement expressive fluent API for creating pull requests with all configuration options.
Acceptance Criteria
PR Creation Builder Interface
namespace ConduitUI\Pr\Contracts;
interface PullRequestBuilderInterface
{
public function title(string $title): self;
public function body(string $body): self;
public function head(string $branch): self;
public function base(string $branch): self;
public function draft(bool $draft = true): self;
public function maintainerCanModify(bool $allowed = true): self;
public function create(): PullRequest;
}
PR Creation Builder Implementation
namespace ConduitUI\Pr\Services;
use ConduitUI\Pr\Contracts\PullRequestBuilderInterface;
use ConduitUI\Pr\Data\PullRequest;
final class PullRequestBuilder implements PullRequestBuilderInterface
{
protected ?string $title = null;
protected ?string $body = null;
protected ?string $head = null;
protected string $base = 'main';
protected bool $draft = false;
protected bool $maintainerCanModify = true;
public function __construct(
protected GitHub $github,
protected string $fullName,
) {}
public function title(string $title): self
{
$this->title = $title;
return $this;
}
public function body(string $body): self
{
$this->body = $body;
return $this;
}
public function head(string $branch): self
{
$this->head = $branch;
return $this;
}
public function base(string $branch): self
{
$this->base = $branch;
return $this;
}
public function draft(bool $draft = true): self
{
$this->draft = $draft;
return $this;
}
public function maintainerCanModify(bool $allowed = true): self
{
$this->maintainerCanModify = $allowed;
return $this;
}
public function create(): PullRequest
{
if ($this->title === null) {
throw new \InvalidArgumentException('Title is required');
}
if ($this->head === null) {
throw new \InvalidArgumentException('Head branch is required');
}
$data = [
'title' => $this->title,
'head' => $this->head,
'base' => $this->base,
'draft' => $this->draft,
'maintainer_can_modify' => $this->maintainerCanModify,
];
if ($this->body !== null) {
$data['body'] = $this->body;
}
$response = $this->github->post(
"/repos/{$this->fullName}/pulls",
$data
);
return PullRequest::fromArray($response->json());
}
}
Post-Creation Actions Builder
final class PullRequestPostActions
{
protected array $labels = [];
protected array $reviewers = [];
protected array $teamReviewers = [];
protected array $assignees = [];
public function __construct(
protected GitHub $github,
protected string $fullName,
protected PullRequest $pullRequest,
) {}
public function labels(array $labels): self
{
$this->labels = $labels;
return $this;
}
public function reviewers(array $usernames): self
{
$this->reviewers = $usernames;
return $this;
}
public function teamReviewers(array $teamSlugs): self
{
$this->teamReviewers = $teamSlugs;
return $this;
}
public function assignees(array $usernames): self
{
$this->assignees = $usernames;
return $this;
}
public function apply(): PullRequest
{
if (!empty($this->labels)) {
$this->github->post(
"/repos/{$this->fullName}/issues/{$this->pullRequest->number}/labels",
['labels' => $this->labels]
);
}
if (!empty($this->reviewers) || !empty($this->teamReviewers)) {
$this->github->post(
"/repos/{$this->fullName}/pulls/{$this->pullRequest->number}/requested_reviewers",
[
'reviewers' => $this->reviewers,
'team_reviewers' => $this->teamReviewers,
]
);
}
if (!empty($this->assignees)) {
$this->github->post(
"/repos/{$this->fullName}/issues/{$this->pullRequest->number}/assignees",
['assignees' => $this->assignees]
);
}
// Refresh and return updated PR
return PullRequests::find($this->fullName, $this->pullRequest->number)->fresh();
}
}
Facade Integration
// Add to PullRequests facade/service
public function create(): PullRequestBuilder
{
return new PullRequestBuilder($this->github, $this->fullName);
}
Usage Examples
Simple PR Creation
$pr = PullRequests::forRepo('owner/repo')
->create()
->title('feat: Add user authentication')
->body('This PR implements JWT-based authentication')
->head('feature/auth')
->base('main')
->create();
Draft PR
$pr = PullRequests::forRepo('owner/repo')
->create()
->title('WIP: Refactor database layer')
->body('Work in progress, do not merge')
->head('refactor/database')
->draft()
->create();
PR with Reviewers and Labels (Chained)
$pr = PullRequests::forRepo('owner/repo')
->create()
->title('fix: Resolve race condition in checkout')
->body('Fixes #123')
->head('bugfix/checkout-race')
->base('develop')
->create();
// Add metadata post-creation
$pr->addLabels(['bug', 'high-priority'])
->requestReviews(['senior-dev', 'team-lead'])
->assign('jordan');
Complete PR Setup
$pr = PullRequests::forRepo('owner/repo')
->create()
->title('feat: Add payment processing')
->body(<<<MD
## Changes
- Stripe integration
- Payment webhooks
- Refund handling
## Testing
- [ ] Unit tests passing
- [ ] Integration tests passing
- [ ] Manual testing completed
MD)
->head('feature/payments')
->base('main')
->create();
// Configure post-creation
$pr->requestReviews(['backend-lead', 'security-team'])
->requestTeamReview('payments-team')
->addLabels(['feature', 'payments'])
->assign('jordan');
Automated PR from CI
// Auto-create dependency update PR
$branch = 'deps/update-laravel-11';
$pr = PullRequests::forRepo('owner/repo')
->create()
->title('chore: Update Laravel to 11.x')
->body('Automated dependency update')
->head($branch)
->base('main')
->draft()
->create();
// Wait for checks
while ($pr->checksPending()) {
sleep(30);
$pr = $pr->fresh();
}
// Auto-approve if passing
if ($pr->checksPass()) {
$pr->markReady()
->approve('Automated approval for passing dependency update')
->submit()
->merge();
}
Cross-Repository PR (Fork)
$pr = PullRequests::forRepo('upstream/repo')
->create()
->title('feat: Add dark mode')
->body('Contributes dark mode support')
->head('myusername:feature/dark-mode') // from fork
->base('main')
->maintainerCanModify(true)
->create();
Template-Based PR
$template = file_get_contents('.github/PULL_REQUEST_TEMPLATE.md');
$pr = PullRequests::forRepo('owner/repo')
->create()
->title('feat: New feature')
->body($template)
->head('feature/new')
->create();
Bulk PR Creation
$branches = ['feature/one', 'feature/two', 'feature/three'];
foreach ($branches as $branch) {
PullRequests::forRepo('owner/repo')
->create()
->title("Feature: {$branch}")
->body("Auto-generated PR for {$branch}")
->head($branch)
->base('develop')
->draft()
->create();
}
Testing Requirements
it('creates a simple PR')
->expect(fn() =>
PullRequests::forRepo('test/repo')
->create()
->title('Test PR')
->head('test-branch')
->create()
)->toBeInstanceOf(PullRequest::class);
it('creates draft PR')
->expect(fn() =>
PullRequests::forRepo('test/repo')
->create()
->title('Draft PR')
->head('draft-branch')
->draft()
->create()
)->toBeInstanceOf(PullRequest::class);
it('requires title')
->expect(fn() =>
PullRequests::forRepo('test/repo')
->create()
->head('branch')
->create()
)->toThrow(InvalidArgumentException::class);
it('requires head branch')
->expect(fn() =>
PullRequests::forRepo('test/repo')
->create()
->title('Test')
->create()
)->toThrow(InvalidArgumentException::class);
Dependencies
References
Labels
- enhancement
- creation
- builder
Summary
Implement expressive fluent API for creating pull requests with all configuration options.
Acceptance Criteria
PR Creation Builder Interface
PR Creation Builder Implementation
Post-Creation Actions Builder
Facade Integration
Usage Examples
Simple PR Creation
Draft PR
PR with Reviewers and Labels (Chained)
Complete PR Setup
Automated PR from CI
Cross-Repository PR (Fork)
Template-Based PR
Bulk PR Creation
Testing Requirements
Dependencies
References
Labels