Repository navigation
483 lines (462 loc) · 21.9 KB
/
Copy pathci.yml
File metadata and controls
483 lines (462 loc) · 21.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
# A push that supersedes an in-flight run makes that run's verdict
# irrelevant, so cancel it rather than paying for both. Keyed by ref, so
# concurrent PRs never cancel each other.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# GitHub removes the Node 20 action runtime on 2026-09-16. mlugg/setup-zig still declares
# `using: node20` at its own latest release, so there is no version to upgrade to. This
# forces the post-cutover runtime now, on ordinary CI runs, rather than discovering on the
# 16th whether the action works under node24.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# No SQL lane here on purpose: any target that builds or tests SQL must
# delegate to cmd/rgit-install, so -tags rgit_sql and parser generation stay
# in exactly one place (AGENTS.md § Delegation boundary). Plain `go build`/
# `go test` never touch it.
#
# cgo is mandatory -- rgit links tree-sitter through cgo, so CGO_ENABLED=0
# is not an option. ubuntu-latest ships gcc (verified against
# actions/runner-images' own software manifest), so no toolchain step is
# needed here.
jobs:
lint:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: Rethunk-Tech/gh-actions/setup-go@1417493f40c23b8826601ac9de8d7abda0b84ec6 # v1.15
with:
run-lint: "true"
run-govulncheck: "true"
# Each suite runs once, carrying every flag CI needs from it. Coverage is
# informational: the totals print, no threshold gates. Always
# -coverpkg=./... (CONTRIBUTING.md § Coverage): much of the suite drives
# code from another package, so per-package numbers report 0.0% where heavy
# coverage exists.
unit:
name: test (unit lane, -short, coverage)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: Rethunk-Tech/gh-actions/setup-go@1417493f40c23b8826601ac9de8d7abda0b84ec6 # v1.15
# The unit lane is where the guarantee lives (CONTRIBUTING.md § Tests):
# a regression must fail here. -short skips only the built-binary
# end-to-end file and the live-server cross-checks, so this job
# enforces that every other test passes without them. Its coverage is
# the number that matters for the same reason.
- run: go test -short -coverpkg=./... -coverprofile=short.out ./...
- run: go tool cover -func=short.out | tail -1
full:
name: test (full suite, -race, coverage)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: Rethunk-Tech/gh-actions/setup-go@1417493f40c23b8826601ac9de8d7abda0b84ec6 # v1.15
# cmd/rgit-install's own tests (TestRunPrereqChecks, TestFindSQLAdapter)
# assert tree-sitter's PATH presence for the prereq check itself -- not
# a build-time SQL lane (this file's own top comment).
- run: npm install -g tree-sitter-cli
# Everything, plus the end-to-end cases that build and exec the binary,
# raced over the full tree: jsonrpc2 and the spawn lock live in
# internal/lsp, but internal/diff and internal/gitx hold concurrent
# paths of their own. Live server and completion cases guard on
# exec.LookPath and skip cleanly when a server or shell (zsh here) is
# absent from the runner.
- run: go test -race -coverpkg=./... -coverprofile=coverage.out ./...
- run: go tool cover -func=coverage.out | tail -1
# Every other Go job here builds for linux/amd64 only, so without this one
# the published linux/arm64 and windows binaries would first be compiled
# at tag time, in release.yml -- a break there fails the release rather
# than the pull request that caused it. internal/lsp/owner_windows.go, the socket
# ownership check guarding LSP dial, has no other job that compiles it at
# all. Compile-only on purpose: running the suite on either platform is a
# separate question (the chmod/symlink cases would need skips first), and
# a link failure is the class of break these targets actually hit.
#
# One job rather than a job per target: runners bill each job
# rounded up to the whole minute, so parallel legs would pay the setup
# floor once per target. The speed comes from caching instead:
#
# - The generated SQL parser is a pure function of cmd/rgit-install
# (which pins the grammar version) and the tree-sitter CLI release, so
# it is cached on exactly those and regenerated (~60s) only when either
# changes. The CLI version is resolved rather than pinned so it stays
# latest. Without the CLI the build still succeeds, just SQL-less, and
# this job would never compile the SQL grammar the release ships.
# - GOCACHE gets a cache of its own, outside the one setup-go shares across
# every job under a single key: the cgo grammars (the SQL parser alone is
# 17 MB of C) compile once per target and are reused until go.sum or the
# generated parser changes. Go keys entries by GOOS/GOARCH/CC, so every
# target in the job shares the one directory.
# - `-o sql-parser` stops `make cross` regenerating a parser the cache (or
# the step before) already produced; -j builds the targets side by side
# on the runner's cores.
cross-compile:
name: cross-compile (linux, windows)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: Rethunk-Tech/gh-actions/setup-go@1417493f40c23b8826601ac9de8d7abda0b84ec6 # v1.15
# Same toolchain release.yml drives: rgit links tree-sitter through
# cgo, so every target needs a real C compiler (docs/INSTALL.md § Cross
# builds). setup-zig caches zig's global cache itself.
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
- name: resolve tree-sitter CLI version
id: tscli
run: echo "version=$(npm view tree-sitter-cli version)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: sql-csrc
with:
path: internal/resolve/sqlgrammar/csrc
key: sql-csrc-${{ runner.os }}-${{ steps.tscli.outputs.version }}-${{ hashFiles('cmd/rgit-install/**', '!cmd/rgit-install/*_test.go') }}
- if: steps.sql-csrc.outputs.cache-hit != 'true'
env:
TSCLI_VERSION: ${{ steps.tscli.outputs.version }}
run: npm install -g "tree-sitter-cli@$TSCLI_VERSION" && make sql-parser
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ runner.temp }}/go-build
key: go-build-cross-${{ runner.os }}-${{ steps.tscli.outputs.version }}-${{ hashFiles('go.sum') }}
restore-keys: go-build-cross-${{ runner.os }}-
- env:
GOCACHE: ${{ runner.temp }}/go-build
run: make -j3 -o sql-parser cross
# The cross-check is the one thing no other job exercises: every grammar
# measured against its own language server, on a corpus carrying the shapes
# that have actually broken. It found both a pyright convention difference
# that made multi-line Python bindings unstageable and a CSS grouped-selector
# defect, neither of which any unit test saw -- because both were
# disagreements between rgit and a real server, not within rgit.
#
# The only CI job depending on external npm and Go tooling, which is why it
# is separate: its failure means "grammar and server drifted", not "the
# build is broken".
xcheck:
name: lsp cross-check
runs-on: ubuntu-latest
timeout-minutes: 20
env:
# Pinned to the version docs/INSTALL.md names, so a gopls release cannot
# change what this job measures without the table changing too.
GOPLS_VERSION: v0.23.0
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: Rethunk-Tech/gh-actions/setup-go@1417493f40c23b8826601ac9de8d7abda0b84ec6 # v1.15
id: go
# Compiling gopls is ~40s; the binary depends only on its own version
# and the Go release that builds it, so it is cached on those two.
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: gopls
with:
path: ~/go/bin/gopls
key: gopls-${{ env.GOPLS_VERSION }}-${{ runner.os }}-go${{ steps.go.outputs.go-version }}
- if: steps.gopls.outputs.cache-hit != 'true'
run: go install "golang.org/x/tools/gopls@$GOPLS_VERSION"
# rust-analyzer ships as a rustup component, not on npm; the runner
# already has a Rust toolchain.
- run: rustup component add rust-analyzer
# vscode-langservers-extracted supplies the json, css and html servers
# from one package (docs/INSTALL.md § Language servers). Each is
# resolved to its current release first and the cache keyed on those
# versions (plus node's), so the servers stay latest while an
# unchanged set restores from a private npm prefix instead of
# reinstalling.
- name: resolve language server versions
id: lsp
shell: bash
run: |
prefix="$RUNNER_TEMP/npm-global"
echo "NPM_CONFIG_PREFIX=$prefix" >> "$GITHUB_ENV"
echo "$prefix/bin" >> "$GITHUB_PATH"
for p in @vtsls/language-server pyright bash-language-server yaml-language-server vscode-langservers-extracted; do
v=$(npm view "$p" version)
printf '%s@%s\n' "$p" "$v"
done > "$RUNNER_TEMP/lsp-packages.txt"
cat "$RUNNER_TEMP/lsp-packages.txt"
echo "key=node$(node --version)-$(sha256sum "$RUNNER_TEMP/lsp-packages.txt" | cut -c1-16)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: lsp-cache
with:
path: ${{ runner.temp }}/npm-global
key: lsp-servers-${{ runner.os }}-${{ steps.lsp.outputs.key }}
- if: steps.lsp-cache.outputs.cache-hit != 'true'
run: xargs npm i -g < "$RUNNER_TEMP/lsp-packages.txt"
# marksman publishes no package; the release binary is the only route.
# Pinned rather than /latest/download/: that endpoint is an extra redirect
# that 504s under load, and an unpinned binary changes the cross-check
# baseline without a commit.
- name: install marksman
env:
MARKSMAN_VERSION: "2026-02-08"
run: |
curl -fsSL --retry 5 --retry-all-errors --retry-max-time 120 \
-o /usr/local/bin/marksman \
"https://github.com/artempyanykh/marksman/releases/download/${MARKSMAN_VERSION}/marksman-linux-x64"
chmod +x /usr/local/bin/marksman
# Fails the job rather than reporting a smaller table: a run that
# silently skipped a server measures nothing about that language.
- run: make xcheck
install-script:
name: install script
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# shellcheck ships preinstalled on ubuntu-latest (actions/runner-images'
# own software manifest) -- no extra Action or install step needed, the
# same reasoning this file's own top comment gives for skipping a gcc
# setup step.
- run: shellcheck scripts/install.sh
# --dry-run never touches the network (docs/INSTALL.md § Install
# script): this is a lint of the script's own control flow -- flag
# parsing, OS/arch detection, the printed plan -- not a real download,
# so it needs nothing from the release it would otherwise fetch.
- run: VERSION=v1.2.0 sh scripts/install.sh --dry-run
- name: Show cosign plan in dry-run
run: |
mock_bin="$(mktemp -d)"
trap 'rm -rf "$mock_bin"' EXIT
printf '%s\n' \
'#!/bin/sh' \
'exit 0' >"$mock_bin/cosign"
chmod +x "$mock_bin/cosign"
output="$(
PATH="$mock_bin:/usr/bin:/bin" VERSION=v1.2.0 \
sh scripts/install.sh --dry-run
)"
printf '%s\n' "$output"
printf '%s\n' "$output" | grep -Fq 'cosign/Sigstore bundle' || {
printf '%s\n' 'missing cosign/Sigstore dry-run plan' >&2
exit 1
}
# No darwin binaries are published, so the script must refuse Darwin
# up front with a reason rather than 404 partway through a download.
- name: Refuse Darwin without network
run: |
mock_bin="$(mktemp -d)"
trap 'rm -rf "$mock_bin"' EXIT
printf '%s\n' \
'#!/bin/sh' \
"case \"\$1\" in" \
' -s) printf "%s\n" Darwin ;;' \
' -m) printf "%s\n" arm64 ;;' \
'esac' >"$mock_bin/uname"
chmod +x "$mock_bin/uname"
if output="$(
PATH="$mock_bin:$PATH" VERSION=v1.2.0 sh scripts/install.sh --dry-run 2>&1
)"; then
printf '%s\n' "$output"
printf '%s\n' 'install.sh accepted Darwin' >&2
exit 1
fi
printf '%s\n' "$output"
printf '%s\n' "$output" | grep -Fq 'no macOS binaries are published' || {
printf '%s\n' 'missing Darwin refusal message' >&2
exit 1
}
- name: Verify optional cosign branch without network
run: |
fixture="$(mktemp -d)"
mock_bin="$(mktemp -d)"
no_cosign_prefix="$(mktemp -d)"
cosign_prefix="$(mktemp -d)"
cosign_marker="$(mktemp)"
trap 'rm -rf "$fixture" "$mock_bin" "$no_cosign_prefix" "$cosign_prefix" "$cosign_marker"' EXIT
asset="rgit-v1.2.0-linux-amd64"
printf '%s\n' \
'#!/bin/sh' \
'printf "%s\n" "rgit v1.2.0"' >"$fixture/$asset"
chmod +x "$fixture/$asset"
asset_digest="$(sha256sum "$fixture/$asset" | cut -d' ' -f1)"
printf '%s %s\n' "$asset_digest" "$asset" >"$fixture/SHA256SUMS"
printf '%s\n' '{"mock":true}' >"$fixture/SHA256SUMS.sigstore.json"
export COSIGN_FIXTURE="$fixture"
export COSIGN_MARKER="$cosign_marker"
printf '%s\n' \
'#!/bin/sh' \
'set -eu' \
'out=' \
'url=' \
"while [ \"\$#\" -gt 0 ]; do" \
" case \"\$1\" in" \
" -o) out=\$2; shift 2 ;;" \
' -*) shift ;;' \
" *) url=\$1; shift ;;" \
' esac' \
'done' \
"case \"\$url\" in" \
" */SHA256SUMS.sigstore.json) cp \"\$COSIGN_FIXTURE/SHA256SUMS.sigstore.json\" \"\$out\" ;;" \
" */SHA256SUMS) cp \"\$COSIGN_FIXTURE/SHA256SUMS\" \"\$out\" ;;" \
" */rgit-v1.2.0-linux-amd64) cp \"\$COSIGN_FIXTURE/rgit-v1.2.0-linux-amd64\" \"\$out\" ;;" \
' *) exit 1 ;;' \
'esac' >"$mock_bin/curl"
chmod +x "$mock_bin/curl"
PATH="$mock_bin:/usr/bin:/bin" \
PREFIX="$no_cosign_prefix" VERSION=v1.2.0 \
sh scripts/install.sh
test ! -s "$cosign_marker"
printf '%s\n' \
'#!/bin/sh' \
'set -eu' \
"[ \"\${1:-}\" = verify-blob ]" \
'shift' \
'bundle=' \
'identity=' \
'issuer=' \
'checksum=' \
"while [ \"\$#\" -gt 0 ]; do" \
" case \"\$1\" in" \
" --bundle) bundle=\$2; shift 2 ;;" \
" --certificate-identity-regexp) identity=\$2; shift 2 ;;" \
" --certificate-oidc-issuer) issuer=\$2; shift 2 ;;" \
" *) checksum=\$1; shift ;;" \
' esac' \
'done' \
"[ \"\$bundle\" = SHA256SUMS.sigstore.json ]" \
"[ \"\$checksum\" = SHA256SUMS ]" \
"[ \"\$identity\" = \"https://github.com/Rethunk-Tech/rethunk-git-cli/.github/workflows/release.yml@.*\" ]" \
"[ \"\$issuer\" = \"https://token.actions.githubusercontent.com\" ]" \
"test -s \"\$bundle\"" \
"test -s \"\$checksum\"" \
"printf \"%s\n\" verified >\"\$COSIGN_MARKER\"" >"$mock_bin/cosign"
chmod +x "$mock_bin/cosign"
PATH="$mock_bin:/usr/bin:/bin" \
PREFIX="$cosign_prefix" VERSION=v1.2.0 \
sh scripts/install.sh
grep -Fqx verified "$cosign_marker"
install-script-windows:
name: install script (windows)
runs-on: windows-latest
timeout-minutes: 5
env:
VERSION: v1.2.0
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Verify Windows dry-run output
shell: pwsh
run: |
$mockBin = Join-Path $env:RUNNER_TEMP "rgit-cosign-dry-run-$([guid]::NewGuid().ToString('N'))"
New-Item -ItemType Directory -Path $mockBin | Out-Null
try {
Set-Content -LiteralPath (Join-Path $mockBin 'cosign.cmd') `
-Value @('@echo off', 'exit /b 0') -Encoding ascii
$oldPath = $env:PATH
$env:PATH = "$mockBin;$oldPath"
$outputLines = @(& .\scripts\install.ps1 -DryRun)
} finally {
$env:PATH = $oldPath
Remove-Item -LiteralPath $mockBin -Recurse -Force -ErrorAction SilentlyContinue
}
if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$output = $outputLines -join [Environment]::NewLine
$output
foreach ($expected in @(
'rgit-v1.2.0-windows-amd64.exe',
'SHA256SUMS',
'cosign/Sigstore bundle'
)) {
if (-not $output.Contains($expected)) {
throw "missing expected output: $expected"
}
}
- name: Verify optional cosign branch without network
shell: pwsh
run: |
$fixture = Join-Path $env:RUNNER_TEMP "rgit-install-fixture-$([guid]::NewGuid().ToString('N'))"
$mockBin = Join-Path $env:RUNNER_TEMP "rgit-cosign-$([guid]::NewGuid().ToString('N'))"
$noCosignBin = Join-Path $env:RUNNER_TEMP "rgit-no-cosign-$([guid]::NewGuid().ToString('N'))"
$noCosignPrefix = Join-Path $env:RUNNER_TEMP "rgit-no-cosign-prefix-$([guid]::NewGuid().ToString('N'))"
$cosignPrefix = Join-Path $env:RUNNER_TEMP "rgit-cosign-prefix-$([guid]::NewGuid().ToString('N'))"
$marker = Join-Path $env:RUNNER_TEMP "rgit-cosign-marker-$([guid]::NewGuid().ToString('N'))"
$asset = 'rgit-v1.2.0-windows-amd64.exe'
foreach ($directory in @(
$fixture,
$mockBin,
$noCosignBin,
$noCosignPrefix,
$cosignPrefix
)) {
New-Item -ItemType Directory -Path $directory | Out-Null
}
[IO.File]::WriteAllText($marker, '')
Copy-Item -LiteralPath (Join-Path $env:SystemRoot 'System32\where.exe') `
-Destination (Join-Path $fixture $asset)
$digest = (Get-FileHash -LiteralPath (Join-Path $fixture $asset) -Algorithm SHA256).Hash
Set-Content -LiteralPath (Join-Path $fixture 'SHA256SUMS') `
-Value "$digest $asset" -Encoding ascii
Set-Content -LiteralPath (Join-Path $fixture 'SHA256SUMS.sigstore.json') `
-Value '{"mock":true}' -Encoding ascii
function Invoke-WebRequest {
param(
[string]$Uri,
[string]$OutFile
)
$name = [IO.Path]::GetFileName(([Uri]$Uri).AbsolutePath)
Copy-Item -LiteralPath (Join-Path $env:COSIGN_FIXTURE $name) `
-Destination $OutFile -Force
}
$cosignScript = @(
'@echo off'
'if /i not "%~1"=="verify-blob" exit /b 1'
'if /i not "%~2"=="--bundle" exit /b 1'
'if /i not "%~nx3"=="SHA256SUMS.sigstore.json" exit /b 1'
'if not exist "%~3" exit /b 1'
'if /i not "%~4"=="--certificate-identity-regexp" exit /b 1'
'if not "%~5"=="https://github.com/Rethunk-Tech/rethunk-git-cli/.github/workflows/release.yml@.*" exit /b 1'
'if /i not "%~6"=="--certificate-oidc-issuer" exit /b 1'
'if not "%~7"=="https://token.actions.githubusercontent.com" exit /b 1'
'if /i not "%~nx8"=="SHA256SUMS" exit /b 1'
'if not exist "%~8" exit /b 1'
'> "%COSIGN_MARKER%" echo verified'
'exit /b 0'
)
Set-Content -LiteralPath (Join-Path $mockBin 'cosign.cmd') `
-Value $cosignScript -Encoding ascii
$oldPath = $env:PATH
try {
$env:COSIGN_FIXTURE = $fixture
$env:COSIGN_MARKER = $marker
$env:PATH = $noCosignBin
$env:PREFIX = $noCosignPrefix
& .\scripts\install.ps1
if (-not (Test-Path -LiteralPath (Join-Path $noCosignPrefix 'rgit.exe'))) {
throw 'Windows install without cosign did not produce rgit.exe'
}
if ((Get-Item -LiteralPath $marker).Length -ne 0) {
throw 'cosign marker was written on the no-cosign path'
}
$env:PATH = "$mockBin;$env:SystemRoot\System32"
$env:PREFIX = $cosignPrefix
& .\scripts\install.ps1
if (-not (Test-Path -LiteralPath (Join-Path $cosignPrefix 'rgit.exe'))) {
throw 'Windows install with cosign did not produce rgit.exe'
}
if ((Get-Content -LiteralPath $marker -Raw).Trim() -ne 'verified') {
throw 'cosign marker was not written by the cosign path'
}
} finally {
$env:PATH = $oldPath
$env:PREFIX = $null
Remove-Item -LiteralPath @(
$fixture,
$mockBin,
$noCosignBin,
$noCosignPrefix,
$cosignPrefix,
$marker
) -Recurse -Force -ErrorAction SilentlyContinue
}