@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -21
- package/README.md +49 -87
- package/dist/as-built.d.ts +2 -2
- package/dist/as-built.js +2 -2
- package/dist/audit.d.ts +111 -0
- package/dist/audit.js +416 -0
- package/dist/boardgen.d.ts +1 -9
- package/dist/boardgen.js +288 -47
- package/dist/chips/resolve.js +20 -0
- package/dist/chips/types.d.ts +22 -1
- package/dist/debug-codegen.js +1 -1
- package/dist/display/bindings.d.ts +55 -0
- package/dist/display/bindings.js +316 -0
- package/dist/display/gfx.d.ts +2 -3
- package/dist/display/gfx.js +166 -154
- package/dist/display/index.js +20 -1
- package/dist/display/mipi-dbi-host.d.ts +9 -0
- package/dist/display/mipi-dbi-host.js +174 -0
- package/dist/display/profiles.d.ts +109 -4
- package/dist/display/profiles.js +270 -7
- package/dist/display/touch-adapter.js +119 -49
- package/dist/display/ui-adapter-eink.d.ts +2 -0
- package/dist/display/ui-adapter-eink.js +4 -0
- package/dist/display/ui-adapter-gray.d.ts +8 -0
- package/dist/display/ui-adapter-gray.js +170 -0
- package/dist/display/ui-adapter-mono.d.ts +13 -0
- package/dist/display/ui-adapter-mono.js +230 -0
- package/dist/display/ui-adapter-native.d.ts +10 -0
- package/dist/display/ui-adapter-native.js +295 -0
- package/dist/display/ui-adapter-shared.d.ts +11 -0
- package/dist/display/ui-adapter-shared.js +122 -0
- package/dist/display/ui-adapter.js +510 -558
- package/dist/doctor.js +4 -4
- package/dist/dt-config/custom-board.js +2 -2
- package/dist/dt-config/kconfig.d.ts +62 -1
- package/dist/dt-config/kconfig.js +141 -38
- package/dist/dt-config/overlay.d.ts +15 -2
- package/dist/dt-config/overlay.js +433 -18
- package/dist/framework.manifest.d.ts +10 -4
- package/dist/framework.manifest.js +133 -17
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -6
- package/dist/licenses.d.ts +2 -2
- package/dist/licenses.js +13 -92
- package/dist/lowering/can.d.ts +25 -0
- package/dist/lowering/can.js +97 -0
- package/dist/lowering/clock.d.ts +17 -0
- package/dist/lowering/clock.js +58 -0
- package/dist/lowering/fs.js +1 -1
- package/dist/lowering/gpio.js +0 -32
- package/dist/lowering/hid.d.ts +27 -0
- package/dist/lowering/hid.js +244 -0
- package/dist/lowering/http.js +264 -32
- package/dist/lowering/i2c.d.ts +8 -0
- package/dist/lowering/i2c.js +137 -5
- package/dist/lowering/i2s.d.ts +27 -0
- package/dist/lowering/i2s.js +98 -0
- package/dist/lowering/index.d.ts +9 -1
- package/dist/lowering/index.js +25 -1
- package/dist/lowering/interrupts.js +6 -0
- package/dist/lowering/matrix.d.ts +15 -0
- package/dist/lowering/matrix.js +63 -0
- package/dist/lowering/mqtt.js +110 -8
- package/dist/lowering/power.d.ts +3 -2
- package/dist/lowering/power.js +20 -45
- package/dist/lowering/pwm.js +25 -0
- package/dist/lowering/sensor.d.ts +2 -2
- package/dist/lowering/sensor.js +8 -4
- package/dist/lowering/strip.d.ts +16 -0
- package/dist/lowering/strip.js +70 -0
- package/dist/lowering/thread.js +5 -1
- package/dist/lowering/trace.d.ts +44 -0
- package/dist/lowering/trace.js +239 -0
- package/dist/lowering/uart.js +6 -1
- package/dist/lowering/usb.d.ts +3 -1
- package/dist/lowering/usb.js +16 -13
- package/dist/lowering/wdt.js +2 -29
- package/dist/sbom.d.ts +181 -0
- package/dist/sbom.js +901 -0
- package/dist/sdk/board-catalog-sync.d.ts +1 -3
- package/dist/sdk/board-catalog-sync.js +4 -10
- package/dist/strategy.d.ts +82 -46
- package/dist/strategy.js +639 -182
- package/dist/tmp-probe.d.ts +2 -0
- package/dist/tmp-probe.js +9 -0
- package/dist/toolchain/debug-config.d.ts +50 -90
- package/dist/toolchain/debug-config.js +239 -510
- package/dist/toolchain/env-check.d.ts +1 -3
- package/dist/toolchain/env-check.js +2 -7
- package/dist/toolchain/index.d.ts +28 -3
- package/dist/toolchain/index.js +498 -62
- package/dist/toolchain/runners.d.ts +16 -0
- package/dist/toolchain/runners.js +75 -0
- package/dist/toolchain/scaffold.d.ts +5 -2
- package/dist/toolchain/scaffold.js +79 -15
- package/dist/toolchain/west-discover.d.ts +6 -0
- package/dist/toolchain/west-discover.js +36 -13
- package/dist/toolchain/west-spawn.js +8 -2
- package/dist/west-inventory.d.ts +25 -0
- package/dist/west-inventory.js +97 -0
- package/installer/README.md +328 -328
- package/installer/install.sh +2 -2
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
- package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
- package/installer/templates/project/.vscode/settings.json +1 -1
- package/installer/templates/project/README.md +2 -2
- package/package.json +5 -5
- package/src/as-built.ts +206 -206
- package/src/audit.ts +529 -0
- package/src/boardgen.ts +265 -50
- package/src/chips/resolve.ts +21 -0
- package/src/chips/types.ts +10 -1
- package/src/display/bindings.ts +347 -0
- package/src/display/gfx.ts +318 -306
- package/src/display/index.ts +87 -70
- package/src/display/mipi-dbi-host.ts +183 -0
- package/src/display/profiles.ts +458 -139
- package/src/display/touch-adapter.ts +119 -49
- package/src/display/ui-adapter-eink.ts +13 -0
- package/src/display/ui-adapter-gray.ts +178 -0
- package/src/display/ui-adapter-mono.ts +238 -0
- package/src/display/ui-adapter-native.ts +304 -0
- package/src/display/ui-adapter-shared.ts +125 -0
- package/src/display/ui-adapter.ts +732 -781
- package/src/doctor.ts +4 -4
- package/src/dt-config/custom-board.ts +2 -2
- package/src/dt-config/kconfig.ts +647 -505
- package/src/dt-config/overlay.ts +1475 -1058
- package/src/framework.manifest.ts +659 -535
- package/src/index.ts +16 -6
- package/src/licenses.ts +346 -425
- package/src/lowering/can.ts +140 -0
- package/src/lowering/clock.ts +91 -0
- package/src/lowering/fs.ts +135 -135
- package/src/lowering/gpio.ts +0 -33
- package/src/lowering/hid.ts +261 -0
- package/src/lowering/http.ts +264 -32
- package/src/lowering/i2c.ts +142 -5
- package/src/lowering/i2s.ts +143 -0
- package/src/lowering/index.ts +18 -1
- package/src/lowering/interrupts.ts +6 -0
- package/src/lowering/matrix.ts +70 -0
- package/src/lowering/mqtt.ts +109 -8
- package/src/lowering/power.ts +41 -0
- package/src/lowering/pwm.ts +192 -167
- package/src/lowering/sensor.ts +159 -155
- package/src/lowering/strip.ts +81 -0
- package/src/lowering/thread.ts +5 -1
- package/src/lowering/trace.ts +270 -0
- package/src/lowering/uart.ts +6 -1
- package/src/lowering/usb.ts +16 -13
- package/src/lowering/wdt.ts +2 -25
- package/src/sbom.ts +1117 -0
- package/src/sdk/board-catalog-sync.ts +4 -25
- package/src/strategy.ts +2680 -2309
- package/src/toolchain/debug-config.ts +262 -522
- package/src/toolchain/env-check.ts +279 -285
- package/src/toolchain/index.ts +1792 -1359
- package/src/toolchain/runners.ts +80 -0
- package/src/toolchain/scaffold.ts +355 -296
- package/src/toolchain/west-discover.ts +35 -13
- package/src/toolchain/west-spawn.ts +174 -168
- package/src/west-inventory.ts +102 -0
- package/dist/async/timer-polyfill.d.ts +0 -10
- package/dist/async/timer-polyfill.js +0 -95
- package/dist/chips/board-overrides.d.ts +0 -7
- package/dist/chips/board-overrides.js +0 -11
- package/dist/chips/esp32.d.ts +0 -2
- package/dist/chips/esp32.js +0 -71
- package/dist/chips/esp32s3.d.ts +0 -2
- package/dist/chips/esp32s3.js +0 -103
- package/dist/chips/soc/.d.ts +0 -2
- package/dist/chips/soc/.js +0 -129
- package/dist/chips/soc/esp32.d.ts +0 -2
- package/dist/chips/soc/esp32.js +0 -120
- package/dist/chips/soc/esp32c3.d.ts +0 -2
- package/dist/chips/soc/esp32c3.js +0 -90
- package/dist/chips/soc/esp32c6.d.ts +0 -2
- package/dist/chips/soc/esp32c6.js +0 -109
- package/dist/chips/soc/esp32s3.d.ts +0 -2
- package/dist/chips/soc/esp32s3.js +0 -189
- package/dist/chips/soc/index.d.ts +0 -2
- package/dist/chips/soc/index.js +0 -23
- package/dist/chips/soc/nrf52840.d.ts +0 -2
- package/dist/chips/soc/nrf52840.js +0 -130
- package/dist/chips/soc/rp2040.d.ts +0 -2
- package/dist/chips/soc/rp2040.js +0 -141
- package/dist/chips/soc/rp2350a.d.ts +0 -2
- package/dist/chips/soc/rp2350a.js +0 -145
- package/dist/chips/soc/samd21g18a.d.ts +0 -2
- package/dist/chips/soc/samd21g18a.js +0 -143
- package/dist/chips/soc/stm32f411xe.d.ts +0 -2
- package/dist/chips/soc/stm32f411xe.js +0 -251
- package/dist/chips/xiao-ble.d.ts +0 -2
- package/dist/chips/xiao-ble.js +0 -100
- package/dist/lowering/pulse.d.ts +0 -7
- package/dist/lowering/pulse.js +0 -51
- package/dist/lowering/tone.d.ts +0 -10
- package/dist/lowering/tone.js +0 -63
- package/dist/lowering/worker-backing.d.ts +0 -14
- package/dist/lowering/worker-backing.js +0 -79
- package/dist/lowering/worker.d.ts +0 -6
- package/dist/lowering/worker.js +0 -14
- package/dist/sdk/board-data.generated.d.ts +0 -2
- package/dist/sdk/board-data.generated.js +0 -4
- package/dist/sdk/catalog-walker.d.ts +0 -90
- package/dist/sdk/catalog-walker.js +0 -682
- package/dist/sdk/dts-reader.d.ts +0 -83
- package/dist/sdk/dts-reader.js +0 -596
- package/src/debug-codegen.ts +0 -207
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// 1. `west` already on PATH (env already activated / global install).
|
|
12
12
|
// 2. $ZEPHYR_BASE venv: ${ZEPHYR_BASE}/../.venv/<python> -m west.
|
|
13
13
|
// 3. micromamba env from @typecad/zephyr-installer (invoked via `micromamba run`,
|
|
14
|
-
// so
|
|
14
|
+
// so typecad-hal builds work with NO manual activation).
|
|
15
15
|
// 4. Well-known workspace layouts: ~/zephyrproject/.venv, /opt/zephyrproject/.
|
|
16
16
|
// venv, etc.
|
|
17
17
|
// 5. System pythons (`python`, `python3`, `py`) via `-m west`.
|
|
@@ -142,6 +142,33 @@ export function discoverFromZephyrBase(): WestInstall | null {
|
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/** The installer-written SDK dir for the default micromamba env, when the
|
|
146
|
+
* installer has run on this machine. Read WITHOUT requiring micromamba
|
|
147
|
+
* mode — a PATH/venv west install is enriched with it so builds spawned
|
|
148
|
+
* through it still pin the installer's SDK (otherwise Zephyr's CMake
|
|
149
|
+
* searches $HOME and can pick up a stray, older SDK). */
|
|
150
|
+
export function installerSdkDir(): string | undefined {
|
|
151
|
+
const mm = findMicromamba();
|
|
152
|
+
if (!mm) return undefined;
|
|
153
|
+
const envName = process.env.TYPECAD_ZEPHYR_ENV || 'zephyr';
|
|
154
|
+
const envDir = join(mm.rootPrefix, 'envs', envName);
|
|
155
|
+
if (!existsSync(envDir)) return undefined;
|
|
156
|
+
const sdk = readMicromambaEnvVar(envDir, 'TYPECAD_ZEPHYR_SDK_INSTALL_DIR');
|
|
157
|
+
return sdk && existsSync(sdk) ? sdk : undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The micromamba env created by `@typecad/zephyr-installer`. The env's west
|
|
162
|
+
* lives at envs/<name>/bin/west (POSIX) or Scripts/west.exe (Windows). Found
|
|
163
|
+
* installs are invoked via `micromamba run -n <name> west …` (see
|
|
164
|
+
* west-spawn.ts), which sets up the env's full PATH (cmake/ninja/dtc) AND runs
|
|
165
|
+
* the activation hook (ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR) — so builds work
|
|
166
|
+
* with NO manual `micromamba activate`. This is what makes a fresh
|
|
167
|
+
* `typecad-hal build` succeed in any project without the user activating.
|
|
168
|
+
*
|
|
169
|
+
* Env name defaults to "zephyr"; override via TYPECAD_ZEPHYR_ENV. File-check
|
|
170
|
+
* based (no spawn) so it's cheap to run on every invocation.
|
|
171
|
+
*/
|
|
145
172
|
// ── Strategy 3: micromamba env (the @typecad/zephyr-installer install) ─────
|
|
146
173
|
|
|
147
174
|
// Locate the micromamba binary + root prefix. The installer downloads
|
|
@@ -156,18 +183,6 @@ function findMicromamba(): { exe: string; rootPrefix: string } | null {
|
|
|
156
183
|
return existsSync(exe) ? { exe, rootPrefix: root } : null;
|
|
157
184
|
}
|
|
158
185
|
|
|
159
|
-
/**
|
|
160
|
-
* The micromamba env created by `@typecad/zephyr-installer`. The env's west
|
|
161
|
-
* lives at envs/<name>/bin/west (POSIX) or Scripts/west.exe (Windows). Found
|
|
162
|
-
* installs are invoked via `micromamba run -n <name> west …` (see
|
|
163
|
-
* west-spawn.ts), which sets up the env's full PATH (cmake/ninja/dtc) AND runs
|
|
164
|
-
* the activation hook (ZEPHYR_BASE / ZEPHYR_SDK_INSTALL_DIR) — so cuttlefish
|
|
165
|
-
* builds work with NO manual `micromamba activate`. This is what makes a fresh
|
|
166
|
-
* `cuttlefish build` succeed in any project without the user activating.
|
|
167
|
-
*
|
|
168
|
-
* Env name defaults to "zephyr"; override via TYPECAD_ZEPHYR_ENV. File-check
|
|
169
|
-
* based (no spawn) so it's cheap to run on every cuttlefish invocation.
|
|
170
|
-
*/
|
|
171
186
|
/** Read a TYPECAD_ZEPHYR_* value from the installer-written env-vars file in
|
|
172
187
|
* a micromamba env. Handles .sh (export VAR="val"), .bat (set "VAR=val"),
|
|
173
188
|
* and .ps1 ($env:VAR = "val"). Returns undefined if absent/unreadable. */
|
|
@@ -325,6 +340,13 @@ export function discoverWest(): WestInstall | null {
|
|
|
325
340
|
install = null;
|
|
326
341
|
}
|
|
327
342
|
if (install) {
|
|
343
|
+
// A PATH/venv west install knows nothing about the installer's SDK.
|
|
344
|
+
// Enrich it: west-spawn pins the child's ZEPHYR_SDK_INSTALL_DIR to the
|
|
345
|
+
// installer dir, so Zephyr's CMake cannot pick up a stray SDK from
|
|
346
|
+
// $HOME (a version mismatch that only surfaces at configure time).
|
|
347
|
+
if (!install.sdkInstallDir) {
|
|
348
|
+
install.sdkInstallDir = installerSdkDir();
|
|
349
|
+
}
|
|
328
350
|
cachedDiscover = install;
|
|
329
351
|
return install;
|
|
330
352
|
}
|
|
@@ -1,168 +1,174 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// west spawn helper — build a spawnSync invocation for `west <args>`
|
|
3
|
-
//
|
|
4
|
-
// Mirrors framework-esp32's idfSpawn(): given the discovered WestInstall,
|
|
5
|
-
// produce {command, args, env} that the toolchain destructures into spawnSync.
|
|
6
|
-
//
|
|
7
|
-
// Two modes:
|
|
8
|
-
// - 'launcher': spawn the `west` launcher directly (mode 'launcher').
|
|
9
|
-
// - 'module' : spawn `<python> -m west ...` (mode 'module'). This is the
|
|
10
|
-
// robust cross-platform form — it activates the venv's
|
|
11
|
-
// interpreter for the one process without sourcing anything.
|
|
12
|
-
//
|
|
13
|
-
// ZEPHYR_BASE is injected into the spawn env when a SDK root was discovered
|
|
14
|
-
// (find_package(Zephyr) needs it). west's prj.conf/CMakeLists are found via
|
|
15
|
-
// the project dir regardless.
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
import type { SpawnSyncOptions } from 'node:child_process';
|
|
19
|
-
import { dirname, join } from 'node:path';
|
|
20
|
-
import { existsSync } from 'node:fs';
|
|
21
|
-
import { type WestInstall, discoverWest } from './west-discover.js';
|
|
22
|
-
|
|
23
|
-
export interface WestInvocation {
|
|
24
|
-
/** The command to spawnSync: a `west` launcher or a Python interpreter. */
|
|
25
|
-
command: string;
|
|
26
|
-
/** Args for the command (includes `-m west` when in module mode). */
|
|
27
|
-
args: string[];
|
|
28
|
-
/** Base spawn options with cwd/timeout/encoding/stdio, plus the env carrying
|
|
29
|
-
* ZEPHYR_BASE when discovered. */
|
|
30
|
-
options: SpawnSyncOptions;
|
|
31
|
-
/** The install the invocation was built from, for surfacing to the user. */
|
|
32
|
-
install: WestInstall;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* The Scripts/ (Windows) or bin/ (POSIX) directory of the venv the discovered
|
|
37
|
-
* west runs under. `pythonExecutable` lives in that directory, so it is its
|
|
38
|
-
* dirname. Returns undefined for launcher-mode installs where no venv is known.
|
|
39
|
-
*/
|
|
40
|
-
function venvBinDir(install: WestInstall): string | undefined {
|
|
41
|
-
return install.pythonExecutable ? dirname(install.pythonExecutable) : undefined;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The env to pass to the west spawn: the process env, with ZEPHYR_BASE injected
|
|
46
|
-
* when a SDK root was discovered, AND the venv's bin/Scripts dir prepended to
|
|
47
|
-
* PATH when west was found via a venv Python.
|
|
48
|
-
*
|
|
49
|
-
* The PATH prepend matters: `west flash` shells out to bare runner tools
|
|
50
|
-
* (`esptool`, `openocd`, `nrfjprog`, …) via check_call, so they resolve from
|
|
51
|
-
* PATH. Without the prepend, the user's PATH may surface a *different* tool
|
|
52
|
-
* ahead of the venv's — e.g. an older esptool whose argument spelling is
|
|
53
|
-
* incompatible with the runner. Putting the venv's bin first makes west's
|
|
54
|
-
* delegated subprocesses resolve to the same versions west itself runs under.
|
|
55
|
-
*/
|
|
56
|
-
export function buildEnv(install: WestInstall): NodeJS.ProcessEnv {
|
|
57
|
-
const env = { ...process.env };
|
|
58
|
-
if (install.zephyrBase && !env.ZEPHYR_BASE) {
|
|
59
|
-
env.ZEPHYR_BASE = install.zephyrBase;
|
|
60
|
-
}
|
|
61
|
-
// SWD flashing (`zephyr.runner: 'openocd'`): west's openocd runner resolves
|
|
62
|
-
// a bare `openocd` from PATH. The Zephyr SDK ships it under
|
|
63
|
-
// hosttools/openocd/bin (the SDK's own setup.cmd puts that dir on PATH for
|
|
64
|
-
// activated terminals) — do the same for spawned west processes so ST-Link
|
|
65
|
-
// flashing works without activation. $ZEPHYR_SDK_INSTALL_DIR (set by an
|
|
66
|
-
// activated env) wins over the discovered install dir.
|
|
67
|
-
const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || install.sdkInstallDir;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// the
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
'
|
|
120
|
-
'
|
|
121
|
-
'
|
|
122
|
-
'
|
|
123
|
-
|
|
124
|
-
'
|
|
125
|
-
'',
|
|
126
|
-
'
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// west spawn helper — build a spawnSync invocation for `west <args>`
|
|
3
|
+
//
|
|
4
|
+
// Mirrors framework-esp32's idfSpawn(): given the discovered WestInstall,
|
|
5
|
+
// produce {command, args, env} that the toolchain destructures into spawnSync.
|
|
6
|
+
//
|
|
7
|
+
// Two modes:
|
|
8
|
+
// - 'launcher': spawn the `west` launcher directly (mode 'launcher').
|
|
9
|
+
// - 'module' : spawn `<python> -m west ...` (mode 'module'). This is the
|
|
10
|
+
// robust cross-platform form — it activates the venv's
|
|
11
|
+
// interpreter for the one process without sourcing anything.
|
|
12
|
+
//
|
|
13
|
+
// ZEPHYR_BASE is injected into the spawn env when a SDK root was discovered
|
|
14
|
+
// (find_package(Zephyr) needs it). west's prj.conf/CMakeLists are found via
|
|
15
|
+
// the project dir regardless.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
import type { SpawnSyncOptions } from 'node:child_process';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { existsSync } from 'node:fs';
|
|
21
|
+
import { type WestInstall, discoverWest } from './west-discover.js';
|
|
22
|
+
|
|
23
|
+
export interface WestInvocation {
|
|
24
|
+
/** The command to spawnSync: a `west` launcher or a Python interpreter. */
|
|
25
|
+
command: string;
|
|
26
|
+
/** Args for the command (includes `-m west` when in module mode). */
|
|
27
|
+
args: string[];
|
|
28
|
+
/** Base spawn options with cwd/timeout/encoding/stdio, plus the env carrying
|
|
29
|
+
* ZEPHYR_BASE when discovered. */
|
|
30
|
+
options: SpawnSyncOptions;
|
|
31
|
+
/** The install the invocation was built from, for surfacing to the user. */
|
|
32
|
+
install: WestInstall;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The Scripts/ (Windows) or bin/ (POSIX) directory of the venv the discovered
|
|
37
|
+
* west runs under. `pythonExecutable` lives in that directory, so it is its
|
|
38
|
+
* dirname. Returns undefined for launcher-mode installs where no venv is known.
|
|
39
|
+
*/
|
|
40
|
+
function venvBinDir(install: WestInstall): string | undefined {
|
|
41
|
+
return install.pythonExecutable ? dirname(install.pythonExecutable) : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The env to pass to the west spawn: the process env, with ZEPHYR_BASE injected
|
|
46
|
+
* when a SDK root was discovered, AND the venv's bin/Scripts dir prepended to
|
|
47
|
+
* PATH when west was found via a venv Python.
|
|
48
|
+
*
|
|
49
|
+
* The PATH prepend matters: `west flash` shells out to bare runner tools
|
|
50
|
+
* (`esptool`, `openocd`, `nrfjprog`, …) via check_call, so they resolve from
|
|
51
|
+
* PATH. Without the prepend, the user's PATH may surface a *different* tool
|
|
52
|
+
* ahead of the venv's — e.g. an older esptool whose argument spelling is
|
|
53
|
+
* incompatible with the runner. Putting the venv's bin first makes west's
|
|
54
|
+
* delegated subprocesses resolve to the same versions west itself runs under.
|
|
55
|
+
*/
|
|
56
|
+
export function buildEnv(install: WestInstall): NodeJS.ProcessEnv {
|
|
57
|
+
const env = { ...process.env };
|
|
58
|
+
if (install.zephyrBase && !env.ZEPHYR_BASE) {
|
|
59
|
+
env.ZEPHYR_BASE = install.zephyrBase;
|
|
60
|
+
}
|
|
61
|
+
// SWD flashing (`zephyr.runner: 'openocd'`): west's openocd runner resolves
|
|
62
|
+
// a bare `openocd` from PATH. The Zephyr SDK ships it under
|
|
63
|
+
// hosttools/openocd/bin (the SDK's own setup.cmd puts that dir on PATH for
|
|
64
|
+
// activated terminals) — do the same for spawned west processes so ST-Link
|
|
65
|
+
// flashing works without activation. $ZEPHYR_SDK_INSTALL_DIR (set by an
|
|
66
|
+
// activated env) wins over the discovered install dir.
|
|
67
|
+
const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || install.sdkInstallDir;
|
|
68
|
+
// Pin the west/cmake child to the discovered SDK: without this, Zephyr's
|
|
69
|
+
// FindZephyr-sdk searches $HOME and can latch onto a stray, older SDK —
|
|
70
|
+
// a configure-time version failure that only surfaces in the build log.
|
|
71
|
+
if (!env.ZEPHYR_SDK_INSTALL_DIR && install.sdkInstallDir) {
|
|
72
|
+
env.ZEPHYR_SDK_INSTALL_DIR = install.sdkInstallDir;
|
|
73
|
+
}
|
|
74
|
+
const openocdBin = sdkRoot ? join(sdkRoot, 'hosttools', 'openocd', 'bin') : undefined;
|
|
75
|
+
const prepend = [
|
|
76
|
+
openocdBin !== undefined && existsSync(openocdBin) ? openocdBin : undefined,
|
|
77
|
+
venvBinDir(install),
|
|
78
|
+
].filter((d): d is string => d !== undefined);
|
|
79
|
+
if (prepend.length > 0) {
|
|
80
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
81
|
+
// On Windows the PATH environment variable may be cased as `Path` (the
|
|
82
|
+
// registry-native form, the only one populated when node is launched from
|
|
83
|
+
// PowerShell/cmd) or `PATH` (POSIX form, set by Git Bash). Writing only one
|
|
84
|
+
// casing can leave the other stale/empty, which under PowerShell would drop
|
|
85
|
+
// the user's real PATH (cmake, ninja, …) — breaking `west` configure. Read
|
|
86
|
+
// whichever casing is populated and write that same casing back, preserving
|
|
87
|
+
// the full existing value with the discovered dirs prepended.
|
|
88
|
+
const existing = env.Path ?? env.PATH ?? '';
|
|
89
|
+
const updated = prepend.join(sep) + sep + existing;
|
|
90
|
+
if (env.Path !== undefined || (env.PATH === undefined && process.platform === 'win32')) {
|
|
91
|
+
env.Path = updated;
|
|
92
|
+
} else {
|
|
93
|
+
env.PATH = updated;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return env;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build a spawn invocation that runs `west <westArgs>`.
|
|
101
|
+
*
|
|
102
|
+
* `baseOptions` carries the cwd/timeout/stdio settings the caller wants;
|
|
103
|
+
* westSpawn preserves them and injects the discovery env. The returned object
|
|
104
|
+
* destructures directly into spawnSync:
|
|
105
|
+
*
|
|
106
|
+
* const inv = westSpawn(['build', '-b', 'xiao_ble', projectRoot], { cwd, timeout });
|
|
107
|
+
* spawnSync(inv.command, inv.args, inv.options);
|
|
108
|
+
*
|
|
109
|
+
* Throws a clear, actionable error when no usable west install is discovered.
|
|
110
|
+
*/
|
|
111
|
+
export function westSpawn(
|
|
112
|
+
westArgs: string[],
|
|
113
|
+
baseOptions: SpawnSyncOptions,
|
|
114
|
+
): WestInvocation {
|
|
115
|
+
const install = discoverWest();
|
|
116
|
+
if (!install) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
[
|
|
119
|
+
'west (the Zephyr build tool) was not found.',
|
|
120
|
+
'',
|
|
121
|
+
'typecad-hal looked for it on PATH, in $ZEPHYR_BASE/.venv, in common',
|
|
122
|
+
'Zephyr workspace dirs (~/zephyrproject/.venv), and as a system',
|
|
123
|
+
"Python module (`python -m west`). To fix:",
|
|
124
|
+
'',
|
|
125
|
+
' • Run the typeCAD Zephyr installer (one command, any OS — needs Node ≥18):',
|
|
126
|
+
' node packages/zephyr-installer/install.mjs',
|
|
127
|
+
' then `micromamba activate zephyr` and retry.',
|
|
128
|
+
' • Or activate an existing Zephyr venv in this shell, or',
|
|
129
|
+
' • Or set ZEPHYR_BASE to your Zephyr SDK root (the venv at $ZEPHYR_BASE/../.venv is then used), or',
|
|
130
|
+
' • Or install west into a discoverable Python: pip install west',
|
|
131
|
+
'',
|
|
132
|
+
'See https://docs.zephyrproject.org/latest/develop/getting_started/index.html',
|
|
133
|
+
].join('\n'),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const env = buildEnv(install);
|
|
138
|
+
// Strip `shell` if present — we pass absolute paths / known commands, and
|
|
139
|
+
// an explicit shell changes arg-quoting semantics on Windows.
|
|
140
|
+
const { shell: _drop, ...optsWithoutShell } = baseOptions as any;
|
|
141
|
+
|
|
142
|
+
if (install.mode === 'micromamba' && install.micromambaExe) {
|
|
143
|
+
// `micromamba run -n <env> west …` sets up the env's full PATH
|
|
144
|
+
// (cmake/ninja/dtc) and runs the activation hook (ZEPHYR_BASE /
|
|
145
|
+
// ZEPHYR_SDK_INSTALL_DIR), so typecad-hal builds work WITHOUT the user
|
|
146
|
+
// activating the env. Inject MAMBA_ROOT_PREFIX so micromamba finds envs.
|
|
147
|
+
const mmEnv = { ...env };
|
|
148
|
+
if (install.mambaRootPrefix) mmEnv.MAMBA_ROOT_PREFIX = install.mambaRootPrefix;
|
|
149
|
+
return {
|
|
150
|
+
command: install.micromambaExe,
|
|
151
|
+
args: ['run', '-n', install.envName ?? 'zephyr', 'west', ...westArgs],
|
|
152
|
+
options: { ...optsWithoutShell, env: mmEnv },
|
|
153
|
+
install,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (install.mode === 'launcher' && install.westExecutable) {
|
|
158
|
+
return {
|
|
159
|
+
command: install.westExecutable,
|
|
160
|
+
args: westArgs,
|
|
161
|
+
options: { ...optsWithoutShell, env },
|
|
162
|
+
install,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// module mode: <python> -m west <args>
|
|
167
|
+
const py = install.pythonExecutable!;
|
|
168
|
+
return {
|
|
169
|
+
command: py,
|
|
170
|
+
args: ['-m', 'west', ...westArgs],
|
|
171
|
+
options: { ...optsWithoutShell, env },
|
|
172
|
+
install,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// @typecad/framework-zephyr — shared west workspace ground truth
|
|
3
|
+
//
|
|
4
|
+
// The as-built inventory both `typecad-hal licenses` and `typecad-hal sbom`
|
|
5
|
+
// report from: which west manifest modules the last build ACTUALLY compiled
|
|
6
|
+
// (linked-module filtering via the build's compile_commands.json), plus the
|
|
7
|
+
// build-directory discovery that locates it. A west manifest carries every
|
|
8
|
+
// vendor HAL/library; almost none are linked by a single project — both the
|
|
9
|
+
// license table and the SBOM scope to the linked set by default, so they stay
|
|
10
|
+
// honest about what is actually inside the firmware.
|
|
11
|
+
//
|
|
12
|
+
// Moved out of licenses.ts when the sbom command grew a second consumer; the
|
|
13
|
+
// behavior (case-insensitive, separator-agnostic path matching; degrade to
|
|
14
|
+
// "no filtering" on unparseable build data) is unchanged.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import type { ReadDir, ReadFile } from '@typecad/cuttlefish/api/shared';
|
|
19
|
+
|
|
20
|
+
/** Normalize a path for case-insensitive, separator-agnostic comparison. */
|
|
21
|
+
export function norm(p: string): string {
|
|
22
|
+
return p.split('\\').join('/').toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Filter the west manifest to the modules whose sources were compiled in the
|
|
27
|
+
* last build. A module is "linked" iff some compiled translation unit lives
|
|
28
|
+
* under its abspath. If the build data is unparseable, no filtering is applied
|
|
29
|
+
* (degrade to listing all modules rather than reporting nothing).
|
|
30
|
+
*/
|
|
31
|
+
export function filterLinkedModules(
|
|
32
|
+
ccText: string,
|
|
33
|
+
modules: { name: string; abspath: string }[],
|
|
34
|
+
): { name: string; abspath: string }[] {
|
|
35
|
+
let arr: unknown;
|
|
36
|
+
try {
|
|
37
|
+
arr = JSON.parse(ccText);
|
|
38
|
+
} catch {
|
|
39
|
+
return modules;
|
|
40
|
+
}
|
|
41
|
+
if (!Array.isArray(arr)) return modules;
|
|
42
|
+
// One normalized blob of every compiled source path; membership is then a
|
|
43
|
+
// substring check per module (O(modules) after an O(TUs) join).
|
|
44
|
+
const blob = arr
|
|
45
|
+
.map((e) => norm(typeof (e as { file?: unknown })?.file === 'string' ? (e as { file: string }).file : ''))
|
|
46
|
+
.join('\n');
|
|
47
|
+
return modules.filter((m) => blob.includes(norm(m.abspath) + '/'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Find the build's compile_commands.json path under `cwd`. Cuttlefish's Zephyr
|
|
52
|
+
* project root is the transpile output dir (e.g. `<cwd>/src/out`), so the
|
|
53
|
+
* build dir is `<root>/build` — not necessarily `<cwd>/build`. Check the
|
|
54
|
+
* common layouts, then fall back to a bounded search (skipping
|
|
55
|
+
* node_modules/.git/dist and dot-directories). Returns the PATH (the caller
|
|
56
|
+
* decides whether to read it); undefined when no build exists.
|
|
57
|
+
*/
|
|
58
|
+
export function findCompileCommandsPath(
|
|
59
|
+
readFile: ReadFile,
|
|
60
|
+
readdir: ReadDir,
|
|
61
|
+
cwd = process.cwd(),
|
|
62
|
+
): string | undefined {
|
|
63
|
+
const direct = [
|
|
64
|
+
path.join(cwd, 'build', 'compile_commands.json'),
|
|
65
|
+
path.join(cwd, 'out', 'build', 'compile_commands.json'),
|
|
66
|
+
path.join(cwd, 'src', 'out', 'build', 'compile_commands.json'),
|
|
67
|
+
];
|
|
68
|
+
for (const c of direct) {
|
|
69
|
+
if (readFile(c)) return c;
|
|
70
|
+
}
|
|
71
|
+
// Bounded recursive search for a build/compile_commands.json.
|
|
72
|
+
const isCc = (p: string) => {
|
|
73
|
+
const n = norm(p);
|
|
74
|
+
return n.endsWith('/build/compile_commands.json') || n.endsWith('\\build\\compile_commands.json');
|
|
75
|
+
};
|
|
76
|
+
let found: string | undefined;
|
|
77
|
+
const seen = new Set<string>();
|
|
78
|
+
const walk = (dir: string, depth: number): void => {
|
|
79
|
+
if (found || depth > 4 || seen.has(dir)) return;
|
|
80
|
+
seen.add(dir);
|
|
81
|
+
let entries: string[];
|
|
82
|
+
try {
|
|
83
|
+
entries = readdir(dir);
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const e of entries) {
|
|
88
|
+
const full = path.join(dir, e);
|
|
89
|
+
if (e === 'compile_commands.json' && isCc(full)) {
|
|
90
|
+
found = full;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const e of entries) {
|
|
95
|
+
if (found) return;
|
|
96
|
+
if (e === 'node_modules' || e === '.git' || e === 'dist' || e.startsWith('.')) continue;
|
|
97
|
+
walk(path.join(dir, e), depth + 1);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
walk(cwd, 0);
|
|
101
|
+
return found;
|
|
102
|
+
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { RuntimePolyfillIR } from '@typecad/cuttlefish/api/shared';
|
|
2
|
-
export interface TimerPolyfillIR extends RuntimePolyfillIR {
|
|
3
|
-
/** The clamped pool size the C++ was generated with. */
|
|
4
|
-
maxTimers: number;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Build the timer_methods polyfill IR. `requestedTimers` is sized from
|
|
8
|
-
* ctx.analysis.timerCallCount; it is clamped to [1, 16].
|
|
9
|
-
*/
|
|
10
|
-
export declare function buildTimerPolyfill(requestedTimers: number): TimerPolyfillIR;
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Timer methods polyfill — Zephyr k_timer + k_work (heap-free)
|
|
3
|
-
//
|
|
4
|
-
// setInterval / setTimeout / clearInterval / clearTimeout backed by a fixed
|
|
5
|
-
// pool of k_timer + k_work pairs. The timer's expiry function (ISR context)
|
|
6
|
-
// submits a k_work item to the system workqueue; the work handler runs the
|
|
7
|
-
// user callback in thread context. This split keeps the ISR cheap and lets the
|
|
8
|
-
// callback run on a real thread (so it can do real work — but must not block,
|
|
9
|
-
// since the system workqueue is shared).
|
|
10
|
-
//
|
|
11
|
-
// EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
|
|
12
|
-
// Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
const MIN_TIMERS = 1;
|
|
15
|
-
const MAX_TIMERS_CAP = 16;
|
|
16
|
-
/**
|
|
17
|
-
* Build the timer_methods polyfill IR. `requestedTimers` is sized from
|
|
18
|
-
* ctx.analysis.timerCallCount; it is clamped to [1, 16].
|
|
19
|
-
*/
|
|
20
|
-
export function buildTimerPolyfill(requestedTimers) {
|
|
21
|
-
const maxTimers = Math.min(MAX_TIMERS_CAP, Math.max(MIN_TIMERS, requestedTimers));
|
|
22
|
-
const helperStructs = [`
|
|
23
|
-
// cuttlefish timer runtime — k_timer + k_work pool (heap-free).
|
|
24
|
-
struct __tc_TimerSlot {
|
|
25
|
-
struct k_timer timer;
|
|
26
|
-
struct k_work work;
|
|
27
|
-
void (*callback)(void);
|
|
28
|
-
bool active;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
static struct __tc_TimerSlot __tc_timer_slots[${maxTimers}];
|
|
32
|
-
|
|
33
|
-
static void __tc_timer_work_handler(struct k_work* w) {
|
|
34
|
-
// Runs on the system workqueue thread (NOT ISR). Find the owning slot by
|
|
35
|
-
// address and invoke the callback. Must not block.
|
|
36
|
-
for (int32_t i = 0; i < ${maxTimers}; i++) {
|
|
37
|
-
if (&__tc_timer_slots[i].work == w) {
|
|
38
|
-
if (__tc_timer_slots[i].callback != nullptr) { __tc_timer_slots[i].callback(); }
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
static void __tc_timer_expiry_fn(struct k_timer* t) {
|
|
45
|
-
// ISR context: submit the work item, do NOT run the callback here.
|
|
46
|
-
for (int32_t i = 0; i < ${maxTimers}; i++) {
|
|
47
|
-
if (&__tc_timer_slots[i].timer == t) {
|
|
48
|
-
(void)k_work_submit(&__tc_timer_slots[i].work);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
static int32_t __tc_timer_add(void (*cb)(void), int32_t ms, bool repeat) {
|
|
55
|
-
for (int32_t i = 0; i < ${maxTimers}; i++) {
|
|
56
|
-
if (!__tc_timer_slots[i].active) {
|
|
57
|
-
__tc_timer_slots[i].callback = cb;
|
|
58
|
-
__tc_timer_slots[i].active = true;
|
|
59
|
-
k_timer_init(&__tc_timer_slots[i].timer, __tc_timer_expiry_fn, nullptr);
|
|
60
|
-
k_work_init(&__tc_timer_slots[i].work, __tc_timer_work_handler);
|
|
61
|
-
k_timer_start(&__tc_timer_slots[i].timer, K_MSEC(ms), repeat ? K_MSEC(ms) : K_FOREVER);
|
|
62
|
-
return i + 1; // 1-based id
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return 0; // pool full
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
static void __tc_timer_clear(int32_t id) {
|
|
69
|
-
if (id > 0 && id <= ${maxTimers}) {
|
|
70
|
-
int32_t i = id - 1;
|
|
71
|
-
k_timer_stop(&__tc_timer_slots[i].timer);
|
|
72
|
-
__tc_timer_slots[i].active = false;
|
|
73
|
-
__tc_timer_slots[i].callback = nullptr;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
`];
|
|
77
|
-
const helperFunctions = [`
|
|
78
|
-
int32_t __tc_setInterval(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, true); }
|
|
79
|
-
int32_t __tc_setTimeout(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, false); }
|
|
80
|
-
void __tc_clearInterval(int32_t id) { __tc_timer_clear(id); }
|
|
81
|
-
void __tc_clearTimeout(int32_t id) { __tc_timer_clear(id); }
|
|
82
|
-
`];
|
|
83
|
-
return {
|
|
84
|
-
kind: 'polyfill',
|
|
85
|
-
id: 'timer_methods',
|
|
86
|
-
domain: 'embedded',
|
|
87
|
-
requiredIncludes: [],
|
|
88
|
-
forwardDeclarations: [],
|
|
89
|
-
helperStructs,
|
|
90
|
-
helperFunctions,
|
|
91
|
-
shimMacros: [],
|
|
92
|
-
dependencies: [],
|
|
93
|
-
maxTimers,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export interface BoardOverride {
|
|
2
|
-
/** LED pin name for boards whose user LED is not a gpio-leds node (the
|
|
3
|
-
* ESP32-S3 DevKitC's addressable RGB on GPIO48 — driven as plain GPIO,
|
|
4
|
-
* same as the deleted board package did). */
|
|
5
|
-
readonly led?: string;
|
|
6
|
-
}
|
|
7
|
-
export declare const BOARD_OVERRIDES: Readonly<Record<string, BoardOverride>>;
|