Skip to content

Commit c95ea93

Browse files
authored
Merge pull request #37 from geerteltink/feat/milestones
Create new milestones automatically
2 parents 6c99d78 + c95c11b commit c95ea93

15 files changed

+463
-44
lines changed

.github/workflows/automatic-release.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,13 @@ jobs:
5555
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
5656
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
5757
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
58+
59+
- name: "Create new milestones"
60+
uses: "./"
61+
with:
62+
command-name: "laminas:automatic-releases:create-milestones"
63+
env:
64+
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
65+
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
66+
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
67+
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ composer-validate:
1010
composer validate
1111

1212
static-analysis:
13-
vendor/bin/phpstan analyse
1413
vendor/bin/psalm
1514

1615
test:

bin/console.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Http\Discovery\Psr17FactoryDiscovery;
1111
use Laminas\AutomaticReleases\Application\Command\BumpChangelogForReleaseBranch;
1212
use Laminas\AutomaticReleases\Application\Command\CreateMergeUpPullRequest;
13+
use Laminas\AutomaticReleases\Application\Command\CreateMilestones;
1314
use Laminas\AutomaticReleases\Application\Command\ReleaseCommand;
1415
use Laminas\AutomaticReleases\Application\Command\SwitchDefaultBranchToNextMinor;
1516
use Laminas\AutomaticReleases\Changelog\BumpAndCommitChangelogVersionViaKeepAChangelog;
@@ -24,6 +25,7 @@
2425
use Laminas\AutomaticReleases\Git\PushViaConsole;
2526
use Laminas\AutomaticReleases\Github\Api\GraphQL\Query\GetMilestoneFirst100IssuesAndPullRequests;
2627
use Laminas\AutomaticReleases\Github\Api\GraphQL\RunGraphQLQuery;
28+
use Laminas\AutomaticReleases\Github\Api\V3\CreateMilestoneThroughApiCall;
2729
use Laminas\AutomaticReleases\Github\Api\V3\CreatePullRequestThroughApiCall;
2830
use Laminas\AutomaticReleases\Github\Api\V3\CreateReleaseThroughApiCall;
2931
use Laminas\AutomaticReleases\Github\Api\V3\SetDefaultBranchThroughApiCall;
@@ -152,6 +154,15 @@ static function (int $errorCode, string $message = '', string $file = '', int $l
152154
$getCandidateBranches,
153155
$bumpChangelogVersion
154156
),
157+
new CreateMilestones(
158+
$loadEvent,
159+
new CreateMilestoneThroughApiCall(
160+
$makeRequests,
161+
$httpClient,
162+
$githubToken,
163+
$logger
164+
)
165+
),
155166
]);
156167

157168
$application->run();

examples/.github/workflows/release-on-milestone-closed-triggering-release-event.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,13 @@ jobs:
5959
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
6060
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
6161
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
62+
63+
- name: "Create new milestones"
64+
uses: "laminas/automatic-releases@v1"
65+
with:
66+
command-name: "laminas:automatic-releases:create-milestones"
67+
env:
68+
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
69+
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
70+
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
71+
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}

examples/.github/workflows/release-on-milestone-closed.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ jobs:
5252
command-name: "laminas:automatic-releases:bump-changelog"
5353
env:
5454
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
55+
56+
- name: "Create new milestones"
57+
uses: "laminas/automatic-releases@v1"
58+
with:
59+
command-name: "laminas:automatic-releases:create-milestones"
60+
env:
61+
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
5562
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
5663
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
5764
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Application\Command;
6+
7+
use Laminas\AutomaticReleases\Git\Value\SemVerVersion;
8+
use Laminas\AutomaticReleases\Github\Api\V3\CreateMilestone;
9+
use Laminas\AutomaticReleases\Github\Api\V3\CreateMilestoneFailed;
10+
use Laminas\AutomaticReleases\Github\Event\Factory\LoadCurrentGithubEvent;
11+
use Laminas\AutomaticReleases\Github\Value\RepositoryName;
12+
use Symfony\Component\Console\Command\Command;
13+
use Symfony\Component\Console\Input\InputInterface;
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
16+
final class CreateMilestones extends Command
17+
{
18+
private LoadCurrentGithubEvent $loadEvent;
19+
private CreateMilestone $createMilestone;
20+
21+
public function __construct(
22+
LoadCurrentGithubEvent $loadEvent,
23+
CreateMilestone $createMilestone
24+
) {
25+
parent::__construct('laminas:automatic-releases:create-milestones');
26+
27+
$this->loadEvent = $loadEvent;
28+
$this->createMilestone = $createMilestone;
29+
}
30+
31+
public function execute(InputInterface $input, OutputInterface $output): int
32+
{
33+
$milestoneClosedEvent = ($this->loadEvent)();
34+
$repositoryName = $milestoneClosedEvent->repository();
35+
$releaseVersion = $milestoneClosedEvent->version();
36+
37+
$this->createMilestoneIfNotExists($repositoryName, $releaseVersion->nextPatch());
38+
$this->createMilestoneIfNotExists($repositoryName, $releaseVersion->nextMinor());
39+
$this->createMilestoneIfNotExists($repositoryName, $releaseVersion->nextMajor());
40+
41+
return 0;
42+
}
43+
44+
private function createMilestoneIfNotExists(RepositoryName $repositoryName, SemVerVersion $version): bool
45+
{
46+
try {
47+
($this->createMilestone)($repositoryName, $version);
48+
} catch (CreateMilestoneFailed $e) {
49+
return false;
50+
}
51+
52+
return true;
53+
}
54+
}

