ci(extension): retire the manual-bump guard, true up the docs (step 5)
CI / lint (push) Successful in 3s
Build images / sign-extension (push) Successful in 4s
CI / extension-version (push) Successful in 3s
Build images / build-agent (push) Successful in 8s
CI / frontend-build (push) Successful in 22s
extension / lint (push) Successful in 25s
CI / backend-lint-and-test (push) Successful in 29s
Build images / build-web (push) Successful in 1m57s
Build images / build-ml (push) Successful in 2m38s
CI / integration (push) Successful in 3m50s

The guard asked whether a packaged extension file changed without the
version moving. Since step 4 nobody moves the version by hand, so it was
checking a fact that had stopped existing — and it was not merely dead
weight: it would have failed the lane on every real extension change,
demanding a bump that decides nothing. Removed rather than left running
beside the new mechanism (rule 22).

What replaces it is thinner and true. The extension-version lane now
asserts the derivation resolves on this commit, that the derived value is
the plain dotted-numeric shape AMO accepts, and that MAJOR.MINOR agrees
between manifest.json and package.json. MAJOR.MINOR is the one part still
hand-set, and packaging.sh reads it from manifest.json ALONE, so a
divergence ships a version package.json disagrees with. The lane keeps
fetch-depth: 0 — checking that the derivation survives a real checkout is
half its remaining value.

Deliberately not checked there: that the derived value beats what is
already signed. That guard belongs in build.yml, where it compares against
the real ext-* releases. Comparing against origin/main in a lane would be
wrong, because dev legitimately derives a LOWER value whenever main is
ahead on the extension, and a lane that fails for being behind is a lane
people learn to ignore.

packaging.sh is down to two consumers from three. version.spec.js's
"ci.yml derives its pathspec" test would have gone red on that, so it is
rewritten to assert the property rather than the consumer: no workflow
inlines an :(exclude)extension/ literal, across all three. That keeps the
#2397 anti-regression value while surviving consumers coming and going.
A second test pins build.yml to packaging.sh version and fails if it goes
back to grepping the committed value — which is not a style regression but
the #3092 bug itself. build.yml joins extension.yml's trigger paths, since
the suite now asserts against it.

The lockstep test narrows from the whole version string to MAJOR.MINOR.
The committed patch numbers are inert now; asserting on them would fail
for a difference that changes nothing.

Docs. extension/README.md's Release section described extension.yml
signing on main and committing the XPI into frontend/public/ — untrue
since 2026-05-25, and it told the reader to hand-bump both files, which is
now exactly the wrong instruction. Rewritten, with a Versioning section
that says plainly that editing the patch number does nothing and why the
key is commit time rather than a count. ci-requirements.md drops the third
packaging.sh consumer and names every job that needs full history. Root
README no longer claims the extension is signed on main only.
This commit is contained in:
2026-08-27 11:29:44 -04:00
parent 9eb946b21b
commit fe48e77821
6 changed files with 158 additions and 125 deletions
+43 -8
View File
@@ -77,21 +77,56 @@ describe('consumers delegate rather than keeping their own copy', () => {
}
})
it('ci.yml derives its pathspec from the script and hardcodes none', () => {
const ci = readText('..', '.forgejo', 'workflows', 'ci.yml')
expect(ci).toContain('extension/scripts/packaging.sh pathspec')
// A literal :(exclude)extension/... in the workflow means someone bypassed
// the shared definition.
expect(ci).not.toMatch(/:\(exclude\)extension\//)
const WORKFLOWS = ['ci.yml', 'build.yml', 'extension.yml']
it('no workflow hardcodes the packaged-file set', () => {
// ci.yml used to substitute `packaging.sh pathspec` directly, for the
// manual-bump guard that milestone 271 step 5 retired. Nothing inlines the
// set today, and nothing should start to: a literal :(exclude)extension/...
// in a workflow means someone bypassed the shared definition, which is
// exactly the drift #2397 was about. Asserted across all three rather than
// against one named consumer, so it keeps holding as consumers come and go.
for (const wf of WORKFLOWS) {
const text = readText('..', '.forgejo', 'workflows', wf)
expect(text, `${wf} inlines an :(exclude) literal`).not.toMatch(/:\(exclude\)extension\//)
}
})
it('build.yml takes the shipped version from the script, not from the repo', () => {
// The version is DERIVED from commit time (#3092, milestone 271 step 4).
// Going back to reading the committed value is not a style regression, it
// is the bug: a hand-set version makes dev and main sign the same number
// for different code, and the ext-<version> cache then serves one channel
// the other's XPI.
const build = readText('..', '.forgejo', 'workflows', 'build.yml')
expect(build).toContain('packaging.sh version')
expect(build, 'build.yml re-reads the committed version instead of deriving it')
.not.toMatch(/grep[^\n]*'"version"'[^\n]*package\.json/)
})
})
describe('extension version', () => {
it('keeps manifest.json and package.json in lockstep', () => {
expect(read('manifest.json').version).toBe(read('package.json').version)
const majorMinor = (v) => v.split('.').slice(0, 2).join('.')
it('keeps the hand-set MAJOR.MINOR in lockstep across both files', () => {
// Narrowed from full-string equality at milestone 271 step 5. Since step 4
// the patch component is derived from commit time and stamped into both
// files at build time, so the committed patch numbers are inert — nothing
// reads them and they are not what ships. Asserting on them would fail for
// a difference that changes nothing.
//
// MAJOR.MINOR is the opposite: still hand-set, still shipped, and
// packaging.sh reads it from manifest.json ALONE. Let the two diverge and
// the extension ships a version package.json disagrees with, with no other
// signal.
expect(majorMinor(read('manifest.json').version))
.toBe(majorMinor(read('package.json').version))
})
it('uses a plain dotted numeric version AMO will accept', () => {
// The committed value seeds MAJOR.MINOR, so it still has to parse even
// though its patch component never ships. ci.yml asserts the same shape on
// the DERIVED value, which is the one AMO actually sees.
expect(read('package.json').version).toMatch(/^\d+(\.\d+)*$/)
})