fix(naga): 新版NagaAgent回调端口与逻辑 (#51) #71
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: Validate release tag matches package version | |
| env: | |
| TAG_NAME: ${{ github.ref_name }} | |
| run: | | |
| uv run python - <<'PY' | |
| import os | |
| import pathlib | |
| import re | |
| import sys | |
| import tomllib | |
| tag_name = os.environ["TAG_NAME"] | |
| expected_version = tag_name.removeprefix("v") | |
| pyproject = tomllib.loads(pathlib.Path("pyproject.toml").read_text(encoding="utf-8")) | |
| project_version = str(pyproject["project"]["version"]).strip() | |
| init_text = pathlib.Path("src/Undefined/__init__.py").read_text(encoding="utf-8") | |
| match = re.search(r'__version__\s*=\s*"([^"]+)"', init_text) | |
| if match is None: | |
| raise SystemExit("Could not find __version__ in src/Undefined/__init__.py") | |
| init_version = match.group(1).strip() | |
| errors: list[str] = [] | |
| if project_version != init_version: | |
| errors.append( | |
| f"Version mismatch: pyproject.toml={project_version}, src/Undefined/__init__.py={init_version}" | |
| ) | |
| if project_version != expected_version: | |
| errors.append( | |
| f"Tag/version mismatch: tag={tag_name}, expected package version={expected_version}, actual={project_version}" | |
| ) | |
| if errors: | |
| raise SystemExit("\n".join(errors)) | |
| print( | |
| f"Validated release version {project_version} from tag {tag_name}, pyproject.toml, and src/Undefined/__init__.py" | |
| ) | |
| PY | |
| - 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: Verify downloaded distributions | |
| env: | |
| TAG_NAME: ${{ github.ref_name }} | |
| run: | | |
| uv run python - <<'PY' | |
| import os | |
| import pathlib | |
| expected_version = os.environ["TAG_NAME"].removeprefix("v") | |
| dist_dir = pathlib.Path("dist") | |
| files = sorted(path.name for path in dist_dir.glob("*")) | |
| if not files: | |
| raise SystemExit("No Python distributions were downloaded") | |
| bad = [name for name in files if f"-{expected_version}" not in name] | |
| if bad: | |
| raise SystemExit( | |
| "Downloaded distributions do not match release tag version " | |
| f"{expected_version}: {bad}" | |
| ) | |
| print("Distributions ready for publish:") | |
| for name in files: | |
| print(f" - {name}") | |
| PY | |
| - name: Publish to PyPI | |
| run: uv publish --check-url https://pypi.org/simple/ dist/* |