Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: Build (${{ matrix.os }})
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"

- name: Restore
run: dotnet restore SC4ModMigrationAssistant.csproj

- name: Build
run: dotnet build SC4ModMigrationAssistant.csproj -c Release --no-restore
178 changes: 178 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: Release

on:
push:
branches: [master]
tags: ["v*.*.*"]

permissions:
contents: write

jobs:
prepare:
name: Prepare version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.set.outputs.version }}
file_version: ${{ steps.set.outputs.file_version }}
release_tag: ${{ steps.set.outputs.release_tag }}
prerelease: ${{ steps.set.outputs.prerelease }}

steps:
- name: Determine version and release tag
id: set
shell: bash
run: |
if [[ "${{ github.ref_type }}" == "tag" ]]; then
TAG="${{ github.ref_name }}"
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Release tags must match vMAJOR.MINOR.PATCH, for example v0.4.0. Got: $TAG"
exit 1
fi
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
echo "file_version=${TAG#v}" >> "$GITHUB_OUTPUT"
echo "release_tag=$TAG" >> "$GITHUB_OUTPUT"
echo "prerelease=false" >> "$GITHUB_OUTPUT"
else
SHORT_SHA="${GITHUB_SHA:0:7}"
echo "version=0.0.0-dev.${{ github.run_number }}+$SHORT_SHA" >> "$GITHUB_OUTPUT"
echo "file_version=0.0.0-dev.${{ github.run_number }}-$SHORT_SHA" >> "$GITHUB_OUTPUT"
echo "release_tag=latest" >> "$GITHUB_OUTPUT"
echo "prerelease=true" >> "$GITHUB_OUTPUT"
fi

package:
name: Package ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
needs: prepare
continue-on-error: ${{ matrix.platform.experimental }}
strategy:
fail-fast: false
matrix:
platform:
- name: Windows
os: windows-latest
rid: win-x64
suffix: windows-x64
experimental: false
- name: Linux
os: ubuntu-latest
rid: linux-x64
suffix: linux-x64
experimental: true
- name: macOS
os: macos-latest
rid: osx-arm64
suffix: macos-arm64
experimental: true

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"

- name: Restore
run: dotnet restore SC4ModMigrationAssistant.csproj

- name: Set archive name
shell: bash
run: echo "ARCHIVE=SC4ModMigrationAssistant-${{ needs.prepare.outputs.file_version }}-${{ matrix.platform.suffix }}.zip" >> "$GITHUB_ENV"

- name: Publish
run: >
dotnet publish SC4ModMigrationAssistant.csproj -c Release
-r ${{ matrix.platform.rid }}
--self-contained true
-p:Version=${{ needs.prepare.outputs.version }}
-p:PublishSingleFile=true
-p:IncludeNativeLibrariesForSelfExtract=true
-p:IncludeAllContentForSelfExtract=true
-p:DebugType=None
-o publish/${{ matrix.platform.rid }}

- name: Ad-hoc sign macOS executable
if: runner.os == 'macOS'
run: codesign --force --sign - publish/${{ matrix.platform.rid }}/SC4ModMigrationAssistant

- name: Mark Unix executable bit
if: runner.os != 'Windows'
run: chmod +x publish/${{ matrix.platform.rid }}/SC4ModMigrationAssistant

