feat: add fork, worktree and handoff support, also enhance desktop UI… #53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Multi-Platform | |
| on: | |
| push: | |
| branches: [ master, main ] | |
| tags: | |
| - 'v*.*.*' | |
| pull_request: | |
| branches: [ master, main ] | |
| types: [labeled] | |
| workflow_dispatch: | |
| jobs: | |
| test: | |
| if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'build') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Restore dependencies | |
| run: dotnet restore dotcraft.sln | |
| - name: Run tests | |
| run: dotnet test dotcraft.sln --configuration Release --no-restore | |
| build: | |
| needs: test | |
| if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'build') | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| os_name: linux | |
| runtime_id: linux-x64 | |
| output_name: DotCraft-linux-x64 | |
| cargo_target: x86_64-unknown-linux-gnu | |
| electron_builder_args: '' | |
| - os: windows-latest | |
| os_name: windows | |
| runtime_id: win-x64 | |
| output_name: DotCraft-win-x64 | |
| cargo_target: x86_64-pc-windows-msvc | |
| electron_builder_args: --win --x64 | |
| - os: windows-latest | |
| os_name: windows | |
| runtime_id: win-arm64 | |
| output_name: DotCraft-win-arm64 | |
| cargo_target: aarch64-pc-windows-msvc | |
| electron_builder_args: --win --arm64 | |
| - os: macos-15-intel | |
| os_name: macos | |
| runtime_id: osx-x64 | |
| output_name: DotCraft-macos-x64 | |
| cargo_target: x86_64-apple-darwin | |
| electron_builder_args: --mac --x64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Restore dependencies | |
| run: dotnet restore src/DotCraft.App/DotCraft.App.csproj | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install Rust target | |
| run: rustup target add ${{ matrix.cargo_target }} | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: tui | |
| - name: Build ${{ matrix.os_name }} | |
| run: dotnet publish src/DotCraft.App/DotCraft.App.csproj -c Release -r ${{ matrix.runtime_id }} -p:PublishProfile=ReleaseProfile -o ./publish/${{ matrix.output_name }} | |
| - name: Setup MSVC x64 tools | |
| if: matrix.runtime_id == 'win-x64' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: x64 | |
| - name: Setup MSVC ARM64 cross tools | |
| if: matrix.runtime_id == 'win-arm64' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| arch: amd64_arm64 | |
| - name: Build TUI (dotcraft-tui) | |
| run: | | |
| cargo build --release --target ${{ matrix.cargo_target }} | |
| working-directory: tui | |
| - name: Copy TUI binary to publish - Windows | |
| if: runner.os == 'Windows' | |
| run: Copy-Item -Path tui\target\${{ matrix.cargo_target }}\release\dotcraft-tui.exe -Destination publish\${{ matrix.output_name }}\ | |
| - name: Copy TUI binary to publish - Linux/macOS | |
| if: runner.os != 'Windows' | |
| run: cp tui/target/${{ matrix.cargo_target }}/release/dotcraft-tui publish/${{ matrix.output_name }}/ | |
| - name: Create archive for Linux/macOS | |
| if: runner.os != 'Windows' | |
| run: | | |
| cd publish/${{ matrix.output_name }} | |
| tar -czf ../../${{ matrix.output_name }}.tar.gz * | |
| cd ../.. | |
| - name: Create archive for Windows | |
| if: runner.os == 'Windows' | |
| run: | | |
| Compress-Archive -Path publish/${{ matrix.output_name }}/* -DestinationPath ${{ matrix.output_name }}.zip | |
| - name: Upload artifact - Linux/macOS | |
| if: runner.os != 'Windows' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.output_name }} | |
| path: ${{ matrix.output_name }}.tar.gz | |
| - name: Upload artifact - Windows | |
| if: runner.os == 'Windows' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: ${{ matrix.output_name }} | |
| path: ${{ matrix.output_name }}.zip | |
| # ── Desktop (Electron) build ────────────────────────────────────────────── | |
| - name: Setup Node.js | |
| if: runner.os != 'Linux' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| cache-dependency-path: | | |
| desktop/package-lock.json | |
| sdk/typescript/package-lock.json | |
| - name: Install desktop dependencies | |
| if: runner.os != 'Linux' | |
| run: npm ci | |
| working-directory: desktop | |
| - name: Install Windows ARM64 optional runtime packages | |
| if: matrix.runtime_id == 'win-arm64' | |
| run: node scripts/install-target-optional-deps.mjs win32 arm64 | |
| working-directory: desktop | |
| - name: Install TypeScript SDK dependencies | |
| if: runner.os != 'Linux' | |
| run: npm ci | |
| working-directory: sdk/typescript | |
| - name: Build TypeScript SDK | |
| if: runner.os != 'Linux' | |
| run: npm run build:all | |
| working-directory: sdk/typescript | |
| - name: Stage embedded binary for desktop - Windows | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path desktop/resources/bin | Out-Null | |
| Copy-Item -Path publish/${{ matrix.output_name }}/dotcraft.exe -Destination desktop/resources/bin/dotcraft.exe -Force | |
| - name: Stage embedded binary for desktop - macOS | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| mkdir -p desktop/resources/bin | |
| cp publish/${{ matrix.output_name }}/dotcraft desktop/resources/bin/dotcraft | |
| - name: Stage bundled modules for desktop - Windows | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $modules = @('channel-feishu', 'channel-qq', 'channel-wecom', 'channel-weixin', 'channel-telegram') | |
| foreach ($mod in $modules) { | |
| $src = "sdk/typescript/packages/$mod" | |
| $dst = "desktop/resources/modules/$mod" | |
| New-Item -ItemType Directory -Force -Path $dst | Out-Null | |
| Copy-Item -Path "$src/manifest.json" -Destination "$dst/manifest.json" -Force | |
| Copy-Item -Path "$src/package.json" -Destination "$dst/package.json" -Force | |
| Copy-Item -Path "$src/dist" -Destination "$dst/dist" -Recurse -Force | |
| } | |
| - name: Stage bundled modules for desktop - macOS | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| for mod in channel-feishu channel-qq channel-wecom channel-weixin channel-telegram; do | |
| src="sdk/typescript/packages/$mod" | |
| dst="desktop/resources/modules/$mod" | |
| mkdir -p "$dst" | |
| cp "$src/manifest.json" "$dst/manifest.json" | |
| cp "$src/package.json" "$dst/package.json" | |
| cp -r "$src/dist" "$dst/dist" | |
| done | |
| # Match build.bat: avoid stale dist confusing electron-builder; safe on fresh checkouts too. | |
| - name: Clean desktop dist | |
| if: runner.os != 'Linux' | |
| shell: bash | |
| run: rm -rf desktop/dist | |
| - name: Build desktop app | |
| if: runner.os != 'Linux' | |
| run: | | |
| npm run build | |
| npx electron-builder ${{ matrix.electron_builder_args }} --publish never | |
| npm run verify:package | |
| working-directory: desktop | |
| env: | |
| # No code signing cert in CI; skip discovery so macOS/Windows builds do not fail. | |
| CSC_IDENTITY_AUTO_DISCOVERY: 'false' | |
| - name: Prepare Windows artifacts | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| mkdir -p desktop/dist/installer | |
| mv desktop/dist/*Setup*.exe desktop/dist/installer/ || true | |
| - name: Upload app installer - Windows | |
| if: runner.os == 'Windows' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: DotCraft-Installer-${{ matrix.runtime_id }} | |
| path: desktop/dist/installer/*.exe | |
| - name: Upload app artifacts - macOS | |
| if: runner.os == 'macOS' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: DotCraft-macos-x64-dmg | |
| path: desktop/dist/*.dmg | |
| release: | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| path: artifacts | |
| - name: Rename artifacts with version | |
| run: | | |
| VERSION=${GITHUB_REF#refs/tags/} | |
| echo "Version: $VERSION" | |
| # Create release directory | |
| mkdir -p release | |
| # Rename CLI artifacts | |
| for file in artifacts/DotCraft-*/DotCraft-*.tar.gz; do | |
| [ -f "$file" ] || continue | |
| target=$(basename "$file") | |
| target=${target#DotCraft-} | |
| target=${target%.tar.gz} | |
| new_name="DotCraft-${VERSION}-${target}.tar.gz" | |
| cp "$file" "release/$new_name" | |
| echo "Renamed: $(basename "$file") -> $new_name" | |
| done | |
| for file in artifacts/DotCraft-*/DotCraft-*.zip; do | |
| [ -f "$file" ] || continue | |
| target=$(basename "$file") | |
| target=${target#DotCraft-} | |
| target=${target%.zip} | |
| new_name="DotCraft-${VERSION}-${target}.zip" | |
| cp "$file" "release/$new_name" | |
| echo "Renamed: $(basename "$file") -> $new_name" | |
| done | |
| # Rename app artifacts - Windows | |
| for file in artifacts/DotCraft-Installer-win-*/*.exe; do | |
| [ -f "$file" ] || continue | |
| target=$(basename "$(dirname "$file")") | |
| target=${target#DotCraft-Installer-} | |
| new_name="DotCraft-${VERSION}-${target}-Setup.exe" | |
| cp "$file" "release/$new_name" | |
| echo "Renamed: $(basename "$file") -> $new_name" | |
| done | |
| # Rename app artifacts - macOS | |
| for file in artifacts/DotCraft-macos-*-dmg/*.dmg; do | |
| [ -f "$file" ] || continue | |
| target=$(basename "$(dirname "$file")") | |
| target=${target#DotCraft-} | |
| target=${target%-dmg} | |
| new_name="DotCraft-${VERSION}-${target}.dmg" | |
| cp "$file" "release/$new_name" | |
| echo "Renamed: $(basename "$file") -> $new_name" | |
| done | |
| echo "Final release files:" | |
| ls -lh release/ | |
| - name: Prepare Release Notes | |
| id: release_notes | |
| run: | | |
| VERSION=${GITHUB_REF#refs/tags/} | |
| REPO=${{ github.repository }} | |
| # Use template file when available. | |
| if [ -f ".github/release_template.md" ]; then | |
| sed \ | |
| -e "s|{{ VERSION }}|$VERSION|g" \ | |
| -e "s|{{ REPO }}|$REPO|g" \ | |
| -e "s|{{ CHANGELOG }}|- See auto-generated notes below.|g" \ | |
| ".github/release_template.md" > "release_notes.md" | |
| else | |
| echo "Automatic release $VERSION" > "release_notes.md" | |
| fi | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: release/* | |
| body_path: release_notes.md | |
| append_body: true | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |