Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 6 additions & 52 deletions .github/workflows/companion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ on:
- v*
paths:
- '.github/workflows/companion.yml'
- '.github/workflows/wasm_modules.yml'
- '.github/actions/python_dependencies/**'
- '.github/actions/wasi_sdk/**'
- '.github/actions/build_companion/**'
- 'companion/**'
- 'fw.json'
- 'pyproject.toml'
- 'tools/build-companion.sh'
- 'tools/build-wasm-modules.sh'
Expand All @@ -22,10 +24,12 @@ on:
pull_request:
paths:
- '.github/workflows/companion.yml'
- '.github/workflows/wasm_modules.yml'
- '.github/actions/python_dependencies/**'
- '.github/actions/wasi_sdk/**'
- '.github/actions/build_companion/**'
- 'companion/**'
- 'fw.json'
- 'pyproject.toml'
- 'tools/build-companion.sh'
- 'tools/build-wasm-modules.sh'
Expand All @@ -49,58 +53,8 @@ on:

jobs:
modules:
name: WASM Modules (${{ matrix.group.id }})
runs-on: macos-15
strategy:
matrix:
group:
- id: color-1
target: [nv14, el18, pl18, pl18ev]
- id: color-2
target: [pl18u, t15, t16, t18, t22]
- id: bw-1
target: [zorro, pocket, commando8, tprov2, tpros, bumblebee, t14]
- id: bw-2
target: [mt12, gx12, t20, t20v2, t12max, tx12mk2, boxer]
- id: color-3
target: [tx15, tx16s, x12s, f16]
- id: mixed-1
target: [v12, v14, v14lcd, v15, v16, x10, x10express]
- id: bw-3
target: [x7access, x9dp2019, x9e]
- id: color-4
target: [nb4p, st16, pa01, c14]
- id: color-5
target: [t15pro, tx16smk3, gx15, h17, t15h7]

steps:
- name: Check out the repo
uses: actions/checkout@v7
with:
submodules: true

- name: Setup Python Dependencies
uses: ./.github/actions/python_dependencies

- name: Setup WASI SDK
uses: ./.github/actions/wasi_sdk

- name: Build modules
shell: bash
env:
FLAVOR: ${{ join(matrix.group.target, ';') }}
CMAKE_BUILD_TYPE: 'Release'
run: |
mkdir output && \
tools/build-wasm-modules.sh "$(pwd)" "$(pwd)/output/"

- name: Archive production artifacts
uses: actions/upload-artifact@v7
with:
name: wasm-modules-${{ matrix.group.id }}
path: ${{github.workspace}}/output
if-no-files-found: error
retention-days: 15
name: Modules
uses: ./.github/workflows/wasm_modules.yml

build-linux:
name: Linux Companion
Expand Down
72 changes: 72 additions & 0 deletions .github/workflows/wasm_modules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: WASM Modules

# Builds one simulator module per target in fw.json and uploads them as
# wasm-modules-* artifacts. Shared by the Companion and web simulator workflows.
on:
workflow_call:

env:
# Each group takes one runner
GROUPS_COUNT: 9

jobs:
plan:
name: Plan WASM module groups
runs-on: ubuntu-latest
outputs:
groups: ${{ steps.groups.outputs.groups }}
steps:
- name: Check out the repo
uses: actions/checkout@v7