- name: Create Windows archive
if: runner.os == 'Windows'
shell: pwsh
run: Compress-Archive -Path publish/${{ matrix.platform.rid }}/* -DestinationPath $env:ARCHIVE

- name: Create Unix archive
if: runner.os != 'Windows'
shell: bash
run: (cd publish/${{ matrix.platform.rid }} && zip -r -y "$OLDPWD/$ARCHIVE" .)

- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ env.ARCHIVE }}
path: ${{ env.ARCHIVE }}
if-no-files-found: error

publish:
name: Publish GitHub Release
runs-on: ubuntu-latest
needs: [prepare, package]

steps:
- name: Download artifacts
uses: actions/download-artifact@v7
with:
pattern: SC4ModMigrationAssistant-*
path: release-artifacts
merge-multiple: true

- name: Write release notes
shell: bash
run: |
cat > release-notes.md << 'EOF'
## Windows

Unzip and run `SC4ModMigrationAssistant.exe`. Single self-contained executable, no
other files needed.

## Linux

Unzip and run `./SC4ModMigrationAssistant` (executable bit already set in the
archive). Single self-contained executable, no other files needed.

## macOS

Apple Silicon (arm64) only, ad-hoc signed and unnotarized. After unzipping, clear the
quarantine flag before launching:

```sh
xattr -rd com.apple.quarantine SC4ModMigrationAssistant
```
EOF

- name: Replace rolling release
if: needs.prepare.outputs.release_tag == 'latest'
env:
GH_TOKEN: ${{ github.token }}
run: gh release delete latest --repo "${{ github.repository }}" --yes --cleanup-tag || true

- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
PRERELEASE_FLAG=()
if [[ "${{ needs.prepare.outputs.prerelease }}" == "true" ]]; then
PRERELEASE_FLAG=(--prerelease)
fi
gh release create "${{ needs.prepare.outputs.release_tag }}" release-artifacts/* \
--repo "${{ github.repository }}" \
--title "${{ needs.prepare.outputs.release_tag == 'latest' && 'Latest (master)' || needs.prepare.outputs.release_tag }}" \
--notes-file release-notes.md \
--target "${{ github.sha }}" \
"${PRERELEASE_FLAG[@]}"
111 changes: 111 additions & 0 deletions App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SC4ModMigrationAssistant.App"
RequestedThemeVariant="Dark">

<Application.Resources>
<!-- ===================== Synthwave dark palette (shared app-wide) ===================== -->

<!-- Overriding SystemAccentColor makes FluentTheme's own accent-driven visuals
(selection highlights, focus rings, default checked states, etc.) pick up our
purple accent automatically, on top of the more specific overrides below. -->
<Color x:Key="SystemAccentColor">#FF7B2FF7</Color>

<Color x:Key="BgTopColor">#FF1B1330</Color>
<Color x:Key="BgBottomColor">#FF241B3A</Color>
<SolidColorBrush x:Key="PanelBrush" Color="#FF1B1626" />
<SolidColorBrush x:Key="PanelLightBrush" Color="#FF2A2140" />
<SolidColorBrush x:Key="PanelHoverBrush" Color="#FF352A52" />
<SolidColorBrush x:Key="BorderAccentBrush" Color="#FF7B2FF7" />
<SolidColorBrush x:Key="AccentPinkBrush" Color="#FFFF3CAC" />
<SolidColorBrush x:Key="AccentCyanBrush" Color="#FF22D3EE" />
<SolidColorBrush x:Key="AccentAmberBrush" Color="#FFFFB86C" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#FFF1EAFB" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#FF9B8FC0" />
<SolidColorBrush x:Key="DisabledTextBrush" Color="#FF5C5478" />
<SolidColorBrush x:Key="DisabledBorderBrush" Color="#FF453A63" />

<LinearGradientBrush x:Key="WindowBackgroundBrush" StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Color="{StaticResource BgTopColor}" Offset="0" />
<GradientStop Color="{StaticResource BgBottomColor}" Offset="1" />
</LinearGradientBrush>

<LinearGradientBrush x:Key="ProgressFillBrush" StartPoint="0%,0%" EndPoint="100%,0%">
<GradientStop Color="#FFFF3CAC" Offset="0" />
<GradientStop Color="#FF7B2FF7" Offset="0.55" />
<GradientStop Color="#FF22D3EE" Offset="1" />
</LinearGradientBrush>
</Application.Resources>

<Application.Styles>
<FluentTheme />

<!-- ===================== Button ===================== -->
<Style Selector="Button">
<Setter Property="Background" Value="{StaticResource PanelLightBrush}" />
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderAccentBrush}" />
<Setter Property="BorderThickness" Value="1.5" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Padding" Value="10,6" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style Selector="Button:pointerover">
<Setter Property="Background" Value="{StaticResource PanelHoverBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource AccentCyanBrush}" />
</Style>
<Style Selector="Button:pressed">
<Setter Property="BorderBrush" Value="{StaticResource AccentPinkBrush}" />
</Style>
<Style Selector="Button:disabled">
<Setter Property="Foreground" Value="{StaticResource DisabledTextBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource DisabledBorderBrush}" />
</Style>

<!-- ===================== TextBox ===================== -->
<Style Selector="TextBox">
<Setter Property="Background" Value="{StaticResource PanelBrush}" />
<Setter Property="Foreground" Value="{StaticResource TextPrimaryBrush}" />
<Setter Property="CaretBrush" Value="{StaticResource AccentCyanBrush}" />
<Setter Property="SelectionBrush" Value="{StaticResource AccentPinkBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderAccentBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="3" />
</Style>

<!-- ===================== ProgressBar ===================== -->
<Style Selector="ProgressBar">
<Setter Property="Height" Value="18" />
<Setter Property="Background" Value="{StaticResource PanelBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderAccentBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Foreground" Value="{StaticResource ProgressFillBrush}" />
</Style>

<!-- ===================== ListBox / ListBoxItem (log + catalog results) ===================== -->
<Style Selector="ListBox">
<Setter Property="Background" Value="{StaticResource PanelBrush}" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="2,1" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Focusable" Value="False" />
</Style>
<Style Selector="ListBoxItem:selected">
<Setter Property="Background" Value="{StaticResource PanelHoverBrush}" />
</Style>
<Style Selector="ListBoxItem:pointerover">
<Setter Property="Background" Value="{StaticResource PanelHoverBrush}" />
</Style>

<!-- ===================== Message dialog ===================== -->
<Style Selector="Window.dialog">
<Setter Property="Background" Value="{StaticResource WindowBackgroundBrush}" />
</Style>
</Application.Styles>
</Application>
23 changes: 23 additions & 0 deletions App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

namespace SC4ModMigrationAssistant;

public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}

base.OnFrameworkInitializationCompleted();
}
}
6 changes: 0 additions & 6 deletions App.xaml

This file was deleted.

7 changes: 0 additions & 7 deletions App.xaml.cs

This file was deleted.

20 changes: 20 additions & 0 deletions Dialogs/MessageDialog.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SC4ModMigrationAssistant.Dialogs.MessageDialog"
Width="420" SizeToContent="Height" CanResize="False"
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False"
Classes="dialog">
<Border BorderBrush="{StaticResource BorderAccentBrush}" BorderThickness="1" CornerRadius="6" Padding="20">
<StackPanel Spacing="18">
<TextBlock x:Name="MessageText"
TextWrapping="Wrap"
Foreground="{StaticResource TextPrimaryBrush}"
FontSize="13" />
<StackPanel x:Name="ButtonPanel"
Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="10" />
</StackPanel>
</Border>
</Window>
Loading
Loading