Skip to content

Commit 101bfa8

Browse files
testing script initial
Signed-off-by: krystal <56278409+theekrystallee@users.noreply.github.com>
1 parent 8b68c27 commit 101bfa8

File tree

6 files changed

+385
-183
lines changed

6 files changed

+385
-183
lines changed

.github/scripts/update-services.js

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Robust update-services.js
4+
* Fetch all releases from GitHub, diff against services.md, and insert missing builds.
5+
* Supports pagination, dry-run, verbose, backups, and error handling.
6+
* Ensures sections and builds are ordered descending, removes duplicate headers,
7+
* cleans up bullet formatting, and collapses excess blank lines.
8+
*
9+
* Usage:
10+
* CONSENSUS_TOKEN=ghp_xxx node update-services.js --md-path path/to/services.md [--dry-run] [--verbose] [--include-prereleases]
11+
*/
12+
13+
const https = require("https");
14+
const fs = require("fs");
15+
const path = require("path");
16+
const os = require("os");
17+
const { spawnSync } = require("child_process");
18+
19+
// Parse CLI arguments
20+
const args = process.argv.slice(2).reduce((acc, cur, idx, arr) => {
21+
if (cur.startsWith("--")) {
22+
const key = cur.slice(2);
23+
const next = arr[idx + 1];
24+
acc[key] = next && !next.startsWith("--") ? next : true;
25+
}
26+
return acc;
27+
}, {});
28+
29+
const mdPath = args["md-path"];
30+
const dryRun = Boolean(args["dry-run"]);
31+
const verbose = Boolean(args["verbose"]);
32+
const includePrereleases = Boolean(args["include-prereleases"]);
33+
const token = process.env.CONSENSUS_TOKEN;
34+
35+
function log(...msgs) {
36+
if (verbose) console.log("[verbose]", ...msgs);
37+
}
38+
39+
if (!mdPath) {
40+
console.error("error: missing --md-path");
41+
process.exit(1);
42+
}
43+
if (!token) {
44+
console.error("error: missing CONSENSUS_TOKEN env var");
45+
process.exit(1);
46+
}
47+
48+
// Semver compare: a > b => 1, a < b => -1, equal => 0
49+
function compareVersions(a, b) {
50+
const pa = a.split(".").map(Number);
51+
const pb = b.split(".").map(Number);
52+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
53+
const na = pa[i] || 0;
54+
const nb = pb[i] || 0;
55+
if (na > nb) return 1;
56+
if (na < nb) return -1;
57+
}
58+
return 0;
59+
}
60+
61+
// Fetch a single page of releases
62+
function fetchPage(page, per_page = 100) {
63+
const options = {
64+
hostname: "api.github.com",
65+
path: `/repos/hiero-ledger/hiero-consensus-node/releases?per_page=${per_page}&page=${page}`,
66+
method: "GET",
67+
headers: {
68+
"User-Agent": "node.js",
69+
Authorization: `token ${token}`,
70+
Accept: "application/vnd.github.v3+json",
71+
},
72+
};
73+
return new Promise((resolve, reject) => {
74+
const req = https.request(options, (res) => {
75+
let data = "";
76+
res.on("data", (chunk) => (data += chunk));
77+
res.on("end", () => {
78+
if (res.statusCode >= 200 && res.statusCode < 300) {
79+
try {
80+
resolve(JSON.parse(data));
81+
} catch (e) {
82+
reject(new Error("Invalid JSON from GitHub"));
83+
}
84+
} else {
85+
reject(new Error(`GitHub API ${res.statusCode}: ${data}`));
86+
}
87+
});
88+
});
89+
req.on("error", reject);
90+
req.end();
91+
});
92+
}
93+
94+
// Fetch all releases with pagination
95+
async function fetchAllReleases() {
96+
let page = 1;
97+
const all = [];
98+
while (true) {
99+
log(`fetching page ${page}`);
100+
const list = await fetchPage(page);
101+
if (!Array.isArray(list) || list.length === 0) break;
102+
all.push(...list);
103+
if (list.length < 100) break;
104+
page++;
105+
}
106+
return all.filter((r) => r.tag_name && (includePrereleases || !r.prerelease));
107+
}
108+
109+
// Build markdown snippet for a release, cleaning up headings
110+
function buildSnippet(release) {
111+
const version = release.tag_name;
112+
const url = release.html_url;
113+
// remove any "What's Changed" headings
114+
const raw = (release.body || "").split(/\r?\n/);
115+
const cleaned = raw
116+
.filter((line) => !/^#+\s*what'?s changed\s*$/i.test(line))
117+
.map((line) => line.trim())
118+
.filter((line) => line);
119+
const bulletized = cleaned.map((line) => `* ${line}`).join("\n");
120+
const [major, minor] = version.replace(/^v/, "").split(".");
121+
return `### [Build ${version}](${url})
122+
123+
<details>
124+
<summary><strong>What's Changed</strong></summary>
125+
126+
${bulletized}
127+
128+
**Full Changelog**: [v${major}.${minor}.0...${version}](https://github.com/hiero-ledger/hiero-consensus-node/compare/v${major}.${minor}.0...${version})
129+
130+
</details>
131+
`;
132+
}
133+
134+
// Insert snippet into content under the correct section
135+
function insertSnippet(content, snippet, version) {
136+
const [major, minor] = version.replace(/^v/, "").split(".");
137+
const header = `## Release v${major}.${minor}`;
138+
const regex = new RegExp(
139+
`(${header}[\s\S]*?)(?=(## Release v\\d+\\.\\d+|$))`
140+
);
141+
if (regex.test(content)) {
142+
return content.replace(
143+
regex,
144+
`$1
145+
146+
${snippet}`
147+
);
148+
}
149+
return (
150+
content.trimEnd() +
151+
`
152+
153+
${header}
154+
155+
${snippet}`
156+
);
157+
}
158+
159+
// Sort build blocks within a section descending, join with two newlines
160+
function sortSectionBuilds(sectionText) {
161+
const marker = "### [Build ";
162+
const idx = sectionText.indexOf(marker);
163+
if (idx === -1) return sectionText;
164+
const prefix = sectionText.slice(0, idx).trimEnd();
165+
const buildsText = sectionText.slice(idx);
166+
const blocks = buildsText
167+
.split(/(?=### \[Build )/g)
168+
.map((b) => b.trim())
169+
.filter((b) => b);
170+
blocks.sort((a, b) => {
171+
const va = a.match(/### \[Build v?(.*?)\]/)[1];
172+
const vb = b.match(/### \[Build v?(.*?)\]/)[1];
173+
return compareVersions(vb, va);
174+
});
175+
return `${prefix}
176+
177+
${blocks.join("\n\n")}`;
178+
}
179+
180+
// Reorder and merge all sections descending by version, then sort builds
181+
function reorderSections(content) {
182+
const splitRegex = /(?=^## Release v\d+\.\d+)/m;
183+
const parts = content.split(splitRegex);
184+
const header = parts.shift();
185+
186+
const map = new Map();
187+
parts.forEach((sec) => {
188+
const m = sec.match(/^## Release v(\d+\.\d+)/);
189+
if (!m) return;
190+
const ver = m[1];
191+
const trimmed = sec.trim();
192+
if (!map.has(ver)) map.set(ver, trimmed);
193+
else {
194+
const body = trimmed.replace(/^## Release v\d+\.\d+/, "").trim();
195+
if (body) map.set(ver, map.get(ver) + "\n\n" + body);
196+
}
197+
});
198+
199+
const sorted = Array.from(map.keys()).sort((a, b) => compareVersions(b, a));
200+
201+
let out = header.trimEnd() + "\n";
202+
sorted.forEach((ver) => {
203+
let sec = map.get(ver);
204+
sec = sortSectionBuilds(sec);
205+
out += sec + "\n\n";
206+
});
207+
208+
return out.trimEnd().replace(/\n{3,}/g, "\n\n");
209+
}
210+
211+
(async () => {
212+
const filePath = path.resolve(mdPath);
213+
if (!fs.existsSync(filePath)) {
214+
console.error(`error: file not found at ${filePath}`);
215+
process.exit(1);
216+
}
217+
const backup = `${filePath}.${Date.now()}.bak`;
218+
fs.copyFileSync(filePath, backup);
219+
log(`backup created at ${backup}`);
220+
221+
let content = fs.readFileSync(filePath, "utf8");
222+
const existing = content
223+
.split(/\r?\n/)
224+
.filter((line) => line.startsWith("### [Build "))
225+
.map((line) => line.match(/\[Build v?(.*?)\]/)[1]);
226+
227+
let releases;
228+
try {
229+
releases = await fetchAllReleases();
230+
} catch (err) {
231+
console.error("error fetching releases:", err.message);
232+
process.exit(1);
233+
}
234+
235+
const fetched = releases.map((r) => r.tag_name.replace(/^v/, ""));
236+
const missing = [...new Set(fetched)]
237+
.filter((v) => !existing.includes(v))
238+
.sort(compareVersions)
239+
.reverse();
240+
241+
if (!missing.length) {
242+
console.log("✅ no missing builds to insert");
243+
return;
244+
}
245+
246+
missing.forEach((ver) => {
247+
const rel = releases.find((r) => r.tag_name.replace(/^v/, "") === ver);
248+
if (!rel) return;
249+
content = insertSnippet(content, buildSnippet(rel), ver);
250+
console.log(`inserted build v${ver}`);
251+
});
252+
253+
content = reorderSections(content);
254+
log("sections and builds reordered, cleaned");
255+
256+
if (dryRun) {
257+
const tmp = path.join(os.tmpdir(), "services.tmp.md");
258+
fs.writeFileSync(tmp, content, "utf8");
259+
const diff = spawnSync("diff", ["-u", backup, tmp], { encoding: "utf8" });
260+
console.log(diff.stdout || "no differences");
261+
fs.unlinkSync(tmp);
262+
return;
263+
}
264+
265+
try {
266+
fs.writeFileSync(filePath, content, "utf8");
267+
console.log(`✅ updated with builds: ${missing.join(", ")}`);
268+
} catch (err) {
269+
console.error("error writing file:", err.message);
270+
process.exit(1);
271+
}
272+
})();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: 📝 Auto-generate consensus-node release notes
2+
on: workflow_dispatch
3+
# push:
4+
# branches:
5+
# - main
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
update-release-notes:
12+
runs-on: hashgraph-docs-linux-medium
13+
steps:
14+
- name: Checkout docs repo
15+
uses: actions/checkout@v3
16+
17+
- name: Run update script
18+
env:
19+
CONSENSUS_TOKEN: ${{ secrets.CONSENSUS_TOKEN }}
20+
run: |
21+
node .github/scripts/update-services.js \
22+
--md-path networks/release-notes/services.md
23+
24+
- name: Commit & push changes
25+
run: |
26+
git config user.name "github-actions[bot]"
27+
git config user.email "github-actions[bot]@users.noreply.github.com"
28+
git add networks/release-notes/services.md
29+
git commit -m "chore: update release notes" || echo "nothing to commit"
30+
git push

.github/workflows/update-node-tables.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
name: Update Node Tables
22

33
on:
4-
push:
5-
branches:
6-
- main
4+
workflow_dispatch
5+
# push:
6+
# branches:
7+
# - main
78

89
permissions:
910
contents: write
@@ -13,7 +14,7 @@ jobs:
1314
runs-on: hashgraph-docs-linux-medium
1415
steps:
1516
- name: Checkout repository
16-
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
17+
uses: actions/checkout@v3 # v3.6.0
1718

1819
- name: Install dependencies
1920
run: sudo apt-get install -y jq

networks/mainnet/mainnet-nodes/README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,14 @@ For the most up-to-date node list:
4646
| IIT Madras | 22 | **0.0.25** | 13.232.240.207:50211, 13.232.240.207:50212, 34.93.112.7:50211, 34.93.112.7:50212, 89.38.98.73:50211, 89.38.98.73:50212 | 0x8852cb8dcddab369e5719cdbac5e5f50098f882176412ad711e2b03ac6e9fe7035826fd47c12a12742d2c00d5075753f |
4747
| Ubisoft | 25 | **0.0.28** | 18.139.47.5:50211, 18.139.47.5:50212, 35.198.220.75:50211, 35.198.220.75:50212 | 0xb02c9e85f49da18147fd1353773baf0e71d72e22a007f1df9051aa8a4761ae87ad4f90ea4c3409811a1c4777cc2fc61f |
4848
| abrdn | 26 | **0.0.29** | 34.142.71.129:50211, 34.142.71.129:50212, 54.74.60.120:50211, 54.74.60.120:50212, 80.85.70.197:50211, 80.85.70.197:50212 | 0xfff800ea4280d62c9c1ff333cf430194e0f8af282b813b45211328533cf72f9f14644c0604aacf12b165b5a6b059acc3 |
49-
| COFRA Holdings | 28 | **0.0.31** | 217.76.57.165:50211, 217.76.57.165:50212, 3.77.94.254:50211, 3.77.94.254:50212, 34.107.78.179:50211, 34.107.78.179:50212 | 0xf7313bc08b9d4bc794cb1404d1f482a73ac5fe0c0293b5c73647de3841b0591066624e9922172d54212b1b9e6a790908 |
50-
| BitGo | 31 | **0.0.34** | 34.16.139.248:50211, 34.16.139.248:50212, 35.155.212.90:50211, 35.155.212.90:50212 | 0xeadd72fcf60fab34228c729a6d2584ad6542c2e4e785a351d31ec83250d482d40dea6177fcdbcd6e01acb3e80c9b0ca8 |
5149

5250
### Mainnet Node Public Keys
5351

5452
Below you will find the mainnet node public keys found in the mainnet address book file `0.0.102`. You can also access the address book by using the [state proof alpha API](../../../sdks-and-apis/rest-api/) or [SDKs](../../../sdks-and-apis/sdks/address-book.md) as well. The public keys stored in the address book are hex-encoded keys (x509).
5553

5654
| Node Account ID | Public Key |
5755
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
58-
| **0.0.3** | 0x308201a2300d06092a864886f70d01010105000382018f003082018a02820181009098865def2f2ab376c7f0f738c1d87a27ac01afd008620c35cb6ebfcbb0c33003193a388c346d30231727012193bb76fd3004b864312c689ef5213cbb901101509deab94f26a732e637929da4c4cb32517e3adbb3811d50ac4c77c1fce8b651606215f34707f3e7265545e58c894609e28376bdb7775fe30439e0e1592fdcb0c3ee1c305773d072a6b8957eafce1a11be965edaff3843366cb6a44ec25a890106e6247567f76b550fda482baec6307d698ec88841fd66f23f210e47b8a9dcba6ba4e1fa716db33c80e30819496dcb5e5609fb6e7c615379bdded427e9231b9254c2baf943608a86d698ae9a3c8639df887d6f6b5a71385d24338d911a212bf71f1e2acc8b186b96ec8e69c86b6d058217776a09c9c68953edb5916578b5a263b2f469e3b0c07eada71a447eea7f8fc1bb8074255567b7f0bd1e6afb0358718c98b429e24b2298596fc76cf6af396ca9434d7926ec7d37d4b92af56d45feff8196095224a916c1ffe6b667e255fc3ac8cccef920dc044b25003132b87806742f0203010001 |
56+
5957
| **0.0.4** | 0x308201a2300d06092a864886f70d01010105000382018f003082018a02820181009131aa368f9345229f97b6259cccaffea23e00cd5ead02e3f696c1e714ee3939dad860e38bf95a2974f9eb48e9343f8aac405ea955d05323e117b3b1c94813a3af42fe8082c3d43baf1bd4d8367e93db00ad696e627a1036ae534f011ead5e56f37a6ffe44b6b9e099401192ad560a0346b41a810095f5f2d7fd32d6eeb655ba758c6b526c129386af7197c7a53ae603d622832254961f16d0efa8079a768561888be733492217956bbcafaebb6135c5fbb2484d5b4a5fdf0336ac02e26c1652c1bd8eaf30dae1d6d3eb00f7b4fab8d6478fe8d95eb911df966a0dea4e522db76b8966570ecc5af09516424f0af5f8ee66e386d5650713997169ac37573bf52fd058de95ab2ff68e68111ab23405ea964b2bb88d02c0f1caed71ecdd4e4e408594876fdb8500bc55c7ba02066e05ab98d9f7e0466d9702eb57ee3722f8fcc85a75505ff3262170288b788723adb97e4de5620cc90ead1382fcd7571889fefb11e6771bc3f6f3feb19c7ac542878d03a90270526c3eed2494eff54e153ca9f6890203010001 |
6058
| **0.0.5** | 0x308201a2300d06092a864886f70d01010105000382018f003082018a0282018100b2ccac65ad0fc7645a817bfabc487ad7e41311e7a3198b37fb842d84c395b3f67d6bd848f10c6f03c290e8f7daa8d001a8441dc352a19160a3193e68b82edf19ae67693a9a33d4cb87e789a1070715515ea772caa8b86a569b91c5450835d9c354f0dacec97fe77091b45b147698b7f8601422dcd2261e928de4dac9c42dcbafdf96c07233ba3027076f37c969e8ed30b6b5d8f5034be7d92c596f8be861e51fcc3a242bf9d8be9e2a9e8e0f155ebcff23effa7cd57c10542811d80776c9585526fdb0eaa34ee1955d51119390fe873e4c04dedd29165884b98b46308788ae7fc4d4aa4a8fc9bc2674ba321493b624455ad410c1de71bc95d1d91fa0f201418a795e309eaf297b699bf27c9fa2763cd59ceb021e16b8200c1060f2817fd83cfc767183489461e3599291b380d6e939baa4b19232a6a272dde651f8046fdc34db276a777d6fb2bec3255b2cc244b4af566b105f30c6506ddae0eb3deddcf947bcb9c60e000984f3b4a8c6c4ed4bf90bc1932b7f94dc3ae6b360008eb902040f9b0203010001 |
6159
| **0.0.6** | 0x308201a2300d06092a864886f70d01010105000382018f003082018a0282018100a3e37b76c6cd5f6622d6924444d12c677c395f2b5902f3bb98b8a8b5055a707706ca028cd75060a2d8702d2d8b04947bdcfe0a8c141aa2844b1e06e66190012e8b6326ab0fa317973bc7cb4d2949f2108aa04c4b0c91baa5728f5b5622ec75abf578a1f7b41ede2a67ebd69c18e581fdf9c6020ac0de9ca2c31f0c6469003311fbb5ce7db49c787e1a7d27aa425ee7b84da7e66939f9c80d0e82fce55e02dfc8b5c78418a26aa43650698719bafcecf0bd49000addcfa405708bdbefbb19749d22dab007e44d45ea23b106f8834c152e25062d4cf24ff25356c7eb3729105393fb49bab904a02f0f0bb417cd919d352890128e6bbff4fac9f90de118a974f2a6dd01e032a79b178f60fa1fcbbd02b5704fb46295c15190816373edd6635c856978f1b9503f1f73b4b0be8aba2ed1feead59953bf82efde93a3471abd55cda3ba8a673fbb3799749fb006d003f0e63f665c3461d2a7b29dc8b204ba59a65668a46ae2878f00d1f9490df9e280febf4315ea04eaa568a3a9fd48c62c63b6ecda690203010001 |

0 commit comments

Comments
 (0)