Skip to content

docs: update readme #422

docs: update readme

docs: update readme #422

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 Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Test Docker configuration rendering
run: node --test docker/dotcraft/render-config.test.mjs
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: sdk/python/pyproject.toml
- name: Select ProtocolGen Python
run: echo "DOTCRAFT_PYTHON=$(python -c 'import sys; print(sys.executable)')" >> "$GITHUB_ENV"
- name: Install Python SDK development dependencies
run: |
"$DOTCRAFT_PYTHON" -m pip install ".[dev]"
working-directory: sdk/python
- 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
electron_builder_args: ''
- os: windows-latest
os_name: windows
runtime_id: win-x64
output_name: DotCraft-win-x64
companion_platform: win32
companion_arch: x64
electron_builder_args: --win --x64
- os: windows-latest
os_name: windows
runtime_id: win-arm64
output_name: DotCraft-win-arm64
companion_platform: win32
companion_arch: arm64
electron_builder_args: --win --arm64
- os: macos-15-intel
os_name: macos
runtime_id: osx-x64
output_name: DotCraft-macos-x64
file_arch: x86_64
companion_platform: darwin
companion_arch: x64
electron_builder_args: --mac --x64
- os: macos-15
os_name: macos
runtime_id: osx-arm64
output_name: DotCraft-macos-arm64
file_arch: arm64
companion_platform: darwin
companion_arch: arm64
electron_builder_args: --mac --arm64
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
dotnet restore src/Oratorio.Server/Oratorio.Server.csproj -r ${{ matrix.runtime_id }}
- 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: Build Oratorio Server for Desktop
if: runner.os != 'Linux'
run: dotnet publish src/Oratorio.Server/Oratorio.Server.csproj -c Release -r ${{ matrix.runtime_id }} --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o ./publish/oratorio-${{ matrix.runtime_id }}
- name: Verify macOS binary architectures
if: runner.os == 'macOS'
shell: bash
run: |
expected_arch="${{ matrix.file_arch }}"
file_output="$(file publish/${{ matrix.output_name }}/dotcraft)"
echo "$file_output"
if ! grep -q "dotcraft:.*Mach-O.*${expected_arch}" <<< "$file_output"; then
echo "::error::dotcraft is not a ${expected_arch} Mach-O binary"
exit 1
fi
oratorio_file_output="$(file publish/oratorio-${{ matrix.runtime_id }}/oratorio-server)"
echo "$oratorio_file_output"
if ! grep -q "oratorio-server:.*Mach-O.*${expected_arch}" <<< "$oratorio_file_output"; then
echo "::error::oratorio-server is not a ${expected_arch} Mach-O binary"
exit 1
fi
- 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 Feishu CLI companion for desktop
if: runner.os != 'Linux'
run: >-
node sdk/typescript/packages/channel-feishu/scripts/stage-official-cli.mjs
--platform ${{ matrix.companion_platform }}
--arch ${{ matrix.companion_arch }}
--output desktop/resources/modules/channel-feishu/vendor
- 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
Copy-Item -Path publish/oratorio-${{ matrix.runtime_id }}/oratorio-server.exe -Destination desktop/resources/bin/oratorio-server.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
cp publish/oratorio-${{ matrix.runtime_id }}/oratorio-server desktop/resources/bin/oratorio-server
chmod +x desktop/resources/bin/oratorio-server
- 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 dist:package -- ${{ matrix.electron_builder_args }}
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: ${{ matrix.output_name }}-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 }}