From 3a947b879f681b52cdbe6fcc99732bc363e20005 Mon Sep 17 00:00:00 2001 From: Iain Date: Sat, 17 Oct 2026 23:52:20 +0100 Subject: [PATCH] =?UTF-8?q?feat(import):=20latest=20iod=C3=A9OS=20builds?= =?UTF-8?q?=20and=20patch=20levels=20from=20its=20OTA=20feed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/upstream/iodeos.yml | 66 ++++++++++++++++- scripts/import/sources/iodeos.js | 80 +++++++++++++++++++-- tests/fixtures/feeds/iodeos-ota-fp5.json | 17 +++++ tests/fixtures/feeds/iodeos-ota-poplar.json | 12 ++++ tests/import/iodeos.test.js | 30 +++++++- 5 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/feeds/iodeos-ota-fp5.json create mode 100644 tests/fixtures/feeds/iodeos-ota-poplar.json diff --git a/data/upstream/iodeos.yml b/data/upstream/iodeos.yml index 254951e..2a4b95a 100644 --- a/data/upstream/iodeos.yml +++ b/data/upstream/iodeos.yml @@ -1,11 +1,12 @@ # yaml-language-server: $schema=../../schema/upstream.json # Generated by npm run import. Edits get overwritten, fix it at the source link instead. -imported: 2026-10-14 +imported: 2026-10-17 devices: a52sxq: status: active - android: 15 + android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -16,6 +17,7 @@ devices: status: active android: 11 relock: no + latest: { version: "2.48", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -25,6 +27,7 @@ devices: status: active android: 11 relock: no + latest: { version: "2.48", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -34,6 +37,7 @@ devices: status: active android: 15 relock: no + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -41,6 +45,7 @@ devices: status: active android: 15 relock: no + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -48,13 +53,15 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } apollon: status: active - android: 15 + android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -62,6 +69,7 @@ devices: status: active android: 15 relock: yes + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -69,6 +77,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -78,6 +87,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -87,6 +97,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -96,6 +107,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -105,6 +117,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -112,6 +125,7 @@ devices: status: active android: 15 relock: yes + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -126,6 +140,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -133,6 +148,7 @@ devices: status: active android: 13 relock: no + latest: { version: "4.37", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -140,6 +156,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -147,6 +164,7 @@ devices: status: active android: 13 relock: no + latest: { version: "4.37", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -156,6 +174,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -165,6 +184,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -174,6 +194,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -184,6 +205,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -191,6 +213,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -198,6 +221,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -205,6 +229,7 @@ devices: status: active android: 11 relock: no + latest: { version: "2.48", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -214,6 +239,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -221,6 +247,7 @@ devices: status: active android: 15 relock: yes + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -228,6 +255,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -235,6 +263,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -242,6 +271,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -249,6 +279,7 @@ devices: status: active android: 15 relock: no + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -258,12 +289,14 @@ devices: status: active android: 15 relock: no + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ gts7l: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -273,12 +306,14 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ hawao: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -286,6 +321,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -293,6 +329,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -300,6 +337,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -307,6 +345,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -314,6 +353,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -321,6 +361,7 @@ devices: status: active android: 13 relock: no + latest: { version: "4.37", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -328,6 +369,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -335,6 +377,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -342,6 +385,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -349,6 +393,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -356,6 +401,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -363,6 +409,7 @@ devices: status: active android: 15 relock: no + latest: { version: "6.19", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -370,6 +417,7 @@ devices: status: active android: 13 relock: no + latest: { version: "4.37", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -377,6 +425,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -384,6 +433,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -391,6 +441,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -398,6 +449,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -405,6 +457,7 @@ devices: status: active android: 16 relock: no + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -412,6 +465,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -419,6 +473,7 @@ devices: status: active android: 13 relock: no + latest: { version: "4.37", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -428,6 +483,7 @@ devices: status: active android: 13 relock: no + latest: { version: "4.37", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: @@ -437,6 +493,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working } @@ -444,6 +501,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -451,6 +509,7 @@ devices: status: active android: 16 relock: yes + latest: { version: "7.9", date: 2026-10-05, patch: 2026-09-01 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: { volte: working, vowifi: working, 5g: working } @@ -458,6 +517,7 @@ devices: status: active android: 11 relock: no + latest: { version: "2.48", date: 2026-10-05 } source: https://iode.tech/iodeos-official-supported-devices/ install: https://iode.tech/installation/ features: diff --git a/scripts/import/sources/iodeos.js b/scripts/import/sources/iodeos.js index 66d73c0..b7f6f12 100644 --- a/scripts/import/sources/iodeos.js +++ b/scripts/import/sources/iodeos.js @@ -1,8 +1,9 @@ -import { BROWSER_UA, fetchText } from '../fetch.js'; +import { BROWSER_UA, fetchText, mapLimit } from '../fetch.js'; import { upsertDevice, withoutBrand } from '../store.js'; const SOURCE = 'https://iode.tech/iodeos-official-supported-devices/'; const INSTALL = 'https://iode.tech/installation/'; +const OTA = 'https://ota.iode.tech'; const columns = { brand: /^brand$/i, @@ -65,6 +66,7 @@ export function parseTable(html) { const name = withoutBrand(fullName, brand).replace(/\s+/g, ' '); const locked = cell('locked').toLowerCase(); const android = Number(cell('version').match(/Android (\d+)/)?.[1]); + const major = cell('version').match(/^\d+/)?.[0]; const features = { ...callFeatures(cell('calls').toLowerCase(), fullName), ...(cell('fiveG').toLowerCase() === 'yes' && { '5g': 'working' }), @@ -89,17 +91,81 @@ export function parseTable(html) { install: INSTALL, ...(Object.keys(features).length && { features }), }, + ...(major && { feed: { codename, major } }), }, ]; }); } -export default async function iodeos() { +const buildFile = /^iode-([\d.]+)-(\d{4})(\d\d)(\d\d)-/; + +/** The newest build in an OTA feed, which comes in LineageOS's old shape or its newer one */ +export function latestBuild(text) { + // the newer feeds leave out the comma after "files" + const feed = JSON.parse(text.replace(/\]\s*"/g, '], "')); + const [build] = (feed.response ?? feed).toSorted((a, b) => b.datetime - a.datetime); + const file = build?.files?.[0] ?? build; + const [, version, year, month, day] = file?.filename?.match(buildFile) ?? []; + if (!version) return undefined; + const patch = file.os_patch_level; + return { version, date: `${year}-${month}-${day}`, ...(patch && { patch }) }; +} + +/** Release channel URLs, with the codename as the table spells it and then in lowercase */ +export const feedUrls = ({ codename, major }) => + [...new Set([codename, codename.toLowerCase()])].map( + (name) => `${OTA}/v${major}-release/${name}.json`, + ); + +async function fetchBuild(feed) { + for (const url of feedUrls(feed)) { + try { + return latestBuild(await fetchText(url)); + } catch (error) { + if (error.status !== 404) throw error; + } + } +} + +const majorOf = (build) => build?.version.split('.')[0]; + +/** The newest build, from the next version's feed when the old one offers an upgrade */ +async function fetchLatest(feed) { + const build = await fetchBuild(feed); + const major = majorOf(build); + return major && major !== feed.major ? ((await fetchBuild({ ...feed, major })) ?? build) : build; +} + +export default async function iodeos({ previous }) { const html = await fetchText(SOURCE, { headers: { 'user-agent': BROWSER_UA } }); - const devices = {}; - for (const { brand, device, row } of parseTable(html)) { + const entries = parseTable(html); + const androidOf = Object.fromEntries(entries.map(({ row, feed }) => [feed?.major, row.android])); + const found = entries.flatMap(({ brand, device, row, feed }) => { const key = upsertDevice(brand, device); - if (key) devices[key] = row; - } - return { devices }; + return key ? [{ key, row, feed }] : []; + }); + + const failures = []; + const devices = {}; + await mapLimit(found, 6, async ({ key, row, feed }) => { + let latest = previous[key]?.latest; + try { + latest = feed && (await fetchLatest(feed)); + } catch (error) { + failures.push(error.message); + } + const { source, install, features, ...head } = row; + const android = androidOf[majorOf(latest)]; + devices[key] = { + ...head, + ...(android && { android }), + ...(latest && { latest }), + source, + install, + ...(features && { features }), + }; + }); + + const note = failures.length && `OTA feed failed ${failures.length} times (${failures[0]})`; + return { devices, ...(note && { note }) }; } diff --git a/tests/fixtures/feeds/iodeos-ota-fp5.json b/tests/fixtures/feeds/iodeos-ota-fp5.json new file mode 100644 index 0000000..36a4d64 --- /dev/null +++ b/tests/fixtures/feeds/iodeos-ota-fp5.json @@ -0,0 +1,17 @@ +[ + { + "date": "2026-10-05", + "datetime": 1790867805, + "files": [ + { + "filename": "iode-7.9-20261005-FP5-ota.zip", + "os_patch_level": "2026-09-01", + "os_sdk_level": 36, + "size": 1639002283, + "url": "https://gitlab.iode.tech/ota/release/-/raw/master/FP5/iode-7.9-20261005-FP5-ota.zip" + } + ] + "type": "release", + "version": "7.9" + } +] diff --git a/tests/fixtures/feeds/iodeos-ota-poplar.json b/tests/fixtures/feeds/iodeos-ota-poplar.json new file mode 100644 index 0000000..58e9b3e --- /dev/null +++ b/tests/fixtures/feeds/iodeos-ota-poplar.json @@ -0,0 +1,12 @@ +{ + "response": [ + { + "datetime": 1790958633, + "filename": "iode-4.37-20261005-poplar-ota.zip", + "romtype": "UNOFFICIAL", + "size": 1219811961, + "url": "https://gitlab.iode.tech/ota/release/-/raw/master/poplar/iode-4.37-20261005-poplar-ota.zip", + "version": "C - 4.37" + } + ] +} diff --git a/tests/import/iodeos.test.js b/tests/import/iodeos.test.js index a290f3a..84b1064 100644 --- a/tests/import/iodeos.test.js +++ b/tests/import/iodeos.test.js @@ -1,9 +1,10 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; -import { parseTable } from '../../scripts/import/sources/iodeos.js'; +import { feedUrls, latestBuild, parseTable } from '../../scripts/import/sources/iodeos.js'; const html = readFileSync('tests/fixtures/feeds/iodeos.html', 'utf8'); +const feed = (name) => readFileSync(`tests/fixtures/feeds/iodeos-ota-${name}.json`, 'utf8'); const entries = parseTable(html); const entry = (codename) => entries.find((item) => item.device.codenames[0] === codename); @@ -52,3 +53,30 @@ test('columns are found by header name, not position', () => { ); assert.deepEqual(parseTable(swap(html)), entries); }); + +test('the release feed is tried as the table spells the codename, then lowercase', () => { + assert.deepEqual(feedUrls(entry('hawao').feed), [ + 'https://ota.iode.tech/v7-release/Hawao.json', + 'https://ota.iode.tech/v7-release/hawao.json', + ]); + assert.deepEqual(feedUrls(entry('gts4lvwifi').feed), [ + 'https://ota.iode.tech/v6-release/gts4lvwifi.json', + ]); +}); + +test('latest build reads the newer feeds, missing comma and all', () => { + assert.deepEqual(latestBuild(feed('fp5')), { + version: '7.9', + date: '2026-10-05', + patch: '2026-09-01', + }); +}); + +test('old style feeds give the version and date from the file name', () => { + const [build] = JSON.parse(feed('poplar')).response; + const older = { ...build, datetime: 1, filename: 'iode-4.36-20260901-poplar-ota.zip' }; + const expected = { version: '4.37', date: '2026-10-05' }; + assert.deepEqual(latestBuild(feed('poplar')), expected); + assert.deepEqual(latestBuild(JSON.stringify({ response: [older, build] })), expected); + assert.equal(latestBuild('{ "response": [] }'), undefined); +});