Publish 1.17.1.9 USA Cascade picker and Mesh America catalogs
Point both stable providers and runtime controls to the validated v1.17.1.9-halo-keymind-cascade-dev-88c85108 family. Record completed package validation while retaining hardware coverage limitations. Give all documentation source pages their exact hosted links and mount the telemetry form only in the browser. Verify current combined Full and nRF52 Full/Reduced catalog fixtures.
This commit is contained in:
1 parent
d682e7422c
commit
e3c7f5efa3
191 files changed
+17402
-18128
No files matched your search
@@ -1156,8 +1156,10 @@ const currentCatalog = picker.buildCatalog([
|
||||
assert.strictEqual(currentCatalog.rows.length, currentAssets.length);
|
||||
assert(currentCatalog.profiles.every(profile => profile.controls && profile.chipFamily !== 'unknown'));
|
||||
const expandedEsp32 = currentCatalog.profiles.find(profile =>
|
||||
profile.target === 'Ebyte_EoRa-S3_Repeater-full-logging');
|
||||
profile.target === 'Ebyte_EoRa-S3_Repeater-full-usb-wifi');
|
||||
assert(expandedEsp32);
|
||||
assert.strictEqual(expandedEsp32.ota, 'lora-receiver');
|
||||
assert.deepStrictEqual(expandedEsp32.loggingModes, ['none', 'usb', 'wifi', 'both']);
|
||||
assert(picker.installSteps(expandedEsp32, 'bin').some(step =>
|
||||
step.includes('exact board/role migration ZIP')));
|
||||
assert(picker.installSteps(expandedEsp32, 'merged-bin').some(step =>
|
||||
@@ -1166,40 +1168,66 @@ assert.strictEqual(
|
||||
picker.migrationReleaseUrl(expandedEsp32, expandedEsp32.files[0]),
|
||||
'https://github.com/mikecarper/MeshCore/releases/tag/utility-' +
|
||||
currentControls.familyTag);
|
||||
const ordinaryEsp32 = currentCatalog.profiles.find(profile =>
|
||||
profile.target === 'Ebyte_EoRa-S3_Repeater');
|
||||
assert(ordinaryEsp32);
|
||||
// Infrastructure now ships as the combined Full profile. A Companion remains
|
||||
// a real, non-expanded negative case; do not invent a removed ordinary image.
|
||||
const companionEsp32 = currentCatalog.profiles.find(profile =>
|
||||
profile.target === 'Ebyte_EoRa-S3_companion_radio_full');
|
||||
assert(companionEsp32);
|
||||
assert.strictEqual(
|
||||
picker.migrationReleaseUrl(ordinaryEsp32, ordinaryEsp32.files[0]), '');
|
||||
const ikokaNormal = currentCatalog.profiles.find(profile =>
|
||||
profile.target === 'ikoka_stick_nrf_30dbm_repeater');
|
||||
picker.migrationReleaseUrl(companionEsp32, companionEsp32.files[0]), '');
|
||||
const ikokaBase = 'ikoka_stick_nrf_30dbm_repeater';
|
||||
const ikokaProfiles = ['full', 'reduced'].map(policy => {
|
||||
const profile = currentCatalog.profiles.find(item => item.target === ikokaBase + '-' + policy);
|
||||
assert(profile, 'The qualified current release must retain the ' + policy + ' nRF52 profile');
|
||||
assert.strictEqual(profile.sensorProfile, policy);
|
||||
assert.strictEqual(profile.feature, policy === 'full' ? 'full' : 'standard');
|
||||
assert.strictEqual(profile.ota, 'lora-receiver');
|
||||
assert.strictEqual(profile.logging, 'usb-runtime');
|
||||
assert.deepStrictEqual(profile.loggingModes, ['none', 'usb']);
|
||||
assert.deepStrictEqual(picker.runtimeDirections(profile, {logging: 'usb'})[0].actions[0].commands,
|
||||
['set usb.logging on']);
|
||||
return profile;
|
||||
});
|
||||
assert(!currentCatalog.profiles.some(profile =>
|
||||
profile.target === 'ikoka_stick_nrf_30dbm_repeater_lora_ota_no_external_sensors'));
|
||||
assert.strictEqual(ikokaNormal.ota, 'lora-receiver');
|
||||
assert.strictEqual(ikokaNormal.logging, 'usb-runtime');
|
||||
assert.deepStrictEqual(ikokaNormal.loggingModes, ['none', 'usb']);
|
||||
assert.deepStrictEqual(picker.runtimeDirections(ikokaNormal, {logging: 'usb'})[0].actions[0].commands,
|
||||
['set usb.logging on']);
|
||||
[ikokaBase, ikokaBase + '_lora_ota_no_external_sensors'].includes(profile.target)));
|
||||
const ikokaUrl = 'https://example.com/firmware_picker/?chipFamily=nrf52&hardwareFamily=ikoka_stick_nrf_30dbm&hardware=ikoka_stick_nrf_30dbm&role=repeater&variant=default&install=zip&chipAuto=1';
|
||||
const ikokaRelease = release(currentControls.familyTag, '2026-09-13T00:00:00Z',
|
||||
[ikokaNormal].map(profile => {
|
||||
ikokaProfiles.flatMap(profile => {
|
||||
const source = currentControls.profiles[profile.target].loggingSource;
|
||||
const tag = currentControls.familyTag.replace(/-[0-9a-f]{8}$/, '-' + source.slice(0, 8));
|
||||
return asset(profile.target + '-ota-' + tag + '.zip');
|
||||
return ['zip', 'uf2'].map(extension => asset(profile.target + '-ota-' + tag + '.' + extension));
|
||||
}));
|
||||
const ikokaCatalog = picker.buildCatalog([ikokaRelease], currentControls);
|
||||
const ikokaSelection = picker.selectionFromUrl(ikokaUrl, ikokaCatalog.profiles);
|
||||
assert.deepStrictEqual(ikokaSelection.unavailable, []);
|
||||
assert.deepStrictEqual(ikokaCatalog.profiles.filter(profile =>
|
||||
picker.profileMatchesFacets(profile, ikokaSelection.filters)).map(profile => profile.target),
|
||||
['ikoka_stick_nrf_30dbm_repeater']);
|
||||
const mismatchedIkoka = picker.buildCatalog([
|
||||
release(currentControls.familyTag, '2026-09-13T00:00:00Z', [
|
||||
asset('ikoka_stick_nrf_30dbm_repeater-ota-' +
|
||||
currentControls.familyTag.replace(/-[0-9a-f]{8}$/, '-306feebe') + '.zip'),
|
||||
]),
|
||||
], currentControls).profiles[0];
|
||||
assert.strictEqual(mismatchedIkoka.logging, 'none', 'Logging metadata must match the published build source');
|
||||
ikokaProfiles.map(profile => profile.target));
|
||||
for (const profile of ikokaProfiles) {
|
||||
const policyUrl = ikokaUrl + '&feature=' + profile.feature + '&ota=lora-receiver';
|
||||
const selection = picker.selectionFromUrl(policyUrl, ikokaCatalog.profiles);
|
||||
assert.deepStrictEqual(selection.unavailable, []);
|
||||
assert.deepStrictEqual(ikokaCatalog.profiles.filter(item =>
|
||||
picker.profileMatchesFacets(item, selection.filters)).map(item => item.target), [profile.target]);
|
||||
const current = ikokaCatalog.profiles.find(item => item.target === profile.target);
|
||||
assert.deepStrictEqual(current.installKinds, ['zip', 'uf2']);
|
||||
const choices = picker.firmwareProfileChoices(ikokaCatalog.profiles, {
|
||||
hardware: current.hardware, role: current.role, install: 'zip',
|
||||
});
|
||||
const choice = choices.find(item => item.value === picker.firmwareProfileValue(current));
|
||||
assert(choice, 'Both current Full/Reduced profiles must have distinct picker choices');
|
||||
assert.deepStrictEqual(ikokaCatalog.profiles.filter(item => picker.profileMatchesFacets(item,
|
||||
{...choice.filters, hardware: current.hardware, role: current.role, install: 'zip'}))
|
||||
.map(item => item.target), [current.target]);
|
||||
const mismatched = picker.buildCatalog([
|
||||
release(currentControls.familyTag, '2026-09-13T00:00:00Z', [
|
||||
asset(profile.target + '-ota-' +
|
||||
currentControls.familyTag.replace(/-[0-9a-f]{8}$/, '-306feebe') + '.zip'),
|
||||
]),
|
||||
], currentControls).profiles[0];
|
||||
assert.strictEqual(mismatched.logging, 'none', 'Logging metadata must match the published build source');
|
||||
assert.strictEqual(mismatched.sensorProfile, undefined, 'Sensor policy must match the published build source');
|
||||
}
|
||||
const capacityProfiles = currentCatalog.profiles.filter(profile => profile.controls.memoryNote);
|
||||
assert(capacityProfiles.length > 0, 'Regeneration must preserve capacity directions');
|
||||
for (const profile of capacityProfiles) {
|
||||
@@ -1384,6 +1412,13 @@ function migrationCatalog(assets = migrationAssets, metadata = currentControls,
|
||||
const withMigrations = migrationCatalog();
|
||||
assert.strictEqual(withMigrations.rows.length, currentCatalog.rows.length,
|
||||
'Migration ZIPs must not become firmware/Serial DFU choices');
|
||||
const expandedWithMigration = withMigrations.profiles.find(profile =>
|
||||
profile.target === expandedEsp32.target);
|
||||
assert.strictEqual(expandedWithMigration.migrationPackage, 'ebyte-eora-s3-repeater');
|
||||
assert.deepStrictEqual(picker.migrationLink(expandedWithMigration, expandedWithMigration.files[0]), {
|
||||
url: migrationZip('ebyte-eora-s3-repeater').browser_download_url,
|
||||
label: 'Download exact partition-expansion ZIP',
|
||||
});
|
||||
const g2Target = 'Station_G2_repeater_observer_mqtt-full-usb-wifi';
|
||||
function g2MigrationLink(catalog) {
|
||||
const profile = catalog.profiles.find(item => item.target === g2Target);
|
||||
@@ -1431,8 +1466,10 @@ for (const profile of currentMigrationProfiles) {
|
||||
assert.strictEqual(picker.migrationLink(profile, profile.files[0]).url,
|
||||
migrationZip(profile.migrationPackage).browser_download_url, profile.target);
|
||||
}
|
||||
assert.strictEqual(picker.migrationLink(ordinaryEsp32, ordinaryEsp32.files[0]), null);
|
||||
assert.strictEqual(picker.migrationLink(ikokaNormal, ikokaNormal.files[0]), null);
|
||||
assert.strictEqual(picker.migrationLink(companionEsp32, companionEsp32.files[0]), null);
|
||||
for (const profile of ikokaProfiles) {
|
||||
assert.strictEqual(picker.migrationLink(profile, profile.files[0]), null);
|
||||
}
|
||||
const unmappedVariant = withMigrations.profiles.find(profile =>
|
||||
profile.target === 'Station_G3_ESP32_r2_repeater_observer_mqtt-full-usb-wifi');
|
||||
assert(unmappedVariant);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const vm = require("node:vm");
|
||||
const { hideHostedDocLinks } = require("../docs/_javascript/hosted_doc_links.js");
|
||||
|
||||
function documentWith(banners) {
|
||||
return { querySelectorAll(selector) {
|
||||
assert.equal(selector, ".meshcore-hosted-doc-link");
|
||||
return banners;
|
||||
} };
|
||||
}
|
||||
|
||||
for (const hostname of ["localhost", "127.0.0.1", "github.com", "example.org", "meshcore-dev.github.io",
|
||||
"mikecarper.github.io.example.org", "notmikecarper.github.io", ""]) {
|
||||
const banners = [{ hidden: false }, { hidden: false }];
|
||||
assert.equal(hideHostedDocLinks(documentWith(banners), hostname), 0);
|
||||
assert.equal(banners.some(banner => banner.hidden), false, hostname);
|
||||
}
|
||||
const banners = [{ hidden: false }, { hidden: false }];
|
||||
assert.equal(hideHostedDocLinks(documentWith(banners), "mikecarper.github.io"), 2);
|
||||
assert.equal(banners.every(banner => banner.hidden), true);
|
||||
assert.equal(hideHostedDocLinks(documentWith([]), "mikecarper.github.io"), 0);
|
||||
|
||||
const script = fs.readFileSync(path.join(__dirname, "../docs/_javascript/hosted_doc_links.js"), "utf8");
|
||||
const loadingBanners = [{ hidden: false }];
|
||||
let ready;
|
||||
let navigation;
|
||||
const loadingDocument = Object.assign(documentWith(loadingBanners), {
|
||||
readyState: "loading",
|
||||
addEventListener(name, callback, options) {
|
||||
assert.equal(name, "DOMContentLoaded");
|
||||
assert.equal(options.once, true);
|
||||
ready = callback;
|
||||
}
|
||||
});
|
||||
vm.runInNewContext(script, {
|
||||
window: { location: { hostname: "mikecarper.github.io" } }, document: loadingDocument,
|
||||
document$: { subscribe(callback) { navigation = callback; } }
|
||||
});
|
||||
assert.equal(loadingBanners[0].hidden, false);
|
||||
ready();
|
||||
assert.equal(loadingBanners[0].hidden, true);
|
||||
loadingBanners.push({ hidden: false });
|
||||
navigation();
|
||||
assert.equal(loadingBanners.every(banner => banner.hidden), true);
|
||||
|
||||
const readyBanners = [{ hidden: false }];
|
||||
vm.runInNewContext(script, {
|
||||
window: { location: { hostname: "mikecarper.github.io" } },
|
||||
document: Object.assign(documentWith(readyBanners), { readyState: "complete" })
|
||||
});
|
||||
assert.equal(readyBanners[0].hidden, true);
|
||||
const localBanners = [{ hidden: false }];
|
||||
vm.runInNewContext(script, {
|
||||
window: { location: { hostname: "localhost" } },
|
||||
document: Object.assign(documentWith(localBanners), { readyState: "complete" })
|
||||
});
|
||||
assert.equal(localBanners[0].hidden, false);
|
||||
console.log("Hosted documentation link domain, startup and navigation tests passed.");
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify source banners against MkDocs' actual published URL mapping."""
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SPEC = importlib.util.spec_from_file_location('hosted_doc_links', ROOT / 'scripts/update_hosted_doc_links.py')
|
||||
LINKS = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(LINKS)
|
||||
|
||||
|
||||
class HostedDocLinksTest(unittest.TestCase):
|
||||
def test_every_served_document_has_its_current_exact_link(self):
|
||||
manifest = LINKS.transform(ROOT)
|
||||
self.assertTrue(manifest['pages'])
|
||||
self.assertTrue(all(row['already_current'] for row in manifest['pages']))
|
||||
routes = {row['source']: row['url'] for row in manifest['pages']}
|
||||
self.assertEqual(routes['index.md'], LINKS.HOSTED_BASE)
|
||||
self.assertEqual(routes['research/index.md'], LINKS.HOSTED_BASE + 'research/')
|
||||
self.assertEqual(routes['WiFi.md'], LINKS.HOSTED_BASE + 'WiFi/')
|
||||
self.assertEqual(routes['releases/1.17.1.9.md'], LINKS.HOSTED_BASE + 'releases/1.17.1.9/')
|
||||
|
||||
def test_mkdocs_routes_cover_spaces_case_nested_index_and_non_navigation_pages(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
(root / 'docs/Nested Space').mkdir(parents=True)
|
||||
(root / 'mkdocs.yml').write_text('site_name: Route fixture\ntheme: {name: mkdocs}\n'
|
||||
'not_in_nav: |\n /Hidden.md\n'
|
||||
'exclude_docs: |\n /Excluded.md\n')
|
||||
for name in ('index.md', 'WiFi.md', 'Hidden.md', 'Nested Space/index.md',
|
||||
'Nested Space/Mixed Case.md', 'Excluded.md'):
|
||||
(root / 'docs' / name).write_bytes(('# ' + name + '\n\nBody stays the same.\n').encode())
|
||||
excluded_before = (root / 'docs/Excluded.md').read_bytes()
|
||||
manifest = LINKS.transform(root, apply=True)
|
||||
routes = {row['source']: row['url'] for row in manifest['pages']}
|
||||
self.assertEqual(routes, {
|
||||
'index.md': LINKS.HOSTED_BASE,
|
||||
'WiFi.md': LINKS.HOSTED_BASE + 'WiFi/',
|
||||
'Hidden.md': LINKS.HOSTED_BASE + 'Hidden/',
|
||||
'Nested Space/index.md': LINKS.HOSTED_BASE + 'Nested%20Space/',
|
||||
'Nested Space/Mixed Case.md': LINKS.HOSTED_BASE + 'Nested%20Space/Mixed%20Case/',
|
||||
})
|
||||
before = {p: p.read_bytes() for p in (root / 'docs').rglob('*.md')}
|
||||
self.assertTrue(all(row['already_current'] for row in LINKS.transform(root, apply=True)['pages']))
|
||||
self.assertEqual(before, {p: p.read_bytes() for p in before})
|
||||
self.assertEqual((root / 'docs/Excluded.md').read_bytes(), excluded_before)
|
||||
|
||||
def test_original_body_frontmatter_bom_and_line_endings_are_preserved(self):
|
||||
for text in ('# Heading\n\nBody with <b>HTML</b>.\n',
|
||||
'---\ntitle: Example\n---\n\n# Heading\n',
|
||||
'\ufeff---\r\ntitle: Example\r\n...\r\n\r\n# Heading\r\n'):
|
||||
with self.subTest(text=text):
|
||||
updated = LINKS.with_banner(text, LINKS.HOSTED_BASE + 'Example/')
|
||||
self.assertEqual(LINKS.document_body(updated), text)
|
||||
self.assertEqual(LINKS.with_banner(updated, LINKS.HOSTED_BASE + 'Example/'), updated)
|
||||
if 'title: Example' in text:
|
||||
self.assertLess(updated.index('title: Example'), updated.index(LINKS.BEGIN))
|
||||
|
||||
def test_moved_page_gets_a_new_link_without_changing_its_body(self):
|
||||
body = '# Example\n\nOriginal text.\n'
|
||||
old = LINKS.with_banner(body, LINKS.HOSTED_BASE + 'old/')
|
||||
updated = LINKS.with_banner(old, LINKS.HOSTED_BASE + 'new/')
|
||||
self.assertNotIn('href="' + LINKS.HOSTED_BASE + 'old/', updated)
|
||||
self.assertIn('href="' + LINKS.HOSTED_BASE + 'new/', updated)
|
||||
self.assertEqual(LINKS.document_body(updated), body)
|
||||
|
||||
def test_duplicate_misplaced_or_incomplete_banner_is_rejected(self):
|
||||
banner = LINKS.with_banner('# Heading\n', LINKS.HOSTED_BASE)
|
||||
for text in (banner + banner, '# Heading\n' + banner, banner.replace(LINKS.END + '\n\n', LINKS.END)):
|
||||
with self.subTest(text=text):
|
||||
with self.assertRaises(ValueError):
|
||||
LINKS.with_banner(text, LINKS.HOSTED_BASE)
|
||||
|
||||
def test_shared_script_is_registered_exactly_once(self):
|
||||
self.assertEqual((ROOT / 'mkdocs.yml').read_text().count(LINKS.SCRIPT), 1)
|
||||
self.assertTrue((ROOT / 'docs/_javascript/hosted_doc_links.js').is_file())
|
||||
|
||||
def test_hooks_preserve_other_tool_config_and_workflow_changes(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
(root / '.github/workflows').mkdir(parents=True)
|
||||
config = 'site_name: Fixture\nextra_javascript:\n - _javascript/telemetry_decoder.js?v=telemetry-fix2\n'
|
||||
workflow = ('name: Tests\njobs:\n management-reports:\n steps:\n'
|
||||
' - run: node scripts/test_telemetry_decoder.js\n')
|
||||
(root / 'mkdocs.yml').write_text(config)
|
||||
(root / '.github/workflows/run-unit-tests.yml').write_text(workflow)
|
||||
LINKS.install_hooks(root)
|
||||
self.assertEqual((root / 'mkdocs.yml').read_text().replace(LINKS.SCRIPT, ''), config)
|
||||
self.assertEqual((root / '.github/workflows/run-unit-tests.yml').read_text()
|
||||
.replace(LINKS.WORKFLOW_JOB, ''), workflow)
|
||||
before = {p: p.read_bytes() for p in root.rglob('*') if p.is_file()}
|
||||
LINKS.install_hooks(root)
|
||||
self.assertEqual(before, {p: p.read_bytes() for p in before})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user