Skip to content

Commit 4131286

Browse files
committed
ci: add release asset workflow
1 parent 53cd410 commit 4131286

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

.github/scripts/build-phar.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
$root = dirname(__DIR__, 2);
6+
$pluginFile = $root . '/plugin.yml';
7+
if (!is_file($pluginFile)) {
8+
fwrite(STDERR, "plugin.yml was not found.\n");
9+
exit(1);
10+
}
11+
12+
$pluginYml = file_get_contents($pluginFile);
13+
if ($pluginYml === false) {
14+
fwrite(STDERR, "Cannot read plugin.yml.\n");
15+
exit(1);
16+
}
17+
18+
$name = readPluginValue($pluginYml, 'name');
19+
$version = readPluginValue($pluginYml, 'version');
20+
$safeName = sanitizeAssetPart($name);
21+
$safeVersion = sanitizeAssetPart($version);
22+
$dist = $root . '/dist';
23+
if (!is_dir($dist) && !mkdir($dist, 0775, true) && !is_dir($dist)) {
24+
fwrite(STDERR, "Cannot create dist directory.\n");
25+
exit(1);
26+
}
27+
28+
$pharPath = $dist . '/' . $safeName . '-' . $safeVersion . '.phar';
29+
if (is_file($pharPath) && !unlink($pharPath)) {
30+
fwrite(STDERR, "Cannot remove previous PHAR: {$pharPath}\n");
31+
exit(1);
32+
}
33+
34+
$phar = new Phar($pharPath);
35+
$phar->startBuffering();
36+
foreach ([
37+
'plugin.yml',
38+
'src',
39+
'resources',
40+
'icon.png',
41+
'icon.gif',
42+
'LICENSE',
43+
] as $entry) {
44+
addPath($phar, $root, $root . '/' . $entry);
45+
}
46+
$phar->setStub("<?php __HALT_COMPILER();\n");
47+
$phar->setSignatureAlgorithm(Phar::SHA256);
48+
$phar->stopBuffering();
49+
50+
$hash = hash_file('sha256', $pharPath);
51+
if (!is_string($hash)) {
52+
fwrite(STDERR, "Cannot calculate PHAR checksum.\n");
53+
exit(1);
54+
}
55+
56+
$asset = basename($pharPath);
57+
file_put_contents($dist . '/checksums.txt', $hash . ' ' . $asset . PHP_EOL);
58+
file_put_contents(
59+
$dist . '/release.env',
60+
implode("\n", [
61+
'NAME=' . $safeName,
62+
'VERSION=' . $safeVersion,
63+
'TAG=v' . $safeVersion,
64+
'PHAR=dist/' . $asset,
65+
]) . "\n"
66+
);
67+
68+
echo "Built {$asset}\n";
69+
echo "SHA-256 {$hash}\n";
70+
71+
function readPluginValue(string $pluginYml, string $key): string {
72+
if (preg_match('/^\s*' . preg_quote($key, '/') . '\s*:\s*["\']?([^"\'#\r\n]+)["\']?/m', $pluginYml, $matches) !== 1) {
73+
fwrite(STDERR, "plugin.yml is missing {$key}.\n");
74+
exit(1);
75+
}
76+
77+
return trim($matches[1]);
78+
}
79+
80+
function sanitizeAssetPart(string $value): string {
81+
$value = trim($value);
82+
if ($value === '' || preg_match('/^[A-Za-z0-9_.-]+$/', $value) !== 1) {
83+
fwrite(STDERR, "Invalid release asset value: {$value}\n");
84+
exit(1);
85+
}
86+
87+
return $value;
88+
}
89+
90+
function addPath(Phar $phar, string $root, string $path): void {
91+
if (!file_exists($path)) {
92+
return;
93+
}
94+
if (is_file($path)) {
95+
$phar->addFile($path, relativePath($root, $path));
96+
return;
97+
}
98+
if (!is_dir($path)) {
99+
return;
100+
}
101+
102+
$iterator = new RecursiveIteratorIterator(
103+
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)
104+
);
105+
foreach ($iterator as $file) {
106+
if ($file instanceof SplFileInfo && $file->isFile()) {
107+
$phar->addFile($file->getPathname(), relativePath($root, $file->getPathname()));
108+
}
109+
}
110+
}
111+
112+
function relativePath(string $root, string $path): string {
113+
return str_replace('\\', '/', substr($path, strlen($root) + 1));
114+
}

.github/workflows/release.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Release Assets
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
12+
concurrency:
13+
group: release-assets-${{ github.ref_name }}
14+
cancel-in-progress: false
15+
16+
jobs:
17+
release:
18+
name: Build and publish PHAR
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Setup PHP
26+
uses: shivammathur/setup-php@v2
27+
with:
28+
php-version: "8.2"
29+
extensions: curl, zip
30+
tools: composer:v2
31+
coverage: none
32+
33+
- name: Validate composer.json
34+
if: ${{ hashFiles('composer.json') != '' }}
35+
run: composer validate --strict
36+
37+
- name: Install Composer dependencies
38+
if: ${{ hashFiles('composer.json') != '' }}
39+
run: composer install --prefer-dist --no-interaction --no-progress
40+
41+
- name: Run quality checks
42+
if: ${{ hashFiles('composer.json') != '' }}
43+
run: composer run quality
44+
45+
- name: Build PHAR
46+
run: php -d phar.readonly=0 .github/scripts/build-phar.php
47+
48+
- name: Read release metadata
49+
id: metadata
50+
shell: bash
51+
run: |
52+
source dist/release.env
53+
if [[ "${{ github.event_name }}" == "push" && "${GITHUB_REF_NAME}" != "${TAG}" ]]; then
54+
echo "Tag ${GITHUB_REF_NAME} does not match plugin version ${TAG}" >&2
55+
exit 1
56+
fi
57+
echo "name=${NAME}" >> "$GITHUB_OUTPUT"
58+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
59+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
60+
echo "phar=${PHAR}" >> "$GITHUB_OUTPUT"
61+
62+
- name: Publish GitHub Release assets
63+
env:
64+
GH_TOKEN: ${{ github.token }}
65+
shell: bash
66+
run: |
67+
TAG="${{ steps.metadata.outputs.tag }}"
68+
TITLE="${{ steps.metadata.outputs.name }} ${{ steps.metadata.outputs.version }}"
69+
PHAR="${{ steps.metadata.outputs.phar }}"
70+
71+
if gh release view "$TAG" >/dev/null 2>&1; then
72+
gh release upload "$TAG" "$PHAR" dist/checksums.txt --clobber
73+
elif [[ "${{ github.event_name }}" == "push" ]]; then
74+
gh release create "$TAG" "$PHAR" dist/checksums.txt \
75+
--verify-tag \
76+
--title "$TITLE" \
77+
--generate-notes
78+
else
79+
gh release create "$TAG" "$PHAR" dist/checksums.txt \
80+
--target "$GITHUB_SHA" \
81+
--title "$TITLE" \
82+
--generate-notes
83+
fi
84+
85+
- name: Verify published PHAR
86+
shell: bash
87+
run: |
88+
TAG="${{ steps.metadata.outputs.tag }}"
89+
PHAR="${{ steps.metadata.outputs.phar }}"
90+
ASSET="$(basename "$PHAR")"
91+
URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${ASSET}"
92+
curl --fail --location --retry 10 --retry-delay 3 --retry-all-errors --output dist/published.phar "$URL"
93+
test "$(sha256sum dist/published.phar | cut -d' ' -f1)" = "$(sha256sum "$PHAR" | cut -d' ' -f1)"

0 commit comments

Comments
 (0)