The readme is rewritten for 2.0: what the library needs, install, the lamp as quick start, concepts, the table of interfaces, recipes, the bridge contract, errors, testing without Alexa, migration from 1.x and limits. scripts/capability-table.js (npm run docs) writes the table from the registry and the text of three example files into their code blocks; "Through Alexa" names only the runs of 2026-09-28, made with 1.5.2. test/readme.test.js fails on a stale table or file, runs examples/testing/lamp.test.js (MemoryPublisher, no broker) and compiles the other code blocks. package.json: description, keywords, CHANGELOG.md in files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
119 lines
5.2 KiB
JavaScript
119 lines
5.2 KiB
JavaScript
"use strict";
|
|
// Writes what readme.md takes from the code: the table of interfaces, from the registry, between the two marker
|
|
// lines, and the text of a file into the code block that follows a line <!-- file: examples/lamp.js -->.
|
|
//
|
|
// npm run build && npm run docs write them
|
|
// node scripts/capability-table.js --check exit 1 when the readme has another text
|
|
//
|
|
// It reads the built registry (dist/cjs), so the build comes first. test/readme.test.js fails on a stale readme.
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const ROOT = path.join(__dirname, "..");
|
|
const README = path.join(ROOT, "readme.md");
|
|
const BEGIN = "<!-- capabilities: written by scripts/capability-table.js, npm run docs -->";
|
|
const END = "<!-- /capabilities -->";
|
|
|
|
// What was driven from a real Alexa account through the public Alex2MQTT service, and when. The runs used
|
|
// alex2node 1.5.2; a row is added here when a run is recorded, not when a descriptor is written.
|
|
const DRIVEN_ON = "2026-09-28";
|
|
const DRIVEN_WITH = "alex2node 1.5.2";
|
|
const DRIVEN = {
|
|
"Alexa": "ReportState",
|
|
"Alexa.BrightnessController": "directives",
|
|
"Alexa.ColorController": "directives",
|
|
"Alexa.ColorTemperatureController": "directives",
|
|
"Alexa.ContactSensor": "change report",
|
|
"Alexa.LockController": "directives, change report",
|
|
"Alexa.ModeController": "directives",
|
|
"Alexa.MotionSensor": "change report",
|
|
"Alexa.PercentageController": "directives",
|
|
"Alexa.PowerController": "directives, change report",
|
|
"Alexa.PowerLevelController": "directives",
|
|
"Alexa.RangeController": "directives",
|
|
"Alexa.SceneController": "directives",
|
|
"Alexa.TemperatureSensor": "change report",
|
|
"Alexa.ThermostatController": "directives",
|
|
"Alexa.ToggleController": "directives",
|
|
};
|
|
|
|
const code = (names) => (names.length > 0 ? names.map((name) => `\`${name}\``).join(", ") : "-");
|
|
|
|
function row(descriptor) {
|
|
const { namespace, version, doc, tier, instanced, properties, directives, events = {} } = descriptor;
|
|
return [
|
|
`[${namespace}](${doc})${instanced ? " (instances)" : ""}`,
|
|
version,
|
|
code(Object.keys(properties)),
|
|
code(Object.keys(directives)),
|
|
code(Object.keys(events)),
|
|
String(tier),
|
|
DRIVEN[namespace] ?? "-",
|
|
];
|
|
}
|
|
|
|
/** The text between the markers, from the registry of the build. */
|
|
function table(registry = require(path.join(ROOT, "dist", "cjs", "index.js")).registry) {
|
|
const all = registry.list();
|
|
const unknown = Object.keys(DRIVEN).filter((namespace) => !registry.has(namespace));
|
|
if (unknown.length > 0) throw new Error(`DRIVEN names ${unknown.join(", ")}, which the registry does not have`);
|
|
|
|
const described = all.filter((descriptor) => descriptor.tier !== 3);
|
|
const named = all.filter((descriptor) => descriptor.tier === 3);
|
|
const header = ["Interface", "Version", "Properties", "Directives", "Events", "Tier", "Through Alexa"];
|
|
const lines = [header, header.map(() => "---"), ...described.map(row)].map((cells) => `| ${cells.join(" | ")} |`);
|
|
return [
|
|
...lines,
|
|
"",
|
|
`${described.length} interfaces are described (tier 1 and 2), ${named.length} are named (tier 3).`,
|
|
`"Through Alexa" is what a real Alexa account drove through the public Alex2MQTT service on ${DRIVEN_ON}, with`,
|
|
`${DRIVEN_WITH}; "-" is an interface that no such run has covered.`,
|
|
"",
|
|
`Tier 3: ${named.map(({ namespace, version, doc }) => `[${namespace}](${doc}) ${version}`).join(", ")}.`,
|
|
].join("\n");
|
|
}
|
|
|
|
// A line that names a file, and the code block after it
|
|
const FILE = /<!-- file: (\S+) -->\n```(\w+)\n[\s\S]*?\n```/g;
|
|
|
|
/** The readme with the text the named files have now in their code blocks. */
|
|
function files(readme) {
|
|
return readme.replace(FILE, (block, file, language) => {
|
|
const text = fs.readFileSync(path.join(ROOT, file), "utf8").trimEnd();
|
|
if (text.includes("```")) throw new Error(`${file} has a code fence, which would end its block in readme.md`);
|
|
return `<!-- file: ${file} -->\n\`\`\`${language}\n${text}\n\`\`\``;
|
|
});
|
|
}
|
|
|
|
/** The files the readme shows, as the marker lines name them. */
|
|
function shown(readme) {
|
|
return [...readme.matchAll(FILE)].map(([, file]) => file);
|
|
}
|
|
|
|
/** The readme with the table of now and the files of now. Throws when the markers are not there, each once and in this order. */
|
|
function update(readme, text = table()) {
|
|
const begin = readme.indexOf(BEGIN);
|
|
const end = readme.indexOf(END);
|
|
if (begin < 0 || end < begin || readme.indexOf(BEGIN, begin + 1) >= 0 || readme.indexOf(END, end + 1) >= 0) {
|
|
throw new Error(`readme.md needs the lines "${BEGIN}" and "${END}", once each and in this order`);
|
|
}
|
|
return files(`${readme.slice(0, begin)}${BEGIN}\n${text}\n${readme.slice(end)}`);
|
|
}
|
|
|
|
if (require.main === module) {
|
|
const before = fs.readFileSync(README, "utf8");
|
|
const after = update(before);
|
|
if (process.argv.includes("--check")) {
|
|
if (after !== before) {
|
|
console.error("readme.md has another table of interfaces or another text of a file than the code gives: npm run build && npm run docs");
|
|
process.exit(1);
|
|
}
|
|
} else if (after !== before) {
|
|
fs.writeFileSync(README, after);
|
|
console.log("readme.md was written");
|
|
} else {
|
|
console.log("readme.md is up to date");
|
|
}
|
|
}
|
|
|
|
module.exports = { table, update, files, shown, BEGIN, END, DRIVEN, README };
|