src/Git/Value/SemVerVersion.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,21 @@ public function minor(): int
5454
return $this->minor;
5555
}
5656

57+
public function nextPatch(): self
58+
{
59+
return new self($this->major, $this->minor, $this->patch + 1);
60+
}
61+
5762
public function nextMinor(): self
5863
{
5964
return new self($this->major, $this->minor + 1, 0);
6065
}
6166

67+
public function nextMajor(): self
68+
{
69+
return new self($this->major + 1, 0, 0);
70+
}
71+
6272
public function targetReleaseBranchName(): BranchName
6373
{
6474
return BranchName::fromName($this->major . '.' . $this->minor . '.x');

src/Github/Api/V3/CreateMilestone.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Github\Api\V3;
6+
7+
use Laminas\AutomaticReleases\Git\Value\SemVerVersion;
8+
use Laminas\AutomaticReleases\Github\Value\RepositoryName;
9+
10+
interface CreateMilestone
11+
{
12+
public function __invoke(
13+
RepositoryName $repository,
14+
SemVerVersion $version
15+
): void;
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Github\Api\V3;
6+
7+
use RuntimeException;
8+
9+
use function sprintf;
10+
11+
class CreateMilestoneFailed extends RuntimeException
12+
{
13+
public static function forVersion(string $version): self
14+
{
15+
return new self(sprintf('Milestone "%s" creation failed', $version));
16+
}
17+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Github\Api\V3;
6+
7+
use Laminas\AutomaticReleases\Git\Value\SemVerVersion;
8+
use Laminas\AutomaticReleases\Github\Value\RepositoryName;
9+
use Psr\Http\Client\ClientInterface;
10+
use Psr\Http\Message\RequestFactoryInterface;
11+
use Psr\Log\LoggerInterface;
12+
use Webmozart\Assert\Assert;
13+
14+
use function Safe\json_decode;
15+
use function Safe\json_encode;
16+
use function sprintf;
17+
18+
final class CreateMilestoneThroughApiCall implements CreateMilestone
19+
{
20+
private const API_ROOT = 'https://api.github.com/';
21+
22+
private RequestFactoryInterface $messageFactory;
23+
24+
private ClientInterface $client;
25+
26+
/** @psalm-var non-empty-string */
27+
private string $apiToken;
28+
29+
private LoggerInterface $logger;
30+
31+
/** @psalm-param non-empty-string $apiToken */
32+
public function __construct(
33+
RequestFactoryInterface $messageFactory,
34+
ClientInterface $client,
35+
string $apiToken,
36+
LoggerInterface $logger
37+
) {
38+
$this->messageFactory = $messageFactory;
39+
$this->client = $client;
40+
$this->apiToken = $apiToken;
41+
$this->logger = $logger;
42+
}
43+
44+
public function __invoke(RepositoryName $repository, SemVerVersion $version): void
45+
{
46+
$this->logger->info(sprintf(
47+
'[CreateMilestoneThroughApiCall] Creating milestone "%s" for "%s/%s"',
48+
$version->fullReleaseName(),
49+
$repository->owner(),
50+
$repository->name()
51+
));
52+
53+
$request = $this->messageFactory
54+
->createRequest(
55+
'POST',
56+
self::API_ROOT . 'repos/' . $repository->owner() . '/' . $repository->name() . '/milestones'
57+
)
58+
->withAddedHeader('Content-Type', 'application/json')
59+
->withAddedHeader('User-Agent', 'Ocramius\'s minimal API V3 client')
60+
->withAddedHeader('Authorization', 'token ' . $this->apiToken);
61+
62+
$request
63+
->getBody()
64+
->write(json_encode([
65+
'title' => $version->fullReleaseName(),
66+
]));
67+
68+
$response = $this->client->sendRequest($request);
69+
70+
$responseBody = $response
71+
->getBody()
72+
->__toString();
73+
74+
$responseData = json_decode($responseBody, true);
75+
Assert::isMap($responseData);
76+
77+
if ($response->getStatusCode() !== 201) {
78+
$this->logger->error(
79+
sprintf(
80+
'[CreateMilestoneThroughApiCall] Failed to create milestone "%s"',
81+
$version->fullReleaseName()
82+
),
83+
['exception' => $responseData]
84+
);
85+
86+
throw CreateMilestoneFailed::forVersion($version->fullReleaseName());
87+
}
88+
89+
Assert::eq($response->getStatusCode(), 201);
90+
91+
$this->logger->info(sprintf(
92+
'[CreateMilestoneThroughApiCall] Milestone "%s" created',
93+
$version->fullReleaseName()
94+
));
95+
}
96+
}

0 commit comments

Comments
 (0)