feat: introduce vendors.json for mapping vendors to models and refactor targets.ts to utilize new data structure

This commit is contained in:
Ben Allfree
2025-12-10 17:39:10 -08:00
parent c38e6b735c
commit cdc4959dca
6 changed files with 498 additions and 263 deletions

View File

@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Minor
- Added vendors.json mapping vendors to models and platformio targets
- Refactored targets.ts to use vendors.json and architecture-hierarchy.json instead of hardware-list.json
- Updated architecture-hierarchy.json generation to use actual PlatformIO environment names (removed normalization)
- Removed normalization from lib/utils.ts since all inputs now use standardized PlatformIO names
### Patch ### Patch
- Fix Convex server functions being imported in browser by moving ArtifactType enum to client-safe location - Fix Convex server functions being imported in browser by moving ArtifactType enum to client-safe location

View File

@@ -1,210 +1,210 @@
{ {
"betafpv2400txmicro": "esp32", "betafpv_2400_tx_micro": "esp32",
"betafpv900txnano": "esp32", "betafpv_900_tx_nano": "esp32",
"chatter2": "esp32", "chatter2": "esp32",
"9m2ibraprsloratracker": "esp32", "9m2ibr_aprs_lora_tracker": "esp32",
"meshtasticdrdev": "esp32", "meshtastic-dr-dev": "esp32",
"hydra": "esp32", "hydra": "esp32",
"meshtasticdiyv1": "esp32", "meshtastic-diy-v1": "esp32",
"meshtasticdiyv11": "esp32", "meshtastic-diy-v1_1": "esp32",
"hackerboxesesp32io": "esp32", "hackerboxes-esp32-io": "esp32",
"heltecv1": "esp32", "heltec-v1": "esp32",
"heltecv20": "esp32", "heltec-v2_0": "esp32",
"heltecv21": "esp32", "heltec-v2_1": "esp32",
"heltecwirelessbridge": "esp32", "heltec-wireless-bridge": "esp32",
"heltecwslv21": "esp32", "heltec-wsl-v2_1": "esp32",
"m5stackcore": "esp32", "m5stack-core": "esp32",
"m5stackcoreink": "esp32", "m5stack-coreink": "esp32",
"nanog1": "esp32", "nano-g1": "esp32",
"nanog1explorer": "esp32", "nano-g1-explorer": "esp32",
"radiomaster900bandit": "esp32", "radiomaster_900_bandit": "esp32",
"radiomaster900banditmicro": "esp32", "radiomaster_900_bandit_micro": "esp32",
"radiomaster900banditnano": "esp32", "radiomaster_900_bandit_nano": "esp32",
"rak11200": "esp32", "rak11200": "esp32",
"stationg1": "esp32", "station-g1": "esp32",
"tbeam": "esp32", "tbeam": "esp32",
"tbeamdisplayshield": "tbeam", "tbeam-displayshield": "tbeam",
"tbeam07": "esp32", "tbeam0_7": "esp32",
"tlorav1": "esp32", "tlora-v1": "esp32",
"tlorav13": "esp32", "tlora_v1_3": "esp32",
"tlorav2": "esp32", "tlora-v2": "esp32",
"tlorav2116": "esp32", "tlora-v2-1-1_6": "esp32",
"sugarcube": "tlorav2116", "sugarcube": "tlora-v2-1-1_6",
"tlorav2116tcxo": "esp32", "tlora-v2-1-1_6-tcxo": "esp32",
"tlorav2118": "esp32", "tlora-v2-1-1_8": "esp32",
"tlorav330tcxo": "esp32", "tlora-v3-3-0-tcxo": "esp32",
"trackerd": "esp32", "trackerd": "esp32",
"wiphone": "esp32", "wiphone": "esp32",
"aic3": "esp32c3", "ai-c3": "esp32c3",
"esp32c3supermini": "esp32c3", "esp32c3_super_mini": "esp32c3",
"esp32c3base": "esp32", "esp32c3_base": "esp32",
"hackerboxesesp32c3oled": "esp32c3", "hackerboxes-esp32c3-oled": "esp32c3",
"heltecht62esp32c3sx1262": "esp32c3", "heltec-ht62-esp32c3-sx1262": "esp32c3",
"heltechru3601": "esp32c3", "heltec-hru-3601": "esp32c3",
"m5stackstampc3": "esp32c3", "m5stack-stamp-c3": "esp32c3",
"esp32c6base": "esp32", "esp32c6_base": "esp32",
"m5stackunitc6l": "esp32c6", "m5stack-unitc6l": "esp32c6",
"tlorac6": "esp32c6", "tlora-c6": "esp32c6",
"esp32s2base": "esp32", "esp32s2_base": "esp32",
"nuggets2lora": "esp32s2", "nugget-s2-lora": "esp32s2",
"CDEBYTEEoRaS3": "esp32s3", "CDEBYTE_EoRa-S3": "esp32s3",
"EBYTEESP32S3": "esp32s3", "EBYTE_ESP32-S3": "esp32s3",
"thinknodem2": "esp32s3", "thinknode_m2": "esp32s3",
"thinknodem5": "esp32s3", "thinknode_m5": "esp32s3",
"bpipicowesp32s3": "esp32s3", "bpi_picow_esp32_s3": "esp32s3",
"crowpanelesp32s35epaper": "esp32s3", "crowpanel-esp32s3-5-epaper": "esp32s3",
"crowpanelesp32s34epaper": "esp32s3", "crowpanel-esp32s3-4-epaper": "esp32s3",
"crowpanelesp32s32epaper": "esp32s3", "crowpanel-esp32s3-2-epaper": "esp32s3",
"myesp32s3diyeink": "esp32s3", "my-esp32s3-diy-eink": "esp32s3",
"myesp32s3diyoled": "esp32s3", "my-esp32s3-diy-oled": "esp32s3",
"tenergys3e22": "esp32s3", "t-energy-s3_e22": "esp32s3",
"dreamcatcher2206": "esp32s3", "dreamcatcher-2206": "esp32s3",
"crowpanelbase": "crowpanel", "crowpanel_base": "crowpanel",
"elecrowadv2428tft": "crowpanelsmallesp32s3base", "elecrow-adv-24-28-tft": "crowpanel_small_esp32s3_base",
"elecrowadv35tft": "crowpanelsmallesp32s3base", "elecrow-adv-35-tft": "crowpanel_small_esp32s3_base",
"elecrowadv1435070tft": "crowpanellargeesp32s3base", "elecrow-adv1-43-50-70-tft": "crowpanel_large_esp32s3_base",
"ESP32S3Pico": "esp32s3", "ESP32-S3-Pico": "esp32s3",
"esp32s3base": "esp32", "esp32s3_base": "esp32",
"hackadaycommunicator": "esp32s3", "hackaday-communicator": "esp32s3",
"helteccapsulesensorv3": "esp32s3", "heltec_capsule_sensor_v3": "esp32s3",
"heltecsensorhub": "esp32s3", "heltec_sensor_hub": "esp32s3",
"heltecv3": "esp32s3", "heltec-v3": "esp32s3",
"heltecv4base": "esp32s3", "heltec_v4_base": "esp32s3",
"heltecv4": "heltecv4base", "heltec-v4": "heltec_v4_base",
"heltecv4tft": "heltecv4base", "heltec-v4-tft": "heltec_v4_base",
"heltecvisionmastere213": "esp32s3", "heltec-vision-master-e213": "esp32s3",
"heltecvisionmastere213inkhud": "esp32s3", "heltec-vision-master-e213-inkhud": "esp32s3",
"heltecvisionmastere290": "esp32s3", "heltec-vision-master-e290": "esp32s3",
"heltecvisionmastere290inkhud": "esp32s3", "heltec-vision-master-e290-inkhud": "esp32s3",
"heltecvisionmastert190": "esp32s3", "heltec-vision-master-t190": "esp32s3",
"heltecwirelesspaper": "esp32s3", "heltec-wireless-paper": "esp32s3",
"heltecwirelesspaperinkhud": "esp32s3", "heltec-wireless-paper-inkhud": "esp32s3",
"heltecwirelesspaperv10": "esp32s3", "heltec-wireless-paper-v1_0": "esp32s3",
"heltecwirelesstracker": "esp32s3", "heltec-wireless-tracker": "esp32s3",
"heltecwirelesstrackerV10": "esp32s3", "heltec-wireless-tracker-V1-0": "esp32s3",
"heltecwirelesstrackerv2": "esp32s3", "heltec-wireless-tracker-v2": "esp32s3",
"heltecwslv3": "esp32s3", "heltec-wsl-v3": "esp32s3",
"icarus": "esp32s3", "icarus": "esp32s3",
"link32s3v1": "esp32s3", "link32-s3-v1": "esp32s3",
"m5stackcores3": "esp32s3", "m5stack-cores3": "esp32s3",
"meshtabbase": "esp32s3", "mesh_tab_base": "esp32s3",
"meshtab32TNresistive": "meshtabbase", "mesh-tab-3-2-TN-resistive": "mesh_tab_base",
"meshtab32IPSresistive": "meshtabbase", "mesh-tab-3-2-IPS-resistive": "mesh_tab_base",
"meshtab35IPSresistive": "meshtabbase", "mesh-tab-3-5-IPS-resistive": "mesh_tab_base",
"meshtab35TNresistive": "meshtabbase", "mesh-tab-3-5-TN-resistive": "mesh_tab_base",
"meshtab32IPScapacitive": "meshtabbase", "mesh-tab-3-2-IPS-capacitive": "mesh_tab_base",
"meshtab35IPScapacitive": "meshtabbase", "mesh-tab-3-5-IPS-capacitive": "mesh_tab_base",
"meshtab40IPScapacitive": "meshtabbase", "mesh-tab-4-0-IPS-capacitive": "mesh_tab_base",
"nibbleesp32": "esp32s3", "nibble-esp32": "esp32s3",
"nuggets3lora": "esp32s3", "nugget-s3-lora": "esp32s3",
"picomputers3": "esp32s3", "picomputer-s3": "esp32s3",
"picomputers3tft": "picomputers3", "picomputer-s3-tft": "picomputer-s3",
"rak3312": "esp32s3", "rak3312": "esp32s3",
"rakwismeshtapv2tft": "rakwismeshtaps3", "rak_wismesh_tap_v2-tft": "rak_wismeshtap_s3",
"seeedsensecapindicator": "esp32s3", "seeed-sensecap-indicator": "esp32s3",
"seeedsensecapindicatortft": "seeedsensecapindicator", "seeed-sensecap-indicator-tft": "seeed-sensecap-indicator",
"seeedxiaos3": "esp32s3", "seeed-xiao-s3": "esp32s3",
"stationg2": "esp32s3", "station-g2": "esp32s3",
"tdeck": "esp32s3", "t-deck": "esp32s3",
"tdecktft": "tdeck", "t-deck-tft": "t-deck",
"tdeckpro": "esp32s3", "t-deck-pro": "esp32s3",
"tethelite": "esp32s3", "t-eth-elite": "esp32s3",
"twatchs3": "esp32s3", "t-watch-s3": "esp32s3",
"tbeams3core": "esp32s3", "tbeam-s3-core": "esp32s3",
"tlorapager": "esp32s3", "tlora-pager": "esp32s3",
"tlorapagertft": "tlorapager", "tlora-pager-tft": "tlora-pager",
"tlorat3s3epaper": "esp32s3", "tlora-t3s3-epaper": "esp32s3",
"tlorat3s3epaperinkhud": "esp32s3", "tlora-t3s3-epaper-inkhud": "esp32s3",
"tlorat3s3v1": "esp32s3", "tlora-t3s3-v1": "esp32s3",
"tracksenger": "esp32s3", "tracksenger": "esp32s3",
"tracksengerlcd": "esp32s3", "tracksenger-lcd": "esp32s3",
"tracksengeroled": "esp32s3", "tracksenger-oled": "esp32s3",
"unphone": "esp32s3", "unphone": "esp32s3",
"unphonetft": "unphone", "unphone-tft": "unphone",
"coverage": "native", "coverage": "native",
"buildroot": "portduino", "buildroot": "portduino",
"pca10059diyeink": "nrf52840", "pca10059_diy_eink": "nrf52840",
"thinknodem1": "nrf52840", "thinknode_m1": "nrf52840",
"thinknodem1inkhud": "nrf52840", "thinknode_m1-inkhud": "nrf52840",
"thinknodem3": "nrf52840", "thinknode_m3": "nrf52840",
"thinknodem6": "nrf52840", "thinknode_m6": "nrf52840",
"ME25LS014Y10TD": "nrf52840", "ME25LS01-4Y10TD": "nrf52840",
"ME25LS014Y10TDeink": "nrf52840", "ME25LS01-4Y10TD_e-ink": "nrf52840",
"ms24sf1": "nrf52840", "ms24sf1": "nrf52840",
"makerpythonnrf52840sx1280eink": "nrf52840", "makerpython_nrf52840_sx1280_eink": "nrf52840",
"makerpythonnrf52840sx1280oled": "nrf52840", "makerpython_nrf52840_sx1280_oled": "nrf52840",
"TWCmeshv4": "nrf52840", "TWC_mesh_v4": "nrf52840",
"canaryone": "nrf52840", "canaryone": "nrf52840",
"WashTastic": "nrf52840", "WashTastic": "nrf52840",
"nrf52promicrodiytcxo": "nrf52840", "nrf52_promicro_diy_tcxo": "nrf52840",
"nrf52promicrodiyinkhud": "nrf52840", "nrf52_promicro_diy-inkhud": "nrf52840",
"seeedxiaonrf52840wiosx1262": "nrf52840", "seeed-xiao-nrf52840-wio-sx1262": "nrf52840",
"seeedxiaonrf52840e22900m30s": "seeedxiaonrf52840kit", "seeed_xiao_nrf52840_e22_900m30s": "seeed_xiao_nrf52840_kit",
"seeedxiaonrf52840e22900m33s": "seeedxiaonrf52840kit", "seeed_xiao_nrf52840_e22_900m33s": "seeed_xiao_nrf52840_kit",
"xiaoble": "seeedxiaonrf52840kit", "xiao_ble": "seeed_xiao_nrf52840_kit",
"featherdiy": "nrf52840", "feather_diy": "nrf52840",
"gat562meshtrialtracker": "nrf52840", "gat562_mesh_trial_tracker": "nrf52840",
"heltecmeshnodet114": "nrf52840", "heltec-mesh-node-t114": "nrf52840",
"heltecmeshnodet114inkhud": "nrf52840", "heltec-mesh-node-t114-inkhud": "nrf52840",
"heltecmeshpocket5000": "nrf52840", "heltec-mesh-pocket-5000": "nrf52840",
"heltecmeshpocket5000inkhud": "nrf52840", "heltec-mesh-pocket-5000-inkhud": "nrf52840",
"heltecmeshpocket10000": "nrf52840", "heltec-mesh-pocket-10000": "nrf52840",
"heltecmeshpocket10000inkhud": "nrf52840", "heltec-mesh-pocket-10000-inkhud": "nrf52840",
"heltecmeshsolarbase": "nrf52840", "heltec_mesh_solar_base": "nrf52840",
"heltecmeshsolar": "heltecmeshsolarbase", "heltec-mesh-solar": "heltec_mesh_solar_base",
"heltecmeshsolareink": "heltecmeshsolarbase", "heltec-mesh-solar-eink": "heltec_mesh_solar_base",
"heltecmeshsolarinkhud": "heltecmeshsolarbase", "heltec-mesh-solar-inkhud": "heltec_mesh_solar_base",
"heltecmeshsolaroled": "heltecmeshsolarbase", "heltec-mesh-solar-oled": "heltec_mesh_solar_base",
"heltecmeshsolartft": "heltecmeshsolarbase", "heltec-mesh-solar-tft": "heltec_mesh_solar_base",
"meshlink": "nrf52840", "meshlink": "nrf52840",
"meshlinkeink": "nrf52840", "meshlink_eink": "nrf52840",
"meshtiny": "nrf52840", "meshtiny": "nrf52840",
"monteopshw1": "nrf52840", "monteops_hw1": "nrf52840",
"muzibase": "nrf52840", "muzi-base": "nrf52840",
"nanog2ultra": "nrf52840", "nano-g2-ultra": "nrf52840",
"nrf52832base": "nrf52", "nrf52832_base": "nrf52",
"nrf52840base": "nrf52", "nrf52840_base": "nrf52",
"r1neo": "nrf52840", "r1-neo": "nrf52840",
"rak2560": "nrf52840", "rak2560": "nrf52840",
"rak34011watt": "nrf52840", "rak3401-1watt": "nrf52840",
"rak4631": "nrf52840", "rak4631": "nrf52840",
"rak4631dbg": "rak4631", "rak4631_dbg": "rak4631",
"rak4631eink": "nrf52840", "rak4631_eink": "nrf52840",
"rak4631einkonrxtx": "nrf52840", "rak4631_eink_onrxtx": "nrf52840",
"rak4631ethgw": "nrf52840", "rak4631_eth_gw": "nrf52840",
"rak4631ethgwdbg": "rak4631", "rak4631_eth_gw_dbg": "rak4631",
"rak4631nomadstarmeteorpro": "nrf52840", "rak4631_nomadstar_meteor_pro": "nrf52840",
"rak4631nomadstarmeteorprodbg": "rak4631nomadstarmeteorpro", "rak4631_nomadstar_meteor_pro_dbg": "rak4631_nomadstar_meteor_pro",
"rakwismeshtag": "nrf52840", "rak_wismeshtag": "nrf52840",
"rakwismeshtap": "nrf52840", "rak_wismeshtap": "nrf52840",
"seeedsolarnode": "nrf52840", "seeed_solar_node": "nrf52840",
"seeedwiotrackerL1": "nrf52840", "seeed_wio_tracker_L1": "nrf52840",
"seeedwiotrackerL1eink": "nrf52840", "seeed_wio_tracker_L1_eink": "nrf52840",
"seeedwiotrackerL1einkinkhud": "nrf52840", "seeed_wio_tracker_L1_eink-inkhud": "nrf52840",
"seeedxiaonrf52840kit": "nrf52840", "seeed_xiao_nrf52840_kit": "nrf52840",
"seeedxiaonrf52840kiti2c": "seeedxiaonrf52840kit", "seeed_xiao_nrf52840_kit_i2c": "seeed_xiao_nrf52840_kit",
"techo": "nrf52840", "t-echo": "nrf52840",
"techoinkhud": "nrf52840", "t-echo-inkhud": "nrf52840",
"techolite": "nrf52840", "t-echo-lite": "nrf52840",
"trackert1000e": "nrf52840", "tracker-t1000-e": "nrf52840",
"wiosdkwm1110": "nrf52840", "wio-sdk-wm1110": "nrf52840",
"wiot1000s": "nrf52840", "wio-t1000-s": "nrf52840",
"wiotrackerwm1110": "nrf52840", "wio-tracker-wm1110": "nrf52840",
"challenger2040lora": "rp2040", "challenger_2040_lora": "rp2040",
"catsniffer": "rp2040", "catsniffer": "rp2040",
"featherrp2040rfm95": "rp2040", "feather_rp2040_rfm95": "rp2040",
"nibblerp2040": "rp2040", "nibble-rp2040": "rp2040",
"rak11310": "rp2040", "rak11310": "rp2040",
"rp2040lora": "rp2040", "rp2040-lora": "rp2040",
"pico": "rp2040", "pico": "rp2040",
"picoslowclock": "rp2040", "pico_slowclock": "rp2040",
"picow": "rp2040", "picow": "rp2040",
"senselorarp2040": "rp2040", "senselora_rp2040": "rp2040",
"pico2": "rp2350", "pico2": "rp2350",
"pico2w": "rp2350", "pico2w": "rp2350",
"CDEBYTEE77MBL": "stm32", "CDEBYTE_E77-MBL": "stm32",
"rak3172": "stm32", "rak3172": "stm32",
"wioe5": "stm32", "wio-e5": "stm32",
"esp32c3": "esp32", "esp32c3": "esp32",
"esp32c6": "esp32", "esp32c6": "esp32",
"esp32s2": "esp32", "esp32s2": "esp32",
@@ -217,4 +217,4 @@
"rp2350": null, "rp2350": null,
"stm32": null, "stm32": null,
"portduino": null "portduino": null
} }

