signalk-server
Version:
An implementation of a [Signal K](http://signalk.org) server for boats.
191 lines (185 loc) • 26.7 kB
HTML
<html class="default" lang="en" data-base="../../../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>Plugin CI/CD | Signal K</title><meta name="description" content="Documentation for Signal K"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../../../assets/style.css?cache=1783009712609"/><link rel="stylesheet" href="../../../assets/highlight.css?cache=1783009712609"/><script defer src="../../../assets/main.js?cache=1783009712609"></script><script async src="../../../assets/icons.js?cache=1783009712609" id="tsd-icons-script"></script><script async src="../../../assets/search.js?cache=1783009712609" id="tsd-search-script"></script><script async src="../../../assets/navigation.js?cache=1783009712609" id="tsd-nav-script"></script><script async src="../../../assets/hierarchy.js?cache=1783009712609" id="tsd-hierarchy-script"></script><link rel="stylesheet" href="../../../assets/theme.css"/><script>if(window.self!==window.top){document.documentElement.classList.add('embedded')}</script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../../../index.html" class="title"><img class="title-logo" src="../../../assets/logo.svg" alt="Signal K"/><svg class="title-home-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-label="Documentation home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg></a><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../../../assets/icons.svg#icon-search"></use></svg><span class="visible@s">Search</span></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><div id="tsd-toolbar-links"><a href="https://discord.gg/uuZrwz4dCS" target="_blank" rel="noopener" class="toolbar-icon visible@s" aria-label="Discord"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M524.5 69.8a1.5 1.5 0 0 0 -.8-.7A485.1 485.1 0 0 0 404.1 32a1.8 1.8 0 0 0 -1.9 .9 337.5 337.5 0 0 0 -14.9 30.6 447.8 447.8 0 0 0 -134.4 0 309.5 309.5 0 0 0 -15.1-30.6 1.9 1.9 0 0 0 -1.9-.9A483.7 483.7 0 0 0 116.1 69.1a1.7 1.7 0 0 0 -.8 .7C39.1 183.7 18.2 294.7 28.4 404.4a2 2 0 0 0 .8 1.4A487.7 487.7 0 0 0 176 479.9a1.9 1.9 0 0 0 2.1-.7A348.2 348.2 0 0 0 208.1 430.4a1.9 1.9 0 0 0 -1-2.6 321.2 321.2 0 0 1 -45.9-21.9 1.9 1.9 0 0 1 -.2-3.1c3.1-2.3 6.2-4.7 9.1-7.1a1.8 1.8 0 0 1 1.9-.3c96.2 43.9 200.4 43.9 295.5 0a1.8 1.8 0 0 1 1.9 .2c2.9 2.4 6 4.9 9.1 7.2a1.9 1.9 0 0 1 -.2 3.1 301.4 301.4 0 0 1 -45.9 21.8 1.9 1.9 0 0 0 -1 2.6 391.1 391.1 0 0 0 30 48.8 1.9 1.9 0 0 0 2.1 .7A486 486 0 0 0 610.7 405.7a1.9 1.9 0 0 0 .8-1.4C623.7 277.6 590.9 167.5 524.5 69.8zM222.5 337.6c-29 0-52.8-26.6-52.8-59.2S193.1 219.1 222.5 219.1c29.7 0 53.3 26.8 52.8 59.2C275.3 311 251.9 337.6 222.5 337.6zm195.4 0c-29 0-52.8-26.6-52.8-59.2S388.4 219.1 417.9 219.1c29.7 0 53.3 26.8 52.8 59.2C470.7 311 447.5 337.6 417.9 337.6z"></path></svg></a><a href="https://github.com/SignalK/signalk-server" target="_blank" rel="noopener" class="toolbar-icon visible@s" aria-label="Discord"><svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg></a><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../../../assets/icons.svg#icon-menu"></use></svg></a></div></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="../../../Developing.html">Developing</a></li><li><a href="../../Plugins.html">Plugins</a></li><li><a href="" aria-current="page">Plugin CI/CD</a></li></ul></div><div class="tsd-panel tsd-typography"><h1 id="continuous-integration-for-plugins" class="tsd-anchor-link">Continuous Integration for Plugins<a href="#continuous-integration-for-plugins" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h1>
<p>Signal K provides a reusable GitHub Actions workflow that tests your plugin across all platforms where Signal K server runs. Even plugins without a test suite benefit — the workflow validates your plugin's structure, entry point, configuration schema, lifecycle, and API usage.</p>
<h2 id="quick-start" class="tsd-anchor-link">Quick Start<a href="#quick-start" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>Create <code>.github/workflows/signalk-ci.yml</code> in your plugin repository:</p>
<pre><code class="yaml"><span class="hl-3">name</span><span class="hl-0">: </span><span class="hl-4">SignalK Plugin CI</span><br/><br/><span class="hl-2">on</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">push</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">branches</span><span class="hl-0">: [</span><span class="hl-4">main</span><span class="hl-0">, </span><span class="hl-4">master</span><span class="hl-0">]</span><br/><span class="hl-0"> </span><span class="hl-3">pull_request</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">branches</span><span class="hl-0">: [</span><span class="hl-4">main</span><span class="hl-0">, </span><span class="hl-4">master</span><span class="hl-0">]</span><br/><br/><span class="hl-3">jobs</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">test</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">uses</span><span class="hl-0">: </span><span class="hl-4">SignalK/signalk-server/.github/workflows/plugin-ci.yml@master</span>
</code><button type="button">Copy</button></pre>
<p>Push to GitHub — your plugin is now tested on Linux (x64 + arm64), macOS, Windows, and armv7 (Cerbo GX).</p>
<h2 id="manual-trigger-with-custom-settings" class="tsd-anchor-link">Manual Trigger with Custom Settings<a href="#manual-trigger-with-custom-settings" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>Add <code>workflow_dispatch</code> to get a <strong>"Run workflow"</strong> button in the GitHub Actions UI where you can override Node versions, toggle armv7/Cerbo GX testing, enable integration tests, and more — without editing your workflow file.</p>
<p>Because <code>workflow_call</code> and <code>workflow_dispatch</code> inputs are separate namespaces in GitHub Actions, the workflow needs two jobs: one for automatic runs (push/PR) with hardcoded defaults, and one for manual runs that passes through your form inputs.</p>
<p>See <a href="../../../media/plugin-caller-example.yml"><code>examples/plugin-caller-example.yml</code></a> for the full workflow with manual trigger support.</p>
<h2 id="what-gets-tested" class="tsd-anchor-link">What Gets Tested<a href="#what-gets-tested" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<h3 id="platforms" class="tsd-anchor-link">Platforms<a href="#platforms" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<table>
<thead>
<tr>
<th>Platform</th>
<th>Architecture</th>
<th>Node versions</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>Linux</td>
<td>x64</td>
<td>22, 24</td>
<td>GitHub-hosted runner</td>
</tr>
<tr>
<td>Linux</td>
<td>arm64</td>
<td>22, 24</td>
<td>GitHub-hosted runner — Raspberry Pi 4/5</td>
</tr>
<tr>
<td>macOS</td>
<td>arm64</td>
<td>22, 24</td>
<td>GitHub-hosted runner</td>
</tr>
<tr>
<td>Windows</td>
<td>x64</td>
<td>22, 24</td>
<td>GitHub-hosted runner</td>
</tr>
<tr>
<td>Linux</td>
<td>armv7 (Cerbo GX)</td>
<td>20</td>
<td>QEMU emulation — matches Venus OS 3.70 (Node 20)</td>
</tr>
</tbody>
</table>
<h3 id="validation-checks" class="tsd-anchor-link">Validation Checks<a href="#validation-checks" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<p>The desktop jobs (Linux, Linux arm64, macOS, Windows) run these checks, even if your plugin has no test suite. The list below is a summary for readers — the authoritative source for what the CI actually validates is the workflow itself: <a href="https://github.com/SignalK/signalk-server/blob/master/.github/workflows/plugin-ci.yml">.github/workflows/plugin-ci.yml</a>.</p>
<p><strong>package.json</strong> — <code>signalk-node-server-plugin</code> keyword, <code>main</code> or <code>exports</code> field, <code>engines.node</code> declaration</p>
<p><strong>Entry point</strong> — After build, verifies the plugin exports a constructor function</p>
<p><strong>plugin.schema()</strong> — Calls <code>schema()</code> and checks it returns a JSON-serializable schema-like object without crashing (not fully validated against the JSON Schema meta-schema)</p>
<p><strong>Lifecycle</strong> — Runs <code>start()</code> → <code>stop()</code> → <code>start()</code> (restart) with an empty configuration. Validates delta messages emitted during startup and checks that <code>registerDeltaInputHandler</code> handlers forward deltas correctly.</p>
<p><strong>API usage</strong> — Scans source files for:</p>
<ul>
<li>Deprecated APIs (<code>setProviderStatus</code> → <code>setPluginStatus</code>, <code>setProviderError</code> → <code>setPluginError</code>)</li>
<li>Internal server properties (<code>app.server</code>, <code>app.deltaCache</code>, <code>app.pluginsMap</code>)</li>
<li>Route registration anti-patterns (direct <code>app.get()</code> instead of <code>registerWithRouter()</code>)</li>
<li>File storage anti-patterns (writing to <code>__dirname</code> or <code>process.cwd()</code> instead of <code>app.getDataDirPath()</code>)</li>
<li>Security anti-patterns (accessing <code>app.securityStrategy</code> or <code>isDummy()</code> — plugin routes are already protected by the server)</li>
<li>Node built-in module version mismatches (<code>node:sqlite</code> requires <code>engines.node >= 22.5.0</code>)</li>
</ul>
<p><strong>npm pack</strong> — Verifies all files referenced by <code>main</code>/<code>exports</code> are included in the published package</p>
<p><strong>App Store compatibility</strong> — Installs the plugin with <code>--ignore-scripts</code> (as the App Store does) and checks for native addon dependencies</p>
<p><strong>Stray files</strong> — Warns when build and test steps leave untracked files</p>
<h2 id="configuration" class="tsd-anchor-link">Configuration<a href="#configuration" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>Override defaults by passing inputs to the shared workflow:</p>
<pre><code class="yaml"><span class="hl-3">jobs</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">test</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">uses</span><span class="hl-0">: </span><span class="hl-4">SignalK/signalk-server/.github/workflows/plugin-ci.yml@master</span><br/><span class="hl-0"> </span><span class="hl-3">with</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">test-command</span><span class="hl-0">: </span><span class="hl-4">'npm run test:ci'</span><br/><span class="hl-0"> </span><span class="hl-3">build-command</span><span class="hl-0">: </span><span class="hl-4">'npm run build:plugin'</span><br/><span class="hl-0"> </span><span class="hl-3">enable-armv7</span><span class="hl-0">: </span><span class="hl-2">false</span><br/><span class="hl-0"> </span><span class="hl-3">enable-signalk-integration</span><span class="hl-0">: </span><span class="hl-2">true</span><br/><span class="hl-0"> </span><span class="hl-3">node-versions</span><span class="hl-0">: </span><span class="hl-4">'["22"]'</span>
</code><button type="button">Copy</button></pre>
<table>
<thead>
<tr>
<th>Input</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>test-command</code></td>
<td><code>npm test</code></td>
<td>Command to run your test suite</td>
</tr>
<tr>
<td><code>build-command</code></td>
<td><code>npm run build --if-present</code></td>
<td>Build command</td>
</tr>
<tr>
<td><code>format-check-command</code></td>
<td><em>(empty)</em></td>
<td>Blocking format check (e.g. <code>npm run prettier:check</code>, <code>npx biome check .</code>); skipped when empty</td>
</tr>
<tr>
<td><code>coverage-command</code></td>
<td><em>(empty)</em></td>
<td>Runs tests with coverage (e.g. <code>npm run coverage</code>); replaces the standard test run and writes output to the step summary</td>
</tr>
<tr>
<td><code>node-versions</code></td>
<td><code>["22", "24"]</code></td>
<td>Node versions for desktop platforms</td>
</tr>
<tr>
<td><code>enable-armv7</code></td>
<td><code>true</code></td>
<td>Test on armv7 (Cerbo GX) via QEMU</td>
</tr>
<tr>
<td><code>enable-signalk-integration</code></td>
<td><code>false</code></td>
<td>Start SignalK server for integration tests</td>
</tr>
<tr>
<td><code>signalk-server-versions</code></td>
<td><code>["latest"]</code></td>
<td>JSON array of signalk-server versions; the integration job fans out over each</td>
</tr>
</tbody>
</table>
<h3 id="formatting-and-coverage" class="tsd-anchor-link">Formatting and coverage<a href="#formatting-and-coverage" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<p>Both are tool-agnostic command strings — the workflow doesn't care whether you use Prettier/Biome or c8/nyc/<code>jest --coverage</code>. Leave either empty to opt out.</p>
<pre><code class="yaml"><span class="hl-3">with</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">format-check-command</span><span class="hl-0">: </span><span class="hl-4">'npm run prettier:check'</span><br/><span class="hl-0"> </span><span class="hl-3">coverage-command</span><span class="hl-0">: </span><span class="hl-4">'npm run coverage'</span>
</code><button type="button">Copy</button></pre>
<p><code>format-check-command</code> runs after lint and <strong>blocks the job</strong> if it fails (unlike <code>npm run lint --if-present</code>, which is advisory). <code>coverage-command</code> replaces the standard <code>Run tests</code> step — its stdout is captured and appended to the GitHub Actions step summary so you can see coverage output without digging through logs.</p>
<h2 id="packagejson" class="tsd-anchor-link">package.json<a href="#packagejson" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>The CI validates the same fields described in the <a href="../Publishing_to_The_AppStore.html">publishing guide</a>. The most important for CI:</p>
<ul>
<li><code>keywords</code> must include <code>signalk-node-server-plugin</code></li>
<li><code>main</code> or <code>exports</code> must point to your entry file</li>
<li><code>engines.node</code> should declare the minimum Node.js version (required if you use <code>node:sqlite</code> or other version-specific built-in modules)</li>
</ul>
<p>Plugins without a <code>test</code> script still get all validation checks — tests are skipped with a notice.</p>
<h2 id="armv7-cerbo-gx-testing" class="tsd-anchor-link">armv7 / Cerbo GX Testing<a href="#armv7-cerbo-gx-testing" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>The Cerbo GX runs an Allwinner dual-core Cortex-A7 (ARMv7, 32-bit) with Venus OS. The CI emulates this environment using QEMU with a <code>node:20-bookworm-slim</code> Docker image plus <code>python3</code>, <code>make</code>, and <code>g++</code> — matching Venus OS 3.70 which ships Node 20 and has build tools available via opkg.</p>
<p>The armv7 job runs install, build, and tests — it does not repeat the full validation suite (that's covered by the desktop jobs). The armv7 Node version is fixed to match the Cerbo GX and is not user-configurable. Expect armv7 jobs to take 3-5x longer than native x64. armv7 failures are <strong>advisory and non-blocking</strong>.</p>
<h3 id="limitations" class="tsd-anchor-link">Limitations<a href="#limitations" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<ul>
<li><strong>Native addons</strong> compile for armv7 inside the container (slow but works — pre-built binaries rarely exist for ARM32)</li>
<li><strong>Hardware peripherals</strong> (GPIO, CAN bus, serial) are not emulated — use a self-hosted runner for those</li>
</ul>
<h2 id="integration-tests" class="tsd-anchor-link">Integration Tests<a href="#integration-tests" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>Enable <code>enable-signalk-integration: true</code> to run your plugin against a real Signal K server.</p>
<p>The job installs a Signal K server, packs and installs your plugin, auto-enables it, and starts the server with sample NMEA 0183 + NMEA 2000 data so the plugin has a realistic data environment (navigation, wind, depth, temperature, battery, and more). It then verifies the plugin loaded, checks provider API registrations, and runs <code>npm run test:integration</code> if defined. Your tests receive <code>SIGNALK_URL=http://localhost:3000</code> to connect to the running server.</p>
<p>The authoritative sequence of steps lives in the workflow itself: <a href="https://github.com/SignalK/signalk-server/blob/master/.github/workflows/plugin-ci.yml">.github/workflows/plugin-ci.yml</a>.</p>
<p>Pass <code>signalk-server-versions</code> as a JSON array to fan the integration job out over multiple server versions — useful for catching regressions across the baconjs 1 → 3 transition (server 2.23.x vs 2.24.0+) and similar cross-generation breakage:</p>
<pre><code class="yaml"><span class="hl-3">with</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">enable-signalk-integration</span><span class="hl-0">: </span><span class="hl-2">true</span><br/><span class="hl-0"> </span><span class="hl-3">signalk-server-versions</span><span class="hl-0">: </span><span class="hl-4">'["2.23.0", "latest"]'</span>
</code><button type="button">Copy</button></pre>
<p>The integration job runs the full Cartesian product of <code>node-versions × signalk-server-versions</code>. The default <code>["22", "24"] × ["latest"]</code> is 2 jobs; <code>["22", "24"] × ["2.23.0", "latest"]</code> is 4. To keep the matrix small, shrink either dimension — integration coverage often only needs a single Node version (<code>node-versions: '["22"]'</code>) even when the desktop jobs exercise several.</p>
<h3 id="provider-api-verification" class="tsd-anchor-link">Provider API Verification<a href="#provider-api-verification" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h3>
<p>If your plugin registers as a provider for one of the server's provider APIs, the integration test verifies the registration actually works by calling the corresponding endpoint:</p>
<table>
<thead>
<tr>
<th>Provider API</th>
<th>Registration method</th>
<th>Endpoint checked</th>
</tr>
</thead>
<tbody>
<tr>
<td>History API v2</td>
<td><code>app.registerHistoryApiProvider()</code></td>
<td><code>/signalk/v2/api/history/values</code> must not return 501</td>
</tr>
</tbody>
</table>
<p>This catches a common class of bugs where a plugin calls a registration method but the endpoint still returns "no provider configured" — for example due to an API mismatch between the plugin and the server version being tested.</p>
<h2 id="self-hosted-runner-for-real-hardware" class="tsd-anchor-link">Self-Hosted Runner for Real Hardware<a href="#self-hosted-runner-for-real-hardware" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<p>For testing against actual hardware (GPIO, CAN bus, serial ports), add a <a href="https://docs.github.com/en/actions/hosting-your-own-runners">self-hosted runner</a> on a Cerbo GX or Raspberry Pi:</p>
<pre><code class="yaml"><span class="hl-3">test-cerbo-hardware</span><span class="hl-0">:</span><br/><span class="hl-0"> </span><span class="hl-3">name</span><span class="hl-0">: </span><span class="hl-4">Cerbo GX (real hardware)</span><br/><span class="hl-0"> </span><span class="hl-3">runs-on</span><span class="hl-0">: [</span><span class="hl-4">self-hosted</span><span class="hl-0">, </span><span class="hl-4">cerbo-gx</span><span class="hl-0">]</span><br/><span class="hl-0"> </span><span class="hl-3">steps</span><span class="hl-0">:</span><br/><span class="hl-0"> - </span><span class="hl-3">uses</span><span class="hl-0">: </span><span class="hl-4">actions/checkout@v6</span><br/><span class="hl-0"> - </span><span class="hl-3">run</span><span class="hl-0">: </span><span class="hl-4">npm ci</span><br/><span class="hl-0"> - </span><span class="hl-3">run</span><span class="hl-0">: </span><span class="hl-4">npm test</span>
</code><button type="button">Copy</button></pre>
<h2 id="see-also" class="tsd-anchor-link">See also<a href="#see-also" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../../../assets/icons.svg#icon-anchor"></use></svg></a></h2>
<ul>
<li><a href="../Releases_and_Changelogs.html">Releases and Changelogs</a> — once CI passes, automate the release cut and publish step.</li>
</ul>
</div></div><div class="col-sidebar"><div class="page-menu"><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../../../assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#quick-start"><span>Quick <wbr/>Start</span></a><a href="#manual-trigger-with-custom-settings"><span>Manual <wbr/>Trigger with <wbr/>Custom <wbr/>Settings</span></a><a href="#what-gets-tested"><span>What <wbr/>Gets <wbr/>Tested</span></a><ul><li><a href="#platforms"><span>Platforms</span></a></li><li><a href="#validation-checks"><span>Validation <wbr/>Checks</span></a></li></ul><a href="#configuration"><span>Configuration</span></a><ul><li><a href="#formatting-and-coverage"><span>Formatting and coverage</span></a></li></ul><a href="#packagejson"><span>package.json</span></a><a href="#armv7-cerbo-gx-testing"><span>armv7 / <wbr/>Cerbo <wbr/>GX <wbr/>Testing</span></a><ul><li><a href="#limitations"><span>Limitations</span></a></li></ul><a href="#integration-tests"><span>Integration <wbr/>Tests</span></a><ul><li><a href="#provider-api-verification"><span>Provider <wbr/>API <wbr/>Verification</span></a></li></ul><a href="#self-hosted-runner-for-real-hardware"><span>Self-<wbr/>Hosted <wbr/>Runner for <wbr/>Real <wbr/>Hardware</span></a><a href="#see-also"><span>See also</span></a></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="../../../modules.html">Signal K</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>