-
Notifications
You must be signed in to change notification settings - Fork 0
442 lines (393 loc) · 15.1 KB
/
Copy pathci.yml
File metadata and controls
442 lines (393 loc) · 15.1 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
name: CI
on:
push:
branches: [main, "feature/*", "fix/*"]
tags: ["v*"]
pull_request:
branches: [main]
workflow_dispatch:
# Cancel in-progress runs for the same branch / PR
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
MIX_ENV: test
OTP_VERSION: "29.0"
ELIXIR_VERSION: "1.20.1"
jobs:
# ── Linux: full CI quality gate ─────────────────────────────────────
linux:
name: "Linux · Elixir 1.20.1 · OTP 29.0"
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
elixir-version: ${{ env.ELIXIR_VERSION }}
- name: Cache build artifacts
uses: actions/cache@v5
with:
path: |
_build
deps
priv/plts
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-
${{ runner.os }}-
- name: Install dependencies
run: mix deps.get
- name: Compile (warnings-as-errors)
# We intentionally don't pass --all-warnings: that flag treats
# third-party dep warnings (hpax bitstring ops, yamerl's
# deprecated catch, etc.) as errors. We only want to enforce
# warnings-as-errors on our own code, which `mix compile
# --warnings-as-errors` already does.
run: mix compile --warnings-as-errors
- name: Check formatting
run: mix format --check-formatted
- name: Credo (strict)
run: mix credo --strict
- name: Audit dependencies for CVEs
run: mix deps.audit
- name: Check for unused dependencies
run: mix deps.unlock --check-unused
- name: xref (no orphan modules)
run: mix xref graph --label compile-connected --fail-above 0
- name: Dialyzer
run: mix ci.dialyzer
timeout-minutes: 15
- name: Run tests with coverage
# mix test --cover runs the tests once and produces both
# the Elixir coverage report (cover/*.html) and the
# excoveralls JSON (cover/excoveralls.json via tool: ExCoveralls).
# The strict threshold check is disabled (threshold: 0 in
# mix.exs) until CLI integration tests are added.
run: mix test --cover
- name: Post coverage to Codecov
# Push the JSON to codecov.io using their bash uploader. This
# is the path recommended by excoveralls for Codecov.
#
# Requires CODECOV_TOKEN in repo Settings -> Secrets -> Actions.
# Get the token at https://codecov.io/gh/gilbertwong96/ado_cli
# -> Settings -> Upload Token. The bash uploader reads it from
# the CODECOV_TOKEN env var.
#
# Skip the step if the secret is not set. We don't fail the
# build over missing coverage uploads — coverage is still
# available as an action artifact download.
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
if [ -z "$CODECOV_TOKEN" ]; then
echo "::notice::CODECOV_TOKEN not set — skipping Codecov upload."
echo "Add it at https://codecov.io/gh/gilbertwong96/ado_cli"
echo "to enable the coverage badge."
exit 0
fi
curl -Os https://uploader.codecov.io/latest/linux/codecov
chmod +x codecov
./codecov --token "$CODECOV_TOKEN" --file ./cover/excoveralls.json
continue-on-error: true
- name: Upload coverage artifact
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-linux
path: cover/excoveralls.json
retention-days: 30
# ── macOS: smoke test (the CLI is cross-compiled via Burrito) ──────
macos:
name: "macOS · Elixir 1.20.1"
runs-on: macos-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
elixir-version: ${{ env.ELIXIR_VERSION }}
- name: Cache build artifacts
uses: actions/cache@v5
with:
path: |
_build
deps
priv/plts
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-
${{ runner.os }}-
- name: Install dependencies
run: mix deps.get
- name: Build escript
run: mix escript.build
- name: "Smoke test: --help works"
run: "./ado --help"
- name: "Smoke test: whoami with no auth returns friendly error"
run: |
rm -f "$HOME/.ado_cli/config.json"
./ado whoami || true
- name: "Smoke test: logout is idempotent"
run: ./ado logout
- name: Run tests
run: mix test
# ── Summary job — gate merge on all green ───────────────────────────
ci-status:
name: CI Status
if: always()
needs: [linux, macos]
runs-on: ubuntu-latest
steps:
- name: Fail if any matrix job failed
if: needs.linux.result != 'success' || needs.macos.result != 'success'
run: |
echo "Linux result: ${{ needs.linux.result }}"
echo "macOS result: ${{ needs.macos.result }}"
exit 1
# ── Release build: cross-compile via Burrito, rename, upload artifacts ─
release:
name: "Release · Burrito · ${{ matrix.label }}"
# Only build releases on pushes to main (not on PRs) and on tag pushes.
# Use workflow_dispatch to trigger manually for ad-hoc builds.
if: |
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))
needs: [ci-status]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
# Don't cancel other platforms if one fails — we want all
# binaries even if e.g. macOS signing fails.
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: linux
cpu: x86_64
ext: ""
label: "Linux x86_64"
artifact: ado-linux-x86_64
# ubuntu-latest reports ImageOS='ubuntu24', so we set it
# explicitly to avoid surprises if it ever changes.
imageos: ubuntu24
- os: ubuntu-24.04-arm
target: linux_arm
cpu: aarch64
ext: ""
label: "Linux ARM64"
artifact: ado-linux-aarch64
# ubuntu-24.04-arm reports ImageOS='ubuntu24-arm64'
# which erlef/setup-beam doesn't recognize. Use 'ubuntu24'
# (Erlang/Elixir prebuilt binaries are arch-independent,
# so the ubuntu24 ones work on the arm64 runner).
imageos: ubuntu24
- os: macos-latest
target: macos
cpu: aarch64
ext: ""
label: "macOS Apple Silicon"
artifact: ado-macos-aarch64
# macos-latest currently reports ImageOS='macos15'.
imageos: macos15
- os: macos-latest
target: macos_x86
cpu: x86_64
ext: ""
label: "macOS Intel"
artifact: ado-macos-x86_64
imageos: macos15
- os: windows-2022
target: windows
cpu: x86_64
ext: ".exe"
label: "Windows x86_64"
artifact: ado-windows-x86_64
# windows-2022 reports ImageOS='win22'.
imageos: win22
steps:
- uses: actions/checkout@v6
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
otp-version: ${{ env.OTP_VERSION }}
elixir-version: ${{ env.ELIXIR_VERSION }}
env:
# Drive ImageOS from the matrix instead of a hardcoded
# value. setup-beam requires ImageOS to be one of:
# ubuntu22, ubuntu24, win19, win22, win25, macos13,
# macos14, macos15, macos26. The runner's default may
# be unrecognized on non-x86 runners (e.g. ubuntu-24.04-arm
# reports 'ubuntu24-arm64'), so we override per-matrix-entry.
ImageOS: ${{ matrix.imageos }}
- name: Install Zig
# Burrito needs a specific zig version (0.16.0) to cross-compile
# BEAM. Install it via mlugg/setup-zig (the recommended
# successor to goto-bus-stop/setup-zig, which is now
# deprecated and hits GitHub's cache service unreliability).
uses: mlugg/setup-zig@v2.2.1
with:
version: 0.16.0
- name: Install xz
# Burrito needs xz to extract zig's tarball and musl. xz-utils
# ships with the Linux build image but not always; install
# defensively.
if: runner.os == 'Linux'
run: sudo apt-get -y install xz-utils
- name: Install xz (macOS)
# macOS runners may or may not ship with xz. We check
# first to avoid a noisy "already installed" warning
# from brew.
if: runner.os == 'macOS'
run: command -v xz >/dev/null 2>&1 || brew install xz
- name: Install 7zip (Windows)
# Burrito requires 7z/7zz in PATH to package Windows
# releases. The 7zip Chocolatey package ships with xz
# support (7z handles both .7z and .xz archives), so
# installing 7zip is sufficient — no separate xz package
# needed. (The 'xz' choco package was removed from
# chocolatey.org at some point.)
if: runner.os == 'Windows'
shell: pwsh
run: choco install -y 7zip
- name: Verify zig/xz
run: |
zig version
xz --version
- name: Cache build artifacts
uses: actions/cache@v5
with:
path: |
_build
deps
priv/plts
~/.cache/burrito_file_cache
key: release-${{ runner.os }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
release-${{ runner.os }}-
- name: Install dependencies
run: mix deps.get
- name: Build Burrito release
env:
MIX_ENV: prod
# Burrito accepts BURRITO_TARGET=<target-key> to build only
# that target from the targets list in mix.exs. This avoids
# cross-compiling for all 4 platforms on each runner.
BURRITO_TARGET: ${{ matrix.target }}
# Burrito writes the binary to burrito_out/ado_<target> by
# convention. We rename the output below to a stable,
# versioned name.
# Use bash explicitly so the `[[ ]]` / `mkdir -p` syntax
# works on Windows (the default PowerShell on windows-2022
# doesn't understand these).
shell: bash
run: |
mkdir -p release_bin
mix release --overwrite
ls -lh burrito_out/
- name: Get version
id: version
# bash shell: `$(...)` is bash syntax, not PowerShell.
shell: bash
run: |
# Read the version from mix.exs so the release artifact name
# always matches the source of truth.
VERSION=$(grep -E '^\s*version:\s*"' mix.exs | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Rename binary
# Normalize the binary name to:
# ado-<version>-<os>-<arch><.exe?>
# e.g. ado-0.1.0-macos-aarch64
# ado-0.1.0-linux-x86_64
# Use bash explicitly so the `[[ ]]` / `case` / `mv` syntax
# works on Windows (PowerShell on windows-2022 doesn't
# understand these). The smoke test below also uses bash.
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
# Map matrix.target to a clean {os}-{arch} string
case "${{ matrix.target }}" in
linux) SUFFIX="linux-x86_64" ;;
linux_arm) SUFFIX="linux-aarch64" ;;
macos) SUFFIX="macos-aarch64" ;;
macos_x86) SUFFIX="macos-x86_64" ;;
windows) SUFFIX="windows-x86_64" ;;
*) echo "::error::Unknown target ${{ matrix.target }}"; exit 1 ;;
esac
OUT_NAME="ado-${VERSION}-${SUFFIX}${{ matrix.ext }}"
SRC="burrito_out/ado_${{ matrix.target }}${{ matrix.ext }}"
DEST="release_bin/${OUT_NAME}"
if [[ ! -f "${SRC}" ]]; then
echo "::error::Expected Burrito output not found: ${SRC}"
ls -lh burrito_out
exit 1
fi
mv "${SRC}" "${DEST}"
chmod +x "${DEST}"
echo "Renamed ${SRC} -> ${DEST}"
ls -lh release_bin
- name: Smoke test the binary
# All release jobs run on a native runner for the target
# platform (e.g. ubuntu-24.04-arm for the Linux ARM build),
# so the binary can always be executed here.
# Use bash explicitly so the `[[ ]]` / `set -euo pipefail`
# syntax works on Windows (PowerShell doesn't understand
# these). On Windows the `file` command is provided by
# Git for Windows; the .exe is executable via bash.
shell: bash
run: |
set -euo pipefail
# Glob is intentional here — there's exactly one file in
# the dir, but actionlint warns about unquoted globs.
BINARY=$(ls release_bin/ado-*)
echo "Testing $BINARY"
file "$BINARY" || true
"$BINARY" --version || "$BINARY" --help | head -20
- name: Upload release binary
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}
path: release_bin/ado-*
retention-days: 90
if-no-files-found: error
# ── Attach binaries to GitHub Release on tag push ──────────────────
release-attach:
name: Publish GitHub Release
if: startsWith(github.ref, 'refs/tags/')
needs: [release]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v8
with:
path: dist/
merge-multiple: true
- name: Display downloaded artifacts
run: ls -lh dist/
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: "AdoCli ${{ github.ref_name }}"
body: |
## AdoCli ${{ github.ref_name }}
Pre-built binaries for all supported platforms.
| Platform | Architecture | Binary |
|----------|--------------|--------|
| Linux | x86_64 | `ado-*-linux-x86_64` |
| Linux | aarch64 | `ado-*-linux-aarch64` |
| macOS | Apple Silicon | `ado-*-macos-aarch64` |
| macOS | Intel | `ado-*-macos-x86_64` |
| Windows | x86_64 | `ado-*-windows-x86_64.exe` |
See [README.md](https://github.com/gilbertwong96/ado_cli) for
installation instructions.
draft: false
prerelease: false
files: |
dist/ado-*