chore: import upstream snapshot with attribution
.NET Tests / test-codebase (push) Waiting to run
Translation Validation / validate-translations (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:01:52 +08:00
commit 643e9f9fcb
1003 changed files with 247032 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# File auto-generated and managed by Devops
/.github/ @devolutions/devops @devolutions/architecture-maintainers @GabrielDuf # Gabriel Dufresne
/.github/dependabot.yml @devolutions/security-managers
+68
View File
@@ -0,0 +1,68 @@
name: '🐞 Report a bug or an issue'
description: Report issues or unexpected behaviors.
title: "[BUG] (Enter your description here)"
labels: ["bug"]
body:
- type: checkboxes
attributes:
label: Please confirm these before moving forward
description: Please confirm the following before posting your issue.
options:
- label: I have searched for my issue and have not found a work-in-progress/duplicate/resolved issue.
required: true
- label: I have tested that this issue has not been fixed in the latest [(beta or stable) release](https://github.com/Devolutions/UniGetUI/releases/).
required: true
- label: I have checked the [FAQ](https://github.com/Devolutions/UniGetUI#frequently-asked-questions) section for solutions.
required: true
- label: This issue is about a bug (if it is not, please use the [correct template](https://github.com/Devolutions/UniGetUI/issues/new/choose)).
required: true
- type: input
attributes:
label: UniGetUI Version
placeholder: 'x.y.z (e.g., 3.1.0)'
validations:
required: true
- type: input
attributes:
label: Windows version, edition, and architecture
placeholder: Windows 11 Pro 10.0.22000.0 x64
validations:
required: true
- type: textarea
attributes:
label: Describe your issue
placeholder: Explain the issue you are experiencing, providing as many details as possible.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce the issue
placeholder: How can this issue be reproduced? List here, if known, the steps followed before this issue appeared.
validations:
required: false
- type: textarea
attributes:
label: UniGetUI Log
render: "text"
placeholder: Paste your UniGetUI logs here. Click the button on the bottom-left corner of UniGetUI -> UniGetUI Log.
validations:
required: true
- type: textarea
attributes:
label: Package Managers Logs
render: "text"
placeholder: Paste your Package Manager logs here. Click the button on the bottom-left corner of UniGetUI -> Package Manager logs.
validations:
required: true
- type: textarea
attributes:
label: Relevant information
placeholder: Other relevant information about this issue. Perhaps you have some special settings enabled or any other detail that could be related.
validations:
required: false
- type: textarea
attributes:
label: Screenshots and videos
placeholder: If applicable, please post here a video or a screenshot of the issue.
validations:
required: false
+12
View File
@@ -0,0 +1,12 @@
blank_issues_enabled: false
contact_links:
- name: 📦 ISSUES INSTALLING/UPDATING A PACKAGE
url: https://github.com/Devolutions/UniGetUI
about: PLEASE READ THIS BEFORE CREATING AN ISSUE RELATED TO A SPECIFIC PACKAGE
- name: 🔒 Security issue or vulnerability
url: https://devolutions.net/security/
about: Found a security issue? Please report it via the Devolutions security page
- name: 📧 Contact us directly, in private
url: https://devolutions.net/contact/
about: Please use only for private inquiries that should not be posted publicly
@@ -0,0 +1,28 @@
name: '🚧 Suggest an improvement for an existing feature'
description: Propose an improvement to UniGetUI.
title: "[IMPROVEMENT] (Enter your description here)"
labels: ["enhancement"]
body:
- type: checkboxes
attributes:
label: Please confirm these before moving forward.
description: Please confirm the following before posting your issue.
options:
- label: I have searched for my feature proposal and have not found a work-in-progress/duplicate/resolved/discarded issue.
required: true
- label: This improvement refers to an existing feature. If you want to suggest a new feature, please use [this template](https://github.com/Devolutions/UniGetUI/issues/new?labels=new-feature&projects=&template=feature-request.yml&title=%5BFEATURE+REQUEST%5D+%28Enter+your+description+here%29).
required: true
- label: This improvement is not a bug. If you want to report a bug, please use [this template](https://github.com/Devolutions/UniGetUI/issues/new?labels=bug&projects=&template=bug-issue.yml&title=%5BBUG%5D+%28Enter+your+description+here%29).
required: true
- type: textarea
attributes:
label: Describe the improvement
placeholder: Explain how you see this enhancement, providing as many details as possible.
validations:
required: true
- type: textarea
attributes:
label: Describe how this improvement could help users
placeholder: For what would it be useful?
validations:
required: true
@@ -0,0 +1,26 @@
name: '💡 Propose a new feature'
description: Propose a new feature that could be useful in UniGetUI.
title: "[FEATURE REQUEST] (Enter your description here)"
labels: ["new-feature"]
body:
- type: checkboxes
attributes:
label: Please confirm these before moving forward.
description: Please confirm the following before posting your issue.
options:
- label: I have searched for my feature proposal and have not found a work-in-progress/duplicate/resolved/discarded issue.
required: true
- label: This proposal is a completely new feature. If you want to suggest an improvement or an enhancement, please use [this template](https://github.com/Devolutions/UniGetUI/issues/new?labels=enhancement&projects=&template=enhancement-improvement.yml&title=%5BENHANCEMENT%5D+%28Enter+your+description+here%29).
required: true
- type: textarea
attributes:
label: Describe the new feature
placeholder: Explain how you see this new feature, providing as many details as possible.
validations:
required: true
- type: textarea
attributes:
label: Describe how this new feature could help users
placeholder: For what would it be useful?
validations:
required: true
+35
View File
@@ -0,0 +1,35 @@
name: '🚑 Report a crash or a hang'
description: UniGetUI is not launching, is hard-crashing, or is hanging at some point.
title: "[CRASH] (Enter your description here)"
labels: ["bug", "important"]
body:
- type: checkboxes
attributes:
label: Please confirm these before moving forward.
description: Please confirm the following before posting your issue.
options:
- label: I have tried deleting a folder named `UniGetUI` under `%UserProfile%\AppData\Local\UniGetUI`.
required: true
- label: I have tried reinstalling UniGetUI.
required: true
- label: I have tested that this issue has not been fixed in the latest [(beta or stable) release](https://github.com/Devolutions/UniGetUI/releases/).
required: true
- type: textarea
attributes:
label: Describe your crash
placeholder: What were you doing when this happened?
validations:
required: true
- type: textarea
attributes:
label: Logs (if possible)
placeholder: If you get an error report or a message, please post it here.
render: "text"
validations:
required: false
- type: textarea
attributes:
label: More details
placeholder: Do you have any other valuable information about this issue?
validations:
required: false
+74
View File
@@ -0,0 +1,74 @@
name: '🧩 Report an issue with Widgets for UniGetUI'
description: Report issues or unexpected behaviors with Widgets for UniGetUI.
title: "[WIDGETS BUG] (Enter your description here)"
labels: ["bug", "widgets-for-unigetui"]
body:
- type: checkboxes
attributes:
label: Please confirm these before moving forward
description: Please confirm the following before posting your issue.
options:
- label: I have searched for my issue and have not found a work-in-progress/duplicate/resolved issue.
required: true
- label: I have tested that this issue has not been fixed in the latest [(beta or stable) release](https://github.com/Devolutions/UniGetUI/releases/).
required: true
- label: I have checked the [FAQ](https://github.com/Devolutions/UniGetUI#frequently-asked-questions) section for solutions.
required: true
- label: This issue is about a bug (if it is not, please use the [correct template](https://github.com/Devolutions/UniGetUI/issues/new/choose)).
required: true
- type: input
attributes:
label: UniGetUI Version
placeholder: 'x.y.z (e.g., 3.1.0)'
validations:
required: true
- type: input
attributes:
label: Widgets for UniGetUI Version
placeholder: 'x.y.z.w (e.g., 0.6.1.0)'
validations:
required: true
- type: input
attributes:
label: Windows version, edition, and architecture
placeholder: Windows 11 Pro 10.0.22000.0 x64
validations:
required: true
- type: textarea
attributes:
label: Describe your issue
placeholder: Explain the issue you are experiencing, providing as many details as possible.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce the issue
placeholder: How can this issue be reproduced? List here, if known, the steps followed before this issue appeared.
validations:
required: false
- type: textarea
attributes:
label: UniGetUI Log
render: "text"
placeholder: Paste your UniGetUI logs here. Click the button on the bottom-left corner of UniGetUI -> UniGetUI Log.
validations:
required: true
- type: textarea
attributes:
label: Package Managers Logs
render: "text"
placeholder: Paste your Package Manager logs here. Click the button on the bottom-left corner of UniGetUI -> Package Manager logs.
validations:
required: true
- type: textarea
attributes:
label: Relevant information
placeholder: Other relevant information about this issue. Perhaps you have some special settings enabled or any other detail that could be related.
validations:
required: false
- type: textarea
attributes:
label: Screenshots and videos
placeholder: If applicable, please post a video or a screenshot of the issue here.
validations:
required: false
+14
View File
@@ -0,0 +1,14 @@
<!-- Provide a general summary of your changes in the title above -->
- [ ] **I have read the [contributing guidelines](https://github.com/Devolutions/UniGetUI/blob/main/CONTRIBUTING.md#coding), and I agree with the [Code of Conduct](https://github.com/Devolutions/UniGetUI/blob/main/CODE_OF_CONDUCT.md)**.
- [ ] **Have you checked that there aren't other open [pull requests](https://github.com/Devolutions/UniGetUI/pulls) for the same changes?**
- [ ] **Have you tested that the committed code can be executed without errors?**
- [ ] **Have you confirmed that this issue is caused by UniGetUI itself, and not by the package manager or the package involved?**
If the same issue can be reproduced outside UniGetUI with the relevant package manager or with the package itself, please report it there first. UniGetUI should only be used to track issues that are specific to UniGetUI's behavior or integration.
-----
<!-- optionally, explain here about the committed code -->
-----
<!-- insert below the issue number (if applicable, do not create a new issue to justify a PR) -->
Closes #XXXX
<!-- and/or -->
Relates to #XXXX
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions-deps:
patterns:
- "*"
+10
View File
@@ -0,0 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"enabledManagers": [
"nuget"
],
"dependencyDashboard": true
}
+896
View File
@@ -0,0 +1,896 @@
name: Build and Release
on:
workflow_dispatch:
inputs:
version:
description: 'Release version (required, e.g. 2026.1.0)'
required: true
draft-release:
description: 'Create the GitHub Release as a draft'
required: true
type: boolean
default: false
skip-publish:
description: 'Skip publishing to GitHub Releases'
required: true
type: boolean
default: false
dry-run:
description: 'Dry run (simulate without publishing)'
required: true
type: boolean
default: true
jobs:
preflight:
name: Preflight
runs-on: ubuntu-latest
outputs:
package-env: ${{ steps.info.outputs.package-env }}
package-version: ${{ steps.info.outputs.package-version }}
onedrive-version: ${{ steps.info.outputs.onedrive-version }}
draft-release: ${{ steps.info.outputs.draft-release }}
skip-publish: ${{ steps.info.outputs.skip-publish }}
dry-run: ${{ steps.info.outputs.dry-run }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Resolve build parameters
id: info
shell: pwsh
run: |
$IsProductionBranch = @('main', 'master') -contains '${{ github.ref_name }}'
try { $DraftRelease = [System.Boolean]::Parse('${{ inputs.draft-release }}') } catch { $DraftRelease = $false }
try { $SkipPublish = [System.Boolean]::Parse('${{ inputs.skip-publish }}') } catch { $SkipPublish = $false }
try { $DryRun = [System.Boolean]::Parse('${{ inputs.dry-run }}') } catch { $DryRun = $true }
$PackageEnv = if ($IsProductionBranch) {
"publish-prod"
} else {
"publish-test"
}
if (-Not $IsProductionBranch) {
$DryRun = $true # force dry run when not on main/master branch
}
if (-Not $SkipPublish -And $PackageEnv -ne 'publish-prod') {
$DryRun = $true # force dry run when publishing outside production environment
}
$PackageVersion = '${{ inputs.version }}'
if ([string]::IsNullOrWhiteSpace($PackageVersion)) {
throw "The workflow_dispatch version input is required."
}
echo "package-env=$PackageEnv" >> $Env:GITHUB_OUTPUT
$OneDriveVersion = "$PackageVersion.0"
echo "package-version=$PackageVersion" >> $Env:GITHUB_OUTPUT
echo "onedrive-version=$OneDriveVersion" >> $Env:GITHUB_OUTPUT
echo "draft-release=$($DraftRelease.ToString().ToLower())" >> $Env:GITHUB_OUTPUT
echo "skip-publish=$($SkipPublish.ToString().ToLower())" >> $Env:GITHUB_OUTPUT
echo "dry-run=$($DryRun.ToString().ToLower())" >> $Env:GITHUB_OUTPUT
echo "::notice::Environment: $PackageEnv"
echo "::notice::Version: $PackageVersion"
echo "::notice::DraftRelease: $DraftRelease"
echo "::notice::DryRun: $DryRun"
build:
name: Build & Sign (${{ matrix.platform }})
runs-on: windows-latest
needs: [preflight]
environment: ${{ needs.preflight.outputs.package-env }}
permissions:
contents: read
env:
UNIGETUI_GITHUB_CLIENT_ID: ${{ secrets.UNIGETUI_GITHUB_CLIENT_ID }}
UNIGETUI_GITHUB_CLIENT_SECRET: ${{ secrets.UNIGETUI_GITHUB_CLIENT_SECRET }}
UNIGETUI_OPENSEARCH_USERNAME: ${{ secrets.UNIGETUI_OPENSEARCH_USERNAME }}
UNIGETUI_OPENSEARCH_PASSWORD: ${{ secrets.UNIGETUI_OPENSEARCH_PASSWORD }}
NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages
strategy:
fail-fast: false
matrix:
platform: [x64, arm64]
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Validate GitHub OAuth secrets
shell: pwsh
run: |
if ([string]::IsNullOrWhiteSpace($env:UNIGETUI_GITHUB_CLIENT_ID)) {
throw "UNIGETUI_GITHUB_CLIENT_ID is not configured for this build environment."
}
if ([string]::IsNullOrWhiteSpace($env:UNIGETUI_GITHUB_CLIENT_SECRET)) {
throw "UNIGETUI_GITHUB_CLIENT_SECRET is not configured for this build environment."
}
Write-Host "::notice::GitHub OAuth secrets are configured for this build."
- name: Install .NET
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@v6
with:
path: ${{ env.NUGET_PACKAGES }}
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Install Python
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Install Inno Setup
shell: pwsh
run: |
choco install innosetup -y --no-progress
echo "C:\Program Files (x86)\Inno Setup 6" >> $Env:GITHUB_PATH
- name: Install code-signing tools
shell: pwsh
run: |
dotnet tool install --global AzureSignTool
Install-Module -Name Devolutions.Authenticode -Force
# Trust test code-signing CA
$TestCertsUrl = "https://raw.githubusercontent.com/Devolutions/devolutions-authenticode/master/data/certs"
Invoke-WebRequest -Uri "$TestCertsUrl/authenticode-test-ca.crt" -OutFile ".\authenticode-test-ca.crt"
Import-Certificate -FilePath ".\authenticode-test-ca.crt" -CertStoreLocation "cert:\LocalMachine\Root"
Remove-Item ".\authenticode-test-ca.crt" -ErrorAction SilentlyContinue | Out-Null
- name: Set version
shell: pwsh
run: |
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
.\scripts\set-version.ps1 -Version $PackageVersion
- name: Restore dependencies
working-directory: src
run: dotnet restore UniGetUI.Windows.slnx
- name: Run tests
working-directory: src
shell: pwsh
run: |
dotnet build-server shutdown
# Retry once to handle flaky tests (e.g. TaskRecyclerTests uses Random)
dotnet test UniGetUI.Windows.slnx --no-restore --verbosity q --nologo /p:Platform=x64 /p:UseSharedCompilation=false /m:1
if ($LASTEXITCODE -ne 0) {
Write-Host "::warning::First test run failed, retrying..."
dotnet build-server shutdown
dotnet test UniGetUI.Windows.slnx --no-restore --verbosity q --nologo /p:Platform=x64 /p:UseSharedCompilation=false /m:1
if ($LASTEXITCODE -ne 0) { exit 1 }
}
- name: Publish
shell: pwsh
run: |
$Platform = '${{ matrix.platform }}'
$TargetFramework = (
dotnet msbuild src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
/nologo `
-getProperty:TargetFramework `
/p:Configuration=Release `
/p:Platform=$Platform `
/p:RuntimeIdentifier=win-$Platform |
Select-Object -Last 1
).Trim()
if ([string]::IsNullOrWhiteSpace($TargetFramework)) {
throw "Could not resolve the Avalonia target framework from MSBuild"
}
dotnet publish src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
/noLogo `
/p:Configuration=Release `
/p:Platform=$Platform `
/p:RuntimeIdentifier=win-$Platform `
/p:PublishProfile=Win-$Platform-NativeAot `
/p:UseSharedCompilation=false `
/p:BuildInParallel=false `
/m:1 `
-v m
if ($LASTEXITCODE -ne 0) { throw "dotnet publish Avalonia failed" }
# Stage binaries
$PublishDir = "src/UniGetUI.Avalonia/bin/$Platform/Release/$TargetFramework/win-$Platform/publish"
if (Test-Path "unigetui_bin") { Remove-Item "unigetui_bin" -Recurse -Force }
New-Item "unigetui_bin" -ItemType Directory | Out-Null
Get-ChildItem $PublishDir | Move-Item -Destination "unigetui_bin" -Force
if (-not (Test-Path "unigetui_bin/UniGetUI.exe")) {
throw "Windows app host was not produced at unigetui_bin/UniGetUI.exe"
}
$MaxShippedPdbSizeBytes = 1MB
$PdbsToRemove = Get-ChildItem "unigetui_bin" -Filter "*.pdb" -File -Recurse | Where-Object {
$_.Length -gt $MaxShippedPdbSizeBytes
}
if ($PdbsToRemove.Count -gt 0) {
$RemovedPdbBytes = ($PdbsToRemove | Measure-Object -Property Length -Sum).Sum
$PdbsToRemove | Remove-Item -Force
Write-Host ("Removed {0} oversized PDBs above {1:N2} MiB ({2:N2} MiB total)." -f $PdbsToRemove.Count, ($MaxShippedPdbSizeBytes / 1MB), ($RemovedPdbBytes / 1MB))
}
- name: Code-sign binaries
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
shell: pwsh
run: |
$ListPath = Join-Path $PWD "signing-files.txt"
$files = Get-ChildItem "unigetui_bin" -Recurse -Include "*.exe", "*.dll" | Where-Object {
(Get-AuthenticodeSignature $_.FullName).Status -eq "NotSigned"
}
$files.FullName | Set-Content $ListPath
Write-Host "Signing list contains $($files.Count) files."
.\scripts\sign.ps1 `
-FileListPath $ListPath `
-AzureTenantId '${{ secrets.AZURE_TENANT_ID }}' `
-KeyVaultUrl '${{ secrets.CODE_SIGNING_KEYVAULT_URL }}' `
-ClientId '${{ secrets.CODE_SIGNING_CLIENT_ID }}' `
-ClientSecret '${{ secrets.CODE_SIGNING_CLIENT_SECRET }}' `
-CertificateName '${{ secrets.CODE_SIGNING_CERTIFICATE_NAME }}' `
-TimestampServer '${{ vars.CODE_SIGNING_TIMESTAMP_SERVER }}'
- name: Build installer
shell: pwsh
run: |
$Platform = '${{ matrix.platform }}'
$OutputDir = Join-Path $PWD "output"
New-Item $OutputDir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
.\scripts\refresh-integrity-tree.ps1 -Path $PWD/unigetui_bin -FailOnUnexpectedFiles
# Configure Inno Setup to use AzureSignTool
$IssPath = "UniGetUI.iss"
# Build the installer (signing of the installer itself happens in the next step)
# Temporarily remove SignTool line so ISCC doesn't try to sign during build
$issContent = Get-Content $IssPath -Raw
try {
$issContentNoSign = $issContent -Replace '(?m)^SignTool=.*$', '; SignTool=azsign (disabled for CI, signed separately)'
$issContentNoSign = $issContentNoSign -Replace '(?m)^SignedUninstaller=yes', 'SignedUninstaller=no'
Set-Content $IssPath $issContentNoSign -NoNewline
$InstallerBaseName = "UniGetUI.Installer.$Platform"
$IsccArgs = @($IssPath, "/F$InstallerBaseName", "/O$OutputDir")
if ([System.Boolean]::Parse('${{ needs.preflight.outputs.dry-run }}') -eq $false) {
Write-Host "Using lzma/ultra64 installer compression for release build."
$IsccArgs = @('/DInstallerCompression=lzma/ultra64') + $IsccArgs
}
& ISCC.exe @IsccArgs
if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed with exit code $LASTEXITCODE" }
}
finally {
Set-Content $IssPath $issContent -NoNewline
}
- name: Stage output
shell: pwsh
run: |
$Platform = '${{ matrix.platform }}'
New-Item "output" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
.\scripts\refresh-integrity-tree.ps1 -Path $PWD/unigetui_bin -FailOnUnexpectedFiles
# Zip
Compress-Archive -Path "unigetui_bin/*" -DestinationPath "output/UniGetUI.$Platform.zip" -CompressionLevel Optimal
# Installer is created in output during the previous step
- name: Capture artifact sizes
shell: pwsh
run: |
$Platform = '${{ matrix.platform }}'
$SizesFile = "output/sizes.$Platform.json"
$UncompressedSize = (Get-ChildItem "unigetui_bin" -Recurse -File | Measure-Object -Property Length -Sum).Sum
$Artifacts = Get-ChildItem "output" -File | Where-Object { $_.Name -ne "sizes.$Platform.json" } | ForEach-Object {
@{ Name = $_.Name; SizeBytes = $_.Length }
}
$Report = @{
Platform = $Platform
RuntimeIdentifier = "win-$Platform"
UncompressedSizeBytes = $UncompressedSize
Artifacts = $Artifacts
Timestamp = [DateTimeOffset]::UtcNow.ToString("o")
}
$Report | ConvertTo-Json -Depth 4 | Set-Content $SizesFile -Encoding UTF8
Write-Host "Artifact sizes saved to $SizesFile"
Get-Content $SizesFile
- name: Code-sign installer
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
shell: pwsh
run: |
$Platform = '${{ matrix.platform }}'
.\scripts\sign.ps1 `
-InstallerPath "output/UniGetUI.Installer.$Platform.exe" `
-AzureTenantId '${{ secrets.AZURE_TENANT_ID }}' `
-KeyVaultUrl '${{ secrets.CODE_SIGNING_KEYVAULT_URL }}' `
-ClientId '${{ secrets.CODE_SIGNING_CLIENT_ID }}' `
-ClientSecret '${{ secrets.CODE_SIGNING_CLIENT_SECRET }}' `
-CertificateName '${{ secrets.CODE_SIGNING_CERTIFICATE_NAME }}' `
-TimestampServer '${{ vars.CODE_SIGNING_TIMESTAMP_SERVER }}'
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: UniGetUI-release-${{ matrix.platform }}
path: output/*
- name: Cleanup
if: always()
shell: pwsh
run: |
Remove-Item "unigetui_bin" -Recurse -Force -ErrorAction SilentlyContinue
build-avalonia:
name: Build (${{ matrix.name }})
runs-on: ${{ matrix.os }}
needs: [preflight]
environment: ${{ needs.preflight.outputs.package-env }}
permissions:
contents: read
env:
UNIGETUI_GITHUB_CLIENT_ID: ${{ secrets.UNIGETUI_GITHUB_CLIENT_ID }}
UNIGETUI_GITHUB_CLIENT_SECRET: ${{ secrets.UNIGETUI_GITHUB_CLIENT_SECRET }}
UNIGETUI_OPENSEARCH_USERNAME: ${{ secrets.UNIGETUI_OPENSEARCH_USERNAME }}
UNIGETUI_OPENSEARCH_PASSWORD: ${{ secrets.UNIGETUI_OPENSEARCH_PASSWORD }}
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
SIGNING_IDENTITY: 'Developer ID Application: Devolutions inc.'
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
name: macos-arm64
runtime: osx-arm64
publish_profile: osx-arm64-NativeAot
- os: macos-latest
name: macos-x64
runtime: osx-x64
publish_profile: osx-x64-NativeAot
- os: ubuntu-latest
name: linux-x64
runtime: linux-x64
publish_profile: linux-x64-NativeAot
- os: ubuntu-latest
name: linux-arm64
runtime: linux-arm64
publish_profile: linux-arm64-NativeAot
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install .NET
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@v6
with:
path: ${{ env.NUGET_PACKAGES }}
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Install Linux arm64 NativeAOT toolchain
if: matrix.runtime == 'linux-arm64'
shell: bash
run: |
set -euo pipefail
. /etc/os-release
sudo dpkg --add-architecture arm64
# Keep the runner's default Ubuntu feeds bound to amd64, then add the
# Ubuntu Ports arm64 feed that provides the target CRT and zlib objects.
if compgen -G "/etc/apt/sources.list.d/*.sources" > /dev/null; then
for source in /etc/apt/sources.list.d/*.sources; do
if ! grep -q '^Architectures:' "$source"; then
sudo sed -i '/^Types: deb$/a Architectures: amd64' "$source"
fi
done
fi
if [ -f /etc/apt/sources.list ]; then
sudo sed -i -E '/^deb /s/^deb /deb [arch=amd64] /' /etc/apt/sources.list
fi
sudo tee /etc/apt/sources.list.d/ubuntu-arm64.sources >/dev/null <<EOF
Types: deb
URIs: http://ports.ubuntu.com/ubuntu-ports/
Suites: ${VERSION_CODENAME} ${VERSION_CODENAME}-updates ${VERSION_CODENAME}-backports
Components: main restricted universe multiverse
Architectures: arm64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
Types: deb
URIs: http://ports.ubuntu.com/ubuntu-ports/
Suites: ${VERSION_CODENAME}-security
Components: main restricted universe multiverse
Architectures: arm64
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
EOF
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
clang \
llvm \
binutils-aarch64-linux-gnu \
gcc-aarch64-linux-gnu \
zlib1g-dev:arm64
- name: Set version
shell: pwsh
run: |
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
./scripts/set-version.ps1 -Version $PackageVersion
- name: Restore dependencies
working-directory: src
run: dotnet restore UniGetUI.Avalonia/UniGetUI.Avalonia.csproj --runtime ${{ matrix.runtime }}
- name: Publish
working-directory: src
shell: pwsh
run: |
$PublishProfile = '${{ matrix.publish_profile }}'
$PublishArgs = @(
'publish',
'UniGetUI.Avalonia/UniGetUI.Avalonia.csproj',
'--configuration', 'Release',
'--runtime', '${{ matrix.runtime }}',
'--self-contained', 'true',
'--output', '../bin/${{ matrix.name }}'
)
if ($PublishProfile) {
$PublishArgs += "/p:PublishProfile=$PublishProfile"
}
dotnet @PublishArgs
- name: Remove oversized Linux debug symbols
if: runner.os == 'Linux'
shell: pwsh
run: |
$PublishDir = "bin/${{ matrix.name }}"
$MaxDebugSymbolSizeBytes = 1MB
$DebugSymbolsToRemove = @(Get-ChildItem $PublishDir -Include "*.dbg", "*.pdb" -File -Recurse | Where-Object {
$_.Length -gt $MaxDebugSymbolSizeBytes
})
if ($DebugSymbolsToRemove.Count -gt 0) {
$RemovedDebugSymbolBytes = ($DebugSymbolsToRemove | Measure-Object -Property Length -Sum).Sum
$DebugSymbolsToRemove | Remove-Item -Force
Write-Host ("Removed {0} oversized Linux debug symbols above {1:N2} MiB ({2:N2} MiB total)." -f $DebugSymbolsToRemove.Count, ($MaxDebugSymbolSizeBytes / 1MB), ($RemovedDebugSymbolBytes / 1MB))
}
- name: Install macOS packaging tools
if: runner.os == 'macOS'
run: brew install create-dmg
- name: Check out Devolutions/actions
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
uses: actions/checkout@v7
with:
repository: Devolutions/actions
ref: v1
token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
path: ./.github/workflows
- name: Import Developer ID certificate
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
uses: ./.github/workflows/macos-code-sign-setup
with:
base64_certificate: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE }}
certificate_password: ${{ secrets.APPLE_APP_DEV_ID_APP_CERTIFICATE_PASSWORD }}
- name: Package (macOS)
if: ${{ runner.os == 'macOS' }}
run: |
set -euo pipefail
VERSION='${{ needs.preflight.outputs.package-version }}'
APP_BUNDLE="UniGetUI.app"
APP_EXECUTABLE="UniGetUI"
DRY_RUN='${{ needs.preflight.outputs.dry-run }}'
rm -rf "${APP_BUNDLE}" output
mkdir -p output
mkdir -p "${APP_BUNDLE}/Contents/MacOS"
mkdir -p "${APP_BUNDLE}/Contents/Resources"
cp -R "bin/${{ matrix.name }}/." "${APP_BUNDLE}/Contents/MacOS/"
find "${APP_BUNDLE}/Contents/MacOS" -name "*.pdb" -type f -delete
chmod +x "${APP_BUNDLE}/Contents/MacOS/${APP_EXECUTABLE}"
sed "s|@VERSION@|${VERSION}|g" \
scripts/macos/Info.plist \
> "${APP_BUNDLE}/Contents/Info.plist"
# Compile the appearance-aware app icon (Default/Dark/Tinted/Clear) from the Icon Composer
# .icon into the bundle: produces Assets.car + AppIcon.icns. Info.plist already declares
# CFBundleIconName=AppIcon, so macOS renders the appearance styles itself (incl. when closed).
xcrun actool scripts/macos/AppIcon.icon \
--compile "${APP_BUNDLE}/Contents/Resources" \
--app-icon AppIcon \
--output-partial-info-plist "$(mktemp).plist" \
--platform macosx \
--minimum-deployment-target 12.0 \
--errors --warnings
# Static .icns fallback for macOS < 26 (CFBundleIconFile) and the dmg volume icon.
cp scripts/macos/UniGetUI.icns "${APP_BUNDLE}/Contents/Resources/"
if [ "$DRY_RUN" = "false" ]; then
ENTITLEMENTS="scripts/macos/Entitlements.plist"
find "${APP_BUNDLE}/Contents/MacOS" -type f \! -name "${APP_EXECUTABLE}" -print0 \
| while IFS= read -r -d '' f; do
codesign --force --timestamp --options runtime \
--entitlements "$ENTITLEMENTS" \
--sign "$SIGNING_IDENTITY" "$f"
done
codesign --force --timestamp --options runtime \
--entitlements "$ENTITLEMENTS" \
--sign "$SIGNING_IDENTITY" "${APP_BUNDLE}"
codesign --verify --verbose=2 "${APP_BUNDLE}"
else
find "${APP_BUNDLE}/Contents/MacOS" -type f | while IFS= read -r f; do
codesign --force --sign - "$f" 2>/dev/null || true
done
codesign --force --sign - "${APP_BUNDLE}"
fi
COPYFILE_DISABLE=1 tar -czf "output/UniGetUI.${{ matrix.name }}.tar.gz" "${APP_BUNDLE}"
create-dmg \
--volname "UniGetUI" \
--volicon "scripts/macos/UniGetUI.icns" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 128 \
--icon "UniGetUI.app" 150 185 \
--hide-extension "UniGetUI.app" \
--app-drop-link 450 185 \
"output/UniGetUI.${{ matrix.name }}.dmg" \
"${APP_BUNDLE}"
test -f "output/UniGetUI.${{ matrix.name }}.tar.gz"
test -f "output/UniGetUI.${{ matrix.name }}.dmg"
- name: Sign DMG
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
run: |
set -euo pipefail
DMG="output/UniGetUI.${{ matrix.name }}.dmg"
codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$DMG"
codesign --verify --verbose=2 "$DMG"
- name: Notarize DMG
if: ${{ runner.os == 'macOS' && fromJSON(needs.preflight.outputs.dry-run) == false }}
uses: ./.github/workflows/macos-notarize
timeout-minutes: 15
with:
apple_password: ${{ secrets.APPLE_BOT_PASSWORD }}
package_path: output/UniGetUI.${{ matrix.name }}.dmg
- name: Install Linux packaging tools
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y rpm
- name: Package (Linux)
if: runner.os == 'Linux'
shell: pwsh
run: |
$Version = '${{ needs.preflight.outputs.package-version }}'
$Parts = $Version -split '-', 2
$BaseVersion = $Parts[0]
$Prerelease = if ($Parts.Count -gt 1) { $Parts[1] } else { $null }
# deb: use tilde notation for pre-releases (Ubuntu 18.04+ compatible)
$DebVersion = if ($Prerelease) { "${BaseVersion}~${Prerelease}" } else { $Version }
# rpm: pre-release encoded in Release/Iteration field (RHEL 8+ / RPM 4.14+)
$RpmVersion = $BaseVersion
$RpmIteration = if ($Prerelease) { "0.${Prerelease}" } else { '1' }
# Map .NET RID -> package arch names
$DebArch, $RpmArch = switch ('${{ matrix.runtime }}') {
'linux-arm64' { 'arm64', 'aarch64' }
default { 'amd64', 'x86_64' }
}
New-Item -ItemType Directory -Force -Path output | Out-Null
# .tar.gz
& tar -czf "output/UniGetUI.${{ matrix.name }}.tar.gz" -C "bin/${{ matrix.name }}" .
if ($LASTEXITCODE -ne 0) { exit 1 }
# .deb — Ubuntu 18.04+ (glibc 2.27, Debian policy 3.9.6)
& ./scripts/package-linux.ps1 `
-PackageType deb `
-SourceDir "bin/${{ matrix.name }}" `
-OutputPath "output/UniGetUI.${{ matrix.name }}.deb" `
-Version $DebVersion `
-Architecture $DebArch
# .rpm — RHEL 8+ (RPM 4.14, glibc 2.28)
& ./scripts/package-linux.ps1 `
-PackageType rpm `
-SourceDir "bin/${{ matrix.name }}" `
-OutputPath "output/UniGetUI.${{ matrix.name }}.rpm" `
-Version $RpmVersion `
-Iteration $RpmIteration `
-Architecture $RpmArch
- name: Capture artifact sizes
shell: pwsh
run: |
$Name = '${{ matrix.name }}'
$Runtime = '${{ matrix.runtime }}'
$SizesFile = "output/sizes.$Name.json"
$SourceDir = if ('${{ runner.os }}' -eq 'macOS') { "UniGetUI.app/Contents/MacOS" } else { "bin/$Name" }
$UncompressedSize = (Get-ChildItem $SourceDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
$Artifacts = Get-ChildItem "output" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne "sizes.$Name.json" } | ForEach-Object {
@{ Name = $_.Name; SizeBytes = $_.Length }
}
$Report = @{
Platform = $Name
RuntimeIdentifier = $Runtime
UncompressedSizeBytes = $UncompressedSize
Artifacts = $Artifacts
Timestamp = [DateTimeOffset]::UtcNow.ToString("o")
}
$Report | ConvertTo-Json -Depth 4 | Set-Content $SizesFile -Encoding UTF8
Write-Host "Artifact sizes saved to $SizesFile"
Get-Content $SizesFile
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: UniGetUI-${{ matrix.name }}
path: output/*
publish:
name: Publish GitHub Release
runs-on: ubuntu-latest
needs: [preflight, build, build-avalonia]
if: ${{ fromJSON(needs.preflight.outputs.skip-publish) == false }}
environment: ${{ needs.preflight.outputs.package-env }}
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v8
with:
path: output
- name: Add legacy installer filename
shell: pwsh
working-directory: output
run: |
$InstallerFiles = Get-ChildItem -Path . -Recurse -File -Filter "UniGetUI.Installer.x64.exe"
if (-not $InstallerFiles) {
throw "Could not find UniGetUI.Installer.x64.exe in downloaded artifacts"
}
$InstallerFiles | ForEach-Object {
$LegacyInstallerPath = Join-Path $_.DirectoryName "UniGetUI.Installer.exe"
Copy-Item -Path $_.FullName -Destination $LegacyInstallerPath -Force
Write-Host "Created legacy installer alias: $LegacyInstallerPath"
}
- name: Generate consolidated checksums
shell: pwsh
working-directory: output
run: |
$ChecksumFile = Join-Path $PWD "checksums.txt"
$ChecksumLines = Get-ChildItem -Path . -Recurse -File | Where-Object {
$_.Name -notmatch '^checksums(\..+)?\.txt$'
} | Sort-Object Name | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
"$hash $($_.Name)"
}
Set-Content -Path $ChecksumFile -Value $ChecksumLines -Encoding utf8NoBOM
echo "::group::checksums"
Get-Content $ChecksumFile
echo "::endgroup::"
- name: Generate consolidated size report
shell: pwsh
working-directory: output
run: |
$SizeFiles = Get-ChildItem -Path . -Recurse -File -Filter "sizes.*.json" | Sort-Object Name
if (-not $SizeFiles) {
Write-Host "No size files found."
return
}
$Rows = foreach ($file in $SizeFiles) {
$data = Get-Content $file.FullName -Raw | ConvertFrom-Json
$uncompressedMiB = [math]::Round($data.UncompressedSizeBytes / 1MB, 2)
foreach ($artifact in $data.Artifacts) {
[PSCustomObject]@{
Platform = $data.Platform
RID = $data.RuntimeIdentifier
Artifact = $artifact.Name
CompressedMiB = [math]::Round($artifact.SizeBytes / 1MB, 2)
UncompressedMiB = $uncompressedMiB
}
}
}
$SizeReportFile = Join-Path $PWD "sizes.md"
$Lines = @("# UniGetUI Release Artifact Sizes", "")
$Lines += $Rows | Format-Table -AutoSize | Out-String -Stream | Where-Object { $_.Trim() -ne '' }
Set-Content -Path $SizeReportFile -Value $Lines -Encoding utf8NoBOM
echo "::group::sizes"
Get-Content $SizeReportFile
echo "::endgroup::"
- name: Create GitHub Release
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: output
run: |
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
$DraftRelease = [System.Boolean]::Parse('${{ needs.preflight.outputs.draft-release }}')
$DryRun = [System.Boolean]::Parse('${{ needs.preflight.outputs.dry-run }}')
echo "::group::checksums"
Get-Content "./checksums.txt"
echo "::endgroup::"
$ReleaseTag = "v$PackageVersion"
$ReleaseTitle = "UniGetUI v${PackageVersion}"
$Repository = $Env:GITHUB_REPOSITORY
$DraftArg = if ($DraftRelease) { '--draft' } else { $null }
$Files = Get-ChildItem -Path . -Recurse -File | Where-Object {
$_.Name -eq 'checksums.txt' -or $_.Name -notmatch '^checksums\..+\.txt$'
}
if ($DryRun) {
Write-Host "Dry Run: skipping GitHub release creation!"
Write-Host "Would create release $ReleaseTag with title '$ReleaseTitle' (draft=$DraftRelease)"
$Files | ForEach-Object { Write-Host " - $($_.FullName)" }
} else {
if ($DraftArg) {
& gh release create $ReleaseTag --repo $Repository --title $ReleaseTitle $DraftArg $Files.FullName
} else {
& gh release create $ReleaseTag --repo $Repository --title $ReleaseTitle $Files.FullName
}
}
- name: Check out Devolutions/actions
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
uses: actions/checkout@v7
with:
repository: Devolutions/actions
ref: v1
token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
path: ./.github/workflows
- name: Install Devolutions Toolbox
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
uses: ./.github/workflows/toolbox-install
with:
github_token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
- name: Stage files for OneDrive upload
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
shell: pwsh
run: |
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
New-Item -Path "onedrive-staging" -ItemType Directory -Force | Out-Null
$OneDriveVersion = '${{ needs.preflight.outputs.onedrive-version }}'
$Mappings = @{
"output/UniGetUI-release-x64/UniGetUI.Installer.x64.exe" = "Devolutions.UniGetUI.win-x64.$OneDriveVersion.exe"
"output/UniGetUI-release-arm64/UniGetUI.Installer.arm64.exe" = "Devolutions.UniGetUI.win-arm64.$OneDriveVersion.exe"
"output/UniGetUI-release-x64/UniGetUI.x64.zip" = "Devolutions.UniGetUI.win-x64.$OneDriveVersion.zip"
"output/UniGetUI-release-arm64/UniGetUI.arm64.zip" = "Devolutions.UniGetUI.win-arm64.$OneDriveVersion.zip"
"output/UniGetUI-macos-x64/UniGetUI.macos-x64.dmg" = "Devolutions.UniGetUI.macos-x64.$OneDriveVersion.dmg"
"output/UniGetUI-macos-arm64/UniGetUI.macos-arm64.dmg" = "Devolutions.UniGetUI.macos-arm64.$OneDriveVersion.dmg"
"output/UniGetUI-macos-x64/UniGetUI.macos-x64.tar.gz" = "Devolutions.UniGetUI.macos-x64.$OneDriveVersion.tar.gz"
"output/UniGetUI-macos-arm64/UniGetUI.macos-arm64.tar.gz" = "Devolutions.UniGetUI.macos-arm64.$OneDriveVersion.tar.gz"
"output/UniGetUI-linux-x64/UniGetUI.linux-x64.deb" = "Devolutions.UniGetUI.linux-x64.$OneDriveVersion.deb"
"output/UniGetUI-linux-arm64/UniGetUI.linux-arm64.deb" = "Devolutions.UniGetUI.linux-arm64.$OneDriveVersion.deb"
"output/UniGetUI-linux-x64/UniGetUI.linux-x64.rpm" = "Devolutions.UniGetUI.linux-x64.$OneDriveVersion.rpm"
"output/UniGetUI-linux-arm64/UniGetUI.linux-arm64.rpm" = "Devolutions.UniGetUI.linux-arm64.$OneDriveVersion.rpm"
"output/UniGetUI-linux-x64/UniGetUI.linux-x64.tar.gz" = "Devolutions.UniGetUI.linux-x64.$OneDriveVersion.tar.gz"
"output/UniGetUI-linux-arm64/UniGetUI.linux-arm64.tar.gz" = "Devolutions.UniGetUI.linux-arm64.$OneDriveVersion.tar.gz"
}
foreach ($entry in $Mappings.GetEnumerator()) {
if (-not (Test-Path $entry.Key)) {
throw "File not found: $($entry.Key)"
}
Copy-Item -Path $entry.Key -Destination "onedrive-staging/$($entry.Value)"
Write-Host "Staged: $($entry.Key) -> $($entry.Value)"
}
- name: Upload to OneDrive
if: ${{ fromJSON(needs.preflight.outputs.dry-run) == false }}
uses: ./.github/workflows/onedrive-upload
with:
azure_client_id: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_ID }}
azure_client_secret: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_SECRET }}
conflict_behavior: replace
destination_path: /UniGetUI/${{ needs.preflight.outputs.onedrive-version }}
remote: releases
source_path: Devolutions.UniGetUI.*
working_directory: onedrive-staging
size-report:
name: Consolidated Size Report
runs-on: ubuntu-latest
needs: [preflight, build, build-avalonia]
if: always() && needs.build.result != 'cancelled' && needs.build-avalonia.result != 'cancelled'
steps:
- name: Download artifacts
uses: actions/download-artifact@v8
with:
path: output
- name: Generate consolidated size report
shell: pwsh
working-directory: output
run: |
$SizeFiles = Get-ChildItem -Path . -Recurse -File -Filter "sizes.*.json" | Sort-Object Name
if (-not $SizeFiles) {
Write-Host "No size files found."
return
}
$Rows = foreach ($file in $SizeFiles) {
$data = Get-Content $file.FullName -Raw | ConvertFrom-Json
$uncompressedMiB = [math]::Round($data.UncompressedSizeBytes / 1MB, 2)
foreach ($artifact in $data.Artifacts) {
[PSCustomObject]@{
Platform = $data.Platform
RID = $data.RuntimeIdentifier
Artifact = $artifact.Name
CompressedMiB = [math]::Round($artifact.SizeBytes / 1MB, 2)
UncompressedMiB = $uncompressedMiB
}
}
}
$SizeReportFile = Join-Path $PWD "sizes.md"
$Lines = @("# UniGetUI Release Artifact Sizes", "")
$Lines += $Rows | Format-Table -AutoSize | Out-String -Stream | Where-Object { $_.Trim() -ne '' }
Set-Content -Path $SizeReportFile -Value $Lines -Encoding utf8NoBOM
echo "::group::sizes"
Get-Content $SizeReportFile
echo "::endgroup::"
- name: Upload size report
uses: actions/upload-artifact@v7
with:
name: UniGetUI-size-report
path: output/sizes.md
+179
View File
@@ -0,0 +1,179 @@
name: CLI Headless E2E
on:
pull_request:
branches: [ "main" ]
paths:
- 'src/**/*.cs'
- 'src/**/*.csproj'
- 'src/**/*.props'
- 'src/**/*.targets'
- 'src/**/*.sln'
- 'src/**/*.slnx'
- 'testing/automation/**'
- '.github/workflows/cli-headless-e2e.yml'
- 'global.json'
workflow_dispatch:
jobs:
cli-headless-e2e:
strategy:
fail-fast: false
matrix:
include:
- name: Windows (Avalonia)
os: windows-latest
solution: UniGetUI.Windows.slnx
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
daemon_build_args: '-p:Platform=x64'
manifest: testing/automation/cli-e2e.manifest.windows.json
- name: Windows (NativeAOT)
os: windows-latest
solution: UniGetUI.Windows.slnx
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
daemon_publish_profile: Win-x64-NativeAot
daemon_build_args: '-p:Platform=x64'
manifest: testing/automation/cli-e2e.manifest.windows.json
- name: Linux (Avalonia)
os: ubuntu-latest
solution: UniGetUI.Avalonia.slnx
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
daemon_build_args: ''
manifest: testing/automation/cli-e2e.manifest.linux.json
- name: Linux (NativeAOT)
os: ubuntu-latest
solution: UniGetUI.Avalonia.slnx
daemon_project: UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
daemon_publish_profile: linux-x64-NativeAot
daemon_build_args: ''
manifest: testing/automation/cli-e2e.manifest.linux-nativeaot.json
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
env:
CONFIGURATION: Release
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Cache NuGet packages
uses: actions/cache@v6
with:
path: ${{ env.NUGET_PACKAGES }}
key: ${{ runner.os }}-nuget-e2e-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
restore-keys: |
${{ runner.os }}-nuget-e2e-
- name: Restore solution
working-directory: src
shell: pwsh
run: dotnet restore ${{ matrix.solution }}
- name: Build headless daemon
working-directory: src
shell: pwsh
run: |
$PublishProfile = '${{ matrix.daemon_publish_profile }}'
$BuildArgs = '${{ matrix.daemon_build_args }}'
dotnet build-server shutdown
if ($PublishProfile) {
$RuntimeIdentifier = $PublishProfile -replace '-NativeAot$', '' -replace '^Win-', 'win-' -replace '^linux-', 'linux-' -replace '^osx-', 'osx-'
$OutputPath = Join-Path (Resolve-Path '..') "artifacts/e2e-nativeaot/$RuntimeIdentifier"
New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null
$args = @(
'publish',
'${{ matrix.daemon_project }}',
'--no-restore',
'--configuration',
'${{ env.CONFIGURATION }}',
'--runtime',
$RuntimeIdentifier,
'--self-contained',
'true',
'--output',
$OutputPath,
'--verbosity',
'minimal',
'/p:UseSharedCompilation=false',
'/m:1',
"/p:PublishProfile=$PublishProfile"
)
if ($BuildArgs) {
$args += $BuildArgs
}
dotnet @args
$exeName = if ('${{ runner.os }}' -eq 'Windows') { 'UniGetUI.exe' } else { 'UniGetUI' }
$daemonExe = Join-Path $OutputPath $exeName
if (-not (Test-Path $daemonExe)) {
throw "NativeAOT daemon executable was not produced at $daemonExe"
}
if ('${{ runner.os }}' -ne 'Windows') {
chmod +x $daemonExe
}
echo "UNIGETUI_DAEMON_EXE=$daemonExe" >> $Env:GITHUB_ENV
}
else {
$args = @(
'build',
'${{ matrix.daemon_project }}',
'--no-restore',
'--configuration',
'${{ env.CONFIGURATION }}',
'--verbosity',
'minimal',
'/p:UseSharedCompilation=false',
'/m:1'
)
if ($BuildArgs) {
$args += $BuildArgs
}
dotnet @args
}
- name: Upgrade pip tooling
shell: pwsh
run: python -m pip install --upgrade pip setuptools wheel
- name: Show package-manager inventory
shell: pwsh
run: |
dotnet --version
python --version
python -m pip --version
npm --version
- name: Run headless CLI E2E
shell: pwsh
env:
UNIGETUI_CLI_E2E_MANIFEST: ${{ matrix.manifest }}
UNIGETUI_CLI_E2E_ARTIFACTS: ${{ github.workspace }}/artifacts/cli-headless-e2e/${{ runner.os }}
run: ./testing/automation/run-cli-e2e.ps1
- name: Upload CLI E2E artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: cli-headless-e2e-${{ matrix.name }}
path: artifacts/cli-headless-e2e/${{ runner.os }}
if-no-files-found: warn
+90
View File
@@ -0,0 +1,90 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: csharp
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"
+115
View File
@@ -0,0 +1,115 @@
name: .NET Tests
on:
push:
branches: [ "main" ]
paths:
- 'global.json'
- '**.cs'
- '**.axaml'
- '**.csproj'
- '**.props'
- '**.targets'
- '**.sln'
- '**.slnx'
- 'src/.editorconfig'
- '.github/workflows/dotnet-test.yml'
pull_request:
branches: [ "main" ]
paths:
- 'global.json'
- '**.cs'
- '**.axaml'
- '**.csproj'
- '**.props'
- '**.targets'
- '**.sln'
- '**.slnx'
- 'src/.editorconfig'
- '.github/workflows/dotnet-test.yml'
workflow_dispatch:
jobs:
test-codebase:
runs-on: windows-latest
env:
NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages
steps:
- name: Checkout the repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Install .NET SDK
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Cache NuGet packages
uses: actions/cache@v6
with:
path: ${{ env.NUGET_PACKAGES }}
key: ${{ runner.os }}-nuget-${{ hashFiles('global.json', 'src/**/*.csproj', 'src/**/*.props', 'src/**/*.targets', 'src/**/*.sln', 'src/**/*.slnx') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Install dependencies
working-directory: src
run: dotnet restore UniGetUI.Windows.slnx
- name: Check whitespace formatting
run: dotnet format whitespace src --folder --verify-no-changes --verbosity minimal
- name: Check code style formatting (Avalonia Windows solution)
working-directory: src
run: dotnet format style UniGetUI.Windows.slnx --no-restore --verify-no-changes --verbosity minimal
- name: Build Avalonia Windows solution
working-directory: src
run: |
dotnet build-server shutdown
dotnet build UniGetUI.Windows.slnx --no-restore --verbosity minimal /p:Platform=x64 /p:UseSharedCompilation=false /m:1
- name: Run Tests
working-directory: src
env:
GITHUB_TOKEN: ${{ github.token }}
run: dotnet test UniGetUI.Windows.slnx --no-restore --verbosity q --nologo /p:Platform=x64 /p:UseSharedCompilation=false /m:1
- name: Report full-trim publish warnings
working-directory: src
shell: pwsh
continue-on-error: true
run: |
dotnet build-server shutdown
dotnet publish UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
--no-restore `
--configuration Release `
--runtime win-x64 `
--self-contained true `
--output "${{ runner.temp }}\unigetui-full-trim-publish" `
/p:Platform=x64 `
/p:TrimMode=full `
/p:TrimmerSingleWarn=false `
/p:UseSharedCompilation=false `
--verbosity minimal
- name: Report NativeAOT publish warnings
working-directory: src
shell: pwsh
run: |
dotnet build-server shutdown
dotnet publish UniGetUI.Avalonia/UniGetUI.Avalonia.csproj `
--no-restore `
--configuration Release `
--runtime win-x64 `
--self-contained true `
--output "${{ runner.temp }}\unigetui-nativeaot-publish" `
/m:1 `
/p:Platform=x64 `
/p:PublishProfile=Win-x64-NativeAot `
/p:TrimmerSingleWarn=false `
/p:UseSharedCompilation=false `
--verbosity minimal
@@ -0,0 +1,44 @@
name: Translation Validation
on:
push:
branches: [ "main" ]
paths:
- 'scripts/translation/**'
- '.agents/skills/translation-*/scripts/**'
- 'src/Languages/**'
- 'src/Languages/Data/LanguagesReference.json'
- 'src/Languages/Data/TranslatedPercentages.json'
- 'src/Languages/Data/Translators.json'
- 'src/UniGetUI.Core.Data/Assets/Data/Contributors.list'
- '.github/workflows/translation-validation.yml'
pull_request:
branches: [ "main" ]
paths:
- 'scripts/translation/**'
- '.agents/skills/translation-*/scripts/**'
- 'src/Languages/**'
- 'src/Languages/Data/LanguagesReference.json'
- 'src/Languages/Data/TranslatedPercentages.json'
- 'src/Languages/Data/Translators.json'
- 'src/UniGetUI.Core.Data/Assets/Data/Contributors.list'
- '.github/workflows/translation-validation.yml'
workflow_dispatch:
jobs:
validate-translations:
runs-on: windows-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v7
- name: Validate translation file structure
shell: pwsh
run: pwsh ./scripts/translation/Verify-Translations.ps1
- name: Validate boundary alignment
shell: pwsh
run: pwsh ./scripts/translation/Set-TranslationBoundaryOrder.ps1 -CheckOnly