diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..781add5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Shell scripts run under bash on every CI runner, including Windows checkouts with autocrlf. +*.sh text eol=lf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca37fec..dfbb05f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,20 +1,25 @@ +# Manual-only release build (PLAN §7.2): no push or pull_request trigger. +# Each RID job runs build -> test -> publish/pack -> upload; a failing step +# skips the rest of that job, so a RID whose tests fail produces no artifact. +# No GitHub Release is created. name: Build -# Manual trigger only. There is no push/pull_request trigger on purpose: -# the project must not depend on push-triggered CI. on: workflow_dispatch: -# Least privilege: the workflow only reads the repository and uploads artifacts. permissions: contents: read +env: + DOTNET_NOLOGO: "true" + DOTNET_CLI_TELEMETRY_OPTOUT: "true" + jobs: build: name: ${{ matrix.rid }} runs-on: ${{ matrix.os }} strategy: - # One failing RID must not cancel the others. + # Every RID stands on its own: one failure does not cancel the others. fail-fast: false matrix: include: @@ -22,35 +27,43 @@ jobs: os: windows-latest - rid: linux-x64 os: ubuntu-latest + - rid: osx-x64 + os: macos-15-intel - rid: osx-arm64 - os: macos-latest - + os: macos-15 + defaults: + run: + shell: bash steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET + - name: Set up .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x - - name: Restore - run: dotnet restore YKanBan.slnx - - name: Build - run: dotnet build YKanBan.slnx -c Release --no-restore + run: dotnet build YKanBan.slnx -c Release - # A failing test fails the job, so the publish and upload steps below are - # skipped and this RID produces no artifact. - name: Test - run: dotnet test YKanBan.slnx -c Release --no-build --no-restore + run: dotnet test YKanBan.slnx -c Release --no-build - name: Publish - run: dotnet publish YKanBan/YKanBan.csproj -c Release -r ${{ matrix.rid }} --self-contained true -o publish/${{ matrix.rid }} + run: > + dotnet publish YKanBan/YKanBan.csproj -c Release -r ${{ matrix.rid }} + --self-contained -o publish/${{ matrix.rid }} + + - name: Pack + id: pack + run: | + version="$(dotnet msbuild YKanBan/YKanBan.csproj -getProperty:Version)" + bash packaging/package.sh ${{ matrix.rid }} publish/${{ matrix.rid }} dist "$version" + echo "name=YKanBan-$version-${{ matrix.rid }}" >> "$GITHUB_OUTPUT" - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: YKanBan-${{ matrix.rid }} - path: publish/${{ matrix.rid }} + name: ${{ steps.pack.outputs.name }} + path: dist/* if-no-files-found: error diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..51e014a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 yyc12345 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 7ce16c5..ce9b71e 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,20 @@ A personal, local/offline kanban board. Cross-platform desktop app built with Av See [USAGE.md](USAGE.md) for workspaces, backups, the search syntax and command-line usage. -## Build and test +## Building + +Requires the .NET 10 SDK. ``` dotnet build YKanBan.slnx dotnet test YKanBan.slnx +dotnet run --project YKanBan -- ``` Packages are pinned in `YKanBan/YKanBan.csproj`. + +Release archives for win-x64, linux-x64, osx-x64 and osx-arm64 are produced by the manually triggered workflow in `.github/workflows/build.yml`; `packaging/package.sh` packs one published RID. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/YKanBan/Assets/avalonia-logo.ico b/YKanBan/Assets/avalonia-logo.ico deleted file mode 100644 index f7da8bb..0000000 Binary files a/YKanBan/Assets/avalonia-logo.ico and /dev/null differ diff --git a/YKanBan/Assets/ykanban.ico b/YKanBan/Assets/ykanban.ico new file mode 100644 index 0000000..24f7aa4 Binary files /dev/null and b/YKanBan/Assets/ykanban.ico differ diff --git a/YKanBan/Views/MainWindow.axaml b/YKanBan/Views/MainWindow.axaml index 36f5995..5b380ad 100644 --- a/YKanBan/Views/MainWindow.axaml +++ b/YKanBan/Views/MainWindow.axaml @@ -6,7 +6,7 @@ mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="640" x:Class="YKanBan.Views.MainWindow" x:DataType="vm:MainWindowViewModel" - Icon="/Assets/avalonia-logo.ico" + Icon="/Assets/ykanban.ico" Title="{Binding Title}" Width="1000" Height="640" MinWidth="720" MinHeight="480"> diff --git a/YKanBan/YKanBan.csproj b/YKanBan/YKanBan.csproj index a8f9181..202d87e 100644 --- a/YKanBan/YKanBan.csproj +++ b/YKanBan/YKanBan.csproj @@ -7,6 +7,7 @@ enable true app.manifest + Assets\ykanban.ico true diff --git a/packaging/macos/Info.plist b/packaging/macos/Info.plist new file mode 100644 index 0000000..4a2962d --- /dev/null +++ b/packaging/macos/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleName + YKanBan + CFBundleDisplayName + YKanBan + CFBundleIdentifier + cn.blumia.code.yyc12345.ykanban + CFBundleIconFile + ykanban.icns + CFBundleExecutable + YKanBan + CFBundlePackageType + APPL + CFBundleShortVersionString + @VERSION@ + CFBundleVersion + @VERSION@ + CFBundleInfoDictionaryVersion + 6.0 + NSHighResolutionCapable + + + diff --git a/packaging/macos/ykanban.icns b/packaging/macos/ykanban.icns new file mode 100644 index 0000000..6982ff9 Binary files /dev/null and b/packaging/macos/ykanban.icns differ diff --git a/packaging/package.sh b/packaging/package.sh new file mode 100755 index 0000000..5044319 --- /dev/null +++ b/packaging/package.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Packs one published RID into a release archive. +# +# packaging/package.sh +# +# win-x64 -> YKanBan--win-x64.zip (folder YKanBan--win-x64/) +# linux-x64 -> YKanBan--linux-x64.tar.gz (folder YKanBan--linux-x64/) +# osx-* -> YKanBan--.tar.gz (YKanBan.app bundle, ad-hoc signed when possible) +# +# tar.gz keeps the executable bit, which a plain zip artifact upload would lose. +set -euo pipefail + +if [ "$#" -ne 4 ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +rid="$1" +publish_dir="$(cd "$2" && pwd)" +mkdir -p "$3" +output_dir="$(cd "$3" && pwd)" +version="$4" +script_dir="$(cd "$(dirname "$0")" && pwd)" +name="YKanBan-${version}-${rid}" +staging="$(mktemp -d)" +trap 'rm -rf "$staging"' EXIT + +case "$rid" in + win-*) + cp -R "$publish_dir" "$staging/$name" + rm -f "$output_dir/$name.zip" + # 7-Zip ships on the Windows runners and writes '/' entry separators; + # Windows PowerShell 5.1's Compress-Archive writes '\', which breaks + # extraction outside Windows. + if ! command -v 7z > /dev/null 2>&1; then + echo "7z not found" >&2 + exit 1 + fi + (cd "$staging" && 7z a -tzip -bso0 -bsp0 "$output_dir/$name.zip" "$name") + ;; + linux-*) + cp -R "$publish_dir" "$staging/$name" + chmod +x "$staging/$name/YKanBan" + tar -C "$staging" -czf "$output_dir/$name.tar.gz" "$name" + ;; + osx-*) + app="$staging/YKanBan.app" + mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources" + cp -R "$publish_dir/." "$app/Contents/MacOS/" + cp "$script_dir/macos/ykanban.icns" "$app/Contents/Resources/" + chmod +x "$app/Contents/MacOS/YKanBan" + sed "s/@VERSION@/${version}/g" "$script_dir/macos/Info.plist" > "$app/Contents/Info.plist" + if command -v codesign > /dev/null 2>&1; then + # Ad-hoc signature for the bundle. Not fatal: the SDK already ad-hoc + # signs the apphost, which is all arm64 needs to run, and --deep may + # trip over the non-Mach-O files in Contents/MacOS. + if ! codesign --force --deep --sign - "$app"; then + echo "warning: ad-hoc signing of YKanBan.app failed; packing it unsigned" >&2 + fi + fi + tar -C "$staging" -czf "$output_dir/$name.tar.gz" YKanBan.app + ;; + *) + echo "unsupported RID: $rid" >&2 + exit 2 + ;; +esac + +echo "packed $rid -> $output_dir"