The org PACKAGES_TOKEN got HTTP 403 on the release lookup; go.yaml and gradle.yaml already use the job token (contents: write). Do the same here and drop the now-unused PACKAGES_TOKEN secret input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 KiB
ci-workflows
Shared Gitea Actions workflows for KOF and QRun repositories. A small, direct
port of the CircleCI qqq-orb. Keep it that way.
Maven
Add one file to the repository, .gitea/workflows/build.yaml:
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
# with:
# java-version: '17'
# maven-args: -Pui-demo
The repository needs a <revision> property in the root pom.xml.
Every push builds once. PRs use the push run's status on the head commit.
Builds run on the container-build runners, which provide a Docker daemon
for Testcontainers.
| Ref | Version | Action |
|---|---|---|
feature and other branches, main |
unchanged | mvn verify |
develop |
next minor -SNAPSHOT after a release, else unchanged |
test, deploy, commit bump |
release/X.Y |
X.Y.0-RC.N |
test, deploy, commit bump, tag vX.Y.0-RC.N |
hotfix/* |
patch + 1 | test, deploy, commit bump, tag vX.Y.Z |
tag vX.Y.Z |
X.Y.Z |
test, deploy, commit to main |
tag publish-* |
X.Y.Z-<feature>-<sha>-SNAPSHOT |
test, deploy |
Packages go to the owner's Gitea Maven registry,
https://git.kof22.com/api/packages/<owner>/maven, using the job token
(or an optional PACKAGES_TOKEN secret). Builds resolve dependencies from the
QRun-IO registry as well as Maven Central.
Node
.gitea/workflows/build.yaml:
name: build
on:
push:
jobs:
node:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/node.yaml@v1
with:
node-version: '24'
commands: |
npm run lint
npm test
| Input | Default | Meaning |
|---|---|---|
commands |
required | Shell commands, one per line, run in order; the first failure fails the build |
node-version |
'22' |
Selects the official node:<version>-bookworm image |
install |
npm ci |
Dependency install command |
uv |
false |
Install a pinned, sha256-verified uv for Python script checks |
working-directory |
. |
Where the install and commands run |
runner |
container-build |
Runner label |
publish |
false |
On a release tag, publish after every command passed (below) |
version-file |
package.json |
The release tag must be v<version> of this file, or the build fails |
npm-publish-dirs |
. |
Package directories to npm publish, one per line, in order; empty = none |
publish-commands |
empty | Commands that publish instead of the npm-publish-dirs loop |
release-files |
empty | Files or globs, one per line, for a Gitea release; empty = no release |
Publishing
Without publish: true the workflow only verifies. With it, branches still only
verify; a tag vX.Y.Z or vX.Y.Z-<pre> (for example v1.2.0-RC.1) publishes:
- The tag must equal
v+ theversion-fileversion. Set the version inpackage.json(npm version 1.2.0-RC.1 --no-git-tag-version), merge, then tag the merge commit. A mismatch fails the build before anything is installed. - Install and
commandsrun as on any push; a failure stops the release. - Each
npm-publish-dirspackage goes to the owner's Gitea npm registry,https://git.kof22.com/api/packages/<owner>/npm/, with thePACKAGES_TOKENorg secret (secrets: inherit). A prerelease version gets dist-tagnext, a final versionlatest. The package keeps its name and scope; apublishConfig.registry(npmjs, GitHub) is ignored for the publish. A version that already exists fails: publish a new version instead. release-filesbecome a Gitea release for the tag (a prerelease for-<pre>versions) with a freshSHA256SUMS, and theCHANGELOG.mdsection whose##heading names the version as its notes. ASHA256SUMSamong the files is checked, then replaced. An existing release for the tag fails.
jobs:
node:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/node.yaml@v1
secrets: inherit
with:
commands: |
npm test
npm run build
publish: true
# npm-publish-dirs: | # workspaces, in dependency order
# packages/client
# packages/cli
# version-file: packages/cli/package.json
# release-files: dist/* # with npm-publish-dirs: '' for a release only
To consume a package:
# .npmrc
@scope:registry=https://git.kof22.com/api/packages/QRun-IO/npm/
//git.kof22.com/api/packages/QRun-IO/npm/:_authToken=${GITEA_TOKEN}
Binaries for macOS or Windows need native runners; repositories that ship
them (for example QRun-IO/qqq-cli) keep their own release workflow.
Android
.gitea/workflows/build.yaml (see QRun-IO/qqq-android):
name: build
on:
push:
branches: ['**']
tags: ['v*']
jobs:
android:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/android.yaml@v1
secrets: inherit
with:
tasks: |
testDebugUnitTest lintDebug assembleDebug
emulator-tasks: connectedDebugAndroidTest
| Input | Default | Meaning |
|---|---|---|
tasks |
build |
Gradle invocations, one per line (arguments to ./gradlew) |
java-version |
'21' |
JDK from the image (8, 17, 21, 25) |
commands |
'' |
Shell commands run before Gradle |
node-version |
'' |
Install this Node.js (official tarball, checksum-verified) for commands |
emulator-tasks |
'' |
Gradle invocations run with a booted emulator (needs /dev/kvm) |
emulator-image |
system-images;android-30;google_apis;x86 |
AVD system image |
publish-tasks |
'' |
Run on vX.Y.Z tags with -Pversion=X.Y.Z |
runner |
android-build |
Runner label (Lore builders) |
Runs in the pinned ci-android-runtime image (cimg/android plus the emulator).
To publish an AAR, declare a maven { name = "gitea" ... credentials(PasswordCredentials::class) }
repository whose URL comes from the giteaMavenUrl Gradle property; the workflow
sets it to the owner's Gitea Maven registry with PACKAGES_TOKEN (or the job token).
Windows and macOS
Host jobs (no container) on the native runners. The repository's own commands run with the tools the host provides; nothing is installed.
name: build
on:
push:
branches: ['**']
tags: ['v*']
jobs:
windows:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/windows.yaml@v1
secrets: inherit
with:
commands: |
dotnet restore App.slnx --locked-mode
dotnet build App.slnx -warnaserror --no-restore
dotnet test tests/App.Tests --no-build
artifacts: artifacts/release/*.msi
macos:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/macos.yaml@v1
secrets: inherit
with:
commands: |
swift build
swift test
| Input | Windows | macOS | Meaning |
|---|---|---|---|
commands |
PowerShell 7, one script | bash -exo pipefail |
Required. The first failure fails the build |
runner |
kof-windows-amd64 |
kof-macos-arm64 |
QRun-IO repositories use qqq-windows-vm6218 / qqq-macos-arm64 |
dotnet-version |
'' |
Fail unless a host SDK starts with this version | |
xcode |
'' |
Select Xcode by version (/Applications/Xcode_<v>.app) or path via DEVELOPER_DIR |
|
lfs |
false |
Fetch Git LFS objects (needs git-lfs on the host) |
|
artifacts |
'' |
'' |
Files or globs, one per line; on v* tags they and a SHA256SUMS are attached to the tag's Gitea release |
checksums |
SHA256SUMS |
SHA256SUMS |
Checksum asset name; give each platform its own when several jobs release one tag |
- Trigger on
pushonly. Host runners run repository code on the machine, and push events come only from branches of the repository itself, never forks. - Windows: native commands with a non-zero exit fail the build. A
.ps1that reports failure withexit 1must run aspwsh -NoProfile -File x.ps1; called with&it would not stop the script. Test tools that assert on child exit codes themselves (Pester suites) need$PSNativeCommandUseErrorActionPreference = $falsefirst. - Releases use the job token (
contents: write); the orgPACKAGES_TOKENgot HTTP 403 on the release lookup. Re-running a tag replaces assets of the same name. A Homebrew or Scoop update can read the release'sSHA256SUMS. - Host limits today: the Windows runner is a batch-logon task with no interactive
desktop (no FlaUI UI tests); the macOS runner is a launchd daemon with no GUI
session, so
xcodebuild testcannot reachtestmanagerd(SwiftPMswift testworks), and the host has nogit-lfs.
Go
.gitea/workflows/build.yaml (see KofTwentyTwo/license-tool):
name: build
on:
push:
branches: ['**']
tags: ['v*']
jobs:
go:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/go.yaml@v1
with:
golangci-lint: true
race: true
release-commands: scripts/build-release.sh "${GITHUB_REF_NAME#v}"
artifacts: dist/*
Every push runs go mod verify, gofmt -l (tracked files), go vet ./...,
optionally golangci-lint, and go test ./..., in golang:<go-version>-bookworm
with GOTOOLCHAIN=local (a go.mod that needs a newer Go fails, nothing is downloaded).
| Input | Default | Meaning |
|---|---|---|
go-version |
'1.26' |
Selects the official golang:<version>-bookworm image |
gofmt |
true |
Fail when a tracked .go file is not gofmt-clean |
golangci-lint |
false |
Pinned, sha256-verified golangci-lint v2 with the repository's .golangci.yml |
race |
false |
go test -race |
test-args |
'' |
Extra go test flags (for example a coverage profile) |
commands |
'' |
Extra gates after the tests, one per line (for example a coverage threshold) |
release-commands |
'' |
Run on v* tags after every gate (cross-compile into dist/) |
artifacts |
'' |
Files or globs; on v* tags they and a SHA256SUMS go on the tag's Gitea release (job token; re-runs replace same-named assets) |
working-directory |
. |
Directory with go.mod |
runner |
container-build |
Runner label |
Gradle
jobs:
gradle:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/gradle.yaml@v1
with:
java-version: '17'
tasks: build
artifacts: build/distributions/*.zip
Runs ./gradlew in gradle:jdk<java-version> (the image's own Gradle is unused).
Before Gradle runs, the wrapper is verified: distributionUrl must be a
services.gradle.org distribution, gradle-wrapper.jar must be that version's
official jar, and distributionSha256Sum must be set to the official checksum so
Gradle verifies the download. Fix a failure with
./gradlew wrapper --gradle-version <v> --gradle-distribution-sha256-sum <sha256>.
| Input | Default | Meaning |
|---|---|---|
tasks |
build |
Gradle invocations, one per line |
java-version |
'21' |
JDK major version (17, 21, 25) |
commands |
'' |
Shell commands run before Gradle |
artifacts |
'' |
Files or globs; on v* tags they and a SHA256SUMS go on the tag's Gitea release |
runner |
container-build |
Runner label |
Android projects use android.yaml.
Lint
Repository hygiene for scripts, configuration and infrastructure code. Every check is off until its input is set.
jobs:
lint:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/lint.yaml@v1
with:
shellcheck: 'ops/**/*.sh'
tofu-fmt: '**/*.tf'
# vendor/: third-party scripts copied verbatim, not ours to restyle
exclude: |
vendor/**
| Input | Check |
|---|---|
shellcheck |
shellcheck (.shellcheckrc applies) |
yamllint |
yamllint (.yamllint* applies) |
json |
every file parses (python3 -m json.tool) |
ruff |
ruff check (ruff.toml / pyproject.toml applies) |
tofu-fmt |
tofu fmt -check -diff |
tofu-validate |
directories, one per line: tofu init -backend=false and tofu validate (no state, no secrets) |
markdownlint |
markdownlint-cli2 globs (.markdownlint-cli2.* applies) |
ansible-lint |
arguments to ansible-lint (. = the repository; .ansible-lint applies), in its own job |
commands |
extra shell commands in the lint image (Node 22, Python 3, git, curl) |
exclude |
globs removed from every file check |
File inputs are git pathspec globs over tracked files (*.sh top level only,
**/*.sh any depth); a glob that matches nothing fails. git-crypt files are
ciphertext in CI and always skipped. Tools are pinned: shellcheck, OpenTofu, ruff
and uv by sha256; yamllint hash-locked; ansible-lint by image digest;
markdownlint-cli2 by exact version with its dependency tree frozen to the release
day (npm --before, integrity-checked). Exclude only vendored or generated files,
with a comment saying why; fix findings in your own files.
Releasing
| To publish | Do this |
|---|---|
| A snapshot | Merge to develop. |
| A release candidate | git push origin develop:release/X.Y (first RC), then push or merge fixes to release/X.Y. Every push publishes the next X.Y.0-RC.N. |
| A release | Merge release/X.Y to main and develop, then git tag vX.Y.0 && git push origin vX.Y.0. |
| A hotfix | Push to hotfix/<name>; each push publishes patch + 1. Merge back to main and develop. |
A push to main never publishes on its own. The tag is the release.
Changing this workflow
Repositories pin @v1, a tag that only moves after a change is proven.
QRun-IO/qbit-sftp-data-integration is the canary and calls @main.
- Push the change to
main. - Prove it on the canary: push a branch there (verify), then re-run its latest
developrun (publish), and any other path the change touches. - Move the tag:
git tag -fa v1 -m "<why>" <commit> && git push -f origin v1.
A breaking change (new required input, changed behavior for existing repos)
gets v2 and a rollout, not a moved v1.
Differences from the orb
- Publishes to the Gitea registry, not Maven Central, and does not GPG-sign.
mainonly verifies; thevX.Y.Ztag publishes the release, so a version is never published twice.- The
release:rcPR label flow is gone. Cut a release candidate by pushingrelease/X.Y. - RC and hotfix publishes create a
v<version>git tag. Maven builds create no GitHub or Gitea release objects; tags appear on the repository's Releases page. Node builds create a Gitea release only whenrelease-filesis set. - Not ported yet: security scans (gitleaks, Semgrep, OWASP, CycloneDX), SpotBugs/PMD, the QQQ middleware API-version check and frontend packaging.