Skip to content

Commit 1b1afc4

Browse files
authored
CI-CD (Build & Release) (#5)
* Added base kiosk functionality (check in/out) * Fixed line endings * Line endings * Fixed up rust QC * Testing ci-cd * Updated workflow for client build * Pinned flutter version in vars * CRLF -> LF * Updated buildable windows (fixed jank) * RM claude * rm duplicate message structure
1 parent 8b0f28b commit 1b1afc4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1005
-404
lines changed

.github/workflows/ci.yml

Lines changed: 266 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,282 @@
1-
name: Build
1+
name: Build & Release
22

33
on:
44
push:
55
branches:
66
- main
77
- master
8+
- ci-cd
89
pull_request:
910
branches:
1011
- main
1112
- master
13+
- ci-cd
1214

1315
jobs:
14-
pre-build:
15-
name: Pre-Build
16+
# ── Read variables from vars.yml ────────────────────────────────────
17+
read-vars:
18+
name: Read Variables
1619
runs-on: ubuntu-latest
20+
outputs:
21+
version: ${{ steps.parse.outputs.version }}
22+
flutter_version: ${{ steps.parse.outputs.flutter_version }}
23+
steps:
24+
- uses: actions/checkout@v4
25+
- name: Parse vars.yml
26+
id: parse
27+
run: |
28+
version=$(grep 'name: tk_version' vars.yml -A1 | grep 'value:' | awk '{print $2}')
29+
flutter_version=$(grep 'name: flutter_version' vars.yml -A1 | grep 'value:' | awk '{print $2}')
30+
echo "version=$version" >> "$GITHUB_OUTPUT"
31+
echo "flutter_version=$flutter_version" >> "$GITHUB_OUTPUT"
32+
echo "Version: $version"
33+
echo "Flutter: $flutter_version"
34+
35+
# ── Build Flutter web client ────────────────────────────────────────
36+
build-web-client:
37+
name: Build Web Client
38+
needs: [read-vars]
39+
runs-on: ubuntu-latest
40+
steps:
41+
- uses: actions/checkout@v4
42+
- uses: subosito/flutter-action@v2
43+
with:
44+
flutter-version: ${{ needs.read-vars.outputs.flutter_version }}
45+
channel: stable
46+
- name: Generate Flutter Icons
47+
working-directory: client
48+
run: dart run flutter_launcher_icons
49+
- name: Build Flutter web (release)
50+
working-directory: client
51+
run: flutter build web --release --wasm --no-web-resources-cdn
52+
- name: Upload web client artifact
53+
uses: actions/upload-artifact@v4
54+
with:
55+
name: web-client
56+
path: client/build/web/
57+
retention-days: 1
58+
59+
# ── Build server binaries ───────────────────────────────────────────
60+
build-server:
61+
name: Build Server (${{ matrix.name }})
62+
needs: [read-vars, build-web-client]
63+
strategy:
64+
matrix:
65+
include:
66+
- name: linux-x86_64
67+
runner: ubuntu-latest
68+
target: x86_64-unknown-linux-musl
69+
artifact: timekeeper-server-linux-x86_64
70+
archive: tar.gz
71+
- name: windows-x86_64
72+
runner: ubuntu-latest
73+
target: x86_64-pc-windows-gnu
74+
artifact: timekeeper-server-windows-x86_64
75+
archive: zip
76+
- name: macos-x86_64
77+
runner: macos-latest
78+
target: x86_64-apple-darwin
79+
artifact: timekeeper-server-macos-x86_64
80+
archive: tar.gz
81+
- name: macos-aarch64
82+
runner: macos-latest
83+
target: aarch64-apple-darwin
84+
artifact: timekeeper-server-macos-aarch64
85+
archive: tar.gz
86+
runs-on: ${{ matrix.runner }}
87+
env:
88+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89+
steps:
90+
- uses: actions/checkout@v4
91+
92+
- name: Install Protoc
93+
uses: arduino/setup-protoc@v3
94+
with:
95+
repo-token: ${{ secrets.GITHUB_TOKEN }}
96+
97+
- uses: actions-rust-lang/setup-rust-toolchain@v1
98+
with:
99+
target: ${{ matrix.target }}
100+
101+
# Linux-specific: install musl toolchain
102+
- name: Install musl tools
103+
if: matrix.target == 'x86_64-unknown-linux-musl'
104+
run: sudo apt-get update && sudo apt-get install -y musl-tools
105+
106+
# Windows cross-compile: install mingw-w64
107+
- name: Install mingw-w64
108+
if: matrix.target == 'x86_64-pc-windows-gnu'
109+
run: sudo apt-get update && sudo apt-get install -y gcc-mingw-w64-x86-64
110+
111+
# Build the server
112+
- name: Build server (release)
113+
run: cargo build --release --target ${{ matrix.target }}
114+
115+
# Download the web client artifact
116+
- name: Download web client
117+
uses: actions/download-artifact@v4
118+
with:
119+
name: web-client
120+
path: staging/client/build/web
121+
122+
# Prepare the release archive
123+
- name: Prepare archive (tar.gz)
124+
if: matrix.archive == 'tar.gz'
125+
run: |
126+
mkdir -p staging
127+
cp target/${{ matrix.target }}/release/main staging/timekeeper-server
128+
chmod +x staging/timekeeper-server
129+
cd staging
130+
tar -czf ../${{ matrix.artifact }}.tar.gz .
131+
132+
- name: Prepare archive (zip)
133+
if: matrix.archive == 'zip'
134+
run: |
135+
mkdir -p staging
136+
cp target/${{ matrix.target }}/release/main.exe staging/timekeeper-server.exe
137+
cd staging
138+
zip -r ../${{ matrix.artifact }}.zip .
139+
140+
- name: Upload release artifact
141+
uses: actions/upload-artifact@v4
142+
with:
143+
name: ${{ matrix.artifact }}
144+
path: ${{ matrix.artifact }}.${{ matrix.archive }}
145+
retention-days: 5
17146

147+
# ── Build client desktop & Android ──────────────────────────────────
148+
build-client:
149+
name: Build Client (${{ matrix.name }})
150+
needs: [read-vars]
151+
strategy:
152+
matrix:
153+
include:
154+
- name: linux-x86_64
155+
runner: ubuntu-latest
156+
build-cmd: linux
157+
artifact: timekeeper-client-linux-x86_64
158+
artifact-path: client/build/linux/x64/release/bundle
159+
archive: tar.gz
160+
- name: windows-x86_64
161+
runner: windows-latest
162+
build-cmd: windows
163+
artifact: timekeeper-client-windows-x86_64
164+
artifact-path: client/build/windows/x64/runner/Release
165+
archive: zip
166+
- name: macos-x86_64
167+
runner: macos-latest
168+
build-cmd: macos
169+
artifact: timekeeper-client-macos-x86_64
170+
artifact-path: client/build/macos/Build/Products/Release
171+
archive: zip
172+
- name: macos-aarch64
173+
runner: macos-latest
174+
build-cmd: macos
175+
artifact: timekeeper-client-macos-aarch64
176+
artifact-path: client/build/macos/Build/Products/Release
177+
archive: zip
178+
- name: android
179+
runner: ubuntu-latest
180+
build-cmd: apk
181+
artifact: timekeeper-client-android
182+
artifact-path: client/build/app/outputs/flutter-apk/app-release.apk
183+
archive: none
184+
runs-on: ${{ matrix.runner }}
18185
steps:
19186
- uses: actions/checkout@v4
187+
188+
- uses: subosito/flutter-action@v2
189+
with:
190+
flutter-version: ${{ needs.read-vars.outputs.flutter_version }}
191+
channel: stable
192+
193+
# Android needs Java
194+
- name: Setup Java
195+
if: matrix.build-cmd == 'apk'
196+
uses: actions/setup-java@v4
197+
with:
198+
distribution: temurin
199+
java-version: "17"
200+
201+
# Linux desktop needs build dependencies
202+
- name: Install Linux dependencies
203+
if: matrix.build-cmd == 'linux'
204+
run: |
205+
sudo apt-get update
206+
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev
207+
208+
- name: Generate Flutter Icons
209+
working-directory: client
210+
run: dart run flutter_launcher_icons
211+
212+
- name: Build client (release)
213+
working-directory: client
214+
run: flutter build ${{ matrix.build-cmd }} --release
215+
216+
# Archive: tar.gz (Linux)
217+
- name: Prepare archive (tar.gz)
218+
if: matrix.archive == 'tar.gz'
219+
run: |
220+
cd ${{ matrix.artifact-path }}
221+
tar -czf ${{ github.workspace }}/${{ matrix.artifact }}.tar.gz .
222+
223+
# Archive: zip (Windows/macOS)
224+
- name: Prepare archive (zip - Unix)
225+
if: matrix.archive == 'zip' && runner.os != 'Windows'
226+
run: |
227+
cd "${{ matrix.artifact-path }}"
228+
zip -r "${{ github.workspace }}/${{ matrix.artifact }}.zip" .
229+
230+
- name: Prepare archive (zip - Windows)
231+
if: matrix.archive == 'zip' && runner.os == 'Windows'
232+
shell: pwsh
233+
run: |
234+
Compress-Archive -Path "${{ matrix.artifact-path }}\*" -DestinationPath "${{ github.workspace }}\${{ matrix.artifact }}.zip"
235+
236+
# Upload archived artifact
237+
- name: Upload artifact (archived)
238+
if: matrix.archive != 'none'
239+
uses: actions/upload-artifact@v4
240+
with:
241+
name: ${{ matrix.artifact }}
242+
path: ${{ matrix.artifact }}.${{ matrix.archive }}
243+
retention-days: 5
244+
245+
# Upload APK directly (no archive needed)
246+
- name: Upload artifact (apk)
247+
if: matrix.archive == 'none'
248+
uses: actions/upload-artifact@v4
249+
with:
250+
name: ${{ matrix.artifact }}
251+
path: ${{ matrix.artifact-path }}
252+
retention-days: 5
253+
254+
# ── Create GitHub Release ───────────────────────────────────────────
255+
release:
256+
name: Create Release
257+
needs: [read-vars, build-server, build-client]
258+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci-cd')
259+
runs-on: ubuntu-latest
260+
permissions:
261+
contents: write
262+
steps:
263+
- uses: actions/checkout@v4
264+
265+
- name: Download all artifacts
266+
uses: actions/download-artifact@v4
267+
with:
268+
path: artifacts
269+
pattern: timekeeper-*
270+
271+
- name: List artifacts
272+
run: find artifacts -type f
273+
274+
- name: Create GitHub Release
275+
uses: softprops/action-gh-release@v2
276+
with:
277+
tag_name: v${{ needs.read-vars.outputs.version }}
278+
name: TimeKeeper v${{ needs.read-vars.outputs.version }}
279+
draft: true
280+
prerelease: false
281+
files: |
282+
artifacts/**/*

.github/workflows/qc.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ on:
1111
- master
1212

1313
jobs:
14+
read-vars:
15+
name: Read Variables
16+
runs-on: ubuntu-latest
17+
outputs:
18+
flutter_version: ${{ steps.parse.outputs.flutter_version }}
19+
steps:
20+
- uses: actions/checkout@v4
21+
- name: Parse vars.yml
22+
id: parse
23+
run: |
24+
flutter_version=$(grep 'name: flutter_version' vars.yml -A1 | grep 'value:' | awk '{print $2}')
25+
echo "flutter_version=$flutter_version" >> "$GITHUB_OUTPUT"
26+
echo "Flutter: $flutter_version"
27+
1428
protobuf-check:
1529
name: Protobuf Linting
1630
runs-on: ubuntu-latest
@@ -48,13 +62,15 @@ jobs:
4862
run: cargo clippy --all-targets -- -D warnings
4963

5064
flutter-check:
65+
needs: [read-vars]
5166
runs-on: ubuntu-latest
5267
env:
5368
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5469
steps:
5570
- uses: actions/checkout@v4
5671
- uses: subosito/flutter-action@v2
5772
with:
73+
flutter-version: ${{ needs.read-vars.outputs.flutter_version }}
5874
channel: stable
5975
- run: flutter --version
6076
- name: Get dependencies

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# Windows NUL device artifact
55
NUL
66

7+
# Claude
8+
.claude
9+
710
# User Specific Ignore
811
logs/
912
tk.db/

client/devtools_options.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
description: This file stores settings for Dart & Flutter DevTools.
2+
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3+
extensions:

client/lib/base/base_rail.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class BaseRail extends HookConsumerWidget {
99
@override
1010
Widget build(BuildContext context, WidgetRef ref) {
1111
// Derive selected index from current route instead of local state
12-
final selectedIndex = AppRoute.currentRailIndex(context) ?? 0;
12+
final selectedIndex = AppRoute.currentRailIndex(context);
1313
final isExtended = useState(false);
1414
return Positioned(
1515
left: 0,

client/lib/generated/api/location.pb.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'dart:core' as $core;
1414

1515
import 'package:protobuf/protobuf.dart' as $pb;
1616

17+
import '../common/common.pbenum.dart' as $2;
1718
import '../db/db.pb.dart' as $1;
1819

1920
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
@@ -216,9 +217,11 @@ class StreamLocationsRequest extends $pb.GeneratedMessage {
216217
class StreamLocationsResponse extends $pb.GeneratedMessage {
217218
factory StreamLocationsResponse({
218219
$core.Iterable<LocationResponse>? locations,
220+
$2.SyncType? syncType,
219221
}) {
220222
final result = create();
221223
if (locations != null) result.locations.addAll(locations);
224+
if (syncType != null) result.syncType = syncType;
222225
return result;
223226
}
224227

@@ -237,6 +240,8 @@ class StreamLocationsResponse extends $pb.GeneratedMessage {
237240
createEmptyInstance: create)
238241
..pPM<LocationResponse>(1, _omitFieldNames ? '' : 'locations',
239242
subBuilder: LocationResponse.create)
243+
..aE<$2.SyncType>(2, _omitFieldNames ? '' : 'syncType',
244+
enumValues: $2.SyncType.values)
240245
..hasRequiredFields = false;
241246

242247
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@@ -261,6 +266,15 @@ class StreamLocationsResponse extends $pb.GeneratedMessage {
261266

262267
@$pb.TagNumber(1)
263268
$pb.PbList<LocationResponse> get locations => $_getList(0);
269+
270+
@$pb.TagNumber(2)
271+
$2.SyncType get syncType => $_getN(1);
272+
@$pb.TagNumber(2)
273+
set syncType($2.SyncType value) => $_setField(2, value);
274+
@$pb.TagNumber(2)
275+
$core.bool hasSyncType() => $_has(1);
276+
@$pb.TagNumber(2)
277+
void clearSyncType() => $_clearField(2);
264278
}
265279

266280
const $core.bool _omitFieldNames =

0 commit comments

Comments
 (0)