.github/workflows/ci.yml
1
---
2
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
3
name: CI
5
on:
6
push:
7
branches:
8
- main
9
pull_request:
10
branches:
11
- main
12
merge_group:
13
workflow_dispatch:
15
concurrency:
16
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
17
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
19
permissions:
20
contents: read
22
jobs:
23
quality:
24
name: quality
25
runs-on: ubuntu-24.04
26
permissions:
27
contents: read
28
steps:
29
- name: Checkout
30
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
31
with:
32
persist-credentials: false
34
- name: Setup Mise
35
uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3
36
with:
37
experimental: true
38
install_args: --locked
40
- name: Cache Go
41
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
42
with:
43
path: |
44
~/.cache/go-build
45
~/go/pkg/mod
46
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
47
restore-keys: ${{ runner.os }}-go-
49
- name: Check formatting
50
run: mise run fmt-check
52
- name: Check module metadata
53
run: mise run tidy-check
55
- name: Run go vet
56
run: mise run vet
58
- name: Check generated files
59
run: mise run generate-check
61
- name: Check annotations
62
run: mise run annotations
64
- name: Check comment policy
65
run: mise run comments
67
- name: Check agent adapters
68
run: mise run agent-policy
70
- name: Lint and audit workflows
71
run: mise run workflow-lint
73
- name: Run govulncheck
74
run: mise run vulncheck
76
lint:
77
name: lint
78
runs-on: ubuntu-24.04
79
permissions:
80
contents: read
81
steps:
82
- name: Checkout
83
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
84
with:
85
persist-credentials: false
87
- name: Setup Mise
88
uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3
89
with:
90
experimental: true
91
install_args: --locked
93
- name: Cache Go
94
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
95
with:
96
path: |
97
~/.cache/go-build
98
~/go/pkg/mod
99
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
100
restore-keys: ${{ runner.os }}-go-
102
- name: Cache golangci-lint
103
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
104
with:
105
path: ~/.cache/golangci-lint
106
key: ${{ runner.os }}-golangci-lint-${{ hashFiles('go.sum', '.golangci.yml', '.mise/mise.lock') }}
108
- name: Run linter
109
run: mise run lint
111
test:
112
name: test
113
runs-on: ubuntu-24.04
114
permissions:
115
contents: read
116
steps:
117
- name: Checkout
118
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
119
with:
120
persist-credentials: false
122
- name: Setup Mise
123
uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3
124
with:
125
experimental: true
126
install_args: --locked
128
- name: Cache Go
129
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
130
with:
131
path: |
132
~/.cache/go-build
133
~/go/pkg/mod
134
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
135
restore-keys: ${{ runner.os }}-go-
137
- name: Run tests
138
run: mise run test
140
- name: Report coverage
141
run: go tool cover -func=coverage.out | tail -1
143
container:
144
name: container
145
runs-on: ubuntu-24.04
146
permissions:
147
contents: read
148
steps:
149
- name: Checkout
150
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
151
with:
152
persist-credentials: false
154
- name: Setup Mise
155
uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3
156
with:
157
experimental: true
158
install: false
160
- name: Resolve Go version
161
id: tools
162
run: echo "go=$(mise config get tools.go)" >> "$GITHUB_OUTPUT"
164
- name: Setup Buildx
165
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
167
- name: Build image
168
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
169
with:
170
context: .
171
push: false
172
load: true
173
tags: koment:ci
174
build-args: GO_VERSION=${{ steps.tools.outputs.go }}
175
cache-from: type=gha
176
cache-to: type=gha,mode=max
178
- name: Run binary
179
run: docker run --rm koment:ci help
181
- name: Exercise unified service
182
env:
183
SYNCHRONIZATION_TOKEN: ${{ secrets.GITHUB_TOKEN }}
184
run: |
185
set -euo pipefail
186
token="$RUNNER_TEMP/koment-synchronization-token"
187
printf '%s' "$SYNCHRONIZATION_TOKEN" > "$token"
188
chmod 0444 "$token"
189
docker run --detach --name koment-ci-service \
190
--publish 127.0.0.1:18080:8080 \
191
--mount type=bind,src="$PWD/testdata/server/repositories.yaml",dst=/config/repositories.yaml,readonly \
192
--mount type=bind,src="$token",dst=/config/synchronization-token,readonly \
193
koment:ci serve --config /config/repositories.yaml \
194
--github-token-file /config/synchronization-token \
195
--listen 0.0.0.0:8080 --trusted-proxies 127.0.0.1/32
196
cleanup() {
197
docker logs koment-ci-service
198
docker rm --force koment-ci-service
199
rm -f "$token"
200
}
201
trap cleanup EXIT
202
for attempt in $(seq 1 30); do
203
if curl --fail --silent http://127.0.0.1:18080/readyz; then
204
break
205
fi
206
if [ "$attempt" -eq 30 ]; then
207
exit 1
208
fi
209
sleep 2
210
done
211
curl --fail --silent http://127.0.0.1:18080/livez
212
test "$(curl --silent --output /dev/null --write-out '%{http_code}' http://127.0.0.1:18080/r/koment/)" = 401
214
- name: Setup Helm
215
uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 # v4.3.0
217
- name: Create Kind cluster
218
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
220
- name: Exercise Helm release
221
env:
222
SYNCHRONIZATION_TOKEN: ${{ secrets.GITHUB_TOKEN }}
223
run: |
224
set -euo pipefail
225
diagnostics() {
226
kubectl get deployments,pods,services --all-namespaces
227
kubectl describe pods --namespace default
228
kubectl logs deployment/koment-koment --namespace default || true
229
}
230
trap diagnostics ERR
231
kind load docker-image koment:ci --name chart-testing
232
kubectl create secret generic koment-synchronization \
233
--from-literal=github-token="$SYNCHRONIZATION_TOKEN"
234
helm upgrade --install koment charts/koment \
235
--set image.repository=koment \
236
--set-string image.tag=ci \
237
--set image.pullPolicy=Never \
238
--set github.existingSecret=koment-synchronization \
239
--wait --timeout 5m
240
helm test koment --logs --timeout 2m
242
setup-action:
243
name: setup-action (${{ matrix.os }})
244
runs-on: ${{ matrix.os }}
245
permissions:
246
contents: read
247
strategy:
248
fail-fast: false
249
matrix:
250
os:
251
- ubuntu-24.04
252
- macos-15
253
steps:
254
- name: Checkout
255
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
256
with:
257
persist-credentials: false
259
- name: Install koment
260
id: setup
261
uses: ./
263
- name: Verify installation
264
env:
265
INSTALLED_VERSION: ${{ steps.setup.outputs.version }}
266
run: |
267
command -v koment
268
koment version
269
test -n "$INSTALLED_VERSION"
270
koment version | grep -q "$INSTALLED_VERSION"
272
windows-archive:
273
name: windows-archive (advisory)
274
runs-on: windows-2025
275
permissions:
276
contents: read
277
steps:
278
- name: Install the published Windows archive the way Scoop and WinGet do
279
shell: pwsh
280
env:
281
KOMENT_SOURCE: janpuc/koment
282
run: |
283
$ErrorActionPreference = 'Stop'
285
$resolved = (Invoke-WebRequest -Uri "https://github.com/$env:KOMENT_SOURCE/releases/latest" -UseBasicParsing).BaseResponse.RequestMessage.RequestUri.AbsoluteUri
286
$tag = ($resolved -split '/')[-1]
287
$version = $tag.TrimStart('v')
288
if ($version -notmatch '^\d+\.\d+\.\d+') {
289
throw "could not resolve a koment release from $resolved"
290
}
292
$archive = "koment_${version}_windows_amd64.zip"
293
$base = "https://github.com/$env:KOMENT_SOURCE/releases/download/$tag"
294
$work = New-Item -ItemType Directory -Force -Path (Join-Path $env:RUNNER_TEMP 'koment-windows')
296
Write-Host "installing koment $version (windows/amd64)"
297
Invoke-WebRequest -Uri "$base/$archive" -OutFile (Join-Path $work $archive) -UseBasicParsing
298
Invoke-WebRequest -Uri "$base/koment_${version}_checksums.txt" -OutFile (Join-Path $work 'checksums.txt') -UseBasicParsing
300
$listed = Select-String -Path (Join-Path $work 'checksums.txt') -Pattern " $([regex]::Escape($archive))$"
301
if (-not $listed) {
302
throw "$archive is not listed in the release checksum manifest"
303
}
304
$expected = ($listed.Line -split '\s+')[0]
305
$actual = (Get-FileHash -Path (Join-Path $work $archive) -Algorithm SHA256).Hash.ToLower()
306
if ($expected -ne $actual) {
307
throw "${archive}: expected $expected, got $actual"
308
}
309
Write-Host "${archive}: OK"
311
$extracted = Join-Path $work 'extracted'
312
Expand-Archive -Path (Join-Path $work $archive) -DestinationPath $extracted -Force
313
foreach ($member in @('koment.exe', 'LICENSE', 'README.md')) {
314
if (-not (Test-Path (Join-Path $extracted $member))) {
315
throw "$member is not at the root of $archive"
316
}
317
}
318
Write-Host 'archive root carries koment.exe, LICENSE and README.md'
320
$reported = (& (Join-Path $extracted 'koment.exe') version) -join ' '
321
Write-Host $reported
322
if ($reported -notmatch [regex]::Escape($version)) {
323
throw "koment.exe reported '$reported', which does not name $version"
324
}
326
helm:
327
name: helm
328
runs-on: ubuntu-24.04
329
permissions:
330
contents: read
331
steps:
332
- name: Checkout
333
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
334
with:
335
persist-credentials: false
337
- name: Setup Mise
338
uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3
339
with:
340
experimental: true
341
install_args: --locked
343
- name: Lint chart
344
run: mise run helm-lint
346
- name: Render supported modes
347
run: mise run helm-template
349
editor:
350
name: editor
351
runs-on: ubuntu-24.04
352
permissions:
353
contents: read
354
steps:
355
- name: Checkout
356
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
357
with:
358
persist-credentials: false
360
- name: Setup Mise
361
uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3
362
with:
363
experimental: true
364
install: false
366
- name: Install the pinned Go toolchain
367
run: |
368
set -euo pipefail
369
mise install go --locked
370
echo "$(mise where go)/bin" >> "$GITHUB_PATH"
372
- name: Cache cross-compilation
373
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
374
with:
375
path: |
376
~/.cache/go-build
377
~/go/pkg/mod
378
key: ${{ runner.os }}-go-cross-${{ hashFiles('go.sum') }}
379
restore-keys: ${{ runner.os }}-go-cross-
381
- name: Install locked editor tools
382
run: mise --cd editors/vscode install --locked
384
- name: Install editor packaging tools
385
run: mise --cd editors/vscode exec -- npm ci
387
- name: Test extension protocol
388
run: mise run extension-test
390
- name: Build the archives the extension bundles
391
run: |
392
set -euo pipefail
393
test "$(go env GOVERSION)" = "go$(mise config get tools.go)"
394
version=0.0.0-ci
395
mkdir -p archives
396
cp LICENSE README.md archives/
397
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do
398
os=${target%/*}
399
arch=${target#*/}
400
binary=koment
401
[ "$os" = windows ] && binary=koment.exe
402
CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -trimpath -o "archives/${binary}" ./cmd/koment
403
name="koment_${version}_${os}_${arch}"
404
if [ "$os" = windows ]; then
405
(cd archives && zip -q "${name}.zip" "$binary" LICENSE README.md && rm "$binary")
406
else
407
(cd archives && tar -czf "${name}.tar.gz" "$binary" LICENSE README.md && rm "$binary")
408
fi
409
done
410
rm archives/LICENSE archives/README.md
411
(cd archives && sha256sum ./*.tar.gz ./*.zip | sed 's| \./| |' > "koment_${version}_checksums.txt")
413
- name: Validate every VSIX
414
run: |
415
set -euo pipefail
416
version=0.0.0-ci
417
manifest=editors/vscode/package.json
418
cp "$manifest" "$RUNNER_TEMP/package.json"
419
trap 'cp "$RUNNER_TEMP/package.json" "$manifest"' EXIT
420
node -e "
421
const p = require('./editors/vscode/package.json');
422
p.version = '$version';
423
require('fs').writeFileSync('editors/vscode/package.json', JSON.stringify(p, null, 2) + '\n');
424
"
425
./editors/vscode/package-vsix.sh "$version" archives dist >/dev/null
427
for pair in linux-x64:koment linux-arm64:koment darwin-x64:koment darwin-arm64:koment \
428
win32-x64:koment.exe win32-arm64:koment.exe; do
429
target=${pair%%:*}
430
expected=${pair##*:}
431
package="dist/koment-vscode_${version}_${target}.vsix"
432
carried=$(unzip -l "$package" | awk '/extension\/bin\//{print $NF}' | sed 's|extension/bin/||')
433
test "$carried" = "$expected" || {
434
echo "::error::$package carries '$carried', expected '$expected'"
435
exit 1
436
}
437
unzip -p "$package" extension.vsixmanifest | grep -q "TargetPlatform=\"${target}\"" || {
438
echo "::error::$package is not declared for $target"
439
exit 1
440
}
441
echo "$package: $expected for $target"
442
done
444
universal="dist/koment-vscode_${version}.vsix"
445
if unzip -l "$universal" | grep -q 'extension/bin/'; then
446
echo "::error::the universal package must carry no binary"
447
exit 1
448
fi
449
echo "$universal: no binary, falls back to PATH"
451
ci:
452
if: ${{ always() }}
453
name: ci
454
needs:
455
- container
456
- editor
457
- helm
458
- lint
459
- quality
460
- setup-action
461
- test
462
runs-on: ubuntu-24.04
463
permissions: {}
464
steps:
465
- name: Reject failed, cancelled, or skipped jobs
466
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
467
run: exit 1
469
- name: Accept successful jobs
470
if: ${{ !(contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')) }}
471
run: echo "All CI jobs passed"