import { XMLParser } from "fast-xml-parser"; import { createWriteStream } from "node:fs"; import { createRequire } from "node:module"; const { upstream } = createRequire(import.meta.url)("../package.json"); const source = "https://forgejo.nishi.boats/api/v1/repos/pyrite-dev/milsko/tags"; // Out files const structsOut = createWriteStream("lib/structs.ts"); const enumsOut = createWriteStream("lib/enums.ts"); const constantsOut = createWriteStream("lib/constants.ts"); function toCamelCase(input) { const split = input.split("_"); for (let i = 0; i < split.length; i++) { const firstChar = i <= 0 ? split[i].charAt(0).toLowerCase() : split[i].charAt(0).toUpperCase(); split[i] = firstChar + split[i].slice(1); } return split.join(""); } function typeToTypescript(type) { switch (type) { case "integer": return "number"; case "short": return "number"; case "string": return "string"; default: "any"; } } function scanStructs(root) { const list = root["structs"]["struct"]; for (const struct of list) { console.log(struct); const structName = struct["@_name"]; structsOut.write("export interface " + structName + " {\n"); for (const [type, children] of Object.entries(struct)) { // attributes start with @_ if (!type.startsWith("@_") && Array.isArray(children)) { for (const child of children) { const attributeName = toCamelCase(child["@_name"]); structsOut.write( attributeName + ": " + typeToTypescript(type) + ";\n" ); } } } structsOut.write("}\n\n"); } structsOut.end(); } function scanEnumerations(root) { const list = root["enumerations"]["enumeration"]; for (const enumeration of list) { const enumName = enumeration["@_name"]; enumsOut.write("export enum " + enumName + " {\n"); for (const [type, children] of Object.entries(enumeration)) { // attributes start with @_ if (!type.startsWith("@_")) { for (const child of children) { const attributeName = child["@_name"]; const attributeValue = child["#text"]; enumsOut.write(attributeName); if (attributeValue) enumsOut.write(" = " + attributeValue); enumsOut.write(",\n"); } } } enumsOut.write("}\n\n"); } enumsOut.end(); } function scanConstants(root) { const list = root["constants"]; for (const [type, children] of Object.entries(list)) { for (const child of children) { const constName = child["@_name"]; const constValue = child["#text"]; constantsOut.write( "export const " + constName + ": " + typeToTypescript(type) + " = " + constValue + ";\n" ); } } constantsOut.end(); } async function bindgen() { const parser = new XMLParser({ attributeNamePrefix: "@_", ignoreAttributes: false, parseAttributeValue: true }); const response = await fetch(source); if (!response.ok) { throw new Error( "Failed to fetch the Milsko repository tags, try running npm run install or npm run update again (" + response.status + ")" ); } const tags = await response.json(); const match = tags.find((k) => k.name == upstream); if (!match) { throw new Error( "Could not find a tag that matches the upstream version " + upstream ); } const commitHash = match["id"] const specURL = "https://forgejo.nishi.boats/pyrite-dev/milsko/raw/commit/" + commitHash + "/milsko.xml"; const spac = await fetch(specURL); if (!spac.ok) { throw new Error( "Failed to fetch the XML API specification with status code " + spac.status ); } const raw = await spac.text(); const root = parser.parse(raw)["milsko"]; scanStructs(root); scanEnumerations(root); scanConstants(root); } bindgen();