Release #69
Workflow file for this run
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: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| id-token: write | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| NODE_VERSION: "22" | |
| APP_DIR: apps/undefined-console | |
| jobs: | |
| verify-python: | |
| name: Verify Python package | |
| runs-on: ubuntu-latest | |
| environment: release | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Install Python dependencies | |
| run: uv sync --group ci -p ${{ env.PYTHON_VERSION }} | |
| - name: Cache Ruff | |
| uses: actions/cache@v4 | |
| with: | |
| path: .ruff_cache | |
| key: ${{ runner.os }}-ruff-${{ hashFiles('**/uv.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-ruff- | |
| - name: Run Ruff | |
| run: | | |
| uv run ruff check . | |
| uv run ruff format --check . | |
| - name: Cache Mypy | |
| uses: actions/cache@v4 | |
| with: | |
| path: .mypy_cache | |
| key: ${{ runner.os }}-mypy-${{ hashFiles('**/uv.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-mypy- | |
| - name: Run Mypy | |
| run: uv run mypy . | |
| - name: Run tests | |
| run: uv run pytest tests/ | |
| - name: Build Python distributions | |
| run: uv build | |
| - name: Verify wheel contains packaged resources | |
| run: | | |
| uv run python -c "import glob, zipfile; wheels=glob.glob('dist/*.whl'); assert wheels, 'no wheel built'; z=zipfile.ZipFile(wheels[0]); names=set(z.namelist()); required={'config.toml.example','res/prompts/undefined.xml','img/xlwy.jpg'}; missing=sorted([p for p in required if p not in names]); assert not missing, f'missing in wheel: {missing}'" | |
| - name: Upload Python distributions | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-dist | |
| path: dist/* | |
| if-no-files-found: error | |
| verify-console: | |
| name: Verify console app | |
| runs-on: ubuntu-latest | |
| environment: release | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Cache npm and node_modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.npm | |
| key: ${{ runner.os }}-undefined-console-npm-${{ hashFiles('apps/undefined-console/package-lock.json', 'apps/undefined-console/package.json', 'apps/undefined-console/biome.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-undefined-console-npm- | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry and target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: | | |
| apps/undefined-console/src-tauri -> target | |
| - name: Install Linux system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf | |
| - name: Install app dependencies | |
| working-directory: ${{ env.APP_DIR }} | |
| run: npm ci | |
| - name: Run console checks | |
| working-directory: ${{ env.APP_DIR }} | |
| run: npm run check | |
| build-tauri-desktop: | |
| name: Build Tauri desktop (${{ matrix.label }}) | |
| runs-on: ${{ matrix.os }} | |
| environment: release | |
| needs: | |
| - verify-python | |
| - verify-console | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| label: linux-x64 | |
| bundles: appimage,deb | |
| - os: windows-latest | |
| label: windows-x64 | |
| bundles: nsis,msi | |
| - os: macos-15 | |
| label: macos-x64 | |
| bundles: dmg | |
| rust-target: x86_64-apple-darwin | |
| - os: macos-15 | |
| label: macos-arm64 | |
| bundles: dmg | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Cache npm and node_modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.npm | |
| key: ${{ runner.os }}-undefined-console-npm-${{ hashFiles('apps/undefined-console/package-lock.json', 'apps/undefined-console/package.json', 'apps/undefined-console/biome.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-undefined-console-npm- | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.rust-target }} | |
| - name: Cache cargo registry and target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.label }} | |
| workspaces: | | |
| apps/undefined-console/src-tauri -> target | |
| - name: Install Linux system dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf | |
| - name: Install app dependencies | |
| working-directory: ${{ env.APP_DIR }} | |
| run: npm ci | |
| - name: Build Tauri bundles | |
| working-directory: ${{ env.APP_DIR }} | |
| shell: bash | |
| run: | | |
| TAURI_ARGS="--ci --bundles ${{ matrix.bundles }}" | |
| if [ -n "${{ matrix.rust-target }}" ]; then | |
| TAURI_ARGS="$TAURI_ARGS --target ${{ matrix.rust-target }}" | |
| fi | |
| if [ "${{ runner.os }}" = "Linux" ]; then | |
| NO_STRIP=true npm run tauri:build -- $TAURI_ARGS | |
| else | |
| npm run tauri:build -- $TAURI_ARGS | |
| fi | |
| - name: Collect release artifacts | |
| shell: bash | |
| env: | |
| TAG: ${{ github.ref_name }} | |
| LABEL: ${{ matrix.label }} | |
| RUST_TARGET: ${{ matrix.rust-target }} | |
| run: | | |
| copy_single_match() { | |
| local search_root="$1" | |
| local pattern="$2" | |
| local destination="$3" | |
| local matches=() | |
| while IFS= read -r line; do | |
| matches+=("$line") | |
| done < <(find "$search_root" -type f -name "$pattern" | sort) | |
| if [ "${#matches[@]}" -ne 1 ]; then | |
| echo "Expected exactly one match for $pattern under $search_root, found ${#matches[@]}" >&2 | |
| printf 'Matches:\n%s\n' "${matches[*]}" >&2 | |
| exit 1 | |
| fi | |
| cp "${matches[0]}" "$destination" | |
| } | |
| if [ -n "$RUST_TARGET" ]; then | |
| BUNDLE_ROOT="$APP_DIR/src-tauri/target/$RUST_TARGET/release/bundle" | |
| else | |
| BUNDLE_ROOT="$APP_DIR/src-tauri/target/release/bundle" | |
| fi | |
| mkdir -p release-artifacts | |
| case "$LABEL" in | |
| linux-x64) | |
| copy_single_match "$BUNDLE_ROOT" '*.AppImage' "release-artifacts/Undefined-Console-${TAG}-${LABEL}.AppImage" | |
| copy_single_match "$BUNDLE_ROOT" '*.deb' "release-artifacts/Undefined-Console-${TAG}-${LABEL}.deb" | |
| ;; | |
| windows-x64) | |
| copy_single_match "$BUNDLE_ROOT" '*.exe' "release-artifacts/Undefined-Console-${TAG}-${LABEL}-setup.exe" | |
| copy_single_match "$BUNDLE_ROOT" '*.msi' "release-artifacts/Undefined-Console-${TAG}-${LABEL}.msi" | |
| ;; | |
| macos-x64|macos-arm64) | |
| copy_single_match "$BUNDLE_ROOT" '*.dmg' "release-artifacts/Undefined-Console-${TAG}-${LABEL}.dmg" | |
| ;; | |
| esac | |
| - name: Upload desktop artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tauri-${{ matrix.label }} | |
| path: release-artifacts/* | |
| if-no-files-found: error | |
| build-tauri-android: | |
| name: Build Tauri Android | |
| runs-on: ubuntu-latest | |
| environment: release | |
| needs: | |
| - verify-python | |
| - verify-console | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| - name: Cache npm and node_modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.npm | |
| key: ${{ runner.os }}-undefined-console-npm-${{ hashFiles('apps/undefined-console/package-lock.json', 'apps/undefined-console/package.json', 'apps/undefined-console/biome.json') }} | |
| restore-keys: | | |
| ${{ runner.os }}-undefined-console-npm- | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android | |
| - name: Cache cargo registry and target | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: android | |
| workspaces: | | |
| apps/undefined-console/src-tauri -> target | |
| - name: Setup Java 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "17" | |
| - name: Setup Android SDK | |
| uses: android-actions/setup-android@v3 | |
| - name: Ensure Android platform packages | |
| run: sdkmanager --install "platform-tools" "platforms;android-34" "build-tools;34.0.0" | |
| - name: Cache Gradle | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Install app dependencies | |
| working-directory: ${{ env.APP_DIR }} | |
| run: npm ci | |
| - name: Initialize Android project for CI | |
| working-directory: ${{ env.APP_DIR }} | |
| run: npm run tauri:android:init | |
| - name: Build Android APK | |
| # If signing secrets are wired into the generated Android project, replace the | |
| # debug fallback with a release build. The scaffold keeps the expectation explicit | |
| # while still publishing an installable APK on every non-iOS release. | |
| working-directory: ${{ env.APP_DIR }} | |
| run: npm run tauri:android:debug -- --ci --apk | |
| - name: Collect Android artifact | |
| shell: bash | |
| env: | |
| TAG: ${{ github.ref_name }} | |
| run: | | |
| copy_single_match() { | |
| local search_root="$1" | |
| local pattern="$2" | |
| local destination="$3" | |
| local matches=() | |
| while IFS= read -r line; do | |
| matches+=("$line") | |
| done < <(find "$search_root" -type f -name "$pattern" | sort) | |
| if [ "${#matches[@]}" -ne 1 ]; then | |
| echo "Expected exactly one match for $pattern under $search_root, found ${#matches[@]}" >&2 | |
| printf 'Matches:\n%s\n' "${matches[*]}" >&2 | |
| exit 1 | |
| fi | |
| cp "${matches[0]}" "$destination" | |
| } | |
| mkdir -p release-artifacts | |
| copy_single_match "$APP_DIR/src-tauri" '*.apk' "release-artifacts/Undefined-Console-${TAG}-android-universal.apk" | |
| - name: Upload Android artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tauri-android | |
| path: release-artifacts/* | |
| if-no-files-found: error | |
| publish-release: | |
| name: Publish release assets | |
| runs-on: ubuntu-latest | |
| needs: | |
| - verify-python | |
| - build-tauri-desktop | |
| - build-tauri-android | |
| environment: release | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Fetch tags | |
| run: git fetch --tags --force | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-downloads | |
| merge-multiple: true | |
| - name: Build release notes | |
| shell: bash | |
| run: | | |
| TAG_NAME="${{ github.ref_name }}" | |
| TAG_TYPE=$(git cat-file -t "$TAG_NAME") | |
| TAG_CONTENT="" | |
| if [ "$TAG_TYPE" = "tag" ]; then | |
| TAG_CONTENT=$(git tag -l --format='%(contents)' "$TAG_NAME") | |
| TAG_CONTENT=$(echo "$TAG_CONTENT" | sed '/-----BEGIN PGP SIGNATURE-----/,/-----END PGP SIGNATURE-----/d') | |
| fi | |
| PREV_TAG=$(git describe --tags --abbrev=0 "${TAG_NAME}^" 2>/dev/null || git rev-list --max-parents=0 HEAD) | |
| if [ -n "$TAG_CONTENT" ]; then | |
| echo "$TAG_CONTENT" > tag_message.txt | |
| printf '\n---\n\n' >> tag_message.txt | |
| else | |
| : > tag_message.txt | |
| fi | |
| cat >> tag_message.txt <<'NOTES' | |
| ## 📦 Bundles | |
| - Python: wheel + sdist | |
| - Desktop: Windows (`.exe`, `.msi`), Linux (`.AppImage`, `.deb`), macOS Intel/Apple Silicon (`.dmg`) | |
| - Android: universal `.apk` | |
| ## 🔐 Signing expectations | |
| - Windows/Linux artifacts are built directly from CI. | |
| - macOS signing/notarization can be added later via Apple secrets. | |
| - Android currently ships an installable APK on every release; wire signing secrets into the generated Android project to promote it to a signed release build. | |
| ## 📝 Detailed Changes | |
| NOTES | |
| FEAT_COMMITS=$(git log ${PREV_TAG}..${TAG_NAME} --grep='^feat' --pretty=format:'* %s (%h)' 2>/dev/null || true) | |
| if [ -n "$FEAT_COMMITS" ]; then | |
| echo -e "\n### 🚀 Features" >> tag_message.txt | |
| echo "$FEAT_COMMITS" >> tag_message.txt | |
| fi | |
| FIX_COMMITS=$(git log ${PREV_TAG}..${TAG_NAME} --grep='^fix' --pretty=format:'* %s (%h)' 2>/dev/null || true) | |
| if [ -n "$FIX_COMMITS" ]; then | |
| echo -e "\n### 🐛 Bug Fixes" >> tag_message.txt | |
| echo "$FIX_COMMITS" >> tag_message.txt | |
| fi | |
| OTHER_COMMITS=$(git log ${PREV_TAG}..${TAG_NAME} --grep='^feat\|^fix' --invert-grep --pretty=format:'* %s (%h)' 2>/dev/null || true) | |
| if [ -n "$OTHER_COMMITS" ]; then | |
| echo -e "\n### 🛠 Maintenance & Others" >> tag_message.txt | |
| echo "$OTHER_COMMITS" >> tag_message.txt | |
| fi | |
| - name: Create GitHub release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release create ${{ github.ref_name }} release-downloads/* \ | |
| --title "Undefined ${{ github.ref_name }}" \ | |
| --notes-file tag_message.txt | |
| publish-pypi: | |
| name: Publish Python package to PyPI | |
| runs-on: ubuntu-latest | |
| needs: | |
| - verify-python | |
| - publish-release | |
| environment: release | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v5 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Download Python dist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: python-dist | |
| path: dist | |
| - name: Publish to PyPI | |
| run: uv publish dist/* |