UNPKG

zigbee2mqtt

Version:

Zigbee to MQTT bridge using Zigbee-herdsman

186 lines (168 loc) 8.09 kB
import {execSync} from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import process from "node:process"; import zhc from "zigbee-herdsman-converters"; import definitionIndex from "zigbee-herdsman-converters/devices/index"; const z2mTillVersion = process.argv[2]; const zhcTillVersion = process.argv[3]; const zhTillVersion = process.argv[4]; const frontendTillVersion = process.argv[5]; const windfrontTillVersion = process.argv[6]; const changelogs = [ { tillVersion: z2mTillVersion, project: "koenkk/zigbee2mqtt", contents: fs.readFileSync(path.join(import.meta.dirname, "..", "CHANGELOG.md"), "utf-8").split("\n"), }, { tillVersion: zhcTillVersion, project: "koenkk/zigbee-herdsman-converters", contents: fs .readFileSync(path.join(import.meta.dirname, "..", "node_modules", "zigbee-herdsman-converters", "CHANGELOG.md"), "utf-8") .split("\n"), }, { tillVersion: zhTillVersion, project: "koenkk/zigbee-herdsman", contents: fs.readFileSync(path.join(import.meta.dirname, "..", "node_modules", "zigbee-herdsman", "CHANGELOG.md"), "utf-8").split("\n"), }, { tillVersion: frontendTillVersion, project: "nurikk/zigbee2mqtt-frontend", isFrontend: true, contents: fs.readFileSync(path.join(import.meta.dirname, "..", "node_modules", "zigbee2mqtt-frontend", "CHANGELOG.md"), "utf-8").split("\n"), }, { tillVersion: windfrontTillVersion, project: "Nerivec/zigbee2mqtt-windfront", }, ]; const releaseRe = /## \[(.+)\]/; const windfrontChangeRe = /^\* (feat|fix): (.+?)(?: by @([^\s]+) in (https:\/\/github\.com\/Nerivec\/zigbee2mqtt-windfront\/pull\/(\d+)))?$/gm; const changes = {features: [], fixes: [], detect: [], add: [], error: [], frontend: [], windfront: []}; let context = null; const changeRe = [ /^\* (\*\*(.+):\*\*)?(.+)\((\[#\d+\]\(.+\))\) \(\[.+\]\(https:.+\/(.+)\)\)$/, /^\* (\*\*(.+):\*\*)?(.+)(https:\/\/github\.com.+) \(\[.+\]\(https:.+\/(.+)\)\)$/, /^\* (\*\*(.+):\*\*)?(.+)() \(\[.+\]\(https:.+\/(.+)\)\)$/, ]; const frontendChangeRe = /^\* (\*\*.+:\*\* )()?(.+) \(\[.+\]\(https:.+\/()(.+)\)\)(.+)?$/; let commitUserLookup = {}; const commitUserFile = path.join(import.meta.dirname, "commit-user-lookup.json"); if (fs.existsSync(commitUserFile)) { commitUserLookup = JSON.parse(fs.readFileSync(commitUserFile, "utf8")); } const definitions = definitionIndex.default.map((d) => zhc.prepareDefinition(d)); const whiteLabels = definitions.filter((d) => d.whiteLabel).flatMap((d) => d.whiteLabel); const capitalizeFirstChar = (str) => str.charAt(0).toUpperCase() + str.slice(1); for (const changelog of changelogs) { if (changelog.project === "Nerivec/zigbee2mqtt-windfront") { const releaseRsp = await fetch("https://api.github.com/repos/Nerivec/zigbee2mqtt-windfront/releases"); const releases = await releaseRsp.json(); for (const release of releases) { if (release.name === `v${windfrontTillVersion}`) { break; } let match = windfrontChangeRe.exec(release.body); while (match !== null) { const [, type, message, user, prLink, prId] = match; const entry = `- ${prId && prLink ? `[#${prId}](${prLink}) ` : ""}${type}: ${message} ${user ? `(@${user.split("[")[0]})` : ""}`; changes.windfront.push(entry); match = windfrontChangeRe.exec(release.body); } } } else { for (const line of changelog.contents) { const releaseMatch = line.match(releaseRe); const changeMatch = changelog.isFrontend ? line.match(frontendChangeRe) : changeRe.map((re) => line.match(re)).find((e) => e); if (releaseMatch) { if (releaseMatch[1] === changelog.tillVersion) { break; } } else if (line === "### Features") { context = "features"; } else if (line === "### Bug Fixes") { context = "fixes"; } else if (line.startsWith("* **ignore:**")) { // continue; } else if (changeMatch) { let localContext = changelog.isFrontend ? "frontend" : changeMatch[2] ? changeMatch[2] : context; if (!changes[localContext]) localContext = "error"; const commit = changeMatch[5]; const commitUserKey = `${changelog.project}-${commit} `; let user = commitUserKey in commitUserLookup ? commitUserLookup[commitUserKey] : execSync(`curl -s https://api.github.com/repos/${changelog.project}/commits/${commit} | jq -r '.author.login'`) .toString() .trim(); if (user !== "null") commitUserLookup[commitUserKey] = user; const messages = []; let message = changeMatch[3].trim(); if (message.endsWith(".")) message = message.substring(0, message.length - 1); if (changelog.isFrontend) { changes[localContext].push( `- [${commit.slice(0, 7)}](https://github.com/${changelog.project}/commit/${commit}) ${message} (@${user})`, ); messages.push(capitalizeFirstChar(message)); } else { const otherUser = message.match(/\[@(.+)\]\(https:\/\/github.com\/.+\)/) || message.match(/@(.+)/); if (otherUser) { user = otherUser[1]; message = message.replace(otherUser[0], ""); } if (localContext === "add") { for (const model of message.split(",")) { const definition = definitions.find((d) => d.model === model.trim()); const whiteLabel = whiteLabels.find((d) => d.model === model.trim()); const match = definition || whiteLabel; if (match) { messages.push(`\`${match.model}\` ${match.vendor} ${match.description}`); } else { changes.error.push(`${line} (model '${model}' does not exist)`); } } } else { messages.push(capitalizeFirstChar(message)); } let issue = changeMatch[4].trim(); if (issue && !issue.startsWith("[#")) issue = `[#${issue.split("/").pop()}](${issue})`; if (!issue) { issue = "_NO_ISSUE_"; localContext = "error"; } for (const message of messages) { changes[localContext].push(`- ${issue} ${message} (@${user})`); } } } else if (line === "# Changelog" || line === "### ⚠ BREAKING CHANGES" || !line) { // continue; } else { changes.error.push(`- Unmatched line: ${line}`); } } } } let result = ""; const names = [ ["features", "Improvements"], ["fixes", "Fixes"], ["windfront", "Windfront"], ["frontend", "Frontend"], ["add", "New supported devices"], ["detect", "Fixed device detections"], ["error", "Changelog generator error"], ]; for (const name of names) { result += `# ${name[1]}\n`; if (name[0] === "add") { result += `This release adds support for ${changes.add.length} devices: \n`; } for (const change of changes[name[0]]) { result += `${change}\n`; } result += "\n"; } fs.writeFileSync(commitUserFile, JSON.stringify(commitUserLookup), "utf-8"); console.log(result.trim());