From 1600f6deab9de94605a7518a11b5f05dbd0f9cec Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 5 May 2026 23:33:57 +0200 Subject: [PATCH 1/2] docs(github-release): document TER publish version-match + v-prefix gotchas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two TYPO3-specific failure modes were siloed in t3x-nr-passkeys-be's local memory; promote them into the skill so any TYPO3 release benefits. 1. tailor ter:publish hard-fails with "configured version does not match" if ext_emconf.php (or Documentation/guides.xml) version disagrees with the git tag. The release-prep PR pattern (release-process.md Phase 1-3) already prevents this; document why it matters so the ordering isn't broken in custom workflows. 2. Custom publish workflows that compare ${GITHUB_REF#refs/tags/} directly against ext_emconf.php compare v0.6.0 vs 0.6.0 and silently fail validation. Show the three-variable strip pattern (CHECKOUT_REF / TAG / VERSION). Note the shared netresearch/typo3-ci-workflows publish-to-ter.yml already implements this — only bespoke per-project workflows need to handle it. Add the new content to references/typo3-ter-publishing.md (separate from ter-republish.md, which is scoped to re-publish-without-re-tag). Add a SKILL.md references-list entry (497 words, under the 500 limit). Cross-link from ter-republish.md. Signed-off-by: Sebastian Mendel --- skills/github-release/SKILL.md | 5 +- .../references/ter-republish.md | 2 + .../references/typo3-ter-publishing.md | 80 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 skills/github-release/references/typo3-ter-publishing.md diff --git a/skills/github-release/SKILL.md b/skills/github-release/SKILL.md index b399c15..f5b73f7 100644 --- a/skills/github-release/SKILL.md +++ b/skills/github-release/SKILL.md @@ -50,6 +50,7 @@ These commands are blocked by hooks. GitHub immutable releases (GA Oct 2025) mak - `references/ecosystem-detection.md` — version file patterns per ecosystem - `references/immutable-releases.md` — GitHub immutable releases and tag burning - `references/supply-chain-security.md` — SLSA, Sigstore, SBOMs, attestations -- `references/recovery-procedures.md` — burned tags, stuck drafts, version drift, release-body clobbering, mis-tagged SemVer releases, branch-protection gotchas -- `references/ter-republish.md` — TYPO3 Extension Repository re-publish patterns +- `references/recovery-procedures.md` — burned tags, stuck drafts, version drift, release-body clobbering, branch-protection gotchas +- `references/ter-republish.md` — TYPO3 TER re-publish patterns +- `references/typo3-ter-publishing.md` — TYPO3 initial-publish gotchas (tag/`ext_emconf.php` version match, `v`-prefix handling) - `references/ci-workflow-templates.md` — CI workflow structure and templates diff --git a/skills/github-release/references/ter-republish.md b/skills/github-release/references/ter-republish.md index 73cb4fa..4b60d46 100644 --- a/skills/github-release/references/ter-republish.md +++ b/skills/github-release/references/ter-republish.md @@ -187,6 +187,8 @@ reference implementation. ## Related +- `typo3-ter-publishing.md` — initial-publish gotchas (tag/`ext_emconf.php` + version match, `v`-prefix handling in custom workflows) - `recovery-procedures.md` — generic release recovery - `release-process.md` — the standard release flow - `immutable-releases.md` — why we can't just re-tag diff --git a/skills/github-release/references/typo3-ter-publishing.md b/skills/github-release/references/typo3-ter-publishing.md new file mode 100644 index 0000000..fa523eb --- /dev/null +++ b/skills/github-release/references/typo3-ter-publishing.md @@ -0,0 +1,80 @@ +# TYPO3 TER Publishing Gotchas + +This reference covers TYPO3-specific failure modes when publishing an +extension *for the first time* on a given version (initial publish, not +re-publish — for re-publishing without re-tagging, see +`ter-republish.md`). + +## Version Match Required Between Tag and `ext_emconf.php` + +`tailor ter:publish` validates that the version it is uploading matches +the version declared inside the extension. If they disagree it aborts +with: + +``` +configured version does not match +``` + +The extension declares its version in **two** places that must both be +bumped before tagging: + +- `ext_emconf.php` — the `'version'` array key (e.g. `'version' => '0.6.0'`) +- `Documentation/guides.xml` — both the `version=` and `release=` + attributes on the `` element + +The Git tag (`v0.6.0`) is the *third* source of truth. All three must +agree at the moment CI runs `tailor ter:publish`. The release-prep PR +pattern documented in `release-process.md` (Phase 1) already enforces +this ordering — bump version files in the release branch, merge the PR, +*then* tag the merge commit. The reason that ordering exists, beyond +clean history, is that any other ordering produces a tag pointing at +commits with stale version files and TER refuses the upload. + +**Don't tag first, bump second.** A signed tag at the wrong commit is +not free to fix: deleting and recreating a signed tag burns the GPG +signature on the new tag (different SHA), invalidates any provenance +attestation that referenced the old SHA, and on GitHub immutable +releases (GA Oct 2025) burns the tag name permanently if a release was +already created against it. + +## `v` Prefix Mismatch in Custom Publish Workflows + +Git tags conventionally use a `v` prefix (`v0.6.0`); `ext_emconf.php` +stores the bare version (`0.6.0`). A workflow that compares +`${GITHUB_REF#refs/tags/}` directly against the `ext_emconf.php` value +will compare `v0.6.0` against `0.6.0` and silently fail validation. + +The fix is to keep three separate variables: + +```yaml +env: + CHECKOUT_REF: ${{ github.ref }} # refs/tags/v0.6.0 — for actions/checkout `ref:` + TAG: ${GITHUB_REF#refs/tags/} # v0.6.0 — for human-facing logs + VERSION: ${TAG#v} # 0.6.0 — for ext_emconf.php comparison + tailor +``` + +`actions/checkout` wants the raw ref so it can find the tag; the +ext_emconf.php comparison and `tailor ter:publish` argument want the +bare version. Conflating them produces the same `configured version +does not match` failure as section 1 above, but for a different reason +— the comparison is wrong, not the file content. + +**If you use the shared reusable workflow you get this for free.** +`netresearch/typo3-ci-workflows`'s `publish-to-ter.yml` already +implements the strip pattern (see the regex and `VERSION="${TAG#v}"` +snippet in `ter-republish.md` § Tag Format Compatibility). Both +`templates/release-typo3.yml` and `templates/ter-publish.yml` in this +skill repo wire that workflow up correctly. + +This gotcha only bites if you write your own per-project publish +workflow that bypasses the shared reusable workflow. If you do, mirror +the three-variable pattern. + +## Related + +- `ter-republish.md` — re-publishing without re-tagging; tag-format + compatibility regex +- `release-process.md` — the version-bump-then-tag ordering (Phase 1 + through Phase 3) that prevents the version-match failure +- `templates/release-typo3.yml` — tag-triggered TYPO3 release caller +- `templates/ter-publish.yml` — `workflow_dispatch` re-publish caller From 0297826e95f76df315341a2080e18f3f1659b194 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 5 May 2026 23:39:31 +0200 Subject: [PATCH 2/2] docs(github-release): clarify ext_emconf.php as validation source; fix env-block expansion Address PR #22 review feedback from gemini-code-assist: 1. Section 1 conflated ext_emconf.php (the actual validation source for tailor ter:publish) with Documentation/guides.xml (a docs.typo3.org rendering concern). Lead with ext_emconf.php as the single source of truth; demote guides.xml to a "keep in sync for docs rendering" item that does not cause the "configured version does not match" error. 2. Section 2 showed a broken pseudo-code env: block: env: TAG: ${GITHUB_REF#refs/tags/} # never expanded VERSION: ${TAG#v} # never expanded GitHub Actions env: blocks do NOT perform shell parameter expansion; the values become literal strings. Replace with the working pattern: pass github.ref directly to actions/checkout, derive TAG/VERSION in a run: step, and write them to $GITHUB_ENV for downstream steps. Add a concrete validation snippet (PHP one-liner reading ext_emconf.php and erroring on mismatch) so the example is end-to-end useful for anyone writing a custom publish workflow. Signed-off-by: Sebastian Mendel --- .../references/typo3-ter-publishing.md | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/skills/github-release/references/typo3-ter-publishing.md b/skills/github-release/references/typo3-ter-publishing.md index fa523eb..eee54bc 100644 --- a/skills/github-release/references/typo3-ter-publishing.md +++ b/skills/github-release/references/typo3-ter-publishing.md @@ -8,27 +8,29 @@ re-publish — for re-publishing without re-tagging, see ## Version Match Required Between Tag and `ext_emconf.php` `tailor ter:publish` validates that the version it is uploading matches -the version declared inside the extension. If they disagree it aborts +the `'version'` key in `ext_emconf.php`. If they disagree it aborts with: ``` configured version does not match ``` -The extension declares its version in **two** places that must both be -bumped before tagging: +`ext_emconf.php` is the **single source of truth** for this validation. +Bump it before tagging (e.g. `'version' => '0.6.0'` for tag `v0.6.0`). -- `ext_emconf.php` — the `'version'` array key (e.g. `'version' => '0.6.0'`) -- `Documentation/guides.xml` — both the `version=` and `release=` - attributes on the `` element +`Documentation/guides.xml` (`version=` and `release=` attributes on the +`` element) should also be kept in sync — not because TER +validates against it, but because docs.typo3.org renders the wrong +version banner if it drifts. Same release branch, same commit, same PR. -The Git tag (`v0.6.0`) is the *third* source of truth. All three must -agree at the moment CI runs `tailor ter:publish`. The release-prep PR -pattern documented in `release-process.md` (Phase 1) already enforces -this ordering — bump version files in the release branch, merge the PR, -*then* tag the merge commit. The reason that ordering exists, beyond -clean history, is that any other ordering produces a tag pointing at -commits with stale version files and TER refuses the upload. +The Git tag (`v0.6.0`) is the second source of truth that must agree +with `ext_emconf.php` at the moment CI runs `tailor ter:publish`. The +release-prep PR pattern documented in `release-process.md` (Phase 1) +already enforces this ordering — bump version files in the release +branch, merge the PR, *then* tag the merge commit. The reason that +ordering exists, beyond clean history, is that any other ordering +produces a tag pointing at commits with stale version files and TER +refuses the upload. **Don't tag first, bump second.** A signed tag at the wrong commit is not free to fix: deleting and recreating a signed tag burns the GPG @@ -44,18 +46,33 @@ stores the bare version (`0.6.0`). A workflow that compares `${GITHUB_REF#refs/tags/}` directly against the `ext_emconf.php` value will compare `v0.6.0` against `0.6.0` and silently fail validation. -The fix is to keep three separate variables: +The fix is to derive the bare version in a `run:` step (GitHub Actions +`env:` blocks do **not** perform shell parameter expansion — `${TAG#v}` +in `env:` is taken literally), and pass `github.ref` straight to +`actions/checkout`: ```yaml -env: - CHECKOUT_REF: ${{ github.ref }} # refs/tags/v0.6.0 — for actions/checkout `ref:` - TAG: ${GITHUB_REF#refs/tags/} # v0.6.0 — for human-facing logs - VERSION: ${TAG#v} # 0.6.0 — for ext_emconf.php comparison + tailor +- uses: actions/checkout@ + with: + ref: ${{ github.ref }} # refs/tags/v0.6.0 — finds the tag + +- name: Resolve version + run: | + TAG="${GITHUB_REF#refs/tags/}" # v0.6.0 — for human-facing logs + VERSION="${TAG#v}" # 0.6.0 — for ext_emconf.php compare + tailor + echo "TAG=$TAG" >> "$GITHUB_ENV" + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + +- name: Publish + run: | + test "$VERSION" = "$(php -r '$EM_CONF=[]; include "ext_emconf.php"; echo $EM_CONF[basename(__DIR__)]["version"];')" \ + || { echo "::error::tag $TAG vs ext_emconf.php mismatch"; exit 1; } + tailor ter:publish --comment "..." "$VERSION" ``` `actions/checkout` wants the raw ref so it can find the tag; the -ext_emconf.php comparison and `tailor ter:publish` argument want the -bare version. Conflating them produces the same `configured version +`ext_emconf.php` comparison and the `tailor ter:publish` argument want +the bare version. Conflating them produces the same `configured version does not match` failure as section 1 above, but for a different reason — the comparison is wrong, not the file content.