publish the plugin from CI, installable with TPM or a drop-in bundle #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "develop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Everything this repository does beyond the switcher itself, landing on main in one reviewed
merge. main was seeded at the last commit before CI existed, so this is the first change it has
ever taken and the first release it will cut.
Two things make the plugin installable by someone who is not the person who built it. A drop-in
bundle per architecture carries the entry point and a built binary and nothing else, so an
install needs no compiler and no clone; deliberately no src/, since the entry point rebuilds
only when the source is newer than the binary and with no source that test is false. And the
repository being reachable makes
set -g @pluginwork, which is also what stops TPM's cleanfrom deleting the plugin directory -- the standing wart with the symlink install.
Two gates now stand between a merge and a publish.
scanruns gitleaks across the commit graphrather than the working tree, because a secret committed and deleted in a later commit is still
published, and a tree-only scan reports clean on exactly that case.
bundle-checkunpacks whatwould be shipped and asserts the layout the entry point resolves against, then checks the name
against the ELF header and that SHA256SUMS covers every artefact.
The rest is the release engine itself: semantic-release deriving the version from these
commits, a merge-message template so a pull request title becomes a Conventional subject, and
the back-merge that keeps develop from falling behind main -- which is load-bearing, since rc
numbering is computed from the newest tag reachable from HEAD.
The two
featcommits are what set the version. With no stable tag in the repository this cuts1.0.0 rather than a minor bump.
Verified rather than assumed: v1.0.0-rc.1 was cut from develop by this pipeline and carries the
bundle, the source tarball and SHA256SUMS;
make testpasses; gitleaks reports no findingsacross the whole history.
Publishing assumed nothing held the tag yet, which stopped being true the moment this repo's history was rewritten: the tag was recreated at the new HEAD, the old release survived the rewrite still holding it, and the POST came back 409 {"message":"Release is has no Tag"} a message that reads as though the tag were missing and means the opposite. The bad part was not the failure but that retrying could not clear it. The tag was reachable from HEAD, so semantic-release considered the version shipped and never called publish again -- a release that does not exist, that nothing will ever try to create, and no error anywhere to say so. The step now looks the tag up first. If a release holds it, it is updated in place and its superseded attachments removed, so a re-run repairs the release rather than colliding with it. If nothing holds it, the POST path is unchanged. Comes with a self-test driving the plugin against a stubbed forge, covering both paths. The publish step is the one piece the release job cannot rehearse, because by the time it runs it either publishes or has already failed -- which is why this was found by hitting it rather than by testing it.Serialising by `${{ github.repository }}` changed nothing: runs #11 and #12 still overlapped, and one of them still failed. Two explanations fit equally well -- the expression not being evaluated in the concurrency context, or this Forgejo ignoring `concurrency` entirely -- and they call for different fixes. A literal group tells them apart. If runs stop overlapping, the expression was the problem. If they still overlap, the key is unsupported here and the duplicate run has to be removed where it is created, by not firing the pull_request trigger for a branch whose pushes are already built. Recorded as a step rather than folded into a fix, because guessing between the two would mean shipping a workaround for a cause that had not been established.This Forgejo ignores `concurrency`. Proven rather than assumed: grouping by `${{ github.repository }}` left runs #11 and #12 overlapping, and a literal group left #13 and #14 overlapping too. Neither serialised anything, so the duplicate cannot be queued and has to not exist. It exists because an open pull request makes every push to its head branch fire twice, once as `push` and once as `pull_request`, for one commit. The test job now skips the pull_request run when the head branch is one the push trigger already covers. Feature branches are unaffected -- their pull_request run is their only one, since they are not pushed to directly. The `concurrency` block stays, with a warning that it currently does nothing. An ignored key that looks like protection is worse than no key, and it will start working if the feature ever lands. This does not help two different branches pushed at once, which is what took out runs #4 and #5. Nothing here can: the collision is between runs, and the runner is where that belongs. Filed separately.Publishes the release pipeline's first stable version. develop has been cutting release candidates since it was created; merging this publishes 1.0.0 and back-merges main into develop. The version is not written down anywhere. It is derived from the Conventional Commit types since the last reachable tag, so the two feat commits here are what make this 1.0.0, and a push carrying only docs or chore publishes nothing at all. Choosing a type is therefore a release decision rather than bookkeeping, which is why docs/commits.md now states which types ship. Publishing to Forgejo is a local plugin rather than a dependency. The Gitea plugin has not been published since 2022, and driving curl from @semantic-release/exec is unsafe here: exec interpolates its command into sh -c unquoted, and release notes are markdown full of backticks and $. A local plugin is handed the notes as a string and cannot be made to run them. Verified rather than assumed: 1.0.0-rc.1 published from develop, prerelease flag set, English notes, three artefacts attached. The source tarball is reproducible, checked by downloading the published artefact and rebuilding it. The first attempt did not match: the runner workspace carries setgid and every directory created under it inherited the bit. The publish step repairs an existing release instead of colliding with it, covered by a self-test against a stubbed forge -- the real one cannot be rehearsed, since by the time it runs it has either published or already failed. A commit typed chore or ci publishes nothing, observed across the last four runs. What cost time, so the next reader does not pay it again. The changelog preset must stay on v9 against release-notes-generator 14, and the mismatch fails at note generation, after the version has already been decided. The release clone must not be shallow, or every run reaches no tags and looks like a first release forever. And semantic-release must be invoked by path, because npx silently fetches its own copy that cannot see the installed plugins. Three guards here exist only because the runner presents an environment an ordinary job does not expect: TERM=dumb, a POSIX locale, and that setgid workspace. Each names the homelab issue proposing a fix at the runner, so they can be removed deliberately rather than surviving as unexplained defensive code. Concurrent runs of this repo also collide, and concurrency is ignored by this Forgejo, so the duplicate build an open pull request creates is skipped at the trigger.The check read the repository's deploy keys over the API and compared them to the public half of the secret. That needs repository admin, which the Actions token does not have, so the guard failed with ::error::cannot list deploy keys -- HTTP 403 Forbidden on a key that was in fact present and writable. A check that cannot pass is worse than no check: it blocks the thing it was meant to protect and says nothing true about it. It now asks ssh to start the read verb and then the write verb against the repository. That needs no special permission, and it tests the operation the back-merge actually performs rather than metadata describing it. Which of the two fails is what tells a key that is not on this repository apart from one that is there but cannot push. Both verbs are asked for their ref advertisement and closed, so nothing is transferred and nothing changes -- confirmed against the real forge before writing it in. Verified across all five states with a stubbed ssh: writable, read-only, not accepted for this repository, secret unset, and secret mangled.The check ran `git-upload-pack` as a remote ssh command, which is how one asks the forge what a key may do without transferring anything. It works -- with a user key. A deploy key gets Forgejo: Failed to execute git command which reads exactly like a rejected key, and is instead a probe the forge does not serve that way. The connection and the authentication had both already succeeded; the host key was recorded in the same output. It now uses ls-remote, a shallow clone, and push --dry-run. Those are the operations the back-merge actually performs, so passing means the back-merge works rather than resembling something that works. --dry-run still negotiates with receive-pack, so a read-only key is refused by it and by nothing earlier. Verified against the real forge before writing it in: both probes succeed over ssh on 443, and the dry-run leaves the branch untouched. Stubbed-git tests cover seven states, including reading refs but failing to clone, which the previous shape could not distinguish.