Skip to content

Commit eb7bc07

Browse files
authored
Merge pull request #69 from ConvertAPI/ci/trunk-based-release
Trunk-based releases from GitHub Releases; 3.0.1
2 parents cb4cc18 + 6e4a259 commit eb7bc07

14 files changed

Lines changed: 498 additions & 51 deletions

File tree

‎.github/dependabot.yml‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Weekly dependency updates, each opened as a pull request against main that must pass the `build` check
2+
# and is merged like any other change (merge commit). Merging releases nothing: a new Packagist version
3+
# still needs a GitHub Release (see RELEASING.md).
4+
version: 2
5+
updates:
6+
# The workflows pin every action to a commit SHA with a version comment; Dependabot updates both.
7+
- package-ecosystem: github-actions
8+
directory: /
9+
schedule:
10+
interval: weekly
11+
day: monday
12+
groups:
13+
github-actions:
14+
patterns: ["*"]
15+
commit-message:
16+
prefix: ci
17+
labels: [dependencies]
18+
19+
# The library itself has no Composer dependencies; this keeps the test tooling (PHPUnit) current.
20+
- package-ecosystem: composer
21+
directory: /
22+
schedule:
23+
interval: weekly
24+
day: monday
25+
groups:
26+
composer-minor-and-patch:
27+
update-types: [minor, patch]
28+
commit-message:
29+
prefix: deps
30+
labels: [dependencies]

