chore: code formatting
This commit is contained in:
@@ -10,7 +10,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
//Debug
|
||||
login = login.replace(/[\n\r]/g, "")
|
||||
console.debug(`metrics/compute/${login} > start`)
|
||||
console.debug(util.inspect(q, {depth:Infinity, maxStringLength:256}))
|
||||
console.debug(util.inspect(q, {depth: Infinity, maxStringLength: 256}))
|
||||
|
||||
//Load template
|
||||
const template = q.template || conf.settings.templates.default
|
||||
@@ -24,14 +24,14 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
//Initialization
|
||||
const pending = []
|
||||
const {queries} = conf
|
||||
const extras = {css:(conf.settings.extras?.css ?? conf.settings.extras?.default) ? q["extras.css"] ?? "" : "", js:(conf.settings.extras?.js ?? conf.settings.extras?.default) ? q["extras.js"] ?? "" : ""}
|
||||
const data = {q, animated:true, large:false, base:{}, config:{}, errors:[], plugins:{}, computed:{}, extras, postscripts:[]}
|
||||
const extras = {css: (conf.settings.extras?.css ?? conf.settings.extras?.default) ? q["extras.css"] ?? "" : "", js: (conf.settings.extras?.js ?? conf.settings.extras?.default) ? q["extras.js"] ?? "" : ""}
|
||||
const data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []}
|
||||
const imports = {
|
||||
plugins:Plugins,
|
||||
templates:Templates,
|
||||
metadata:conf.metadata,
|
||||
plugins: Plugins,
|
||||
templates: Templates,
|
||||
metadata: conf.metadata,
|
||||
...utils,
|
||||
...utils.formatters({timeZone:q["config.timezone"]}),
|
||||
...utils.formatters({timeZone: q["config.timezone"]}),
|
||||
...(/markdown/.test(convert)
|
||||
? {
|
||||
imgb64(url, options) {
|
||||
@@ -60,7 +60,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
//Executing base plugin and compute metrics
|
||||
console.debug(`metrics/compute/${login} > compute`)
|
||||
await Plugins.base({login, q, data, rest, graphql, plugins, queries, pending, imports}, conf)
|
||||
await computer({login, q}, {conf, data, rest, graphql, plugins, queries, account:data.account, convert, template}, {pending, imports})
|
||||
await computer({login, q}, {conf, data, rest, graphql, plugins, queries, account: data.account, convert, template}, {pending, imports})
|
||||
const promised = await Promise.all(pending)
|
||||
|
||||
//Check plugins errors
|
||||
@@ -70,7 +70,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
if (die)
|
||||
throw new Error("An error occured during rendering, dying")
|
||||
else
|
||||
console.debug(util.inspect(errors, {depth:Infinity, maxStringLength:256}))
|
||||
console.debug(util.inspect(errors, {depth: Infinity, maxStringLength: 256}))
|
||||
}
|
||||
|
||||
//JSON output
|
||||
@@ -89,7 +89,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
}
|
||||
return value
|
||||
}))
|
||||
return {rendered, mime:"application/json"}
|
||||
return {rendered, mime: "application/json"}
|
||||
}
|
||||
|
||||
//Markdown output
|
||||
@@ -100,12 +100,12 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
try {
|
||||
let template = `${q.markdown}`.replace(/\n/g, "")
|
||||
if (!/^https:/.test(template)) {
|
||||
const {data:{default_branch:branch, full_name:repo}} = await rest.repos.get({owner:login, repo:q.repo || login})
|
||||
const {data: {default_branch: branch, full_name: repo}} = await rest.repos.get({owner: login, repo: q.repo || login})
|
||||
console.debug(`metrics/compute/${login} > on ${repo} with default branch ${branch}`)
|
||||
template = `https://raw.githubusercontent.com/${repo}/${branch}/${template}`
|
||||
}
|
||||
console.debug(`metrics/compute/${login} > fetching ${template}`)
|
||||
;({data:source} = await imports.axios.get(template, {headers:{Accept:"text/plain"}}))
|
||||
;({data: source} = await imports.axios.get(template, {headers: {Accept: "text/plain"}}))
|
||||
}
|
||||
catch (error) {
|
||||
console.debug(error)
|
||||
@@ -123,7 +123,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
console.debug(`metrics/compute/${login} > embed called with`)
|
||||
console.debug(q)
|
||||
let {base} = q
|
||||
q = {..._q, ...Object.fromEntries(Object.keys(Plugins).map(key => [key, false])), ...Object.fromEntries(conf.settings.plugins.base.parts.map(part => [`base.${part}`, false])), template:q.repo ? "repository" : "classic", ...q}
|
||||
q = {..._q, ...Object.fromEntries(Object.keys(Plugins).map(key => [key, false])), ...Object.fromEntries(conf.settings.plugins.base.parts.map(part => [`base.${part}`, false])), template: q.repo ? "repository" : "classic", ...q}
|
||||
//Translate action syntax to web syntax
|
||||
let parts = []
|
||||
if (base === true)
|
||||
@@ -140,33 +140,33 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
}
|
||||
q = Object.fromEntries([...Object.entries(q).map(([key, value]) => [key.replace(/^plugin_/, "").replace(/_/g, "."), value]), ["base", false]])
|
||||
//Compute rendering
|
||||
const {rendered} = await metrics({login, q}, {...arguments[1], convert:["svg", "png", "jpeg"].includes(q["config.output"]) ? q["config.output"] : null}, arguments[2])
|
||||
const {rendered} = await metrics({login, q}, {...arguments[1], convert: ["svg", "png", "jpeg"].includes(q["config.output"]) ? q["config.output"] : null}, arguments[2])
|
||||
console.debug(`metrics/compute/${login}/embed > ${name} > success >>>>>>>>>>>>>>>>>>>>>>`)
|
||||
return `<img class="metrics-cachable" data-name="${name}" src="data:image/${{png:"png", jpeg:"jpeg"}[q["config.output"]] ?? "svg+xml"};base64,${Buffer.from(rendered).toString("base64")}">`
|
||||
return `<img class="metrics-cachable" data-name="${name}" src="data:image/${{png: "png", jpeg: "jpeg"}[q["config.output"]] ?? "svg+xml"};base64,${Buffer.from(rendered).toString("base64")}">`
|
||||
}
|
||||
//Rendering template source
|
||||
let rendered = source.replace(/\{\{ (?<content>[\s\S]*?) \}\}/g, "{%= $<content> %}")
|
||||
console.debug(rendered)
|
||||
for (const delimiters of [{openDelimiter:"<", closeDelimiter:">"}, {openDelimiter:"{", closeDelimiter:"}"}])
|
||||
rendered = await ejs.render(rendered, {...data, s:imports.s, f:imports.format, embed}, {views, async:true, ...delimiters})
|
||||
for (const delimiters of [{openDelimiter: "<", closeDelimiter: ">"}, {openDelimiter: "{", closeDelimiter: "}"}])
|
||||
rendered = await ejs.render(rendered, {...data, s: imports.s, f: imports.format, embed}, {views, async: true, ...delimiters})
|
||||
console.debug(`metrics/compute/${login} > success`)
|
||||
//Output
|
||||
if (convert === "markdown-pdf") {
|
||||
return imports.svg.pdf(rendered, {
|
||||
paddings:q["config.padding"] || conf.settings.padding,
|
||||
style:extras.css,
|
||||
twemojis:q["config.twemoji"],
|
||||
gemojis:q["config.gemoji"],
|
||||
octicons:q["config.octicon"],
|
||||
paddings: q["config.padding"] || conf.settings.padding,
|
||||
style: extras.css,
|
||||
twemojis: q["config.twemoji"],
|
||||
gemojis: q["config.gemoji"],
|
||||
octicons: q["config.octicon"],
|
||||
rest,
|
||||
})
|
||||
}
|
||||
return {rendered, mime:"text/html"}
|
||||
return {rendered, mime: "text/html"}
|
||||
}
|
||||
|
||||
//Rendering
|
||||
console.debug(`metrics/compute/${login} > render`)
|
||||
let rendered = await ejs.render(image, {...data, s:imports.s, f:imports.format, style, fonts}, {views, async:true})
|
||||
let rendered = await ejs.render(image, {...data, s: imports.s, f: imports.format, style, fonts}, {views, async: true})
|
||||
|
||||
//Additional transformations
|
||||
if (q["config.twemoji"])
|
||||
@@ -192,7 +192,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
console.debug(`metrics/compute/${login} > verified SVG, no parsing errors found`)
|
||||
}
|
||||
//Resizing
|
||||
const {resized, mime} = await imports.svg.resize(rendered, {paddings:q["config.padding"] || conf.settings.padding, convert:convert === "svg" ? null : convert, scripts:[...data.postscripts, extras.js || null].filter(x => x)})
|
||||
const {resized, mime} = await imports.svg.resize(rendered, {paddings: q["config.padding"] || conf.settings.padding, convert: convert === "svg" ? null : convert, scripts: [...data.postscripts, extras.js || null].filter(x => x)})
|
||||
rendered = resized
|
||||
|
||||
//Result
|
||||
@@ -212,37 +212,37 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
||||
//Metrics insights
|
||||
metrics.insights = async function({login}, {graphql, rest, conf}, {Plugins, Templates}) {
|
||||
const q = {
|
||||
template:"classic",
|
||||
achievements:true,
|
||||
"achievements.threshold":"X",
|
||||
isocalendar:true,
|
||||
"isocalendar.duration":"full-year",
|
||||
languages:true,
|
||||
"languages.limit":0,
|
||||
activity:true,
|
||||
"activity.limit":100,
|
||||
"activity.days":0,
|
||||
notable:true,
|
||||
followup:true,
|
||||
"followup.sections":"repositories, user",
|
||||
habits:true,
|
||||
"habits.from":100,
|
||||
"habits.days":7,
|
||||
"habits.facts":false,
|
||||
"habits.charts":true,
|
||||
introduction:true,
|
||||
template: "classic",
|
||||
achievements: true,
|
||||
"achievements.threshold": "X",
|
||||
isocalendar: true,
|
||||
"isocalendar.duration": "full-year",
|
||||
languages: true,
|
||||
"languages.limit": 0,
|
||||
activity: true,
|
||||
"activity.limit": 100,
|
||||
"activity.days": 0,
|
||||
notable: true,
|
||||
followup: true,
|
||||
"followup.sections": "repositories, user",
|
||||
habits: true,
|
||||
"habits.from": 100,
|
||||
"habits.days": 7,
|
||||
"habits.facts": false,
|
||||
"habits.charts": true,
|
||||
introduction: true,
|
||||
}
|
||||
const plugins = {
|
||||
achievements:{enabled:true},
|
||||
isocalendar:{enabled:true},
|
||||
languages:{enabled:true, extras:false},
|
||||
activity:{enabled:true, markdown:"extended"},
|
||||
notable:{enabled:true},
|
||||
followup:{enabled:true},
|
||||
habits:{enabled:true, extras:false},
|
||||
introduction:{enabled:true},
|
||||
achievements: {enabled: true},
|
||||
isocalendar: {enabled: true},
|
||||
languages: {enabled: true, extras: false},
|
||||
activity: {enabled: true, markdown: "extended"},
|
||||
notable: {enabled: true},
|
||||
followup: {enabled: true},
|
||||
habits: {enabled: true, extras: false},
|
||||
introduction: {enabled: true},
|
||||
}
|
||||
return metrics({login, q}, {graphql, rest, plugins, conf, convert:"json"}, {Plugins, Templates})
|
||||
return metrics({login, q}, {graphql, rest, plugins, conf, convert: "json"}, {Plugins, Templates})
|
||||
}
|
||||
|
||||
//Metrics insights static render
|
||||
@@ -260,7 +260,7 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
|
||||
await page.goto(`${server}/about/${login}?embed=1&localstorage=1`)
|
||||
await page.evaluate(async json => localStorage.setItem("local.metrics", json), json) //eslint-disable-line no-undef
|
||||
await page.goto(`${server}/about/${login}?embed=1&localstorage=1`)
|
||||
await page.waitForSelector(".container .user", {timeout:10 * 60 * 1000})
|
||||
await page.waitForSelector(".container .user", {timeout: 10 * 60 * 1000})
|
||||
|
||||
//Rendering
|
||||
console.debug(`metrics/compute/${login} > insights > rendering data`)
|
||||
@@ -273,9 +273,9 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
|
||||
</head>
|
||||
<body>
|
||||
${await page.evaluate(() => document.querySelector("main").outerHTML)}
|
||||
${(await Promise.all([".css/style.vars.css", ".css/style.css", "about/.statics/style.css"].map(path => utils.axios.get(`${server}/${path}`)))).map(({data:style}) => `<style>${style}</style>`).join("\n")}
|
||||
${(await Promise.all([".css/style.vars.css", ".css/style.css", "about/.statics/style.css"].map(path => utils.axios.get(`${server}/${path}`)))).map(({data: style}) => `<style>${style}</style>`).join("\n")}
|
||||
</body>
|
||||
</html>`
|
||||
await browser.close()
|
||||
return {mime:"text/html", rendered}
|
||||
return {mime: "text/html", rendered}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//Imports
|
||||
import fs from "fs"
|
||||
import yaml from "js-yaml"
|
||||
import {marked} from "marked"
|
||||
import { marked } from "marked"
|
||||
import fetch from "node-fetch"
|
||||
import path from "path"
|
||||
import url from "url"
|
||||
@@ -13,7 +13,7 @@ const categories = ["core", "github", "social", "community"]
|
||||
let previous = null
|
||||
|
||||
//Environment
|
||||
const env = {ghactions:`${process.env.GITHUB_ACTIONS}` === "true"}
|
||||
const env = {ghactions: `${process.env.GITHUB_ACTIONS}` === "true"}
|
||||
|
||||
/**Metadata descriptor parser */
|
||||
export default async function metadata({log = true, diff = false} = {}) {
|
||||
@@ -50,7 +50,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 [community/${name}]`)
|
||||
Plugins[name] = await metadata.plugin({__plugins:___plugins, __templates, name, logger})
|
||||
Plugins[name] = await metadata.plugin({__plugins: ___plugins, __templates, name, logger})
|
||||
Plugins[name].community = true
|
||||
}
|
||||
continue
|
||||
@@ -84,7 +84,7 @@ export default async function metadata({log = true, diff = false} = {}) {
|
||||
const descriptor = yaml.load(`${await fs.promises.readFile(__descriptor, "utf-8")}`)
|
||||
|
||||
//Metadata
|
||||
return {plugins:Plugins, templates:Templates, packaged, descriptor, env}
|
||||
return {plugins: Plugins, templates: Templates, packaged, descriptor, env}
|
||||
}
|
||||
|
||||
/**Metadata extractor for inputs */
|
||||
@@ -104,14 +104,14 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
|
||||
//Inputs parser
|
||||
{
|
||||
meta.inputs = function({data:{user = null} = {}, q, account}, defaults = {}) {
|
||||
meta.inputs = function({data: {user = null} = {}, q, account}, defaults = {}) {
|
||||
//Support check
|
||||
if (!account)
|
||||
console.debug(`metrics/inputs > account type not set for plugin ${name}!`)
|
||||
if (account !== "bypass") {
|
||||
const context = q.repo ? "repository" : account
|
||||
if (!meta.supports?.includes(context))
|
||||
throw {error:{message:`Not supported for: ${context}`, instance:new Error()}}
|
||||
throw {error: {message: `Not supported for: ${context}`, instance: new Error()}}
|
||||
}
|
||||
//Special values replacer
|
||||
const replacer = value => {
|
||||
@@ -128,7 +128,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
}
|
||||
//Inputs checks
|
||||
const result = Object.fromEntries(
|
||||
Object.entries(inputs).map(([key, {type, format, default:defaulted, min, max, values, inherits:_inherits}]) => [
|
||||
Object.entries(inputs).map(([key, {type, format, default: defaulted, min, max, values, inherits: _inherits}]) => [
|
||||
//Format key
|
||||
metadata.to.query(key, {name}),
|
||||
//Format value
|
||||
@@ -165,7 +165,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
logger(`metrics/inputs > failed to decode uri : ${value}`)
|
||||
value = defaulted
|
||||
}
|
||||
const separators = {"comma-separated":",", "space-separated":" "}
|
||||
const separators = {"comma-separated": ",", "space-separated": " "}
|
||||
const separator = separators[[format].flat().filter(s => s in separators)[0]] ?? ","
|
||||
return value.split(separator).map(v => replacer(v).toLocaleLowerCase()).filter(v => Array.isArray(values) ? values.includes(v) : true).filter(v => v)
|
||||
}
|
||||
@@ -230,13 +230,14 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
Object.entries(inputs).map(([key, value]) => [
|
||||
key,
|
||||
{
|
||||
comment:"",
|
||||
descriptor:yaml.dump({
|
||||
[key]:Object.fromEntries(
|
||||
Object.entries(value).filter(([key]) => ["description", "default", "required"].includes(key)).map(([k, v]) => k === "description" ? [k, v.split("\n")[0]] : k === "default" ? [k, ((/^\$\{\{[\s\S]+\}\}$/.test(v)) || (["config_presets", "config_timezone", "use_prebuilt_image"].includes(key))) ? v : "<default-value>"] : [k, v]
|
||||
comment: "",
|
||||
descriptor: yaml.dump({
|
||||
[key]: Object.fromEntries(
|
||||
Object.entries(value).filter(([key]) => ["description", "default", "required"].includes(key)).map(([k, v]) =>
|
||||
k === "description" ? [k, v.split("\n")[0]] : k === "default" ? [k, ((/^\$\{\{[\s\S]+\}\}$/.test(v)) || (["config_presets", "config_timezone", "use_prebuilt_image"].includes(key))) ? v : "<default-value>"] : [k, v]
|
||||
),
|
||||
),
|
||||
}, {quotingType:'"', noCompatMode:true}),
|
||||
}, {quotingType: '"', noCompatMode: true}),
|
||||
},
|
||||
]),
|
||||
)
|
||||
@@ -258,9 +259,9 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
value = "<default-value>"
|
||||
}
|
||||
}
|
||||
else
|
||||
else {
|
||||
value = process.env[`INPUT_${key.toUpperCase()}`]?.trim() ?? "<default-value>"
|
||||
|
||||
}
|
||||
|
||||
const unspecified = value === "<default-value>"
|
||||
//From presets
|
||||
@@ -279,32 +280,32 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
q[key] = value
|
||||
}
|
||||
}
|
||||
return meta.inputs({q, account:"bypass"})
|
||||
return meta.inputs({q, account: "bypass"})
|
||||
}
|
||||
}
|
||||
|
||||
//Web metadata
|
||||
{
|
||||
meta.web = Object.fromEntries(
|
||||
Object.entries(inputs).map(([key, {type, description:text, example, default:defaulted, min = 0, max = 9999, values}]) => [
|
||||
Object.entries(inputs).map(([key, {type, description: text, example, default: defaulted, min = 0, max = 9999, values}]) => [
|
||||
//Format key
|
||||
metadata.to.query(key),
|
||||
//Value descriptor
|
||||
(() => {
|
||||
switch (type) {
|
||||
case "boolean":
|
||||
return {text, type:"boolean", defaulted:/^(?:[Tt]rue|[Oo]n|[Yy]es|1)$/.test(defaulted) ? true : /^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(defaulted) ? false : defaulted}
|
||||
return {text, type: "boolean", defaulted: /^(?:[Tt]rue|[Oo]n|[Yy]es|1)$/.test(defaulted) ? true : /^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(defaulted) ? false : defaulted}
|
||||
case "number":
|
||||
return {text, type:"number", min, max, defaulted}
|
||||
return {text, type: "number", min, max, defaulted}
|
||||
case "array":
|
||||
return {text, type:"text", placeholder:example ?? defaulted, defaulted}
|
||||
return {text, type: "text", placeholder: example ?? defaulted, defaulted}
|
||||
case "string": {
|
||||
if (Array.isArray(values))
|
||||
return {text, type:"select", values, defaulted}
|
||||
return {text, type:"text", placeholder:example ?? defaulted, defaulted}
|
||||
return {text, type: "select", values, defaulted}
|
||||
return {text, type: "text", placeholder: example ?? defaulted, defaulted}
|
||||
}
|
||||
case "json":
|
||||
return {text, type:"text", placeholder:example ?? defaulted, defaulted}
|
||||
return {text, type: "text", placeholder: example ?? defaulted, defaulted}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
@@ -317,7 +318,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
{
|
||||
//Extract demos
|
||||
const raw = `${await fs.promises.readFile(path.join(__plugins, name, "README.md"), "utf-8")}`
|
||||
const demo = meta.examples ? demos({examples:meta.examples}) : raw.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "<td></td>"
|
||||
const demo = meta.examples ? demos({examples: meta.examples}) : raw.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? "<td></td>"
|
||||
|
||||
//Compatibility
|
||||
const templates = {}
|
||||
@@ -337,7 +338,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
const header = [
|
||||
"<table>",
|
||||
` <tr><th colspan="2"><h3>${meta.name}</h3></th></tr>`,
|
||||
` <tr><td colspan="2" align="center">${marked.parse(meta.description ?? "", {silent:true})}</td></tr>`,
|
||||
` <tr><td colspan="2" align="center">${marked.parse(meta.description ?? "", {silent: true})}</td></tr>`,
|
||||
meta.authors?.length ? `<tr><th>Authors</th><td>${[meta.authors].flat().map(author => `<a href="https://github.com/${author}">@${author}</a>`)}</td></tr>` : "",
|
||||
" <tr>",
|
||||
' <th rowspan="3">Supported features<br><sub><a href="metadata.yml">→ Full specification</a></sub></th>',
|
||||
@@ -353,14 +354,16 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
}</td>`,
|
||||
" </tr>",
|
||||
" <tr>",
|
||||
` <td>${[
|
||||
...(meta.scopes ?? []).map(scope => `<code>🔑 ${{public_access:"(scopeless)"}[scope] ?? scope}</code>`),
|
||||
...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `<code>🗝️ ${token}</code>`),
|
||||
...(meta.scopes?.length ? ["read:org", "read:user", "read:packages", "repo"].map(scope => !meta.scopes.includes(scope) ? `<code>${scope} (optional)</code>` : null).filter(v => v) : []),
|
||||
].filter(v => v).join(" ") || "<i>No tokens are required for this plugin</i>"}</td>`,
|
||||
` <td>${
|
||||
[
|
||||
...(meta.scopes ?? []).map(scope => `<code>🔑 ${{public_access: "(scopeless)"}[scope] ?? scope}</code>`),
|
||||
...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `<code>🗝️ ${token}</code>`),
|
||||
...(meta.scopes?.length ? ["read:org", "read:user", "read:packages", "repo"].map(scope => !meta.scopes.includes(scope) ? `<code>${scope} (optional)</code>` : null).filter(v => v) : []),
|
||||
].filter(v => v).join(" ") || "<i>No tokens are required for this plugin</i>"
|
||||
}</td>`,
|
||||
" </tr>",
|
||||
" <tr>",
|
||||
demos({colspan:2, wrap:name === "base", examples:meta.examples}),
|
||||
demos({colspan: 2, wrap: name === "base", examples: meta.examples}),
|
||||
" </tr>",
|
||||
"</table>",
|
||||
].filter(v => v).join("\n")
|
||||
@@ -415,7 +418,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||
cell.push(`<b>allowed values:</b><ul>${o.values.map(value => `<li>${value}</li>`).join("")}</ul>`)
|
||||
return ` <tr>
|
||||
<td nowrap="nowrap"><h4><code>${option}</code></h4></td>
|
||||
<td rowspan="2">${marked.parse(description, {silent:true})}<img width="900" height="1" alt=""></td>
|
||||
<td rowspan="2">${marked.parse(description, {silent: true})}<img width="900" height="1" alt=""></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td nowrap="nowrap">${cell.join("\n")}</td>
|
||||
@@ -462,7 +465,7 @@ metadata.template = async function({__templates, name, plugins}) {
|
||||
const header = [
|
||||
"<table>",
|
||||
` <tr><th colspan="2"><h3>${meta.name ?? "(unnamed template)"}</h3></th></tr>`,
|
||||
` <tr><td colspan="2" align="center">${marked.parse(meta.description ?? "", {silent:true})}</td></tr>`,
|
||||
` <tr><td colspan="2" align="center">${marked.parse(meta.description ?? "", {silent: true})}</td></tr>`,
|
||||
" <tr>",
|
||||
' <th rowspan="3">Supported features<br><sub><a href="metadata.yml">→ Full specification</a></sub></th>',
|
||||
` <td>${Object.entries(compatibility).filter(([_, value]) => value).map(([id]) => `<a href="/source/plugins/${id}/README.md" title="${plugins[id].name}">${plugins[id].icon}</a>`).join(" ")}${meta.formats?.includes("markdown") ? " <code>✓ embed()</code>" : ""}</td>`,
|
||||
@@ -489,24 +492,24 @@ metadata.template = async function({__templates, name, plugins}) {
|
||||
}</td>`,
|
||||
" </tr>",
|
||||
" <tr>",
|
||||
demos({colspan:2, examples:meta.examples}),
|
||||
demos({colspan: 2, examples: meta.examples}),
|
||||
" </tr>",
|
||||
"</table>",
|
||||
].join("\n")
|
||||
|
||||
//Result
|
||||
return {
|
||||
name:meta.name ?? "(unnamed template)",
|
||||
description:meta.description ?? "",
|
||||
index:meta.index ?? null,
|
||||
formats:meta.formats ?? null,
|
||||
supports:meta.supports ?? null,
|
||||
readme:{
|
||||
demo:demos({examples:meta.examples}),
|
||||
compatibility:{
|
||||
name: meta.name ?? "(unnamed template)",
|
||||
description: meta.description ?? "",
|
||||
index: meta.index ?? null,
|
||||
formats: meta.formats ?? null,
|
||||
supports: meta.supports ?? null,
|
||||
readme: {
|
||||
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])),
|
||||
base:true,
|
||||
base: true,
|
||||
},
|
||||
header,
|
||||
},
|
||||
|
||||
@@ -7,8 +7,8 @@ import metadata from "./metadata.mjs"
|
||||
/**Presets parser */
|
||||
export default async function presets(list, {log = true, core = null} = {}) {
|
||||
//Init
|
||||
const {plugins} = await metadata({log:false})
|
||||
const {"config.presets":files} = plugins.core.inputs({q:{"config.presets":list}, account:"bypass"})
|
||||
const {plugins} = await metadata({log: false})
|
||||
const {"config.presets": files} = plugins.core.inputs({q: {"config.presets": list}, account: "bypass"})
|
||||
const logger = log ? console.debug : () => null
|
||||
const allowed = Object.entries(metadata.inputs).filter(([_, {type, preset}]) => (type !== "token") && (!/^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(preset))).map(([key]) => key)
|
||||
const env = core ? "action" : "web"
|
||||
@@ -36,7 +36,7 @@ export default async function presets(list, {log = true, core = null} = {}) {
|
||||
logger(`metrics/presets > ${file} cannot be loaded in current environment ${env}, skipping`)
|
||||
continue
|
||||
}
|
||||
const {schema, with:inputs} = yaml.load(text)
|
||||
const {schema, with: inputs} = yaml.load(text)
|
||||
logger(`metrics/presets > ${file} preset schema is ${schema}`)
|
||||
|
||||
//Evaluate preset
|
||||
|
||||
@@ -27,15 +27,15 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
const logger = log ? console.debug : () => null
|
||||
logger("metrics/setup > setup")
|
||||
const conf = {
|
||||
authenticated:null,
|
||||
templates:{},
|
||||
queries:{},
|
||||
settings:{port:3000},
|
||||
metadata:{},
|
||||
paths:{
|
||||
statics:__statics,
|
||||
templates:__templates,
|
||||
node_modules:__modules,
|
||||
authenticated: null,
|
||||
templates: {},
|
||||
queries: {},
|
||||
settings: {port: 3000},
|
||||
metadata: {},
|
||||
paths: {
|
||||
statics: __statics,
|
||||
templates: __templates,
|
||||
node_modules: __modules,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -64,13 +64,13 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
}
|
||||
|
||||
if (!conf.settings.templates)
|
||||
conf.settings.templates = {default:"classic", enabled:[]}
|
||||
conf.settings.templates = {default: "classic", enabled: []}
|
||||
if (!conf.settings.plugins)
|
||||
conf.settings.plugins = {}
|
||||
conf.settings.community = {...conf.settings.community, ...community}
|
||||
conf.settings.plugins.base = {parts:["header", "activity", "community", "repositories", "metadata"]}
|
||||
conf.settings.plugins.base = {parts: ["header", "activity", "community", "repositories", "metadata"]}
|
||||
if (conf.settings.debug)
|
||||
logger(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256}))
|
||||
logger(util.inspect(conf.settings, {depth: Infinity, maxStringLength: 256}))
|
||||
|
||||
//Load package settings
|
||||
logger("metrics/setup > load package.json")
|
||||
@@ -85,7 +85,7 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
if ((Array.isArray(conf.settings.community.templates)) && (conf.settings.community.templates.length)) {
|
||||
//Clean remote repository
|
||||
logger(`metrics/setup > ${conf.settings.community.templates.length} community templates to install`)
|
||||
await fs.promises.rm(path.join(__templates, ".community"), {recursive:true, force:true})
|
||||
await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true})
|
||||
//Download community templates
|
||||
for (const template of conf.settings.community.templates) {
|
||||
try {
|
||||
@@ -95,10 +95,10 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}`
|
||||
logger(`metrics/setup > run ${command}`)
|
||||
//Clone remote repository
|
||||
processes.execSync(command, {stdio:"ignore"})
|
||||
processes.execSync(command, {stdio: "ignore"})
|
||||
//Extract template
|
||||
logger(`metrics/setup > extract ${name} from ${repo}@${branch}`)
|
||||
await fs.promises.rm(path.join(__templates, `@${name}`), {recursive:true, force:true})
|
||||
await fs.promises.rm(path.join(__templates, `@${name}`), {recursive: true, force: true})
|
||||
await fs.promises.rename(path.join(__templates, ".community/source/templates", name), path.join(__templates, `@${name}`))
|
||||
//JavaScript file
|
||||
if (trust)
|
||||
@@ -113,18 +113,18 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
logger(`metrics/setup > @${name} extended from ${inherit}`)
|
||||
await fs.promises.copyFile(path.join(__templates, inherit, "template.mjs"), path.join(__templates, `@${name}`, "template.mjs"))
|
||||
}
|
||||
else
|
||||
else {
|
||||
logger(`metrics/setup > @${name} could not extends ${inherit} as it does not exist`)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else {
|
||||
logger(`metrics/setup > @${name}/template.mjs does not exist`)
|
||||
|
||||
}
|
||||
|
||||
//Clean remote repository
|
||||
logger(`metrics/setup > clean ${repo}@${branch}`)
|
||||
await fs.promises.rm(path.join(__templates, ".community"), {recursive:true, force:true})
|
||||
await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true})
|
||||
logger(`metrics/setup > loaded community template ${name}`)
|
||||
}
|
||||
catch (error) {
|
||||
@@ -133,9 +133,9 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else {
|
||||
logger("metrics/setup > no community templates to install")
|
||||
|
||||
}
|
||||
|
||||
//Load templates
|
||||
for (const name of await fs.promises.readdir(__templates)) {
|
||||
@@ -145,10 +145,10 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
continue
|
||||
logger(`metrics/setup > load template [${name}]`)
|
||||
//Cache templates files
|
||||
const files = ["image.svg", "style.css", "fonts.css"].map(file => path.join(__templates, (fs.existsSync(path.join(directory, file)) ? name : "classic"), file))
|
||||
const files = ["image.svg", "style.css", "fonts.css"].map(file => path.join(__templates, fs.existsSync(path.join(directory, file)) ? name : "classic", file))
|
||||
const [image, style, fonts] = await Promise.all(files.map(async file => `${await fs.promises.readFile(file)}`))
|
||||
const partials = JSON.parse(`${await fs.promises.readFile(path.join(directory, "partials/_.json"))}`)
|
||||
conf.templates[name] = {image, style, fonts, partials, views:[directory]}
|
||||
conf.templates[name] = {image, style, fonts, partials, views: [directory]}
|
||||
|
||||
//Cache templates scripts
|
||||
Templates[name] = await (async () => {
|
||||
@@ -165,7 +165,7 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
const [image, style, fonts] = files.map(file => `${fs.readFileSync(file)}`)
|
||||
const partials = JSON.parse(`${fs.readFileSync(path.join(directory, "partials/_.json"))}`)
|
||||
logger(`metrics/setup > reload template [${name}] > success`)
|
||||
return {image, style, fonts, partials, views:[directory]}
|
||||
return {image, style, fonts, partials, views: [directory]}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -177,7 +177,7 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
case "community": {
|
||||
const ___plugins = path.join(__plugins, "community")
|
||||
for (const name of await fs.promises.readdir(___plugins))
|
||||
await load.plugin(name, {__plugins:___plugins, Plugins, conf, logger})
|
||||
await load.plugin(name, {__plugins: ___plugins, Plugins, conf, logger})
|
||||
continue
|
||||
}
|
||||
default:
|
||||
@@ -191,7 +191,7 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
||||
//Store authenticated user
|
||||
if (conf.settings.token) {
|
||||
try {
|
||||
conf.authenticated = (await (new OctokitRest.Octokit({auth:conf.settings.token})).users.getAuthenticated()).data.login
|
||||
conf.authenticated = (await (new OctokitRest.Octokit({auth: conf.settings.token})).users.getAuthenticated()).data.login
|
||||
logger(`metrics/setup > setup > authenticated as ${conf.authenticated}`)
|
||||
}
|
||||
catch (error) {
|
||||
@@ -251,7 +251,8 @@ const load = {
|
||||
}
|
||||
}
|
||||
//Create queries formatters
|
||||
Object.keys(queries).map(query => queries[query.substring(1)] = (vars = {}) => {
|
||||
Object.keys(queries).map(query =>
|
||||
queries[query.substring(1)] = (vars = {}) => {
|
||||
let queried = queries[query]
|
||||
for (const [key, value] of Object.entries(vars))
|
||||
queried = queried.replace(new RegExp(`[$]${key}`, "g"), value)
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
//Imports
|
||||
import octicons from "@primer/octicons"
|
||||
import fs from "fs/promises"
|
||||
import prism_lang from "prismjs/components/index.js"
|
||||
import axios from "axios"
|
||||
import processes from "child_process"
|
||||
import crypto from "crypto"
|
||||
import {minify as csso} from "csso"
|
||||
import { minify as csso } from "csso"
|
||||
import emoji from "emoji-name-map"
|
||||
import fss from "fs"
|
||||
import fs from "fs/promises"
|
||||
import GIFEncoder from "gifencoder"
|
||||
import jimp from "jimp"
|
||||
import linguist from "linguist-js"
|
||||
import {marked} from "marked"
|
||||
import { marked } from "marked"
|
||||
import minimatch from "minimatch"
|
||||
import nodechartist from "node-chartist"
|
||||
import fetch from "node-fetch"
|
||||
@@ -20,6 +19,7 @@ import os from "os"
|
||||
import paths from "path"
|
||||
import PNG from "png-js"
|
||||
import prism from "prismjs"
|
||||
import prism_lang from "prismjs/components/index.js"
|
||||
import _puppeteer from "puppeteer"
|
||||
import purgecss from "purgecss"
|
||||
import readline from "readline"
|
||||
@@ -34,7 +34,7 @@ import xmlformat from "xml-formatter"
|
||||
prism_lang()
|
||||
|
||||
//Exports
|
||||
export {axios, emoji, fetch, fs, git, jimp, minimatch, opengraph, os, paths, processes, rss, url, util}
|
||||
export { axios, emoji, fetch, fs, git, jimp, minimatch, opengraph, os, paths, processes, rss, url, util }
|
||||
|
||||
/**Returns module __dirname */
|
||||
export function __module(module) {
|
||||
@@ -45,25 +45,25 @@ export function __module(module) {
|
||||
export const puppeteer = {
|
||||
async launch() {
|
||||
return _puppeteer.launch({
|
||||
headless:this.headless,
|
||||
executablePath:process.env.PUPPETEER_BROWSER_PATH,
|
||||
args:this.headless ? ["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] : [],
|
||||
ignoreDefaultArgs:["--disable-extensions"],
|
||||
headless: this.headless,
|
||||
executablePath: process.env.PUPPETEER_BROWSER_PATH,
|
||||
args: this.headless ? ["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] : [],
|
||||
ignoreDefaultArgs: ["--disable-extensions"],
|
||||
})
|
||||
},
|
||||
headless:true,
|
||||
headless: true,
|
||||
}
|
||||
|
||||
/**Plural formatter */
|
||||
export function s(value, end = "") {
|
||||
return value !== 1 ? {y:"ies", "":"s"}[end] : end
|
||||
return value !== 1 ? {y: "ies", "": "s"}[end] : end
|
||||
}
|
||||
|
||||
/**Formatters */
|
||||
export function formatters({timeZone} = {}) {
|
||||
//Check options
|
||||
try {
|
||||
new Date().toLocaleString("fr", {timeZoneName:"short", timeZone})
|
||||
new Date().toLocaleString("fr", {timeZoneName: "short", timeZone})
|
||||
}
|
||||
catch {
|
||||
timeZone = undefined
|
||||
@@ -72,7 +72,7 @@ export function formatters({timeZone} = {}) {
|
||||
/**Formatter */
|
||||
const format = function(n, {sign = false, unit = true, fixed} = {}) {
|
||||
if (unit) {
|
||||
for (const {u, v} of [{u:"b", v:10 ** 9}, {u:"m", v:10 ** 6}, {u:"k", v:10 ** 3}]) {
|
||||
for (const {u, v} of [{u: "b", v: 10 ** 9}, {u: "m", v: 10 ** 6}, {u: "k", v: 10 ** 3}]) {
|
||||
if (n / v >= 1)
|
||||
return `${(sign) && (n > 0) ? "+" : ""}${(n / v).toFixed(fixed ?? 2).substr(0, 4).replace(/[.]0*$/, "")}${u}`
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export function formatters({timeZone} = {}) {
|
||||
|
||||
/**Bytes formatter */
|
||||
format.bytes = function(n) {
|
||||
for (const {u, v} of [{u:"E", v:10 ** 18}, {u:"P", v:10 ** 15}, {u:"T", v:10 ** 12}, {u:"G", v:10 ** 9}, {u:"M", v:10 ** 6}, {u:"k", v:10 ** 3}]) {
|
||||
for (const {u, v} of [{u: "E", v: 10 ** 18}, {u: "P", v: 10 ** 15}, {u: "T", v: 10 ** 12}, {u: "G", v: 10 ** 9}, {u: "M", v: 10 ** 6}, {u: "k", v: 10 ** 3}]) {
|
||||
if (n / v >= 1)
|
||||
return `${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B`
|
||||
}
|
||||
@@ -110,11 +110,11 @@ export function formatters({timeZone} = {}) {
|
||||
format.date = function(string, options) {
|
||||
if (options.date) {
|
||||
delete options.date
|
||||
Object.assign(options, {day:"numeric", month:"short", year:"numeric"})
|
||||
Object.assign(options, {day: "numeric", month: "short", year: "numeric"})
|
||||
}
|
||||
if (options.time) {
|
||||
delete options.time
|
||||
Object.assign(options, {hour:"2-digit", minute:"2-digit", second:"2-digit"})
|
||||
Object.assign(options, {hour: "2-digit", minute: "2-digit", second: "2-digit"})
|
||||
}
|
||||
return new Intl.DateTimeFormat("en-GB", {timeZone, ...options}).format(new Date(string))
|
||||
}
|
||||
@@ -139,7 +139,7 @@ export function shuffle(array) {
|
||||
}
|
||||
|
||||
/**Escape html */
|
||||
export function htmlescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) {
|
||||
export function htmlescape(string, u = {"&": true, "<": true, ">": true, '"': true, "'": true}) {
|
||||
return string
|
||||
.replace(/&(?!(?:amp|lt|gt|quot|apos);)/g, u["&"] ? "&" : "&")
|
||||
.replace(/</g, u["<"] ? "<" : "<")
|
||||
@@ -149,7 +149,7 @@ export function htmlescape(string, u = {"&":true, "<":true, ">":true, '"':true,
|
||||
}
|
||||
|
||||
/**Unescape html */
|
||||
export function htmlunescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) {
|
||||
export function htmlunescape(string, u = {"&": true, "<": true, ">": true, '"': true, "'": true}) {
|
||||
return string
|
||||
.replace(/</g, u["<"] ? "<" : "<")
|
||||
.replace(/>/g, u[">"] ? ">" : ">")
|
||||
@@ -173,7 +173,7 @@ export async function chartist() {
|
||||
/**Language analyzer (single file) */
|
||||
export async function language({filename, patch}) {
|
||||
console.debug(`metrics/language > ${filename}`)
|
||||
const {files:{results}} = await linguist(filename, {fileContent:patch})
|
||||
const {files: {results}} = await linguist(filename, {fileContent: patch})
|
||||
const result = (results[filename] ?? "unknown").toLocaleLowerCase()
|
||||
console.debug(`metrics/language > ${filename} > result: ${result}`)
|
||||
return result
|
||||
@@ -181,7 +181,7 @@ export async function language({filename, patch}) {
|
||||
|
||||
/**Run command (use this to execute commands and process whole output at once, may not be suitable for large outputs) */
|
||||
export async function run(command, options, {prefixed = true, log = true} = {}) {
|
||||
const prefix = {win32:"wsl"}[process.platform] ?? ""
|
||||
const prefix = {win32: "wsl"}[process.platform] ?? ""
|
||||
command = `${prefixed ? prefix : ""} ${command}`.trim()
|
||||
return new Promise((solve, reject) => {
|
||||
console.debug(`metrics/command/run > ${command}`)
|
||||
@@ -202,7 +202,7 @@ export async function run(command, options, {prefixed = true, log = true} = {})
|
||||
|
||||
/**Spawn command (use this to execute commands and process output on the fly) */
|
||||
export async function spawn(command, args = [], options = {}, {prefixed = true, timeout = 300 * 1000, stdout} = {}) { //eslint-disable-line max-params
|
||||
const prefix = {win32:"wsl"}[process.platform] ?? ""
|
||||
const prefix = {win32: "wsl"}[process.platform] ?? ""
|
||||
if ((prefixed) && (prefix)) {
|
||||
args.unshift(command)
|
||||
command = prefix
|
||||
@@ -211,8 +211,8 @@ export async function spawn(command, args = [], options = {}, {prefixed = true,
|
||||
throw new Error("`stdout` argument was not provided, use run() instead of spawn() if processing output is not needed")
|
||||
return new Promise((solve, reject) => {
|
||||
console.debug(`metrics/command/spawn > ${command} with ${args.join(" ")}`)
|
||||
const child = processes.spawn(command, args, {...options, shell:true, timeout})
|
||||
const reader = readline.createInterface({input:child.stdout})
|
||||
const child = processes.spawn(command, args, {...options, shell: true, timeout})
|
||||
const reader = readline.createInterface({input: child.stdout})
|
||||
reader.on("line", stdout)
|
||||
const closed = new Promise(close => reader.on("close", close))
|
||||
child.on("close", async code => {
|
||||
@@ -245,18 +245,18 @@ export function highlight(code, lang) {
|
||||
/**Markdown-html sanitizer-interpreter */
|
||||
export async function markdown(text, {mode = "inline", codelines = Infinity} = {}) {
|
||||
//Sanitize user input once to prevent injections and parse into markdown
|
||||
let rendered = await marked.parse(htmlunescape(htmlsanitize(text)), {highlight, silent:true, xhtml:true})
|
||||
let rendered = await marked.parse(htmlunescape(htmlsanitize(text)), {highlight, silent: true, xhtml: true})
|
||||
//Markdown mode
|
||||
switch (mode) {
|
||||
case "inline": {
|
||||
rendered = htmlsanitize(
|
||||
htmlsanitize(rendered, {
|
||||
allowedTags:["h1", "h2", "h3", "h4", "h5", "h6", "br", "blockquote", "code", "span"],
|
||||
allowedAttributes:{code:["class"], span:["class"]},
|
||||
allowedTags: ["h1", "h2", "h3", "h4", "h5", "h6", "br", "blockquote", "code", "span"],
|
||||
allowedAttributes: {code: ["class"], span: ["class"]},
|
||||
}),
|
||||
{
|
||||
allowedAttributes:{code:["class"], span:["class"]},
|
||||
transformTags:{h1:"b", h2:"b", h3:"b", h4:"b", h5:"b", h6:"b", blockquote:"i"},
|
||||
allowedAttributes: {code: ["class"], span: ["class"]},
|
||||
transformTags: {h1: "b", h2: "b", h3: "b", h4: "b", h5: "b", h6: "b", blockquote: "i"},
|
||||
},
|
||||
)
|
||||
break
|
||||
@@ -349,7 +349,7 @@ export const svg = {
|
||||
}
|
||||
//Additional transformations
|
||||
if (twemojis)
|
||||
rendered = await svg.twemojis(rendered, {custom:false})
|
||||
rendered = await svg.twemojis(rendered, {custom: false})
|
||||
if ((gemojis) && (rest))
|
||||
rendered = await svg.gemojis(rendered, {rest})
|
||||
if (octicons)
|
||||
@@ -358,13 +358,13 @@ export const svg = {
|
||||
//Render through browser and print pdf
|
||||
console.debug("metrics/svg/pdf > loading svg")
|
||||
const page = await svg.resize.browser.newPage()
|
||||
page.on("console", ({_text:text}) => console.debug(`metrics/svg/pdf > puppeteer > ${text}`))
|
||||
await page.setContent(`<main class="markdown-body">${rendered}</main>`, {waitUntil:["load", "domcontentloaded", "networkidle2"]})
|
||||
page.on("console", ({_text: text}) => console.debug(`metrics/svg/pdf > puppeteer > ${text}`))
|
||||
await page.setContent(`<main class="markdown-body">${rendered}</main>`, {waitUntil: ["load", "domcontentloaded", "networkidle2"]})
|
||||
console.debug("metrics/svg/pdf > loaded svg successfully")
|
||||
const margins = (Array.isArray(paddings) ? paddings : paddings.split(",")).join(" ")
|
||||
console.debug(`metrics/svg/pdf > margins set to ${margins}`)
|
||||
await page.addStyleTag({
|
||||
content:`
|
||||
content: `
|
||||
main { margin: ${margins}; }
|
||||
main svg { height: 1em; width: 1em; }
|
||||
${await fs.readFile(paths.join(__module(import.meta.url), "../../../node_modules", "@primer/css/dist/markdown.css")).catch(_ => "")}${style}
|
||||
@@ -374,7 +374,7 @@ export const svg = {
|
||||
//Result
|
||||
await page.close()
|
||||
console.debug("metrics/svg/pdf > rendering complete")
|
||||
return {rendered, mime:"application/pdf"}
|
||||
return {rendered, mime: "application/pdf"}
|
||||
},
|
||||
/**Render and resize svg */
|
||||
async resize(rendered, {paddings, convert, scripts = []}) {
|
||||
@@ -384,7 +384,7 @@ export const svg = {
|
||||
console.debug(`metrics/svg/resize > started ${await svg.resize.browser.version()}`)
|
||||
}
|
||||
//Format padding
|
||||
const padding = {width:1, height:1, absolute:{width:0, height:0}}
|
||||
const padding = {width: 1, height: 1, absolute: {width: 0, height: 0}}
|
||||
paddings = Array.isArray(paddings) ? paddings : `${paddings}`.split(",").map(x => x.trim())
|
||||
for (const [i, dimension] of [[0, "width"], [1, "height"]]) {
|
||||
let operands = (paddings?.[i] ?? paddings[0])
|
||||
@@ -400,18 +400,18 @@ export const svg = {
|
||||
//Render through browser and resize height
|
||||
console.debug("metrics/svg/resize > loading svg")
|
||||
const page = await svg.resize.browser.newPage()
|
||||
page.setViewport({width:980, height:980})
|
||||
page.setViewport({width: 980, height: 980})
|
||||
page
|
||||
.on("console", message => console.debug(`metrics/svg/resize > puppeteer > ${message.text()}`))
|
||||
.on("pageerror", error => console.debug(`metrics/svg/resize > puppeteer > ${error.message}`))
|
||||
await page.setContent(rendered, {waitUntil:["load", "domcontentloaded", "networkidle2"]})
|
||||
await page.setContent(rendered, {waitUntil: ["load", "domcontentloaded", "networkidle2"]})
|
||||
console.debug("metrics/svg/resize > loaded svg successfully")
|
||||
await page.addStyleTag({content:"body { margin: 0; padding: 0; }"})
|
||||
await page.addStyleTag({content: "body { margin: 0; padding: 0; }"})
|
||||
let mime = "image/svg+xml"
|
||||
console.debug("metrics/svg/resize > resizing svg")
|
||||
let height, resized, width
|
||||
try {
|
||||
({resized, width, height} = await page.evaluate(
|
||||
;({resized, width, height} = await page.evaluate(
|
||||
async (padding, scripts) => {
|
||||
//Execute additional JavaScript
|
||||
for (const script of scripts) {
|
||||
@@ -431,7 +431,7 @@ export const svg = {
|
||||
console.debug(`animations are ${animated ? "enabled" : "disabled"}`)
|
||||
await new Promise(solve => setTimeout(solve, 2400))
|
||||
//Get bounds and resize
|
||||
let {y:height, width} = document.querySelector("svg #metrics-end").getBoundingClientRect()
|
||||
let {y: height, width} = document.querySelector("svg #metrics-end").getBoundingClientRect()
|
||||
console.debug(`bounds width=${width}, height=${height}`)
|
||||
height = Math.max(1, Math.ceil(height * padding.height + padding.absolute.height))
|
||||
width = Math.max(1, Math.ceil(width * padding.width + padding.absolute.width))
|
||||
@@ -445,7 +445,7 @@ export const svg = {
|
||||
if (animated)
|
||||
document.querySelector("svg").classList.remove("no-animations")
|
||||
//Result
|
||||
return {resized:new XMLSerializer().serializeToString(document.querySelector("svg")), height, width}
|
||||
return {resized: new XMLSerializer().serializeToString(document.querySelector("svg")), height, width}
|
||||
},
|
||||
padding,
|
||||
scripts,
|
||||
@@ -458,7 +458,7 @@ export const svg = {
|
||||
//Convert if required
|
||||
if (convert) {
|
||||
console.debug(`metrics/svg/resize > convert to ${convert}`)
|
||||
resized = await page.screenshot({type:convert, clip:{x:0, y:0, width, height}, omitBackground:true})
|
||||
resized = await page.screenshot({type: convert, clip: {x: 0, y: 0, width, height}, omitBackground: true})
|
||||
mime = `image/${convert}`
|
||||
}
|
||||
//Result
|
||||
@@ -478,7 +478,7 @@ export const svg = {
|
||||
}
|
||||
//Compute hash
|
||||
const page = await svg.resize.browser.newPage()
|
||||
await page.setContent(rendered, {waitUntil:["load", "domcontentloaded", "networkidle2"]})
|
||||
await page.setContent(rendered, {waitUntil: ["load", "domcontentloaded", "networkidle2"]})
|
||||
const data = await page.evaluate(async () => {
|
||||
document.querySelector("footer")?.remove()
|
||||
return document.querySelector("svg").outerHTML
|
||||
@@ -494,7 +494,7 @@ export const svg = {
|
||||
//Load emojis
|
||||
console.debug("metrics/svg/twemojis > rendering twemojis")
|
||||
const emojis = new Map()
|
||||
for (const {text:emoji, url} of twemojis.parse(rendered)) {
|
||||
for (const {text: emoji, url} of twemojis.parse(rendered)) {
|
||||
if (!emojis.has(emoji))
|
||||
emojis.set(emoji, (await axios.get(url)).data.replace(/^<svg /, '<svg class="twemoji" '))
|
||||
}
|
||||
@@ -513,7 +513,7 @@ export const svg = {
|
||||
const emojis = new Map()
|
||||
try {
|
||||
for (const [emoji, url] of Object.entries((await rest.emojis.get()).data).map(([key, value]) => [`:${key}:`, value])) {
|
||||
if (((!emojis.has(emoji))) && (new RegExp(emoji, "g").test(rendered)))
|
||||
if ((!emojis.has(emoji)) && (new RegExp(emoji, "g").test(rendered)))
|
||||
emojis.set(emoji, `<img class="gemoji" src="${await imgb64(url)}" height="16" width="16" alt="" />`)
|
||||
}
|
||||
}
|
||||
@@ -535,9 +535,9 @@ export const svg = {
|
||||
for (const size of Object.keys(heights)) {
|
||||
const octicon = `:octicon-${name}-${size}:`
|
||||
if (new RegExp(`:octicon-${name}(?:-[0-9]+)?:`, "g").test(rendered)) {
|
||||
icons.set(octicon, toSVG({height:size, width:size}))
|
||||
icons.set(octicon, toSVG({height: size, width: size}))
|
||||
if (Number(size) === 16)
|
||||
icons.set(`:octicon-${name}:`, toSVG({height:size, width:size}))
|
||||
icons.set(`:octicon-${name}:`, toSVG({height: size, width: size}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -547,7 +547,7 @@ export const svg = {
|
||||
return rendered
|
||||
},
|
||||
/**Optimizers */
|
||||
optimize:{
|
||||
optimize: {
|
||||
/**CSS optimizer */
|
||||
async css(rendered) {
|
||||
//Extract styles
|
||||
@@ -558,9 +558,9 @@ export const svg = {
|
||||
while (regex.test(rendered)) {
|
||||
const style = htmlunescape(rendered.match(regex)?.groups?.style ?? "")
|
||||
rendered = rendered.replace(regex, cleaned)
|
||||
css.push({raw:style})
|
||||
css.push({raw: style})
|
||||
}
|
||||
const content = [{raw:rendered, extension:"html"}]
|
||||
const content = [{raw: rendered, extension: "html"}]
|
||||
|
||||
//Purge CSS
|
||||
const purged = await new purgecss.PurgeCSS().purge({content, css})
|
||||
@@ -574,7 +574,7 @@ export const svg = {
|
||||
console.debug("metrics/svg/optimize/xml > skipped as raw option is enabled")
|
||||
return rendered
|
||||
}
|
||||
return xmlformat(rendered, {lineSeparator:"\n", collapseContent:true})
|
||||
return xmlformat(rendered, {lineSeparator: "\n", collapseContent: true})
|
||||
},
|
||||
/**SVG optimizer */
|
||||
async svg(rendered, {raw = false} = {}, experimental = new Set()) {
|
||||
@@ -587,16 +587,16 @@ export const svg = {
|
||||
console.debug("metrics/svg/optimize/svg > this feature require experimental feature flag --optimize-svg")
|
||||
return rendered
|
||||
}
|
||||
const {error, data:optimized} = await SVGO.optimize(rendered, {
|
||||
multipass:true,
|
||||
plugins:SVGO.extendDefaultPlugins([
|
||||
const {error, data: optimized} = await SVGO.optimize(rendered, {
|
||||
multipass: true,
|
||||
plugins: SVGO.extendDefaultPlugins([
|
||||
//Additional cleanup
|
||||
{name:"cleanupListOfValues"},
|
||||
{name:"removeRasterImages"},
|
||||
{name:"removeScriptElement"},
|
||||
{name: "cleanupListOfValues"},
|
||||
{name: "removeRasterImages"},
|
||||
{name: "removeScriptElement"},
|
||||
//Force CSS style consistency
|
||||
{name:"inlineStyles", active:false},
|
||||
{name:"removeViewBox", active:false},
|
||||
{name: "inlineStyles", active: false},
|
||||
{name: "removeViewBox", active: false},
|
||||
]),
|
||||
})
|
||||
if (error)
|
||||
@@ -616,7 +616,7 @@ export async function record({page, width, height, frames, scale = 1, quality =
|
||||
//Register images frames
|
||||
const images = []
|
||||
for (let i = 0; i < frames; i++) {
|
||||
images.push(await page.screenshot({type:"png", clip:{width, height, x, y}, omitBackground:background}))
|
||||
images.push(await page.screenshot({type: "png", clip: {width, height, x, y}, omitBackground: background}))
|
||||
await wait(delay / 1000)
|
||||
if (i % 10 === 0)
|
||||
console.debug(`metrics/record > processed ${i}/${frames} frames`)
|
||||
@@ -643,7 +643,7 @@ export async function gif({page, width, height, frames, x = 0, y = 0, repeat = t
|
||||
encoder.setQuality(quality)
|
||||
//Register frames
|
||||
for (let i = 0; i < frames; i++) {
|
||||
const buffer = new PNG(await page.screenshot({clip:{width, height, x, y}}))
|
||||
const buffer = new PNG(await page.screenshot({clip: {width, height, x, y}}))
|
||||
encoder.addFrame(await new Promise(solve => buffer.decode(pixels => solve(pixels))))
|
||||
if (frames % 10 === 0)
|
||||
console.debug(`metrics/puppeteergif > processed ${i}/${frames} frames`)
|
||||
|
||||
Reference in New Issue
Block a user