diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 87e1945..8739cb8 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -1,11 +1,18 @@ -name: Android build - +# Android APK build (debug + optional release). +# +# The Vite frontend and offline Reticulum manual are produced by the reusable +# Frontend build workflow and downloaded into meshchatx/public/. This avoids +# re-running the full pnpm install + build pipeline alongside the much heavier +# Android NDK/wheel build steps. +# # Pinned first-party actions (bump tag and SHA together when upgrading): # actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8 # actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405 -# actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f # actions/setup-java@v4.7.1 c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4 +# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0 + +name: Android build on: pull_request: @@ -45,13 +52,31 @@ env: CHAQUOPY_REF: "9f563f45108a873d7feb363e1f754c0173f1114e" jobs: + frontend: + name: Build frontend artifact + uses: ./.github/workflows/frontend-build.yml + permissions: + contents: read + with: + artifact_name: meshchatx-frontend-android-${{ github.run_id }}-${{ github.run_attempt }} + retention_days: 1 + # Android wheels run on Python 3.11; using the same here avoids + # spurious version drift in the docs-bundling step. + python_version: "3.11" + android: name: Android test/build runs-on: ubuntu-latest + needs: frontend timeout-minutes: 90 + permissions: + contents: read + actions: write defaults: run: shell: bash + env: + FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }} steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 @@ -67,19 +92,18 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} - - name: Set up Node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + - name: Download frontend artifact + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 with: - node-version: ${{ env.NODE_VERSION }} + name: ${{ env.FRONTEND_ARTIFACT_NAME }} + path: meshchatx/public - - name: Enable pnpm - run: corepack enable && corepack prepare "pnpm@${PNPM_VERSION}" --activate - - - name: Install frontend dependencies - run: pnpm install --frozen-lockfile - - - name: Build frontend (meshchatx/public for APK) - run: pnpm run build-frontend + - name: Verify frontend artifact contents + run: | + set -euo pipefail + test -f meshchatx/public/index.html + test -d meshchatx/public/assets + test -d meshchatx/public/reticulum-docs-bundled/current - name: Install Android wheel build dependencies run: | @@ -103,7 +127,6 @@ jobs: exit 1 fi NDK_VERSION="27.3.13750724" - # Feed a bounded stream of 'y' so the producer cannot SIGPIPE under set -o pipefail. yes_n() { for _ in $(seq 1 "$1"); do echo y; done; } if [[ ! -d "${SDK_ROOT}/ndk/${NDK_VERSION}" ]]; then yes_n 200 | "${SDKMANAGER}" --sdk_root="${SDK_ROOT}" --licenses >/dev/null diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 84b0771..783668b 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -1,11 +1,13 @@ -# Tagged releases: Windows + macOS builds (same idea as .gitea/workflows/build.yml — any tag -# points at a commit; no requirement that the tag be on master). +# Tagged releases: Windows + macOS builds. Reuses the Frontend build workflow +# so each platform job only runs the cx_Freeze backend + electron-builder +# steps on top of the shared, prebuilt meshchatx/public artifact. # # Pinned first-party actions (bump tag and SHA together when upgrading): # actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8 # actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405 # actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f -# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4 +# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4 +# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0 name: Build release @@ -32,8 +34,21 @@ env: PNPM_VERSION: "10.32.1" jobs: + frontend: + name: Build frontend artifact + uses: ./.github/workflows/frontend-build.yml + permissions: + contents: read + with: + artifact_name: meshchatx-frontend-release-${{ github.run_id }}-${{ github.run_attempt }} + retention_days: 7 + build-release: name: Build release (${{ matrix.label }}) + needs: frontend + permissions: + contents: read + actions: write strategy: fail-fast: false matrix: @@ -53,6 +68,9 @@ jobs: defaults: run: shell: bash + env: + FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }} + MESHCHATX_FRONTEND_PREBUILT: "1" steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 @@ -105,6 +123,19 @@ jobs: "$PY_X64" -m pip install "cx-freeze>=7.0.0" "$PY_X64" -m pip install -e . + - name: Download frontend artifact + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 + with: + name: ${{ env.FRONTEND_ARTIFACT_NAME }} + path: meshchatx/public + + - name: Verify frontend artifact contents + run: | + set -euo pipefail + test -f meshchatx/public/index.html + test -d meshchatx/public/assets + test -d meshchatx/public/reticulum-docs-bundled/current + - name: Build distributables run: bash "${{ matrix.build_script }}" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d458e6..39aedb8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,14 @@ # Native build verification (Windows + macOS). # +# Pulls the prebuilt meshchatx/public artifact produced by the reusable +# Frontend build workflow so that each platform job only has to compile the +# cx_Freeze backend and run electron-builder. +# # Pinned first-party actions (bump tag and SHA together when upgrading): # actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8 # actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405 # actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f +# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0 name: Build @@ -32,8 +37,20 @@ env: PNPM_VERSION: "10.32.1" jobs: + frontend: + name: Build frontend artifact + uses: ./.github/workflows/frontend-build.yml + permissions: + contents: read + with: + artifact_name: meshchatx-frontend-build-${{ github.run_id }}-${{ github.run_attempt }} + retention_days: 1 + build-test: name: Build test (${{ matrix.label }}) + needs: frontend + permissions: + contents: read strategy: fail-fast: false matrix: @@ -51,6 +68,9 @@ jobs: defaults: run: shell: bash + env: + FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }} + MESHCHATX_FRONTEND_PREBUILT: "1" steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 @@ -103,5 +123,18 @@ jobs: "$PY_X64" -m pip install "cx-freeze>=7.0.0" "$PY_X64" -m pip install -e . + - name: Download frontend artifact + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 + with: + name: ${{ env.FRONTEND_ARTIFACT_NAME }} + path: meshchatx/public + + - name: Verify frontend artifact contents + run: | + set -euo pipefail + test -f meshchatx/public/index.html + test -d meshchatx/public/assets + test -d meshchatx/public/reticulum-docs-bundled/current + - name: Build distributables run: bash "${{ matrix.build_script }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12ada71..05cc2d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,8 @@ -# Linux CI moved from Gitea for faster/free runners. +# Linux CI: lint, frontend/backend tests, localization, and a Linux build check. +# +# The frontend bundle is produced once by the reusable Frontend build workflow +# and downloaded by the Linux build-check job (and by the platform build/release +# workflows) instead of being rebuilt on every job. # # Pinned first-party actions (bump tag and SHA together when upgrading): # actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8 @@ -34,10 +38,21 @@ env: PNPM_VERSION: "10.32.1" jobs: + frontend: + name: Build frontend artifact + uses: ./.github/workflows/frontend-build.yml + permissions: + contents: read + with: + artifact_name: meshchatx-frontend-ci-${{ github.run_id }}-${{ github.run_attempt }} + retention_days: 1 + verify: name: ${{ matrix.task.name }} runs-on: ubuntu-latest timeout-minutes: ${{ matrix.task.timeout }} + permissions: + contents: read strategy: fail-fast: false matrix: @@ -54,9 +69,6 @@ jobs: - name: Localization tests id: lang-tests timeout: 20 - - name: Linux build check - id: build-check - timeout: 60 steps: - name: Checkout @@ -104,19 +116,67 @@ jobs: pnpm exec vitest run tests/frontend/i18n.test.js poetry run python -m pytest tests/backend/test_translator_handler.py ;; - build-check) - pnpm run build-frontend - poetry run python -m compileall meshchatx/ - pnpm run build-backend - ;; *) echo "Unknown matrix task: ${{ matrix.task.id }}" >&2 exit 1 ;; esac + build-check: + name: Linux build check + runs-on: ubuntu-latest + needs: frontend + timeout-minutes: 60 + permissions: + contents: read + env: + FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }} + MESHCHATX_FRONTEND_PREBUILT: "1" + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install Poetry (PyPI pin) + env: + POETRY_VERSION: ${{ env.POETRY_VERSION }} + run: bash scripts/ci/github-install-poetry.sh + + - name: Set up Node + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Enable pnpm (corepack) + run: corepack enable && corepack prepare "pnpm@${PNPM_VERSION}" --activate + + - name: Install dependencies + run: bash scripts/ci/github-install-deps.sh + + - name: Download frontend artifact + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 + with: + name: ${{ env.FRONTEND_ARTIFACT_NAME }} + path: meshchatx/public + + - name: Verify frontend artifact contents + run: | + set -euo pipefail + test -f meshchatx/public/index.html + test -d meshchatx/public/assets + test -d meshchatx/public/reticulum-docs-bundled/current + + - name: Compile backend sources + run: poetry run python -m compileall meshchatx/ + + - name: Build backend (cx_Freeze) + run: pnpm run build-backend + - name: Upload Linux build-check artifacts - if: matrix.task.id == 'build-check' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 with: name: meshchatx-linux-build-check-${{ github.ref_name }}-${{ github.run_id }} @@ -129,7 +189,9 @@ jobs: name: Validate Linux build artifact runs-on: ubuntu-latest timeout-minutes: 15 - needs: verify + needs: build-check + permissions: + contents: read steps: - name: Download Linux build-check artifact uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c7a578c..c86f34f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,54 +1,70 @@ +# Pinned first-party actions (bump tag and SHA together when upgrading): +# actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8 +# github/codeql-action/init@v4.31.6 95e58e9a2cdfd71adc6e0353d5c52f41a045d225 +# github/codeql-action/analyze@v4.31.6 95e58e9a2cdfd71adc6e0353d5c52f41a045d225 + name: "CodeQL Advanced" on: - push: - branches: [ "master", "dev" ] - schedule: - - cron: '35 18 * * 3' + push: + branches: ["master", "dev"] + pull_request: + branches: ["master", "dev"] + schedule: + - cron: "35 18 * * 3" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - security-events: write - packages: read - actions: read - contents: read + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: 360 + permissions: + security-events: write + packages: read + actions: read + contents: read - strategy: - fail-fast: false - matrix: - include: - - language: actions - build-mode: none - - language: go - build-mode: autobuild - - language: java-kotlin - build-mode: none - - language: javascript-typescript - build-mode: none - - language: python - build-mode: none + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: go + build-mode: autobuild + - language: java-kotlin + build-mode: none + - language: javascript-typescript + build-mode: none + - language: python + build-mode: none - steps: - - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - - name: Initialize CodeQL - uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} + - name: Initialize CodeQL + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} - - name: Run manual build steps - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'Manual build mode requires custom build commands.' - exit 1 + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'Manual build mode requires custom build commands.' + exit 1 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 - with: - category: "/language:${{matrix.language}}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/frontend-build.yml b/.github/workflows/frontend-build.yml new file mode 100644 index 0000000..ae18a2c --- /dev/null +++ b/.github/workflows/frontend-build.yml @@ -0,0 +1,116 @@ +# Reusable workflow that builds the Vite frontend and bundles the offline +# Reticulum manual into ``meshchatx/public/``. Other workflows invoke this once +# via ``workflow_call`` and download the resulting artifact instead of +# re-running the same node/pnpm pipeline on every job. +# +# Pinned first-party actions (bump tag and SHA together when upgrading): +# actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8 +# actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405 +# actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f +# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4 + +name: Frontend build + +on: + workflow_call: + inputs: + artifact_name: + description: >- + Name of the artifact uploaded to the calling workflow run. + Defaults to ``meshchatx-frontend--``. + required: false + type: string + default: "" + retention_days: + description: Artifact retention in days. + required: false + type: number + default: 1 + node_version: + description: Node.js major version used for the build. + required: false + type: string + default: "24" + pnpm_version: + description: pnpm version activated through corepack. + required: false + type: string + default: "10.32.1" + python_version: + description: Python version used by the docs bundler. + required: false + type: string + default: "3.14" + outputs: + artifact_name: + description: Name of the uploaded frontend artifact. + value: ${{ jobs.build.outputs.artifact_name }} + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + NODE_OPTIONS: --max-old-space-size=8192 + +jobs: + build: + name: Build frontend artifact + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + outputs: + artifact_name: ${{ steps.resolve.outputs.artifact_name }} + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + + - name: Resolve artifact name + id: resolve + env: + REQUESTED_NAME: ${{ inputs.artifact_name }} + run: | + set -euo pipefail + if [[ -n "${REQUESTED_NAME}" ]]; then + name="${REQUESTED_NAME}" + else + name="meshchatx-frontend-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + fi + echo "artifact_name=${name}" >> "${GITHUB_OUTPUT}" + + - name: Set up Python (for docs bundler) + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 + with: + python-version: ${{ inputs.python_version }} + + - name: Set up Node + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + with: + node-version: ${{ inputs.node_version }} + + - name: Enable pnpm (corepack) + env: + PNPM_VERSION: ${{ inputs.pnpm_version }} + run: corepack enable && corepack prepare "pnpm@${PNPM_VERSION}" --activate + + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + + - name: Sync version metadata + run: pnpm run version:sync + + - name: Build frontend (vite) + run: pnpm run build-frontend + + - name: Bundle Reticulum manual (offline) + run: pnpm run build-docs + + - name: Upload frontend artifact + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 + with: + name: ${{ steps.resolve.outputs.artifact_name }} + path: meshchatx/public + if-no-files-found: error + retention-days: ${{ inputs.retention_days }} + include-hidden-files: false diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 0b5af71..b07172f 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -33,6 +33,8 @@ jobs: scan: runs-on: ubuntu-latest timeout-minutes: 45 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8