* Reconcile the release target when updating an existing release
A pre-existing draft release (for example one created by the Release Drafter job on the default branch) kept its original target_commitish, because --target was only passed on the create path. Reconcile it on the update path too, and require target_ref to be chosen explicitly instead of defaulting to main.
* Clean up comments in create-release.yml
Removed comments about reconciling target for releases.
* Refine target_ref description for better understanding
Clarify target_ref description in create-release.yml
* Refuse to attach an archive to a tag pointing at a different commit
An existing tag pins the release to a commit, and both --target on create and target_commitish on update are ignored once it exists. Verify the tag's peeled commit matches the checked-out ref before uploading so a re-run against a moved branch cannot publish an artifact that does not match the tag.
* Validate the release tag before uploading the archive to R2
The tag/commit check ran inside the GitHub release step, after the archive had already been mirrored to R2, so a mismatched tag could overwrite the version-specific download object before the job failed. Run it as a standalone step ahead of the R2 upload instead.
* Upgrade Tabler to 1.6 and adopt its admin components
* Fix spellcheck for Tabler integration notes and tests
* Build admin widget assets before Playwright tests
* Fix date input synchronization and clipboard fallback
* Refresh clipboard instances after mapping legacy targets
* Support Tabler clipboard attribute alias in fallback
* Add open-source gettext extractor and translation source check
Replaces the manual Poedit Pro step for regenerating messages.pot.
The extractor covers Twig |trans literals and PHP __trans /
__pluralTrans / exception keywords, mirroring the historical Poedit
runs (validated against the May 2026 messages.pot: only obsolete
fossils differ). Also adds an informational per-PR check reporting
msgid changes, as gettext matching is case-sensitive and casing-only
changes silently invalidate translations.
* Remove AI slop from extractor and workflow
Drop the unused function, unused parameters, unreachable branches,
restating comments and oversized docstring. No behavior change:
output verified byte-identical.
* Fix actions/github-script pin to a resolvable SHA
* Refresh i18n POT header and check label
* Rename translation workflow title
* Run translation source check on ubuntu-slim
* Ignore Python bytecode cache in scripts dir
* Address review: plural edge case and workflow injection
Skip __pluralTrans calls without a complete static plural instead
of emitting a misleading singular entry. Pass the pot diff to the
comment script via env to close the template-injection vector.
* Address review: one-to-one casing match and comment pagination
Consume each removed msgid at most once when pairing casing-only
changes, and paginate issue comments when locating the sticky
check comment.
* Address review: string-content calls, concat fragments, plural diff, credentials
Ignore keyword-like text inside PHP string/heredoc content, skip
concatenated first arguments instead of recording untranslatable
fragments (13 such fragments dropped from current output), report
plural changes across casing pairs, and set persist-credentials:
false with a non-failing comment step for fork PRs.
* Skip PHP string-content matches and fix heredoc terminators
Track string/heredoc spans during comment masking and ignore
keyword-like text inside them; also accept comma-terminated
flexible heredocs. Removes phantom refs and entries, keeps real
calls with correct line numbers (verified: timeago entries
intact, May-validation residuals all still excluded-path
fossils or untranslatable fragments).
* Mirror release archives to R2 for IPv6-only installs (#2479)
github.com and api.github.com have no AAAA record, so FOSSBilling
installs on IPv6-only hosts (no NAT64) can check for updates via
api.fossbilling.net but can never actually download one - the
download_url it returns always points at a GitHub release asset.
The preview update channel already solves this: its download_url is
required to come from download.fossbilling.org, a Cloudflare R2
bucket that is confirmed reachable over IPv6. This extends the same
mirroring to stable releases:
- create-release.yml now uploads each release archive to R2 as well
as the GitHub release, keyed by version
(releases/{version}/FOSSBilling-{version}.zip) so old releases
stay resolvable. The SHA-256 digest is computed once from the
uploaded file and stored as R2 object metadata, so it can be read
back without re-downloading and re-hashing.
- Update.php now trusts https://download.fossbilling.org/releases/
as a download source, alongside the existing GitHub prefixes.
This only covers the in-repo half. api.fossbilling.net still needs
to start returning the download.fossbilling.org mirror URL (and its
digest) instead of the GitHub asset URL for the release branch -
that's tracked separately against that project.
* Remove inert --acl public-read from R2 uploads (#4256)
R2 doesn't implement S3 object-level ACLs; public access to
fossbilling-download objects is granted entirely at the bucket level
via the download.fossbilling.org custom-domain binding. The flag is
silently accepted but has no effect - confirmed against Cloudflare's
R2 docs and empirically via curl -6 against existing objects. Drop it
from both R2 upload steps (ci.yml preview upload, create-release.yml
release mirror) so it doesn't mislead someone into thinking it's what
makes these objects public.
Follow-up to a CodeRabbit comment on #4255, declined there as
out-of-scope since fixing only the new job would've been inconsistent
with the pre-existing pattern in ci.yml.
The 'Load Secrets for FOSSBilling Bot' step omitted with.version,
installing the latest 1Password CLI on every scheduled run instead of
a pinned, tested version. Same class of finding CodeRabbit raised on
PR #4249's release-sentry.yml, which uses the same action.
* Report published releases to Sentry via sentry-cli
Adds a dedicated release-sentry.yml workflow, triggered on the GitHub
'release published' event (same trigger release-docker.yml already
uses), that creates/finalizes the matching Sentry release and
associates the commit range since the previous one (set_commits:
auto).
This intentionally does NOT live in create-release.yml: that workflow
defaults to creating a *draft* release (inputs.draft defaults to
true), and can be re-run multiple times against the same draft before
it's ever actually published. Reporting to Sentry from there would
mark a version 'released' before it's actually out, and would fire
again on every draft-update re-run. Triggering on 'release published'
instead captures the real moment a version becomes available -
whether that's a direct non-draft run or a draft published later by
hand - and only fires once.
This is separate from GitHub's repo-level commit-tracking integration
(which only feeds suspect-commit metadata generally) - nothing in the
existing setup was creating real Release objects with commit data for
0.8.1 onward, which is why every recent release shows no commits in
Sentry.
Needs a SENTRY_AUTH_TOKEN repo secret (project:releases + org:read
scopes) to actually run; until it's added the step just fails
harmlessly (continue-on-error: true).
* Report releases to Sentry as package@version for semver parsing
Sentry only parses a release identifier as semver (populating
major/minor/patch, enabling version-order regression detection and
release:latest) when it's in `package@version` form - a bare version
string like "0.8.6" doesn't qualify. Without it, Sentry falls back to
comparing release *creation dates* for things like 'resolved in next
release', which is unreliable for us specifically: an old release's
dateCreated is whenever some install still running it happens to send
its first event under the current Sentry project, not when the
version actually shipped (see the release-date backfill from earlier
this session - several were years off).
Adds SentryHelper::SENTRY_RELEASE_PACKAGE ('fossbilling') and reports
release as `fossbilling@{Version::VERSION}` going forward. Deliberately
not composer.json's "fossbilling/fossbilling" package name - Sentry
release identifiers can't contain a "/".
Updates release-sentry.yml's version input to match exactly
(fossbilling@<tag>), since the SDK's reported release string and what
CI associates commits/finalizes against have to agree or they won't
line up.
This only affects releases cut from here forward - already-shipped
0.6.x/0.7.x/0.8.x installs keep reporting their bare version string
forever, and there's no way to retroactively fix that.
* Load SENTRY_AUTH_TOKEN from 1Password instead of a raw repo secret
Matches auto-cs-fix.yml's existing pattern (1password/load-secrets-action,
reusing the same OP_SERVICE_ACCOUNT_TOKEN secret already configured for
that workflow) rather than asking for a new plaintext SENTRY_AUTH_TOKEN
GitHub secret. No new repo secret needed - just 1Password-side read access
to the vault item for the existing service account.
* Trim comments to match this repo's actual house style
Both files ended up with multi-paragraph inline explanations for
things that either duplicate each other, or belong in the PR
description instead of permanent code comments - out of step with
this repo's workflows (release-docker.yml has zero comments in 60
lines, create-release.yml has 1 in 279) and with SentryHelper.php's
own existing comment density elsewhere.
* Clarify Sentry auth token comment in workflow
* Address CodeRabbit findings: VM runner, pinned op CLI, release input
- runs-on: ubuntu-latest instead of ubuntu-slim - getsentry/action-release
runs as a docker:// container action on Linux, and ubuntu-slim can't run
a Docker daemon (unprivileged, confirmed against GitHub's own runner docs).
This would very likely have failed on first real use.
- Pin 1password/load-secrets-action's CLI version instead of installing
latest on every run (matches the currently-installed 2.39.0).
- Use the release: input instead of version:, which action-release marks
deprecated as of this pinned SHA.
Declined a fourth suggestion (enforce release-version equality against
FOSSBilling\Version::VERSION in create-release.yml): Version::VERSION is
written FROM inputs.version during the Docker build (regex substitution
that hard-fails if it doesn't apply exactly once), not sourced
independently, so it can't diverge - see reply on the PR thread.
* 🎨 Apply pending Rector/PHP-CS-Fixer fixes
Drift left over from the auto-cs-fix.yml run that failed to open its
PR due to an expired BOT_TOKEN.
* 🐛 Fix code quality workflow and add PR-level style check
- Rotate the sweep's push credential from BOT_TOKEN (expired) to
FOSSBILLING_BOT_TOKEN, a fresh fine-grained PAT scoped to just this
repo (contents, pull-requests, issues: read/write).
- Factor the shared Rector/PHP-CS-Fixer setup out of auto-cs-fix.yml
into a local composite action, .github/actions/php-quality-setup.
- Add code-quality-check.yml: a read-only dry-run check that runs on
every pull request, including forks, scoped to just the files each
PR changes so pre-existing drift on main can't fail an unrelated
PR. Complements the scheduled sweep, which now only needs to catch
drift from rule/tool-version changes on code no open PR touches.
* 🎨 Replace peter-evans/create-pull-request with gh CLI
Removes the third-party Action dependency for opening the sweep's PR
in favor of the officially-maintained gh CLI, which ships preinstalled
on GitHub-hosted runners:
- gh auth setup-git wires GH_TOKEN into git's credential helper, so
plain git push/fetch authenticate without a hand-rolled
http.extraheader (what silently broke with the old BOT_TOKEN).
- chore/cs-fix is fully regenerated every run, so there's no need to
diff against existing remote content — force-with-lease push plus
gh pr list/create covers create-or-update.
- Also more portable: the same script runs unchanged outside GitHub
Actions' action-invocation model (other CI, cron, a terminal),
whereas a third-party Action cannot.
- Top-level permissions trimmed to contents: read — nothing in the
job uses the implicit GITHUB_TOKEN anymore.
* 🔒 Pin 1password/load-secrets-action to v5.0.1 by SHA
Was floating on the mutable @v4 tag, inconsistent with every other
third-party action in this repo (all pinned by commit SHA), and a
version behind — v5.0.1 is current as of last week and includes
1Password's own CI hardening. No breaking changes for our usage
(OP_SERVICE_ACCOUNT_TOKEN path is untouched; v5.0.0 only adds an
alternative OIDC auth method).
* 🎨 Remove unused create_pr operation output
Set in both branches but never read — the job summary only uses
.outputs.url, and the same created-vs-updated info is already in the
step's own log output.
* 🐛 Fix two Rector rules that generate code breaking Pest
CI on PR #4206 caught these — I'd only run php -l (syntax check) on
the earlier drift-cleanup commit, not the actual test suite, and both
slipped through:
- AddArrowFunctionReturnTypeRector typed Pest expect()-chain closures
as their IDE-only @mixin stub (Pest\Mixins\Expectation) instead of
the real runtime class (Pest\Expectation) — throws a TypeError.
- ArrowFunctionDelegatingCallToFirstClassCallableRector converted a
Pest ->with() dataset closure to a first-class callable reference,
which breaks Pest's Closure::bindTo() lazy-evaluation step (no scope
to rebind on a closure derived from a plain function).
Both only misfire against Pest's dynamic patterns in tests/, so
they're skipped there specifically rather than repo-wide — src/ keeps
the benefit. Reverts the two bad transformations and re-verifies with
the full Pest suite this time, not just php -l.
* 🎨 Apply Rector fixes for drift introduced by #4205
RecastingRemovalRector: three redundant (string) casts, plus one
PHP 8.3 typed-const/literal-return-type modernization in
CacheFactory.php — all from files #4205 touched after this branch
forked from main.
Verified with the full suite this time (2812 tests, 10979 assertions)
and PHPStan, not just php -l.
* 🎨 Fix import order in rector.php
Caught by the new PR check itself — it passes rector.php as one of
the changed files, so PHP-CS-Fixer processes it directly (path-mode
override), unlike the scheduled sweep whose Finder never scans
repo-root config files at all.
* 🔒 Fix script-injection risk and stale fetch ref from CodeRabbit review
- code-quality-check.yml: the changed-file list was interpolated via
${{ steps.changed_files.outputs.files }} directly into a run: script.
That's literal text substitution before bash parses anything, so a
PHP filename containing shell metacharacters (or just a space) could
break argument parsing or execute as a command. Fixed by writing the
NUL-delimited path list to a file and reading it back into a real
bash array with mapfile in every step that needs it — only a plain
boolean (has_php_files) now crosses the ${{ }} boundary. Verified:
a space-containing filename was silently split into two broken
arguments under the old design; the array-based version preserves
it as one element.
- auto-cs-fix.yml: pin the fetch to update the actual remote-tracking
ref (refs/remotes/origin/chore/cs-fix), not just FETCH_HEAD, so
--force-with-lease always has a real baseline to compare against.
* 🔒 Add option-terminator before changed-file paths
A filename beginning with '-' would otherwise be parsed as a Rector/
PHP-CS-Fixer CLI option rather than a path. Not currently reachable
given rector.php/.php-cs-fixer.dist.php scope to src/ and tests/ (a
repo-relative diff path always has that prefix), but zero-cost to
harden regardless. Verified the composed composer script -- ... -- ...
invocation forwards the second terminator correctly.
* 🎨 Apply Rector/CS-Fixer fixes for drift introduced by #4204
readonly on Registrar_Rdap::$httpClient (verified: only ever assigned
in the constructor), PHP 8.3 typed consts, redundant @param mixed
removal, pre-increment style. Verified with the full suite (2854
tests, 11055 assertions) and PHPStan.
* Migrate E2E suite from Cypress to Playwright
* Address review feedback for Playwright migration
Use Symfony Filesystem::mkdir() for the test uploads directory instead of
suppressed native mkdir, and tolerate an optional trailing slash in the
admin smoke test URL assertions.
* Add preview archive metadata to CI upload
* Fix preview artifact upload in CI
* Add short SHA to preview artifact name
* Version preview ZIP artifacts by commit
* Simplify preview artifact staging
* Fix short SHA export in CI preview step