- name: Split fw.json targets into groups
id: groups
shell: bash
run: |
groups=$(jq -c --argjson n "${GROUPS_COUNT}" '
[.targets[][1] | rtrimstr("-")] as $t
| [range($n) as $i
| {id: "group-\($i + 1)",
target: [range($i; $t | length; $n) as $j | $t[$j]]}]
| map(select(.target | length > 0))
' fw.json)
echo "groups=${groups}" >> "$GITHUB_OUTPUT"

modules:
name: WASM Modules (${{ matrix.group.id }})
needs: plan
runs-on: ubuntu-latest
strategy:
matrix:
group: ${{ fromJSON(needs.plan.outputs.groups) }}

steps:
- name: Check out the repo
uses: actions/checkout@v7
with:
submodules: true

- name: Setup Python Dependencies
uses: ./.github/actions/python_dependencies

- name: Setup WASI SDK
uses: ./.github/actions/wasi_sdk
with:
arch: x86_64-linux

- name: Build modules
shell: bash
env:
FLAVOR: ${{ join(matrix.group.target, ';') }}
CMAKE_BUILD_TYPE: 'Release'
run: |
mkdir output && \
tools/build-wasm-modules.sh "$(pwd)" "$(pwd)/output/"

- name: Archive production artifacts
uses: actions/upload-artifact@v7
with:
name: wasm-modules-${{ matrix.group.id }}
path: ${{github.workspace}}/output
if-no-files-found: error
retention-days: 15
156 changes: 156 additions & 0 deletions .github/workflows/web_simulator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
name: Web Simulator

on:
schedule:
- cron: '0 3 * * *' # run at 3 AM UTC
pull_request:
paths:
- '.github/workflows/web_simulator.yml'
- 'web/**'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
NODE_VERSION: '22'

jobs:
check:
name: Check web build
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v7

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: web/package-lock.json

- name: Build
working-directory: web
run: |
npm ci
npm run build

modules:
name: Modules
# Repositories synced from this one get the schedule too
if: github.event_name != 'pull_request' && github.repository == 'EdgeTX/edgetx'
uses: ./.github/workflows/wasm_modules.yml

build:
name: Build site
needs: modules
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v7

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: web/package-lock.json

- name: Download WASM modules
uses: actions/download-artifact@v8
with:
pattern: wasm-modules-*
path: wasm-modules
merge-multiple: true

# The artifacts also contain build logs, which must not be published
- name: Copy WASM modules
run: cp wasm-modules/*.wasm web/public/

- name: Check that every radio has a module
shell: bash
run: |
missing=0
while read -r module; do
if [[ ! -f "web/public/${module}" ]]; then
echo "::error::Missing module ${module}"
missing=1
fi
done < <(jq -r '.[].wasm' web/public/radios.json)
exit "${missing}"

- name: Build
working-directory: web
run: |
npm ci
npm run build

# Before the deploy job, which only runs if this one succeeds
- name: Start every radio
working-directory: web
run: |
npx playwright-core install --with-deps chromium
npm run smoke-test

- name: Archive site
uses: actions/upload-artifact@v7
with:
name: web-simulator-dist
path: web/dist
if-no-files-found: error
retention-days: 3

# Separate from the build, so that the Cloudflare token is only available to
# wrangler. The environment only accepts runs from the deployed branches.
deploy:
name: Deploy site
needs: build
runs-on: ubuntu-latest
environment: web-simulator
steps:
- name: Check out the repo
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@v7
with:
node-version: ${{ env.NODE_VERSION }}

- name: Install wrangler
working-directory: web
run: npm ci --ignore-scripts

- name: Download site
uses: actions/download-artifact@v8
with:
name: web-simulator-dist
path: web/dist

- name: Deploy
id: deploy
working-directory: web
shell: bash
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
npx wrangler deploy | tee "${RUNNER_TEMP}/deploy.log"
url=$(grep -oE 'https://[^ ]+\.workers\.dev' "${RUNNER_TEMP}/deploy.log" | head -n 1)
if [[ -z "${url}" ]]; then
echo "::error::Could not find the deployed URL in the wrangler output"
exit 1
fi
echo "url=${url}" >> "$GITHUB_OUTPUT"

- name: Check deployed site
env:
SITE_URL: ${{ steps.deploy.outputs.url }}
run: node web/scripts/check-deployment.js "${SITE_URL}"
1 change: 1 addition & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
dist
.vite
.wrangler
public/*.wasm
23 changes: 19 additions & 4 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Browser-based EdgeTX radio simulator using WebAssembly + WASI threads.

## Prerequisites

- Node.js 20+
- Node.js 22+
- Pre-built `.wasm` modules in `public/` (see [Building WASM Modules](#building-wasm-modules))

## Quick Start
Expand Down Expand Up @@ -113,6 +113,8 @@ Passing flavour names as arguments limits generation to those radios only — th
| `public/radios.json` | Radio definitions (generated — do not edit manually) |
| `public/_headers` | COOP/COEP headers for production deployment |
| `scripts/gen-radios-json.js` | Generates `radios.json` from `radio/src/boards/hw_defs/` |
| `scripts/check-deployment.js` | Checks a deployed site: modules, content types and COOP/COEP headers |
| `wrangler.jsonc` | Cloudflare Workers configuration |

## Browser Requirements

Expand All @@ -127,6 +129,7 @@ Passing flavour names as arguments limits generation to those radios only — th
npm run dev # Start dev server with HMR
npm run build # Production build to dist/
npm run preview # Preview production build
npm run smoke-test # Start every radio of the production build in a browser
```

### Debugging
Expand All @@ -136,16 +139,28 @@ npm run preview # Preview production build

## Production Deployment

The dev server sets the required COOP/COEP headers automatically. For production, `public/_headers` provides them for platforms like Cloudflare Pages. For other hosts, configure your web server to set:
The dev server sets the required COOP/COEP headers automatically. For production, `public/_headers` provides them on Cloudflare Workers. For other hosts, configure your web server to set:

```
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
```

### Cloudflare Pages
### Cloudflare Workers

The site is deployed as a Worker with static assets, configured in `wrangler.jsonc`. The [Web Simulator workflow](../.github/workflows/web_simulator.yml) builds all modules and deploys `main` every night. It can also be started manually from the Actions tab.

To deploy by hand, with every module in `public/`:

```bash
npm run build
npx wrangler pages deploy dist --project-name=edgetx-simulator
npx wrangler deploy
node scripts/check-deployment.js <deployed-url>
```

`wrangler deploy` goes live immediately.

CI uses two secrets from the `web-simulator` GitHub environment, which only accepts runs from `main`:

- `CLOUDFLARE_API_TOKEN`: an account API token with the Editor role on this Worker only. It cannot create a Worker, so the first deployment has to be done by hand.
- `CLOUDFLARE_ACCOUNT_ID`
Loading
Loading