121 lines
5.0 KiB
YAML
121 lines
5.0 KiB
YAML
# Release, Go library. Runs on a version tag (v1.2.3) pushed to main.
|
|
#
|
|
# The pipeline does one thing: it publishes the release from the tag, notes and
|
|
# all. No gate runs here. The tagged tree was tested on every push to
|
|
# development, the deep suite is the suite workflow's business, and race never
|
|
# runs in CI at all. A library ships no assets, so there is no build and
|
|
# nothing to upload.
|
|
#
|
|
# Every step is one command, and the scripted steps are Perl with builtins
|
|
# only. Perl drives curl through a list, so no argument is ever word-split,
|
|
# globbed or quoted wrong.
|
|
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags: ["v*"]
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: fedora
|
|
permissions:
|
|
# contents: read is required for the checkout: a job that declares any
|
|
# permissions gets a token scoped to exactly those, and releases: write
|
|
# alone leaves the fetch with no read access, which Gitea answers with
|
|
# a 404 "Repository not found". Verified on the instance 2026-09-16.
|
|
contents: read
|
|
releases: write
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Validate the tag
|
|
env:
|
|
VERSION: ${{ gitea.ref_name }}
|
|
run: |
|
|
perl -e '
|
|
my $v = $ENV{VERSION} // q{};
|
|
$v =~ m{^v[0-9]+(\.[0-9]+){0,2}([-+].*)?$}
|
|
or die qq{ERROR: expected a semver tag like v1.2.3, got: $v\n};
|
|
print qq{tag $v\n};
|
|
'
|
|
|
|
- name: Extract the CHANGELOG section
|
|
env:
|
|
VERSION: ${{ gitea.ref_name }}
|
|
run: |
|
|
# Each step derives what it needs from the tag, so no value has to travel
|
|
# between steps.
|
|
perl -e '
|
|
my $v = $ENV{VERSION} // q{};
|
|
$v =~ s{^v}{};
|
|
open(my $vout, q{>}, q{version-no-v.txt}) or die qq{version-no-v.txt: $!};
|
|
print $vout $v;
|
|
close($vout);
|
|
open(my $in, q{<}, q{CHANGELOG.md}) or die qq{CHANGELOG.md: $!};
|
|
my @lines = <$in>;
|
|
close($in);
|
|
my ($start, $end) = (-1, scalar @lines);
|
|
for my $i (0 .. $#lines) {
|
|
if ($start < 0) { $start = $i if $lines[$i] =~ m{^##\s+\[\Q$v\E\]} }
|
|
elsif ($lines[$i] =~ m{^##\s+\[}) { $end = $i; last }
|
|
}
|
|
$start >= 0 or die qq{ERROR: no CHANGELOG section for $v, expected a heading like: ## [$v] - YYYY-MM-DD\n};
|
|
my @body = grep { m{\S} } @lines[$start + 1 .. $end - 1];
|
|
@body or die qq{ERROR: the CHANGELOG section for $v is empty\n};
|
|
open(my $out, q{>}, q{release-body.md}) or die qq{release-body.md: $!};
|
|
print $out @body;
|
|
close($out);
|
|
printf qq{notes for %s: %d lines\n}, $v, scalar @body;
|
|
'
|
|
|
|
- name: Build the release request
|
|
run: |
|
|
perl -e '
|
|
open(my $vin, q{<}, q{version-no-v.txt}) or die qq{version-no-v.txt: $!};
|
|
my $v = <$vin>;
|
|
close($vin);
|
|
chomp $v;
|
|
open(my $in, q{<:raw}, q{release-body.md}) or die qq{release-body.md: $!};
|
|
my $body = do { local $/; <$in> };
|
|
close($in);
|
|
# Byte-oriented escaping: JSON is UTF-8, so non-ASCII passes through and
|
|
# only the characters JSON forbids are rewritten.
|
|
$body =~ s/([\\"])/\\$1/g;
|
|
$body =~ s/\t/\\t/g;
|
|
$body =~ s/\r//g;
|
|
$body =~ s/\n/\\n/g;
|
|
$body =~ s/([\x00-\x08\x0b\x0c\x0e-\x1f])/sprintf(q{\u%04x}, ord($1))/ge;
|
|
my $json = sprintf(qq{{"tag_name":"v%s","name":"v%s","body":"%s","draft":false,"prerelease":false}}, $v, $v, $body);
|
|
open(my $out, q{>}, q{release.json}) or die qq{release.json: $!};
|
|
print $out $json;
|
|
close($out);
|
|
print qq{release.json written for v$v\n};
|
|
'
|
|
|
|
- name: Create the release
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
|
GITEA_REPOSITORY: ${{ gitea.repository }}
|
|
run: |
|
|
perl -e '
|
|
my @cmd = (q{curl}, q{-sS}, q{-o}, q{response.json}, q{-w}, q{%{http_code}},
|
|
q{-H}, qq{Authorization: token $ENV{GITEA_TOKEN}},
|
|
q{-H}, q{Content-Type: application/json},
|
|
q{-X}, q{POST},
|
|
qq{$ENV{GITEA_SERVER_URL}/api/v1/repos/$ENV{GITEA_REPOSITORY}/releases},
|
|
q{--data-binary}, q{@release.json});
|
|
open(my $curl, q{-|}, @cmd) or die qq{curl: $!};
|
|
my $code = <$curl>;
|
|
my $ok = close($curl);
|
|
my $exit = $? >> 8;
|
|
$code = defined $code ? $code : q{};
|
|
$ok or die qq{ERROR: curl failed (exit $exit) calling $ENV{GITEA_SERVER_URL}\n};
|
|
open(my $r, q{<:raw}, q{response.json}) or die qq{response.json: $!};
|
|
my $body = do { local $/; <$r> };
|
|
close($r);
|
|
$code eq q{201} or die qq{ERROR: the release was not created, HTTP $code: $body\n};
|
|
$body =~ m{"id"\s*:\s*([0-9]+)} or die qq{ERROR: no release id in the response: $body\n};
|
|
print qq{release v$ENV{VERSION} is live (id $1)\n};
|
|
'
|