‎.github/workflows/build.yml‎

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
name: Build
2+
run-name: >-
3+
${{ github.event_name == 'pull_request' && format('Checking that #{0} builds', github.event.pull_request.number)
4+
|| format('Building {0}', github.ref_name) }}
5+
6+
# Trunk-based: main is the only long-lived branch. Every pull request and every push to main validates
7+
# composer.json, lints every PHP file and runs the offline tests on each supported PHP version. Nothing is
8+
# released here: Packagist publishes a version when a v{semver} tag appears, and tags come only from a
9+
# GitHub Release (see RELEASING.md and release.yml).
10+
#
11+
# The required status check is the `build` job, which passes only when every PHP version passed. The
12+
# `integration` job calls the ConvertAPI API with the CONVERT_API_SECRET secret; it is not required, so an
13+
# API outage cannot block a merge, and it is skipped where the secret is unavailable (forks, Dependabot).
14+
on:
15+
pull_request:
16+
branches: [main]
17+
push:
18+
branches: [main]
19+
workflow_dispatch:
20+
21+
permissions:
22+
contents: read
23+
24+
concurrency:
25+
# A newer push to the same pull request cancels the older build; builds of main never cancel each other.
26+
group: build-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.run_id }}
27+
cancel-in-progress: true
28+
29+
jobs:
30+
test:
31+
name: PHP ${{ matrix.php }}
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 15
34+
strategy:
35+
fail-fast: false
36+
matrix:
37+
php: ['8.1', '8.2', '8.3', '8.4', '8.5']
38+
steps:
39+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
40+
with:
41+
persist-credentials: false
42+
43+
- uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
44+
with:
45+
php-version: ${{ matrix.php }}
46+
tools: composer:v2
47+
coverage: none
48+
49+
- name: Validate composer.json
50+
run: composer validate --strict
51+
52+
- name: Install
53+
run: composer install --no-interaction --no-progress --prefer-dist
54+
55+
- name: Lint
56+
run: |
57+
set -euo pipefail
58+
find lib tests examples -name '*.php' -print0 | xargs -0 -n1 php -l >/dev/null
59+
60+
- name: Offline tests
61+
run: bin/phpunit --exclude-group integration
62+
63+
integration:
64+
name: integration (PHP ${{ matrix.php }})
65+
needs: test
66+
runs-on: ubuntu-latest
67+
timeout-minutes: 15
68+
strategy:
69+
fail-fast: false
70+
matrix:
71+
php: ['8.1', '8.5']
72+
env:
73+
CONVERT_API_SECRET: ${{ secrets.CONVERT_API_SECRET }}
74+
CONVERT_API_BASE: ${{ vars.CONVERT_API_BASE }}
75+
steps:
76+
- name: Check for the API secret
77+
id: secret
78+
run: |
79+
if [ -z "$CONVERT_API_SECRET" ]; then
80+
echo "::notice::CONVERT_API_SECRET is not available to this run (a fork or Dependabot pull request), so the integration tests are skipped."
81+
echo "run=false" >> "$GITHUB_OUTPUT"
82+
else
83+
echo "run=true" >> "$GITHUB_OUTPUT"
84+
fi
85+
86+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
87+
if: steps.secret.outputs.run == 'true'
88+
with:
89+
persist-credentials: false
90+
91+
- uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
92+
if: steps.secret.outputs.run == 'true'
93+
with:
94+
php-version: ${{ matrix.php }}
95+
tools: composer:v2
96+
coverage: none
97+
98+
- name: Install
99+
if: steps.secret.outputs.run == 'true'
100+
run: composer install --no-interaction --no-progress --prefer-dist
101+
102+
- name: Integration tests against ${{ vars.CONVERT_API_BASE || 'https://v2.convertapi.com/' }}
103+
if: steps.secret.outputs.run == 'true'
104+
run: bin/phpunit --group integration
105+
106+
build:
107+
name: build
108+
needs: test
109+
if: always()
110+
runs-on: ubuntu-latest
111+
timeout-minutes: 5
112+
steps:
113+
- name: Check every PHP version passed
114+
env:
115+
RESULT: ${{ needs.test.result }}
116+
run: |
117+
if [ "$RESULT" != success ]; then
118+
echo "::error::The PHP version matrix finished with '$RESULT'."
119+
exit 1
120+
fi
121+
122+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
123+
with:
124+
fetch-depth: 0
125+
persist-credentials: false
126+
127+
# ConvertApi::VERSION goes into the User-Agent of every request, and release.yml refuses a tag that
128+
# does not match it. It may run ahead of the latest release, never behind it.
129+
- name: Check the version constant
130+
id: version
131+
run: |
132+
set -euo pipefail
133+
VERSION=$(sed -n "s/^ *const VERSION = '\(.*\)';.*/\1/p" lib/ConvertApi/ConvertApi.php)
134+
SEMVER='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
135+
if ! [[ "$VERSION" =~ $SEMVER ]]; then
136+
echo "::error file=lib/ConvertApi/ConvertApi.php::ConvertApi::VERSION '$VERSION' is not {major}.{minor}.{patch}[-{pre-release}]"
137+
exit 1
138+
fi
139+
LATEST=$(git tag --list 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sed 's/^v//' | sort -V | tail -n1)
140+
if [ -n "$LATEST" ] && [ "$(printf '%s\n%s\n' "$LATEST" "${VERSION%%-*}" | sort -V | tail -n1)" != "${VERSION%%-*}" ]; then
141+
echo "::error file=lib/ConvertApi/ConvertApi.php::ConvertApi::VERSION $VERSION is lower than the latest release $LATEST"
142+
exit 1
143+
fi
144+
{
145+
echo "version=$VERSION"
146+
echo "latest=$LATEST"
147+
} >> "$GITHUB_OUTPUT"
148+
149+
- name: Summary
150+
env:
151+
VERSION: ${{ steps.version.outputs.version }}
152+
LATEST: ${{ steps.version.outputs.latest }}
153+
run: |
154+
{
155+
echo "| | |"
156+
echo "|---|---|"
157+
echo "| \`ConvertApi::VERSION\` | \`$VERSION\` (latest release: \`v$LATEST\`) |"
158+
echo "| PHP | 8.1, 8.2, 8.3, 8.4, 8.5 |"
159+
} >> "$GITHUB_STEP_SUMMARY"

