-
Notifications
You must be signed in to change notification settings - Fork 0
468 lines (416 loc) · 18.1 KB
/
Copy pathbuild.yml
File metadata and controls
468 lines (416 loc) · 18.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
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
name: Build NexAI
on:
push:
branches:
- "**"
workflow_dispatch:
inputs:
build_target:
description: "Build target platform"
required: true
default: "all"
type: choice
options:
- windows
- android
- web
- all
permissions:
contents: read
jobs:
build-android:
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.build_target == 'android' || github.event.inputs.build_target == 'all' }}
runs-on: ubuntu-latest
permissions:
contents: write
packages: read
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5
with:
distribution: "zulu"
java-version: "21"
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
with:
flutter-version: "3.44.5"
channel: "stable"
cache: true
- name: Cache Gradle dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
~/.gradle/caches/modules-2
~/.gradle/caches/jars-*
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('android/*.gradle*', 'android/**/*.gradle*', 'android/gradle/wrapper/gradle-wrapper.properties', 'pubspec.lock') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache Flutter pub dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-pub-
- name: Clean stale pub git cache
run: rm -rf ~/.pub-cache/git/
- name: Subset JetBrains Mono font
run: python scripts/subset_jetbrains_mono.py
- name: Install dependencies
run: flutter pub get
- name: Analyze Dart code
run: flutter analyze --no-fatal-infos
- name: Run Flutter tests
run: flutter test
- name: Resolve lumen-crash SDK
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
chmod +x scripts/resolve-lumen-crash.sh
bash scripts/resolve-lumen-crash.sh
echo "lumenCrashVersion=$(grep '^lumenCrashVersion=' android/gradle.properties | cut -d= -f2-)"
echo "local-maven contents:"
find android/local-maven -type f | sed 's/^/ /' || true
- name: Write signing config
run: |
# Ensure gradle.properties has Unix line endings
if [ -f "android/gradle.properties" ]; then
sed -i 's/\r$//' android/gradle.properties
# Ensure file ends with newline
[ -n "$(tail -c1 android/gradle.properties)" ] && echo "" >> android/gradle.properties
fi
missing=()
if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
missing+=("KEYSTORE_BASE64")
fi
if [ -z "${{ secrets.KEYSTORE_PASSWORD }}" ]; then
missing+=("KEYSTORE_PASSWORD")
fi
if [ -z "${{ secrets.KEY_ALIAS }}" ]; then
missing+=("KEY_ALIAS")
fi
if [ -z "${{ secrets.KEY_PASSWORD }}" ]; then
missing+=("KEY_PASSWORD")
fi
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::Missing Android release signing secrets: ${missing[*]}"
exit 1
fi
echo "✓ Decoding keystore from secret..."
if ! echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > ${{ github.workspace }}/nexai.jks; then
echo "::error::Failed to decode KEYSTORE_BASE64"
exit 1
fi
if [ ! -s "${{ github.workspace }}/nexai.jks" ]; then
echo "::error::Keystore file not created"
exit 1
fi
printf 'NEXAI_STORE_FILE=${{ github.workspace }}/nexai.jks\n' >> android/gradle.properties
printf 'NEXAI_STORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}\n' >> android/gradle.properties
printf 'NEXAI_KEY_ALIAS=${{ secrets.KEY_ALIAS }}\n' >> android/gradle.properties
printf 'NEXAI_KEY_PASSWORD=${{ secrets.KEY_PASSWORD }}\n' >> android/gradle.properties
echo "✓ Release signing configured"
echo "=== gradle.properties content (masked) ==="
grep -v "PASSWORD\|ALIAS" android/gradle.properties || true
echo "Has NEXAI_STORE_FILE: $(grep -c 'NEXAI_STORE_FILE' android/gradle.properties)"
- name: Accept Android SDK licenses
run: yes | flutter doctor --android-licenses || true
- name: Run build.ps1 (version control)
run: pwsh scripts/build.ps1 -Arg android
- name: Build Android APK
env:
NEXAI_APP_SIGN_SECRET: ${{ secrets.NEXAI_APP_SIGN_SECRET }}
run: |
# NEXAI_APP_SIGN_SECRET (GitHub Actions secret) signs anonymous/gated
# NexAI requests (e.g. /api/nexai/security/status) before login.
# Optional: if unset, client falls back to unsigned soft-skip (see
# lib/utils/request_signer.dart). Never add this define to Web builds
# — dart-define values are inspectable in the compiled web bundle.
flutter build apk --release --split-per-abi \
--obfuscate \
--tree-shake-icons \
--split-debug-info=debug_symbols/android \
--dart-define-from-file=nexai_release.json \
--dart-define=NEXAI_APP_SIGN_SECRET="$NEXAI_APP_SIGN_SECRET" \
--build-number=${{ env.VERSION_CODE }}
# - name: Build Android App Bundle
# run: flutter build appbundle --release --dart-define-from-file=nexai_release.json --build-number=${{ env.VERSION_CODE }}
- name: Rename APK outputs for release
run: |
VERSION="${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}"
echo "Version: $VERSION"
echo "=== Original APK files ==="
ls -lh build/app/outputs/flutter-apk/
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
if [ -f "$file" ]; then
abi=$(echo "$file" | sed -E 's|.*app-(.*)-release\.apk|\1|')
echo "Processing: $file -> NexAI_android_${VERSION}_${abi}.apk"
cp "$file" "build/app/outputs/flutter-apk/NexAI_android_${VERSION}_${abi}.apk"
fi
done
echo "=== Renamed APK files ==="
ls -lh build/app/outputs/flutter-apk/NexAI_android_*.apk
- name: Generate APK checksums
run: |
cd build/app/outputs/flutter-apk
sha256sum NexAI_android_*.apk > APK_SHA256SUMS.txt
python - <<'PY'
import hashlib, json, pathlib, os
tag = "v${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}"
assets = []
for path in sorted(pathlib.Path(".").glob("NexAI_android_*.apk")):
data = path.read_bytes()
assets.append({
"name": path.name,
"sha256": hashlib.sha256(data).hexdigest(),
"size": len(data),
})
pathlib.Path("release-manifest.json").write_text(json.dumps({
"schemaVersion": 1,
"tag": tag,
"generatedAt": "${{ github.run_id }}",
"assets": assets,
}, indent=2), encoding="utf-8")
PY
{
echo "## NexAI ${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}"
echo ""
echo "### APK SHA256"
while read -r hash file; do
echo "- \`$file\`"
echo " - sha256:$hash"
done < APK_SHA256SUMS.txt
} > ${{ github.workspace }}/RELEASE_NOTES.md
- name: Automatic release
if: ${{ success() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
make_latest: true
body_path: RELEASE_NOTES.md
tag_name: "v${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}"
prerelease: false
name: "NexAI ${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}"
files: |
build/app/outputs/flutter-apk/NexAI_android_*.apk
build/app/outputs/flutter-apk/APK_SHA256SUMS.txt
build/app/outputs/flutter-apk/release-manifest.json
# - name: Rename AAB output
# run: |
# VERSION="${{ env.version }}"
# AAB_FILE="build/app/outputs/bundle/release/app-release.aab"
# if [ -f "$AAB_FILE" ]; then
# echo "Renaming AAB: $AAB_FILE -> NexAI_android_${VERSION}.aab"
# cp "$AAB_FILE" "build/app/outputs/bundle/release/NexAI_android_${VERSION}.aab"
# fi
#
# echo "=== AAB files ==="
# ls -lh build/app/outputs/bundle/release/
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: android-debug-symbols
path: debug_symbols/
retention-days: 90
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: NexAI-Android-APK
path: build/app/outputs/flutter-apk/*.apk
retention-days: 30
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: android-outputs
path: build/app/outputs/
# - name: Commit generated files
# run: |
# git config user.name "github-actions[bot]"
# git config user.email "github-actions[bot]@users.noreply.github.com"
# git add -A
# git diff --cached --quiet || git commit -m "ci: generate android build files [skip ci]"
# git push || true
# env:
# GITHUB_
build-web:
# if: false # Disabled - Web build not needed
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.build_target == 'web' || github.event.inputs.build_target == 'all' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
with:
flutter-version: "3.44.5"
channel: "stable"
cache: true
- name: Cache Flutter pub dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-pub-
- name: Subset JetBrains Mono font
run: python scripts/subset_jetbrains_mono.py
- name: Install dependencies
run: flutter pub get
- name: Analyze Dart code
run: flutter analyze --no-fatal-infos
- name: Run Flutter tests
run: flutter test
- name: Run build.ps1 (version control)
run: pwsh scripts/build.ps1
- name: Build Web
run: flutter build web --release --dart-define-from-file=nexai_release.json
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: NexAI-Web
path: build/web/
retention-days: 30
build-windows:
if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.build_target == 'windows' || github.event.inputs.build_target == 'all' }}
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Setup .NET
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: '9.0.x'
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2
- name: Compute version metadata
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$commitHash = (git rev-parse HEAD).Trim()
$shortHash = $commitHash.Substring(0, 9)
$versionName = '1.0.7'
if (Test-Path 'pubspec.yaml') {
$line = Select-String -Path 'pubspec.yaml' -Pattern '^\s*version:\s*([\d\.]+)' | Select-Object -First 1
if ($line -and $line.Matches.Count -gt 0) {
$versionName = $line.Matches[0].Groups[1].Value
}
}
$displayName = "$versionName-$shortHash"
"VERSION_NAME=$versionName" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"SHORT_HASH=$shortHash" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"VERSION_DISPLAY=$displayName" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Host "WinUI version: $displayName"
- name: Restore WinUI solution
shell: pwsh
run: |
msbuild winui\NexAI.WinUI3.sln /t:Restore /p:Configuration=Release /p:Platform=x64 /v:m
- name: Precheck WinUI resource keys
shell: pwsh
run: |
# Fail fast with ALL PRI resource/scope collisions in one pass.
# MSBuild PRI generation typically only surfaces the first collision.
pwsh -File scripts/check-winui-resw.ps1 -CheckCodeReferences
- name: Build WinUI3 client
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
New-Item -ItemType Directory -Force -Path artifacts | Out-Null
$log = (Join-Path $PWD 'artifacts/winui-build.log')
# Avoid ';' inside /flp: PowerShell/GHA argument parsing can split it and
# MSBuild then treats the log path as a second project (MSB1008).
$msbuildArgs = @(
'winui/NexAI.WinUI3/NexAI.WinUI3.csproj',
'/p:Configuration=Release',
'/p:Platform=x64',
'/p:WindowsPackageType=None',
'/p:WindowsAppSDKSelfContained=true',
'/p:Restore=false',
'/v:n',
'/fl',
"/flp:LogFile=$log",
'/flp:Verbosity=normal'
)
Write-Host ("msbuild " + ($msbuildArgs -join ' '))
& msbuild @msbuildArgs
$code = $LASTEXITCODE
if ($code -ne 0) {
Write-Host '--- WinUI build failed. Emitting error/warning summary from log ---'
if (Test-Path -LiteralPath $log) {
Select-String -LiteralPath $log -Pattern ' error | warning ' | ForEach-Object { $_.Line }
} else {
Write-Host "Log file not found: $log"
}
exit $code
}
- name: Upload WinUI build log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: winui-build-log
path: artifacts/winui-build.log
if-no-files-found: ignore
retention-days: 14
- name: Package Windows full zip
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$candidates = @(
'winui/NexAI.WinUI3/bin/x64/Release/net9.0-windows10.0.19041.0',
'winui/NexAI.WinUI3/bin/Release/net9.0-windows10.0.19041.0',
'winui/NexAI.WinUI3/bin/x64/Release/net9.0-windows10.0.19041.0/win-x64',
'winui/NexAI.WinUI3/bin/Release/net9.0-windows10.0.19041.0/win-x64'
)
$source = $null
foreach ($candidate in $candidates) {
if (Test-Path $candidate) {
$exe = Get-ChildItem -Path $candidate -Filter NexAI.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
if ($exe) {
$source = $exe.Directory.FullName
break
}
}
}
if (-not $source) {
$exe = Get-ChildItem -Path 'winui/NexAI.WinUI3/bin' -Filter NexAI.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
if ($exe) { $source = $exe.Directory.FullName }
}
if (-not $source) {
Get-ChildItem -Recurse winui/NexAI.WinUI3/bin -ErrorAction SilentlyContinue | Select-Object -First 80 FullName
throw 'WinUI build output not found.'
}
$version = if ($env:VERSION_DISPLAY) { $env:VERSION_DISPLAY } else { 'dev' }
$zipName = "NexAI_windows_${version}_x64.zip"
$zipPath = Join-Path $PWD $zipName
if (Test-Path $zipPath) { Remove-Item -Force $zipPath }
Write-Host "Packaging from: $source"
Compress-Archive -Path (Join-Path $source '*') -DestinationPath $zipPath -Force
$hash = (Get-FileHash -Algorithm SHA256 -Path $zipPath).Hash.ToLowerInvariant()
Set-Content -Path 'WINDOWS_SHA256SUMS.txt' -Value "$hash $zipName" -Encoding utf8
"WINDOWS_ZIP_NAME=$zipName" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"WINDOWS_ZIP_PATH=$zipPath" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Get-Item $zipPath | Format-List FullName, Length
Get-Content 'WINDOWS_SHA256SUMS.txt'
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: NexAI-Windows
path: |
${{ env.WINDOWS_ZIP_PATH }}
WINDOWS_SHA256SUMS.txt
retention-days: 30
- name: Publish Windows zip to GitHub Release
if: ${{ success() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
make_latest: true
tag_name: "v${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}"
prerelease: false
name: "NexAI ${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}"
body: |
## NexAI ${{ env.VERSION_NAME }}-${{ env.SHORT_HASH }}
### Windows (WinUI3)
- Full runnable package: `${{ env.WINDOWS_ZIP_NAME }}`
- Native WinUI3 client from `winui/`
- Checksum file: `WINDOWS_SHA256SUMS.txt`
files: |
${{ env.WINDOWS_ZIP_PATH }}
WINDOWS_SHA256SUMS.txt