diff --git a/.github/scripts/build.mjs b/.github/scripts/build.mjs index 64ce943e..04dbf4cc 100644 --- a/.github/scripts/build.mjs +++ b/.github/scripts/build.mjs @@ -174,6 +174,5 @@ function testcase(name, env, args) { Object.assign(result.with, {use_mocked_data:"yes", verify:"yes"}) } - console.log(arguments, result) return result } \ No newline at end of file diff --git a/source/app/metrics/metadata.mjs b/source/app/metrics/metadata.mjs index c2162543..56935ad8 100644 --- a/source/app/metrics/metadata.mjs +++ b/source/app/metrics/metadata.mjs @@ -40,7 +40,7 @@ export default async function metadata({log = true, diff = false} = {}) { if (!(await fs.promises.lstat(path.join(__plugins, name))).isDirectory()) continue logger(`metrics/metadata > loading plugin metadata [${name}]`) - Plugins[name] = await metadata.plugin({__plugins, name, logger}) + Plugins[name] = await metadata.plugin({__plugins, __templates, name, logger}) } //Reorder keys const {base, core, ...plugins} = Plugins //eslint-disable-line no-unused-vars @@ -70,7 +70,7 @@ export default async function metadata({log = true, diff = false} = {}) { } /**Metadata extractor for templates */ -metadata.plugin = async function({__plugins, name, logger}) { +metadata.plugin = async function({__plugins, __templates, name, logger}) { try { //Load meta descriptor const raw = `${await fs.promises.readFile(path.join(__plugins, name, "metadata.yml"), "utf-8")}` @@ -266,7 +266,21 @@ metadata.plugin = async function({__plugins, name, logger}) { { //Extract demos const raw = `${await fs.promises.readFile(path.join(__plugins, name, "README.md"), "utf-8")}` - const demo = raw.match(/(?[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "" + const demo = meta.examples ? demos({examples:meta.examples}) : raw.match(/(?
[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "" + + //Compatibility + const templates = {} + const compatibility = {} + for (const template of await fs.promises.readdir(__templates)) { + if (!(await fs.promises.lstat(path.join(__templates, template))).isDirectory()) + continue + templates[template] = yaml.load(`${await fs.promises.readFile(path.join(__templates, template, "metadata.yml"), "utf-8")}`) + const partials = path.join(__templates, template, "partials") + if ((fs.existsSync(partials)) && ((await fs.promises.lstat(partials)).isDirectory())) { + const supported = [...await fs.promises.readdir(partials)] + compatibility[template] = !!supported.filter(id => id.match(new RegExp(`^${name}(?:[.][\s\S]+)?[.]ejs$`))).length + } + } //Header table const header = [ @@ -275,100 +289,81 @@ metadata.plugin = async function({__plugins, name, logger}) { ` `, " ", ' ', - //` `, + ` `, " ", " ", ` `, + meta.supports?.includes("user") ? "👤 Users" : "", + meta.supports?.includes("organization") ? "👥 Organizations" : "", + meta.supports?.includes("repository") ? "📓 Repositories" : "" + ].filter(v => v).join(" ")}`, " ", " ", - ` `, + ` `, " ", " ", + demos({colspan:2, examples:meta.examples}), " ", "
${(meta.description ?? "").replaceAll("\n", "
")}
Supported features
→ Full specification
${Object.entries(compatibility).filter(([_, value]) => value).map(([id]) => `${plugins[id].icon}`).join(" ")}${meta.formats?.includes("markdown") ? " ✓ embed()" : ""}${Object.entries(compatibility).filter(([_, value]) => value).map(([id]) => `${templates[id].name ?? ""}`).join(" ")}
${[ - meta.supports?.includes("user") ? "👤 Users" : "", - meta.supports?.includes("organization") ? "👥 Organizations" : "", - meta.supports?.includes("repository") ? "📓 Repositories" : "" - ].filter(v => v).join(", ")}
${[...(meta.scopes ?? []).map(scope => `🔑 ${scope}`), ...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `🗝️ ${token}`)].join(", ")}${[ + ...(meta.scopes ?? []).map(scope => `🔑 ${{public_access:"(scopeless)"}[scope] ?? scope}`), + ...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `🗝️ ${token}`), + ...(meta.scopes?.length ? ["read:org", "read:user", "repo"].map(scope => !meta.scopes.includes(scope) ? `${scope} (optional)` : null).filter(v => v) : []) + ].join(" ")}
" ].join("\n") //Options table - let flags = new Set() const table = [ - "| Option | Type *(format)* **[default]** *{allowed values}* | Description |", - "| ------ | -------------------------------- | ----------- |", + "", + " ", + ' ', + " ", Object.entries(inputs).map(([option, {description, type, ...o}]) => { - let row = [] - { - let cell = [] - if (o.required) { - cell.push("✔️") - flags.add("required") - } - if (type === "token") { - cell.push("🔐") - flags.add("secret") - } - if (o.inherits) { - cell.push("⏩") - flags.add("inherits") - } - if (o.global) { - cell.push("⏭️") - flags.add("global") - } - if (o.testing) { - cell.push("🔧") - flags.add("testing") - } - if (!Object.keys(previous?.inputs ?? {}).includes(option)) { - cell.push("✨") - flags.add("beta") - } - if (o.extras) { - cell.push("🧰") - flags.add("extras") - } - cell = cell.map(flag => `${flag}`) - cell.unshift(`${"`"}${option}${"`"}`) - row.push(cell.join(" ")) + const cell = [] + if (o.required) + cell.push("✔️ Required
") + if (type === "token") + cell.push("🔐 Token
") + if (o.inherits) + cell.push(`⏩ Inherits ${o.inherits}
`) + if (o.global) + cell.push("⏭️ Global option
") + if (o.testing) + cell.push("🔧 For development") + if (!Object.keys(previous?.inputs ?? {}).includes(option)) + cell.push("✨ On master/main
") + if (o.extras) + cell.push("🌐 Web instances must configure settings.json
") + cell.push(`type:${type}`) + if ("format" in o) + cell.push(`(${Array.isArray(o.format) ? o.format[0] : o.format})`) + cell.push("
") + if ("min" in o) + cell.push(`(${o.min} ≤`) + if (("min" in o)||("max" in o)) + cell.push(`${"min" in o ? "" : "("}𝑥${"max" in o ? "" : ")"}`) + if ("max" in o) + cell.push(`≤ ${o.max})`) + if (("default" in o)&&(o.default !== "")) { + let text = o.default + if (o.default === ".user.login") + text = "→ User login" + if (o.default === ".user.twitter") + text = "→ User attached twitter" + if (o.default === ".user.website") + text = "→ User attached website" + cell.push(`default: ${text}
`) } - { - const cell = [`${"`"}${type}${"`"}`] - if ("format" in o) - cell.push(`*(${Array.isArray(o.format) ? o.format[0] : o.format})*`) - if ("default" in o) { - let text = o.default - if (o.default === ".user.login") - text = "*→ User login*" - if (o.default === ".user.twitter") - text = "*→ User attached twitter*" - if (o.default === ".user.website") - text = "*→ User attached website*" - cell.push(`**[${text}]**`) - } - if ("values" in o) - cell.push(`*{${o.values.map(value => `"${value}"`).join(", ")}}*`) - if ("min" in o) - cell.push(`*{${o.min} ≤`) - if (("min" in o)||("max" in o)) - cell.push(`${"min" in o ? "" : "*{"}𝑥${"max" in o ? "" : "}*"}`) - if ("max" in o) - cell.push(`≤ ${o.max}}*`) - row.push(cell.join(" ")) - } - row.push(description) - return `| ${row.join(" | ")} |` + if ("values" in o) + cell.push(`allowed values:`) + return ` + + + + + + ` }).join("\n"), - "\n", - flags.size ? "Legend for option icons:" : "", - flags.has("required") ? "* ✔️ Value must be provided" : "", - flags.has("secret") ? "* 🔐 Value should be stored in repository secrets" : "", - flags.has("inherits") ? "* ⏩ Value inherits from its related global-level option" : "", - flags.has("global") ? "* ⏭️ Value be inherited by its related plugin-level option" : "", - flags.has("testing") ? "* 🔧 For development purposes, use with caution" : "", - flags.has("beta") ? "* ✨ Currently in beta-testing on `master`/`main`" : "", - flags.has("extras") ? "* 🧰 Must be enabled in `settings.json` (for web instances)" : "", + "
TypeDescription
${option}${description}
${cell.join("\n")}
", ].flat(Infinity).filter(s => s).join("\n") //Readme descriptor @@ -405,23 +400,6 @@ metadata.template = async function({__templates, name, plugins, logger}) { } } - //Demo for main and individual readmes - function demo({colspan = null} = {}) { //eslint-disable-line no-inner-declarations - return [ - ` `, - `${Object.entries(meta.examples ?? {}).map(([text, link]) => { - let img = `` - if (text !== "default") { - const open = text.charAt(0) === "+" ? " open" : "" - img = `
${open ? text.substring(1) : text}${img}
` - } - return ` ${img}` - }).join("\n")}`, - ' ', - " " - ].join("\n") - } - //Header table const header = [ "", @@ -433,23 +411,23 @@ metadata.template = async function({__templates, name, plugins, logger}) { " ", " ", ` `, + meta.supports?.includes("user") ? "👤 Users" : "", + meta.supports?.includes("organization") ? "👥 Organizations" : "", + meta.supports?.includes("repository") ? "📓 Repositories" : "" + ].filter(v => v).join(" ")}`, " ", " ", ` `, + meta.formats?.includes("svg") ? "*️⃣ SVG" : "", + meta.formats?.includes("png") ? "*️⃣ PNG" : "", + meta.formats?.includes("jpeg") ? "*️⃣ JPEG" : "", + meta.formats?.includes("json") ? "#️⃣ JSON" : "", + meta.formats?.includes("markdown") ? "🔠 Markdown" : "", + meta.formats?.includes("markdown-pdf") ? "🔠 Markdown (PDF)" : "", + ].filter(v => v).join(" ")}`, " ", " ", - demo({colspan:2}), + demos({colspan:2, examples:meta.examples}), " ", "
${[ - meta.supports?.includes("user") ? "👤 Users" : "", - meta.supports?.includes("organization") ? "👥 Organizations" : "", - meta.supports?.includes("repository") ? "📓 Repositories" : "" - ].filter(v => v).join(", ")}
${[ - meta.formats?.includes("svg") ? "*️⃣ SVG" : "", - meta.formats?.includes("png") ? "*️⃣ PNG" : "", - meta.formats?.includes("jpeg") ? "*️⃣ JPEG" : "", - meta.formats?.includes("json") ? "#️⃣ JSON" : "", - meta.formats?.includes("markdown") ? "🔠 Markdown" : "", - meta.formats?.includes("markdown-pdf") ? "🔠 Markdown (PDF)" : "", - ].filter(v => v).join(", ")}
" ].join("\n") @@ -462,7 +440,7 @@ metadata.template = async function({__templates, name, plugins, logger}) { formats:meta.formats ?? null, supports:meta.supports ?? null, readme:{ - demo:demo(), + demo:demos({examples:meta.examples}), compatibility:{ ...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => value)), ...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => !value).map(([key, value]) => [key, meta.formats?.includes("markdown") ? "embed" : value])), @@ -496,3 +474,22 @@ metadata.to = { return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key }, } + +//Demo for main and individual readmes +function demos({colspan = null, examples = {}} = {}) { + return [ + ` `, + `${Object.entries(examples).map(([text, link]) => { + let img = `` + if (text !== "default") { + const open = text.charAt(0) === "+" ? " open" : "" + text = open ? text.substring(1) : text + text = `${text.charAt(0).toLocaleUpperCase()}${text.substring(1)}` + img = `${text}${img}` + } + return ` ${img}` + }).join("\n")}`, + ' ', + " " + ].join("\n") +}