View File

@@ -1,4 +1,5 @@
import hardwareList from "@/vendor/web-flasher/public/data/hardware-list.json" import architectureHierarchy from "@/constants/architecture-hierarchy.json"
import vendorsData from "@/constants/vendors.json"
export interface TargetMetadata { export interface TargetMetadata {
name: string name: string
@@ -6,17 +7,43 @@ export interface TargetMetadata {
architecture?: string architecture?: string
} }
/**
* Trace a target back to its base architecture
*/
function getBaseArchitecture(target: string): string | null {
const parentMap = architectureHierarchy as Record<string, string | null>
const visited = new Set<string>()
let current: string | null = target
while (current && !visited.has(current)) {
visited.add(current)
if (!current) break
const parent: string | null | undefined = parentMap[current]
if (parent === null) {
return current
}
if (parent === undefined) {
return current
}
current = parent
}
return current || target
}
export const TARGETS: Record<string, TargetMetadata> = {} export const TARGETS: Record<string, TargetMetadata> = {}
// Sort by display name // Build TARGETS from vendors.json and architecture-hierarchy.json
const sortedHardware = [...hardwareList].sort((a, b) => (a.displayName || "").localeCompare(b.displayName || "")) for (const [vendor, models] of Object.entries(vendorsData)) {
for (const [modelName, target] of Object.entries(models)) {
sortedHardware.forEach(hw => { const architecture = getBaseArchitecture(target)
if (hw.platformioTarget) { TARGETS[target] = {
TARGETS[hw.platformioTarget] = { name: modelName,
name: hw.displayName || hw.platformioTarget, category: vendor,
category: hw.tags?.[0] || "Other", architecture: architecture || undefined,
architecture: hw.architecture,
} }
} }
}) }

244
constants/vendors.json Normal file
View File

@@ -0,0 +1,244 @@
{
"B&Q": {
"Nano G1": "nano-g1",
"Nano G1 Explorer": "nano-g1-explorer",
"Nano G2 Ultra": "nano-g2-ultra",
"Station G1": "station-g1",
"Station G2": "station-g2"
},
"BetaFPV": {
"2400TX Micro": "betafpv_2400_tx_micro",
"900TX Nano": "betafpv_900_tx_nano"
},
"Canary": {
"One": "canaryone"
},
"CDEByte": {
"EoRa S3": "CDEBYTE_EoRa-S3",
"E77MBL": "CDEBYTE_E77-MBL"
},
"DIY": {
"DR-DEV": "meshtastic-dr-dev",
"Hydra": "hydra",
"V1": "meshtastic-diy-v1",
"V1.1": "meshtastic-diy-v1_1",
"NRF52 Pro-micro DIY": "nrf52_promicro_diy_tcxo",
"NRF52 Pro-micro DIY InkHUD": "nrf52_promicro_diy-inkhud",
"PCA10059 DIY E-Ink": "pca10059_diy_eink"
},
"EByte": {
"ESP32-S3": "EBYTE_ESP32-S3"
},
"Elecrow": {
"Crowpanel Adv 2.4/2.8 TFT": "elecrow-adv-24-28-tft",
"Crowpanel Adv 3.5 TFT": "elecrow-adv-35-tft",
"Crowpanel Adv 4.3/5.0/7.0 TFT": "elecrow-adv1-43-50-70-tft",
"ThinkNode M1": "thinknode_m1",
"ThinkNode M2": "thinknode_m2",
"ThinkNode M3": "thinknode_m3",
"ThinkNode M5": "thinknode_m5",
"ThinkNode M6": "thinknode_m6"
},
"Heltec": {
"V1": "heltec-v1",
"V2.0": "heltec-v2_0",
"V2.1": "heltec-v2_1",
"V3": "heltec-v3",
"V4": "heltec-v4",
"V4 TFT": "heltec-v4-tft",
"HT62": "heltec-ht62-esp32c3-sx1262",
"RU3601": "heltec-hru-3601",
"Wireless Bridge": "heltec-wireless-bridge",
"Wireless Stick Lite V2.1": "heltec-wsl-v2_1",
"Wireless Stick Lite V3": "heltec-wsl-v3",
"Wireless Paper": "heltec-wireless-paper",
"Wireless Paper InkHUD": "heltec-wireless-paper-inkhud",
"Wireless Paper V1.0": "heltec-wireless-paper-v1_0",
"Wireless Tracker": "heltec-wireless-tracker",
"Wireless Tracker V1.0": "heltec-wireless-tracker-V1-0",
"Wireless Tracker V2": "heltec-wireless-tracker-v2",
"Vision Master E213": "heltec-vision-master-e213",
"Vision Master E213 InkHUD": "heltec-vision-master-e213-inkhud",
"Vision Master E290": "heltec-vision-master-e290",
"Vision Master E290 InkHUD": "heltec-vision-master-e290-inkhud",
"Vision Master T190": "heltec-vision-master-t190",
"Capsule Sensor V3": "heltec_capsule_sensor_v3",
"Sensor Hub": "heltec_sensor_hub",
"Mesh Node T114": "heltec-mesh-node-t114",
"Mesh Node T114 InkHUD": "heltec-mesh-node-t114-inkhud",
"MeshPocket 5000": "heltec-mesh-pocket-5000",
"MeshPocket 5000 InkHUD": "heltec-mesh-pocket-5000-inkhud",
"MeshPocket 10000": "heltec-mesh-pocket-10000",
"MeshPocket 10000 InkHUD": "heltec-mesh-pocket-10000-inkhud",
"MeshSolar": "heltec-mesh-solar",
"MeshSolar E-Ink": "heltec-mesh-solar-eink",
"MeshSolar InkHUD": "heltec-mesh-solar-inkhud",
"MeshSolar OLED": "heltec-mesh-solar-oled",
"MeshSolar TFT": "heltec-mesh-solar-tft"
},
"HackerBoxes": {
"ESP32 IO": "hackerboxes-esp32-io",
"ESP32-C3 OLED": "hackerboxes-esp32c3-oled"
},
"LilyGo": {
"T-Beam": "tbeam",
"T-Beam Display Shield": "tbeam-displayshield",
"T-Beam V0.7": "tbeam0_7",
"T-Beam S3 Core": "tbeam-s3-core",
"T-Deck": "t-deck",
"T-Deck TFT": "t-deck-tft",
"T-Deck Pro": "t-deck-pro",
"T-Echo": "t-echo",
"T-Echo InkHUD": "t-echo-inkhud",
"T-Echo Lite": "t-echo-lite",
"T-LoRa V1": "tlora-v1",
"T-LoRa V1.3": "tlora_v1_3",
"T-LoRa V2": "tlora-v2",
"T-LoRa V2.1-1.6": "tlora-v2-1-1_6",
"T-LoRa V2.1-1.6 TCXO": "tlora-v2-1-1_6-tcxo",
"T-LoRa V2.1-1.8": "tlora-v2-1-1_8",
"T-LoRa V3.3.0 TCXO": "tlora-v3-3-0-tcxo",
"T-LoRa C6": "tlora-c6",
"T-LoRa Pager": "tlora-pager",
"T-LoRa Pager TFT": "tlora-pager-tft",
"T-LoRa T3-S3": "tlora-t3s3-v1",
"T-LoRa T3-S3 E-Paper": "tlora-t3s3-epaper",
"T-LoRa T3-S3 E-Paper InkHUD": "tlora-t3s3-epaper-inkhud",
"T-Watch S3": "t-watch-s3",
"Sugar Cube": "sugarcube"
},
"M5Stack": {
"Core": "m5stack-core",
"Core Ink": "m5stack-coreink",
"Core S3": "m5stack-cores3",
"Stamp C3": "m5stack-stamp-c3",
"Unit C6L": "m5stack-unitc6l"
},
"MakerPython": {
"NRF52840 SX1280 E-Ink": "makerpython_nrf52840_sx1280_eink",
"NRF52840 SX1280 OLED": "makerpython_nrf52840_sx1280_oled"
},
"muzi": {
"R1 Neo": "r1-neo"
},
"NomadStar": {
"Meteor Pro": "rak4631_nomadstar_meteor_pro",
"Meteor Pro Debug": "rak4631_nomadstar_meteor_pro_dbg"
},
"RAK": {
"WisBlock 11200": "rak11200",
"WisBlock 11310": "rak11310",
"WisBlock 4631": "rak4631",
"WisBlock 4631 Debug": "rak4631_dbg",
"WisBlock 4631 E-Ink": "rak4631_eink",
"WisBlock 4631 E-Ink on RX/TX": "rak4631_eink_onrxtx",
"WisBlock 4631 ETH Gateway": "rak4631_eth_gw",
"WisBlock 4631 ETH Gateway Debug": "rak4631_eth_gw_dbg",
"WisBlock 3312": "rak3312",
"WisBlock 3172": "rak3172",
"WisBlock 2560": "rak2560",
"WisBlock 3401 1Watt": "rak3401-1watt",
"WisMesh Tag": "rak_wismeshtag",
"WisMesh Tap": "rak_wismeshtap",
"WisMesh Tap V2 TFT": "rak_wismesh_tap_v2-tft"
},
"RadioMaster": {
"900 Bandit": "radiomaster_900_bandit",
"900 Bandit Micro": "radiomaster_900_bandit_micro",
"900 Bandit Nano": "radiomaster_900_bandit_nano"
},
"RPi": {
"Pico": "pico",
"Pico Slow Clock": "pico_slowclock",
"Pico W": "picow",
"Pico 2": "pico2",
"Pico 2W": "pico2w"
},
"Seeed": {
"Xiao ESP32-S3": "seeed-xiao-s3",
"Xiao NRF52840 Kit": "seeed_xiao_nrf52840_kit",
"Xiao NRF52840 Kit I2C": "seeed_xiao_nrf52840_kit_i2c",
"Xiao NRF52840 WIO SX1262": "seeed-xiao-nrf52840-wio-sx1262",
"Xiao NRF52840 E22900M30S": "seeed_xiao_nrf52840_e22_900m30s",
"Xiao NRF52840 E22900M33S": "seeed_xiao_nrf52840_e22_900m33s",
"Xiao BLE": "xiao_ble",
"Wio Tracker L1": "seeed_wio_tracker_L1",
"Wio Tracker L1 E-Ink": "seeed_wio_tracker_L1_eink",
"Wio Tracker L1 E-Ink InkHUD": "seeed_wio_tracker_L1_eink-inkhud",
"Wio Tracker WM1110": "wio-tracker-wm1110",
"Wio SDK WM1110": "wio-sdk-wm1110",
"Wio T1000S": "wio-t1000-s",
"SenseCAP Indicator": "seeed-sensecap-indicator",
"SenseCAP Indicator TFT": "seeed-sensecap-indicator-tft",
"Solar Node": "seeed_solar_node"
},
"Waveshare": {
"RP2040 LoRa": "rp2040-lora"
},
"Other": {
"Chatter2": "chatter2",
"9M2 IBRA PRS LoRa Tracker": "9m2ibr_aprs_lora_tracker",
"AIC3": "ai-c3",
"ESP32-C3": "esp32c3",
"ESP32-C3 Super Mini": "esp32c3_super_mini",
"ESP32-C6": "esp32c6",
"ESP32-S2": "esp32s2",
"ESP32-S3": "esp32s3",
"ESP32-S3 Pico": "ESP32-S3-Pico",
"Crowpanel ESP32-S3 2 E-Paper": "crowpanel-esp32s3-2-epaper",
"Crowpanel ESP32-S3 4 E-Paper": "crowpanel-esp32s3-4-epaper",
"Crowpanel ESP32-S3 5 E-Paper": "crowpanel-esp32s3-5-epaper",
"BPi Pico W ESP32-S3": "bpi_picow_esp32_s3",
"My ESP32-S3 DIY E-Ink": "my-esp32s3-diy-eink",
"My ESP32-S3 DIY OLED": "my-esp32s3-diy-oled",
"T-Energy S3 E22": "t-energy-s3_e22",
"Dreamcatcher 2206": "dreamcatcher-2206",
"Hackaday Communicator": "hackaday-communicator",
"Icarus": "icarus",
"Link32S3 V1": "link32-s3-v1",
"MeshTab 32 TN Resistive": "mesh-tab-3-2-TN-resistive",
"MeshTab 32 IPS Resistive": "mesh-tab-3-2-IPS-resistive",
"MeshTab 35 IPS Resistive": "mesh-tab-3-5-IPS-resistive",
"MeshTab 35 TN Resistive": "mesh-tab-3-5-TN-resistive",
"MeshTab 32 IPS Capacitive": "mesh-tab-3-2-IPS-capacitive",
"MeshTab 35 IPS Capacitive": "mesh-tab-3-5-IPS-capacitive",
"MeshTab 40 IPS Capacitive": "mesh-tab-4-0-IPS-capacitive",
"Nibble ESP32": "nibble-esp32",
"Nugget S2 LoRa": "nugget-s2-lora",
"Nugget S3 LoRa": "nugget-s3-lora",
"Pi Computer S3": "picomputer-s3",
"Pi Computer S3 TFT": "picomputer-s3-tft",
"T-Echo Lite": "t-eth-elite",
"Tracksenger": "tracksenger",
"Tracksenger LCD": "tracksenger-lcd",
"Tracksenger OLED": "tracksenger-oled",
"unPhone": "unphone",
"unPhone TFT": "unphone-tft",
"NRF52832": "nrf52832",
"NRF52840": "nrf52840",
"ThinkNode M1 InkHUD": "thinknode_m1-inkhud",
"ME25LS014Y10TD": "ME25LS01-4Y10TD",
"ME25LS014Y10TD E-Ink": "ME25LS01-4Y10TD_e-ink",
"MS24SF1": "ms24sf1",
"TWC Mesh V4": "TWC_mesh_v4",
"WashTastic": "WashTastic",
"Feather DIY": "feather_diy",
"Feather RP2040 RFM95": "feather_rp2040_rfm95",
"GAT562 Mesh Trial Tracker": "gat562_mesh_trial_tracker",
"MeshLink": "meshlink",
"MeshLink E-Ink": "meshlink_eink",
"MeshTiny": "meshtiny",
"MonteOps HW1": "monteops_hw1",
"R1 Neo": "r1-neo",
"Tracker T1000E": "tracker-t1000-e",
"Tracker D": "trackerd",
"WiPhone": "wiphone",
"Challenger 2040 LoRa": "challenger_2040_lora",
"Cat Sniffer": "catsniffer",
"Nibbler RP2040": "nibble-rp2040",
"SenseLoRa RP2040": "senselora_rp2040",
"Wio E5": "wio-e5",
"Coverage": "coverage",
"Buildroot": "buildroot"
}
}

View File

@@ -127,30 +127,19 @@ export function isRequiredByOther(
return false return false
} }
/**
* Normalize architecture name (remove hyphens and underscores to match PlatformIO format)
* PlatformIO uses "esp32s3", "nrf52840" (no hyphens, no underscores)
* Hardware list uses "esp32-s3" (with hyphens)
* Some sources might use "esp32_s3" (with underscores)
*/
function normalizeArchitecture(arch: string): string {
return arch.replace(/[-_]/g, "")
}
/** /**
* Trace a target/variant/architecture back to its base architecture * Trace a target/variant/architecture back to its base architecture
* Follows the parent chain until it reaches a base architecture (null parent) * Follows the parent chain until it reaches a base architecture (null parent)
*/ */
export function getBaseArchitecture(name: string): string | null { export function getBaseArchitecture(name: string): string | null {
const normalized = normalizeArchitecture(name)
const parentMap = PARENT_MAP as Record<string, string | null> const parentMap = PARENT_MAP as Record<string, string | null>
const visited = new Set<string>() const visited = new Set<string>()
let current = normalized let current: string | null = name
while (current && !visited.has(current)) { while (current && !visited.has(current)) {
visited.add(current) visited.add(current)
const parent = parentMap[current] if (!current) break
const parent: string | null | undefined = parentMap[current]
// If parent is null, we've reached a base architecture // If parent is null, we've reached a base architecture
if (parent === null) { if (parent === null) {
@@ -162,11 +151,11 @@ export function getBaseArchitecture(name: string): string | null {
return current return current
} }
current = normalizeArchitecture(parent) current = parent
} }
// Circular reference or unknown, return the last known // Circular reference or unknown, return the last known
return current || normalized return current || name
} }
/** /**
@@ -174,17 +163,16 @@ export function getBaseArchitecture(name: string): string | null {
* (including itself and all parent architectures up to base) * (including itself and all parent architectures up to base)
*/ */
export function getCompatibleArchitectures(arch: string): string[] { export function getCompatibleArchitectures(arch: string): string[] {
const normalized = normalizeArchitecture(arch)
const parentMap = PARENT_MAP as Record<string, string | null> const parentMap = PARENT_MAP as Record<string, string | null>
const compatible = [arch]
const compatible = [normalized]
const visited = new Set<string>() const visited = new Set<string>()
let current = normalized let current: string | null = arch
// Follow parent chain up to base architecture // Follow parent chain up to base architecture
while (current && !visited.has(current)) { while (current && !visited.has(current)) {
visited.add(current) visited.add(current)
const parent = parentMap[current] if (!current) break
const parent: string | null | undefined = parentMap[current]
if (parent === null) { if (parent === null) {
// Reached base architecture // Reached base architecture
@@ -196,12 +184,11 @@ export function getCompatibleArchitectures(arch: string): string[] {
break break
} }
const normalizedParent = normalizeArchitecture(parent) if (!compatible.includes(parent)) {
if (!compatible.includes(normalizedParent)) { compatible.push(parent)
compatible.push(normalizedParent)
} }
current = normalizedParent current = parent
} }
return compatible return compatible
@@ -227,18 +214,16 @@ export function isPluginCompatibleWithTarget(
const parentMap = PARENT_MAP as Record<string, string | null> const parentMap = PARENT_MAP as Record<string, string | null>
// Normalize target name first (all keys in parentMap are normalized)
const normalizedTarget = normalizeArchitecture(targetName)
// Get all compatible names for the target (target itself + all parents up to base architecture) // Get all compatible names for the target (target itself + all parents up to base architecture)
const compatibleNames = new Set<string>([normalizedTarget]) const compatibleNames = new Set<string>([targetName])
const visited = new Set<string>() const visited = new Set<string>()
let current = normalizedTarget let current: string | null = targetName
// Follow parent chain (all keys and values in parentMap are already normalized) // Follow parent chain
while (current && !visited.has(current)) { while (current && !visited.has(current)) {
visited.add(current) visited.add(current)
const parent = parentMap[current] if (!current) break
const parent: string | null | undefined = parentMap[current]
if (parent === null) { if (parent === null) {
// Reached base architecture // Reached base architecture
@@ -251,30 +236,21 @@ export function isPluginCompatibleWithTarget(
break break
} }
// Parent is already normalized (from JSON)
compatibleNames.add(parent) compatibleNames.add(parent)
current = parent current = parent
} }
// Check excludes first - if target matches any exclude, it's incompatible // Check excludes first - if target matches any exclude, it's incompatible
// compatibleNames are already normalized, normalize excludes for comparison
if (pluginExcludes && pluginExcludes.length > 0) { if (pluginExcludes && pluginExcludes.length > 0) {
const isExcluded = pluginExcludes.some(exclude => { const isExcluded = pluginExcludes.some(exclude => compatibleNames.has(exclude))
const normalizedExclude = normalizeArchitecture(exclude)
return compatibleNames.has(normalizedExclude)
})
if (isExcluded) { if (isExcluded) {
return false return false
} }
} }
// If includes are specified, target must match at least one include // If includes are specified, target must match at least one include
// compatibleNames are already normalized, normalize includes for comparison
if (pluginIncludes && pluginIncludes.length > 0) { if (pluginIncludes && pluginIncludes.length > 0) {
return pluginIncludes.some(include => { return pluginIncludes.some(include => compatibleNames.has(include))
const normalizedInclude = normalizeArchitecture(include)
return compatibleNames.has(normalizedInclude)
})
} }
// If no includes/excludes specified, assume compatible with all (backward compatible) // If no includes/excludes specified, assume compatible with all (backward compatible)
@@ -301,34 +277,30 @@ export function isPluginCompatibleWithArchitecture(
export function getTargetsCompatibleWithIncludes(includes: string[]): Set<string> { export function getTargetsCompatibleWithIncludes(includes: string[]): Set<string> {
const parentMap = PARENT_MAP as Record<string, string | null> const parentMap = PARENT_MAP as Record<string, string | null>
const compatibleTargets = new Set<string>() const compatibleTargets = new Set<string>()
const includesSet = new Set(includes)
// Normalize includes
const normalizedIncludes = new Set(includes.map(include => normalizeArchitecture(include)))
// For each target in the parent map, check if it or any of its ancestors match the includes // For each target in the parent map, check if it or any of its ancestors match the includes
for (const target of Object.keys(parentMap)) { for (const target of Object.keys(parentMap)) {
const normalizedTarget = normalizeArchitecture(target)
const visited = new Set<string>() const visited = new Set<string>()
let current: string | null = normalizedTarget let current: string | null = target
// Trace up the parent chain // Trace up the parent chain
while (current && !visited.has(current)) { while (current && !visited.has(current)) {
visited.add(current) visited.add(current)
if (!current) break
// Check if current matches any of the includes // Check if current matches any of the includes
if (normalizedIncludes.has(current)) { if (includesSet.has(current)) {
// Add both the normalized version and the original (for matching against TARGETS)
compatibleTargets.add(normalizedTarget)
compatibleTargets.add(target) compatibleTargets.add(target)
break break
} }
// Move to parent // Move to parent
const parentValue = parentMap[current] const parent: string | null | undefined = parentMap[current]
if (parentValue === null || parentValue === undefined) { if (parent === null || parent === undefined) {
break break
} }
current = normalizeArchitecture(parentValue) current = parent
} }
} }

View File

@@ -9,14 +9,6 @@ const FIRMWARE_DIR = path.resolve(__dirname, "../vendor/firmware")
const VARIANTS_DIR = path.join(FIRMWARE_DIR, "variants") const VARIANTS_DIR = path.join(FIRMWARE_DIR, "variants")
const OUTPUT_FILE = path.resolve(__dirname, "../constants/architecture-hierarchy.json") const OUTPUT_FILE = path.resolve(__dirname, "../constants/architecture-hierarchy.json")
/**
* Normalize architecture/target name (remove hyphens and underscores)
* This ensures consistent format matching PlatformIO architecture names
*/
function normalizeName(name) {
return name.replace(/[-_]/g, "")
}
/** /**
* Parse PlatformIO ini file to extract sections and their properties * Parse PlatformIO ini file to extract sections and their properties
*/ */
@@ -342,15 +334,8 @@ function buildParentMapping() {
delete resolvedParentMap[key] delete resolvedParentMap[key]
} }
// Normalize all keys and values (strip hyphens and underscores) // Return map with actual PlatformIO environment names (no normalization)
const normalizedMap = {} return resolvedParentMap
for (const [key, value] of Object.entries(resolvedParentMap)) {
const normalizedKey = normalizeName(key)
const normalizedValue = value !== null ? normalizeName(value) : null
normalizedMap[normalizedKey] = normalizedValue
}
return normalizedMap
} }
/** /**