Skip to content

feat(ci): publish ESP32 firmware as @virtusco/porter-firmware npm pac… #7

feat(ci): publish ESP32 firmware as @virtusco/porter-firmware npm pac…

feat(ci): publish ESP32 firmware as @virtusco/porter-firmware npm pac… #7

Workflow file for this run

# Copyright 2026 VirtusCo
name: Porter Build & Release
on:
push:
branches: [main, prototype]
workflow_dispatch:
defaults:
run:
shell: bash
env:
ZEPHYR_SDK_VERSION: "0.17.0"
ZEPHYR_VERSION: "v4.0.0"
jobs:
version:
name: Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- uses: actions/checkout@v4
- id: ver
run: |
VER=$(cat VERSION 2>/dev/null | tr -d '[:space:]' || echo "0.0.0")
echo "version=$VER" >> "$GITHUB_OUTPUT"
build-docker:
name: Build & Publish Docker Image
runs-on: ubuntu-latest
needs: version
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: virtusco/porter-robot
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ needs.version.outputs.version }}
type=raw,value=latest,enable=${{ github.ref_name == 'main' }}
type=sha,prefix=
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile.prod
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Save image for release artifacts
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }}
docker save ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.version.outputs.version }} | gzip > porter-robot-${{ needs.version.outputs.version }}.tar.gz
- uses: actions/upload-artifact@v4
with:
name: docker-image
path: porter-robot-*.tar.gz
build-esp32:
name: Build ESP32 Firmware & Publish Package
runs-on: ubuntu-latest
needs: version
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://npm.pkg.github.com'
scope: '@virtusco'
- name: Install tools
run: |
pip install west pyelftools intelhex esptool
sudo apt-get update && sudo apt-get install -y cmake ninja-build gperf ccache \
device-tree-compiler wget xz-utils file make gcc gcc-multilib g++-multilib
- name: West init + update
run: |
cd esp32_firmware
west init -l .
west update --narrow --fetch-opt=--depth=1
- name: Install Zephyr SDK + ESP32 toolchain
run: |
wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZEPHYR_SDK_VERSION}/zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64_minimal.tar.xz" -O /tmp/sdk.tar.xz
tar xf /tmp/sdk.tar.xz -C ~/
~/zephyr-sdk-${ZEPHYR_SDK_VERSION}/setup.sh -c
wget -q "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZEPHYR_SDK_VERSION}/toolchain_linux-x86_64_xtensa-espressif_esp32_zephyr-elf.tar.xz" -O /tmp/tc.tar.xz
tar xf /tmp/tc.tar.xz -C ~/zephyr-sdk-${ZEPHYR_SDK_VERSION}/
- name: Build motor controller
run: cd esp32_firmware && west build -b esp32_devkitc_wroom motor_controller -d build/motor
- name: Build sensor fusion
run: cd esp32_firmware && west build -b esp32_devkitc_wroom sensor_fusion -d build/sensor
- name: Collect artifacts
run: |
mkdir -p release
cp esp32_firmware/build/motor/zephyr/zephyr.bin release/motor_controller.bin || true
cp esp32_firmware/build/motor/zephyr/zephyr.elf release/motor_controller.elf || true
cp esp32_firmware/build/sensor/zephyr/zephyr.bin release/sensor_fusion.bin || true
cp esp32_firmware/build/sensor/zephyr/zephyr.elf release/sensor_fusion.elf || true
cd release && sha256sum * > SHA256SUMS.txt 2>/dev/null || true
- name: Publish firmware npm package
run: |
cd esp32_firmware/package
mkdir -p bin
cp ../../release/motor_controller.bin bin/ 2>/dev/null || true
cp ../../release/sensor_fusion.bin bin/ 2>/dev/null || true
# Set version from repo VERSION file
VER="${{ needs.version.outputs.version }}"
npm version "$VER" --no-git-tag-version --allow-same-version
npm publish || echo "Publish failed (may already exist at this version)"
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v4
with:
name: esp32-firmware
path: release/
build-flutter:
name: Build Flutter GUI
runs-on: ubuntu-latest
needs: version
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
- name: Install Linux build deps
run: |
sudo apt-get update && sudo apt-get install -y \
libgtk-3-dev libblkid-dev liblzma-dev
- name: Build
run: |
cd src/porter_gui
flutter pub get
flutter build linux --release
- name: Package
run: |
tar czf porter-gui-linux-x64-${{ needs.version.outputs.version }}.tar.gz \
-C src/porter_gui/build/linux/x64/release bundle/
- uses: actions/upload-artifact@v4
with:
name: flutter-gui
path: porter-gui-*.tar.gz
test:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install pytest
- run: pytest tests/unit/ -v
release:
name: GitHub Release
runs-on: ubuntu-latest
needs: [version, build-docker, build-esp32, build-flutter, test]
if: github.event_name == 'push'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Generate tag
id: tag
run: |
VER="${{ needs.version.outputs.version }}"
SHA="${GITHUB_SHA:0:7}"
TAG="v${VER}+${SHA}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "Release tag: $TAG"
- name: Create release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: "Porter Robot v${{ needs.version.outputs.version }} (${{ github.ref_name }})"
generate_release_notes: true
make_latest: ${{ github.ref_name == 'main' }}
prerelease: ${{ github.ref_name != 'main' }}
files: |
artifacts/docker-image/*
artifacts/esp32-firmware/*
artifacts/flutter-gui/*