‎.github/workflows/main.yml‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎.github/workflows/release.yml‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
name: Release
2+
run-name: Checking convertapi/convertapi-php ${{ github.event.release.tag_name }} on Packagist
3+
4+
# A release is a GitHub Release, never a merge. Merging to main only builds (build.yml). Publishing the
5+
# Release v{semver} creates the tag, and Packagist publishes every v* tag it sees through the repository's
6+
# GitHub hook - there is no upload step and no registry key. This workflow checks that the release is
7+
# legitimate and complete: who published it, that the tag is SemVer and on main, that ConvertApi::VERSION
8+
# matches it, that the tests pass against the API, and that Packagist serves the version from this commit.
9+
#
10+
# gh release create v3.0.1 --target main --generate-notes -> convertapi/convertapi-php v3.0.1
11+
# gh release create v3.1.0-beta.1 --target main --prerelease ... -> convertapi/convertapi-php v3.1.0-beta.1
12+
#
13+
# Packagist versions are public as soon as the tag exists, so a failure here does not stop a release; it
14+
# says the version needs a fix-forward release (see RELEASING.md). Nothing here retries or moves a tag.
15+
on:
16+
release:
17+
types: [published]
18+
19+
permissions:
20+
contents: read
21+
22+
env:
23+
# Only this account may publish a Release. GitHub cannot express this as a permission on the Team plan,
24+
# so it is checked here; the real control is the v* tag ruleset (only repository admins create v* tags).
25+
PUBLISHER: tomasr78
26+
PACKAGE: convertapi/convertapi-php
27+
28+
jobs:
29+
release:
30+
name: release
31+
runs-on: ubuntu-latest
32+
timeout-minutes: 30
33+
environment:
34+
name: packagist
35+
url: https://packagist.org/packages/convertapi/convertapi-php#${{ github.event.release.tag_name }}
36+
concurrency:
37+
group: release-packagist
38+
cancel-in-progress: false
39+
steps:
40+
- name: Check who published the release
41+
run: |
42+
# A re-run keeps GITHUB_ACTOR as the original user; GITHUB_TRIGGERING_ACTOR is whoever pressed
43+
# "Re-run", so both must be the publisher or anyone with write access could replay a release.
44+
if [ "$GITHUB_ACTOR" != "$PUBLISHER" ] || [ "${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}" != "$PUBLISHER" ]; then
45+
echo "::error::Only $PUBLISHER may publish a release - this release was published by $GITHUB_ACTOR and run by ${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}."
46+
exit 1
47+
fi
48+
echo "Published by $GITHUB_ACTOR"
49+
50+
# The release event runs this file from the tag, so the workflow and the code come from the same commit.
51+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
52+
with:
53+
fetch-depth: 0
54+
persist-credentials: false
55+
56+
- name: Check the tag and the version
57+
id: tag
58+
env:
59+
TAG: ${{ github.event.release.tag_name }}
60+
PRERELEASE: ${{ github.event.release.prerelease }}
61+
run: |
62+
set -euo pipefail
63+
# SemVer 2.0 without build metadata (Composer ignores "+...").
64+
SEMVER='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
65+
if ! [[ "$TAG" =~ $SEMVER ]]; then
66+
echo "::error::Tag '$TAG' is not v{major}.{minor}.{patch} or v{major}.{minor}.{patch}-{pre-release}, e.g. v3.0.1 or v3.1.0-beta.1"
67+
exit 1
68+
fi
69+
if [ "$GITHUB_REF" != "refs/tags/$TAG" ]; then
70+
echo "::error::The run is on $GITHUB_REF, not on the release tag refs/tags/$TAG"
71+
exit 1
72+
fi
73+
VERSION="${TAG#v}"
74+
SHA=$(git rev-parse "refs/tags/$TAG^{commit}")
75+
if [ "$(git rev-parse HEAD)" != "$SHA" ]; then
76+
echo "::error::Checked out $(git rev-parse HEAD), but $TAG points at $SHA"
77+
exit 1
78+
fi
79+
80+
# The GitHub "pre-release" tick and the version must agree, so a preview cannot pass for the
81+
# latest stable release on GitHub, and a stable version cannot hide as a pre-release.
82+
if [[ "$VERSION" == *-* ]]; then
83+
if [ "$PRERELEASE" != true ]; then
84+
echo "::error::$TAG is a pre-release version, so the GitHub Release must be marked as a pre-release (gh release create --prerelease)"
85+
exit 1
86+
fi
87+
elif [ "$PRERELEASE" = true ]; then
88+
echo "::error::$TAG is a stable version, but the GitHub Release is marked as a pre-release. Use a pre-release version such as ${TAG}-beta.1, or untick pre-release."
89+
exit 1
90+
fi
91+
if ! git merge-base --is-ancestor "$SHA" origin/main; then
92+
echo "::error::$TAG ($SHA) is not on main. Releases, pre-releases included, are tagged on main only."
93+
exit 1
94+
fi
95+
96+
# The library has no build step - Packagist serves the tagged tree as it is - so the version the
97+
# client reports in its User-Agent is the constant in the code, and it must be the tag's.
98+
CONSTANT=$(sed -n "s/^ *const VERSION = '\(.*\)';.*/\1/p" lib/ConvertApi/ConvertApi.php)
99+
if [ "$CONSTANT" != "$VERSION" ]; then
100+
echo "::error file=lib/ConvertApi/ConvertApi.php::ConvertApi::VERSION is '$CONSTANT' at $TAG, not '$VERSION'. Packagist already serves $TAG; release the next version with the constant bumped."
101+
exit 1
102+
fi
103+
104+
{
105+
echo "version=$VERSION"
106+
echo "sha=$SHA"
107+
} >> "$GITHUB_OUTPUT"
108+
echo "Releasing $PACKAGE $TAG from $SHA"
109+
110+
- uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
111+
with:
112+
php-version: '8.5'
113+
tools: composer:v2
114+
coverage: none
115+
116+
- name: Install
117+
run: |
118+
composer validate --strict
119+
composer install --no-interaction --no-progress --prefer-dist
120+
121+
# Every test, the API ones included. They run against CONVERT_API_BASE (the API the repository is
122+
# configured to test against) with CONVERT_API_SECRET, both set in the repository's Actions settings.
123+
- name: Tests
124+
env:
125+
CONVERT_API_SECRET: ${{ secrets.CONVERT_API_SECRET }}
126+
CONVERT_API_BASE: ${{ vars.CONVERT_API_BASE }}
127+
run: |
128+
if [ -z "$CONVERT_API_SECRET" ]; then
129+
echo "::error::The CONVERT_API_SECRET repository secret is not set, so the API tests cannot run."
130+
exit 1
131+
fi
132+
bin/phpunit
133+
134+
# The GitHub hook tells Packagist about the new tag within seconds; Packagist then crawls the
135+
# repository and adds the version, usually within a few minutes. The tag must be served from the
136+
# tagged commit, or Packagist has an older copy of the tag.
137+
- name: Wait for Packagist to serve the version
138+
env:
139+
TAG: ${{ github.event.release.tag_name }}
140+
SHA: ${{ steps.tag.outputs.sha }}
141+
run: |
142+
set -uo pipefail
143+
for _ in $(seq 1 30); do
144+
REF=$(curl -fsS -H 'Cache-Control: no-cache' "https://repo.packagist.org/p2/$PACKAGE.json" \
145+
| jq -r --arg p "$PACKAGE" --arg v "$TAG" '.packages[$p][] | select(.version == $v) | .source.reference // empty')
146+
if [ "$REF" = "$SHA" ]; then
147+
echo "$PACKAGE $TAG is on Packagist from $SHA"
148+
exit 0
149+
elif [ -n "$REF" ]; then
150+
echo "::error::Packagist serves $TAG from $REF, not from $SHA. Check https://packagist.org/packages/$PACKAGE"
151+
exit 1
152+
fi
153+
sleep 30
154+
done
155+
echo "::error::Packagist has not served $PACKAGE $TAG after 15 minutes. Check the GitHub hook (Settings > Webhooks) and https://packagist.org/packages/$PACKAGE, then press Update there."
156+
exit 1
157+
158+
- name: Summary
159+
env:
160+
TAG: ${{ github.event.release.tag_name }}
161+
SHA: ${{ steps.tag.outputs.sha }}
162+
API: ${{ vars.CONVERT_API_BASE || 'https://v2.convertapi.com/' }}
163+
run: |
164+
{
165+
echo "| | |"
166+
echo "|---|---|"
167+
echo "| Package | [\`$PACKAGE $TAG\`](https://packagist.org/packages/$PACKAGE#$TAG) |"
168+
echo "| Commit | \`$SHA\` |"
169+
echo "| Tested against | $API (PHP 8.5) |"
170+
} >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)